source: spip-zone/_core_/plugins/compresseur/inc/compresseur.php @ 95385

Last change on this file since 95385 was 95385, checked in by cedric@…, 5 years ago

Amelioration : on peut definir une URL statique pour servir les ressources (images, js) sans cookie.
L'URL definie est alors utilisee pour les images contenues dans les CSS minifiees, et dans le HTML des pages servies, sans modification du squelette.
On n'utilise pas cette URL pour servir la CSS minifiee car celle-ci est prefetch avec une header http link, et la servir sur un domaine different retarde son arrivee car il faut une requete DNS en plus.

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