PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.4.2
Jetpack – WP Security, Backup, Speed, & Growth v12.4.2
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / custom-css / csstidy / class.csstidy-optimise.php
jetpack / modules / custom-css / csstidy Last commit date
class.csstidy-ctype.php 4 years ago class.csstidy-optimise.php 3 years ago class.csstidy-print.php 3 years ago class.csstidy.php 3 years ago cssparse-rtl.css 4 years ago cssparse-rtl.min.css 4 years ago cssparse.css 7 years ago cssparse.min.css 4 years ago cssparsed-rtl.css 4 years ago cssparsed-rtl.min.css 4 years ago cssparsed.css 14 years ago cssparsed.min.css 4 years ago data-wp.inc.php 4 years ago data.inc.php 3 years ago lang.inc.php 4 years ago wordpress-standard.tpl 11 years ago
class.csstidy-optimise.php
1007 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * CSSTidy - CSS Parser and Optimiser
4 *
5 * CSS Optimising Class
6 * This class optimises CSS data generated by csstidy.
7 *
8 * Copyright 2005, 2006, 2007 Florian Schmitz
9 *
10 * This file is part of CSSTidy.
11 *
12 * CSSTidy is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU Lesser General Public License as published by
14 * the Free Software Foundation; either version 2.1 of the License, or
15 * (at your option) any later version.
16 *
17 * CSSTidy is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Lesser General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public License
23 * along with this program. If not, see <https://www.gnu.org/licenses/>.
24 *
25 * @license https://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
26 * @package csstidy
27 * @author Florian Schmitz (floele at gmail dot com) 2005-2007
28 * @author Brett Zamir (brettz9 at yahoo dot com) 2007
29 * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010
30 */
31
32 /**
33 * CSS Optimising Class
34 *
35 * This class optimises CSS data generated by csstidy.
36 *
37 * @package csstidy
38 * @author Florian Schmitz (floele at gmail dot com) 2005-2006
39 * @version 1.0
40 */
41 #[AllowDynamicProperties]
42 class csstidy_optimise { // phpcs:ignore
43 /**
44 * Constructor
45 *
46 * @param array $css contains the class csstidy.
47 * @access private
48 * @version 1.0
49 */
50 public function __construct( &$css ) {
51 $this->parser = & $css;
52 $this->css = & $css->css;
53 $this->sub_value = & $css->sub_value;
54 $this->at = & $css->at;
55 $this->selector = & $css->selector;
56 $this->property = & $css->property;
57 $this->value = & $css->value;
58 }
59
60 /**
61 * Call constructor function.
62 *
63 * @param object $css - the CSS.
64 */
65 public function csstidy_optimise( &$css ) {
66 $this->__construct( $css );
67 }
68
69 /**
70 * Optimises $css after parsing
71 *
72 * @access public
73 * @version 1.0
74 */
75 public function postparse() {
76 if ( $this->parser->get_cfg( 'preserve_css' ) ) {
77 return;
78 }
79
80 if ( $this->parser->get_cfg( 'merge_selectors' ) === 2 ) {
81 foreach ( $this->css as $medium => $value ) {
82 $this->merge_selectors( $this->css[ $medium ] );
83 }
84 }
85
86 if ( $this->parser->get_cfg( 'discard_invalid_selectors' ) ) {
87 foreach ( $this->css as $medium => $value ) {
88 $this->discard_invalid_selectors( $this->css[ $medium ] );
89 }
90 }
91
92 if ( $this->parser->get_cfg( 'optimise_shorthands' ) > 0 ) {
93 foreach ( $this->css as $medium => $value ) {
94 foreach ( $value as $selector => $value1 ) {
95 $this->css[ $medium ][ $selector ] = self::merge_4value_shorthands( $this->css[ $medium ][ $selector ] );
96
97 if ( $this->parser->get_cfg( 'optimise_shorthands' ) < 2 ) {
98 continue;
99 }
100
101 $this->css[ $medium ][ $selector ] = self::merge_font( $this->css[ $medium ][ $selector ] );
102
103 if ( $this->parser->get_cfg( 'optimise_shorthands' ) < 3 ) {
104 continue;
105 }
106
107 $this->css[ $medium ][ $selector ] = self::merge_bg( $this->css[ $medium ][ $selector ] );
108 if ( empty( $this->css[ $medium ][ $selector ] ) ) {
109 unset( $this->css[ $medium ][ $selector ] );
110 }
111 }
112 }
113 }
114 }
115
116 /**
117 * Optimises values
118 *
119 * @access public
120 * @version 1.0
121 */
122 public function value() {
123 $shorthands = & $GLOBALS['csstidy']['shorthands'];
124
125 // optimise shorthand properties.
126 if ( isset( $shorthands[ $this->property ] ) ) {
127 $temp = self::shorthand( $this->value ); // FIXME - move.
128 if ( $temp !== $this->value ) {
129 $this->parser->log( 'Optimised shorthand notation (' . $this->property . '): Changed "' . $this->value . '" to "' . $temp . '"', 'Information' );
130 }
131 $this->value = $temp;
132 }
133
134 // Remove whitespace at ! important.
135 if ( $this->value !== $this->compress_important( $this->value ) ) {
136 $this->parser->log( 'Optimised !important', 'Information' );
137 }
138 }
139
140 /**
141 * Optimises shorthands
142 *
143 * @access public
144 * @version 1.0
145 */
146 public function shorthands() {
147 $shorthands = & $GLOBALS['csstidy']['shorthands'];
148
149 if ( ! $this->parser->get_cfg( 'optimise_shorthands' ) || $this->parser->get_cfg( 'preserve_css' ) ) {
150 return;
151 }
152
153 if ( $this->property === 'font' && $this->parser->get_cfg( 'optimise_shorthands' ) > 1 ) {
154 $this->css[ $this->at ][ $this->selector ]['font'] = '';
155 $this->parser->merge_css_blocks( $this->at, $this->selector, self::dissolve_short_font( $this->value ) );
156 }
157 if ( $this->property === 'background' && $this->parser->get_cfg( 'optimise_shorthands' ) > 2 ) {
158 $this->css[ $this->at ][ $this->selector ]['background'] = '';
159 $this->parser->merge_css_blocks( $this->at, $this->selector, self::dissolve_short_bg( $this->value ) );
160 }
161 if ( isset( $shorthands[ $this->property ] ) ) {
162 $this->parser->merge_css_blocks( $this->at, $this->selector, self::dissolve_4value_shorthands( $this->property, $this->value ) );
163 if ( is_array( $shorthands[ $this->property ] ) ) {
164 $this->css[ $this->at ][ $this->selector ][ $this->property ] = '';
165 }
166 }
167 }
168
169 /**
170 * Optimises a sub-value
171 *
172 * @access public
173 * @version 1.0
174 */
175 public function subvalue() {
176 $replace_colors = & $GLOBALS['csstidy']['replace_colors'];
177
178 $this->sub_value = trim( $this->sub_value );
179 if ( $this->sub_value === '' ) {
180 return;
181 }
182
183 $important = '';
184 if ( csstidy::is_important( $this->sub_value ) ) {
185 $important = '!important';
186 }
187 $this->sub_value = csstidy::gvw_important( $this->sub_value );
188
189 // Compress font-weight.
190 if ( $this->property === 'font-weight' && $this->parser->get_cfg( 'compress_font-weight' ) ) {
191 if ( $this->sub_value === 'bold' ) {
192 $this->sub_value = '700';
193 $this->parser->log( 'Optimised font-weight: Changed "bold" to "700"', 'Information' );
194 } elseif ( $this->sub_value === 'normal' ) {
195 $this->sub_value = '400';
196 $this->parser->log( 'Optimised font-weight: Changed "normal" to "400"', 'Information' );
197 }
198 }
199
200 $temp = $this->compress_numbers( $this->sub_value );
201 if ( strcasecmp( $temp, $this->sub_value ) !== 0 ) {
202 if ( strlen( $temp ) > strlen( $this->sub_value ) ) {
203 $this->parser->log( 'Fixed invalid number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning' );
204 } else {
205 $this->parser->log( 'Optimised number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information' );
206 }
207 $this->sub_value = $temp;
208 }
209 if ( $this->parser->get_cfg( 'compress_colors' ) ) {
210 $temp = $this->cut_color( $this->sub_value );
211 if ( $temp !== $this->sub_value ) {
212 if ( isset( $replace_colors[ $this->sub_value ] ) ) {
213 $this->parser->log( 'Fixed invalid color name: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning' );
214 } else {
215 $this->parser->log( 'Optimised color: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information' );
216 }
217 $this->sub_value = $temp;
218 }
219 }
220 $this->sub_value .= $important;
221 }
222
223 /**
224 * Compresses shorthand values. Example: margin:1px 1px 1px 1px -> margin:1px
225 *
226 * @param string $value - the value.
227 * @access public
228 * @return string
229 * @version 1.0
230 */
231 public static function shorthand( $value ) {
232 $important = '';
233 if ( csstidy::is_important( $value ) ) {
234 $values = csstidy::gvw_important( $value );
235 $important = '!important';
236 } else {
237 $values = $value;
238 }
239
240 $values = explode( ' ', $values );
241 switch ( count( $values ) ) {
242 case 4:
243 if ( $values[0] === $values[1] && $values[0] === $values[2] && $values[0] === $values[3] ) {
244 return $values[0] . $important;
245 } elseif ( $values[1] === $values[3] && $values[0] === $values[2] ) {
246 return $values[0] . ' ' . $values[1] . $important;
247 } elseif ( $values[1] === $values[3] ) {
248 return $values[0] . ' ' . $values[1] . ' ' . $values[2] . $important;
249 }
250 break;
251
252 case 3:
253 if ( $values[0] === $values[1] && $values[0] === $values[2] ) {
254 return $values[0] . $important;
255 } elseif ( $values[0] === $values[2] ) {
256 return $values[0] . ' ' . $values[1] . $important;
257 }
258 break;
259
260 case 2:
261 if ( $values[0] === $values[1] ) {
262 return $values[0] . $important;
263 }
264 break;
265 }
266
267 return $value;
268 }
269
270 /**
271 * Removes unnecessary whitespace in ! important
272 *
273 * @param string $string - the string.
274 * @return string
275 * @access public
276 * @version 1.1
277 */
278 public function compress_important( &$string ) {
279 if ( csstidy::is_important( $string ) ) {
280 $string = csstidy::gvw_important( $string ) . ' !important'; }
281 return $string;
282 }
283
284 /**
285 * Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values.
286 *
287 * @param string $color - the color.
288 * @return string
289 * @version 1.1
290 */
291 public function cut_color( $color ) {
292 $replace_colors = & $GLOBALS['csstidy']['replace_colors'];
293
294 // an example: rgb(0,0,0) -> #000000 (or #000 in this case later).
295 if ( strtolower( substr( $color, 0, 4 ) ) === 'rgb(' ) {
296 $color_tmp = substr( $color, 4, strlen( $color ) - 5 );
297 $color_tmp = explode( ',', $color_tmp );
298 for ( $i = 0, $l = count( $color_tmp ); $i < $l; $i++ ) {
299 $color_tmp[ $i ] = trim( $color_tmp[ $i ] );
300 if ( substr( $color_tmp[ $i ], -1 ) === '%' ) {
301 $color_tmp[ $i ] = round( ( 255 * $color_tmp[ $i ] ) / 100 );
302 }
303 if ( $color_tmp[ $i ] > 255 ) {
304 $color_tmp[ $i ] = 255;
305 }
306 }
307 $color = '#';
308 for ( $i = 0; $i < 3; $i++ ) {
309 if ( $color_tmp[ $i ] < 16 ) {
310 $color .= '0' . dechex( $color_tmp[ $i ] );
311 } else {
312 $color .= dechex( $color_tmp[ $i ] );
313 }
314 }
315 }
316
317 // Fix bad color names.
318 if ( isset( $replace_colors[ strtolower( $color ) ] ) ) {
319 $color = $replace_colors[ strtolower( $color ) ];
320 }
321
322 // #aabbcc -> #abc
323 if ( strlen( $color ) === 7 ) {
324 $color_temp = strtolower( $color );
325 if ( $color_temp[0] === '#' && $color_temp[1] === $color_temp[2] && $color_temp[3] === $color_temp[4] && $color_temp[5] === $color_temp[6] ) {
326 $color = '#' . $color[1] . $color[3] . $color[5];
327 }
328 }
329
330 switch ( strtolower( $color ) ) {
331 /* color name -> hex code */
332 case 'black':
333 return '#000';
334 case 'fuchsia':
335 return '#f0f';
336 case 'white':
337 return '#fff';
338 case 'yellow':
339 return '#ff0';
340
341 /* hex code -> color name */
342 case '#800000':
343 return 'maroon';
344 case '#ffa500':
345 return 'orange';
346 case '#808000':
347 return 'olive';
348 case '#800080':
349 return 'purple';
350 case '#008000':
351 return 'green';
352 case '#000080':
353 return 'navy';
354 case '#008080':
355 return 'teal';
356 case '#c0c0c0':
357 return 'silver';
358 case '#808080':
359 return 'gray';
360 case '#f00':
361 return 'red';
362 }
363
364 return $color;
365 }
366
367 /**
368 * Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1 )
369 *
370 * @param string $subvalue - the subvalue.
371 * @return string
372 * @version 1.2
373 */
374 public function compress_numbers( $subvalue ) {
375 $unit_values = & $GLOBALS['csstidy']['unit_values'];
376 $color_values = & $GLOBALS['csstidy']['color_values'];
377
378 // for font:1em/1em sans-serif...;.
379 if ( $this->property === 'font' ) {
380 $temp = explode( '/', $subvalue );
381 } else {
382 $temp = array( $subvalue );
383 }
384
385 for ( $l = 0, $m = count( $temp ); $l < $m; $l++ ) {
386 // if we are not dealing with a number at this point, do not optimise anything.
387 $number = $this->analyse_css_number( $temp[ $l ] );
388 if ( $number === false ) {
389 return $subvalue;
390 }
391
392 // Fix bad colors.
393 if ( in_array( $this->property, $color_values, true ) ) {
394 if ( strlen( $temp[ $l ] ) === 3 || strlen( $temp[ $l ] ) === 6 ) {
395 $temp[ $l ] = '#' . $temp[ $l ];
396 } else {
397 $temp[ $l ] = '0';
398 }
399 continue;
400 }
401
402 if ( abs( $number[0] ) > 0 ) {
403 if ( $number[1] === '' && in_array( $this->property, $unit_values, true ) ) {
404 $number[1] = 'px';
405 }
406 } else {
407 $number[1] = '';
408 }
409
410 $temp[ $l ] = $number[0] . $number[1];
411 }
412
413 return ( ( count( $temp ) > 1 ) ? $temp[0] . '/' . $temp[1] : $temp[0] );
414 }
415
416 /**
417 * Checks if a given string is a CSS valid number. If it is,
418 * an array containing the value and unit is returned
419 *
420 * @param string $string - the string we're checking.
421 * @return array ('unit' if unit is found or '' if no unit exists, number value) or false if no number
422 */
423 public function analyse_css_number( $string ) {
424 // most simple checks first
425 if ( $string === '' || ctype_alpha( $string[0] ) ) {
426 return false;
427 }
428
429 $units = & $GLOBALS['csstidy']['units'];
430 $return = array( 0, '' );
431
432 $return[0] = (float) $string;
433 if ( abs( $return[0] ) > 0 && abs( $return[0] ) < 1 ) {
434 // Removes the initial `0` from a decimal number, e.g., `0.7 => .7` or `-0.666 => -.666`.
435 if ( ! $this->parser->get_cfg( 'preserve_leading_zeros' ) ) {
436 if ( $return[0] < 0 ) {
437 $return[0] = '-' . ltrim( substr( $return[0], 1 ), '0' );
438 } else {
439 $return[0] = ltrim( $return[0], '0' );
440 }
441 }
442 }
443
444 // Look for unit and split from value if exists
445 foreach ( $units as $unit ) {
446 $expect_unit_at = strlen( $string ) - strlen( $unit );
447 $unit_in_string = stristr( $string, $unit );
448 if ( ! $unit_in_string ) { // mb_strpos() fails with "false"
449 continue;
450 }
451 $actual_position = strpos( $string, $unit_in_string );
452 if ( $expect_unit_at === $actual_position ) {
453 $return[1] = $unit;
454 $string = substr( $string, 0, - strlen( $unit ) );
455 break;
456 }
457 }
458 if ( ! is_numeric( $string ) ) {
459 return false;
460 }
461 return $return;
462 }
463
464 /**
465 * Merges selectors with same properties. Example: a{color:red} b{color:red} -> a,b{color:red}
466 * Very basic and has at least one bug. Hopefully there is a replacement soon.
467 *
468 * @param array $array - the selector array.
469 * @access public
470 * @version 1.2
471 */
472 public function merge_selectors( &$array ) {
473 $css = $array;
474 foreach ( $css as $key => $value ) {
475 if ( ! isset( $css[ $key ] ) ) {
476 continue;
477 }
478 $newsel = '';
479
480 // Check if properties also exist in another selector.
481 $keys = array();
482 // PHP bug (?) without $css = $array; here.
483 foreach ( $css as $selector => $vali ) {
484 if ( $selector === $key ) {
485 continue;
486 }
487
488 if ( $css[ $key ] === $vali ) {
489 $keys[] = $selector;
490 }
491 }
492
493 if ( ! empty( $keys ) ) {
494 $newsel = $key;
495 unset( $css[ $key ] );
496 foreach ( $keys as $selector ) {
497 unset( $css[ $selector ] );
498 $newsel .= ',' . $selector;
499 }
500 $css[ $newsel ] = $value;
501 }
502 }
503 $array = $css;
504 }
505
506 /**
507 * Removes invalid selectors and their corresponding rule-sets as
508 * defined by 4.1.7 in REC-CSS2. This is a very rudimentary check
509 * and should be replaced by a full-blown parsing algorithm or
510 * regular expression
511 *
512 * @version 1.4
513 *
514 * @param array $array - selector array.
515 */
516 public function discard_invalid_selectors( &$array ) {
517 foreach ( $array as $selector => $decls ) {
518 $ok = true;
519 $selectors = array_map( 'trim', explode( ',', $selector ) );
520 foreach ( $selectors as $s ) {
521 $simple_selectors = preg_split( '/\s*[+>~\s]\s*/', $s );
522 foreach ( $simple_selectors as $ss ) {
523 if ( $ss === '' ) {
524 $ok = false;
525 }
526 // could also check $ss for internal structure, but that probably would be too slow.
527 }
528 }
529 if ( ! $ok ) {
530 unset( $array[ $selector ] );
531 }
532 }
533 }
534
535 /**
536 * Dissolves properties like padding:10px 10px 10px to padding-top:10px;padding-bottom:10px;...
537 *
538 * @param string $property - the property.
539 * @param string $value - the value.
540 * @return array
541 * @version 1.0
542 * @see merge_4value_shorthands()
543 */
544 public static function dissolve_4value_shorthands( $property, $value ) {
545 $shorthands = & $GLOBALS['csstidy']['shorthands'];
546 if ( ! is_array( $shorthands[ $property ] ) ) {
547 $return = array();
548 $return[ $property ] = $value;
549 return $return;
550 }
551
552 $important = '';
553 if ( csstidy::is_important( $value ) ) {
554 $value = csstidy::gvw_important( $value );
555 $important = '!important';
556 }
557 $values = explode( ' ', $value );
558
559 $return = array();
560 if ( count( $values ) === 4 ) {
561 for ( $i = 0; $i < 4; $i++ ) {
562 $return[ $shorthands[ $property ][ $i ] ] = $values[ $i ] . $important;
563 }
564 } elseif ( count( $values ) === 3 ) {
565 $return[ $shorthands[ $property ][0] ] = $values[0] . $important;
566 $return[ $shorthands[ $property ][1] ] = $values[1] . $important;
567 $return[ $shorthands[ $property ][3] ] = $values[1] . $important;
568 $return[ $shorthands[ $property ][2] ] = $values[2] . $important;
569 } elseif ( count( $values ) === 2 ) {
570 for ( $i = 0; $i < 4; $i++ ) {
571 $return[ $shorthands[ $property ][ $i ] ] = ( ( $i % 2 !== 0 ) ) ? $values[1] . $important : $values[0] . $important;
572 }
573 } else {
574 for ( $i = 0; $i < 4; $i++ ) {
575 $return[ $shorthands[ $property ][ $i ] ] = $values[0] . $important;
576 }
577 }
578
579 return $return;
580 }
581
582 /**
583 * Explodes a string as explode() does, however, not if $sep is escaped or within a string.
584 *
585 * @param string $sep - seperator.
586 * @param string $string - the string.
587 * @return array
588 * @version 1.0
589 */
590 public static function explode_ws( $sep, $string ) {
591 $status = 'st';
592 $to = '';
593
594 $output = array();
595 $num = 0;
596 for ( $i = 0, $len = strlen( $string ); $i < $len; $i++ ) {
597 switch ( $status ) {
598 case 'st':
599 if ( $string[ $i ] === $sep && ! csstidy::escaped( $string, $i ) ) {
600 ++$num;
601 } elseif ( $string[ $i ] === '"' || $string[ $i ] === '\'' || $string[ $i ] === '(' && ! csstidy::escaped( $string, $i ) ) {
602 $status = 'str';
603 $to = ( $string[ $i ] === '(' ) ? ')' : $string[ $i ];
604 ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $string[ $i ] : $output[ $num ] = $string[ $i ];
605 } else {
606 ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $string[ $i ] : $output[ $num ] = $string[ $i ];
607 }
608 break;
609
610 case 'str':
611 if ( $string[ $i ] === $to && ! csstidy::escaped( $string, $i ) ) {
612 $status = 'st';
613 }
614 ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $string[ $i ] : $output[ $num ] = $string[ $i ];
615 break;
616 }
617 }
618
619 if ( isset( $output[0] ) ) {
620 return $output;
621 } else {
622 return array( $output );
623 }
624 }
625
626 /**
627 * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands()
628 *
629 * @param array $array - the property array.
630 * @return array
631 * @version 1.2
632 * @see dissolve_4value_shorthands()
633 */
634 public static function merge_4value_shorthands( $array ) {
635 $return = $array;
636 $shorthands = & $GLOBALS['csstidy']['shorthands'];
637
638 foreach ( $shorthands as $key => $value ) {
639 if ( isset( $array[ $value[0] ] ) && isset( $array[ $value[1] ] )
640 && isset( $array[ $value[2] ] ) && isset( $array[ $value[3] ] ) && $value !== 0 ) {
641 $return[ $key ] = '';
642
643 $important = '';
644 for ( $i = 0; $i < 4; $i++ ) {
645 $val = $array[ $value[ $i ] ];
646 if ( csstidy::is_important( $val ) ) {
647 $important = '!important';
648 $return[ $key ] .= csstidy::gvw_important( $val ) . ' ';
649 } else {
650 $return[ $key ] .= $val . ' ';
651 }
652 unset( $return[ $value[ $i ] ] );
653 }
654 $return[ $key ] = self::shorthand( trim( $return[ $key ] . $important ) );
655 }
656 }
657 return $return;
658 }
659
660 /**
661 * Dissolve background property
662 *
663 * @param string $str_value - the string value.
664 * @return array
665 * @version 1.0
666 * @see merge_bg()
667 * @todo full CSS 3 compliance
668 */
669 public static function dissolve_short_bg( $str_value ) {
670 $have = array();
671 // don't try to explose background gradient !
672 if ( stripos( $str_value, 'gradient(' ) !== false ) {
673 return array( 'background' => $str_value );
674 }
675
676 $background_prop_default = & $GLOBALS['csstidy']['background_prop_default'];
677 $repeat = array( 'repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space' );
678 $attachment = array( 'scroll', 'fixed', 'local' );
679 $clip = array( 'border', 'padding' );
680 $origin = array( 'border', 'padding', 'content' );
681 $pos = array( 'top', 'center', 'bottom', 'left', 'right' );
682 $important = '';
683 $return = array(
684 'background-image' => null,
685 'background-size' => null,
686 'background-repeat' => null,
687 'background-position' => null,
688 'background-attachment' => null,
689 'background-clip' => null,
690 'background-origin' => null,
691 'background-color' => null,
692 );
693
694 if ( csstidy::is_important( $str_value ) ) {
695 $important = ' !important';
696 $str_value = csstidy::gvw_important( $str_value );
697 }
698
699 $str_value = self::explode_ws( ',', $str_value );
700 for ( $i = 0, $l = count( $str_value ); $i < $l; $i++ ) {
701 $have['clip'] = false;
702 $have['pos'] = false;
703 $have['color'] = false;
704 $have['bg'] = false;
705
706 if ( is_array( $str_value[ $i ] ) ) {
707 $str_value[ $i ] = $str_value[ $i ][0];
708 }
709 $str_value[ $i ] = self::explode_ws( ' ', trim( $str_value[ $i ] ) );
710
711 for ( $j = 0, $k = count( $str_value[ $i ] ); $j < $k; $j++ ) {
712 if ( $have['bg'] === false && ( substr( $str_value[ $i ][ $j ], 0, 4 ) === 'url(' || $str_value[ $i ][ $j ] === 'none' ) ) {
713 $return['background-image'] .= $str_value[ $i ][ $j ] . ',';
714 $have['bg'] = true;
715 } elseif ( in_array( $str_value[ $i ][ $j ], $repeat, true ) ) {
716 $return['background-repeat'] .= $str_value[ $i ][ $j ] . ',';
717 } elseif ( in_array( $str_value[ $i ][ $j ], $attachment, true ) ) {
718 $return['background-attachment'] .= $str_value[ $i ][ $j ] . ',';
719 } elseif ( in_array( $str_value[ $i ][ $j ], $clip, true ) && ! $have['clip'] ) {
720 $return['background-clip'] .= $str_value[ $i ][ $j ] . ',';
721 $have['clip'] = true;
722 } elseif ( in_array( $str_value[ $i ][ $j ], $origin, true ) ) {
723 $return['background-origin'] .= $str_value[ $i ][ $j ] . ',';
724 } elseif ( $str_value[ $i ][ $j ][0] === '(' ) {
725 $return['background-size'] .= substr( $str_value[ $i ][ $j ], 1, -1 ) . ',';
726 } elseif ( in_array( $str_value[ $i ][ $j ], $pos, true ) || is_numeric( $str_value[ $i ][ $j ][0] ) || $str_value[ $i ][ $j ][0] === null || $str_value[ $i ][ $j ][0] === '-' || $str_value[ $i ][ $j ][0] === '.' ) {
727 $return['background-position'] .= $str_value[ $i ][ $j ];
728 if ( ! $have['pos'] ) {
729 $return['background-position'] .= ' ';
730 } else {
731 $return['background-position'] .= ',';
732 }
733 $have['pos'] = true;
734 } elseif ( ! $have['color'] ) {
735 $return['background-color'] .= $str_value[ $i ][ $j ] . ',';
736 $have['color'] = true;
737 }
738 }
739 }
740
741 foreach ( $background_prop_default as $bg_prop => $default_value ) {
742 if ( $return[ $bg_prop ] !== null ) {
743 $return[ $bg_prop ] = substr( $return[ $bg_prop ], 0, -1 ) . $important;
744 } else {
745 $return[ $bg_prop ] = $default_value . $important;
746 }
747 }
748 return $return;
749 }
750
751 /**
752 * Merges all background properties
753 *
754 * @param array $input_css - inputted CSS.
755 * @return array
756 * @version 1.0
757 * @see dissolve_short_bg()
758 * @todo full CSS 3 compliance
759 */
760 public static function merge_bg( $input_css ) {
761 $background_prop_default = & $GLOBALS['csstidy']['background_prop_default'];
762 // Max number of background images. CSS3 not yet fully implemented.
763 $number_of_values = @max( count( self::explode_ws( ',', $input_css['background-image'] ) ), count( self::explode_ws( ',', $input_css['background-color'] ) ), 1 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
764 // Array with background images to check if BG image exists.
765 $bg_img_array = @self::explode_ws( ',', csstidy::gvw_important( $input_css['background-image'] ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
766 $new_bg_value = '';
767 $important = '';
768
769 // if background properties is here and not empty, don't try anything.
770 if ( isset( $input_css['background'] ) && $input_css['background'] ) {
771 return $input_css;
772 }
773
774 for ( $i = 0; $i < $number_of_values; $i++ ) {
775 foreach ( $background_prop_default as $bg_property => $default_value ) {
776 // Skip if property does not exist
777 if ( ! isset( $input_css[ $bg_property ] ) ) {
778 continue;
779 }
780
781 $cur_value = $input_css[ $bg_property ];
782 // skip all optimisation if gradient() somewhere.
783 if ( stripos( $cur_value, 'gradient(' ) !== false ) {
784 return $input_css;
785 }
786
787 // Skip some properties if there is no background image.
788 if ( ( ! isset( $bg_img_array[ $i ] ) || $bg_img_array[ $i ] === 'none' )
789 && ( $bg_property === 'background-size' || $bg_property === 'background-position'
790 || $bg_property === 'background-attachment' || $bg_property === 'background-repeat' ) ) {
791 continue;
792 }
793
794 // Remove !important.
795 if ( csstidy::is_important( $cur_value ) ) {
796 $important = ' !important';
797 $cur_value = csstidy::gvw_important( $cur_value );
798 }
799
800 // Do not add default values.
801 if ( $cur_value === $default_value ) {
802 continue;
803 }
804
805 $temp = self::explode_ws( ',', $cur_value );
806
807 if ( isset( $temp[ $i ] ) ) {
808 if ( $bg_property === 'background-size' ) {
809 $new_bg_value .= '(' . $temp[ $i ] . ') ';
810 } else {
811 $new_bg_value .= $temp[ $i ] . ' ';
812 }
813 }
814 }
815
816 $new_bg_value = trim( $new_bg_value );
817 if ( $i !== $number_of_values - 1 ) {
818 $new_bg_value .= ',';
819 }
820 }
821
822 // Delete all background-properties.
823 foreach ( $background_prop_default as $bg_property => $default_value ) {
824 unset( $input_css[ $bg_property ] );
825 }
826
827 // Add new background property.
828 if ( $new_bg_value !== '' ) {
829 $input_css['background'] = $new_bg_value . $important;
830 } elseif ( isset( $input_css['background'] ) ) {
831 $input_css['background'] = 'none';
832 }
833
834 return $input_css;
835 }
836
837 /**
838 * Dissolve font property
839 *
840 * @param string $str_value - the string value.
841 * @return array
842 * @version 1.3
843 * @see merge_font()
844 */
845 public static function dissolve_short_font( $str_value ) {
846 $have = array();
847 $font_prop_default = & $GLOBALS['csstidy']['font_prop_default'];
848 $font_weight = array( 'normal', 'bold', 'bolder', 'lighter', '100', '200', '300', '400', '500', '600', '700', '800', '900' );
849 $font_variant = array( 'normal', 'small-caps' );
850 $font_style = array( 'normal', 'italic', 'oblique' );
851 $important = '';
852 $return = array(
853 'font-style' => null,
854 'font-variant' => null,
855 'font-weight' => null,
856 'font-size' => null,
857 'line-height' => null,
858 'font-family' => null,
859 );
860
861 if ( csstidy::is_important( $str_value ) ) {
862 $important = '!important';
863 $str_value = csstidy::gvw_important( $str_value );
864 }
865
866 $have['style'] = false;
867 $have['variant'] = false;
868 $have['weight'] = false;
869 $have['size'] = false;
870 // Detects if font-family consists of several words w/o quotes.
871 $multiwords = false;
872
873 // Workaround with multiple font-family.
874 $str_value = self::explode_ws( ',', trim( $str_value ) );
875
876 $str_value[0] = self::explode_ws( ' ', trim( $str_value[0] ) );
877
878 for ( $j = 0, $k = count( $str_value[0] ); $j < $k; $j++ ) {
879 if ( $have['weight'] === false && in_array( $str_value[0][ $j ], $font_weight, true ) ) {
880 $return['font-weight'] = $str_value[0][ $j ];
881 $have['weight'] = true;
882 } elseif ( $have['variant'] === false && in_array( $str_value[0][ $j ], $font_variant, true ) ) {
883 $return['font-variant'] = $str_value[0][ $j ];
884 $have['variant'] = true;
885 } elseif ( $have['style'] === false && in_array( $str_value[0][ $j ], $font_style, true ) ) {
886 $return['font-style'] = $str_value[0][ $j ];
887 $have['style'] = true;
888 } elseif ( $have['size'] === false && ( is_numeric( $str_value[0][ $j ][0] ) || $str_value[0][ $j ][0] === null || $str_value[0][ $j ][0] === '.' ) ) {
889 $size = self::explode_ws( '/', trim( $str_value[0][ $j ] ) );
890 $return['font-size'] = $size[0];
891 if ( isset( $size[1] ) ) {
892 $return['line-height'] = $size[1];
893 } else {
894 $return['line-height'] = ''; // don't add 'normal' !
895 }
896 $have['size'] = true;
897 } elseif ( isset( $return['font-family'] ) ) {
898 $return['font-family'] .= ' ' . $str_value[0][ $j ];
899 $multiwords = true;
900 } else {
901 $return['font-family'] = $str_value[0][ $j ];
902 }
903 }
904 // add quotes if we have several qords in font-family.
905 if ( $multiwords !== false ) {
906 $return['font-family'] = '"' . $return['font-family'] . '"';
907 }
908 $i = 1;
909 while ( isset( $str_value[ $i ] ) ) {
910 $return['font-family'] .= ',' . trim( $str_value[ $i ] );
911 ++$i;
912 }
913
914 // Fix for 100 and more font-size.
915 if ( $have['size'] === false && isset( $return['font-weight'] ) &&
916 is_numeric( $return['font-weight'][0] )
917 ) {
918 $return['font-size'] = $return['font-weight'];
919 unset( $return['font-weight'] );
920 }
921
922 foreach ( $font_prop_default as $font_prop => $default_value ) {
923 if ( $return[ $font_prop ] !== null ) {
924 $return[ $font_prop ] = $return[ $font_prop ] . $important;
925 } else {
926 $return[ $font_prop ] = $default_value . $important;
927 }
928 }
929 return $return;
930 }
931
932 /**
933 * Merges all fonts properties
934 *
935 * @param array $input_css - input CSS.
936 * @return array
937 * @version 1.3
938 * @see dissolve_short_font()
939 */
940 public static function merge_font( $input_css ) {
941 $font_prop_default = & $GLOBALS['csstidy']['font_prop_default'];
942 $new_font_value = '';
943 $important = '';
944 // Skip if not font-family and font-size set.
945 if ( isset( $input_css['font-family'] ) && isset( $input_css['font-size'] ) ) {
946 // fix several words in font-family - add quotes.
947 if ( isset( $input_css['font-family'] ) ) {
948 $families = explode( ',', $input_css['font-family'] );
949 $result_families = array();
950 foreach ( $families as $family ) {
951 $family = trim( $family );
952 $len = strlen( $family );
953 if ( strpos( $family, ' ' ) &&
954 ! ( ( $family[0] === '"' && $family[ $len - 1 ] === '"' ) ||
955 ( $family[0] === "'" && $family[ $len - 1 ] === "'" ) ) ) {
956 $family = '"' . $family . '"';
957 }
958 $result_families[] = $family;
959 }
960 $input_css['font-family'] = implode( ',', $result_families );
961 }
962 foreach ( $font_prop_default as $font_property => $default_value ) {
963
964 // Skip if property does not exist.
965 if ( ! isset( $input_css[ $font_property ] ) ) {
966 continue;
967 }
968
969 $cur_value = $input_css[ $font_property ];
970
971 // Skip if default value is used.
972 if ( $cur_value === $default_value ) {
973 continue;
974 }
975
976 // Remove !important.
977 if ( csstidy::is_important( $cur_value ) ) {
978 $important = '!important';
979 $cur_value = csstidy::gvw_important( $cur_value );
980 }
981
982 $new_font_value .= $cur_value;
983 // Add delimiter.
984 $new_font_value .= ( $font_property === 'font-size' &&
985 isset( $input_css['line-height'] ) ) ? '/' : ' ';
986 }
987
988 $new_font_value = trim( $new_font_value );
989
990 // Delete all font-properties.
991 foreach ( $font_prop_default as $font_property => $default_value ) {
992 if ( $font_property !== 'font' || ! $new_font_value ) {
993 unset( $input_css[ $font_property ] );
994 }
995 }
996
997 // Add new font property.
998 if ( $new_font_value !== '' ) {
999 $input_css['font'] = $new_font_value . $important;
1000 }
1001 }
1002
1003 return $input_css;
1004 }
1005
1006 }
1007