PageRenderTime 25ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/sally/core/lib/sly/Service/Asset.php

https://bitbucket.org/SallyCMS/0.6
PHP | 408 lines | 259 code | 88 blank | 61 comment | 30 complexity | 9b82d923b0c9db532f3ecb8f3ad9a2c9 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /*
  3. * Copyright (c) 2012, webvariants GbR, http://www.webvariants.de
  4. *
  5. * This file is released under the terms of the MIT license. You can find the
  6. * complete text in the attached LICENSE file or online at:
  7. *
  8. * http://www.opensource.org/licenses/mit-license.php
  9. */
  10. /**
  11. * @author Dave
  12. */
  13. class sly_Service_Asset {
  14. const CACHE_DIR = 'public/sally/static-cache'; ///< string
  15. const TEMP_DIR = 'internal/sally/temp'; ///< string
  16. const EVENT_PROCESS_ASSET = 'SLY_CACHE_PROCESS_ASSET'; ///< string
  17. const EVENT_REVALIDATE_ASSETS = 'SLY_CACHE_REVALIDATE_ASSETS'; ///< string
  18. const EVENT_GET_PROTECTED_ASSETS = 'SLY_CACHE_GET_PROTECTED_ASSETS'; ///< string
  19. const EVENT_IS_PROTECTED_ASSET = 'SLY_CACHE_IS_PROTECTED_ASSET'; ///< string
  20. const ACCESS_PUBLIC = 'public'; ///< string
  21. const ACCESS_PROTECTED = 'protected'; ///< string
  22. private $forceGen = true; ///< boolean
  23. public function __construct() {
  24. $this->initCache();
  25. $dispatcher = sly_Core::dispatcher();
  26. $dispatcher->register(self::EVENT_PROCESS_ASSET, array($this, 'processScaffold'), array(), true);
  27. }
  28. /**
  29. * @param boolean $force
  30. */
  31. public function setForceGeneration($force = true) {
  32. $this->forceGen = (boolean) $force;
  33. }
  34. public function validateCache() {
  35. $dispatcher = sly_Core::dispatcher();
  36. // [assets/css/main.css, data/mediapool/foo.jpg, ...] (echte, vorhandene Dateien)
  37. $protected = $dispatcher->filter(self::EVENT_GET_PROTECTED_ASSETS, array());
  38. $protected = array_unique(array_filter($protected));
  39. foreach (array(self::ACCESS_PUBLIC, self::ACCESS_PROTECTED) as $access) {
  40. foreach (array('plain', 'gzip', 'deflate') as $encoding) {
  41. $dir = new sly_Util_Directory(realpath($this->getCacheDir($access, $encoding)));
  42. $list = $dir->listRecursive();
  43. // just in case ... should never happen because of $this->initCache()
  44. if ($list === false) continue;
  45. foreach ($list as $file) {
  46. // "/../data/dyn/public/sally/static-cache/gzip/assets/css/main.css"
  47. $cacheFile = $this->getCacheFile($file, $access, $encoding);
  48. $realfile = SLY_BASE.'/'.$file;
  49. if (!file_exists($cacheFile)) {
  50. continue;
  51. }
  52. // if original file is missing, ask listeners
  53. if (!file_exists($realfile)) {
  54. $translated = $dispatcher->filter(self::EVENT_REVALIDATE_ASSETS, array($file));
  55. $realfile = SLY_BASE.'/'.reset($translated); // "there can only be one!" ... or at least we hope so
  56. }
  57. $relative = str_replace(SLY_BASE, '', $realfile);
  58. $relative = str_replace('\\', '/', $relative);
  59. $relative = trim($relative, '/');
  60. // delete cache file if original is missing or outdated
  61. if (!file_exists($realfile) || filemtime($cacheFile) < filemtime($realfile)) {
  62. unlink($cacheFile);
  63. }
  64. // delete file if it's protected but where in public or vice versa
  65. elseif (in_array($relative, $protected) !== ($access === self::ACCESS_PROTECTED)) {
  66. unlink($cacheFile);
  67. }
  68. }
  69. }
  70. }
  71. }
  72. protected function normalizePath($path) {
  73. $path = sly_Util_Directory::normalize($path);
  74. $path = str_replace('..', '', $path);
  75. $path = str_replace(DIRECTORY_SEPARATOR, '/', sly_Util_Directory::normalize($path));
  76. $path = str_replace('./', '/', $path);
  77. $path = str_replace(DIRECTORY_SEPARATOR, '/', sly_Util_Directory::normalize($path));
  78. if (empty($path)) {
  79. return '';
  80. }
  81. if ($path[0] === '/') {
  82. $path = substr($path, 1);
  83. }
  84. return $path;
  85. }
  86. public function process($file, $encoding) {
  87. // check if the file can be streamed
  88. $blocked = sly_Core::config()->get('BLOCKED_EXTENSIONS');
  89. $ok = true;
  90. foreach ($blocked as $ext) {
  91. if (sly_Util_String::endsWith($file, $ext)) {
  92. $ok = false;
  93. break;
  94. }
  95. }
  96. if ($ok) {
  97. $normalized = $this->normalizePath($file);
  98. $ok = strpos($normalized, '/') === false; // allow files in root directory (favicon)
  99. if (!$ok) {
  100. $allowed = sly_Core::config()->get('ASSETS_DIRECTORIES');
  101. foreach ($allowed as $path) {
  102. if (sly_Util_String::startsWith($file, $path)) {
  103. $ok = true;
  104. break;
  105. }
  106. }
  107. }
  108. }
  109. if (!$ok) {
  110. throw new sly_Authorisation_Exception('Forbidden');
  111. }
  112. // do the work
  113. $dispatcher = sly_Core::dispatcher();
  114. $isProtected = $dispatcher->filter(self::EVENT_IS_PROTECTED_ASSET, false, compact('file'));
  115. $access = $isProtected ? self::ACCESS_PROTECTED : self::ACCESS_PUBLIC;
  116. // "/../data/dyn/public/sally/static-cache/[access]/gzip/assets/css/main.css"
  117. $cacheFile = $this->getCacheFile($file, $access, $encoding);
  118. // if the file already exists, stop here (can only happen if someone
  119. // manually requests index.php?slycontroller...).
  120. if (file_exists($cacheFile) && !$this->forceGen) {
  121. return new sly_Response('', 400);
  122. }
  123. // let listeners process the file
  124. $tmpFile = $dispatcher->filter(self::EVENT_PROCESS_ASSET, $file);
  125. // now we can check if a listener has generated a valid file
  126. if (!is_file($tmpFile)) return null;
  127. // create the encoded file
  128. $this->generateCacheFile($tmpFile, $cacheFile, $encoding);
  129. if (!file_exists($cacheFile)) {
  130. return null;
  131. }
  132. // return the plain, unencoded file to the asset controller
  133. return $tmpFile;
  134. }
  135. public function removeCacheFiles($file) {
  136. foreach (array(self::ACCESS_PUBLIC, self::ACCESS_PROTECTED) as $access) {
  137. foreach (array('plain', 'gzip', 'deflate') as $encoding) {
  138. // "/../data/dyn/public/sally/static-cache/gzip/assets/css/main.css"
  139. $cacheFile = $this->getCacheFile($file, $access, $encoding);
  140. if (!file_exists($cacheFile)) {
  141. continue;
  142. }
  143. unlink($cacheFile);
  144. }
  145. }
  146. }
  147. /**
  148. * @param string $access
  149. * @param string $encoding
  150. * @return string
  151. */
  152. protected function getCacheDir($access, $encoding) {
  153. return sly_Util_Directory::join(SLY_DYNFOLDER, self::CACHE_DIR, $access, $encoding);
  154. }
  155. /**
  156. * @param string $file
  157. * @param string $access
  158. * @param string $encoding
  159. * @return string
  160. */
  161. protected function getCacheFile($file, $access, $encoding) {
  162. return sly_Util_Directory::join($this->getCacheDir($access, $encoding), $file);
  163. }
  164. /**
  165. * @param string $sourceFile
  166. * @param string $cacheFile
  167. */
  168. protected function generateCacheFile($sourceFile, $cacheFile, $encoding) {
  169. clearstatcache();
  170. sly_Util_Directory::create(dirname($cacheFile), $this->getDirPerm());
  171. $level = error_reporting(0);
  172. switch ($encoding) {
  173. case 'gzip':
  174. $out = gzopen($cacheFile, 'wb');
  175. $in = fopen($sourceFile, 'rb');
  176. while (!feof($in)) {
  177. $buf = fread($in, 4096);
  178. gzwrite($out, $buf);
  179. }
  180. fclose($in);
  181. gzclose($out);
  182. break;
  183. case 'deflate':
  184. $out = fopen($cacheFile, 'wb');
  185. $in = fopen($sourceFile, 'rb');
  186. stream_filter_append($out, 'zlib.deflate', STREAM_FILTER_WRITE, 6);
  187. while (!feof($in)) {
  188. fwrite($out, fread($in, 4096));
  189. }
  190. fclose($in);
  191. fclose($out);
  192. break;
  193. case 'plain':
  194. default:
  195. copy($sourceFile, $cacheFile);
  196. }
  197. if (file_exists($cacheFile)) {
  198. chmod($cacheFile, $this->getFilePerm());
  199. }
  200. error_reporting($level);
  201. }
  202. /**
  203. * @param string $origFile
  204. * @param string $file
  205. */
  206. protected function printCacheFile($file) {
  207. $fp = @fopen($file, 'rb');
  208. if (!$fp) {
  209. $errors = 'Cannot open file.';
  210. }
  211. if (empty($errors)) {
  212. while (!feof($fp)) {
  213. print fread($fp, 65536);
  214. }
  215. fclose($fp);
  216. }
  217. else {
  218. throw new sly_Exception($errors);
  219. }
  220. }
  221. /**
  222. * @param array $params
  223. * @return string
  224. */
  225. public function processScaffold($params) {
  226. $file = $params['subject'];
  227. if (sly_Util_String::endsWith($file, '.css') && file_exists(SLY_BASE.'/'.$file)) {
  228. $css = sly_Util_Scaffold::process($file);
  229. $dir = SLY_DYNFOLDER.'/'.self::TEMP_DIR;
  230. $tmpFile = $dir.'/'.md5($file).'.css';
  231. sly_Util_Directory::create($dir, $this->getDirPerm());
  232. file_put_contents($tmpFile, $css);
  233. chmod($tmpFile, $this->getFilePerm());
  234. return $tmpFile;
  235. }
  236. return $file;
  237. }
  238. private function initCache() {
  239. $dirPerm = $this->getDirPerm();
  240. $filePerm = $this->getFilePerm();
  241. $dir = SLY_DYNFOLDER.'/'.self::CACHE_DIR;
  242. sly_Util_Directory::create($dir, $dirPerm);
  243. $install = SLY_COREFOLDER.'/install/static-cache/';
  244. $htaccess = $dir.'/.htaccess';
  245. if (!file_exists($htaccess)) {
  246. copy($install.'.htaccess', $htaccess);
  247. chmod($htaccess, $filePerm);
  248. }
  249. $protect_php = $dir.'/protect.php';
  250. if (!file_exists($protect_php)) {
  251. $jumper = self::getJumper($dir);
  252. $contents = file_get_contents($install.'protect.php');
  253. $contents = str_replace('___JUMPER___', $jumper, $contents);
  254. file_put_contents($protect_php, $contents);
  255. chmod($protect_php, $filePerm);
  256. }
  257. foreach (array(self::ACCESS_PUBLIC, self::ACCESS_PROTECTED) as $access) {
  258. sly_Util_Directory::create($dir.'/'.$access.'/gzip', $dirPerm);
  259. sly_Util_Directory::create($dir.'/'.$access.'/deflate', $dirPerm);
  260. sly_Util_Directory::create($dir.'/'.$access.'/plain', $dirPerm);
  261. $file = $dir.'/'.$access.'/gzip/.htaccess';
  262. if (!file_exists($file)) {
  263. copy($install.'gzip.htaccess', $file);
  264. chmod($file, $filePerm);
  265. }
  266. $file = $dir.'/'.$access.'/deflate/.htaccess';
  267. if (!file_exists($file)) {
  268. copy($install.'deflate.htaccess', $file);
  269. chmod($file, $filePerm);
  270. }
  271. }
  272. sly_Util_Directory::createHttpProtected($dir.'/'.self::ACCESS_PROTECTED);
  273. }
  274. /**
  275. * @param string $dir
  276. * @return string
  277. */
  278. private static function getJumper($dir) {
  279. static $jumper = null;
  280. if ($jumper === null) {
  281. $pathDiff = trim(substr($dir, strlen(SLY_BASE)), DIRECTORY_SEPARATOR);
  282. $jumper = str_repeat('../', substr_count($pathDiff, DIRECTORY_SEPARATOR)+1);
  283. }
  284. return $jumper;
  285. }
  286. public static function clearCache() {
  287. $me = new self();
  288. $dir = $me->getCacheDir('', '');
  289. // Remember htaccess files, so that we do not kill and re-create them.
  290. // This is important for servers which have custom rules (like RewriteBase settings).
  291. $htaccess['root'] = file_get_contents($dir.'/.htaccess');
  292. foreach (array(self::ACCESS_PUBLIC, self::ACCESS_PROTECTED) as $access) {
  293. $htaccess[$access.'_gzip'] = file_get_contents($dir.'/'.$access.'/gzip/.htaccess');
  294. $htaccess[$access.'_deflate'] = file_get_contents($dir.'/'.$access.'/deflate/.htaccess');
  295. }
  296. // remove the cache directory
  297. $obj = new sly_Util_Directory($dir);
  298. $obj->delete(true);
  299. // clear the Scaffold temp dir
  300. $scaffoldDir = sly_Util_Directory::join(SLY_DYNFOLDER, self::TEMP_DIR);
  301. $obj = new sly_Util_Directory($scaffoldDir);
  302. $obj->deleteFiles(true);
  303. // re-init the cache dir
  304. $me->initCache();
  305. // restore the original .htaccess files again
  306. $htaccess['root'] = file_put_contents($dir.'/.htaccess', $htaccess['root']);
  307. foreach (array(self::ACCESS_PUBLIC, self::ACCESS_PROTECTED) as $access) {
  308. file_put_contents($dir.'/'.$access.'/gzip/.htaccess', $htaccess[$access.'_gzip']);
  309. file_put_contents($dir.'/'.$access.'/deflate/.htaccess', $htaccess[$access.'_deflate']);
  310. }
  311. }
  312. private function getFilePerm() {
  313. return sly_Core::getFilePerm();
  314. }
  315. private function getDirPerm() {
  316. return sly_Core::getDirPerm();
  317. }
  318. }