source: spip-zone/_plugins_/scssphp/trunk/scssphp_fonctions.php @ 115179

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

Utiliser le formatter expanded plutot que le nested par defaut, tous les logs dans scssphp.log et petit renommage de vaiabble cosmetique

File size: 11.1 KB
Line 
1<?php
2/*
3 * Plugin Scss
4 * Distribue sous licence MIT
5 *
6 */
7use Leafo\ScssPhp\Compiler;
8
9if (!defined('_ECRIRE_INC_VERSION')) {
10        return;
11}
12
13
14function scss_cache_dir() {
15        static $cache_dir;
16        if (is_null($cache_dir)) {
17                $cache_dir = sous_repertoire (_DIR_VAR, 'cache-scss');
18                $cache_dir = sous_repertoire($cache_dir, 'compile');
19        }
20        return $cache_dir;
21}
22
23/**
24 * Compiler des styles inline SCSS en CSS
25 *
26 * @param string $style
27 *   contenu du .scss
28 * @param array $contexte
29 *   file : chemin du fichier compile
30 *          utilise en cas de message d'erreur, et pour le repertoire de reference des @import
31 * @return string
32 */
33function scss_compile($style, $contexte = array()) {
34        include_spip('lib/scssphp/scss.inc');
35        spip_timer('scss_compile');
36
37        $import_dirs = _chemin();
38
39        $cache_options = array(
40                'cache_dir' => scss_cache_dir(),
41                // il faut prefixer avec une empreinte du import_dirs qui change le resultat
42                'prefix' => 'scssphp_'. substr(md5(json_encode($import_dirs)),0,4) . '_',
43                'force_refresh' => false,
44        );
45
46        if (defined('_VAR_MODE') and
47                (_request('var_mode') == 'css' or in_array(_VAR_MODE, array('css', 'recalcul'))) ) {
48                $cache_options['force_refresh'] = true;
49        }
50
51        // le compilateur Leafo\ScssPhp\Compiler compile le contenu
52        $scss = new Compiler($cache_options);
53        $scss->setFormatter("Leafo\ScssPhp\Formatter\Expanded");
54
55        // lui transmettre le path qu'il utilise pour les @import
56        $scss->setImportPaths(_chemin());
57
58        // pouvoir importer des @import 'css/truc' sur fichier 'css/truc.scss.html'
59        $scss->addImportPath(function($path) {
60                // SPIP 3.0+
61                if (function_exists('produire_fond_statique')) {
62                        if ($f = find_in_path($path . '.scss.html')) {
63                                $f = produire_fond_statique($path . '.scss', array('format' => 'scss'));
64                                $f = supprimer_timestamp($f);
65                                return $f;
66                        }
67                }
68                return null;
69        });
70        // pipeline : scss_variables
71        // Surcharger des variables depuis un plugin ou une configuration
72        // les variables sont un tableau 'variable'=>'scss value'
73        // ex : 'header'=> '(background:pink,color:white)'
74        $scss_vars = pipeline('scss_variables',array());
75        $scss->setVariables($scss_vars);
76
77        // Inline source maps
78        // http://leafo.github.io/scssphp/docs/#source-maps
79        // https://github.com/leafo/scssphp/wiki/Source-Maps
80        if (defined('_SCSS_SOURCE_MAP') and '_SCSS_SOURCE_MAP' == true) {
81                $scss->setSourceMap(Compiler::SOURCE_MAP_INLINE);
82                $scss->setSourceMapOptions(array(
83                        // This value is prepended to the individual entries in the 'source' field.
84                        'sourceRoot' => '',
85                        // an optional name of the generated code that this source map is associated with.
86                        'sourceMapFilename' => null,
87                        // url of the map
88                        'sourceMapURL' => null,
89                        // absolute path to a file to write the map to
90                        'sourceMapWriteTo' => null,
91                        // output source contents?
92                        'outputSourceFiles' => false,
93                        // base path for filename normalization
94                        'sourceMapRootpath' => '/',
95                        // base path for filename normalization
96                        // difference between file & url locations, removed from ALL source files in .map
97                        'sourceMapBasepath' => '/local/cache-scss/'
98          ));
99        }
100
101        try {
102                $out = $scss->compile($style, isset($contexte['file']) ? $contexte['file'] : null);
103                spip_log('scss_compile compile '.(isset($contexte['file'])?$contexte['file']:substr($style,0,100)).' :: '.spip_timer('scss_compile'), 'scssphp');
104        } catch (exception $ex) {
105                // en cas d'erreur, on retourne du vide...
106                spip_log('SCSS Compiler fatal error:'.$ex->getMessage(), 'scssphp'._LOG_ERREUR);
107                $display_file = '';
108                if (isset($contexte['file'])) {
109                        $display_file = $contexte['file'];
110                        if (strpos($ex->getMessage(), '.scss') !== false) {
111                                $display_file = basename($display_file);
112                        }
113                        $display_file= " fichier $display_file";
114                }
115                erreur_squelette(
116                        'SCSS : Echec compilation'
117                        . $display_file
118                        . '<br />' . $ex->getMessage()
119                );
120                return '';
121        }
122
123        if ($files = $scss->getParsedFiles()
124          AND count($files)){
125                $files = array_keys($files);
126                $l = strlen(_DIR_RACINE);
127                $lr = strlen(_ROOT_RACINE);
128                foreach($files as $k=>$file){
129                        if ($l and strncmp($file,_DIR_RACINE,$l)==0){
130                                $files[$k] = substr($file,$l);
131                        }
132                        if ($lr and strncmp($file,_ROOT_RACINE,$lr)==0){
133                                $files[$k] = substr($file,$lr);
134                        }
135                }
136                $out = "/*\n#@".implode("\n#@",$files)."\n*"."/\n" . $out;
137        }
138        return $out;
139}
140
141/**
142 * Transformer du SCSS en CSS
143 * Peut prendre en entree
144 * - un fichier .css ou .scss :
145 *   [(#CHEMIN{messtyles.scss.css}|scss_css)]
146 *   la sortie est un chemin vers un fichier CSS
147 * - des styles inline,
148 *   pour appliquer dans une feulle scss calculee :
149 *   #FILTRE{scss_css}
150 *   la sortie est du style inline
151 *
152 * @param string $source
153 * @return string
154 */
155function scss_css($source) {
156        static $chemin = null;
157
158        // Si on n'importe pas, est-ce un fichier ?
159        if (
160                !preg_match(',[\s{}],', $source)
161                and preg_match(',\.(scss|css)$,i', $source, $r)
162                and file_exists($source)
163        ) {
164                static $done = array();
165                // ne pas essayer de compiler deux fois le meme fichier dans le meme hit
166                // si on a echoue une fois, on echouera pareil
167                if (isset($done[$source])) {
168                        return $done[$source];
169                }
170
171                if (is_null($chemin)) {
172                        $chemin = _chemin();
173                        $chemin = md5(serialize($chemin));
174                }
175
176                $f = basename($source, $r[0]);
177                $f = sous_repertoire(_DIR_VAR, 'cache-scss')
178                . preg_replace(
179                        ',(.*?)(_rtl|_ltr)?$,',
180                        "\\1-cssify-" . substr(md5("$source-scss-$chemin"), 0, 7) . "\\2",
181                        $f,
182                        1
183                )
184                . '.css';
185
186                # si la feuille compilee est plus recente que la feuille source
187                # l'utiliser sans rien faire, sauf si il y a un var_mode
188                # dans ca cas on passe par la compilation qui utilise un cache et est donc rapide si rien de change
189                $changed = false;
190                if (@filemtime($f) < @filemtime($source)){
191                        $changed = true;
192                }
193
194                // si pas change ET pas de var_mode du tout, rien a faire (performance)
195                if (!$changed
196                        AND !defined('_VAR_MODE'))
197                        return $f;
198
199                $contenu = false;
200                if (!lire_fichier($source, $contenu)) {
201                        return $source;
202                }
203
204                // compiler le SCSS si besoin (ne pas generer une erreur si source vide
205                if (!$contenu) {
206                        $contenu = "/* Source $source : vide */\n";
207                } else {
208                        $contenu = scss_compile($contenu, array('file'=>$source));
209                }
210
211                // si erreur de compilation on renvoit un commentaire, et il y a deja eu un log
212                if (!$contenu) {
213                        $contenu = "/* Compilation $source : vide */\n";
214                }
215
216                // passer la css en url absolue (on ne peut pas le faire avant, car c'est du SCSS, pas des CSS)
217                $contenu = urls_absolues_css($contenu, $source);
218
219                // ecrire le fichier destination, en cas d'echec renvoyer la source
220                // on ecrit sur un fichier
221                if (ecrire_fichier($f.'.last', $contenu, true)) {
222                        if ($changed or md5_file($f) != md5_file($f.'.last')) {
223                                @copy($f.'.last', $f);
224                                // eviter que PHP ne reserve le vieux timestamp
225                                if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
226                                        clearstatcache(true, $f);
227                                } else {
228                                        clearstatcache();
229                                }
230                        }
231
232                        return $done[$source] = $f;
233                } else {
234                        return $done[$source] = $source;
235                }
236        }
237
238        $source = scss_compile($source);
239
240        if (!$source) {
241                return '/* Erreur compilation SCSS : cf scss.log */';
242        } else {
243                return $source;
244        }
245}
246
247
248/**
249 * injecter l'appel au compresseur sous la forme de filtre
250 * pour intervenir sur l'ensemble du head
251 * du squelette public
252 *
253 * @param string $flux
254 * @return string
255 */
256function scss_insert_head($flux) {
257        $flux .= '<'
258                .'?php header("X-Spip-Filtre: '
259                .'scss_cssify_head'
260                .'"); ?'.'>';
261        return $flux;
262}
263
264
265/**
266 * Attraper automatiquement toutes les .scss ou .scss.css du head
267 * les compiler, et les remplacer par leur css compilee
268 *
269 * @param string $head
270 * @return void
271 */
272function scss_cssify_head($head) {
273        $url_base = url_de_base();
274        $balises = extraire_balises($head, 'link');
275        $files = array();
276
277        foreach ($balises as $s) {
278                if (
279                        extraire_attribut($s, 'rel') === 'stylesheet'
280                        and (!($type = extraire_attribut($s, 'type')) or $type == 'text/css')
281                        and $src = extraire_attribut($s, 'href')
282                        // format .scss.css ou .scss avec un eventuel timestamp ?123456
283                        and preg_match(',\.(scss\.css|scss)(\?\d+)?$,', $src)
284                        and $src = preg_replace(',\?\d+$,', '', $src)
285                        and $src = preg_replace(",^$url_base,", _DIR_RACINE, $src)
286                        and file_exists($src)
287                ) {
288                        $files[$s] = $src;
289                }
290        }
291
292        if (!count($files)) {
293                return $head;
294        }
295
296        foreach ($files as $s => $scssfile) {
297                $cssfile = scss_css($scssfile);
298                $m = @filemtime($cssfile);
299                $s2 = inserer_attribut($s, 'href', "$cssfile?$m");
300                $head = str_replace($s, $s2, $head);
301        }
302
303        return $head;
304}
305
306/*
307 * Prise en charge de la balise #CSS{style.css}
308 * Regles :
309 * - cherche un .css ou un .css.html ou un .scss comme feuille de style
310 * - si un seul des 3 trouve dans le chemin il est renvoye (et compile au passage si .scss)
311 * - si un .css.html et un .css trouves dans le chemin, c'est le .css.html qui est pris
312 * (surcharge d'un statique avec une css calculee)
313 * - si un .scss et un (.css ou .css.html) on compare la priorite du chemin des deux trouves :
314 *   le plus prioritaire des 2 est choisi
315 *   si priorite equivalente on choisi le (.css ou .css.html) qui est le moins couteux a produire
316 *   permet d'avoir dans le meme dossier le .scss et sa version compilee .css : cette derniere est utilisee
317 *
318 * #CSS{style.css} renvoie dans tous les cas un fichier .css qui est soit :
319 * - un .scss compile en .css
320 * - un .css statique
321 * - un .css.html calcule en .css
322 */
323if (!function_exists('balise_CSS')) {
324        function balise_CSS($p) {
325                $_css = interprete_argument_balise(1, $p);
326                $p->code = "timestamp(direction_css(scss_select_css($_css)))";
327                $p->interdire_scripts = false;
328                return $p;
329        }
330}
331
332/**
333 * Selectionner de preference la feuille .scss (en la compilant)
334 * et sinon garder la .css classiquement
335 *
336 * @param string $css_file
337 * @return string
338 */
339function scss_select_css($css_file) {
340        if (
341                function_exists('scss_css')
342                and substr($css_file, -4) == '.css'
343        ) {
344                $scss_file = substr($css_file, 0, -4).'.scss';
345                $scss_or_css = scss_find_scss_or_css_in_path($scss_file, $css_file);
346
347                if (substr($scss_or_css, -5) == '.scss') {
348                        return scss_css($scss_or_css);
349                } else {
350                        return $scss_or_css;
351                }
352        }
353
354        return find_in_path($css_file);
355}
356
357/**
358 * Faire un find_in_path en cherchant un fichier .scss ou .css
359 * et en prenant le plus prioritaire des deux
360 * ce qui permet de surcharger un .css avec un .scss ou le contraire
361 * Si ils sont dans le meme repertoire, c'est le .css qui est prioritaire,
362 * par soucis de rapidite
363 *
364 * @param string $scss_file
365 * @param string $css_file
366 * @return string
367 */
368function scss_find_scss_or_css_in_path($scss_file, $css_file) {
369        $s = find_in_path($scss_file);
370        $c = $f = trouver_fond($css_file);
371
372        if (!$c) {
373                $c = find_in_path($css_file);
374        }
375
376        if (!$s) {
377                return ($f ? produire_fond_statique($css_file, array('format' => 'css')) : $c);
378        } elseif (!$c) {
379                return $s;
380        }
381
382        // on a un scss et un css en concurence
383        // prioriser en fonction de leur position dans le path
384        $path = creer_chemin();
385        foreach ($path as $dir) {
386                // css prioritaire
387                if (strncmp($c, $dir . $css_file, strlen($dir . $css_file)) == 0) {
388                        return ($f ? produire_fond_statique($css_file, array('format'=>'css')) : $c);
389                }
390                if ($s == $dir . $scss_file) {
391                        return $s;
392                }
393        }
394
395        // on ne doit jamais arriver la !
396        spip_log('Resolution chemin scss/css impossible', 'scssphp' . _LOG_CRITIQUE);
397        debug_print_backtrace();
398        die('Erreur fatale, je suis perdu');
399}
Note: See TracBrowser for help on using the repository browser.