PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/blog/wp-content/plugins/w3-total-cache/lib/Minify/Minify.php

https://github.com/kennethreitz-archive/wordpress-skeleton
PHP | 568 lines | 361 code | 23 blank | 184 comment | 45 complexity | 29d4bb6eb72d504d0968c47190630891 MD5 | raw file
  1. <?php
  2. /**
  3. * Class Minify
  4. * @package Minify
  5. */
  6. /**
  7. * Minify_Source
  8. */
  9. require_once W3TC_LIB_MINIFY_DIR . '/Minify/Source.php';
  10. /**
  11. * Minify - Combines, minifies, and caches JavaScript and CSS files on demand.
  12. *
  13. * See README for usage instructions (for now).
  14. *
  15. * This library was inspired by {@link mailto:flashkot@mail.ru jscsscomp by Maxim Martynyuk}
  16. * and by the article {@link http://www.hunlock.com/blogs/Supercharged_Javascript "Supercharged JavaScript" by Patrick Hunlock}.
  17. *
  18. * Requires PHP 5.1.0.
  19. * Tested on PHP 5.1.6.
  20. *
  21. * @package Minify
  22. * @author Ryan Grove <ryan@wonko.com>
  23. * @author Stephen Clay <steve@mrclay.org>
  24. * @copyright 2008 Ryan Grove, Stephen Clay. All rights reserved.
  25. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  26. * @link http://code.google.com/p/minify/
  27. */
  28. class Minify {
  29. const VERSION = '2.1.3';
  30. const TYPE_CSS = 'text/css';
  31. const TYPE_HTML = 'text/html';
  32. // there is some debate over the ideal JS Content-Type, but this is the
  33. // Apache default and what Yahoo! uses..
  34. const TYPE_JS = 'application/x-javascript';
  35. /**
  36. * How many hours behind are the file modification times of uploaded files?
  37. *
  38. * If you upload files from Windows to a non-Windows server, Windows may report
  39. * incorrect mtimes for the files. Immediately after modifying and uploading a
  40. * file, use the touch command to update the mtime on the server. If the mtime
  41. * jumps ahead by a number of hours, set this variable to that number. If the mtime
  42. * moves back, this should not be needed.
  43. *
  44. * @var int $uploaderHoursBehind
  45. */
  46. public static $uploaderHoursBehind = 0;
  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. * Cache ID
  58. * @var string
  59. */
  60. protected static $_cacheId = null;
  61. /**
  62. * Specify a cache object (with identical interface as Minify_Cache_File) or
  63. * a path to use with Minify_Cache_File.
  64. *
  65. * If not called, Minify will not use a cache and, for each 200 response, will
  66. * need to recombine files, minify and encode the output.
  67. *
  68. * @param mixed $cache object with identical interface as Minify_Cache_File or
  69. * a directory path, or null to disable caching. (default = '')
  70. *
  71. * @param bool $fileLocking (default = true) This only applies if the first
  72. * parameter is a string.
  73. *
  74. * @return null
  75. */
  76. public static function setCache($cache = '', $fileLocking = true)
  77. {
  78. if (is_string($cache)) {
  79. require_once W3TC_LIB_MINIFY_DIR . '/Minify/Cache/File.php';
  80. self::$_cache = new Minify_Cache_File($cache, $fileLocking);
  81. } else {
  82. self::$_cache = $cache;
  83. }
  84. }
  85. /**
  86. * Serve a request for a minified file.
  87. *
  88. * Here are the available options and defaults in the base controller:
  89. *
  90. * 'isPublic' : send "public" instead of "private" in Cache-Control
  91. * headers, allowing shared caches to cache the output. (default true)
  92. *
  93. * 'quiet' : set to true to have serve() return an array rather than sending
  94. * any headers/output (default false)
  95. *
  96. * 'encodeOutput' : set to false to disable content encoding, and not send
  97. * the Vary header (default true)
  98. *
  99. * 'encodeMethod' : generally you should let this be determined by
  100. * HTTP_Encoder (leave null), but you can force a particular encoding
  101. * to be returned, by setting this to 'gzip' or '' (no encoding)
  102. *
  103. * 'encodeLevel' : level of encoding compression (0 to 9, default 9)
  104. *
  105. * 'contentTypeCharset' : appended to the Content-Type header sent. Set to a falsey
  106. * value to remove. (default 'utf-8')
  107. *
  108. * 'maxAge' : set this to the number of seconds the client should use its cache
  109. * before revalidating with the server. This sets Cache-Control: max-age and the
  110. * Expires header. Unlike the old 'setExpires' setting, this setting will NOT
  111. * prevent conditional GETs. Note this has nothing to do with server-side caching.
  112. *
  113. * 'rewriteCssUris' : If true, serve() will automatically set the 'currentDir'
  114. * minifier option to enable URI rewriting in CSS files (default true)
  115. *
  116. * 'bubbleCssImports' : If true, all @import declarations in combined CSS
  117. * files will be move to the top. Note this may alter effective CSS values
  118. * due to a change in order. (default false)
  119. *
  120. * 'debug' : set to true to minify all sources with the 'Lines' controller, which
  121. * eases the debugging of combined files. This also prevents 304 responses.
  122. * @see Minify_Lines::minify()
  123. *
  124. * 'minifiers' : to override Minify's default choice of minifier function for
  125. * a particular content-type, specify your callback under the key of the
  126. * content-type:
  127. * <code>
  128. * // call customCssMinifier($css) for all CSS minification
  129. * $options['minifiers'][Minify::TYPE_CSS] = 'customCssMinifier';
  130. *
  131. * // don't minify Javascript at all
  132. * $options['minifiers'][Minify::TYPE_JS] = '';
  133. * </code>
  134. *
  135. * 'minifierOptions' : to send options to the minifier function, specify your options
  136. * under the key of the content-type. E.g. To send the CSS minifier an option:
  137. * <code>
  138. * // give CSS minifier array('optionName' => 'optionValue') as 2nd argument
  139. * $options['minifierOptions'][Minify::TYPE_CSS]['optionName'] = 'optionValue';
  140. * </code>
  141. *
  142. * 'contentType' : (optional) this is only needed if your file extension is not
  143. * js/css/html. The given content-type will be sent regardless of source file
  144. * extension, so this should not be used in a Groups config with other
  145. * Javascript/CSS files.
  146. *
  147. * Any controller options are documented in that controller's setupSources() method.
  148. *
  149. * @param mixed instance of subclass of Minify_Controller_Base or string name of
  150. * controller. E.g. 'Files'
  151. *
  152. * @param array $options controller/serve options
  153. *
  154. * @return mixed null, or, if the 'quiet' option is set to true, an array
  155. * with keys "success" (bool), "statusCode" (int), "content" (string), and
  156. * "headers" (array).
  157. */
  158. public static function serve($controller, $options = array())
  159. {
  160. if (is_string($controller)) {
  161. // make $controller into object
  162. $class = 'Minify_Controller_' . $controller;
  163. if (! class_exists($class, false)) {
  164. require_once W3TC_LIB_MINIFY_DIR . "/Minify/Controller/"
  165. . str_replace('_', '/', $controller) . ".php";
  166. }
  167. $controller = new $class();
  168. }
  169. // set up controller sources and mix remaining options with
  170. // controller defaults
  171. $options = $controller->setupSources($options);
  172. $options = $controller->analyzeSources($options);
  173. self::$_options = $controller->mixInDefaultOptions($options);
  174. // check request validity
  175. if (! $controller->sources) {
  176. // invalid request!
  177. if (! self::$_options['quiet']) {
  178. header(self::$_options['badRequestHeader']);
  179. echo self::$_options['badRequestHeader'];
  180. return;
  181. } else {
  182. list(,$statusCode) = explode(' ', self::$_options['badRequestHeader']);
  183. return array(
  184. 'success' => false
  185. ,'statusCode' => (int)$statusCode
  186. ,'content' => ''
  187. ,'headers' => array()
  188. );
  189. }
  190. }
  191. self::$_controller = $controller;
  192. if (self::$_options['debug']) {
  193. self::_setupDebug($controller->sources);
  194. self::$_options['maxAge'] = 0;
  195. }
  196. // determine encoding
  197. if (self::$_options['encodeOutput'] != '') {
  198. if (self::$_options['encodeMethod'] !== null) {
  199. // controller specifically requested this
  200. $contentEncoding = self::$_options['encodeMethod'];
  201. } else {
  202. // sniff request header
  203. require_once W3TC_LIB_MINIFY_DIR . '/HTTP/Encoder.php';
  204. // depending on what the client accepts, $contentEncoding may be
  205. // 'x-gzip' while our internal encodeMethod is 'gzip'. Calling
  206. // getAcceptedEncoding(false, false) leaves out compress and deflate as options.
  207. list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(self::$_options['encodeOutput']);
  208. }
  209. } else {
  210. self::$_options['encodeMethod'] = ''; // identity (no encoding)
  211. }
  212. // check client cache
  213. require_once W3TC_LIB_MINIFY_DIR . '/HTTP/ConditionalGet.php';
  214. $cgOptions = array(
  215. 'lastModifiedTime' => self::$_options['lastModifiedTime']
  216. ,'isPublic' => self::$_options['isPublic']
  217. ,'encoding' => self::$_options['encodeMethod']
  218. );
  219. if (self::$_options['maxAge'] > 0) {
  220. $cgOptions['maxAge'] = self::$_options['maxAge'];
  221. }
  222. $cg = new HTTP_ConditionalGet($cgOptions);
  223. if ($cg->cacheIsValid) {
  224. // client's cache is valid
  225. if (! self::$_options['quiet']) {
  226. $cg->sendHeaders();
  227. return;
  228. } else {
  229. return array(
  230. 'success' => true
  231. ,'statusCode' => 304
  232. ,'content' => ''
  233. ,'headers' => $cg->getHeaders()
  234. );
  235. }
  236. } else {
  237. // client will need output
  238. $headers = $cg->getHeaders();
  239. unset($cg);
  240. }
  241. if (self::$_options['contentType'] === self::TYPE_CSS
  242. && self::$_options['rewriteCssUris']) {
  243. reset($controller->sources);
  244. while (list($key, $source) = each($controller->sources)) {
  245. if ($source->filepath
  246. && !isset($source->minifyOptions['currentDir'])
  247. && !isset($source->minifyOptions['prependRelativePath'])
  248. ) {
  249. $source->minifyOptions['currentDir'] = dirname($source->filepath);
  250. }
  251. }
  252. }
  253. // check server cache
  254. if (null !== self::$_cache) {
  255. // using cache
  256. // the goal is to use only the cache methods to sniff the length and
  257. // output the content, as they do not require ever loading the file into
  258. // memory.
  259. $cacheId = self::_getCacheId();
  260. $fullCacheId = (self::$_options['encodeMethod'])
  261. ? $cacheId . self::$_options['encodeMethod']
  262. : $cacheId;
  263. // check cache for valid entry
  264. $cacheIsReady = self::$_cache->isValid($fullCacheId, self::$_options['lastModifiedTime']);
  265. if ($cacheIsReady) {
  266. $cacheContentLength = self::$_cache->getSize($fullCacheId);
  267. } else {
  268. // generate & cache content
  269. $content = self::_combineMinify();
  270. self::$_cache->store($cacheId, $content);
  271. if (stristr(self::$_options['encodeOutput'], 'gzip') && function_exists('gzencode')) {
  272. self::$_cache->store($cacheId . '.gzip', gzencode($content, self::$_options['encodeLevel']));
  273. }
  274. if (stristr(self::$_options['encodeOutput'], 'deflate') && function_exists('gzdeflate')) {
  275. self::$_cache->store($cacheId . '.deflate', gzdeflate($content, self::$_options['encodeLevel']));
  276. }
  277. }
  278. } else {
  279. // no cache
  280. $cacheIsReady = false;
  281. $content = self::_combineMinify();
  282. }
  283. if (! $cacheIsReady) {
  284. switch (self::$_options['encodeMethod']) {
  285. case 'gzip':
  286. $content = gzencode($content, self::$_options['encodeLevel']);
  287. break;
  288. case 'deflate':
  289. $content = gzdeflate($content, self::$_options['encodeLevel']);
  290. break;
  291. }
  292. // still need to encode
  293. }
  294. // add headers
  295. $headers['Content-Length'] = $cacheIsReady
  296. ? $cacheContentLength
  297. : strlen($content);
  298. $headers['Content-Type'] = self::$_options['contentTypeCharset']
  299. ? self::$_options['contentType'] . '; charset=' . self::$_options['contentTypeCharset']
  300. : self::$_options['contentType'];
  301. if (self::$_options['encodeMethod'] !== '') {
  302. $headers['Content-Encoding'] = $contentEncoding;
  303. }
  304. if (self::$_options['encodeOutput']) {
  305. $headers['Vary'] = 'Accept-Encoding';
  306. }
  307. if (! self::$_options['quiet']) {
  308. // output headers & content
  309. foreach ($headers as $name => $val) {
  310. header($name . ': ' . $val);
  311. }
  312. if ($cacheIsReady) {
  313. self::$_cache->display($fullCacheId);
  314. } else {
  315. echo $content;
  316. }
  317. } else {
  318. return array(
  319. 'success' => true
  320. ,'statusCode' => 200
  321. ,'content' => $cacheIsReady
  322. ? self::$_cache->fetch($fullCacheId)
  323. : $content
  324. ,'headers' => $headers
  325. );
  326. }
  327. }
  328. /**
  329. * Return combined minified content for a set of sources
  330. *
  331. * No internal caching will be used and the content will not be HTTP encoded.
  332. *
  333. * @param array $sources array of filepaths and/or Minify_Source objects
  334. *
  335. * @param array $options (optional) array of options for serve. By default
  336. * these are already set: quiet = true, encodeMethod = '', lastModifiedTime = 0.
  337. *
  338. * @return string
  339. */
  340. public static function combine($sources, $options = array())
  341. {
  342. $cache = self::$_cache;
  343. self::$_cache = null;
  344. $options = array_merge(array(
  345. 'files' => (array)$sources
  346. ,'quiet' => true
  347. ,'encodeMethod' => ''
  348. ,'lastModifiedTime' => 0
  349. ), $options);
  350. $out = self::serve('Files', $options);
  351. self::$_cache = $cache;
  352. return $out['content'];
  353. }
  354. /**
  355. * On IIS, create $_SERVER['DOCUMENT_ROOT']
  356. *
  357. * @param bool $unsetPathInfo (default false) if true, $_SERVER['PATH_INFO']
  358. * will be unset (it is inconsistent with Apache's setting)
  359. *
  360. * @return null
  361. */
  362. public static function setDocRoot($unsetPathInfo = false)
  363. {
  364. if (isset($_SERVER['SERVER_SOFTWARE'])
  365. && 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/')
  366. ) {
  367. $_SERVER['DOCUMENT_ROOT'] = rtrim(substr(
  368. $_SERVER['PATH_TRANSLATED']
  369. ,0
  370. ,strlen($_SERVER['PATH_TRANSLATED']) - strlen($_SERVER['SCRIPT_NAME'])
  371. ), '\\');
  372. if ($unsetPathInfo) {
  373. unset($_SERVER['PATH_INFO']);
  374. }
  375. require_once W3TC_LIB_MINIFY_DIR . '/Minify/Logger.php';
  376. Minify_Logger::log("setDocRoot() set DOCUMENT_ROOT to \"{$_SERVER['DOCUMENT_ROOT']}\"");
  377. }
  378. }
  379. /**
  380. * @var mixed Minify_Cache_* object or null (i.e. no server cache is used)
  381. */
  382. private static $_cache = null;
  383. /**
  384. * @var Minify_Controller active controller for current request
  385. */
  386. protected static $_controller = null;
  387. /**
  388. * @var array options for current request
  389. */
  390. protected static $_options = null;
  391. /**
  392. * Set up sources to use Minify_Lines
  393. *
  394. * @param array $sources Minify_Source instances
  395. *
  396. * @return null
  397. */
  398. protected static function _setupDebug($sources)
  399. {
  400. foreach ($sources as $source) {
  401. $source->minifier = array('Minify_Lines', 'minify');
  402. $id = $source->getId();
  403. $source->minifyOptions = array_merge((array) $source->minifyOptions, array(
  404. 'id' => (is_file($id) ? basename($id) : $id))
  405. );
  406. }
  407. }
  408. /**
  409. * Combines sources and minifies the result.
  410. *
  411. * @return string
  412. */
  413. protected static function _combineMinify()
  414. {
  415. $type = self::$_options['contentType']; // ease readability
  416. // when combining scripts, make sure all statements separated and
  417. // trailing single line comment is terminated
  418. $implodeSeparator = ($type === self::TYPE_JS)
  419. ? "\n;"
  420. : '';
  421. // allow the user to pass a particular array of options to each
  422. // minifier (designated by type). source objects may still override
  423. // these
  424. $defaultOptions = isset(self::$_options['minifierOptions'][$type])
  425. ? self::$_options['minifierOptions'][$type]
  426. : array();
  427. // if minifier not set, default is no minification. source objects
  428. // may still override this
  429. $defaultMinifier = isset(self::$_options['minifiers'][$type])
  430. ? self::$_options['minifiers'][$type]
  431. : false;
  432. if (Minify_Source::haveNoMinifyPrefs(self::$_controller->sources)) {
  433. // all source have same options/minifier, better performance
  434. // to combine, then minify once
  435. foreach (self::$_controller->sources as $source) {
  436. $pieces[] = $source->getContent();
  437. }
  438. $content = implode($implodeSeparator, $pieces);
  439. if ($defaultMinifier) {
  440. self::$_controller->loadMinifier($defaultMinifier);
  441. $content = call_user_func($defaultMinifier, $content, $defaultOptions);
  442. }
  443. } else {
  444. // minify each source with its own options and minifier, then combine
  445. foreach (self::$_controller->sources as $source) {
  446. // allow the source to override our minifier and options
  447. $minifier = (null !== $source->minifier)
  448. ? $source->minifier
  449. : $defaultMinifier;
  450. $options = (null !== $source->minifyOptions)
  451. ? array_merge($defaultOptions, $source->minifyOptions)
  452. : $defaultOptions;
  453. if ($minifier) {
  454. self::$_controller->loadMinifier($minifier);
  455. // get source content and minify it
  456. $pieces[] = call_user_func($minifier, $source->getContent(), $options);
  457. } else {
  458. $pieces[] = $source->getContent();
  459. }
  460. }
  461. $content = implode($implodeSeparator, $pieces);
  462. }
  463. if ($type === self::TYPE_CSS && false !== strpos($content, '@import')) {
  464. $content = self::_handleCssImports($content);
  465. }
  466. // do any post-processing (esp. for editing build URIs)
  467. if (self::$_options['postprocessorRequire']) {
  468. require_once self::$_options['postprocessorRequire'];
  469. }
  470. if (self::$_options['postprocessor']) {
  471. $content = call_user_func(self::$_options['postprocessor'], $content, $type);
  472. }
  473. return $content;
  474. }
  475. /**
  476. * Sets cache ID
  477. * @param string $cacheId
  478. */
  479. public static function setCacheId($cacheId = null)
  480. {
  481. self::$_cacheId = $cacheId;
  482. }
  483. /**
  484. * Returns cache ID
  485. */
  486. public static function getCacheId()
  487. {
  488. return self::$_cacheId;
  489. }
  490. /**
  491. * Make a unique cache id for for this request.
  492. *
  493. * Any settings that could affect output are taken into consideration
  494. *
  495. * @return string
  496. */
  497. protected static function _getCacheId()
  498. {
  499. return (self::$_cacheId ? self::$_cacheId : md5(serialize(array(
  500. Minify_Source::getDigest(self::$_controller->sources)
  501. ,self::$_options['minifiers']
  502. ,self::$_options['minifierOptions']
  503. ,self::$_options['postprocessor']
  504. ,self::$_options['bubbleCssImports']
  505. ))));
  506. }
  507. /**
  508. * Bubble CSS @imports to the top or prepend a warning if an
  509. * @import is detected not at the top.
  510. */
  511. protected static function _handleCssImports($css)
  512. {
  513. if (self::$_options['bubbleCssImports']) {
  514. // bubble CSS imports
  515. preg_match_all('/@import.*?;/', $css, $imports);
  516. $css = implode('', $imports[0]) . preg_replace('/@import.*?;/', '', $css);
  517. } else if ('' !== self::$importWarning) {
  518. // remove comments so we don't mistake { in a comment as a block
  519. $noCommentCss = preg_replace('@/\\*[\\s\\S]*?\\*/@', '', $css);
  520. $lastImportPos = strrpos($noCommentCss, '@import');
  521. $firstBlockPos = strpos($noCommentCss, '{');
  522. if (false !== $lastImportPos
  523. && false !== $firstBlockPos
  524. && $firstBlockPos < $lastImportPos
  525. ) {
  526. // { appears before @import : prepend warning
  527. $css = self::$importWarning . $css;
  528. }
  529. }
  530. return $css;
  531. }
  532. }