PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/jelix/utils/jMinifier.class.php

https://bitbucket.org/doubleface/jelix-trunk
PHP | 284 lines | 162 code | 43 blank | 79 comment | 27 complexity | 9f5cf2df0dbab8f1da5bdaa1d1b631b2 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /**
  3. * @package jelix
  4. * @subpackage core
  5. * @author Brice Tence
  6. * @copyright 2010 Brice Tence
  7. * Idea of this class was picked from the Minify project ( Minify 2.1.3, http://code.google.com/p/minify )
  8. * @link http://www.jelix.org
  9. * @licence GNU Lesser General Public Licence see LICENCE file or http://www.gnu.org/licenses/lgpl.html
  10. */
  11. define('MINIFY_MIN_DIR', LIB_PATH.'minify/min/');
  12. // setup include path
  13. set_include_path(MINIFY_MIN_DIR.'/lib' . PATH_SEPARATOR . get_include_path());
  14. require_once "Minify/Controller/MinApp.php";
  15. require_once 'Minify/Source.php';
  16. require_once 'Minify.php';
  17. /**
  18. * This object is responsible to concatenate and minify CSS or JS files.
  19. * There is a cache system so that previously minified files are served directly.
  20. * Otherwise, files ares concatenated, minified and stored in cache.
  21. * We also check if cache is up to date and needs to be refreshed.
  22. * @package jelix
  23. * @subpackage core
  24. * @author Brice Tence
  25. * @copyright 2010 Brice Tence
  26. * @licence GNU Lesser General Public Licence see LICENCE file or http://www.gnu.org/licenses/lgpl.html .
  27. */
  28. class jMinifier {
  29. const TYPE_CSS = 'text/css';
  30. const TYPE_HTML = 'text/html';
  31. // there is some debate over the ideal JS Content-Type, but this is the
  32. // Apache default and what Yahoo! uses..
  33. const TYPE_JS = 'application/x-javascript';
  34. /**
  35. * @var Minify_Controller active controller for current request
  36. */
  37. protected static $_controller = null;
  38. /**
  39. * @var array options for current request
  40. */
  41. protected static $_options = null;
  42. /**
  43. * Cache file locking. Set to false of filesystem is NFS. On at least one
  44. * NFS system flock-ing attempts stalled PHP for 30 seconds!
  45. */
  46. protected static $min_cacheFileLocking = true;
  47. /**
  48. * If this string is not empty AND the serve() option 'bubbleCssImports' is
  49. * NOT set, then serve() will check CSS files for @import declarations that
  50. * appear too late in the combined stylesheet. If found, serve() will prepend
  51. * the output with this warning.
  52. *
  53. * @var string $importWarning
  54. */
  55. public static $importWarning = "/* See http://code.google.com/p/minify/wiki/CommonProblems#@imports_can_appear_in_invalid_locations_in_combined_CSS_files */\n";
  56. /**
  57. * This is a static class, so private constructor
  58. */
  59. private function __construct(){
  60. }
  61. /**
  62. * @param array $fileList Array of file URLs to include
  63. * @return array of file path to minify's www cached file. A further improvment could be to output several files (size limit for iPhone). That's why this is an array.
  64. */
  65. public static function minify( $fileList, $fileType ){
  66. global $gJConfig,$gJCoord;
  67. $cachePathCSS = 'cache/minify/css/';
  68. jFile::createDir(JELIX_APP_WWW_PATH.$cachePathCSS);
  69. $cachePathJS = 'cache/minify/js/';
  70. jFile::createDir(JELIX_APP_WWW_PATH.$cachePathJS);
  71. $minifiedFiles = array();
  72. $minAppMaxFiles = count($fileList);
  73. //compute a hash of source files to manage cache
  74. $sourcesHash = md5(implode(';', $fileList));
  75. $options = array();
  76. $options['MinApp']['maxFiles'] = $minAppMaxFiles;
  77. $cachePath = '';
  78. $cacheExt = '';
  79. switch ($fileType) {
  80. case 'js':
  81. $options['contentType'] = self::TYPE_JS;
  82. $cachePath = $cachePathJS;
  83. $cacheExt = 'js';
  84. break;
  85. case 'css':
  86. $options['contentType'] = self::TYPE_CSS;
  87. $cachePath = $cachePathCSS;
  88. $cacheExt = 'css';
  89. break;
  90. default:
  91. return;
  92. }
  93. $cacheFilepath = $cachePath . $sourcesHash . '.' . $cacheExt;
  94. $cacheFilepathFilemtime = null;
  95. if( is_file( JELIX_APP_WWW_PATH.$cacheFilepath ) ) {
  96. $cacheFilepathFilemtime = filemtime( JELIX_APP_WWW_PATH.$cacheFilepath );
  97. }
  98. //If we should not check filemtime of source files, let's see if we have the result in our cache
  99. //We assume minifyCheckCacheFiletime is "on" if not set
  100. if( isset($GLOBALS['gJConfig']->responseHtml) &&
  101. $GLOBALS['gJConfig']->responseHtml['minifyCheckCacheFiletime'] === false &&
  102. $cacheFilepathFilemtime !== null ) {
  103. $minifiedFiles[] = $cacheFilepath;
  104. return $minifiedFiles;
  105. }
  106. $sources = array();
  107. //add source files
  108. foreach ($fileList as $file) {
  109. $minifySource = new Minify_Source(array(
  110. 'filepath' => realpath($_SERVER['DOCUMENT_ROOT'] . $file)
  111. ));
  112. $sources[] = $minifySource;
  113. }
  114. $controller = new Minify_Controller_MinApp();
  115. $controller->sources = $sources;
  116. $options = $controller->analyzeSources($options);
  117. $options = $controller->mixInDefaultOptions($options);
  118. self::$_options = $options;
  119. self::$_controller = $controller;
  120. if( $cacheFilepathFilemtime === null ||
  121. $cacheFilepathFilemtime < self::$_options['lastModifiedTime'] ) {
  122. //cache does not exist or is to old. Let's refresh it :
  123. //rewrite URL in CSS files
  124. if (self::$_options['contentType'] === self::TYPE_CSS && self::$_options['rewriteCssUris']) {
  125. reset($controller->sources);
  126. while (list($key, $source) = each(self::$_controller->sources)) {
  127. if ($source->filepath
  128. && !isset($source->minifyOptions['currentDir'])
  129. && !isset($source->minifyOptions['prependRelativePath'])
  130. ) {
  131. $source->minifyOptions['currentDir'] = dirname($source->filepath);
  132. }
  133. }
  134. }
  135. $cacheData = self::combineAndMinify();
  136. $flag = self::$min_cacheFileLocking
  137. ? LOCK_EX
  138. : null;
  139. if (is_file(JELIX_APP_WWW_PATH.$cacheFilepath)) {
  140. @unlink(JELIX_APP_WWW_PATH.$cacheFilepath);
  141. }
  142. if (! @file_put_contents(JELIX_APP_WWW_PATH.$cacheFilepath, $cacheData, $flag)) {
  143. return false;
  144. }
  145. }
  146. $minifiedFiles[] = $cacheFilepath;
  147. return $minifiedFiles;
  148. }
  149. /**
  150. * Combines sources and minifies the result.
  151. *
  152. * @return string
  153. */
  154. protected static function combineAndMinify()
  155. {
  156. $type = self::$_options['contentType']; // ease readability
  157. // when combining scripts, make sure all statements separated and
  158. // trailing single line comment is terminated
  159. $implodeSeparator = ($type === self::TYPE_JS)
  160. ? "\n;"
  161. : '';
  162. // allow the user to pass a particular array of options to each
  163. // minifier (designated by type). source objects may still override
  164. // these
  165. $defaultOptions = isset(self::$_options['minifierOptions'][$type])
  166. ? self::$_options['minifierOptions'][$type]
  167. : array();
  168. // if minifier not set, default is no minification. source objects
  169. // may still override this
  170. $defaultMinifier = isset(self::$_options['minifiers'][$type])
  171. ? self::$_options['minifiers'][$type]
  172. : false;
  173. if (Minify_Source::haveNoMinifyPrefs(self::$_controller->sources)) {
  174. // all source have same options/minifier, better performance
  175. // to combine, then minify once
  176. foreach (self::$_controller->sources as $source) {
  177. $pieces[] = $source->getContent();
  178. }
  179. $content = implode($implodeSeparator, $pieces);
  180. if ($defaultMinifier) {
  181. self::$_controller->loadMinifier($defaultMinifier);
  182. $content = call_user_func($defaultMinifier, $content, $defaultOptions);
  183. }
  184. } else {
  185. // minify each source with its own options and minifier, then combine
  186. foreach (self::$_controller->sources as $source) {
  187. // allow the source to override our minifier and options
  188. $minifier = (null !== $source->minifier)
  189. ? $source->minifier
  190. : $defaultMinifier;
  191. $options = (null !== $source->minifyOptions)
  192. ? array_merge($defaultOptions, $source->minifyOptions)
  193. : $defaultOptions;
  194. if ($minifier) {
  195. self::$_controller->loadMinifier($minifier);
  196. // get source content and minify it
  197. $pieces[] = call_user_func($minifier, $source->getContent(), $options);
  198. } else {
  199. $pieces[] = $source->getContent();
  200. }
  201. }
  202. $content = implode($implodeSeparator, $pieces);
  203. }
  204. if ($type === self::TYPE_CSS && false !== strpos($content, '@import')) {
  205. $content = self::_handleCssImports($content);
  206. }
  207. // do any post-processing (esp. for editing build URIs)
  208. if (self::$_options['postprocessorRequire']) {
  209. require_once self::$_options['postprocessorRequire'];
  210. }
  211. if (self::$_options['postprocessor']) {
  212. $content = call_user_func(self::$_options['postprocessor'], $content, $type);
  213. }
  214. return $content;
  215. }
  216. /**
  217. * Bubble CSS @imports to the top or prepend a warning if an
  218. * @import is detected not at the top.
  219. */
  220. protected static function _handleCssImports($css)
  221. {
  222. if (self::$_options['bubbleCssImports']) {
  223. // bubble CSS imports
  224. preg_match_all('/@import.*?;/', $css, $imports);
  225. $css = implode('', $imports[0]) . preg_replace('/@import.*?;/', '', $css);
  226. } else if ('' !== self::$importWarning) {
  227. // remove comments so we don't mistake { in a comment as a block
  228. $noCommentCss = preg_replace('@/\\*[\\s\\S]*?\\*/@', '', $css);
  229. $lastImportPos = strrpos($noCommentCss, '@import');
  230. $firstBlockPos = strpos($noCommentCss, '{');
  231. if (false !== $lastImportPos
  232. && false !== $firstBlockPos
  233. && $firstBlockPos < $lastImportPos
  234. ) {
  235. // { appears before @import : prepend warning
  236. $css = self::$importWarning . $css;
  237. }
  238. }
  239. return $css;
  240. }
  241. }