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

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

bonne année.

File size: 13.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        }
199        if ($format == 'js' and $GLOBALS['meta']['auto_compress_closure'] == 'oui') {
200                $callbacks['all_min'] = 'minifier_encore_js';
201        }
202
203        include_spip('inc/compresseur_concatener');
204        include_spip('inc/compresseur_minifier');
205        if (list($src, $comms) = concatener_fichiers($files, $format, $callbacks)
206                and $src
207        ) {
208                $compacte_ecrire_balise = charger_fonction("compresseur_ecrire_balise_$format", '');
209                $files = array_keys($files);
210                // retrouver la position du premier fichier compacte
211                $pos = strpos($flux, reset($files));
212                // supprimer tous les fichiers compactes du flux
213                $flux = str_replace($files, "", $flux);
214                // inserer la balise (deleguer a la fonction, en lui donnant le necessaire)
215                $flux = $compacte_ecrire_balise($flux, $pos, $src, $comms);
216        }
217
218        return $flux;
219}
220
221
222/**
223 * Lister les fonctions de préparation des feuilles css
224 * avant minification
225 *
226 * @return array
227 *     Liste des fonctions à appliquer sur les feuilles CSS
228 */
229function compresseur_liste_fonctions_prepare_css() {
230        static $fonctions = null;
231
232        if (is_null($fonctions)) {
233                $fonctions = array('css_resolve_atimport', 'urls_absolues_css');
234                // les fonctions de preparation aux CSS peuvent etre personalisees
235                // via la globale $compresseur_filtres_css sous forme de tableau de fonctions ordonnees
236                if (isset($GLOBALS['compresseur_filtres_css']) and is_array($GLOBALS['compresseur_filtres_css'])) {
237                        $fonctions = $GLOBALS['compresseur_filtres_css'] + $fonctions;
238                }
239        }
240
241        return $fonctions;
242}
243
244
245/**
246 * Préparer un fichier CSS avant sa minification
247 *
248 * @param string $css
249 * @param bool|string $is_inline
250 * @param string $fonctions
251 * @return bool|int|null|string
252 */
253function &compresseur_callback_prepare_css(&$css, $is_inline = false, $fonctions = null) {
254        if ($is_inline) {
255                return compresseur_callback_prepare_css_inline($css, $is_inline);
256        }
257        if (!preg_match(',\.css$,i', $css, $r)) {
258                return $css;
259        }
260
261        $url_absolue_css = url_absolue($css);
262        // retirer le protocole de $url_absolue_css
263        $url_absolue_css_implicite = protocole_implicite($url_absolue_css);
264
265        if (!$fonctions) {
266                $fonctions = compresseur_liste_fonctions_prepare_css();
267        } elseif (is_string($fonctions)) {
268                $fonctions = array($fonctions);
269        }
270
271        $sign = implode(",", $fonctions);
272        $sign = substr(md5("$url_absolue_css_implicite-$sign"), 0, 8);
273
274        $file = basename($css, '.css');
275        $file = sous_repertoire(_DIR_VAR, 'cache-css')
276                . preg_replace(",(.*?)(_rtl|_ltr)?$,", "\\1-f-" . $sign . "\\2", $file)
277                . '.css';
278
279        if ((@filemtime($file) > @filemtime($css))
280                and (!defined('_VAR_MODE') or _VAR_MODE != 'recalcul')
281        ) {
282                return $file;
283        }
284
285        if ($url_absolue_css == $css) {
286                if (strncmp($GLOBALS['meta']['adresse_site'] . "/", $css, $l = strlen($GLOBALS['meta']['adresse_site'] . "/")) != 0
287                        or !lire_fichier(_DIR_RACINE . substr($css, $l), $contenu)
288                ) {
289                        include_spip('inc/distant');
290                        if (!$contenu = recuperer_page($css)) {
291                                return $css;
292                        }
293                }
294        } elseif (!lire_fichier($css, $contenu)) {
295                return $css;
296        }
297
298        $contenu = compresseur_callback_prepare_css_inline($contenu, $url_absolue_css_implicite, $css, $fonctions);
299
300        // ecrire la css
301        if (!ecrire_fichier($file, $contenu)) {
302                return $css;
303        }
304
305        return $file;
306}
307
308/**
309 * Préparer du contenu CSS inline avant minification
310 *
311 * @param string $contenu
312 *   contenu de la CSS
313 * @param string $url_base
314 *   url de la CSS ou de la page si c'est un style inline
315 * @param string $filename
316 *   nom du fichier de la CSS (ou vide si c'est un style inline)
317 * @param array $fonctions
318 *   liste des fonctions appliquees a la CSS
319 * @return string
320 */
321function &compresseur_callback_prepare_css_inline(&$contenu, $url_base, $filename = '', $fonctions = null) {
322        if (!$fonctions) {
323                $fonctions = compresseur_liste_fonctions_prepare_css();
324        } elseif (is_string($fonctions)) {
325                $fonctions = array($fonctions);
326        }
327
328        // retirer le protocole de $url_base
329        $url_base = protocole_implicite(url_absolue($url_base));
330
331        foreach ($fonctions as $f) {
332                if (!function_exists($f)) {
333                        $f = chercher_filtre($f);
334                }
335                if ($f and function_exists($f)) {
336                        $contenu = $f($contenu, $url_base, $filename);
337                }
338        }
339
340        return $contenu;
341}
342
343/**
344 * Resoudre et inliner les @import
345 * ceux-ci ne peuvent etre presents qu'en debut de CSS et on ne veut pas changer l'ordre des directives
346 *
347 * @param string $contenu
348 * @param string $url_base
349 * @return string
350 */
351function css_resolve_atimport($contenu, $url_base) {
352        // vite si rien a faire
353        if (strpos($contenu, "@import") === false) {
354                return $contenu;
355        }
356
357        $imports_non_resolvables = array();
358        preg_match_all(",@import ([^;]*);,UmsS", $contenu, $matches, PREG_SET_ORDER);
359
360        if ($matches and count($matches)) {
361                foreach ($matches as $m) {
362                        $url = $media = $erreur = "";
363                        if (preg_match(",^\s*url\s*\(\s*['\"]?([^'\"]*)['\"]?\s*\),Ums", $m[1], $r)) {
364                                $url = $r[1];
365                                $media = trim(substr($m[1], strlen($r[0])));
366                        } elseif (preg_match(",^\s*['\"]([^'\"]+)['\"],Ums", $m[1], $r)) {
367                                $url = $r[1];
368                                $media = trim(substr($m[1], strlen($r[0])));
369                        }
370                        if (!$url) {
371                                $erreur = "Compresseur : <tt>" . $m[0] . ";</tt> non resolu dans <tt>$url_base</tt>";
372                        } else {
373                                $url = suivre_lien($url_base, $url);
374                                // url relative ?
375                                $root = protocole_implicite($GLOBALS['meta']['adresse_site'] . "/");
376                                if (strncmp($url, $root, strlen($root)) == 0) {
377                                        $url = _DIR_RACINE . substr($url, strlen($root));
378                                } else {
379                                        // si l'url a un protocole http(s):// on ne considère qu'on ne peut pas
380                                        // résoudre le stockage. Par exemple
381                                        // @import url(https://fonts.googleapis.com/css?family=Ubuntu);
382                                        // retournant un contenu différent en fonction navigateur
383                                        // tous les @import restant seront remontes en tete de CSS en fin de concatenation
384                                        if (preg_match(',^https?://,', $url)) {
385                                                $url = "";
386                                        } else {
387                                                // protocole implicite //
388                                                $url = "http:$url";
389                                        }
390                                }
391
392                                if ($url) {
393                                        // on renvoit dans la boucle pour que le fichier inclus
394                                        // soit aussi processe (@import, url absolue etc...)
395                                        $css = compresseur_callback_prepare_css($url);
396                                        if ($css == $url
397                                                or !lire_fichier($css, $contenu_imported)
398                                        ) {
399                                                $erreur = "Compresseur : url $url de <tt>" . $m[0] . ";</tt> non resolu dans <tt>$url_base</tt>";
400                                        } else {
401                                                if ($media) {
402                                                        $contenu_imported = "@media $media{\n$contenu_imported\n}\n";
403                                                }
404                                                $contenu = str_replace($m[0], $contenu_imported, $contenu);
405                                        }
406                                }
407                        }
408
409                        if ($erreur) {
410                                $contenu = str_replace($m[0], "/* erreur @ import " . $m[1] . "*/", $contenu);
411                                erreur_squelette($erreur);
412                        }
413                }
414        }
415
416        return $contenu;
417}
418
419/**
420 * Regrouper les @import restants dans la CSS concatenee en debut de celle-ci
421 *
422 * @param string $nom_tmp
423 * @param string $nom
424 * @return bool|string
425 */
426function css_regroup_atimport($nom_tmp, $nom) {
427        lire_fichier($nom_tmp, $contenu);
428        if (!$contenu or strpos($contenu, "@import") === false) {
429                return false;
430        } // rien a faire
431
432        preg_match_all(",@import ([^;]*);,UmsS", $contenu, $matches, PREG_SET_ORDER);
433        $imports = array_map("reset", $matches);
434        $contenu = str_replace($imports, "", $contenu);
435        $contenu = implode("\n", $imports) . "\n" . $contenu;
436        ecrire_fichier($nom, $contenu, true);
437        // ecrire une version .gz pour content-negociation par apache, cf. [11539]
438        ecrire_fichier("$nom.gz", $contenu, true);
439
440        return $nom;
441}
Note: See TracBrowser for help on using the repository browser.