source: spip-zone/_plugins_/less-css/trunk/lesscss_fonctions.php @ 72536

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

Optimiser la production de CSS compilee :

  • on ne reecrit le fichier que quand il change effectivement en passant par un fichier intermediaire .css.last (evite le renouvellement inutile des timestamp, qui produisent des feuilles compilees inutilement)
  • quand on reecrit le fichier il faut appeler clearstatcache() sur ce fichier, sinon PHP ressert le vieux timestamp jusqu'a la fin du hit, et il faut recharger 2 fois la page pour avoir la mise a jour
  • Property svn:executable set to *
File size: 7.7 KB
Line 
1<?php
2/*
3 * Plugin LessCSS
4 * Distribue sous licence MIT
5 *
6 */
7
8if (!defined("_ECRIRE_INC_VERSION")) return;
9
10/**
11 * Compiler des styles inline LESS en CSS
12 *
13 * @param string $style
14 *   contenu du .less
15 * @param array $contexte
16 *   file : chemin du fichier compile
17 *          utilise en cas de message d'erreur, et pour le repertoire de reference des @import
18 * @return string
19 */
20function lesscss_compile($style, $contexte = array()){
21        require_once 'lessphp/lessc.inc.php';
22
23        // le compilateur lessc compile le contenu
24        $less = new lessc();
25        // lui transmettre le path qu'il utilise pour les @import
26        $less->importDir = _chemin();
27
28        try {
29                $out = $less->parse($style);
30                return $out;
31        }
32        // en cas d'erreur, on retourne du vide...
33        catch (exception $ex) {
34                spip_log('lessc fatal error:'.$ex->getMessage(),'less'._LOG_ERREUR);
35                erreur_squelette(
36                        "LESS : Echec compilation"
37                        . (isset($contexte['file'])?" fichier ".$contexte['file']:"")
38                  . "<br />".$ex->getMessage()
39                );
40                return '';
41        }
42}
43
44/**
45 * Transformer du LESS en CSS
46 * Peut prendre en entree
47 * - un fichier .css ou .less :
48 *   [(#CHEMIN{messtyles.less.css}|less_css)]
49 *   la sortie est un chemin vers un fichier CSS
50 * - des styles inline,
51 *   pour appliquer dans une feulle less calculee :
52 *   #FILTRE{less_css}
53 *   la sortie est du style inline
54 *
55 * @param string $source
56 * @return string
57 */
58function less_css($source){
59        static $chemin = null;
60
61        // Si on n'importe pas, est-ce un fichier ?
62        if (!preg_match(',[\s{}],', $source)
63          AND preg_match(',\.(less|css)$,i', $source, $r)
64          AND file_exists($source)) {
65                static $done = array();
66                // ne pas essayer de compiler deux fois le meme fichier dans le meme hit
67                // si on a echoue une fois, on echouera pareil
68                if (isset($done[$source])) return $done[$source];
69
70                if (is_null($chemin)){
71                        $chemin = _chemin();
72                        $chemin = md5(serialize($chemin));
73                }
74                $f = basename($source,$r[0]);
75                $f = sous_repertoire (_DIR_VAR, 'cache-less')
76                . preg_replace(",(.*?)(_rtl|_ltr)?$,",
77                                "\\1-cssify-" . substr(md5("$source-lesscss-$chemin"), 0,7) . "\\2",
78                                $f, 1)
79                . '.css';
80
81                # si la feuille compilee est plus recente que la feuille source
82                # l'utiliser sans rien faire, sauf si recalcul explicite
83                $changed = false;
84                if (@filemtime($f) < @filemtime($source))
85                        $changed = true;
86
87                if (!$changed
88                  AND (!defined('_VAR_MODE') OR _VAR_MODE != 'recalcul'))
89                        return $f;
90
91                if (!lire_fichier($source, $contenu))
92                        return $source;
93
94                # compiler le LESS si besoin (ne pas generer une erreur si source vide
95                if (!$contenu){
96                        $contenu = "/* Source $source : vide */\n";
97                }
98                else {
99                        $contenu = lesscss_compile($contenu, array('file'=>$source));
100                }
101                // si erreur de compilation on renvoit un commentaire, et il y a deja eu un log
102                if (!$contenu){
103                        $contenu = "/* Compilation $source : vide */\n";
104                }
105
106                # passer la css en url absolue (on ne peut pas le faire avant, car c'est du LESS, pas des CSS)
107                $contenu = urls_absolues_css($contenu, $source);
108
109                // ecrire le fichier destination, en cas d'echec renvoyer la source
110                // on ecrit sur un fichier
111                if (ecrire_fichier($f.".last", $contenu, true)){
112                        if ($changed OR md5_file($f)!=md5_file($f.".last")){
113                                @copy($f.".last",$f);
114                                clearstatcache(true,$f); // eviter que PHP ne reserve le vieux timestamp
115                        }
116                        return $done[$source] = $f;
117                }
118                else
119                        return $done[$source] = $source;
120        }
121        $source = lesscss_compile($source);
122        if (!$source)
123                return "/* Erreur compilation LESS : cf less.log */";
124        else
125                return $source;
126}
127
128
129/**
130 * injecter l'appel au compresseur sous la forme de filtre
131 * pour intervenir sur l'ensemble du head
132 * du squelette public
133 *
134 * @param string $flux
135 * @return string
136 */
137function lesscss_insert_head($flux){
138        $flux .= '<'
139                .'?php header("X-Spip-Filtre: '
140                .'lesscss_cssify_head'
141                .'"); ?'.'>';
142        return $flux;
143}
144
145
146/**
147 * Attraper automatiquement toutes les .less ou .less.css du head
148 * les compiler, et les remplacer par leur css compilee
149 *
150 * @param string $head
151 * @return void
152 */
153function lesscss_cssify_head($head){
154        $url_base = url_de_base();
155        $balises = extraire_balises($head,'link');
156        $files = array();
157        foreach ($balises as $s){
158                if (extraire_attribut($s, 'rel') === 'stylesheet'
159                        AND (!($type = extraire_attribut($s, 'type')) OR $type == 'text/css')
160                        AND $src = extraire_attribut($s, 'href')
161                        // format .less.css ou .less avec un eventuel timestamp ?123456
162                        AND preg_match(",\.(less\.css|less)(\?\d+)?$,",$src)
163                        AND $src = preg_replace(",\?\d+$,","",$src)
164                        AND $src = preg_replace(",^$url_base,",_DIR_RACINE,$src)
165                        AND file_exists($src))
166                        $files[$s] = $src;
167        }
168
169        if (!count($files))
170                return $head;
171
172        foreach($files as $s=>$lessfile){
173                $cssfile = less_css($lessfile);
174                $m = @filemtime($cssfile);
175                $s2 = inserer_attribut($s,"href","$cssfile?$m");
176                $head = str_replace($s, $s2, $head);
177        }
178       
179        return $head;
180}
181
182/*
183 * Prise en charge de la balise #CSS{style.css}
184 * Regles :
185 * - cherche un .css ou un .css.html ou un .less comme feuille de style
186 * - si un seul des 3 trouve dans le chemin il est renvoye (et compile au passage si .less)
187 * - si un .css.html et un .css trouves dans le chemin, c'est le .css.html qui est pris (surcharge d'un statique avec une css calculee)
188 * - si un .less et un (.css ou .css.html) on compare la priorite du chemin des deux trouves :
189 *   le plus prioritaire des 2 est choisi
190 *   si priorite equivalente on choisi le (.css ou .css.html) qui est le moins couteux a produire
191 *   permet d'avoir dans le meme dossier le .less et sa version compilee .css : cette derniere est utilisee
192 *
193 * #CSS{style.css} renvoie dans tous les cas un fichier .css qui est soit :
194 * - un .less compile en .css
195 * - un .css statique
196 * - un .css.html calcule en .css
197 */
198function balise_CSS($p) {
199        $_css = interprete_argument_balise(1,$p);
200        $p->code = "timestamp(direction_css(lesscss_select_css($_css)))";
201        $p->interdire_scripts = false;
202        return $p;
203}
204
205/**
206 * Selectionner de preference la feuille .less (en la compilant)
207 * et sinon garder la .css classiquement
208 *
209 * @param string $css_file
210 * @return string
211 */
212function lesscss_select_css($css_file){
213        if (function_exists('less_css')
214          AND substr($css_file,-4)==".css"){
215                $less_file = substr($css_file,0,-4).".less";
216                $less_or_css = lesscss_find_less_or_css_in_path($less_file, $css_file);
217                if (substr($less_or_css,-5)==".less")
218                        return less_css($less_or_css);
219                else
220                        return $less_or_css;
221        }
222        return find_in_path($css_file);
223}
224
225/**
226 * Faire un find_in_path en cherchant un fichier .less ou .css
227 * et en prenant le plus prioritaire des deux
228 * ce qui permet de surcharger un .css avec un .less ou le contraire
229 * Si ils sont dans le meme repertoire, c'est le .css qui est prioritaire,
230 * par soucis de rapidite
231 *
232 * @param string $less_file
233 * @param string $css_file
234 * @return string
235 */
236function lesscss_find_less_or_css_in_path($less_file, $css_file){
237        $l = find_in_path($less_file);
238        $c = $f = trouver_fond($css_file);
239        if (!$c)
240                $c = find_in_path($css_file);
241
242        if (!$l)
243                return ($f?produire_fond_statique($css_file,array('format'=>'css')):$c);
244        elseif(!$c)
245                return $l;
246
247        // on a un less et un css en concurence
248        // prioriser en fonction de leur position dans le path
249        $path = creer_chemin();
250        foreach($path as $dir) {
251                // css prioritaire
252                if (strncmp($c,$dir . $css_file,strlen($dir . $css_file))==0)
253                        return ($f?produire_fond_statique($css_file,array('format'=>'css')):$c);;
254                if ($l == $dir . $less_file)
255                        return $l;
256        }
257        // on ne doit jamais arriver la !
258        spip_log('Resolution chemin less/css impossible',_LOG_CRITIQUE);
259        debug_print_backtrace();
260        die('Erreur fatale, je suis perdu');
261}
262
263?>
Note: See TracBrowser for help on using the repository browser.