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

Last change on this file since 109313 was 109313, checked in by arnaud.berard@…, 3 years ago

Donc ce coup ci ça fonctionnne, les sourcemaps sont inline, donc a ne pas activer lors de la minification css peut être faut t'il tester si c'est activé ou non en config

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