1 | <?php |
---|
2 | /* |
---|
3 | * Plugin LessCSS |
---|
4 | * Distribue sous licence MIT |
---|
5 | * |
---|
6 | */ |
---|
7 | |
---|
8 | if (!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 | */ |
---|
20 | function 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 | */ |
---|
111 | function 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 | */ |
---|
200 | function 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 | */ |
---|
216 | function 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 | */ |
---|
261 | if (!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 | */ |
---|
277 | function 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 | */ |
---|
301 | function 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 | } |
---|