[SPIP] ~v3.0.20-->v3.0.25
[lhc/web/clavette_www.git] / www / plugins-dist / compresseur / inc / compresseur.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2016 *
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 d'aide pour le compresseur
15 *
16 * @package SPIP\Compresseur\Fonctions
17 */
18 if (!defined("_ECRIRE_INC_VERSION")) return;
19
20 /**
21 * Ecrire la balise javascript pour insérer le fichier compressé
22 *
23 * C'est cette fonction qui décide où il est le plus pertinent
24 * d'insérer le fichier, et dans quelle forme d'ecriture
25 *
26 * @param string $flux
27 * Contenu du head nettoyé des fichiers qui ont été compressé
28 * @param int $pos
29 * Position initiale du premier fichier inclu dans le fichier compressé
30 * @param string $src
31 * Nom du fichier compressé
32 * @param string $comments
33 * Commentaires à insérer devant
34 * @return string
35 * Code HTML de la balise <script>
36 */
37 function compresseur_ecrire_balise_js_dist(&$flux, $pos, $src, $comments = ""){
38 $comments .= "<script type='text/javascript' src='$src'></script>";
39 $flux = substr_replace($flux,$comments,$pos,0);
40 return $flux;
41 }
42
43 /**
44 * Ecrire la balise CSS pour insérer le fichier compressé
45 *
46 * C'est cette fonction qui décide ou il est le plus pertinent
47 * d'insérer le fichier, et dans quelle forme d'écriture
48 *
49 * @param string $flux
50 * Contenu du head nettoyé des fichiers qui ont ete compressé
51 * @param int $pos
52 * Position initiale du premier fichier inclu dans le fichier compressé
53 * @param string $src
54 * Nom du fichier compressé
55 * @param string $comments
56 * Commentaires à insérer devant
57 * @param string $media
58 * Type de media si précisé (print|screen...)
59 * @return string
60 * Code HTML de la balise <link>
61 */
62 function compresseur_ecrire_balise_css_dist(&$flux, $pos, $src, $comments = "", $media=""){
63 $comments .= "<link rel='stylesheet'".($media?" media='$media'":"")." href='$src' type='text/css' />";
64 $flux = substr_replace($flux,$comments,$pos,0);
65 return $flux;
66 }
67
68 /**
69 * Extraire les balises CSS à compacter
70 *
71 * @param string $flux
72 * Contenu HTML dont on extrait les balises CSS
73 * @param string $url_base
74 * @return array
75 * Couples (balise => src)
76 */
77 function compresseur_extraire_balises_css_dist($flux, $url_base){
78 $balises = extraire_balises($flux,'link');
79 $files = array();
80 foreach ($balises as $s){
81 if (extraire_attribut($s, 'rel') === 'stylesheet'
82 AND (!($type = extraire_attribut($s, 'type'))
83 OR $type == 'text/css')
84 AND is_null(extraire_attribut($s, 'name')) # css nommee : pas touche
85 AND is_null(extraire_attribut($s, 'id')) # idem
86 AND !strlen(strip_tags($s))
87 AND $src = preg_replace(",^$url_base,",_DIR_RACINE,extraire_attribut($s, 'href')))
88 $files[$s] = $src;
89 }
90 return $files;
91 }
92
93 /**
94 * Extraire les balises JS à compacter
95 *
96 * @param string $flux
97 * Contenu HTML dont on extrait les balises CSS
98 * @param string $url_base
99 * @return array
100 * Couples (balise => src)
101 */
102 function compresseur_extraire_balises_js_dist($flux, $url_base){
103 $balises = extraire_balises($flux,'script');
104 $files = array();
105 foreach ($balises as $s){
106 if (extraire_attribut($s, 'type') === 'text/javascript'
107 AND is_null(extraire_attribut($s, 'id')) # script avec un id : pas touche
108 AND $src = extraire_attribut($s, 'src')
109 AND !strlen(strip_tags($s)))
110 $files[$s] = $src;
111 }
112 return $files;
113 }
114
115 /**
116 * Compacter (concaténer+minifier) les fichiers format CSS ou JS
117 * du head.
118 *
119 * Repérer fichiers statiques vs. url squelettes
120 * Compacte le tout dans un fichier statique posé dans local/
121 *
122 * @param string $flux
123 * Contenu du <head> de la page html
124 * @param string $format
125 * css ou js
126 * @return string
127 * Contenu compressé du <head> de la page html
128 */
129 function compacte_head_files($flux,$format) {
130 $url_base = url_de_base();
131 $url_page = substr(generer_url_public('A'), 0, -1);
132 $dir = preg_quote($url_page,',').'|'.preg_quote(preg_replace(",^$url_base,",_DIR_RACINE,$url_page),',');
133
134 if (!$extraire_balises = charger_fonction("compresseur_extraire_balises_$format",'',true))
135 return $flux;
136
137 $files = array();
138 $flux_nocomment = preg_replace(",<!--.*-->,Uims","",$flux);
139 foreach ($extraire_balises($flux_nocomment, $url_base) as $s=>$src) {
140 if (
141 preg_match(',^('.$dir.')(.*)$,', $src, $r)
142 OR (
143 // ou si c'est un fichier
144 $src = preg_replace(',^'.preg_quote(url_de_base(),',').',', '', $src)
145 // enlever un timestamp eventuel derriere un nom de fichier statique
146 AND $src2 = preg_replace(",[.]{$format}[?].+$,",".$format",$src)
147 // verifier qu'il n'y a pas de ../ ni / au debut (securite)
148 AND !preg_match(',(^/|\.\.),', substr($src,strlen(_DIR_RACINE)))
149 // et si il est lisible
150 AND @is_readable($src2)
151 )
152 ) {
153 if ($r)
154 $files[$s] = explode('&', str_replace('&amp;', '&', $r[2]), 2);
155 else
156 $files[$s] = $src;
157 }
158 }
159
160 $callbacks = array('each_min'=>'callback_minifier_'.$format.'_file');
161
162 if ($format=="css"){
163 $callbacks['each_pre'] = 'compresseur_callback_prepare_css';
164 $callbacks['all_min'] = 'css_regroup_atimport';
165 // ce n'est pas une callback, mais en injectant l'url de base ici
166 // on differencie les caches quand l'url de base change
167 // puisque la css compresse inclue l'url courante du site (en url absolue)
168 // on exclue le protocole car la compression se fait en url relative au protocole
169 $callbacks[] = protocole_implicite($url_base);
170 }
171 if ($format=='js' AND $GLOBALS['meta']['auto_compress_closure']=='oui'){
172 $callbacks['all_min'] = 'minifier_encore_js';
173 }
174
175 include_spip('inc/compresseur_concatener');
176 include_spip('inc/compresseur_minifier');
177 if (list($src,$comms) = concatener_fichiers($files, $format, $callbacks)
178 AND $src){
179 $compacte_ecrire_balise = charger_fonction("compresseur_ecrire_balise_$format",'');
180 $files = array_keys($files);
181 // retrouver la position du premier fichier compacte
182 $pos = strpos($flux,reset($files));
183 // supprimer tous les fichiers compactes du flux
184 $flux = str_replace($files,"",$flux);
185 // inserer la balise (deleguer a la fonction, en lui donnant le necessaire)
186 $flux = $compacte_ecrire_balise($flux, $pos, $src, $comms);
187 }
188
189 return $flux;
190 }
191
192
193 /**
194 * Lister les fonctions de préparation des feuilles css
195 * avant minification
196 *
197 * @return array
198 * Liste des fonctions à appliquer sur les feuilles CSS
199 */
200 function compresseur_liste_fonctions_prepare_css(){
201 static $fonctions = null;
202
203 if (is_null($fonctions)){
204 $fonctions = array('css_resolve_atimport','urls_absolues_css');
205 // les fonctions de preparation aux CSS peuvent etre personalisees
206 // via la globale $compresseur_filtres_css sous forme de tableau de fonctions ordonnees
207 if (isset($GLOBALS['compresseur_filtres_css']) AND is_array($GLOBALS['compresseur_filtres_css']))
208 $fonctions = $GLOBALS['compresseur_filtres_css'] + $fonctions;
209 }
210 return $fonctions;
211 }
212
213
214 /**
215 * Préparer un fichier CSS avant sa minification
216 *
217 * @param string $css
218 * @param bool|string $is_inline
219 * @param string $fonctions
220 * @return bool|int|null|string
221 */
222 function &compresseur_callback_prepare_css(&$css, $is_inline = false, $fonctions=null) {
223 if ($is_inline) return compresseur_callback_prepare_css_inline($css,$is_inline);
224 if (!preg_match(',\.css$,i', $css, $r)) return $css;
225
226 $url_absolue_css = url_absolue($css);
227 // retirer le protocole de $url_absolue_css
228 $url_absolue_css_implicite = protocole_implicite($url_absolue_css);
229
230 if (!$fonctions) $fonctions = compresseur_liste_fonctions_prepare_css();
231 elseif (is_string($fonctions)) $fonctions = array($fonctions);
232
233 $sign = implode(",",$fonctions);
234 $sign = substr(md5("$url_absolue_css_implicite-$sign"), 0,8);
235
236 $file = basename($css,'.css');
237 $file = sous_repertoire (_DIR_VAR, 'cache-css')
238 . preg_replace(",(.*?)(_rtl|_ltr)?$,","\\1-f-" . $sign . "\\2",$file)
239 . '.css';
240
241 if ((@filemtime($file) > @filemtime($css))
242 AND (!defined('_VAR_MODE') OR _VAR_MODE != 'recalcul'))
243 return $file;
244
245 if ($url_absolue_css==$css){
246 if (strncmp($GLOBALS['meta']['adresse_site']."/",$css,$l=strlen($GLOBALS['meta']['adresse_site']."/"))!=0
247 OR !lire_fichier(_DIR_RACINE . substr($css,$l), $contenu)){
248 include_spip('inc/distant');
249 if (!$contenu = recuperer_page($css))
250 return $css;
251 }
252 }
253 elseif (!lire_fichier($css, $contenu))
254 return $css;
255
256 $contenu = compresseur_callback_prepare_css_inline($contenu, $url_absolue_css_implicite, $fonctions);
257
258 // ecrire la css
259 if (!ecrire_fichier($file, $contenu))
260 return $css;
261
262 return $file;
263 }
264
265 /**
266 * Préparer du contenu CSS inline avant minification
267 *
268 * @param string $contenu
269 * @param string $url_base
270 * @param array $fonctions
271 * @return string
272 */
273 function &compresseur_callback_prepare_css_inline(&$contenu, $url_base, $fonctions=null) {
274 if (!$fonctions) $fonctions = compresseur_liste_fonctions_prepare_css();
275 elseif (is_string($fonctions)) $fonctions = array($fonctions);
276
277 // retirer le protocole de $url_base
278 $url_base = protocole_implicite(url_absolue($url_base));
279
280 foreach($fonctions as $f)
281 if (function_exists($f))
282 $contenu = $f($contenu, $url_base);
283
284 return $contenu;
285 }
286
287 /**
288 * Resoudre et inliner les @import
289 * ceux-ci ne peuvent etre presents qu'en debut de CSS et on ne veut pas changer l'ordre des directives
290 *
291 * @param string $contenu
292 * @param string $url_base
293 * @return string
294 */
295 function css_resolve_atimport($contenu, $url_base){
296 // vite si rien a faire
297 if (strpos($contenu,"@import")===false)
298 return $contenu;
299
300 $imports_non_resolvables = array();
301 preg_match_all(",@import ([^;]*);,UmsS",$contenu,$matches,PREG_SET_ORDER);
302
303 if ($matches AND count($matches)){
304 foreach($matches as $m){
305 $url = $media = $erreur = "";
306 if (preg_match(",^\s*url\s*\(\s*['\"]?([^'\"]*)['\"]?\s*\),Ums",$m[1],$r)){
307 $url = $r[1];
308 $media = trim(substr($m[1],strlen($r[0])));
309 }
310 elseif(preg_match(",^\s*['\"]([^'\"]+)['\"],Ums",$m[1],$r)){
311 $url = $r[1];
312 $media = trim(substr($m[1],strlen($r[0])));
313 }
314 if (!$url){
315 $erreur = "Compresseur : <tt>".$m[0].";</tt> non resolu dans <tt>$url_base</tt>";
316 }
317 else {
318 $url = suivre_lien($url_base,$url);
319 // url relative ?
320 $root = protocole_implicite($GLOBALS['meta']['adresse_site']."/");
321 if (strncmp($url,$root,strlen($root))==0){
322 $url = _DIR_RACINE . substr($url,strlen($root));
323 }
324 else {
325 // si l'url a un protocole http(s):// on ne considère qu'on ne peut pas
326 // résoudre le stockage. Par exemple
327 // @import url(https://fonts.googleapis.com/css?family=Ubuntu);
328 // retournant un contenu différent en fonction navigateur
329 // tous les @import restant seront remontes en tete de CSS en fin de concatenation
330 if (preg_match(',^https?://,', $url)) {
331 $url = "";
332 } else {
333 // protocole implicite //
334 $url = "http:$url";
335 }
336 }
337
338 if ($url) {
339 // on renvoit dans la boucle pour que le fichier inclus
340 // soit aussi processe (@import, url absolue etc...)
341 $css = compresseur_callback_prepare_css($url);
342 if ($css==$url
343 OR !lire_fichier($css,$contenu_imported)){
344 $erreur = "Compresseur : url $url de <tt>".$m[0].";</tt> non resolu dans <tt>$url_base</tt>";
345 }
346 else {
347 if ($media){
348 $contenu_imported = "@media $media{\n$contenu_imported\n}\n";
349 }
350 $contenu = str_replace($m[0],$contenu_imported,$contenu);
351 }
352 }
353 }
354
355 if ($erreur){
356 $contenu = str_replace($m[0],"/* erreur @ import ".$m[1]."*/",$contenu);
357 erreur_squelette($erreur);
358 }
359 }
360 }
361
362 return $contenu;
363 }
364
365 /**
366 * Regrouper les @import restants dans la CSS concatenee en debut de celle-ci
367 *
368 * @param string $nom_tmp
369 * @param string $nom
370 * @return bool|string
371 */
372 function css_regroup_atimport($nom_tmp, $nom){
373 lire_fichier($nom_tmp,$contenu);
374 if (!$contenu OR strpos($contenu,"@import")===false) return false; // rien a faire
375
376 preg_match_all(",@import ([^;]*);,UmsS",$contenu,$matches,PREG_SET_ORDER);
377 $imports = array_map("reset",$matches);
378 $contenu = str_replace($imports,"",$contenu);
379 $contenu = implode("\n",$imports)."\n".$contenu;
380 ecrire_fichier($nom,$contenu,true);
381 // ecrire une version .gz pour content-negociation par apache, cf. [11539]
382 ecrire_fichier("$nom.gz",$contenu,true);
383
384 return $nom;
385 }