[SPIP] ~version 3.0.7-->3.0.10
[ptitvelo/web/www.git] / www / plugins-dist / compresseur / lib / minify_html / class.minify_html.php
1 <?php
2
3 /**
4 * Class Minify_HTML
5 * @package Minify
6 */
7
8
9 /**
10 * Surcharge du minifieur HTML
11 *
12 * Surcharge pour ne pas manger les commentaires <!--extra-->
13 * qui servent parfois aux plugins, et parfois même après
14 * le passage du compacteur HTML
15 *
16 * C'était le cas du bouton statistiques du formulaire admin par exemple
17 *
18 * On permet de conserver également tout commentaire commençant par <!--keepme: -->
19 *
20 * @package SPIP\Compresseur\Minifieur
21 **/
22 class Minify_HTML_SPIP extends Minify_HTML {
23
24 /**
25 * {@inheritdoc}
26 *
27 *
28 * @param string $html
29 * HTML à minifier
30 * @param array $options
31 * Tableau d'option avec les index possibles
32 * - 'cssMinifier' : (optional) callback function to process content of STYLE
33 * elements.
34 * - 'jsMinifier' : (optional) callback function to process content of SCRIPT
35 * elements. Note: the type attribute is ignored.
36 * - 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
37 * unset, minify will sniff for an XHTML doctype.
38 * @return string
39 * HTML minifié
40 **/
41 public static function minify($html, $options = array()) {
42 $min = new Minify_HTML_SPIP($html, $options);
43 return $min->process();
44 }
45
46 /**
47 * Minification des commentaires
48 *
49 * Le cas <!--extra--> doit être conservé dans les commentaires,
50 * tout comme <!--keepme: xxx -->
51 *
52 * @param array $m Matches du preg_match d'un commentaire HTML
53 * @return string Contenu minifié
54 */
55 protected function _commentCB($m)
56 {
57 if ($m[1] === 'extra') return $m[0];
58 if ($m[1] AND $m[1][0] === 'k' AND substr($m[1],0,7) === 'keepme:') return $m[0];
59 return parent::_commentCB($m);
60 }
61 }
62
63
64 /**
65 * Compress HTML
66 *
67 * This is a heavy regex-based removal of whitespace, unnecessary comments and
68 * tokens. IE conditional comments are preserved. There are also options to have
69 * STYLE and SCRIPT blocks compressed by callback functions.
70 * https://github.com/mrclay/minify/blob/master/min/lib/Minify/HTML.php
71 *
72 * A test suite is available.
73 *
74 * @package Minify
75 * @author Stephen Clay <steve@mrclay.org>
76 */
77 class Minify_HTML {
78 /**
79 * @var boolean
80 */
81 protected $_jsCleanComments = true;
82
83 /**
84 * "Minify" an HTML page
85 *
86 * @param string $html
87 *
88 * @param array $options
89 *
90 * 'cssMinifier' : (optional) callback function to process content of STYLE
91 * elements.
92 *
93 * 'jsMinifier' : (optional) callback function to process content of SCRIPT
94 * elements. Note: the type attribute is ignored.
95 *
96 * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
97 * unset, minify will sniff for an XHTML doctype.
98 *
99 * @return string
100 */
101 public static function minify($html, $options = array()) {
102 $min = new self($html, $options);
103 return $min->process();
104 }
105
106
107 /**
108 * Create a minifier object
109 *
110 * @param string $html
111 *
112 * @param array $options
113 *
114 * 'cssMinifier' : (optional) callback function to process content of STYLE
115 * elements.
116 *
117 * 'jsMinifier' : (optional) callback function to process content of SCRIPT
118 * elements. Note: the type attribute is ignored.
119 *
120 * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
121 *
122 * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
123 * unset, minify will sniff for an XHTML doctype.
124 *
125 * @return null
126 */
127 public function __construct($html, $options = array())
128 {
129 $this->_html = str_replace("\r\n", "\n", trim($html));
130 if (isset($options['xhtml'])) {
131 $this->_isXhtml = (bool)$options['xhtml'];
132 }
133 if (isset($options['cssMinifier'])) {
134 $this->_cssMinifier = $options['cssMinifier'];
135 }
136 if (isset($options['jsMinifier'])) {
137 $this->_jsMinifier = $options['jsMinifier'];
138 }
139 if (isset($options['jsCleanComments'])) {
140 $this->_jsCleanComments = (bool)$options['jsCleanComments'];
141 }
142 }
143
144
145 /**
146 * Minify the markeup given in the constructor
147 *
148 * @return string
149 */
150 public function process()
151 {
152 if ($this->_isXhtml === null) {
153 $this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
154 }
155
156 $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
157 $this->_placeholders = array();
158
159 // replace SCRIPTs (and minify) with placeholders
160 $this->_html = preg_replace_callback(
161 '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
162 ,array($this, '_removeScriptCB')
163 ,$this->_html);
164
165 // replace STYLEs (and minify) with placeholders
166 $this->_html = preg_replace_callback(
167 '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
168 ,array($this, '_removeStyleCB')
169 ,$this->_html);
170
171 // remove HTML comments (not containing IE conditional comments).
172 $this->_html = preg_replace_callback(
173 '/<!--([\\s\\S]*?)-->/'
174 ,array($this, '_commentCB')
175 ,$this->_html);
176
177 // replace PREs with placeholders
178 $this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
179 ,array($this, '_removePreCB')
180 ,$this->_html);
181
182 // replace TEXTAREAs with placeholders
183 $this->_html = preg_replace_callback(
184 '/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
185 ,array($this, '_removeTextareaCB')
186 ,$this->_html);
187
188 // replace INPUTs with placeholders
189 $this->_html = preg_replace_callback(
190 '/\\s*<input(\\b[^>]*?>)\\s*/i'
191 ,array($this, '_removeInputCB')
192 ,$this->_html);
193
194 // trim each line.
195 // @todo take into account attribute values that span multiple lines.
196 // 2 regexp because merging un /^\\s+|\\s+$/m also del a lot of newline chars ???
197 $this->_html = preg_replace('/\\s+$/m', '', $this->_html);
198 $this->_html = preg_replace('/^\\s+/m', '', $this->_html);
199
200 // remove ws around block/undisplayed elements
201 $this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
202 .'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
203 .'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
204 .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
205 .'|ul)\\b[^>]*>)/i', '$1', $this->_html);
206
207 // remove ws outside of all elements
208 $this->_html = preg_replace(
209 '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
210 ,'>$1$2$3<'
211 ,$this->_html);
212
213 // use newlines before 1st attribute in open tags (to limit line lengths)
214 $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
215
216 // fill placeholders
217 $this->_html = str_replace(
218 array_keys($this->_placeholders)
219 ,array_values($this->_placeholders)
220 ,$this->_html
221 );
222 // issue 229: multi-pass to catch scripts that didn't get replaced in textareas
223 $this->_html = str_replace(
224 array_keys($this->_placeholders)
225 ,array_values($this->_placeholders)
226 ,$this->_html
227 );
228 return $this->_html;
229 }
230
231 protected function _commentCB($m)
232 {
233 return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
234 ? $m[0]
235 : '';
236 }
237
238 protected function _reservePlace($content)
239 {
240 $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
241 $this->_placeholders[$placeholder] = $content;
242 return $placeholder;
243 }
244
245 protected $_isXhtml = null;
246 protected $_replacementHash = null;
247 protected $_placeholders = array();
248 protected $_cssMinifier = null;
249 protected $_jsMinifier = null;
250
251 protected function _removePreCB($m)
252 {
253 return $this->_reservePlace("<pre{$m[1]}");
254 }
255
256 protected function _removeInputCB($m)
257 {
258 return $this->_reservePlace("<input{$m[1]}");
259 }
260
261 protected function _removeTextareaCB($m)
262 {
263 return $this->_reservePlace("<textarea{$m[1]}");
264 }
265
266 protected function _removeStyleCB($m)
267 {
268 $openStyle = "<style{$m[1]}";
269 $css = $m[2];
270 // remove HTML comments
271 $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
272
273 // remove CDATA section markers
274 $css = $this->_removeCdata($css);
275
276 // minify
277 $minifier = $this->_cssMinifier
278 ? $this->_cssMinifier
279 : 'trim';
280 $css = call_user_func($minifier, $css);
281
282 return $this->_reservePlace($this->_needsCdata($css)
283 ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
284 : "{$openStyle}{$css}</style>"
285 );
286 }
287
288 protected function _removeScriptCB($m)
289 {
290 $openScript = "<script{$m[2]}";
291 $js = $m[3];
292
293 // whitespace surrounding? preserve at least one space
294 $ws1 = ($m[1] === '') ? '' : ' ';
295 $ws2 = ($m[4] === '') ? '' : ' ';
296
297 // remove HTML comments (and ending "//" if present)
298 if ($this->_jsCleanComments) {
299 $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
300 }
301
302 // remove CDATA section markers
303 $js = $this->_removeCdata($js);
304
305 // minify
306 $minifier = $this->_jsMinifier
307 ? $this->_jsMinifier
308 : 'trim';
309 $js = call_user_func($minifier, $js);
310
311 return $this->_reservePlace($this->_needsCdata($js)
312 ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
313 : "{$ws1}{$openScript}{$js}</script>{$ws2}"
314 );
315 }
316
317 protected function _removeCdata($str)
318 {
319 return (false !== strpos($str, '<![CDATA['))
320 ? str_replace(array('<![CDATA[', ']]>'), '', $str)
321 : $str;
322 }
323
324 protected function _needsCdata($str)
325 {
326 return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
327 }
328 }