PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.7.3
Jetpack – WP Security, Backup, Speed, & Growth v12.7.3
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.php
jetpack / modules / custom-css / csstidy Last commit date
class.csstidy-ctype.php 2 years ago class.csstidy-optimise.php 2 years ago class.csstidy-print.php 2 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 2 years ago data.inc.php 2 years ago lang.inc.php 2 years ago wordpress-standard.tpl 11 years ago
class.csstidy.php
1310 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * CSSTidy - CSS Parser and Optimiser
4 *
5 * CSS Parser class
6 *
7 * Copyright 2005, 2006, 2007 Florian Schmitz
8 *
9 * This file is part of CSSTidy.
10 *
11 * CSSTidy is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2.1 of the License, or
14 * (at your option) any later version.
15 *
16 * CSSTidy is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 *
24 * @license https://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
25 * @package csstidy
26 * @author Florian Schmitz (floele at gmail dot com) 2005-2007
27 * @author Brett Zamir (brettz9 at yahoo dot com) 2007
28 * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010
29 * @author Cedric Morin (cedric at yterium dot com) 2010
30 */
31
32 // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
33
34 /**
35 * Defines ctype functions if required
36 *
37 * @version 1.0
38 */
39 require_once __DIR__ . '/class.csstidy-ctype.php';
40
41 /**
42 * Various CSS data needed for correct optimisations etc.
43 *
44 * @version 1.3
45 */
46 require __DIR__ . '/data.inc.php';
47
48 /**
49 * Contains a class for printing CSS code
50 *
51 * @version 1.0
52 */
53 require __DIR__ . '/class.csstidy-print.php';
54
55 /**
56 * Contains a class for optimising CSS code
57 *
58 * @version 1.0
59 */
60 require __DIR__ . '/class.csstidy-optimise.php';
61
62 /**
63 * CSS Parser class
64
65 * This class represents a CSS parser which reads CSS code and saves it in an array.
66 * In opposite to most other CSS parsers, it does not use regular expressions and
67 * thus has full CSS2 support and a higher reliability.
68 * Additional to that it applies some optimisations and fixes to the CSS code.
69 * An online version should be available here: https://cdburnerxp.se/cssparse/css_optimiser.php
70 *
71 * @package csstidy
72 * @author Florian Schmitz (floele at gmail dot com) 2005-2006
73 * @version 1.3.1
74 */
75 #[AllowDynamicProperties]
76 class csstidy { // phpcs:ignore
77
78 /**
79 * Saves the parsed CSS. This array is empty if preserve_css is on.
80 *
81 * @var array
82 * @access public
83 */
84 public $css = array();
85 /**
86 * Saves the parsed CSS (raw)
87 *
88 * @var array
89 * @access private
90 */
91 public $tokens = array();
92 /**
93 * Printer class
94 *
95 * @see csstidy_print
96 * @var object
97 * @access public
98 */
99 public $print;
100 /**
101 * Optimiser class
102 *
103 * @see csstidy_optimise
104 * @var object
105 * @access private
106 */
107 public $optimise;
108 /**
109 * Saves the CSS charset (@charset)
110 *
111 * @var string
112 * @access private
113 */
114 public $charset = '';
115 /**
116 * Saves all @import URLs
117 *
118 * @var array
119 * @access private
120 */
121 public $import = array();
122 /**
123 * Saves the namespace
124 *
125 * @var string
126 * @access private
127 */
128 public $namespace = '';
129 /**
130 * Contains the version of csstidy
131 *
132 * @var string
133 * @access private
134 */
135 public $version = '1.3';
136 /**
137 * Stores the settings
138 *
139 * @var array
140 * @access private
141 */
142 public $settings = array();
143 /**
144 * Saves the parser-status.
145 *
146 * Possible values:
147 * - is = in selector
148 * - ip = in property
149 * - iv = in value
150 * - instr = in string (started at " or ' or ( )
151 * - ic = in comment (ignore everything)
152 * - at = in @-block
153 *
154 * @var string
155 * @access private
156 */
157 public $status = 'is';
158 /**
159 * Saves the current at rule (@media)
160 *
161 * @var string
162 * @access private
163 */
164 public $at = '';
165 /**
166 * Saves the current selector
167 *
168 * @var string
169 * @access private
170 */
171 public $selector = '';
172 /**
173 * Saves the current property
174 *
175 * @var string
176 * @access private
177 */
178 public $property = '';
179 /**
180 * Saves the position of , in selectors
181 *
182 * @var array
183 * @access private
184 */
185 public $sel_separate = array();
186 /**
187 * Saves the current value
188 *
189 * @var string
190 * @access private
191 */
192 public $value = '';
193 /**
194 * Saves the current sub-value
195 *
196 * Example for a subvalue:
197 * background:url(foo.png) red no-repeat;
198 * "url(foo.png)", "red", and "no-repeat" are subvalues,
199 * separated by whitespace
200 *
201 * @var string
202 * @access private
203 */
204 public $sub_value = '';
205 /**
206 * Array which saves all subvalues for a property.
207 *
208 * @var array
209 * @see sub_value
210 * @access private
211 */
212 public $sub_value_arr = array();
213 /**
214 * Saves the stack of characters that opened the current strings
215 *
216 * @var array
217 * @access private
218 */
219 public $str_char = array();
220 /**
221 * Current strings.
222 *
223 * @var array
224 * @access private
225 */
226 public $cur_string = array();
227 /**
228 * Status from which the parser switched to ic or instr
229 *
230 * @var array
231 * @access private
232 */
233 public $from = array();
234 /**
235 /**
236 * =true if in invalid at-rule
237 *
238 * @var bool
239 * @access private
240 */
241 public $invalid_at = false;
242 /**
243 * =true if something has been added to the current selector
244 *
245 * @var bool
246 * @access private
247 */
248 public $added = false;
249 /**
250 * Array which saves the message log
251 *
252 * @var array
253 * @access private
254 */
255 public $log = array();
256 /**
257 * Saves the line number
258 *
259 * @var integer
260 * @access private
261 */
262 public $line = 1;
263 /**
264 * Marks if we need to leave quotes for a string
265 *
266 * @var array
267 * @access private
268 */
269 public $quoted_string = array();
270
271 /**
272 * List of tokens
273 *
274 * @var string
275 */
276 public $tokens_list = '';
277
278 /**
279 * Loads standard template and sets default settings.
280 *
281 * @access private
282 * @version 1.3
283 */
284 public function __construct() {
285 $this->settings['remove_bslash'] = true;
286 $this->settings['compress_colors'] = true;
287 $this->settings['compress_font-weight'] = true;
288 $this->settings['lowercase_s'] = false;
289
290 /*
291 1 common shorthands optimization
292 2 + font property optimization
293 3 + background property optimization
294 */
295 $this->settings['optimise_shorthands'] = 1;
296 $this->settings['remove_last_;'] = true;
297 /* rewrite all properties with low case, better for later gzip OK, safe*/
298 $this->settings['case_properties'] = 1;
299
300 /*
301 * sort properties in alpabetic order, better for later gzip
302 * but can cause trouble in case of overiding same propertie or using hack
303 */
304 $this->settings['sort_properties'] = false;
305
306 /*
307 1, 3, 5, etc -- enable sorting selectors inside @media: a{}b{}c{}
308 2, 5, 8, etc -- enable sorting selectors inside one CSS declaration: a,b,c{}
309 preserve order by default cause it can break functionnality
310 */
311 $this->settings['sort_selectors'] = 0;
312 /* is dangeroues to be used: CSS is broken sometimes */
313 $this->settings['merge_selectors'] = 0;
314 /* preserve or not browser hacks */
315 $this->settings['discard_invalid_selectors'] = false;
316 $this->settings['discard_invalid_properties'] = false;
317 $this->settings['css_level'] = 'CSS2.1';
318 $this->settings['preserve_css'] = false;
319 $this->settings['timestamp'] = false;
320 $this->settings['template'] = ''; // say that propertie exist.
321 $this->set_cfg( 'template', 'default' ); // call load_template.
322 /* Tells csstidy_optimise to keep leading zeros on decimal numbers, e.g., 0.7 */
323 $this->settings['preserve_leading_zeros'] = false;
324 $this->optimise = new csstidy_optimise( $this );
325
326 $this->tokens_list = & $GLOBALS['csstidy']['tokens'];
327 }
328
329 /**
330 * Call the construct function.
331 */
332 public function csstidy() {
333 $this->__construct();
334 }
335
336 /**
337 * Get the value of a setting.
338 *
339 * @param string $setting - the settings.
340 * @access public
341 * @return mixed
342 * @version 1.0
343 */
344 public function get_cfg( $setting ) {
345 if ( isset( $this->settings[ $setting ] ) ) {
346 return $this->settings[ $setting ];
347 }
348 return false;
349 }
350
351 /**
352 * Load a template
353 *
354 * @param string $template used by set_cfg to load a template via a configuration setting.
355 * @access private
356 * @version 1.4
357 */
358 public function _load_template( $template ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
359 switch ( $template ) {
360 case 'default':
361 $this->load_template( 'default' );
362 break;
363
364 case 'highest':
365 $this->load_template( 'highest_compression' );
366 break;
367
368 case 'high':
369 $this->load_template( 'high_compression' );
370 break;
371
372 case 'low':
373 $this->load_template( 'low_compression' );
374 break;
375
376 default:
377 $this->load_template( $template );
378 break;
379 }
380 }
381
382 /**
383 * Set the value of a setting.
384 *
385 * @param string $setting - the setting.
386 * @param mixed $value - the value we're setting.
387 * @access public
388 * @return bool
389 * @version 1.0
390 */
391 public function set_cfg( $setting, $value = null ) {
392 if ( is_array( $setting ) && null === $value ) {
393 foreach ( $setting as $setprop => $setval ) {
394 $this->settings[ $setprop ] = $setval;
395 }
396 if ( array_key_exists( 'template', $setting ) ) {
397 $this->_load_template( $this->settings['template'] );
398 }
399 return true;
400 } elseif ( isset( $this->settings[ $setting ] ) && '' !== $value ) {
401 $this->settings[ $setting ] = $value;
402 if ( 'template' === $setting ) {
403 $this->_load_template( $this->settings['template'] );
404 }
405 return true;
406 }
407 return false;
408 }
409
410 /**
411 * Adds a token to $this->tokens
412 *
413 * @param mixed $type - the type.
414 * @param string $data - data.
415 * @param bool $do add a token even if preserve_css is off.
416 * @access private
417 * @version 1.0
418 */
419 public function _add_token( $type, $data, $do = false ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
420 if ( $this->get_cfg( 'preserve_css' ) || $do ) {
421 $this->tokens[] = array( $type, ( COMMENT === $type ) ? $data : trim( $data ) );
422 }
423 }
424
425 /**
426 * Add a message to the message log
427 *
428 * @param string $message - the message.
429 * @param string $type - the type of message.
430 * @param integer $line - the line.
431 * @access private
432 * @version 1.0
433 */
434 public function log( $message, $type, $line = -1 ) {
435 if ( -1 === $line ) {
436 $line = $this->line;
437 }
438 $line = (int) $line;
439 $add = array(
440 'm' => $message,
441 't' => $type,
442 );
443 if ( ! isset( $this->log[ $line ] ) || ! in_array( $add, $this->log[ $line ], true ) ) {
444 $this->log[ $line ][] = $add;
445 }
446 }
447
448 /**
449 * Parse unicode notations and find a replacement character
450 *
451 * @param string $string - a string.
452 * @param integer $i - counting integer.
453 * @access private
454 * @return string
455 * @version 1.2
456 */
457 public function _unicode( &$string, &$i ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
458 ++$i;
459 $add = '';
460 $replaced = false;
461
462 while ( $i < strlen( $string ) && ( ctype_xdigit( $string[ $i ] ) || ctype_space( $string[ $i ] ) ) && strlen( $add ) < 6 ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found
463 $add .= $string[ $i ];
464
465 if ( ctype_space( $string[ $i ] ) ) {
466 break;
467 }
468 ++$i;
469 }
470
471 if ( hexdec( $add ) > 47 && hexdec( $add ) < 58 || hexdec( $add ) > 64 && hexdec( $add ) < 91 || hexdec( $add ) > 96 && hexdec( $add ) < 123 ) {
472 $this->log( 'Replaced unicode notation: Changed \\' . $add . ' to ' . chr( hexdec( $add ) ), 'Information' );
473 $add = chr( hexdec( $add ) );
474 $replaced = true;
475 } else {
476 $add = trim( '\\' . $add );
477 }
478
479 if ( @ctype_xdigit( $string[ $i + 1 ] ) && ctype_space( $string[ $i ] )
480 && ! $replaced || ! ctype_space( $string[ $i ] ) ) {
481 --$i;
482 }
483
484 if ( '\\' !== $add || ! $this->get_cfg( 'remove_bslash' ) || strpos( $this->tokens_list, $string[ $i + 1 ] ) !== false ) {
485 return $add;
486 }
487
488 if ( '\\' === $add ) {
489 $this->log( 'Removed unnecessary backslash', 'Information' );
490 }
491 return '';
492 }
493
494 /**
495 * Write formatted output to a file
496 *
497 * @param string $filename - the file na,e.
498 * @param string $doctype when printing formatted, is a shorthand for the document type.
499 * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet.
500 * @param string $title when printing formatted, is the title to be added in the head of the document.
501 * @param string $lang when printing formatted, gives a two-letter language code to be added to the output.
502 * @access public
503 * @version 1.4
504 */
505 public function write_page( $filename, $doctype = 'xhtml1.1', $externalcss = true, $title = '', $lang = 'en' ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
506 $this->write( $filename, true );
507 }
508
509 /**
510 * Write plain output to a file
511 *
512 * @param string $filename the file name.
513 * @param bool $formatted whether to print formatted or not.
514 * @param string $doctype when printing formatted, is a shorthand for the document type.
515 * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet.
516 * @param string $title when printing formatted, is the title to be added in the head of the document.
517 * @param string $lang when printing formatted, gives a two-letter language code to be added to the output.
518 * @param bool $pre_code whether to add pre and code tags around the code (for light HTML formatted templates).
519 * @access public
520 * @version 1.4
521 */
522 public function write( $filename, $formatted = false, $doctype = 'xhtml1.1', $externalcss = true, $title = '', $lang = 'en', $pre_code = true ) {
523 $filename .= ( $formatted ) ? '.xhtml' : '.css';
524
525 if ( ! is_dir( 'temp' ) ) {
526 $madedir = mkdir( 'temp' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir
527 if ( ! $madedir ) {
528 print 'Could not make directory "temp" in ' . __DIR__;
529 exit;
530 }
531 }
532 $handle = fopen( 'temp/' . $filename, 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
533 if ( $handle ) {
534 if ( ! $formatted ) {
535 fwrite( $handle, $this->print->plain() ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite
536 } else {
537 fwrite( $handle, $this->print->formatted_page( $doctype, $externalcss, $title, $lang, $pre_code ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite
538 }
539 }
540 fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
541 }
542
543 /**
544 * Loads a new template
545 *
546 * @param string $content either filename (if $from_file == true), content of a template file, "high_compression", "highest_compression", "low_compression", or "default".
547 * @param bool $from_file uses $content as filename if true.
548 * @access public
549 * @version 1.1
550 * @see http://csstidy.sourceforge.net/templates.php
551 */
552 public function load_template( $content, $from_file = true ) {
553 $predefined_templates = & $GLOBALS['csstidy']['predefined_templates'];
554 if ( 'high_compression' === $content || 'default' === $content || 'highest_compression' === $content || 'low_compression' === $content ) {
555 $this->template = $predefined_templates[ $content ];
556 return;
557 }
558
559 if ( $from_file ) {
560 $content = strip_tags( file_get_contents( $content ), '<span>' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
561 }
562 $content = str_replace( "\r\n", "\n", $content ); // Unify newlines (because the output also only uses \n).
563 $template = explode( '|', $content );
564
565 $this->template = array_replace( $this->template, $template );
566 }
567
568 /**
569 * Starts parsing from URL
570 *
571 * @param string $url - the URL.
572 * @access public
573 * @version 1.0
574 */
575 public function parse_from_url( $url ) {
576 return $this->parse( @file_get_contents( $url ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
577 }
578
579 /**
580 * Checks if there is a token at the current position
581 *
582 * @param string $string - the string we're checking.
583 * @param integer $i - an int.
584 * @access public
585 * @version 1.11
586 */
587 public function is_token( &$string, $i ) {
588 return ( strpos( $this->tokens_list, $string[ $i ] ) !== false && ! self::escaped( $string, $i ) );
589 }
590
591 /**
592 * Parses CSS in $string. The code is saved as array in $this->css
593 *
594 * @param string $string the CSS code.
595 * @access public
596 * @return bool
597 * @version 1.1
598 */
599 public function parse( $string ) {
600 // Temporarily set locale to en_US in order to handle floats properly.
601 $old = @setlocale( LC_ALL, 0 );
602 @setlocale( LC_ALL, 'C' );
603
604 // PHP bug? Settings need to be refreshed in PHP4.
605 $this->print = new csstidy_print( $this );
606
607 $at_rules = & $GLOBALS['csstidy']['at_rules'];
608 $quoted_string_properties = & $GLOBALS['csstidy']['quoted_string_properties'];
609
610 $this->css = array();
611 $this->print->input_css = $string;
612 $string = str_replace( "\r\n", "\n", $string ) . ' ';
613 $cur_comment = '';
614
615 for ( $i = 0, $size = strlen( $string ); $i < $size; $i++ ) {
616 if ( "\n" === $string[ $i ] || "\r" === $string[ $i ] ) {
617 ++$this->line;
618 }
619
620 switch ( $this->status ) {
621 /* Case in at-block */
622 case 'at':
623 if ( self::is_token( $string, $i ) ) {
624 if ( '/' === $string[ $i ] && '*' === @$string[ $i + 1 ] ) {
625 $this->status = 'ic';
626 ++$i;
627 $this->from[] = 'at';
628 } elseif ( '{' === $string[ $i ] ) {
629 $this->status = 'is';
630 $this->at = $this->css_new_media_section( $this->at );
631 $this->_add_token( AT_START, $this->at );
632 } elseif ( ',' === $string[ $i ] ) {
633 $this->at = trim( $this->at ) . ',';
634 } elseif ( '\\' === $string[ $i ] ) {
635 $this->at .= $this->_unicode( $string, $i );
636 } elseif ( in_array( $string[ $i ], array( '(', ')', ':', '.', '/' ), true ) ) {
637 // fix for complicated media, i.e @media screen and (-webkit-min-device-pixel-ratio:1.5)
638 // '/' is included for ratios in Opera: (-o-min-device-pixel-ratio: 3/2).
639 $this->at .= $string[ $i ];
640 }
641 } else {
642 $lastpos = strlen( $this->at ) - 1;
643 if ( ! ( ( ctype_space( $this->at[ $lastpos ] ) || self::is_token( $this->at, $lastpos ) && ',' === $this->at[ $lastpos ] ) && ctype_space( $string[ $i ] ) ) ) {
644 $this->at .= $string[ $i ];
645 }
646 }
647 break;
648
649 /* Case in-selector */
650 case 'is':
651 if ( self::is_token( $string, $i ) ) {
652 if ( '/' === $string[ $i ] && '*' === @$string[ $i + 1 ] && '' === trim( $this->selector ) ) {
653 $this->status = 'ic';
654 ++$i;
655 $this->from[] = 'is';
656 } elseif ( '@' === $string[ $i ] && '' === trim( $this->selector ) ) {
657 // Check for at-rule.
658 $this->invalid_at = true;
659 foreach ( $at_rules as $name => $type ) {
660 if ( ! strcasecmp( substr( $string, $i + 1, strlen( $name ) ), $name ) ) {
661 ( 'at' === $type ) ? $this->at = '@' . $name : $this->selector = '@' . $name;
662 $this->status = $type;
663 $i += strlen( $name );
664 $this->invalid_at = false;
665 }
666 }
667
668 if ( $this->invalid_at ) {
669 $this->selector = '@';
670 $invalid_at_name = '';
671 for ( $j = $i + 1; $j < $size; ++$j ) {
672 if ( ! ctype_alpha( $string[ $j ] ) ) {
673 break;
674 }
675 $invalid_at_name .= $string[ $j ];
676 }
677 $this->log( 'Invalid @-rule: ' . $invalid_at_name . ' (removed)', 'Warning' );
678 }
679 } elseif ( ( '"' === $string[ $i ] || "'" === $string[ $i ] ) ) {
680 $this->cur_string[] = $string[ $i ];
681 $this->status = 'instr';
682 $this->str_char[] = $string[ $i ];
683 $this->from[] = 'is';
684 /* fixing CSS3 attribute selectors, i.e. a[href$=".mp3" */
685 $this->quoted_string[] = ( '=' === $string[ $i - 1 ] );
686 } elseif ( $this->invalid_at && ';' === $string[ $i ] ) {
687 $this->invalid_at = false;
688 $this->status = 'is';
689 } elseif ( '{' === $string[ $i ] ) {
690 $this->status = 'ip';
691 if ( '' === $this->at ) {
692 $this->at = $this->css_new_media_section( DEFAULT_AT );
693 }
694 $this->selector = $this->css_new_selector( $this->at, $this->selector );
695 $this->_add_token( SEL_START, $this->selector );
696 $this->added = false;
697 } elseif ( '}' === $string[ $i ] ) {
698 $this->_add_token( AT_END, $this->at );
699 $this->at = '';
700 $this->selector = '';
701 $this->sel_separate = array();
702 } elseif ( ',' === $string[ $i ] ) {
703 $this->selector = trim( $this->selector ) . ',';
704 $this->sel_separate[] = strlen( $this->selector );
705 } elseif ( '\\' === $string[ $i ] ) {
706 $this->selector .= $this->_unicode( $string, $i );
707 } elseif ( '*' === $string[ $i ] && @in_array( $string[ $i + 1 ], array( '.', '#', '[', ':' ), true ) ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedElseif
708 // remove unnecessary universal selector, FS#147.
709 } else {
710 $this->selector .= $string[ $i ];
711 }
712 } else {
713 $lastpos = strlen( $this->selector ) - 1;
714 if ( -1 === $lastpos || ! ( ( ctype_space( $this->selector[ $lastpos ] ) || self::is_token( $this->selector, $lastpos ) && ',' === $this->selector[ $lastpos ] ) && ctype_space( $string[ $i ] ) ) ) {
715 $this->selector .= $string[ $i ];
716 } elseif ( ctype_space( $string[ $i ] ) && $this->get_cfg( 'preserve_css' ) && ! $this->get_cfg( 'merge_selectors' ) ) {
717 $this->selector .= $string[ $i ];
718 }
719 }
720 break;
721
722 /* Case in-property */
723 case 'ip':
724 if ( self::is_token( $string, $i ) ) {
725 if ( ( ':' === $string[ $i ] || '=' === $string[ $i ] ) && '' !== $this->property ) {
726 $this->status = 'iv';
727 if ( ! $this->get_cfg( 'discard_invalid_properties' ) || self::property_is_valid( $this->property ) ) {
728 $this->property = $this->css_new_property( $this->at, $this->selector, $this->property );
729 $this->_add_token( PROPERTY, $this->property );
730 }
731 } elseif ( '/' === $string[ $i ] && '*' === @$string[ $i + 1 ] && '' === $this->property ) {
732 $this->status = 'ic';
733 ++$i;
734 $this->from[] = 'ip';
735 } elseif ( '}' === $string[ $i ] ) {
736 $this->explode_selectors();
737 $this->status = 'is';
738 $this->invalid_at = false;
739 $this->_add_token( SEL_END, $this->selector );
740 $this->selector = '';
741 $this->property = '';
742 } elseif ( ';' === $string[ $i ] ) {
743 $this->property = '';
744 } elseif ( '\\' === $string[ $i ] ) {
745 $this->property .= $this->_unicode( $string, $i );
746 } elseif ( '' === $this->property && ! ctype_space( $string[ $i ] ) ) {
747 // else this is dumb IE a hack, keep it.
748 $this->property .= $string[ $i ];
749 }
750 } elseif ( ! ctype_space( $string[ $i ] ) ) {
751 $this->property .= $string[ $i ];
752 }
753 break;
754
755 /* Case in-value */
756 case 'iv':
757 $pn = ( ( "\n" === $string[ $i ] || "\r" === $string[ $i ] ) && $this->property_is_next( $string, $i + 1 ) || strlen( $string ) - 1 === $i );
758 if ( ( self::is_token( $string, $i ) || $pn ) && ( ! ( ',' === $string[ $i ] && ! ctype_space( $string[ $i + 1 ] ) ) ) ) {
759 if ( '/' === $string[ $i ] && '*' === @$string[ $i + 1 ] ) {
760 $this->status = 'ic';
761 ++$i;
762 $this->from[] = 'iv';
763 } elseif ( ( '"' === $string[ $i ] || "'" === $string[ $i ] || '(' === $string[ $i ] ) ) {
764 $this->cur_string[] = $string[ $i ];
765 $this->str_char[] = ( '(' === $string[ $i ] ) ? ')' : $string[ $i ];
766 $this->status = 'instr';
767 $this->from[] = 'iv';
768 $this->quoted_string[] = in_array( strtolower( $this->property ), $quoted_string_properties, true );
769 } elseif ( ',' === $string[ $i ] ) {
770 $this->sub_value = trim( $this->sub_value ) . ',';
771 } elseif ( '\\' === $string[ $i ] ) {
772 $this->sub_value .= $this->_unicode( $string, $i );
773 } elseif ( ';' === $string[ $i ] || $pn ) {
774 if ( '@' === $this->selector[0] && isset( $at_rules[ substr( $this->selector, 1 ) ] ) && 'iv' === $at_rules[ substr( $this->selector, 1 ) ] ) {
775 $this->status = 'is';
776
777 switch ( $this->selector ) {
778 case '@charset':
779 /* Add quotes to charset */
780 $this->sub_value_arr[] = '"' . trim( $this->sub_value ) . '"';
781 $this->charset = $this->sub_value_arr[0];
782 break;
783 case '@namespace':
784 /* Add quotes to namespace */
785 $this->sub_value_arr[] = '"' . trim( $this->sub_value ) . '"';
786 $this->namespace = implode( ' ', $this->sub_value_arr );
787 break;
788 case '@import':
789 $this->sub_value = trim( $this->sub_value );
790
791 if ( empty( $this->sub_value_arr ) ) {
792 // Quote URLs in imports only if they're not already inside url() and not already quoted.
793 if ( substr( $this->sub_value, 0, 4 ) !== 'url(' ) {
794 if ( ! ( substr( $this->sub_value, -1 ) === $this->sub_value[0] && in_array( $this->sub_value[0], array( "'", '"' ), true ) ) ) {
795 $this->sub_value = '"' . $this->sub_value . '"';
796 }
797 }
798 }
799
800 $this->sub_value_arr[] = $this->sub_value;
801 $this->import[] = implode( ' ', $this->sub_value_arr );
802 break;
803 }
804
805 $this->sub_value_arr = array();
806 $this->sub_value = '';
807 $this->selector = '';
808 $this->sel_separate = array();
809 } else {
810 $this->status = 'ip';
811 }
812 } elseif ( '}' !== $string[ $i ] ) {
813 $this->sub_value .= $string[ $i ];
814 }
815 if ( ( '}' === $string[ $i ] || ';' === $string[ $i ] || $pn ) && ! empty( $this->selector ) ) {
816 if ( '' === $this->at ) {
817 $this->at = $this->css_new_media_section( DEFAULT_AT );
818 }
819
820 // case settings.
821 if ( $this->get_cfg( 'lowercase_s' ) ) {
822 $this->selector = strtolower( $this->selector );
823 }
824 $this->property = strtolower( $this->property );
825
826 $this->optimise->subvalue();
827 if ( '' !== $this->sub_value ) {
828 if ( substr( $this->sub_value, 0, 6 ) === 'format' ) {
829 $format_strings = self::parse_string_list( substr( $this->sub_value, 7, -1 ) );
830 if ( ! $format_strings ) {
831 $this->sub_value = '';
832 } else {
833 $this->sub_value = 'format(';
834
835 foreach ( $format_strings as $format_string ) {
836 $this->sub_value .= '"' . str_replace( '"', '\\"', $format_string ) . '",';
837 }
838
839 $this->sub_value = substr( $this->sub_value, 0, -1 ) . ')';
840 }
841 }
842 if ( '' !== $this->sub_value ) {
843 $this->sub_value_arr[] = $this->sub_value;
844 }
845 $this->sub_value = '';
846 }
847
848 $this->value = array_shift( $this->sub_value_arr );
849 while ( $this->sub_value_arr ) {
850 $this->value .= ' ' . array_shift( $this->sub_value_arr );
851 }
852
853 $this->optimise->value();
854
855 $valid = self::property_is_valid( $this->property );
856 if ( ( ! $this->invalid_at || $this->get_cfg( 'preserve_css' ) ) && ( ! $this->get_cfg( 'discard_invalid_properties' ) || $valid ) ) {
857 $this->css_add_property( $this->at, $this->selector, $this->property, $this->value );
858 $this->_add_token( VALUE, $this->value );
859 $this->optimise->shorthands();
860 }
861 if ( ! $valid ) {
862 if ( $this->get_cfg( 'discard_invalid_properties' ) ) {
863 $this->log( 'Removed invalid property: ' . $this->property, 'Warning' );
864 } else {
865 $this->log( 'Invalid property in ' . strtoupper( $this->get_cfg( 'css_level' ) ) . ': ' . $this->property, 'Warning' );
866 }
867 }
868
869 $this->property = '';
870 $this->sub_value_arr = array();
871 $this->value = '';
872 }
873 if ( '}' === $string[ $i ] ) {
874 $this->explode_selectors();
875 $this->_add_token( SEL_END, $this->selector );
876 $this->status = 'is';
877 $this->invalid_at = false;
878 $this->selector = '';
879 }
880 } elseif ( ! $pn ) {
881 $this->sub_value .= $string[ $i ];
882
883 if ( ctype_space( $string[ $i ] ) || ',' === $string[ $i ] ) {
884 $this->optimise->subvalue();
885 if ( '' !== $this->sub_value ) {
886 $this->sub_value_arr[] = $this->sub_value;
887 $this->sub_value = '';
888 }
889 }
890 }
891 break;
892
893 /* Case in string */
894 case 'instr':
895 $_str_char = $this->str_char[ count( $this->str_char ) - 1 ];
896 $_cur_string = $this->cur_string[ count( $this->cur_string ) - 1 ];
897 $temp_add = $string[ $i ];
898
899 // Add another string to the stack. Strings can't be nested inside of quotes, only parentheses, but
900 // parentheticals can be nested more than once.
901 if ( ')' === $_str_char && ( '(' === $string[ $i ] || '"' === $string[ $i ] || '\'' === $string[ $i ] ) && ! self::escaped( $string, $i ) ) {
902 $this->cur_string[] = $string[ $i ];
903 $this->str_char[] = $string[ $i ] === '(' ? ')' : $string[ $i ];
904 $this->from[] = 'instr';
905 $this->quoted_string[] = ! ( '(' === $string[ $i ] );
906 continue 2;
907 }
908
909 if ( ')' !== $_str_char && ( "\n" === $string[ $i ] || "\r" === $string[ $i ] ) && ! ( '\\' === $string[ $i - 1 ] && ! self::escaped( $string, $i - 1 ) ) ) {
910 $temp_add = '\\A';
911 $this->log( 'Fixed incorrect newline in string', 'Warning' );
912 }
913
914 $_cur_string .= $temp_add;
915
916 if ( $string[ $i ] === $_str_char && ! self::escaped( $string, $i ) ) {
917 $_quoted_string = array_pop( $this->quoted_string );
918
919 $this->status = array_pop( $this->from );
920
921 if ( ! preg_match( '|[' . implode( '', $GLOBALS['csstidy']['whitespace'] ) . ']|uis', $_cur_string ) && 'content' !== $this->property ) {
922 if ( ! $_quoted_string ) {
923 if ( ')' !== $_str_char ) {
924 // Convert properties like
925 // font-family: 'Arial';
926 // to
927 // font-family: Arial;
928 // or
929 // url("abc")
930 // to
931 // url(abc).
932 $_cur_string = substr( $_cur_string, 1, -1 );
933 }
934 } else {
935 $_quoted_string = false;
936 }
937 }
938
939 array_pop( $this->cur_string );
940 array_pop( $this->str_char );
941
942 if ( ')' === $_str_char ) {
943 $_cur_string = '(' . trim( substr( $_cur_string, 1, -1 ) ) . ')';
944 }
945
946 if ( 'iv' === $this->status ) {
947 // phpcs:disable Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoNewLine
948 // WPCOM hack: prevents CSSTidy from removing spaces after commas inside
949 // declaration's values.
950 // For more information, see D74626-code.
951 /*if ( ! $_quoted_string ) {
952 if ( strpos( $_cur_string, ',' ) !== false ) {
953 // we can on only remove space next to ','.
954 $_cur_string = implode( ',', array_map( 'trim', explode( ',', $_cur_string ) ) );
955 }
956 // and multiple spaces (too expensive).
957 if ( strpos( $_cur_string, ' ' ) !== false ) {
958 $_cur_string = preg_replace( ',\s+,', ' ', $_cur_string );
959 }
960 }*/
961 // phpcs:enable Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoNewLine
962 $this->sub_value .= $_cur_string;
963 } elseif ( 'is' === $this->status ) {
964 $this->selector .= $_cur_string;
965 } elseif ( 'instr' === $this->status ) {
966 $this->cur_string[ count( $this->cur_string ) - 1 ] .= $_cur_string;
967 }
968 } else {
969 $this->cur_string[ count( $this->cur_string ) - 1 ] = $_cur_string;
970 }
971 break;
972
973 /* Case in-comment */
974 case 'ic':
975 if ( '*' === $string[ $i ] && '/' === $string[ $i + 1 ] ) {
976 $this->status = array_pop( $this->from );
977 ++$i;
978 $this->_add_token( COMMENT, $cur_comment );
979 $cur_comment = '';
980 } else {
981 $cur_comment .= $string[ $i ];
982 }
983 break;
984 }
985 }
986
987 $this->optimise->postparse();
988
989 $this->print->_reset();
990
991 @setlocale( LC_ALL, $old ); // Set locale back to original setting.
992
993 return ! ( empty( $this->css ) && empty( $this->import ) && empty( $this->charset ) && empty( $this->tokens ) && empty( $this->namespace ) );
994 }
995
996 /**
997 * Explodes selectors
998 *
999 * @access private
1000 * @version 1.0
1001 */
1002 public function explode_selectors() {
1003 // Explode multiple selectors.
1004 if ( $this->get_cfg( 'merge_selectors' ) === 1 ) {
1005 $new_sels = array();
1006 $lastpos = 0;
1007 $this->sel_separate[] = strlen( $this->selector );
1008 foreach ( $this->sel_separate as $num => $pos ) {
1009 if ( count( $this->sel_separate ) - 1 === $num ) {
1010 ++$pos;
1011 }
1012
1013 $new_sels[] = substr( $this->selector, $lastpos, $pos - $lastpos - 1 );
1014 $lastpos = $pos;
1015 }
1016
1017 if ( count( $new_sels ) > 1 ) {
1018 foreach ( $new_sels as $selector ) {
1019 if ( isset( $this->css[ $this->at ][ $this->selector ] ) ) {
1020 $this->merge_css_blocks( $this->at, $selector, $this->css[ $this->at ][ $this->selector ] );
1021 }
1022 }
1023 unset( $this->css[ $this->at ][ $this->selector ] );
1024 }
1025 }
1026 $this->sel_separate = array();
1027 }
1028
1029 /**
1030 * Checks if a character is escaped (and returns true if it is)
1031 *
1032 * @param string $string - the string.
1033 * @param integer $pos - the position.
1034 * @access public
1035 * @return bool
1036 * @version 1.02
1037 */
1038 public static function escaped( &$string, $pos ) {
1039 return ! ( @( '\\' !== $string[ $pos - 1 ] ) || self::escaped( $string, $pos - 1 ) );
1040 }
1041
1042 /**
1043 * Adds a property with value to the existing CSS code
1044 *
1045 * @param string $media - the media.
1046 * @param string $selector - the selector.
1047 * @param string $property - the property.
1048 * @param string $new_val - new value.
1049 * @access private
1050 * @version 1.2
1051 */
1052 public function css_add_property( $media, $selector, $property, $new_val ) {
1053 if ( $this->get_cfg( 'preserve_css' ) || '' === trim( $new_val ) ) {
1054 return;
1055 }
1056
1057 $this->added = true;
1058 if ( isset( $this->css[ $media ][ $selector ][ $property ] ) ) {
1059 if ( ( self::is_important( $this->css[ $media ][ $selector ][ $property ] ) && self::is_important( $new_val ) ) || ! self::is_important( $this->css[ $media ][ $selector ][ $property ] ) ) {
1060 $this->css[ $media ][ $selector ][ $property ] = trim( $new_val );
1061 }
1062 } else {
1063 $this->css[ $media ][ $selector ][ $property ] = trim( $new_val );
1064 }
1065 }
1066
1067 /**
1068 * Start a new media section.
1069 * Check if the media is not already known,
1070 * else rename it with extra spaces
1071 * to avoid merging
1072 *
1073 * @param string $media - the media.
1074 * @return string
1075 */
1076 public function css_new_media_section( $media ) {
1077 if ( $this->get_cfg( 'preserve_css' ) ) {
1078 return $media;
1079 }
1080
1081 // if the last @media is the same as this keep it.
1082 if ( ! $this->css || ! is_array( $this->css ) || empty( $this->css ) ) {
1083 return $media;
1084 }
1085 end( $this->css );
1086 $at = current( $this->css );
1087 if ( $at === $media ) {
1088 return $media;
1089 }
1090 while ( isset( $this->css[ $media ] ) ) {
1091 if ( is_numeric( $media ) ) {
1092 ++$media;
1093 } else {
1094 $media .= ' ';
1095 }
1096 }
1097 return $media;
1098 }
1099
1100 /**
1101 * Start a new selector.
1102 * If already referenced in this media section,
1103 * rename it with extra space to avoid merging
1104 * except if merging is required,
1105 * or last selector is the same (merge siblings)
1106 *
1107 * Never merge @font-face
1108 *
1109 * @param string $media - the media.
1110 * @param string $selector - the selector.
1111 * @return string
1112 */
1113 public function css_new_selector( $media, $selector ) {
1114 if ( $this->get_cfg( 'preserve_css' ) ) {
1115 return $selector;
1116 }
1117 $selector = trim( $selector );
1118 if ( strncmp( $selector, '@font-face', 10 ) !== 0 ) {
1119 if ( $this->settings['merge_selectors'] ) {
1120 return $selector;
1121 }
1122
1123 if ( ! $this->css || ! isset( $this->css[ $media ] ) || ! $this->css[ $media ] ) {
1124 return $selector;
1125 }
1126
1127 // if last is the same, keep it.
1128 end( $this->css[ $media ] );
1129 $sel = current( $this->css[ $media ] );
1130 if ( $sel === $selector ) {
1131 return $selector;
1132 }
1133 }
1134
1135 while ( isset( $this->css[ $media ][ $selector ] ) ) {
1136 $selector .= ' ';
1137 }
1138 return $selector;
1139 }
1140
1141 /**
1142 * Start a new propertie.
1143 * If already references in this selector,
1144 * rename it with extra space to avoid override
1145 *
1146 * @param string $media - the media.
1147 * @param string $selector - the selector.
1148 * @param string $property - the property.
1149 * @return string
1150 */
1151 public function css_new_property( $media, $selector, $property ) {
1152 if ( $this->get_cfg( 'preserve_css' ) ) {
1153 return $property;
1154 }
1155 if ( ! $this->css || ! isset( $this->css[ $media ][ $selector ] ) || ! $this->css[ $media ][ $selector ] ) {
1156 return $property;
1157 }
1158
1159 while ( isset( $this->css[ $media ][ $selector ][ $property ] ) ) {
1160 $property .= ' ';
1161 }
1162
1163 return $property;
1164 }
1165
1166 /**
1167 * Adds CSS to an existing media/selector
1168 *
1169 * @param string $media - the media.
1170 * @param string $selector - the selector.
1171 * @param array $css_add - css being added.
1172 * @access private
1173 * @version 1.1
1174 */
1175 public function merge_css_blocks( $media, $selector, $css_add ) {
1176 foreach ( $css_add as $property => $value ) {
1177 $this->css_add_property( $media, $selector, $property, $value );
1178 }
1179 }
1180
1181 /**
1182 * Checks if $value is !important.
1183 *
1184 * @param string $value - the value.
1185 * @return bool
1186 * @access public
1187 * @version 1.0
1188 */
1189 public static function is_important( &$value ) {
1190 return ( ! strcasecmp( substr( str_replace( $GLOBALS['csstidy']['whitespace'], '', $value ), -10, 10 ), '!important' ) );
1191 }
1192
1193 /**
1194 * Returns a value without !important
1195 *
1196 * @param string $value - the value.
1197 * @return string
1198 * @access public
1199 * @version 1.0
1200 */
1201 public static function gvw_important( $value ) {
1202 if ( self::is_important( $value ) ) {
1203 $value = trim( $value );
1204 $value = substr( $value, 0, -9 );
1205 $value = trim( $value );
1206 $value = substr( $value, 0, -1 );
1207 $value = trim( $value );
1208 return $value;
1209 }
1210 return $value;
1211 }
1212
1213 /**
1214 * Checks if the next word in a string from pos is a CSS property
1215 *
1216 * @param string $istring - if it's a string.
1217 * @param integer $pos - position.
1218 * @return bool
1219 * @access private
1220 * @version 1.2
1221 */
1222 public function property_is_next( $istring, $pos ) {
1223 $all_properties = & $GLOBALS['csstidy']['all_properties'];
1224 $istring = substr( $istring, $pos, strlen( $istring ) - $pos );
1225 $pos = strpos( $istring, ':' );
1226 if ( false === $pos ) {
1227 return false;
1228 }
1229 $istring = strtolower( trim( substr( $istring, 0, $pos ) ) );
1230 if ( isset( $all_properties[ $istring ] ) ) {
1231 $this->log( 'Added semicolon to the end of declaration', 'Warning' );
1232 return true;
1233 }
1234 return false;
1235 }
1236
1237 /**
1238 * Checks if a property is valid
1239 *
1240 * @param string $property - the property.
1241 * @return bool;
1242 * @access public
1243 * @version 1.0
1244 */
1245 public function property_is_valid( $property ) {
1246 $property = strtolower( $property );
1247 if ( in_array( trim( $property ), $GLOBALS['csstidy']['multiple_properties'], true ) ) {
1248 $property = trim( $property );
1249 }
1250 $all_properties = & $GLOBALS['csstidy']['all_properties'];
1251 return ( isset( $all_properties[ $property ] ) && strpos( $all_properties[ $property ], strtoupper( $this->get_cfg( 'css_level' ) ) ) !== false );
1252 }
1253
1254 /**
1255 * Accepts a list of strings (e.g., the argument to format() in a @font-face src property)
1256 * and returns a list of the strings. Converts things like:
1257 *
1258 * Format(abc) => format("abc")
1259 * format(abc def) => format("abc","def")
1260 * format(abc "def") => format("abc","def")
1261 * format(abc, def, ghi) => format("abc","def","ghi")
1262 * format("abc",'def') => format("abc","def")
1263 * format("abc, def, ghi") => format("abc, def, ghi")
1264 *
1265 * @param string $value - the value.
1266 * @return array
1267 */
1268 public function parse_string_list( $value ) {
1269 $value = trim( $value );
1270
1271 // Case: if it's empty.
1272 if ( ! $value ) {
1273 return array();
1274 }
1275
1276 $strings = array();
1277
1278 $in_str = false;
1279 $current_string = '';
1280
1281 for ( $i = 0, $_len = strlen( $value ); $i < $_len; $i++ ) {
1282 if ( ( ',' === $value[ $i ] || ' ' === $value[ $i ] ) && true === $in_str ) {
1283 $in_str = false;
1284 $strings[] = $current_string;
1285 $current_string = '';
1286 } elseif ( '"' === $value[ $i ] || "'" === $value[ $i ] ) {
1287 if ( $in_str === $value[ $i ] ) {
1288 $strings[] = $current_string;
1289 $in_str = false;
1290 $current_string = '';
1291 continue;
1292 } elseif ( ! $in_str ) {
1293 $in_str = $value[ $i ];
1294 }
1295 } elseif ( $in_str ) {
1296 $current_string .= $value[ $i ];
1297 } elseif ( ! preg_match( '/[\s,]/', $value[ $i ] ) ) {
1298 $in_str = true;
1299 $current_string = $value[ $i ];
1300 }
1301 }
1302
1303 if ( $current_string ) {
1304 $strings[] = $current_string;
1305 }
1306
1307 return $strings;
1308 }
1309 }
1310