[SPIP] ~v3.2.3-->v3.2.4
[lhc/web/www.git] / www / plugins-dist / compresseur / inc / compresseur_minifier.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Fonctions de minification
15 *
16 * @package SPIP\Compresseur\Minifier
17 */
18 if (!defined("_ECRIRE_INC_VERSION")) {
19 return;
20 }
21
22 /**
23 * Minifier un contenu CSS
24 *
25 * Si $options est vide on utilise la methode regexp simple
26 *
27 * Si $options est une chaine non vide elle definit un media à appliquer
28 * à la css. Si la css ne contient aucun @media ni @import, on encapsule tout
29 * dans "@media $option {...}" et on utilise regexp sinon on utilise csstidy
30 * pour ne pas faire d'erreur, mais c'est 12 fois plus lent
31 *
32 * Si $options sous forme de array() on passe par csstidy pour parser le code
33 * et produire un contenu plus compact et prefixé eventuellement par un @media
34 * options disponibles :
35 * - string media : media qui seront utilisés pour encapsuler par @media
36 * les selecteurs sans media
37 * - string template : format de sortie parmi 'low','default','high','highest'
38 *
39 * @param string $contenu
40 * Contenu CSS
41 * @param mixed $options
42 * Options de minification
43 * @return string
44 * Contenu CSS minifié
45 */
46 function minifier_css($contenu, $options = '') {
47 if (is_string($options) and $options) {
48 if ($options == "all") // facile : media all => ne rien preciser
49 {
50 $options = "";
51 } elseif (
52 strpos($contenu, "@media") == false
53 and strpos($contenu, "@import") == false
54 and strpos($contenu, "@font-face") == false
55 ) {
56 $contenu = "@media $options {\n$contenu\n}\n";
57 $options = "";
58 } else {
59 $options = array('media' => $options);
60 }
61 }
62 if (!is_array($options)) {
63
64 // nettoyer la css de tout ce qui sert pas
65 // pas de commentaires
66 // https://core.spip.net/issues/3987 sauf les commentaires important commençant par un ! qui sont en general des credits
67 $contenu = preg_replace(",/\*(\*|[^!].*\*)/,Ums", "", $contenu);
68 $contenu = preg_replace(",\s//[^\n]*\n,Ums", "", $contenu);
69 // espaces autour des retour lignes
70 $contenu = str_replace("\r\n", "\n", $contenu);
71 $contenu = preg_replace(",\s+\n,ms", "\n", $contenu);
72 $contenu = preg_replace(",\n\s+,ms", "\n", $contenu);
73 // pas d'espaces consecutifs
74 $contenu = preg_replace(",\s(?=\s),Ums", "", $contenu);
75 // pas d'espaces avant et apres { ; ,
76 $contenu = preg_replace("/\s?({|;|,)\s?/ms", "$1", $contenu);
77 // supprimer les espaces devant : sauf si suivi d'une lettre (:after, :first...)
78 $contenu = preg_replace("/\s:([^a-z])/ims", ":$1", $contenu);
79 // supprimer les espaces apres :
80 $contenu = preg_replace("/:\s/ms", ":", $contenu);
81 // pas d'espaces devant }
82 $contenu = preg_replace("/\s}/ms", "}", $contenu);
83
84 // ni de point virgule sur la derniere declaration
85 $contenu = preg_replace("/;}/ms", "}", $contenu);
86 // pas d'espace avant !important
87 $contenu = preg_replace("/\s!\s?important/ms", "!important", $contenu);
88 // passser les codes couleurs en 3 car si possible
89 // uniquement si non precedees d'un [="'] ce qui indique qu'on est dans un filter(xx=#?...)
90 $contenu = preg_replace(";([:\s,(])#([0-9a-f])(\\2)([0-9a-f])(\\4)([0-9a-f])(\\6)(?=[^\w\-]);i", "$1#$2$4$6",
91 $contenu);
92 // remplacer font-weight:bold par font-weight:700
93 $contenu = preg_replace("/font-weight:bold(?!er)/ims", "font-weight:700", $contenu);
94 // remplacer font-weight:normal par font-weight:400
95 $contenu = preg_replace("/font-weight:normal/ims", "font-weight:400", $contenu);
96
97 // si elle est 0, enlever la partie entière des unites decimales
98 $contenu = preg_replace("/\b0+\.(\d+em)/ims", ".$1", $contenu);
99 // supprimer les declarations vides
100 $contenu = preg_replace(",(^|})([^{}]*){},Ums", "$1", $contenu);
101 // pas besoin d'unité quand la valeur est zéro (sauf pour % car casse les @keyframes cf https://core.spip.net/issues/3128 - préserve également les chaînes en base64 cf https://core.spip.net/issues/3991)
102 $contenu = preg_replace("/((?::|\s+|\()0)(em|px|pt|rem|ex|pc|vh|vw|vmin|vmax|cm|mm|in|ch)/ms", "$1", $contenu);
103
104 // renommer les couleurs par leurs versions courtes quand c'est possible
105 $colors = array(
106 'source' => array(
107 'black',
108 'fuchsia',
109 'white',
110 'yellow',
111 '#800000',
112 '#ffa500',
113 '#808000',
114 '#800080',
115 '#008000',
116 '#000080',
117 '#008080',
118 '#c0c0c0',
119 '#808080',
120 '#f00'
121 ),
122 'replace' => array(
123 '#000',
124 '#F0F',
125 '#FFF',
126 '#FF0',
127 'maroon',
128 'orange',
129 'olive',
130 'purple',
131 'green',
132 'navy',
133 'teal',
134 'silver',
135 'gray',
136 'red'
137 )
138 );
139 foreach ($colors['source'] as $k => $v) {
140 $colors['source'][$k] = ";([:\s,(])" . $v . "(?=[^\w\-]);ms";
141 $colors['replace'][$k] = "$1" . $colors['replace'][$k];
142 }
143 $contenu = preg_replace($colors['source'], $colors['replace'], $contenu);
144
145 // raccourcir les padding qui le peuvent (sur 3 ou 2 valeurs)
146 $contenu = preg_replace(",padding:([^\s;}]+)\s([^\s;}]+)\s([^\s;}]+)\s(\\2),ims", "padding:$1 $2 $3", $contenu);
147 $contenu = preg_replace(",padding:([^\s;}]+)\s([^\s;}]+)\s(\\1)([;}!]),ims", "padding:$1 $2$4", $contenu);
148
149 // raccourcir les margin qui le peuvent (sur 3 ou 2 valeurs)
150 $contenu = preg_replace(",margin:([^\s;}]+)\s([^\s;}]+)\s([^\s;}]+)\s(\\2),ims", "margin:$1 $2 $3", $contenu);
151 $contenu = preg_replace(",margin:([^\s;}]+)\s([^\s;}]+)\s(\\1)([;}!]),ims", "margin:$1 $2$4", $contenu);
152
153 $contenu = trim($contenu);
154
155 return $contenu;
156 } else {
157 // compression avancee en utilisant csstidy
158 // beaucoup plus lent, mais necessaire pour placer des @media la ou il faut
159 // si il y a deja des @media ou des @import
160
161 // modele de sortie plus ou moins compact
162 $template = 'high';
163 if (isset($options['template']) and in_array($options['template'], array('low', 'default', 'high', 'highest'))) {
164 $template = $options['template'];
165 }
166 // @media eventuel pour prefixe toutes les css
167 // et regrouper plusieurs css entre elles
168 $media = "";
169 if (isset($options['media'])) {
170 $media = "@media " . $options['media'] . " ";
171 }
172
173 include_spip("lib/csstidy/class.csstidy");
174 $css = new csstidy();
175
176 // essayer d'optimiser les font, margin, padding avec des ecritures raccourcies
177 $css->set_cfg('optimise_shorthands', 1);
178 $css->set_cfg('template', $template);
179 $css->parse($contenu);
180
181 return $css->print->plain($media);
182 }
183 }
184
185
186 /**
187 * Compacte du javascript grace a Dean Edward's JavaScriptPacker
188 *
189 * Bench du 15/11/2010 sur jQuery.js :
190 * JSMIN (https://github.com/rgrove/jsmin-php/)
191 * 61% de la taille initiale / 2 895 ms
192 * JavaScriptPacker
193 * 62% de la taille initiale / 752 ms
194 *
195 * @param string $flux
196 * Contenu JS
197 * @return string
198 * Contenu JS minifié
199 */
200 function minifier_js($flux) {
201 if (!strlen($flux)) {
202 return $flux;
203 }
204
205 include_spip('lib/JavascriptPacker/class.JavaScriptPacker');
206 $packer = new JavaScriptPacker($flux, 0, true, false);
207
208 // en cas d'echec (?) renvoyer l'original
209 if (!strlen($t = $packer->pack())) {
210 spip_log('erreur de minifier_js', _LOG_INFO_IMPORTANTE);
211
212 return $flux;
213 }
214
215 return $t;
216 }
217
218
219 /**
220 * Une callback applicable sur chaque balise link qui minifie un fichier CSS
221 *
222 * @param string $contenu
223 * @param string $balise
224 * @return string
225 */
226 function callback_minifier_css_file($contenu, $balise) {
227 return minifier_css($contenu, extraire_attribut($balise, 'media'));
228 }
229
230 /**
231 * Une callback applicable sur chaque balise script qui minifie un JS
232 *
233 * @param string $contenu
234 * @param string $balise
235 * @return string
236 */
237 function callback_minifier_js_file($contenu, $balise) {
238 return minifier_js($contenu);
239 }
240
241
242 /**
243 * Minifier du HTML
244 *
245 * @param string $flux HTML à compresser
246 * @return string HTML compressé
247 */
248 function minifier_html($flux) {
249
250 // si pas de contenu ni de balise html, ne rien faire
251 if (!strlen($flux) or strpos($flux, "<") === false) {
252 return $flux;
253 }
254
255 static $options = null;
256 if (is_null($options)) {
257 $options = array();
258 if ($GLOBALS['meta']['auto_compress_css'] == 'oui') {
259 $options['cssMinifier'] = 'minifier_css';
260 }
261 if ($GLOBALS['meta']['auto_compress_js'] == 'oui') {
262 $options['jsMinifier'] = 'minifier_js';
263 }
264 include_spip('lib/minify_html/class.minify_html');
265 }
266
267 return Minify_HTML_SPIP::minify($flux, $options);
268 }