fbb9c897928335b00315059165f8f4bb44d17b07
[lhc/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 *
71 * A test suite is available.
72 *
73 * @link https://github.com/mrclay/minify/blob/master/min/lib/Minify/HTML.php
74 * @version 9e4176f1
75 * @note Librairie modifiée pour prendre en compte les attributs avec multilignes.
76 *
77 * @package Minify
78 * @author Stephen Clay <steve@mrclay.org>
79 */
80 class Minify_HTML {
81 /**
82 * @var boolean
83 */
84 protected $_jsCleanComments = true;
85
86 /**
87 * "Minify" an HTML page
88 *
89 * @param string $html
90 *
91 * @param array $options
92 *
93 * 'cssMinifier' : (optional) callback function to process content of STYLE
94 * elements.
95 *
96 * 'jsMinifier' : (optional) callback function to process content of SCRIPT
97 * elements. Note: the type attribute is ignored.
98 *
99 * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
100 * unset, minify will sniff for an XHTML doctype.
101 *
102 * @return string
103 */
104 public static function minify($html, $options = array()) {
105 $min = new self($html, $options);
106 return $min->process();
107 }
108
109
110 /**
111 * Create a minifier object
112 *
113 * @param string $html
114 *
115 * @param array $options
116 *
117 * 'cssMinifier' : (optional) callback function to process content of STYLE
118 * elements.
119 *
120 * 'jsMinifier' : (optional) callback function to process content of SCRIPT
121 * elements. Note: the type attribute is ignored.
122 *
123 * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
124 *
125 * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
126 * unset, minify will sniff for an XHTML doctype.
127 */
128 public function __construct($html, $options = array())
129 {
130 $this->_html = str_replace("\r\n", "\n", trim($html));
131 if (isset($options['xhtml'])) {
132 $this->_isXhtml = (bool)$options['xhtml'];
133 }
134 if (isset($options['cssMinifier'])) {
135 $this->_cssMinifier = $options['cssMinifier'];
136 }
137 if (isset($options['jsMinifier'])) {
138 $this->_jsMinifier = $options['jsMinifier'];
139 }
140 if (isset($options['jsCleanComments'])) {
141 $this->_jsCleanComments = (bool)$options['jsCleanComments'];
142 }
143 }
144
145
146 /**
147 * Minify the markeup given in the constructor
148 *
149 * @return string
150 */
151 public function process()
152 {
153 if ($this->_isXhtml === null) {
154 $this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
155 }
156
157 $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
158 $this->_placeholders = array();
159
160 // replace SCRIPTs (and minify) with placeholders
161 $this->_html = preg_replace_callback(
162 '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
163 ,array($this, '_removeScriptCB')
164 ,$this->_html);
165
166 // replace STYLEs (and minify) with placeholders
167 $this->_html = preg_replace_callback(
168 '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
169 ,array($this, '_removeStyleCB')
170 ,$this->_html);
171
172 // remove HTML comments (not containing IE conditional comments).
173 $this->_html = preg_replace_callback(
174 '/<!--([\\s\\S]*?)-->/'
175 ,array($this, '_commentCB')
176 ,$this->_html);
177
178 // replace PREs with placeholders
179 $this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
180 ,array($this, '_removePreCB')
181 ,$this->_html);
182
183 // replace TEXTAREAs with placeholders
184 $this->_html = preg_replace_callback(
185 '/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
186 ,array($this, '_removeTextareaCB')
187 ,$this->_html);
188
189 // replace INPUTs with placeholders
190 $this->_html = preg_replace_callback(
191 '/\\s*<input(\\b[^>]*?>)\\s*/i'
192 ,array($this, '_removeInputCB')
193 ,$this->_html);
194
195 // trim each line.
196 // @todo take into account attribute values that span multiple lines.
197 // 2 regexp because merging un /^\\s+|\\s+$/m also del a lot of newline chars ???
198 $this->_html = preg_replace('/\\s+$/m', '', $this->_html);
199 $this->_html = preg_replace('/^\\s+/m', '', $this->_html);
200
201 // remove ws around block/undisplayed elements
202 $this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
203 .'|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
204 .'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
205 .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
206 .'|ul)\\b[^>]*>)/i', '$1', $this->_html);
207
208 // remove ws outside of all elements
209 $this->_html = preg_replace(
210 '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
211 ,'>$1$2$3<'
212 ,$this->_html);
213
214 // use newlines before 1st attribute in open tags (to limit line lengths)
215 $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
216
217 // fill placeholders
218 $this->_html = str_replace(
219 array_keys($this->_placeholders)
220 ,array_values($this->_placeholders)
221 ,$this->_html
222 );
223 // issue 229: multi-pass to catch scripts that didn't get replaced in textareas
224 $this->_html = str_replace(
225 array_keys($this->_placeholders)
226 ,array_values($this->_placeholders)
227 ,$this->_html
228 );
229 return $this->_html;
230 }
231
232 protected function _commentCB($m)
233 {
234 return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
235 ? $m[0]
236 : '';
237 }
238
239 protected function _reservePlace($content)
240 {
241 $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
242 $this->_placeholders[$placeholder] = $content;
243 return $placeholder;
244 }
245
246 protected $_isXhtml = null;
247 protected $_replacementHash = null;
248 protected $_placeholders = array();
249 protected $_cssMinifier = null;
250 protected $_jsMinifier = null;
251
252 protected function _removePreCB($m)
253 {
254 return $this->_reservePlace("<pre{$m[1]}");
255 }
256
257 protected function _removeInputCB($m)
258 {
259 return $this->_reservePlace("<input{$m[1]}");
260 }
261
262 protected function _removeTextareaCB($m)
263 {
264 return $this->_reservePlace("<textarea{$m[1]}");
265 }
266
267 protected function _removeStyleCB($m)
268 {
269 $openStyle = "<style{$m[1]}";
270 $css = $m[2];
271 // remove HTML comments
272 $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
273
274 // remove CDATA section markers
275 $css = $this->_removeCdata($css);
276
277 // minify
278 $minifier = $this->_cssMinifier
279 ? $this->_cssMinifier
280 : 'trim';
281 $css = call_user_func($minifier, $css);
282
283 return $this->_reservePlace($this->_needsCdata($css)
284 ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
285 : "{$openStyle}{$css}</style>"
286 );
287 }
288
289 protected function _removeScriptCB($m)
290 {
291 $openScript = "<script{$m[2]}";
292 $js = $m[3];
293
294 // whitespace surrounding? preserve at least one space
295 $ws1 = ($m[1] === '') ? '' : ' ';
296 $ws2 = ($m[4] === '') ? '' : ' ';
297
298 // remove HTML comments (and ending "//" if present)
299 if ($this->_jsCleanComments) {
300 $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
301 }
302
303 // remove CDATA section markers
304 $js = $this->_removeCdata($js);
305
306 // minify
307 $minifier = $this->_jsMinifier
308 ? $this->_jsMinifier
309 : 'trim';
310 $js = call_user_func($minifier, $js);
311
312 return $this->_reservePlace($this->_needsCdata($js)
313 ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
314 : "{$ws1}{$openScript}{$js}</script>{$ws2}"
315 );
316 }
317
318 protected function _removeCdata($str)
319 {
320 return (false !== strpos($str, '<![CDATA['))
321 ? str_replace(array('<![CDATA[', ']]>'), '', $str)
322 : $str;
323 }
324
325 protected function _needsCdata($str)
326 {
327 return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
328 }
329 }