Completed
Push — merge/css-tidy ( fbe73f )
by
unknown
16:38
created

csstidy::is_important()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * CSSTidy - CSS Parser and Optimiser
5
 *
6
 * CSS Parser class
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 <http://www.gnu.org/licenses/>.
24
 *
25
 * @license http://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
 * @author Cedric Morin (cedric at yterium dot com) 2010
31
 */
32
/**
33
 * Defines ctype functions if required
34
 *
35
 * @version 1.0
36
 */
37
require_once( dirname( __FILE__ ) . '/class.csstidy_ctype.php' );
38
39
/**
40
 * Various CSS data needed for correct optimisations etc.
41
 *
42
 * @version 1.3
43
 */
44
require( dirname( __FILE__ ) . '/data.inc.php' );
45
46
/**
47
 * Contains a class for printing CSS code
48
 *
49
 * @version 1.0
50
 */
51
require( dirname( __FILE__ ) . '/class.csstidy_print.php' );
52
53
/**
54
 * Contains a class for optimising CSS code
55
 *
56
 * @version 1.0
57
 */
58
require( dirname( __FILE__ ) . '/class.csstidy_optimise.php' );
59
60
/**
61
 * CSS Parser class
62
 *
63
64
 * This class represents a CSS parser which reads CSS code and saves it in an array.
65
 * In opposite to most other CSS parsers, it does not use regular expressions and
66
 * thus has full CSS2 support and a higher reliability.
67
 * Additional to that it applies some optimisations and fixes to the CSS code.
68
 * An online version should be available here: http://cdburnerxp.se/cssparse/css_optimiser.php
69
 * @package csstidy
70
 * @author Florian Schmitz (floele at gmail dot com) 2005-2006
71
 * @version 1.3.1
72
 */
73
class csstidy {
74
75
	/**
76
	 * Saves the parsed CSS. This array is empty if preserve_css is on.
77
	 * @var array
78
	 * @access public
79
	 */
80
	public $css = array();
81
	/**
82
	 * Saves the parsed CSS (raw)
83
	 * @var array
84
	 * @access private
85
	 */
86
	public $tokens = array();
87
	/**
88
	 * Printer class
89
	 * @see csstidy_print
90
	 * @var object
91
	 * @access public
92
	 */
93
	public $print;
94
	/**
95
	 * Optimiser class
96
	 * @see csstidy_optimise
97
	 * @var object
98
	 * @access private
99
	 */
100
	public $optimise;
101
	/**
102
	 * Saves the CSS charset (@charset)
103
	 * @var string
104
	 * @access private
105
	 */
106
	public $charset = '';
107
	/**
108
	 * Saves all @import URLs
109
	 * @var array
110
	 * @access private
111
	 */
112
	public $import = array();
113
	/**
114
	 * Saves the namespace
115
	 * @var string
116
	 * @access private
117
	 */
118
	public $namespace = '';
119
	/**
120
	 * Contains the version of csstidy
121
	 * @var string
122
	 * @access private
123
	 */
124
	public $version = '1.3';
125
	/**
126
	 * Stores the settings
127
	 * @var array
128
	 * @access private
129
	 */
130
	public $settings = array();
131
	/**
132
	 * Saves the parser-status.
133
	 *
134
	 * Possible values:
135
	 * - is = in selector
136
	 * - ip = in property
137
	 * - iv = in value
138
	 * - instr = in string (started at " or ' or ( )
139
	 * - ic = in comment (ignore everything)
140
	 * - at = in @-block
141
	 *
142
	 * @var string
143
	 * @access private
144
	 */
145
	public $status = 'is';
146
	/**
147
	 * Saves the current at rule (@media)
148
	 * @var string
149
	 * @access private
150
	 */
151
	public $at = '';
152
	/**
153
	 * Saves the current selector
154
	 * @var string
155
	 * @access private
156
	 */
157
	public $selector = '';
158
	/**
159
	 * Saves the current property
160
	 * @var string
161
	 * @access private
162
	 */
163
	public $property = '';
164
	/**
165
	 * Saves the position of , in selectors
166
	 * @var array
167
	 * @access private
168
	 */
169
	public $sel_separate = array();
170
	/**
171
	 * Saves the current value
172
	 * @var string
173
	 * @access private
174
	 */
175
	public $value = '';
176
	/**
177
	 * Saves the current sub-value
178
	 *
179
	 * Example for a subvalue:
180
	 * background:url(foo.png) red no-repeat;
181
	 * "url(foo.png)", "red", and  "no-repeat" are subvalues,
182
	 * separated by whitespace
183
	 * @var string
184
	 * @access private
185
	 */
186
	public $sub_value = '';
187
	/**
188
	 * Array which saves all subvalues for a property.
189
	 * @var array
190
	 * @see sub_value
191
	 * @access private
192
	 */
193
	public $sub_value_arr = array();
194
	/**
195
	 * Saves the stack of characters that opened the current strings
196
	 * @var array
197
	 * @access private
198
	 */
199
	public $str_char = array();
200
	public $cur_string = array();
201
	/**
202
	 * Status from which the parser switched to ic or instr
203
	 * @var array
204
	 * @access private
205
	 */
206
	public $from = array();
207
	/**
208
	/**
209
	 * =true if in invalid at-rule
210
	 * @var bool
211
	 * @access private
212
	 */
213
	public $invalid_at = false;
214
	/**
215
	 * =true if something has been added to the current selector
216
	 * @var bool
217
	 * @access private
218
	 */
219
	public $added = false;
220
	/**
221
	 * Array which saves the message log
222
	 * @var array
223
	 * @access private
224
	 */
225
	public $log = array();
226
	/**
227
	 * Saves the line number
228
	 * @var integer
229
	 * @access private
230
	 */
231
	public $line = 1;
232
	/**
233
	 * Marks if we need to leave quotes for a string
234
	 * @var array
235
	 * @access private
236
	 */
237
	public $quoted_string = array();
238
239
	/**
240
	 * List of tokens
241
	 * @var string
242
	 */
243
	public $tokens_list = "";
244
	function csstidy() {
245
		$this->__construct();
246
	}
247
	/**
248
	 * Loads standard template and sets default settings
249
	 * @access private
250
	 * @version 1.3
251
	 */
252
	function __construct() {
253
		$this->settings['remove_bslash'] = true;
254
		$this->settings['compress_colors'] = true;
255
		$this->settings['compress_font-weight'] = true;
256
		$this->settings['lowercase_s'] = false;
257
		/*
258
		  1 common shorthands optimization
259
		  2 + font property optimization
260
		  3 + background property optimization
261
		 */
262
		$this->settings['optimise_shorthands'] = 1;
263
		$this->settings['remove_last_;'] = true;
264
		/* rewrite all properties with low case, better for later gzip OK, safe*/
265
		$this->settings['case_properties'] = 1;
266
		/* sort properties in alpabetic order, better for later gzip
267
		 * but can cause trouble in case of overiding same propertie or using hack
268
		 */
269
		$this->settings['sort_properties'] = false;
270
		/*
271
		  1, 3, 5, etc -- enable sorting selectors inside @media: a{}b{}c{}
272
		  2, 5, 8, etc -- enable sorting selectors inside one CSS declaration: a,b,c{}
273
		  preserve order by default cause it can break functionnality
274
		 */
275
		$this->settings['sort_selectors'] = 0;
276
		/* is dangeroues to be used: CSS is broken sometimes */
277
		$this->settings['merge_selectors'] = 0;
278
		/* preserve or not browser hacks */
279
		$this->settings['discard_invalid_selectors'] = false;
280
		$this->settings['discard_invalid_properties'] = false;
281
		$this->settings['css_level'] = 'CSS2.1';
282
		$this->settings['preserve_css'] = false;
283
		$this->settings['timestamp'] = false;
284
		$this->settings['template'] = ''; // say that propertie exist
285
		$this->set_cfg('template','default'); // call load_template
286
		$this->optimise = new csstidy_optimise($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<csstidy>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
287
288
		$this->tokens_list = & $GLOBALS['csstidy']['tokens'];
289
	}
290
291
	/**
292
	 * Get the value of a setting.
293
	 * @param string $setting
294
	 * @access public
295
	 * @return mixed
296
	 * @version 1.0
297
	 */
298
	function get_cfg($setting) {
299
		if (isset($this->settings[$setting])) {
300
			return $this->settings[$setting];
301
		}
302
		return false;
303
	}
304
305
	/**
306
	 * Load a template
307
	 * @param string $template used by set_cfg to load a template via a configuration setting
308
	 * @access private
309
	 * @version 1.4
310
	 */
311
	function _load_template($template) {
312
		switch ($template) {
313
			case 'default':
314
				$this->load_template('default');
315
				break;
316
317
			case 'highest':
318
				$this->load_template('highest_compression');
319
				break;
320
321
			case 'high':
322
				$this->load_template('high_compression');
323
				break;
324
325
			case 'low':
326
				$this->load_template('low_compression');
327
				break;
328
329
			default:
330
				$this->load_template($template);
331
				break;
332
		}
333
	}
334
335
	/**
336
	 * Set the value of a setting.
337
	 * @param string $setting
338
	 * @param mixed $value
339
	 * @access public
340
	 * @return bool
341
	 * @version 1.0
342
	 */
343
	function set_cfg($setting, $value=null) {
344
		if (is_array($setting) && $value === null) {
345
			foreach ($setting as $setprop => $setval) {
346
				$this->settings[$setprop] = $setval;
347
			}
348
			if (array_key_exists('template', $setting)) {
349
				$this->_load_template($this->settings['template']);
350
			}
351
			return true;
352
		} else if (isset($this->settings[$setting]) && $value !== '') {
353
			$this->settings[$setting] = $value;
354
			if ($setting === 'template') {
355
				$this->_load_template($this->settings['template']);
356
			}
357
			return true;
358
		}
359
		return false;
360
	}
361
362
	/**
363
	 * Adds a token to $this->tokens
364
	 * @param mixed $type
365
	 * @param string $data
366
	 * @param bool $do add a token even if preserve_css is off
367
	 * @access private
368
	 * @version 1.0
369
	 */
370
	function _add_token($type, $data, $do = false) {
371
		if ($this->get_cfg('preserve_css') || $do) {
372
			$this->tokens[] = array($type, ($type == COMMENT) ? $data : trim($data));
373
		}
374
	}
375
376
	/**
377
	 * Add a message to the message log
378
	 * @param string $message
379
	 * @param string $type
380
	 * @param integer $line
381
	 * @access private
382
	 * @version 1.0
383
	 */
384
	function log($message, $type, $line = -1) {
385
		if ($line === -1) {
386
			$line = $this->line;
387
		}
388
		$line = intval($line);
389
		$add = array('m' => $message, 't' => $type);
390
		if (!isset($this->log[$line]) || !in_array($add, $this->log[$line])) {
391
			$this->log[$line][] = $add;
392
		}
393
	}
394
395
	/**
396
	 * Parse unicode notations and find a replacement character
397
	 * @param string $string
398
	 * @param integer $i
399
	 * @access private
400
	 * @return string
401
	 * @version 1.2
402
	 */
403
	function _unicode(&$string, &$i) {
404
		++$i;
405
		$add = '';
406
		$replaced = false;
407
408
		while ($i < strlen($string) && (ctype_xdigit($string{$i}) || ctype_space($string{$i})) && strlen($add) < 6) {
409
			$add .= $string{$i};
410
411
			if (ctype_space($string{$i})) {
412
				break;
413
			}
414
			$i++;
415
		}
416
417
		if (hexdec($add) > 47 && hexdec($add) < 58 || hexdec($add) > 64 && hexdec($add) < 91 || hexdec($add) > 96 && hexdec($add) < 123) {
418
			$this->log('Replaced unicode notation: Changed \\' . $add . ' to ' . chr(hexdec($add)), 'Information');
419
			$add = chr(hexdec($add));
420
			$replaced = true;
421
		} else {
422
			$add = trim('\\' . $add);
423
		}
424
425
		if (@ctype_xdigit($string{$i + 1}) && ctype_space($string{$i})
426
						&& !$replaced || !ctype_space($string{$i})) {
427
			$i--;
428
		}
429
430
		if ($add !== '\\' || !$this->get_cfg('remove_bslash') || strpos($this->tokens_list, $string{$i + 1}) !== false) {
431
			return $add;
432
		}
433
434
		if ($add === '\\') {
435
			$this->log('Removed unnecessary backslash', 'Information');
436
		}
437
		return '';
438
	}
439
440
	/**
441
	 * Write formatted output to a file
442
	 * @param string $filename
443
	 * @param string $doctype when printing formatted, is a shorthand for the document type
444
	 * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet
445
	 * @param string $title when printing formatted, is the title to be added in the head of the document
446
	 * @param string $lang when printing formatted, gives a two-letter language code to be added to the output
447
	 * @access public
448
	 * @version 1.4
449
	 */
450
	function write_page($filename, $doctype='xhtml1.1', $externalcss=true, $title='', $lang='en') {
451
		$this->write($filename, true);
452
	}
453
454
	/**
455
	 * Write plain output to a file
456
	 * @param string $filename
457
	 * @param bool $formatted whether to print formatted or not
458
	 * @param string $doctype when printing formatted, is a shorthand for the document type
459
	 * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet
460
	 * @param string $title when printing formatted, is the title to be added in the head of the document
461
	 * @param string $lang when printing formatted, gives a two-letter language code to be added to the output
462
	 * @param bool $pre_code whether to add pre and code tags around the code (for light HTML formatted templates)
463
	 * @access public
464
	 * @version 1.4
465
	 */
466
	function write($filename, $formatted=false, $doctype='xhtml1.1', $externalcss=true, $title='', $lang='en', $pre_code=true) {
467
		$filename .= ( $formatted) ? '.xhtml' : '.css';
468
469
		if (!is_dir('temp')) {
470
			$madedir = mkdir('temp');
471
			if (!$madedir) {
472
				print 'Could not make directory "temp" in ' . dirname(__FILE__);
473
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method write() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
474
			}
475
		}
476
		$handle = fopen('temp/' . $filename, 'w');
477
		if ($handle) {
478
			if (!$formatted) {
479
				fwrite($handle, $this->print->plain());
480
			} else {
481
				fwrite($handle, $this->print->formatted_page($doctype, $externalcss, $title, $lang, $pre_code));
482
			}
483
		}
484
		fclose($handle);
485
	}
486
487
	/**
488
	 * Loads a new template
489
	 * @param string $content either filename (if $from_file == true), content of a template file, "high_compression", "highest_compression", "low_compression", or "default"
490
	 * @param bool $from_file uses $content as filename if true
491
	 * @access public
492
	 * @version 1.1
493
	 * @see http://csstidy.sourceforge.net/templates.php
494
	 */
495
	function load_template($content, $from_file=true) {
496
		$predefined_templates = & $GLOBALS['csstidy']['predefined_templates'];
497
		if ($content === 'high_compression' || $content === 'default' || $content === 'highest_compression' || $content === 'low_compression') {
498
			$this->template = $predefined_templates[$content];
0 ignored issues
show
Bug introduced by
The property template does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
499
			return;
500
		}
501
502
503
		if ($from_file) {
504
			$content = strip_tags(file_get_contents($content), '<span>');
505
		}
506
		$content = str_replace("\r\n", "\n", $content); // Unify newlines (because the output also only uses \n)
507
		$template = explode('|', $content);
508
509
		for ($i = 0; $i < count($template); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
510
			$this->template[$i] = $template[$i];
511
		}
512
	}
513
514
	/**
515
	 * Starts parsing from URL
516
	 * @param string $url
517
	 * @access public
518
	 * @version 1.0
519
	 */
520
	function parse_from_url($url) {
521
		return $this->parse(@file_get_contents($url));
522
	}
523
524
	/**
525
	 * Checks if there is a token at the current position
526
	 * @param string $string
527
	 * @param integer $i
528
	 * @access public
529
	 * @version 1.11
530
	 */
531
	function is_token(&$string, $i) {
532
		return (strpos($this->tokens_list, $string{$i}) !== false && !csstidy::escaped($string, $i));
533
	}
534
535
	/**
536
	 * Parses CSS in $string. The code is saved as array in $this->css
537
	 * @param string $string the CSS code
538
	 * @access public
539
	 * @return bool
540
	 * @version 1.1
541
	 */
542
	function parse($string) {
543
		// Temporarily set locale to en_US in order to handle floats properly
544
		$old = @setlocale(LC_ALL, 0);
545
		@setlocale(LC_ALL, 'C');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
546
547
		// PHP bug? Settings need to be refreshed in PHP4
548
		$this->print = new csstidy_print($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<csstidy>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
549
		//$this->optimise = new csstidy_optimise($this);
550
551
		$all_properties = & $GLOBALS['csstidy']['all_properties'];
552
		$at_rules = & $GLOBALS['csstidy']['at_rules'];
553
		$quoted_string_properties = & $GLOBALS['csstidy']['quoted_string_properties'];
554
555
		$this->css = array();
556
		$this->print->input_css = $string;
557
		$string = str_replace("\r\n", "\n", $string) . ' ';
558
		$cur_comment = '';
559
560
		for ($i = 0, $size = strlen($string); $i < $size; $i++) {
561
			if ($string{$i} === "\n" || $string{$i} === "\r") {
562
				++$this->line;
563
			}
564
565
			switch ($this->status) {
566
				/* Case in at-block */
567
				case 'at':
568
					if (csstidy::is_token($string, $i)) {
569
						if ($string{$i} === '/' && @$string{$i + 1} === '*') {
570
							$this->status = 'ic';
571
							++$i;
572
							$this->from[] = 'at';
573
						} elseif ($string{$i} === '{') {
574
							$this->status = 'is';
575
							$this->at = $this->css_new_media_section($this->at);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->css_new_media_section($this->at) can also be of type integer or double. However, the property $at is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
576
							$this->_add_token(AT_START, $this->at);
577
						} elseif ($string{$i} === ',') {
578
							$this->at = trim($this->at) . ',';
579
						} elseif ($string{$i} === '\\') {
580
							$this->at .= $this->_unicode($string, $i);
581
						}
582
						// fix for complicated media, i.e @media screen and (-webkit-min-device-pixel-ratio:1.5)
583
						// '/' is included for ratios in Opera: (-o-min-device-pixel-ratio: 3/2)
584 View Code Duplication
						elseif (in_array($string{$i}, array('(', ')', ':', '.', '/'))) {
585
							$this->at .= $string{$i};
586
						}
587
					} else {
588
						$lastpos = strlen($this->at) - 1;
589
						if (!( (ctype_space($this->at{$lastpos}) || csstidy::is_token($this->at, $lastpos) && $this->at{$lastpos} === ',') && ctype_space($string{$i}))) {
590
							$this->at .= $string{$i};
591
						}
592
					}
593
					break;
594
595
				/* Case in-selector */
596
				case 'is':
597
					if (csstidy::is_token($string, $i)) {
598
						if ($string{$i} === '/' && @$string{$i + 1} === '*' && trim($this->selector) == '') {
599
							$this->status = 'ic';
600
							++$i;
601
							$this->from[] = 'is';
602
						} elseif ($string{$i} === '@' && trim($this->selector) == '') {
603
							// Check for at-rule
604
							$this->invalid_at = true;
605
							foreach ($at_rules as $name => $type) {
606
								if (!strcasecmp(substr($string, $i + 1, strlen($name)), $name)) {
607
									($type === 'at') ? $this->at = '@' . $name : $this->selector = '@' . $name;
608
									$this->status = $type;
609
									$i += strlen($name);
610
									$this->invalid_at = false;
611
								}
612
							}
613
614
							if ($this->invalid_at) {
615
								$this->selector = '@';
616
								$invalid_at_name = '';
617
								for ($j = $i + 1; $j < $size; ++$j) {
618
									if (!ctype_alpha($string{$j})) {
619
										break;
620
									}
621
									$invalid_at_name .= $string{$j};
622
								}
623
								$this->log('Invalid @-rule: ' . $invalid_at_name . ' (removed)', 'Warning');
624
							}
625
						} elseif (($string{$i} === '"' || $string{$i} === "'")) {
626
							$this->cur_string[] = $string{$i};
627
							$this->status = 'instr';
628
							$this->str_char[] = $string{$i};
629
							$this->from[] = 'is';
630
							/* fixing CSS3 attribute selectors, i.e. a[href$=".mp3" */
631
							$this->quoted_string[] = ($string{$i - 1} == '=' );
632
						} elseif ($this->invalid_at && $string{$i} === ';') {
633
							$this->invalid_at = false;
634
							$this->status = 'is';
635
						} elseif ($string{$i} === '{') {
636
							$this->status = 'ip';
637
							if($this->at == '') {
638
								$this->at = $this->css_new_media_section(DEFAULT_AT);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->css_new_media_section(DEFAULT_AT) can also be of type integer or double. However, the property $at is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
639
							}
640
							$this->selector = $this->css_new_selector($this->at,$this->selector);
641
							$this->_add_token(SEL_START, $this->selector);
642
							$this->added = false;
643
						} elseif ($string{$i} === '}') {
644
							$this->_add_token(AT_END, $this->at);
645
							$this->at = '';
646
							$this->selector = '';
647
							$this->sel_separate = array();
648
						} elseif ($string{$i} === ',') {
649
							$this->selector = trim($this->selector) . ',';
650
							$this->sel_separate[] = strlen($this->selector);
651
						} elseif ($string{$i} === '\\') {
652
							$this->selector .= $this->_unicode($string, $i);
653 View Code Duplication
						} elseif ($string{$i} === '*' && @in_array($string{$i + 1}, array('.', '#', '[', ':'))) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
654
							// remove unnecessary universal selector, FS#147
655
						} else {
656
							$this->selector .= $string{$i};
657
						}
658
					} else {
659
						$lastpos = strlen($this->selector) - 1;
660
						if ($lastpos == -1 || !( (ctype_space($this->selector{$lastpos}) || csstidy::is_token($this->selector, $lastpos) && $this->selector{$lastpos} === ',') && ctype_space($string{$i}))) {
661
							$this->selector .= $string{$i};
662
						}
663
						else if (ctype_space($string{$i}) && $this->get_cfg('preserve_css') && !$this->get_cfg('merge_selectors')) {
664
							$this->selector .= $string{$i};
665
						}
666
					}
667
					break;
668
669
				/* Case in-property */
670
				case 'ip':
671
					if (csstidy::is_token($string, $i)) {
672
						if (($string{$i} === ':' || $string{$i} === '=') && $this->property != '') {
673
							$this->status = 'iv';
674
							if (!$this->get_cfg('discard_invalid_properties') || csstidy::property_is_valid($this->property)) {
675
								$this->property = $this->css_new_property($this->at,$this->selector,$this->property);
676
								$this->_add_token(PROPERTY, $this->property);
677
							}
678
						} elseif ($string{$i} === '/' && @$string{$i + 1} === '*' && $this->property == '') {
679
							$this->status = 'ic';
680
							++$i;
681
							$this->from[] = 'ip';
682 View Code Duplication
						} elseif ($string{$i} === '}') {
683
							$this->explode_selectors();
684
							$this->status = 'is';
685
							$this->invalid_at = false;
686
							$this->_add_token(SEL_END, $this->selector);
687
							$this->selector = '';
688
							$this->property = '';
689
						} elseif ($string{$i} === ';') {
690
							$this->property = '';
691
						} elseif ($string{$i} === '\\') {
692
							$this->property .= $this->_unicode($string, $i);
693
						}
694
						// else this is dumb IE a hack, keep it
695
						elseif ($this->property=='' AND !ctype_space($string{$i})) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
696
							$this->property .= $string{$i};
697
						}
698
					}
699
					elseif (!ctype_space($string{$i})) {
700
						$this->property .= $string{$i};
701
					}
702
					break;
703
704
				/* Case in-value */
705
				case 'iv':
706
					$pn = (($string{$i} === "\n" || $string{$i} === "\r") && $this->property_is_next($string, $i + 1) || $i == strlen($string) - 1);
707
					if ((csstidy::is_token($string, $i) || $pn) && (!($string{$i} == ',' && !ctype_space($string{$i+1})))) {
708
						if ($string{$i} === '/' && @$string{$i + 1} === '*') {
709
							$this->status = 'ic';
710
							++$i;
711
							$this->from[] = 'iv';
712
						} elseif (($string{$i} === '"' || $string{$i} === "'" || $string{$i} === '(')) {
713
							$this->cur_string[] = $string{$i};
714
							$this->str_char[] = ($string{$i} === '(') ? ')' : $string{$i};
715
							$this->status = 'instr';
716
							$this->from[] = 'iv';
717
							$this->quoted_string[] = in_array(strtolower($this->property), $quoted_string_properties);
718
						} elseif ($string{$i} === ',') {
719
							$this->sub_value = trim($this->sub_value) . ',';
720
						} elseif ($string{$i} === '\\') {
721
							$this->sub_value .= $this->_unicode($string, $i);
722
						} elseif ($string{$i} === ';' || $pn) {
723
							if ($this->selector{0} === '@' && isset($at_rules[substr($this->selector, 1)]) && $at_rules[substr($this->selector, 1)] === 'iv') {
724
								$this->status = 'is';
725
726
								switch ($this->selector) {
727
									case '@charset':
728
										/* Add quotes to charset */
729
										$this->sub_value_arr[] = '"' . trim($this->sub_value) . '"';
730
										$this->charset = $this->sub_value_arr[0];
731
										break;
732
									case '@namespace':
733
										/* Add quotes to namespace */
734
										$this->sub_value_arr[] = '"' . trim($this->sub_value) . '"';
735
										$this->namespace = implode(' ', $this->sub_value_arr);
736
										break;
737
									case '@import':
738
										$this->sub_value = trim($this->sub_value);
739
740
										if (empty($this->sub_value_arr)) {
741
											// Quote URLs in imports only if they're not already inside url() and not already quoted.
742
											if (substr($this->sub_value, 0, 4) != 'url(') {
743
												if (!($this->sub_value{0} == substr($this->sub_value, -1) && in_array($this->sub_value{0}, array("'", '"')))) {
744
													$this->sub_value = '"' . $this->sub_value . '"';
745
												}
746
											}
747
										}
748
749
										$this->sub_value_arr[] = $this->sub_value;
750
										$this->import[] = implode(' ', $this->sub_value_arr);
751
										break;
752
								}
753
754
								$this->sub_value_arr = array();
755
								$this->sub_value = '';
756
								$this->selector = '';
757
								$this->sel_separate = array();
758
							} else {
759
								$this->status = 'ip';
760
							}
761
						} elseif ($string{$i} !== '}') {
762
							$this->sub_value .= $string{$i};
763
						}
764
						if (($string{$i} === '}' || $string{$i} === ';' || $pn) && !empty($this->selector)) {
765
							if ($this->at == '') {
766
								$this->at = $this->css_new_media_section(DEFAULT_AT);
767
							}
768
769
							// case settings
770
							if ($this->get_cfg('lowercase_s')) {
771
								$this->selector = strtolower($this->selector);
772
							}
773
							$this->property = strtolower($this->property);
774
775
							$this->optimise->subvalue();
776
							if ($this->sub_value != '') {
777
								if (substr($this->sub_value, 0, 6) == 'format') {
778
									$format_strings = csstidy::parse_string_list(substr($this->sub_value, 7, -1));
779
									if (!$format_strings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $format_strings of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
780
										$this->sub_value = "";
781
									}
782
									else {
783
										$this->sub_value = "format(";
784
785
										foreach ($format_strings as $format_string) {
786
											$this->sub_value .= '"' . str_replace('"', '\\"', $format_string) . '",';
787
										}
788
789
										$this->sub_value = substr($this->sub_value, 0, -1) . ")";
790
									}
791
								}
792
								if ($this->sub_value != '') {
793
									$this->sub_value_arr[] = $this->sub_value;
794
								}
795
								$this->sub_value = '';
796
							}
797
798
							$this->value = array_shift($this->sub_value_arr);
799
							while(count($this->sub_value_arr)){
800
								//$this->value .= (substr($this->value,-1,1)==','?'':' ').array_shift($this->sub_value_arr);
801
								$this->value .= ' '.array_shift($this->sub_value_arr);
802
							}
803
804
							$this->optimise->value();
805
806
							$valid = csstidy::property_is_valid($this->property);
807
							if ((!$this->invalid_at || $this->get_cfg('preserve_css')) && (!$this->get_cfg('discard_invalid_properties') || $valid)) {
808
								$this->css_add_property($this->at, $this->selector, $this->property, $this->value);
809
								$this->_add_token(VALUE, $this->value);
810
								$this->optimise->shorthands();
811
							}
812
							if (!$valid) {
813
								if ($this->get_cfg('discard_invalid_properties')) {
814
									$this->log('Removed invalid property: ' . $this->property, 'Warning');
815
								} else {
816
									$this->log('Invalid property in ' . strtoupper($this->get_cfg('css_level')) . ': ' . $this->property, 'Warning');
817
								}
818
							}
819
820
							$this->property = '';
821
							$this->sub_value_arr = array();
822
							$this->value = '';
823
						}
824 View Code Duplication
						if ($string{$i} === '}') {
825
							$this->explode_selectors();
826
							$this->_add_token(SEL_END, $this->selector);
827
							$this->status = 'is';
828
							$this->invalid_at = false;
829
							$this->selector = '';
830
						}
831
					} elseif (!$pn) {
832
						$this->sub_value .= $string{$i};
833
834
						if (ctype_space($string{$i}) || $string{$i} == ',') {
835
							$this->optimise->subvalue();
836
							if ($this->sub_value != '') {
837
								$this->sub_value_arr[] = $this->sub_value;
838
								$this->sub_value = '';
839
							}
840
						}
841
					}
842
					break;
843
844
				/* Case in string */
845
				case 'instr':
846
					$_str_char = $this->str_char[count($this->str_char)-1];
847
					$_cur_string = $this->cur_string[count($this->cur_string)-1];
848
					$temp_add = $string{$i};
849
850
					// Add another string to the stack. Strings can't be nested inside of quotes, only parentheses, but
851
					// parentheticals can be nested more than once.
852
					if ($_str_char === ")" && ($string{$i} === "(" || $string{$i} === '"' || $string{$i} === '\'') && !csstidy::escaped($string, $i)) {
853
						$this->cur_string[] = $string{$i};
854
						$this->str_char[] = $string{$i} == "(" ? ")" : $string{$i};
855
						$this->from[] = 'instr';
856
						$this->quoted_string[] = !($string{$i} === "(");
857
						continue;
858
					}
859
860
					if ($_str_char !== ")" && ($string{$i} === "\n" || $string{$i} === "\r") && !($string{$i - 1} === '\\' && !csstidy::escaped($string, $i - 1))) {
861
						$temp_add = "\\A";
862
						$this->log('Fixed incorrect newline in string', 'Warning');
863
					}
864
865
					$_cur_string .= $temp_add;
866
867
					if ($string{$i} === $_str_char && !csstidy::escaped($string, $i)) {
868
						$_quoted_string = array_pop($this->quoted_string);
869
870
						$this->status = array_pop($this->from);
871
872
						if (!preg_match('|[' . implode('', $GLOBALS['csstidy']['whitespace']) . ']|uis', $_cur_string) && $this->property !== 'content') {
873
							if (!$_quoted_string) {
874
								if ($_str_char !== ')') {
875
									// Convert properties like
876
									// font-family: 'Arial';
877
									// to
878
									// font-family: Arial;
879
									// or
880
									// url("abc")
881
									// to
882
									// url(abc)
883
									$_cur_string = substr($_cur_string, 1, -1);
884
								}
885
							} else {
886
								$_quoted_string = false;
887
							}
888
						}
889
890
						array_pop($this->cur_string);
891
						array_pop($this->str_char);
892
893
						if ($_str_char === ")") {
894
							$_cur_string = "(" . trim(substr($_cur_string, 1, -1)) . ")";
895
						}
896
897
						if ($this->status === 'iv') {
898
							if (!$_quoted_string){
899
								if (strpos($_cur_string,',')!==false)
900
									// we can on only remove space next to ','
901
									$_cur_string = implode(',',array_map('trim',explode(',',$_cur_string)));
902
								// and multiple spaces (too expensive)
903
								if (strpos($_cur_string,'  ')!==false)
904
									$_cur_string = preg_replace(",\s+,"," ",$_cur_string);
905
							}
906
							$this->sub_value .= $_cur_string;
907
						} elseif ($this->status === 'is') {
908
							$this->selector .= $_cur_string;
909
						} elseif ($this->status === 'instr') {
910
							$this->cur_string[count($this->cur_string)-1] .= $_cur_string;
911
						}
912
					}
913
					else {
914
						$this->cur_string[count($this->cur_string)-1] = $_cur_string;
915
					}
916
					break;
917
918
				/* Case in-comment */
919
				case 'ic':
920
					if ($string{$i} === '*' && $string{$i + 1} === '/') {
921
						$this->status = array_pop($this->from);
922
						$i++;
923
						$this->_add_token(COMMENT, $cur_comment);
924
						$cur_comment = '';
925
					} else {
926
						$cur_comment .= $string{$i};
927
					}
928
					break;
929
			}
930
		}
931
932
		$this->optimise->postparse();
933
934
		$this->print->_reset();
935
936
		@setlocale(LC_ALL, $old); // Set locale back to original setting
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
937
938
		return!(empty($this->css) && empty($this->import) && empty($this->charset) && empty($this->tokens) && empty($this->namespace));
939
	}
940
941
	/**
942
	 * Explodes selectors
943
	 * @access private
944
	 * @version 1.0
945
	 */
946
	function explode_selectors() {
947
		// Explode multiple selectors
948
		if ($this->get_cfg('merge_selectors') === 1) {
949
			$new_sels = array();
950
			$lastpos = 0;
951
			$this->sel_separate[] = strlen($this->selector);
952
			foreach ($this->sel_separate as $num => $pos) {
953
				if ($num == count($this->sel_separate) - 1) {
954
					$pos += 1;
955
				}
956
957
				$new_sels[] = substr($this->selector, $lastpos, $pos - $lastpos - 1);
958
				$lastpos = $pos;
959
			}
960
961
			if (count($new_sels) > 1) {
962
				foreach ($new_sels as $selector) {
963
					if (isset($this->css[$this->at][$this->selector])) {
964
						$this->merge_css_blocks($this->at, $selector, $this->css[$this->at][$this->selector]);
965
					}
966
				}
967
				unset($this->css[$this->at][$this->selector]);
968
			}
969
		}
970
		$this->sel_separate = array();
971
	}
972
973
	/**
974
	 * Checks if a character is escaped (and returns true if it is)
975
	 * @param string $string
976
	 * @param integer $pos
977
	 * @access public
978
	 * @return bool
979
	 * @version 1.02
980
	 */
981
	static function escaped(&$string, $pos) {
982
		return!(@($string{$pos - 1} !== '\\') || csstidy::escaped($string, $pos - 1));
983
	}
984
985
	/**
986
	 * Adds a property with value to the existing CSS code
987
	 * @param string $media
988
	 * @param string $selector
989
	 * @param string $property
990
	 * @param string $new_val
991
	 * @access private
992
	 * @version 1.2
993
	 */
994
	function css_add_property($media, $selector, $property, $new_val) {
995
		if ($this->get_cfg('preserve_css') || trim($new_val) == '') {
996
			return;
997
		}
998
999
		$this->added = true;
1000
		if (isset($this->css[$media][$selector][$property])) {
1001
			if ((csstidy::is_important($this->css[$media][$selector][$property]) && csstidy::is_important($new_val)) || !csstidy::is_important($this->css[$media][$selector][$property])) {
1002
				$this->css[$media][$selector][$property] = trim($new_val);
1003
			}
1004
		} else {
1005
			$this->css[$media][$selector][$property] = trim($new_val);
1006
		}
1007
	}
1008
1009
	/**
1010
	 * Start a new media section.
1011
	 * Check if the media is not already known,
1012
	 * else rename it with extra spaces
1013
	 * to avoid merging
1014
	 *
1015
	 * @param string $media
1016
	 * @return string
1017
	 */
1018
	function css_new_media_section($media){
1019
		if($this->get_cfg('preserve_css')) {
1020
			return $media;
1021
		}
1022
1023
		// if the last @media is the same as this
1024
		// keep it
1025
		if (!$this->css OR !is_array($this->css) OR empty($this->css)){
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1026
			return $media;
1027
		}
1028
		end($this->css);
1029
		list($at,) = each($this->css);
1030
		if ($at == $media){
1031
			return $media;
1032
		}
1033
		while (isset($this->css[$media]))
1034
			if (is_numeric($media))
1035
				$media++;
1036
			else
1037
				$media .= " ";
1038
		return $media;
1039
	}
1040
1041
	/**
1042
	 * Start a new selector.
1043
	 * If already referenced in this media section,
1044
	 * rename it with extra space to avoid merging
1045
	 * except if merging is required,
1046
	 * or last selector is the same (merge siblings)
1047
	 *
1048
	 * never merge @font-face
1049
	 *
1050
	 * @param string $media
1051
	 * @param string $selector
1052
	 * @return string
1053
	 */
1054
	function css_new_selector($media,$selector){
1055
		if($this->get_cfg('preserve_css')) {
1056
			return $selector;
1057
		}
1058
		$selector = trim($selector);
1059
		if (strncmp($selector,"@font-face",10)!=0){
1060
			if ($this->settings['merge_selectors'] != false)
1061
				return $selector;
1062
1063 View Code Duplication
			if (!$this->css OR !isset($this->css[$media]) OR !$this->css[$media])
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1064
				return $selector;
1065
1066
			// if last is the same, keep it
1067
			end($this->css[$media]);
1068
			list($sel,) = each($this->css[$media]);
1069
			if ($sel == $selector){
1070
				return $selector;
1071
			}
1072
		}
1073
1074
		while (isset($this->css[$media][$selector]))
1075
			$selector .= " ";
1076
		return $selector;
1077
	}
1078
1079
	/**
1080
	 * Start a new propertie.
1081
	 * If already references in this selector,
1082
	 * rename it with extra space to avoid override
1083
	 *
1084
	 * @param string $media
1085
	 * @param string $selector
1086
	 * @param string $property
1087
	 * @return string
1088
	 */
1089
	function css_new_property($media, $selector, $property){
1090
		if($this->get_cfg('preserve_css')) {
1091
			return $property;
1092
		}
1093 View Code Duplication
		if (!$this->css OR !isset($this->css[$media][$selector]) OR !$this->css[$media][$selector])
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1094
			return $property;
1095
1096
		while (isset($this->css[$media][$selector][$property]))
1097
			$property .= " ";
1098
1099
		return $property;
1100
	}
1101
1102
	/**
1103
	 * Adds CSS to an existing media/selector
1104
	 * @param string $media
1105
	 * @param string $selector
1106
	 * @param array $css_add
1107
	 * @access private
1108
	 * @version 1.1
1109
	 */
1110
	function merge_css_blocks($media, $selector, $css_add) {
1111
		foreach ($css_add as $property => $value) {
1112
			$this->css_add_property($media, $selector, $property, $value, false);
0 ignored issues
show
Unused Code introduced by
The call to csstidy::css_add_property() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1113
		}
1114
	}
1115
1116
	/**
1117
	 * Checks if $value is !important.
1118
	 * @param string $value
1119
	 * @return bool
1120
	 * @access public
1121
	 * @version 1.0
1122
	 */
1123
	static function is_important(&$value) {
1124
		return (!strcasecmp(substr(str_replace($GLOBALS['csstidy']['whitespace'], '', $value), -10, 10), '!important'));
1125
	}
1126
1127
	/**
1128
	 * Returns a value without !important
1129
	 * @param string $value
1130
	 * @return string
1131
	 * @access public
1132
	 * @version 1.0
1133
	 */
1134
	static function gvw_important($value) {
1135
		if (csstidy::is_important($value)) {
1136
			$value = trim($value);
1137
			$value = substr($value, 0, -9);
1138
			$value = trim($value);
1139
			$value = substr($value, 0, -1);
1140
			$value = trim($value);
1141
			return $value;
1142
		}
1143
		return $value;
1144
	}
1145
1146
	/**
1147
	 * Checks if the next word in a string from pos is a CSS property
1148
	 * @param string $istring
1149
	 * @param integer $pos
1150
	 * @return bool
1151
	 * @access private
1152
	 * @version 1.2
1153
	 */
1154
	function property_is_next($istring, $pos) {
1155
		$all_properties = & $GLOBALS['csstidy']['all_properties'];
1156
		$istring = substr($istring, $pos, strlen($istring) - $pos);
1157
		$pos = strpos($istring, ':');
1158
		if ($pos === false) {
1159
			return false;
1160
		}
1161
		$istring = strtolower(trim(substr($istring, 0, $pos)));
1162
		if (isset($all_properties[$istring])) {
1163
			$this->log('Added semicolon to the end of declaration', 'Warning');
1164
			return true;
1165
		}
1166
		return false;
1167
	}
1168
1169
	/**
1170
	 * Checks if a property is valid
1171
	 * @param string $property
1172
	 * @return bool;
0 ignored issues
show
Documentation introduced by
The doc-type bool; could not be parsed: Expected "|" or "end of type", but got ";" at position 4. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1173
	 * @access public
1174
	 * @version 1.0
1175
	 */
1176
	function property_is_valid($property) {
1177
		$property = strtolower($property);
1178
		if (in_array(trim($property), $GLOBALS['csstidy']['multiple_properties'])) $property = trim($property);
1179
		$all_properties = & $GLOBALS['csstidy']['all_properties'];
1180
		return (isset($all_properties[$property]) && strpos($all_properties[$property], strtoupper($this->get_cfg('css_level'))) !== false );
1181
	}
1182
1183
	/**
1184
	 * Accepts a list of strings (e.g., the argument to format() in a @font-face src property)
1185
	 * and returns a list of the strings.  Converts things like:
1186
	 *
1187
	 * format(abc) => format("abc")
1188
	 * format(abc def) => format("abc","def")
1189
	 * format(abc "def") => format("abc","def")
1190
	 * format(abc, def, ghi) => format("abc","def","ghi")
1191
	 * format("abc",'def') => format("abc","def")
1192
	 * format("abc, def, ghi") => format("abc, def, ghi")
1193
	 *
1194
	 * @param string
1195
	 * @return array
1196
	 */
1197
1198
	function parse_string_list($value) {
1199
		$value = trim($value);
1200
1201
		// Case: empty
1202
		if (!$value) return array();
1203
1204
		$strings = array();
1205
1206
		$in_str = false;
1207
		$current_string = "";
1208
1209
		for ($i = 0, $_len = strlen($value); $i < $_len; $i++) {
1210
			if (($value{$i} == "," || $value{$i} === " ") && $in_str === true) {
1211
				$in_str = false;
1212
				$strings[] = $current_string;
1213
				$current_string = "";
1214
			}
1215
			else if ($value{$i} == '"' || $value{$i} == "'"){
1216
				if ($in_str === $value{$i}) {
1217
					$strings[] = $current_string;
1218
					$in_str = false;
1219
					$current_string = "";
1220
					continue;
1221
				}
1222
				else if (!$in_str) {
1223
					$in_str = $value{$i};
1224
				}
1225
			}
1226
			else {
1227
				if ($in_str){
1228
					$current_string .= $value{$i};
1229
				}
1230
				else {
1231
					if (!preg_match("/[\s,]/", $value{$i})) {
1232
						$in_str = true;
1233
						$current_string = $value{$i};
1234
					}
1235
				}
1236
			}
1237
		}
1238
1239
		if ($current_string) {
1240
			$strings[] = $current_string;
1241
		}
1242
1243
		return $strings;
1244
	}
1245
}
1246