[SPIP] ~maj v3.0.14-->v3.0.17
[ptitvelo/web/www.git] / www / plugins-dist / compresseur / inc / compresseur_minifier.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2014 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Fonctions de minification
15 *
16 * @package SPIP\Compresseur\Minifier
17 */
18 if (!defined("_ECRIRE_INC_VERSION")) return;
19
20 /**
21 * Minifier un contenu CSS
22 *
23 * Si $options est vide on utilise la methode regexp simple
24 *
25 * Si $options est une chaine non vide elle definit un media à appliquer
26 * à la css. Si la css ne contient aucun @media ni @import, on encapsule tout
27 * dans "@media $option {...}" et on utilise regexp sinon on utilise csstidy
28 * pour ne pas faire d'erreur, mais c'est 12 fois plus lent
29 *
30 * Si $options sous forme de array() on passe par csstidy pour parser le code
31 * et produire un contenu plus compact et prefixé eventuellement par un @media
32 * options disponibles :
33 * - string media : media qui seront utilisés pour encapsuler par @media
34 * les selecteurs sans media
35 * - string template : format de sortie parmi 'low','default','high','highest'
36 *
37 * @param string $contenu
38 * Contenu CSS
39 * @param mixed $options
40 * Options de minification
41 * @return string
42 * Contenu CSS minifié
43 */
44 function minifier_css ($contenu, $options='') {
45 if (is_string($options) AND $options){
46 if ($options=="all") // facile : media all => ne rien preciser
47 $options = "";
48 elseif (
49 strpos($contenu,"@media")==false
50 AND strpos($contenu,"@import")==false
51 AND strpos($contenu,"@font-face")==false
52 ){
53 $contenu = "@media $options {\n$contenu\n}\n";
54 $options="";
55 }
56 else
57 $options = array('media'=>$options);
58 }
59 if (!is_array($options)){
60
61 // nettoyer la css de tout ce qui sert pas
62 // pas de commentaires
63 $contenu = preg_replace(",/\*.*\*/,Ums","",$contenu);
64 $contenu = preg_replace(",\s//[^\n]*\n,Ums","",$contenu);
65 // espaces autour des retour lignes
66 $contenu = str_replace("\r\n","\n",$contenu);
67 $contenu = preg_replace(",\s+\n,ms","\n",$contenu);
68 $contenu = preg_replace(",\n\s+,ms","\n",$contenu);
69 // pas d'espaces consecutifs
70 $contenu = preg_replace(",\s(?=\s),Ums","",$contenu);
71 // pas d'espaces avant et apres { ; ,
72 $contenu = preg_replace("/\s?({|;|,)\s?/ms","$1",$contenu);
73 // supprimer les espaces devant : sauf si suivi d'une lettre (:after, :first...)
74 $contenu = preg_replace("/\s:([^a-z])/ims",":$1",$contenu);
75 // supprimer les espaces apres :
76 $contenu = preg_replace("/:\s/ms",":",$contenu);
77 // pas d'espaces devant }
78 $contenu = preg_replace("/\s}/ms","}",$contenu);
79
80 // ni de point virgule sur la derniere declaration
81 $contenu = preg_replace("/;}/ms","}",$contenu);
82 // pas d'espace avant !important
83 $contenu = preg_replace("/\s!\s?important/ms","!important",$contenu);
84 // passser les codes couleurs en 3 car si possible
85 // uniquement si non precedees d'un [="'] ce qui indique qu'on est dans un filter(xx=#?...)
86 $contenu = preg_replace(";([:\s,(])#([0-9a-f])(\\2)([0-9a-f])(\\4)([0-9a-f])(\\6)(?=[^\w\-]);i","$1#$2$4$6",$contenu);
87 // remplacer font-weight:bold par font-weight:700
88 $contenu = preg_replace("/font-weight:bold(?!er)/ims","font-weight:700",$contenu);
89 // remplacer font-weight:normal par font-weight:400
90 $contenu = preg_replace("/font-weight:normal/ims","font-weight:400",$contenu);
91
92 // si elle est 0, enlever la partie entière des unites decimales
93 $contenu = preg_replace("/\b0+\.(\d+em)/ims",".$1",$contenu);
94 // supprimer les declarations vides
95 $contenu = preg_replace(",(^|})([^{}]*){},Ums","$1",$contenu);
96 // zero est zero, quelle que soit l'unite
97 $contenu = preg_replace("/([^0-9.]0)(em|px|pt|%)/ms","$1",$contenu);
98
99 // renommer les couleurs par leurs versions courtes quand c'est possible
100 $colors = array(
101 'source'=>array('black','fuchsia','white','yellow','#800000','#ffa500','#808000','#800080','#008000','#000080','#008080','#c0c0c0','#808080','#f00'),
102 'replace'=>array('#000' ,'#F0F' ,'#FFF' ,'#FF0' ,'maroon' ,'orange' ,'olive' ,'purple' ,'green' ,'navy' ,'teal' ,'silver' ,'gray' ,'red')
103 );
104 foreach($colors['source'] as $k=>$v){
105 $colors['source'][$k]=";([:\s,(])".$v."(?=[^\w\-]);ms";
106 $colors['replace'][$k] = "$1".$colors['replace'][$k];
107 }
108 $contenu = preg_replace($colors['source'],$colors['replace'],$contenu);
109
110 // raccourcir les padding qui le peuvent (sur 3 ou 2 valeurs)
111 $contenu = preg_replace(",padding:([^\s;}]+)\s([^\s;}]+)\s([^\s;}]+)\s(\\2),ims","padding:$1 $2 $3",$contenu);
112 $contenu = preg_replace(",padding:([^\s;}]+)\s([^\s;}]+)\s(\\1)([;}!]),ims","padding:$1 $2$4",$contenu);
113
114 // raccourcir les margin qui le peuvent (sur 3 ou 2 valeurs)
115 $contenu = preg_replace(",margin:([^\s;}]+)\s([^\s;}]+)\s([^\s;}]+)\s(\\2),ims","margin:$1 $2 $3",$contenu);
116 $contenu = preg_replace(",margin:([^\s;}]+)\s([^\s;}]+)\s(\\1)([;}!]),ims","margin:$1 $2$4",$contenu);
117
118 $contenu = trim($contenu);
119
120 return $contenu;
121 }
122 else {
123 // compression avancee en utilisant csstidy
124 // beaucoup plus lent, mais necessaire pour placer des @media la ou il faut
125 // si il y a deja des @media ou des @import
126
127 // modele de sortie plus ou moins compact
128 $template = 'high';
129 if (isset($options['template']) AND in_array($options['template'],array('low','default','high','highest')))
130 $template = $options['template'];
131 // @media eventuel pour prefixe toutes les css
132 // et regrouper plusieurs css entre elles
133 $media = "";
134 if (isset($options['media']))
135 $media = "@media ".$options['media']." ";
136
137 include_spip("lib/csstidy/class.csstidy");
138 $css = new csstidy();
139
140 // essayer d'optimiser les font, margin, padding avec des ecritures raccourcies
141 $css->set_cfg('optimise_shorthands',2);
142 $css->set_cfg('template',$template);
143 $css->parse($contenu);
144 return $css->print->plain($media);
145 }
146 }
147
148
149 /**
150 * Compacte du javascript grace a Dean Edward's JavaScriptPacker
151 *
152 * Bench du 15/11/2010 sur jQuery.js :
153 * JSMIN (https://github.com/rgrove/jsmin-php/)
154 * 61% de la taille initiale / 2 895 ms
155 * JavaScriptPacker
156 * 62% de la taille initiale / 752 ms
157 *
158 * Closure Compiler
159 * 44% de la taille initiale / 3 785 ms
160 *
161 * JavaScriptPacker + Closure Compiler
162 * 43% de la taille initiale / 3 100 ms au total
163 *
164 * Il est donc plus rapide&efficace
165 * - de packer d'abord en local avec JavaScriptPacker
166 * - d'envoyer ensuite au closure compiler
167 * Cela permet en outre d'avoir un niveau de compression decent si closure
168 * compiler echoue
169 *
170 * Dans cette fonction on ne fait que le compactage local,
171 * l'appel a closure compiler est fait une unique fois pour tous les js concatene
172 * afin d'eviter les requetes externes
173 *
174 * @param string $flux
175 * Contenu JS
176 * @return string
177 * Contenu JS minifié
178 */
179 function minifier_js($flux) {
180 if (!strlen($flux))
181 return $flux;
182
183 include_spip('lib/JavascriptPacker/class.JavaScriptPacker');
184 $packer = new JavaScriptPacker($flux, 0, true, false);
185
186 // en cas d'echec (?) renvoyer l'original
187 if (!strlen($t = $packer->pack())) {
188 spip_log('erreur de minifier_js',_LOG_INFO_IMPORTANTE);
189 return $flux;
190 }
191 return $t;
192 }
193
194
195 /**
196 * Minification additionnelle de JS
197 *
198 * Compacter du javascript plus intensivement
199 * grâce au google closure compiler
200 *
201 * @param string $content
202 * Contenu JS à compresser
203 * @param bool $file
204 * Indique si $content est ou non un fichier, et retourne un fichier dans ce dernier cas
205 * Si $file est une chaîne, c'est un nom de ficher sous lequel on écrit aussi le fichier destination
206 * @return string
207 * Contenu JS compressé
208 */
209 function minifier_encore_js($content,$file=false) {
210 # Closure Compiler n'accepte pas des POST plus gros que 200 000 octets
211 # au-dela il faut stocker dans un fichier, et envoyer l'url du fichier
212 # dans code_url ; en localhost ca ne marche evidemment pas
213 if ($file) {
214 $nom = $content;
215 lire_fichier($nom, $content);
216 $dest = dirname($nom).'/'.md5($content.$file).'.js';
217 if (file_exists($dest) AND (!is_string($file) OR file_exists($file)))
218 if (filesize($dest))
219 return is_string($file)?$file:$dest;
220 else {
221 spip_log("minifier_encore_js: Fichier $dest vide",_LOG_INFO);
222 return $nom;
223 }
224 }
225
226 if (!$file AND strlen($content)>200000)
227 return $content;
228
229 include_spip('inc/distant');
230
231 $datas=array(
232 'output_format' => 'text',
233 'output_info' => 'compiled_code',
234 'compilation_level' => 'SIMPLE_OPTIMIZATIONS', // 'SIMPLE_OPTIMIZATIONS', 'WHITESPACE_ONLY', 'ADVANCED_OPTIMIZATIONS'
235 );
236 if (!$file OR strlen($content) < 200000)
237 $datas['js_code'] = $content;
238 else
239 $datas['url_code'] = url_absolue($nom);
240
241 $cc = recuperer_page('http://closure-compiler.appspot.com/compile',
242 $trans=false, $get_headers=false,
243 $taille_max = null,
244 $datas,
245 $boundary = -1);
246
247 if ($cc AND !preg_match(',^\s*Error,', $cc)) {
248 spip_log('Closure Compiler: success');
249 $cc = "/* $nom + Closure Compiler */\n".$cc;
250 if ($file){
251 ecrire_fichier ($dest, $cc, true);
252 ecrire_fichier ("$dest.gz", $cc, true);
253 $content = $dest;
254 if (is_string($file)){
255 ecrire_fichier ($file, $cc, true);
256 spip_clearstatcache(true,$file);
257 ecrire_fichier ("$file.gz", $cc, true);
258 $content = $file;
259 }
260 }
261 else
262 $content = &$cc;
263 } else {
264 if ($file){
265 spip_log("minifier_encore_js:Echec appel Closure Compiler. Ecriture fichier $dest vide",_LOG_INFO_IMPORTANTE);
266 ecrire_fichier ($dest, '', true);
267 }
268 }
269 return $content;
270 }
271
272
273 /**
274 * Une callback applicable sur chaque balise link qui minifie un fichier CSS
275 *
276 * @param string $contenu
277 * @param string $balise
278 * @return string
279 */
280 function callback_minifier_css_file($contenu, $balise){
281 return minifier_css($contenu, extraire_attribut($balise,'media'));
282 }
283
284 /**
285 * Une callback applicable sur chaque balise script qui minifie un JS
286 *
287 * @param string $contenu
288 * @param string $balise
289 * @return string
290 */
291 function callback_minifier_js_file($contenu, $balise){
292 return minifier_js($contenu);
293 }
294
295
296 /**
297 * Minifier du HTML
298 *
299 * @param string $flux HTML à compresser
300 * @return string HTML compressé
301 */
302 function minifier_html($flux){
303
304 // si pas de contenu ni de balise html, ne rien faire
305 if (!strlen($flux) OR strpos($flux,"<")===FALSE)
306 return $flux;
307
308 static $options = null;
309 if (is_null($options)){
310 $options = array();
311 if ($GLOBALS['meta']['auto_compress_css'] == 'oui')
312 $options['cssMinifier'] = 'minifier_css';
313 if ($GLOBALS['meta']['auto_compress_js'] == 'oui')
314 $options['jsMinifier'] = 'minifier_js';
315 include_spip('lib/minify_html/class.minify_html');
316 }
317
318 return Minify_HTML_SPIP::minify($flux,$options);
319 }