PluginProbe
TablePress – Tables in WordPress made easy / 1.12
TablePress – Tables in WordPress made easy v1.12
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / csstidy / class.csstidy_optimise.php

class.csstidy_optimise.php in TablePress – Tables in WordPress made easy 1.12, at libraries/csstidy/class.csstidy_optimise.php

1,198 lines 34.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CSSTidy Optimising PHP Class
4 *
5 * @package TablePress
6 * @subpackage CSS
7 * @author Florian Schmitz, Brett Zamir, Nikolay Matsievsky, Cedric Morin, Christopher Finke, Mark Scherer, Tobias Bäthge
8 * @since 1.0.0
9 */
10
11 // Prohibit direct script loading.
12 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
13
14 /**
15 * CSSTidy - CSS Parser and Optimiser
16 *
17 * CSS Optimising Class
18 * This class optimises CSS data generated by CSSTidy.
19 *
20 * Copyright 2005, 2006, 2007 Florian Schmitz
21 *
22 * This file is part of CSSTidy.
23 *
24 * CSSTidy is free software; you can redistribute it and/or modify
25 * it under the terms of the GNU Lesser General Public License as published by
26 * the Free Software Foundation; either version 2.1 of the License, or
27 * (at your option) any later version.
28 *
29 * CSSTidy is distributed in the hope that it will be useful,
30 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 * GNU Lesser General Public License for more details.
33 *
34 * You should have received a copy of the GNU Lesser General Public License
35 * along with this program. If not, see <https://www.gnu.org/licenses/>.
36 *
37 * @license https://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
38 * @package CSSTidy
39 * @author Florian Schmitz (floele at gmail dot com) 2005-2007
40 * @author Brett Zamir (brettz9 at yahoo dot com) 2007
41 * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010
42 * @author Cedric Morin (cedric at yterium dot com) 2010-2012
43 */
44
45 /**
46 * CSS Optimising Class
47 *
48 * This class optimises CSS data generated by CSSTidy.
49 *
50 * @package CSSTidy
51 * @author Florian Schmitz (floele at gmail dot com) 2005-2006
52 * @version 1.0
53 */
54 class TablePress_CSSTidy_optimise {
55
56 /**
57 * CSSTidy instance.
58 *
59 * @since 1.0.0
60 * @var CSSTidy
61 */
62 public $parser;
63
64 /**
65 * The parsed CSS.
66 *
67 * @since 1.0.0
68 * @var array
69 */
70 public $css = array();
71
72 /**
73 * The current sub-value.
74 *
75 * @since 1.0.0
76 * @var string
77 */
78 public $sub_value = '';
79
80 /**
81 * The current at rule (@media).
82 *
83 * @since 1.0.0
84 * @var string
85 */
86 public $at = '';
87
88 /**
89 * The current selector.
90 *
91 * @since 1.0.0
92 * @var string
93 */
94 public $selector = '';
95
96 /**
97 * The current property.
98 *
99 * @since 1.0.0
100 * @var string
101 */
102 public $property = '';
103
104 /**
105 * The current value.
106 *
107 * @since 1.0.0
108 * @var string
109 */
110 public $value = '';
111
112 /**
113 * Constructor.
114 *
115 * @since 1.0.0
116 *
117 * @param CSSTidy $css Instance of the CSSTidy class.
118 */
119 public function __construct( $css ) {
120 $this->parser = $css;
121 $this->css = &$css->css;
122 $this->sub_value = &$css->sub_value;
123 $this->at = &$css->at;
124 $this->selector = &$css->selector;
125 $this->property = &$css->property;
126 $this->value = &$css->value;
127 }
128
129 /**
130 * Optimises $css after parsing.
131 *
132 * @since 1.0.0
133 */
134 public function postparse() {
135 if ( $this->parser->get_cfg( 'preserve_css' ) ) {
136 return;
137 }
138
139 if ( 2 === (int) $this->parser->get_cfg( 'merge_selectors' ) ) {
140 foreach ( $this->css as $medium => $value ) {
141 if ( is_array( $value ) ) {
142 $this->merge_selectors( $this->css[ $medium ] );
143 }
144 }
145 }
146
147 if ( $this->parser->get_cfg( 'discard_invalid_selectors' ) ) {
148 foreach ( $this->css as $medium => $value ) {
149 if ( is_array( $value ) ) {
150 $this->discard_invalid_selectors( $this->css[ $medium ] );
151 }
152 }
153 }
154
155 if ( $this->parser->get_cfg( 'optimise_shorthands' ) > 0 ) {
156 foreach ( $this->css as $medium => $value ) {
157 if ( is_array( $value ) ) {
158 foreach ( $value as $selector => $value1 ) {
159 $this->css[ $medium ][ $selector ] = $this->merge_4value_shorthands( $this->css[ $medium ][ $selector ] );
160
161 if ( $this->parser->get_cfg( 'optimise_shorthands' ) < 2 ) {
162 continue;
163 }
164
165 $this->css[ $medium ][ $selector ] = $this->merge_font( $this->css[ $medium ][ $selector ] );
166
167 if ( $this->parser->get_cfg( 'optimise_shorthands' ) < 3 ) {
168 continue;
169 }
170
171 $this->css[ $medium ][ $selector ] = $this->merge_bg( $this->css[ $medium ][ $selector ] );
172 if ( empty( $this->css[ $medium ][ $selector ] ) ) {
173 unset( $this->css[ $medium ][ $selector ] );
174 }
175 }
176 }
177 }
178 }
179 }
180
181 /**
182 * Optimises values
183 *
184 * @since 1.0.0
185 */
186 public function value() {
187 $shorthands = &$this->parser->data['csstidy']['shorthands'];
188
189 // Optimise shorthand properties.
190 if ( isset( $shorthands[ $this->property ] ) && $this->parser->get_cfg( 'optimise_shorthands' ) > 0 ) {
191 $temp = $this->shorthand( $this->value ); // FIXME - move
192 if ( $temp !== $this->value ) {
193 $this->parser->log( 'Optimised shorthand notation (' . $this->property . '): Changed "' . $this->value . '" to "' . $temp . '"', 'Information' );
194 }
195 $this->value = $temp;
196 }
197
198 // Remove whitespace at !important
199 if ( $this->value !== $this->compress_important( $this->value ) ) {
200 $this->parser->log( 'Optimised !important', 'Information' );
201 }
202 }
203
204 /**
205 * Optimises shorthands.
206 *
207 * @since 1.0.0
208 */
209 public function shorthands() {
210 $shorthands = &$this->parser->data['csstidy']['shorthands'];
211
212 if ( ! $this->parser->get_cfg( 'optimise_shorthands' ) || $this->parser->get_cfg( 'preserve_css' ) ) {
213 return;
214 }
215
216 if ( 'font' === $this->property && $this->parser->get_cfg( 'optimise_shorthands' ) > 1 ) {
217 $this->css[ $this->at ][ $this->selector ]['font'] = '';
218 $this->parser->merge_css_blocks( $this->at, $this->selector, $this->dissolve_short_font( $this->value ) );
219 }
220 if ( 'background' === $this->property && $this->parser->get_cfg( 'optimise_shorthands' ) > 2 ) {
221 $this->css[ $this->at ][ $this->selector ]['background'] = '';
222 $this->parser->merge_css_blocks( $this->at, $this->selector, $this->dissolve_short_bg( $this->value ) );
223 }
224 if ( isset( $shorthands[ $this->property ] ) ) {
225 $this->parser->merge_css_blocks( $this->at, $this->selector, $this->dissolve_4value_shorthands( $this->property, $this->value ) );
226 if ( is_array( $shorthands[ $this->property ] ) ) {
227 $this->css[ $this->at ][ $this->selector ][ $this->property ] = '';
228 }
229 }
230 }
231
232 /**
233 * Optimises a sub-value.
234 *
235 * @since 1.0.0
236 */
237 public function subvalue() {
238 $replace_colors = &$this->parser->data['csstidy']['replace_colors'];
239
240 $this->sub_value = trim( $this->sub_value );
241 if ( '' === $this->sub_value ) { // caution : '0'
242 return;
243 }
244
245 $important = '';
246 if ( $this->parser->is_important( $this->sub_value ) ) {
247 $important = ' !important';
248 }
249 $this->sub_value = $this->parser->gvw_important( $this->sub_value );
250
251 // Compress font-weight.
252 if ( 'font-weight' === $this->property && $this->parser->get_cfg( 'compress_font-weight' ) ) {
253 if ( 'bold' === $this->sub_value ) {
254 $this->sub_value = '700';
255 $this->parser->log( 'Optimised font-weight: Changed "bold" to "700"', 'Information' );
256 } elseif ( 'normal' === $this->sub_value ) {
257 $this->sub_value = '400';
258 $this->parser->log( 'Optimised font-weight: Changed "normal" to "400"', 'Information' );
259 }
260 }
261
262 $temp = $this->compress_numbers( $this->sub_value );
263 if ( 0 !== strcasecmp( $temp, $this->sub_value ) ) {
264 if ( strlen( $temp ) > strlen( $this->sub_value ) ) {
265 $this->parser->log( 'Fixed invalid number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning' );
266 } else {
267 $this->parser->log( 'Optimised number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information' );
268 }
269 $this->sub_value = $temp;
270 }
271 if ( $this->parser->get_cfg( 'compress_colors' ) ) {
272 $temp = $this->cut_color( $this->sub_value );
273 if ( $temp !== $this->sub_value ) {
274 if ( isset( $replace_colors[ $this->sub_value ] ) ) {
275 $this->parser->log( 'Fixed invalid color name: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning' );
276 } else {
277 $this->parser->log( 'Optimised color: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information' );
278 }
279 $this->sub_value = $temp;
280 }
281 }
282 $this->sub_value .= $important;
283 }
284
285 /**
286 * Compresses shorthand values.
287 *
288 * Example: `margin: 1px 1px 1px 1px` will become `margin: 1px`.
289 *
290 * @since 1.0.0
291 *
292 * @param string $value Shorthand value.
293 * @return string Compressed value.
294 */
295 public function shorthand( $value ) {
296 $important = '';
297 if ( $this->parser->is_important( $value ) ) {
298 $values = $this->parser->gvw_important( $value );
299 $important = ' !important';
300 } else {
301 $values = $value;
302 }
303
304 $values = explode( ' ', $values );
305 switch ( count( $values ) ) {
306 case 4:
307 if ( $values[0] === $values[1] && $values[0] === $values[2] && $values[0] === $values[3] ) {
308 return $values[0] . $important;
309 } elseif ( $values[1] === $values[3] && $values[0] === $values[2] ) {
310 return $values[0] . ' ' . $values[1] . $important;
311 } elseif ( $values[1] === $values[3] ) {
312 return $values[0] . ' ' . $values[1] . ' ' . $values[2] . $important;
313 }
314 break;
315 case 3:
316 if ( $values[0] === $values[1] && $values[0] === $values[2] ) {
317 return $values[0] . $important;
318 } elseif ( $values[0] === $values[2] ) {
319 return $values[0] . ' ' . $values[1] . $important;
320 }
321 break;
322 case 2:
323 if ( $values[0] === $values[1] ) {
324 return $values[0] . $important;
325 }
326 break;
327 }
328
329 return $value;
330 }
331
332 /**
333 * Removes unnecessary whitespace in ! important.
334 *
335 * @since 1.0.0
336 *
337 * @param string $string String.
338 * @return string Cleaned string.
339 */
340 public function compress_important( &$string ) {
341 if ( $this->parser->is_important( $string ) ) {
342 $string = $this->parser->gvw_important( $string ) . ' !important';
343 }
344 return $string;
345 }
346
347 /**
348 * Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values.
349 *
350 * @since 1.0.0
351 *
352 * @param string $color Color value.
353 * @return string Compressed color.
354 */
355 public function cut_color( $color ) {
356 $replace_colors = &$this->parser->data['csstidy']['replace_colors'];
357
358 // If it's a string, don't touch!
359 if ( 0 === strncmp( $color, "'", 1 ) || 0 === strncmp( $color, '"', 1 ) ) {
360 return $color;
361 }
362
363 // Complex gradient expressions
364 if ( false !== strpos( $color, '(' ) && 0 !== strncmp( $color, 'rgb(', 4 ) ) {
365 // Don't touch properties within MSIE filters, those are to sensitive.
366 if ( false !== stripos( $color, 'progid:' ) ) {
367 return $color;
368 }
369 preg_match_all( ',rgb\([^)]+\),i', $color, $matches, PREG_SET_ORDER );
370 if ( count( $matches ) ) {
371 foreach ( $matches as $m ) {
372 $color = str_replace( $m[0], $this->cut_color( $m[0] ), $color );
373 }
374 }
375 preg_match_all( ',#[0-9a-f]{6}(?=[^0-9a-f]),i', $color, $matches, PREG_SET_ORDER );
376 if ( count( $matches ) ) {
377 foreach ( $matches as $m ) {
378 $color = str_replace( $m[0], $this->cut_color( $m[0] ), $color );
379 }
380 }
381 return $color;
382 }
383
384 // rgb(0,0,0) -> #000000 (or #000 in this case later)
385 if ( 0 === strncasecmp( $color, 'rgb(', 4 ) ) {
386 $color_tmp = substr( $color, 4, strlen( $color ) - 5 );
387 $color_tmp = explode( ',', $color_tmp );
388 for ( $i = 0; $i < count( $color_tmp ); $i++ ) {
389 $color_tmp[ $i ] = trim( $color_tmp[ $i ] );
390 if ( '%' === substr( $color_tmp[ $i ], -1 ) ) {
391 $color_tmp[ $i ] = round( ( 255 * $color_tmp[ $i ] ) / 100 );
392 }
393 if ( $color_tmp[ $i ] > 255 ) {
394 $color_tmp[ $i ] = 255;
395 }
396 }
397 $color = '#';
398 for ( $i = 0; $i < 3; $i++ ) {
399 if ( $color_tmp[ $i ] < 16 ) {
400 $color .= '0' . dechex( $color_tmp[ $i ] );
401 } else {
402 $color .= dechex( $color_tmp[ $i ] );
403 }
404 }
405 }
406
407 // Fix bad color names.
408 if ( isset( $replace_colors[ strtolower( $color ) ] ) ) {
409 $color = $replace_colors[ strtolower( $color ) ];
410 }
411
412 // #aabbcc -> #abc
413 if ( 7 === strlen( $color ) ) {
414 $color_temp = strtolower( $color );
415 if ( '#' === $color_temp[0] && $color_temp[1] === $color_temp[2] && $color_temp[3] === $color_temp[4] && $color_temp[5] === $color_temp[6] ) {
416 $color = '#' . $color[1] . $color[3] . $color[5];
417 }
418 }
419
420 switch ( strtolower( $color ) ) {
421 /* color name -> hex code */
422 case 'black':
423 return '#000';
424 case 'fuchsia':
425 return '#f0f';
426 case 'white':
427 return '#fff';
428 case 'yellow':
429 return '#ff0';
430
431 /* hex code -> color name */
432 case '#800000':
433 return 'maroon';
434 case '#ffa500':
435 return 'orange';
436 case '#808000':
437 return 'olive';
438 case '#800080':
439 return 'purple';
440 case '#008000':
441 return 'green';
442 case '#000080':
443 return 'navy';
444 case '#008080':
445 return 'teal';
446 case '#c0c0c0':
447 return 'silver';
448 case '#808080':
449 return 'gray';
450 case '#f00':
451 return 'red';
452 }
453
454 return $color;
455 }
456
457 /**
458 * Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1).
459 *
460 * @since 1.0.0
461 *
462 * @param string $subvalue Value.
463 * @return string Compressed value.
464 */
465 public function compress_numbers( $subvalue ) {
466 $unit_values = &$this->parser->data['csstidy']['unit_values'];
467 $color_values = &$this->parser->data['csstidy']['color_values'];
468
469 // for font:1em/1em sans-serif...;
470 if ( 'font' === $this->property ) {
471 $temp = explode( '/', $subvalue );
472 } else {
473 $temp = array( $subvalue );
474 }
475
476 for ( $l = 0; $l < count( $temp ); $l++ ) {
477 // If we are not dealing with a number at this point, do not optimize anything.
478 $number = $this->AnalyseCssNumber( $temp[ $l ] );
479 if ( false === $number ) {
480 return $subvalue;
481 }
482
483 // Fix bad colors.
484 if ( in_array( $this->property, $color_values ) ) {
485 if ( 3 === strlen( $temp[ $l ] ) || 6 === strlen( $temp[ $l ] ) ) {
486 $temp[ $l ] = '#' . $temp[ $l ];
487 } else {
488 $temp[ $l ] = '0';
489 }
490 continue;
491 }
492
493 if ( abs( $number[0] ) > 0 ) {
494 if ( '' === $number[1] && in_array( $this->property, $unit_values, true ) ) {
495 $number[1] = 'px';
496 }
497 } elseif ( 's' !== $number[1] && 'ms' !== $number[1] ) {
498 $number[1] = '';
499 }
500
501 $temp[ $l ] = $number[0] . $number[1];
502 }
503
504 return ( count( $temp ) > 1 ) ? $temp[0] . '/' . $temp[1] : $temp[0];
505 }
506
507 /**
508 * Checks if a given string is a CSS valid number. If it is, an array containing the value and unit is returned.
509 *
510 * @since 1.0.0
511 *
512 * @param string $string String.
513 * @return array ('unit' if unit is found or '' if no unit exists, number value) or false if no number.
514 */
515 public function analyseCssNumber( $string ) {
516 // most simple checks first
517 if ( 0 === strlen( $string ) || ctype_alpha( $string[0] ) ) {
518 return false;
519 }
520
521 $units = &$this->parser->data['csstidy']['units'];
522 $return = array( 0, '' );
523
524 $return[0] = floatval( $string );
525 if ( abs( $return[0] ) > 0 && abs( $return[0] ) < 1 ) {
526 if ( $return[0] < 0 ) {
527 $return[0] = '-' . ltrim( substr( $return[0], 1 ), '0' );
528 } else {
529 $return[0] = ltrim( $return[0], '0' );
530 }
531 }
532
533 // Look for unit and split from value if exists
534 foreach ( $units as $unit ) {
535 $expectUnitAt = strlen( $string ) - strlen( $unit );
536 if ( ! ( $unitInString = stristr( $string, $unit ) ) ) { // mb_strpos() fails with "false"
537 continue;
538 }
539 $actualPosition = strpos( $string, $unitInString );
540 if ( $expectUnitAt === $actualPosition ) {
541 $return[1] = $unit;
542 $string = substr( $string, 0, - strlen( $unit ) );
543 break;
544 }
545 }
546 if ( ! is_numeric( $string ) ) {
547 return false;
548 }
549 return $return;
550 }
551
552 /**
553 * Merges selectors with same properties. Example: a{color:red} b{color:red} -> a,b{color:red}
554 * Very basic and has at least one bug. Hopefully there is a replacement soon.
555 *
556 * @since 1.0.0
557 *
558 * @param array $array
559 * @return array
560 */
561 public function merge_selectors( array &$array ) {
562 $css = $array;
563 foreach ( $css as $key => $value ) {
564 if ( ! isset( $css[ $key ] ) ) {
565 continue;
566 }
567
568 // Check if properties also exist in another selector.
569 $keys = array();
570 // PHP bug (?) without $css = $array; here
571 foreach ( $css as $selector => $vali ) {
572 if ( $selector === $key ) {
573 continue;
574 }
575
576 if ( $css[ $key ] === $vali ) {
577 $keys[] = $selector;
578 }
579 }
580
581 if ( ! empty( $keys ) ) {
582 $newsel = $key;
583 unset( $css[ $key ] );
584 foreach ( $keys as $selector ) {
585 unset( $css[ $selector ] );
586 $newsel .= ',' . $selector;
587 }
588 $css[ $newsel ] = $value;
589 }
590 }
591 $array = $css;
592 }
593
594 /**
595 * Removes invalid selectors and their corresponding rule-sets as
596 * defined by 4.1.7 in REC-CSS2. This is a very rudimentary check
597 * and should be replaced by a full-blown parsing algorithm or
598 * regular expression.
599 *
600 * @since 1.0.0
601 *
602 * @param [type] $array [description]
603 */
604 public function discard_invalid_selectors( &$array ) {
605 foreach ( $array as $selector => $decls ) {
606 $ok = true;
607 $selectors = array_map( 'trim', explode( ',', $selector ) );
608 foreach ( $selectors as $s ) {
609 $simple_selectors = preg_split( '/\s*[+>~\s]\s*/', $s );
610 foreach ( $simple_selectors as $ss ) {
611 if ( '' === $ss ) {
612 $ok = false;
613 }
614 // could also check $ss for internal structure,
615 // but that probably would be too slow
616 }
617 }
618 if ( ! $ok ) {
619 unset( $array[ $selector ] );
620 }
621 }
622 }
623
624 /**
625 * Dissolves properties like padding:10px 10px 10px to padding-top:10px;padding-bottom:10px;...
626 *
627 * @since 1.0.0
628 *
629 * @param string $property [description]
630 * @param string $value [description]
631 *
632 * @return [type] [description]
633 */
634 public function dissolve_4value_shorthands( $property, $value ) {
635 $return = array();
636
637 $shorthands = &$this->parser->data['csstidy']['shorthands'];
638 if ( ! is_array( $shorthands[ $property ] ) ) {
639 $return[ $property ] = $value;
640 return $return;
641 }
642
643 $important = '';
644 if ( $this->parser->is_important( $value ) ) {
645 $value = $this->parser->gvw_important( $value );
646 $important = ' !important';
647 }
648 $values = explode( ' ', $value );
649
650 if ( 4 === count( $values ) ) {
651 for ( $i = 0; $i < 4; $i++ ) {
652 $return[ $shorthands[ $property ][ $i ] ] = $values[ $i ] . $important;
653 }
654 } elseif ( 3 === count( $values ) ) {
655 $return[ $shorthands[ $property ][0] ] = $values[0] . $important;
656 $return[ $shorthands[ $property ][1] ] = $values[1] . $important;
657 $return[ $shorthands[ $property ][3] ] = $values[1] . $important;
658 $return[ $shorthands[ $property ][2] ] = $values[2] . $important;
659 } elseif ( 2 === count( $values ) ) {
660 for ( $i = 0; $i < 4; $i++ ) {
661 $return[ $shorthands[ $property ][ $i ] ] = ( 0 !== $i % 2 ) ? $values[1] . $important : $values[0] . $important;
662 }
663 } else {
664 for ( $i = 0; $i < 4; $i++ ) {
665 $return[ $shorthands[ $property ][ $i ] ] = $values[0] . $important;
666 }
667 }
668
669 return $return;
670 }
671
672 /**
673 * Explodes a string as explode() does, however, not if $sep is escaped or within a string.
674 *
675 * @since 1.0.0
676 *
677 * @param string $sep Separator.
678 * @param string $string String.
679 * @return array
680 */
681 public function explode_ws( $sep, $string ) {
682 $status = 'st';
683 $to = '';
684
685 $output = array();
686 $num = 0;
687 for ( $i = 0, $len = strlen( $string ); $i < $len; $i++ ) {
688 switch ( $status ) {
689 case 'st':
690 if ( $string[ $i ] === $sep && ! $this->parser->escaped( $string, $i ) ) {
691 ++$num;
692 } elseif ( '"' === $string[ $i ] || "'" === $string[ $i ] || '(' === $string[ $i ] && ! $this->parser->escaped( $string, $i ) ) {
693 $status = 'str';
694 $to = ( '(' === $string[ $i ] ) ? ')' : $string[ $i ];
695 ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $string[ $i ] : $output[ $num ] = $string[ $i ];
696 } else {
697 ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $string[ $i ] : $output[ $num ] = $string[ $i ];
698 }
699 break;
700
701 case 'str':
702 if ( $string[ $i ] === $to && ! $this->parser->escaped( $string, $i ) ) {
703 $status = 'st';
704 }
705 ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $string[ $i ] : $output[ $num ] = $string[ $i ];
706 break;
707 }
708 }
709
710 if ( isset( $output[0] ) ) {
711 return $output;
712 } else {
713 return array( $output );
714 }
715 }
716
717 /**
718 * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands().
719 *
720 * @since 1.0.0
721 *
722 * @param [type] $array [description]
723 * @return [type] [description]
724 */
725 public function merge_4value_shorthands( $array ) {
726 $return = $array;
727 $shorthands = &$this->parser->data['csstidy']['shorthands'];
728
729 foreach ( $shorthands as $key => $value ) {
730 if ( isset( $array[ $value[0] ] ) && isset( $array[ $value[1] ] )
731 && isset( $array[ $value[2] ] ) && isset( $array[ $value[3] ] ) && 0 !== $value ) {
732 $return[ $key ] = '';
733
734 $important = '';
735 for ( $i = 0; $i < 4; $i++ ) {
736 $val = $array[ $value[ $i ] ];
737 if ( $this->parser->is_important( $val ) ) {
738 $important = ' !important';
739 $return[ $key ] .= $this->parser->gvw_important( $val ) . ' ';
740 } else {
741 $return[ $key ] .= $val . ' ';
742 }
743 unset( $return[ $value[ $i ] ] );
744 }
745 $return[ $key ] = $this->shorthand( trim( $return[ $key ] . $important ) );
746 }
747 }
748 return $return;
749 }
750
751 /**
752 * Dissolve background property.
753 *
754 * @TODO Full CSS3 compliance.
755 *
756 * @since 1.0.0
757 *
758 * @param string $str_value String value.
759 * @return array Array.
760 */
761 public function dissolve_short_bg( $str_value ) {
762 // Don't try to explose background gradient!
763 if ( false !== stripos( $str_value, 'gradient(' ) ) {
764 return array( 'background' => $str_value );
765 }
766
767 $background_prop_default = &$this->parser->data['csstidy']['background_prop_default'];
768 $repeat = array( 'repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space' );
769 $attachment = array( 'scroll', 'fixed', 'local' );
770 $clip = array( 'border', 'padding' );
771 $origin = array( 'border', 'padding', 'content' );
772 $pos = array( 'top', 'center', 'bottom', 'left', 'right' );
773 $important = '';
774 $return = array(
775 'background-image' => null,
776 'background-size' => null,
777 'background-repeat' => null,
778 'background-position' => null,
779 'background-attachment' => null,
780 'background-clip' => null,
781 'background-origin' => null,
782 'background-color' => null,
783 );
784
785 if ( $this->parser->is_important( $str_value ) ) {
786 $important = ' !important';
787 $str_value = $this->parser->gvw_important( $str_value );
788 }
789
790 $have = array();
791 $str_value = $this->explode_ws( ',', $str_value );
792 for ( $i = 0; $i < count( $str_value ); $i++ ) {
793 $have['clip'] = false;
794 $have['pos'] = false;
795 $have['color'] = false;
796 $have['bg'] = false;
797
798 if ( is_array( $str_value[ $i ] ) ) {
799 $str_value[ $i ] = $str_value[ $i ][0];
800 }
801 $str_value[ $i ] = $this->explode_ws( ' ', trim( $str_value[ $i ] ) );
802
803 for ( $j = 0; $j < count( $str_value[ $i ] ); $j++ ) {
804 if ( false === $have['bg'] && ( 'url(' === substr( $str_value[ $i ][ $j ], 0, 4 ) || 'none' === $str_value[ $i ][ $j ] ) ) {
805 $return['background-image'] .= $str_value[ $i ][ $j ] . ',';
806 $have['bg'] = true;
807 } elseif ( in_array( $str_value[ $i ][ $j ], $repeat, true ) ) {
808 $return['background-repeat'] .= $str_value[ $i ][ $j ] . ',';
809 } elseif ( in_array( $str_value[ $i ][ $j ], $attachment, true ) ) {
810 $return['background-attachment'] .= $str_value[ $i ][ $j ] . ',';
811 } elseif ( in_array( $str_value[ $i ][ $j ], $clip, true ) && ! $have['clip'] ) {
812 $return['background-clip'] .= $str_value[ $i ][ $j ] . ',';
813 $have['clip'] = true;
814 } elseif ( in_array( $str_value[ $i ][ $j ], $origin, true ) ) {
815 $return['background-origin'] .= $str_value[ $i ][ $j ] . ',';
816 } elseif ( '(' === $str_value[ $i ][ $j ][0] ) {
817 $return['background-size'] .= substr( $str_value[ $i ][ $j ], 1, -1 ) . ',';
818 } elseif ( in_array( $str_value[ $i ][ $j ], $pos, true ) || is_numeric( $str_value[ $i ][ $j ][0] ) || null === $str_value[ $i ][ $j ][0] || '-' === $str_value[ $i ][ $j ][0] || '.' === $str_value[ $i ][ $j ][0] ) {
819 $return['background-position'] .= $str_value[ $i ][ $j ];
820 if ( ! $have['pos'] ) {
821 $return['background-position'] .= ' ';
822 } else {
823 $return['background-position'] .= ',';
824 }
825 $have['pos'] = true;
826 } elseif ( ! $have['color'] ) {
827 $return['background-color'] .= $str_value[ $i ][ $j ] . ',';
828 $have['color'] = true;
829 }
830 }
831 }
832
833 foreach ( $background_prop_default as $bg_prop => $default_value ) {
834 if ( null !== $return[ $bg_prop ] ) {
835 $return[ $bg_prop ] = substr( $return[ $bg_prop ], 0, -1 ) . $important;
836 } else {
837 $return[ $bg_prop ] = $default_value . $important;
838 }
839 }
840 return $return;
841 }
842
843 /**
844 * Merges all background properties.
845 *
846 * @TODO Full CSS3 compliance.
847 *
848 * @since 1.0.0
849 *
850 * @param array $input_css CSS.
851 * @return array Array.
852 */
853 public function merge_bg( array $input_css ) {
854 $background_prop_default = &$this->parser->data['csstidy']['background_prop_default'];
855 // Max number of background images. CSS3 not yet fully implemented.
856 $number_of_values = @max( count( $this->explode_ws( ',', $input_css['background-image'] ) ), count( $this->explode_ws( ',', $input_css['background-color'] ) ), 1 );
857 // Array with background images to check if BG image exists.
858 $bg_img_array = @$this->explode_ws( ',', $this->parser->gvw_important( $input_css['background-image'] ) );
859 $new_bg_value = '';
860 $important = '';
861
862 // If background properties is here and not empty, don't try anything.
863 if ( isset( $input_css['background'] ) && $input_css['background'] ) {
864 return $input_css;
865 }
866
867 for ( $i = 0; $i < $number_of_values; $i++ ) {
868 foreach ( $background_prop_default as $bg_property => $default_value ) {
869 // Skip if property does not exist.
870 if ( ! isset( $input_css[ $bg_property ] ) ) {
871 continue;
872 }
873
874 $cur_value = $input_css[ $bg_property ];
875 // Skip all optimisation if gradient() somewhere.
876 if ( false !== stripos( $cur_value, 'gradient(' ) ) {
877 return $input_css;
878 }
879
880 // Skip some properties if there is no background image.
881 if ( ( ! isset( $bg_img_array[ $i ] ) || 'none' === $bg_img_array[ $i ] )
882 && ( 'background-size' === $bg_property || 'background-position' === $bg_property || 'background-attachment' === $bg_property || 'background-repeat' === $bg_property ) ) {
883 continue;
884 }
885
886 // Remove !important.
887 if ( $this->parser->is_important( $cur_value ) ) {
888 $important = ' !important';
889 $cur_value = $this->parser->gvw_important( $cur_value );
890 }
891
892 // Do not add default values.
893 if ( $cur_value === $default_value ) {
894 continue;
895 }
896
897 $temp = $this->explode_ws( ',', $cur_value );
898
899 if ( isset( $temp[ $i ] ) ) {
900 if ( 'background-size' === $bg_property ) {
901 $new_bg_value .= '(' . $temp[ $i ] . ') ';
902 } else {
903 $new_bg_value .= $temp[ $i ] . ' ';
904 }
905 }
906 }
907
908 $new_bg_value = trim( $new_bg_value );
909 if ( $i !== $number_of_values - 1 ) {
910 $new_bg_value .= ',';
911 }
912 }
913
914 // Delete all background properties.
915 foreach ( $background_prop_default as $bg_property => $default_value ) {
916 unset( $input_css[ $bg_property ] );
917 }
918
919 // Add new background property.
920 if ( '' !== $new_bg_value ) {
921 $input_css['background'] = $new_bg_value . $important;
922 } elseif ( isset( $input_css['background'] ) ) {
923 $input_css['background'] = 'none';
924 }
925
926 return $input_css;
927 }
928
929 /**
930 * Dissolve font property.
931 *
932 * @since 1.0.0
933 *
934 * @param [type] $str_value [description]
935 * @return [type] [description]
936 */
937 public function dissolve_short_font( $str_value ) {
938 $font_prop_default = &$this->parser->data['csstidy']['font_prop_default'];
939 $font_weight = array( 'normal', 'bold', 'bolder', 'lighter', 100, 200, 300, 400, 500, 600, 700, 800, 900 );
940 $font_variant = array( 'normal', 'small-caps' );
941 $font_style = array( 'normal', 'italic', 'oblique' );
942 $important = '';
943 $return = array(
944 'font-style' => null,
945 'font-variant' => null,
946 'font-weight' => null,
947 'font-size' => null,
948 'line-height' => null,
949 'font-family' => null,
950 );
951
952 if ( $this->parser->is_important( $str_value ) ) {
953 $important = ' !important';
954 $str_value = $this->parser->gvw_important( $str_value );
955 }
956
957 $have = array();
958 $have['style'] = false;
959 $have['variant'] = false;
960 $have['weight'] = false;
961 $have['size'] = false;
962 // Detects if font-family consists of several words w/o quotes.
963 $multiwords = false;
964
965 // Workaround with multiple font-families.
966 $str_value = $this->explode_ws( ',', trim( $str_value ) );
967
968 $str_value[0] = $this->explode_ws( ' ', trim( $str_value[0] ) );
969
970 for ( $j = 0; $j < count( $str_value[0] ); $j++ ) {
971 if ( false === $have['weight'] && in_array( $str_value[0][ $j ], $font_weight ) ) {
972 $return['font-weight'] = $str_value[0][ $j ];
973 $have['weight'] = true;
974 } elseif ( false === $have['variant'] && in_array( $str_value[0][ $j ], $font_variant ) ) {
975 $return['font-variant'] = $str_value[0][ $j ];
976 $have['variant'] = true;
977 } elseif ( false === $have['style'] && in_array( $str_value[0][ $j ], $font_style ) ) {
978 $return['font-style'] = $str_value[0][ $j ];
979 $have['style'] = true;
980 } elseif ( false === $have['size'] && ( is_numeric( $str_value[0][ $j ][0] ) || null === $str_value[0][ $j ][0] || '.' === $str_value[0][ $j ][0] ) ) {
981 $size = $this->explode_ws( '/', trim( $str_value[0][ $j ] ) );
982 $return['font-size'] = $size[0];
983 if ( isset( $size[1] ) ) {
984 $return['line-height'] = $size[1];
985 } else {
986 $return['line-height'] = ''; // Don't add 'normal'!
987 }
988 $have['size'] = true;
989 } else {
990 if ( isset( $return['font-family'] ) ) {
991 $return['font-family'] .= ' ' . $str_value[0][ $j ];
992 $multiwords = true;
993 } else {
994 $return['font-family'] = $str_value[0][ $j ];
995 }
996 }
997 }
998 // Add quotes if we have several words in font-family.
999 if ( false !== $multiwords ) {
1000 $return['font-family'] = '"' . $return['font-family'] . '"';
1001 }
1002 $i = 1;
1003 while ( isset( $str_value[ $i ] ) ) {
1004 $return['font-family'] .= ',' . trim( $str_value[ $i ] );
1005 $i++;
1006 }
1007
1008 // Fix for font-size 100 and higher.
1009 if ( false === $have['size'] && isset( $return['font-weight'] ) && is_numeric( $return['font-weight'][0] ) ) {
1010 $return['font-size'] = $return['font-weight'];
1011 unset( $return['font-weight'] );
1012 }
1013
1014 foreach ( $font_prop_default as $font_prop => $default_value ) {
1015 if ( null !== $return[ $font_prop ] ) {
1016 $return[ $font_prop ] = $return[ $font_prop ] . $important;
1017 } else {
1018 $return[ $font_prop ] = $default_value . $important;
1019 }
1020 }
1021 return $return;
1022 }
1023
1024 /**
1025 * Merges all fonts properties.
1026 *
1027 * @since 1.0.0
1028 *
1029 * @param [type] $input_css [description]
1030 * @return [type] [description]
1031 */
1032 public function merge_font( $input_css ) {
1033 $font_prop_default = &$this->parser->data['csstidy']['font_prop_default'];
1034 $new_font_value = '';
1035 $important = '';
1036 // Skip if no font-family and font-size set.
1037 if ( isset( $input_css['font-family'] ) && isset( $input_css['font-size'] ) && 'inherit' !== $input_css['font-family'] ) {
1038 // Fix several words in font-family - add quotes.
1039 if ( isset( $input_css['font-family'] ) ) {
1040 $families = explode( ',', $input_css['font-family'] );
1041 $result_families = array();
1042 foreach ( $families as $family ) {
1043 $family = trim( $family );
1044 $len = strlen( $family );
1045 if ( strpos( $family, ' ' ) &&
1046 ! ( ( '"' === $family[0] && '"' === $family[ $len - 1 ] ) ||
1047 ( "'" === $family[0] && "'" === $family[ $len - 1 ] ) ) ) {
1048 $family = '"' . $family . '"';
1049 }
1050 $result_families[] = $family;
1051 }
1052 $input_css['font-family'] = implode( ',', $result_families );
1053 }
1054 foreach ( $font_prop_default as $font_property => $default_value ) {
1055 // Skip if property does not exist.
1056 if ( ! isset( $input_css[ $font_property ] ) ) {
1057 continue;
1058 }
1059
1060 $cur_value = $input_css[ $font_property ];
1061
1062 // Skip if default value is used.
1063 if ( $cur_value === $default_value ) {
1064 continue;
1065 }
1066
1067 // Remove !important.
1068 if ( $this->parser->is_important( $cur_value ) ) {
1069 $important = ' !important';
1070 $cur_value = $this->parser->gvw_important( $cur_value );
1071 }
1072
1073 $new_font_value .= $cur_value;
1074 // Add delimiter.
1075 $new_font_value .= ( 'font-size' === $font_property && isset( $input_css['line-height'] ) ) ? '/' : ' ';
1076 }
1077
1078 $new_font_value = trim( $new_font_value );
1079
1080 // Delete all font properties.
1081 foreach ( $font_prop_default as $font_property => $default_value ) {
1082 if ( 'font' !== $font_property || ! $new_font_value ) {
1083 unset( $input_css[ $font_property ] );
1084 }
1085 }
1086
1087 // Add new font property.
1088 if ( '' !== $new_font_value ) {
1089 $input_css['font'] = $new_font_value . $important;
1090 }
1091 }
1092
1093 return $input_css;
1094 }
1095
1096 } // class TablePress_CSSTidy_optimise
1097
1098 /**
1099 * Sanitization class
1100 */
1101 class TablePress_CSSTidy_custom_sanitize extends TablePress_CSSTidy_optimise {
1102
1103 /**
1104 * [$props_w_urls description]
1105 *
1106 * @since 1.0.0
1107 * @var array
1108 */
1109 protected $props_w_urls = array( 'background', 'background-image', 'list-style', 'list-style-image' );
1110
1111 /**
1112 * [$allowed_protocols description]
1113 *
1114 * @since 1.0.0
1115 * @var array
1116 */
1117 protected $allowed_protocols = array( 'http', 'https' );
1118
1119 /**
1120 * [__construct description]
1121 *
1122 * @since 1.0.0
1123 *
1124 * @param [type] $css [description]
1125 */
1126 public function __construct( $css ) {
1127 return parent::__construct( $css );
1128 }
1129
1130 /**
1131 * [postparse description]
1132 *
1133 * @since 1.0.0
1134 *
1135 * @return [type] [description]
1136 */
1137 public function postparse() {
1138 if ( ! empty( $this->parser->import ) ) {
1139 $this->parser->import = array();
1140 }
1141 if ( ! empty( $this->parser->charset ) ) {
1142 $this->parser->charset = array();
1143 }
1144
1145 return parent::postparse();
1146 }
1147
1148 /**
1149 * [subvalue description]
1150 *
1151 * @since 1.0.0
1152 *
1153 * @return [type] [description]
1154 */
1155 public function subvalue() {
1156 $this->sub_value = trim( $this->sub_value );
1157
1158 // Send any urls through our filter
1159 if ( preg_match( '!^\\s*url\\s*(?:\\(|\\\\0028)(.*)(?:\\)|\\\\0029).*$!Dis', $this->sub_value, $matches ) ) {
1160 $this->sub_value = $this->clean_url( $matches[1] );
1161 }
1162
1163 // Strip any expressions
1164 if ( preg_match( '!^\\s*expression!Dis', $this->sub_value ) ) {
1165 $this->sub_value = '';
1166 }
1167
1168 return parent::subvalue();
1169 }
1170
1171 /**
1172 * [clean_url description]
1173 *
1174 * @since 1.0.0
1175 *
1176 * @param [type] $url [description]
1177 * @return [type] [description]
1178 */
1179 protected function clean_url( $url ) {
1180 // Clean up the string.
1181 $url = trim( $url, "'\"\r\n " );
1182
1183 // Check against whitelist for properties allowed to have URL values.
1184 if ( ! in_array( $this->property, $this->props_w_urls ) ) {
1185 return '';
1186 }
1187
1188 $url = wp_kses_bad_protocol_once( $url, $this->allowed_protocols );
1189
1190 if ( empty( $url ) ) {
1191 return '';
1192 }
1193
1194 return "url('$url')";
1195 }
1196
1197 } // class TablePress_CSSTidy_custom_sanitize
1198