Apptrian_Minify_HTML_CSS_JS - Version 2.0.0

Version Notes

* Complete rewrite from the ground up
+ Support for Knockout JavaScript library comments
+ Support for script tags of type="text/x-magento-template"
+ Logging of errors with url where errors occur
+ Compliance with Magento's Extension Quality Program (EQP)

Download this release

Release Info

Developer Apptrian
Extension Apptrian_Minify_HTML_CSS_JS
Version 2.0.0
Comparing to
See all releases


Code changes from version 1.5.1 to 2.0.0

Files changed (58) hide show
  1. app/code/community/Apptrian/Minify/Block/Adminhtml/About.php +5 -5
  2. app/code/community/Apptrian/Minify/Block/Adminhtml/Button/Minify.php +1 -3
  3. app/code/community/Apptrian/Minify/Block/Adminhtml/Info.php +26 -26
  4. app/code/community/Apptrian/Minify/Helper/Data.php +63 -50
  5. app/code/community/Apptrian/Minify/Model/Observer.php +116 -91
  6. app/code/community/Apptrian/Minify/controllers/Adminhtml/Apptrian/MinifyController.php +1 -8
  7. app/code/community/Apptrian/Minify/etc/config.xml +2 -2
  8. app/code/community/Apptrian/Minify/etc/system.xml +1 -1
  9. app/etc/modules/Apptrian_Minify.xml +1 -1
  10. lib/Apptrian/Minify/Css.php +1018 -0
  11. lib/Apptrian/Minify/Html.php +396 -0
  12. lib/Apptrian/Minify/Js.php +617 -0
  13. lib/CSSmin.php +0 -777
  14. lib/DooDigestAuth.php +0 -121
  15. lib/FirePHP.php +0 -1370
  16. lib/HTTP/ConditionalGet.php +0 -366
  17. lib/HTTP/Encoder.php +0 -335
  18. lib/JSMin.php +0 -449
  19. lib/JSMinMax.php +0 -460
  20. lib/JSMinPlus.php +0 -2086
  21. lib/JavaScriptPacker.php +0 -741
  22. lib/Minify.php +0 -608
  23. lib/Minify/Build.php +0 -101
  24. lib/Minify/CSS.php +0 -99
  25. lib/Minify/CSS/Compressor.php +0 -249
  26. lib/Minify/CSS/UriRewriter.php +0 -307
  27. lib/Minify/CSSmin.php +0 -85
  28. lib/Minify/Cache/APC.php +0 -133
  29. lib/Minify/Cache/File.php +0 -197
  30. lib/Minify/Cache/Memcache.php +0 -140
  31. lib/Minify/Cache/WinCache.php +0 -130
  32. lib/Minify/Cache/XCache.php +0 -126
  33. lib/Minify/Cache/ZendPlatform.php +0 -142
  34. lib/Minify/ClosureCompiler.php +0 -139
  35. lib/Minify/CommentPreserver.php +0 -89
  36. lib/Minify/Controller/Base.php +0 -222
  37. lib/Minify/Controller/Files.php +0 -76
  38. lib/Minify/Controller/Groups.php +0 -91
  39. lib/Minify/Controller/MinApp.php +0 -237
  40. lib/Minify/Controller/Page.php +0 -68
  41. lib/Minify/Controller/Version1.php +0 -119
  42. lib/Minify/DebugDetector.php +0 -26
  43. lib/Minify/HTML.php +0 -264
  44. lib/Minify/HTML/Helper.php +0 -225
  45. lib/Minify/HTMLComp.php +0 -274
  46. lib/Minify/HTMLMax.php +0 -277
  47. lib/Minify/HTMLMaxComp.php +0 -277
  48. lib/Minify/ImportProcessor.php +0 -216
  49. lib/Minify/JS/ClosureCompiler.php +0 -230
  50. lib/Minify/Lines.php +0 -143
  51. lib/Minify/Loader.php +0 -28
  52. lib/Minify/Logger.php +0 -47
  53. lib/Minify/Packer.php +0 -39
  54. lib/Minify/Source.php +0 -187
  55. lib/Minify/YUI/CssCompressor.java +0 -382
  56. lib/Minify/YUI/CssCompressor.php +0 -171
  57. lib/Minify/YUICompressor.php +0 -156
  58. package.xml +10 -7
app/code/community/Apptrian/Minify/Block/Adminhtml/About.php CHANGED
@@ -3,7 +3,7 @@
3
  * @category Apptrian
4
  * @package Apptrian_Minify
5
  * @author Apptrian
6
- * @copyright Copyright (c) 2016 Apptrian (http://www.apptrian.com)
7
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
  */
9
  class Apptrian_Minify_Block_Adminhtml_About
@@ -21,7 +21,7 @@ class Apptrian_Minify_Block_Adminhtml_About
21
  {
22
  $element = null;
23
  $version = Mage::helper('apptrian_minify')->getExtensionVersion();
24
- $logopath = 'http://www.apptrian.com/media/apptrian.gif';
25
  $html = <<<HTML
26
  <div style="background:url('$logopath') no-repeat scroll 15px 15px #e7efef;
27
  border:1px solid #ccc; min-height:100px; margin:5px 0;
@@ -33,12 +33,12 @@ default Magento CSS/JS merger.
33
  </p>
34
  <p>
35
  Website:
36
- <a href="http://www.apptrian.com" target="_blank">www.apptrian.com</a><br />
37
  Like, share and follow us on
38
  <a href="https://www.facebook.com/apptrian" target="_blank">Facebook</a>,
39
  <a href="https://plus.google.com/+ApptrianCom" target="_blank">Google+</a>,
40
- <a href="http://www.pinterest.com/apptrian" target="_blank">Pinterest</a>, and
41
- <a href="http://twitter.com/apptrian" target="_blank">Twitter</a>.<br />
42
  If you have any questions send email at
43
  <a href="mailto:service@apptrian.com">service@apptrian.com</a>.
44
  </p>
3
  * @category Apptrian
4
  * @package Apptrian_Minify
5
  * @author Apptrian
6
+ * @copyright Copyright (c) 2017 Apptrian (http://www.apptrian.com)
7
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
  */
9
  class Apptrian_Minify_Block_Adminhtml_About
21
  {
22
  $element = null;
23
  $version = Mage::helper('apptrian_minify')->getExtensionVersion();
24
+ $logopath = 'https://www.apptrian.com/media/apptrian.gif';
25
  $html = <<<HTML
26
  <div style="background:url('$logopath') no-repeat scroll 15px 15px #e7efef;
27
  border:1px solid #ccc; min-height:100px; margin:5px 0;
33
  </p>
34
  <p>
35
  Website:
36
+ <a href="https://www.apptrian.com" target="_blank">www.apptrian.com</a><br />
37
  Like, share and follow us on
38
  <a href="https://www.facebook.com/apptrian" target="_blank">Facebook</a>,
39
  <a href="https://plus.google.com/+ApptrianCom" target="_blank">Google+</a>,
40
+ <a href="https://www.pinterest.com/apptrian" target="_blank">Pinterest</a>, and
41
+ <a href="https://twitter.com/apptrian" target="_blank">Twitter</a>.<br />
42
  If you have any questions send email at
43
  <a href="mailto:service@apptrian.com">service@apptrian.com</a>.
44
  </p>
app/code/community/Apptrian/Minify/Block/Adminhtml/Button/Minify.php CHANGED
@@ -3,7 +3,7 @@
3
  * @category Apptrian
4
  * @package Apptrian_Minify
5
  * @author Apptrian
6
- * @copyright Copyright (c) 2016 Apptrian (http://www.apptrian.com)
7
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
  */
9
  class Apptrian_Minify_Block_Adminhtml_Button_Minify
@@ -19,7 +19,6 @@ class Apptrian_Minify_Block_Adminhtml_Button_Minify
19
  Varien_Data_Form_Element_Abstract $element
20
  )
21
  {
22
-
23
  $elementOriginalData = $element->getOriginalData();
24
 
25
  if (isset($elementOriginalData['label'])) {
@@ -40,6 +39,5 @@ class Apptrian_Minify_Block_Adminhtml_Button_Minify
40
  ->toHtml();
41
 
42
  return $html;
43
-
44
  }
45
  }
3
  * @category Apptrian
4
  * @package Apptrian_Minify
5
  * @author Apptrian
6
+ * @copyright Copyright (c) 2017 Apptrian (http://www.apptrian.com)
7
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
  */
9
  class Apptrian_Minify_Block_Adminhtml_Button_Minify
19
  Varien_Data_Form_Element_Abstract $element
20
  )
21
  {
 
22
  $elementOriginalData = $element->getOriginalData();
23
 
24
  if (isset($elementOriginalData['label'])) {
39
  ->toHtml();
40
 
41
  return $html;
 
42
  }
43
  }
app/code/community/Apptrian/Minify/Block/Adminhtml/Info.php CHANGED
@@ -3,7 +3,7 @@
3
  * @category Apptrian
4
  * @package Apptrian_Minify
5
  * @author Apptrian
6
- * @copyright Copyright (c) 2016 Apptrian (http://www.apptrian.com)
7
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
  */
9
  class Apptrian_Minify_Block_Adminhtml_Info
@@ -20,78 +20,78 @@ class Apptrian_Minify_Block_Adminhtml_Info
20
  public function render(Varien_Data_Form_Element_Abstract $element)
21
  {
22
  $element = null;
23
- $logopath = 'http://www.apptrian.com/media/apptrian.gif';
24
  $html = <<<HTML
25
  <div style="background:url('$logopath') no-repeat scroll 15px 15px #e7efef;
26
  border:1px solid #ccc; min-height:100px; margin:5px 0;
27
  padding:15px 15px 15px 140px;">
28
  <p>
29
  <strong>Magento Online Stores &amp; Extensions</strong><br />
30
- <a href="http://www.apptrian.com" target="_blank">Apptrian</a> offers a wide
31
  choice of products and services for your online business.
32
  </p>
33
  <p>
34
- Website: <a href="http://www.apptrian.com" target="_blank">www.apptrian.com</a>
35
  <br />
36
  Like, share and follow us on
37
  <a href="https://www.facebook.com/apptrian" target="_blank">Facebook</a>,
38
  <a href="https://plus.google.com/+ApptrianCom" target="_blank">Google+</a>,
39
- <a href="http://www.pinterest.com/apptrian" target="_blank">Pinterest</a>, and
40
- <a href="http://twitter.com/apptrian" target="_blank">Twitter</a>.<br />
41
  If you have any questions send email at
42
  <a href="mailto:service@apptrian.com">service@apptrian.com</a>.
43
  </p>
44
  </div>
45
  <div>
46
  <p><strong>Products and services you might be interested in:</strong></p>
47
- <a href="http://www.apptrian.com/facebook-pixel-for-magento"
48
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
49
- <img src="http://www.apptrian.com/media/facebook-pixel.jpg"
50
  alt="Facebook Pixel" style="border:1px solid #ccc;" />
51
  </a>
52
- <a href="http://www.apptrian.com/image-optimizer-for-magento"
53
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
54
- <img src="http://www.apptrian.com/media/image-optimizer.jpg"
55
  alt="Image Optimizer" style="border:1px solid #ccc;" />
56
  </a>
57
- <a href="http://www.apptrian.com/minify-html-css-js-for-magento"
58
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
59
- <img src="http://www.apptrian.com/media/minify-html-css-js.jpg"
60
  alt="Minify HTML CSS JS" style="border:1px solid #ccc;" />
61
  </a>
62
- <a href="http://www.apptrian.com/professional-magento-installation"
63
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
64
- <img src="http://www.apptrian.com/media/professional-magento-installation.jpg"
65
  alt="Professional Magento Installation" style="border:1px solid #ccc;" />
66
  </a>
67
- <a href="http://www.apptrian.com/quick-search-for-magento"
68
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
69
- <img src="http://www.apptrian.com/media/quick-search.jpg"
70
  alt="Quick Search" style="border:1px solid #ccc;" />
71
  </a>
72
- <a href="http://www.apptrian.com/responsive-product-slider-for-magento"
73
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
74
- <img src="http://www.apptrian.com/media/responsive-product-slider.jpg"
75
  alt="Responsive Product Slider" style="border:1px solid #ccc;" />
76
  </a>
77
- <a href="http://www.apptrian.com/schema-org-microdata-for-magento"
78
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
79
- <img src="http://www.apptrian.com/media/schema-org-microdata-for-magento.jpg"
80
  alt="Schema.org Microdata for Magento" style="border:1px solid #ccc;" />
81
  </a>
82
- <a href="http://www.apptrian.com/snippets-generator-for-magento"
83
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
84
- <img src="http://www.apptrian.com/media/snippets-generator.jpg"
85
  alt="Snippets Generator" style="border:1px solid #ccc;" />
86
  </a>
87
- <a href="http://www.apptrian.com/social-integrator-for-magento"
88
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
89
- <img src="http://www.apptrian.com/media/social-integrator.jpg"
90
  alt="Social Integrator" style="border:1px solid #ccc;" />
91
  </a>
92
- <a href="http://www.apptrian.com/subcategories-grid-list-for-magento"
93
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
94
- <img src="http://www.apptrian.com/media/subcategories-grid-list.jpg"
95
  alt="Subcategories Grid/List" style="border:1px solid #ccc;" />
96
  </a>
97
  </div>
3
  * @category Apptrian
4
  * @package Apptrian_Minify
5
  * @author Apptrian
6
+ * @copyright Copyright (c) 2017 Apptrian (http://www.apptrian.com)
7
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
  */
9
  class Apptrian_Minify_Block_Adminhtml_Info
20
  public function render(Varien_Data_Form_Element_Abstract $element)
21
  {
22
  $element = null;
23
+ $logopath = 'https://www.apptrian.com/media/apptrian.gif';
24
  $html = <<<HTML
25
  <div style="background:url('$logopath') no-repeat scroll 15px 15px #e7efef;
26
  border:1px solid #ccc; min-height:100px; margin:5px 0;
27
  padding:15px 15px 15px 140px;">
28
  <p>
29
  <strong>Magento Online Stores &amp; Extensions</strong><br />
30
+ <a href="https://www.apptrian.com" target="_blank">Apptrian</a> offers a wide
31
  choice of products and services for your online business.
32
  </p>
33
  <p>
34
+ Website: <a href="https://www.apptrian.com" target="_blank">www.apptrian.com</a>
35
  <br />
36
  Like, share and follow us on
37
  <a href="https://www.facebook.com/apptrian" target="_blank">Facebook</a>,
38
  <a href="https://plus.google.com/+ApptrianCom" target="_blank">Google+</a>,
39
+ <a href="https://www.pinterest.com/apptrian" target="_blank">Pinterest</a>, and
40
+ <a href="https://twitter.com/apptrian" target="_blank">Twitter</a>.<br />
41
  If you have any questions send email at
42
  <a href="mailto:service@apptrian.com">service@apptrian.com</a>.
43
  </p>
44
  </div>
45
  <div>
46
  <p><strong>Products and services you might be interested in:</strong></p>
47
+ <a href="https://www.apptrian.com/facebook-pixel-for-magento"
48
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
49
+ <img src="https://www.apptrian.com/media/facebook-pixel.jpg"
50
  alt="Facebook Pixel" style="border:1px solid #ccc;" />
51
  </a>
52
+ <a href="https://www.apptrian.com/image-optimizer-for-magento"
53
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
54
+ <img src="https://www.apptrian.com/media/image-optimizer.jpg"
55
  alt="Image Optimizer" style="border:1px solid #ccc;" />
56
  </a>
57
+ <a href="https://www.apptrian.com/minify-html-css-js-for-magento"
58
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
59
+ <img src="https://www.apptrian.com/media/minify-html-css-js.jpg"
60
  alt="Minify HTML CSS JS" style="border:1px solid #ccc;" />
61
  </a>
62
+ <a href="https://www.apptrian.com/professional-magento-installation"
63
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
64
+ <img src="https://www.apptrian.com/media/professional-magento-installation.jpg"
65
  alt="Professional Magento Installation" style="border:1px solid #ccc;" />
66
  </a>
67
+ <a href="https://www.apptrian.com/quick-search-for-magento"
68
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
69
+ <img src="https://www.apptrian.com/media/quick-search.jpg"
70
  alt="Quick Search" style="border:1px solid #ccc;" />
71
  </a>
72
+ <a href="https://www.apptrian.com/responsive-product-slider-for-magento"
73
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
74
+ <img src="https://www.apptrian.com/media/responsive-product-slider.jpg"
75
  alt="Responsive Product Slider" style="border:1px solid #ccc;" />
76
  </a>
77
+ <a href="https://www.apptrian.com/schema-org-microdata-for-magento"
78
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
79
+ <img src="https://www.apptrian.com/media/schema-org-microdata-for-magento.jpg"
80
  alt="Schema.org Microdata for Magento" style="border:1px solid #ccc;" />
81
  </a>
82
+ <a href="https://www.apptrian.com/snippets-generator-for-magento"
83
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
84
+ <img src="https://www.apptrian.com/media/snippets-generator.jpg"
85
  alt="Snippets Generator" style="border:1px solid #ccc;" />
86
  </a>
87
+ <a href="https://www.apptrian.com/social-integrator-for-magento"
88
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
89
+ <img src="https://www.apptrian.com/media/social-integrator.jpg"
90
  alt="Social Integrator" style="border:1px solid #ccc;" />
91
  </a>
92
+ <a href="https://www.apptrian.com/subcategories-grid-list-for-magento"
93
  target="_blank" style="margin: 0 15px 15px 0; display: inline-block;">
94
+ <img src="https://www.apptrian.com/media/subcategories-grid-list.jpg"
95
  alt="Subcategories Grid/List" style="border:1px solid #ccc;" />
96
  </a>
97
  </div>
app/code/community/Apptrian/Minify/Helper/Data.php CHANGED
@@ -3,14 +3,14 @@
3
  * @category Apptrian
4
  * @package Apptrian_Minify
5
  * @author Apptrian
6
- * @copyright Copyright (c) 2016 Apptrian (http://www.apptrian.com)
7
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
  */
9
  class Apptrian_Minify_Helper_Data extends Mage_Core_Helper_Abstract
10
  {
11
  /**
12
  * Array of paths that will be scaned for css and js files.
13
- *
14
  * @var array
15
  */
16
  protected $_paths = null;
@@ -28,13 +28,12 @@ class Apptrian_Minify_Helper_Data extends Mage_Core_Helper_Abstract
28
 
29
  /**
30
  * Returns array of paths that will be scaned for css and js files.
31
- *
32
  * @return array
33
  */
34
  public function getPaths()
35
  {
36
  if ($this->_paths === null) {
37
-
38
  $list = array();
39
  $baseDirMedia = Mage::getBaseDir('media');
40
  $css = $baseDirMedia . DS . 'css';
@@ -54,7 +53,6 @@ class Apptrian_Minify_Helper_Data extends Mage_Core_Helper_Abstract
54
  }
55
 
56
  $this->_paths = $list;
57
-
58
  }
59
 
60
  return $this->_paths;
@@ -62,42 +60,41 @@ class Apptrian_Minify_Helper_Data extends Mage_Core_Helper_Abstract
62
 
63
  /**
64
  * Minifies CSS and JS files.
65
- *
66
  */
67
  public function process()
68
  {
69
  // Get remove important comments option
70
- $removeComments = (int) Mage::getConfig()->getNode(
71
- 'apptrian_minify/minify_css_js/remove_comments', 'default'
 
72
  );
73
 
74
  foreach ($this->getPaths() as $path) {
75
-
76
  $iterator = new RecursiveIteratorIterator(
77
  new RecursiveDirectoryIterator(
78
- $path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS
 
79
  )
80
  );
81
 
82
  foreach ($iterator as $filename => $file) {
83
-
84
- if ($file->isFile()
85
  && preg_match('/^.+\.(css|js)$/i', $file->getFilename())
86
  ) {
87
-
88
  $filePath = $file->getRealPath();
89
  if (!is_writable($filePath)) {
90
  Mage::log(
91
- 'Minification failed for '
92
  . $filePath . ' File is not writable.'
93
  );
94
  continue;
95
  }
96
 
97
- //This is available from php v5.3.6
98
  //$ext = $file->getExtension();
99
  // Using this for compatibility
100
- $ext = strtolower(
101
  pathinfo($filePath, PATHINFO_EXTENSION)
102
  );
103
  $optimized = '';
@@ -111,58 +108,74 @@ class Apptrian_Minify_Helper_Data extends Mage_Core_Helper_Abstract
111
 
112
  // CSS files
113
  if ($ext == 'css') {
114
-
115
- if ($removeComments == 1) {
116
-
117
- $optimized = Minify_CSS::minify(
118
- $unoptimized, array('preserveComments' => false)
119
- );
120
-
121
- } else {
122
-
123
- $optimized = Minify_CSS::minify($unoptimized);
124
-
125
- }
126
-
127
  // JS files
128
  } else {
129
-
130
-
131
- if ($removeComments == 1) {
132
-
133
- $optimized = JSMinMax::minify($unoptimized);
134
-
135
- } else {
136
-
137
- $optimized = JSMin::minify($unoptimized);
138
-
139
- }
140
-
141
-
142
  }
143
 
144
-
145
  // If optimization failed
146
  if (!$optimized) {
147
  Mage::log('File ' . $filePath . ' was not minified.');
148
  continue;
149
  }
150
 
151
-
152
  if (file_put_contents(
153
- $filePath, $optimized, LOCK_EX
 
 
154
  ) === false) {
155
-
156
  Mage::log('Minification failed for ' . $filePath);
157
-
158
  }
159
-
160
  }
161
-
162
  }
163
-
164
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
 
166
  }
167
 
 
 
 
 
 
 
 
 
 
168
  }
3
  * @category Apptrian
4
  * @package Apptrian_Minify
5
  * @author Apptrian
6
+ * @copyright Copyright (c) 2017 Apptrian (http://www.apptrian.com)
7
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
  */
9
  class Apptrian_Minify_Helper_Data extends Mage_Core_Helper_Abstract
10
  {
11
  /**
12
  * Array of paths that will be scaned for css and js files.
13
+ *
14
  * @var array
15
  */
16
  protected $_paths = null;
28
 
29
  /**
30
  * Returns array of paths that will be scaned for css and js files.
31
+ *
32
  * @return array
33
  */
34
  public function getPaths()
35
  {
36
  if ($this->_paths === null) {
 
37
  $list = array();
38
  $baseDirMedia = Mage::getBaseDir('media');
39
  $css = $baseDirMedia . DS . 'css';
53
  }
54
 
55
  $this->_paths = $list;
 
56
  }
57
 
58
  return $this->_paths;
60
 
61
  /**
62
  * Minifies CSS and JS files.
63
+ *
64
  */
65
  public function process()
66
  {
67
  // Get remove important comments option
68
+ $removeComments = (bool) Mage::getConfig()->getNode(
69
+ 'apptrian_minify/minify_css_js/remove_comments',
70
+ 'default'
71
  );
72
 
73
  foreach ($this->getPaths() as $path) {
 
74
  $iterator = new RecursiveIteratorIterator(
75
  new RecursiveDirectoryIterator(
76
+ $path,
77
+ RecursiveDirectoryIterator::FOLLOW_SYMLINKS
78
  )
79
  );
80
 
81
  foreach ($iterator as $filename => $file) {
82
+ if ($file->isFile()
 
83
  && preg_match('/^.+\.(css|js)$/i', $file->getFilename())
84
  ) {
 
85
  $filePath = $file->getRealPath();
86
  if (!is_writable($filePath)) {
87
  Mage::log(
88
+ 'Minification failed for '
89
  . $filePath . ' File is not writable.'
90
  );
91
  continue;
92
  }
93
 
94
+ //This is available from php v5.3.6
95
  //$ext = $file->getExtension();
96
  // Using this for compatibility
97
+ $ext = strtolower(
98
  pathinfo($filePath, PATHINFO_EXTENSION)
99
  );
100
  $optimized = '';
108
 
109
  // CSS files
110
  if ($ext == 'css') {
111
+ $optimized = $this->minifyCss(
112
+ $unoptimized,
113
+ $removeComments
114
+ );
 
 
 
 
 
 
 
 
 
115
  // JS files
116
  } else {
117
+ $optimized = $this->minifyJs(
118
+ $unoptimized,
119
+ $removeComments
120
+ );
 
 
 
 
 
 
 
 
 
121
  }
122
 
 
123
  // If optimization failed
124
  if (!$optimized) {
125
  Mage::log('File ' . $filePath . ' was not minified.');
126
  continue;
127
  }
128
 
 
129
  if (file_put_contents(
130
+ $filePath,
131
+ $optimized,
132
+ LOCK_EX
133
  ) === false) {
 
134
  Mage::log('Minification failed for ' . $filePath);
 
135
  }
 
136
  }
 
137
  }
 
138
  }
139
+ }
140
+
141
+ public function minifyHtml(
142
+ $html,
143
+ $removeComments = true,
144
+ $cacheCompatibility = false,
145
+ $maxMinification = false
146
+ ) {
147
+ $options = array(
148
+ 'removeComments' => $removeComments,
149
+ 'cacheCompatibility' => $cacheCompatibility,
150
+ 'maxMinification' => $maxMinification
151
+ );
152
+
153
+ try {
154
+ return Apptrian_Minify_Html::minify($html, $options);
155
+ } catch (Exception $e) {
156
+ $url = Mage::helper('core/url')->getCurrentUrl();;
157
+ Mage::log('You have HTML/CSS/JS error on your web page.');
158
+ Mage::log('Page URL: ' . $url);
159
+ Mage::log('Exception Message: ' . $e->getMessage());
160
+ Mage::log('Exception Trace: ' . $e->getTraceAsString());
161
+ return $html;
162
+ }
163
+ }
164
+
165
+ public function minifyCss($css, $removeComments = true)
166
+ {
167
+ $minifier = new Apptrian_Minify_Css(true, $removeComments);
168
 
169
+ return $minifier->run($css);
170
  }
171
 
172
+ public function minifyJs($js, $removeComments = true)
173
+ {
174
+ $flaggedComments = !$removeComments;
175
+
176
+ return Apptrian_Minify_Js::minify(
177
+ $js,
178
+ array('flaggedComments' => $flaggedComments)
179
+ );
180
+ }
181
  }
app/code/community/Apptrian/Minify/Model/Observer.php CHANGED
@@ -3,175 +3,200 @@
3
  * @category Apptrian
4
  * @package Apptrian_Minify
5
  * @author Apptrian
6
- * @copyright Copyright (c) 2016 Apptrian (http://www.apptrian.com)
7
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
  */
9
  class Apptrian_Minify_Model_Observer
10
  {
 
 
 
 
 
 
11
 
12
  /**
13
- * Flag used to determine if block HTML minification is set in config.
14
- *
15
  * @var null|bool
16
  */
17
- protected $_blockMinifyFlag = null;
18
 
19
  /**
20
- * Flag used to determine if maximum HTML minification is set in config.
21
- *
22
  * @var null|bool
23
  */
24
- protected $_maxMinifyFlag = null;
25
 
26
  /**
27
- * Minify options array.
28
  *
29
- * @var array
30
  */
31
- protected $_minifyOptions = array(
32
- 'cssMinifier' => array('Minify_CSS', 'minify'),
33
- 'jsMinifier' => array('JSMin', 'minify')
34
- );
35
 
36
  /**
37
- * Method returns status of block minification.
38
- *
 
 
 
 
 
 
 
39
  * @return bool
40
  */
41
- public function getBlockMinifyStatus()
42
  {
43
-
44
- if ($this->_blockMinifyFlag === null) {
45
-
46
- if (Mage::getStoreConfigFlag('apptrian_minify/minify_html/enabled')
47
- && Mage::getStoreConfigFlag(
48
- 'apptrian_minify/minify_html/compatibility'
49
- )
50
- ) {
51
-
52
- $this->_blockMinifyFlag = true;
53
-
54
- } else {
55
-
56
- $this->_blockMinifyFlag = false;
57
-
58
- }
59
-
60
  }
61
-
62
- return $this->_blockMinifyFlag;
63
 
 
64
  }
65
 
66
  /**
67
- * Method returns status of maximum HTML minification.
68
- *
69
  * @return bool
70
  */
71
- public function getMaxMinifyStatus()
72
  {
73
-
74
- if ($this->_maxMinifyFlag === null) {
75
-
76
- $this->_maxMinifyFlag = Mage::getStoreConfigFlag(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  'apptrian_minify/minify_html/max_minification'
78
  );
79
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }
81
 
82
- return $this->_maxMinifyFlag;
83
-
84
  }
85
 
86
  /**
87
  * This method is minifying HTML of every block.
88
  * Multiple calls per page but they are cached.
89
- *
90
  * @param Varien_Event_Observer $observer
91
  */
92
  public function minifyBlockHtml(Varien_Event_Observer $observer)
93
  {
94
-
95
  if ($this->getBlockMinifyStatus()) {
96
-
97
  $block = $observer->getBlock();
98
  $transport = $observer->getTransport();
99
  $html = $transport->getHtml();
100
 
101
- if ($this->getMaxMinifyStatus()) {
102
- $transport->setHtml(
103
- Minify_HTMLMaxComp::minify($html, $this->_minifyOptions)
104
- );
105
- } else {
106
- $transport->setHtml(
107
- Minify_HTMLComp::minify($html, $this->_minifyOptions)
108
- );
109
- }
110
 
 
 
 
 
 
 
 
 
111
  }
112
-
113
  }
114
 
115
  /**
116
  * This method is minifying HTML of entire page.
117
  * One call per entire page.
118
- *
119
  * @param Varien_Event_Observer $observer
120
  */
121
  public function minifyPageHtml(Varien_Event_Observer $observer)
122
  {
 
 
 
 
123
 
124
- if (Mage::getStoreConfigFlag('apptrian_minify/minify_html/enabled')
125
- && !Mage::getStoreConfigFlag(
126
- 'apptrian_minify/minify_html/compatibility'
127
- )
128
- ) {
129
-
130
  $response = $observer->getEvent()->getControllerAction()
131
  ->getResponse();
132
  $html = $response->getBody();
133
 
134
  if (stripos($html, '<!DOCTYPE html') !== false) {
135
-
136
  $type = false;
137
 
138
  foreach ($response->getHeaders() as $header) {
139
-
140
  if (stripos($header['name'], 'Content-Type') !== false) {
141
-
142
  if (stripos($header['value'], 'text/html') !== false) {
143
-
144
  $type = true;
145
-
146
  break;
147
-
148
  }
149
-
150
  }
151
-
152
  }
153
 
154
  if ($type) {
155
-
156
- if (Mage::getStoreConfigFlag(
157
- 'apptrian_minify/minify_html/max_minification'
158
- )
159
- ) {
160
- $response->setBody(
161
- Minify_HTMLMax::minify($html, $this->_minifyOptions)
162
- );
163
- } else {
164
- $response->setBody(
165
- Minify_HTML::minify($html, $this->_minifyOptions)
166
- );
167
- }
168
-
169
  }
170
-
171
  }
172
-
173
  }
174
-
175
  }
176
-
177
  }
3
  * @category Apptrian
4
  * @package Apptrian_Minify
5
  * @author Apptrian
6
+ * @copyright Copyright (c) 2017 Apptrian (http://www.apptrian.com)
7
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
  */
9
  class Apptrian_Minify_Model_Observer
10
  {
11
+ /**
12
+ * Flag used to determine if Minify extension is enabled in config.
13
+ *
14
+ * @var null|bool
15
+ */
16
+ protected $_minifyEnabled = null;
17
 
18
  /**
19
+ * Flag used to determine if Cache Compatibility option is set in config.
20
+ *
21
  * @var null|bool
22
  */
23
+ protected $_cacheCompatibility = null;
24
 
25
  /**
26
+ * Flag used to determine if Maximum HTML Minification is set in config.
27
+ *
28
  * @var null|bool
29
  */
30
+ protected $_maxMinification = null;
31
 
32
  /**
33
+ * Flag used to determine if Remove Important Comments is set in config.
34
  *
35
+ * @var null|bool
36
  */
37
+ protected $_removeComments = null;
 
 
 
38
 
39
  /**
40
+ * Flag used to determine if block level HTML minification is set in config.
41
+ *
42
+ * @var null|bool
43
+ */
44
+ protected $_blockMinify = null;
45
+
46
+ /**
47
+ * Method returns status of minify extension. Is it enabled or not?
48
+ *
49
  * @return bool
50
  */
51
+ public function getMinifyEnabledStatus()
52
  {
53
+ if ($this->_minifyEnabled === null) {
54
+ $this->_minifyEnabled = Mage::getStoreConfigFlag(
55
+ 'apptrian_minify/minify_html/enabled'
56
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  }
 
 
58
 
59
+ return $this->_minifyEnabled;
60
  }
61
 
62
  /**
63
+ * Method returns status of cache comatibility option.
64
+ *
65
  * @return bool
66
  */
67
+ public function getCacheCompatibilityStatus()
68
  {
69
+ if ($this->_cacheCompatibility === null) {
70
+ $this->_cacheCompatibility = Mage::getStoreConfigFlag(
71
+ 'apptrian_minify/minify_html/compatibility'
72
+ );
73
+ }
74
+
75
+ return $this->_cacheCompatibility;
76
+ }
77
+
78
+ /**
79
+ * Method returns status of maximum HTML minification option.
80
+ *
81
+ * @return bool
82
+ */
83
+ public function getMaxMinificationStatus()
84
+ {
85
+ if ($this->_maxMinification === null) {
86
+ $this->_maxMinification = Mage::getStoreConfigFlag(
87
  'apptrian_minify/minify_html/max_minification'
88
  );
89
+ }
90
+
91
+ return $this->_maxMinification;
92
+ }
93
+
94
+ /**
95
+ * Method returns status of Remove Important Comments option.
96
+ *
97
+ * @return bool
98
+ */
99
+ public function getRemoveCommentsStatus()
100
+ {
101
+ if ($this->_removeComments === null) {
102
+ $this->_removeComments = Mage::getStoreConfigFlag(
103
+ 'apptrian_minify/minify_css_js/remove_comments'
104
+ );
105
+ }
106
+
107
+ return $this->_removeComments;
108
+ }
109
+
110
+ /**
111
+ * Method returns status of block minification.
112
+ *
113
+ * @return bool
114
+ */
115
+ public function getBlockMinifyStatus()
116
+ {
117
+ if ($this->_blockMinify === null) {
118
+ if ($this->getMinifyEnabledStatus()
119
+ && $this->getCacheCompatibilityStatus()
120
+ ) {
121
+ $this->_blockMinify = true;
122
+ } else {
123
+ $this->_blockMinify = false;
124
+ }
125
  }
126
 
127
+ return $this->_blockMinify;
 
128
  }
129
 
130
  /**
131
  * This method is minifying HTML of every block.
132
  * Multiple calls per page but they are cached.
133
+ *
134
  * @param Varien_Event_Observer $observer
135
  */
136
  public function minifyBlockHtml(Varien_Event_Observer $observer)
137
  {
 
138
  if ($this->getBlockMinifyStatus()) {
 
139
  $block = $observer->getBlock();
140
  $transport = $observer->getTransport();
141
  $html = $transport->getHtml();
142
 
143
+ $removeComments = $this->getRemoveCommentsStatus();
144
+ $maxMinification = $this->getMaxMinificationStatus();
 
 
 
 
 
 
 
145
 
146
+ $transport->setHtml(
147
+ Mage::helper('apptrian_minify')->minifyHtml(
148
+ $html,
149
+ $removeComments,
150
+ true,
151
+ $maxMinification
152
+ )
153
+ );
154
  }
 
155
  }
156
 
157
  /**
158
  * This method is minifying HTML of entire page.
159
  * One call per entire page.
160
+ *
161
  * @param Varien_Event_Observer $observer
162
  */
163
  public function minifyPageHtml(Varien_Event_Observer $observer)
164
  {
165
+ $minifyEnabled = $this->getMinifyEnabledStatus();
166
+ $cacheCompatibility = $this->getCacheCompatibilityStatus();
167
+ $maxMinification = $this->getMaxMinificationStatus();
168
+ $removeComments = $this->getRemoveCommentsStatus();
169
 
170
+ // !$cacheCompatibility must be there because it will minify twice
171
+ // once on block level and once on page level
172
+ if ($minifyEnabled && !$cacheCompatibility) {
 
 
 
173
  $response = $observer->getEvent()->getControllerAction()
174
  ->getResponse();
175
  $html = $response->getBody();
176
 
177
  if (stripos($html, '<!DOCTYPE html') !== false) {
 
178
  $type = false;
179
 
180
  foreach ($response->getHeaders() as $header) {
 
181
  if (stripos($header['name'], 'Content-Type') !== false) {
 
182
  if (stripos($header['value'], 'text/html') !== false) {
 
183
  $type = true;
 
184
  break;
 
185
  }
 
186
  }
 
187
  }
188
 
189
  if ($type) {
190
+ $response->setBody(
191
+ Mage::helper('apptrian_minify')->minifyHtml(
192
+ $html,
193
+ $removeComments,
194
+ $cacheCompatibility,
195
+ $maxMinification
196
+ )
197
+ );
 
 
 
 
 
 
198
  }
 
199
  }
 
200
  }
 
201
  }
 
202
  }
app/code/community/Apptrian/Minify/controllers/Adminhtml/Apptrian/MinifyController.php CHANGED
@@ -3,7 +3,7 @@
3
  * @category Apptrian
4
  * @package Apptrian_Minify
5
  * @author Apptrian
6
- * @copyright Copyright (c) 2016 Apptrian (http://www.apptrian.com)
7
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
  */
9
  class Apptrian_Minify_Adminhtml_Apptrian_MinifyController
@@ -26,13 +26,11 @@ class Apptrian_Minify_Adminhtml_Apptrian_MinifyController
26
  */
27
  public function processAction()
28
  {
29
-
30
  set_time_limit(18000);
31
 
32
  $helper = Mage::helper('apptrian_minify');
33
 
34
  try {
35
-
36
  $helper->process();
37
 
38
  $message = $this->__(
@@ -40,20 +38,15 @@ class Apptrian_Minify_Adminhtml_Apptrian_MinifyController
40
  );
41
 
42
  Mage::getSingleton('adminhtml/session')->addSuccess($message);
43
-
44
  } catch (Exception $e) {
45
-
46
  $message = $this->__('Minification failed.');
47
  Mage::getSingleton('adminhtml/session')->addError($message);
48
  Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
49
-
50
  }
51
 
52
  $url = Mage::helper('adminhtml')
53
  ->getUrl('adminhtml/system_config/edit/section/apptrian_minify');
54
 
55
  Mage::app()->getResponse()->setRedirect($url);
56
-
57
  }
58
-
59
  }
3
  * @category Apptrian
4
  * @package Apptrian_Minify
5
  * @author Apptrian
6
+ * @copyright Copyright (c) 2017 Apptrian (http://www.apptrian.com)
7
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
  */
9
  class Apptrian_Minify_Adminhtml_Apptrian_MinifyController
26
  */
27
  public function processAction()
28
  {
 
29
  set_time_limit(18000);
30
 
31
  $helper = Mage::helper('apptrian_minify');
32
 
33
  try {
 
34
  $helper->process();
35
 
36
  $message = $this->__(
38
  );
39
 
40
  Mage::getSingleton('adminhtml/session')->addSuccess($message);
 
41
  } catch (Exception $e) {
 
42
  $message = $this->__('Minification failed.');
43
  Mage::getSingleton('adminhtml/session')->addError($message);
44
  Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
 
45
  }
46
 
47
  $url = Mage::helper('adminhtml')
48
  ->getUrl('adminhtml/system_config/edit/section/apptrian_minify');
49
 
50
  Mage::app()->getResponse()->setRedirect($url);
 
51
  }
 
52
  }
app/code/community/Apptrian/Minify/etc/config.xml CHANGED
@@ -4,14 +4,14 @@
4
  * @category Apptrian
5
  * @package Apptrian_Minify
6
  * @author Apptrian
7
- * @copyright Copyright (c) 2016 Apptrian (http://www.apptrian.com)
8
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
9
  */
10
  -->
11
  <config>
12
  <modules>
13
  <Apptrian_Minify>
14
- <version>1.5.1</version>
15
  </Apptrian_Minify>
16
  </modules>
17
  <global>
4
  * @category Apptrian
5
  * @package Apptrian_Minify
6
  * @author Apptrian
7
+ * @copyright Copyright (c) 2017 Apptrian (http://www.apptrian.com)
8
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
9
  */
10
  -->
11
  <config>
12
  <modules>
13
  <Apptrian_Minify>
14
+ <version>2.0.0</version>
15
  </Apptrian_Minify>
16
  </modules>
17
  <global>
app/code/community/Apptrian/Minify/etc/system.xml CHANGED
@@ -4,7 +4,7 @@
4
  * @category Apptrian
5
  * @package Apptrian_Minify
6
  * @author Apptrian
7
- * @copyright Copyright (c) 2016 Apptrian (http://www.apptrian.com)
8
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
9
  */
10
  -->
4
  * @category Apptrian
5
  * @package Apptrian_Minify
6
  * @author Apptrian
7
+ * @copyright Copyright (c) 2017 Apptrian (http://www.apptrian.com)
8
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
9
  */
10
  -->
app/etc/modules/Apptrian_Minify.xml CHANGED
@@ -4,7 +4,7 @@
4
  * @category Apptrian
5
  * @package Apptrian_Minify
6
  * @author Apptrian
7
- * @copyright Copyright (c) 2016 Apptrian (http://www.apptrian.com)
8
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
9
  */
10
  -->
4
  * @category Apptrian
5
  * @package Apptrian_Minify
6
  * @author Apptrian
7
+ * @copyright Copyright (c) 2017 Apptrian (http://www.apptrian.com)
8
  * @license http://opensource.org/licenses/osl-3.0.php Open Software License
9
  */
10
  -->
lib/Apptrian/Minify/Css.php ADDED
@@ -0,0 +1,1018 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @category Apptrian
4
+ * @package Apptrian_Minify
5
+ * @author Apptrian
6
+ * @copyright Copyright (c) 2017 Apptrian (http://www.apptrian.com)
7
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
+ */
9
+
10
+ // @codingStandardsIgnoreFile
11
+
12
+ /**
13
+ * This class is taken from tubalmartin/cssmin library and modified into Magento
14
+ * compatible class. Some additional features are added to it for seamless
15
+ * integration with Magento.
16
+ */
17
+
18
+ /*!
19
+ * cssmin.php v2.4.8-4
20
+ * Author: Tubal Martin - http://tubalmartin.me/
21
+ * Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port
22
+ *
23
+ * This is a PHP port of the CSS minification tool distributed with
24
+ * YUICompressor, itself a port of the cssmin utility by
25
+ * Isaac Schlueter - http://foohack.com/
26
+ * Permission is hereby granted to use the PHP version under the same
27
+ * conditions as the YUICompressor.
28
+ */
29
+
30
+ /*!
31
+ * YUI Compressor
32
+ * http://developer.yahoo.com/yui/compressor/
33
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
34
+ * Copyright (c) 2013 Yahoo! Inc. All rights reserved.
35
+ * The copyrights embodied in the content of this file are licensed
36
+ * by Yahoo! Inc. under the BSD (revised) open source license.
37
+ */
38
+
39
+ class Apptrian_Minify_Css
40
+ {
41
+ const NL = '___YUICSSMIN_PRESERVED_NL___';
42
+ const TOKEN = '___YUICSSMIN_PRESERVED_TOKEN_';
43
+ const COMMENT = '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_';
44
+ const CLASSCOLON = '___YUICSSMIN_PSEUDOCLASSCOLON___';
45
+ const QUERY_FRACTION = '___YUICSSMIN_QUERY_FRACTION___';
46
+
47
+ public $comments;
48
+ public $preservedTokens;
49
+ public $memoryLimit;
50
+ public $maxExecutionTime;
51
+ public $pcreBacktrackLimit;
52
+ public $pcreRecursionLimit;
53
+ public $raisePhpLimits;
54
+
55
+ public $removeComments = true;
56
+
57
+ /**
58
+ * @param bool|int $raisePhpLimits
59
+ * If true, PHP settings will be raised if needed
60
+ */
61
+ public function __construct($raisePhpLimits = true, $removeComments = true)
62
+ {
63
+ // Set suggested PHP limits
64
+ $this->memoryLimit = 128 * 1048576; // 128MB in bytes
65
+ $this->maxExecutionTime = 60; // 1 min
66
+ $this->pcreBacktrackLimit = 1000 * 1000;
67
+ $this->pcreRecursionLimit = 500 * 1000;
68
+
69
+ $this->raisePhpLimits = (bool) $raisePhpLimits;
70
+
71
+ $this->removeComments = $removeComments;
72
+ }
73
+
74
+ /**
75
+ * Minify a string of CSS
76
+ * @param string $css
77
+ * @param int|bool $linebreakPos
78
+ * @return string
79
+ */
80
+ public function run($css = '', $linebreakPos = false)
81
+ {
82
+ if (empty($css)) {
83
+ return '';
84
+ }
85
+
86
+ if ($this->raisePhpLimits) {
87
+ $this->doRaisePhpLimits();
88
+ }
89
+
90
+ $this->comments = array();
91
+ $this->preservedTokens = array();
92
+
93
+ $startIndex = 0;
94
+ $length = strlen($css);
95
+
96
+ $css = $this->extractDataUrls($css);
97
+
98
+ // collect all comment blocks...
99
+ while (($startIndex = $this->indexOf($css, '/*', $startIndex)) >= 0) {
100
+ $endIndex = $this->indexOf($css, '*/', $startIndex + 2);
101
+ if ($endIndex < 0) {
102
+ $endIndex = $length;
103
+ }
104
+
105
+ $commentFound = $this->strSlice($css, $startIndex + 2, $endIndex);
106
+ $this->comments[] = $commentFound;
107
+ $commentPreserveString = self::COMMENT
108
+ . (count($this->comments) - 1) . '___';
109
+ $css = $this->strSlice($css, 0, $startIndex + 2)
110
+ . $commentPreserveString . $this->strSlice($css, $endIndex);
111
+ // Set correct startIndex: Fixes issue #2528130
112
+ $startIndex = $endIndex + 2 + strlen($commentPreserveString)
113
+ - strlen($commentFound);
114
+ }
115
+
116
+ // preserve strings so their content doesn't get accidentally minified
117
+ $css = preg_replace_callback(
118
+ '/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'
119
+ ."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S",
120
+ array($this, 'replaceString'),
121
+ $css
122
+ );
123
+
124
+ // Let's divide css code in chunks of 5.000 chars aprox.
125
+ // Reason: PHP's PCRE functions like preg_replace have a
126
+ // "backtrack limit" of 100.000 chars by default (php < 5.3.7) so if
127
+ // we're dealing with really long strings and a (sub)pattern matches
128
+ // a number of chars greater than the backtrack limit number
129
+ // (i.e. /(.*)/s) PCRE functions may fail silently returning NULL and
130
+ // $css would be empty.
131
+ $charset = '';
132
+ $charsetRegexp = '/(@charset)( [^;]+;)/i';
133
+ $cssChunks = array();
134
+ $cssChunkLength = 5000; // aprox size, not exact
135
+ $startIndex = 0;
136
+ $i = $cssChunkLength; // save initial iterations
137
+ $l = strlen($css);
138
+
139
+ // if the number of characters is 5000 or less, do not chunk
140
+ if ($l <= $cssChunkLength) {
141
+ $cssChunks[] = $css;
142
+ } else {
143
+ // chunk css code securely
144
+ while ($i < $l) {
145
+ $i += 50; // save iterations
146
+ if ($l - $startIndex <= $cssChunkLength || $i >= $l) {
147
+ $cssChunks[] = $this->strSlice($css, $startIndex);
148
+ break;
149
+ }
150
+
151
+ if ($css[$i - 1] === '}' && $i - $startIndex > $cssChunkLength
152
+ ) {
153
+ // If there are two ending curly braces }} separated or not
154
+ // by spaces, join them in the same chunk
155
+ // (i.e. @media blocks)
156
+ $nextChunk = substr($css, $i);
157
+ if (preg_match('/^\s*\}/', $nextChunk)) {
158
+ $i = $i + $this->indexOf($nextChunk, '}') + 1;
159
+ }
160
+
161
+ $cssChunks[] = $this->strSlice($css, $startIndex, $i);
162
+ $startIndex = $i;
163
+ }
164
+ }
165
+ }
166
+
167
+ // Minify each chunk
168
+ for ($i = 0, $n = count($cssChunks); $i < $n; $i++) {
169
+ $cssChunks[$i] = $this->minify($cssChunks[$i], $linebreakPos);
170
+ // Keep the first @charset at-rule found
171
+ if (empty($charset)
172
+ && preg_match($charsetRegexp, $cssChunks[$i], $matches)
173
+ ) {
174
+ $charset = strtolower($matches[1]) . $matches[2];
175
+ }
176
+
177
+ // Delete all @charset at-rules
178
+ $cssChunks[$i] = preg_replace($charsetRegexp, '', $cssChunks[$i]);
179
+ }
180
+
181
+ // Update the first chunk and push the charset to the top of the file.
182
+ $cssChunks[0] = $charset . $cssChunks[0];
183
+
184
+ return implode('', $cssChunks);
185
+ }
186
+
187
+ /**
188
+ * Sets the memory limit for this script
189
+ * @param int|string $limit
190
+ */
191
+ public function setMemoryLimit($limit)
192
+ {
193
+ $this->memoryLimit = $this->normalizeInt($limit);
194
+ }
195
+
196
+ /**
197
+ * Sets the maximum execution time for this script
198
+ * @param int|string $seconds
199
+ */
200
+ public function setMaxExecutionTime($seconds)
201
+ {
202
+ $this->maxExecutionTime = (int) $seconds;
203
+ }
204
+
205
+ /**
206
+ * Sets the PCRE backtrack limit for this script
207
+ * @param int $limit
208
+ */
209
+ public function setPcreBacktrackLimit($limit)
210
+ {
211
+ $this->pcreBacktrackLimit = (int) $limit;
212
+ }
213
+
214
+ /**
215
+ * Sets the PCRE recursion limit for this script
216
+ * @param int $limit
217
+ */
218
+ public function setPcreRecursionLimit($limit)
219
+ {
220
+ $this->pcreRecursionLimit = (int) $limit;
221
+ }
222
+
223
+ /**
224
+ * Try to configure PHP to use at least the suggested minimum settings
225
+ */
226
+ public function doRaisePhpLimits()
227
+ {
228
+ $phpLimits = array(
229
+ 'memory_limit' => $this->memoryLimit,
230
+ 'max_execution_time' => $this->maxExecutionTime,
231
+ 'pcre.backtrack_limit' => $this->pcreBacktrackLimit,
232
+ 'pcre.recursion_limit' => $this->pcreRecursionLimit
233
+ );
234
+
235
+ // If current settings are higher respect them.
236
+ foreach ($phpLimits as $name => $suggested) {
237
+ $current = $this->normalizeInt(ini_get($name));
238
+ // memory_limit exception: allow -1 for "no memory limit".
239
+ if ($current > -1 && ($suggested == -1 || $current < $suggested)) {
240
+ ini_set($name, $suggested);
241
+ }
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Does bulk of the minification
247
+ * @param string $css
248
+ * @param int|bool $linebreakPos
249
+ * @return string
250
+ */
251
+ public function minify($css, $linebreakPos)
252
+ {
253
+ // strings are safe, now wrestle the comments
254
+ for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
255
+ $token = $this->comments[$i];
256
+ $placeholder = '/' . self::COMMENT . $i . '___/';
257
+
258
+ if (!$this->removeComments) {
259
+ // ! in the first position of the comment means preserve
260
+ // so push to the preserved tokens keeping the !
261
+ if (substr($token, 0, 1) === '!') {
262
+ $this->preservedTokens[] = $token;
263
+ $tokenString = self::TOKEN
264
+ . (count($this->preservedTokens) - 1) . '___';
265
+ $css = preg_replace($placeholder, $tokenString, $css, 1);
266
+ // Preserve new lines for /*! important comments
267
+ $css = preg_replace(
268
+ '/\s*[\n\r\f]+\s*(\/\*'. $tokenString .')/S',
269
+ self::NL.'$1',
270
+ $css
271
+ );
272
+ $css = preg_replace(
273
+ '/('. $tokenString .'\*\/)\s*[\n\r\f]+\s*/',
274
+ '$1'.self::NL,
275
+ $css
276
+ );
277
+ continue;
278
+ }
279
+ }
280
+
281
+ // \ in the last position looks like hack for Mac/IE5
282
+ // shorten that to /*\*/ and the next one to /**/
283
+ if (substr($token, (strlen($token) - 1), 1) === '\\') {
284
+ $this->preservedTokens[] = '\\';
285
+ $css = preg_replace(
286
+ $placeholder,
287
+ self::TOKEN . (count($this->preservedTokens) - 1) . '___',
288
+ $css,
289
+ 1
290
+ );
291
+ $i = $i + 1; // attn: advancing the loop
292
+ $this->preservedTokens[] = '';
293
+ $css = preg_replace(
294
+ '/' . self::COMMENT . $i . '___/',
295
+ self::TOKEN . (count($this->preservedTokens) - 1) . '___',
296
+ $css,
297
+ 1
298
+ );
299
+ continue;
300
+ }
301
+
302
+ // keep empty comments after child selectors (IE7 hack)
303
+ // e.g. html >/**/ body
304
+ if (strlen($token) === 0) {
305
+ $startIndex = $this->indexOf(
306
+ $css,
307
+ $this->strSlice($placeholder, 1, -1)
308
+ );
309
+ if ($startIndex > 2) {
310
+ if (substr($css, $startIndex - 3, 1) === '>') {
311
+ $this->preservedTokens[] = '';
312
+ $css = preg_replace(
313
+ $placeholder,
314
+ self::TOKEN . (count($this->preservedTokens) - 1)
315
+ . '___',
316
+ $css,
317
+ 1
318
+ );
319
+ }
320
+ }
321
+ }
322
+
323
+ // in all other cases kill the comment
324
+ $css = preg_replace(
325
+ '/\/\*' . $this->strSlice($placeholder, 1, -1) . '\*\//',
326
+ '',
327
+ $css,
328
+ 1
329
+ );
330
+ }
331
+
332
+ // Normalize all whitespace strings to single spaces.
333
+ // Easier to work with that way.
334
+ $css = preg_replace('/\s+/', ' ', $css);
335
+
336
+ // Fix IE7 issue on matrix filters which browser accept whitespaces
337
+ // between Matrix parameters
338
+ $css = preg_replace_callback(
339
+ '/\s*filter\:\s*progid:DXImage'
340
+ .'Transform\.Microsoft\.Matrix\(([^\)]+)\)/',
341
+ array($this, 'preserveOldIeSpecificMatrixDefinition'),
342
+ $css
343
+ );
344
+
345
+ // Shorten & preserve calculations calc(...) since spaces are important
346
+ $css = preg_replace_callback(
347
+ '/calc(\(((?:[^\(\)]+|(?1))*)\))/i',
348
+ array($this, 'replaceCalc'),
349
+ $css
350
+ );
351
+
352
+ // Replace positive sign from numbers preceded by : or a white-space
353
+ // before the leading space is removed
354
+ // +1.2em to 1.2em, +.8px to .8px, +2% to 2%
355
+ $css = preg_replace('/((?<!\\\\)\:|\s)\+(\.?\d+)/S', '$1$2', $css);
356
+
357
+ // Remove leading zeros from integer and float numbers preceded by : or
358
+ // a white-space
359
+ // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05
360
+ $css = preg_replace(
361
+ '/((?<!\\\\)\:|\s)(\-?)0+(\.?\d+)/S',
362
+ '$1$2$3',
363
+ $css
364
+ );
365
+
366
+ // Remove trailing zeros from float numbers preceded by : or a
367
+ // white-space
368
+ // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px
369
+ $css = preg_replace(
370
+ '/((?<!\\\\)\:|\s)(\-?)(\d?\.\d+?)0+([^\d])/S',
371
+ '$1$2$3$4',
372
+ $css
373
+ );
374
+
375
+ // Remove trailing .0 -> -9.0 to -9
376
+ $css = preg_replace(
377
+ '/((?<!\\\\)\:|\s)(\-?\d+)\.0([^\d])/S',
378
+ '$1$2$3',
379
+ $css
380
+ );
381
+
382
+ // Replace 0 length numbers with 0
383
+ $css = preg_replace(
384
+ '/((?<!\\\\)\:|\s)\-?\.?0+([^\d])/S',
385
+ '${1}0$2',
386
+ $css
387
+ );
388
+
389
+ // Remove the spaces before the things that should not have spaces
390
+ // before them. But, be careful not to turn "p :link {...}" into
391
+ // "p:link{...}" Swap out any pseudo-class colons with the token,
392
+ // and then swap back.
393
+ $css = preg_replace_callback(
394
+ '/(?:^|\})[^\{]*\s+\:/',
395
+ array($this, 'replaceColon'),
396
+ $css
397
+ );
398
+
399
+ // Remove spaces before the things that should not have spaces before
400
+ // them.
401
+ $css = preg_replace('/\s+([\!\{\}\;\:\>\+\(\)\]\~\=,])/', '$1', $css);
402
+
403
+ // Restore spaces for !important
404
+ $css = preg_replace('/\!important/i', ' !important', $css);
405
+
406
+ // bring back the colon
407
+ $css = preg_replace('/' . self::CLASSCOLON . '/', ':', $css);
408
+
409
+ // retain space for special IE6 cases
410
+ $css = preg_replace_callback(
411
+ '/\:first\-(line|letter)(\{|,)/i',
412
+ array($this, 'lowercasePseudoFirst'),
413
+ $css
414
+ );
415
+
416
+ // no space after the end of a preserved comment
417
+ $css = preg_replace('/\*\/ /', '*/', $css);
418
+
419
+ // lowercase some popular @directives
420
+ $css = preg_replace_callback(
421
+ '/@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?key'
422
+ .'frame|media|page|namespace)/i',
423
+ array($this, 'lowercaseDirectives'),
424
+ $css
425
+ );
426
+
427
+ // lowercase some more common pseudo-elements
428
+ $css = preg_replace_callback(
429
+ '/:(active|after|before|checked|disabled|empty|enabled|first-(?:'
430
+ .'child|of-type)|focus|hover|last-(?:child|of-type)|link|only'
431
+ .'-(?:child|of-type)|root|:selection|target|visited)/i',
432
+ array($this, 'lowercasePseudoElements'),
433
+ $css
434
+ );
435
+
436
+ // lowercase some more common functions
437
+ $css = preg_replace_callback(
438
+ '/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type'
439
+ .'|(?:-(?:moz|webkit)-)?any)\(/i',
440
+ array($this, 'lowercaseCommonFunctions'),
441
+ $css
442
+ );
443
+
444
+ // lower case some common function that can be values
445
+ // NOTE: rgb() isn't useful as we replace with #hex later, as well as
446
+ // and() is already done for us
447
+ $css = preg_replace_callback(
448
+ '/([:,\( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml'
449
+ .'|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:'
450
+ .'linear|radial)-gradient)|-webkit-gradient)/iS',
451
+ array($this, 'lowercaseCommonFunctionsValues'),
452
+ $css
453
+ );
454
+
455
+ // Put the space back in some cases, to support stuff like
456
+ // @media screen and (-webkit-min-device-pixel-ratio:0){
457
+ $css = preg_replace('/\band\(/i', 'and (', $css);
458
+
459
+ // Remove the spaces after the things that should not have spaces after
460
+ // them.
461
+ $css = preg_replace('/([\!\{\}\:;\>\+\(\[\~\=,])\s+/S', '$1', $css);
462
+
463
+ // remove unnecessary semicolons
464
+ $css = preg_replace('/;+\}/', '}', $css);
465
+
466
+ // Fix for issue: #2528146
467
+ // Restore semicolon if the last property is prefixed with a `*`
468
+ // (lte IE7 hack) to avoid issues on Symbian S60 3.x browsers.
469
+ $css = preg_replace('/(\*[a-z0-9\-]+\s*\:[^;\}]+)(\})/', '$1;$2', $css);
470
+
471
+ // Replace 0 <length> and 0 <percentage> values with 0.
472
+ // <length> data type:
473
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/length
474
+ // <percentage> data type:
475
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/percentage
476
+ $css = preg_replace(
477
+ '/([^\\\\]\:|\s)0(?:em|ex|ch|rem|vw|vh|vm|vmin|cm|mm|in'
478
+ .'|px|pt|pc|%)/iS',
479
+ '${1}0',
480
+ $css
481
+ );
482
+
483
+ // 0% step in a keyframe? restore the % unit
484
+ $css = preg_replace_callback(
485
+ '/(@[a-z\-]*?keyframes[^\{]+\{)(.*?)(\}\})/iS',
486
+ array($this, 'replaceKeyframeZero'),
487
+ $css
488
+ );
489
+
490
+ // Replace 0 0; or 0 0 0; or 0 0 0 0; with 0.
491
+ $css = preg_replace('/\:0(?: 0){1,3}(;|\}| \!)/', ':0$1', $css);
492
+
493
+ // Fix for issue: #2528142
494
+ // Replace text-shadow:0; with text-shadow:0 0 0;
495
+ $css = preg_replace('/(text-shadow\:0)(;|\}| \!)/i', '$1 0 0$2', $css);
496
+
497
+ // Replace background-position:0; with background-position:0 0;
498
+ // same for transform-origin
499
+ // Changing -webkit-mask-position: 0 0 to just a single 0 will result
500
+ // in the second parameter defaulting to 50% (center)
501
+ $css = preg_replace(
502
+ '/(background\-position|webkit-mask-position|(?:webkit|moz|o|ms|)\-'
503
+ .'?transform\-origin)\:0(;|\}| \!)/iS',
504
+ '$1:0 0$2',
505
+ $css
506
+ );
507
+
508
+ // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to
509
+ // #ff0000 (sRGB color space)
510
+ // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space)
511
+ // This makes it more likely that it'll get further compressed in the
512
+ // next step.
513
+ $css = preg_replace_callback(
514
+ '/rgb\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i',
515
+ array($this, 'rgbToHex'),
516
+ $css
517
+ );
518
+ $css = preg_replace_callback(
519
+ '/hsl\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i',
520
+ array($this, 'hslToHex'),
521
+ $css
522
+ );
523
+
524
+ // Shorten colors from #AABBCC to #ABC or short color name.
525
+ $css = $this->compressHexColors($css);
526
+
527
+ // border: none to border:0, outline: none to outline:0
528
+ $css = preg_replace(
529
+ '/(border\-?(?:top|right|bottom|left|)|outline)\:none(;|\}| \!)/iS',
530
+ '$1:0$2',
531
+ $css
532
+ );
533
+
534
+ // shorter opacity IE filter
535
+ $css = preg_replace(
536
+ '/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i',
537
+ 'alpha(opacity=',
538
+ $css
539
+ );
540
+
541
+ // Find a fraction that is used for Opera's -o-device-pixel-ratio query
542
+ // Add token to add the "\" back in later
543
+ $css = preg_replace(
544
+ '/\(([a-z\-]+):([0-9]+)\/([0-9]+)\)/i',
545
+ '($1:$2'. self::QUERY_FRACTION .'$3)',
546
+ $css
547
+ );
548
+
549
+ // Remove empty rules.
550
+ $css = preg_replace('/[^\};\{\/]+\{\}/S', '', $css);
551
+
552
+ // Add "/" back to fix Opera -o-device-pixel-ratio query
553
+ $css = preg_replace('/'. self::QUERY_FRACTION .'/', '/', $css);
554
+
555
+ // Replace multiple semi-colons in a row by a single one
556
+ // See SF bug #1980989
557
+ $css = preg_replace('/;;+/', ';', $css);
558
+
559
+ // Restore new lines for /*! important comments
560
+ $css = preg_replace('/'. self::NL .'/', "\n", $css);
561
+
562
+ // Lowercase all uppercase properties
563
+ $css = preg_replace_callback(
564
+ '/(\{|\;)([A-Z\-]+)(\:)/',
565
+ array($this, 'lowercaseProperties'),
566
+ $css
567
+ );
568
+
569
+ // Some source control tools don't like it when files containing lines
570
+ // longer than, say 8000 characters, are checked in. The linebreak
571
+ // option is used in that case to split long lines after a specific
572
+ // column.
573
+ if ($linebreakPos !== false && (int) $linebreakPos >= 0) {
574
+ $linebreakPos = (int) $linebreakPos;
575
+ $startIndex = $i = 0;
576
+ while ($i < strlen($css)) {
577
+ $i++;
578
+ if ($css[$i - 1] === '}' && $i - $startIndex > $linebreakPos) {
579
+ $css = $this->strSlice($css, 0, $i) . "\n"
580
+ . $this->strSlice($css, $i);
581
+ $startIndex = $i;
582
+ }
583
+ }
584
+ }
585
+
586
+ // restore preserved comments and strings in reverse order
587
+ for ($i = count($this->preservedTokens) - 1; $i >= 0; $i--) {
588
+ $css = preg_replace(
589
+ '/' . self::TOKEN . $i . '___/',
590
+ $this->preservedTokens[$i],
591
+ $css,
592
+ 1
593
+ );
594
+ }
595
+
596
+ // Trim the final string (for any leading or trailing white spaces)
597
+ return trim($css);
598
+ }
599
+
600
+ /**
601
+ * Utility method to replace all data urls with tokens before we start
602
+ * compressing, to avoid performance issues running some of the subsequent
603
+ * regexes against large strings chunks.
604
+ *
605
+ * @param string $css
606
+ * @return string
607
+ */
608
+ public function extractDataUrls($css)
609
+ {
610
+ // Leave data urls alone to increase parse performance.
611
+ $maxIndex = strlen($css) - 1;
612
+ $appendIndex = $index = $lastIndex = $offset = 0;
613
+ $sb = array();
614
+ $pattern = '/url\(\s*(["\']?)data\:/i';
615
+
616
+ // Since we need to account for non-base64 data urls, we need to handle
617
+ // ' and ) being part of the data string. Hence switching to indexOf,
618
+ // to determine whether or not we have matching string terminators and
619
+ // handling sb appends directly, instead of using matcher.append*
620
+ // methods.
621
+
622
+ while (preg_match($pattern, $css, $m, 0, $offset)) {
623
+ $index = $this->indexOf($css, $m[0], $offset);
624
+ $lastIndex = $index + strlen($m[0]);
625
+ $startIndex = $index + 4; // "url(".length()
626
+ $endIndex = $lastIndex - 1;
627
+ $terminator = $m[1]; // ', " or empty (not quoted)
628
+ $foundTerminator = false;
629
+
630
+ if (strlen($terminator) === 0) {
631
+ $terminator = ')';
632
+ }
633
+
634
+ while ($foundTerminator === false && $endIndex+1 <= $maxIndex) {
635
+ $endIndex = $this->indexOf($css, $terminator, $endIndex + 1);
636
+
637
+ // endIndex == 0 doesn't really apply here
638
+ if ($endIndex > 0 && substr($css, $endIndex - 1, 1) !== '\\') {
639
+ $foundTerminator = true;
640
+ if (')' != $terminator) {
641
+ $endIndex = $this->indexOf($css, ')', $endIndex);
642
+ }
643
+ }
644
+ }
645
+
646
+ // Enough searching, start moving stuff over to the buffer
647
+ $sb[] = $this->strSlice($css, $appendIndex, $index);
648
+
649
+ if ($foundTerminator) {
650
+ $token = $this->strSlice($css, $startIndex, $endIndex);
651
+ $token = preg_replace('/\s+/', '', $token);
652
+ $this->preservedTokens[] = $token;
653
+
654
+ $preserver = 'url(' . self::TOKEN
655
+ . (count($this->preservedTokens) - 1) . '___)';
656
+ $sb[] = $preserver;
657
+
658
+ $appendIndex = $endIndex + 1;
659
+ } else {
660
+ // No end terminator found, re-add the whole match. Should we
661
+ // throw/warn here?
662
+ $sb[] = $this->strSlice($css, $index, $lastIndex);
663
+ $appendIndex = $lastIndex;
664
+ }
665
+
666
+ $offset = $lastIndex;
667
+ }
668
+
669
+ $sb[] = $this->strSlice($css, $appendIndex);
670
+
671
+ return implode('', $sb);
672
+ }
673
+
674
+ /**
675
+ * Utility method to compress hex color values of the form #AABBCC to #ABC
676
+ * or short color name.
677
+ *
678
+ * DOES NOT compress CSS ID selectors which match the above pattern
679
+ * (which would break things).
680
+ * e.g. #AddressForm { ... }
681
+ *
682
+ * DOES NOT compress IE filters, which have hex color values
683
+ * (which would break things).
684
+ * e.g. filter: chroma(color="#FFFFFF");
685
+ *
686
+ * DOES NOT compress invalid hex values.
687
+ * e.g. background-color: #aabbccdd
688
+ *
689
+ * @param string $css
690
+ * @return string
691
+ */
692
+ public function compressHexColors($css)
693
+ {
694
+ // Look for hex colors inside { ... } (to avoid IDs) and which don't
695
+ // have a =, or a " in front of them (to avoid filters)
696
+ $pattern = '/(\=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(['
697
+ .'0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS';
698
+ $idx = $index = $lastIndex = $offset = 0;
699
+ $sb = array();
700
+ // See: http://ajaxmin.codeplex.com/wikipage?title=CSS%20Colors
701
+ $shortSafe = array(
702
+ '#808080' => 'gray',
703
+ '#008000' => 'green',
704
+ '#800000' => 'maroon',
705
+ '#000080' => 'navy',
706
+ '#808000' => 'olive',
707
+ '#ffa500' => 'orange',
708
+ '#800080' => 'purple',
709
+ '#c0c0c0' => 'silver',
710
+ '#008080' => 'teal',
711
+ '#f00' => 'red'
712
+ );
713
+
714
+ while (preg_match($pattern, $css, $m, 0, $offset)) {
715
+ $index = $this->indexOf($css, $m[0], $offset);
716
+ $lastIndex = $index + strlen($m[0]);
717
+ $isFilter = $m[1] !== null && $m[1] !== '';
718
+
719
+ $sb[] = $this->strSlice($css, $idx, $index);
720
+
721
+ if ($isFilter) {
722
+ // Restore, maintain case, otherwise filter will break
723
+ $sb[] = $m[1] . '#' . $m[2] . $m[3] . $m[4] . $m[5] . $m[6]
724
+ . $m[7];
725
+ } else {
726
+ if (strtolower($m[2]) == strtolower($m[3]) &&
727
+ strtolower($m[4]) == strtolower($m[5]) &&
728
+ strtolower($m[6]) == strtolower($m[7])) {
729
+ // Compress.
730
+ $hex = '#' . strtolower($m[3] . $m[5] . $m[7]);
731
+ } else {
732
+ // Non compressible color, restore but lower case.
733
+ $hex = '#' . strtolower(
734
+ $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]
735
+ );
736
+ }
737
+
738
+ // replace Hex colors to short safe color names
739
+ $sb[] = array_key_exists($hex, $shortSafe)
740
+ ? $shortSafe[$hex]
741
+ : $hex;
742
+ }
743
+
744
+ $idx = $offset = $lastIndex - strlen($m[8]);
745
+ }
746
+
747
+ $sb[] = $this->strSlice($css, $idx);
748
+
749
+ return implode('', $sb);
750
+ }
751
+
752
+ /* CALLBACKS
753
+ * ------------------------------------------------------------------------
754
+ */
755
+
756
+ public function replaceString($matches)
757
+ {
758
+ $match = $matches[0];
759
+ $quote = substr($match, 0, 1);
760
+ // Must use addcslashes in PHP to avoid parsing of backslashes
761
+ $match = addcslashes($this->strSlice($match, 1, -1), '\\');
762
+
763
+ // maybe the string contains a comment-like substring?
764
+ // one, maybe more? put'em back then
765
+ if (($pos = $this->indexOf($match, self::COMMENT)) >= 0) {
766
+ for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
767
+ $match = preg_replace(
768
+ '/' . self::COMMENT . $i . '___/',
769
+ $this->comments[$i],
770
+ $match,
771
+ 1
772
+ );
773
+ }
774
+ }
775
+
776
+ // minify alpha opacity in filter strings
777
+ $match = preg_replace(
778
+ '/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i',
779
+ 'alpha(opacity=',
780
+ $match
781
+ );
782
+
783
+ $this->preservedTokens[] = $match;
784
+ return $quote . self::TOKEN . (count($this->preservedTokens) - 1)
785
+ . '___' . $quote;
786
+ }
787
+
788
+ public function replaceColon($matches)
789
+ {
790
+ return preg_replace('/\:/', self::CLASSCOLON, $matches[0]);
791
+ }
792
+
793
+ public function replaceCalc($matches)
794
+ {
795
+ $this->preservedTokens[] = trim(
796
+ preg_replace(
797
+ '/\s*([\*\/\(\),])\s*/',
798
+ '$1',
799
+ $matches[2]
800
+ )
801
+ );
802
+ return 'calc('. self::TOKEN . (count($this->preservedTokens) - 1)
803
+ . '___' . ')';
804
+ }
805
+
806
+ public function preserveOldIeSpecificMatrixDefinition($matches)
807
+ {
808
+ $this->preservedTokens[] = $matches[1];
809
+ return 'filter:progid:DXImageTransform.Microsoft.Matrix(' . self::TOKEN
810
+ . (count($this->preservedTokens) - 1) . '___' . ')';
811
+ }
812
+
813
+ public function replaceKeyframeZero($matches)
814
+ {
815
+ return $matches[1] . preg_replace(
816
+ '/0(\{|,[^\)\{]+\{)/',
817
+ '0%$1',
818
+ $matches[2]
819
+ ) . $matches[3];
820
+ }
821
+
822
+ public function rgbToHex($matches)
823
+ {
824
+ // Support for percentage values rgb(100%, 0%, 45%);
825
+ if ($this->indexOf($matches[1], '%') >= 0) {
826
+ $rgbcolors = explode(',', str_replace('%', '', $matches[1]));
827
+ for ($i = 0; $i < count($rgbcolors); $i++) {
828
+ $rgbcolors[$i] = $this->roundNumber(
829
+ floatval($rgbcolors[$i]) * 2.55
830
+ );
831
+ }
832
+ } else {
833
+ $rgbcolors = explode(',', $matches[1]);
834
+ }
835
+
836
+ // Values outside the sRGB color space should be clipped (0-255)
837
+ for ($i = 0; $i < count($rgbcolors); $i++) {
838
+ $rgbcolors[$i] = $this->clampNumber(
839
+ intval($rgbcolors[$i], 10),
840
+ 0,
841
+ 255
842
+ );
843
+ $rgbcolors[$i] = sprintf("%02x", $rgbcolors[$i]);
844
+ }
845
+
846
+ // Fix for issue #2528093
847
+ if (!preg_match('/[\s\,\);\}]/', $matches[2])) {
848
+ $matches[2] = ' ' . $matches[2];
849
+ }
850
+
851
+ return '#' . implode('', $rgbcolors) . $matches[2];
852
+ }
853
+
854
+ public function hslToHex($matches)
855
+ {
856
+ $values = explode(',', str_replace('%', '', $matches[1]));
857
+ $h = floatval($values[0]);
858
+ $s = floatval($values[1]);
859
+ $l = floatval($values[2]);
860
+
861
+ // Wrap and clamp, then fraction!
862
+ $h = ((($h % 360) + 360) % 360) / 360;
863
+ $s = $this->clampNumber($s, 0, 100) / 100;
864
+ $l = $this->clampNumber($l, 0, 100) / 100;
865
+
866
+ if ($s == 0) {
867
+ $r = $g = $b = $this->roundNumber(255 * $l);
868
+ } else {
869
+ $vB = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l);
870
+ $vA = (2 * $l) - $vB;
871
+ $r = $this->roundNumber(255*$this->hueToRgb($vA, $vB, $h + (1/3)));
872
+ $g = $this->roundNumber(255*$this->hueToRgb($vA, $vB, $h));
873
+ $b = $this->roundNumber(255*$this->hueToRgb($vA, $vB, $h - (1/3)));
874
+ }
875
+
876
+ return $this->rgbToHex(array('', $r.','.$g.','.$b, $matches[2]));
877
+ }
878
+
879
+ public function lowercasePseudoFirst($matches)
880
+ {
881
+ return ':first-'. strtolower($matches[1]) .' '. $matches[2];
882
+ }
883
+
884
+ public function lowercaseDirectives($matches)
885
+ {
886
+ return '@'. strtolower($matches[1]);
887
+ }
888
+
889
+ public function lowercasePseudoElements($matches)
890
+ {
891
+ return ':'. strtolower($matches[1]);
892
+ }
893
+
894
+ public function lowercaseCommonFunctions($matches)
895
+ {
896
+ return ':'. strtolower($matches[1]) .'(';
897
+ }
898
+
899
+ public function lowercaseCommonFunctionsValues($matches)
900
+ {
901
+ return $matches[1] . strtolower($matches[2]);
902
+ }
903
+
904
+ public function lowercaseProperties($matches)
905
+ {
906
+ return $matches[1].strtolower($matches[2]).$matches[3];
907
+ }
908
+
909
+ /* HELPERS
910
+ * ------------------------------------------------------------------------
911
+ */
912
+
913
+ public function hueToRgb($vA, $vB, $vh)
914
+ {
915
+ $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh);
916
+
917
+ if ($vh * 6 < 1) {
918
+ return $vA + ($vB - $vA) * 6 * $vh;
919
+ }
920
+
921
+ if ($vh * 2 < 1) {
922
+ return $vB;
923
+ }
924
+
925
+ if ($vh * 3 < 2) {
926
+ return $vA + ($vB - $vA) * ((2/3) - $vh) * 6;
927
+ }
928
+
929
+ return $vA;
930
+ }
931
+
932
+ public function roundNumber($n)
933
+ {
934
+ return intval(floor(floatval($n) + 0.5), 10);
935
+ }
936
+
937
+ public function clampNumber($n, $min, $max)
938
+ {
939
+ return min(max($n, $min), $max);
940
+ }
941
+
942
+ /**
943
+ * PHP port of Javascript's "indexOf" function for strings only
944
+ * Author: Tubal Martin http://blog.margenn.com
945
+ *
946
+ * @param string $haystack
947
+ * @param string $needle
948
+ * @param int $offset index (optional)
949
+ * @return int
950
+ */
951
+ public function indexOf($haystack, $needle, $offset = 0)
952
+ {
953
+ $index = strpos($haystack, $needle, $offset);
954
+
955
+ return ($index !== false) ? $index : -1;
956
+ }
957
+
958
+ /**
959
+ * PHP port of Javascript's "slice" function for strings only
960
+ * Author: Tubal Martin http://blog.margenn.com
961
+ * Tests: http://margenn.com/tubal/strSlice/
962
+ *
963
+ * @param string $str
964
+ * @param int $start index
965
+ * @param int|bool $end index (optional)
966
+ * @return string
967
+ */
968
+ public function strSlice($str, $start = 0, $end = false)
969
+ {
970
+ if ($end !== false && ($start < 0 || $end <= 0)) {
971
+ $max = strlen($str);
972
+
973
+ if ($start < 0) {
974
+ if (($start = $max + $start) < 0) {
975
+ return '';
976
+ }
977
+ }
978
+
979
+ if ($end < 0) {
980
+ if (($end = $max + $end) < 0) {
981
+ return '';
982
+ }
983
+ }
984
+
985
+ if ($end <= $start) {
986
+ return '';
987
+ }
988
+ }
989
+
990
+ $slice = ($end === false) ? substr($str, $start)
991
+ : substr($str, $start, $end - $start);
992
+ return ($slice === false) ? '' : $slice;
993
+ }
994
+
995
+ /**
996
+ * Convert strings like "64M" or "30" to int values
997
+ * @param mixed $size
998
+ * @return int
999
+ */
1000
+ public function normalizeInt($size)
1001
+ {
1002
+ if (is_string($size)) {
1003
+ switch (substr($size, -1)) {
1004
+ case 'M':
1005
+ case 'm':
1006
+ return $size * 1048576;
1007
+ case 'K':
1008
+ case 'k':
1009
+ return $size * 1024;
1010
+ case 'G':
1011
+ case 'g':
1012
+ return $size * 1073741824;
1013
+ }
1014
+ }
1015
+
1016
+ return (int) $size;
1017
+ }
1018
+ }
lib/Apptrian/Minify/Html.php ADDED
@@ -0,0 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @category Apptrian
4
+ * @package Apptrian_Minify
5
+ * @author Apptrian
6
+ * @copyright Copyright (c) 2017 Apptrian (http://www.apptrian.com)
7
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
+ */
9
+
10
+ // @codingStandardsIgnoreFile
11
+
12
+ /**
13
+ * This class is taken from mrclay/minify library and modified into Magento
14
+ * compatible class. Some additional features are added to it for seamless
15
+ * integration with Magento.
16
+ */
17
+
18
+ /**
19
+ * Class Minify_HTML
20
+ * @package Minify
21
+ */
22
+
23
+ /**
24
+ * Compress HTML
25
+ *
26
+ * This is a heavy regex-based removal of whitespace, unnecessary comments and
27
+ * tokens. IE conditional comments are preserved. There are also options to have
28
+ * STYLE and SCRIPT blocks compressed by callback functions.
29
+ *
30
+ * A test suite is available.
31
+ *
32
+ * @package Minify
33
+ * @author Stephen Clay <steve@mrclay.org>
34
+ */
35
+
36
+ class Apptrian_Minify_Html
37
+ {
38
+ /**
39
+ * @var null|bool
40
+ */
41
+ public $isXhtml = null;
42
+
43
+ /**
44
+ * @var null|string
45
+ */
46
+ public $replacementHash = null;
47
+
48
+ /**
49
+ * @var array
50
+ */
51
+ public $placeholders = array();
52
+
53
+ /**
54
+ * @var null|string
55
+ */
56
+ public $cssMinifier = null;
57
+
58
+ /**
59
+ * @var null|string
60
+ */
61
+ public $jsMinifier = null;
62
+
63
+ /**
64
+ * @var boolean
65
+ */
66
+ public $jsCleanComments = true;
67
+
68
+ /**
69
+ * Remove Comments.
70
+ *
71
+ * @var bool
72
+ */
73
+ public $removeComments = true;
74
+
75
+ /**
76
+ * Cache Compatibility.
77
+ *
78
+ * @var bool
79
+ */
80
+ public $cacheCompatibility = false;
81
+
82
+ /**
83
+ * Maximum Minification (entire code on one line).
84
+ *
85
+ * @var bool
86
+ */
87
+ public $maxMinification = false;
88
+
89
+ /**
90
+ * Options
91
+ *
92
+ * @var array
93
+ */
94
+ public $options = array();
95
+
96
+ /**
97
+ * "Minify" an HTML page
98
+ *
99
+ * @param string $html
100
+ *
101
+ * @param array $options
102
+ *
103
+ * 'cssMinifier' : (optional) callback function to process content of STYLE
104
+ * elements.
105
+ *
106
+ * 'jsMinifier' : (optional) callback function to process content of SCRIPT
107
+ * elements. Note: the type attribute is ignored.
108
+ *
109
+ * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
110
+ * unset, minify will sniff for an XHTML doctype.
111
+ *
112
+ * @return string
113
+ */
114
+ public static function minify($html, $options = array())
115
+ {
116
+ $min = new self($html, $options);
117
+
118
+ return $min->process();
119
+ }
120
+
121
+ /**
122
+ * Create a minifier object
123
+ *
124
+ * @param string $html
125
+ *
126
+ * @param array $options
127
+ *
128
+ * 'cssMinifier' : (optional) callback function to process content of STYLE
129
+ * elements.
130
+ *
131
+ * 'jsMinifier' : (optional) callback function to process content of SCRIPT
132
+ * elements. Note: the type attribute is ignored.
133
+ *
134
+ * 'jsCleanComments' : (optional) whether to remove HTML comments beginning
135
+ * and end of script block
136
+ *
137
+ * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
138
+ * unset, minify will sniff for an XHTML doctype.
139
+ */
140
+ public function __construct($html, $options = array())
141
+ {
142
+ $this->_html = str_replace("\r\n", "\n", trim($html));
143
+ if (isset($options['xhtml'])) {
144
+ $this->isXhtml = (bool)$options['xhtml'];
145
+ }
146
+
147
+ if (isset($options['cssMinifier'])) {
148
+ $this->cssMinifier = $options['cssMinifier'];
149
+ }
150
+
151
+ if (isset($options['jsMinifier'])) {
152
+ $this->jsMinifier = $options['jsMinifier'];
153
+ }
154
+
155
+ if (isset($options['jsCleanComments'])) {
156
+ $this->jsCleanComments = (bool)$options['jsCleanComments'];
157
+ }
158
+
159
+ if (isset($options['removeComments'])) {
160
+ $this->removeComments = (bool)$options['removeComments'];
161
+ }
162
+
163
+ if (isset($options['cacheCompatibility'])) {
164
+ $this->cacheCompatibility = (bool)$options['cacheCompatibility'];
165
+ }
166
+
167
+ if (isset($options['maxMinification'])) {
168
+ $this->maxMinification = (bool)$options['maxMinification'];
169
+ }
170
+
171
+ // Preserve options if needed for script type="text/template"
172
+ $this->options = $options;
173
+ }
174
+
175
+ /**
176
+ * Minify the markeup given in the constructor
177
+ *
178
+ * @return string
179
+ */
180
+ public function process()
181
+ {
182
+ if ($this->isXhtml === null) {
183
+ $this->isXhtml = (false !== strpos(
184
+ $this->_html,
185
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'
186
+ ));
187
+ }
188
+
189
+ $this->replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
190
+ $this->placeholders = array();
191
+
192
+ // replace SCRIPTs (and minify) with placeholders
193
+ $this->_html = preg_replace_callback(
194
+ '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i',
195
+ array($this, 'removeScriptCB'),
196
+ $this->_html
197
+ );
198
+
199
+ // replace STYLEs (and minify) with placeholders
200
+ $this->_html = preg_replace_callback(
201
+ '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i',
202
+ array($this, 'removeStyleCB'),
203
+ $this->_html
204
+ );
205
+
206
+ // remove HTML comments (not containing IE conditional comments).
207
+ $this->_html = preg_replace_callback(
208
+ '/<!--([\\s\\S]*?)-->/',
209
+ array($this, 'commentCB'),
210
+ $this->_html
211
+ );
212
+
213
+ // replace PREs with placeholders
214
+ $this->_html = preg_replace_callback(
215
+ '/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i',
216
+ array($this, 'removePreCB'),
217
+ $this->_html
218
+ );
219
+
220
+ // replace TEXTAREAs with placeholders
221
+ $this->_html = preg_replace_callback(
222
+ '/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i',
223
+ array($this, 'removeTextareaCB'),
224
+ $this->_html
225
+ );
226
+
227
+ // trim each line.
228
+ // To be done > take into account attribute values that span multiple
229
+ // lines.
230
+ $this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
231
+
232
+ // remove ws around block/undisplayed elements
233
+ $this->_html = preg_replace(
234
+ '/\\s+(<\\/?(?:area|article|aside|base(?:font)?|blockquote|body'
235
+ .'|canvas|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset'
236
+ .'|figcaption|figure|footer|form|frame(?:set)?|h[1-6]|head|header'
237
+ .'|hgroup|hr|html|legend|li|link|main|map|menu|meta|nav|ol'
238
+ .'|opt(?:group|ion)|output|p|param|section'
239
+ .'|t(?:able|body|head|d|h||r|foot|itle)|ul|video)\\b[^>]*>)/i',
240
+ '$1',
241
+ $this->_html
242
+ );
243
+
244
+ // remove ws outside of all elements
245
+ $this->_html = preg_replace(
246
+ '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</',
247
+ '>$1$2$3<',
248
+ $this->_html
249
+ );
250
+
251
+ if ($this->maxMinification) {
252
+ // Strip all multiple spaces to one space
253
+ $this->_html = preg_replace('/\s+/ui', ' ', $this->_html);
254
+ } else {
255
+ // use newlines before 1st attribute in open tags
256
+ // (to limit line lengths)
257
+ $this->_html = preg_replace(
258
+ '/(<[a-z\\-]+)\\s+([^>]+>)/i',
259
+ "$1\n$2",
260
+ $this->_html
261
+ );
262
+ }
263
+
264
+ // fill placeholders
265
+ $this->_html = str_replace(
266
+ array_keys($this->placeholders),
267
+ array_values($this->placeholders),
268
+ $this->_html
269
+ );
270
+ // issue 229: multi-pass to catch scripts that didn't get replaced
271
+ // in textareas
272
+ $this->_html = str_replace(
273
+ array_keys($this->placeholders),
274
+ array_values($this->placeholders),
275
+ $this->_html
276
+ );
277
+
278
+ return $this->_html;
279
+ }
280
+
281
+ public function commentCB($m)
282
+ {
283
+ if ($this->cacheCompatibility) {
284
+ return (0 === strpos($m[1], '[')
285
+ || false !== strpos($m[1], '<![')
286
+ || false !== stripos($m[1], ' ko ')
287
+ || false !== stripos($m[1], ' /ko ')
288
+ || false !== stripos($m[1], 'esi <')
289
+ || false !== stripos($m[1], ' fpc')
290
+ )
291
+ ? $m[0]
292
+ : '';
293
+ } else {
294
+ return (0 === strpos($m[1], '[')
295
+ || false !== strpos($m[1], '<![')
296
+ || false !== stripos($m[1], ' ko ')
297
+ || false !== stripos($m[1], ' /ko ')
298
+ )
299
+ ? $m[0]
300
+ : '';
301
+ }
302
+ }
303
+
304
+ public function reservePlace($content)
305
+ {
306
+ $placeholder = '%' . $this->replacementHash
307
+ . count($this->placeholders) . '%';
308
+ $this->placeholders[$placeholder] = $content;
309
+
310
+ return $placeholder;
311
+ }
312
+
313
+ public function removePreCB($m)
314
+ {
315
+ return $this->reservePlace("<pre{$m[1]}");
316
+ }
317
+
318
+ public function removeTextareaCB($m)
319
+ {
320
+ return $this->reservePlace("<textarea{$m[1]}");
321
+ }
322
+
323
+ public function removeStyleCB($m)
324
+ {
325
+ $openStyle = "<style{$m[1]}";
326
+ $css = $m[2];
327
+ // remove HTML comments
328
+ $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
329
+
330
+ // remove CDATA section markers
331
+ $css = $this->removeCdata($css);
332
+
333
+ // minify
334
+ $css = Mage::helper('apptrian_minify')
335
+ ->minifyCss($css, $this->removeComments);
336
+
337
+ return $this->reservePlace(
338
+ $this->needsCdata($css)
339
+ ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
340
+ : "{$openStyle}{$css}</style>"
341
+ );
342
+ }
343
+
344
+ public function removeScriptCB($m)
345
+ {
346
+ $openScript = "<script{$m[2]}";
347
+ $js = $m[3];
348
+
349
+ // whitespace surrounding? preserve at least one space
350
+ $wsA = ($m[1] === '') ? '' : ' ';
351
+ $wsB = ($m[4] === '') ? '' : ' ';
352
+
353
+ // remove HTML comments (and ending "//" if present)
354
+ if ($this->jsCleanComments) {
355
+ $js = preg_replace(
356
+ '/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/',
357
+ '',
358
+ $js
359
+ );
360
+ }
361
+
362
+ // remove CDATA section markers
363
+ $js = $this->removeCdata($js);
364
+
365
+ if (false !== stripos($openScript, 'type="text/template"')
366
+ || false !== stripos($openScript, 'type="text/x-magento-template"')
367
+ ) {
368
+ $js = Apptrian_Minify_Html::minify($js, $this->options);
369
+ } else {
370
+ // minify
371
+ $js = Mage::helper('apptrian_minify')
372
+ ->minifyJs($js, $this->removeComments);
373
+ }
374
+
375
+ return $this->reservePlace(
376
+ $this->needsCdata($js)
377
+ ? "{$wsA}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$wsB}"
378
+ : "{$wsA}{$openScript}{$js}</script>{$wsB}"
379
+ );
380
+ }
381
+
382
+ public function removeCdata($str)
383
+ {
384
+ return (false !== strpos($str, '<![CDATA['))
385
+ ? str_replace(array('<![CDATA[', ']]>'), '', $str)
386
+ : $str;
387
+ }
388
+
389
+ public function needsCdata($str)
390
+ {
391
+ return ($this->isXhtml && preg_match(
392
+ '/(?:[<&]|\\-\\-|\\]\\]>)/',
393
+ $str
394
+ ));
395
+ }
396
+ }
lib/Apptrian/Minify/Js.php ADDED
@@ -0,0 +1,617 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @category Apptrian
4
+ * @package Apptrian_Minify
5
+ * @author Apptrian
6
+ * @copyright Copyright (c) 2017 Apptrian (http://www.apptrian.com)
7
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License
8
+ */
9
+
10
+ // @codingStandardsIgnoreFile
11
+
12
+ /**
13
+ * This class is taken from tedivm/jshrink library and modified into Magento
14
+ * compatible class. Some additional features are added to it for seamless
15
+ * integration with Magento.
16
+ */
17
+
18
+ /*
19
+ * This file is part of the JShrink package.
20
+ *
21
+ * (c) Robert Hafner <tedivm@tedivm.com>
22
+ *
23
+ * For the full copyright and license information, please view the LICENSE
24
+ * file that was distributed with this source code.
25
+ */
26
+
27
+ /**
28
+ * JShrink
29
+ *
30
+ *
31
+ * @package JShrink
32
+ * @author Robert Hafner <tedivm@tedivm.com>
33
+ */
34
+
35
+ /**
36
+ * Minifier
37
+ *
38
+ * Usage - Minifier::minify($js);
39
+ * Usage - Minifier::minify($js, $options);
40
+ * Usage - Minifier::minify($js, array('flaggedComments' => false));
41
+ *
42
+ * @package JShrink
43
+ * @author Robert Hafner <tedivm@tedivm.com>
44
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
45
+ */
46
+
47
+ class Apptrian_Minify_Js
48
+ {
49
+ /**
50
+ * The input javascript to be minified.
51
+ *
52
+ * @var string
53
+ */
54
+ public $input;
55
+
56
+ /**
57
+ * The location of the character (in the input string) that is next to be
58
+ * processed.
59
+ *
60
+ * @var int
61
+ */
62
+ public $index = 0;
63
+
64
+ /**
65
+ * The first of the characters currently being looked at.
66
+ *
67
+ * @var string
68
+ */
69
+ public $a = '';
70
+
71
+ /**
72
+ * The next character being looked at (after a);
73
+ *
74
+ * @var string
75
+ */
76
+ public $b = '';
77
+
78
+ /**
79
+ * This character is only active when certain look ahead actions take place.
80
+ *
81
+ * @var string
82
+ */
83
+ public $c;
84
+
85
+ /**
86
+ * Contains the options for the current minification process.
87
+ *
88
+ * @var array
89
+ */
90
+ public $options;
91
+
92
+ /**
93
+ * Contains the default options for minification. This array is merged with
94
+ * the one passed in by the user to create the request specific set of
95
+ * options (stored in the $options attribute).
96
+ *
97
+ * @var array
98
+ */
99
+ public static $defaultOptions = array('flaggedComments' => true);
100
+
101
+ /**
102
+ * Contains lock ids which are used to replace certain code patterns and
103
+ * prevent them from being minified
104
+ *
105
+ * @var array
106
+ */
107
+ public $locks = array();
108
+
109
+ /**
110
+ * Takes a string containing javascript and removes unneeded characters in
111
+ * order to shrink the code without altering it's functionality.
112
+ *
113
+ * @param string $js The raw javascript to be minified
114
+ * @param array $options Runtime options in an associative array
115
+ * @throws \Exception
116
+ * @return bool|string
117
+ */
118
+ public static function minify($js, $options = array())
119
+ {
120
+ try {
121
+ ob_start();
122
+
123
+ $jshrink = new Apptrian_Minify_Js();
124
+ $js = $jshrink->lock($js);
125
+ $jshrink->minifyDirectToOutput($js, $options);
126
+
127
+ // Sometimes there's a leading new line, so we trim that out here.
128
+ $js = ltrim(ob_get_clean());
129
+ $js = $jshrink->unlock($js);
130
+ unset($jshrink);
131
+
132
+ return $js;
133
+ } catch (\Exception $e) {
134
+ if (isset($jshrink)) {
135
+ // Since the breakdownScript function probably wasn't finished
136
+ // we clean it out before discarding it.
137
+ $jshrink->clean();
138
+ unset($jshrink);
139
+ }
140
+
141
+ // without this call things get weird, with partially outputted js.
142
+ ob_end_clean();
143
+ throw $e;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Processes a javascript string and outputs only the required characters,
149
+ * stripping out all unneeded characters.
150
+ *
151
+ * @param string $js The raw javascript to be minified
152
+ * @param array $options Various runtime options in an associative array
153
+ */
154
+ public function minifyDirectToOutput($js, $options)
155
+ {
156
+ $this->initialize($js, $options);
157
+ $this->loop();
158
+ $this->clean();
159
+ }
160
+
161
+ /**
162
+ * Initializes internal variables, normalizes new lines,
163
+ *
164
+ * @param string $js The raw javascript to be minified
165
+ * @param array $options Various runtime options in an associative array
166
+ */
167
+ public function initialize($js, $options)
168
+ {
169
+ $this->options = array_merge(static::$defaultOptions, $options);
170
+ $js = str_replace("\r\n", "\n", $js);
171
+ $js = str_replace('/**/', '', $js);
172
+ $this->input = str_replace("\r", "\n", $js);
173
+
174
+ // We add a newline to the end of the script to make it easier to deal
175
+ // with comments at the bottom of the script- this prevents the unclosed
176
+ // comment error that can otherwise occur.
177
+ $this->input .= PHP_EOL;
178
+
179
+ // Populate "a" with a new line, "b" with the first character, before
180
+ // entering the loop
181
+ $this->a = "\n";
182
+ $this->b = $this->getReal();
183
+ }
184
+
185
+ /**
186
+ * The primary action occurs here. This function loops through the input
187
+ * string, outputting anything that's relevant and discarding anything that
188
+ * is not.
189
+ */
190
+ public function loop()
191
+ {
192
+ while ($this->a !== false && $this->a !== null && $this->a !== '') {
193
+ switch ($this->a) {
194
+ // new lines
195
+ case "\n":
196
+ // if the next line is something that can't stand alone
197
+ // preserve the newline
198
+ if (strpos('(-+{[@', $this->b) !== false) {
199
+ echo $this->a;
200
+ $this->saveString();
201
+ break;
202
+ }
203
+
204
+ // if B is a space we skip the rest of the switch block and
205
+ // go down to the string/regex check below, resetting
206
+ // $this->b with getReal
207
+ if ($this->b === ' ') {
208
+ break;
209
+ }
210
+
211
+ // otherwise we treat the newline like a space
212
+
213
+ case ' ':
214
+ if (static::isAlphaNumeric($this->b)) {
215
+ echo $this->a;
216
+ }
217
+
218
+ $this->saveString();
219
+ break;
220
+
221
+ default:
222
+ switch ($this->b) {
223
+ case "\n":
224
+ if (strpos('}])+-"\'', $this->a) !== false) {
225
+ echo $this->a;
226
+ $this->saveString();
227
+ break;
228
+ } else {
229
+ if (static::isAlphaNumeric($this->a)) {
230
+ echo $this->a;
231
+ $this->saveString();
232
+ }
233
+ }
234
+ break;
235
+
236
+ case ' ':
237
+ if (!static::isAlphaNumeric($this->a)) {
238
+ break;
239
+ }
240
+
241
+ // no break
242
+ default:
243
+ // check for some regex that breaks stuff
244
+ if ($this->a === '/'
245
+ && ($this->b === '\'' || $this->b === '"')
246
+ ) {
247
+ $this->saveRegex();
248
+ continue;
249
+ }
250
+
251
+ echo $this->a;
252
+ $this->saveString();
253
+ break;
254
+ }
255
+ }
256
+
257
+ // do reg check of doom
258
+ $this->b = $this->getReal();
259
+
260
+ if (($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) {
261
+ $this->saveRegex();
262
+ }
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Resets attributes that do not need to be stored between requests so that
268
+ * the next request is ready to go. Another reason for this is to make sure
269
+ * the variables are cleared and are not taking up memory.
270
+ */
271
+ public function clean()
272
+ {
273
+ unset($this->input);
274
+ $this->index = 0;
275
+ $this->a = $this->b = '';
276
+ unset($this->c);
277
+ unset($this->options);
278
+ }
279
+
280
+ /**
281
+ * Returns the next string for processing based off of the current index.
282
+ *
283
+ * @return string
284
+ */
285
+ public function getChar()
286
+ {
287
+ // Check to see if we had anything in the look ahead buffer and use that
288
+ if (isset($this->c)) {
289
+ $char = $this->c;
290
+ unset($this->c);
291
+
292
+ // Otherwise we start pulling from the input.
293
+ } else {
294
+ $char = substr($this->input, $this->index, 1);
295
+
296
+ // If the next character doesn't exist return false.
297
+ if (isset($char) && $char === false) {
298
+ return false;
299
+ }
300
+
301
+ // Otherwise increment the pointer and use this char.
302
+ $this->index++;
303
+ }
304
+
305
+ // Normalize all whitespace except for the newline character into a
306
+ // standard space.
307
+ if ($char !== "\n" && ord($char) < 32) {
308
+ return ' ';
309
+ }
310
+
311
+ return $char;
312
+ }
313
+
314
+ /**
315
+ * This function gets the next "real" character. It is essentially a wrapper
316
+ * around the getChar function that skips comments. This has significant
317
+ * performance benefits as the skipping is done using native functions (ie,
318
+ * c code) rather than in script php.
319
+ *
320
+ *
321
+ * @return string Next 'real' character to be processed.
322
+ * @throws \RuntimeException
323
+ */
324
+ public function getReal()
325
+ {
326
+ $startIndex = $this->index;
327
+ $char = $this->getChar();
328
+
329
+ // Check to see if we're potentially in a comment
330
+ if ($char !== '/') {
331
+ return $char;
332
+ }
333
+
334
+ $this->c = $this->getChar();
335
+
336
+ if ($this->c === '/') {
337
+ return $this->processOneLineComments($startIndex);
338
+ } elseif ($this->c === '*') {
339
+ return $this->processMultiLineComments($startIndex);
340
+ }
341
+
342
+ return $char;
343
+ }
344
+
345
+ /**
346
+ * Removed one line comments, with the exception of some very specific types
347
+ * of conditional comments.
348
+ *
349
+ * @param int $startIndex The index point where "getReal" function start
350
+ * @return string
351
+ */
352
+ public function processOneLineComments($startIndex)
353
+ {
354
+ $thirdCommentString = substr($this->input, $this->index, 1);
355
+
356
+ // kill rest of line
357
+ $this->getNext("\n");
358
+
359
+ if ($thirdCommentString == '@') {
360
+ $endPoint = $this->index - $startIndex;
361
+ unset($this->c);
362
+ $char = "\n" . substr($this->input, $startIndex, $endPoint);
363
+ } else {
364
+ // first one is contents of $this->c
365
+ $this->getChar();
366
+ $char = $this->getChar();
367
+ }
368
+
369
+ return $char;
370
+ }
371
+
372
+ /**
373
+ * Skips multiline comments where appropriate, and includes them where
374
+ * needed. Conditional comments and "license" style blocks are preserved.
375
+ *
376
+ * @param int $startIndex The index point where getReal start
377
+ * @return bool|string False if there's no character
378
+ * @throws \RuntimeException Unclosed comments will throw an error
379
+ */
380
+ public function processMultiLineComments($startIndex)
381
+ {
382
+ $this->getChar(); // current C
383
+ $thirdCommentString = $this->getChar();
384
+
385
+ // kill everything up to the next */ if it's there
386
+ if ($this->getNext('*/')) {
387
+ $this->getChar(); // get *
388
+ $this->getChar(); // get /
389
+ $char = $this->getChar(); // get next real character
390
+
391
+ // Now we reinsert conditional comments and YUI-style licensing
392
+ // comments
393
+ if (($this->options['flaggedComments']
394
+ && $thirdCommentString === '!')
395
+ || ($thirdCommentString === '@') ) {
396
+ // If conditional comments or flagged comments are not the first
397
+ // thing in the script we need to echo a and fill it with
398
+ // a space before moving on.
399
+ if ($startIndex > 0) {
400
+ echo $this->a;
401
+ $this->a = " ";
402
+
403
+ // If the comment started on a new line we let it stay on
404
+ // the new line
405
+ if ($this->input[($startIndex - 1)] === "\n") {
406
+ echo "\n";
407
+ }
408
+ }
409
+
410
+ $endPoint = ($this->index - 1) - $startIndex;
411
+ echo substr($this->input, $startIndex, $endPoint);
412
+
413
+ return $char;
414
+ }
415
+ } else {
416
+ $char = false;
417
+ }
418
+
419
+ if ($char === false) {
420
+ throw new \RuntimeException(
421
+ 'Unclosed multiline comment at position: ' . ($this->index - 2)
422
+ );
423
+ }
424
+
425
+ // if we're here c is part of the comment and therefore tossed
426
+ if (isset($this->c)) {
427
+ unset($this->c);
428
+ }
429
+
430
+ return $char;
431
+ }
432
+
433
+ /**
434
+ * Pushes the index ahead to the next instance of the supplied string. If it
435
+ * is found the first character of the string is returned and the index is
436
+ * set to it's position.
437
+ *
438
+ * @param string $string
439
+ * @return string|false Returns the first character of the string or false.
440
+ */
441
+ public function getNext($string)
442
+ {
443
+ // Find the next occurrence of "string" after the current position.
444
+ $pos = strpos($this->input, $string, $this->index);
445
+
446
+ // If it's not there return false.
447
+ if ($pos === false) {
448
+ return false;
449
+ }
450
+
451
+ // Adjust position of index to jump ahead to the asked for string
452
+ $this->index = $pos;
453
+
454
+ // Return the first character of that string.
455
+ return substr($this->input, $this->index, 1);
456
+ }
457
+
458
+ /**
459
+ * When a javascript string is detected this function crawls for the end of
460
+ * it and saves the whole string.
461
+ *
462
+ * @throws \RuntimeException Unclosed strings will throw an error
463
+ */
464
+ public function saveString()
465
+ {
466
+ $startpos = $this->index;
467
+
468
+ // saveString is always called after a gets cleared, so we push b into
469
+ // that spot.
470
+ $this->a = $this->b;
471
+
472
+ // If this isn't a string we don't need to do anything.
473
+ if ($this->a !== "'" && $this->a !== '"') {
474
+ return;
475
+ }
476
+
477
+ // String type is the quote used, " or '
478
+ $stringType = $this->a;
479
+
480
+ // Echo out that starting quote
481
+ echo $this->a;
482
+
483
+ // Loop until the string is done
484
+ while (true) {
485
+ // Grab the very next character and load it into a
486
+ $this->a = $this->getChar();
487
+
488
+ switch ($this->a) {
489
+ // If the string opener (single or double quote) is used
490
+ // output it and break out of the while loop-
491
+ // The string is finished!
492
+ case $stringType:
493
+ break 2;
494
+
495
+ // New lines in strings without line delimiters are bad- actual
496
+ // new lines will be represented by the string \n and not the
497
+ // actual character, so those will be treated just fine using
498
+ // the switch block below.
499
+ case "\n":
500
+ throw new \RuntimeException(
501
+ 'Unclosed string at position: ' . $startpos
502
+ );
503
+ break;
504
+
505
+ // Escaped characters get picked up here. If it's an escaped
506
+ // new line it's not really needed
507
+ case '\\':
508
+ // a is a slash. We want to keep it, and the next character,
509
+ // unless it's a new line. New lines as actual strings will
510
+ // be preserved, but escaped new lines should be reduced.
511
+ $this->b = $this->getChar();
512
+
513
+ // If b is a new line we discard a and b and restart
514
+ // the loop.
515
+ if ($this->b === "\n") {
516
+ break;
517
+ }
518
+
519
+ // echo out the escaped character and restart the loop.
520
+ echo $this->a . $this->b;
521
+ break;
522
+
523
+ // Since we're not dealing with any special cases we simply
524
+ // output the character and continue our loop.
525
+ default:
526
+ echo $this->a;
527
+ }
528
+ }
529
+ }
530
+
531
+ /**
532
+ * When a regular expression is detected this function crawls for the end of
533
+ * it and saves the whole regex.
534
+ *
535
+ * @throws \RuntimeException Unclosed regex will throw an error
536
+ */
537
+ public function saveRegex()
538
+ {
539
+ echo $this->a . $this->b;
540
+
541
+ while (($this->a = $this->getChar()) !== false) {
542
+ if ($this->a === '/') {
543
+ break;
544
+ }
545
+
546
+ if ($this->a === '\\') {
547
+ echo $this->a;
548
+ $this->a = $this->getChar();
549
+ }
550
+
551
+ if ($this->a === "\n") {
552
+ throw new \RuntimeException(
553
+ 'Unclosed regex pattern at position: ' . $this->index
554
+ );
555
+ }
556
+
557
+ echo $this->a;
558
+ }
559
+
560
+ $this->b = $this->getReal();
561
+ }
562
+
563
+ /**
564
+ * Checks to see if a character is alphanumeric.
565
+ *
566
+ * @param string $char Just one character
567
+ * @return bool
568
+ */
569
+ public static function isAlphaNumeric($char)
570
+ {
571
+ return preg_match('/^[\w\$\pL]$/', $char) === 1 || $char == '/';
572
+ }
573
+
574
+ /**
575
+ * Replace patterns in the given string and store the replacement
576
+ *
577
+ * @param string $js The string to lock
578
+ * @return bool
579
+ */
580
+ public function lock($js)
581
+ {
582
+ /* lock things like <code>"asd" + ++x;</code> */
583
+ $lock = '"LOCK---' . crc32(time()) . '"';
584
+
585
+ $matches = array();
586
+ preg_match('/([+-])(\s+)([+-])/S', $js, $matches);
587
+ if (empty($matches)) {
588
+ return $js;
589
+ }
590
+
591
+ $this->locks[$lock] = $matches[2];
592
+
593
+ $js = preg_replace('/([+-])\s+([+-])/S', "$1{$lock}$2", $js);
594
+ /* -- */
595
+
596
+ return $js;
597
+ }
598
+
599
+ /**
600
+ * Replace "locks" with the original characters
601
+ *
602
+ * @param string $js The string to unlock
603
+ * @return bool
604
+ */
605
+ public function unlock($js)
606
+ {
607
+ if (empty($this->locks)) {
608
+ return $js;
609
+ }
610
+
611
+ foreach ($this->locks as $lock => $replacement) {
612
+ $js = str_replace($lock, $replacement, $js);
613
+ }
614
+
615
+ return $js;
616
+ }
617
+ }
lib/CSSmin.php DELETED
@@ -1,777 +0,0 @@
1
- <?php
2
-
3
- /*!
4
- * cssmin.php v2.4.8-4
5
- * Author: Tubal Martin - http://tubalmartin.me/
6
- * Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port
7
- *
8
- * This is a PHP port of the CSS minification tool distributed with YUICompressor,
9
- * itself a port of the cssmin utility by Isaac Schlueter - http://foohack.com/
10
- * Permission is hereby granted to use the PHP version under the same
11
- * conditions as the YUICompressor.
12
- */
13
-
14
- /*!
15
- * YUI Compressor
16
- * http://developer.yahoo.com/yui/compressor/
17
- * Author: Julien Lecomte - http://www.julienlecomte.net/
18
- * Copyright (c) 2013 Yahoo! Inc. All rights reserved.
19
- * The copyrights embodied in the content of this file are licensed
20
- * by Yahoo! Inc. under the BSD (revised) open source license.
21
- */
22
-
23
- class CSSmin
24
- {
25
- const NL = '___YUICSSMIN_PRESERVED_NL___';
26
- const TOKEN = '___YUICSSMIN_PRESERVED_TOKEN_';
27
- const COMMENT = '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_';
28
- const CLASSCOLON = '___YUICSSMIN_PSEUDOCLASSCOLON___';
29
- const QUERY_FRACTION = '___YUICSSMIN_QUERY_FRACTION___';
30
-
31
- private $comments;
32
- private $preserved_tokens;
33
- private $memory_limit;
34
- private $max_execution_time;
35
- private $pcre_backtrack_limit;
36
- private $pcre_recursion_limit;
37
- private $raise_php_limits;
38
-
39
- /**
40
- * @param bool|int $raise_php_limits
41
- * If true, PHP settings will be raised if needed
42
- */
43
- public function __construct($raise_php_limits = TRUE)
44
- {
45
- // Set suggested PHP limits
46
- $this->memory_limit = 128 * 1048576; // 128MB in bytes
47
- $this->max_execution_time = 60; // 1 min
48
- $this->pcre_backtrack_limit = 1000 * 1000;
49
- $this->pcre_recursion_limit = 500 * 1000;
50
-
51
- $this->raise_php_limits = (bool) $raise_php_limits;
52
- }
53
-
54
- /**
55
- * Minify a string of CSS
56
- * @param string $css
57
- * @param int|bool $linebreak_pos
58
- * @return string
59
- */
60
- public function run($css = '', $linebreak_pos = FALSE)
61
- {
62
- if (empty($css)) {
63
- return '';
64
- }
65
-
66
- if ($this->raise_php_limits) {
67
- $this->do_raise_php_limits();
68
- }
69
-
70
- $this->comments = array();
71
- $this->preserved_tokens = array();
72
-
73
- $start_index = 0;
74
- $length = strlen($css);
75
-
76
- $css = $this->extract_data_urls($css);
77
-
78
- // collect all comment blocks...
79
- while (($start_index = $this->index_of($css, '/*', $start_index)) >= 0) {
80
- $end_index = $this->index_of($css, '*/', $start_index + 2);
81
- if ($end_index < 0) {
82
- $end_index = $length;
83
- }
84
- $comment_found = $this->str_slice($css, $start_index + 2, $end_index);
85
- $this->comments[] = $comment_found;
86
- $comment_preserve_string = self::COMMENT . (count($this->comments) - 1) . '___';
87
- $css = $this->str_slice($css, 0, $start_index + 2) . $comment_preserve_string . $this->str_slice($css, $end_index);
88
- // Set correct start_index: Fixes issue #2528130
89
- $start_index = $end_index + 2 + strlen($comment_preserve_string) - strlen($comment_found);
90
- }
91
-
92
- // preserve strings so their content doesn't get accidentally minified
93
- $css = preg_replace_callback('/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S", array($this, 'replace_string'), $css);
94
-
95
- // Let's divide css code in chunks of 5.000 chars aprox.
96
- // Reason: PHP's PCRE functions like preg_replace have a "backtrack limit"
97
- // of 100.000 chars by default (php < 5.3.7) so if we're dealing with really
98
- // long strings and a (sub)pattern matches a number of chars greater than
99
- // the backtrack limit number (i.e. /(.*)/s) PCRE functions may fail silently
100
- // returning NULL and $css would be empty.
101
- $charset = '';
102
- $charset_regexp = '/(@charset)( [^;]+;)/i';
103
- $css_chunks = array();
104
- $css_chunk_length = 5000; // aprox size, not exact
105
- $start_index = 0;
106
- $i = $css_chunk_length; // save initial iterations
107
- $l = strlen($css);
108
-
109
-
110
- // if the number of characters is 5000 or less, do not chunk
111
- if ($l <= $css_chunk_length) {
112
- $css_chunks[] = $css;
113
- } else {
114
- // chunk css code securely
115
- while ($i < $l) {
116
- $i += 50; // save iterations
117
- if ($l - $start_index <= $css_chunk_length || $i >= $l) {
118
- $css_chunks[] = $this->str_slice($css, $start_index);
119
- break;
120
- }
121
- if ($css[$i - 1] === '}' && $i - $start_index > $css_chunk_length) {
122
- // If there are two ending curly braces }} separated or not by spaces,
123
- // join them in the same chunk (i.e. @media blocks)
124
- $next_chunk = substr($css, $i);
125
- if (preg_match('/^\s*\}/', $next_chunk)) {
126
- $i = $i + $this->index_of($next_chunk, '}') + 1;
127
- }
128
-
129
- $css_chunks[] = $this->str_slice($css, $start_index, $i);
130
- $start_index = $i;
131
- }
132
- }
133
- }
134
-
135
- // Minify each chunk
136
- for ($i = 0, $n = count($css_chunks); $i < $n; $i++) {
137
- $css_chunks[$i] = $this->minify($css_chunks[$i], $linebreak_pos);
138
- // Keep the first @charset at-rule found
139
- if (empty($charset) && preg_match($charset_regexp, $css_chunks[$i], $matches)) {
140
- $charset = strtolower($matches[1]) . $matches[2];
141
- }
142
- // Delete all @charset at-rules
143
- $css_chunks[$i] = preg_replace($charset_regexp, '', $css_chunks[$i]);
144
- }
145
-
146
- // Update the first chunk and push the charset to the top of the file.
147
- $css_chunks[0] = $charset . $css_chunks[0];
148
-
149
- return implode('', $css_chunks);
150
- }
151
-
152
- /**
153
- * Sets the memory limit for this script
154
- * @param int|string $limit
155
- */
156
- public function set_memory_limit($limit)
157
- {
158
- $this->memory_limit = $this->normalize_int($limit);
159
- }
160
-
161
- /**
162
- * Sets the maximum execution time for this script
163
- * @param int|string $seconds
164
- */
165
- public function set_max_execution_time($seconds)
166
- {
167
- $this->max_execution_time = (int) $seconds;
168
- }
169
-
170
- /**
171
- * Sets the PCRE backtrack limit for this script
172
- * @param int $limit
173
- */
174
- public function set_pcre_backtrack_limit($limit)
175
- {
176
- $this->pcre_backtrack_limit = (int) $limit;
177
- }
178
-
179
- /**
180
- * Sets the PCRE recursion limit for this script
181
- * @param int $limit
182
- */
183
- public function set_pcre_recursion_limit($limit)
184
- {
185
- $this->pcre_recursion_limit = (int) $limit;
186
- }
187
-
188
- /**
189
- * Try to configure PHP to use at least the suggested minimum settings
190
- */
191
- private function do_raise_php_limits()
192
- {
193
- $php_limits = array(
194
- 'memory_limit' => $this->memory_limit,
195
- 'max_execution_time' => $this->max_execution_time,
196
- 'pcre.backtrack_limit' => $this->pcre_backtrack_limit,
197
- 'pcre.recursion_limit' => $this->pcre_recursion_limit
198
- );
199
-
200
- // If current settings are higher respect them.
201
- foreach ($php_limits as $name => $suggested) {
202
- $current = $this->normalize_int(ini_get($name));
203
- // memory_limit exception: allow -1 for "no memory limit".
204
- if ($current > -1 && ($suggested == -1 || $current < $suggested)) {
205
- ini_set($name, $suggested);
206
- }
207
- }
208
- }
209
-
210
- /**
211
- * Does bulk of the minification
212
- * @param string $css
213
- * @param int|bool $linebreak_pos
214
- * @return string
215
- */
216
- private function minify($css, $linebreak_pos)
217
- {
218
- // strings are safe, now wrestle the comments
219
- for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
220
-
221
- $token = $this->comments[$i];
222
- $placeholder = '/' . self::COMMENT . $i . '___/';
223
-
224
- // ! in the first position of the comment means preserve
225
- // so push to the preserved tokens keeping the !
226
- if (substr($token, 0, 1) === '!') {
227
- $this->preserved_tokens[] = $token;
228
- $token_tring = self::TOKEN . (count($this->preserved_tokens) - 1) . '___';
229
- $css = preg_replace($placeholder, $token_tring, $css, 1);
230
- // Preserve new lines for /*! important comments
231
- $css = preg_replace('/\s*[\n\r\f]+\s*(\/\*'. $token_tring .')/S', self::NL.'$1', $css);
232
- $css = preg_replace('/('. $token_tring .'\*\/)\s*[\n\r\f]+\s*/', '$1'.self::NL, $css);
233
- continue;
234
- }
235
-
236
- // \ in the last position looks like hack for Mac/IE5
237
- // shorten that to /*\*/ and the next one to /**/
238
- if (substr($token, (strlen($token) - 1), 1) === '\\') {
239
- $this->preserved_tokens[] = '\\';
240
- $css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
241
- $i = $i + 1; // attn: advancing the loop
242
- $this->preserved_tokens[] = '';
243
- $css = preg_replace('/' . self::COMMENT . $i . '___/', self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
244
- continue;
245
- }
246
-
247
- // keep empty comments after child selectors (IE7 hack)
248
- // e.g. html >/**/ body
249
- if (strlen($token) === 0) {
250
- $start_index = $this->index_of($css, $this->str_slice($placeholder, 1, -1));
251
- if ($start_index > 2) {
252
- if (substr($css, $start_index - 3, 1) === '>') {
253
- $this->preserved_tokens[] = '';
254
- $css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
255
- }
256
- }
257
- }
258
-
259
- // in all other cases kill the comment
260
- $css = preg_replace('/\/\*' . $this->str_slice($placeholder, 1, -1) . '\*\//', '', $css, 1);
261
- }
262
-
263
-
264
- // Normalize all whitespace strings to single spaces. Easier to work with that way.
265
- $css = preg_replace('/\s+/', ' ', $css);
266
-
267
- // Fix IE7 issue on matrix filters which browser accept whitespaces between Matrix parameters
268
- $css = preg_replace_callback('/\s*filter\:\s*progid:DXImageTransform\.Microsoft\.Matrix\(([^\)]+)\)/', array($this, 'preserve_old_IE_specific_matrix_definition'), $css);
269
-
270
- // Shorten & preserve calculations calc(...) since spaces are important
271
- $css = preg_replace_callback('/calc(\(((?:[^\(\)]+|(?1))*)\))/i', array($this, 'replace_calc'), $css);
272
-
273
- // Replace positive sign from numbers preceded by : or a white-space before the leading space is removed
274
- // +1.2em to 1.2em, +.8px to .8px, +2% to 2%
275
- $css = preg_replace('/((?<!\\\\)\:|\s)\+(\.?\d+)/S', '$1$2', $css);
276
-
277
- // Remove leading zeros from integer and float numbers preceded by : or a white-space
278
- // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05
279
- $css = preg_replace('/((?<!\\\\)\:|\s)(\-?)0+(\.?\d+)/S', '$1$2$3', $css);
280
-
281
- // Remove trailing zeros from float numbers preceded by : or a white-space
282
- // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px
283
- $css = preg_replace('/((?<!\\\\)\:|\s)(\-?)(\d?\.\d+?)0+([^\d])/S', '$1$2$3$4', $css);
284
-
285
- // Remove trailing .0 -> -9.0 to -9
286
- $css = preg_replace('/((?<!\\\\)\:|\s)(\-?\d+)\.0([^\d])/S', '$1$2$3', $css);
287
-
288
- // Replace 0 length numbers with 0
289
- $css = preg_replace('/((?<!\\\\)\:|\s)\-?\.?0+([^\d])/S', '${1}0$2', $css);
290
-
291
- // Remove the spaces before the things that should not have spaces before them.
292
- // But, be careful not to turn "p :link {...}" into "p:link{...}"
293
- // Swap out any pseudo-class colons with the token, and then swap back.
294
- $css = preg_replace_callback('/(?:^|\})[^\{]*\s+\:/', array($this, 'replace_colon'), $css);
295
-
296
- // Remove spaces before the things that should not have spaces before them.
297
- $css = preg_replace('/\s+([\!\{\}\;\:\>\+\(\)\]\~\=,])/', '$1', $css);
298
-
299
- // Restore spaces for !important
300
- $css = preg_replace('/\!important/i', ' !important', $css);
301
-
302
- // bring back the colon
303
- $css = preg_replace('/' . self::CLASSCOLON . '/', ':', $css);
304
-
305
- // retain space for special IE6 cases
306
- $css = preg_replace_callback('/\:first\-(line|letter)(\{|,)/i', array($this, 'lowercase_pseudo_first'), $css);
307
-
308
- // no space after the end of a preserved comment
309
- $css = preg_replace('/\*\/ /', '*/', $css);
310
-
311
- // lowercase some popular @directives
312
- $css = preg_replace_callback('/@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)/i', array($this, 'lowercase_directives'), $css);
313
-
314
- // lowercase some more common pseudo-elements
315
- $css = preg_replace_callback('/:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/i', array($this, 'lowercase_pseudo_elements'), $css);
316
-
317
- // lowercase some more common functions
318
- $css = preg_replace_callback('/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\(/i', array($this, 'lowercase_common_functions'), $css);
319
-
320
- // lower case some common function that can be values
321
- // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us
322
- $css = preg_replace_callback('/([:,\( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)/iS', array($this, 'lowercase_common_functions_values'), $css);
323
-
324
- // Put the space back in some cases, to support stuff like
325
- // @media screen and (-webkit-min-device-pixel-ratio:0){
326
- $css = preg_replace('/\band\(/i', 'and (', $css);
327
-
328
- // Remove the spaces after the things that should not have spaces after them.
329
- $css = preg_replace('/([\!\{\}\:;\>\+\(\[\~\=,])\s+/S', '$1', $css);
330
-
331
- // remove unnecessary semicolons
332
- $css = preg_replace('/;+\}/', '}', $css);
333
-
334
- // Fix for issue: #2528146
335
- // Restore semicolon if the last property is prefixed with a `*` (lte IE7 hack)
336
- // to avoid issues on Symbian S60 3.x browsers.
337
- $css = preg_replace('/(\*[a-z0-9\-]+\s*\:[^;\}]+)(\})/', '$1;$2', $css);
338
-
339
- // Replace 0 <length> and 0 <percentage> values with 0.
340
- // <length> data type: https://developer.mozilla.org/en-US/docs/Web/CSS/length
341
- // <percentage> data type: https://developer.mozilla.org/en-US/docs/Web/CSS/percentage
342
- $css = preg_replace('/([^\\\\]\:|\s)0(?:em|ex|ch|rem|vw|vh|vm|vmin|cm|mm|in|px|pt|pc|%)/iS', '${1}0', $css);
343
-
344
- // 0% step in a keyframe? restore the % unit
345
- $css = preg_replace_callback('/(@[a-z\-]*?keyframes[^\{]+\{)(.*?)(\}\})/iS', array($this, 'replace_keyframe_zero'), $css);
346
-
347
- // Replace 0 0; or 0 0 0; or 0 0 0 0; with 0.
348
- $css = preg_replace('/\:0(?: 0){1,3}(;|\}| \!)/', ':0$1', $css);
349
-
350
- // Fix for issue: #2528142
351
- // Replace text-shadow:0; with text-shadow:0 0 0;
352
- $css = preg_replace('/(text-shadow\:0)(;|\}| \!)/i', '$1 0 0$2', $css);
353
-
354
- // Replace background-position:0; with background-position:0 0;
355
- // same for transform-origin
356
- // Changing -webkit-mask-position: 0 0 to just a single 0 will result in the second parameter defaulting to 50% (center)
357
- $css = preg_replace('/(background\-position|webkit-mask-position|(?:webkit|moz|o|ms|)\-?transform\-origin)\:0(;|\}| \!)/iS', '$1:0 0$2', $css);
358
-
359
- // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space)
360
- // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space)
361
- // This makes it more likely that it'll get further compressed in the next step.
362
- $css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'rgb_to_hex'), $css);
363
- $css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'hsl_to_hex'), $css);
364
-
365
- // Shorten colors from #AABBCC to #ABC or short color name.
366
- $css = $this->compress_hex_colors($css);
367
-
368
- // border: none to border:0, outline: none to outline:0
369
- $css = preg_replace('/(border\-?(?:top|right|bottom|left|)|outline)\:none(;|\}| \!)/iS', '$1:0$2', $css);
370
-
371
- // shorter opacity IE filter
372
- $css = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $css);
373
-
374
- // Find a fraction that is used for Opera's -o-device-pixel-ratio query
375
- // Add token to add the "\" back in later
376
- $css = preg_replace('/\(([a-z\-]+):([0-9]+)\/([0-9]+)\)/i', '($1:$2'. self::QUERY_FRACTION .'$3)', $css);
377
-
378
- // Remove empty rules.
379
- $css = preg_replace('/[^\};\{\/]+\{\}/S', '', $css);
380
-
381
- // Add "/" back to fix Opera -o-device-pixel-ratio query
382
- $css = preg_replace('/'. self::QUERY_FRACTION .'/', '/', $css);
383
-
384
- // Replace multiple semi-colons in a row by a single one
385
- // See SF bug #1980989
386
- $css = preg_replace('/;;+/', ';', $css);
387
-
388
- // Restore new lines for /*! important comments
389
- $css = preg_replace('/'. self::NL .'/', "\n", $css);
390
-
391
- // Lowercase all uppercase properties
392
- $css = preg_replace_callback('/(\{|\;)([A-Z\-]+)(\:)/', array($this, 'lowercase_properties'), $css);
393
-
394
- // Some source control tools don't like it when files containing lines longer
395
- // than, say 8000 characters, are checked in. The linebreak option is used in
396
- // that case to split long lines after a specific column.
397
- if ($linebreak_pos !== FALSE && (int) $linebreak_pos >= 0) {
398
- $linebreak_pos = (int) $linebreak_pos;
399
- $start_index = $i = 0;
400
- while ($i < strlen($css)) {
401
- $i++;
402
- if ($css[$i - 1] === '}' && $i - $start_index > $linebreak_pos) {
403
- $css = $this->str_slice($css, 0, $i) . "\n" . $this->str_slice($css, $i);
404
- $start_index = $i;
405
- }
406
- }
407
- }
408
-
409
- // restore preserved comments and strings in reverse order
410
- for ($i = count($this->preserved_tokens) - 1; $i >= 0; $i--) {
411
- $css = preg_replace('/' . self::TOKEN . $i . '___/', $this->preserved_tokens[$i], $css, 1);
412
- }
413
-
414
- // Trim the final string (for any leading or trailing white spaces)
415
- return trim($css);
416
- }
417
-
418
- /**
419
- * Utility method to replace all data urls with tokens before we start
420
- * compressing, to avoid performance issues running some of the subsequent
421
- * regexes against large strings chunks.
422
- *
423
- * @param string $css
424
- * @return string
425
- */
426
- private function extract_data_urls($css)
427
- {
428
- // Leave data urls alone to increase parse performance.
429
- $max_index = strlen($css) - 1;
430
- $append_index = $index = $last_index = $offset = 0;
431
- $sb = array();
432
- $pattern = '/url\(\s*(["\']?)data\:/i';
433
-
434
- // Since we need to account for non-base64 data urls, we need to handle
435
- // ' and ) being part of the data string. Hence switching to indexOf,
436
- // to determine whether or not we have matching string terminators and
437
- // handling sb appends directly, instead of using matcher.append* methods.
438
-
439
- while (preg_match($pattern, $css, $m, 0, $offset)) {
440
- $index = $this->index_of($css, $m[0], $offset);
441
- $last_index = $index + strlen($m[0]);
442
- $start_index = $index + 4; // "url(".length()
443
- $end_index = $last_index - 1;
444
- $terminator = $m[1]; // ', " or empty (not quoted)
445
- $found_terminator = FALSE;
446
-
447
- if (strlen($terminator) === 0) {
448
- $terminator = ')';
449
- }
450
-
451
- while ($found_terminator === FALSE && $end_index+1 <= $max_index) {
452
- $end_index = $this->index_of($css, $terminator, $end_index + 1);
453
-
454
- // endIndex == 0 doesn't really apply here
455
- if ($end_index > 0 && substr($css, $end_index - 1, 1) !== '\\') {
456
- $found_terminator = TRUE;
457
- if (')' != $terminator) {
458
- $end_index = $this->index_of($css, ')', $end_index);
459
- }
460
- }
461
- }
462
-
463
- // Enough searching, start moving stuff over to the buffer
464
- $sb[] = $this->str_slice($css, $append_index, $index);
465
-
466
- if ($found_terminator) {
467
- $token = $this->str_slice($css, $start_index, $end_index);
468
- $token = preg_replace('/\s+/', '', $token);
469
- $this->preserved_tokens[] = $token;
470
-
471
- $preserver = 'url(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___)';
472
- $sb[] = $preserver;
473
-
474
- $append_index = $end_index + 1;
475
- } else {
476
- // No end terminator found, re-add the whole match. Should we throw/warn here?
477
- $sb[] = $this->str_slice($css, $index, $last_index);
478
- $append_index = $last_index;
479
- }
480
-
481
- $offset = $last_index;
482
- }
483
-
484
- $sb[] = $this->str_slice($css, $append_index);
485
-
486
- return implode('', $sb);
487
- }
488
-
489
- /**
490
- * Utility method to compress hex color values of the form #AABBCC to #ABC or short color name.
491
- *
492
- * DOES NOT compress CSS ID selectors which match the above pattern (which would break things).
493
- * e.g. #AddressForm { ... }
494
- *
495
- * DOES NOT compress IE filters, which have hex color values (which would break things).
496
- * e.g. filter: chroma(color="#FFFFFF");
497
- *
498
- * DOES NOT compress invalid hex values.
499
- * e.g. background-color: #aabbccdd
500
- *
501
- * @param string $css
502
- * @return string
503
- */
504
- private function compress_hex_colors($css)
505
- {
506
- // Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters)
507
- $pattern = '/(\=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS';
508
- $_index = $index = $last_index = $offset = 0;
509
- $sb = array();
510
- // See: http://ajaxmin.codeplex.com/wikipage?title=CSS%20Colors
511
- $short_safe = array(
512
- '#808080' => 'gray',
513
- '#008000' => 'green',
514
- '#800000' => 'maroon',
515
- '#000080' => 'navy',
516
- '#808000' => 'olive',
517
- '#ffa500' => 'orange',
518
- '#800080' => 'purple',
519
- '#c0c0c0' => 'silver',
520
- '#008080' => 'teal',
521
- '#f00' => 'red'
522
- );
523
-
524
- while (preg_match($pattern, $css, $m, 0, $offset)) {
525
- $index = $this->index_of($css, $m[0], $offset);
526
- $last_index = $index + strlen($m[0]);
527
- $is_filter = $m[1] !== null && $m[1] !== '';
528
-
529
- $sb[] = $this->str_slice($css, $_index, $index);
530
-
531
- if ($is_filter) {
532
- // Restore, maintain case, otherwise filter will break
533
- $sb[] = $m[1] . '#' . $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7];
534
- } else {
535
- if (strtolower($m[2]) == strtolower($m[3]) &&
536
- strtolower($m[4]) == strtolower($m[5]) &&
537
- strtolower($m[6]) == strtolower($m[7])) {
538
- // Compress.
539
- $hex = '#' . strtolower($m[3] . $m[5] . $m[7]);
540
- } else {
541
- // Non compressible color, restore but lower case.
542
- $hex = '#' . strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]);
543
- }
544
- // replace Hex colors to short safe color names
545
- $sb[] = array_key_exists($hex, $short_safe) ? $short_safe[$hex] : $hex;
546
- }
547
-
548
- $_index = $offset = $last_index - strlen($m[8]);
549
- }
550
-
551
- $sb[] = $this->str_slice($css, $_index);
552
-
553
- return implode('', $sb);
554
- }
555
-
556
- /* CALLBACKS
557
- * ---------------------------------------------------------------------------------------------
558
- */
559
-
560
- private function replace_string($matches)
561
- {
562
- $match = $matches[0];
563
- $quote = substr($match, 0, 1);
564
- // Must use addcslashes in PHP to avoid parsing of backslashes
565
- $match = addcslashes($this->str_slice($match, 1, -1), '\\');
566
-
567
- // maybe the string contains a comment-like substring?
568
- // one, maybe more? put'em back then
569
- if (($pos = $this->index_of($match, self::COMMENT)) >= 0) {
570
- for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
571
- $match = preg_replace('/' . self::COMMENT . $i . '___/', $this->comments[$i], $match, 1);
572
- }
573
- }
574
-
575
- // minify alpha opacity in filter strings
576
- $match = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $match);
577
-
578
- $this->preserved_tokens[] = $match;
579
- return $quote . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . $quote;
580
- }
581
-
582
- private function replace_colon($matches)
583
- {
584
- return preg_replace('/\:/', self::CLASSCOLON, $matches[0]);
585
- }
586
-
587
- private function replace_calc($matches)
588
- {
589
- $this->preserved_tokens[] = trim(preg_replace('/\s*([\*\/\(\),])\s*/', '$1', $matches[2]));
590
- return 'calc('. self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')';
591
- }
592
-
593
- private function preserve_old_IE_specific_matrix_definition($matches)
594
- {
595
- $this->preserved_tokens[] = $matches[1];
596
- return 'filter:progid:DXImageTransform.Microsoft.Matrix(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')';
597
- }
598
-
599
- private function replace_keyframe_zero($matches)
600
- {
601
- return $matches[1] . preg_replace('/0(\{|,[^\)\{]+\{)/', '0%$1', $matches[2]) . $matches[3];
602
- }
603
-
604
- private function rgb_to_hex($matches)
605
- {
606
- // Support for percentage values rgb(100%, 0%, 45%);
607
- if ($this->index_of($matches[1], '%') >= 0){
608
- $rgbcolors = explode(',', str_replace('%', '', $matches[1]));
609
- for ($i = 0; $i < count($rgbcolors); $i++) {
610
- $rgbcolors[$i] = $this->round_number(floatval($rgbcolors[$i]) * 2.55);
611
- }
612
- } else {
613
- $rgbcolors = explode(',', $matches[1]);
614
- }
615
-
616
- // Values outside the sRGB color space should be clipped (0-255)
617
- for ($i = 0; $i < count($rgbcolors); $i++) {
618
- $rgbcolors[$i] = $this->clamp_number(intval($rgbcolors[$i], 10), 0, 255);
619
- $rgbcolors[$i] = sprintf("%02x", $rgbcolors[$i]);
620
- }
621
-
622
- // Fix for issue #2528093
623
- if (!preg_match('/[\s\,\);\}]/', $matches[2])){
624
- $matches[2] = ' ' . $matches[2];
625
- }
626
-
627
- return '#' . implode('', $rgbcolors) . $matches[2];
628
- }
629
-
630
- private function hsl_to_hex($matches)
631
- {
632
- $values = explode(',', str_replace('%', '', $matches[1]));
633
- $h = floatval($values[0]);
634
- $s = floatval($values[1]);
635
- $l = floatval($values[2]);
636
-
637
- // Wrap and clamp, then fraction!
638
- $h = ((($h % 360) + 360) % 360) / 360;
639
- $s = $this->clamp_number($s, 0, 100) / 100;
640
- $l = $this->clamp_number($l, 0, 100) / 100;
641
-
642
- if ($s == 0) {
643
- $r = $g = $b = $this->round_number(255 * $l);
644
- } else {
645
- $v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l);
646
- $v1 = (2 * $l) - $v2;
647
- $r = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h + (1/3)));
648
- $g = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h));
649
- $b = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h - (1/3)));
650
- }
651
-
652
- return $this->rgb_to_hex(array('', $r.','.$g.','.$b, $matches[2]));
653
- }
654
-
655
- private function lowercase_pseudo_first($matches)
656
- {
657
- return ':first-'. strtolower($matches[1]) .' '. $matches[2];
658
- }
659
-
660
- private function lowercase_directives($matches)
661
- {
662
- return '@'. strtolower($matches[1]);
663
- }
664
-
665
- private function lowercase_pseudo_elements($matches)
666
- {
667
- return ':'. strtolower($matches[1]);
668
- }
669
-
670
- private function lowercase_common_functions($matches)
671
- {
672
- return ':'. strtolower($matches[1]) .'(';
673
- }
674
-
675
- private function lowercase_common_functions_values($matches)
676
- {
677
- return $matches[1] . strtolower($matches[2]);
678
- }
679
-
680
- private function lowercase_properties($matches)
681
- {
682
- return $matches[1].strtolower($matches[2]).$matches[3];
683
- }
684
-
685
- /* HELPERS
686
- * ---------------------------------------------------------------------------------------------
687
- */
688
-
689
- private function hue_to_rgb($v1, $v2, $vh)
690
- {
691
- $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh);
692
- if ($vh * 6 < 1) return $v1 + ($v2 - $v1) * 6 * $vh;
693
- if ($vh * 2 < 1) return $v2;
694
- if ($vh * 3 < 2) return $v1 + ($v2 - $v1) * ((2/3) - $vh) * 6;
695
- return $v1;
696
- }
697
-
698
- private function round_number($n)
699
- {
700
- return intval(floor(floatval($n) + 0.5), 10);
701
- }
702
-
703
- private function clamp_number($n, $min, $max)
704
- {
705
- return min(max($n, $min), $max);
706
- }
707
-
708
- /**
709
- * PHP port of Javascript's "indexOf" function for strings only
710
- * Author: Tubal Martin http://blog.margenn.com
711
- *
712
- * @param string $haystack
713
- * @param string $needle
714
- * @param int $offset index (optional)
715
- * @return int
716
- */
717
- private function index_of($haystack, $needle, $offset = 0)
718
- {
719
- $index = strpos($haystack, $needle, $offset);
720
-
721
- return ($index !== FALSE) ? $index : -1;
722
- }
723
-
724
- /**
725
- * PHP port of Javascript's "slice" function for strings only
726
- * Author: Tubal Martin http://blog.margenn.com
727
- * Tests: http://margenn.com/tubal/str_slice/
728
- *
729
- * @param string $str
730
- * @param int $start index
731
- * @param int|bool $end index (optional)
732
- * @return string
733
- */
734
- private function str_slice($str, $start = 0, $end = FALSE)
735
- {
736
- if ($end !== FALSE && ($start < 0 || $end <= 0)) {
737
- $max = strlen($str);
738
-
739
- if ($start < 0) {
740
- if (($start = $max + $start) < 0) {
741
- return '';
742
- }
743
- }
744
-
745
- if ($end < 0) {
746
- if (($end = $max + $end) < 0) {
747
- return '';
748
- }
749
- }
750
-
751
- if ($end <= $start) {
752
- return '';
753
- }
754
- }
755
-
756
- $slice = ($end === FALSE) ? substr($str, $start) : substr($str, $start, $end - $start);
757
- return ($slice === FALSE) ? '' : $slice;
758
- }
759
-
760
- /**
761
- * Convert strings like "64M" or "30" to int values
762
- * @param mixed $size
763
- * @return int
764
- */
765
- private function normalize_int($size)
766
- {
767
- if (is_string($size)) {
768
- switch (substr($size, -1)) {
769
- case 'M': case 'm': return $size * 1048576;
770
- case 'K': case 'k': return $size * 1024;
771
- case 'G': case 'g': return $size * 1073741824;
772
- }
773
- }
774
-
775
- return (int) $size;
776
- }
777
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/DooDigestAuth.php DELETED
@@ -1,121 +0,0 @@
1
- <?php
2
- /**
3
- * DooDigestAuth class file.
4
- *
5
- * @author Leng Sheng Hong <darkredz@gmail.com>
6
- * @link http://www.doophp.com/
7
- * @copyright Copyright &copy; 2009 Leng Sheng Hong
8
- * @license http://www.doophp.com/license
9
- */
10
-
11
- /**
12
- * Handles HTTP digest authentication
13
- *
14
- * <p>HTTP digest authentication can be used with the URI router.
15
- * HTTP digest is much more recommended over the use of HTTP Basic auth which doesn't provide any encryption.
16
- * If you are running PHP on Apache in CGI/FastCGI mode, you would need to
17
- * add the following line to your .htaccess for digest auth to work correctly.</p>
18
- * <code>RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]</code>
19
- *
20
- * <p>This class is tested under Apache 2.2 and Cherokee web server. It should work in both mod_php and cgi mode.</p>
21
- *
22
- * @author Leng Sheng Hong <darkredz@gmail.com>
23
- * @version $Id: DooDigestAuth.php 1000 2009-07-7 18:27:22
24
- * @package doo.auth
25
- * @since 1.0
26
- */
27
- class DooDigestAuth{
28
-
29
- /**
30
- * Authenticate against a list of username and passwords.
31
- *
32
- * <p>HTTP Digest Authentication doesn't work with PHP in CGI mode,
33
- * you have to add this into your .htaccess <code>RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]</code></p>
34
- *
35
- * @param string $realm Name of the authentication session
36
- * @param array $users An assoc array of username and password: array('uname1'=>'pwd1', 'uname2'=>'pwd2')
37
- * @param string $fail_msg Message to be displayed if the User cancel the login
38
- * @param string $fail_url URL to be redirect if the User cancel the login
39
- * @return string The username if login success.
40
- */
41
- public static function http_auth($realm, $users, $fail_msg=NULL, $fail_url=NULL){
42
- $realm = "Restricted area - $realm";
43
-
44
- //user => password
45
- //$users = array('admin' => '1234', 'guest' => 'guest');
46
- if(!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && strpos($_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 'Digest')===0){
47
- $_SERVER['PHP_AUTH_DIGEST'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
48
- }
49
-
50
- if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
51
- header('WWW-Authenticate: Digest realm="'.$realm.
52
- '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
53
- header('HTTP/1.1 401 Unauthorized');
54
- if($fail_msg!=NULL)
55
- die($fail_msg);
56
- if($fail_url!=NULL)
57
- die("<script>window.location.href = '$fail_url'</script>");
58
- exit;
59
- }
60
-
61
- // analyze the PHP_AUTH_DIGEST variable
62
- if (!($data = self::http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || !isset($users[$data['username']])){
63
- header('WWW-Authenticate: Digest realm="'.$realm.
64
- '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
65
- header('HTTP/1.1 401 Unauthorized');
66
- if($fail_msg!=NULL)
67
- die($fail_msg);
68
- if($fail_url!=NULL)
69
- die("<script>window.location.href = '$fail_url'</script>");
70
- exit;
71
- }
72
-
73
- // generate the valid response
74
- $A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
75
- $A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
76
- $valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
77
-
78
- if ($data['response'] != $valid_response){
79
- header('HTTP/1.1 401 Unauthorized');
80
- header('WWW-Authenticate: Digest realm="'.$realm.
81
- '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
82
- if($fail_msg!=NULL)
83
- die($fail_msg);
84
- if($fail_url!=NULL)
85
- die("<script>window.location.href = '$fail_url'</script>");
86
- exit;
87
- }
88
-
89
- // ok, valid username & password
90
- return $data['username'];
91
- }
92
-
93
- /**
94
- * Method to parse the http auth header, works with IE.
95
- *
96
- * Internet Explorer returns a qop="xxxxxxxxxxx" in the header instead of qop=xxxxxxxxxxx as most browsers do.
97
- *
98
- * @param string $txt header string to parse
99
- * @return array An assoc array of the digest auth session
100
- */
101
- private static function http_digest_parse($txt)
102
- {
103
- $res = preg_match("/username=\"([^\"]+)\"/i", $txt, $match);
104
- $data['username'] = (isset($match[1]))?$match[1]:null;
105
- $res = preg_match('/nonce=\"([^\"]+)\"/i', $txt, $match);
106
- $data['nonce'] = $match[1];
107
- $res = preg_match('/nc=([0-9]+)/i', $txt, $match);
108
- $data['nc'] = $match[1];
109
- $res = preg_match('/cnonce=\"([^\"]+)\"/i', $txt, $match);
110
- $data['cnonce'] = $match[1];
111
- $res = preg_match('/qop=([^,]+)/i', $txt, $match);
112
- $data['qop'] = str_replace('"','',$match[1]);
113
- $res = preg_match('/uri=\"([^\"]+)\"/i', $txt, $match);
114
- $data['uri'] = $match[1];
115
- $res = preg_match('/response=\"([^\"]+)\"/i', $txt, $match);
116
- $data['response'] = $match[1];
117
- return $data;
118
- }
119
-
120
-
121
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/FirePHP.php DELETED
@@ -1,1370 +0,0 @@
1
- <?php
2
- /**
3
- * *** BEGIN LICENSE BLOCK *****
4
- *
5
- * This file is part of FirePHP (http://www.firephp.org/).
6
- *
7
- * Software License Agreement (New BSD License)
8
- *
9
- * Copyright (c) 2006-2008, Christoph Dorn
10
- * All rights reserved.
11
- *
12
- * Redistribution and use in source and binary forms, with or without modification,
13
- * are permitted provided that the following conditions are met:
14
- *
15
- * * Redistributions of source code must retain the above copyright notice,
16
- * this list of conditions and the following disclaimer.
17
- *
18
- * * Redistributions in binary form must reproduce the above copyright notice,
19
- * this list of conditions and the following disclaimer in the documentation
20
- * and/or other materials provided with the distribution.
21
- *
22
- * * Neither the name of Christoph Dorn nor the names of its
23
- * contributors may be used to endorse or promote products derived from this
24
- * software without specific prior written permission.
25
- *
26
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
27
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
28
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
30
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
33
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36
- *
37
- * ***** END LICENSE BLOCK *****
38
- *
39
- * @copyright Copyright (C) 2007-2008 Christoph Dorn
40
- * @author Christoph Dorn <christoph@christophdorn.com>
41
- * @license http://www.opensource.org/licenses/bsd-license.php
42
- * @package FirePHP
43
- */
44
-
45
-
46
- /**
47
- * Sends the given data to the FirePHP Firefox Extension.
48
- * The data can be displayed in the Firebug Console or in the
49
- * "Server" request tab.
50
- *
51
- * For more information see: http://www.firephp.org/
52
- *
53
- * @copyright Copyright (C) 2007-2008 Christoph Dorn
54
- * @author Christoph Dorn <christoph@christophdorn.com>
55
- * @license http://www.opensource.org/licenses/bsd-license.php
56
- * @package FirePHP
57
- */
58
- class FirePHP {
59
-
60
- /**
61
- * FirePHP version
62
- *
63
- * @var string
64
- */
65
- const VERSION = '0.2.0';
66
-
67
- /**
68
- * Firebug LOG level
69
- *
70
- * Logs a message to firebug console.
71
- *
72
- * @var string
73
- */
74
- const LOG = 'LOG';
75
-
76
- /**
77
- * Firebug INFO level
78
- *
79
- * Logs a message to firebug console and displays an info icon before the message.
80
- *
81
- * @var string
82
- */
83
- const INFO = 'INFO';
84
-
85
- /**
86
- * Firebug WARN level
87
- *
88
- * Logs a message to firebug console, displays an warning icon before the message and colors the line turquoise.
89
- *
90
- * @var string
91
- */
92
- const WARN = 'WARN';
93
-
94
- /**
95
- * Firebug ERROR level
96
- *
97
- * Logs a message to firebug console, displays an error icon before the message and colors the line yellow. Also increments the firebug error count.
98
- *
99
- * @var string
100
- */
101
- const ERROR = 'ERROR';
102
-
103
- /**
104
- * Dumps a variable to firebug's server panel
105
- *
106
- * @var string
107
- */
108
- const DUMP = 'DUMP';
109
-
110
- /**
111
- * Displays a stack trace in firebug console
112
- *
113
- * @var string
114
- */
115
- const TRACE = 'TRACE';
116
-
117
- /**
118
- * Displays an exception in firebug console
119
- *
120
- * Increments the firebug error count.
121
- *
122
- * @var string
123
- */
124
- const EXCEPTION = 'EXCEPTION';
125
-
126
- /**
127
- * Displays an table in firebug console
128
- *
129
- * @var string
130
- */
131
- const TABLE = 'TABLE';
132
-
133
- /**
134
- * Starts a group in firebug console
135
- *
136
- * @var string
137
- */
138
- const GROUP_START = 'GROUP_START';
139
-
140
- /**
141
- * Ends a group in firebug console
142
- *
143
- * @var string
144
- */
145
- const GROUP_END = 'GROUP_END';
146
-
147
- /**
148
- * Singleton instance of FirePHP
149
- *
150
- * @var FirePHP
151
- */
152
- protected static $instance = null;
153
-
154
- /**
155
- * Wildfire protocol message index
156
- *
157
- * @var int
158
- */
159
- protected $messageIndex = 1;
160
-
161
- /**
162
- * Options for the library
163
- *
164
- * @var array
165
- */
166
- protected $options = array();
167
-
168
- /**
169
- * Filters used to exclude object members when encoding
170
- *
171
- * @var array
172
- */
173
- protected $objectFilters = array();
174
-
175
- /**
176
- * A stack of objects used to detect recursion during object encoding
177
- *
178
- * @var object
179
- */
180
- protected $objectStack = array();
181
-
182
- /**
183
- * Flag to enable/disable logging
184
- *
185
- * @var boolean
186
- */
187
- protected $enabled = true;
188
-
189
- /**
190
- * The object constructor
191
- */
192
- function __construct() {
193
- $this->options['maxObjectDepth'] = 10;
194
- $this->options['maxArrayDepth'] = 20;
195
- $this->options['useNativeJsonEncode'] = true;
196
- $this->options['includeLineNumbers'] = true;
197
- }
198
-
199
- /**
200
- * When the object gets serialized only include specific object members.
201
- *
202
- * @return array
203
- */
204
- public function __sleep() {
205
- return array('options','objectFilters','enabled');
206
- }
207
-
208
- /**
209
- * Gets singleton instance of FirePHP
210
- *
211
- * @param boolean $AutoCreate
212
- * @return FirePHP
213
- */
214
- public static function getInstance($AutoCreate=false) {
215
- if($AutoCreate===true && !self::$instance) {
216
- self::init();
217
- }
218
- return self::$instance;
219
- }
220
-
221
- /**
222
- * Creates FirePHP object and stores it for singleton access
223
- *
224
- * @return FirePHP
225
- */
226
- public static function init() {
227
- return self::$instance = new self();
228
- }
229
-
230
- /**
231
- * Enable and disable logging to Firebug
232
- *
233
- * @param boolean $Enabled TRUE to enable, FALSE to disable
234
- * @return void
235
- */
236
- public function setEnabled($Enabled) {
237
- $this->enabled = $Enabled;
238
- }
239
-
240
- /**
241
- * Check if logging is enabled
242
- *
243
- * @return boolean TRUE if enabled
244
- */
245
- public function getEnabled() {
246
- return $this->enabled;
247
- }
248
-
249
- /**
250
- * Specify a filter to be used when encoding an object
251
- *
252
- * Filters are used to exclude object members.
253
- *
254
- * @param string $Class The class name of the object
255
- * @param array $Filter An array or members to exclude
256
- * @return void
257
- */
258
- public function setObjectFilter($Class, $Filter) {
259
- $this->objectFilters[$Class] = $Filter;
260
- }
261
-
262
- /**
263
- * Set some options for the library
264
- *
265
- * Options:
266
- * - maxObjectDepth: The maximum depth to traverse objects (default: 10)
267
- * - maxArrayDepth: The maximum depth to traverse arrays (default: 20)
268
- * - useNativeJsonEncode: If true will use json_encode() (default: true)
269
- * - includeLineNumbers: If true will include line numbers and filenames (default: true)
270
- *
271
- * @param array $Options The options to be set
272
- * @return void
273
- */
274
- public function setOptions($Options) {
275
- $this->options = array_merge($this->options,$Options);
276
- }
277
-
278
- /**
279
- * Register FirePHP as your error handler
280
- *
281
- * Will throw exceptions for each php error.
282
- */
283
- public function registerErrorHandler()
284
- {
285
- //NOTE: The following errors will not be caught by this error handler:
286
- // E_ERROR, E_PARSE, E_CORE_ERROR,
287
- // E_CORE_WARNING, E_COMPILE_ERROR,
288
- // E_COMPILE_WARNING, E_STRICT
289
-
290
- set_error_handler(array($this,'errorHandler'));
291
- }
292
-
293
- /**
294
- * FirePHP's error handler
295
- *
296
- * Throws exception for each php error that will occur.
297
- *
298
- * @param int $errno
299
- * @param string $errstr
300
- * @param string $errfile
301
- * @param int $errline
302
- * @param array $errcontext
303
- */
304
- public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext)
305
- {
306
- // Don't throw exception if error reporting is switched off
307
- if (error_reporting() == 0) {
308
- return;
309
- }
310
- // Only throw exceptions for errors we are asking for
311
- if (error_reporting() & $errno) {
312
- throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
313
- }
314
- }
315
-
316
- /**
317
- * Register FirePHP as your exception handler
318
- */
319
- public function registerExceptionHandler()
320
- {
321
- set_exception_handler(array($this,'exceptionHandler'));
322
- }
323
-
324
- /**
325
- * FirePHP's exception handler
326
- *
327
- * Logs all exceptions to your firebug console and then stops the script.
328
- *
329
- * @param Exception $Exception
330
- * @throws Exception
331
- */
332
- function exceptionHandler($Exception) {
333
- $this->fb($Exception);
334
- }
335
-
336
- /**
337
- * Set custom processor url for FirePHP
338
- *
339
- * @param string $URL
340
- */
341
- public function setProcessorUrl($URL)
342
- {
343
- $this->setHeader('X-FirePHP-ProcessorURL', $URL);
344
- }
345
-
346
- /**
347
- * Set custom renderer url for FirePHP
348
- *
349
- * @param string $URL
350
- */
351
- public function setRendererUrl($URL)
352
- {
353
- $this->setHeader('X-FirePHP-RendererURL', $URL);
354
- }
355
-
356
- /**
357
- * Start a group for following messages
358
- *
359
- * @param string $Name
360
- * @return true
361
- * @throws Exception
362
- */
363
- public function group($Name) {
364
- return $this->fb(null, $Name, FirePHP::GROUP_START);
365
- }
366
-
367
- /**
368
- * Ends a group you have started before
369
- *
370
- * @return true
371
- * @throws Exception
372
- */
373
- public function groupEnd() {
374
- return $this->fb(null, null, FirePHP::GROUP_END);
375
- }
376
-
377
- /**
378
- * Log object with label to firebug console
379
- *
380
- * @see FirePHP::LOG
381
- * @param mixes $Object
382
- * @param string $Label
383
- * @return true
384
- * @throws Exception
385
- */
386
- public function log($Object, $Label=null) {
387
- return $this->fb($Object, $Label, FirePHP::LOG);
388
- }
389
-
390
- /**
391
- * Log object with label to firebug console
392
- *
393
- * @see FirePHP::INFO
394
- * @param mixes $Object
395
- * @param string $Label
396
- * @return true
397
- * @throws Exception
398
- */
399
- public function info($Object, $Label=null) {
400
- return $this->fb($Object, $Label, FirePHP::INFO);
401
- }
402
-
403
- /**
404
- * Log object with label to firebug console
405
- *
406
- * @see FirePHP::WARN
407
- * @param mixes $Object
408
- * @param string $Label
409
- * @return true
410
- * @throws Exception
411
- */
412
- public function warn($Object, $Label=null) {
413
- return $this->fb($Object, $Label, FirePHP::WARN);
414
- }
415
-
416
- /**
417
- * Log object with label to firebug console
418
- *
419
- * @see FirePHP::ERROR
420
- * @param mixes $Object
421
- * @param string $Label
422
- * @return true
423
- * @throws Exception
424
- */
425
- public function error($Object, $Label=null) {
426
- return $this->fb($Object, $Label, FirePHP::ERROR);
427
- }
428
-
429
- /**
430
- * Dumps key and variable to firebug server panel
431
- *
432
- * @see FirePHP::DUMP
433
- * @param string $Key
434
- * @param mixed $Variable
435
- * @return true
436
- * @throws Exception
437
- */
438
- public function dump($Key, $Variable) {
439
- return $this->fb($Variable, $Key, FirePHP::DUMP);
440
- }
441
-
442
- /**
443
- * Log a trace in the firebug console
444
- *
445
- * @see FirePHP::TRACE
446
- * @param string $Label
447
- * @return true
448
- * @throws Exception
449
- */
450
- public function trace($Label) {
451
- return $this->fb($Label, FirePHP::TRACE);
452
- }
453
-
454
- /**
455
- * Log a table in the firebug console
456
- *
457
- * @see FirePHP::TABLE
458
- * @param string $Label
459
- * @param string $Table
460
- * @return true
461
- * @throws Exception
462
- */
463
- public function table($Label, $Table) {
464
- return $this->fb($Table, $Label, FirePHP::TABLE);
465
- }
466
-
467
- /**
468
- * Check if FirePHP is installed on client
469
- *
470
- * @return boolean
471
- */
472
- public function detectClientExtension() {
473
- /* Check if FirePHP is installed on client */
474
- if(!@preg_match_all('/\sFirePHP\/([\.|\d]*)\s?/si',$this->getUserAgent(),$m) ||
475
- !version_compare($m[1][0],'0.0.6','>=')) {
476
- return false;
477
- }
478
- return true;
479
- }
480
-
481
- /**
482
- * Log varible to Firebug
483
- *
484
- * @see http://www.firephp.org/Wiki/Reference/Fb
485
- * @param mixed $Object The variable to be logged
486
- * @return true Return TRUE if message was added to headers, FALSE otherwise
487
- * @throws Exception
488
- */
489
- public function fb($Object) {
490
-
491
- if(!$this->enabled) {
492
- return false;
493
- }
494
-
495
- if (headers_sent($filename, $linenum)) {
496
- throw $this->newException('Headers already sent in '.$filename.' on line '.$linenum.'. Cannot send log data to FirePHP. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.');
497
- }
498
-
499
- $Type = null;
500
- $Label = null;
501
-
502
- if(func_num_args()==1) {
503
- } else
504
- if(func_num_args()==2) {
505
- switch(func_get_arg(1)) {
506
- case self::LOG:
507
- case self::INFO:
508
- case self::WARN:
509
- case self::ERROR:
510
- case self::DUMP:
511
- case self::TRACE:
512
- case self::EXCEPTION:
513
- case self::TABLE:
514
- case self::GROUP_START:
515
- case self::GROUP_END:
516
- $Type = func_get_arg(1);
517
- break;
518
- default:
519
- $Label = func_get_arg(1);
520
- break;
521
- }
522
- } else
523
- if(func_num_args()==3) {
524
- $Type = func_get_arg(2);
525
- $Label = func_get_arg(1);
526
- } else {
527
- throw $this->newException('Wrong number of arguments to fb() function!');
528
- }
529
-
530
-
531
- if(!$this->detectClientExtension()) {
532
- return false;
533
- }
534
-
535
- $meta = array();
536
- $skipFinalObjectEncode = false;
537
-
538
- if($Object instanceof Exception) {
539
-
540
- $meta['file'] = $this->_escapeTraceFile($Object->getFile());
541
- $meta['line'] = $Object->getLine();
542
-
543
- $trace = $Object->getTrace();
544
- if($Object instanceof ErrorException
545
- && isset($trace[0]['function'])
546
- && $trace[0]['function']=='errorHandler'
547
- && isset($trace[0]['class'])
548
- && $trace[0]['class']=='FirePHP') {
549
-
550
- $severity = false;
551
- switch($Object->getSeverity()) {
552
- case E_WARNING: $severity = 'E_WARNING'; break;
553
- case E_NOTICE: $severity = 'E_NOTICE'; break;
554
- case E_USER_ERROR: $severity = 'E_USER_ERROR'; break;
555
- case E_USER_WARNING: $severity = 'E_USER_WARNING'; break;
556
- case E_USER_NOTICE: $severity = 'E_USER_NOTICE'; break;
557
- case E_STRICT: $severity = 'E_STRICT'; break;
558
- case E_RECOVERABLE_ERROR: $severity = 'E_RECOVERABLE_ERROR'; break;
559
- case E_DEPRECATED: $severity = 'E_DEPRECATED'; break;
560
- case E_USER_DEPRECATED: $severity = 'E_USER_DEPRECATED'; break;
561
- }
562
-
563
- $Object = array('Class'=>get_class($Object),
564
- 'Message'=>$severity.': '.$Object->getMessage(),
565
- 'File'=>$this->_escapeTraceFile($Object->getFile()),
566
- 'Line'=>$Object->getLine(),
567
- 'Type'=>'trigger',
568
- 'Trace'=>$this->_escapeTrace(array_splice($trace,2)));
569
- $skipFinalObjectEncode = true;
570
- } else {
571
- $Object = array('Class'=>get_class($Object),
572
- 'Message'=>$Object->getMessage(),
573
- 'File'=>$this->_escapeTraceFile($Object->getFile()),
574
- 'Line'=>$Object->getLine(),
575
- 'Type'=>'throw',
576
- 'Trace'=>$this->_escapeTrace($trace));
577
- $skipFinalObjectEncode = true;
578
- }
579
- $Type = self::EXCEPTION;
580
-
581
- } else
582
- if($Type==self::TRACE) {
583
-
584
- $trace = debug_backtrace();
585
- if(!$trace) return false;
586
- for( $i=0 ; $i<sizeof($trace) ; $i++ ) {
587
-
588
- if(isset($trace[$i]['class'])
589
- && isset($trace[$i]['file'])
590
- && ($trace[$i]['class']=='FirePHP'
591
- || $trace[$i]['class']=='FB')
592
- && (substr($this->_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php'
593
- || substr($this->_standardizePath($trace[$i]['file']),-29,29)=='FirePHPCore/FirePHP.class.php')) {
594
- /* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */
595
- } else
596
- if(isset($trace[$i]['class'])
597
- && isset($trace[$i+1]['file'])
598
- && $trace[$i]['class']=='FirePHP'
599
- && substr($this->_standardizePath($trace[$i+1]['file']),-18,18)=='FirePHPCore/fb.php') {
600
- /* Skip fb() */
601
- } else
602
- if($trace[$i]['function']=='fb'
603
- || $trace[$i]['function']=='trace'
604
- || $trace[$i]['function']=='send') {
605
- $Object = array('Class'=>isset($trace[$i]['class'])?$trace[$i]['class']:'',
606
- 'Type'=>isset($trace[$i]['type'])?$trace[$i]['type']:'',
607
- 'Function'=>isset($trace[$i]['function'])?$trace[$i]['function']:'',
608
- 'Message'=>$trace[$i]['args'][0],
609
- 'File'=>isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):'',
610
- 'Line'=>isset($trace[$i]['line'])?$trace[$i]['line']:'',
611
- 'Args'=>isset($trace[$i]['args'])?$this->encodeObject($trace[$i]['args']):'',
612
- 'Trace'=>$this->_escapeTrace(array_splice($trace,$i+1)));
613
-
614
- $skipFinalObjectEncode = true;
615
- $meta['file'] = isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):'';
616
- $meta['line'] = isset($trace[$i]['line'])?$trace[$i]['line']:'';
617
- break;
618
- }
619
- }
620
-
621
- } else
622
- if($Type==self::TABLE) {
623
-
624
- if(isset($Object[0]) && is_string($Object[0])) {
625
- $Object[1] = $this->encodeTable($Object[1]);
626
- } else {
627
- $Object = $this->encodeTable($Object);
628
- }
629
-
630
- $skipFinalObjectEncode = true;
631
-
632
- } else {
633
- if($Type===null) {
634
- $Type = self::LOG;
635
- }
636
- }
637
-
638
- if($this->options['includeLineNumbers']) {
639
- if(!isset($meta['file']) || !isset($meta['line'])) {
640
-
641
- $trace = debug_backtrace();
642
- for( $i=0 ; $trace && $i<sizeof($trace) ; $i++ ) {
643
-
644
- if(isset($trace[$i]['class'])
645
- && isset($trace[$i]['file'])
646
- && ($trace[$i]['class']=='FirePHP'
647
- || $trace[$i]['class']=='FB')
648
- && (substr($this->_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php'
649
- || substr($this->_standardizePath($trace[$i]['file']),-29,29)=='FirePHPCore/FirePHP.class.php')) {
650
- /* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */
651
- } else
652
- if(isset($trace[$i]['class'])
653
- && isset($trace[$i+1]['file'])
654
- && $trace[$i]['class']=='FirePHP'
655
- && substr($this->_standardizePath($trace[$i+1]['file']),-18,18)=='FirePHPCore/fb.php') {
656
- /* Skip fb() */
657
- } else
658
- if(isset($trace[$i]['file'])
659
- && substr($this->_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php') {
660
- /* Skip FB::fb() */
661
- } else {
662
- $meta['file'] = isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):'';
663
- $meta['line'] = isset($trace[$i]['line'])?$trace[$i]['line']:'';
664
- break;
665
- }
666
- }
667
-
668
- }
669
- } else {
670
- unset($meta['file']);
671
- unset($meta['line']);
672
- }
673
-
674
- $this->setHeader('X-Wf-Protocol-1','http://meta.wildfirehq.org/Protocol/JsonStream/0.2');
675
- $this->setHeader('X-Wf-1-Plugin-1','http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/'.self::VERSION);
676
-
677
- $structure_index = 1;
678
- if($Type==self::DUMP) {
679
- $structure_index = 2;
680
- $this->setHeader('X-Wf-1-Structure-2','http://meta.firephp.org/Wildfire/Structure/FirePHP/Dump/0.1');
681
- } else {
682
- $this->setHeader('X-Wf-1-Structure-1','http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1');
683
- }
684
-
685
- if($Type==self::DUMP) {
686
- $msg = '{"'.$Label.'":'.$this->jsonEncode($Object, $skipFinalObjectEncode).'}';
687
- } else {
688
- $msg_meta = array('Type'=>$Type);
689
- if($Label!==null) {
690
- $msg_meta['Label'] = $Label;
691
- }
692
- if(isset($meta['file'])) {
693
- $msg_meta['File'] = $meta['file'];
694
- }
695
- if(isset($meta['line'])) {
696
- $msg_meta['Line'] = $meta['line'];
697
- }
698
- $msg = '['.$this->jsonEncode($msg_meta).','.$this->jsonEncode($Object, $skipFinalObjectEncode).']';
699
- }
700
-
701
- $parts = explode("\n",chunk_split($msg, 5000, "\n"));
702
-
703
- for( $i=0 ; $i<count($parts) ; $i++) {
704
-
705
- $part = $parts[$i];
706
- if ($part) {
707
-
708
- if(count($parts)>2) {
709
- // Message needs to be split into multiple parts
710
- $this->setHeader('X-Wf-1-'.$structure_index.'-'.'1-'.$this->messageIndex,
711
- (($i==0)?strlen($msg):'')
712
- . '|' . $part . '|'
713
- . (($i<count($parts)-2)?'\\':''));
714
- } else {
715
- $this->setHeader('X-Wf-1-'.$structure_index.'-'.'1-'.$this->messageIndex,
716
- strlen($part) . '|' . $part . '|');
717
- }
718
-
719
- $this->messageIndex++;
720
-
721
- if ($this->messageIndex > 99999) {
722
- throw new Exception('Maximum number (99,999) of messages reached!');
723
- }
724
- }
725
- }
726
-
727
- $this->setHeader('X-Wf-1-Index',$this->messageIndex-1);
728
-
729
- return true;
730
- }
731
-
732
- /**
733
- * Standardizes path for windows systems.
734
- *
735
- * @param string $Path
736
- * @return string
737
- */
738
- protected function _standardizePath($Path) {
739
- return preg_replace('/\\\\+/','/',$Path);
740
- }
741
-
742
- /**
743
- * Escape trace path for windows systems
744
- *
745
- * @param array $Trace
746
- * @return array
747
- */
748
- protected function _escapeTrace($Trace) {
749
- if(!$Trace) return $Trace;
750
- for( $i=0 ; $i<sizeof($Trace) ; $i++ ) {
751
- if(isset($Trace[$i]['file'])) {
752
- $Trace[$i]['file'] = $this->_escapeTraceFile($Trace[$i]['file']);
753
- }
754
- if(isset($Trace[$i]['args'])) {
755
- $Trace[$i]['args'] = $this->encodeObject($Trace[$i]['args']);
756
- }
757
- }
758
- return $Trace;
759
- }
760
-
761
- /**
762
- * Escape file information of trace for windows systems
763
- *
764
- * @param string $File
765
- * @return string
766
- */
767
- protected function _escapeTraceFile($File) {
768
- /* Check if we have a windows filepath */
769
- if(strpos($File,'\\')) {
770
- /* First strip down to single \ */
771
-
772
- $file = preg_replace('/\\\\+/','\\',$File);
773
-
774
- return $file;
775
- }
776
- return $File;
777
- }
778
-
779
- /**
780
- * Send header
781
- *
782
- * @param string $Name
783
- * @param string_type $Value
784
- */
785
- protected function setHeader($Name, $Value) {
786
- return header($Name.': '.$Value);
787
- }
788
-
789
- /**
790
- * Get user agent
791
- *
792
- * @return string|false
793
- */
794
- protected function getUserAgent() {
795
- if(!isset($_SERVER['HTTP_USER_AGENT'])) return false;
796
- return $_SERVER['HTTP_USER_AGENT'];
797
- }
798
-
799
- /**
800
- * Returns a new exception
801
- *
802
- * @param string $Message
803
- * @return Exception
804
- */
805
- protected function newException($Message) {
806
- return new Exception($Message);
807
- }
808
-
809
- /**
810
- * Encode an object into a JSON string
811
- *
812
- * Uses PHP's jeson_encode() if available
813
- *
814
- * @param object $Object The object to be encoded
815
- * @return string The JSON string
816
- */
817
- protected function jsonEncode($Object, $skipObjectEncode=false)
818
- {
819
- if(!$skipObjectEncode) {
820
- $Object = $this->encodeObject($Object);
821
- }
822
-
823
- if(function_exists('json_encode')
824
- && $this->options['useNativeJsonEncode']!=false) {
825
-
826
- return json_encode($Object);
827
- } else {
828
- return $this->json_encode($Object);
829
- }
830
- }
831
-
832
- /**
833
- * Encodes a table by encoding each row and column with encodeObject()
834
- *
835
- * @param array $Table The table to be encoded
836
- * @return array
837
- */
838
- protected function encodeTable($Table) {
839
- if(!$Table) return $Table;
840
- for( $i=0 ; $i<count($Table) ; $i++ ) {
841
- if(is_array($Table[$i])) {
842
- for( $j=0 ; $j<count($Table[$i]) ; $j++ ) {
843
- $Table[$i][$j] = $this->encodeObject($Table[$i][$j]);
844
- }
845
- }
846
- }
847
- return $Table;
848
- }
849
-
850
- /**
851
- * Encodes an object including members with
852
- * protected and private visibility
853
- *
854
- * @param Object $Object The object to be encoded
855
- * @param int $Depth The current traversal depth
856
- * @return array All members of the object
857
- */
858
- protected function encodeObject($Object, $ObjectDepth = 1, $ArrayDepth = 1)
859
- {
860
- $return = array();
861
-
862
- if (is_object($Object)) {
863
-
864
- if ($ObjectDepth > $this->options['maxObjectDepth']) {
865
- return '** Max Object Depth ('.$this->options['maxObjectDepth'].') **';
866
- }
867
-
868
- foreach ($this->objectStack as $refVal) {
869
- if ($refVal === $Object) {
870
- return '** Recursion ('.get_class($Object).') **';
871
- }
872
- }
873
- array_push($this->objectStack, $Object);
874
-
875
- $return['__className'] = $class = get_class($Object);
876
-
877
- $reflectionClass = new ReflectionClass($class);
878
- $properties = array();
879
- foreach( $reflectionClass->getProperties() as $property) {
880
- $properties[$property->getName()] = $property;
881
- }
882
-
883
- $members = (array)$Object;
884
-
885
- foreach( $properties as $raw_name => $property ) {
886
-
887
- $name = $raw_name;
888
- if($property->isStatic()) {
889
- $name = 'static:'.$name;
890
- }
891
- if($property->isPublic()) {
892
- $name = 'public:'.$name;
893
- } else
894
- if($property->isPrivate()) {
895
- $name = 'private:'.$name;
896
- $raw_name = "\0".$class."\0".$raw_name;
897
- } else
898
- if($property->isProtected()) {
899
- $name = 'protected:'.$name;
900
- $raw_name = "\0".'*'."\0".$raw_name;
901
- }
902
-
903
- if(!(isset($this->objectFilters[$class])
904
- && is_array($this->objectFilters[$class])
905
- && in_array($raw_name,$this->objectFilters[$class]))) {
906
-
907
- if(array_key_exists($raw_name,$members)
908
- && !$property->isStatic()) {
909
-
910
- $return[$name] = $this->encodeObject($members[$raw_name], $ObjectDepth + 1, 1);
911
-
912
- } else {
913
- if(method_exists($property,'setAccessible')) {
914
- $property->setAccessible(true);
915
- $return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth + 1, 1);
916
- } else
917
- if($property->isPublic()) {
918
- $return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth + 1, 1);
919
- } else {
920
- $return[$name] = '** Need PHP 5.3 to get value **';
921
- }
922
- }
923
- } else {
924
- $return[$name] = '** Excluded by Filter **';
925
- }
926
- }
927
-
928
- // Include all members that are not defined in the class
929
- // but exist in the object
930
- foreach( $members as $raw_name => $value ) {
931
-
932
- $name = $raw_name;
933
-
934
- if ($name{0} == "\0") {
935
- $parts = explode("\0", $name);
936
- $name = $parts[2];
937
- }
938
-
939
- if(!isset($properties[$name])) {
940
- $name = 'undeclared:'.$name;
941
-
942
- if(!(isset($this->objectFilters[$class])
943
- && is_array($this->objectFilters[$class])
944
- && in_array($raw_name,$this->objectFilters[$class]))) {
945
-
946
- $return[$name] = $this->encodeObject($value, $ObjectDepth + 1, 1);
947
- } else {
948
- $return[$name] = '** Excluded by Filter **';
949
- }
950
- }
951
- }
952
-
953
- array_pop($this->objectStack);
954
-
955
- } elseif (is_array($Object)) {
956
-
957
- if ($ArrayDepth > $this->options['maxArrayDepth']) {
958
- return '** Max Array Depth ('.$this->options['maxArrayDepth'].') **';
959
- }
960
-
961
- foreach ($Object as $key => $val) {
962
-
963
- // Encoding the $GLOBALS PHP array causes an infinite loop
964
- // if the recursion is not reset here as it contains
965
- // a reference to itself. This is the only way I have come up
966
- // with to stop infinite recursion in this case.
967
- if($key=='GLOBALS'
968
- && is_array($val)
969
- && array_key_exists('GLOBALS',$val)) {
970
- $val['GLOBALS'] = '** Recursion (GLOBALS) **';
971
- }
972
-
973
- $return[$key] = $this->encodeObject($val, 1, $ArrayDepth + 1);
974
- }
975
- } else {
976
- if(self::is_utf8($Object)) {
977
- return $Object;
978
- } else {
979
- return utf8_encode($Object);
980
- }
981
- }
982
- return $return;
983
- }
984
-
985
- /**
986
- * Returns true if $string is valid UTF-8 and false otherwise.
987
- *
988
- * @param mixed $str String to be tested
989
- * @return boolean
990
- */
991
- protected static function is_utf8($str) {
992
- $c=0; $b=0;
993
- $bits=0;
994
- $len=strlen($str);
995
- for($i=0; $i<$len; $i++){
996
- $c=ord($str[$i]);
997
- if($c > 128){
998
- if(($c >= 254)) return false;
999
- elseif($c >= 252) $bits=6;
1000
- elseif($c >= 248) $bits=5;
1001
- elseif($c >= 240) $bits=4;
1002
- elseif($c >= 224) $bits=3;
1003
- elseif($c >= 192) $bits=2;
1004
- else return false;
1005
- if(($i+$bits) > $len) return false;
1006
- while($bits > 1){
1007
- $i++;
1008
- $b=ord($str[$i]);
1009
- if($b < 128 || $b > 191) return false;
1010
- $bits--;
1011
- }
1012
- }
1013
- }
1014
- return true;
1015
- }
1016
-
1017
- /**
1018
- * Converts to and from JSON format.
1019
- *
1020
- * JSON (JavaScript Object Notation) is a lightweight data-interchange
1021
- * format. It is easy for humans to read and write. It is easy for machines
1022
- * to parse and generate. It is based on a subset of the JavaScript
1023
- * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
1024
- * This feature can also be found in Python. JSON is a text format that is
1025
- * completely language independent but uses conventions that are familiar
1026
- * to programmers of the C-family of languages, including C, C++, C#, Java,
1027
- * JavaScript, Perl, TCL, and many others. These properties make JSON an
1028
- * ideal data-interchange language.
1029
- *
1030
- * This package provides a simple encoder and decoder for JSON notation. It
1031
- * is intended for use with client-side Javascript applications that make
1032
- * use of HTTPRequest to perform server communication functions - data can
1033
- * be encoded into JSON notation for use in a client-side javascript, or
1034
- * decoded from incoming Javascript requests. JSON format is native to
1035
- * Javascript, and can be directly eval()'ed with no further parsing
1036
- * overhead
1037
- *
1038
- * All strings should be in ASCII or UTF-8 format!
1039
- *
1040
- * LICENSE: Redistribution and use in source and binary forms, with or
1041
- * without modification, are permitted provided that the following
1042
- * conditions are met: Redistributions of source code must retain the
1043
- * above copyright notice, this list of conditions and the following
1044
- * disclaimer. Redistributions in binary form must reproduce the above
1045
- * copyright notice, this list of conditions and the following disclaimer
1046
- * in the documentation and/or other materials provided with the
1047
- * distribution.
1048
- *
1049
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
1050
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
1051
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
1052
- * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
1053
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
1054
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
1055
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
1056
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
1057
- * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
1058
- * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
1059
- * DAMAGE.
1060
- *
1061
- * @category
1062
- * @package Services_JSON
1063
- * @author Michal Migurski <mike-json@teczno.com>
1064
- * @author Matt Knapp <mdknapp[at]gmail[dot]com>
1065
- * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
1066
- * @author Christoph Dorn <christoph@christophdorn.com>
1067
- * @copyright 2005 Michal Migurski
1068
- * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
1069
- * @license http://www.opensource.org/licenses/bsd-license.php
1070
- * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
1071
- */
1072
-
1073
-
1074
- /**
1075
- * Keep a list of objects as we descend into the array so we can detect recursion.
1076
- */
1077
- private $json_objectStack = array();
1078
-
1079
-
1080
- /**
1081
- * convert a string from one UTF-8 char to one UTF-16 char
1082
- *
1083
- * Normally should be handled by mb_convert_encoding, but
1084
- * provides a slower PHP-only method for installations
1085
- * that lack the multibye string extension.
1086
- *
1087
- * @param string $utf8 UTF-8 character
1088
- * @return string UTF-16 character
1089
- * @access private
1090
- */
1091
- private function json_utf82utf16($utf8)
1092
- {
1093
- // oh please oh please oh please oh please oh please
1094
- if(function_exists('mb_convert_encoding')) {
1095
- return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
1096
- }
1097
-
1098
- switch(strlen($utf8)) {
1099
- case 1:
1100
- // this case should never be reached, because we are in ASCII range
1101
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1102
- return $utf8;
1103
-
1104
- case 2:
1105
- // return a UTF-16 character from a 2-byte UTF-8 char
1106
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1107
- return chr(0x07 & (ord($utf8{0}) >> 2))
1108
- . chr((0xC0 & (ord($utf8{0}) << 6))
1109
- | (0x3F & ord($utf8{1})));
1110
-
1111
- case 3:
1112
- // return a UTF-16 character from a 3-byte UTF-8 char
1113
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1114
- return chr((0xF0 & (ord($utf8{0}) << 4))
1115
- | (0x0F & (ord($utf8{1}) >> 2)))
1116
- . chr((0xC0 & (ord($utf8{1}) << 6))
1117
- | (0x7F & ord($utf8{2})));
1118
- }
1119
-
1120
- // ignoring UTF-32 for now, sorry
1121
- return '';
1122
- }
1123
-
1124
- /**
1125
- * encodes an arbitrary variable into JSON format
1126
- *
1127
- * @param mixed $var any number, boolean, string, array, or object to be encoded.
1128
- * see argument 1 to Services_JSON() above for array-parsing behavior.
1129
- * if var is a strng, note that encode() always expects it
1130
- * to be in ASCII or UTF-8 format!
1131
- *
1132
- * @return mixed JSON string representation of input var or an error if a problem occurs
1133
- * @access public
1134
- */
1135
- private function json_encode($var)
1136
- {
1137
-
1138
- if(is_object($var)) {
1139
- if(in_array($var,$this->json_objectStack)) {
1140
- return '"** Recursion **"';
1141
- }
1142
- }
1143
-
1144
- switch (gettype($var)) {
1145
- case 'boolean':
1146
- return $var ? 'true' : 'false';
1147
-
1148
- case 'NULL':
1149
- return 'null';
1150
-
1151
- case 'integer':
1152
- return (int) $var;
1153
-
1154
- case 'double':
1155
- case 'float':
1156
- return (float) $var;
1157
-
1158
- case 'string':
1159
- // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
1160
- $ascii = '';
1161
- $strlen_var = strlen($var);
1162
-
1163
- /*
1164
- * Iterate over every character in the string,
1165
- * escaping with a slash or encoding to UTF-8 where necessary
1166
- */
1167
- for ($c = 0; $c < $strlen_var; ++$c) {
1168
-
1169
- $ord_var_c = ord($var{$c});
1170
-
1171
- switch (true) {
1172
- case $ord_var_c == 0x08:
1173
- $ascii .= '\b';
1174
- break;
1175
- case $ord_var_c == 0x09:
1176
- $ascii .= '\t';
1177
- break;
1178
- case $ord_var_c == 0x0A:
1179
- $ascii .= '\n';
1180
- break;
1181
- case $ord_var_c == 0x0C:
1182
- $ascii .= '\f';
1183
- break;
1184
- case $ord_var_c == 0x0D:
1185
- $ascii .= '\r';
1186
- break;
1187
-
1188
- case $ord_var_c == 0x22:
1189
- case $ord_var_c == 0x2F:
1190
- case $ord_var_c == 0x5C:
1191
- // double quote, slash, slosh
1192
- $ascii .= '\\'.$var{$c};
1193
- break;
1194
-
1195
- case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
1196
- // characters U-00000000 - U-0000007F (same as ASCII)
1197
- $ascii .= $var{$c};
1198
- break;
1199
-
1200
- case (($ord_var_c & 0xE0) == 0xC0):
1201
- // characters U-00000080 - U-000007FF, mask 110XXXXX
1202
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1203
- $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
1204
- $c += 1;
1205
- $utf16 = $this->json_utf82utf16($char);
1206
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
1207
- break;
1208
-
1209
- case (($ord_var_c & 0xF0) == 0xE0):
1210
- // characters U-00000800 - U-0000FFFF, mask 1110XXXX
1211
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1212
- $char = pack('C*', $ord_var_c,
1213
- ord($var{$c + 1}),
1214
- ord($var{$c + 2}));
1215
- $c += 2;
1216
- $utf16 = $this->json_utf82utf16($char);
1217
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
1218
- break;
1219
-
1220
- case (($ord_var_c & 0xF8) == 0xF0):
1221
- // characters U-00010000 - U-001FFFFF, mask 11110XXX
1222
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1223
- $char = pack('C*', $ord_var_c,
1224
- ord($var{$c + 1}),
1225
- ord($var{$c + 2}),
1226
- ord($var{$c + 3}));
1227
- $c += 3;
1228
- $utf16 = $this->json_utf82utf16($char);
1229
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
1230
- break;
1231
-
1232
- case (($ord_var_c & 0xFC) == 0xF8):
1233
- // characters U-00200000 - U-03FFFFFF, mask 111110XX
1234
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1235
- $char = pack('C*', $ord_var_c,
1236
- ord($var{$c + 1}),
1237
- ord($var{$c + 2}),
1238
- ord($var{$c + 3}),
1239
- ord($var{$c + 4}));
1240
- $c += 4;
1241
- $utf16 = $this->json_utf82utf16($char);
1242
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
1243
- break;
1244
-
1245
- case (($ord_var_c & 0xFE) == 0xFC):
1246
- // characters U-04000000 - U-7FFFFFFF, mask 1111110X
1247
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
1248
- $char = pack('C*', $ord_var_c,
1249
- ord($var{$c + 1}),
1250
- ord($var{$c + 2}),
1251
- ord($var{$c + 3}),
1252
- ord($var{$c + 4}),
1253
- ord($var{$c + 5}));
1254
- $c += 5;
1255
- $utf16 = $this->json_utf82utf16($char);
1256
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
1257
- break;
1258
- }
1259
- }
1260
-
1261
- return '"'.$ascii.'"';
1262
-
1263
- case 'array':
1264
- /*
1265
- * As per JSON spec if any array key is not an integer
1266
- * we must treat the the whole array as an object. We
1267
- * also try to catch a sparsely populated associative
1268
- * array with numeric keys here because some JS engines
1269
- * will create an array with empty indexes up to
1270
- * max_index which can cause memory issues and because
1271
- * the keys, which may be relevant, will be remapped
1272
- * otherwise.
1273
- *
1274
- * As per the ECMA and JSON specification an object may
1275
- * have any string as a property. Unfortunately due to
1276
- * a hole in the ECMA specification if the key is a
1277
- * ECMA reserved word or starts with a digit the
1278
- * parameter is only accessible using ECMAScript's
1279
- * bracket notation.
1280
- */
1281
-
1282
- // treat as a JSON object
1283
- if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
1284
-
1285
- $this->json_objectStack[] = $var;
1286
-
1287
- $properties = array_map(array($this, 'json_name_value'),
1288
- array_keys($var),
1289
- array_values($var));
1290
-
1291
- array_pop($this->json_objectStack);
1292
-
1293
- foreach($properties as $property) {
1294
- if($property instanceof Exception) {
1295
- return $property;
1296
- }
1297
- }
1298
-
1299
- return '{' . join(',', $properties) . '}';
1300
- }
1301
-
1302
- $this->json_objectStack[] = $var;
1303
-
1304
- // treat it like a regular array
1305
- $elements = array_map(array($this, 'json_encode'), $var);
1306
-
1307
- array_pop($this->json_objectStack);
1308
-
1309
- foreach($elements as $element) {
1310
- if($element instanceof Exception) {
1311
- return $element;
1312
- }
1313
- }
1314
-
1315
- return '[' . join(',', $elements) . ']';
1316
-
1317
- case 'object':
1318
- $vars = self::encodeObject($var);
1319
-
1320
- $this->json_objectStack[] = $var;
1321
-
1322
- $properties = array_map(array($this, 'json_name_value'),
1323
- array_keys($vars),
1324
- array_values($vars));
1325
-
1326
- array_pop($this->json_objectStack);
1327
-
1328
- foreach($properties as $property) {
1329
- if($property instanceof Exception) {
1330
- return $property;
1331
- }
1332
- }
1333
-
1334
- return '{' . join(',', $properties) . '}';
1335
-
1336
- default:
1337
- return null;
1338
- }
1339
- }
1340
-
1341
- /**
1342
- * array-walking function for use in generating JSON-formatted name-value pairs
1343
- *
1344
- * @param string $name name of key to use
1345
- * @param mixed $value reference to an array element to be encoded
1346
- *
1347
- * @return string JSON-formatted name-value pair, like '"name":value'
1348
- * @access private
1349
- */
1350
- private function json_name_value($name, $value)
1351
- {
1352
- // Encoding the $GLOBALS PHP array causes an infinite loop
1353
- // if the recursion is not reset here as it contains
1354
- // a reference to itself. This is the only way I have come up
1355
- // with to stop infinite recursion in this case.
1356
- if($name=='GLOBALS'
1357
- && is_array($value)
1358
- && array_key_exists('GLOBALS',$value)) {
1359
- $value['GLOBALS'] = '** Recursion **';
1360
- }
1361
-
1362
- $encoded_value = $this->json_encode($value);
1363
-
1364
- if($encoded_value instanceof Exception) {
1365
- return $encoded_value;
1366
- }
1367
-
1368
- return $this->json_encode(strval($name)) . ':' . $encoded_value;
1369
- }
1370
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/HTTP/ConditionalGet.php DELETED
@@ -1,366 +0,0 @@
1
- <?php
2
- /**
3
- * Class HTTP_ConditionalGet
4
- * @package Minify
5
- * @subpackage HTTP
6
- */
7
-
8
- /**
9
- * Implement conditional GET via a timestamp or hash of content
10
- *
11
- * E.g. Content from DB with update time:
12
- * <code>
13
- * list($updateTime, $content) = getDbUpdateAndContent();
14
- * $cg = new HTTP_ConditionalGet(array(
15
- * 'lastModifiedTime' => $updateTime
16
- * ,'isPublic' => true
17
- * ));
18
- * $cg->sendHeaders();
19
- * if ($cg->cacheIsValid) {
20
- * exit();
21
- * }
22
- * echo $content;
23
- * </code>
24
- *
25
- * E.g. Shortcut for the above
26
- * <code>
27
- * HTTP_ConditionalGet::check($updateTime, true); // exits if client has cache
28
- * echo $content;
29
- * </code>
30
- *
31
- * E.g. Content from DB with no update time:
32
- * <code>
33
- * $content = getContentFromDB();
34
- * $cg = new HTTP_ConditionalGet(array(
35
- * 'contentHash' => md5($content)
36
- * ));
37
- * $cg->sendHeaders();
38
- * if ($cg->cacheIsValid) {
39
- * exit();
40
- * }
41
- * echo $content;
42
- * </code>
43
- *
44
- * E.g. Static content with some static includes:
45
- * <code>
46
- * // before content
47
- * $cg = new HTTP_ConditionalGet(array(
48
- * 'lastUpdateTime' => max(
49
- * filemtime(__FILE__)
50
- * ,filemtime('/path/to/header.inc')
51
- * ,filemtime('/path/to/footer.inc')
52
- * )
53
- * ));
54
- * $cg->sendHeaders();
55
- * if ($cg->cacheIsValid) {
56
- * exit();
57
- * }
58
- * </code>
59
- * @package Minify
60
- * @subpackage HTTP
61
- * @author Stephen Clay <steve@mrclay.org>
62
- */
63
- class HTTP_ConditionalGet {
64
-
65
- /**
66
- * Does the client have a valid copy of the requested resource?
67
- *
68
- * You'll want to check this after instantiating the object. If true, do
69
- * not send content, just call sendHeaders() if you haven't already.
70
- *
71
- * @var bool
72
- */
73
- public $cacheIsValid = null;
74
-
75
- /**
76
- * @param array $spec options
77
- *
78
- * 'isPublic': (bool) if false, the Cache-Control header will contain
79
- * "private", allowing only browser caching. (default false)
80
- *
81
- * 'lastModifiedTime': (int) if given, both ETag AND Last-Modified headers
82
- * will be sent with content. This is recommended.
83
- *
84
- * 'encoding': (string) if set, the header "Vary: Accept-Encoding" will
85
- * always be sent and a truncated version of the encoding will be appended
86
- * to the ETag. E.g. "pub123456;gz". This will also trigger a more lenient
87
- * checking of the client's If-None-Match header, as the encoding portion of
88
- * the ETag will be stripped before comparison.
89
- *
90
- * 'contentHash': (string) if given, only the ETag header can be sent with
91
- * content (only HTTP1.1 clients can conditionally GET). The given string
92
- * should be short with no quote characters and always change when the
93
- * resource changes (recommend md5()). This is not needed/used if
94
- * lastModifiedTime is given.
95
- *
96
- * 'eTag': (string) if given, this will be used as the ETag header rather
97
- * than values based on lastModifiedTime or contentHash. Also the encoding
98
- * string will not be appended to the given value as described above.
99
- *
100
- * 'invalidate': (bool) if true, the client cache will be considered invalid
101
- * without testing. Effectively this disables conditional GET.
102
- * (default false)
103
- *
104
- * 'maxAge': (int) if given, this will set the Cache-Control max-age in
105
- * seconds, and also set the Expires header to the equivalent GMT date.
106
- * After the max-age period has passed, the browser will again send a
107
- * conditional GET to revalidate its cache.
108
- */
109
- public function __construct($spec)
110
- {
111
- $scope = (isset($spec['isPublic']) && $spec['isPublic'])
112
- ? 'public'
113
- : 'private';
114
- $maxAge = 0;
115
- // backwards compatibility (can be removed later)
116
- if (isset($spec['setExpires'])
117
- && is_numeric($spec['setExpires'])
118
- && ! isset($spec['maxAge'])) {
119
- $spec['maxAge'] = $spec['setExpires'] - $_SERVER['REQUEST_TIME'];
120
- }
121
- if (isset($spec['maxAge'])) {
122
- $maxAge = $spec['maxAge'];
123
- $this->_headers['Expires'] = self::gmtDate(
124
- $_SERVER['REQUEST_TIME'] + $spec['maxAge']
125
- );
126
- }
127
- $etagAppend = '';
128
- if (isset($spec['encoding'])) {
129
- $this->_stripEtag = true;
130
- $this->_headers['Vary'] = 'Accept-Encoding';
131
- if ('' !== $spec['encoding']) {
132
- if (0 === strpos($spec['encoding'], 'x-')) {
133
- $spec['encoding'] = substr($spec['encoding'], 2);
134
- }
135
- $etagAppend = ';' . substr($spec['encoding'], 0, 2);
136
- }
137
- }
138
- if (isset($spec['lastModifiedTime'])) {
139
- $this->_setLastModified($spec['lastModifiedTime']);
140
- if (isset($spec['eTag'])) { // Use it
141
- $this->_setEtag($spec['eTag'], $scope);
142
- } else { // base both headers on time
143
- $this->_setEtag($spec['lastModifiedTime'] . $etagAppend, $scope);
144
- }
145
- } elseif (isset($spec['eTag'])) { // Use it
146
- $this->_setEtag($spec['eTag'], $scope);
147
- } elseif (isset($spec['contentHash'])) { // Use the hash as the ETag
148
- $this->_setEtag($spec['contentHash'] . $etagAppend, $scope);
149
- }
150
- $privacy = ($scope === 'private')
151
- ? ', private'
152
- : '';
153
- $this->_headers['Cache-Control'] = "max-age={$maxAge}{$privacy}";
154
- // invalidate cache if disabled, otherwise check
155
- $this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate'])
156
- ? false
157
- : $this->_isCacheValid();
158
- }
159
-
160
- /**
161
- * Get array of output headers to be sent
162
- *
163
- * In the case of 304 responses, this array will only contain the response
164
- * code header: array('_responseCode' => 'HTTP/1.0 304 Not Modified')
165
- *
166
- * Otherwise something like:
167
- * <code>
168
- * array(
169
- * 'Cache-Control' => 'max-age=0, public'
170
- * ,'ETag' => '"foobar"'
171
- * )
172
- * </code>
173
- *
174
- * @return array
175
- */
176
- public function getHeaders()
177
- {
178
- return $this->_headers;
179
- }
180
-
181
- /**
182
- * Set the Content-Length header in bytes
183
- *
184
- * With most PHP configs, as long as you don't flush() output, this method
185
- * is not needed and PHP will buffer all output and set Content-Length for
186
- * you. Otherwise you'll want to call this to let the client know up front.
187
- *
188
- * @param int $bytes
189
- *
190
- * @return int copy of input $bytes
191
- */
192
- public function setContentLength($bytes)
193
- {
194
- return $this->_headers['Content-Length'] = $bytes;
195
- }
196
-
197
- /**
198
- * Send headers
199
- *
200
- * @see getHeaders()
201
- *
202
- * Note this doesn't "clear" the headers. Calling sendHeaders() will
203
- * call header() again (but probably have not effect) and getHeaders() will
204
- * still return the headers.
205
- *
206
- * @return null
207
- */
208
- public function sendHeaders()
209
- {
210
- $headers = $this->_headers;
211
- if (array_key_exists('_responseCode', $headers)) {
212
- // FastCGI environments require 3rd arg to header() to be set
213
- list(, $code) = explode(' ', $headers['_responseCode'], 3);
214
- header($headers['_responseCode'], true, $code);
215
- unset($headers['_responseCode']);
216
- }
217
- foreach ($headers as $name => $val) {
218
- header($name . ': ' . $val);
219
- }
220
- }
221
-
222
- /**
223
- * Exit if the client's cache is valid for this resource
224
- *
225
- * This is a convenience method for common use of the class
226
- *
227
- * @param int $lastModifiedTime if given, both ETag AND Last-Modified headers
228
- * will be sent with content. This is recommended.
229
- *
230
- * @param bool $isPublic (default false) if true, the Cache-Control header
231
- * will contain "public", allowing proxies to cache the content. Otherwise
232
- * "private" will be sent, allowing only browser caching.
233
- *
234
- * @param array $options (default empty) additional options for constructor
235
- */
236
- public static function check($lastModifiedTime = null, $isPublic = false, $options = array())
237
- {
238
- if (null !== $lastModifiedTime) {
239
- $options['lastModifiedTime'] = (int)$lastModifiedTime;
240
- }
241
- $options['isPublic'] = (bool)$isPublic;
242
- $cg = new HTTP_ConditionalGet($options);
243
- $cg->sendHeaders();
244
- if ($cg->cacheIsValid) {
245
- exit();
246
- }
247
- }
248
-
249
-
250
- /**
251
- * Get a GMT formatted date for use in HTTP headers
252
- *
253
- * <code>
254
- * header('Expires: ' . HTTP_ConditionalGet::gmtdate($time));
255
- * </code>
256
- *
257
- * @param int $time unix timestamp
258
- *
259
- * @return string
260
- */
261
- public static function gmtDate($time)
262
- {
263
- return gmdate('D, d M Y H:i:s \G\M\T', $time);
264
- }
265
-
266
- protected $_headers = array();
267
- protected $_lmTime = null;
268
- protected $_etag = null;
269
- protected $_stripEtag = false;
270
-
271
- /**
272
- * @param string $hash
273
- *
274
- * @param string $scope
275
- */
276
- protected function _setEtag($hash, $scope)
277
- {
278
- $this->_etag = '"' . substr($scope, 0, 3) . $hash . '"';
279
- $this->_headers['ETag'] = $this->_etag;
280
- }
281
-
282
- /**
283
- * @param int $time
284
- */
285
- protected function _setLastModified($time)
286
- {
287
- $this->_lmTime = (int)$time;
288
- $this->_headers['Last-Modified'] = self::gmtDate($time);
289
- }
290
-
291
- /**
292
- * Determine validity of client cache and queue 304 header if valid
293
- *
294
- * @return bool
295
- */
296
- protected function _isCacheValid()
297
- {
298
- if (null === $this->_etag) {
299
- // lmTime is copied to ETag, so this condition implies that the
300
- // server sent neither ETag nor Last-Modified, so the client can't
301
- // possibly has a valid cache.
302
- return false;
303
- }
304
- $isValid = ($this->resourceMatchedEtag() || $this->resourceNotModified());
305
- if ($isValid) {
306
- $this->_headers['_responseCode'] = 'HTTP/1.0 304 Not Modified';
307
- }
308
- return $isValid;
309
- }
310
-
311
- /**
312
- * @return bool
313
- */
314
- protected function resourceMatchedEtag()
315
- {
316
- if (!isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
317
- return false;
318
- }
319
- $clientEtagList = get_magic_quotes_gpc()
320
- ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
321
- : $_SERVER['HTTP_IF_NONE_MATCH'];
322
- $clientEtags = explode(',', $clientEtagList);
323
-
324
- $compareTo = $this->normalizeEtag($this->_etag);
325
- foreach ($clientEtags as $clientEtag) {
326
- if ($this->normalizeEtag($clientEtag) === $compareTo) {
327
- // respond with the client's matched ETag, even if it's not what
328
- // we would've sent by default
329
- $this->_headers['ETag'] = trim($clientEtag);
330
- return true;
331
- }
332
- }
333
- return false;
334
- }
335
-
336
- /**
337
- * @param string $etag
338
- *
339
- * @return string
340
- */
341
- protected function normalizeEtag($etag) {
342
- $etag = trim($etag);
343
- return $this->_stripEtag
344
- ? preg_replace('/;\\w\\w"$/', '"', $etag)
345
- : $etag;
346
- }
347
-
348
- /**
349
- * @return bool
350
- */
351
- protected function resourceNotModified()
352
- {
353
- if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
354
- return false;
355
- }
356
- // strip off IE's extra data (semicolon)
357
- list($ifModifiedSince) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE'], 2);
358
- if (strtotime($ifModifiedSince) >= $this->_lmTime) {
359
- // Apache 2.2's behavior. If there was no ETag match, send the
360
- // non-encoded version of the ETag value.
361
- $this->_headers['ETag'] = $this->normalizeEtag($this->_etag);
362
- return true;
363
- }
364
- return false;
365
- }
366
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/HTTP/Encoder.php DELETED
@@ -1,335 +0,0 @@
1
- <?php
2
- /**
3
- * Class HTTP_Encoder
4
- * @package Minify
5
- * @subpackage HTTP
6
- */
7
-
8
- /**
9
- * Encode and send gzipped/deflated content
10
- *
11
- * The "Vary: Accept-Encoding" header is sent. If the client allows encoding,
12
- * Content-Encoding and Content-Length are added.
13
- *
14
- * <code>
15
- * // Send a CSS file, compressed if possible
16
- * $he = new HTTP_Encoder(array(
17
- * 'content' => file_get_contents($cssFile)
18
- * ,'type' => 'text/css'
19
- * ));
20
- * $he->encode();
21
- * $he->sendAll();
22
- * </code>
23
- *
24
- * <code>
25
- * // Shortcut to encoding output
26
- * header('Content-Type: text/css'); // needed if not HTML
27
- * HTTP_Encoder::output($css);
28
- * </code>
29
- *
30
- * <code>
31
- * // Just sniff for the accepted encoding
32
- * $encoding = HTTP_Encoder::getAcceptedEncoding();
33
- * </code>
34
- *
35
- * For more control over headers, use getHeaders() and getData() and send your
36
- * own output.
37
- *
38
- * Note: If you don't need header mgmt, use PHP's native gzencode, gzdeflate,
39
- * and gzcompress functions for gzip, deflate, and compress-encoding
40
- * respectively.
41
- *
42
- * @package Minify
43
- * @subpackage HTTP
44
- * @author Stephen Clay <steve@mrclay.org>
45
- */
46
- class HTTP_Encoder {
47
-
48
- /**
49
- * Should the encoder allow HTTP encoding to IE6?
50
- *
51
- * If you have many IE6 users and the bandwidth savings is worth troubling
52
- * some of them, set this to true.
53
- *
54
- * By default, encoding is only offered to IE7+. When this is true,
55
- * getAcceptedEncoding() will return an encoding for IE6 if its user agent
56
- * string contains "SV1". This has been documented in many places as "safe",
57
- * but there seem to be remaining, intermittent encoding bugs in patched
58
- * IE6 on the wild web.
59
- *
60
- * @var bool
61
- */
62
- public static $encodeToIe6 = true;
63
-
64
-
65
- /**
66
- * Default compression level for zlib operations
67
- *
68
- * This level is used if encode() is not given a $compressionLevel
69
- *
70
- * @var int
71
- */
72
- public static $compressionLevel = 6;
73
-
74
-
75
- /**
76
- * Get an HTTP Encoder object
77
- *
78
- * @param array $spec options
79
- *
80
- * 'content': (string required) content to be encoded
81
- *
82
- * 'type': (string) if set, the Content-Type header will have this value.
83
- *
84
- * 'method: (string) only set this if you are forcing a particular encoding
85
- * method. If not set, the best method will be chosen by getAcceptedEncoding()
86
- * The available methods are 'gzip', 'deflate', 'compress', and '' (no
87
- * encoding)
88
- */
89
- public function __construct($spec)
90
- {
91
- $this->_useMbStrlen = (function_exists('mb_strlen')
92
- && (ini_get('mbstring.func_overload') !== '')
93
- && ((int)ini_get('mbstring.func_overload') & 2));
94
- $this->_content = $spec['content'];
95
- $this->_headers['Content-Length'] = $this->_useMbStrlen
96
- ? (string)mb_strlen($this->_content, '8bit')
97
- : (string)strlen($this->_content);
98
- if (isset($spec['type'])) {
99
- $this->_headers['Content-Type'] = $spec['type'];
100
- }
101
- if (isset($spec['method'])
102
- && in_array($spec['method'], array('gzip', 'deflate', 'compress', '')))
103
- {
104
- $this->_encodeMethod = array($spec['method'], $spec['method']);
105
- } else {
106
- $this->_encodeMethod = self::getAcceptedEncoding();
107
- }
108
- }
109
-
110
- /**
111
- * Get content in current form
112
- *
113
- * Call after encode() for encoded content.
114
- *
115
- * @return string
116
- */
117
- public function getContent()
118
- {
119
- return $this->_content;
120
- }
121
-
122
- /**
123
- * Get array of output headers to be sent
124
- *
125
- * E.g.
126
- * <code>
127
- * array(
128
- * 'Content-Length' => '615'
129
- * ,'Content-Encoding' => 'x-gzip'
130
- * ,'Vary' => 'Accept-Encoding'
131
- * )
132
- * </code>
133
- *
134
- * @return array
135
- */
136
- public function getHeaders()
137
- {
138
- return $this->_headers;
139
- }
140
-
141
- /**
142
- * Send output headers
143
- *
144
- * You must call this before headers are sent and it probably cannot be
145
- * used in conjunction with zlib output buffering / mod_gzip. Errors are
146
- * not handled purposefully.
147
- *
148
- * @see getHeaders()
149
- */
150
- public function sendHeaders()
151
- {
152
- foreach ($this->_headers as $name => $val) {
153
- header($name . ': ' . $val);
154
- }
155
- }
156
-
157
- /**
158
- * Send output headers and content
159
- *
160
- * A shortcut for sendHeaders() and echo getContent()
161
- *
162
- * You must call this before headers are sent and it probably cannot be
163
- * used in conjunction with zlib output buffering / mod_gzip. Errors are
164
- * not handled purposefully.
165
- */
166
- public function sendAll()
167
- {
168
- $this->sendHeaders();
169
- echo $this->_content;
170
- }
171
-
172
- /**
173
- * Determine the client's best encoding method from the HTTP Accept-Encoding
174
- * header.
175
- *
176
- * If no Accept-Encoding header is set, or the browser is IE before v6 SP2,
177
- * this will return ('', ''), the "identity" encoding.
178
- *
179
- * A syntax-aware scan is done of the Accept-Encoding, so the method must
180
- * be non 0. The methods are favored in order of gzip, deflate, then
181
- * compress. Deflate is always smallest and generally faster, but is
182
- * rarely sent by servers, so client support could be buggier.
183
- *
184
- * @param bool $allowCompress allow the older compress encoding
185
- *
186
- * @param bool $allowDeflate allow the more recent deflate encoding
187
- *
188
- * @return array two values, 1st is the actual encoding method, 2nd is the
189
- * alias of that method to use in the Content-Encoding header (some browsers
190
- * call gzip "x-gzip" etc.)
191
- */
192
- public static function getAcceptedEncoding($allowCompress = true, $allowDeflate = true)
193
- {
194
- // @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
195
-
196
- if (! isset($_SERVER['HTTP_ACCEPT_ENCODING'])
197
- || self::isBuggyIe())
198
- {
199
- return array('', '');
200
- }
201
- $ae = $_SERVER['HTTP_ACCEPT_ENCODING'];
202
- // gzip checks (quick)
203
- if (0 === strpos($ae, 'gzip,') // most browsers
204
- || 0 === strpos($ae, 'deflate, gzip,') // opera
205
- ) {
206
- return array('gzip', 'gzip');
207
- }
208
- // gzip checks (slow)
209
- if (preg_match(
210
- '@(?:^|,)\\s*((?:x-)?gzip)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
211
- ,$ae
212
- ,$m)) {
213
- return array('gzip', $m[1]);
214
- }
215
- if ($allowDeflate) {
216
- // deflate checks
217
- $aeRev = strrev($ae);
218
- if (0 === strpos($aeRev, 'etalfed ,') // ie, webkit
219
- || 0 === strpos($aeRev, 'etalfed,') // gecko
220
- || 0 === strpos($ae, 'deflate,') // opera
221
- // slow parsing
222
- || preg_match(
223
- '@(?:^|,)\\s*deflate\\s*(?:$|,|;\\s*q=(?:0\\.|1))@', $ae)) {
224
- return array('deflate', 'deflate');
225
- }
226
- }
227
- if ($allowCompress && preg_match(
228
- '@(?:^|,)\\s*((?:x-)?compress)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
229
- ,$ae
230
- ,$m)) {
231
- return array('compress', $m[1]);
232
- }
233
- return array('', '');
234
- }
235
-
236
- /**
237
- * Encode (compress) the content
238
- *
239
- * If the encode method is '' (none) or compression level is 0, or the 'zlib'
240
- * extension isn't loaded, we return false.
241
- *
242
- * Then the appropriate gz_* function is called to compress the content. If
243
- * this fails, false is returned.
244
- *
245
- * The header "Vary: Accept-Encoding" is added. If encoding is successful,
246
- * the Content-Length header is updated, and Content-Encoding is also added.
247
- *
248
- * @param int $compressionLevel given to zlib functions. If not given, the
249
- * class default will be used.
250
- *
251
- * @return bool success true if the content was actually compressed
252
- */
253
- public function encode($compressionLevel = null)
254
- {
255
- if (! self::isBuggyIe()) {
256
- $this->_headers['Vary'] = 'Accept-Encoding';
257
- }
258
- if (null === $compressionLevel) {
259
- $compressionLevel = self::$compressionLevel;
260
- }
261
- if ('' === $this->_encodeMethod[0]
262
- || ($compressionLevel == 0)
263
- || !extension_loaded('zlib'))
264
- {
265
- return false;
266
- }
267
- if ($this->_encodeMethod[0] === 'deflate') {
268
- $encoded = gzdeflate($this->_content, $compressionLevel);
269
- } elseif ($this->_encodeMethod[0] === 'gzip') {
270
- $encoded = gzencode($this->_content, $compressionLevel);
271
- } else {
272
- $encoded = gzcompress($this->_content, $compressionLevel);
273
- }
274
- if (false === $encoded) {
275
- return false;
276
- }
277
- $this->_headers['Content-Length'] = $this->_useMbStrlen
278
- ? (string)mb_strlen($encoded, '8bit')
279
- : (string)strlen($encoded);
280
- $this->_headers['Content-Encoding'] = $this->_encodeMethod[1];
281
- $this->_content = $encoded;
282
- return true;
283
- }
284
-
285
- /**
286
- * Encode and send appropriate headers and content
287
- *
288
- * This is a convenience method for common use of the class
289
- *
290
- * @param string $content
291
- *
292
- * @param int $compressionLevel given to zlib functions. If not given, the
293
- * class default will be used.
294
- *
295
- * @return bool success true if the content was actually compressed
296
- */
297
- public static function output($content, $compressionLevel = null)
298
- {
299
- if (null === $compressionLevel) {
300
- $compressionLevel = self::$compressionLevel;
301
- }
302
- $he = new HTTP_Encoder(array('content' => $content));
303
- $ret = $he->encode($compressionLevel);
304
- $he->sendAll();
305
- return $ret;
306
- }
307
-
308
- /**
309
- * Is the browser an IE version earlier than 6 SP2?
310
- *
311
- * @return bool
312
- */
313
- public static function isBuggyIe()
314
- {
315
- if (empty($_SERVER['HTTP_USER_AGENT'])) {
316
- return false;
317
- }
318
- $ua = $_SERVER['HTTP_USER_AGENT'];
319
- // quick escape for non-IEs
320
- if (0 !== strpos($ua, 'Mozilla/4.0 (compatible; MSIE ')
321
- || false !== strpos($ua, 'Opera')) {
322
- return false;
323
- }
324
- // no regex = faaast
325
- $version = (float)substr($ua, 30);
326
- return self::$encodeToIe6
327
- ? ($version < 6 || ($version == 6 && false === strpos($ua, 'SV1')))
328
- : ($version < 7);
329
- }
330
-
331
- protected $_content = '';
332
- protected $_headers = array();
333
- protected $_encodeMethod = array('', '');
334
- protected $_useMbStrlen = false;
335
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/JSMin.php DELETED
@@ -1,449 +0,0 @@
1
- <?php
2
- /**
3
- * JSMin.php - modified PHP implementation of Douglas Crockford's JSMin.
4
- *
5
- * <code>
6
- * $minifiedJs = JSMin::minify($js);
7
- * </code>
8
- *
9
- * This is a modified port of jsmin.c. Improvements:
10
- *
11
- * Does not choke on some regexp literals containing quote characters. E.g. /'/
12
- *
13
- * Spaces are preserved after some add/sub operators, so they are not mistakenly
14
- * converted to post-inc/dec. E.g. a + ++b -> a+ ++b
15
- *
16
- * Preserves multi-line comments that begin with /*!
17
- *
18
- * PHP 5 or higher is required.
19
- *
20
- * Permission is hereby granted to use this version of the library under the
21
- * same terms as jsmin.c, which has the following license:
22
- *
23
- * --
24
- * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
25
- *
26
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
27
- * this software and associated documentation files (the "Software"), to deal in
28
- * the Software without restriction, including without limitation the rights to
29
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
30
- * of the Software, and to permit persons to whom the Software is furnished to do
31
- * so, subject to the following conditions:
32
- *
33
- * The above copyright notice and this permission notice shall be included in all
34
- * copies or substantial portions of the Software.
35
- *
36
- * The Software shall be used for Good, not Evil.
37
- *
38
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44
- * SOFTWARE.
45
- * --
46
- *
47
- * @package JSMin
48
- * @author Ryan Grove <ryan@wonko.com> (PHP port)
49
- * @author Steve Clay <steve@mrclay.org> (modifications + cleanup)
50
- * @author Andrea Giammarchi <http://www.3site.eu> (spaceBeforeRegExp)
51
- * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
52
- * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
53
- * @license http://opensource.org/licenses/mit-license.php MIT License
54
- * @link http://code.google.com/p/jsmin-php/
55
- */
56
-
57
- class JSMin {
58
- const ORD_LF = 10;
59
- const ORD_SPACE = 32;
60
- const ACTION_KEEP_A = 1;
61
- const ACTION_DELETE_A = 2;
62
- const ACTION_DELETE_A_B = 3;
63
-
64
- protected $a = "\n";
65
- protected $b = '';
66
- protected $input = '';
67
- protected $inputIndex = 0;
68
- protected $inputLength = 0;
69
- protected $lookAhead = null;
70
- protected $output = '';
71
- protected $lastByteOut = '';
72
- protected $keptComment = '';
73
-
74
- /**
75
- * Minify Javascript.
76
- *
77
- * @param string $js Javascript to be minified
78
- *
79
- * @return string
80
- */
81
- public static function minify($js)
82
- {
83
- $jsmin = new JSMin($js);
84
- return $jsmin->min();
85
- }
86
-
87
- /**
88
- * @param string $input
89
- */
90
- public function __construct($input)
91
- {
92
- $this->input = $input;
93
- }
94
-
95
- /**
96
- * Perform minification, return result
97
- *
98
- * @return string
99
- */
100
- public function min()
101
- {
102
- if ($this->output !== '') { // min already run
103
- return $this->output;
104
- }
105
-
106
- $mbIntEnc = null;
107
- if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
108
- $mbIntEnc = mb_internal_encoding();
109
- mb_internal_encoding('8bit');
110
- }
111
- $this->input = str_replace("\r\n", "\n", $this->input);
112
- $this->inputLength = strlen($this->input);
113
-
114
- $this->action(self::ACTION_DELETE_A_B);
115
-
116
- while ($this->a !== null) {
117
- // determine next command
118
- $command = self::ACTION_KEEP_A; // default
119
- if ($this->a === ' ') {
120
- if (($this->lastByteOut === '+' || $this->lastByteOut === '-')
121
- && ($this->b === $this->lastByteOut)) {
122
- // Don't delete this space. If we do, the addition/subtraction
123
- // could be parsed as a post-increment
124
- } elseif (! $this->isAlphaNum($this->b)) {
125
- $command = self::ACTION_DELETE_A;
126
- }
127
- } elseif ($this->a === "\n") {
128
- if ($this->b === ' ') {
129
- $command = self::ACTION_DELETE_A_B;
130
-
131
- // in case of mbstring.func_overload & 2, must check for null b,
132
- // otherwise mb_strpos will give WARNING
133
- } elseif ($this->b === null
134
- || (false === strpos('{[(+-!~', $this->b)
135
- && ! $this->isAlphaNum($this->b))) {
136
- $command = self::ACTION_DELETE_A;
137
- }
138
- } elseif (! $this->isAlphaNum($this->a)) {
139
- if ($this->b === ' '
140
- || ($this->b === "\n"
141
- && (false === strpos('}])+-"\'', $this->a)))) {
142
- $command = self::ACTION_DELETE_A_B;
143
- }
144
- }
145
- $this->action($command);
146
- }
147
- $this->output = trim($this->output);
148
-
149
- if ($mbIntEnc !== null) {
150
- mb_internal_encoding($mbIntEnc);
151
- }
152
- return $this->output;
153
- }
154
-
155
- /**
156
- * ACTION_KEEP_A = Output A. Copy B to A. Get the next B.
157
- * ACTION_DELETE_A = Copy B to A. Get the next B.
158
- * ACTION_DELETE_A_B = Get the next B.
159
- *
160
- * @param int $command
161
- * @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException
162
- */
163
- protected function action($command)
164
- {
165
- // make sure we don't compress "a + ++b" to "a+++b", etc.
166
- if ($command === self::ACTION_DELETE_A_B
167
- && $this->b === ' '
168
- && ($this->a === '+' || $this->a === '-')) {
169
- // Note: we're at an addition/substraction operator; the inputIndex
170
- // will certainly be a valid index
171
- if ($this->input[$this->inputIndex] === $this->a) {
172
- // This is "+ +" or "- -". Don't delete the space.
173
- $command = self::ACTION_KEEP_A;
174
- }
175
- }
176
-
177
- switch ($command) {
178
- case self::ACTION_KEEP_A: // 1
179
- $this->output .= $this->a;
180
-
181
- if ($this->keptComment) {
182
- $this->output = rtrim($this->output, "\n");
183
- $this->output .= $this->keptComment;
184
- $this->keptComment = '';
185
- }
186
-
187
- $this->lastByteOut = $this->a;
188
-
189
- // fallthrough intentional
190
- case self::ACTION_DELETE_A: // 2
191
- $this->a = $this->b;
192
- if ($this->a === "'" || $this->a === '"') { // string literal
193
- $str = $this->a; // in case needed for exception
194
- for(;;) {
195
- $this->output .= $this->a;
196
- $this->lastByteOut = $this->a;
197
-
198
- $this->a = $this->get();
199
- if ($this->a === $this->b) { // end quote
200
- break;
201
- }
202
- if ($this->isEOF($this->a)) {
203
- $byte = $this->inputIndex - 1;
204
- throw new JSMin_UnterminatedStringException(
205
- "JSMin: Unterminated String at byte {$byte}: {$str}");
206
- }
207
- $str .= $this->a;
208
- if ($this->a === '\\') {
209
- $this->output .= $this->a;
210
- $this->lastByteOut = $this->a;
211
-
212
- $this->a = $this->get();
213
- $str .= $this->a;
214
- }
215
- }
216
- }
217
-
218
- // fallthrough intentional
219
- case self::ACTION_DELETE_A_B: // 3
220
- $this->b = $this->next();
221
- if ($this->b === '/' && $this->isRegexpLiteral()) {
222
- $this->output .= $this->a . $this->b;
223
- $pattern = '/'; // keep entire pattern in case we need to report it in the exception
224
- for(;;) {
225
- $this->a = $this->get();
226
- $pattern .= $this->a;
227
- if ($this->a === '[') {
228
- for(;;) {
229
- $this->output .= $this->a;
230
- $this->a = $this->get();
231
- $pattern .= $this->a;
232
- if ($this->a === ']') {
233
- break;
234
- }
235
- if ($this->a === '\\') {
236
- $this->output .= $this->a;
237
- $this->a = $this->get();
238
- $pattern .= $this->a;
239
- }
240
- if ($this->isEOF($this->a)) {
241
- throw new JSMin_UnterminatedRegExpException(
242
- "JSMin: Unterminated set in RegExp at byte "
243
- . $this->inputIndex .": {$pattern}");
244
- }
245
- }
246
- }
247
-
248
- if ($this->a === '/') { // end pattern
249
- break; // while (true)
250
- } elseif ($this->a === '\\') {
251
- $this->output .= $this->a;
252
- $this->a = $this->get();
253
- $pattern .= $this->a;
254
- } elseif ($this->isEOF($this->a)) {
255
- $byte = $this->inputIndex - 1;
256
- throw new JSMin_UnterminatedRegExpException(
257
- "JSMin: Unterminated RegExp at byte {$byte}: {$pattern}");
258
- }
259
- $this->output .= $this->a;
260
- $this->lastByteOut = $this->a;
261
- }
262
- $this->b = $this->next();
263
- }
264
- // end case ACTION_DELETE_A_B
265
- }
266
- }
267
-
268
- /**
269
- * @return bool
270
- */
271
- protected function isRegexpLiteral()
272
- {
273
- if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) {
274
- // we obviously aren't dividing
275
- return true;
276
- }
277
-
278
- // we have to check for a preceding keyword, and we don't need to pattern
279
- // match over the whole output.
280
- $recentOutput = substr($this->output, -10);
281
-
282
- // check if return/typeof directly precede a pattern without a space
283
- foreach (array('return', 'typeof') as $keyword) {
284
- if ($this->a !== substr($keyword, -1)) {
285
- // certainly wasn't keyword
286
- continue;
287
- }
288
- if (preg_match("~(^|[\\s\\S])" . substr($keyword, 0, -1) . "$~", $recentOutput, $m)) {
289
- if ($m[1] === '' || !$this->isAlphaNum($m[1])) {
290
- return true;
291
- }
292
- }
293
- }
294
-
295
- // check all keywords
296
- if ($this->a === ' ' || $this->a === "\n") {
297
- if (preg_match('~(^|[\\s\\S])(?:case|else|in|return|typeof)$~', $recentOutput, $m)) {
298
- if ($m[1] === '' || !$this->isAlphaNum($m[1])) {
299
- return true;
300
- }
301
- }
302
- }
303
-
304
- return false;
305
- }
306
-
307
- /**
308
- * Return the next character from stdin. Watch out for lookahead. If the character is a control character,
309
- * translate it to a space or linefeed.
310
- *
311
- * @return string
312
- */
313
- protected function get()
314
- {
315
- $c = $this->lookAhead;
316
- $this->lookAhead = null;
317
- if ($c === null) {
318
- // getc(stdin)
319
- if ($this->inputIndex < $this->inputLength) {
320
- $c = $this->input[$this->inputIndex];
321
- $this->inputIndex += 1;
322
- } else {
323
- $c = null;
324
- }
325
- }
326
- if (ord($c) >= self::ORD_SPACE || $c === "\n" || $c === null) {
327
- return $c;
328
- }
329
- if ($c === "\r") {
330
- return "\n";
331
- }
332
- return ' ';
333
- }
334
-
335
- /**
336
- * Does $a indicate end of input?
337
- *
338
- * @param string $a
339
- * @return bool
340
- */
341
- protected function isEOF($a)
342
- {
343
- return ord($a) <= self::ORD_LF;
344
- }
345
-
346
- /**
347
- * Get next char (without getting it). If is ctrl character, translate to a space or newline.
348
- *
349
- * @return string
350
- */
351
- protected function peek()
352
- {
353
- $this->lookAhead = $this->get();
354
- return $this->lookAhead;
355
- }
356
-
357
- /**
358
- * Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character.
359
- *
360
- * @param string $c
361
- *
362
- * @return bool
363
- */
364
- protected function isAlphaNum($c)
365
- {
366
- return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126);
367
- }
368
-
369
- /**
370
- * Consume a single line comment from input (possibly retaining it)
371
- */
372
- protected function consumeSingleLineComment()
373
- {
374
- $comment = '';
375
- while (true) {
376
- $get = $this->get();
377
- $comment .= $get;
378
- if (ord($get) <= self::ORD_LF) { // end of line reached
379
- // if IE conditional comment
380
- if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
381
- $this->keptComment .= "/{$comment}";
382
- }
383
- return;
384
- }
385
- }
386
- }
387
-
388
- /**
389
- * Consume a multiple line comment from input (possibly retaining it)
390
- *
391
- * @throws JSMin_UnterminatedCommentException
392
- */
393
- protected function consumeMultipleLineComment()
394
- {
395
- $this->get();
396
- $comment = '';
397
- for(;;) {
398
- $get = $this->get();
399
- if ($get === '*') {
400
- if ($this->peek() === '/') { // end of comment reached
401
- $this->get();
402
- if (0 === strpos($comment, '!')) {
403
- // preserved by YUI Compressor
404
- if (!$this->keptComment) {
405
- // don't prepend a newline if two comments right after one another
406
- $this->keptComment = "\n";
407
- }
408
- $this->keptComment .= "/*!" . substr($comment, 1) . "*/\n";
409
- } else if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
410
- // IE conditional
411
- $this->keptComment .= "/*{$comment}*/";
412
- }
413
- return;
414
- }
415
- } elseif ($get === null) {
416
- throw new JSMin_UnterminatedCommentException(
417
- "JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}");
418
- }
419
- $comment .= $get;
420
- }
421
- }
422
-
423
- /**
424
- * Get the next character, skipping over comments. Some comments may be preserved.
425
- *
426
- * @return string
427
- */
428
- protected function next()
429
- {
430
- $get = $this->get();
431
- if ($get === '/') {
432
- switch ($this->peek()) {
433
- case '/':
434
- $this->consumeSingleLineComment();
435
- $get = "\n";
436
- break;
437
- case '*':
438
- $this->consumeMultipleLineComment();
439
- $get = ' ';
440
- break;
441
- }
442
- }
443
- return $get;
444
- }
445
- }
446
-
447
- class JSMin_UnterminatedStringException extends Exception {}
448
- class JSMin_UnterminatedCommentException extends Exception {}
449
- class JSMin_UnterminatedRegExpException extends Exception {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/JSMinMax.php DELETED
@@ -1,460 +0,0 @@
1
- <?php
2
- /**
3
- * JSMinMax.php - modified PHP implementation of Douglas Crockford's JSMinMax.
4
- *
5
- * <code>
6
- * $minifiedJs = JSMinMax::minify($js);
7
- * </code>
8
- *
9
- * This is a modified port of jsmin.c. Improvements:
10
- *
11
- * Does not choke on some regexp literals containing quote characters. E.g. /'/
12
- *
13
- * Spaces are preserved after some add/sub operators, so they are not mistakenly
14
- * converted to post-inc/dec. E.g. a + ++b -> a+ ++b
15
- *
16
- * Preserves multi-line comments that begin with /*!
17
- *
18
- * PHP 5 or higher is required.
19
- *
20
- * Permission is hereby granted to use this version of the library under the
21
- * same terms as jsmin.c, which has the following license:
22
- *
23
- * --
24
- * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
25
- *
26
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
27
- * this software and associated documentation files (the "Software"), to deal in
28
- * the Software without restriction, including without limitation the rights to
29
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
30
- * of the Software, and to permit persons to whom the Software is furnished to do
31
- * so, subject to the following conditions:
32
- *
33
- * The above copyright notice and this permission notice shall be included in all
34
- * copies or substantial portions of the Software.
35
- *
36
- * The Software shall be used for Good, not Evil.
37
- *
38
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44
- * SOFTWARE.
45
- * --
46
- *
47
- * @package JSMinMax
48
- * @author Ryan Grove <ryan@wonko.com> (PHP port)
49
- * @author Steve Clay <steve@mrclay.org> (modifications + cleanup)
50
- * @author Andrea Giammarchi <http://www.3site.eu> (spaceBeforeRegExp)
51
- * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
52
- * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
53
- * @license http://opensource.org/licenses/mit-license.php MIT License
54
- * @link http://code.google.com/p/jsmin-php/
55
- */
56
-
57
- /**
58
- * Modified version of a class for max minification (Removes important comments).
59
- *
60
- * @category Apptrian
61
- * @package Apptrian_Minify
62
- * @author Apptrian
63
- * @copyright Copyright (c) 2015 Apptrian (http://www.apptrian.com)
64
- * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
65
- */
66
-
67
- class JSMinMax {
68
- const ORD_LF = 10;
69
- const ORD_SPACE = 32;
70
- const ACTION_KEEP_A = 1;
71
- const ACTION_DELETE_A = 2;
72
- const ACTION_DELETE_A_B = 3;
73
-
74
- protected $a = "\n";
75
- protected $b = '';
76
- protected $input = '';
77
- protected $inputIndex = 0;
78
- protected $inputLength = 0;
79
- protected $lookAhead = null;
80
- protected $output = '';
81
- protected $lastByteOut = '';
82
- protected $keptComment = '';
83
-
84
- /**
85
- * Minify Javascript.
86
- *
87
- * @param string $js Javascript to be minified
88
- *
89
- * @return string
90
- */
91
- public static function minify($js)
92
- {
93
- $jsmin = new JSMinMax($js);
94
- return $jsmin->min();
95
- }
96
-
97
- /**
98
- * @param string $input
99
- */
100
- public function __construct($input)
101
- {
102
- $this->input = $input;
103
- }
104
-
105
- /**
106
- * Perform minification, return result
107
- *
108
- * @return string
109
- */
110
- public function min()
111
- {
112
- if ($this->output !== '') { // min already run
113
- return $this->output;
114
- }
115
-
116
- $mbIntEnc = null;
117
- if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
118
- $mbIntEnc = mb_internal_encoding();
119
- mb_internal_encoding('8bit');
120
- }
121
- $this->input = str_replace("\r\n", "\n", $this->input);
122
- $this->inputLength = strlen($this->input);
123
-
124
- $this->action(self::ACTION_DELETE_A_B);
125
-
126
- while ($this->a !== null) {
127
- // determine next command
128
- $command = self::ACTION_KEEP_A; // default
129
- if ($this->a === ' ') {
130
- if (($this->lastByteOut === '+' || $this->lastByteOut === '-')
131
- && ($this->b === $this->lastByteOut)) {
132
- // Don't delete this space. If we do, the addition/subtraction
133
- // could be parsed as a post-increment
134
- } elseif (! $this->isAlphaNum($this->b)) {
135
- $command = self::ACTION_DELETE_A;
136
- }
137
- } elseif ($this->a === "\n") {
138
- if ($this->b === ' ') {
139
- $command = self::ACTION_DELETE_A_B;
140
-
141
- // in case of mbstring.func_overload & 2, must check for null b,
142
- // otherwise mb_strpos will give WARNING
143
- } elseif ($this->b === null
144
- || (false === strpos('{[(+-!~', $this->b)
145
- && ! $this->isAlphaNum($this->b))) {
146
- $command = self::ACTION_DELETE_A;
147
- }
148
- } elseif (! $this->isAlphaNum($this->a)) {
149
- if ($this->b === ' '
150
- || ($this->b === "\n"
151
- && (false === strpos('}])+-"\'', $this->a)))) {
152
- $command = self::ACTION_DELETE_A_B;
153
- }
154
- }
155
- $this->action($command);
156
- }
157
- $this->output = trim($this->output);
158
-
159
- if ($mbIntEnc !== null) {
160
- mb_internal_encoding($mbIntEnc);
161
- }
162
- return $this->output;
163
- }
164
-
165
- /**
166
- * ACTION_KEEP_A = Output A. Copy B to A. Get the next B.
167
- * ACTION_DELETE_A = Copy B to A. Get the next B.
168
- * ACTION_DELETE_A_B = Get the next B.
169
- *
170
- * @param int $command
171
- * @throws JSMinMax_UnterminatedRegExpException|JSMinMax_UnterminatedStringException
172
- */
173
- protected function action($command)
174
- {
175
- // make sure we don't compress "a + ++b" to "a+++b", etc.
176
- if ($command === self::ACTION_DELETE_A_B
177
- && $this->b === ' '
178
- && ($this->a === '+' || $this->a === '-')) {
179
- // Note: we're at an addition/substraction operator; the inputIndex
180
- // will certainly be a valid index
181
- if ($this->input[$this->inputIndex] === $this->a) {
182
- // This is "+ +" or "- -". Don't delete the space.
183
- $command = self::ACTION_KEEP_A;
184
- }
185
- }
186
-
187
- switch ($command) {
188
- case self::ACTION_KEEP_A: // 1
189
- $this->output .= $this->a;
190
-
191
- if ($this->keptComment) {
192
- $this->output = rtrim($this->output, "\n");
193
- $this->output .= $this->keptComment;
194
- $this->keptComment = '';
195
- }
196
-
197
- $this->lastByteOut = $this->a;
198
-
199
- // fallthrough intentional
200
- case self::ACTION_DELETE_A: // 2
201
- $this->a = $this->b;
202
- if ($this->a === "'" || $this->a === '"') { // string literal
203
- $str = $this->a; // in case needed for exception
204
- for(;;) {
205
- $this->output .= $this->a;
206
- $this->lastByteOut = $this->a;
207
-
208
- $this->a = $this->get();
209
- if ($this->a === $this->b) { // end quote
210
- break;
211
- }
212
- if ($this->isEOF($this->a)) {
213
- $byte = $this->inputIndex - 1;
214
- throw new JSMinMax_UnterminatedStringException(
215
- "JSMinMax: Unterminated String at byte {$byte}: {$str}");
216
- }
217
- $str .= $this->a;
218
- if ($this->a === '\\') {
219
- $this->output .= $this->a;
220
- $this->lastByteOut = $this->a;
221
-
222
- $this->a = $this->get();
223
- $str .= $this->a;
224
- }
225
- }
226
- }
227
-
228
- // fallthrough intentional
229
- case self::ACTION_DELETE_A_B: // 3
230
- $this->b = $this->next();
231
- if ($this->b === '/' && $this->isRegexpLiteral()) {
232
- $this->output .= $this->a . $this->b;
233
- $pattern = '/'; // keep entire pattern in case we need to report it in the exception
234
- for(;;) {
235
- $this->a = $this->get();
236
- $pattern .= $this->a;
237
- if ($this->a === '[') {
238
- for(;;) {
239
- $this->output .= $this->a;
240
- $this->a = $this->get();
241
- $pattern .= $this->a;
242
- if ($this->a === ']') {
243
- break;
244
- }
245
- if ($this->a === '\\') {
246
- $this->output .= $this->a;
247
- $this->a = $this->get();
248
- $pattern .= $this->a;
249
- }
250
- if ($this->isEOF($this->a)) {
251
- throw new JSMinMax_UnterminatedRegExpException(
252
- "JSMinMax: Unterminated set in RegExp at byte "
253
- . $this->inputIndex .": {$pattern}");
254
- }
255
- }
256
- }
257
-
258
- if ($this->a === '/') { // end pattern
259
- break; // while (true)
260
- } elseif ($this->a === '\\') {
261
- $this->output .= $this->a;
262
- $this->a = $this->get();
263
- $pattern .= $this->a;
264
- } elseif ($this->isEOF($this->a)) {
265
- $byte = $this->inputIndex - 1;
266
- throw new JSMinMax_UnterminatedRegExpException(
267
- "JSMinMax: Unterminated RegExp at byte {$byte}: {$pattern}");
268
- }
269
- $this->output .= $this->a;
270
- $this->lastByteOut = $this->a;
271
- }
272
- $this->b = $this->next();
273
- }
274
- // end case ACTION_DELETE_A_B
275
- }
276
- }
277
-
278
- /**
279
- * @return bool
280
- */
281
- protected function isRegexpLiteral()
282
- {
283
- if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) {
284
- // we obviously aren't dividing
285
- return true;
286
- }
287
-
288
- // we have to check for a preceding keyword, and we don't need to pattern
289
- // match over the whole output.
290
- $recentOutput = substr($this->output, -10);
291
-
292
- // check if return/typeof directly precede a pattern without a space
293
- foreach (array('return', 'typeof') as $keyword) {
294
- if ($this->a !== substr($keyword, -1)) {
295
- // certainly wasn't keyword
296
- continue;
297
- }
298
- if (preg_match("~(^|[\\s\\S])" . substr($keyword, 0, -1) . "$~", $recentOutput, $m)) {
299
- if ($m[1] === '' || !$this->isAlphaNum($m[1])) {
300
- return true;
301
- }
302
- }
303
- }
304
-
305
- // check all keywords
306
- if ($this->a === ' ' || $this->a === "\n") {
307
- if (preg_match('~(^|[\\s\\S])(?:case|else|in|return|typeof)$~', $recentOutput, $m)) {
308
- if ($m[1] === '' || !$this->isAlphaNum($m[1])) {
309
- return true;
310
- }
311
- }
312
- }
313
-
314
- return false;
315
- }
316
-
317
- /**
318
- * Return the next character from stdin. Watch out for lookahead. If the character is a control character,
319
- * translate it to a space or linefeed.
320
- *
321
- * @return string
322
- */
323
- protected function get()
324
- {
325
- $c = $this->lookAhead;
326
- $this->lookAhead = null;
327
- if ($c === null) {
328
- // getc(stdin)
329
- if ($this->inputIndex < $this->inputLength) {
330
- $c = $this->input[$this->inputIndex];
331
- $this->inputIndex += 1;
332
- } else {
333
- $c = null;
334
- }
335
- }
336
- if (ord($c) >= self::ORD_SPACE || $c === "\n" || $c === null) {
337
- return $c;
338
- }
339
- if ($c === "\r") {
340
- return "\n";
341
- }
342
- return ' ';
343
- }
344
-
345
- /**
346
- * Does $a indicate end of input?
347
- *
348
- * @param string $a
349
- * @return bool
350
- */
351
- protected function isEOF($a)
352
- {
353
- return ord($a) <= self::ORD_LF;
354
- }
355
-
356
- /**
357
- * Get next char (without getting it). If is ctrl character, translate to a space or newline.
358
- *
359
- * @return string
360
- */
361
- protected function peek()
362
- {
363
- $this->lookAhead = $this->get();
364
- return $this->lookAhead;
365
- }
366
-
367
- /**
368
- * Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character.
369
- *
370
- * @param string $c
371
- *
372
- * @return bool
373
- */
374
- protected function isAlphaNum($c)
375
- {
376
- return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126);
377
- }
378
-
379
- /**
380
- * Consume a single line comment from input (possibly retaining it)
381
- */
382
- protected function consumeSingleLineComment()
383
- {
384
- $comment = '';
385
- while (true) {
386
- $get = $this->get();
387
- $comment .= $get;
388
- if (ord($get) <= self::ORD_LF) { // end of line reached
389
- // if IE conditional comment
390
- if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
391
- $this->keptComment .= "/{$comment}";
392
- }
393
- return;
394
- }
395
- }
396
- }
397
-
398
- /**
399
- * Consume a multiple line comment from input (possibly retaining it)
400
- *
401
- * @throws JSMinMax_UnterminatedCommentException
402
- */
403
- protected function consumeMultipleLineComment()
404
- {
405
- $this->get();
406
- $comment = '';
407
- for(;;) {
408
- $get = $this->get();
409
- if ($get === '*') {
410
- if ($this->peek() === '/') { // end of comment reached
411
- $this->get();
412
- if (0 === strpos($comment, '!')) {
413
- // preserved by YUI Compressor
414
- if (!$this->keptComment) {
415
- // don't prepend a newline if two comments right after one another
416
- $this->keptComment = "\n";
417
- }
418
- //$this->keptComment .= "/*!" . substr($comment, 1) . "*/\n";
419
- $this->keptComment .= "\n";
420
- } else if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
421
- // IE conditional
422
- $this->keptComment .= "/*{$comment}*/";
423
- }
424
- return;
425
- }
426
- } elseif ($get === null) {
427
- throw new JSMinMax_UnterminatedCommentException(
428
- "JSMinMax: Unterminated comment at byte {$this->inputIndex}: /*{$comment}");
429
- }
430
- $comment .= $get;
431
- }
432
- }
433
-
434
- /**
435
- * Get the next character, skipping over comments. Some comments may be preserved.
436
- *
437
- * @return string
438
- */
439
- protected function next()
440
- {
441
- $get = $this->get();
442
- if ($get === '/') {
443
- switch ($this->peek()) {
444
- case '/':
445
- $this->consumeSingleLineComment();
446
- $get = "\n";
447
- break;
448
- case '*':
449
- $this->consumeMultipleLineComment();
450
- $get = ' ';
451
- break;
452
- }
453
- }
454
- return $get;
455
- }
456
- }
457
-
458
- class JSMinMax_UnterminatedStringException extends Exception {}
459
- class JSMinMax_UnterminatedCommentException extends Exception {}
460
- class JSMinMax_UnterminatedRegExpException extends Exception {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/JSMinPlus.php DELETED
@@ -1,2086 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * JSMinPlus version 1.4
5
- *
6
- * Minifies a javascript file using a javascript parser
7
- *
8
- * This implements a PHP port of Brendan Eich's Narcissus open source javascript engine (in javascript)
9
- * References: http://en.wikipedia.org/wiki/Narcissus_(JavaScript_engine)
10
- * Narcissus sourcecode: http://mxr.mozilla.org/mozilla/source/js/narcissus/
11
- * JSMinPlus weblog: http://crisp.tweakblogs.net/blog/cat/716
12
- *
13
- * Tino Zijdel <crisp@tweakers.net>
14
- *
15
- * Usage: $minified = JSMinPlus::minify($script [, $filename])
16
- *
17
- * Versionlog (see also changelog.txt):
18
- * 23-07-2011 - remove dynamic creation of OP_* and KEYWORD_* defines and declare them on top
19
- * reduce memory footprint by minifying by block-scope
20
- * some small byte-saving and performance improvements
21
- * 12-05-2009 - fixed hook:colon precedence, fixed empty body in loop and if-constructs
22
- * 18-04-2009 - fixed crashbug in PHP 5.2.9 and several other bugfixes
23
- * 12-04-2009 - some small bugfixes and performance improvements
24
- * 09-04-2009 - initial open sourced version 1.0
25
- *
26
- * Latest version of this script: http://files.tweakers.net/jsminplus/jsminplus.zip
27
- *
28
- */
29
-
30
- /* ***** BEGIN LICENSE BLOCK *****
31
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
32
- *
33
- * The contents of this file are subject to the Mozilla Public License Version
34
- * 1.1 (the "License"); you may not use this file except in compliance with
35
- * the License. You may obtain a copy of the License at
36
- * http://www.mozilla.org/MPL/
37
- *
38
- * Software distributed under the License is distributed on an "AS IS" basis,
39
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
40
- * for the specific language governing rights and limitations under the
41
- * License.
42
- *
43
- * The Original Code is the Narcissus JavaScript engine.
44
- *
45
- * The Initial Developer of the Original Code is
46
- * Brendan Eich <brendan@mozilla.org>.
47
- * Portions created by the Initial Developer are Copyright (C) 2004
48
- * the Initial Developer. All Rights Reserved.
49
- *
50
- * Contributor(s): Tino Zijdel <crisp@tweakers.net>
51
- * PHP port, modifications and minifier routine are (C) 2009-2011
52
- *
53
- * Alternatively, the contents of this file may be used under the terms of
54
- * either the GNU General Public License Version 2 or later (the "GPL"), or
55
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
56
- * in which case the provisions of the GPL or the LGPL are applicable instead
57
- * of those above. If you wish to allow use of your version of this file only
58
- * under the terms of either the GPL or the LGPL, and not to allow others to
59
- * use your version of this file under the terms of the MPL, indicate your
60
- * decision by deleting the provisions above and replace them with the notice
61
- * and other provisions required by the GPL or the LGPL. If you do not delete
62
- * the provisions above, a recipient may use your version of this file under
63
- * the terms of any one of the MPL, the GPL or the LGPL.
64
- *
65
- * ***** END LICENSE BLOCK ***** */
66
-
67
- define('TOKEN_END', 1);
68
- define('TOKEN_NUMBER', 2);
69
- define('TOKEN_IDENTIFIER', 3);
70
- define('TOKEN_STRING', 4);
71
- define('TOKEN_REGEXP', 5);
72
- define('TOKEN_NEWLINE', 6);
73
- define('TOKEN_CONDCOMMENT_START', 7);
74
- define('TOKEN_CONDCOMMENT_END', 8);
75
-
76
- define('JS_SCRIPT', 100);
77
- define('JS_BLOCK', 101);
78
- define('JS_LABEL', 102);
79
- define('JS_FOR_IN', 103);
80
- define('JS_CALL', 104);
81
- define('JS_NEW_WITH_ARGS', 105);
82
- define('JS_INDEX', 106);
83
- define('JS_ARRAY_INIT', 107);
84
- define('JS_OBJECT_INIT', 108);
85
- define('JS_PROPERTY_INIT', 109);
86
- define('JS_GETTER', 110);
87
- define('JS_SETTER', 111);
88
- define('JS_GROUP', 112);
89
- define('JS_LIST', 113);
90
-
91
- define('JS_MINIFIED', 999);
92
-
93
- define('DECLARED_FORM', 0);
94
- define('EXPRESSED_FORM', 1);
95
- define('STATEMENT_FORM', 2);
96
-
97
- /* Operators */
98
- define('OP_SEMICOLON', ';');
99
- define('OP_COMMA', ',');
100
- define('OP_HOOK', '?');
101
- define('OP_COLON', ':');
102
- define('OP_OR', '||');
103
- define('OP_AND', '&&');
104
- define('OP_BITWISE_OR', '|');
105
- define('OP_BITWISE_XOR', '^');
106
- define('OP_BITWISE_AND', '&');
107
- define('OP_STRICT_EQ', '===');
108
- define('OP_EQ', '==');
109
- define('OP_ASSIGN', '=');
110
- define('OP_STRICT_NE', '!==');
111
- define('OP_NE', '!=');
112
- define('OP_LSH', '<<');
113
- define('OP_LE', '<=');
114
- define('OP_LT', '<');
115
- define('OP_URSH', '>>>');
116
- define('OP_RSH', '>>');
117
- define('OP_GE', '>=');
118
- define('OP_GT', '>');
119
- define('OP_INCREMENT', '++');
120
- define('OP_DECREMENT', '--');
121
- define('OP_PLUS', '+');
122
- define('OP_MINUS', '-');
123
- define('OP_MUL', '*');
124
- define('OP_DIV', '/');
125
- define('OP_MOD', '%');
126
- define('OP_NOT', '!');
127
- define('OP_BITWISE_NOT', '~');
128
- define('OP_DOT', '.');
129
- define('OP_LEFT_BRACKET', '[');
130
- define('OP_RIGHT_BRACKET', ']');
131
- define('OP_LEFT_CURLY', '{');
132
- define('OP_RIGHT_CURLY', '}');
133
- define('OP_LEFT_PAREN', '(');
134
- define('OP_RIGHT_PAREN', ')');
135
- define('OP_CONDCOMMENT_END', '@*/');
136
-
137
- define('OP_UNARY_PLUS', 'U+');
138
- define('OP_UNARY_MINUS', 'U-');
139
-
140
- /* Keywords */
141
- define('KEYWORD_BREAK', 'break');
142
- define('KEYWORD_CASE', 'case');
143
- define('KEYWORD_CATCH', 'catch');
144
- define('KEYWORD_CONST', 'const');
145
- define('KEYWORD_CONTINUE', 'continue');
146
- define('KEYWORD_DEBUGGER', 'debugger');
147
- define('KEYWORD_DEFAULT', 'default');
148
- define('KEYWORD_DELETE', 'delete');
149
- define('KEYWORD_DO', 'do');
150
- define('KEYWORD_ELSE', 'else');
151
- define('KEYWORD_ENUM', 'enum');
152
- define('KEYWORD_FALSE', 'false');
153
- define('KEYWORD_FINALLY', 'finally');
154
- define('KEYWORD_FOR', 'for');
155
- define('KEYWORD_FUNCTION', 'function');
156
- define('KEYWORD_IF', 'if');
157
- define('KEYWORD_IN', 'in');
158
- define('KEYWORD_INSTANCEOF', 'instanceof');
159
- define('KEYWORD_NEW', 'new');
160
- define('KEYWORD_NULL', 'null');
161
- define('KEYWORD_RETURN', 'return');
162
- define('KEYWORD_SWITCH', 'switch');
163
- define('KEYWORD_THIS', 'this');
164
- define('KEYWORD_THROW', 'throw');
165
- define('KEYWORD_TRUE', 'true');
166
- define('KEYWORD_TRY', 'try');
167
- define('KEYWORD_TYPEOF', 'typeof');
168
- define('KEYWORD_VAR', 'var');
169
- define('KEYWORD_VOID', 'void');
170
- define('KEYWORD_WHILE', 'while');
171
- define('KEYWORD_WITH', 'with');
172
-
173
-
174
- class JSMinPlus
175
- {
176
- private $parser;
177
- private $reserved = array(
178
- 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
179
- 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
180
- 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
181
- 'void', 'while', 'with',
182
- // Words reserved for future use
183
- 'abstract', 'boolean', 'byte', 'char', 'class', 'const', 'debugger',
184
- 'double', 'enum', 'export', 'extends', 'final', 'float', 'goto',
185
- 'implements', 'import', 'int', 'interface', 'long', 'native',
186
- 'package', 'private', 'protected', 'public', 'short', 'static',
187
- 'super', 'synchronized', 'throws', 'transient', 'volatile',
188
- // These are not reserved, but should be taken into account
189
- // in isValidIdentifier (See jslint source code)
190
- 'arguments', 'eval', 'true', 'false', 'Infinity', 'NaN', 'null', 'undefined'
191
- );
192
-
193
- private function __construct()
194
- {
195
- $this->parser = new JSParser($this);
196
- }
197
-
198
- public static function minify($js, $filename='')
199
- {
200
- static $instance;
201
-
202
- // this is a singleton
203
- if(!$instance)
204
- $instance = new JSMinPlus();
205
-
206
- return $instance->min($js, $filename);
207
- }
208
-
209
- private function min($js, $filename)
210
- {
211
- try
212
- {
213
- $n = $this->parser->parse($js, $filename, 1);
214
- return $this->parseTree($n);
215
- }
216
- catch(Exception $e)
217
- {
218
- echo $e->getMessage() . "\n";
219
- }
220
-
221
- return false;
222
- }
223
-
224
- public function parseTree($n, $noBlockGrouping = false)
225
- {
226
- $s = '';
227
-
228
- switch ($n->type)
229
- {
230
- case JS_MINIFIED:
231
- $s = $n->value;
232
- break;
233
-
234
- case JS_SCRIPT:
235
- // we do nothing yet with funDecls or varDecls
236
- $noBlockGrouping = true;
237
- // FALL THROUGH
238
-
239
- case JS_BLOCK:
240
- $childs = $n->treeNodes;
241
- $lastType = 0;
242
- for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++)
243
- {
244
- $type = $childs[$i]->type;
245
- $t = $this->parseTree($childs[$i]);
246
- if (strlen($t))
247
- {
248
- if ($c)
249
- {
250
- $s = rtrim($s, ';');
251
-
252
- if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM)
253
- {
254
- // put declared functions on a new line
255
- $s .= "\n";
256
- }
257
- elseif ($type == KEYWORD_VAR && $type == $lastType)
258
- {
259
- // mutiple var-statements can go into one
260
- $t = ',' . substr($t, 4);
261
- }
262
- else
263
- {
264
- // add terminator
265
- $s .= ';';
266
- }
267
- }
268
-
269
- $s .= $t;
270
-
271
- $c++;
272
- $lastType = $type;
273
- }
274
- }
275
-
276
- if ($c > 1 && !$noBlockGrouping)
277
- {
278
- $s = '{' . $s . '}';
279
- }
280
- break;
281
-
282
- case KEYWORD_FUNCTION:
283
- $s .= 'function' . ($n->name ? ' ' . $n->name : '') . '(';
284
- $params = $n->params;
285
- for ($i = 0, $j = count($params); $i < $j; $i++)
286
- $s .= ($i ? ',' : '') . $params[$i];
287
- $s .= '){' . $this->parseTree($n->body, true) . '}';
288
- break;
289
-
290
- case KEYWORD_IF:
291
- $s = 'if(' . $this->parseTree($n->condition) . ')';
292
- $thenPart = $this->parseTree($n->thenPart);
293
- $elsePart = $n->elsePart ? $this->parseTree($n->elsePart) : null;
294
-
295
- // empty if-statement
296
- if ($thenPart == '')
297
- $thenPart = ';';
298
-
299
- if ($elsePart)
300
- {
301
- // be carefull and always make a block out of the thenPart; could be more optimized but is a lot of trouble
302
- if ($thenPart != ';' && $thenPart[0] != '{')
303
- $thenPart = '{' . $thenPart . '}';
304
-
305
- $s .= $thenPart . 'else';
306
-
307
- // we could check for more, but that hardly ever applies so go for performance
308
- if ($elsePart[0] != '{')
309
- $s .= ' ';
310
-
311
- $s .= $elsePart;
312
- }
313
- else
314
- {
315
- $s .= $thenPart;
316
- }
317
- break;
318
-
319
- case KEYWORD_SWITCH:
320
- $s = 'switch(' . $this->parseTree($n->discriminant) . '){';
321
- $cases = $n->cases;
322
- for ($i = 0, $j = count($cases); $i < $j; $i++)
323
- {
324
- $case = $cases[$i];
325
- if ($case->type == KEYWORD_CASE)
326
- $s .= 'case' . ($case->caseLabel->type != TOKEN_STRING ? ' ' : '') . $this->parseTree($case->caseLabel) . ':';
327
- else
328
- $s .= 'default:';
329
-
330
- $statement = $this->parseTree($case->statements, true);
331
- if ($statement)
332
- {
333
- $s .= $statement;
334
- // no terminator for last statement
335
- if ($i + 1 < $j)
336
- $s .= ';';
337
- }
338
- }
339
- $s .= '}';
340
- break;
341
-
342
- case KEYWORD_FOR:
343
- $s = 'for(' . ($n->setup ? $this->parseTree($n->setup) : '')
344
- . ';' . ($n->condition ? $this->parseTree($n->condition) : '')
345
- . ';' . ($n->update ? $this->parseTree($n->update) : '') . ')';
346
-
347
- $body = $this->parseTree($n->body);
348
- if ($body == '')
349
- $body = ';';
350
-
351
- $s .= $body;
352
- break;
353
-
354
- case KEYWORD_WHILE:
355
- $s = 'while(' . $this->parseTree($n->condition) . ')';
356
-
357
- $body = $this->parseTree($n->body);
358
- if ($body == '')
359
- $body = ';';
360
-
361
- $s .= $body;
362
- break;
363
-
364
- case JS_FOR_IN:
365
- $s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')';
366
-
367
- $body = $this->parseTree($n->body);
368
- if ($body == '')
369
- $body = ';';
370
-
371
- $s .= $body;
372
- break;
373
-
374
- case KEYWORD_DO:
375
- $s = 'do{' . $this->parseTree($n->body, true) . '}while(' . $this->parseTree($n->condition) . ')';
376
- break;
377
-
378
- case KEYWORD_BREAK:
379
- case KEYWORD_CONTINUE:
380
- $s = $n->value . ($n->label ? ' ' . $n->label : '');
381
- break;
382
-
383
- case KEYWORD_TRY:
384
- $s = 'try{' . $this->parseTree($n->tryBlock, true) . '}';
385
- $catchClauses = $n->catchClauses;
386
- for ($i = 0, $j = count($catchClauses); $i < $j; $i++)
387
- {
388
- $t = $catchClauses[$i];
389
- $s .= 'catch(' . $t->varName . ($t->guard ? ' if ' . $this->parseTree($t->guard) : '') . '){' . $this->parseTree($t->block, true) . '}';
390
- }
391
- if ($n->finallyBlock)
392
- $s .= 'finally{' . $this->parseTree($n->finallyBlock, true) . '}';
393
- break;
394
-
395
- case KEYWORD_THROW:
396
- case KEYWORD_RETURN:
397
- $s = $n->type;
398
- if ($n->value)
399
- {
400
- $t = $this->parseTree($n->value);
401
- if (strlen($t))
402
- {
403
- if ($this->isWordChar($t[0]) || $t[0] == '\\')
404
- $s .= ' ';
405
-
406
- $s .= $t;
407
- }
408
- }
409
- break;
410
-
411
- case KEYWORD_WITH:
412
- $s = 'with(' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body);
413
- break;
414
-
415
- case KEYWORD_VAR:
416
- case KEYWORD_CONST:
417
- $s = $n->value . ' ';
418
- $childs = $n->treeNodes;
419
- for ($i = 0, $j = count($childs); $i < $j; $i++)
420
- {
421
- $t = $childs[$i];
422
- $s .= ($i ? ',' : '') . $t->name;
423
- $u = $t->initializer;
424
- if ($u)
425
- $s .= '=' . $this->parseTree($u);
426
- }
427
- break;
428
-
429
- case KEYWORD_IN:
430
- case KEYWORD_INSTANCEOF:
431
- $left = $this->parseTree($n->treeNodes[0]);
432
- $right = $this->parseTree($n->treeNodes[1]);
433
-
434
- $s = $left;
435
-
436
- if ($this->isWordChar(substr($left, -1)))
437
- $s .= ' ';
438
-
439
- $s .= $n->type;
440
-
441
- if ($this->isWordChar($right[0]) || $right[0] == '\\')
442
- $s .= ' ';
443
-
444
- $s .= $right;
445
- break;
446
-
447
- case KEYWORD_DELETE:
448
- case KEYWORD_TYPEOF:
449
- $right = $this->parseTree($n->treeNodes[0]);
450
-
451
- $s = $n->type;
452
-
453
- if ($this->isWordChar($right[0]) || $right[0] == '\\')
454
- $s .= ' ';
455
-
456
- $s .= $right;
457
- break;
458
-
459
- case KEYWORD_VOID:
460
- $s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')';
461
- break;
462
-
463
- case KEYWORD_DEBUGGER:
464
- throw new Exception('NOT IMPLEMENTED: DEBUGGER');
465
- break;
466
-
467
- case TOKEN_CONDCOMMENT_START:
468
- case TOKEN_CONDCOMMENT_END:
469
- $s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : '');
470
- $childs = $n->treeNodes;
471
- for ($i = 0, $j = count($childs); $i < $j; $i++)
472
- $s .= $this->parseTree($childs[$i]);
473
- break;
474
-
475
- case OP_SEMICOLON:
476
- if ($expression = $n->expression)
477
- $s = $this->parseTree($expression);
478
- break;
479
-
480
- case JS_LABEL:
481
- $s = $n->label . ':' . $this->parseTree($n->statement);
482
- break;
483
-
484
- case OP_COMMA:
485
- $childs = $n->treeNodes;
486
- for ($i = 0, $j = count($childs); $i < $j; $i++)
487
- $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
488
- break;
489
-
490
- case OP_ASSIGN:
491
- $s = $this->parseTree($n->treeNodes[0]) . $n->value . $this->parseTree($n->treeNodes[1]);
492
- break;
493
-
494
- case OP_HOOK:
495
- $s = $this->parseTree($n->treeNodes[0]) . '?' . $this->parseTree($n->treeNodes[1]) . ':' . $this->parseTree($n->treeNodes[2]);
496
- break;
497
-
498
- case OP_OR: case OP_AND:
499
- case OP_BITWISE_OR: case OP_BITWISE_XOR: case OP_BITWISE_AND:
500
- case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
501
- case OP_LT: case OP_LE: case OP_GE: case OP_GT:
502
- case OP_LSH: case OP_RSH: case OP_URSH:
503
- case OP_MUL: case OP_DIV: case OP_MOD:
504
- $s = $this->parseTree($n->treeNodes[0]) . $n->type . $this->parseTree($n->treeNodes[1]);
505
- break;
506
-
507
- case OP_PLUS:
508
- case OP_MINUS:
509
- $left = $this->parseTree($n->treeNodes[0]);
510
- $right = $this->parseTree($n->treeNodes[1]);
511
-
512
- switch ($n->treeNodes[1]->type)
513
- {
514
- case OP_PLUS:
515
- case OP_MINUS:
516
- case OP_INCREMENT:
517
- case OP_DECREMENT:
518
- case OP_UNARY_PLUS:
519
- case OP_UNARY_MINUS:
520
- $s = $left . $n->type . ' ' . $right;
521
- break;
522
-
523
- case TOKEN_STRING:
524
- //combine concatted strings with same quotestyle
525
- if ($n->type == OP_PLUS && substr($left, -1) == $right[0])
526
- {
527
- $s = substr($left, 0, -1) . substr($right, 1);
528
- break;
529
- }
530
- // FALL THROUGH
531
-
532
- default:
533
- $s = $left . $n->type . $right;
534
- }
535
- break;
536
-
537
- case OP_NOT:
538
- case OP_BITWISE_NOT:
539
- case OP_UNARY_PLUS:
540
- case OP_UNARY_MINUS:
541
- $s = $n->value . $this->parseTree($n->treeNodes[0]);
542
- break;
543
-
544
- case OP_INCREMENT:
545
- case OP_DECREMENT:
546
- if ($n->postfix)
547
- $s = $this->parseTree($n->treeNodes[0]) . $n->value;
548
- else
549
- $s = $n->value . $this->parseTree($n->treeNodes[0]);
550
- break;
551
-
552
- case OP_DOT:
553
- $s = $this->parseTree($n->treeNodes[0]) . '.' . $this->parseTree($n->treeNodes[1]);
554
- break;
555
-
556
- case JS_INDEX:
557
- $s = $this->parseTree($n->treeNodes[0]);
558
- // See if we can replace named index with a dot saving 3 bytes
559
- if ( $n->treeNodes[0]->type == TOKEN_IDENTIFIER &&
560
- $n->treeNodes[1]->type == TOKEN_STRING &&
561
- $this->isValidIdentifier(substr($n->treeNodes[1]->value, 1, -1))
562
- )
563
- $s .= '.' . substr($n->treeNodes[1]->value, 1, -1);
564
- else
565
- $s .= '[' . $this->parseTree($n->treeNodes[1]) . ']';
566
- break;
567
-
568
- case JS_LIST:
569
- $childs = $n->treeNodes;
570
- for ($i = 0, $j = count($childs); $i < $j; $i++)
571
- $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
572
- break;
573
-
574
- case JS_CALL:
575
- $s = $this->parseTree($n->treeNodes[0]) . '(' . $this->parseTree($n->treeNodes[1]) . ')';
576
- break;
577
-
578
- case KEYWORD_NEW:
579
- case JS_NEW_WITH_ARGS:
580
- $s = 'new ' . $this->parseTree($n->treeNodes[0]) . '(' . ($n->type == JS_NEW_WITH_ARGS ? $this->parseTree($n->treeNodes[1]) : '') . ')';
581
- break;
582
-
583
- case JS_ARRAY_INIT:
584
- $s = '[';
585
- $childs = $n->treeNodes;
586
- for ($i = 0, $j = count($childs); $i < $j; $i++)
587
- {
588
- $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
589
- }
590
- $s .= ']';
591
- break;
592
-
593
- case JS_OBJECT_INIT:
594
- $s = '{';
595
- $childs = $n->treeNodes;
596
- for ($i = 0, $j = count($childs); $i < $j; $i++)
597
- {
598
- $t = $childs[$i];
599
- if ($i)
600
- $s .= ',';
601
- if ($t->type == JS_PROPERTY_INIT)
602
- {
603
- // Ditch the quotes when the index is a valid identifier
604
- if ( $t->treeNodes[0]->type == TOKEN_STRING &&
605
- $this->isValidIdentifier(substr($t->treeNodes[0]->value, 1, -1))
606
- )
607
- $s .= substr($t->treeNodes[0]->value, 1, -1);
608
- else
609
- $s .= $t->treeNodes[0]->value;
610
-
611
- $s .= ':' . $this->parseTree($t->treeNodes[1]);
612
- }
613
- else
614
- {
615
- $s .= $t->type == JS_GETTER ? 'get' : 'set';
616
- $s .= ' ' . $t->name . '(';
617
- $params = $t->params;
618
- for ($i = 0, $j = count($params); $i < $j; $i++)
619
- $s .= ($i ? ',' : '') . $params[$i];
620
- $s .= '){' . $this->parseTree($t->body, true) . '}';
621
- }
622
- }
623
- $s .= '}';
624
- break;
625
-
626
- case TOKEN_NUMBER:
627
- $s = $n->value;
628
- if (preg_match('/^([1-9]+)(0{3,})$/', $s, $m))
629
- $s = $m[1] . 'e' . strlen($m[2]);
630
- break;
631
-
632
- case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
633
- case TOKEN_IDENTIFIER: case TOKEN_STRING: case TOKEN_REGEXP:
634
- $s = $n->value;
635
- break;
636
-
637
- case JS_GROUP:
638
- if (in_array(
639
- $n->treeNodes[0]->type,
640
- array(
641
- JS_ARRAY_INIT, JS_OBJECT_INIT, JS_GROUP,
642
- TOKEN_NUMBER, TOKEN_STRING, TOKEN_REGEXP, TOKEN_IDENTIFIER,
643
- KEYWORD_NULL, KEYWORD_THIS, KEYWORD_TRUE, KEYWORD_FALSE
644
- )
645
- ))
646
- {
647
- $s = $this->parseTree($n->treeNodes[0]);
648
- }
649
- else
650
- {
651
- $s = '(' . $this->parseTree($n->treeNodes[0]) . ')';
652
- }
653
- break;
654
-
655
- default:
656
- throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type);
657
- }
658
-
659
- return $s;
660
- }
661
-
662
- private function isValidIdentifier($string)
663
- {
664
- return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved);
665
- }
666
-
667
- private function isWordChar($char)
668
- {
669
- return $char == '_' || $char == '$' || ctype_alnum($char);
670
- }
671
- }
672
-
673
- class JSParser
674
- {
675
- private $t;
676
- private $minifier;
677
-
678
- private $opPrecedence = array(
679
- ';' => 0,
680
- ',' => 1,
681
- '=' => 2, '?' => 2, ':' => 2,
682
- // The above all have to have the same precedence, see bug 330975
683
- '||' => 4,
684
- '&&' => 5,
685
- '|' => 6,
686
- '^' => 7,
687
- '&' => 8,
688
- '==' => 9, '!=' => 9, '===' => 9, '!==' => 9,
689
- '<' => 10, '<=' => 10, '>=' => 10, '>' => 10, 'in' => 10, 'instanceof' => 10,
690
- '<<' => 11, '>>' => 11, '>>>' => 11,
691
- '+' => 12, '-' => 12,
692
- '*' => 13, '/' => 13, '%' => 13,
693
- 'delete' => 14, 'void' => 14, 'typeof' => 14,
694
- '!' => 14, '~' => 14, 'U+' => 14, 'U-' => 14,
695
- '++' => 15, '--' => 15,
696
- 'new' => 16,
697
- '.' => 17,
698
- JS_NEW_WITH_ARGS => 0, JS_INDEX => 0, JS_CALL => 0,
699
- JS_ARRAY_INIT => 0, JS_OBJECT_INIT => 0, JS_GROUP => 0
700
- );
701
-
702
- private $opArity = array(
703
- ',' => -2,
704
- '=' => 2,
705
- '?' => 3,
706
- '||' => 2,
707
- '&&' => 2,
708
- '|' => 2,
709
- '^' => 2,
710
- '&' => 2,
711
- '==' => 2, '!=' => 2, '===' => 2, '!==' => 2,
712
- '<' => 2, '<=' => 2, '>=' => 2, '>' => 2, 'in' => 2, 'instanceof' => 2,
713
- '<<' => 2, '>>' => 2, '>>>' => 2,
714
- '+' => 2, '-' => 2,
715
- '*' => 2, '/' => 2, '%' => 2,
716
- 'delete' => 1, 'void' => 1, 'typeof' => 1,
717
- '!' => 1, '~' => 1, 'U+' => 1, 'U-' => 1,
718
- '++' => 1, '--' => 1,
719
- 'new' => 1,
720
- '.' => 2,
721
- JS_NEW_WITH_ARGS => 2, JS_INDEX => 2, JS_CALL => 2,
722
- JS_ARRAY_INIT => 1, JS_OBJECT_INIT => 1, JS_GROUP => 1,
723
- TOKEN_CONDCOMMENT_START => 1, TOKEN_CONDCOMMENT_END => 1
724
- );
725
-
726
- public function __construct($minifier=null)
727
- {
728
- $this->minifier = $minifier;
729
- $this->t = new JSTokenizer();
730
- }
731
-
732
- public function parse($s, $f, $l)
733
- {
734
- // initialize tokenizer
735
- $this->t->init($s, $f, $l);
736
-
737
- $x = new JSCompilerContext(false);
738
- $n = $this->Script($x);
739
- if (!$this->t->isDone())
740
- throw $this->t->newSyntaxError('Syntax error');
741
-
742
- return $n;
743
- }
744
-
745
- private function Script($x)
746
- {
747
- $n = $this->Statements($x);
748
- $n->type = JS_SCRIPT;
749
- $n->funDecls = $x->funDecls;
750
- $n->varDecls = $x->varDecls;
751
-
752
- // minify by scope
753
- if ($this->minifier)
754
- {
755
- $n->value = $this->minifier->parseTree($n);
756
-
757
- // clear tree from node to save memory
758
- $n->treeNodes = null;
759
- $n->funDecls = null;
760
- $n->varDecls = null;
761
-
762
- $n->type = JS_MINIFIED;
763
- }
764
-
765
- return $n;
766
- }
767
-
768
- private function Statements($x)
769
- {
770
- $n = new JSNode($this->t, JS_BLOCK);
771
- array_push($x->stmtStack, $n);
772
-
773
- while (!$this->t->isDone() && $this->t->peek() != OP_RIGHT_CURLY)
774
- $n->addNode($this->Statement($x));
775
-
776
- array_pop($x->stmtStack);
777
-
778
- return $n;
779
- }
780
-
781
- private function Block($x)
782
- {
783
- $this->t->mustMatch(OP_LEFT_CURLY);
784
- $n = $this->Statements($x);
785
- $this->t->mustMatch(OP_RIGHT_CURLY);
786
-
787
- return $n;
788
- }
789
-
790
- private function Statement($x)
791
- {
792
- $tt = $this->t->get();
793
- $n2 = null;
794
-
795
- // Cases for statements ending in a right curly return early, avoiding the
796
- // common semicolon insertion magic after this switch.
797
- switch ($tt)
798
- {
799
- case KEYWORD_FUNCTION:
800
- return $this->FunctionDefinition(
801
- $x,
802
- true,
803
- count($x->stmtStack) > 1 ? STATEMENT_FORM : DECLARED_FORM
804
- );
805
- break;
806
-
807
- case OP_LEFT_CURLY:
808
- $n = $this->Statements($x);
809
- $this->t->mustMatch(OP_RIGHT_CURLY);
810
- return $n;
811
-
812
- case KEYWORD_IF:
813
- $n = new JSNode($this->t);
814
- $n->condition = $this->ParenExpression($x);
815
- array_push($x->stmtStack, $n);
816
- $n->thenPart = $this->Statement($x);
817
- $n->elsePart = $this->t->match(KEYWORD_ELSE) ? $this->Statement($x) : null;
818
- array_pop($x->stmtStack);
819
- return $n;
820
-
821
- case KEYWORD_SWITCH:
822
- $n = new JSNode($this->t);
823
- $this->t->mustMatch(OP_LEFT_PAREN);
824
- $n->discriminant = $this->Expression($x);
825
- $this->t->mustMatch(OP_RIGHT_PAREN);
826
- $n->cases = array();
827
- $n->defaultIndex = -1;
828
-
829
- array_push($x->stmtStack, $n);
830
-
831
- $this->t->mustMatch(OP_LEFT_CURLY);
832
-
833
- while (($tt = $this->t->get()) != OP_RIGHT_CURLY)
834
- {
835
- switch ($tt)
836
- {
837
- case KEYWORD_DEFAULT:
838
- if ($n->defaultIndex >= 0)
839
- throw $this->t->newSyntaxError('More than one switch default');
840
- // FALL THROUGH
841
- case KEYWORD_CASE:
842
- $n2 = new JSNode($this->t);
843
- if ($tt == KEYWORD_DEFAULT)
844
- $n->defaultIndex = count($n->cases);
845
- else
846
- $n2->caseLabel = $this->Expression($x, OP_COLON);
847
- break;
848
- default:
849
- throw $this->t->newSyntaxError('Invalid switch case');
850
- }
851
-
852
- $this->t->mustMatch(OP_COLON);
853
- $n2->statements = new JSNode($this->t, JS_BLOCK);
854
- while (($tt = $this->t->peek()) != KEYWORD_CASE && $tt != KEYWORD_DEFAULT && $tt != OP_RIGHT_CURLY)
855
- $n2->statements->addNode($this->Statement($x));
856
-
857
- array_push($n->cases, $n2);
858
- }
859
-
860
- array_pop($x->stmtStack);
861
- return $n;
862
-
863
- case KEYWORD_FOR:
864
- $n = new JSNode($this->t);
865
- $n->isLoop = true;
866
- $this->t->mustMatch(OP_LEFT_PAREN);
867
-
868
- if (($tt = $this->t->peek()) != OP_SEMICOLON)
869
- {
870
- $x->inForLoopInit = true;
871
- if ($tt == KEYWORD_VAR || $tt == KEYWORD_CONST)
872
- {
873
- $this->t->get();
874
- $n2 = $this->Variables($x);
875
- }
876
- else
877
- {
878
- $n2 = $this->Expression($x);
879
- }
880
- $x->inForLoopInit = false;
881
- }
882
-
883
- if ($n2 && $this->t->match(KEYWORD_IN))
884
- {
885
- $n->type = JS_FOR_IN;
886
- if ($n2->type == KEYWORD_VAR)
887
- {
888
- if (count($n2->treeNodes) != 1)
889
- {
890
- throw $this->t->SyntaxError(
891
- 'Invalid for..in left-hand side',
892
- $this->t->filename,
893
- $n2->lineno
894
- );
895
- }
896
-
897
- // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name.
898
- $n->iterator = $n2->treeNodes[0];
899
- $n->varDecl = $n2;
900
- }
901
- else
902
- {
903
- $n->iterator = $n2;
904
- $n->varDecl = null;
905
- }
906
-
907
- $n->object = $this->Expression($x);
908
- }
909
- else
910
- {
911
- $n->setup = $n2 ? $n2 : null;
912
- $this->t->mustMatch(OP_SEMICOLON);
913
- $n->condition = $this->t->peek() == OP_SEMICOLON ? null : $this->Expression($x);
914
- $this->t->mustMatch(OP_SEMICOLON);
915
- $n->update = $this->t->peek() == OP_RIGHT_PAREN ? null : $this->Expression($x);
916
- }
917
-
918
- $this->t->mustMatch(OP_RIGHT_PAREN);
919
- $n->body = $this->nest($x, $n);
920
- return $n;
921
-
922
- case KEYWORD_WHILE:
923
- $n = new JSNode($this->t);
924
- $n->isLoop = true;
925
- $n->condition = $this->ParenExpression($x);
926
- $n->body = $this->nest($x, $n);
927
- return $n;
928
-
929
- case KEYWORD_DO:
930
- $n = new JSNode($this->t);
931
- $n->isLoop = true;
932
- $n->body = $this->nest($x, $n, KEYWORD_WHILE);
933
- $n->condition = $this->ParenExpression($x);
934
- if (!$x->ecmaStrictMode)
935
- {
936
- // <script language="JavaScript"> (without version hints) may need
937
- // automatic semicolon insertion without a newline after do-while.
938
- // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
939
- $this->t->match(OP_SEMICOLON);
940
- return $n;
941
- }
942
- break;
943
-
944
- case KEYWORD_BREAK:
945
- case KEYWORD_CONTINUE:
946
- $n = new JSNode($this->t);
947
-
948
- if ($this->t->peekOnSameLine() == TOKEN_IDENTIFIER)
949
- {
950
- $this->t->get();
951
- $n->label = $this->t->currentToken()->value;
952
- }
953
-
954
- $ss = $x->stmtStack;
955
- $i = count($ss);
956
- $label = $n->label;
957
- if ($label)
958
- {
959
- do
960
- {
961
- if (--$i < 0)
962
- throw $this->t->newSyntaxError('Label not found');
963
- }
964
- while ($ss[$i]->label != $label);
965
- }
966
- else
967
- {
968
- do
969
- {
970
- if (--$i < 0)
971
- throw $this->t->newSyntaxError('Invalid ' . $tt);
972
- }
973
- while (!$ss[$i]->isLoop && ($tt != KEYWORD_BREAK || $ss[$i]->type != KEYWORD_SWITCH));
974
- }
975
-
976
- $n->target = $ss[$i];
977
- break;
978
-
979
- case KEYWORD_TRY:
980
- $n = new JSNode($this->t);
981
- $n->tryBlock = $this->Block($x);
982
- $n->catchClauses = array();
983
-
984
- while ($this->t->match(KEYWORD_CATCH))
985
- {
986
- $n2 = new JSNode($this->t);
987
- $this->t->mustMatch(OP_LEFT_PAREN);
988
- $n2->varName = $this->t->mustMatch(TOKEN_IDENTIFIER)->value;
989
-
990
- if ($this->t->match(KEYWORD_IF))
991
- {
992
- if ($x->ecmaStrictMode)
993
- throw $this->t->newSyntaxError('Illegal catch guard');
994
-
995
- if (count($n->catchClauses) && !end($n->catchClauses)->guard)
996
- throw $this->t->newSyntaxError('Guarded catch after unguarded');
997
-
998
- $n2->guard = $this->Expression($x);
999
- }
1000
- else
1001
- {
1002
- $n2->guard = null;
1003
- }
1004
-
1005
- $this->t->mustMatch(OP_RIGHT_PAREN);
1006
- $n2->block = $this->Block($x);
1007
- array_push($n->catchClauses, $n2);
1008
- }
1009
-
1010
- if ($this->t->match(KEYWORD_FINALLY))
1011
- $n->finallyBlock = $this->Block($x);
1012
-
1013
- if (!count($n->catchClauses) && !$n->finallyBlock)
1014
- throw $this->t->newSyntaxError('Invalid try statement');
1015
- return $n;
1016
-
1017
- case KEYWORD_CATCH:
1018
- case KEYWORD_FINALLY:
1019
- throw $this->t->newSyntaxError($tt + ' without preceding try');
1020
-
1021
- case KEYWORD_THROW:
1022
- $n = new JSNode($this->t);
1023
- $n->value = $this->Expression($x);
1024
- break;
1025
-
1026
- case KEYWORD_RETURN:
1027
- if (!$x->inFunction)
1028
- throw $this->t->newSyntaxError('Invalid return');
1029
-
1030
- $n = new JSNode($this->t);
1031
- $tt = $this->t->peekOnSameLine();
1032
- if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1033
- $n->value = $this->Expression($x);
1034
- else
1035
- $n->value = null;
1036
- break;
1037
-
1038
- case KEYWORD_WITH:
1039
- $n = new JSNode($this->t);
1040
- $n->object = $this->ParenExpression($x);
1041
- $n->body = $this->nest($x, $n);
1042
- return $n;
1043
-
1044
- case KEYWORD_VAR:
1045
- case KEYWORD_CONST:
1046
- $n = $this->Variables($x);
1047
- break;
1048
-
1049
- case TOKEN_CONDCOMMENT_START:
1050
- case TOKEN_CONDCOMMENT_END:
1051
- $n = new JSNode($this->t);
1052
- return $n;
1053
-
1054
- case KEYWORD_DEBUGGER:
1055
- $n = new JSNode($this->t);
1056
- break;
1057
-
1058
- case TOKEN_NEWLINE:
1059
- case OP_SEMICOLON:
1060
- $n = new JSNode($this->t, OP_SEMICOLON);
1061
- $n->expression = null;
1062
- return $n;
1063
-
1064
- default:
1065
- if ($tt == TOKEN_IDENTIFIER)
1066
- {
1067
- $this->t->scanOperand = false;
1068
- $tt = $this->t->peek();
1069
- $this->t->scanOperand = true;
1070
- if ($tt == OP_COLON)
1071
- {
1072
- $label = $this->t->currentToken()->value;
1073
- $ss = $x->stmtStack;
1074
- for ($i = count($ss) - 1; $i >= 0; --$i)
1075
- {
1076
- if ($ss[$i]->label == $label)
1077
- throw $this->t->newSyntaxError('Duplicate label');
1078
- }
1079
-
1080
- $this->t->get();
1081
- $n = new JSNode($this->t, JS_LABEL);
1082
- $n->label = $label;
1083
- $n->statement = $this->nest($x, $n);
1084
-
1085
- return $n;
1086
- }
1087
- }
1088
-
1089
- $n = new JSNode($this->t, OP_SEMICOLON);
1090
- $this->t->unget();
1091
- $n->expression = $this->Expression($x);
1092
- $n->end = $n->expression->end;
1093
- break;
1094
- }
1095
-
1096
- if ($this->t->lineno == $this->t->currentToken()->lineno)
1097
- {
1098
- $tt = $this->t->peekOnSameLine();
1099
- if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1100
- throw $this->t->newSyntaxError('Missing ; before statement');
1101
- }
1102
-
1103
- $this->t->match(OP_SEMICOLON);
1104
-
1105
- return $n;
1106
- }
1107
-
1108
- private function FunctionDefinition($x, $requireName, $functionForm)
1109
- {
1110
- $f = new JSNode($this->t);
1111
-
1112
- if ($f->type != KEYWORD_FUNCTION)
1113
- $f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER;
1114
-
1115
- if ($this->t->match(TOKEN_IDENTIFIER))
1116
- $f->name = $this->t->currentToken()->value;
1117
- elseif ($requireName)
1118
- throw $this->t->newSyntaxError('Missing function identifier');
1119
-
1120
- $this->t->mustMatch(OP_LEFT_PAREN);
1121
- $f->params = array();
1122
-
1123
- while (($tt = $this->t->get()) != OP_RIGHT_PAREN)
1124
- {
1125
- if ($tt != TOKEN_IDENTIFIER)
1126
- throw $this->t->newSyntaxError('Missing formal parameter');
1127
-
1128
- array_push($f->params, $this->t->currentToken()->value);
1129
-
1130
- if ($this->t->peek() != OP_RIGHT_PAREN)
1131
- $this->t->mustMatch(OP_COMMA);
1132
- }
1133
-
1134
- $this->t->mustMatch(OP_LEFT_CURLY);
1135
-
1136
- $x2 = new JSCompilerContext(true);
1137
- $f->body = $this->Script($x2);
1138
-
1139
- $this->t->mustMatch(OP_RIGHT_CURLY);
1140
- $f->end = $this->t->currentToken()->end;
1141
-
1142
- $f->functionForm = $functionForm;
1143
- if ($functionForm == DECLARED_FORM)
1144
- array_push($x->funDecls, $f);
1145
-
1146
- return $f;
1147
- }
1148
-
1149
- private function Variables($x)
1150
- {
1151
- $n = new JSNode($this->t);
1152
-
1153
- do
1154
- {
1155
- $this->t->mustMatch(TOKEN_IDENTIFIER);
1156
-
1157
- $n2 = new JSNode($this->t);
1158
- $n2->name = $n2->value;
1159
-
1160
- if ($this->t->match(OP_ASSIGN))
1161
- {
1162
- if ($this->t->currentToken()->assignOp)
1163
- throw $this->t->newSyntaxError('Invalid variable initialization');
1164
-
1165
- $n2->initializer = $this->Expression($x, OP_COMMA);
1166
- }
1167
-
1168
- $n2->readOnly = $n->type == KEYWORD_CONST;
1169
-
1170
- $n->addNode($n2);
1171
- array_push($x->varDecls, $n2);
1172
- }
1173
- while ($this->t->match(OP_COMMA));
1174
-
1175
- return $n;
1176
- }
1177
-
1178
- private function Expression($x, $stop=false)
1179
- {
1180
- $operators = array();
1181
- $operands = array();
1182
- $n = false;
1183
-
1184
- $bl = $x->bracketLevel;
1185
- $cl = $x->curlyLevel;
1186
- $pl = $x->parenLevel;
1187
- $hl = $x->hookLevel;
1188
-
1189
- while (($tt = $this->t->get()) != TOKEN_END)
1190
- {
1191
- if ($tt == $stop &&
1192
- $x->bracketLevel == $bl &&
1193
- $x->curlyLevel == $cl &&
1194
- $x->parenLevel == $pl &&
1195
- $x->hookLevel == $hl
1196
- )
1197
- {
1198
- // Stop only if tt matches the optional stop parameter, and that
1199
- // token is not quoted by some kind of bracket.
1200
- break;
1201
- }
1202
-
1203
- switch ($tt)
1204
- {
1205
- case OP_SEMICOLON:
1206
- // NB: cannot be empty, Statement handled that.
1207
- break 2;
1208
-
1209
- case OP_HOOK:
1210
- if ($this->t->scanOperand)
1211
- break 2;
1212
-
1213
- while ( !empty($operators) &&
1214
- $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1215
- )
1216
- $this->reduce($operators, $operands);
1217
-
1218
- array_push($operators, new JSNode($this->t));
1219
-
1220
- ++$x->hookLevel;
1221
- $this->t->scanOperand = true;
1222
- $n = $this->Expression($x);
1223
-
1224
- if (!$this->t->match(OP_COLON))
1225
- break 2;
1226
-
1227
- --$x->hookLevel;
1228
- array_push($operands, $n);
1229
- break;
1230
-
1231
- case OP_COLON:
1232
- if ($x->hookLevel)
1233
- break 2;
1234
-
1235
- throw $this->t->newSyntaxError('Invalid label');
1236
- break;
1237
-
1238
- case OP_ASSIGN:
1239
- if ($this->t->scanOperand)
1240
- break 2;
1241
-
1242
- // Use >, not >=, for right-associative ASSIGN
1243
- while ( !empty($operators) &&
1244
- $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1245
- )
1246
- $this->reduce($operators, $operands);
1247
-
1248
- array_push($operators, new JSNode($this->t));
1249
- end($operands)->assignOp = $this->t->currentToken()->assignOp;
1250
- $this->t->scanOperand = true;
1251
- break;
1252
-
1253
- case KEYWORD_IN:
1254
- // An in operator should not be parsed if we're parsing the head of
1255
- // a for (...) loop, unless it is in the then part of a conditional
1256
- // expression, or parenthesized somehow.
1257
- if ($x->inForLoopInit && !$x->hookLevel &&
1258
- !$x->bracketLevel && !$x->curlyLevel &&
1259
- !$x->parenLevel
1260
- )
1261
- break 2;
1262
- // FALL THROUGH
1263
- case OP_COMMA:
1264
- // A comma operator should not be parsed if we're parsing the then part
1265
- // of a conditional expression unless it's parenthesized somehow.
1266
- if ($tt == OP_COMMA && $x->hookLevel &&
1267
- !$x->bracketLevel && !$x->curlyLevel &&
1268
- !$x->parenLevel
1269
- )
1270
- break 2;
1271
- // Treat comma as left-associative so reduce can fold left-heavy
1272
- // COMMA trees into a single array.
1273
- // FALL THROUGH
1274
- case OP_OR:
1275
- case OP_AND:
1276
- case OP_BITWISE_OR:
1277
- case OP_BITWISE_XOR:
1278
- case OP_BITWISE_AND:
1279
- case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
1280
- case OP_LT: case OP_LE: case OP_GE: case OP_GT:
1281
- case KEYWORD_INSTANCEOF:
1282
- case OP_LSH: case OP_RSH: case OP_URSH:
1283
- case OP_PLUS: case OP_MINUS:
1284
- case OP_MUL: case OP_DIV: case OP_MOD:
1285
- case OP_DOT:
1286
- if ($this->t->scanOperand)
1287
- break 2;
1288
-
1289
- while ( !empty($operators) &&
1290
- $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt]
1291
- )
1292
- $this->reduce($operators, $operands);
1293
-
1294
- if ($tt == OP_DOT)
1295
- {
1296
- $this->t->mustMatch(TOKEN_IDENTIFIER);
1297
- array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t)));
1298
- }
1299
- else
1300
- {
1301
- array_push($operators, new JSNode($this->t));
1302
- $this->t->scanOperand = true;
1303
- }
1304
- break;
1305
-
1306
- case KEYWORD_DELETE: case KEYWORD_VOID: case KEYWORD_TYPEOF:
1307
- case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS:
1308
- case KEYWORD_NEW:
1309
- if (!$this->t->scanOperand)
1310
- break 2;
1311
-
1312
- array_push($operators, new JSNode($this->t));
1313
- break;
1314
-
1315
- case OP_INCREMENT: case OP_DECREMENT:
1316
- if ($this->t->scanOperand)
1317
- {
1318
- array_push($operators, new JSNode($this->t)); // prefix increment or decrement
1319
- }
1320
- else
1321
- {
1322
- // Don't cross a line boundary for postfix {in,de}crement.
1323
- $t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3];
1324
- if ($t && $t->lineno != $this->t->lineno)
1325
- break 2;
1326
-
1327
- if (!empty($operators))
1328
- {
1329
- // Use >, not >=, so postfix has higher precedence than prefix.
1330
- while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt])
1331
- $this->reduce($operators, $operands);
1332
- }
1333
-
1334
- $n = new JSNode($this->t, $tt, array_pop($operands));
1335
- $n->postfix = true;
1336
- array_push($operands, $n);
1337
- }
1338
- break;
1339
-
1340
- case KEYWORD_FUNCTION:
1341
- if (!$this->t->scanOperand)
1342
- break 2;
1343
-
1344
- array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM));
1345
- $this->t->scanOperand = false;
1346
- break;
1347
-
1348
- case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
1349
- case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
1350
- if (!$this->t->scanOperand)
1351
- break 2;
1352
-
1353
- array_push($operands, new JSNode($this->t));
1354
- $this->t->scanOperand = false;
1355
- break;
1356
-
1357
- case TOKEN_CONDCOMMENT_START:
1358
- case TOKEN_CONDCOMMENT_END:
1359
- if ($this->t->scanOperand)
1360
- array_push($operators, new JSNode($this->t));
1361
- else
1362
- array_push($operands, new JSNode($this->t));
1363
- break;
1364
-
1365
- case OP_LEFT_BRACKET:
1366
- if ($this->t->scanOperand)
1367
- {
1368
- // Array initialiser. Parse using recursive descent, as the
1369
- // sub-grammar here is not an operator grammar.
1370
- $n = new JSNode($this->t, JS_ARRAY_INIT);
1371
- while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET)
1372
- {
1373
- if ($tt == OP_COMMA)
1374
- {
1375
- $this->t->get();
1376
- $n->addNode(null);
1377
- continue;
1378
- }
1379
-
1380
- $n->addNode($this->Expression($x, OP_COMMA));
1381
- if (!$this->t->match(OP_COMMA))
1382
- break;
1383
- }
1384
-
1385
- $this->t->mustMatch(OP_RIGHT_BRACKET);
1386
- array_push($operands, $n);
1387
- $this->t->scanOperand = false;
1388
- }
1389
- else
1390
- {
1391
- // Property indexing operator.
1392
- array_push($operators, new JSNode($this->t, JS_INDEX));
1393
- $this->t->scanOperand = true;
1394
- ++$x->bracketLevel;
1395
- }
1396
- break;
1397
-
1398
- case OP_RIGHT_BRACKET:
1399
- if ($this->t->scanOperand || $x->bracketLevel == $bl)
1400
- break 2;
1401
-
1402
- while ($this->reduce($operators, $operands)->type != JS_INDEX)
1403
- continue;
1404
-
1405
- --$x->bracketLevel;
1406
- break;
1407
-
1408
- case OP_LEFT_CURLY:
1409
- if (!$this->t->scanOperand)
1410
- break 2;
1411
-
1412
- // Object initialiser. As for array initialisers (see above),
1413
- // parse using recursive descent.
1414
- ++$x->curlyLevel;
1415
- $n = new JSNode($this->t, JS_OBJECT_INIT);
1416
- while (!$this->t->match(OP_RIGHT_CURLY))
1417
- {
1418
- do
1419
- {
1420
- $tt = $this->t->get();
1421
- $tv = $this->t->currentToken()->value;
1422
- if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER)
1423
- {
1424
- if ($x->ecmaStrictMode)
1425
- throw $this->t->newSyntaxError('Illegal property accessor');
1426
-
1427
- $n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM));
1428
- }
1429
- else
1430
- {
1431
- switch ($tt)
1432
- {
1433
- case TOKEN_IDENTIFIER:
1434
- case TOKEN_NUMBER:
1435
- case TOKEN_STRING:
1436
- $id = new JSNode($this->t);
1437
- break;
1438
-
1439
- case OP_RIGHT_CURLY:
1440
- if ($x->ecmaStrictMode)
1441
- throw $this->t->newSyntaxError('Illegal trailing ,');
1442
- break 3;
1443
-
1444
- default:
1445
- throw $this->t->newSyntaxError('Invalid property name');
1446
- }
1447
-
1448
- $this->t->mustMatch(OP_COLON);
1449
- $n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA)));
1450
- }
1451
- }
1452
- while ($this->t->match(OP_COMMA));
1453
-
1454
- $this->t->mustMatch(OP_RIGHT_CURLY);
1455
- break;
1456
- }
1457
-
1458
- array_push($operands, $n);
1459
- $this->t->scanOperand = false;
1460
- --$x->curlyLevel;
1461
- break;
1462
-
1463
- case OP_RIGHT_CURLY:
1464
- if (!$this->t->scanOperand && $x->curlyLevel != $cl)
1465
- throw new Exception('PANIC: right curly botch');
1466
- break 2;
1467
-
1468
- case OP_LEFT_PAREN:
1469
- if ($this->t->scanOperand)
1470
- {
1471
- array_push($operators, new JSNode($this->t, JS_GROUP));
1472
- }
1473
- else
1474
- {
1475
- while ( !empty($operators) &&
1476
- $this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW]
1477
- )
1478
- $this->reduce($operators, $operands);
1479
-
1480
- // Handle () now, to regularize the n-ary case for n > 0.
1481
- // We must set scanOperand in case there are arguments and
1482
- // the first one is a regexp or unary+/-.
1483
- $n = end($operators);
1484
- $this->t->scanOperand = true;
1485
- if ($this->t->match(OP_RIGHT_PAREN))
1486
- {
1487
- if ($n && $n->type == KEYWORD_NEW)
1488
- {
1489
- array_pop($operators);
1490
- $n->addNode(array_pop($operands));
1491
- }
1492
- else
1493
- {
1494
- $n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST));
1495
- }
1496
-
1497
- array_push($operands, $n);
1498
- $this->t->scanOperand = false;
1499
- break;
1500
- }
1501
-
1502
- if ($n && $n->type == KEYWORD_NEW)
1503
- $n->type = JS_NEW_WITH_ARGS;
1504
- else
1505
- array_push($operators, new JSNode($this->t, JS_CALL));
1506
- }
1507
-
1508
- ++$x->parenLevel;
1509
- break;
1510
-
1511
- case OP_RIGHT_PAREN:
1512
- if ($this->t->scanOperand || $x->parenLevel == $pl)
1513
- break 2;
1514
-
1515
- while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP &&
1516
- $tt != JS_CALL && $tt != JS_NEW_WITH_ARGS
1517
- )
1518
- {
1519
- continue;
1520
- }
1521
-
1522
- if ($tt != JS_GROUP)
1523
- {
1524
- $n = end($operands);
1525
- if ($n->treeNodes[1]->type != OP_COMMA)
1526
- $n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]);
1527
- else
1528
- $n->treeNodes[1]->type = JS_LIST;
1529
- }
1530
-
1531
- --$x->parenLevel;
1532
- break;
1533
-
1534
- // Automatic semicolon insertion means we may scan across a newline
1535
- // and into the beginning of another statement. If so, break out of
1536
- // the while loop and let the t.scanOperand logic handle errors.
1537
- default:
1538
- break 2;
1539
- }
1540
- }
1541
-
1542
- if ($x->hookLevel != $hl)
1543
- throw $this->t->newSyntaxError('Missing : in conditional expression');
1544
-
1545
- if ($x->parenLevel != $pl)
1546
- throw $this->t->newSyntaxError('Missing ) in parenthetical');
1547
-
1548
- if ($x->bracketLevel != $bl)
1549
- throw $this->t->newSyntaxError('Missing ] in index expression');
1550
-
1551
- if ($this->t->scanOperand)
1552
- throw $this->t->newSyntaxError('Missing operand');
1553
-
1554
- // Resume default mode, scanning for operands, not operators.
1555
- $this->t->scanOperand = true;
1556
- $this->t->unget();
1557
-
1558
- while (count($operators))
1559
- $this->reduce($operators, $operands);
1560
-
1561
- return array_pop($operands);
1562
- }
1563
-
1564
- private function ParenExpression($x)
1565
- {
1566
- $this->t->mustMatch(OP_LEFT_PAREN);
1567
- $n = $this->Expression($x);
1568
- $this->t->mustMatch(OP_RIGHT_PAREN);
1569
-
1570
- return $n;
1571
- }
1572
-
1573
- // Statement stack and nested statement handler.
1574
- private function nest($x, $node, $end = false)
1575
- {
1576
- array_push($x->stmtStack, $node);
1577
- $n = $this->statement($x);
1578
- array_pop($x->stmtStack);
1579
-
1580
- if ($end)
1581
- $this->t->mustMatch($end);
1582
-
1583
- return $n;
1584
- }
1585
-
1586
- private function reduce(&$operators, &$operands)
1587
- {
1588
- $n = array_pop($operators);
1589
- $op = $n->type;
1590
- $arity = $this->opArity[$op];
1591
- $c = count($operands);
1592
- if ($arity == -2)
1593
- {
1594
- // Flatten left-associative trees
1595
- if ($c >= 2)
1596
- {
1597
- $left = $operands[$c - 2];
1598
- if ($left->type == $op)
1599
- {
1600
- $right = array_pop($operands);
1601
- $left->addNode($right);
1602
- return $left;
1603
- }
1604
- }
1605
- $arity = 2;
1606
- }
1607
-
1608
- // Always use push to add operands to n, to update start and end
1609
- $a = array_splice($operands, $c - $arity);
1610
- for ($i = 0; $i < $arity; $i++)
1611
- $n->addNode($a[$i]);
1612
-
1613
- // Include closing bracket or postfix operator in [start,end]
1614
- $te = $this->t->currentToken()->end;
1615
- if ($n->end < $te)
1616
- $n->end = $te;
1617
-
1618
- array_push($operands, $n);
1619
-
1620
- return $n;
1621
- }
1622
- }
1623
-
1624
- class JSCompilerContext
1625
- {
1626
- public $inFunction = false;
1627
- public $inForLoopInit = false;
1628
- public $ecmaStrictMode = false;
1629
- public $bracketLevel = 0;
1630
- public $curlyLevel = 0;
1631
- public $parenLevel = 0;
1632
- public $hookLevel = 0;
1633
-
1634
- public $stmtStack = array();
1635
- public $funDecls = array();
1636
- public $varDecls = array();
1637
-
1638
- public function __construct($inFunction)
1639
- {
1640
- $this->inFunction = $inFunction;
1641
- }
1642
- }
1643
-
1644
- class JSNode
1645
- {
1646
- private $type;
1647
- private $value;
1648
- private $lineno;
1649
- private $start;
1650
- private $end;
1651
-
1652
- public $treeNodes = array();
1653
- public $funDecls = array();
1654
- public $varDecls = array();
1655
-
1656
- public function __construct($t, $type=0)
1657
- {
1658
- if ($token = $t->currentToken())
1659
- {
1660
- $this->type = $type ? $type : $token->type;
1661
- $this->value = $token->value;
1662
- $this->lineno = $token->lineno;
1663
- $this->start = $token->start;
1664
- $this->end = $token->end;
1665
- }
1666
- else
1667
- {
1668
- $this->type = $type;
1669
- $this->lineno = $t->lineno;
1670
- }
1671
-
1672
- if (($numargs = func_num_args()) > 2)
1673
- {
1674
- $args = func_get_args();
1675
- for ($i = 2; $i < $numargs; $i++)
1676
- $this->addNode($args[$i]);
1677
- }
1678
- }
1679
-
1680
- // we don't want to bloat our object with all kind of specific properties, so we use overloading
1681
- public function __set($name, $value)
1682
- {
1683
- $this->$name = $value;
1684
- }
1685
-
1686
- public function __get($name)
1687
- {
1688
- if (isset($this->$name))
1689
- return $this->$name;
1690
-
1691
- return null;
1692
- }
1693
-
1694
- public function addNode($node)
1695
- {
1696
- if ($node !== null)
1697
- {
1698
- if ($node->start < $this->start)
1699
- $this->start = $node->start;
1700
- if ($this->end < $node->end)
1701
- $this->end = $node->end;
1702
- }
1703
-
1704
- $this->treeNodes[] = $node;
1705
- }
1706
- }
1707
-
1708
- class JSTokenizer
1709
- {
1710
- private $cursor = 0;
1711
- private $source;
1712
-
1713
- public $tokens = array();
1714
- public $tokenIndex = 0;
1715
- public $lookahead = 0;
1716
- public $scanNewlines = false;
1717
- public $scanOperand = true;
1718
-
1719
- public $filename;
1720
- public $lineno;
1721
-
1722
- private $keywords = array(
1723
- 'break',
1724
- 'case', 'catch', 'const', 'continue',
1725
- 'debugger', 'default', 'delete', 'do',
1726
- 'else', 'enum',
1727
- 'false', 'finally', 'for', 'function',
1728
- 'if', 'in', 'instanceof',
1729
- 'new', 'null',
1730
- 'return',
1731
- 'switch',
1732
- 'this', 'throw', 'true', 'try', 'typeof',
1733
- 'var', 'void',
1734
- 'while', 'with'
1735
- );
1736
-
1737
- private $opTypeNames = array(
1738
- ';', ',', '?', ':', '||', '&&', '|', '^',
1739
- '&', '===', '==', '=', '!==', '!=', '<<', '<=',
1740
- '<', '>>>', '>>', '>=', '>', '++', '--', '+',
1741
- '-', '*', '/', '%', '!', '~', '.', '[',
1742
- ']', '{', '}', '(', ')', '@*/'
1743
- );
1744
-
1745
- private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%');
1746
- private $opRegExp;
1747
-
1748
- public function __construct()
1749
- {
1750
- $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', $this->opTypeNames)) . ')#';
1751
- }
1752
-
1753
- public function init($source, $filename = '', $lineno = 1)
1754
- {
1755
- $this->source = $source;
1756
- $this->filename = $filename ? $filename : '[inline]';
1757
- $this->lineno = $lineno;
1758
-
1759
- $this->cursor = 0;
1760
- $this->tokens = array();
1761
- $this->tokenIndex = 0;
1762
- $this->lookahead = 0;
1763
- $this->scanNewlines = false;
1764
- $this->scanOperand = true;
1765
- }
1766
-
1767
- public function getInput($chunksize)
1768
- {
1769
- if ($chunksize)
1770
- return substr($this->source, $this->cursor, $chunksize);
1771
-
1772
- return substr($this->source, $this->cursor);
1773
- }
1774
-
1775
- public function isDone()
1776
- {
1777
- return $this->peek() == TOKEN_END;
1778
- }
1779
-
1780
- public function match($tt)
1781
- {
1782
- return $this->get() == $tt || $this->unget();
1783
- }
1784
-
1785
- public function mustMatch($tt)
1786
- {
1787
- if (!$this->match($tt))
1788
- throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected');
1789
-
1790
- return $this->currentToken();
1791
- }
1792
-
1793
- public function peek()
1794
- {
1795
- if ($this->lookahead)
1796
- {
1797
- $next = $this->tokens[($this->tokenIndex + $this->lookahead) & 3];
1798
- if ($this->scanNewlines && $next->lineno != $this->lineno)
1799
- $tt = TOKEN_NEWLINE;
1800
- else
1801
- $tt = $next->type;
1802
- }
1803
- else
1804
- {
1805
- $tt = $this->get();
1806
- $this->unget();
1807
- }
1808
-
1809
- return $tt;
1810
- }
1811
-
1812
- public function peekOnSameLine()
1813
- {
1814
- $this->scanNewlines = true;
1815
- $tt = $this->peek();
1816
- $this->scanNewlines = false;
1817
-
1818
- return $tt;
1819
- }
1820
-
1821
- public function currentToken()
1822
- {
1823
- if (!empty($this->tokens))
1824
- return $this->tokens[$this->tokenIndex];
1825
- }
1826
-
1827
- public function get($chunksize = 1000)
1828
- {
1829
- while($this->lookahead)
1830
- {
1831
- $this->lookahead--;
1832
- $this->tokenIndex = ($this->tokenIndex + 1) & 3;
1833
- $token = $this->tokens[$this->tokenIndex];
1834
- if ($token->type != TOKEN_NEWLINE || $this->scanNewlines)
1835
- return $token->type;
1836
- }
1837
-
1838
- $conditional_comment = false;
1839
-
1840
- // strip whitespace and comments
1841
- while(true)
1842
- {
1843
- $input = $this->getInput($chunksize);
1844
-
1845
- // whitespace handling; gobble up \r as well (effectively we don't have support for MAC newlines!)
1846
- $re = $this->scanNewlines ? '/^[ \r\t]+/' : '/^\s+/';
1847
- if (preg_match($re, $input, $match))
1848
- {
1849
- $spaces = $match[0];
1850
- $spacelen = strlen($spaces);
1851
- $this->cursor += $spacelen;
1852
- if (!$this->scanNewlines)
1853
- $this->lineno += substr_count($spaces, "\n");
1854
-
1855
- if ($spacelen == $chunksize)
1856
- continue; // complete chunk contained whitespace
1857
-
1858
- $input = $this->getInput($chunksize);
1859
- if ($input == '' || $input[0] != '/')
1860
- break;
1861
- }
1862
-
1863
- // Comments
1864
- if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?.*?\*\/|\/[^\n]*)/s', $input, $match))
1865
- {
1866
- if (!$chunksize)
1867
- break;
1868
-
1869
- // retry with a full chunk fetch; this also prevents breakage of long regular expressions (which will never match a comment)
1870
- $chunksize = null;
1871
- continue;
1872
- }
1873
-
1874
- // check if this is a conditional (JScript) comment
1875
- if (!empty($match[1]))
1876
- {
1877
- $match[0] = '/*' . $match[1];
1878
- $conditional_comment = true;
1879
- break;
1880
- }
1881
- else
1882
- {
1883
- $this->cursor += strlen($match[0]);
1884
- $this->lineno += substr_count($match[0], "\n");
1885
- }
1886
- }
1887
-
1888
- if ($input == '')
1889
- {
1890
- $tt = TOKEN_END;
1891
- $match = array('');
1892
- }
1893
- elseif ($conditional_comment)
1894
- {
1895
- $tt = TOKEN_CONDCOMMENT_START;
1896
- }
1897
- else
1898
- {
1899
- switch ($input[0])
1900
- {
1901
- case '0':
1902
- // hexadecimal
1903
- if (($input[1] == 'x' || $input[1] == 'X') && preg_match('/^0x[0-9a-f]+/i', $input, $match))
1904
- {
1905
- $tt = TOKEN_NUMBER;
1906
- break;
1907
- }
1908
- // FALL THROUGH
1909
-
1910
- case '1': case '2': case '3': case '4': case '5':
1911
- case '6': case '7': case '8': case '9':
1912
- // should always match
1913
- preg_match('/^\d+(?:\.\d*)?(?:[eE][-+]?\d+)?/', $input, $match);
1914
- $tt = TOKEN_NUMBER;
1915
- break;
1916
-
1917
- case "'":
1918
- if (preg_match('/^\'(?:[^\\\\\'\r\n]++|\\\\(?:.|\r?\n))*\'/', $input, $match))
1919
- {
1920
- $tt = TOKEN_STRING;
1921
- }
1922
- else
1923
- {
1924
- if ($chunksize)
1925
- return $this->get(null); // retry with a full chunk fetch
1926
-
1927
- throw $this->newSyntaxError('Unterminated string literal');
1928
- }
1929
- break;
1930
-
1931
- case '"':
1932
- if (preg_match('/^"(?:[^\\\\"\r\n]++|\\\\(?:.|\r?\n))*"/', $input, $match))
1933
- {
1934
- $tt = TOKEN_STRING;
1935
- }
1936
- else
1937
- {
1938
- if ($chunksize)
1939
- return $this->get(null); // retry with a full chunk fetch
1940
-
1941
- throw $this->newSyntaxError('Unterminated string literal');
1942
- }
1943
- break;
1944
-
1945
- case '/':
1946
- if ($this->scanOperand && preg_match('/^\/((?:\\\\.|\[(?:\\\\.|[^\]])*\]|[^\/])+)\/([gimy]*)/', $input, $match))
1947
- {
1948
- $tt = TOKEN_REGEXP;
1949
- break;
1950
- }
1951
- // FALL THROUGH
1952
-
1953
- case '|':
1954
- case '^':
1955
- case '&':
1956
- case '<':
1957
- case '>':
1958
- case '+':
1959
- case '-':
1960
- case '*':
1961
- case '%':
1962
- case '=':
1963
- case '!':
1964
- // should always match
1965
- preg_match($this->opRegExp, $input, $match);
1966
- $op = $match[0];
1967
- if (in_array($op, $this->assignOps) && $input[strlen($op)] == '=')
1968
- {
1969
- $tt = OP_ASSIGN;
1970
- $match[0] .= '=';
1971
- }
1972
- else
1973
- {
1974
- $tt = $op;
1975
- if ($this->scanOperand)
1976
- {
1977
- if ($op == OP_PLUS)
1978
- $tt = OP_UNARY_PLUS;
1979
- elseif ($op == OP_MINUS)
1980
- $tt = OP_UNARY_MINUS;
1981
- }
1982
- $op = null;
1983
- }
1984
- break;
1985
-
1986
- case '.':
1987
- if (preg_match('/^\.\d+(?:[eE][-+]?\d+)?/', $input, $match))
1988
- {
1989
- $tt = TOKEN_NUMBER;
1990
- break;
1991
- }
1992
- // FALL THROUGH
1993
-
1994
- case ';':
1995
- case ',':
1996
- case '?':
1997
- case ':':
1998
- case '~':
1999
- case '[':
2000
- case ']':
2001
- case '{':
2002
- case '}':
2003
- case '(':
2004
- case ')':
2005
- // these are all single
2006
- $match = array($input[0]);
2007
- $tt = $input[0];
2008
- break;
2009
-
2010
- case '@':
2011
- // check end of conditional comment
2012
- if (substr($input, 0, 3) == '@*/')
2013
- {
2014
- $match = array('@*/');
2015
- $tt = TOKEN_CONDCOMMENT_END;
2016
- }
2017
- else
2018
- throw $this->newSyntaxError('Illegal token');
2019
- break;
2020
-
2021
- case "\n":
2022
- if ($this->scanNewlines)
2023
- {
2024
- $match = array("\n");
2025
- $tt = TOKEN_NEWLINE;
2026
- }
2027
- else
2028
- throw $this->newSyntaxError('Illegal token');
2029
- break;
2030
-
2031
- default:
2032
- // FIXME: add support for unicode and unicode escape sequence \uHHHH
2033
- if (preg_match('/^[$\w]+/', $input, $match))
2034
- {
2035
- $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER;
2036
- }
2037
- else
2038
- throw $this->newSyntaxError('Illegal token');
2039
- }
2040
- }
2041
-
2042
- $this->tokenIndex = ($this->tokenIndex + 1) & 3;
2043
-
2044
- if (!isset($this->tokens[$this->tokenIndex]))
2045
- $this->tokens[$this->tokenIndex] = new JSToken();
2046
-
2047
- $token = $this->tokens[$this->tokenIndex];
2048
- $token->type = $tt;
2049
-
2050
- if ($tt == OP_ASSIGN)
2051
- $token->assignOp = $op;
2052
-
2053
- $token->start = $this->cursor;
2054
-
2055
- $token->value = $match[0];
2056
- $this->cursor += strlen($match[0]);
2057
-
2058
- $token->end = $this->cursor;
2059
- $token->lineno = $this->lineno;
2060
-
2061
- return $tt;
2062
- }
2063
-
2064
- public function unget()
2065
- {
2066
- if (++$this->lookahead == 4)
2067
- throw $this->newSyntaxError('PANIC: too much lookahead!');
2068
-
2069
- $this->tokenIndex = ($this->tokenIndex - 1) & 3;
2070
- }
2071
-
2072
- public function newSyntaxError($m)
2073
- {
2074
- return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno);
2075
- }
2076
- }
2077
-
2078
- class JSToken
2079
- {
2080
- public $type;
2081
- public $value;
2082
- public $start;
2083
- public $end;
2084
- public $lineno;
2085
- public $assignOp;
2086
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/JavaScriptPacker.php DELETED
@@ -1,741 +0,0 @@
1
- <?php
2
- /* 9 April 2008. version 1.1
3
- *
4
- * This is the php version of the Dean Edwards JavaScript's Packer,
5
- * Based on :
6
- *
7
- * ParseMaster, version 1.0.2 (2005-08-19) Copyright 2005, Dean Edwards
8
- * a multi-pattern parser.
9
- * KNOWN BUG: erroneous behavior when using escapeChar with a replacement
10
- * value that is a function
11
- *
12
- * packer, version 2.0.2 (2005-08-19) Copyright 2004-2005, Dean Edwards
13
- *
14
- * License: http://creativecommons.org/licenses/LGPL/2.1/
15
- *
16
- * Ported to PHP by Nicolas Martin.
17
- *
18
- * ----------------------------------------------------------------------
19
- * changelog:
20
- * 1.1 : correct a bug, '\0' packed then unpacked becomes '\'.
21
- * ----------------------------------------------------------------------
22
- *
23
- * examples of usage :
24
- * $myPacker = new JavaScriptPacker($script, 62, true, false);
25
- * $packed = $myPacker->pack();
26
- *
27
- * or
28
- *
29
- * $myPacker = new JavaScriptPacker($script, 'Normal', true, false);
30
- * $packed = $myPacker->pack();
31
- *
32
- * or (default values)
33
- *
34
- * $myPacker = new JavaScriptPacker($script);
35
- * $packed = $myPacker->pack();
36
- *
37
- *
38
- * params of the constructor :
39
- * $script: the JavaScript to pack, string.
40
- * $encoding: level of encoding, int or string :
41
- * 0,10,62,95 or 'None', 'Numeric', 'Normal', 'High ASCII'.
42
- * default: 62.
43
- * $fastDecode: include the fast decoder in the packed result, boolean.
44
- * default : true.
45
- * $specialChars: if you are flagged your private and local variables
46
- * in the script, boolean.
47
- * default: false.
48
- *
49
- * The pack() method return the compressed JavasScript, as a string.
50
- *
51
- * see http://dean.edwards.name/packer/usage/ for more information.
52
- *
53
- * Notes :
54
- * # need PHP 5 . Tested with PHP 5.1.2, 5.1.3, 5.1.4, 5.2.3
55
- *
56
- * # The packed result may be different than with the Dean Edwards
57
- * version, but with the same length. The reason is that the PHP
58
- * function usort to sort array don't necessarily preserve the
59
- * original order of two equal member. The Javascript sort function
60
- * in fact preserve this order (but that's not require by the
61
- * ECMAScript standard). So the encoded keywords order can be
62
- * different in the two results.
63
- *
64
- * # Be careful with the 'High ASCII' Level encoding if you use
65
- * UTF-8 in your files...
66
- */
67
-
68
-
69
- class JavaScriptPacker {
70
- // constants
71
- const IGNORE = '$1';
72
-
73
- // validate parameters
74
- private $_script = '';
75
- private $_encoding = 62;
76
- private $_fastDecode = true;
77
- private $_specialChars = false;
78
-
79
- private $LITERAL_ENCODING = array(
80
- 'None' => 0,
81
- 'Numeric' => 10,
82
- 'Normal' => 62,
83
- 'High ASCII' => 95
84
- );
85
-
86
- public function __construct($_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false)
87
- {
88
- $this->_script = $_script . "\n";
89
- if (array_key_exists($_encoding, $this->LITERAL_ENCODING))
90
- $_encoding = $this->LITERAL_ENCODING[$_encoding];
91
- $this->_encoding = min((int)$_encoding, 95);
92
- $this->_fastDecode = $_fastDecode;
93
- $this->_specialChars = $_specialChars;
94
- }
95
-
96
- public function pack() {
97
- $this->_addParser('_basicCompression');
98
- if ($this->_specialChars)
99
- $this->_addParser('_encodeSpecialChars');
100
- if ($this->_encoding)
101
- $this->_addParser('_encodeKeywords');
102
-
103
- // go!
104
- return $this->_pack($this->_script);
105
- }
106
-
107
- // apply all parsing routines
108
- private function _pack($script) {
109
- for ($i = 0; isset($this->_parsers[$i]); $i++) {
110
- $script = call_user_func(array(&$this,$this->_parsers[$i]), $script);
111
- }
112
- return $script;
113
- }
114
-
115
- // keep a list of parsing functions, they'll be executed all at once
116
- private $_parsers = array();
117
- private function _addParser($parser) {
118
- $this->_parsers[] = $parser;
119
- }
120
-
121
- // zero encoding - just removal of white space and comments
122
- private function _basicCompression($script) {
123
- $parser = new ParseMaster();
124
- // make safe
125
- $parser->escapeChar = '\\';
126
- // protect strings
127
- $parser->add('/\'[^\'\\n\\r]*\'/', self::IGNORE);
128
- $parser->add('/"[^"\\n\\r]*"/', self::IGNORE);
129
- // remove comments
130
- $parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' ');
131
- $parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' ');
132
- // protect regular expressions
133
- $parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE
134
- $parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE);
135
- // remove: ;;; doSomething();
136
- if ($this->_specialChars) $parser->add('/;;;[^\\n\\r]+[\\n\\r]/');
137
- // remove redundant semi-colons
138
- $parser->add('/\\(;;\\)/', self::IGNORE); // protect for (;;) loops
139
- $parser->add('/;+\\s*([};])/', '$2');
140
- // apply the above
141
- $script = $parser->exec($script);
142
-
143
- // remove white-space
144
- $parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3');
145
- $parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3');
146
- $parser->add('/\\s+/', '');
147
- // done
148
- return $parser->exec($script);
149
- }
150
-
151
- private function _encodeSpecialChars($script) {
152
- $parser = new ParseMaster();
153
- // replace: $name -> n, $$name -> na
154
- $parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/',
155
- array('fn' => '_replace_name')
156
- );
157
- // replace: _name -> _0, double-underscore (__name) is ignored
158
- $regexp = '/\\b_[A-Za-z\\d]\\w*/';
159
- // build the word list
160
- $keywords = $this->_analyze($script, $regexp, '_encodePrivate');
161
- // quick ref
162
- $encoded = $keywords['encoded'];
163
-
164
- $parser->add($regexp,
165
- array(
166
- 'fn' => '_replace_encoded',
167
- 'data' => $encoded
168
- )
169
- );
170
- return $parser->exec($script);
171
- }
172
-
173
- private function _encodeKeywords($script) {
174
- // escape high-ascii values already in the script (i.e. in strings)
175
- if ($this->_encoding > 62)
176
- $script = $this->_escape95($script);
177
- // create the parser
178
- $parser = new ParseMaster();
179
- $encode = $this->_getEncoder($this->_encoding);
180
- // for high-ascii, don't encode single character low-ascii
181
- $regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/';
182
- // build the word list
183
- $keywords = $this->_analyze($script, $regexp, $encode);
184
- $encoded = $keywords['encoded'];
185
-
186
- // encode
187
- $parser->add($regexp,
188
- array(
189
- 'fn' => '_replace_encoded',
190
- 'data' => $encoded
191
- )
192
- );
193
- if (empty($script)) return $script;
194
- else {
195
- //$res = $parser->exec($script);
196
- //$res = $this->_bootStrap($res, $keywords);
197
- //return $res;
198
- return $this->_bootStrap($parser->exec($script), $keywords);
199
- }
200
- }
201
-
202
- private function _analyze($script, $regexp, $encode) {
203
- // analyse
204
- // retreive all words in the script
205
- $all = array();
206
- preg_match_all($regexp, $script, $all);
207
- $_sorted = array(); // list of words sorted by frequency
208
- $_encoded = array(); // dictionary of word->encoding
209
- $_protected = array(); // instances of "protected" words
210
- $all = $all[0]; // simulate the javascript comportement of global match
211
- if (!empty($all)) {
212
- $unsorted = array(); // same list, not sorted
213
- $protected = array(); // "protected" words (dictionary of word->"word")
214
- $value = array(); // dictionary of charCode->encoding (eg. 256->ff)
215
- $this->_count = array(); // word->count
216
- $i = count($all); $j = 0; //$word = null;
217
- // count the occurrences - used for sorting later
218
- do {
219
- --$i;
220
- $word = '$' . $all[$i];
221
- if (!isset($this->_count[$word])) {
222
- $this->_count[$word] = 0;
223
- $unsorted[$j] = $word;
224
- // make a dictionary of all of the protected words in this script
225
- // these are words that might be mistaken for encoding
226
- //if (is_string($encode) && method_exists($this, $encode))
227
- $values[$j] = call_user_func(array(&$this, $encode), $j);
228
- $protected['$' . $values[$j]] = $j++;
229
- }
230
- // increment the word counter
231
- $this->_count[$word]++;
232
- } while ($i > 0);
233
- // prepare to sort the word list, first we must protect
234
- // words that are also used as codes. we assign them a code
235
- // equivalent to the word itself.
236
- // e.g. if "do" falls within our encoding range
237
- // then we store keywords["do"] = "do";
238
- // this avoids problems when decoding
239
- $i = count($unsorted);
240
- do {
241
- $word = $unsorted[--$i];
242
- if (isset($protected[$word]) /*!= null*/) {
243
- $_sorted[$protected[$word]] = substr($word, 1);
244
- $_protected[$protected[$word]] = true;
245
- $this->_count[$word] = 0;
246
- }
247
- } while ($i);
248
-
249
- // sort the words by frequency
250
- // Note: the javascript and php version of sort can be different :
251
- // in php manual, usort :
252
- // " If two members compare as equal,
253
- // their order in the sorted array is undefined."
254
- // so the final packed script is different of the Dean's javascript version
255
- // but equivalent.
256
- // the ECMAscript standard does not guarantee this behaviour,
257
- // and thus not all browsers (e.g. Mozilla versions dating back to at
258
- // least 2003) respect this.
259
- usort($unsorted, array(&$this, '_sortWords'));
260
- $j = 0;
261
- // because there are "protected" words in the list
262
- // we must add the sorted words around them
263
- do {
264
- if (!isset($_sorted[$i]))
265
- $_sorted[$i] = substr($unsorted[$j++], 1);
266
- $_encoded[$_sorted[$i]] = $values[$i];
267
- } while (++$i < count($unsorted));
268
- }
269
- return array(
270
- 'sorted' => $_sorted,
271
- 'encoded' => $_encoded,
272
- 'protected' => $_protected);
273
- }
274
-
275
- private $_count = array();
276
- private function _sortWords($match1, $match2) {
277
- return $this->_count[$match2] - $this->_count[$match1];
278
- }
279
-
280
- // build the boot function used for loading and decoding
281
- private function _bootStrap($packed, $keywords) {
282
- $ENCODE = $this->_safeRegExp('$encode\\($count\\)');
283
-
284
- // $packed: the packed script
285
- $packed = "'" . $this->_escape($packed) . "'";
286
-
287
- // $ascii: base for encoding
288
- $ascii = min(count($keywords['sorted']), $this->_encoding);
289
- if ($ascii == 0) $ascii = 1;
290
-
291
- // $count: number of words contained in the script
292
- $count = count($keywords['sorted']);
293
-
294
- // $keywords: list of words contained in the script
295
- foreach ($keywords['protected'] as $i=>$value) {
296
- $keywords['sorted'][$i] = '';
297
- }
298
- // convert from a string to an array
299
- ksort($keywords['sorted']);
300
- $keywords = "'" . implode('|',$keywords['sorted']) . "'.split('|')";
301
-
302
- $encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii);
303
- $encode = $this->_getJSFunction($encode);
304
- $encode = preg_replace('/_encoding/','$ascii', $encode);
305
- $encode = preg_replace('/arguments\\.callee/','$encode', $encode);
306
- $inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : '');
307
-
308
- // $decode: code snippet to speed up decoding
309
- if ($this->_fastDecode) {
310
- // create the decoder
311
- $decode = $this->_getJSFunction('_decodeBody');
312
- if ($this->_encoding > 62)
313
- $decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode);
314
- // perform the encoding inline for lower ascii values
315
- elseif ($ascii < 36)
316
- $decode = preg_replace($ENCODE, $inline, $decode);
317
- // special case: when $count==0 there are no keywords. I want to keep
318
- // the basic shape of the unpacking funcion so i'll frig the code...
319
- if ($count == 0)
320
- $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1);
321
- }
322
-
323
- // boot function
324
- $unpack = $this->_getJSFunction('_unpack');
325
- if ($this->_fastDecode) {
326
- // insert the decoder
327
- $this->buffer = $decode;
328
- $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastDecode'), $unpack, 1);
329
- }
330
- $unpack = preg_replace('/"/', "'", $unpack);
331
- if ($this->_encoding > 62) { // high-ascii
332
- // get rid of the word-boundaries for regexp matches
333
- $unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack);
334
- }
335
- if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) {
336
- // insert the encode function
337
- $this->buffer = $encode;
338
- $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastEncode'), $unpack, 1);
339
- } else {
340
- // perform the encoding inline
341
- $unpack = preg_replace($ENCODE, $inline, $unpack);
342
- }
343
- // pack the boot function too
344
- $unpackPacker = new JavaScriptPacker($unpack, 0, false, true);
345
- $unpack = $unpackPacker->pack();
346
-
347
- // arguments
348
- $params = array($packed, $ascii, $count, $keywords);
349
- if ($this->_fastDecode) {
350
- $params[] = 0;
351
- $params[] = '{}';
352
- }
353
- $params = implode(',', $params);
354
-
355
- // the whole thing
356
- return 'eval(' . $unpack . '(' . $params . "))\n";
357
- }
358
-
359
- private $buffer;
360
- private function _insertFastDecode($match) {
361
- return '{' . $this->buffer . ';';
362
- }
363
- private function _insertFastEncode($match) {
364
- return '{$encode=' . $this->buffer . ';';
365
- }
366
-
367
- // mmm.. ..which one do i need ??
368
- private function _getEncoder($ascii) {
369
- return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ?
370
- '_encode95' : '_encode62' : '_encode36' : '_encode10';
371
- }
372
-
373
- // zero encoding
374
- // characters: 0123456789
375
- private function _encode10($charCode) {
376
- return $charCode;
377
- }
378
-
379
- // inherent base36 support
380
- // characters: 0123456789abcdefghijklmnopqrstuvwxyz
381
- private function _encode36($charCode) {
382
- return base_convert($charCode, 10, 36);
383
- }
384
-
385
- // hitch a ride on base36 and add the upper case alpha characters
386
- // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
387
- private function _encode62($charCode) {
388
- $res = '';
389
- if ($charCode >= $this->_encoding) {
390
- $res = $this->_encode62((int)($charCode / $this->_encoding));
391
- }
392
- $charCode = $charCode % $this->_encoding;
393
-
394
- if ($charCode > 35)
395
- return $res . chr($charCode + 29);
396
- else
397
- return $res . base_convert($charCode, 10, 36);
398
- }
399
-
400
- // use high-ascii values
401
- // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
402
- private function _encode95($charCode) {
403
- $res = '';
404
- if ($charCode >= $this->_encoding)
405
- $res = $this->_encode95($charCode / $this->_encoding);
406
-
407
- return $res . chr(($charCode % $this->_encoding) + 161);
408
- }
409
-
410
- private function _safeRegExp($string) {
411
- return '/'.preg_replace('/\$/', '\\\$', $string).'/';
412
- }
413
-
414
- private function _encodePrivate($charCode) {
415
- return "_" . $charCode;
416
- }
417
-
418
- // protect characters used by the parser
419
- private function _escape($script) {
420
- return preg_replace('/([\\\\\'])/', '\\\$1', $script);
421
- }
422
-
423
- // protect high-ascii characters already in the script
424
- private function _escape95($script) {
425
- return preg_replace_callback(
426
- '/[\\xa1-\\xff]/',
427
- array(&$this, '_escape95Bis'),
428
- $script
429
- );
430
- }
431
- private function _escape95Bis($match) {
432
- return '\x'.((string)dechex(ord($match)));
433
- }
434
-
435
-
436
- private function _getJSFunction($aName) {
437
- if (defined('self::JSFUNCTION'.$aName))
438
- return constant('self::JSFUNCTION'.$aName);
439
- else
440
- return '';
441
- }
442
-
443
- // JavaScript Functions used.
444
- // Note : In Dean's version, these functions are converted
445
- // with 'String(aFunctionName);'.
446
- // This internal conversion complete the original code, ex :
447
- // 'while (aBool) anAction();' is converted to
448
- // 'while (aBool) { anAction(); }'.
449
- // The JavaScript functions below are corrected.
450
-
451
- // unpacking function - this is the boot strap function
452
- // data extracted from this packing routine is passed to
453
- // this function when decoded in the target
454
- // NOTE ! : without the ';' final.
455
- const JSFUNCTION_unpack =
456
-
457
- 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
458
- while ($count--) {
459
- if ($keywords[$count]) {
460
- $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
461
- }
462
- }
463
- return $packed;
464
- }';
465
- /*
466
- 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
467
- while ($count--)
468
- if ($keywords[$count])
469
- $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
470
- return $packed;
471
- }';
472
- */
473
-
474
- // code-snippet inserted into the unpacker to speed up decoding
475
- const JSFUNCTION_decodeBody =
476
- //_decode = function() {
477
- // does the browser support String.replace where the
478
- // replacement value is a function?
479
-
480
- ' if (!\'\'.replace(/^/, String)) {
481
- // decode all the values we need
482
- while ($count--) {
483
- $decode[$encode($count)] = $keywords[$count] || $encode($count);
484
- }
485
- // global replacement function
486
- $keywords = [function ($encoded) {return $decode[$encoded]}];
487
- // generic match
488
- $encode = function () {return \'\\\\w+\'};
489
- // reset the loop counter - we are now doing a global replace
490
- $count = 1;
491
- }
492
- ';
493
- //};
494
- /*
495
- ' if (!\'\'.replace(/^/, String)) {
496
- // decode all the values we need
497
- while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count);
498
- // global replacement function
499
- $keywords = [function ($encoded) {return $decode[$encoded]}];
500
- // generic match
501
- $encode = function () {return\'\\\\w+\'};
502
- // reset the loop counter - we are now doing a global replace
503
- $count = 1;
504
- }';
505
- */
506
-
507
- // zero encoding
508
- // characters: 0123456789
509
- const JSFUNCTION_encode10 =
510
- 'function($charCode) {
511
- return $charCode;
512
- }';//;';
513
-
514
- // inherent base36 support
515
- // characters: 0123456789abcdefghijklmnopqrstuvwxyz
516
- const JSFUNCTION_encode36 =
517
- 'function($charCode) {
518
- return $charCode.toString(36);
519
- }';//;';
520
-
521
- // hitch a ride on base36 and add the upper case alpha characters
522
- // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
523
- const JSFUNCTION_encode62 =
524
- 'function($charCode) {
525
- return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) +
526
- (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36));
527
- }';
528
-
529
- // use high-ascii values
530
- // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
531
- const JSFUNCTION_encode95 =
532
- 'function($charCode) {
533
- return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) +
534
- String.fromCharCode($charCode % _encoding + 161);
535
- }';
536
-
537
- }
538
-
539
-
540
- class ParseMaster {
541
- public $ignoreCase = false;
542
- public $escapeChar = '';
543
-
544
- // constants
545
- const EXPRESSION = 0;
546
- const REPLACEMENT = 1;
547
- const LENGTH = 2;
548
-
549
- // used to determine nesting levels
550
- private $GROUPS = '/\\(/';//g
551
- private $SUB_REPLACE = '/\\$\\d/';
552
- private $INDEXED = '/^\\$\\d+$/';
553
- private $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/';
554
- private $ESCAPE = '/\\\./';//g
555
- private $QUOTE = '/\'/';
556
- private $DELETED = '/\\x01[^\\x01]*\\x01/';//g
557
-
558
- public function add($expression, $replacement = '') {
559
- // count the number of sub-expressions
560
- // - add one because each pattern is itself a sub-expression
561
- $length = 1 + preg_match_all($this->GROUPS, $this->_internalEscape((string)$expression), $out);
562
-
563
- // treat only strings $replacement
564
- if (is_string($replacement)) {
565
- // does the pattern deal with sub-expressions?
566
- if (preg_match($this->SUB_REPLACE, $replacement)) {
567
- // a simple lookup? (e.g. "$2")
568
- if (preg_match($this->INDEXED, $replacement)) {
569
- // store the index (used for fast retrieval of matched strings)
570
- $replacement = (int)(substr($replacement, 1)) - 1;
571
- } else { // a complicated lookup (e.g. "Hello $2 $1")
572
- // build a function to do the lookup
573
- $quote = preg_match($this->QUOTE, $this->_internalEscape($replacement))
574
- ? '"' : "'";
575
- $replacement = array(
576
- 'fn' => '_backReferences',
577
- 'data' => array(
578
- 'replacement' => $replacement,
579
- 'length' => $length,
580
- 'quote' => $quote
581
- )
582
- );
583
- }
584
- }
585
- }
586
- // pass the modified arguments
587
- if (!empty($expression)) $this->_add($expression, $replacement, $length);
588
- else $this->_add('/^$/', $replacement, $length);
589
- }
590
-
591
- public function exec($string) {
592
- // execute the global replacement
593
- $this->_escaped = array();
594
-
595
- // simulate the _patterns.toSTring of Dean
596
- $regexp = '/';
597
- foreach ($this->_patterns as $reg) {
598
- $regexp .= '(' . substr($reg[self::EXPRESSION], 1, -1) . ')|';
599
- }
600
- $regexp = substr($regexp, 0, -1) . '/';
601
- $regexp .= ($this->ignoreCase) ? 'i' : '';
602
-
603
- $string = $this->_escape($string, $this->escapeChar);
604
- $string = preg_replace_callback(
605
- $regexp,
606
- array(
607
- &$this,
608
- '_replacement'
609
- ),
610
- $string
611
- );
612
- $string = $this->_unescape($string, $this->escapeChar);
613
-
614
- return preg_replace($this->DELETED, '', $string);
615
- }
616
-
617
- public function reset() {
618
- // clear the patterns collection so that this object may be re-used
619
- $this->_patterns = array();
620
- }
621
-
622
- // private
623
- private $_escaped = array(); // escaped characters
624
- private $_patterns = array(); // patterns stored by index
625
-
626
- // create and add a new pattern to the patterns collection
627
- private function _add() {
628
- $arguments = func_get_args();
629
- $this->_patterns[] = $arguments;
630
- }
631
-
632
- // this is the global replace function (it's quite complicated)
633
- private function _replacement($arguments) {
634
- if (empty($arguments)) return '';
635
-
636
- $i = 1; $j = 0;
637
- // loop through the patterns
638
- while (isset($this->_patterns[$j])) {
639
- $pattern = $this->_patterns[$j++];
640
- // do we have a result?
641
- if (isset($arguments[$i]) && ($arguments[$i] != '')) {
642
- $replacement = $pattern[self::REPLACEMENT];
643
-
644
- if (is_array($replacement) && isset($replacement['fn'])) {
645
-
646
- if (isset($replacement['data'])) $this->buffer = $replacement['data'];
647
- return call_user_func(array(&$this, $replacement['fn']), $arguments, $i);
648
-
649
- } elseif (is_int($replacement)) {
650
- return $arguments[$replacement + $i];
651
-
652
- }
653
- $delete = ($this->escapeChar == '' ||
654
- strpos($arguments[$i], $this->escapeChar) === false)
655
- ? '' : "\x01" . $arguments[$i] . "\x01";
656
- return $delete . $replacement;
657
-
658
- // skip over references to sub-expressions
659
- } else {
660
- $i += $pattern[self::LENGTH];
661
- }
662
- }
663
- }
664
-
665
- private function _backReferences($match, $offset) {
666
- $replacement = $this->buffer['replacement'];
667
- $quote = $this->buffer['quote'];
668
- $i = $this->buffer['length'];
669
- while ($i) {
670
- $replacement = str_replace('$'.$i--, $match[$offset + $i], $replacement);
671
- }
672
- return $replacement;
673
- }
674
-
675
- private function _replace_name($match, $offset){
676
- $length = strlen($match[$offset + 2]);
677
- $start = $length - max($length - strlen($match[$offset + 3]), 0);
678
- return substr($match[$offset + 1], $start, $length) . $match[$offset + 4];
679
- }
680
-
681
- private function _replace_encoded($match, $offset) {
682
- return $this->buffer[$match[$offset]];
683
- }
684
-
685
-
686
- // php : we cannot pass additional data to preg_replace_callback,
687
- // and we cannot use &$this in create_function, so let's go to lower level
688
- private $buffer;
689
-
690
- // encode escaped characters
691
- private function _escape($string, $escapeChar) {
692
- if ($escapeChar) {
693
- $this->buffer = $escapeChar;
694
- return preg_replace_callback(
695
- '/\\' . $escapeChar . '(.)' .'/',
696
- array(&$this, '_escapeBis'),
697
- $string
698
- );
699
-
700
- } else {
701
- return $string;
702
- }
703
- }
704
- private function _escapeBis($match) {
705
- $this->_escaped[] = $match[1];
706
- return $this->buffer;
707
- }
708
-
709
- // decode escaped characters
710
- private function _unescape($string, $escapeChar) {
711
- if ($escapeChar) {
712
- $regexp = '/'.'\\'.$escapeChar.'/';
713
- $this->buffer = array('escapeChar'=> $escapeChar, 'i' => 0);
714
- return preg_replace_callback
715
- (
716
- $regexp,
717
- array(&$this, '_unescapeBis'),
718
- $string
719
- );
720
-
721
- } else {
722
- return $string;
723
- }
724
- }
725
- private function _unescapeBis() {
726
- if (isset($this->_escaped[$this->buffer['i']])
727
- && $this->_escaped[$this->buffer['i']] != '')
728
- {
729
- $temp = $this->_escaped[$this->buffer['i']];
730
- } else {
731
- $temp = '';
732
- }
733
- $this->buffer['i']++;
734
- return $this->buffer['escapeChar'] . $temp;
735
- }
736
-
737
- private function _internalEscape($string) {
738
- return preg_replace($this->ESCAPE, '', $string);
739
- }
740
- }
741
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify.php DELETED
@@ -1,608 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Minify - Combines, minifies, and caches JavaScript and CSS files on demand.
9
- *
10
- * See README for usage instructions (for now).
11
- *
12
- * This library was inspired by {@link mailto:flashkot@mail.ru jscsscomp by Maxim Martynyuk}
13
- * and by the article {@link http://www.hunlock.com/blogs/Supercharged_Javascript "Supercharged JavaScript" by Patrick Hunlock}.
14
- *
15
- * Requires PHP 5.1.0.
16
- * Tested on PHP 5.1.6.
17
- *
18
- * @package Minify
19
- * @author Ryan Grove <ryan@wonko.com>
20
- * @author Stephen Clay <steve@mrclay.org>
21
- * @copyright 2008 Ryan Grove, Stephen Clay. All rights reserved.
22
- * @license http://opensource.org/licenses/bsd-license.php New BSD License
23
- * @link http://code.google.com/p/minify/
24
- */
25
- class Minify {
26
-
27
- const VERSION = '2.2.0';
28
- const TYPE_CSS = 'text/css';
29
- const TYPE_HTML = 'text/html';
30
- // there is some debate over the ideal JS Content-Type, but this is the
31
- // Apache default and what Yahoo! uses..
32
- const TYPE_JS = 'application/x-javascript';
33
- const URL_DEBUG = 'http://code.google.com/p/minify/wiki/Debugging';
34
-
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
- /**
49
- * If this string is not empty AND the serve() option 'bubbleCssImports' is
50
- * NOT set, then serve() will check CSS files for @import declarations that
51
- * appear too late in the combined stylesheet. If found, serve() will prepend
52
- * the output with this warning.
53
- *
54
- * @var string $importWarning
55
- */
56
- public static $importWarning = "/* See http://code.google.com/p/minify/wiki/CommonProblems#@imports_can_appear_in_invalid_locations_in_combined_CSS_files */\n";
57
-
58
- /**
59
- * Has the DOCUMENT_ROOT been set in user code?
60
- *
61
- * @var bool
62
- */
63
- public static $isDocRootSet = false;
64
-
65
- /**
66
- * Specify a cache object (with identical interface as Minify_Cache_File) or
67
- * a path to use with Minify_Cache_File.
68
- *
69
- * If not called, Minify will not use a cache and, for each 200 response, will
70
- * need to recombine files, minify and encode the output.
71
- *
72
- * @param mixed $cache object with identical interface as Minify_Cache_File or
73
- * a directory path, or null to disable caching. (default = '')
74
- *
75
- * @param bool $fileLocking (default = true) This only applies if the first
76
- * parameter is a string.
77
- *
78
- * @return null
79
- */
80
- public static function setCache($cache = '', $fileLocking = true)
81
- {
82
- if (is_string($cache)) {
83
- self::$_cache = new Minify_Cache_File($cache, $fileLocking);
84
- } else {
85
- self::$_cache = $cache;
86
- }
87
- }
88
-
89
- /**
90
- * Serve a request for a minified file.
91
- *
92
- * Here are the available options and defaults in the base controller:
93
- *
94
- * 'isPublic' : send "public" instead of "private" in Cache-Control
95
- * headers, allowing shared caches to cache the output. (default true)
96
- *
97
- * 'quiet' : set to true to have serve() return an array rather than sending
98
- * any headers/output (default false)
99
- *
100
- * 'encodeOutput' : set to false to disable content encoding, and not send
101
- * the Vary header (default true)
102
- *
103
- * 'encodeMethod' : generally you should let this be determined by
104
- * HTTP_Encoder (leave null), but you can force a particular encoding
105
- * to be returned, by setting this to 'gzip' or '' (no encoding)
106
- *
107
- * 'encodeLevel' : level of encoding compression (0 to 9, default 9)
108
- *
109
- * 'contentTypeCharset' : appended to the Content-Type header sent. Set to a falsey
110
- * value to remove. (default 'utf-8')
111
- *
112
- * 'maxAge' : set this to the number of seconds the client should use its cache
113
- * before revalidating with the server. This sets Cache-Control: max-age and the
114
- * Expires header. Unlike the old 'setExpires' setting, this setting will NOT
115
- * prevent conditional GETs. Note this has nothing to do with server-side caching.
116
- *
117
- * 'rewriteCssUris' : If true, serve() will automatically set the 'currentDir'
118
- * minifier option to enable URI rewriting in CSS files (default true)
119
- *
120
- * 'bubbleCssImports' : If true, all @import declarations in combined CSS
121
- * files will be move to the top. Note this may alter effective CSS values
122
- * due to a change in order. (default false)
123
- *
124
- * 'debug' : set to true to minify all sources with the 'Lines' controller, which
125
- * eases the debugging of combined files. This also prevents 304 responses.
126
- * @see Minify_Lines::minify()
127
- *
128
- * 'minifiers' : to override Minify's default choice of minifier function for
129
- * a particular content-type, specify your callback under the key of the
130
- * content-type:
131
- * <code>
132
- * // call customCssMinifier($css) for all CSS minification
133
- * $options['minifiers'][Minify::TYPE_CSS] = 'customCssMinifier';
134
- *
135
- * // don't minify Javascript at all
136
- * $options['minifiers'][Minify::TYPE_JS] = '';
137
- * </code>
138
- *
139
- * 'minifierOptions' : to send options to the minifier function, specify your options
140
- * under the key of the content-type. E.g. To send the CSS minifier an option:
141
- * <code>
142
- * // give CSS minifier array('optionName' => 'optionValue') as 2nd argument
143
- * $options['minifierOptions'][Minify::TYPE_CSS]['optionName'] = 'optionValue';
144
- * </code>
145
- *
146
- * 'contentType' : (optional) this is only needed if your file extension is not
147
- * js/css/html. The given content-type will be sent regardless of source file
148
- * extension, so this should not be used in a Groups config with other
149
- * Javascript/CSS files.
150
- *
151
- * Any controller options are documented in that controller's setupSources() method.
152
- *
153
- * @param mixed $controller instance of subclass of Minify_Controller_Base or string
154
- * name of controller. E.g. 'Files'
155
- *
156
- * @param array $options controller/serve options
157
- *
158
- * @return null|array if the 'quiet' option is set to true, an array
159
- * with keys "success" (bool), "statusCode" (int), "content" (string), and
160
- * "headers" (array).
161
- *
162
- * @throws Exception
163
- */
164
- public static function serve($controller, $options = array())
165
- {
166
- if (! self::$isDocRootSet && 0 === stripos(PHP_OS, 'win')) {
167
- self::setDocRoot();
168
- }
169
-
170
- if (is_string($controller)) {
171
- // make $controller into object
172
- $class = 'Minify_Controller_' . $controller;
173
- $controller = new $class();
174
- /* @var Minify_Controller_Base $controller */
175
- }
176
-
177
- // set up controller sources and mix remaining options with
178
- // controller defaults
179
- $options = $controller->setupSources($options);
180
- $options = $controller->analyzeSources($options);
181
- self::$_options = $controller->mixInDefaultOptions($options);
182
-
183
- // check request validity
184
- if (! $controller->sources) {
185
- // invalid request!
186
- if (! self::$_options['quiet']) {
187
- self::_errorExit(self::$_options['badRequestHeader'], self::URL_DEBUG);
188
- } else {
189
- list(,$statusCode) = explode(' ', self::$_options['badRequestHeader']);
190
- return array(
191
- 'success' => false
192
- ,'statusCode' => (int)$statusCode
193
- ,'content' => ''
194
- ,'headers' => array()
195
- );
196
- }
197
- }
198
-
199
- self::$_controller = $controller;
200
-
201
- if (self::$_options['debug']) {
202
- self::_setupDebug($controller->sources);
203
- self::$_options['maxAge'] = 0;
204
- }
205
-
206
- // determine encoding
207
- if (self::$_options['encodeOutput']) {
208
- $sendVary = true;
209
- if (self::$_options['encodeMethod'] !== null) {
210
- // controller specifically requested this
211
- $contentEncoding = self::$_options['encodeMethod'];
212
- } else {
213
- // sniff request header
214
- // depending on what the client accepts, $contentEncoding may be
215
- // 'x-gzip' while our internal encodeMethod is 'gzip'. Calling
216
- // getAcceptedEncoding(false, false) leaves out compress and deflate as options.
217
- list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false, false);
218
- $sendVary = ! HTTP_Encoder::isBuggyIe();
219
- }
220
- } else {
221
- self::$_options['encodeMethod'] = ''; // identity (no encoding)
222
- }
223
-
224
- // check client cache
225
- $cgOptions = array(
226
- 'lastModifiedTime' => self::$_options['lastModifiedTime']
227
- ,'isPublic' => self::$_options['isPublic']
228
- ,'encoding' => self::$_options['encodeMethod']
229
- );
230
- if (self::$_options['maxAge'] > 0) {
231
- $cgOptions['maxAge'] = self::$_options['maxAge'];
232
- } elseif (self::$_options['debug']) {
233
- $cgOptions['invalidate'] = true;
234
- }
235
- $cg = new HTTP_ConditionalGet($cgOptions);
236
- if ($cg->cacheIsValid) {
237
- // client's cache is valid
238
- if (! self::$_options['quiet']) {
239
- $cg->sendHeaders();
240
- return;
241
- } else {
242
- return array(
243
- 'success' => true
244
- ,'statusCode' => 304
245
- ,'content' => ''
246
- ,'headers' => $cg->getHeaders()
247
- );
248
- }
249
- } else {
250
- // client will need output
251
- $headers = $cg->getHeaders();
252
- unset($cg);
253
- }
254
-
255
- if (self::$_options['contentType'] === self::TYPE_CSS
256
- && self::$_options['rewriteCssUris']) {
257
- foreach($controller->sources as $key => $source) {
258
- if ($source->filepath
259
- && !isset($source->minifyOptions['currentDir'])
260
- && !isset($source->minifyOptions['prependRelativePath'])
261
- ) {
262
- $source->minifyOptions['currentDir'] = dirname($source->filepath);
263
- }
264
- }
265
- }
266
-
267
- // check server cache
268
- if (null !== self::$_cache && ! self::$_options['debug']) {
269
- // using cache
270
- // the goal is to use only the cache methods to sniff the length and
271
- // output the content, as they do not require ever loading the file into
272
- // memory.
273
- $cacheId = self::_getCacheId();
274
- $fullCacheId = (self::$_options['encodeMethod'])
275
- ? $cacheId . '.gz'
276
- : $cacheId;
277
- // check cache for valid entry
278
- $cacheIsReady = self::$_cache->isValid($fullCacheId, self::$_options['lastModifiedTime']);
279
- if ($cacheIsReady) {
280
- $cacheContentLength = self::$_cache->getSize($fullCacheId);
281
- } else {
282
- // generate & cache content
283
- try {
284
- $content = self::_combineMinify();
285
- } catch (Exception $e) {
286
- self::$_controller->log($e->getMessage());
287
- if (! self::$_options['quiet']) {
288
- self::_errorExit(self::$_options['errorHeader'], self::URL_DEBUG);
289
- }
290
- throw $e;
291
- }
292
- self::$_cache->store($cacheId, $content);
293
- if (function_exists('gzencode') && self::$_options['encodeMethod']) {
294
- self::$_cache->store($cacheId . '.gz', gzencode($content, self::$_options['encodeLevel']));
295
- }
296
- }
297
- } else {
298
- // no cache
299
- $cacheIsReady = false;
300
- try {
301
- $content = self::_combineMinify();
302
- } catch (Exception $e) {
303
- self::$_controller->log($e->getMessage());
304
- if (! self::$_options['quiet']) {
305
- self::_errorExit(self::$_options['errorHeader'], self::URL_DEBUG);
306
- }
307
- throw $e;
308
- }
309
- }
310
- if (! $cacheIsReady && self::$_options['encodeMethod']) {
311
- // still need to encode
312
- $content = gzencode($content, self::$_options['encodeLevel']);
313
- }
314
-
315
- // add headers
316
- $headers['Content-Length'] = $cacheIsReady
317
- ? $cacheContentLength
318
- : ((function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
319
- ? mb_strlen($content, '8bit')
320
- : strlen($content)
321
- );
322
- $headers['Content-Type'] = self::$_options['contentTypeCharset']
323
- ? self::$_options['contentType'] . '; charset=' . self::$_options['contentTypeCharset']
324
- : self::$_options['contentType'];
325
- if (self::$_options['encodeMethod'] !== '') {
326
- $headers['Content-Encoding'] = $contentEncoding;
327
- }
328
- if (self::$_options['encodeOutput'] && $sendVary) {
329
- $headers['Vary'] = 'Accept-Encoding';
330
- }
331
-
332
- if (! self::$_options['quiet']) {
333
- // output headers & content
334
- foreach ($headers as $name => $val) {
335
- header($name . ': ' . $val);
336
- }
337
- if ($cacheIsReady) {
338
- self::$_cache->display($fullCacheId);
339
- } else {
340
- echo $content;
341
- }
342
- } else {
343
- return array(
344
- 'success' => true
345
- ,'statusCode' => 200
346
- ,'content' => $cacheIsReady
347
- ? self::$_cache->fetch($fullCacheId)
348
- : $content
349
- ,'headers' => $headers
350
- );
351
- }
352
- }
353
-
354
- /**
355
- * Return combined minified content for a set of sources
356
- *
357
- * No internal caching will be used and the content will not be HTTP encoded.
358
- *
359
- * @param array $sources array of filepaths and/or Minify_Source objects
360
- *
361
- * @param array $options (optional) array of options for serve. By default
362
- * these are already set: quiet = true, encodeMethod = '', lastModifiedTime = 0.
363
- *
364
- * @return string
365
- */
366
- public static function combine($sources, $options = array())
367
- {
368
- $cache = self::$_cache;
369
- self::$_cache = null;
370
- $options = array_merge(array(
371
- 'files' => (array)$sources
372
- ,'quiet' => true
373
- ,'encodeMethod' => ''
374
- ,'lastModifiedTime' => 0
375
- ), $options);
376
- $out = self::serve('Files', $options);
377
- self::$_cache = $cache;
378
- return $out['content'];
379
- }
380
-
381
- /**
382
- * Set $_SERVER['DOCUMENT_ROOT']. On IIS, the value is created from SCRIPT_FILENAME and SCRIPT_NAME.
383
- *
384
- * @param string $docRoot value to use for DOCUMENT_ROOT
385
- */
386
- public static function setDocRoot($docRoot = '')
387
- {
388
- self::$isDocRootSet = true;
389
- if ($docRoot) {
390
- $_SERVER['DOCUMENT_ROOT'] = $docRoot;
391
- } elseif (isset($_SERVER['SERVER_SOFTWARE'])
392
- && 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/')) {
393
- $_SERVER['DOCUMENT_ROOT'] = substr(
394
- $_SERVER['SCRIPT_FILENAME']
395
- ,0
396
- ,strlen($_SERVER['SCRIPT_FILENAME']) - strlen($_SERVER['SCRIPT_NAME']));
397
- $_SERVER['DOCUMENT_ROOT'] = rtrim($_SERVER['DOCUMENT_ROOT'], '\\');
398
- }
399
- }
400
-
401
- /**
402
- * Any Minify_Cache_* object or null (i.e. no server cache is used)
403
- *
404
- * @var Minify_Cache_File
405
- */
406
- private static $_cache = null;
407
-
408
- /**
409
- * Active controller for current request
410
- *
411
- * @var Minify_Controller_Base
412
- */
413
- protected static $_controller = null;
414
-
415
- /**
416
- * Options for current request
417
- *
418
- * @var array
419
- */
420
- protected static $_options = null;
421
-
422
- /**
423
- * @param string $header
424
- *
425
- * @param string $url
426
- */
427
- protected static function _errorExit($header, $url)
428
- {
429
- $url = htmlspecialchars($url, ENT_QUOTES);
430
- list(,$h1) = explode(' ', $header, 2);
431
- $h1 = htmlspecialchars($h1);
432
- // FastCGI environments require 3rd arg to header() to be set
433
- list(, $code) = explode(' ', $header, 3);
434
- header($header, true, $code);
435
- header('Content-Type: text/html; charset=utf-8');
436
- echo "<h1>$h1</h1>";
437
- echo "<p>Please see <a href='$url'>$url</a>.</p>";
438
- exit;
439
- }
440
-
441
- /**
442
- * Set up sources to use Minify_Lines
443
- *
444
- * @param Minify_Source[] $sources Minify_Source instances
445
- */
446
- protected static function _setupDebug($sources)
447
- {
448
- foreach ($sources as $source) {
449
- $source->minifier = array('Minify_Lines', 'minify');
450
- $id = $source->getId();
451
- $source->minifyOptions = array(
452
- 'id' => (is_file($id) ? basename($id) : $id)
453
- );
454
- }
455
- }
456
-
457
- /**
458
- * Combines sources and minifies the result.
459
- *
460
- * @return string
461
- *
462
- * @throws Exception
463
- */
464
- protected static function _combineMinify()
465
- {
466
- $type = self::$_options['contentType']; // ease readability
467
-
468
- // when combining scripts, make sure all statements separated and
469
- // trailing single line comment is terminated
470
- $implodeSeparator = ($type === self::TYPE_JS)
471
- ? "\n;"
472
- : '';
473
- // allow the user to pass a particular array of options to each
474
- // minifier (designated by type). source objects may still override
475
- // these
476
- $defaultOptions = isset(self::$_options['minifierOptions'][$type])
477
- ? self::$_options['minifierOptions'][$type]
478
- : array();
479
- // if minifier not set, default is no minification. source objects
480
- // may still override this
481
- $defaultMinifier = isset(self::$_options['minifiers'][$type])
482
- ? self::$_options['minifiers'][$type]
483
- : false;
484
-
485
- // process groups of sources with identical minifiers/options
486
- $content = array();
487
- $i = 0;
488
- $l = count(self::$_controller->sources);
489
- $groupToProcessTogether = array();
490
- $lastMinifier = null;
491
- $lastOptions = null;
492
- do {
493
- // get next source
494
- $source = null;
495
- if ($i < $l) {
496
- $source = self::$_controller->sources[$i];
497
- /* @var Minify_Source $source */
498
- $sourceContent = $source->getContent();
499
-
500
- // allow the source to override our minifier and options
501
- $minifier = (null !== $source->minifier)
502
- ? $source->minifier
503
- : $defaultMinifier;
504
- $options = (null !== $source->minifyOptions)
505
- ? array_merge($defaultOptions, $source->minifyOptions)
506
- : $defaultOptions;
507
- }
508
- // do we need to process our group right now?
509
- if ($i > 0 // yes, we have at least the first group populated
510
- && (
511
- ! $source // yes, we ran out of sources
512
- || $type === self::TYPE_CSS // yes, to process CSS individually (avoiding PCRE bugs/limits)
513
- || $minifier !== $lastMinifier // yes, minifier changed
514
- || $options !== $lastOptions) // yes, options changed
515
- )
516
- {
517
- // minify previous sources with last settings
518
- $imploded = implode($implodeSeparator, $groupToProcessTogether);
519
- $groupToProcessTogether = array();
520
- if ($lastMinifier) {
521
- try {
522
- $content[] = call_user_func($lastMinifier, $imploded, $lastOptions);
523
- } catch (Exception $e) {
524
- throw new Exception("Exception in minifier: " . $e->getMessage());
525
- }
526
- } else {
527
- $content[] = $imploded;
528
- }
529
- }
530
- // add content to the group
531
- if ($source) {
532
- $groupToProcessTogether[] = $sourceContent;
533
- $lastMinifier = $minifier;
534
- $lastOptions = $options;
535
- }
536
- $i++;
537
- } while ($source);
538
-
539
- $content = implode($implodeSeparator, $content);
540
-
541
- if ($type === self::TYPE_CSS && false !== strpos($content, '@import')) {
542
- $content = self::_handleCssImports($content);
543
- }
544
-
545
- // do any post-processing (esp. for editing build URIs)
546
- if (self::$_options['postprocessorRequire']) {
547
- require_once self::$_options['postprocessorRequire'];
548
- }
549
- if (self::$_options['postprocessor']) {
550
- $content = call_user_func(self::$_options['postprocessor'], $content, $type);
551
- }
552
- return $content;
553
- }
554
-
555
- /**
556
- * Make a unique cache id for for this request.
557
- *
558
- * Any settings that could affect output are taken into consideration
559
- *
560
- * @param string $prefix
561
- *
562
- * @return string
563
- */
564
- protected static function _getCacheId($prefix = 'minify')
565
- {
566
- $name = preg_replace('/[^a-zA-Z0-9\\.=_,]/', '', self::$_controller->selectionId);
567
- $name = preg_replace('/\\.+/', '.', $name);
568
- $name = substr($name, 0, 100 - 34 - strlen($prefix));
569
- $md5 = md5(serialize(array(
570
- Minify_Source::getDigest(self::$_controller->sources)
571
- ,self::$_options['minifiers']
572
- ,self::$_options['minifierOptions']
573
- ,self::$_options['postprocessor']
574
- ,self::$_options['bubbleCssImports']
575
- ,self::VERSION
576
- )));
577
- return "{$prefix}_{$name}_{$md5}";
578
- }
579
-
580
- /**
581
- * Bubble CSS @imports to the top or prepend a warning if an import is detected not at the top.
582
- *
583
- * @param string $css
584
- *
585
- * @return string
586
- */
587
- protected static function _handleCssImports($css)
588
- {
589
- if (self::$_options['bubbleCssImports']) {
590
- // bubble CSS imports
591
- preg_match_all('/@import.*?;/', $css, $imports);
592
- $css = implode('', $imports[0]) . preg_replace('/@import.*?;/', '', $css);
593
- } else if ('' !== self::$importWarning) {
594
- // remove comments so we don't mistake { in a comment as a block
595
- $noCommentCss = preg_replace('@/\\*[\\s\\S]*?\\*/@', '', $css);
596
- $lastImportPos = strrpos($noCommentCss, '@import');
597
- $firstBlockPos = strpos($noCommentCss, '{');
598
- if (false !== $lastImportPos
599
- && false !== $firstBlockPos
600
- && $firstBlockPos < $lastImportPos
601
- ) {
602
- // { appears before @import : prepend warning
603
- $css = self::$importWarning . $css;
604
- }
605
- }
606
- return $css;
607
- }
608
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Build.php DELETED
@@ -1,101 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Build
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Maintain a single last modification time for a group of Minify sources to
9
- * allow use of far off Expires headers in Minify.
10
- *
11
- * <code>
12
- * // in config file
13
- * $groupSources = array(
14
- * 'js' => array('file1.js', 'file2.js')
15
- * ,'css' => array('file1.css', 'file2.css', 'file3.css')
16
- * )
17
- *
18
- * // during HTML generation
19
- * $jsBuild = new Minify_Build($groupSources['js']);
20
- * $cssBuild = new Minify_Build($groupSources['css']);
21
- *
22
- * $script = "<script type='text/javascript' src='"
23
- * . $jsBuild->uri('/min.php/js') . "'></script>";
24
- * $link = "<link rel='stylesheet' type='text/css' href='"
25
- * . $cssBuild->uri('/min.php/css') . "'>";
26
- *
27
- * // in min.php
28
- * Minify::serve('Groups', array(
29
- * 'groups' => $groupSources
30
- * ,'setExpires' => (time() + 86400 * 365)
31
- * ));
32
- * </code>
33
- *
34
- * @package Minify
35
- * @author Stephen Clay <steve@mrclay.org>
36
- */
37
- class Minify_Build {
38
-
39
- /**
40
- * Last modification time of all files in the build
41
- *
42
- * @var int
43
- */
44
- public $lastModified = 0;
45
-
46
- /**
47
- * String to use as ampersand in uri(). Set this to '&' if
48
- * you are not HTML-escaping URIs.
49
- *
50
- * @var string
51
- */
52
- public static $ampersand = '&amp;';
53
-
54
- /**
55
- * Get a time-stamped URI
56
- *
57
- * <code>
58
- * echo $b->uri('/site.js');
59
- * // outputs "/site.js?1678242"
60
- *
61
- * echo $b->uri('/scriptaculous.js?load=effects');
62
- * // outputs "/scriptaculous.js?load=effects&amp1678242"
63
- * </code>
64
- *
65
- * @param string $uri
66
- * @param boolean $forceAmpersand (default = false) Force the use of ampersand to
67
- * append the timestamp to the URI.
68
- * @return string
69
- */
70
- public function uri($uri, $forceAmpersand = false) {
71
- $sep = ($forceAmpersand || strpos($uri, '?') !== false)
72
- ? self::$ampersand
73
- : '?';
74
- return "{$uri}{$sep}{$this->lastModified}";
75
- }
76
-
77
- /**
78
- * Create a build object
79
- *
80
- * @param array $sources array of Minify_Source objects and/or file paths
81
- *
82
- * @return null
83
- */
84
- public function __construct($sources)
85
- {
86
- $max = 0;
87
- foreach ((array)$sources as $source) {
88
- if ($source instanceof Minify_Source) {
89
- $max = max($max, $source->lastModified);
90
- } elseif (is_string($source)) {
91
- if (0 === strpos($source, '//')) {
92
- $source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1);
93
- }
94
- if (is_file($source)) {
95
- $max = max($max, filemtime($source));
96
- }
97
- }
98
- }
99
- $this->lastModified = $max;
100
- }
101
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/CSS.php DELETED
@@ -1,99 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_CSS
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Minify CSS
9
- *
10
- * This class uses Minify_CSS_Compressor and Minify_CSS_UriRewriter to
11
- * minify CSS and rewrite relative URIs.
12
- *
13
- * @package Minify
14
- * @author Stephen Clay <steve@mrclay.org>
15
- * @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
16
- */
17
- class Minify_CSS {
18
-
19
- /**
20
- * Minify a CSS string
21
- *
22
- * @param string $css
23
- *
24
- * @param array $options available options:
25
- *
26
- * 'preserveComments': (default true) multi-line comments that begin
27
- * with "/*!" will be preserved with newlines before and after to
28
- * enhance readability.
29
- *
30
- * 'removeCharsets': (default true) remove all @charset at-rules
31
- *
32
- * 'prependRelativePath': (default null) if given, this string will be
33
- * prepended to all relative URIs in import/url declarations
34
- *
35
- * 'currentDir': (default null) if given, this is assumed to be the
36
- * directory of the current CSS file. Using this, minify will rewrite
37
- * all relative URIs in import/url declarations to correctly point to
38
- * the desired files. For this to work, the files *must* exist and be
39
- * visible by the PHP process.
40
- *
41
- * 'symlinks': (default = array()) If the CSS file is stored in
42
- * a symlink-ed directory, provide an array of link paths to
43
- * target paths, where the link paths are within the document root. Because
44
- * paths need to be normalized for this to work, use "//" to substitute
45
- * the doc root in the link paths (the array keys). E.g.:
46
- * <code>
47
- * array('//symlink' => '/real/target/path') // unix
48
- * array('//static' => 'D:\\staticStorage') // Windows
49
- * </code>
50
- *
51
- * 'docRoot': (default = $_SERVER['DOCUMENT_ROOT'])
52
- * see Minify_CSS_UriRewriter::rewrite
53
- *
54
- * @return string
55
- */
56
- public static function minify($css, $options = array())
57
- {
58
- $options = array_merge(array(
59
- 'compress' => true,
60
- 'removeCharsets' => true,
61
- 'preserveComments' => true,
62
- 'currentDir' => null,
63
- 'docRoot' => $_SERVER['DOCUMENT_ROOT'],
64
- 'prependRelativePath' => null,
65
- 'symlinks' => array(),
66
- ), $options);
67
-
68
- if ($options['removeCharsets']) {
69
- $css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
70
- }
71
- if ($options['compress']) {
72
- if (! $options['preserveComments']) {
73
- $css = Minify_CSS_Compressor::process($css, $options);
74
- } else {
75
- $css = Minify_CommentPreserver::process(
76
- $css
77
- ,array('Minify_CSS_Compressor', 'process')
78
- ,array($options)
79
- );
80
- }
81
- }
82
- if (! $options['currentDir'] && ! $options['prependRelativePath']) {
83
- return $css;
84
- }
85
- if ($options['currentDir']) {
86
- return Minify_CSS_UriRewriter::rewrite(
87
- $css
88
- ,$options['currentDir']
89
- ,$options['docRoot']
90
- ,$options['symlinks']
91
- );
92
- } else {
93
- return Minify_CSS_UriRewriter::prepend(
94
- $css
95
- ,$options['prependRelativePath']
96
- );
97
- }
98
- }
99
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/CSS/Compressor.php DELETED
@@ -1,249 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_CSS_Compressor
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Compress CSS
9
- *
10
- * This is a heavy regex-based removal of whitespace, unnecessary
11
- * comments and tokens, and some CSS value minimization, where practical.
12
- * Many steps have been taken to avoid breaking comment-based hacks,
13
- * including the ie5/mac filter (and its inversion), but expect tricky
14
- * hacks involving comment tokens in 'content' value strings to break
15
- * minimization badly. A test suite is available.
16
- *
17
- * @package Minify
18
- * @author Stephen Clay <steve@mrclay.org>
19
- * @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
20
- */
21
- class Minify_CSS_Compressor {
22
-
23
- /**
24
- * Minify a CSS string
25
- *
26
- * @param string $css
27
- *
28
- * @param array $options (currently ignored)
29
- *
30
- * @return string
31
- */
32
- public static function process($css, $options = array())
33
- {
34
- $obj = new Minify_CSS_Compressor($options);
35
- return $obj->_process($css);
36
- }
37
-
38
- /**
39
- * @var array
40
- */
41
- protected $_options = null;
42
-
43
- /**
44
- * Are we "in" a hack? I.e. are some browsers targetted until the next comment?
45
- *
46
- * @var bool
47
- */
48
- protected $_inHack = false;
49
-
50
-
51
- /**
52
- * Constructor
53
- *
54
- * @param array $options (currently ignored)
55
- */
56
- private function __construct($options) {
57
- $this->_options = $options;
58
- }
59
-
60
- /**
61
- * Minify a CSS string
62
- *
63
- * @param string $css
64
- *
65
- * @return string
66
- */
67
- protected function _process($css)
68
- {
69
- $css = str_replace("\r\n", "\n", $css);
70
-
71
- // preserve empty comment after '>'
72
- // http://www.webdevout.net/css-hacks#in_css-selectors
73
- $css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
74
-
75
- // preserve empty comment between property and value
76
- // http://css-discuss.incutio.com/?page=BoxModelHack
77
- $css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
78
- $css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
79
-
80
- // apply callback to all valid comments (and strip out surrounding ws
81
- $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
82
- ,array($this, '_commentCB'), $css);
83
-
84
- // remove ws around { } and last semicolon in declaration block
85
- $css = preg_replace('/\\s*{\\s*/', '{', $css);
86
- $css = preg_replace('/;?\\s*}\\s*/', '}', $css);
87
-
88
- // remove ws surrounding semicolons
89
- $css = preg_replace('/\\s*;\\s*/', ';', $css);
90
-
91
- // remove ws around urls
92
- $css = preg_replace('/
93
- url\\( # url(
94
- \\s*
95
- ([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
96
- \\s*
97
- \\) # )
98
- /x', 'url($1)', $css);
99
-
100
- // remove ws between rules and colons
101
- $css = preg_replace('/
102
- \\s*
103
- ([{;]) # 1 = beginning of block or rule separator
104
- \\s*
105
- ([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
106
- \\s*
107
- :
108
- \\s*
109
- (\\b|[#\'"-]) # 3 = first character of a value
110
- /x', '$1$2:$3', $css);
111
-
112
- // remove ws in selectors
113
- $css = preg_replace_callback('/
114
- (?: # non-capture
115
- \\s*
116
- [^~>+,\\s]+ # selector part
117
- \\s*
118
- [,>+~] # combinators
119
- )+
120
- \\s*
121
- [^~>+,\\s]+ # selector part
122
- { # open declaration block
123
- /x'
124
- ,array($this, '_selectorsCB'), $css);
125
-
126
- // minimize hex colors
127
- $css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
128
- , '$1#$2$3$4$5', $css);
129
-
130
- // remove spaces between font families
131
- $css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
132
- ,array($this, '_fontFamilyCB'), $css);
133
-
134
- $css = preg_replace('/@import\\s+url/', '@import url', $css);
135
-
136
- // replace any ws involving newlines with a single newline
137
- $css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
138
-
139
- // separate common descendent selectors w/ newlines (to limit line lengths)
140
- $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
141
-
142
- // Use newline after 1st numeric value (to limit line lengths).
143
- $css = preg_replace('/
144
- ((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
145
- \\s+
146
- /x'
147
- ,"$1\n", $css);
148
-
149
- // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
150
- $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
151
-
152
- return trim($css);
153
- }
154
-
155
- /**
156
- * Replace what looks like a set of selectors
157
- *
158
- * @param array $m regex matches
159
- *
160
- * @return string
161
- */
162
- protected function _selectorsCB($m)
163
- {
164
- // remove ws around the combinators
165
- return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
166
- }
167
-
168
- /**
169
- * Process a comment and return a replacement
170
- *
171
- * @param array $m regex matches
172
- *
173
- * @return string
174
- */
175
- protected function _commentCB($m)
176
- {
177
- $hasSurroundingWs = (trim($m[0]) !== $m[1]);
178
- $m = $m[1];
179
- // $m is the comment content w/o the surrounding tokens,
180
- // but the return value will replace the entire comment.
181
- if ($m === 'keep') {
182
- return '/**/';
183
- }
184
- if ($m === '" "') {
185
- // component of http://tantek.com/CSS/Examples/midpass.html
186
- return '/*" "*/';
187
- }
188
- if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
189
- // component of http://tantek.com/CSS/Examples/midpass.html
190
- return '/*";}}/* */';
191
- }
192
- if ($this->_inHack) {
193
- // inversion: feeding only to one browser
194
- if (preg_match('@
195
- ^/ # comment started like /*/
196
- \\s*
197
- (\\S[\\s\\S]+?) # has at least some non-ws content
198
- \\s*
199
- /\\* # ends like /*/ or /**/
200
- @x', $m, $n)) {
201
- // end hack mode after this comment, but preserve the hack and comment content
202
- $this->_inHack = false;
203
- return "/*/{$n[1]}/**/";
204
- }
205
- }
206
- if (substr($m, -1) === '\\') { // comment ends like \*/
207
- // begin hack mode and preserve hack
208
- $this->_inHack = true;
209
- return '/*\\*/';
210
- }
211
- if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
212
- // begin hack mode and preserve hack
213
- $this->_inHack = true;
214
- return '/*/*/';
215
- }
216
- if ($this->_inHack) {
217
- // a regular comment ends hack mode but should be preserved
218
- $this->_inHack = false;
219
- return '/**/';
220
- }
221
- // Issue 107: if there's any surrounding whitespace, it may be important, so
222
- // replace the comment with a single space
223
- return $hasSurroundingWs // remove all other comments
224
- ? ' '
225
- : '';
226
- }
227
-
228
- /**
229
- * Process a font-family listing and return a replacement
230
- *
231
- * @param array $m regex matches
232
- *
233
- * @return string
234
- */
235
- protected function _fontFamilyCB($m)
236
- {
237
- // Issue 210: must not eliminate WS between words in unquoted families
238
- $pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
239
- $out = 'font-family:';
240
- while (null !== ($piece = array_shift($pieces))) {
241
- if ($piece[0] !== '"' && $piece[0] !== "'") {
242
- $piece = preg_replace('/\\s+/', ' ', $piece);
243
- $piece = preg_replace('/\\s?,\\s?/', ',', $piece);
244
- }
245
- $out .= $piece;
246
- }
247
- return $out . $m[2];
248
- }
249
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/CSS/UriRewriter.php DELETED
@@ -1,307 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_CSS_UriRewriter
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Rewrite file-relative URIs as root-relative in CSS files
9
- *
10
- * @package Minify
11
- * @author Stephen Clay <steve@mrclay.org>
12
- */
13
- class Minify_CSS_UriRewriter {
14
-
15
- /**
16
- * rewrite() and rewriteRelative() append debugging information here
17
- *
18
- * @var string
19
- */
20
- public static $debugText = '';
21
-
22
- /**
23
- * In CSS content, rewrite file relative URIs as root relative
24
- *
25
- * @param string $css
26
- *
27
- * @param string $currentDir The directory of the current CSS file.
28
- *
29
- * @param string $docRoot The document root of the web site in which
30
- * the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).
31
- *
32
- * @param array $symlinks (default = array()) If the CSS file is stored in
33
- * a symlink-ed directory, provide an array of link paths to
34
- * target paths, where the link paths are within the document root. Because
35
- * paths need to be normalized for this to work, use "//" to substitute
36
- * the doc root in the link paths (the array keys). E.g.:
37
- * <code>
38
- * array('//symlink' => '/real/target/path') // unix
39
- * array('//static' => 'D:\\staticStorage') // Windows
40
- * </code>
41
- *
42
- * @return string
43
- */
44
- public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array())
45
- {
46
- self::$_docRoot = self::_realpath(
47
- $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
48
- );
49
- self::$_currentDir = self::_realpath($currentDir);
50
- self::$_symlinks = array();
51
-
52
- // normalize symlinks
53
- foreach ($symlinks as $link => $target) {
54
- $link = ($link === '//')
55
- ? self::$_docRoot
56
- : str_replace('//', self::$_docRoot . '/', $link);
57
- $link = strtr($link, '/', DIRECTORY_SEPARATOR);
58
- self::$_symlinks[$link] = self::_realpath($target);
59
- }
60
-
61
- self::$debugText .= "docRoot : " . self::$_docRoot . "\n"
62
- . "currentDir : " . self::$_currentDir . "\n";
63
- if (self::$_symlinks) {
64
- self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
65
- }
66
- self::$debugText .= "\n";
67
-
68
- $css = self::_trimUrls($css);
69
-
70
- // rewrite
71
- $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
72
- ,array(self::$className, '_processUriCB'), $css);
73
- $css = preg_replace_callback('/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'
74
- ,array(self::$className, '_processUriCB'), $css);
75
-
76
- return $css;
77
- }
78
-
79
- /**
80
- * In CSS content, prepend a path to relative URIs
81
- *
82
- * @param string $css
83
- *
84
- * @param string $path The path to prepend.
85
- *
86
- * @return string
87
- */
88
- public static function prepend($css, $path)
89
- {
90
- self::$_prependPath = $path;
91
-
92
- $css = self::_trimUrls($css);
93
-
94
- // append
95
- $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
96
- ,array(self::$className, '_processUriCB'), $css);
97
- $css = preg_replace_callback('/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'
98
- ,array(self::$className, '_processUriCB'), $css);
99
-
100
- self::$_prependPath = null;
101
- return $css;
102
- }
103
-
104
- /**
105
- * Get a root relative URI from a file relative URI
106
- *
107
- * <code>
108
- * Minify_CSS_UriRewriter::rewriteRelative(
109
- * '../img/hello.gif'
110
- * , '/home/user/www/css' // path of CSS file
111
- * , '/home/user/www' // doc root
112
- * );
113
- * // returns '/img/hello.gif'
114
- *
115
- * // example where static files are stored in a symlinked directory
116
- * Minify_CSS_UriRewriter::rewriteRelative(
117
- * 'hello.gif'
118
- * , '/var/staticFiles/theme'
119
- * , '/home/user/www'
120
- * , array('/home/user/www/static' => '/var/staticFiles')
121
- * );
122
- * // returns '/static/theme/hello.gif'
123
- * </code>
124
- *
125
- * @param string $uri file relative URI
126
- *
127
- * @param string $realCurrentDir realpath of the current file's directory.
128
- *
129
- * @param string $realDocRoot realpath of the site document root.
130
- *
131
- * @param array $symlinks (default = array()) If the file is stored in
132
- * a symlink-ed directory, provide an array of link paths to
133
- * real target paths, where the link paths "appear" to be within the document
134
- * root. E.g.:
135
- * <code>
136
- * array('/home/foo/www/not/real/path' => '/real/target/path') // unix
137
- * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
138
- * </code>
139
- *
140
- * @return string
141
- */
142
- public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array())
143
- {
144
- // prepend path with current dir separator (OS-independent)
145
- $path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
146
- . DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
147
-
148
- self::$debugText .= "file-relative URI : {$uri}\n"
149
- . "path prepended : {$path}\n";
150
-
151
- // "unresolve" a symlink back to doc root
152
- foreach ($symlinks as $link => $target) {
153
- if (0 === strpos($path, $target)) {
154
- // replace $target with $link
155
- $path = $link . substr($path, strlen($target));
156
-
157
- self::$debugText .= "symlink unresolved : {$path}\n";
158
-
159
- break;
160
- }
161
- }
162
- // strip doc root
163
- $path = substr($path, strlen($realDocRoot));
164
-
165
- self::$debugText .= "docroot stripped : {$path}\n";
166
-
167
- // fix to root-relative URI
168
- $uri = strtr($path, '/\\', '//');
169
- $uri = self::removeDots($uri);
170
-
171
- self::$debugText .= "traversals removed : {$uri}\n\n";
172
-
173
- return $uri;
174
- }
175
-
176
- /**
177
- * Remove instances of "./" and "../" where possible from a root-relative URI
178
- *
179
- * @param string $uri
180
- *
181
- * @return string
182
- */
183
- public static function removeDots($uri)
184
- {
185
- $uri = str_replace('/./', '/', $uri);
186
- // inspired by patch from Oleg Cherniy
187
- do {
188
- $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
189
- } while ($changed);
190
- return $uri;
191
- }
192
-
193
- /**
194
- * Defines which class to call as part of callbacks, change this
195
- * if you extend Minify_CSS_UriRewriter
196
- *
197
- * @var string
198
- */
199
- protected static $className = 'Minify_CSS_UriRewriter';
200
-
201
- /**
202
- * Get realpath with any trailing slash removed. If realpath() fails,
203
- * just remove the trailing slash.
204
- *
205
- * @param string $path
206
- *
207
- * @return mixed path with no trailing slash
208
- */
209
- protected static function _realpath($path)
210
- {
211
- $realPath = realpath($path);
212
- if ($realPath !== false) {
213
- $path = $realPath;
214
- }
215
- return rtrim($path, '/\\');
216
- }
217
-
218
- /**
219
- * Directory of this stylesheet
220
- *
221
- * @var string
222
- */
223
- private static $_currentDir = '';
224
-
225
- /**
226
- * DOC_ROOT
227
- *
228
- * @var string
229
- */
230
- private static $_docRoot = '';
231
-
232
- /**
233
- * directory replacements to map symlink targets back to their
234
- * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
235
- *
236
- * @var array
237
- */
238
- private static $_symlinks = array();
239
-
240
- /**
241
- * Path to prepend
242
- *
243
- * @var string
244
- */
245
- private static $_prependPath = null;
246
-
247
- /**
248
- * @param string $css
249
- *
250
- * @return string
251
- */
252
- private static function _trimUrls($css)
253
- {
254
- return preg_replace('/
255
- url\\( # url(
256
- \\s*
257
- ([^\\)]+?) # 1 = URI (assuming does not contain ")")
258
- \\s*
259
- \\) # )
260
- /x', 'url($1)', $css);
261
- }
262
-
263
- /**
264
- * @param array $m
265
- *
266
- * @return string
267
- */
268
- private static function _processUriCB($m)
269
- {
270
- // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
271
- $isImport = ($m[0][0] === '@');
272
- // determine URI and the quote character (if any)
273
- if ($isImport) {
274
- $quoteChar = $m[1];
275
- $uri = $m[2];
276
- } else {
277
- // $m[1] is either quoted or not
278
- $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
279
- ? $m[1][0]
280
- : '';
281
- $uri = ($quoteChar === '')
282
- ? $m[1]
283
- : substr($m[1], 1, strlen($m[1]) - 2);
284
- }
285
- // if not root/scheme relative and not starts with scheme
286
- if (!preg_match('~^(/|[a-z]+\:)~', $uri)) {
287
- // URI is file-relative: rewrite depending on options
288
- if (self::$_prependPath === null) {
289
- $uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
290
- } else {
291
- $uri = self::$_prependPath . $uri;
292
- if ($uri[0] === '/') {
293
- $root = '';
294
- $rootRelative = $uri;
295
- $uri = $root . self::removeDots($rootRelative);
296
- } elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) {
297
- $root = $m[1];
298
- $rootRelative = substr($uri, strlen($root));
299
- $uri = $root . self::removeDots($rootRelative);
300
- }
301
- }
302
- }
303
- return $isImport
304
- ? "@import {$quoteChar}{$uri}{$quoteChar}"
305
- : "url({$quoteChar}{$uri}{$quoteChar})";
306
- }
307
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/CSSmin.php DELETED
@@ -1,85 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_CSSmin
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Wrapper for CSSmin
9
- *
10
- * This class uses CSSmin and Minify_CSS_UriRewriter to minify CSS and rewrite relative URIs.
11
- *
12
- * @package Minify
13
- * @author Stephen Clay <steve@mrclay.org>
14
- */
15
- class Minify_CSSmin {
16
-
17
- /**
18
- * Minify a CSS string
19
- *
20
- * @param string $css
21
- *
22
- * @param array $options available options:
23
- *
24
- * 'removeCharsets': (default true) remove all @charset at-rules
25
- *
26
- * 'prependRelativePath': (default null) if given, this string will be
27
- * prepended to all relative URIs in import/url declarations
28
- *
29
- * 'currentDir': (default null) if given, this is assumed to be the
30
- * directory of the current CSS file. Using this, minify will rewrite
31
- * all relative URIs in import/url declarations to correctly point to
32
- * the desired files. For this to work, the files *must* exist and be
33
- * visible by the PHP process.
34
- *
35
- * 'symlinks': (default = array()) If the CSS file is stored in
36
- * a symlink-ed directory, provide an array of link paths to
37
- * target paths, where the link paths are within the document root. Because
38
- * paths need to be normalized for this to work, use "//" to substitute
39
- * the doc root in the link paths (the array keys). E.g.:
40
- * <code>
41
- * array('//symlink' => '/real/target/path') // unix
42
- * array('//static' => 'D:\\staticStorage') // Windows
43
- * </code>
44
- *
45
- * 'docRoot': (default = $_SERVER['DOCUMENT_ROOT'])
46
- * see Minify_CSS_UriRewriter::rewrite
47
- *
48
- * @return string
49
- */
50
- public static function minify($css, $options = array())
51
- {
52
- $options = array_merge(array(
53
- 'compress' => true,
54
- 'removeCharsets' => true,
55
- 'currentDir' => null,
56
- 'docRoot' => $_SERVER['DOCUMENT_ROOT'],
57
- 'prependRelativePath' => null,
58
- 'symlinks' => array(),
59
- ), $options);
60
-
61
- if ($options['removeCharsets']) {
62
- $css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
63
- }
64
- if ($options['compress']) {
65
- $obj = new CSSmin();
66
- $css = $obj->run($css);
67
- }
68
- if (! $options['currentDir'] && ! $options['prependRelativePath']) {
69
- return $css;
70
- }
71
- if ($options['currentDir']) {
72
- return Minify_CSS_UriRewriter::rewrite(
73
- $css
74
- ,$options['currentDir']
75
- ,$options['docRoot']
76
- ,$options['symlinks']
77
- );
78
- } else {
79
- return Minify_CSS_UriRewriter::prepend(
80
- $css
81
- ,$options['prependRelativePath']
82
- );
83
- }
84
- }
85
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Cache/APC.php DELETED
@@ -1,133 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Cache_APC
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * APC-based cache class for Minify
9
- *
10
- * <code>
11
- * Minify::setCache(new Minify_Cache_APC());
12
- * </code>
13
- *
14
- * @package Minify
15
- * @author Chris Edwards
16
- **/
17
- class Minify_Cache_APC {
18
-
19
- /**
20
- * Create a Minify_Cache_APC object, to be passed to
21
- * Minify::setCache().
22
- *
23
- *
24
- * @param int $expire seconds until expiration (default = 0
25
- * meaning the item will not get an expiration date)
26
- *
27
- * @return null
28
- */
29
- public function __construct($expire = 0)
30
- {
31
- $this->_exp = $expire;
32
- }
33
-
34
- /**
35
- * Write data to cache.
36
- *
37
- * @param string $id cache id
38
- *
39
- * @param string $data
40
- *
41
- * @return bool success
42
- */
43
- public function store($id, $data)
44
- {
45
- return apc_store($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp);
46
- }
47
-
48
- /**
49
- * Get the size of a cache entry
50
- *
51
- * @param string $id cache id
52
- *
53
- * @return int size in bytes
54
- */
55
- public function getSize($id)
56
- {
57
- if (! $this->_fetch($id)) {
58
- return false;
59
- }
60
- return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
61
- ? mb_strlen($this->_data, '8bit')
62
- : strlen($this->_data);
63
- }
64
-
65
- /**
66
- * Does a valid cache entry exist?
67
- *
68
- * @param string $id cache id
69
- *
70
- * @param int $srcMtime mtime of the original source file(s)
71
- *
72
- * @return bool exists
73
- */
74
- public function isValid($id, $srcMtime)
75
- {
76
- return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
77
- }
78
-
79
- /**
80
- * Send the cached content to output
81
- *
82
- * @param string $id cache id
83
- */
84
- public function display($id)
85
- {
86
- echo $this->_fetch($id)
87
- ? $this->_data
88
- : '';
89
- }
90
-
91
- /**
92
- * Fetch the cached content
93
- *
94
- * @param string $id cache id
95
- *
96
- * @return string
97
- */
98
- public function fetch($id)
99
- {
100
- return $this->_fetch($id)
101
- ? $this->_data
102
- : '';
103
- }
104
-
105
- private $_exp = null;
106
-
107
- // cache of most recently fetched id
108
- private $_lm = null;
109
- private $_data = null;
110
- private $_id = null;
111
-
112
- /**
113
- * Fetch data and timestamp from apc, store in instance
114
- *
115
- * @param string $id
116
- *
117
- * @return bool success
118
- */
119
- private function _fetch($id)
120
- {
121
- if ($this->_id === $id) {
122
- return true;
123
- }
124
- $ret = apc_fetch($id);
125
- if (false === $ret) {
126
- $this->_id = null;
127
- return false;
128
- }
129
- list($this->_lm, $this->_data) = explode('|', $ret, 2);
130
- $this->_id = $id;
131
- return true;
132
- }
133
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Cache/File.php DELETED
@@ -1,197 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Cache_File
4
- * @package Minify
5
- */
6
-
7
- class Minify_Cache_File {
8
-
9
- public function __construct($path = '', $fileLocking = false)
10
- {
11
- if (! $path) {
12
- $path = self::tmp();
13
- }
14
- $this->_locking = $fileLocking;
15
- $this->_path = $path;
16
- }
17
-
18
- /**
19
- * Write data to cache.
20
- *
21
- * @param string $id cache id (e.g. a filename)
22
- *
23
- * @param string $data
24
- *
25
- * @return bool success
26
- */
27
- public function store($id, $data)
28
- {
29
- $flag = $this->_locking
30
- ? LOCK_EX
31
- : null;
32
- $file = $this->_path . '/' . $id;
33
- if (! @file_put_contents($file, $data, $flag)) {
34
- $this->_log("Minify_Cache_File: Write failed to '$file'");
35
- }
36
- // write control
37
- if ($data !== $this->fetch($id)) {
38
- @unlink($file);
39
- $this->_log("Minify_Cache_File: Post-write read failed for '$file'");
40
- return false;
41
- }
42
- return true;
43
- }
44
-
45
- /**
46
- * Get the size of a cache entry
47
- *
48
- * @param string $id cache id (e.g. a filename)
49
- *
50
- * @return int size in bytes
51
- */
52
- public function getSize($id)
53
- {
54
- return filesize($this->_path . '/' . $id);
55
- }
56
-
57
- /**
58
- * Does a valid cache entry exist?
59
- *
60
- * @param string $id cache id (e.g. a filename)
61
- *
62
- * @param int $srcMtime mtime of the original source file(s)
63
- *
64
- * @return bool exists
65
- */
66
- public function isValid($id, $srcMtime)
67
- {
68
- $file = $this->_path . '/' . $id;
69
- return (is_file($file) && (filemtime($file) >= $srcMtime));
70
- }
71
-
72
- /**
73
- * Send the cached content to output
74
- *
75
- * @param string $id cache id (e.g. a filename)
76
- */
77
- public function display($id)
78
- {
79
- if ($this->_locking) {
80
- $fp = fopen($this->_path . '/' . $id, 'rb');
81
- flock($fp, LOCK_SH);
82
- fpassthru($fp);
83
- flock($fp, LOCK_UN);
84
- fclose($fp);
85
- } else {
86
- readfile($this->_path . '/' . $id);
87
- }
88
- }
89
-
90
- /**
91
- * Fetch the cached content
92
- *
93
- * @param string $id cache id (e.g. a filename)
94
- *
95
- * @return string
96
- */
97
- public function fetch($id)
98
- {
99
- if ($this->_locking) {
100
- $fp = fopen($this->_path . '/' . $id, 'rb');
101
- if (!$fp) {
102
- return false;
103
- }
104
- flock($fp, LOCK_SH);
105
- $ret = stream_get_contents($fp);
106
- flock($fp, LOCK_UN);
107
- fclose($fp);
108
- return $ret;
109
- } else {
110
- return file_get_contents($this->_path . '/' . $id);
111
- }
112
- }
113
-
114
- /**
115
- * Fetch the cache path used
116
- *
117
- * @return string
118
- */
119
- public function getPath()
120
- {
121
- return $this->_path;
122
- }
123
-
124
- /**
125
- * Get a usable temp directory
126
- *
127
- * Adapted from Solar/Dir.php
128
- * @author Paul M. Jones <pmjones@solarphp.com>
129
- * @license http://opensource.org/licenses/bsd-license.php BSD
130
- * @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php
131
- *
132
- * @return string
133
- */
134
- public static function tmp()
135
- {
136
- static $tmp = null;
137
- if (! $tmp) {
138
- $tmp = function_exists('sys_get_temp_dir')
139
- ? sys_get_temp_dir()
140
- : self::_tmp();
141
- $tmp = rtrim($tmp, DIRECTORY_SEPARATOR);
142
- }
143
- return $tmp;
144
- }
145
-
146
- /**
147
- * Returns the OS-specific directory for temporary files
148
- *
149
- * @author Paul M. Jones <pmjones@solarphp.com>
150
- * @license http://opensource.org/licenses/bsd-license.php BSD
151
- * @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php
152
- *
153
- * @return string
154
- */
155
- protected static function _tmp()
156
- {
157
- // non-Windows system?
158
- if (strtolower(substr(PHP_OS, 0, 3)) != 'win') {
159
- $tmp = empty($_ENV['TMPDIR']) ? getenv('TMPDIR') : $_ENV['TMPDIR'];
160
- if ($tmp) {
161
- return $tmp;
162
- } else {
163
- return '/tmp';
164
- }
165
- }
166
- // Windows 'TEMP'
167
- $tmp = empty($_ENV['TEMP']) ? getenv('TEMP') : $_ENV['TEMP'];
168
- if ($tmp) {
169
- return $tmp;
170
- }
171
- // Windows 'TMP'
172
- $tmp = empty($_ENV['TMP']) ? getenv('TMP') : $_ENV['TMP'];
173
- if ($tmp) {
174
- return $tmp;
175
- }
176
- // Windows 'windir'
177
- $tmp = empty($_ENV['windir']) ? getenv('windir') : $_ENV['windir'];
178
- if ($tmp) {
179
- return $tmp;
180
- }
181
- // final fallback for Windows
182
- return getenv('SystemRoot') . '\\temp';
183
- }
184
-
185
- /**
186
- * Send message to the Minify logger
187
- * @param string $msg
188
- * @return null
189
- */
190
- protected function _log($msg)
191
- {
192
- Minify_Logger::log($msg);
193
- }
194
-
195
- private $_path = null;
196
- private $_locking = null;
197
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Cache/Memcache.php DELETED
@@ -1,140 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Cache_Memcache
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Memcache-based cache class for Minify
9
- *
10
- * <code>
11
- * // fall back to disk caching if memcache can't connect
12
- * $memcache = new Memcache;
13
- * if ($memcache->connect('localhost', 11211)) {
14
- * Minify::setCache(new Minify_Cache_Memcache($memcache));
15
- * } else {
16
- * Minify::setCache();
17
- * }
18
- * </code>
19
- **/
20
- class Minify_Cache_Memcache {
21
-
22
- /**
23
- * Create a Minify_Cache_Memcache object, to be passed to
24
- * Minify::setCache().
25
- *
26
- * @param Memcache $memcache already-connected instance
27
- *
28
- * @param int $expire seconds until expiration (default = 0
29
- * meaning the item will not get an expiration date)
30
- *
31
- * @return null
32
- */
33
- public function __construct($memcache, $expire = 0)
34
- {
35
- $this->_mc = $memcache;
36
- $this->_exp = $expire;
37
- }
38
-
39
- /**
40
- * Write data to cache.
41
- *
42
- * @param string $id cache id
43
- *
44
- * @param string $data
45
- *
46
- * @return bool success
47
- */
48
- public function store($id, $data)
49
- {
50
- return $this->_mc->set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", 0, $this->_exp);
51
- }
52
-
53
-
54
- /**
55
- * Get the size of a cache entry
56
- *
57
- * @param string $id cache id
58
- *
59
- * @return int size in bytes
60
- */
61
- public function getSize($id)
62
- {
63
- if (! $this->_fetch($id)) {
64
- return false;
65
- }
66
- return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
67
- ? mb_strlen($this->_data, '8bit')
68
- : strlen($this->_data);
69
- }
70
-
71
- /**
72
- * Does a valid cache entry exist?
73
- *
74
- * @param string $id cache id
75
- *
76
- * @param int $srcMtime mtime of the original source file(s)
77
- *
78
- * @return bool exists
79
- */
80
- public function isValid($id, $srcMtime)
81
- {
82
- return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
83
- }
84
-
85
- /**
86
- * Send the cached content to output
87
- *
88
- * @param string $id cache id
89
- */
90
- public function display($id)
91
- {
92
- echo $this->_fetch($id)
93
- ? $this->_data
94
- : '';
95
- }
96
-
97
- /**
98
- * Fetch the cached content
99
- *
100
- * @param string $id cache id
101
- *
102
- * @return string
103
- */
104
- public function fetch($id)
105
- {
106
- return $this->_fetch($id)
107
- ? $this->_data
108
- : '';
109
- }
110
-
111
- private $_mc = null;
112
- private $_exp = null;
113
-
114
- // cache of most recently fetched id
115
- private $_lm = null;
116
- private $_data = null;
117
- private $_id = null;
118
-
119
- /**
120
- * Fetch data and timestamp from memcache, store in instance
121
- *
122
- * @param string $id
123
- *
124
- * @return bool success
125
- */
126
- private function _fetch($id)
127
- {
128
- if ($this->_id === $id) {
129
- return true;
130
- }
131
- $ret = $this->_mc->get($id);
132
- if (false === $ret) {
133
- $this->_id = null;
134
- return false;
135
- }
136
- list($this->_lm, $this->_data) = explode('|', $ret, 2);
137
- $this->_id = $id;
138
- return true;
139
- }
140
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Cache/WinCache.php DELETED
@@ -1,130 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Cache_Wincache
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * WinCache-based cache class for Minify
9
- *
10
- * <code>
11
- * Minify::setCache(new Minify_Cache_WinCache());
12
- * </code>
13
- *
14
- * @package Minify
15
- * @author Matthias Fax
16
- **/
17
- class Minify_Cache_WinCache
18
- {
19
-
20
- /**
21
- * Create a Minify_Cache_Wincache object, to be passed to
22
- * Minify::setCache().
23
- *
24
- *
25
- * @param int $expire seconds until expiration (default = 0
26
- * meaning the item will not get an expiration date)
27
- */
28
- public function __construct($expire = 0)
29
- {
30
- if (!function_exists('wincache_ucache_info')) {
31
- throw new Exception("WinCache for PHP is not installed to be able to use Minify_Cache_WinCache!");
32
- }
33
- $this->_exp = $expire;
34
- }
35
-
36
- /**
37
- * Write data to cache.
38
- *
39
- * @param string $id cache id
40
- *
41
- * @param string $data
42
- *
43
- * @return bool success
44
- */
45
- public function store($id, $data)
46
- {
47
- return wincache_ucache_set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp);
48
- }
49
-
50
- /**
51
- * Get the size of a cache entry
52
- *
53
- * @param string $id cache id
54
- *
55
- * @return int size in bytes
56
- */
57
- public function getSize($id)
58
- {
59
- if (!$this->_fetch($id)) {
60
- return false;
61
- }
62
- return (function_exists('mb_strlen') && ((int) ini_get('mbstring.func_overload') & 2)) ? mb_strlen($this->_data, '8bit') : strlen($this->_data);
63
- }
64
-
65
- /**
66
- * Does a valid cache entry exist?
67
- *
68
- * @param string $id cache id
69
- *
70
- * @param int $srcMtime mtime of the original source file(s)
71
- *
72
- * @return bool exists
73
- */
74
- public function isValid($id, $srcMtime)
75
- {
76
- return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
77
- }
78
-
79
- /**
80
- * Send the cached content to output
81
- *
82
- * @param string $id cache id
83
- */
84
- public function display($id)
85
- {
86
- echo $this->_fetch($id) ? $this->_data : '';
87
- }
88
-
89
- /**
90
- * Fetch the cached content
91
- *
92
- * @param string $id cache id
93
- *
94
- * @return string
95
- */
96
- public function fetch($id)
97
- {
98
- return $this->_fetch($id) ? $this->_data : '';
99
- }
100
-
101
- private $_exp = NULL;
102
-
103
- // cache of most recently fetched id
104
- private $_lm = NULL;
105
- private $_data = NULL;
106
- private $_id = NULL;
107
-
108
- /**
109
- * Fetch data and timestamp from WinCache, store in instance
110
- *
111
- * @param string $id
112
- *
113
- * @return bool success
114
- */
115
- private function _fetch($id)
116
- {
117
- if ($this->_id === $id) {
118
- return true;
119
- }
120
- $suc = false;
121
- $ret = wincache_ucache_get($id, $suc);
122
- if (!$suc) {
123
- $this->_id = NULL;
124
- return false;
125
- }
126
- list($this->_lm, $this->_data) = explode('|', $ret, 2);
127
- $this->_id = $id;
128
- return true;
129
- }
130
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Cache/XCache.php DELETED
@@ -1,126 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Cache_XCache
4
- *
5
- * @link http://xcache.lighttpd.net/
6
- * @package Minify
7
- */
8
-
9
- /**
10
- * XCache-based cache class for Minify
11
- * {@see http://xcache.lighttpd.net/wiki/XcacheApi XCache API}
12
- *
13
- * <code>
14
- * Minify::setCache(new Minify_Cache_XCache());
15
- * </code>
16
- *
17
- * @package Minify
18
- * @author Elan Ruusamäe <glen@delfi.ee>
19
- **/
20
- class Minify_Cache_XCache {
21
-
22
- /**
23
- * Create a Minify_Cache_XCache object, to be passed to
24
- * Minify::setCache().
25
- *
26
- * @param int $expire seconds until expiration (default = 0
27
- * meaning the item will not get an expiration date)
28
- */
29
- public function __construct($expire = 0)
30
- {
31
- $this->_exp = $expire;
32
- }
33
-
34
- /**
35
- * Write data to cache.
36
- *
37
- * @param string $id cache id
38
- * @param string $data
39
- * @return bool success
40
- */
41
- public function store($id, $data)
42
- {
43
- return xcache_set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp);
44
- }
45
-
46
- /**
47
- * Get the size of a cache entry
48
- *
49
- * @param string $id cache id
50
- * @return int size in bytes
51
- */
52
- public function getSize($id)
53
- {
54
- if (! $this->_fetch($id)) {
55
- return false;
56
- }
57
- return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
58
- ? mb_strlen($this->_data, '8bit')
59
- : strlen($this->_data);
60
- }
61
-
62
- /**
63
- * Does a valid cache entry exist?
64
- *
65
- * @param string $id cache id
66
- * @param int $srcMtime mtime of the original source file(s)
67
- * @return bool exists
68
- */
69
- public function isValid($id, $srcMtime)
70
- {
71
- return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
72
- }
73
-
74
- /**
75
- * Send the cached content to output
76
- *
77
- * @param string $id cache id
78
- */
79
- public function display($id)
80
- {
81
- echo $this->_fetch($id)
82
- ? $this->_data
83
- : '';
84
- }
85
-
86
- /**
87
- * Fetch the cached content
88
- *
89
- * @param string $id cache id
90
- * @return string
91
- */
92
- public function fetch($id)
93
- {
94
- return $this->_fetch($id)
95
- ? $this->_data
96
- : '';
97
- }
98
-
99
- private $_exp = null;
100
-
101
- // cache of most recently fetched id
102
- private $_lm = null;
103
- private $_data = null;
104
- private $_id = null;
105
-
106
- /**
107
- * Fetch data and timestamp from xcache, store in instance
108
- *
109
- * @param string $id
110
- * @return bool success
111
- */
112
- private function _fetch($id)
113
- {
114
- if ($this->_id === $id) {
115
- return true;
116
- }
117
- $ret = xcache_get($id);
118
- if (false === $ret) {
119
- $this->_id = null;
120
- return false;
121
- }
122
- list($this->_lm, $this->_data) = explode('|', $ret, 2);
123
- $this->_id = $id;
124
- return true;
125
- }
126
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Cache/ZendPlatform.php DELETED
@@ -1,142 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Cache_ZendPlatform
4
- * @package Minify
5
- */
6
-
7
-
8
- /**
9
- * ZendPlatform-based cache class for Minify
10
- *
11
- * Based on Minify_Cache_APC, uses output_cache_get/put (currently deprecated)
12
- *
13
- * <code>
14
- * Minify::setCache(new Minify_Cache_ZendPlatform());
15
- * </code>
16
- *
17
- * @package Minify
18
- * @author Patrick van Dissel
19
- */
20
- class Minify_Cache_ZendPlatform {
21
-
22
-
23
- /**
24
- * Create a Minify_Cache_ZendPlatform object, to be passed to
25
- * Minify::setCache().
26
- *
27
- * @param int $expire seconds until expiration (default = 0
28
- * meaning the item will not get an expiration date)
29
- *
30
- * @return null
31
- */
32
- public function __construct($expire = 0)
33
- {
34
- $this->_exp = $expire;
35
- }
36
-
37
-
38
- /**
39
- * Write data to cache.
40
- *
41
- * @param string $id cache id
42
- *
43
- * @param string $data
44
- *
45
- * @return bool success
46
- */
47
- public function store($id, $data)
48
- {
49
- return output_cache_put($id, "{$_SERVER['REQUEST_TIME']}|{$data}");
50
- }
51
-
52
-
53
- /**
54
- * Get the size of a cache entry
55
- *
56
- * @param string $id cache id
57
- *
58
- * @return int size in bytes
59
- */
60
- public function getSize($id)
61
- {
62
- return $this->_fetch($id)
63
- ? strlen($this->_data)
64
- : false;
65
- }
66
-
67
-
68
- /**
69
- * Does a valid cache entry exist?
70
- *
71
- * @param string $id cache id
72
- *
73
- * @param int $srcMtime mtime of the original source file(s)
74
- *
75
- * @return bool exists
76
- */
77
- public function isValid($id, $srcMtime)
78
- {
79
- $ret = ($this->_fetch($id) && ($this->_lm >= $srcMtime));
80
- return $ret;
81
- }
82
-
83
-
84
- /**
85
- * Send the cached content to output
86
- *
87
- * @param string $id cache id
88
- */
89
- public function display($id)
90
- {
91
- echo $this->_fetch($id)
92
- ? $this->_data
93
- : '';
94
- }
95
-
96
-
97
- /**
98
- * Fetch the cached content
99
- *
100
- * @param string $id cache id
101
- *
102
- * @return string
103
- */
104
- public function fetch($id)
105
- {
106
- return $this->_fetch($id)
107
- ? $this->_data
108
- : '';
109
- }
110
-
111
-
112
- private $_exp = null;
113
-
114
-
115
- // cache of most recently fetched id
116
- private $_lm = null;
117
- private $_data = null;
118
- private $_id = null;
119
-
120
-
121
- /**
122
- * Fetch data and timestamp from ZendPlatform, store in instance
123
- *
124
- * @param string $id
125
- *
126
- * @return bool success
127
- */
128
- private function _fetch($id)
129
- {
130
- if ($this->_id === $id) {
131
- return true;
132
- }
133
- $ret = output_cache_get($id, $this->_exp);
134
- if (false === $ret) {
135
- $this->_id = null;
136
- return false;
137
- }
138
- list($this->_lm, $this->_data) = explode('|', $ret, 2);
139
- $this->_id = $id;
140
- return true;
141
- }
142
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/ClosureCompiler.php DELETED
@@ -1,139 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_ClosureCompiler
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Compress Javascript using the Closure Compiler
9
- *
10
- * You must set $jarFile and $tempDir before calling the minify functions.
11
- * Also, depending on your shell's environment, you may need to specify
12
- * the full path to java in $javaExecutable or use putenv() to setup the
13
- * Java environment.
14
- *
15
- * <code>
16
- * Minify_ClosureCompiler::$jarFile = '/path/to/closure-compiler-20120123.jar';
17
- * Minify_ClosureCompiler::$tempDir = '/tmp';
18
- * $code = Minify_ClosureCompiler::minify(
19
- * $code,
20
- * array('compilation_level' => 'SIMPLE_OPTIMIZATIONS')
21
- * );
22
- *
23
- * --compilation_level WHITESPACE_ONLY, SIMPLE_OPTIMIZATIONS, ADVANCED_OPTIMIZATIONS
24
- *
25
- * </code>
26
- *
27
- * @todo unit tests, $options docs
28
- * @todo more options support (or should just passthru them all?)
29
- *
30
- * @package Minify
31
- * @author Stephen Clay <steve@mrclay.org>
32
- * @author Elan Ruusamäe <glen@delfi.ee>
33
- */
34
- class Minify_ClosureCompiler {
35
-
36
- const OPTION_CHARSET = 'charset';
37
- const OPTION_COMPILATION_LEVEL = 'compilation_level';
38
-
39
- public static $isDebug = false;
40
-
41
- /**
42
- * Filepath of the Closure Compiler jar file. This must be set before
43
- * calling minifyJs().
44
- *
45
- * @var string
46
- */
47
- public static $jarFile = null;
48
-
49
- /**
50
- * Writable temp directory. This must be set before calling minifyJs().
51
- *
52
- * @var string
53
- */
54
- public static $tempDir = null;
55
-
56
- /**
57
- * Filepath of "java" executable (may be needed if not in shell's PATH)
58
- *
59
- * @var string
60
- */
61
- public static $javaExecutable = 'java';
62
-
63
- /**
64
- * Minify a Javascript string
65
- *
66
- * @param string $js
67
- *
68
- * @param array $options (verbose is ignored)
69
- *
70
- * @see https://code.google.com/p/closure-compiler/source/browse/trunk/README
71
- *
72
- * @return string
73
- *
74
- * @throws Minify_ClosureCompiler_Exception
75
- */
76
- public static function minify($js, $options = array())
77
- {
78
- self::_prepare();
79
- if (! ($tmpFile = tempnam(self::$tempDir, 'cc_'))) {
80
- throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : could not create temp file in "'.self::$tempDir.'".');
81
- }
82
- file_put_contents($tmpFile, $js);
83
- $cmd = self::_getCmd($options, $tmpFile);
84
- exec($cmd, $output, $result_code);
85
- unlink($tmpFile);
86
- if ($result_code != 0) {
87
- $message = 'Minify_ClosureCompiler : Closure Compiler execution failed.';
88
- if (self::$isDebug) {
89
- exec($cmd . ' 2>&1', $error);
90
- if ($error) {
91
- $message .= "\nReason:\n" . join("\n", $error);
92
- }
93
- }
94
- throw new Minify_ClosureCompiler_Exception($message);
95
- }
96
- return implode("\n", $output);
97
- }
98
-
99
- private static function _getCmd($userOptions, $tmpFile)
100
- {
101
- $o = array_merge(
102
- array(
103
- self::OPTION_CHARSET => 'utf-8',
104
- self::OPTION_COMPILATION_LEVEL => 'SIMPLE_OPTIMIZATIONS',
105
- ),
106
- $userOptions
107
- );
108
- $charsetOption = $o[self::OPTION_CHARSET];
109
- $cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile)
110
- . (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $charsetOption)
111
- ? " --charset {$charsetOption}"
112
- : '');
113
-
114
- foreach (array(self::OPTION_COMPILATION_LEVEL) as $opt) {
115
- if ($o[$opt]) {
116
- $cmd .= " --{$opt} ". escapeshellarg($o[$opt]);
117
- }
118
- }
119
- return $cmd . ' ' . escapeshellarg($tmpFile);
120
- }
121
-
122
- private static function _prepare()
123
- {
124
- if (! is_file(self::$jarFile)) {
125
- throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : $jarFile('.self::$jarFile.') is not a valid file.');
126
- }
127
- if (! is_readable(self::$jarFile)) {
128
- throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : $jarFile('.self::$jarFile.') is not readable.');
129
- }
130
- if (! is_dir(self::$tempDir)) {
131
- throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : $tempDir('.self::$tempDir.') is not a valid direcotry.');
132
- }
133
- if (! is_writable(self::$tempDir)) {
134
- throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : $tempDir('.self::$tempDir.') is not writable.');
135
- }
136
- }
137
- }
138
-
139
- class Minify_ClosureCompiler_Exception extends Exception {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/CommentPreserver.php DELETED
@@ -1,89 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_CommentPreserver
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Process a string in pieces preserving C-style comments that begin with "/*!"
9
- *
10
- * @package Minify
11
- * @author Stephen Clay <steve@mrclay.org>
12
- */
13
- class Minify_CommentPreserver {
14
-
15
- /**
16
- * String to be prepended to each preserved comment
17
- *
18
- * @var string
19
- */
20
- public static $prepend = "\n";
21
-
22
- /**
23
- * String to be appended to each preserved comment
24
- *
25
- * @var string
26
- */
27
- public static $append = "\n";
28
-
29
- /**
30
- * Process a string outside of C-style comments that begin with "/*!"
31
- *
32
- * On each non-empty string outside these comments, the given processor
33
- * function will be called. The comments will be surrounded by
34
- * Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append.
35
- *
36
- * @param string $content
37
- * @param callback $processor function
38
- * @param array $args array of extra arguments to pass to the processor
39
- * function (default = array())
40
- * @return string
41
- */
42
- public static function process($content, $processor, $args = array())
43
- {
44
- $ret = '';
45
- while (true) {
46
- list($beforeComment, $comment, $afterComment) = self::_nextComment($content);
47
- if ('' !== $beforeComment) {
48
- $callArgs = $args;
49
- array_unshift($callArgs, $beforeComment);
50
- $ret .= call_user_func_array($processor, $callArgs);
51
- }
52
- if (false === $comment) {
53
- break;
54
- }
55
- $ret .= $comment;
56
- $content = $afterComment;
57
- }
58
- return $ret;
59
- }
60
-
61
- /**
62
- * Extract comments that YUI Compressor preserves.
63
- *
64
- * @param string $in input
65
- *
66
- * @return array 3 elements are returned. If a YUI comment is found, the
67
- * 2nd element is the comment and the 1st and 3rd are the surrounding
68
- * strings. If no comment is found, the entire string is returned as the
69
- * 1st element and the other two are false.
70
- */
71
- private static function _nextComment($in)
72
- {
73
- if (
74
- false === ($start = strpos($in, '/*!'))
75
- || false === ($end = strpos($in, '*/', $start + 3))
76
- ) {
77
- return array($in, false, false);
78
- }
79
- $ret = array(
80
- substr($in, 0, $start)
81
- ,self::$prepend . '/*!' . substr($in, $start + 3, $end - $start - 1) . self::$append
82
- );
83
- $endChars = (strlen($in) - $end - 2);
84
- $ret[] = (0 === $endChars)
85
- ? ''
86
- : substr($in, -$endChars);
87
- return $ret;
88
- }
89
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Controller/Base.php DELETED
@@ -1,222 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Controller_Base
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Base class for Minify controller
9
- *
10
- * The controller class validates a request and uses it to create sources
11
- * for minification and set options like contentType. It's also responsible
12
- * for loading minifier code upon request.
13
- *
14
- * @package Minify
15
- * @author Stephen Clay <steve@mrclay.org>
16
- */
17
- abstract class Minify_Controller_Base {
18
-
19
- /**
20
- * Setup controller sources and set an needed options for Minify::source
21
- *
22
- * You must override this method in your subclass controller to set
23
- * $this->sources. If the request is NOT valid, make sure $this->sources
24
- * is left an empty array. Then strip any controller-specific options from
25
- * $options and return it. To serve files, $this->sources must be an array of
26
- * Minify_Source objects.
27
- *
28
- * @param array $options controller and Minify options
29
- *
30
- * @return array $options Minify::serve options
31
- */
32
- abstract public function setupSources($options);
33
-
34
- /**
35
- * Get default Minify options for this controller.
36
- *
37
- * Override in subclass to change defaults
38
- *
39
- * @return array options for Minify
40
- */
41
- public function getDefaultMinifyOptions() {
42
- return array(
43
- 'isPublic' => true
44
- ,'encodeOutput' => function_exists('gzdeflate')
45
- ,'encodeMethod' => null // determine later
46
- ,'encodeLevel' => 9
47
- ,'minifierOptions' => array() // no minifier options
48
- ,'contentTypeCharset' => 'utf-8'
49
- ,'maxAge' => 1800 // 30 minutes
50
- ,'rewriteCssUris' => true
51
- ,'bubbleCssImports' => false
52
- ,'quiet' => false // serve() will send headers and output
53
- ,'debug' => false
54
-
55
- // if you override these, the response codes MUST be directly after
56
- // the first space.
57
- ,'badRequestHeader' => 'HTTP/1.0 400 Bad Request'
58
- ,'errorHeader' => 'HTTP/1.0 500 Internal Server Error'
59
-
60
- // callback function to see/modify content of all sources
61
- ,'postprocessor' => null
62
- // file to require to load preprocessor
63
- ,'postprocessorRequire' => null
64
- );
65
- }
66
-
67
- /**
68
- * Get default minifiers for this controller.
69
- *
70
- * Override in subclass to change defaults
71
- *
72
- * @return array minifier callbacks for common types
73
- */
74
- public function getDefaultMinifers() {
75
- $ret[Minify::TYPE_JS] = array('JSMin', 'minify');
76
- $ret[Minify::TYPE_CSS] = array('Minify_CSS', 'minify');
77
- $ret[Minify::TYPE_HTML] = array('Minify_HTML', 'minify');
78
- return $ret;
79
- }
80
-
81
- /**
82
- * Is a user-given file within an allowable directory, existing,
83
- * and having an extension js/css/html/txt ?
84
- *
85
- * This is a convenience function for controllers that have to accept
86
- * user-given paths
87
- *
88
- * @param string $file full file path (already processed by realpath())
89
- *
90
- * @param array $safeDirs directories where files are safe to serve. Files can also
91
- * be in subdirectories of these directories.
92
- *
93
- * @return bool file is safe
94
- *
95
- * @deprecated use checkAllowDirs, checkNotHidden instead
96
- */
97
- public static function _fileIsSafe($file, $safeDirs)
98
- {
99
- $pathOk = false;
100
- foreach ((array)$safeDirs as $safeDir) {
101
- if (strpos($file, $safeDir) === 0) {
102
- $pathOk = true;
103
- break;
104
- }
105
- }
106
- $base = basename($file);
107
- if (! $pathOk || ! is_file($file) || $base[0] === '.') {
108
- return false;
109
- }
110
- list($revExt) = explode('.', strrev($base));
111
- return in_array(strrev($revExt), array('js', 'css', 'html', 'txt'));
112
- }
113
-
114
- /**
115
- * @param string $file
116
- * @param array $allowDirs
117
- * @param string $uri
118
- * @return bool
119
- * @throws Exception
120
- */
121
- public static function checkAllowDirs($file, $allowDirs, $uri)
122
- {
123
- foreach ((array)$allowDirs as $allowDir) {
124
- if (strpos($file, $allowDir) === 0) {
125
- return true;
126
- }
127
- }
128
- throw new Exception("File '$file' is outside \$allowDirs. If the path is"
129
- . " resolved via an alias/symlink, look into the \$min_symlinks option."
130
- . " E.g. \$min_symlinks['/" . dirname($uri) . "'] = '" . dirname($file) . "';");
131
- }
132
-
133
- /**
134
- * @param string $file
135
- * @throws Exception
136
- */
137
- public static function checkNotHidden($file)
138
- {
139
- $b = basename($file);
140
- if (0 === strpos($b, '.')) {
141
- throw new Exception("Filename '$b' starts with period (may be hidden)");
142
- }
143
- }
144
-
145
- /**
146
- * instances of Minify_Source, which provide content and any individual minification needs.
147
- *
148
- * @var array
149
- *
150
- * @see Minify_Source
151
- */
152
- public $sources = array();
153
-
154
- /**
155
- * Short name to place inside cache id
156
- *
157
- * The setupSources() method may choose to set this, making it easier to
158
- * recognize a particular set of sources/settings in the cache folder. It
159
- * will be filtered and truncated to make the final cache id <= 250 bytes.
160
- *
161
- * @var string
162
- */
163
- public $selectionId = '';
164
-
165
- /**
166
- * Mix in default controller options with user-given options
167
- *
168
- * @param array $options user options
169
- *
170
- * @return array mixed options
171
- */
172
- public final function mixInDefaultOptions($options)
173
- {
174
- $ret = array_merge(
175
- $this->getDefaultMinifyOptions(), $options
176
- );
177
- if (! isset($options['minifiers'])) {
178
- $options['minifiers'] = array();
179
- }
180
- $ret['minifiers'] = array_merge(
181
- $this->getDefaultMinifers(), $options['minifiers']
182
- );
183
- return $ret;
184
- }
185
-
186
- /**
187
- * Analyze sources (if there are any) and set $options 'contentType'
188
- * and 'lastModifiedTime' if they already aren't.
189
- *
190
- * @param array $options options for Minify
191
- *
192
- * @return array options for Minify
193
- */
194
- public final function analyzeSources($options = array())
195
- {
196
- if ($this->sources) {
197
- if (! isset($options['contentType'])) {
198
- $options['contentType'] = Minify_Source::getContentType($this->sources);
199
- }
200
- // last modified is needed for caching, even if setExpires is set
201
- if (! isset($options['lastModifiedTime'])) {
202
- $max = 0;
203
- foreach ($this->sources as $source) {
204
- $max = max($source->lastModified, $max);
205
- }
206
- $options['lastModifiedTime'] = $max;
207
- }
208
- }
209
- return $options;
210
- }
211
-
212
- /**
213
- * Send message to the Minify logger
214
- *
215
- * @param string $msg
216
- *
217
- * @return null
218
- */
219
- public function log($msg) {
220
- Minify_Logger::log($msg);
221
- }
222
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Controller/Files.php DELETED
@@ -1,76 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Controller_Files
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Controller class for minifying a set of files
9
- *
10
- * E.g. the following would serve the minified Javascript for a site
11
- * <code>
12
- * Minify::serve('Files', array(
13
- * 'files' => array(
14
- * '//js/jquery.js'
15
- * ,'//js/plugins.js'
16
- * ,'/home/username/file.js'
17
- * )
18
- * ));
19
- * </code>
20
- *
21
- * As a shortcut, the controller will replace "//" at the beginning
22
- * of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'.
23
- *
24
- * @package Minify
25
- * @author Stephen Clay <steve@mrclay.org>
26
- */
27
- class Minify_Controller_Files extends Minify_Controller_Base {
28
-
29
- /**
30
- * Set up file sources
31
- *
32
- * @param array $options controller and Minify options
33
- * @return array Minify options
34
- *
35
- * Controller options:
36
- *
37
- * 'files': (required) array of complete file paths, or a single path
38
- */
39
- public function setupSources($options) {
40
- // strip controller options
41
-
42
- $files = $options['files'];
43
- // if $files is a single object, casting will break it
44
- if (is_object($files)) {
45
- $files = array($files);
46
- } elseif (! is_array($files)) {
47
- $files = (array)$files;
48
- }
49
- unset($options['files']);
50
-
51
- $sources = array();
52
- foreach ($files as $file) {
53
- if ($file instanceof Minify_Source) {
54
- $sources[] = $file;
55
- continue;
56
- }
57
- if (0 === strpos($file, '//')) {
58
- $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
59
- }
60
- $realPath = realpath($file);
61
- if (is_file($realPath)) {
62
- $sources[] = new Minify_Source(array(
63
- 'filepath' => $realPath
64
- ));
65
- } else {
66
- $this->log("The path \"{$file}\" could not be found (or was not a file)");
67
- return $options;
68
- }
69
- }
70
- if ($sources) {
71
- $this->sources = $sources;
72
- }
73
- return $options;
74
- }
75
- }
76
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Controller/Groups.php DELETED
@@ -1,91 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Controller_Groups
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Controller class for serving predetermined groups of minimized sets, selected
9
- * by PATH_INFO
10
- *
11
- * <code>
12
- * Minify::serve('Groups', array(
13
- * 'groups' => array(
14
- * 'css' => array('//css/type.css', '//css/layout.css')
15
- * ,'js' => array('//js/jquery.js', '//js/site.js')
16
- * )
17
- * ));
18
- * </code>
19
- *
20
- * If the above code were placed in /serve.php, it would enable the URLs
21
- * /serve.php/js and /serve.php/css
22
- *
23
- * As a shortcut, the controller will replace "//" at the beginning
24
- * of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'.
25
- *
26
- * @package Minify
27
- * @author Stephen Clay <steve@mrclay.org>
28
- */
29
- class Minify_Controller_Groups extends Minify_Controller_Base {
30
-
31
- /**
32
- * Set up groups of files as sources
33
- *
34
- * @param array $options controller and Minify options
35
- *
36
- * 'groups': (required) array mapping PATH_INFO strings to arrays
37
- * of complete file paths. @see Minify_Controller_Groups
38
- *
39
- * @return array Minify options
40
- */
41
- public function setupSources($options) {
42
- // strip controller options
43
- $groups = $options['groups'];
44
- unset($options['groups']);
45
-
46
- // mod_fcgid places PATH_INFO in ORIG_PATH_INFO
47
- $pi = isset($_SERVER['ORIG_PATH_INFO'])
48
- ? substr($_SERVER['ORIG_PATH_INFO'], 1)
49
- : (isset($_SERVER['PATH_INFO'])
50
- ? substr($_SERVER['PATH_INFO'], 1)
51
- : false
52
- );
53
- if (false === $pi || ! isset($groups[$pi])) {
54
- // no PATH_INFO or not a valid group
55
- $this->log("Missing PATH_INFO or no group set for \"$pi\"");
56
- return $options;
57
- }
58
- $sources = array();
59
-
60
- $files = $groups[$pi];
61
- // if $files is a single object, casting will break it
62
- if (is_object($files)) {
63
- $files = array($files);
64
- } elseif (! is_array($files)) {
65
- $files = (array)$files;
66
- }
67
- foreach ($files as $file) {
68
- if ($file instanceof Minify_Source) {
69
- $sources[] = $file;
70
- continue;
71
- }
72
- if (0 === strpos($file, '//')) {
73
- $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
74
- }
75
- $realPath = realpath($file);
76
- if (is_file($realPath)) {
77
- $sources[] = new Minify_Source(array(
78
- 'filepath' => $realPath
79
- ));
80
- } else {
81
- $this->log("The path \"{$file}\" could not be found (or was not a file)");
82
- return $options;
83
- }
84
- }
85
- if ($sources) {
86
- $this->sources = $sources;
87
- }
88
- return $options;
89
- }
90
- }
91
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Controller/MinApp.php DELETED
@@ -1,237 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Controller_MinApp
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Controller class for requests to /min/index.php
9
- *
10
- * @package Minify
11
- * @author Stephen Clay <steve@mrclay.org>
12
- */
13
- class Minify_Controller_MinApp extends Minify_Controller_Base {
14
-
15
- /**
16
- * Set up groups of files as sources
17
- *
18
- * @param array $options controller and Minify options
19
- *
20
- * @return array Minify options
21
- */
22
- public function setupSources($options) {
23
- // PHP insecure by default: realpath() and other FS functions can't handle null bytes.
24
- foreach (array('g', 'b', 'f') as $key) {
25
- if (isset($_GET[$key])) {
26
- $_GET[$key] = str_replace("\x00", '', (string)$_GET[$key]);
27
- }
28
- }
29
-
30
- // filter controller options
31
- $cOptions = array_merge(
32
- array(
33
- 'allowDirs' => '//'
34
- ,'groupsOnly' => false
35
- ,'groups' => array()
36
- ,'noMinPattern' => '@[-\\.]min\\.(?:js|css)$@i' // matched against basename
37
- )
38
- ,(isset($options['minApp']) ? $options['minApp'] : array())
39
- );
40
- unset($options['minApp']);
41
- $sources = array();
42
- $this->selectionId = '';
43
- $firstMissingResource = null;
44
- if (isset($_GET['g'])) {
45
- // add group(s)
46
- $this->selectionId .= 'g=' . $_GET['g'];
47
- $keys = explode(',', $_GET['g']);
48
- if ($keys != array_unique($keys)) {
49
- $this->log("Duplicate group key found.");
50
- return $options;
51
- }
52
- foreach ($keys as $key) {
53
- if (! isset($cOptions['groups'][$key])) {
54
- $this->log("A group configuration for \"{$key}\" was not found");
55
- return $options;
56
- }
57
- $files = $cOptions['groups'][$key];
58
- // if $files is a single object, casting will break it
59
- if (is_object($files)) {
60
- $files = array($files);
61
- } elseif (! is_array($files)) {
62
- $files = (array)$files;
63
- }
64
- foreach ($files as $file) {
65
- if ($file instanceof Minify_Source) {
66
- $sources[] = $file;
67
- continue;
68
- }
69
- if (0 === strpos($file, '//')) {
70
- $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1);
71
- }
72
- $realpath = realpath($file);
73
- if ($realpath && is_file($realpath)) {
74
- $sources[] = $this->_getFileSource($realpath, $cOptions);
75
- } else {
76
- $this->log("The path \"{$file}\" (realpath \"{$realpath}\") could not be found (or was not a file)");
77
- if (null === $firstMissingResource) {
78
- $firstMissingResource = basename($file);
79
- continue;
80
- } else {
81
- $secondMissingResource = basename($file);
82
- $this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource'");
83
- return $options;
84
- }
85
- }
86
- }
87
- if ($sources) {
88
- try {
89
- $this->checkType($sources[0]);
90
- } catch (Exception $e) {
91
- $this->log($e->getMessage());
92
- return $options;
93
- }
94
- }
95
- }
96
- }
97
- if (! $cOptions['groupsOnly'] && isset($_GET['f'])) {
98
- // try user files
99
- // The following restrictions are to limit the URLs that minify will
100
- // respond to.
101
- if (// verify at least one file, files are single comma separated,
102
- // and are all same extension
103
- ! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f'], $m)
104
- // no "//"
105
- || strpos($_GET['f'], '//') !== false
106
- // no "\"
107
- || strpos($_GET['f'], '\\') !== false
108
- ) {
109
- $this->log("GET param 'f' was invalid");
110
- return $options;
111
- }
112
- $ext = ".{$m[1]}";
113
- try {
114
- $this->checkType($m[1]);
115
- } catch (Exception $e) {
116
- $this->log($e->getMessage());
117
- return $options;
118
- }
119
- $files = explode(',', $_GET['f']);
120
- if ($files != array_unique($files)) {
121
- $this->log("Duplicate files were specified");
122
- return $options;
123
- }
124
- if (isset($_GET['b'])) {
125
- // check for validity
126
- if (preg_match('@^[^/]+(?:/[^/]+)*$@', $_GET['b'])
127
- && false === strpos($_GET['b'], '..')
128
- && $_GET['b'] !== '.') {
129
- // valid base
130
- $base = "/{$_GET['b']}/";
131
- } else {
132
- $this->log("GET param 'b' was invalid");
133
- return $options;
134
- }
135
- } else {
136
- $base = '/';
137
- }
138
- $allowDirs = array();
139
- foreach ((array)$cOptions['allowDirs'] as $allowDir) {
140
- $allowDirs[] = realpath(str_replace('//', $_SERVER['DOCUMENT_ROOT'] . '/', $allowDir));
141
- }
142
- $basenames = array(); // just for cache id
143
- foreach ($files as $file) {
144
- $uri = $base . $file;
145
- $path = $_SERVER['DOCUMENT_ROOT'] . $uri;
146
- $realpath = realpath($path);
147
- if (false === $realpath || ! is_file($realpath)) {
148
- $this->log("The path \"{$path}\" (realpath \"{$realpath}\") could not be found (or was not a file)");
149
- if (null === $firstMissingResource) {
150
- $firstMissingResource = $uri;
151
- continue;
152
- } else {
153
- $secondMissingResource = $uri;
154
- $this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource`'");
155
- return $options;
156
- }
157
- }
158
- try {
159
- parent::checkNotHidden($realpath);
160
- parent::checkAllowDirs($realpath, $allowDirs, $uri);
161
- } catch (Exception $e) {
162
- $this->log($e->getMessage());
163
- return $options;
164
- }
165
- $sources[] = $this->_getFileSource($realpath, $cOptions);
166
- $basenames[] = basename($realpath, $ext);
167
- }
168
- if ($this->selectionId) {
169
- $this->selectionId .= '_f=';
170
- }
171
- $this->selectionId .= implode(',', $basenames) . $ext;
172
- }
173
- if ($sources) {
174
- if (null !== $firstMissingResource) {
175
- array_unshift($sources, new Minify_Source(array(
176
- 'id' => 'missingFile'
177
- // should not cause cache invalidation
178
- ,'lastModified' => 0
179
- // due to caching, filename is unreliable.
180
- ,'content' => "/* Minify: at least one missing file. See " . Minify::URL_DEBUG . " */\n"
181
- ,'minifier' => ''
182
- )));
183
- }
184
- $this->sources = $sources;
185
- } else {
186
- $this->log("No sources to serve");
187
- }
188
- return $options;
189
- }
190
-
191
- /**
192
- * @param string $file
193
- *
194
- * @param array $cOptions
195
- *
196
- * @return Minify_Source
197
- */
198
- protected function _getFileSource($file, $cOptions)
199
- {
200
- $spec['filepath'] = $file;
201
- if ($cOptions['noMinPattern'] && preg_match($cOptions['noMinPattern'], basename($file))) {
202
- if (preg_match('~\.css$~i', $file)) {
203
- $spec['minifyOptions']['compress'] = false;
204
- } else {
205
- $spec['minifier'] = '';
206
- }
207
- }
208
- return new Minify_Source($spec);
209
- }
210
-
211
- protected $_type = null;
212
-
213
- /**
214
- * Make sure that only source files of a single type are registered
215
- *
216
- * @param string $sourceOrExt
217
- *
218
- * @throws Exception
219
- */
220
- public function checkType($sourceOrExt)
221
- {
222
- if ($sourceOrExt === 'js') {
223
- $type = Minify::TYPE_JS;
224
- } elseif ($sourceOrExt === 'css') {
225
- $type = Minify::TYPE_CSS;
226
- } elseif ($sourceOrExt->contentType !== null) {
227
- $type = $sourceOrExt->contentType;
228
- } else {
229
- return;
230
- }
231
- if ($this->_type === null) {
232
- $this->_type = $type;
233
- } elseif ($this->_type !== $type) {
234
- throw new Exception('Content-Type mismatch');
235
- }
236
- }
237
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Controller/Page.php DELETED
@@ -1,68 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Controller_Page
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Controller class for serving a single HTML page
9
- *
10
- * @link http://code.google.com/p/minify/source/browse/trunk/web/examples/1/index.php#59
11
- * @package Minify
12
- * @author Stephen Clay <steve@mrclay.org>
13
- */
14
- class Minify_Controller_Page extends Minify_Controller_Base {
15
-
16
- /**
17
- * Set up source of HTML content
18
- *
19
- * @param array $options controller and Minify options
20
- * @return array Minify options
21
- *
22
- * Controller options:
23
- *
24
- * 'content': (required) HTML markup
25
- *
26
- * 'id': (required) id of page (string for use in server-side caching)
27
- *
28
- * 'lastModifiedTime': timestamp of when this content changed. This
29
- * is recommended to allow both server and client-side caching.
30
- *
31
- * 'minifyAll': should all CSS and Javascript blocks be individually
32
- * minified? (default false)
33
- *
34
- * @todo Add 'file' option to read HTML file.
35
- */
36
- public function setupSources($options) {
37
- if (isset($options['file'])) {
38
- $sourceSpec = array(
39
- 'filepath' => $options['file']
40
- );
41
- $f = $options['file'];
42
- } else {
43
- // strip controller options
44
- $sourceSpec = array(
45
- 'content' => $options['content']
46
- ,'id' => $options['id']
47
- );
48
- $f = $options['id'];
49
- unset($options['content'], $options['id']);
50
- }
51
- // something like "builder,index.php" or "directory,file.html"
52
- $this->selectionId = strtr(substr($f, 1 + strlen(dirname(dirname($f)))), '/\\', ',,');
53
-
54
- if (isset($options['minifyAll'])) {
55
- // this will be the 2nd argument passed to Minify_HTML::minify()
56
- $sourceSpec['minifyOptions'] = array(
57
- 'cssMinifier' => array('Minify_CSS', 'minify')
58
- ,'jsMinifier' => array('JSMin', 'minify')
59
- );
60
- unset($options['minifyAll']);
61
- }
62
- $this->sources[] = new Minify_Source($sourceSpec);
63
-
64
- $options['contentType'] = Minify::TYPE_HTML;
65
- return $options;
66
- }
67
- }
68
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Controller/Version1.php DELETED
@@ -1,119 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Controller_Version1
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Controller class for emulating version 1 of minify.php (mostly a proof-of-concept)
9
- *
10
- * <code>
11
- * Minify::serve('Version1');
12
- * </code>
13
- *
14
- * @package Minify
15
- * @author Stephen Clay <steve@mrclay.org>
16
- */
17
- class Minify_Controller_Version1 extends Minify_Controller_Base {
18
-
19
- /**
20
- * Set up groups of files as sources
21
- *
22
- * @param array $options controller and Minify options
23
- * @return array Minify options
24
- *
25
- */
26
- public function setupSources($options) {
27
- // PHP insecure by default: realpath() and other FS functions can't handle null bytes.
28
- if (isset($_GET['files'])) {
29
- $_GET['files'] = str_replace("\x00", '', (string)$_GET['files']);
30
- }
31
-
32
- self::_setupDefines();
33
- if (MINIFY_USE_CACHE) {
34
- $cacheDir = defined('MINIFY_CACHE_DIR')
35
- ? MINIFY_CACHE_DIR
36
- : '';
37
- Minify::setCache($cacheDir);
38
- }
39
- $options['badRequestHeader'] = 'HTTP/1.0 404 Not Found';
40
- $options['contentTypeCharset'] = MINIFY_ENCODING;
41
-
42
- // The following restrictions are to limit the URLs that minify will
43
- // respond to. Ideally there should be only one way to reference a file.
44
- if (! isset($_GET['files'])
45
- // verify at least one file, files are single comma separated,
46
- // and are all same extension
47
- || ! preg_match('/^[^,]+\\.(css|js)(,[^,]+\\.\\1)*$/', $_GET['files'], $m)
48
- // no "//" (makes URL rewriting easier)
49
- || strpos($_GET['files'], '//') !== false
50
- // no "\"
51
- || strpos($_GET['files'], '\\') !== false
52
- // no "./"
53
- || preg_match('/(?:^|[^\\.])\\.\\//', $_GET['files'])
54
- ) {
55
- return $options;
56
- }
57
-
58
- $files = explode(',', $_GET['files']);
59
- if (count($files) > MINIFY_MAX_FILES) {
60
- return $options;
61
- }
62
-
63
- // strings for prepending to relative/absolute paths
64
- $prependRelPaths = dirname($_SERVER['SCRIPT_FILENAME'])
65
- . DIRECTORY_SEPARATOR;
66
- $prependAbsPaths = $_SERVER['DOCUMENT_ROOT'];
67
-
68
- $goodFiles = array();
69
- $hasBadSource = false;
70
-
71
- $allowDirs = isset($options['allowDirs'])
72
- ? $options['allowDirs']
73
- : MINIFY_BASE_DIR;
74
-
75
- foreach ($files as $file) {
76
- // prepend appropriate string for abs/rel paths
77
- $file = ($file[0] === '/' ? $prependAbsPaths : $prependRelPaths) . $file;
78
- // make sure a real file!
79
- $file = realpath($file);
80
- // don't allow unsafe or duplicate files
81
- if (parent::_fileIsSafe($file, $allowDirs)
82
- && !in_array($file, $goodFiles))
83
- {
84
- $goodFiles[] = $file;
85
- $srcOptions = array(
86
- 'filepath' => $file
87
- );
88
- $this->sources[] = new Minify_Source($srcOptions);
89
- } else {
90
- $hasBadSource = true;
91
- break;
92
- }
93
- }
94
- if ($hasBadSource) {
95
- $this->sources = array();
96
- }
97
- if (! MINIFY_REWRITE_CSS_URLS) {
98
- $options['rewriteCssUris'] = false;
99
- }
100
- return $options;
101
- }
102
-
103
- private static function _setupDefines()
104
- {
105
- $defaults = array(
106
- 'MINIFY_BASE_DIR' => realpath($_SERVER['DOCUMENT_ROOT'])
107
- ,'MINIFY_ENCODING' => 'utf-8'
108
- ,'MINIFY_MAX_FILES' => 16
109
- ,'MINIFY_REWRITE_CSS_URLS' => true
110
- ,'MINIFY_USE_CACHE' => true
111
- );
112
- foreach ($defaults as $const => $val) {
113
- if (! defined($const)) {
114
- define($const, $val);
115
- }
116
- }
117
- }
118
- }
119
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/DebugDetector.php DELETED
@@ -1,26 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Detect whether request should be debugged
5
- *
6
- * @package Minify
7
- * @author Stephen Clay <steve@mrclay.org>
8
- */
9
- class Minify_DebugDetector {
10
- public static function shouldDebugRequest($cookie, $get, $requestUri)
11
- {
12
- if (isset($get['debug'])) {
13
- return true;
14
- }
15
- if (! empty($cookie['minifyDebug'])) {
16
- foreach (preg_split('/\\s+/', $cookie['minifyDebug']) as $debugUri) {
17
- $pattern = '@' . preg_quote($debugUri, '@') . '@i';
18
- $pattern = str_replace(array('\\*', '\\?'), array('.*', '.'), $pattern);
19
- if (preg_match($pattern, $requestUri)) {
20
- return true;
21
- }
22
- }
23
- }
24
- return false;
25
- }
26
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/HTML.php DELETED
@@ -1,264 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_HTML
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Compress HTML
9
- *
10
- * This is a heavy regex-based removal of whitespace, unnecessary comments and
11
- * tokens. IE conditional comments are preserved. There are also options to have
12
- * STYLE and SCRIPT blocks compressed by callback functions.
13
- *
14
- * A test suite is available.
15
- *
16
- * @package Minify
17
- * @author Stephen Clay <steve@mrclay.org>
18
- */
19
- class Minify_HTML {
20
- /**
21
- * @var boolean
22
- */
23
- protected $_jsCleanComments = true;
24
-
25
- /**
26
- * "Minify" an HTML page
27
- *
28
- * @param string $html
29
- *
30
- * @param array $options
31
- *
32
- * 'cssMinifier' : (optional) callback function to process content of STYLE
33
- * elements.
34
- *
35
- * 'jsMinifier' : (optional) callback function to process content of SCRIPT
36
- * elements. Note: the type attribute is ignored.
37
- *
38
- * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
39
- * unset, minify will sniff for an XHTML doctype.
40
- *
41
- * @return string
42
- */
43
- public static function minify($html, $options = array()) {
44
- $min = new self($html, $options);
45
- return $min->process();
46
- }
47
-
48
-
49
- /**
50
- * Create a minifier object
51
- *
52
- * @param string $html
53
- *
54
- * @param array $options
55
- *
56
- * 'cssMinifier' : (optional) callback function to process content of STYLE
57
- * elements.
58
- *
59
- * 'jsMinifier' : (optional) callback function to process content of SCRIPT
60
- * elements. Note: the type attribute is ignored.
61
- *
62
- * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
63
- *
64
- * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
65
- * unset, minify will sniff for an XHTML doctype.
66
- */
67
- public function __construct($html, $options = array())
68
- {
69
- $this->_html = str_replace("\r\n", "\n", trim($html));
70
- if (isset($options['xhtml'])) {
71
- $this->_isXhtml = (bool)$options['xhtml'];
72
- }
73
- if (isset($options['cssMinifier'])) {
74
- $this->_cssMinifier = $options['cssMinifier'];
75
- }
76
- if (isset($options['jsMinifier'])) {
77
- $this->_jsMinifier = $options['jsMinifier'];
78
- }
79
- if (isset($options['jsCleanComments'])) {
80
- $this->_jsCleanComments = (bool)$options['jsCleanComments'];
81
- }
82
- }
83
-
84
-
85
- /**
86
- * Minify the markeup given in the constructor
87
- *
88
- * @return string
89
- */
90
- public function process()
91
- {
92
- if ($this->_isXhtml === null) {
93
- $this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
94
- }
95
-
96
- $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
97
- $this->_placeholders = array();
98
-
99
- // replace SCRIPTs (and minify) with placeholders
100
- $this->_html = preg_replace_callback(
101
- '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
102
- ,array($this, '_removeScriptCB')
103
- ,$this->_html);
104
-
105
- // replace STYLEs (and minify) with placeholders
106
- $this->_html = preg_replace_callback(
107
- '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
108
- ,array($this, '_removeStyleCB')
109
- ,$this->_html);
110
-
111
- // remove HTML comments (not containing IE conditional comments).
112
- $this->_html = preg_replace_callback(
113
- '/<!--([\\s\\S]*?)-->/'
114
- ,array($this, '_commentCB')
115
- ,$this->_html);
116
-
117
- // replace PREs with placeholders
118
- $this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
119
- ,array($this, '_removePreCB')
120
- ,$this->_html);
121
-
122
- // replace TEXTAREAs with placeholders
123
- $this->_html = preg_replace_callback(
124
- '/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
125
- ,array($this, '_removeTextareaCB')
126
- ,$this->_html);
127
-
128
- // trim each line.
129
- // @todo take into account attribute values that span multiple lines.
130
- $this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
131
-
132
- // remove ws around block/undisplayed elements
133
- $this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
134
- .'|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
135
- .'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
136
- .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
137
- .'|ul)\\b[^>]*>)/i', '$1', $this->_html);
138
-
139
- // remove ws outside of all elements
140
- $this->_html = preg_replace(
141
- '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
142
- ,'>$1$2$3<'
143
- ,$this->_html);
144
-
145
- // use newlines before 1st attribute in open tags (to limit line lengths)
146
- $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
147
-
148
- // fill placeholders
149
- $this->_html = str_replace(
150
- array_keys($this->_placeholders)
151
- ,array_values($this->_placeholders)
152
- ,$this->_html
153
- );
154
- // issue 229: multi-pass to catch scripts that didn't get replaced in textareas
155
- $this->_html = str_replace(
156
- array_keys($this->_placeholders)
157
- ,array_values($this->_placeholders)
158
- ,$this->_html
159
- );
160
- return $this->_html;
161
- }
162
-
163
- protected function _commentCB($m)
164
- {
165
- return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
166
- ? $m[0]
167
- : '';
168
- }
169
-
170
- protected function _reservePlace($content)
171
- {
172
- $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
173
- $this->_placeholders[$placeholder] = $content;
174
- return $placeholder;
175
- }
176
-
177
- protected $_isXhtml = null;
178
- protected $_replacementHash = null;
179
- protected $_placeholders = array();
180
- protected $_cssMinifier = null;
181
- protected $_jsMinifier = null;
182
-
183
- protected function _removePreCB($m)
184
- {
185
- return $this->_reservePlace("<pre{$m[1]}");
186
- }
187
-
188
- protected function _removeTextareaCB($m)
189
- {
190
- return $this->_reservePlace("<textarea{$m[1]}");
191
- }
192
-
193
- protected function _removeStyleCB($m)
194
- {
195
- $openStyle = "<style{$m[1]}";
196
- $css = $m[2];
197
- // remove HTML comments
198
- $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
199
-
200
- // remove CDATA section markers
201
- $css = $this->_removeCdata($css);
202
-
203
- // minify
204
- $minifier = $this->_cssMinifier
205
- ? $this->_cssMinifier
206
- : 'trim';
207
- $css = call_user_func($minifier, $css);
208
-
209
- return $this->_reservePlace($this->_needsCdata($css)
210
- ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
211
- : "{$openStyle}{$css}</style>"
212
- );
213
- }
214
-
215
- protected function _removeScriptCB($m)
216
- {
217
- $openScript = "<script{$m[2]}";
218
- $js = $m[3];
219
-
220
- // whitespace surrounding? preserve at least one space
221
- $ws1 = ($m[1] === '') ? '' : ' ';
222
- $ws2 = ($m[4] === '') ? '' : ' ';
223
-
224
- // remove HTML comments (and ending "//" if present)
225
- if ($this->_jsCleanComments) {
226
- $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
227
- }
228
-
229
- // remove CDATA section markers
230
- $js = $this->_removeCdata($js);
231
-
232
- if (false !== stripos($openScript, '<script type="text/template"')) {
233
-
234
- // minify
235
- $js = Minify_HTML::minify($js);
236
-
237
- } else {
238
-
239
- // minify
240
- $minifier = $this->_jsMinifier
241
- ? $this->_jsMinifier
242
- : 'trim';
243
- $js = call_user_func($minifier, $js);
244
-
245
- }
246
-
247
- return $this->_reservePlace($this->_needsCdata($js)
248
- ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
249
- : "{$ws1}{$openScript}{$js}</script>{$ws2}"
250
- );
251
- }
252
-
253
- protected function _removeCdata($str)
254
- {
255
- return (false !== strpos($str, '<![CDATA['))
256
- ? str_replace(array('<![CDATA[', ']]>'), '', $str)
257
- : $str;
258
- }
259
-
260
- protected function _needsCdata($str)
261
- {
262
- return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
263
- }
264
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/HTML/Helper.php DELETED
@@ -1,225 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_HTML_Helper
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Helpers for writing Minfy URIs into HTML
9
- *
10
- * @package Minify
11
- * @author Stephen Clay <steve@mrclay.org>
12
- */
13
- class Minify_HTML_Helper {
14
- public $rewriteWorks = true;
15
- public $minAppUri = '/min';
16
- public $groupsConfigFile = '';
17
-
18
- /**
19
- * Get an HTML-escaped Minify URI for a group or set of files
20
- *
21
- * @param string|array $keyOrFiles a group key or array of filepaths/URIs
22
- * @param array $opts options:
23
- * 'farExpires' : (default true) append a modified timestamp for cache revving
24
- * 'debug' : (default false) append debug flag
25
- * 'charset' : (default 'UTF-8') for htmlspecialchars
26
- * 'minAppUri' : (default '/min') URI of min directory
27
- * 'rewriteWorks' : (default true) does mod_rewrite work in min app?
28
- * 'groupsConfigFile' : specify if different
29
- * @return string
30
- */
31
- public static function getUri($keyOrFiles, $opts = array())
32
- {
33
- $opts = array_merge(array( // default options
34
- 'farExpires' => true
35
- ,'debug' => false
36
- ,'charset' => 'UTF-8'
37
- ,'minAppUri' => '/min'
38
- ,'rewriteWorks' => true
39
- ,'groupsConfigFile' => ''
40
- ), $opts);
41
- $h = new self;
42
- $h->minAppUri = $opts['minAppUri'];
43
- $h->rewriteWorks = $opts['rewriteWorks'];
44
- $h->groupsConfigFile = $opts['groupsConfigFile'];
45
- if (is_array($keyOrFiles)) {
46
- $h->setFiles($keyOrFiles, $opts['farExpires']);
47
- } else {
48
- $h->setGroup($keyOrFiles, $opts['farExpires']);
49
- }
50
- $uri = $h->getRawUri($opts['farExpires'], $opts['debug']);
51
- return htmlspecialchars($uri, ENT_QUOTES, $opts['charset']);
52
- }
53
-
54
- /**
55
- * Get non-HTML-escaped URI to minify the specified files
56
- *
57
- * @param bool $farExpires
58
- * @param bool $debug
59
- * @return string
60
- */
61
- public function getRawUri($farExpires = true, $debug = false)
62
- {
63
- $path = rtrim($this->minAppUri, '/') . '/';
64
- if (! $this->rewriteWorks) {
65
- $path .= '?';
66
- }
67
- if (null === $this->_groupKey) {
68
- // @todo: implement shortest uri
69
- $path = self::_getShortestUri($this->_filePaths, $path);
70
- } else {
71
- $path .= "g=" . $this->_groupKey;
72
- }
73
- if ($debug) {
74
- $path .= "&debug";
75
- } elseif ($farExpires && $this->_lastModified) {
76
- $path .= "&" . $this->_lastModified;
77
- }
78
- return $path;
79
- }
80
-
81
- /**
82
- * Set the files that will comprise the URI we're building
83
- *
84
- * @param array $files
85
- * @param bool $checkLastModified
86
- */
87
- public function setFiles($files, $checkLastModified = true)
88
- {
89
- $this->_groupKey = null;
90
- if ($checkLastModified) {
91
- $this->_lastModified = self::getLastModified($files);
92
- }
93
- // normalize paths like in /min/f=<paths>
94
- foreach ($files as $k => $file) {
95
- if (0 === strpos($file, '//')) {
96
- $file = substr($file, 2);
97
- } elseif (0 === strpos($file, '/')
98
- || 1 === strpos($file, ':\\')) {
99
- $file = substr($file, strlen($_SERVER['DOCUMENT_ROOT']) + 1);
100
- }
101
- $file = strtr($file, '\\', '/');
102
- $files[$k] = $file;
103
- }
104
- $this->_filePaths = $files;
105
- }
106
-
107
- /**
108
- * Set the group of files that will comprise the URI we're building
109
- *
110
- * @param string $key
111
- * @param bool $checkLastModified
112
- */
113
- public function setGroup($key, $checkLastModified = true)
114
- {
115
- $this->_groupKey = $key;
116
- if ($checkLastModified) {
117
- if (! $this->groupsConfigFile) {
118
- $this->groupsConfigFile = dirname(dirname(dirname(dirname(__FILE__)))) . '/groupsConfig.php';
119
- }
120
- if (is_file($this->groupsConfigFile)) {
121
- $gc = (require $this->groupsConfigFile);
122
- $keys = explode(',', $key);
123
- foreach ($keys as $key) {
124
- if (isset($gc[$key])) {
125
- $this->_lastModified = self::getLastModified($gc[$key], $this->_lastModified);
126
- }
127
- }
128
- }
129
- }
130
- }
131
-
132
- /**
133
- * Get the max(lastModified) of all files
134
- *
135
- * @param array|string $sources
136
- * @param int $lastModified
137
- * @return int
138
- */
139
- public static function getLastModified($sources, $lastModified = 0)
140
- {
141
- $max = $lastModified;
142
- foreach ((array)$sources as $source) {
143
- if (is_object($source) && isset($source->lastModified)) {
144
- $max = max($max, $source->lastModified);
145
- } elseif (is_string($source)) {
146
- if (0 === strpos($source, '//')) {
147
- $source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1);
148
- }
149
- if (is_file($source)) {
150
- $max = max($max, filemtime($source));
151
- }
152
- }
153
- }
154
- return $max;
155
- }
156
-
157
- protected $_groupKey = null; // if present, URI will be like g=...
158
- protected $_filePaths = array();
159
- protected $_lastModified = null;
160
-
161
-
162
- /**
163
- * In a given array of strings, find the character they all have at
164
- * a particular index
165
- *
166
- * @param array $arr array of strings
167
- * @param int $pos index to check
168
- * @return mixed a common char or '' if any do not match
169
- */
170
- protected static function _getCommonCharAtPos($arr, $pos) {
171
- if (!isset($arr[0][$pos])) {
172
- return '';
173
- }
174
- $c = $arr[0][$pos];
175
- $l = count($arr);
176
- if ($l === 1) {
177
- return $c;
178
- }
179
- for ($i = 1; $i < $l; ++$i) {
180
- if ($arr[$i][$pos] !== $c) {
181
- return '';
182
- }
183
- }
184
- return $c;
185
- }
186
-
187
- /**
188
- * Get the shortest URI to minify the set of source files
189
- *
190
- * @param array $paths root-relative URIs of files
191
- * @param string $minRoot root-relative URI of the "min" application
192
- * @return string
193
- */
194
- protected static function _getShortestUri($paths, $minRoot = '/min/') {
195
- $pos = 0;
196
- $base = '';
197
- while (true) {
198
- $c = self::_getCommonCharAtPos($paths, $pos);
199
- if ($c === '') {
200
- break;
201
- } else {
202
- $base .= $c;
203
- }
204
- ++$pos;
205
- }
206
- $base = preg_replace('@[^/]+$@', '', $base);
207
- $uri = $minRoot . 'f=' . implode(',', $paths);
208
-
209
- if (substr($base, -1) === '/') {
210
- // we have a base dir!
211
- $basedPaths = $paths;
212
- $l = count($paths);
213
- for ($i = 0; $i < $l; ++$i) {
214
- $basedPaths[$i] = substr($paths[$i], strlen($base));
215
- }
216
- $base = substr($base, 0, strlen($base) - 1);
217
- $bUri = $minRoot . 'b=' . $base . '&f=' . implode(',', $basedPaths);
218
-
219
- $uri = strlen($uri) < strlen($bUri)
220
- ? $uri
221
- : $bUri;
222
- }
223
- return $uri;
224
- }
225
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/HTMLComp.php DELETED
@@ -1,274 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_HTML
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Compress HTML
9
- *
10
- * This is a heavy regex-based removal of whitespace, unnecessary comments and
11
- * tokens. IE conditional comments are preserved. There are also options to have
12
- * STYLE and SCRIPT blocks compressed by callback functions.
13
- *
14
- * A test suite is available.
15
- *
16
- * @package Minify
17
- * @author Stephen Clay <steve@mrclay.org>
18
- */
19
-
20
- /**
21
- * Modified version of a class for compatibility with Varnish and FPC cache extensions for Magento.
22
- *
23
- * @category Apptrian
24
- * @package Apptrian_Minify
25
- * @author Apptrian
26
- * @copyright Copyright (c) 2015 Apptrian (http://www.apptrian.com)
27
- * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
28
- */
29
- class Minify_HTMLComp {
30
- /**
31
- * @var boolean
32
- */
33
- protected $_jsCleanComments = true;
34
-
35
- /**
36
- * "Minify" an HTML page
37
- *
38
- * @param string $html
39
- *
40
- * @param array $options
41
- *
42
- * 'cssMinifier' : (optional) callback function to process content of STYLE
43
- * elements.
44
- *
45
- * 'jsMinifier' : (optional) callback function to process content of SCRIPT
46
- * elements. Note: the type attribute is ignored.
47
- *
48
- * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
49
- * unset, minify will sniff for an XHTML doctype.
50
- *
51
- * @return string
52
- */
53
- public static function minify($html, $options = array()) {
54
- $min = new self($html, $options);
55
- return $min->process();
56
- }
57
-
58
-
59
- /**
60
- * Create a minifier object
61
- *
62
- * @param string $html
63
- *
64
- * @param array $options
65
- *
66
- * 'cssMinifier' : (optional) callback function to process content of STYLE
67
- * elements.
68
- *
69
- * 'jsMinifier' : (optional) callback function to process content of SCRIPT
70
- * elements. Note: the type attribute is ignored.
71
- *
72
- * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
73
- *
74
- * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
75
- * unset, minify will sniff for an XHTML doctype.
76
- */
77
- public function __construct($html, $options = array())
78
- {
79
- $this->_html = str_replace("\r\n", "\n", trim($html));
80
- if (isset($options['xhtml'])) {
81
- $this->_isXhtml = (bool)$options['xhtml'];
82
- }
83
- if (isset($options['cssMinifier'])) {
84
- $this->_cssMinifier = $options['cssMinifier'];
85
- }
86
- if (isset($options['jsMinifier'])) {
87
- $this->_jsMinifier = $options['jsMinifier'];
88
- }
89
- if (isset($options['jsCleanComments'])) {
90
- $this->_jsCleanComments = (bool)$options['jsCleanComments'];
91
- }
92
- }
93
-
94
-
95
- /**
96
- * Minify the markeup given in the constructor
97
- *
98
- * @return string
99
- */
100
- public function process()
101
- {
102
- if ($this->_isXhtml === null) {
103
- $this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
104
- }
105
-
106
- $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
107
- $this->_placeholders = array();
108
-
109
- // replace SCRIPTs (and minify) with placeholders
110
- $this->_html = preg_replace_callback(
111
- '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
112
- ,array($this, '_removeScriptCB')
113
- ,$this->_html);
114
-
115
- // replace STYLEs (and minify) with placeholders
116
- $this->_html = preg_replace_callback(
117
- '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
118
- ,array($this, '_removeStyleCB')
119
- ,$this->_html);
120
-
121
- // remove HTML comments (not containing IE conditional comments).
122
- $this->_html = preg_replace_callback(
123
- '/<!--([\\s\\S]*?)-->/'
124
- ,array($this, '_commentCB')
125
- ,$this->_html);
126
-
127
- // replace PREs with placeholders
128
- $this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
129
- ,array($this, '_removePreCB')
130
- ,$this->_html);
131
-
132
- // replace TEXTAREAs with placeholders
133
- $this->_html = preg_replace_callback(
134
- '/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
135
- ,array($this, '_removeTextareaCB')
136
- ,$this->_html);
137
-
138
- // trim each line.
139
- // @todo take into account attribute values that span multiple lines.
140
- $this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
141
-
142
- // remove ws around block/undisplayed elements
143
- $this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
144
- .'|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
145
- .'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
146
- .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
147
- .'|ul)\\b[^>]*>)/i', '$1', $this->_html);
148
-
149
- // remove ws outside of all elements
150
- $this->_html = preg_replace(
151
- '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
152
- ,'>$1$2$3<'
153
- ,$this->_html);
154
-
155
- // use newlines before 1st attribute in open tags (to limit line lengths)
156
- $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
157
-
158
- // fill placeholders
159
- $this->_html = str_replace(
160
- array_keys($this->_placeholders)
161
- ,array_values($this->_placeholders)
162
- ,$this->_html
163
- );
164
- // issue 229: multi-pass to catch scripts that didn't get replaced in textareas
165
- $this->_html = str_replace(
166
- array_keys($this->_placeholders)
167
- ,array_values($this->_placeholders)
168
- ,$this->_html
169
- );
170
- return $this->_html;
171
- }
172
-
173
- protected function _commentCB($m)
174
- {
175
- return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<![') || false !== stripos($m[1], 'esi <') || false !== stripos($m[1], ' fpc'))
176
- ? $m[0]
177
- : '';
178
- }
179
-
180
- protected function _reservePlace($content)
181
- {
182
- $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
183
- $this->_placeholders[$placeholder] = $content;
184
- return $placeholder;
185
- }
186
-
187
- protected $_isXhtml = null;
188
- protected $_replacementHash = null;
189
- protected $_placeholders = array();
190
- protected $_cssMinifier = null;
191
- protected $_jsMinifier = null;
192
-
193
- protected function _removePreCB($m)
194
- {
195
- return $this->_reservePlace("<pre{$m[1]}");
196
- }
197
-
198
- protected function _removeTextareaCB($m)
199
- {
200
- return $this->_reservePlace("<textarea{$m[1]}");
201
- }
202
-
203
- protected function _removeStyleCB($m)
204
- {
205
- $openStyle = "<style{$m[1]}";
206
- $css = $m[2];
207
- // remove HTML comments
208
- $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
209
-
210
- // remove CDATA section markers
211
- $css = $this->_removeCdata($css);
212
-
213
- // minify
214
- $minifier = $this->_cssMinifier
215
- ? $this->_cssMinifier
216
- : 'trim';
217
- $css = call_user_func($minifier, $css);
218
-
219
- return $this->_reservePlace($this->_needsCdata($css)
220
- ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
221
- : "{$openStyle}{$css}</style>"
222
- );
223
- }
224
-
225
- protected function _removeScriptCB($m)
226
- {
227
- $openScript = "<script{$m[2]}";
228
- $js = $m[3];
229
-
230
- // whitespace surrounding? preserve at least one space
231
- $ws1 = ($m[1] === '') ? '' : ' ';
232
- $ws2 = ($m[4] === '') ? '' : ' ';
233
-
234
- // remove HTML comments (and ending "//" if present)
235
- if ($this->_jsCleanComments) {
236
- $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
237
- }
238
-
239
- // remove CDATA section markers
240
- $js = $this->_removeCdata($js);
241
-
242
- if (false !== stripos($openScript, '<script type="text/template"')) {
243
-
244
- // minify
245
- $js = Minify_HTMLComp::minify($js);
246
-
247
- } else {
248
-
249
- // minify
250
- $minifier = $this->_jsMinifier
251
- ? $this->_jsMinifier
252
- : 'trim';
253
- $js = call_user_func($minifier, $js);
254
-
255
- }
256
-
257
- return $this->_reservePlace($this->_needsCdata($js)
258
- ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
259
- : "{$ws1}{$openScript}{$js}</script>{$ws2}"
260
- );
261
- }
262
-
263
- protected function _removeCdata($str)
264
- {
265
- return (false !== strpos($str, '<![CDATA['))
266
- ? str_replace(array('<![CDATA[', ']]>'), '', $str)
267
- : $str;
268
- }
269
-
270
- protected function _needsCdata($str)
271
- {
272
- return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
273
- }
274
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/HTMLMax.php DELETED
@@ -1,277 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_HTML
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Compress HTML
9
- *
10
- * This is a heavy regex-based removal of whitespace, unnecessary comments and
11
- * tokens. IE conditional comments are preserved. There are also options to have
12
- * STYLE and SCRIPT blocks compressed by callback functions.
13
- *
14
- * A test suite is available.
15
- *
16
- * @package Minify
17
- * @author Stephen Clay <steve@mrclay.org>
18
- */
19
-
20
- /**
21
- * Modified version of a class for max minification.
22
- *
23
- * @category Apptrian
24
- * @package Apptrian_Minify
25
- * @author Apptrian
26
- * @copyright Copyright (c) 2015 Apptrian (http://www.apptrian.com)
27
- * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
28
- */
29
- class Minify_HTMLMax {
30
- /**
31
- * @var boolean
32
- */
33
- protected $_jsCleanComments = true;
34
-
35
- /**
36
- * "Minify" an HTML page
37
- *
38
- * @param string $html
39
- *
40
- * @param array $options
41
- *
42
- * 'cssMinifier' : (optional) callback function to process content of STYLE
43
- * elements.
44
- *
45
- * 'jsMinifier' : (optional) callback function to process content of SCRIPT
46
- * elements. Note: the type attribute is ignored.
47
- *
48
- * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
49
- * unset, minify will sniff for an XHTML doctype.
50
- *
51
- * @return string
52
- */
53
- public static function minify($html, $options = array()) {
54
- $min = new self($html, $options);
55
- return $min->process();
56
- }
57
-
58
-
59
- /**
60
- * Create a minifier object
61
- *
62
- * @param string $html
63
- *
64
- * @param array $options
65
- *
66
- * 'cssMinifier' : (optional) callback function to process content of STYLE
67
- * elements.
68
- *
69
- * 'jsMinifier' : (optional) callback function to process content of SCRIPT
70
- * elements. Note: the type attribute is ignored.
71
- *
72
- * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
73
- *
74
- * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
75
- * unset, minify will sniff for an XHTML doctype.
76
- */
77
- public function __construct($html, $options = array())
78
- {
79
- $this->_html = str_replace("\r\n", "\n", trim($html));
80
- if (isset($options['xhtml'])) {
81
- $this->_isXhtml = (bool)$options['xhtml'];
82
- }
83
- if (isset($options['cssMinifier'])) {
84
- $this->_cssMinifier = $options['cssMinifier'];
85
- }
86
- if (isset($options['jsMinifier'])) {
87
- $this->_jsMinifier = $options['jsMinifier'];
88
- }
89
- if (isset($options['jsCleanComments'])) {
90
- $this->_jsCleanComments = (bool)$options['jsCleanComments'];
91
- }
92
- }
93
-
94
-
95
- /**
96
- * Minify the markeup given in the constructor
97
- *
98
- * @return string
99
- */
100
- public function process()
101
- {
102
- if ($this->_isXhtml === null) {
103
- $this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
104
- }
105
-
106
- $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
107
- $this->_placeholders = array();
108
-
109
- // replace SCRIPTs (and minify) with placeholders
110
- $this->_html = preg_replace_callback(
111
- '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
112
- ,array($this, '_removeScriptCB')
113
- ,$this->_html);
114
-
115
- // replace STYLEs (and minify) with placeholders
116
- $this->_html = preg_replace_callback(
117
- '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
118
- ,array($this, '_removeStyleCB')
119
- ,$this->_html);
120
-
121
- // remove HTML comments (not containing IE conditional comments).
122
- $this->_html = preg_replace_callback(
123
- '/<!--([\\s\\S]*?)-->/'
124
- ,array($this, '_commentCB')
125
- ,$this->_html);
126
-
127
- // replace PREs with placeholders
128
- $this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
129
- ,array($this, '_removePreCB')
130
- ,$this->_html);
131
-
132
- // replace TEXTAREAs with placeholders
133
- $this->_html = preg_replace_callback(
134
- '/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
135
- ,array($this, '_removeTextareaCB')
136
- ,$this->_html);
137
-
138
- // trim each line.
139
- // @todo take into account attribute values that span multiple lines.
140
- $this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
141
-
142
- // remove ws around block/undisplayed elements
143
- $this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
144
- .'|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
145
- .'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
146
- .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
147
- .'|ul)\\b[^>]*>)/i', '$1', $this->_html);
148
-
149
- // remove ws outside of all elements
150
- $this->_html = preg_replace(
151
- '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
152
- ,'>$1$2$3<'
153
- ,$this->_html);
154
-
155
- // use newlines before 1st attribute in open tags (to limit line lengths)
156
- //$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
157
-
158
- // Strip all multiple spaces to one space
159
- $this->_html = preg_replace('/\s+/ui', ' ', $this->_html);
160
-
161
- // fill placeholders
162
- $this->_html = str_replace(
163
- array_keys($this->_placeholders)
164
- ,array_values($this->_placeholders)
165
- ,$this->_html
166
- );
167
- // issue 229: multi-pass to catch scripts that didn't get replaced in textareas
168
- $this->_html = str_replace(
169
- array_keys($this->_placeholders)
170
- ,array_values($this->_placeholders)
171
- ,$this->_html
172
- );
173
- return $this->_html;
174
- }
175
-
176
- protected function _commentCB($m)
177
- {
178
- return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
179
- ? $m[0]
180
- : '';
181
- }
182
-
183
- protected function _reservePlace($content)
184
- {
185
- $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
186
- $this->_placeholders[$placeholder] = $content;
187
- return $placeholder;
188
- }
189
-
190
- protected $_isXhtml = null;
191
- protected $_replacementHash = null;
192
- protected $_placeholders = array();
193
- protected $_cssMinifier = null;
194
- protected $_jsMinifier = null;
195
-
196
- protected function _removePreCB($m)
197
- {
198
- return $this->_reservePlace("<pre{$m[1]}");
199
- }
200
-
201
- protected function _removeTextareaCB($m)
202
- {
203
- return $this->_reservePlace("<textarea{$m[1]}");
204
- }
205
-
206
- protected function _removeStyleCB($m)
207
- {
208
- $openStyle = "<style{$m[1]}";
209
- $css = $m[2];
210
- // remove HTML comments
211
- $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
212
-
213
- // remove CDATA section markers
214
- $css = $this->_removeCdata($css);
215
-
216
- // minify
217
- $minifier = $this->_cssMinifier
218
- ? $this->_cssMinifier
219
- : 'trim';
220
- $css = call_user_func($minifier, $css);
221
-
222
- return $this->_reservePlace($this->_needsCdata($css)
223
- ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
224
- : "{$openStyle}{$css}</style>"
225
- );
226
- }
227
-
228
- protected function _removeScriptCB($m)
229
- {
230
- $openScript = "<script{$m[2]}";
231
- $js = $m[3];
232
-
233
- // whitespace surrounding? preserve at least one space
234
- $ws1 = ($m[1] === '') ? '' : ' ';
235
- $ws2 = ($m[4] === '') ? '' : ' ';
236
-
237
- // remove HTML comments (and ending "//" if present)
238
- if ($this->_jsCleanComments) {
239
- $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
240
- }
241
-
242
- // remove CDATA section markers
243
- $js = $this->_removeCdata($js);
244
-
245
- if (false !== stripos($openScript, '<script type="text/template"')) {
246
-
247
- // minify
248
- $js = Minify_HTMLMax::minify($js);
249
-
250
- } else {
251
-
252
- // minify
253
- $minifier = $this->_jsMinifier
254
- ? $this->_jsMinifier
255
- : 'trim';
256
- $js = call_user_func($minifier, $js);
257
-
258
- }
259
-
260
- return $this->_reservePlace($this->_needsCdata($js)
261
- ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
262
- : "{$ws1}{$openScript}{$js}</script>{$ws2}"
263
- );
264
- }
265
-
266
- protected function _removeCdata($str)
267
- {
268
- return (false !== strpos($str, '<![CDATA['))
269
- ? str_replace(array('<![CDATA[', ']]>'), '', $str)
270
- : $str;
271
- }
272
-
273
- protected function _needsCdata($str)
274
- {
275
- return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
276
- }
277
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/HTMLMaxComp.php DELETED
@@ -1,277 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_HTML
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Compress HTML
9
- *
10
- * This is a heavy regex-based removal of whitespace, unnecessary comments and
11
- * tokens. IE conditional comments are preserved. There are also options to have
12
- * STYLE and SCRIPT blocks compressed by callback functions.
13
- *
14
- * A test suite is available.
15
- *
16
- * @package Minify
17
- * @author Stephen Clay <steve@mrclay.org>
18
- */
19
-
20
- /**
21
- * Modified version of a class for max minification and compatibility with Varnish and FPC cache extensions for Magento.
22
- *
23
- * @category Apptrian
24
- * @package Apptrian_Minify
25
- * @author Apptrian
26
- * @copyright Copyright (c) 2015 Apptrian (http://www.apptrian.com)
27
- * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
28
- */
29
- class Minify_HTMLMaxComp {
30
- /**
31
- * @var boolean
32
- */
33
- protected $_jsCleanComments = true;
34
-
35
- /**
36
- * "Minify" an HTML page
37
- *
38
- * @param string $html
39
- *
40
- * @param array $options
41
- *
42
- * 'cssMinifier' : (optional) callback function to process content of STYLE
43
- * elements.
44
- *
45
- * 'jsMinifier' : (optional) callback function to process content of SCRIPT
46
- * elements. Note: the type attribute is ignored.
47
- *
48
- * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
49
- * unset, minify will sniff for an XHTML doctype.
50
- *
51
- * @return string
52
- */
53
- public static function minify($html, $options = array()) {
54
- $min = new self($html, $options);
55
- return $min->process();
56
- }
57
-
58
-
59
- /**
60
- * Create a minifier object
61
- *
62
- * @param string $html
63
- *
64
- * @param array $options
65
- *
66
- * 'cssMinifier' : (optional) callback function to process content of STYLE
67
- * elements.
68
- *
69
- * 'jsMinifier' : (optional) callback function to process content of SCRIPT
70
- * elements. Note: the type attribute is ignored.
71
- *
72
- * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
73
- *
74
- * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
75
- * unset, minify will sniff for an XHTML doctype.
76
- */
77
- public function __construct($html, $options = array())
78
- {
79
- $this->_html = str_replace("\r\n", "\n", trim($html));
80
- if (isset($options['xhtml'])) {
81
- $this->_isXhtml = (bool)$options['xhtml'];
82
- }
83
- if (isset($options['cssMinifier'])) {
84
- $this->_cssMinifier = $options['cssMinifier'];
85
- }
86
- if (isset($options['jsMinifier'])) {
87
- $this->_jsMinifier = $options['jsMinifier'];
88
- }
89
- if (isset($options['jsCleanComments'])) {
90
- $this->_jsCleanComments = (bool)$options['jsCleanComments'];
91
- }
92
- }
93
-
94
-
95
- /**
96
- * Minify the markeup given in the constructor
97
- *
98
- * @return string
99
- */
100
- public function process()
101
- {
102
- if ($this->_isXhtml === null) {
103
- $this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
104
- }
105
-
106
- $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
107
- $this->_placeholders = array();
108
-
109
- // replace SCRIPTs (and minify) with placeholders
110
- $this->_html = preg_replace_callback(
111
- '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
112
- ,array($this, '_removeScriptCB')
113
- ,$this->_html);
114
-
115
- // replace STYLEs (and minify) with placeholders
116
- $this->_html = preg_replace_callback(
117
- '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
118
- ,array($this, '_removeStyleCB')
119
- ,$this->_html);
120
-
121
- // remove HTML comments (not containing IE conditional comments).
122
- $this->_html = preg_replace_callback(
123
- '/<!--([\\s\\S]*?)-->/'
124
- ,array($this, '_commentCB')
125
- ,$this->_html);
126
-
127
- // replace PREs with placeholders
128
- $this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
129
- ,array($this, '_removePreCB')
130
- ,$this->_html);
131
-
132
- // replace TEXTAREAs with placeholders
133
- $this->_html = preg_replace_callback(
134
- '/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
135
- ,array($this, '_removeTextareaCB')
136
- ,$this->_html);
137
-
138
- // trim each line.
139
- // @todo take into account attribute values that span multiple lines.
140
- $this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
141
-
142
- // remove ws around block/undisplayed elements
143
- $this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
144
- .'|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
145
- .'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
146
- .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
147
- .'|ul)\\b[^>]*>)/i', '$1', $this->_html);
148
-
149
- // remove ws outside of all elements
150
- $this->_html = preg_replace(
151
- '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
152
- ,'>$1$2$3<'
153
- ,$this->_html);
154
-
155
- // use newlines before 1st attribute in open tags (to limit line lengths)
156
- //$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
157
-
158
- // Strip all multiple spaces to one space
159
- $this->_html = preg_replace('/\s+/ui', ' ', $this->_html);
160
-
161
- // fill placeholders
162
- $this->_html = str_replace(
163
- array_keys($this->_placeholders)
164
- ,array_values($this->_placeholders)
165
- ,$this->_html
166
- );
167
- // issue 229: multi-pass to catch scripts that didn't get replaced in textareas
168
- $this->_html = str_replace(
169
- array_keys($this->_placeholders)
170
- ,array_values($this->_placeholders)
171
- ,$this->_html
172
- );
173
- return $this->_html;
174
- }
175
-
176
- protected function _commentCB($m)
177
- {
178
- return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<![') || false !== stripos($m[1], 'esi <') || false !== stripos($m[1], ' fpc'))
179
- ? $m[0]
180
- : '';
181
- }
182
-
183
- protected function _reservePlace($content)
184
- {
185
- $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
186
- $this->_placeholders[$placeholder] = $content;
187
- return $placeholder;
188
- }
189
-
190
- protected $_isXhtml = null;
191
- protected $_replacementHash = null;
192
- protected $_placeholders = array();
193
- protected $_cssMinifier = null;
194
- protected $_jsMinifier = null;
195
-
196
- protected function _removePreCB($m)
197
- {
198
- return $this->_reservePlace("<pre{$m[1]}");
199
- }
200
-
201
- protected function _removeTextareaCB($m)
202
- {
203
- return $this->_reservePlace("<textarea{$m[1]}");
204
- }
205
-
206
- protected function _removeStyleCB($m)
207
- {
208
- $openStyle = "<style{$m[1]}";
209
- $css = $m[2];
210
- // remove HTML comments
211
- $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
212
-
213
- // remove CDATA section markers
214
- $css = $this->_removeCdata($css);
215
-
216
- // minify
217
- $minifier = $this->_cssMinifier
218
- ? $this->_cssMinifier
219
- : 'trim';
220
- $css = call_user_func($minifier, $css);
221
-
222
- return $this->_reservePlace($this->_needsCdata($css)
223
- ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
224
- : "{$openStyle}{$css}</style>"
225
- );
226
- }
227
-
228
- protected function _removeScriptCB($m)
229
- {
230
- $openScript = "<script{$m[2]}";
231
- $js = $m[3];
232
-
233
- // whitespace surrounding? preserve at least one space
234
- $ws1 = ($m[1] === '') ? '' : ' ';
235
- $ws2 = ($m[4] === '') ? '' : ' ';
236
-
237
- // remove HTML comments (and ending "//" if present)
238
- if ($this->_jsCleanComments) {
239
- $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
240
- }
241
-
242
- // remove CDATA section markers
243
- $js = $this->_removeCdata($js);
244
-
245
- if (false !== stripos($openScript, '<script type="text/template"')) {
246
-
247
- // minify
248
- $js = Minify_HTMLMaxComp::minify($js);
249
-
250
- } else {
251
-
252
- // minify
253
- $minifier = $this->_jsMinifier
254
- ? $this->_jsMinifier
255
- : 'trim';
256
- $js = call_user_func($minifier, $js);
257
-
258
- }
259
-
260
- return $this->_reservePlace($this->_needsCdata($js)
261
- ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
262
- : "{$ws1}{$openScript}{$js}</script>{$ws2}"
263
- );
264
- }
265
-
266
- protected function _removeCdata($str)
267
- {
268
- return (false !== strpos($str, '<![CDATA['))
269
- ? str_replace(array('<![CDATA[', ']]>'), '', $str)
270
- : $str;
271
- }
272
-
273
- protected function _needsCdata($str)
274
- {
275
- return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
276
- }
277
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/ImportProcessor.php DELETED
@@ -1,216 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_ImportProcessor
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Linearize a CSS/JS file by including content specified by CSS import
9
- * declarations. In CSS files, relative URIs are fixed.
10
- *
11
- * @imports will be processed regardless of where they appear in the source
12
- * files; i.e. @imports commented out or in string content will still be
13
- * processed!
14
- *
15
- * This has a unit test but should be considered "experimental".
16
- *
17
- * @package Minify
18
- * @author Stephen Clay <steve@mrclay.org>
19
- * @author Simon Schick <simonsimcity@gmail.com>
20
- */
21
- class Minify_ImportProcessor {
22
-
23
- public static $filesIncluded = array();
24
-
25
- public static function process($file)
26
- {
27
- self::$filesIncluded = array();
28
- self::$_isCss = (strtolower(substr($file, -4)) === '.css');
29
- $obj = new Minify_ImportProcessor(dirname($file));
30
- return $obj->_getContent($file);
31
- }
32
-
33
- // allows callback funcs to know the current directory
34
- private $_currentDir = null;
35
-
36
- // allows callback funcs to know the directory of the file that inherits this one
37
- private $_previewsDir = null;
38
-
39
- // allows _importCB to write the fetched content back to the obj
40
- private $_importedContent = '';
41
-
42
- private static $_isCss = null;
43
-
44
- /**
45
- * @param String $currentDir
46
- * @param String $previewsDir Is only used internally
47
- */
48
- private function __construct($currentDir, $previewsDir = "")
49
- {
50
- $this->_currentDir = $currentDir;
51
- $this->_previewsDir = $previewsDir;
52
- }
53
-
54
- private function _getContent($file, $is_imported = false)
55
- {
56
- $file = realpath($file);
57
- if (! $file
58
- || in_array($file, self::$filesIncluded)
59
- || false === ($content = @file_get_contents($file))
60
- ) {
61
- // file missing, already included, or failed read
62
- return '';
63
- }
64
- self::$filesIncluded[] = realpath($file);
65
- $this->_currentDir = dirname($file);
66
-
67
- // remove UTF-8 BOM if present
68
- if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) {
69
- $content = substr($content, 3);
70
- }
71
- // ensure uniform EOLs
72
- $content = str_replace("\r\n", "\n", $content);
73
-
74
- // process @imports
75
- $content = preg_replace_callback(
76
- '/
77
- @import\\s+
78
- (?:url\\(\\s*)? # maybe url(
79
- [\'"]? # maybe quote
80
- (.*?) # 1 = URI
81
- [\'"]? # maybe end quote
82
- (?:\\s*\\))? # maybe )
83
- ([a-zA-Z,\\s]*)? # 2 = media list
84
- ; # end token
85
- /x'
86
- ,array($this, '_importCB')
87
- ,$content
88
- );
89
-
90
- // You only need to rework the import-path if the script is imported
91
- if (self::$_isCss && $is_imported) {
92
- // rewrite remaining relative URIs
93
- $content = preg_replace_callback(
94
- '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
95
- ,array($this, '_urlCB')
96
- ,$content
97
- );
98
- }
99
-
100
- return $this->_importedContent . $content;
101
- }
102
-
103
- private function _importCB($m)
104
- {
105
- $url = $m[1];
106
- $mediaList = preg_replace('/\\s+/', '', $m[2]);
107
-
108
- if (strpos($url, '://') > 0) {
109
- // protocol, leave in place for CSS, comment for JS
110
- return self::$_isCss
111
- ? $m[0]
112
- : "/* Minify_ImportProcessor will not include remote content */";
113
- }
114
- if ('/' === $url[0]) {
115
- // protocol-relative or root path
116
- $url = ltrim($url, '/');
117
- $file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR
118
- . strtr($url, '/', DIRECTORY_SEPARATOR);
119
- } else {
120
- // relative to current path
121
- $file = $this->_currentDir . DIRECTORY_SEPARATOR
122
- . strtr($url, '/', DIRECTORY_SEPARATOR);
123
- }
124
- $obj = new Minify_ImportProcessor(dirname($file), $this->_currentDir);
125
- $content = $obj->_getContent($file, true);
126
- if ('' === $content) {
127
- // failed. leave in place for CSS, comment for JS
128
- return self::$_isCss
129
- ? $m[0]
130
- : "/* Minify_ImportProcessor could not fetch '{$file}' */";
131
- }
132
- return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList))
133
- ? $content
134
- : "@media {$mediaList} {\n{$content}\n}\n";
135
- }
136
-
137
- private function _urlCB($m)
138
- {
139
- // $m[1] is either quoted or not
140
- $quote = ($m[1][0] === "'" || $m[1][0] === '"')
141
- ? $m[1][0]
142
- : '';
143
- $url = ($quote === '')
144
- ? $m[1]
145
- : substr($m[1], 1, strlen($m[1]) - 2);
146
- if ('/' !== $url[0]) {
147
- if (strpos($url, '//') > 0) {
148
- // probably starts with protocol, do not alter
149
- } else {
150
- // prepend path with current dir separator (OS-independent)
151
- $path = $this->_currentDir
152
- . DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR);
153
- // update the relative path by the directory of the file that imported this one
154
- $url = self::getPathDiff(realpath($this->_previewsDir), $path);
155
- }
156
- }
157
- return "url({$quote}{$url}{$quote})";
158
- }
159
-
160
- /**
161
- * @param string $from
162
- * @param string $to
163
- * @param string $ps
164
- * @return string
165
- */
166
- private function getPathDiff($from, $to, $ps = DIRECTORY_SEPARATOR)
167
- {
168
- $realFrom = $this->truepath($from);
169
- $realTo = $this->truepath($to);
170
-
171
- $arFrom = explode($ps, rtrim($realFrom, $ps));
172
- $arTo = explode($ps, rtrim($realTo, $ps));
173
- while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0]))
174
- {
175
- array_shift($arFrom);
176
- array_shift($arTo);
177
- }
178
- return str_pad("", count($arFrom) * 3, '..' . $ps) . implode($ps, $arTo);
179
- }
180
-
181
- /**
182
- * This function is to replace PHP's extremely buggy realpath().
183
- * @param string $path The original path, can be relative etc.
184
- * @return string The resolved path, it might not exist.
185
- * @see http://stackoverflow.com/questions/4049856/replace-phps-realpath
186
- */
187
- function truepath($path)
188
- {
189
- // whether $path is unix or not
190
- $unipath = strlen($path) == 0 || $path{0} != '/';
191
- // attempts to detect if path is relative in which case, add cwd
192
- if (strpos($path, ':') === false && $unipath)
193
- $path = $this->_currentDir . DIRECTORY_SEPARATOR . $path;
194
-
195
- // resolve path parts (single dot, double dot and double delimiters)
196
- $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
197
- $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
198
- $absolutes = array();
199
- foreach ($parts as $part) {
200
- if ('.' == $part)
201
- continue;
202
- if ('..' == $part) {
203
- array_pop($absolutes);
204
- } else {
205
- $absolutes[] = $part;
206
- }
207
- }
208
- $path = implode(DIRECTORY_SEPARATOR, $absolutes);
209
- // resolve any symlinks
210
- if (file_exists($path) && linkinfo($path) > 0)
211
- $path = readlink($path);
212
- // put initial separator that could have been lost
213
- $path = !$unipath ? '/' . $path : $path;
214
- return $path;
215
- }
216
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/JS/ClosureCompiler.php DELETED
@@ -1,230 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_JS_ClosureCompiler
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Minify Javascript using Google's Closure Compiler API
9
- *
10
- * @link http://code.google.com/closure/compiler/
11
- * @package Minify
12
- * @author Stephen Clay <steve@mrclay.org>
13
- *
14
- * @todo can use a stream wrapper to unit test this?
15
- */
16
- class Minify_JS_ClosureCompiler {
17
-
18
- /**
19
- * @var string The option key for the maximum POST byte size
20
- */
21
- const OPTION_MAX_BYTES = 'maxBytes';
22
-
23
- /**
24
- * @var string The option key for additional params. @see __construct
25
- */
26
- const OPTION_ADDITIONAL_OPTIONS = 'additionalParams';
27
-
28
- /**
29
- * @var string The option key for the fallback Minifier
30
- */
31
- const OPTION_FALLBACK_FUNCTION = 'fallbackFunc';
32
-
33
- /**
34
- * @var string The option key for the service URL
35
- */
36
- const OPTION_COMPILER_URL = 'compilerUrl';
37
-
38
- /**
39
- * @var int The default maximum POST byte size according to https://developers.google.com/closure/compiler/docs/api-ref
40
- */
41
- const DEFAULT_MAX_BYTES = 200000;
42
-
43
- /**
44
- * @var string[] $DEFAULT_OPTIONS The default options to pass to the compiler service
45
- *
46
- * @note This would be a constant if PHP allowed it
47
- */
48
- private static $DEFAULT_OPTIONS = array(
49
- 'output_format' => 'text',
50
- 'compilation_level' => 'SIMPLE_OPTIMIZATIONS'
51
- );
52
-
53
- /**
54
- * @var string $url URL of compiler server. defaults to Google's
55
- */
56
- protected $serviceUrl = 'http://closure-compiler.appspot.com/compile';
57
-
58
- /**
59
- * @var int $maxBytes The maximum JS size that can be sent to the compiler server in bytes
60
- */
61
- protected $maxBytes = self::DEFAULT_MAX_BYTES;
62
-
63
- /**
64
- * @var string[] $additionalOptions Additional options to pass to the compiler service
65
- */
66
- protected $additionalOptions = array();
67
-
68
- /**
69
- * @var callable Function to minify JS if service fails. Default is JSMin
70
- */
71
- protected $fallbackMinifier = array('JSMin', 'minify');
72
-
73
- /**
74
- * Minify JavaScript code via HTTP request to a Closure Compiler API
75
- *
76
- * @param string $js input code
77
- * @param array $options Options passed to __construct(). @see __construct
78
- *
79
- * @return string
80
- */
81
- public static function minify($js, array $options = array())
82
- {
83
- $obj = new self($options);
84
- return $obj->min($js);
85
- }
86
-
87
- /**
88
- * @param array $options Options with keys available below:
89
- *
90
- * fallbackFunc : (callable) function to minify if service unavailable. Default is JSMin.
91
- *
92
- * compilerUrl : (string) URL to closure compiler server
93
- *
94
- * maxBytes : (int) The maximum amount of bytes to be sent as js_code in the POST request.
95
- * Defaults to 200000.
96
- *
97
- * additionalParams : (string[]) Additional parameters to pass to the compiler server. Can be anything named
98
- * in https://developers.google.com/closure/compiler/docs/api-ref except for js_code and
99
- * output_info
100
- */
101
- public function __construct(array $options = array())
102
- {
103
- if (isset($options[self::OPTION_FALLBACK_FUNCTION])) {
104
- $this->fallbackMinifier = $options[self::OPTION_FALLBACK_FUNCTION];
105
- }
106
- if (isset($options[self::OPTION_COMPILER_URL])) {
107
- $this->serviceUrl = $options[self::OPTION_COMPILER_URL];
108
- }
109
- if (isset($options[self::OPTION_ADDITIONAL_OPTIONS]) && is_array($options[self::OPTION_ADDITIONAL_OPTIONS])) {
110
- $this->additionalOptions = $options[self::OPTION_ADDITIONAL_OPTIONS];
111
- }
112
- if (isset($options[self::OPTION_MAX_BYTES])) {
113
- $this->maxBytes = (int) $options[self::OPTION_MAX_BYTES];
114
- }
115
- }
116
-
117
- /**
118
- * Call the service to perform the minification
119
- *
120
- * @param string $js JavaScript code
121
- * @return string
122
- * @throws Minify_JS_ClosureCompiler_Exception
123
- */
124
- public function min($js)
125
- {
126
- $postBody = $this->buildPostBody($js);
127
-
128
- if ($this->maxBytes > 0) {
129
- $bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
130
- ? mb_strlen($postBody, '8bit')
131
- : strlen($postBody);
132
- if ($bytes > $this->maxBytes) {
133
- throw new Minify_JS_ClosureCompiler_Exception(
134
- 'POST content larger than ' . $this->maxBytes . ' bytes'
135
- );
136
- }
137
- }
138
-
139
- $response = $this->getResponse($postBody);
140
-
141
- if (preg_match('/^Error\(\d\d?\):/', $response)) {
142
- if (is_callable($this->fallbackMinifier)) {
143
- // use fallback
144
- $response = "/* Received errors from Closure Compiler API:\n$response"
145
- . "\n(Using fallback minifier)\n*/\n";
146
- $response .= call_user_func($this->fallbackMinifier, $js);
147
- } else {
148
- throw new Minify_JS_ClosureCompiler_Exception($response);
149
- }
150
- }
151
-
152
- if ($response === '') {
153
- $errors = $this->getResponse($this->buildPostBody($js, true));
154
- throw new Minify_JS_ClosureCompiler_Exception($errors);
155
- }
156
-
157
- return $response;
158
- }
159
-
160
- /**
161
- * Get the response for a given POST body
162
- *
163
- * @param string $postBody
164
- * @return string
165
- * @throws Minify_JS_ClosureCompiler_Exception
166
- */
167
- protected function getResponse($postBody)
168
- {
169
- $allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
170
-
171
- if ($allowUrlFopen) {
172
- $contents = file_get_contents($this->serviceUrl, false, stream_context_create(array(
173
- 'http' => array(
174
- 'method' => 'POST',
175
- 'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close\r\n",
176
- 'content' => $postBody,
177
- 'max_redirects' => 0,
178
- 'timeout' => 15,
179
- )
180
- )));
181
- } elseif (defined('CURLOPT_POST')) {
182
- $ch = curl_init($this->serviceUrl);
183
- curl_setopt($ch, CURLOPT_POST, true);
184
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
185
- curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded'));
186
- curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
187
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
188
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
189
- $contents = curl_exec($ch);
190
- curl_close($ch);
191
- } else {
192
- throw new Minify_JS_ClosureCompiler_Exception(
193
- "Could not make HTTP request: allow_url_open is false and cURL not available"
194
- );
195
- }
196
-
197
- if (false === $contents) {
198
- throw new Minify_JS_ClosureCompiler_Exception(
199
- "No HTTP response from server"
200
- );
201
- }
202
-
203
- return trim($contents);
204
- }
205
-
206
- /**
207
- * Build a POST request body
208
- *
209
- * @param string $js JavaScript code
210
- * @param bool $returnErrors
211
- * @return string
212
- */
213
- protected function buildPostBody($js, $returnErrors = false)
214
- {
215
- return http_build_query(
216
- array_merge(
217
- self::$DEFAULT_OPTIONS,
218
- $this->additionalOptions,
219
- array(
220
- 'js_code' => $js,
221
- 'output_info' => ($returnErrors ? 'errors' : 'compiled_code')
222
- )
223
- ),
224
- null,
225
- '&'
226
- );
227
- }
228
- }
229
-
230
- class Minify_JS_ClosureCompiler_Exception extends Exception {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Lines.php DELETED
@@ -1,143 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Lines
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Add line numbers in C-style comments for easier debugging of combined content
9
- *
10
- * @package Minify
11
- * @author Stephen Clay <steve@mrclay.org>
12
- * @author Adam Pedersen (Issue 55 fix)
13
- */
14
- class Minify_Lines {
15
-
16
- /**
17
- * Add line numbers in C-style comments
18
- *
19
- * This uses a very basic parser easily fooled by comment tokens inside
20
- * strings or regexes, but, otherwise, generally clean code will not be
21
- * mangled. URI rewriting can also be performed.
22
- *
23
- * @param string $content
24
- *
25
- * @param array $options available options:
26
- *
27
- * 'id': (optional) string to identify file. E.g. file name/path
28
- *
29
- * 'currentDir': (default null) if given, this is assumed to be the
30
- * directory of the current CSS file. Using this, minify will rewrite
31
- * all relative URIs in import/url declarations to correctly point to
32
- * the desired files, and prepend a comment with debugging information about
33
- * this process.
34
- *
35
- * @return string
36
- */
37
- public static function minify($content, $options = array())
38
- {
39
- $id = (isset($options['id']) && $options['id'])
40
- ? $options['id']
41
- : '';
42
- $content = str_replace("\r\n", "\n", $content);
43
-
44
- // Hackily rewrite strings with XPath expressions that are
45
- // likely to throw off our dumb parser (for Prototype 1.6.1).
46
- $content = str_replace('"/*"', '"/"+"*"', $content);
47
- $content = preg_replace('@([\'"])(\\.?//?)\\*@', '$1$2$1+$1*', $content);
48
-
49
- $lines = explode("\n", $content);
50
- $numLines = count($lines);
51
- // determine left padding
52
- $padTo = strlen((string) $numLines); // e.g. 103 lines = 3 digits
53
- $inComment = false;
54
- $i = 0;
55
- $newLines = array();
56
- while (null !== ($line = array_shift($lines))) {
57
- if (('' !== $id) && (0 == $i % 50)) {
58
- if ($inComment) {
59
- array_push($newLines, '', "/* {$id} *|", '');
60
- } else {
61
- array_push($newLines, '', "/* {$id} */", '');
62
- }
63
- }
64
- ++$i;
65
- $newLines[] = self::_addNote($line, $i, $inComment, $padTo);
66
- $inComment = self::_eolInComment($line, $inComment);
67
- }
68
- $content = implode("\n", $newLines) . "\n";
69
-
70
- // check for desired URI rewriting
71
- if (isset($options['currentDir'])) {
72
- Minify_CSS_UriRewriter::$debugText = '';
73
- $content = Minify_CSS_UriRewriter::rewrite(
74
- $content
75
- ,$options['currentDir']
76
- ,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
77
- ,isset($options['symlinks']) ? $options['symlinks'] : array()
78
- );
79
- $content = "/* Minify_CSS_UriRewriter::\$debugText\n\n"
80
- . Minify_CSS_UriRewriter::$debugText . "*/\n"
81
- . $content;
82
- }
83
-
84
- return $content;
85
- }
86
-
87
- /**
88
- * Is the parser within a C-style comment at the end of this line?
89
- *
90
- * @param string $line current line of code
91
- *
92
- * @param bool $inComment was the parser in a comment at the
93
- * beginning of the line?
94
- *
95
- * @return bool
96
- */
97
- private static function _eolInComment($line, $inComment)
98
- {
99
- // crude way to avoid things like // */
100
- $line = preg_replace('~//.*?(\\*/|/\\*).*~', '', $line);
101
-
102
- while (strlen($line)) {
103
- $search = $inComment
104
- ? '*/'
105
- : '/*';
106
- $pos = strpos($line, $search);
107
- if (false === $pos) {
108
- return $inComment;
109
- } else {
110
- if ($pos == 0
111
- || ($inComment
112
- ? substr($line, $pos, 3)
113
- : substr($line, $pos-1, 3)) != '*/*')
114
- {
115
- $inComment = ! $inComment;
116
- }
117
- $line = substr($line, $pos + 2);
118
- }
119
- }
120
- return $inComment;
121
- }
122
-
123
- /**
124
- * Prepend a comment (or note) to the given line
125
- *
126
- * @param string $line current line of code
127
- *
128
- * @param string $note content of note/comment
129
- *
130
- * @param bool $inComment was the parser in a comment at the
131
- * beginning of the line?
132
- *
133
- * @param int $padTo minimum width of comment
134
- *
135
- * @return string
136
- */
137
- private static function _addNote($line, $note, $inComment, $padTo)
138
- {
139
- return $inComment
140
- ? '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' *| ' . $line
141
- : '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' */ ' . $line;
142
- }
143
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Loader.php DELETED
@@ -1,28 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Loader
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Class autoloader
9
- *
10
- * @package Minify
11
- * @author Stephen Clay <steve@mrclay.org>
12
- */
13
- class Minify_Loader {
14
- public function loadClass($class)
15
- {
16
- $file = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR;
17
- $file .= strtr($class, "\\_", DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR) . '.php';
18
- if (is_readable($file)) {
19
- require $file;
20
- }
21
- }
22
-
23
- static public function register()
24
- {
25
- $inst = new self();
26
- spl_autoload_register(array($inst, 'loadClass'));
27
- }
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Logger.php DELETED
@@ -1,47 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Logger
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Message logging class
9
- *
10
- * @package Minify
11
- * @author Stephen Clay <steve@mrclay.org>
12
- *
13
- * @todo lose this singleton! pass log object in Minify::serve and distribute to others
14
- */
15
- class Minify_Logger {
16
-
17
- /**
18
- * Set logger object.
19
- *
20
- * The object should have a method "log" that accepts a value as 1st argument and
21
- * an optional string label as the 2nd.
22
- *
23
- * @param mixed $obj or a "falsey" value to disable
24
- * @return null
25
- */
26
- public static function setLogger($obj = null) {
27
- self::$_logger = $obj
28
- ? $obj
29
- : null;
30
- }
31
-
32
- /**
33
- * Pass a message to the logger (if set)
34
- *
35
- * @param string $msg message to log
36
- * @return null
37
- */
38
- public static function log($msg, $label = 'Minify') {
39
- if (! self::$_logger) return;
40
- self::$_logger->log($msg, $label);
41
- }
42
-
43
- /**
44
- * @var mixed logger object (like FirePHP) or null (i.e. no logger available)
45
- */
46
- private static $_logger = null;
47
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Packer.php DELETED
@@ -1,39 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Packer
4
- *
5
- * To use this class you must first download the PHP port of Packer
6
- * and place the file "class.JavaScriptPacker.php" in /lib (or your
7
- * include_path).
8
- * @link http://joliclic.free.fr/php/javascript-packer/en/
9
- *
10
- * Be aware that, as long as HTTP encoding is used, scripts minified with JSMin
11
- * will provide better client-side performance, as they need not be unpacked in
12
- * client-side code.
13
- *
14
- * @package Minify
15
- */
16
-
17
- /*
18
- if (false === (@include 'class.JavaScriptPacker.php')) {
19
- trigger_error(
20
- 'The script "class.JavaScriptPacker.php" is required. Please see: http:'
21
- .'//code.google.com/p/minify/source/browse/trunk/min/lib/Minify/Packer.php'
22
- ,E_USER_ERROR
23
- );
24
- }
25
- */
26
-
27
- /**
28
- * Minify Javascript using Dean Edward's Packer
29
- *
30
- * @package Minify
31
- */
32
- class Minify_Packer {
33
- public static function minify($code, $options = array())
34
- {
35
- // @todo: set encoding options based on $options :)
36
- $packer = new JavascriptPacker($code, 'Normal', true, false);
37
- return trim($packer->pack());
38
- }
39
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/Source.php DELETED
@@ -1,187 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_Source
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * A content source to be minified by Minify.
9
- *
10
- * This allows per-source minification options and the mixing of files with
11
- * content from other sources.
12
- *
13
- * @package Minify
14
- * @author Stephen Clay <steve@mrclay.org>
15
- */
16
- class Minify_Source {
17
-
18
- /**
19
- * @var int time of last modification
20
- */
21
- public $lastModified = null;
22
-
23
- /**
24
- * @var callback minifier function specifically for this source.
25
- */
26
- public $minifier = null;
27
-
28
- /**
29
- * @var array minification options specific to this source.
30
- */
31
- public $minifyOptions = null;
32
-
33
- /**
34
- * @var string full path of file
35
- */
36
- public $filepath = null;
37
-
38
- /**
39
- * @var string HTTP Content Type (Minify requires one of the constants Minify::TYPE_*)
40
- */
41
- public $contentType = null;
42
-
43
- /**
44
- * Create a Minify_Source
45
- *
46
- * In the $spec array(), you can either provide a 'filepath' to an existing
47
- * file (existence will not be checked!) or give 'id' (unique string for
48
- * the content), 'content' (the string content) and 'lastModified'
49
- * (unixtime of last update).
50
- *
51
- * As a shortcut, the controller will replace "//" at the beginning
52
- * of a filepath with $_SERVER['DOCUMENT_ROOT'] . '/'.
53
- *
54
- * @param array $spec options
55
- */
56
- public function __construct($spec)
57
- {
58
- if (isset($spec['filepath'])) {
59
- if (0 === strpos($spec['filepath'], '//')) {
60
- $spec['filepath'] = $_SERVER['DOCUMENT_ROOT'] . substr($spec['filepath'], 1);
61
- }
62
- $segments = explode('.', $spec['filepath']);
63
- $ext = strtolower(array_pop($segments));
64
- switch ($ext) {
65
- case 'js' : $this->contentType = 'application/x-javascript';
66
- break;
67
- case 'css' : $this->contentType = 'text/css';
68
- break;
69
- case 'htm' : // fallthrough
70
- case 'html' : $this->contentType = 'text/html';
71
- break;
72
- }
73
- $this->filepath = $spec['filepath'];
74
- $this->_id = $spec['filepath'];
75
- $this->lastModified = filemtime($spec['filepath'])
76
- // offset for Windows uploaders with out of sync clocks
77
- + round(Minify::$uploaderHoursBehind * 3600);
78
- } elseif (isset($spec['id'])) {
79
- $this->_id = 'id::' . $spec['id'];
80
- if (isset($spec['content'])) {
81
- $this->_content = $spec['content'];
82
- } else {
83
- $this->_getContentFunc = $spec['getContentFunc'];
84
- }
85
- $this->lastModified = isset($spec['lastModified'])
86
- ? $spec['lastModified']
87
- : time();
88
- }
89
- if (isset($spec['contentType'])) {
90
- $this->contentType = $spec['contentType'];
91
- }
92
- if (isset($spec['minifier'])) {
93
- $this->minifier = $spec['minifier'];
94
- }
95
- if (isset($spec['minifyOptions'])) {
96
- $this->minifyOptions = $spec['minifyOptions'];
97
- }
98
- }
99
-
100
- /**
101
- * Get content
102
- *
103
- * @return string
104
- */
105
- public function getContent()
106
- {
107
- $content = (null !== $this->filepath)
108
- ? file_get_contents($this->filepath)
109
- : ((null !== $this->_content)
110
- ? $this->_content
111
- : call_user_func($this->_getContentFunc, $this->_id)
112
- );
113
- // remove UTF-8 BOM if present
114
- return (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3))
115
- ? substr($content, 3)
116
- : $content;
117
- }
118
-
119
- /**
120
- * Get id
121
- *
122
- * @return string
123
- */
124
- public function getId()
125
- {
126
- return $this->_id;
127
- }
128
-
129
- /**
130
- * Verifies a single minification call can handle all sources
131
- *
132
- * @param array $sources Minify_Source instances
133
- *
134
- * @return bool true iff there no sources with specific minifier preferences.
135
- */
136
- public static function haveNoMinifyPrefs($sources)
137
- {
138
- foreach ($sources as $source) {
139
- if (null !== $source->minifier
140
- || null !== $source->minifyOptions) {
141
- return false;
142
- }
143
- }
144
- return true;
145
- }
146
-
147
- /**
148
- * Get unique string for a set of sources
149
- *
150
- * @param array $sources Minify_Source instances
151
- *
152
- * @return string
153
- */
154
- public static function getDigest($sources)
155
- {
156
- foreach ($sources as $source) {
157
- $info[] = array(
158
- $source->_id, $source->minifier, $source->minifyOptions
159
- );
160
- }
161
- return md5(serialize($info));
162
- }
163
-
164
- /**
165
- * Get content type from a group of sources
166
- *
167
- * This is called if the user doesn't pass in a 'contentType' options
168
- *
169
- * @param array $sources Minify_Source instances
170
- *
171
- * @return string content type. e.g. 'text/css'
172
- */
173
- public static function getContentType($sources)
174
- {
175
- foreach ($sources as $source) {
176
- if ($source->contentType !== null) {
177
- return $source->contentType;
178
- }
179
- }
180
- return 'text/plain';
181
- }
182
-
183
- protected $_content = null;
184
- protected $_getContentFunc = null;
185
- protected $_id = null;
186
- }
187
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/YUI/CssCompressor.java DELETED
@@ -1,382 +0,0 @@
1
- /*
2
- * YUI Compressor
3
- * http://developer.yahoo.com/yui/compressor/
4
- * Author: Julien Lecomte - http://www.julienlecomte.net/
5
- * Author: Isaac Schlueter - http://foohack.com/
6
- * Author: Stoyan Stefanov - http://phpied.com/
7
- * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
8
- * The copyrights embodied in the content of this file are licensed
9
- * by Yahoo! Inc. under the BSD (revised) open source license.
10
- */
11
- package com.yahoo.platform.yui.compressor;
12
-
13
- import java.io.IOException;
14
- import java.io.Reader;
15
- import java.io.Writer;
16
- import java.util.regex.Pattern;
17
- import java.util.regex.Matcher;
18
- import java.util.ArrayList;
19
-
20
- public class CssCompressor {
21
-
22
- private StringBuffer srcsb = new StringBuffer();
23
-
24
- public CssCompressor(Reader in) throws IOException {
25
- // Read the stream...
26
- int c;
27
- while ((c = in.read()) != -1) {
28
- srcsb.append((char) c);
29
- }
30
- }
31
-
32
- // Leave data urls alone to increase parse performance.
33
- protected String extractDataUrls(String css, ArrayList preservedTokens) {
34
-
35
- int maxIndex = css.length() - 1;
36
- int appendIndex = 0;
37
-
38
- StringBuffer sb = new StringBuffer();
39
-
40
- Pattern p = Pattern.compile("url\\(\\s*([\"']?)data\\:");
41
- Matcher m = p.matcher(css);
42
-
43
- /*
44
- * Since we need to account for non-base64 data urls, we need to handle
45
- * ' and ) being part of the data string. Hence switching to indexOf,
46
- * to determine whether or not we have matching string terminators and
47
- * handling sb appends directly, instead of using matcher.append* methods.
48
- */
49
-
50
- while (m.find()) {
51
-
52
- int startIndex = m.start() + 4; // "url(".length()
53
- String terminator = m.group(1); // ', " or empty (not quoted)
54
-
55
- if (terminator.length() == 0) {
56
- terminator = ")";
57
- }
58
-
59
- boolean foundTerminator = false;
60
-
61
- int endIndex = m.end() - 1;
62
- while(foundTerminator == false && endIndex+1 <= maxIndex) {
63
- endIndex = css.indexOf(terminator, endIndex+1);
64
-
65
- if ((endIndex > 0) && (css.charAt(endIndex-1) != '\\')) {
66
- foundTerminator = true;
67
- if (!")".equals(terminator)) {
68
- endIndex = css.indexOf(")", endIndex);
69
- }
70
- }
71
- }
72
-
73
- // Enough searching, start moving stuff over to the buffer
74
- sb.append(css.substring(appendIndex, m.start()));
75
-
76
- if (foundTerminator) {
77
- String token = css.substring(startIndex, endIndex);
78
- token = token.replaceAll("\\s+", "");
79
- preservedTokens.add(token);
80
-
81
- String preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)";
82
- sb.append(preserver);
83
-
84
- appendIndex = endIndex + 1;
85
- } else {
86
- // No end terminator found, re-add the whole match. Should we throw/warn here?
87
- sb.append(css.substring(m.start(), m.end()));
88
- appendIndex = m.end();
89
- }
90
- }
91
-
92
- sb.append(css.substring(appendIndex));
93
-
94
- return sb.toString();
95
- }
96
-
97
- public void compress(Writer out, int linebreakpos)
98
- throws IOException {
99
-
100
- Pattern p;
101
- Matcher m;
102
- String css = srcsb.toString();
103
-
104
- int startIndex = 0;
105
- int endIndex = 0;
106
- int i = 0;
107
- int max = 0;
108
- ArrayList preservedTokens = new ArrayList(0);
109
- ArrayList comments = new ArrayList(0);
110
- String token;
111
- int totallen = css.length();
112
- String placeholder;
113
-
114
- css = this.extractDataUrls(css, preservedTokens);
115
-
116
- StringBuffer sb = new StringBuffer(css);
117
-
118
- // collect all comment blocks...
119
- while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) {
120
- endIndex = sb.indexOf("*/", startIndex + 2);
121
- if (endIndex < 0) {
122
- endIndex = totallen;
123
- }
124
-
125
- token = sb.substring(startIndex + 2, endIndex);
126
- comments.add(token);
127
- sb.replace(startIndex + 2, endIndex, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___");
128
- startIndex += 2;
129
- }
130
- css = sb.toString();
131
-
132
- // preserve strings so their content doesn't get accidentally minified
133
- sb = new StringBuffer();
134
- p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')");
135
- m = p.matcher(css);
136
- while (m.find()) {
137
- token = m.group();
138
- char quote = token.charAt(0);
139
- token = token.substring(1, token.length() - 1);
140
-
141
- // maybe the string contains a comment-like substring?
142
- // one, maybe more? put'em back then
143
- if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) {
144
- for (i = 0, max = comments.size(); i < max; i += 1) {
145
- token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i).toString());
146
- }
147
- }
148
-
149
- // minify alpha opacity in filter strings
150
- token = token.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
151
-
152
- preservedTokens.add(token);
153
- String preserver = quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote;
154
- m.appendReplacement(sb, preserver);
155
- }
156
- m.appendTail(sb);
157
- css = sb.toString();
158
-
159
-
160
- // strings are safe, now wrestle the comments
161
- for (i = 0, max = comments.size(); i < max; i += 1) {
162
-
163
- token = comments.get(i).toString();
164
- placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
165
-
166
- // ! in the first position of the comment means preserve
167
- // so push to the preserved tokens while stripping the !
168
- if (token.startsWith("!")) {
169
- preservedTokens.add(token);
170
- css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
171
- continue;
172
- }
173
-
174
- // \ in the last position looks like hack for Mac/IE5
175
- // shorten that to /*\*/ and the next one to /**/
176
- if (token.endsWith("\\")) {
177
- preservedTokens.add("\\");
178
- css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
179
- i = i + 1; // attn: advancing the loop
180
- preservedTokens.add("");
181
- css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
182
- continue;
183
- }
184
-
185
- // keep empty comments after child selectors (IE7 hack)
186
- // e.g. html >/**/ body
187
- if (token.length() == 0) {
188
- startIndex = css.indexOf(placeholder);
189
- if (startIndex > 2) {
190
- if (css.charAt(startIndex - 3) == '>') {
191
- preservedTokens.add("");
192
- css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
193
- }
194
- }
195
- }
196
-
197
- // in all other cases kill the comment
198
- css = css.replace("/*" + placeholder + "*/", "");
199
- }
200
-
201
-
202
- // Normalize all whitespace strings to single spaces. Easier to work with that way.
203
- css = css.replaceAll("\\s+", " ");
204
-
205
- // Remove the spaces before the things that should not have spaces before them.
206
- // But, be careful not to turn "p :link {...}" into "p:link{...}"
207
- // Swap out any pseudo-class colons with the token, and then swap back.
208
- sb = new StringBuffer();
209
- p = Pattern.compile("(^|\\})(([^\\{:])+:)+([^\\{]*\\{)");
210
- m = p.matcher(css);
211
- while (m.find()) {
212
- String s = m.group();
213
- s = s.replaceAll(":", "___YUICSSMIN_PSEUDOCLASSCOLON___");
214
- s = s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\\$", "\\\\\\$" );
215
- m.appendReplacement(sb, s);
216
- }
217
- m.appendTail(sb);
218
- css = sb.toString();
219
- // Remove spaces before the things that should not have spaces before them.
220
- css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1");
221
- // bring back the colon
222
- css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":");
223
-
224
- // retain space for special IE6 cases
225
- css = css.replaceAll(":first\\-(line|letter)(\\{|,)", ":first-$1 $2");
226
-
227
- // no space after the end of a preserved comment
228
- css = css.replaceAll("\\*/ ", "*/");
229
-
230
- // If there is a @charset, then only allow one, and push to the top of the file.
231
- css = css.replaceAll("^(.*)(@charset \"[^\"]*\";)", "$2$1");
232
- css = css.replaceAll("^(\\s*@charset [^;]+;\\s*)+", "$1");
233
-
234
- // Put the space back in some cases, to support stuff like
235
- // @media screen and (-webkit-min-device-pixel-ratio:0){
236
- css = css.replaceAll("\\band\\(", "and (");
237
-
238
- // Remove the spaces after the things that should not have spaces after them.
239
- css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");
240
-
241
- // remove unnecessary semicolons
242
- css = css.replaceAll(";+}", "}");
243
-
244
- // Replace 0(px,em,%) with 0.
245
- css = css.replaceAll("([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", "$1$2");
246
-
247
- // Replace 0 0 0 0; with 0.
248
- css = css.replaceAll(":0 0 0 0(;|})", ":0$1");
249
- css = css.replaceAll(":0 0 0(;|})", ":0$1");
250
- css = css.replaceAll(":0 0(;|})", ":0$1");
251
-
252
-
253
- // Replace background-position:0; with background-position:0 0;
254
- // same for transform-origin
255
- sb = new StringBuffer();
256
- p = Pattern.compile("(?i)(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|})");
257
- m = p.matcher(css);
258
- while (m.find()) {
259
- m.appendReplacement(sb, m.group(1).toLowerCase() + ":0 0" + m.group(2));
260
- }
261
- m.appendTail(sb);
262
- css = sb.toString();
263
-
264
- // Replace 0.6 to .6, but only when preceded by : or a white-space
265
- css = css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2");
266
-
267
- // Shorten colors from rgb(51,102,153) to #336699
268
- // This makes it more likely that it'll get further compressed in the next step.
269
- p = Pattern.compile("rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)");
270
- m = p.matcher(css);
271
- sb = new StringBuffer();
272
- while (m.find()) {
273
- String[] rgbcolors = m.group(1).split(",");
274
- StringBuffer hexcolor = new StringBuffer("#");
275
- for (i = 0; i < rgbcolors.length; i++) {
276
- int val = Integer.parseInt(rgbcolors[i]);
277
- if (val < 16) {
278
- hexcolor.append("0");
279
- }
280
- hexcolor.append(Integer.toHexString(val));
281
- }
282
- m.appendReplacement(sb, hexcolor.toString());
283
- }
284
- m.appendTail(sb);
285
- css = sb.toString();
286
-
287
- // Shorten colors from #AABBCC to #ABC. Note that we want to make sure
288
- // the color is not preceded by either ", " or =. Indeed, the property
289
- // filter: chroma(color="#FFFFFF");
290
- // would become
291
- // filter: chroma(color="#FFF");
292
- // which makes the filter break in IE.
293
- // We also want to make sure we're only compressing #AABBCC patterns inside { }, not id selectors ( #FAABAC {} )
294
- // We also want to avoid compressing invalid values (e.g. #AABBCCD to #ABCD)
295
- p = Pattern.compile("(\\=\\s*?[\"']?)?" + "#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])" + "(:?\\}|[^0-9a-fA-F{][^{]*?\\})");
296
-
297
- m = p.matcher(css);
298
- sb = new StringBuffer();
299
- int index = 0;
300
-
301
- while (m.find(index)) {
302
-
303
- sb.append(css.substring(index, m.start()));
304
-
305
- boolean isFilter = (m.group(1) != null && !"".equals(m.group(1)));
306
-
307
- if (isFilter) {
308
- // Restore, as is. Compression will break filters
309
- sb.append(m.group(1) + "#" + m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7));
310
- } else {
311
- if( m.group(2).equalsIgnoreCase(m.group(3)) &&
312
- m.group(4).equalsIgnoreCase(m.group(5)) &&
313
- m.group(6).equalsIgnoreCase(m.group(7))) {
314
-
315
- // #AABBCC pattern
316
- sb.append("#" + (m.group(3) + m.group(5) + m.group(7)).toLowerCase());
317
-
318
- } else {
319
-
320
- // Non-compressible color, restore, but lower case.
321
- sb.append("#" + (m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)).toLowerCase());
322
- }
323
- }
324
-
325
- index = m.end(7);
326
- }
327
-
328
- sb.append(css.substring(index));
329
- css = sb.toString();
330
-
331
- // border: none -> border:0
332
- sb = new StringBuffer();
333
- p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|})");
334
- m = p.matcher(css);
335
- while (m.find()) {
336
- m.appendReplacement(sb, m.group(1).toLowerCase() + ":0" + m.group(2));
337
- }
338
- m.appendTail(sb);
339
- css = sb.toString();
340
-
341
- // shorter opacity IE filter
342
- css = css.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
343
-
344
- // Remove empty rules.
345
- css = css.replaceAll("[^\\}\\{/;]+\\{\\}", "");
346
-
347
- // TODO: Should this be after we re-insert tokens. These could alter the break points. However then
348
- // we'd need to make sure we don't break in the middle of a string etc.
349
- if (linebreakpos >= 0) {
350
- // Some source control tools don't like it when files containing lines longer
351
- // than, say 8000 characters, are checked in. The linebreak option is used in
352
- // that case to split long lines after a specific column.
353
- i = 0;
354
- int linestartpos = 0;
355
- sb = new StringBuffer(css);
356
- while (i < sb.length()) {
357
- char c = sb.charAt(i++);
358
- if (c == '}' && i - linestartpos > linebreakpos) {
359
- sb.insert(i, '\n');
360
- linestartpos = i;
361
- }
362
- }
363
-
364
- css = sb.toString();
365
- }
366
-
367
- // Replace multiple semi-colons in a row by a single one
368
- // See SF bug #1980989
369
- css = css.replaceAll(";;+", ";");
370
-
371
- // restore preserved comments and strings
372
- for(i = 0, max = preservedTokens.size(); i < max; i++) {
373
- css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString());
374
- }
375
-
376
- // Trim the final string (for any leading or trailing white spaces)
377
- css = css.trim();
378
-
379
- // Write the output...
380
- out.write(css);
381
- }
382
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/YUI/CssCompressor.php DELETED
@@ -1,171 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_YUI_CssCompressor
4
- * @package Minify
5
- *
6
- * YUI Compressor
7
- * Author: Julien Lecomte - http://www.julienlecomte.net/
8
- * Author: Isaac Schlueter - http://foohack.com/
9
- * Author: Stoyan Stefanov - http://phpied.com/
10
- * Author: Steve Clay - http://www.mrclay.org/ (PHP port)
11
- * Copyright (c) 2009 Yahoo! Inc. All rights reserved.
12
- * The copyrights embodied in the content of this file are licensed
13
- * by Yahoo! Inc. under the BSD (revised) open source license.
14
- */
15
-
16
- /**
17
- * Compress CSS (incomplete DO NOT USE)
18
- *
19
- * @see https://github.com/yui/yuicompressor/blob/master/src/com/yahoo/platform/yui/compressor/CssCompressor.java
20
- *
21
- * @package Minify
22
- */
23
- class Minify_YUI_CssCompressor {
24
-
25
- /**
26
- * Minify a CSS string
27
- *
28
- * @param string $css
29
- *
30
- * @return string
31
- */
32
- public function compress($css, $linebreakpos = 0)
33
- {
34
- $css = str_replace("\r\n", "\n", $css);
35
-
36
- /**
37
- * @todo comment removal
38
- * @todo re-port from newer Java version
39
- */
40
-
41
- // Normalize all whitespace strings to single spaces. Easier to work with that way.
42
- $css = preg_replace('@\s+@', ' ', $css);
43
-
44
- // Make a pseudo class for the Box Model Hack
45
- $css = preg_replace("@\"\\\\\"}\\\\\"\"@", "___PSEUDOCLASSBMH___", $css);
46
-
47
- // Remove the spaces before the things that should not have spaces before them.
48
- // But, be careful not to turn "p :link {...}" into "p:link{...}"
49
- // Swap out any pseudo-class colons with the token, and then swap back.
50
- $css = preg_replace_callback("@(^|\\})(([^\\{:])+:)+([^\\{]*\\{)@", array($this, '_removeSpacesCB'), $css);
51
-
52
- $css = preg_replace("@\\s+([!{};:>+\\(\\)\\],])@", "$1", $css);
53
- $css = str_replace("___PSEUDOCLASSCOLON___", ":", $css);
54
-
55
- // Remove the spaces after the things that should not have spaces after them.
56
- $css = preg_replace("@([!{}:;>+\\(\\[,])\\s+@", "$1", $css);
57
-
58
- // Add the semicolon where it's missing.
59
- $css = preg_replace("@([^;\\}])}@", "$1;}", $css);
60
-
61
- // Replace 0(px,em,%) with 0.
62
- $css = preg_replace("@([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)@", "$1$2", $css);
63
-
64
- // Replace 0 0 0 0; with 0.
65
- $css = str_replace(":0 0 0 0;", ":0;", $css);
66
- $css = str_replace(":0 0 0;", ":0;", $css);
67
- $css = str_replace(":0 0;", ":0;", $css);
68
-
69
- // Replace background-position:0; with background-position:0 0;
70
- $css = str_replace("background-position:0;", "background-position:0 0;", $css);
71
-
72
- // Replace 0.6 to .6, but only when preceded by : or a white-space
73
- $css = preg_replace("@(:|\\s)0+\\.(\\d+)@", "$1.$2", $css);
74
-
75
- // Shorten colors from rgb(51,102,153) to #336699
76
- // This makes it more likely that it'll get further compressed in the next step.
77
- $css = preg_replace_callback("@rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)@", array($this, '_shortenRgbCB'), $css);
78
-
79
- // Shorten colors from #AABBCC to #ABC. Note that we want to make sure
80
- // the color is not preceded by either ", " or =. Indeed, the property
81
- // filter: chroma(color="#FFFFFF");
82
- // would become
83
- // filter: chroma(color="#FFF");
84
- // which makes the filter break in IE.
85
- $css = preg_replace_callback("@([^\"'=\\s])(\\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])@", array($this, '_shortenHexCB'), $css);
86
-
87
- // Remove empty rules.
88
- $css = preg_replace("@[^\\}]+\\{;\\}@", "", $css);
89
-
90
- $linebreakpos = isset($this->_options['linebreakpos'])
91
- ? $this->_options['linebreakpos']
92
- : 0;
93
-
94
- if ($linebreakpos > 0) {
95
- // Some source control tools don't like it when files containing lines longer
96
- // than, say 8000 characters, are checked in. The linebreak option is used in
97
- // that case to split long lines after a specific column.
98
- $i = 0;
99
- $linestartpos = 0;
100
- $sb = $css;
101
-
102
- // make sure strlen returns byte count
103
- $mbIntEnc = null;
104
- if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
105
- $mbIntEnc = mb_internal_encoding();
106
- mb_internal_encoding('8bit');
107
- }
108
- $sbLength = strlen($css);
109
- while ($i < $sbLength) {
110
- $c = $sb[$i++];
111
- if ($c === '}' && $i - $linestartpos > $linebreakpos) {
112
- $sb = substr_replace($sb, "\n", $i, 0);
113
- $sbLength++;
114
- $linestartpos = $i;
115
- }
116
- }
117
- $css = $sb;
118
-
119
- // undo potential mb_encoding change
120
- if ($mbIntEnc !== null) {
121
- mb_internal_encoding($mbIntEnc);
122
- }
123
- }
124
-
125
- // Replace the pseudo class for the Box Model Hack
126
- $css = str_replace("___PSEUDOCLASSBMH___", "\"\\\\\"}\\\\\"\"", $css);
127
-
128
- // Replace multiple semi-colons in a row by a single one
129
- // See SF bug #1980989
130
- $css = preg_replace("@;;+@", ";", $css);
131
-
132
- // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
133
- $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
134
-
135
- // Trim the final string (for any leading or trailing white spaces)
136
- $css = trim($css);
137
-
138
- return $css;
139
- }
140
-
141
- protected function _removeSpacesCB($m)
142
- {
143
- return str_replace(':', '___PSEUDOCLASSCOLON___', $m[0]);
144
- }
145
-
146
- protected function _shortenRgbCB($m)
147
- {
148
- $rgbcolors = explode(',', $m[1]);
149
- $hexcolor = '#';
150
- for ($i = 0; $i < count($rgbcolors); $i++) {
151
- $val = round($rgbcolors[$i]);
152
- if ($val < 16) {
153
- $hexcolor .= '0';
154
- }
155
- $hexcolor .= dechex($val);
156
- }
157
- return $hexcolor;
158
- }
159
-
160
- protected function _shortenHexCB($m)
161
- {
162
- // Test for AABBCC pattern
163
- if ((strtolower($m[3])===strtolower($m[4])) &&
164
- (strtolower($m[5])===strtolower($m[6])) &&
165
- (strtolower($m[7])===strtolower($m[8]))) {
166
- return $m[1] . $m[2] . "#" . $m[3] . $m[5] . $m[7];
167
- } else {
168
- return $m[0];
169
- }
170
- }
171
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Minify/YUICompressor.php DELETED
@@ -1,156 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_YUICompressor
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Compress Javascript/CSS using the YUI Compressor
9
- *
10
- * You must set $jarFile and $tempDir before calling the minify functions.
11
- * Also, depending on your shell's environment, you may need to specify
12
- * the full path to java in $javaExecutable or use putenv() to setup the
13
- * Java environment.
14
- *
15
- * <code>
16
- * Minify_YUICompressor::$jarFile = '/path/to/yuicompressor-2.4.6.jar';
17
- * Minify_YUICompressor::$tempDir = '/tmp';
18
- * $code = Minify_YUICompressor::minifyJs(
19
- * $code
20
- * ,array('nomunge' => true, 'line-break' => 1000)
21
- * );
22
- * </code>
23
- *
24
- * Note: In case you run out stack (default is 512k), you may increase stack size in $options:
25
- * array('stack-size' => '2048k')
26
- *
27
- * @todo unit tests, $options docs
28
- *
29
- * @package Minify
30
- * @author Stephen Clay <steve@mrclay.org>
31
- */
32
- class Minify_YUICompressor {
33
-
34
- /**
35
- * Filepath of the YUI Compressor jar file. This must be set before
36
- * calling minifyJs() or minifyCss().
37
- *
38
- * @var string
39
- */
40
- public static $jarFile = null;
41
-
42
- /**
43
- * Writable temp directory. This must be set before calling minifyJs()
44
- * or minifyCss().
45
- *
46
- * @var string
47
- */
48
- public static $tempDir = null;
49
-
50
- /**
51
- * Filepath of "java" executable (may be needed if not in shell's PATH)
52
- *
53
- * @var string
54
- */
55
- public static $javaExecutable = 'java';
56
-
57
- /**
58
- * Minify a Javascript string
59
- *
60
- * @param string $js
61
- *
62
- * @param array $options (verbose is ignored)
63
- *
64
- * @see http://www.julienlecomte.net/yuicompressor/README
65
- *
66
- * @return string
67
- */
68
- public static function minifyJs($js, $options = array())
69
- {
70
- return self::_minify('js', $js, $options);
71
- }
72
-
73
- /**
74
- * Minify a CSS string
75
- *
76
- * @param string $css
77
- *
78
- * @param array $options (verbose is ignored)
79
- *
80
- * @see http://www.julienlecomte.net/yuicompressor/README
81
- *
82
- * @return string
83
- */
84
- public static function minifyCss($css, $options = array())
85
- {
86
- return self::_minify('css', $css, $options);
87
- }
88
-
89
- private static function _minify($type, $content, $options)
90
- {
91
- self::_prepare();
92
- if (! ($tmpFile = tempnam(self::$tempDir, 'yuic_'))) {
93
- throw new Exception('Minify_YUICompressor : could not create temp file in "'.self::$tempDir.'".');
94
- }
95
- file_put_contents($tmpFile, $content);
96
- exec(self::_getCmd($options, $type, $tmpFile), $output, $result_code);
97
- unlink($tmpFile);
98
- if ($result_code != 0) {
99
- throw new Exception('Minify_YUICompressor : YUI compressor execution failed.');
100
- }
101
- return implode("\n", $output);
102
- }
103
-
104
- private static function _getCmd($userOptions, $type, $tmpFile)
105
- {
106
- $o = array_merge(
107
- array(
108
- 'charset' => ''
109
- ,'line-break' => 5000
110
- ,'type' => $type
111
- ,'nomunge' => false
112
- ,'preserve-semi' => false
113
- ,'disable-optimizations' => false
114
- ,'stack-size' => ''
115
- )
116
- ,$userOptions
117
- );
118
- $cmd = self::$javaExecutable
119
- . (!empty($o['stack-size'])
120
- ? ' -Xss' . $o['stack-size']
121
- : '')
122
- . ' -jar ' . escapeshellarg(self::$jarFile)
123
- . " --type {$type}"
124
- . (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset'])
125
- ? " --charset {$o['charset']}"
126
- : '')
127
- . (is_numeric($o['line-break']) && $o['line-break'] >= 0
128
- ? ' --line-break ' . (int)$o['line-break']
129
- : '');
130
- if ($type === 'js') {
131
- foreach (array('nomunge', 'preserve-semi', 'disable-optimizations') as $opt) {
132
- $cmd .= $o[$opt]
133
- ? " --{$opt}"
134
- : '';
135
- }
136
- }
137
- return $cmd . ' ' . escapeshellarg($tmpFile);
138
- }
139
-
140
- private static function _prepare()
141
- {
142
- if (! is_file(self::$jarFile)) {
143
- throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not a valid file.');
144
- }
145
- if (! is_readable(self::$jarFile)) {
146
- throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not readable.');
147
- }
148
- if (! is_dir(self::$tempDir)) {
149
- throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not a valid direcotry.');
150
- }
151
- if (! is_writable(self::$tempDir)) {
152
- throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not writable.');
153
- }
154
- }
155
- }
156
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
package.xml CHANGED
@@ -1,19 +1,22 @@
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Apptrian_Minify_HTML_CSS_JS</name>
4
- <version>1.5.1</version>
5
  <stability>stable</stability>
6
  <license uri="http://opensource.org/licenses/osl-3.0.php">Open Software License (OSL 3.0)</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Minify HTML CSS JS including inline CSS/JS and speed up your site. Works with default Magento CSS/JS merger.</summary>
10
- <description>Apptrian Minify HTML CSS JS is a very small and efficient extension. It will minify HTML including inline CSS and JS code. Minification of CSS and JS files is compatible with default Magento CSS and JS file merger. There are no complex setups nor query strings on minified CSS and JS files. Extension is very easy to install and use. Compatible with FPC and Varnish cache extensions.</description>
11
- <notes>* Fixed few small issues with admin blocks&#xD;
12
- * Code standards improvements</notes>
 
 
 
13
  <authors><author><name>Apptrian</name><user>apptrian</user><email>apptrian@yahoo.com</email></author></authors>
14
- <date>2016-07-14</date>
15
- <time>17:35:13</time>
16
- <contents><target name="magecommunity"><dir name="Apptrian"><dir name="Minify"><dir name="Block"><dir name="Adminhtml"><file name="About.php" hash="8cd6fbcad4b309fd76b26a03e4a701d6"/><dir name="Button"><file name="Minify.php" hash="038c7a7de95baeb5b854622ae405b78b"/></dir><file name="Info.php" hash="9fda14526121a82265bf55dd60f70849"/></dir></dir><dir name="Helper"><file name="Data.php" hash="0a6fb118bc333bf44be76cd4146eb25c"/></dir><dir name="Model"><file name="Observer.php" hash="fd59c9d0fb80792e29389917b1ccc570"/></dir><dir name="controllers"><dir name="Adminhtml"><dir name="Apptrian"><file name="MinifyController.php" hash="c40273a5d2949c12e2996246655c9166"/></dir></dir></dir><dir name="etc"><file name="config.xml" hash="75eb86af50c81c2e2f8003864c319dfa"/><file name="system.xml" hash="aa192a307897903db8715e185cde4260"/></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Apptrian_Minify.xml" hash="d51ecf2afc48da4417b9e9c5b2777df0"/></dir></target><target name="magelocale"><dir name="en_US"><file name="Apptrian_Minify.csv" hash="694d5a244eeaaa7293520581b82ffcf7"/></dir></target><target name="magelib"><dir name="HTTP"><file name="ConditionalGet.php" hash="84d5f4e0b97e37228310a31a4bdc1240"/><file name="Encoder.php" hash="d697f04651d6363d1dd5dd8c2ce94cbd"/></dir><dir name="Minify"><file name="Build.php" hash="99800ab664e1fb0ff46a7133ab13bb1b"/><dir name="CSS"><file name="Compressor.php" hash="6f1d5d8c3f7ac47cabaca9d6ee923206"/><file name="UriRewriter.php" hash="d6c800c1b9b0603c3386f84074bc41a0"/></dir><file name="CSS.php" hash="c585f82e0a6f8af12da2d3337aa7e901"/><file name="CSSmin.php" hash="7e0549e63f49b99aac0f6688bfcf99ef"/><dir name="Cache"><file name="APC.php" hash="f83c096f1ea3eb712bf020371b7d3755"/><file name="File.php" hash="5550b7ad5d3cc20710a0835fdf20e3ec"/><file name="Memcache.php" hash="a572e300270f5cdb535de67eb1d9ce3b"/><file name="WinCache.php" hash="037003b972c8f648880b43b2c912afb7"/><file name="XCache.php" hash="a3239040e672955a8a520eef6e251d68"/><file name="ZendPlatform.php" hash="a20755bc554cfef6733ac64574c4f90e"/></dir><file name="ClosureCompiler.php" hash="0d04a74dae339bd8d2d0465503590819"/><file name="CommentPreserver.php" hash="f9fbae74aea3125d25f375942846d9c0"/><dir name="Controller"><file name="Base.php" hash="756cd27141da15f6bc0a9d34960e0338"/><file name="Files.php" hash="a0bc419aa48d256e19a64596dee53991"/><file name="Groups.php" hash="bf35ccd3d384c40033b978a402eaf917"/><file name="MinApp.php" hash="0910bc4af5280098b8411a8a23aa3e91"/><file name="Page.php" hash="c54a4b19474f9d0e9beb8ccb4e3678b3"/><file name="Version1.php" hash="0e4bce53a0b66c79aa6d15e52cf8ec50"/></dir><file name="DebugDetector.php" hash="d1bee7f6ab4dcf7be5d36dbcd1b81354"/><dir name="HTML"><file name="Helper.php" hash="af89e2e30f70dd935dd86d31649159e6"/></dir><file name="HTML.php" hash="35190e3378ff263774eb9cf975de22f0"/><file name="HTMLComp.php" hash="4435690b51daba79b367b57ffb930aa5"/><file name="HTMLMax.php" hash="355bd23860d3856bed6242c59b69fa19"/><file name="HTMLMaxComp.php" hash="2335b33c4014ae96046deb41ff1e17e2"/><file name="ImportProcessor.php" hash="075c561afa4825021fa44c3cac68ab94"/><dir name="JS"><file name="ClosureCompiler.php" hash="0d6d0017c3b9decdf0e1f647c0b53f17"/></dir><file name="Lines.php" hash="526499d43d682432dac9483da4509179"/><file name="Loader.php" hash="5e84b0e739587d8df742f47953f135d5"/><file name="Logger.php" hash="ee493543ebb47aa06f976f23a0f3d86a"/><file name="Packer.php" hash="e91f63a82c4fbd660c2341163e87dd4b"/><file name="Source.php" hash="f7055f963f00e5ae9f5b0005d5e4a5ec"/><dir name="YUI"><file name="CssCompressor.java" hash="cb15a586f2dcc4fead535bc982e3f91e"/><file name="CssCompressor.php" hash="12d5b4e38488c68bc84cc0efba6eb338"/></dir><file name="YUICompressor.php" hash="3ada081677d0cf118bba1f4b533b97cf"/></dir><dir name="."><file name="CSSmin.php" hash="b88ddd36d0ff681aa8a221467c0c71c1"/><file name="DooDigestAuth.php" hash="9d66abc8cfa37b5f593fc09c734f0269"/><file name="FirePHP.php" hash="f619b5a77fee4b21e4397e98d858fbf4"/><file name="JavaScriptPacker.php" hash="84900da372a375d3b2f117c5abe740a9"/><file name="JSMin.php" hash="63ada69cc753fe4136c67e1d3daabca3"/><file name="JSMinMax.php" hash="2fed0d2f38ae28220da56216ec2fe38f"/><file name="JSMinPlus.php" hash="13d47b54dd73ef825e5d86e6cc633e32"/><file name="Minify.php" hash="df50518e69c132b1354eb8ba73a8ed7c"/></dir></target></contents>
17
  <compatible/>
18
  <dependencies><required><php><min>5.1.0</min><max>8.0.0</max></php></required></dependencies>
19
  </package>
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Apptrian_Minify_HTML_CSS_JS</name>
4
+ <version>2.0.0</version>
5
  <stability>stable</stability>
6
  <license uri="http://opensource.org/licenses/osl-3.0.php">Open Software License (OSL 3.0)</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Minify HTML CSS JS including inline CSS/JS and speed up your site. Works with default Magento CSS/JS merger.</summary>
10
+ <description>Apptrian Minify HTML CSS JS is a very small and efficient extension. It will minify HTML including inline CSS and JS code. Minification of CSS and JS files is compatible with default Magento CSS and JS file merger. There are no complex setups nor query strings on minified CSS and JS files. Extension is very easy to install and use. Compatible with FPC and Varnish cache extensions. If there is a serious error in some page code that page will not be minified. Extension will log the error and url of the page where this error occurs so developers can track it down and fix the code.</description>
11
+ <notes>* Complete rewrite from the ground up&#xD;
12
+ + Support for Knockout JavaScript library comments&#xD;
13
+ + Support for script tags of type="text/x-magento-template"&#xD;
14
+ + Logging of errors with url where errors occur&#xD;
15
+ + Compliance with Magento's Extension Quality Program (EQP)</notes>
16
  <authors><author><name>Apptrian</name><user>apptrian</user><email>apptrian@yahoo.com</email></author></authors>
17
+ <date>2017-01-06</date>
18
+ <time>17:29:10</time>
19
+ <contents><target name="magecommunity"><dir name="Apptrian"><dir name="Minify"><dir name="Block"><dir name="Adminhtml"><file name="About.php" hash="f64192564d4cf23ff796d019e6cc59fd"/><dir name="Button"><file name="Minify.php" hash="fb682754c2959e3faa976f949c1f62c2"/></dir><file name="Info.php" hash="ff8203653ffc21f22613c8a07150efb4"/></dir></dir><dir name="Helper"><file name="Data.php" hash="da081b337c9daab9ab0f0a5908adc318"/></dir><dir name="Model"><file name="Observer.php" hash="c88016f8414982f615e47d21420fa478"/></dir><dir name="controllers"><dir name="Adminhtml"><dir name="Apptrian"><file name="MinifyController.php" hash="ff9d7192ade05edbf0aee65a2763cde5"/></dir></dir></dir><dir name="etc"><file name="config.xml" hash="9e667c95bf1ae3f8e9cc41876ff15822"/><file name="system.xml" hash="e356c9e13699a3550fd02955ffda675a"/></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Apptrian_Minify.xml" hash="c46de9988ef639ef94525ad207da6ef2"/></dir></target><target name="magelocale"><dir name="en_US"><file name="Apptrian_Minify.csv" hash="694d5a244eeaaa7293520581b82ffcf7"/></dir></target><target name="magelib"><dir name="Apptrian"><dir name="Minify"><file name="Css.php" hash="cbfb109198d520f31033ba14110a1ba5"/><file name="Html.php" hash="1f3ce3e743530aadb78e7071dd4e49ef"/><file name="Js.php" hash="30c28af3f71c954be8fbff10228dd02b"/></dir></dir></target></contents>
20
  <compatible/>
21
  <dependencies><required><php><min>5.1.0</min><max>8.0.0</max></php></required></dependencies>
22
  </package>