Completed
Push — branch-4.0 ( fc0d6f...bf547c )
by
unknown
08:22
created

csstidy::css_new_property()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 2
Ratio 16.67 %

Importance

Changes 0
Metric Value
cc 6
eloc 8
nc 4
nop 3
dl 2
loc 12
rs 8.8571
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
	/**
245
	 * Loads standard template and sets default settings
246
	 * @access private
247
	 * @version 1.3
248
	 */
249
	function csstidy() {
0 ignored issues
show
Coding Style Best Practice introduced by
Please use __construct() instead of a PHP4-style constructor that is named after the class.
Loading history...
250
		$this->settings['remove_bslash'] = true;
251
		$this->settings['compress_colors'] = true;
252
		$this->settings['compress_font-weight'] = true;
253
		$this->settings['lowercase_s'] = false;
254
		/*
255
		  1 common shorthands optimization
256
		  2 + font property optimization
257
		  3 + background property optimization
258
		 */
259
		$this->settings['optimise_shorthands'] = 1;
260
		$this->settings['remove_last_;'] = true;
261
		/* rewrite all properties with low case, better for later gzip OK, safe*/
262
		$this->settings['case_properties'] = 1;
263
		/* sort properties in alpabetic order, better for later gzip
264
		 * but can cause trouble in case of overiding same propertie or using hack
265
		 */
266
		$this->settings['sort_properties'] = false;
267
		/*
268
		  1, 3, 5, etc -- enable sorting selectors inside @media: a{}b{}c{}
269
		  2, 5, 8, etc -- enable sorting selectors inside one CSS declaration: a,b,c{}
270
		  preserve order by default cause it can break functionnality
271
		 */
272
		$this->settings['sort_selectors'] = 0;
273
		/* is dangeroues to be used: CSS is broken sometimes */
274
		$this->settings['merge_selectors'] = 0;
275
		/* preserve or not browser hacks */
276
		$this->settings['discard_invalid_selectors'] = false;
277
		$this->settings['discard_invalid_properties'] = false;
278
		$this->settings['css_level'] = 'CSS2.1';
279
		$this->settings['preserve_css'] = false;
280
		$this->settings['timestamp'] = false;
281
		$this->settings['template'] = ''; // say that propertie exist
282
		$this->set_cfg('template','default'); // call load_template
283
		$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...
284
285
		$this->tokens_list = & $GLOBALS['csstidy']['tokens'];
286
	}
287
288
	/**
289
	 * Get the value of a setting.
290
	 * @param string $setting
291
	 * @access public
292
	 * @return mixed
293
	 * @version 1.0
294
	 */
295
	function get_cfg($setting) {
296
		if (isset($this->settings[$setting])) {
297
			return $this->settings[$setting];
298
		}
299
		return false;
300
	}
301
302
	/**
303
	 * Load a template
304
	 * @param string $template used by set_cfg to load a template via a configuration setting
305
	 * @access private
306
	 * @version 1.4
307
	 */
308
	function _load_template($template) {
309
		switch ($template) {
310
			case 'default':
311
				$this->load_template('default');
312
				break;
313
314
			case 'highest':
315
				$this->load_template('highest_compression');
316
				break;
317
318
			case 'high':
319
				$this->load_template('high_compression');
320
				break;
321
322
			case 'low':
323
				$this->load_template('low_compression');
324
				break;
325
326
			default:
327
				$this->load_template($template);
328
				break;
329
		}
330
	}
331
332
	/**
333
	 * Set the value of a setting.
334
	 * @param string $setting
335
	 * @param mixed $value
336
	 * @access public
337
	 * @return bool
338
	 * @version 1.0
339
	 */
340
	function set_cfg($setting, $value=null) {
341
		if (is_array($setting) && $value === null) {
342
			foreach ($setting as $setprop => $setval) {
343
				$this->settings[$setprop] = $setval;
344
			}
345
			if (array_key_exists('template', $setting)) {
346
				$this->_load_template($this->settings['template']);
347
			}
348
			return true;
349
		} else if (isset($this->settings[$setting]) && $value !== '') {
350
			$this->settings[$setting] = $value;
351
			if ($setting === 'template') {
352
				$this->_load_template($this->settings['template']);
353
			}
354
			return true;
355
		}
356
		return false;
357
	}
358
359
	/**
360
	 * Adds a token to $this->tokens
361
	 * @param mixed $type
362
	 * @param string $data
363
	 * @param bool $do add a token even if preserve_css is off
364
	 * @access private
365
	 * @version 1.0
366
	 */
367
	function _add_token($type, $data, $do = false) {
368
		if ($this->get_cfg('preserve_css') || $do) {
369
			$this->tokens[] = array($type, ($type == COMMENT) ? $data : trim($data));
370
		}
371
	}
372
373
	/**
374
	 * Add a message to the message log
375
	 * @param string $message
376
	 * @param string $type
377
	 * @param integer $line
378
	 * @access private
379
	 * @version 1.0
380
	 */
381
	function log($message, $type, $line = -1) {
382
		if ($line === -1) {
383
			$line = $this->line;
384
		}
385
		$line = intval($line);
386
		$add = array('m' => $message, 't' => $type);
387
		if (!isset($this->log[$line]) || !in_array($add, $this->log[$line])) {
388
			$this->log[$line][] = $add;
389
		}
390
	}
391
392
	/**
393
	 * Parse unicode notations and find a replacement character
394
	 * @param string $string
395
	 * @param integer $i
396
	 * @access private
397
	 * @return string
398
	 * @version 1.2
399
	 */
400
	function _unicode(&$string, &$i) {
401
		++$i;
402
		$add = '';
403
		$replaced = false;
404
405
		while ($i < strlen($string) && (ctype_xdigit($string{$i}) || ctype_space($string{$i})) && strlen($add) < 6) {
406
			$add .= $string{$i};
407
408
			if (ctype_space($string{$i})) {
409
				break;
410
			}
411
			$i++;
412
		}
413
414
		if (hexdec($add) > 47 && hexdec($add) < 58 || hexdec($add) > 64 && hexdec($add) < 91 || hexdec($add) > 96 && hexdec($add) < 123) {
415
			$this->log('Replaced unicode notation: Changed \\' . $add . ' to ' . chr(hexdec($add)), 'Information');
416
			$add = chr(hexdec($add));
417
			$replaced = true;
418
		} else {
419
			$add = trim('\\' . $add);
420
		}
421
422
		if (@ctype_xdigit($string{$i + 1}) && ctype_space($string{$i})
423
						&& !$replaced || !ctype_space($string{$i})) {
424
			$i--;
425
		}
426
427
		if ($add !== '\\' || !$this->get_cfg('remove_bslash') || strpos($this->tokens_list, $string{$i + 1}) !== false) {
428
			return $add;
429
		}
430
431
		if ($add === '\\') {
432
			$this->log('Removed unnecessary backslash', 'Information');
433
		}
434
		return '';
435
	}
436
437
	/**
438
	 * Write formatted output to a file
439
	 * @param string $filename
440
	 * @param string $doctype when printing formatted, is a shorthand for the document type
441
	 * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet
442
	 * @param string $title when printing formatted, is the title to be added in the head of the document
443
	 * @param string $lang when printing formatted, gives a two-letter language code to be added to the output
444
	 * @access public
445
	 * @version 1.4
446
	 */
447
	function write_page($filename, $doctype='xhtml1.1', $externalcss=true, $title='', $lang='en') {
0 ignored issues
show
Unused Code introduced by
The parameter $doctype is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $externalcss is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $title is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $lang is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
448
		$this->write($filename, true);
449
	}
450
451
	/**
452
	 * Write plain output to a file
453
	 * @param string $filename
454
	 * @param bool $formatted whether to print formatted or not
455
	 * @param string $doctype when printing formatted, is a shorthand for the document type
456
	 * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet
457
	 * @param string $title when printing formatted, is the title to be added in the head of the document
458
	 * @param string $lang when printing formatted, gives a two-letter language code to be added to the output
459
	 * @param bool $pre_code whether to add pre and code tags around the code (for light HTML formatted templates)
460
	 * @access public
461
	 * @version 1.4
462
	 */
463
	function write($filename, $formatted=false, $doctype='xhtml1.1', $externalcss=true, $title='', $lang='en', $pre_code=true) {
464
		$filename .= ( $formatted) ? '.xhtml' : '.css';
465
466
		if (!is_dir('temp')) {
467
			$madedir = mkdir('temp');
468
			if (!$madedir) {
469
				print 'Could not make directory "temp" in ' . dirname(__FILE__);
470
				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...
471
			}
472
		}
473
		$handle = fopen('temp/' . $filename, 'w');
474
		if ($handle) {
475
			if (!$formatted) {
476
				fwrite($handle, $this->print->plain());
477
			} else {
478
				fwrite($handle, $this->print->formatted_page($doctype, $externalcss, $title, $lang, $pre_code));
479
			}
480
		}
481
		fclose($handle);
482
	}
483
484
	/**
485
	 * Loads a new template
486
	 * @param string $content either filename (if $from_file == true), content of a template file, "high_compression", "highest_compression", "low_compression", or "default"
487
	 * @param bool $from_file uses $content as filename if true
488
	 * @access public
489
	 * @version 1.1
490
	 * @see http://csstidy.sourceforge.net/templates.php
491
	 */
492
	function load_template($content, $from_file=true) {
493
		$predefined_templates = & $GLOBALS['csstidy']['predefined_templates'];
494
		if ($content === 'high_compression' || $content === 'default' || $content === 'highest_compression' || $content === 'low_compression') {
495
			$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...
496
			return;
497
		}
498
499
500
		if ($from_file) {
501
			$content = strip_tags(file_get_contents($content), '<span>');
502
		}
503
		$content = str_replace("\r\n", "\n", $content); // Unify newlines (because the output also only uses \n)
504
		$template = explode('|', $content);
505
506
		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...
507
			$this->template[$i] = $template[$i];
508
		}
509
	}
510
511
	/**
512
	 * Starts parsing from URL
513
	 * @param string $url
514
	 * @access public
515
	 * @version 1.0
516
	 */
517
	function parse_from_url($url) {
518
		return $this->parse(@file_get_contents($url));
519
	}
520
521
	/**
522
	 * Checks if there is a token at the current position
523
	 * @param string $string
524
	 * @param integer $i
525
	 * @access public
526
	 * @version 1.11
527
	 */
528
	function is_token(&$string, $i) {
529
		return (strpos($this->tokens_list, $string{$i}) !== false && !csstidy::escaped($string, $i));
530
	}
531
532
	/**
533
	 * Parses CSS in $string. The code is saved as array in $this->css
534
	 * @param string $string the CSS code
535
	 * @access public
536
	 * @return bool
537
	 * @version 1.1
538
	 */
539
	function parse($string) {
540
		// Temporarily set locale to en_US in order to handle floats properly
541
		$old = @setlocale(LC_ALL, 0);
542
		@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...
543
544
		// PHP bug? Settings need to be refreshed in PHP4
545
		$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...
546
		//$this->optimise = new csstidy_optimise($this);
547
548
		$all_properties = & $GLOBALS['csstidy']['all_properties'];
549
		$at_rules = & $GLOBALS['csstidy']['at_rules'];
550
		$quoted_string_properties = & $GLOBALS['csstidy']['quoted_string_properties'];
551
552
		$this->css = array();
553
		$this->print->input_css = $string;
554
		$string = str_replace("\r\n", "\n", $string) . ' ';
555
		$cur_comment = '';
556
557
		for ($i = 0, $size = strlen($string); $i < $size; $i++) {
558
			if ($string{$i} === "\n" || $string{$i} === "\r") {
559
				++$this->line;
560
			}
561
562
			switch ($this->status) {
563
				/* Case in at-block */
564
				case 'at':
565
					if (csstidy::is_token($string, $i)) {
566
						if ($string{$i} === '/' && @$string{$i + 1} === '*') {
567
							$this->status = 'ic';
568
							++$i;
569
							$this->from[] = 'at';
570
						} elseif ($string{$i} === '{') {
571
							$this->status = 'is';
572
							$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...
573
							$this->_add_token(AT_START, $this->at);
574
						} elseif ($string{$i} === ',') {
575
							$this->at = trim($this->at) . ',';
576
						} elseif ($string{$i} === '\\') {
577
							$this->at .= $this->_unicode($string, $i);
578
						}
579
						// fix for complicated media, i.e @media screen and (-webkit-min-device-pixel-ratio:1.5)
580
						// '/' is included for ratios in Opera: (-o-min-device-pixel-ratio: 3/2)
581 View Code Duplication
						elseif (in_array($string{$i}, array('(', ')', ':', '.', '/'))) {
582
							$this->at .= $string{$i};
583
						}
584
					} else {
585
						$lastpos = strlen($this->at) - 1;
586
						if (!( (ctype_space($this->at{$lastpos}) || csstidy::is_token($this->at, $lastpos) && $this->at{$lastpos} === ',') && ctype_space($string{$i}))) {
587
							$this->at .= $string{$i};
588
						}
589
					}
590
					break;
591
592
				/* Case in-selector */
593
				case 'is':
594
					if (csstidy::is_token($string, $i)) {
595
						if ($string{$i} === '/' && @$string{$i + 1} === '*' && trim($this->selector) == '') {
596
							$this->status = 'ic';
597
							++$i;
598
							$this->from[] = 'is';
599
						} elseif ($string{$i} === '@' && trim($this->selector) == '') {
600
							// Check for at-rule
601
							$this->invalid_at = true;
602
							foreach ($at_rules as $name => $type) {
603
								if (!strcasecmp(substr($string, $i + 1, strlen($name)), $name)) {
604
									($type === 'at') ? $this->at = '@' . $name : $this->selector = '@' . $name;
605
									$this->status = $type;
606
									$i += strlen($name);
607
									$this->invalid_at = false;
608
								}
609
							}
610
611
							if ($this->invalid_at) {
612
								$this->selector = '@';
613
								$invalid_at_name = '';
614
								for ($j = $i + 1; $j < $size; ++$j) {
615
									if (!ctype_alpha($string{$j})) {
616
										break;
617
									}
618
									$invalid_at_name .= $string{$j};
619
								}
620
								$this->log('Invalid @-rule: ' . $invalid_at_name . ' (removed)', 'Warning');
621
							}
622
						} elseif (($string{$i} === '"' || $string{$i} === "'")) {
623
							$this->cur_string[] = $string{$i};
624
							$this->status = 'instr';
625
							$this->str_char[] = $string{$i};
626
							$this->from[] = 'is';
627
							/* fixing CSS3 attribute selectors, i.e. a[href$=".mp3" */
628
							$this->quoted_string[] = ($string{$i - 1} == '=' );
629
						} elseif ($this->invalid_at && $string{$i} === ';') {
630
							$this->invalid_at = false;
631
							$this->status = 'is';
632
						} elseif ($string{$i} === '{') {
633
							$this->status = 'ip';
634
							if($this->at == '') {
635
								$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...
636
							}
637
							$this->selector = $this->css_new_selector($this->at,$this->selector);
638
							$this->_add_token(SEL_START, $this->selector);
639
							$this->added = false;
640
						} elseif ($string{$i} === '}') {
641
							$this->_add_token(AT_END, $this->at);
642
							$this->at = '';
643
							$this->selector = '';
644
							$this->sel_separate = array();
645
						} elseif ($string{$i} === ',') {
646
							$this->selector = trim($this->selector) . ',';
647
							$this->sel_separate[] = strlen($this->selector);
648
						} elseif ($string{$i} === '\\') {
649
							$this->selector .= $this->_unicode($string, $i);
650 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...
651
							// remove unnecessary universal selector, FS#147
652
						} else {
653
							$this->selector .= $string{$i};
654
						}
655
					} else {
656
						$lastpos = strlen($this->selector) - 1;
657
						if ($lastpos == -1 || !( (ctype_space($this->selector{$lastpos}) || csstidy::is_token($this->selector, $lastpos) && $this->selector{$lastpos} === ',') && ctype_space($string{$i}))) {
658
							$this->selector .= $string{$i};
659
						}
660
						else if (ctype_space($string{$i}) && $this->get_cfg('preserve_css') && !$this->get_cfg('merge_selectors')) {
661
							$this->selector .= $string{$i};
662
						}
663
					}
664
					break;
665
666
				/* Case in-property */
667
				case 'ip':
668
					if (csstidy::is_token($string, $i)) {
669
						if (($string{$i} === ':' || $string{$i} === '=') && $this->property != '') {
670
							$this->status = 'iv';
671
							if (!$this->get_cfg('discard_invalid_properties') || csstidy::property_is_valid($this->property)) {
672
								$this->property = $this->css_new_property($this->at,$this->selector,$this->property);
673
								$this->_add_token(PROPERTY, $this->property);
674
							}
675
						} elseif ($string{$i} === '/' && @$string{$i + 1} === '*' && $this->property == '') {
676
							$this->status = 'ic';
677
							++$i;
678
							$this->from[] = 'ip';
679 View Code Duplication
						} elseif ($string{$i} === '}') {
680
							$this->explode_selectors();
681
							$this->status = 'is';
682
							$this->invalid_at = false;
683
							$this->_add_token(SEL_END, $this->selector);
684
							$this->selector = '';
685
							$this->property = '';
686
						} elseif ($string{$i} === ';') {
687
							$this->property = '';
688
						} elseif ($string{$i} === '\\') {
689
							$this->property .= $this->_unicode($string, $i);
690
						}
691
						// else this is dumb IE a hack, keep it
692
						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...
693
							$this->property .= $string{$i};
694
						}
695
					}
696
					elseif (!ctype_space($string{$i})) {
697
						$this->property .= $string{$i};
698
					}
699
					break;
700
701
				/* Case in-value */
702
				case 'iv':
703
					$pn = (($string{$i} === "\n" || $string{$i} === "\r") && $this->property_is_next($string, $i + 1) || $i == strlen($string) - 1);
704
					if ((csstidy::is_token($string, $i) || $pn) && (!($string{$i} == ',' && !ctype_space($string{$i+1})))) {
705
						if ($string{$i} === '/' && @$string{$i + 1} === '*') {
706
							$this->status = 'ic';
707
							++$i;
708
							$this->from[] = 'iv';
709
						} elseif (($string{$i} === '"' || $string{$i} === "'" || $string{$i} === '(')) {
710
							$this->cur_string[] = $string{$i};
711
							$this->str_char[] = ($string{$i} === '(') ? ')' : $string{$i};
712
							$this->status = 'instr';
713
							$this->from[] = 'iv';
714
							$this->quoted_string[] = in_array(strtolower($this->property), $quoted_string_properties);
715
						} elseif ($string{$i} === ',') {
716
							$this->sub_value = trim($this->sub_value) . ',';
717
						} elseif ($string{$i} === '\\') {
718
							$this->sub_value .= $this->_unicode($string, $i);
719
						} elseif ($string{$i} === ';' || $pn) {
720
							if ($this->selector{0} === '@' && isset($at_rules[substr($this->selector, 1)]) && $at_rules[substr($this->selector, 1)] === 'iv') {
721
								$this->status = 'is';
722
723
								switch ($this->selector) {
724
									case '@charset':
725
										/* Add quotes to charset */
726
										$this->sub_value_arr[] = '"' . trim($this->sub_value) . '"';
727
										$this->charset = $this->sub_value_arr[0];
728
										break;
729
									case '@namespace':
730
										/* Add quotes to namespace */
731
										$this->sub_value_arr[] = '"' . trim($this->sub_value) . '"';
732
										$this->namespace = implode(' ', $this->sub_value_arr);
733
										break;
734
									case '@import':
735
										$this->sub_value = trim($this->sub_value);
736
737
										if (empty($this->sub_value_arr)) {
738
											// Quote URLs in imports only if they're not already inside url() and not already quoted.
739
											if (substr($this->sub_value, 0, 4) != 'url(') {
740
												if (!($this->sub_value{0} == substr($this->sub_value, -1) && in_array($this->sub_value{0}, array("'", '"')))) {
741
													$this->sub_value = '"' . $this->sub_value . '"';
742
												}
743
											}
744
										}
745
746
										$this->sub_value_arr[] = $this->sub_value;
747
										$this->import[] = implode(' ', $this->sub_value_arr);
748
										break;
749
								}
750
751
								$this->sub_value_arr = array();
752
								$this->sub_value = '';
753
								$this->selector = '';
754
								$this->sel_separate = array();
755
							} else {
756
								$this->status = 'ip';
757
							}
758
						} elseif ($string{$i} !== '}') {
759
							$this->sub_value .= $string{$i};
760
						}
761
						if (($string{$i} === '}' || $string{$i} === ';' || $pn) && !empty($this->selector)) {
762
							if ($this->at == '') {
763
								$this->at = $this->css_new_media_section(DEFAULT_AT);
764
							}
765
766
							// case settings
767
							if ($this->get_cfg('lowercase_s')) {
768
								$this->selector = strtolower($this->selector);
769
							}
770
							$this->property = strtolower($this->property);
771
772
							$this->optimise->subvalue();
773
							if ($this->sub_value != '') {
774
								if (substr($this->sub_value, 0, 6) == 'format') {
775
									$format_strings = csstidy::parse_string_list(substr($this->sub_value, 7, -1));
776
									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...
777
										$this->sub_value = "";
778
									}
779
									else {
780
										$this->sub_value = "format(";
781
782
										foreach ($format_strings as $format_string) {
783
											$this->sub_value .= '"' . str_replace('"', '\\"', $format_string) . '",';
784
										}
785
786
										$this->sub_value = substr($this->sub_value, 0, -1) . ")";
787
									}
788
								}
789
								if ($this->sub_value != '') {
790
									$this->sub_value_arr[] = $this->sub_value;
791
								}
792
								$this->sub_value = '';
793
							}
794
795
							$this->value = array_shift($this->sub_value_arr);
796
							while(count($this->sub_value_arr)){
797
								//$this->value .= (substr($this->value,-1,1)==','?'':' ').array_shift($this->sub_value_arr);
798
								$this->value .= ' '.array_shift($this->sub_value_arr);
799
							}
800
801
							$this->optimise->value();
802
803
							$valid = csstidy::property_is_valid($this->property);
804
							if ((!$this->invalid_at || $this->get_cfg('preserve_css')) && (!$this->get_cfg('discard_invalid_properties') || $valid)) {
805
								$this->css_add_property($this->at, $this->selector, $this->property, $this->value);
806
								$this->_add_token(VALUE, $this->value);
807
								$this->optimise->shorthands();
808
							}
809
							if (!$valid) {
810
								if ($this->get_cfg('discard_invalid_properties')) {
811
									$this->log('Removed invalid property: ' . $this->property, 'Warning');
812
								} else {
813
									$this->log('Invalid property in ' . strtoupper($this->get_cfg('css_level')) . ': ' . $this->property, 'Warning');
814
								}
815
							}
816
817
							$this->property = '';
818
							$this->sub_value_arr = array();
819
							$this->value = '';
820
						}
821 View Code Duplication
						if ($string{$i} === '}') {
822
							$this->explode_selectors();
823
							$this->_add_token(SEL_END, $this->selector);
824
							$this->status = 'is';
825
							$this->invalid_at = false;
826
							$this->selector = '';
827
						}
828
					} elseif (!$pn) {
829
						$this->sub_value .= $string{$i};
830
831
						if (ctype_space($string{$i}) || $string{$i} == ',') {
832
							$this->optimise->subvalue();
833
							if ($this->sub_value != '') {
834
								$this->sub_value_arr[] = $this->sub_value;
835
								$this->sub_value = '';
836
							}
837
						}
838
					}
839
					break;
840
841
				/* Case in string */
842
				case 'instr':
843
					$_str_char = $this->str_char[count($this->str_char)-1];
844
					$_cur_string = $this->cur_string[count($this->cur_string)-1];
845
					$temp_add = $string{$i};
846
847
					// Add another string to the stack. Strings can't be nested inside of quotes, only parentheses, but
848
					// parentheticals can be nested more than once.
849
					if ($_str_char === ")" && ($string{$i} === "(" || $string{$i} === '"' || $string{$i} === '\'') && !csstidy::escaped($string, $i)) {
850
						$this->cur_string[] = $string{$i};
851
						$this->str_char[] = $string{$i} == "(" ? ")" : $string{$i};
852
						$this->from[] = 'instr';
853
						$this->quoted_string[] = !($string{$i} === "(");
854
						continue;
855
					}
856
857
					if ($_str_char !== ")" && ($string{$i} === "\n" || $string{$i} === "\r") && !($string{$i - 1} === '\\' && !csstidy::escaped($string, $i - 1))) {
858
						$temp_add = "\\A";
859
						$this->log('Fixed incorrect newline in string', 'Warning');
860
					}
861
862
					$_cur_string .= $temp_add;
863
864
					if ($string{$i} === $_str_char && !csstidy::escaped($string, $i)) {
865
						$_quoted_string = array_pop($this->quoted_string);
866
867
						$this->status = array_pop($this->from);
868
869
						if (!preg_match('|[' . implode('', $GLOBALS['csstidy']['whitespace']) . ']|uis', $_cur_string) && $this->property !== 'content') {
870
							if (!$_quoted_string) {
871
								if ($_str_char !== ')') {
872
									// Convert properties like
873
									// font-family: 'Arial';
874
									// to
875
									// font-family: Arial;
876
									// or
877
									// url("abc")
878
									// to
879
									// url(abc)
880
									$_cur_string = substr($_cur_string, 1, -1);
881
								}
882
							} else {
883
								$_quoted_string = false;
884
							}
885
						}
886
887
						array_pop($this->cur_string);
888
						array_pop($this->str_char);
889
890
						if ($_str_char === ")") {
891
							$_cur_string = "(" . trim(substr($_cur_string, 1, -1)) . ")";
892
						}
893
894
						if ($this->status === 'iv') {
895
							if (!$_quoted_string){
896
								if (strpos($_cur_string,',')!==false)
897
									// we can on only remove space next to ','
898
									$_cur_string = implode(',',array_map('trim',explode(',',$_cur_string)));
899
								// and multiple spaces (too expensive)
900
								if (strpos($_cur_string,'  ')!==false)
901
									$_cur_string = preg_replace(",\s+,"," ",$_cur_string);
902
							}
903
							$this->sub_value .= $_cur_string;
904
						} elseif ($this->status === 'is') {
905
							$this->selector .= $_cur_string;
906
						} elseif ($this->status === 'instr') {
907
							$this->cur_string[count($this->cur_string)-1] .= $_cur_string;
908
						}
909
					}
910
					else {
911
						$this->cur_string[count($this->cur_string)-1] = $_cur_string;
912
					}
913
					break;
914
915
				/* Case in-comment */
916
				case 'ic':
917
					if ($string{$i} === '*' && $string{$i + 1} === '/') {
918
						$this->status = array_pop($this->from);
919
						$i++;
920
						$this->_add_token(COMMENT, $cur_comment);
921
						$cur_comment = '';
922
					} else {
923
						$cur_comment .= $string{$i};
924
					}
925
					break;
926
			}
927
		}
928
929
		$this->optimise->postparse();
930
931
		$this->print->_reset();
932
933
		@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...
934
935
		return!(empty($this->css) && empty($this->import) && empty($this->charset) && empty($this->tokens) && empty($this->namespace));
936
	}
937
938
	/**
939
	 * Explodes selectors
940
	 * @access private
941
	 * @version 1.0
942
	 */
943
	function explode_selectors() {
944
		// Explode multiple selectors
945
		if ($this->get_cfg('merge_selectors') === 1) {
946
			$new_sels = array();
947
			$lastpos = 0;
948
			$this->sel_separate[] = strlen($this->selector);
949
			foreach ($this->sel_separate as $num => $pos) {
950
				if ($num == count($this->sel_separate) - 1) {
951
					$pos += 1;
952
				}
953
954
				$new_sels[] = substr($this->selector, $lastpos, $pos - $lastpos - 1);
955
				$lastpos = $pos;
956
			}
957
958
			if (count($new_sels) > 1) {
959
				foreach ($new_sels as $selector) {
960
					if (isset($this->css[$this->at][$this->selector])) {
961
						$this->merge_css_blocks($this->at, $selector, $this->css[$this->at][$this->selector]);
962
					}
963
				}
964
				unset($this->css[$this->at][$this->selector]);
965
			}
966
		}
967
		$this->sel_separate = array();
968
	}
969
970
	/**
971
	 * Checks if a character is escaped (and returns true if it is)
972
	 * @param string $string
973
	 * @param integer $pos
974
	 * @access public
975
	 * @return bool
976
	 * @version 1.02
977
	 */
978
	static function escaped(&$string, $pos) {
979
		return!(@($string{$pos - 1} !== '\\') || csstidy::escaped($string, $pos - 1));
980
	}
981
982
	/**
983
	 * Adds a property with value to the existing CSS code
984
	 * @param string $media
985
	 * @param string $selector
986
	 * @param string $property
987
	 * @param string $new_val
988
	 * @access private
989
	 * @version 1.2
990
	 */
991
	function css_add_property($media, $selector, $property, $new_val) {
992
		if ($this->get_cfg('preserve_css') || trim($new_val) == '') {
993
			return;
994
		}
995
996
		$this->added = true;
997
		if (isset($this->css[$media][$selector][$property])) {
998
			if ((csstidy::is_important($this->css[$media][$selector][$property]) && csstidy::is_important($new_val)) || !csstidy::is_important($this->css[$media][$selector][$property])) {
999
				$this->css[$media][$selector][$property] = trim($new_val);
1000
			}
1001
		} else {
1002
			$this->css[$media][$selector][$property] = trim($new_val);
1003
		}
1004
	}
1005
1006
	/**
1007
	 * Start a new media section.
1008
	 * Check if the media is not already known,
1009
	 * else rename it with extra spaces
1010
	 * to avoid merging
1011
	 *
1012
	 * @param string $media
1013
	 * @return string
1014
	 */
1015
	function css_new_media_section($media){
1016
		if($this->get_cfg('preserve_css')) {
1017
			return $media;
1018
		}
1019
1020
		// if the last @media is the same as this
1021
		// keep it
1022
		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...
1023
			return $media;
1024
		}
1025
		end($this->css);
1026
		list($at,) = each($this->css);
1027
		if ($at == $media){
1028
			return $media;
1029
		}
1030
		while (isset($this->css[$media]))
1031
			if (is_numeric($media))
1032
				$media++;
1033
			else
1034
				$media .= " ";
1035
		return $media;
1036
	}
1037
1038
	/**
1039
	 * Start a new selector.
1040
	 * If already referenced in this media section,
1041
	 * rename it with extra space to avoid merging
1042
	 * except if merging is required,
1043
	 * or last selector is the same (merge siblings)
1044
	 *
1045
	 * never merge @font-face
1046
	 *
1047
	 * @param string $media
1048
	 * @param string $selector
1049
	 * @return string
1050
	 */
1051
	function css_new_selector($media,$selector){
1052
		if($this->get_cfg('preserve_css')) {
1053
			return $selector;
1054
		}
1055
		$selector = trim($selector);
1056
		if (strncmp($selector,"@font-face",10)!=0){
1057
			if ($this->settings['merge_selectors'] != false)
1058
				return $selector;
1059
1060 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...
1061
				return $selector;
1062
1063
			// if last is the same, keep it
1064
			end($this->css[$media]);
1065
			list($sel,) = each($this->css[$media]);
1066
			if ($sel == $selector){
1067
				return $selector;
1068
			}
1069
		}
1070
1071
		while (isset($this->css[$media][$selector]))
1072
			$selector .= " ";
1073
		return $selector;
1074
	}
1075
1076
	/**
1077
	 * Start a new propertie.
1078
	 * If already references in this selector,
1079
	 * rename it with extra space to avoid override
1080
	 *
1081
	 * @param string $media
1082
	 * @param string $selector
1083
	 * @param string $property
1084
	 * @return string
1085
	 */
1086
	function css_new_property($media, $selector, $property){
1087
		if($this->get_cfg('preserve_css')) {
1088
			return $property;
1089
		}
1090 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...
1091
			return $property;
1092
1093
		while (isset($this->css[$media][$selector][$property]))
1094
			$property .= " ";
1095
1096
		return $property;
1097
	}
1098
1099
	/**
1100
	 * Adds CSS to an existing media/selector
1101
	 * @param string $media
1102
	 * @param string $selector
1103
	 * @param array $css_add
1104
	 * @access private
1105
	 * @version 1.1
1106
	 */
1107
	function merge_css_blocks($media, $selector, $css_add) {
1108
		foreach ($css_add as $property => $value) {
1109
			$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...
1110
		}
1111
	}
1112
1113
	/**
1114
	 * Checks if $value is !important.
1115
	 * @param string $value
1116
	 * @return bool
1117
	 * @access public
1118
	 * @version 1.0
1119
	 */
1120
	static function is_important(&$value) {
1121
		return (!strcasecmp(substr(str_replace($GLOBALS['csstidy']['whitespace'], '', $value), -10, 10), '!important'));
1122
	}
1123
1124
	/**
1125
	 * Returns a value without !important
1126
	 * @param string $value
1127
	 * @return string
1128
	 * @access public
1129
	 * @version 1.0
1130
	 */
1131
	static function gvw_important($value) {
1132
		if (csstidy::is_important($value)) {
1133
			$value = trim($value);
1134
			$value = substr($value, 0, -9);
1135
			$value = trim($value);
1136
			$value = substr($value, 0, -1);
1137
			$value = trim($value);
1138
			return $value;
1139
		}
1140
		return $value;
1141
	}
1142
1143
	/**
1144
	 * Checks if the next word in a string from pos is a CSS property
1145
	 * @param string $istring
1146
	 * @param integer $pos
1147
	 * @return bool
1148
	 * @access private
1149
	 * @version 1.2
1150
	 */
1151
	function property_is_next($istring, $pos) {
1152
		$all_properties = & $GLOBALS['csstidy']['all_properties'];
1153
		$istring = substr($istring, $pos, strlen($istring) - $pos);
1154
		$pos = strpos($istring, ':');
1155
		if ($pos === false) {
1156
			return false;
1157
		}
1158
		$istring = strtolower(trim(substr($istring, 0, $pos)));
1159
		if (isset($all_properties[$istring])) {
1160
			$this->log('Added semicolon to the end of declaration', 'Warning');
1161
			return true;
1162
		}
1163
		return false;
1164
	}
1165
1166
	/**
1167
	 * Checks if a property is valid
1168
	 * @param string $property
1169
	 * @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...
1170
	 * @access public
1171
	 * @version 1.0
1172
	 */
1173
	function property_is_valid($property) {
1174
		$property = strtolower($property);
1175
		if (in_array(trim($property), $GLOBALS['csstidy']['multiple_properties'])) $property = trim($property);
1176
		$all_properties = & $GLOBALS['csstidy']['all_properties'];
1177
		return (isset($all_properties[$property]) && strpos($all_properties[$property], strtoupper($this->get_cfg('css_level'))) !== false );
1178
	}
1179
1180
	/**
1181
	 * Accepts a list of strings (e.g., the argument to format() in a @font-face src property)
1182
	 * and returns a list of the strings.  Converts things like:
1183
	 *
1184
	 * format(abc) => format("abc")
1185
	 * format(abc def) => format("abc","def")
1186
	 * format(abc "def") => format("abc","def")
1187
	 * format(abc, def, ghi) => format("abc","def","ghi")
1188
	 * format("abc",'def') => format("abc","def")
1189
	 * format("abc, def, ghi") => format("abc, def, ghi")
1190
	 *
1191
	 * @param string
1192
	 * @return array
1193
	 */
1194
1195
	function parse_string_list($value) {
1196
		$value = trim($value);
1197
1198
		// Case: empty
1199
		if (!$value) return array();
1200
1201
		$strings = array();
1202
1203
		$in_str = false;
1204
		$current_string = "";
1205
1206
		for ($i = 0, $_len = strlen($value); $i < $_len; $i++) {
1207
			if (($value{$i} == "," || $value{$i} === " ") && $in_str === true) {
1208
				$in_str = false;
1209
				$strings[] = $current_string;
1210
				$current_string = "";
1211
			}
1212
			else if ($value{$i} == '"' || $value{$i} == "'"){
1213
				if ($in_str === $value{$i}) {
1214
					$strings[] = $current_string;
1215
					$in_str = false;
1216
					$current_string = "";
1217
					continue;
1218
				}
1219
				else if (!$in_str) {
1220
					$in_str = $value{$i};
1221
				}
1222
			}
1223
			else {
1224
				if ($in_str){
1225
					$current_string .= $value{$i};
1226
				}
1227
				else {
1228
					if (!preg_match("/[\s,]/", $value{$i})) {
1229
						$in_str = true;
1230
						$current_string = $value{$i};
1231
					}
1232
				}
1233
			}
1234
		}
1235
1236
		if ($current_string) {
1237
			$strings[] = $current_string;
1238
		}
1239
1240
		return $strings;
1241
	}
1242
}
1243