PageRenderTime 46ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/library/external/minify.php

http://github.com/forkcms/forkcms
PHP | 937 lines | 414 code | 131 blank | 392 comment | 76 complexity | 30f744f59542539969b0dbb636548165 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, MIT, AGPL-3.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /**
  3. * MinifyCSS class
  4. *
  5. * This source file can be used to minify CSS files.
  6. *
  7. * The class is documented in the file itself. If you find any bugs help me out and report them. Reporting can be done by sending an email to minify@mullie.eu.
  8. * If you report a bug, make sure you give me enough information (include your code).
  9. *
  10. * License
  11. * Copyright (c) 2012, Matthias Mullie. All rights reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
  14. *
  15. * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  16. * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  17. * 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
  18. *
  19. * This software is provided by the author "as is" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the author be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
  20. *
  21. * @author Matthias Mullie <minify@mullie.eu>
  22. * @author Tijs Verkoyen <php-css-to-inline-styles@verkoyen.eu>
  23. * @version 1.0.0
  24. *
  25. * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
  26. * @license BSD License
  27. */
  28. class MinifyCSS extends Minify
  29. {
  30. /**
  31. * Combine CSS from import statements.
  32. * @import's will be loaded and their content merged into the original file, to save HTTP requests.
  33. *
  34. * @param string $source The file to combine imports for.
  35. * @param string[optional] $path The path the data should be written to.
  36. * @return string
  37. */
  38. public function combineImports($source, $path = false)
  39. {
  40. // little "hack" for internal use
  41. $content = @func_get_arg(2);
  42. // load the content
  43. if($content === false) $content = $this->load($source);
  44. // validate data
  45. if($content == $source) throw new MinifyException('The data for "' . $source . '" could not be loaded, please make sure the path is correct.');
  46. // the regex to match import statements
  47. $importRegex = '/
  48. # import statement
  49. @import
  50. # whitespace
  51. \s+
  52. # (optional) open url()
  53. (?<url>url\()?
  54. # open path enclosure
  55. (?<quotes>["\'])
  56. # fetch path
  57. (?<path>
  58. # do not fetch data uris
  59. (?!(
  60. ["\']?
  61. data:
  62. ))
  63. .+
  64. )
  65. # close path enclosure
  66. (?P=quotes)
  67. # (optional) close url()
  68. (?(url)\))
  69. # (optional) trailing whitespace
  70. \s*
  71. # (optional) media statement(s)
  72. (?<media>.*?)
  73. # (optional) trailing whitespace
  74. \s*
  75. # (optional) closing semi-colon
  76. ;?
  77. /ix';
  78. // find all relative imports in css (for now we don't support imports with media, and imports should use url(xxx))
  79. if(preg_match_all($importRegex, $content, $matches, PREG_SET_ORDER))
  80. {
  81. $search = array();
  82. $replace = array();
  83. // loop the matches
  84. foreach($matches as $i => $match)
  85. {
  86. // get the path for the file that will be imported
  87. $importPath = dirname($source) . '/' . $match['path'];
  88. // only replace the import with the content if we can grab the content of the file
  89. if(@file_exists($importPath) && is_file($importPath))
  90. {
  91. // grab content
  92. $importContent = @file_get_contents($importPath);
  93. // fix relative paths
  94. $importContent = $this->move($importPath, $source, $importContent);
  95. // check if this is only valid for certain media
  96. if($match['media']) $importContent = '@media '. $match['media'] . '{' . "\n" . $importContent . "\n" . '}';
  97. // add to replacement array
  98. $search[] = $match[0];
  99. $replace[] = $importContent;
  100. }
  101. }
  102. // replace the import statements
  103. $content = str_replace($search, $replace, $content);
  104. // ge recursive (if imports have occured)
  105. if($search) $content = $this->combineImports($source, false, $content);
  106. }
  107. // save to path
  108. if($path !== false && @func_get_arg(2) === false) $this->save($content, $path);
  109. return $content;
  110. }
  111. /**
  112. * Convert relative paths based upon 1 path to another.
  113. *
  114. * E.g.
  115. * ../images/img.gif based upon /home/forkcms/frontend/core/layout/css, should become
  116. * ../../core/layout/images/img.gif based upon /home/forkcms/frontend/cache/minified_css
  117. *
  118. * @param string $path The relative path that needs to be converted.
  119. * @param string $from The original base path.
  120. * @param string $to The new base path.
  121. * @return string The new relative path.
  122. */
  123. protected function convertRelativePath($path, $from, $to)
  124. {
  125. // make sure we're dealing with directories
  126. $from = @is_file($from) ? dirname($from) : $from;
  127. $to = @is_file($to) ? dirname($to) : $to;
  128. // deal with different operating systems' directory structure
  129. $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
  130. $from = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $from), '/');
  131. $to = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $to), '/');
  132. // if we're not dealing with a relative path, just return absolute
  133. if(strpos($path, '/') === 0) return $path;
  134. /*
  135. * Example:
  136. * $path = ../images/img.gif
  137. * $from = /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css
  138. * $to = /home/forkcms/frontend/cache/minified_css
  139. */
  140. // normalize paths
  141. do
  142. {
  143. $path = preg_replace('/[^\.\.\/]+?\/\.\.\//', '', $path, -1, $count);
  144. }
  145. while($count);
  146. do
  147. {
  148. $from = preg_replace('/[^\/]+?\/\.\.\//', '', $from, -1, $count);
  149. }
  150. while($count);
  151. do
  152. {
  153. $to = preg_replace('/[^\/]+?\/\.\.\//', '', $to, -1, $count);
  154. }
  155. while($count);
  156. /*
  157. * At this point:
  158. * $path = ../images/img.gif
  159. * $from = /home/forkcms/frontend/core/layout/css
  160. * $to = /home/forkcms/frontend/cache/minified_css
  161. */
  162. // resolve path the relative url is based upon
  163. do
  164. {
  165. $path = preg_replace('/^\.\.\//', '', $path, 1, $count);
  166. // for every level up, adjust dirname
  167. if($count) $from = dirname($from);
  168. }
  169. while($count);
  170. /*
  171. * At this point:
  172. * $path = images/img.gif
  173. * $from = /home/forkcms/frontend/core/layout
  174. * $to = /home/forkcms/frontend/cache/minified_css
  175. */
  176. // compare paths & strip identical parents
  177. $from = explode('/', $from);
  178. $to = explode('/', $to);
  179. foreach($from as $i => $chunk)
  180. {
  181. if($from[$i] == $to[$i]) unset($from[$i], $to[$i]);
  182. else break;
  183. }
  184. /*
  185. * At this point:
  186. * $path = images/img.gif
  187. * $from = array('core', 'layout')
  188. * $to = array('cache', 'minified_css')
  189. */
  190. // add .. for every directory that needs to be traversed for new path
  191. $new = str_repeat('../', count($to));
  192. /*
  193. * At this point:
  194. * $path = images/img.gif
  195. * $from = array('core', 'layout')
  196. * $to = *no longer matters*
  197. * $new = ../../
  198. */
  199. // add path, relative from this point, to traverse to image
  200. $new .= implode('/', $from);
  201. // if $from contained no elements, we still have a redundant trailing slash
  202. if(empty($from)) $new = rtrim($new, '/');
  203. /*
  204. * At this point:
  205. * $path = images/img.gif
  206. * $from = *no longer matters*
  207. * $to = *no longer matters*
  208. * $new = ../../core/layout
  209. */
  210. // add remaining path
  211. $new .= '/' . $path;
  212. /*
  213. * At this point:
  214. * $path = *no longer matters*
  215. * $from = *no longer matters*
  216. * $to = *no longer matters*
  217. * $new = ../../core/layout/images/img.gif
  218. */
  219. // Tada!
  220. return $new;
  221. }
  222. /**
  223. * Import images into the CSS, base64-ized.
  224. * @url(image.jpg) images will be loaded and their content merged into the original file, to save HTTP requests.
  225. *
  226. * @param string $source The file to import images for.
  227. * @param string[optional] $path The path the data should be written to.
  228. * @return string
  229. */
  230. public function importImages($source, $path = false)
  231. {
  232. // little "hack" for internal use
  233. $content = @func_get_arg(2);
  234. // load the content
  235. if($content === false) $content = $this->load($source);
  236. // validate data
  237. if($content == $source) throw new MinifyException('The data for "' . $source . '" could not be loaded, please make sure the path is correct.');
  238. if(preg_match_all('/url\((["\']?)((?!["\']?data:).*?\.(gif|png|jpg|jpeg|tiff|svg))\\1\)/i', $content, $matches, PREG_SET_ORDER))
  239. {
  240. $search = array();
  241. $replace = array();
  242. // loop the matches
  243. foreach($matches as $match)
  244. {
  245. // get the path for the file that will be imported
  246. $path = $match[2];
  247. $path = dirname($source) . '/' . $path;
  248. // only replace the import with the content if we can grab the content of the file
  249. if(@file_exists($path) && is_file($path))
  250. {
  251. // grab content
  252. $importContent = @file_get_contents($path);
  253. // base-64-ize
  254. $importContent = base64_encode($importContent);
  255. // build replacement
  256. $search[] = $match[0];
  257. $replace[] = 'url(data:image/' . $match[3] . ';base64,' . $importContent .')';
  258. }
  259. }
  260. // replace the import statements
  261. $content = str_replace($search, $replace, $content);
  262. }
  263. // save to path
  264. if($path !== false && @func_get_arg(2) === false) $this->save($content, $path);
  265. return $content;
  266. }
  267. /**
  268. * Minify the data.
  269. * Perform CSS optimizations.
  270. *
  271. * @param string[optional] $path The path the data should be written to.
  272. * @return string The minified data.
  273. */
  274. public function minify($path = false, $stripComments = true, $stripWhitespace = true, $shortenHex = true, $combineImports = true, $importImages = true)
  275. {
  276. $content = '';
  277. // loop files
  278. foreach($this->data as $source => $css)
  279. {
  280. // if we're saving to a new path, we'll have to fix the relative paths
  281. if($path !== false && $source !== 0) $css = $this->move($source, $path, $css);
  282. // combine css
  283. $content .= $css;
  284. }
  285. if($combineImports) $content = $this->combineImports($path, false, $content);
  286. if($stripComments) $content = $this->stripComments($content);
  287. if($stripWhitespace) $content = $this->stripWhitespace($content);
  288. if($shortenHex) $content = $this->shortenHex($content);
  289. if($importImages) $content = $this->importImages($path, false, $content);
  290. // save to path
  291. if($path !== false) $this->save($content, $path);
  292. return $content;
  293. }
  294. /**
  295. * Moving a css file should update all relative urls.
  296. * Relative references (e.g. ../images/image.gif) in a certain css file, will have to be updated when a file is
  297. * being saved at another location (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper)
  298. *
  299. * @param string $source The file to update relative urls for.
  300. * @param string $path The path the data will be written to.
  301. * @return string
  302. */
  303. public function move($source, $path)
  304. {
  305. // little "hack" for internal use
  306. $content = @func_get_arg(2);
  307. // load the content
  308. if($content === false) $content = $this->load($source);
  309. // validate data
  310. if($content == $source) throw new MinifyException('The data for "' . $source . '" could not be loaded, please make sure the path is correct.');
  311. // regex to match paths
  312. $pathsRegex = '/
  313. # enable possiblity of giving multiple subpatterns same name
  314. (?J)
  315. # @import "xxx" or @import url(xxx)
  316. # import statement
  317. @import
  318. # whitespace
  319. \s+
  320. # (optional) open url()
  321. (?<url>url\()?
  322. # open path enclosure
  323. (?<quotes>["\'])
  324. # fetch path
  325. (?<path>
  326. # do not fetch data uris
  327. (?!(
  328. ["\']?
  329. data:
  330. ))
  331. .+
  332. )
  333. # close path enclosure
  334. (?P=quotes)
  335. # (optional) close url()
  336. (?(url)\))
  337. |
  338. # url(xxx)
  339. # open url()
  340. url\(
  341. # open path enclosure
  342. (?<quotes>["\'])?
  343. # fetch path
  344. (?<path>
  345. # do not fetch data uris
  346. (?!(
  347. ["\']?
  348. data:
  349. ))
  350. .+
  351. )
  352. # close path enclosure
  353. (?(quotes)(?P=quotes))
  354. # close url()
  355. \)
  356. /ix';
  357. // find all relative urls in css
  358. if(preg_match_all($pathsRegex, $content, $matches, PREG_SET_ORDER))
  359. {
  360. $search = array();
  361. $replace = array();
  362. // loop all urls
  363. foreach($matches as $match)
  364. {
  365. // determine if it's a url() or an @import match
  366. $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
  367. // fix relative url
  368. $url = $this->convertRelativePath($match['path'], dirname($source), dirname($path));
  369. // build replacement
  370. $search[] = $match[0];
  371. if($type == 'url') $replace[] = 'url(' . $url . ')';
  372. elseif($type == 'import') $replace[] = '@import "' . $url . '"';
  373. }
  374. // replace urls
  375. $content = str_replace($search, $replace, $content);
  376. }
  377. // save to path (not for internal use!)
  378. if(@func_get_arg(2) === false) $this->save($content, $path);
  379. return $content;
  380. }
  381. /**
  382. * Shorthand hex color codes.
  383. * #FF0000 -> #F00
  384. *
  385. * @param string $content The file/content to shorten the hex color codes for.
  386. * @param string[optional] $path The path the data should be written to.
  387. * @return string
  388. */
  389. public function shortenHex($content, $path = false)
  390. {
  391. // load the content
  392. $content = $this->load($content);
  393. // shorthand hex color codes
  394. $content = preg_replace('/#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3/i', '#$1$2$3', $content);
  395. // save to path
  396. if($path !== false) $this->save($content, $path);
  397. return $content;
  398. }
  399. /**
  400. * Strip comments.
  401. *
  402. * @param string $content The file/content to strip the comments for.
  403. * @param string[optional] $path The path the data should be written to.
  404. * @return string
  405. */
  406. public function stripComments($content, $path = false)
  407. {
  408. // load the content
  409. $content = $this->load($content);
  410. // strip comments
  411. $content = preg_replace('/\/\*(.*?)\*\//is', '', $content);
  412. // save to path
  413. if($path !== false) $this->save($content, $path);
  414. return $content;
  415. }
  416. /**
  417. * Strip whitespace.
  418. *
  419. * @param string $content The file/content to strip the whitespace for.
  420. * @param string[optional] $path The path the data should be written to.
  421. * @return string
  422. */
  423. public function stripWhitespace($content, $path = false)
  424. {
  425. // load the content
  426. $content = $this->load($content);
  427. // semicolon/space before closing bracket > replace by bracket
  428. $content = preg_replace('/;?\s*}/', '}', $content);
  429. // bracket, colon, semicolon or comma preceeded or followed by whitespace > remove space
  430. $content = preg_replace('/\s*([\{:;,])\s*/', '$1', $content);
  431. // preceeding/trailing whitespace > remove
  432. $content = preg_replace('/^\s*|\s*$/m', '', $content);
  433. // newlines > remove
  434. $content = preg_replace('/\n/', '', $content);
  435. // save to path
  436. if($path !== false) $this->save($content, $path);
  437. return $content;
  438. }
  439. }
  440. /**
  441. * MinifyJS class
  442. *
  443. * This source file can be used to minify Javascript files.
  444. *
  445. * The class is documented in the file itself. If you find any bugs help me out and report them. Reporting can be done by sending an email to minify@mullie.eu.
  446. * If you report a bug, make sure you give me enough information (include your code).
  447. *
  448. * License
  449. * Copyright (c) 2012, Matthias Mullie. All rights reserved.
  450. *
  451. * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
  452. *
  453. * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  454. * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  455. * 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
  456. *
  457. * This software is provided by the author "as is" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the author be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
  458. *
  459. * @author Matthias Mullie <minify@mullie.eu>
  460. * @author Tijs Verkoyen <php-css-to-inline-styles@verkoyen.eu>
  461. * @version 1.0.0
  462. *
  463. * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
  464. * @license BSD License
  465. */
  466. class MinifyJS extends Minify
  467. {
  468. /**
  469. * Extract comments & strings from source code (and replace them with a placeholder)
  470. * This fancy parsing is neccessary because comments can contain string demarcators and vice versa, and both can
  471. * contain content that is very similar to the rest of the code.
  472. *
  473. * @param string $content The file/content to extract comments & strings for.
  474. * @return array An array containing the (manipulated) content, the strings & the comments.
  475. */
  476. protected function extract($content)
  477. {
  478. // load the content
  479. $content = $this->load($content);
  480. // initialize array that will contain all strings found in the code
  481. $strings = array();
  482. $comments = array();
  483. // loop all characters
  484. for($i = 0; $i < strlen($content); $i++)
  485. {
  486. $character = $content[$i];
  487. switch($content[$i])
  488. {
  489. // string demarcation: ' or "
  490. case '\'':
  491. case '"':
  492. $stringOpener = $character;
  493. $string = $character;
  494. // process through content until we find the end of the string
  495. for($j = $i + 1; $j < strlen($content); $j++)
  496. {
  497. $character = $content[$j];
  498. $previousCharacter = isset($content[$j - 1]) ? $content[$j - 1] : '';
  499. /*
  500. * Find end of string:
  501. * - string started with double quotes ends in double quotes, likewise for single quotes.
  502. * - unterminated string ends at newline (bad code), unless newline is escaped (though nobody
  503. * knows this.)
  504. */
  505. if(($stringOpener == $character && $previousCharacter != '\\') || (in_array($character, array("\r", "\n")) && $previousCharacter != '\\'))
  506. {
  507. // save string
  508. $replacement = '[MINIFY-STRING-' . count($strings) . ']';
  509. $strings[$replacement] = substr($content, $i, $j - $i + 1);
  510. // replace string by stub
  511. $content = substr_replace($content, $replacement, $i, $j - $i + 1);
  512. // reset pointer to the end of this string
  513. $i += strlen($replacement);
  514. break;
  515. }
  516. }
  517. break;
  518. // comment demarcation: // or /*
  519. case '/':
  520. $commentOpener = $character . (isset($content[$i + 1]) ? $content[$i + 1] : '');
  521. /*
  522. * Both comment opening tags are 2 characters, so grab the next character and verify we're really
  523. * opening a comment here.
  524. */
  525. if(in_array($commentOpener, array('//', '/*')))
  526. {
  527. // process through content until we find the end of the comment
  528. for($j = $i + 1; $j < strlen($content); $j++)
  529. {
  530. $character = $content[$j];
  531. $previousCharacter = isset($content[$j - 1]) ? $content[$j - 1] : '';
  532. /*
  533. * Find end of comment:
  534. * - // single line comments end at newline.
  535. * - /* multiline comments and at their respective closing tag, which I can't use here or
  536. * it'd end this very comment.
  537. */
  538. if(($commentOpener == '//' && in_array($character, array("\r", "\n"))) || ($commentOpener == '/*' && $previousCharacter . $character == '*/'))
  539. {
  540. // save comment
  541. $replacement = '[MINIFY-COMMENT-' . count($comments) . ']';
  542. $comments[$replacement] = substr($content, $i, $j - $i + 1);
  543. // replace comment by stub
  544. $content = substr_replace($content, $replacement, $i, $j - $i + 1);
  545. // reset pointer to the end of this string
  546. $i += strlen($replacement);
  547. break;
  548. }
  549. }
  550. }
  551. break;
  552. }
  553. }
  554. return array($content, $strings, $comments);
  555. }
  556. /**
  557. * Minify the data.
  558. * Perform JS optimizations.
  559. *
  560. * @param string[optional] $path The path the data should be written to.
  561. * @return string The minified data.
  562. */
  563. public function minify($path = false, $stripComments = true, $stripWhitespace = true)
  564. {
  565. $content = '';
  566. // loop files
  567. foreach($this->data as $source => $js)
  568. {
  569. // combine js
  570. $content .= $js;
  571. }
  572. // extract comments & strings from content
  573. list($content, $strings, $comments) = $this->extract($content);
  574. // minify
  575. if($stripComments) $content = $this->stripComments($content, false, $comments);
  576. // strip whitespace
  577. if($stripWhitespace) $content = $this->stripWhitespace($content, false, $strings, $comments);
  578. // reset strings
  579. $content = str_replace(array_keys($strings), array_values($strings), $content);
  580. // save to path
  581. if($path !== false) $this->save($content, $path);
  582. return $content;
  583. }
  584. /**
  585. * Strip comments from source code.
  586. *
  587. * @param string $content The file/content to strip the comments for.
  588. * @param string[optional] $path The path the data should be written to.
  589. * @return string
  590. */
  591. public function stripComments($content, $path = false)
  592. {
  593. // little "hack" for internal use
  594. $comments = @func_get_arg(2);
  595. // load the content
  596. $content = $this->load($content);
  597. // content has not been parsed before, do so now
  598. if($comments === false)
  599. {
  600. // extract strings & comments
  601. list($content, $strings, $comments) = $this->extract($content);
  602. // reset strings
  603. $content = str_replace(array_keys($strings), array_values($strings), $content);
  604. }
  605. // strip comments
  606. $content = str_replace(array_keys($comments), array_fill(0, count($comments), ''), $content);
  607. // save to path (not for internal use!)
  608. if(@func_get_arg(2) === false) $this->save($content, $path);
  609. return $content;
  610. }
  611. /**
  612. * Strip whitespace.
  613. *
  614. * @param string $content The file/content to strip the whitespace for.
  615. * @param string[optional] $path The path the data should be written to.
  616. * @return string
  617. */
  618. public function stripWhitespace($content, $path = false)
  619. {
  620. // little "hack" for internal use
  621. $strings = @func_get_arg(2);
  622. $comments = @func_get_arg(3);
  623. // load the content
  624. $content = $this->load($content);
  625. // content has not been parsed before, do so now
  626. if($strings === false || $comments === false)
  627. {
  628. // extract strings & comments
  629. list($content, $strings, $comments) = $this->extract($content);
  630. }
  631. // newlines > linefeed
  632. $content = str_replace(array("\r\n", "\r", "\n"), "\n", $content);
  633. // empty lines > collapse
  634. $content = preg_replace('/^[ \t]*|[ \t]*$/m', '', $content);
  635. $content = preg_replace('/\n+/m', "\n", $content);
  636. $content = trim($content);
  637. // redundant whitespace > remove
  638. $content = preg_replace('/(?<=[{}\[\]\(\)=><&\|;:,\?!\+-])[ \t]*|[ \t]*(?=[{}\[\]\(\)=><&\|;:,\?!\+-])/i', '', $content);
  639. $content = preg_replace('/[ \t]+/', ' ', $content);
  640. // redundant semicolons (followed by another semicolon or closing curly bracket) > remove
  641. $content = preg_replace('/;\s*(?=[;}])/s', '', $content);
  642. /*
  643. * @todo: we could remove all line feeds, but then we have to be certain that all statements are properly
  644. * terminated with a semi-colon. So we'd first have to parse the statements to see which require a semi-colon,
  645. * add it if it's not present, and then remove the line feeds. The semi-colon just before a closing curly
  646. * bracket can then also be omitted.
  647. */
  648. // reset data if this function has not been called upon through internal methods
  649. if(@func_get_arg(2) === false || @func_get_arg(3) === false)
  650. {
  651. // reset strings & comments
  652. $content = str_replace(array_keys($strings), array_values($strings), $content);
  653. $content = str_replace(array_keys($comments), array_values($comments), $content);
  654. // save to path
  655. if($path !== false) $this->save($content, $path);
  656. }
  657. return $content;
  658. }
  659. }
  660. /**
  661. * Minify abstract class
  662. *
  663. * This source file can be used to write minifiers for multiple file types.
  664. *
  665. * The class is documented in the file itself. If you find any bugs help me out and report them. Reporting can be done by sending an email to minify@mullie.eu.
  666. * If you report a bug, make sure you give me enough information (include your code).
  667. *
  668. * License
  669. * Copyright (c) 2012, Matthias Mullie. All rights reserved.
  670. *
  671. * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
  672. *
  673. * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  674. * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  675. * 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
  676. *
  677. * This software is provided by the author "as is" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the author be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
  678. *
  679. * @author Matthias Mullie <minify@mullie.eu>
  680. * @version 1.0.0
  681. *
  682. * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
  683. * @license BSD License
  684. */
  685. abstract class Minify
  686. {
  687. /**
  688. * The data to be minified
  689. *
  690. * @var array
  691. */
  692. protected $data = array();
  693. /**
  694. * Init the minify class - optionally, css may be passed along already.
  695. *
  696. * @param string[optional] $css
  697. */
  698. public function __construct()
  699. {
  700. // it's possible to add the css through the constructor as well ;)
  701. $arguments = func_get_args();
  702. if(func_num_args()) call_user_func_array(array($this, 'add'), $arguments);
  703. }
  704. /**
  705. * Add a file or straight-up code to be minified.
  706. *
  707. * @param string $data
  708. */
  709. public function add($data)
  710. {
  711. // this method can be overloaded
  712. foreach(func_get_args() as $data)
  713. {
  714. // redefine var
  715. $data = (string) $data;
  716. // load data
  717. $value = $this->load($data);
  718. $key = ($data != $value) ? $data : 0;
  719. // initialize key
  720. if(!array_key_exists($key, $this->data)) $this->data[$key] = '';
  721. // store data
  722. $this->data[$key] .= $value;
  723. }
  724. }
  725. /**
  726. * Load data.
  727. *
  728. * @param string $data Either a path to a file or the content itself.
  729. * @return string
  730. */
  731. protected function load($data)
  732. {
  733. // check if the data is a file
  734. if(@file_exists($data) && is_file($data))
  735. {
  736. // grab content
  737. return @file_get_contents($data);
  738. }
  739. // no file, just return the data itself
  740. else return $data;
  741. }
  742. /**
  743. * Minify the data.
  744. *
  745. * @param string[optional] $path The path the data should be written to.
  746. * @return string The minified data.
  747. */
  748. abstract public function minify($path = null);
  749. /**
  750. * Save to file
  751. *
  752. * @param string $content The minified data.
  753. * @param string $path The path to save the minified data to.
  754. */
  755. public function save($content, $path)
  756. {
  757. // create file & open for writing
  758. if(($handler = @fopen($path, 'w')) === false) throw new MinifyException('The file "' . $path . '" could not be opened. Check if PHP has enough permissions.');
  759. // write to file
  760. if(@fwrite($handler, $content) === false) throw new MinifyException('The file "' . $path . '" could not be written to. Check if PHP has enough permissions.');
  761. // close the file
  762. @fclose($handler);
  763. }
  764. }
  765. /**
  766. * Minify Exception class
  767. *
  768. * @author Matthias Mullie <minify@mullie.eu>
  769. */
  770. class MinifyException extends Exception
  771. {
  772. }