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)#x2F;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)#x2F;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 @@