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

Last change on this file since 109915 was 109915, checked in by real3t@…, 3 years ago

Éviter un gros plantage PHP si le plugin est activé en même temps que SCSS PHP (il manque vraiment une balise dans paquet.xml pour indiquer une incompatibilité entre 2 plugins)

  • Property svn:executable set to *
File size: 9.6 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        static $import_dirs = null;
22        static $parser_options = null;
23
24        spip_timer('lesscss_compile');
25        if (!class_exists('Less_Parser')){
26                require_once 'less.php/Less.php';
27        }
28        if (!function_exists('lire_config')) {
29                include_spip('inc/config');
30        }
31
32        if (is_null($import_dirs)){
33                $path = _chemin();
34                $import_dirs = array();
35                foreach($path as $p){
36                        $import_dirs[$p] = protocole_implicite(url_absolue($p?$p:"./"));
37                }
38        }
39        if (is_null($parser_options)) {
40                $parser_options = array(
41                        'sourceMap' => lire_config('lesscss/activer_sourcemaps', false) == "on" ? true : false,
42                        //'cache_dir' => sous_repertoire(_DIR_CACHE,'less'),
43                );
44        }
45
46        $parser = new Less_Parser($parser_options);
47        $parser->setImportDirs($import_dirs);
48        $parser->relativeUrls = true;
49
50        try {
51                $url_absolue = (!empty($contexte['file'])?protocole_implicite(url_absolue($contexte['file'])):null);
52                $parser->parse($style,$url_absolue);
53                $out = $parser->getCss();
54
55                if ($files = Less_Parser::AllParsedFiles()
56                  AND count($files)){
57
58                        $l = strlen(_DIR_RACINE);
59                        foreach($files as $k=>$file){
60                                if (strncmp($file,_DIR_RACINE,$l)==0){
61                                        $files[$k] = substr($file,$l);
62                                }
63                        }
64                        $out = "/*\n#@".implode("\n#@",$files)."\n*"."/\n" . $out;
65                }
66
67                spip_log('lesscss_compile '.(isset($contexte['file'])?$contexte['file']:substr($style,0,100)).' :: '.spip_timer('lesscss_compile'), 'less');
68                return $out;
69        }
70        // en cas d'erreur, on retourne du vide...
71        catch (exception $ex) {
72                spip_log('less.php fatal error:'.$ex->getMessage(),'less'._LOG_ERREUR);
73                erreur_squelette(
74                        "LESS : Echec compilation"
75                        . (isset($contexte['file'])?" fichier ".$contexte['file']:"")
76                  . "<br />".$ex->getMessage()
77                );
78                return '';
79        }
80}
81
82/**
83 * Transformer du LESS en CSS
84 * Peut prendre en entree
85 * - un fichier .css ou .less :
86 *   [(#CHEMIN{messtyles.less.css}|less_css)]
87 *   la sortie est un chemin vers un fichier CSS
88 * - des styles inline,
89 *   pour appliquer dans une feulle less calculee :
90 *   #FILTRE{less_css}
91 *   la sortie est du style inline
92 *
93 * @param string $source
94 * @return string
95 */
96function less_css($source){
97        static $chemin = null;
98
99        // Si on n'importe pas, est-ce un fichier ?
100        if (!preg_match(',[\s{}],', $source)
101          AND preg_match(',\.(less|css)$,i', $source, $r)
102          AND file_exists($source)) {
103                static $done = array();
104                // ne pas essayer de compiler deux fois le meme fichier dans le meme hit
105                // si on a echoue une fois, on echouera pareil
106                if (isset($done[$source])) return $done[$source];
107
108                if (is_null($chemin)){
109                        $chemin = _chemin();
110                        $chemin = md5(serialize($chemin));
111                }
112                // url de base de la source
113                // qui se trouvera dans la css car url absolue des images
114                // il faut que la css generee en depende
115                $url_base_source = protocole_implicite(url_absolue($source));
116
117                $f = basename($source,$r[0]);
118                $f = sous_repertoire (_DIR_VAR, 'cache-less')
119                . preg_replace(",(.*?)(_rtl|_ltr)?$,",
120                                "\\1-cssify-" . substr(md5("$url_base_source-lesscss-$chemin"), 0,7) . "\\2",
121                                $f, 1)
122                . '.css';
123
124                # si la feuille compilee est plus recente que la feuille source
125                # l'utiliser sans rien faire, sauf si recalcul explicite
126                $changed = false;
127                if (@filemtime($f) < @filemtime($source))
128                        $changed = true;
129
130                if (!$changed
131                  AND (!defined('_VAR_MODE') OR _VAR_MODE != 'recalcul'))
132                        return $f;
133
134                if (!lire_fichier($source, $contenu))
135                        return $source;
136
137                # compiler le LESS si besoin (ne pas generer une erreur si source vide
138                if (!$contenu){
139                        $contenu = "/* Source $source : vide */\n";
140                }
141                else {
142                        $contenu = lesscss_compile($contenu, array('file'=>$source));
143                }
144                // si erreur de compilation on renvoit un commentaire, et il y a deja eu un log
145                if (!$contenu){
146                        $contenu = "/* Compilation $source : vide */\n";
147                }
148
149                # passer la css en url absolue
150                # plus la peine : le parser CSS resoud les ULRs absolues des images en meme temps qu'il les cherche dans le path
151                # $contenu = urls_absolues_css($contenu, $url_base_source);
152
153                // ecrire le fichier destination, en cas d'echec renvoyer la source
154                // on ecrit sur un fichier
155                if (ecrire_fichier($f.".last", $contenu, true)){
156                        if ($changed OR md5_file($f)!=md5_file($f.".last")){
157                                @copy($f.".last",$f);
158                                // eviter que PHP ne reserve le vieux timestamp
159                                if (version_compare(PHP_VERSION, '5.3.0') >= 0)
160                                        clearstatcache(true,$f);
161                                else
162                                        clearstatcache();
163                        }
164                        return $done[$source] = $f;
165                }
166                else
167                        return $done[$source] = $source;
168        }
169        $source = lesscss_compile($source);
170        if (!$source)
171                return "/* Erreur compilation LESS : cf less.log */";
172        else
173                return $source;
174}
175
176
177/**
178 * injecter l'appel au compresseur sous la forme de filtre
179 * pour intervenir sur l'ensemble du head
180 * du squelette public
181 *
182 * @param string $flux
183 * @return string
184 */
185function lesscss_insert_head($flux){
186        $flux .= '<'
187                .'?php header("X-Spip-Filtre: '
188                .'lesscss_cssify_head'
189                .'"); ?'.'>';
190        return $flux;
191}
192
193
194/**
195 * Attraper automatiquement toutes les .less ou .less.css du head
196 * les compiler, et les remplacer par leur css compilee
197 *
198 * @param string $head
199 * @return string
200 */
201function lesscss_cssify_head($head){
202        $url_base = url_de_base();
203        $balises = extraire_balises($head,'link');
204        $files = array();
205        foreach ($balises as $s){
206                if (extraire_attribut($s, 'rel') === 'stylesheet'
207                        AND (!($type = extraire_attribut($s, 'type')) OR $type == 'text/css')
208                        AND $src = extraire_attribut($s, 'href')
209                        // format .less.css ou .less avec un eventuel timestamp ?123456
210                        AND preg_match(",\.(less\.css|less)(\?\d+)?$,",$src)
211                        AND $src = preg_replace(",\?\d+$,","",$src)
212                        AND $src = preg_replace(",^$url_base,",_DIR_RACINE,$src)
213                        AND file_exists($src))
214                        $files[$s] = $src;
215        }
216
217        if (!count($files))
218                return $head;
219
220        foreach($files as $s=>$lessfile){
221                $cssfile = less_css($lessfile);
222                $m = @filemtime($cssfile);
223                $s2 = inserer_attribut($s,"href","$cssfile?$m");
224                $head = str_replace($s, $s2, $head);
225        }
226       
227        return $head;
228}
229
230/*
231 * Prise en charge de la balise #CSS{style.css}
232 * Regles :
233 * - cherche un .css ou un .css.html ou un .less comme feuille de style
234 * - si un seul des 3 trouve dans le chemin il est renvoye (et compile au passage si .less)
235 * - 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)
236 * - si un .less et un (.css ou .css.html) on compare la priorite du chemin des deux trouves :
237 *   le plus prioritaire des 2 est choisi
238 *   si priorite equivalente on choisi le (.css ou .css.html) qui est le moins couteux a produire
239 *   permet d'avoir dans le meme dossier le .less et sa version compilee .css : cette derniere est utilisee
240 *
241 * #CSS{style.css} renvoie dans tous les cas un fichier .css qui est soit :
242 * - un .less compile en .css
243 * - un .css statique
244 * - un .css.html calcule en .css
245 */
246if (!function_exists('balise_CSS')) {
247        function balise_CSS($p) {
248                $_css = interprete_argument_balise(1,$p);
249                $p->code = "timestamp(direction_css(lesscss_select_css($_css)))";
250                $p->interdire_scripts = false;
251                return $p;
252        }
253}
254
255/**
256 * Selectionner de preference la feuille .less (en la compilant)
257 * et sinon garder la .css classiquement
258 *
259 * @param string $css_file
260 * @return string
261 */
262function lesscss_select_css($css_file){
263        if (function_exists('less_css')
264          AND substr($css_file,-4)==".css"){
265                $less_file = substr($css_file,0,-4).".less";
266                $less_or_css = lesscss_find_less_or_css_in_path($less_file, $css_file);
267                if (substr($less_or_css,-5)==".less")
268                        return less_css($less_or_css);
269                else
270                        return $less_or_css;
271        }
272        return find_in_path($css_file);
273}
274
275/**
276 * Faire un find_in_path en cherchant un fichier .less ou .css
277 * et en prenant le plus prioritaire des deux
278 * ce qui permet de surcharger un .css avec un .less ou le contraire
279 * Si ils sont dans le meme repertoire, c'est le .css qui est prioritaire,
280 * par soucis de rapidite
281 *
282 * @param string $less_file
283 * @param string $css_file
284 * @return string
285 */
286function lesscss_find_less_or_css_in_path($less_file, $css_file){
287        $l = find_in_path($less_file);
288        $c = $f = trouver_fond($css_file);
289        if (!$c)
290                $c = find_in_path($css_file);
291
292        if (!$l){
293                // passer le host en contexte pour differencier les CSS en fonction du HOST car il est inscrit en url absolue
294                // dans les chemins d'urls
295                return ($f?produire_fond_statique($css_file,array('format'=>'css','host'=>$_SERVER['HTTP_HOST'])):$c);
296        }
297        elseif(!$c)
298                return $l;
299
300        // on a un less et un css en concurence
301        // prioriser en fonction de leur position dans le path
302        $path = creer_chemin();
303        foreach($path as $dir) {
304                // css prioritaire
305                if (strncmp($c,$dir . $css_file,strlen($dir . $css_file))==0){
306                        // passer le host en contexte pour differencier les CSS en fonction du HOST car il est inscrit en url absolue
307                        // dans les chemins d'urls
308                        return ($f?produire_fond_statique($css_file,array('format'=>'css','host'=>$_SERVER['HTTP_HOST'])):$c);
309                }
310                if ($l == $dir . $less_file)
311                        return $l;
312        }
313        // on ne doit jamais arriver la !
314        spip_log('Resolution chemin less/css impossible',_LOG_CRITIQUE);
315        debug_print_backtrace();
316        die('Erreur fatale, je suis perdu');
317}
Note: See TracBrowser for help on using the repository browser.