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

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

r92952 a introduit l'option permettant d'activer le sourceMap lors de la compilation de less, mais ca n'a pas du servir beaucoup car tous les chemins des inclusions less etaient faux
On definit donc les path et URLs des sourcemaps pour avoir quelque chose de fonctionnel

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