Completed
Push — add/blogname-locale-connect-ur... ( 948a6a...8e4901 )
by
unknown
30:01 queued 20:34
created

scss_parser::to()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 11
Code Lines 9

Duplication

Lines 11
Ratio 100 %
Metric Value
dl 11
loc 11
rs 8.8571
cc 5
eloc 9
nc 9
nop 4
1
<?php
2
/**
3
 * SCSS compiler written in PHP
4
 *
5
 * @copyright 2012-2013 Leaf Corcoran
6
 *
7
 * @license http://opensource.org/licenses/gpl-license GPL-3.0
8
 * @license http://opensource.org/licenses/MIT MIT
9
 *
10
 * @link http://leafo.net/scssphp
11
 */
12
13
/**
14
 * The scss compiler and parser.
15
 *
16
 * Converting SCSS to CSS is a three stage process. The incoming file is parsed
17
 * by `scssc_parser` into a syntax tree, then it is compiled into another tree
18
 * representing the CSS structure by `scssc`. The CSS tree is fed into a
19
 * formatter, like `scssc_formatter` which then outputs CSS as a string.
20
 *
21
 * During the first compile, all values are *reduced*, which means that their
22
 * types are brought to the lowest form before being dump as strings. This
23
 * handles math equations, variable dereferences, and the like.
24
 *
25
 * The `parse` function of `scssc` is the entry point.
26
 *
27
 * In summary:
28
 *
29
 * The `scssc` class creates an instance of the parser, feeds it SCSS code,
30
 * then transforms the resulting tree to a CSS tree. This class also holds the
31
 * evaluation context, such as all available mixins and variables at any given
32
 * time.
33
 *
34
 * The `scssc_parser` class is only concerned with parsing its input.
35
 *
36
 * The `scssc_formatter` takes a CSS tree, and dumps it to a formatted string,
37
 * handling things like indentation.
38
 */
39
40
/**
41
 * SCSS compiler
42
 *
43
 * @author Leaf Corcoran <[email protected]>
44
 */
45
class scssc {
46
	static public $VERSION = "v0.0.9";
47
48
	static protected $operatorNames = array(
49
		'+' => "add",
50
		'-' => "sub",
51
		'*' => "mul",
52
		'/' => "div",
53
		'%' => "mod",
54
55
		'==' => "eq",
56
		'!=' => "neq",
57
		'<' => "lt",
58
		'>' => "gt",
59
60
		'<=' => "lte",
61
		'>=' => "gte",
62
	);
63
64
	static protected $namespaces = array(
65
		"special" => "%",
66
		"mixin" => "@",
67
		"function" => "^",
68
	);
69
70
	static protected $unitTable = array(
71
		"in" => array(
72
			"in" => 1,
73
			"pt" => 72,
74
			"pc" => 6,
75
			"cm" => 2.54,
76
			"mm" => 25.4,
77
			"px" => 96,
78
		)
79
	);
80
81
	static public $true = array("keyword", "true");
82
	static public $false = array("keyword", "false");
83
	static public $null = array("null");
84
85
	static public $defaultValue = array("keyword", "");
86
	static public $selfSelector = array("self");
87
88
	protected $importPaths = array("");
89
	protected $importCache = array();
90
91
	protected $userFunctions = array();
92
93
	protected $numberPrecision = 5;
94
95
	protected $formatter = "scss_formatter_nested";
96
97
	public function compile($code, $name=null) {
98
		$this->indentLevel = -1;
0 ignored issues
show
Bug introduced by
The property indentLevel 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...
99
		$this->commentsSeen = array();
0 ignored issues
show
Bug introduced by
The property commentsSeen 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...
100
		$this->extends = array();
0 ignored issues
show
Bug introduced by
The property extends 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...
101
		$this->extendsMap = array();
0 ignored issues
show
Bug introduced by
The property extendsMap does not seem to exist. Did you mean extends?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
102
103
		$locale = setlocale(LC_NUMERIC, 0);
104
		setlocale(LC_NUMERIC, "C");
105
106
		$this->parsedFiles = array();
0 ignored issues
show
Bug introduced by
The property parsedFiles 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...
107
		$this->parser = new scss_parser($name);
0 ignored issues
show
Bug introduced by
The property parser 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...
108
		$tree = $this->parser->parse($code);
109
110
		$this->formatter = new $this->formatter();
0 ignored issues
show
Documentation Bug introduced by
It seems like new $this->formatter() of type object is incompatible with the declared type string of property $formatter.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
111
112
		$this->env = null;
0 ignored issues
show
Bug introduced by
The property env 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...
113
		$this->scope = null;
0 ignored issues
show
Bug introduced by
The property scope 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...
114
115
		$this->compileRoot($tree);
116
117
		$out = $this->formatter->format($this->scope);
118
119
		setlocale(LC_NUMERIC, $locale);
120
		return $out;
121
	}
122
123
	protected function isSelfExtend($target, $origin) {
124
		foreach ($origin as $sel) {
125
			if (in_array($target, $sel)) {
126
				return true;
127
			}
128
		}
129
130
		return false;
131
	}
132
133
	protected function pushExtends($target, $origin) {
134
		if ($this->isSelfExtend($target, $origin)) {
135
			return;
136
		}
137
138
		$i = count($this->extends);
139
		$this->extends[] = array($target, $origin);
140
141
		foreach ($target as $part) {
142
			if (isset($this->extendsMap[$part])) {
0 ignored issues
show
Bug introduced by
The property extendsMap does not seem to exist. Did you mean extends?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
143
				$this->extendsMap[$part][] = $i;
0 ignored issues
show
Bug introduced by
The property extendsMap does not seem to exist. Did you mean extends?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
144
			} else {
145
				$this->extendsMap[$part] = array($i);
0 ignored issues
show
Bug introduced by
The property extendsMap does not seem to exist. Did you mean extends?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
146
			}
147
		}
148
	}
149
150
	protected function makeOutputBlock($type, $selectors = null) {
151
		$out = new stdClass;
152
		$out->type = $type;
153
		$out->lines = array();
154
		$out->children = array();
155
		$out->parent = $this->scope;
156
		$out->selectors = $selectors;
157
		$out->depth = $this->env->depth;
158
159
		return $out;
160
	}
161
162
	protected function matchExtendsSingle($single, &$outOrigin) {
163
		$counts = array();
164
		foreach ($single as $part) {
165
			if (!is_string($part)) return false; // hmm
166
167
			if (isset($this->extendsMap[$part])) {
0 ignored issues
show
Bug introduced by
The property extendsMap does not seem to exist. Did you mean extends?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
168
				foreach ($this->extendsMap[$part] as $idx) {
0 ignored issues
show
Bug introduced by
The property extendsMap does not seem to exist. Did you mean extends?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
169
					$counts[$idx] =
170
						isset($counts[$idx]) ? $counts[$idx] + 1 : 1;
171
				}
172
			}
173
		}
174
175
		$outOrigin = array();
176
		$found = false;
177
178
		foreach ($counts as $idx => $count) {
179
			list($target, $origin) = $this->extends[$idx];
180
181
			// check count
182
			if ($count != count($target)) continue;
183
184
			// check if target is subset of single
185
			if (array_diff(array_intersect($single, $target), $target)) continue;
186
187
			$rem = array_diff($single, $target);
188
189
			foreach ($origin as $j => $new) {
190
				// prevent infinite loop when target extends itself
191
				foreach ($new as $new_selector) {
192
					if (!array_diff($single, $new_selector)) {
193
						continue 2;
194
					}
195
				}
196
197
				$origin[$j][count($origin[$j]) - 1] = $this->combineSelectorSingle(end($new), $rem);
198
			}
199
200
			$outOrigin = array_merge($outOrigin, $origin);
201
202
			$found = true;
203
		}
204
205
		return $found;
206
	}
207
208
	protected function combineSelectorSingle($base, $other) {
209
		$tag = null;
210
		$out = array();
211
212
		foreach (array($base, $other) as $single) {
213
			foreach ($single as $part) {
214
				if (preg_match('/^[^\[.#:]/', $part)) {
215
					$tag = $part;
216
				} else {
217
					$out[] = $part;
218
				}
219
			}
220
		}
221
222
		if ($tag) {
223
			array_unshift($out, $tag);
224
		}
225
226
		return $out;
227
	}
228
229
	protected function matchExtends($selector, &$out, $from = 0, $initial=true) {
230
		foreach ($selector as $i => $part) {
231
			if ($i < $from) continue;
232
233
			if ($this->matchExtendsSingle($part, $origin)) {
234
				$before = array_slice($selector, 0, $i);
235
				$after = array_slice($selector, $i + 1);
236
237
				foreach ($origin as $new) {
238
					$k = 0;
239
240
					// remove shared parts
241
					if ($initial) {
242
						foreach ($before as $k => $val) {
243
							if (!isset($new[$k]) || $val != $new[$k]) {
244
								break;
245
							}
246
						}
247
					}
248
249
					$result = array_merge(
250
						$before,
251
						$k > 0 ? array_slice($new, $k) : $new,
252
						$after);
253
254
255
					if ($result == $selector) continue;
256
					$out[] = $result;
257
258
					// recursively check for more matches
259
					$this->matchExtends($result, $out, $i, false);
260
261
					// selector sequence merging
262
					if (!empty($before) && count($new) > 1) {
263
						$result2 = array_merge(
264
							array_slice($new, 0, -1),
265
							$k > 0 ? array_slice($before, $k) : $before,
266
							array_slice($new, -1),
267
							$after);
268
269
						$out[] = $result2;
270
					}
271
				}
272
			}
273
		}
274
	}
275
276
	protected function flattenSelectors($block, $parentKey = null) {
277
		if ($block->selectors) {
278
			$selectors = array();
279
			foreach ($block->selectors as $s) {
280
				$selectors[] = $s;
281
				if (!is_array($s)) continue;
282
				// check extends
283
				if (!empty($this->extendsMap)) {
0 ignored issues
show
Bug introduced by
The property extendsMap does not seem to exist. Did you mean extends?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
284
					$this->matchExtends($s, $selectors);
285
				}
286
			}
287
288
			$block->selectors = array();
289
			$placeholderSelector = false;
290
			foreach ($selectors as $selector) {
291
				if ($this->hasSelectorPlaceholder($selector)) {
292
					$placeholderSelector = true;
293
					continue;
294
				}
295
				$block->selectors[] = $this->compileSelector($selector);
296
			}
297
298
			if ($placeholderSelector && 0 == count($block->selectors) && null !== $parentKey) {
299
				unset($block->parent->children[$parentKey]);
300
				return;
301
			}
302
		}
303
304
		foreach ($block->children as $key => $child) {
305
			$this->flattenSelectors($child, $key);
306
		}
307
	}
308
309
	protected function compileRoot($rootBlock) {
310
		$this->pushEnv($rootBlock);
311
		$this->scope = $this->makeOutputBlock("root");
312
313
		$this->compileChildren($rootBlock->children, $this->scope);
314
		$this->flattenSelectors($this->scope);
315
316
		$this->popEnv();
317
	}
318
319
	protected function compileMedia($media) {
320
		$this->pushEnv($media);
321
		$parentScope = $this->mediaParent($this->scope);
322
323
		$this->scope = $this->makeOutputBlock("media", array(
324
			$this->compileMediaQuery($this->multiplyMedia($this->env)))
325
		);
326
327
		$parentScope->children[] = $this->scope;
328
329
		// top level properties in a media cause it to be wrapped
330
		$needsWrap = false;
331
		foreach ($media->children as $child) {
332
			$type = $child[0];
333
			if ($type !== 'block' && $type !== 'media' && $type !== 'directive') {
334
				$needsWrap = true;
335
				break;
336
			}
337
		}
338
339
		if ($needsWrap) {
340
			$wrapped = (object)array(
341
				"selectors" => array(),
342
				"children" => $media->children
343
			);
344
			$media->children = array(array("block", $wrapped));
345
		}
346
347
		$this->compileChildren($media->children, $this->scope);
348
349
		$this->scope = $this->scope->parent;
350
		$this->popEnv();
351
	}
352
353 View Code Duplication
	protected function mediaParent($scope) {
354
		while (!empty($scope->parent)) {
355
			if (!empty($scope->type) && $scope->type != "media") {
356
				break;
357
			}
358
			$scope = $scope->parent;
359
		}
360
361
		return $scope;
362
	}
363
364
	// TODO refactor compileNestedBlock and compileMedia into same thing
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
365 View Code Duplication
	protected function compileNestedBlock($block, $selectors) {
366
		$this->pushEnv($block);
367
368
		$this->scope = $this->makeOutputBlock($block->type, $selectors);
369
		$this->scope->parent->children[] = $this->scope;
370
		$this->compileChildren($block->children, $this->scope);
371
372
		$this->scope = $this->scope->parent;
373
		$this->popEnv();
374
	}
375
376
	/**
377
	 * Recursively compiles a block.
378
	 *
379
	 * A block is analogous to a CSS block in most cases. A single SCSS document
380
	 * is encapsulated in a block when parsed, but it does not have parent tags
381
	 * so all of its children appear on the root level when compiled.
382
	 *
383
	 * Blocks are made up of selectors and children.
384
	 *
385
	 * The children of a block are just all the blocks that are defined within.
386
	 *
387
	 * Compiling the block involves pushing a fresh environment on the stack,
388
	 * and iterating through the props, compiling each one.
389
	 *
390
	 * @see scss::compileChild()
391
	 *
392
	 * @param \StdClass $block
393
	 */
394
	protected function compileBlock($block) {
395
		$env = $this->pushEnv($block);
396
397
		$env->selectors =
398
			array_map(array($this, "evalSelector"), $block->selectors);
399
400
		$out = $this->makeOutputBlock(null, $this->multiplySelectors($env));
401
		$this->scope->children[] = $out;
402
		$this->compileChildren($block->children, $out);
403
404
		$this->popEnv();
405
	}
406
407
	// joins together .classes and #ids
408
	protected function flattenSelectorSingle($single) {
409
		$joined = array();
410
		foreach ($single as $part) {
411
			if (empty($joined) ||
412
				!is_string($part) ||
413
				preg_match('/[\[.:#%]/', $part))
414
			{
415
				$joined[] = $part;
416
				continue;
417
			}
418
419
			if (is_array(end($joined))) {
420
				$joined[] = $part;
421
			} else {
422
				$joined[count($joined) - 1] .= $part;
423
			}
424
		}
425
426
		return $joined;
427
	}
428
429
	// replaces all the interpolates
430
	protected function evalSelector($selector) {
431
		return array_map(array($this, "evalSelectorPart"), $selector);
432
	}
433
434
	protected function evalSelectorPart($piece) {
435 View Code Duplication
		foreach ($piece as &$p) {
436
			if (!is_array($p)) continue;
437
438
			switch ($p[0]) {
439
			case "interpolate":
440
				$p = $this->compileValue($p);
441
				break;
442
			case "string":
443
				$p = $this->compileValue($p);
444
				break;
445
			}
446
		}
447
448
		return $this->flattenSelectorSingle($piece);
449
	}
450
451
	// compiles to string
452
	// self(&) should have been replaced by now
453
	protected function compileSelector($selector) {
454
		if (!is_array($selector)) return $selector; // media and the like
455
456
		return implode(" ", array_map(
457
			array($this, "compileSelectorPart"), $selector));
458
	}
459
460
	protected function compileSelectorPart($piece) {
461 View Code Duplication
		foreach ($piece as &$p) {
462
			if (!is_array($p)) continue;
463
464
			switch ($p[0]) {
465
			case "self":
466
				$p = "&";
467
				break;
468
			default:
469
				$p = $this->compileValue($p);
470
				break;
471
			}
472
		}
473
474
		return implode($piece);
475
	}
476
477
	protected function hasSelectorPlaceholder($selector)
478
	{
479
		if (!is_array($selector)) return false;
480
481
		foreach ($selector as $parts) {
482
			foreach ($parts as $part) {
483
				if ('%' == $part[0]) {
484
					return true;
485
				}
486
			}
487
		}
488
489
		return false;
490
	}
491
492
	protected function compileChildren($stms, $out) {
493
		foreach ($stms as $stm) {
494
			$ret = $this->compileChild($stm, $out);
495
			if (!is_null($ret)) return $ret;
496
		}
497
	}
498
499
	protected function compileMediaQuery($queryList) {
500
		$out = "@media";
501
		$first = true;
502
		foreach ($queryList as $query){
503
			$parts = array();
504
			foreach ($query as $q) {
505
				switch ($q[0]) {
506
					case "mediaType":
507
						$parts[] = implode(" ", array_map(array($this, "compileValue"), array_slice($q, 1)));
508
						break;
509
					case "mediaExp":
510
						if (isset($q[2])) {
511
							$parts[] = "(". $this->compileValue($q[1]) . $this->formatter->assignSeparator . $this->compileValue($q[2]) . ")";
512
						} else {
513
							$parts[] = "(" . $this->compileValue($q[1]) . ")";
514
						}
515
						break;
516
				}
517
			}
518
			if (!empty($parts)) {
519
				if ($first) {
520
					$first = false;
521
					$out .= " ";
522
				} else {
523
					$out .= $this->formatter->tagSeparator;
524
				}
525
				$out .= implode(" and ", $parts);
526
			}
527
		}
528
		return $out;
529
	}
530
531
	// returns true if the value was something that could be imported
532
	protected function compileImport($rawPath, $out) {
533
		if ($rawPath[0] == "string") {
534
			$path = $this->compileStringContent($rawPath);
535
			if ($path = $this->findImport($path)) {
536
				$this->importFile($path, $out);
537
				return true;
538
			}
539
			return false;
540
		}
541
		if ($rawPath[0] == "list") {
542
			// handle a list of strings
543
			if (count($rawPath[2]) == 0) return false;
544
			foreach ($rawPath[2] as $path) {
545
				if ($path[0] != "string") return false;
546
			}
547
548
			foreach ($rawPath[2] as $path) {
549
				$this->compileImport($path, $out);
550
			}
551
552
			return true;
553
		}
554
555
		return false;
556
	}
557
558
	// return a value to halt execution
559
	protected function compileChild($child, $out) {
560
		$this->sourcePos = isset($child[-1]) ? $child[-1] : -1;
0 ignored issues
show
Bug introduced by
The property sourcePos 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...
561
		$this->sourceParser = isset($child[-2]) ? $child[-2] : $this->parser;
0 ignored issues
show
Bug introduced by
The property sourceParser does not seem to exist. Did you mean parser?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
562
563
		switch ($child[0]) {
564 View Code Duplication
		case "import":
565
			list(,$rawPath) = $child;
566
			$rawPath = $this->reduce($rawPath);
567
			if (!$this->compileImport($rawPath, $out)) {
568
				$out->lines[] = "@import " . $this->compileValue($rawPath) . ";";
569
			}
570
			break;
571 View Code Duplication
		case "directive":
572
			list(, $directive) = $child;
573
			$s = "@" . $directive->name;
574
			if (!empty($directive->value)) {
575
				$s .= " " . $this->compileValue($directive->value);
576
			}
577
			$this->compileNestedBlock($directive, array($s));
578
			break;
579
		case "media":
580
			$this->compileMedia($child[1]);
581
			break;
582
		case "block":
583
			$this->compileBlock($child[1]);
584
			break;
585
		case "charset":
586
			$out->lines[] = "@charset ".$this->compileValue($child[1]).";";
587
			break;
588
		case "assign":
589
			list(,$name, $value) = $child;
590
			if ($name[0] == "var") {
591
				$isDefault = !empty($child[3]);
592
593
				if ($isDefault) {
594
					$existingValue = $this->get($name[1], true);
595
					$shouldSet = $existingValue === true || $existingValue == self::$null;
596
				}
597
598
				if (!$isDefault || $shouldSet) {
0 ignored issues
show
Bug introduced by
The variable $shouldSet does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
599
					$this->set($name[1], $this->reduce($value));
600
				}
601
				break;
602
			}
603
604
			// if the value reduces to null from something else then
605
			// the property should be discarded
606
			if ($value[0] != "null") {
607
				$value = $this->reduce($value);
608
				if ($value[0] == "null") {
609
					break;
610
				}
611
			}
612
613
			$compiledValue = $this->compileValue($value);
614
			$out->lines[] = $this->formatter->property(
0 ignored issues
show
Bug introduced by
The method property cannot be called on $this->formatter (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
615
				$this->compileValue($name),
616
				$compiledValue);
617
			break;
618
		case "comment":
619
			$out->lines[] = $child[1];
620
			break;
621
		case "mixin":
622
		case "function":
623
			list(,$block) = $child;
624
			$this->set(self::$namespaces[$block->type] . $block->name, $block);
625
			break;
626
		case "extend":
627
			list(, $selectors) = $child;
628
			foreach ($selectors as $sel) {
629
				// only use the first one
630
				$sel = current($this->evalSelector($sel));
631
				$this->pushExtends($sel, $out->selectors);
632
			}
633
			break;
634
		case "if":
635
			list(, $if) = $child;
636
			if ($this->isTruthy($this->reduce($if->cond, true))) {
637
				return $this->compileChildren($if->children, $out);
638
			} else {
639
				foreach ($if->cases as $case) {
640
					if ($case->type == "else" ||
641
						$case->type == "elseif" && $this->isTruthy($this->reduce($case->cond)))
642
					{
643
						return $this->compileChildren($case->children, $out);
644
					}
645
				}
646
			}
647
			break;
648
		case "return":
649
			return $this->reduce($child[1], true);
650
		case "each":
651
			list(,$each) = $child;
652
			$list = $this->coerceList($this->reduce($each->list));
653
			foreach ($list[2] as $item) {
654
				$this->pushEnv();
655
				$this->set($each->var, $item);
656
				// TODO: allow return from here
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
657
				$this->compileChildren($each->children, $out);
658
				$this->popEnv();
659
			}
660
			break;
661
		case "while":
662
			list(,$while) = $child;
663
			while ($this->isTruthy($this->reduce($while->cond, true))) {
664
				$ret = $this->compileChildren($while->children, $out);
665
				if ($ret) return $ret;
666
			}
667
			break;
668
		case "for":
669
			list(,$for) = $child;
670
			$start = $this->reduce($for->start, true);
671
			$start = $start[1];
672
			$end = $this->reduce($for->end, true);
673
			$end = $end[1];
674
			$d = $start < $end ? 1 : -1;
675
676
			while (true) {
677
				if ((!$for->until && $start - $d == $end) ||
678
					($for->until && $start == $end))
679
				{
680
					break;
681
				}
682
683
				$this->set($for->var, array("number", $start, ""));
684
				$start += $d;
685
686
				$ret = $this->compileChildren($for->children, $out);
687
				if ($ret) return $ret;
688
			}
689
690
			break;
691
		case "nestedprop":
692
			list(,$prop) = $child;
693
			$prefixed = array();
694
			$prefix = $this->compileValue($prop->prefix) . "-";
695
			foreach ($prop->children as $child) {
696
				if ($child[0] == "assign") {
697
					array_unshift($child[1][2], $prefix);
698
				}
699
				if ($child[0] == "nestedprop") {
700
					array_unshift($child[1]->prefix[2], $prefix);
701
				}
702
				$prefixed[] = $child;
703
			}
704
			$this->compileChildren($prefixed, $out);
705
			break;
706
		case "include": // including a mixin
707
			list(,$name, $argValues, $content) = $child;
708
			$mixin = $this->get(self::$namespaces["mixin"] . $name, false);
709
			if (!$mixin) {
710
				$this->throwError("Undefined mixin $name");
711
			}
712
713
			$callingScope = $this->env;
714
715
			// push scope, apply args
716
			$this->pushEnv();
717
			if ($this->env->depth > 0) {
718
				$this->env->depth--;
719
			}
720
721
			if (!is_null($content)) {
722
				$content->scope = $callingScope;
723
				$this->setRaw(self::$namespaces["special"] . "content", $content);
724
			}
725
726
			if (!is_null($mixin->args)) {
727
				$this->applyArguments($mixin->args, $argValues);
728
			}
729
730
			foreach ($mixin->children as $child) {
731
				$this->compileChild($child, $out);
732
			}
733
734
			$this->popEnv();
735
736
			break;
737
		case "mixin_content":
738
			$content = $this->get(self::$namespaces["special"] . "content");
739
			if (is_null($content)) {
740
				$this->throwError("Expected @content inside of mixin");
741
			}
742
743
			$strongTypes = array('include', 'block', 'for', 'while');
744
			foreach ($content->children as $child) {
745
				$this->storeEnv = (in_array($child[0], $strongTypes))
0 ignored issues
show
Bug introduced by
The property storeEnv 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...
746
					? null
747
					: $content->scope;
748
749
				$this->compileChild($child, $out);
750
			}
751
752
			unset($this->storeEnv);
753
			break;
754
		case "debug":
755
			list(,$value, $pos) = $child;
756
			$line = $this->parser->getLineNo($pos);
757
			$value = $this->compileValue($this->reduce($value, true));
758
			fwrite(STDERR, "Line $line DEBUG: $value\n");
759
			break;
760
		default:
761
			$this->throwError("unknown child type: $child[0]");
762
		}
763
	}
764
765
	protected function expToString($exp) {
766
		list(, $op, $left, $right, $inParens, $whiteLeft, $whiteRight) = $exp;
0 ignored issues
show
Unused Code introduced by
The assignment to $inParens is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
767
		$content = array($this->reduce($left));
768
		if ($whiteLeft) $content[] = " ";
769
		$content[] = $op;
770
		if ($whiteRight) $content[] = " ";
771
		$content[] = $this->reduce($right);
772
		return array("string", "", $content);
773
	}
774
775
	protected function isTruthy($value) {
776
		return $value != self::$false && $value != self::$null;
777
	}
778
779
	// should $value cause its operand to eval
780
	protected function shouldEval($value) {
781
		switch ($value[0]) {
782
		case "exp":
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
783
			if ($value[1] == "/") {
784
				return $this->shouldEval($value[2], $value[3]);
0 ignored issues
show
Unused Code introduced by
The call to scssc::shouldEval() has too many arguments starting with $value[3].

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...
785
			}
786
		case "var":
787
		case "fncall":
788
			return true;
789
		}
790
		return false;
791
	}
792
793
	protected function reduce($value, $inExp = false) {
794
		list($type) = $value;
795
		switch ($type) {
796
			case "exp":
797
				list(, $op, $left, $right, $inParens) = $value;
798
				$opName = isset(self::$operatorNames[$op]) ? self::$operatorNames[$op] : $op;
799
800
				$inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right);
801
802
				$left = $this->reduce($left, true);
803
				$right = $this->reduce($right, true);
804
805
				// only do division in special cases
806
				if ($opName == "div" && !$inParens && !$inExp) {
807
					if ($left[0] != "color" && $right[0] != "color") {
808
						return $this->expToString($value);
809
					}
810
				}
811
812
				$left = $this->coerceForExpression($left);
813
				$right = $this->coerceForExpression($right);
814
815
				$ltype = $left[0];
816
				$rtype = $right[0];
817
818
				// this tries:
819
				// 1. op_[op name]_[left type]_[right type]
820
				// 2. op_[left type]_[right type] (passing the op as first arg
821
				// 3. op_[op name]
822
				$fn = "op_${opName}_${ltype}_${rtype}";
823
				if (is_callable(array($this, $fn)) ||
824
					(($fn = "op_${ltype}_${rtype}") &&
825
						is_callable(array($this, $fn)) &&
826
						$passOp = true) ||
827
					(($fn = "op_${opName}") &&
828
						is_callable(array($this, $fn)) &&
829
						$genOp = true))
830
				{
831
					$unitChange = false;
832
					if (!isset($genOp) &&
833
						$left[0] == "number" && $right[0] == "number")
834
					{
835
						if ($opName == "mod" && $right[2] != "") {
836
							$this->throwError("Cannot modulo by a number with units: $right[1]$right[2].");
837
						}
838
839
						$unitChange = true;
840
						$emptyUnit = $left[2] == "" || $right[2] == "";
841
						$targetUnit = "" != $left[2] ? $left[2] : $right[2];
842
843
						if ($opName != "mul") {
844
							$left[2] = "" != $left[2] ? $left[2] : $targetUnit;
845
							$right[2] = "" != $right[2] ? $right[2] : $targetUnit;
846
						}
847
848
						if ($opName != "mod") {
849
							$left = $this->normalizeNumber($left);
850
							$right = $this->normalizeNumber($right);
851
						}
852
853
						if ($opName == "div" && !$emptyUnit && $left[2] == $right[2]) {
854
							$targetUnit = "";
855
						}
856
857
						if ($opName == "mul") {
858
							$left[2] = "" != $left[2] ? $left[2] : $right[2];
859
							$right[2] = "" != $right[2] ? $right[2] : $left[2];
860
						} elseif ($opName == "div" && $left[2] == $right[2]) {
861
							$left[2] = "";
862
							$right[2] = "";
863
						}
864
					}
865
866
					$shouldEval = $inParens || $inExp;
867
					if (isset($passOp)) {
868
						$out = $this->$fn($op, $left, $right, $shouldEval);
869
					} else {
870
						$out = $this->$fn($left, $right, $shouldEval);
871
					}
872
873
					if (!is_null($out)) {
874
						if ($unitChange && $out[0] == "number") {
875
							$out = $this->coerceUnit($out, $targetUnit);
0 ignored issues
show
Bug introduced by
The variable $targetUnit does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
876
						}
877
						return $out;
878
					}
879
				}
880
881
				return $this->expToString($value);
882
			case "unary":
883
				list(, $op, $exp, $inParens) = $value;
884
				$inExp = $inExp || $this->shouldEval($exp);
885
886
				$exp = $this->reduce($exp);
887 View Code Duplication
				if ($exp[0] == "number") {
888
					switch ($op) {
889
					case "+":
890
						return $exp;
891
					case "-":
892
						$exp[1] *= -1;
893
						return $exp;
894
					}
895
				}
896
897
				if ($op == "not") {
898
					if ($inExp || $inParens) {
899
						if ($exp == self::$false) {
900
							return self::$true;
901
						} else {
902
							return self::$false;
903
						}
904
					} else {
905
						$op = $op . " ";
906
					}
907
				}
908
909
				return array("string", "", array($op, $exp));
910
			case "var":
911
				list(, $name) = $value;
912
				return $this->reduce($this->get($name));
913
			case "list":
914
				foreach ($value[2] as &$item) {
915
					$item = $this->reduce($item);
916
				}
917
				return $value;
918
			case "string":
919
				foreach ($value[2] as &$item) {
920
					if (is_array($item)) {
921
						$item = $this->reduce($item);
922
					}
923
				}
924
				return $value;
925
			case "interpolate":
926
				$value[1] = $this->reduce($value[1]);
927
				return $value;
928
			case "fncall":
929
				list(,$name, $argValues) = $value;
930
931
				// user defined function?
932
				$func = $this->get(self::$namespaces["function"] . $name, false);
933
				if ($func) {
934
					$this->pushEnv();
935
936
					// set the args
937
					if (isset($func->args)) {
938
						$this->applyArguments($func->args, $argValues);
939
					}
940
941
					// throw away lines and children
942
					$tmp = (object)array(
943
						"lines" => array(),
944
						"children" => array()
945
					);
946
					$ret = $this->compileChildren($func->children, $tmp);
947
					$this->popEnv();
948
949
					return is_null($ret) ? self::$defaultValue : $ret;
950
				}
951
952
				// built in function
953
				if ($this->callBuiltin($name, $argValues, $returnValue)) {
954
					return $returnValue;
955
				}
956
957
				// need to flatten the arguments into a list
958
				$listArgs = array();
959
				foreach ((array)$argValues as $arg) {
960
					if (empty($arg[0])) {
961
						$listArgs[] = $this->reduce($arg[1]);
962
					}
963
				}
964
				return array("function", $name, array("list", ",", $listArgs));
965
			default:
966
				return $value;
967
		}
968
	}
969
970
	public function normalizeValue($value) {
971
		$value = $this->coerceForExpression($this->reduce($value));
972
		list($type) = $value;
973
974
		switch ($type) {
975
		case "list":
976
			$value = $this->extractInterpolation($value);
977
			if ($value[0] != "list") {
978
				return array("keyword", $this->compileValue($value));
979
			}
980
			foreach ($value[2] as $key => $item) {
981
				$value[2][$key] = $this->normalizeValue($item);
982
			}
983
			return $value;
984
		case "number":
985
			return $this->normalizeNumber($value);
986
		default:
987
			return $value;
988
		}
989
	}
990
991
	// just does physical lengths for now
992
	protected function normalizeNumber($number) {
993
		list(, $value, $unit) = $number;
994
		if (isset(self::$unitTable["in"][$unit])) {
995
			$conv = self::$unitTable["in"][$unit];
996
			return array("number", $value / $conv, "in");
997
		}
998
		return $number;
999
	}
1000
1001
	// $number should be normalized
1002
	protected function coerceUnit($number, $unit) {
1003
		list(, $value, $baseUnit) = $number;
1004
		if (isset(self::$unitTable[$baseUnit][$unit])) {
1005
			$value = $value * self::$unitTable[$baseUnit][$unit];
1006
		}
1007
1008
		return array("number", $value, $unit);
1009
	}
1010
1011
	protected function op_add_number_number($left, $right) {
1012
		return array("number", $left[1] + $right[1], $left[2]);
1013
	}
1014
1015
	protected function op_mul_number_number($left, $right) {
1016
		return array("number", $left[1] * $right[1], $left[2]);
1017
	}
1018
1019
	protected function op_sub_number_number($left, $right) {
1020
		return array("number", $left[1] - $right[1], $left[2]);
1021
	}
1022
1023
	protected function op_div_number_number($left, $right) {
1024
		return array("number", $left[1] / $right[1], $left[2]);
1025
	}
1026
1027
	protected function op_mod_number_number($left, $right) {
1028
		return array("number", $left[1] % $right[1], $left[2]);
1029
	}
1030
1031
	// adding strings
1032
	protected function op_add($left, $right) {
1033 View Code Duplication
		if ($strLeft = $this->coerceString($left)) {
1034
			if ($right[0] == "string") {
1035
				$right[1] = "";
1036
			}
1037
			$strLeft[2][] = $right;
1038
			return $strLeft;
1039
		}
1040
1041 View Code Duplication
		if ($strRight = $this->coerceString($right)) {
1042
			if ($left[0] == "string") {
1043
				$left[1] = "";
1044
			}
1045
			array_unshift($strRight[2], $left);
1046
			return $strRight;
1047
		}
1048
	}
1049
1050
	protected function op_and($left, $right, $shouldEval) {
1051
		if (!$shouldEval) return;
1052
		if ($left != self::$false) return $right;
1053
		return $left;
1054
	}
1055
1056
	protected function op_or($left, $right, $shouldEval) {
1057
		if (!$shouldEval) return;
1058
		if ($left != self::$false) return $left;
1059
		return $right;
1060
	}
1061
1062
	protected function op_color_color($op, $left, $right) {
1063
		$out = array('color');
1064
		foreach (range(1, 3) as $i) {
1065
			$lval = isset($left[$i]) ? $left[$i] : 0;
1066
			$rval = isset($right[$i]) ? $right[$i] : 0;
1067
			switch ($op) {
1068
			case '+':
1069
				$out[] = $lval + $rval;
1070
				break;
1071
			case '-':
1072
				$out[] = $lval - $rval;
1073
				break;
1074
			case '*':
1075
				$out[] = $lval * $rval;
1076
				break;
1077
			case '%':
1078
				$out[] = $lval % $rval;
1079
				break;
1080 View Code Duplication
			case '/':
1081
				if ($rval == 0) {
1082
					$this->throwError("color: Can't divide by zero");
1083
				}
1084
				$out[] = $lval / $rval;
1085
				break;
1086
			case "==":
1087
				return $this->op_eq($left, $right);
1088
			case "!=":
1089
				return $this->op_neq($left, $right);
1090
			default:
1091
				$this->throwError("color: unknown op $op");
1092
			}
1093
		}
1094
1095
		if (isset($left[4])) $out[4] = $left[4];
1096
		elseif (isset($right[4])) $out[4] = $right[4];
1097
1098
		return $this->fixColor($out);
1099
	}
1100
1101
	protected function op_color_number($op, $left, $right) {
1102
		$value = $right[1];
1103
		return $this->op_color_color($op, $left,
1104
			array("color", $value, $value, $value));
1105
	}
1106
1107
	protected function op_number_color($op, $left, $right) {
1108
		$value = $left[1];
1109
		return $this->op_color_color($op,
1110
			array("color", $value, $value, $value), $right);
1111
	}
1112
1113
	protected function op_eq($left, $right) {
1114
		if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
1115
			$lStr[1] = "";
1116
			$rStr[1] = "";
1117
			return $this->toBool($this->compileValue($lStr) == $this->compileValue($rStr));
1118
		}
1119
1120
		return $this->toBool($left == $right);
1121
	}
1122
1123
	protected function op_neq($left, $right) {
1124
		return $this->toBool($left != $right);
1125
	}
1126
1127
	protected function op_gte_number_number($left, $right) {
1128
		return $this->toBool($left[1] >= $right[1]);
1129
	}
1130
1131
	protected function op_gt_number_number($left, $right) {
1132
		return $this->toBool($left[1] > $right[1]);
1133
	}
1134
1135
	protected function op_lte_number_number($left, $right) {
1136
		return $this->toBool($left[1] <= $right[1]);
1137
	}
1138
1139
	protected function op_lt_number_number($left, $right) {
1140
		return $this->toBool($left[1] < $right[1]);
1141
	}
1142
1143
	public function toBool($thing) {
1144
		return $thing ? self::$true : self::$false;
1145
	}
1146
1147
	/**
1148
	 * Compiles a primitive value into a CSS property value.
1149
	 *
1150
	 * Values in scssphp are typed by being wrapped in arrays, their format is
1151
	 * typically:
1152
	 *
1153
	 *     array(type, contents [, additional_contents]*)
1154
	 *
1155
	 * The input is expected to be reduced. This function will not work on
1156
	 * things like expressions and variables.
1157
	 *
1158
	 * @param array $value
1159
	 */
1160
	protected function compileValue($value) {
1161
		$value = $this->reduce($value);
1162
1163
		list($type) = $value;
1164
		switch ($type) {
1165
		case "keyword":
1166
			return $value[1];
1167
		case "color":
1168
			// [1] - red component (either number for a %)
1169
			// [2] - green component
1170
			// [3] - blue component
1171
			// [4] - optional alpha component
1172
			list(, $r, $g, $b) = $value;
1173
1174
			$r = round($r);
1175
			$g = round($g);
1176
			$b = round($b);
1177
1178 View Code Duplication
			if (count($value) == 5 && $value[4] != 1) { // rgba
1179
				return 'rgba('.$r.', '.$g.', '.$b.', '.$value[4].')';
1180
			}
1181
1182
			$h = sprintf("#%02x%02x%02x", $r, $g, $b);
1183
1184
			// Converting hex color to short notation (e.g. #003399 to #039)
1185 View Code Duplication
			if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
1186
				$h = '#' . $h[1] . $h[3] . $h[5];
1187
			}
1188
1189
			return $h;
1190
		case "number":
1191
			return round($value[1], $this->numberPrecision) . $value[2];
1192
		case "string":
1193
			return $value[1] . $this->compileStringContent($value) . $value[1];
1194
		case "function":
1195
			$args = !empty($value[2]) ? $this->compileValue($value[2]) : "";
1196
			return "$value[1]($args)";
1197
		case "list":
1198
			$value = $this->extractInterpolation($value);
1199
			if ($value[0] != "list") return $this->compileValue($value);
1200
1201
			list(, $delim, $items) = $value;
1202
1203
			$filtered = array();
1204
			foreach ($items as $item) {
1205
				if ($item[0] == "null") continue;
1206
				$filtered[] = $this->compileValue($item);
1207
			}
1208
1209
			return implode("$delim ", $filtered);
1210
		case "interpolated": # node created by extractInterpolation
1211
			list(, $interpolate, $left, $right) = $value;
1212
			list(,, $whiteLeft, $whiteRight) = $interpolate;
1213
1214
			$left = count($left[2]) > 0 ?
1215
				$this->compileValue($left).$whiteLeft : "";
1216
1217
			$right = count($right[2]) > 0 ?
1218
				$whiteRight.$this->compileValue($right) : "";
1219
1220
			return $left.$this->compileValue($interpolate).$right;
1221
1222
		case "interpolate": # raw parse node
1223
			list(, $exp) = $value;
1224
1225
			// strip quotes if it's a string
1226
			$reduced = $this->reduce($exp);
1227
			switch ($reduced[0]) {
1228
				case "string":
1229
					$reduced = array("keyword",
1230
						$this->compileStringContent($reduced));
1231
					break;
1232
				case "null":
1233
					$reduced = array("keyword", "");
1234
			}
1235
1236
			return $this->compileValue($reduced);
1237
		case "null":
1238
			return "null";
1239
		default:
1240
			$this->throwError("unknown value type: $type");
1241
		}
1242
	}
1243
1244
	protected function compileStringContent($string) {
1245
		$parts = array();
1246
		foreach ($string[2] as $part) {
1247
			if (is_array($part)) {
1248
				$parts[] = $this->compileValue($part);
1249
			} else {
1250
				$parts[] = $part;
1251
			}
1252
		}
1253
1254
		return implode($parts);
1255
	}
1256
1257
	// doesn't need to be recursive, compileValue will handle that
1258
	protected function extractInterpolation($list) {
1259
		$items = $list[2];
1260
		foreach ($items as $i => $item) {
1261
			if ($item[0] == "interpolate") {
1262
				$before = array("list", $list[1], array_slice($items, 0, $i));
1263
				$after = array("list", $list[1], array_slice($items, $i + 1));
1264
				return array("interpolated", $item, $before, $after);
1265
			}
1266
		}
1267
		return $list;
1268
	}
1269
1270
	// find the final set of selectors
1271
	protected function multiplySelectors($env) {
1272
		$envs = array();
1273
		while (null !== $env) {
1274
			if (!empty($env->selectors)) {
1275
				$envs[] = $env;
1276
			}
1277
			$env = $env->parent;
1278
		};
1279
1280
		$selectors = array();
1281
		$parentSelectors = array(array());
1282
		while ($env = array_pop($envs)) {
1283
			$selectors = array();
1284
			foreach ($env->selectors as $selector) {
1285
				foreach ($parentSelectors as $parent) {
1286
					$selectors[] = $this->joinSelectors($parent, $selector);
1287
				}
1288
			}
1289
			$parentSelectors = $selectors;
1290
		}
1291
1292
		return $selectors;
1293
	}
1294
1295
	// looks for & to replace, or append parent before child
1296
	protected function joinSelectors($parent, $child) {
1297
		$setSelf = false;
1298
		$out = array();
1299
		foreach ($child as $part) {
1300
			$newPart = array();
1301
			foreach ($part as $p) {
1302
				if ($p == self::$selfSelector) {
1303
					$setSelf = true;
1304
					foreach ($parent as $i => $parentPart) {
1305
						if ($i > 0) {
1306
							$out[] = $newPart;
1307
							$newPart = array();
1308
						}
1309
1310
						foreach ($parentPart as $pp) {
1311
							$newPart[] = $pp;
1312
						}
1313
					}
1314
				} else {
1315
					$newPart[] = $p;
1316
				}
1317
			}
1318
1319
			$out[] = $newPart;
1320
		}
1321
1322
		return $setSelf ? $out : array_merge($parent, $child);
1323
	}
1324
1325 View Code Duplication
	protected function multiplyMedia($env, $childQueries = null) {
1326
		if (is_null($env) ||
1327
			!empty($env->block->type) && $env->block->type != "media")
1328
		{
1329
			return $childQueries;
1330
		}
1331
1332
		// plain old block, skip
1333
		if (empty($env->block->type)) {
1334
			return $this->multiplyMedia($env->parent, $childQueries);
1335
		}
1336
1337
		$parentQueries = $env->block->queryList;
1338
		if ($childQueries == null) {
1339
			$childQueries = $parentQueries;
1340
		} else {
1341
			$originalQueries = $childQueries;
1342
			$childQueries = array();
1343
1344
			foreach ($parentQueries as $parentQuery){
1345
				foreach ($originalQueries as $childQuery) {
1346
					$childQueries []= array_merge($parentQuery, $childQuery);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 0 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
1347
				}
1348
			}
1349
		}
1350
1351
		return $this->multiplyMedia($env->parent, $childQueries);
1352
	}
1353
1354
	// convert something to list
1355
	protected function coerceList($item, $delim = ",") {
1356
		if (!is_null($item) && $item[0] == "list") {
1357
			return $item;
1358
		}
1359
1360
		return array("list", $delim, is_null($item) ? array(): array($item));
1361
	}
1362
1363
	protected function applyArguments($argDef, $argValues) {
1364
		$hasVariable = false;
1365
		$args = array();
1366
		foreach ($argDef as $i => $arg) {
1367
			list($name, $default, $isVariable) = $argDef[$i];
1368
			$args[$name] = array($i, $name, $default, $isVariable);
1369
			$hasVariable |= $isVariable;
1370
		}
1371
1372
		$keywordArgs = array();
1373
		$deferredKeywordArgs = array();
1374
		$remaining = array();
1375
		// assign the keyword args
1376
		foreach ((array) $argValues as $arg) {
1377
			if (!empty($arg[0])) {
1378
				if (!isset($args[$arg[0][1]])) {
1379
					if ($hasVariable) {
1380
						$deferredKeywordArgs[$arg[0][1]] = $arg[1];
1381
					} else {
1382
						$this->throwError("Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
1383
					}
1384
				} elseif ($args[$arg[0][1]][0] < count($remaining)) {
1385
					$this->throwError("The argument $%s was passed both by position and by name.", $arg[0][1]);
1386
				} else {
1387
					$keywordArgs[$arg[0][1]] = $arg[1];
1388
				}
1389
			} elseif (count($keywordArgs)) {
1390
				$this->throwError('Positional arguments must come before keyword arguments.');
1391
			} elseif ($arg[2] == true) {
1392
				$val = $this->reduce($arg[1], true);
1393
				if ($val[0] == "list") {
1394
					foreach ($val[2] as $name => $item) {
1395
						if (!is_numeric($name)) {
1396
							$keywordArgs[$name] = $item;
1397
						} else {
1398
							$remaining[] = $item;
1399
						}
1400
					}
1401
				} else {
1402
					$remaining[] = $val;
1403
				}
1404
			} else {
1405
				$remaining[] = $arg[1];
1406
			}
1407
		}
1408
1409
		foreach ($args as $arg) {
1410
			list($i, $name, $default, $isVariable) = $arg;
1411
			if ($isVariable) {
1412
				$val = array("list", ",", array());
1413
				for ($count = count($remaining); $i < $count; $i++) {
1414
					$val[2][] = $remaining[$i];
1415
				}
1416
				foreach ($deferredKeywordArgs as $itemName => $item) {
1417
					$val[2][$itemName] = $item;
1418
				}
1419
			} elseif (isset($remaining[$i])) {
1420
				$val = $remaining[$i];
1421
			} elseif (isset($keywordArgs[$name])) {
1422
				$val = $keywordArgs[$name];
1423
			} elseif (!empty($default)) {
1424
				$val = $default;
1425
			} else {
1426
				$this->throwError("Missing argument $name");
1427
			}
1428
1429
			$this->set($name, $this->reduce($val, true), true);
0 ignored issues
show
Bug introduced by
The variable $val does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1430
		}
1431
	}
1432
1433
	protected function pushEnv($block=null) {
1434
		$env = new stdClass;
1435
		$env->parent = $this->env;
1436
		$env->store = array();
1437
		$env->block = $block;
1438
		$env->depth = isset($this->env->depth) ? $this->env->depth + 1 : 0;
1439
1440
		$this->env = $env;
1441
		return $env;
1442
	}
1443
1444
	protected function normalizeName($name) {
1445
		return str_replace("-", "_", $name);
1446
	}
1447
1448
	protected function getStoreEnv() {
1449
		return isset($this->storeEnv) ? $this->storeEnv : $this->env;
1450
	}
1451
1452
	protected function set($name, $value, $shadow=false) {
1453
		$name = $this->normalizeName($name);
1454
1455
		if ($shadow) {
1456
			$this->setRaw($name, $value);
1457
		} else {
1458
			$this->setExisting($name, $value);
1459
		}
1460
	}
1461
1462
	protected function setExisting($name, $value, $env = null) {
1463
		if (is_null($env)) $env = $this->getStoreEnv();
1464
1465
		if (isset($env->store[$name]) || is_null($env->parent)) {
1466
			$env->store[$name] = $value;
1467
		} else {
1468
			$this->setExisting($name, $value, $env->parent);
1469
		}
1470
	}
1471
1472
	protected function setRaw($name, $value) {
1473
		$env = $this->getStoreEnv();
1474
		$env->store[$name] = $value;
1475
	}
1476
1477
	public function get($name, $defaultValue = null, $env = null) {
1478
		$name = $this->normalizeName($name);
1479
1480
		if (is_null($env)) $env = $this->getStoreEnv();
1481
		if (is_null($defaultValue)) $defaultValue = self::$defaultValue;
1482
1483 View Code Duplication
		if (isset($env->store[$name])) {
1484
			return $env->store[$name];
1485
		} elseif (isset($env->parent)) {
1486
			return $this->get($name, $defaultValue, $env->parent);
1487
		}
1488
1489
		return $defaultValue; // found nothing
1490
	}
1491
1492
	protected function popEnv() {
1493
		$env = $this->env;
1494
		$this->env = $this->env->parent;
1495
		return $env;
1496
	}
1497
1498
	public function getParsedFiles() {
1499
		return $this->parsedFiles;
1500
	}
1501
1502
	public function addImportPath($path) {
1503
		$this->importPaths[] = $path;
1504
	}
1505
1506
	public function setImportPaths($path) {
1507
		$this->importPaths = (array)$path;
1508
	}
1509
1510
	public function setNumberPrecision($numberPrecision) {
1511
		$this->numberPrecision = $numberPrecision;
1512
	}
1513
1514
	public function setFormatter($formatterName) {
1515
		$this->formatter = $formatterName;
1516
	}
1517
1518
	public function registerFunction($name, $func) {
1519
		$this->userFunctions[$this->normalizeName($name)] = $func;
1520
	}
1521
1522
	public function unregisterFunction($name) {
1523
		unset($this->userFunctions[$this->normalizeName($name)]);
1524
	}
1525
1526
	protected function importFile($path, $out) {
1527
		// see if tree is cached
1528
		$realPath = realpath($path);
1529
		if (isset($this->importCache[$realPath])) {
1530
			$tree = $this->importCache[$realPath];
1531
		} else {
1532
			$code = file_get_contents($path);
1533
			$parser = new scss_parser($path, false);
1534
			$tree = $parser->parse($code);
1535
			$this->parsedFiles[] = $path;
1536
1537
			$this->importCache[$realPath] = $tree;
1538
		}
1539
1540
		$pi = pathinfo($path);
1541
		array_unshift($this->importPaths, $pi['dirname']);
1542
		$this->compileChildren($tree->children, $out);
1543
		array_shift($this->importPaths);
1544
	}
1545
1546
	// results the file path for an import url if it exists
1547
	public function findImport($url) {
1548
		$urls = array();
1549
1550
		// for "normal" scss imports (ignore vanilla css and external requests)
1551
		if (!preg_match('/\.css|^http:\/\/$/', $url)) {
1552
			// try both normal and the _partial filename
1553
			$urls = array($url, preg_replace('/[^\/]+$/', '_\0', $url));
1554
		}
1555
1556
		foreach ($this->importPaths as $dir) {
1557
			if (is_string($dir)) {
1558
				// check urls for normal import paths
1559
				foreach ($urls as $full) {
1560
					$full = $dir .
1561
						(!empty($dir) && substr($dir, -1) != '/' ? '/' : '') .
1562
						$full;
1563
1564
					if ($this->fileExists($file = $full.'.scss') ||
1565
						$this->fileExists($file = $full))
1566
					{
1567
						return $file;
1568
					}
1569
				}
1570
			} else {
1571
				// check custom callback for import path
1572
				$file = call_user_func($dir,$url,$this);
1573
				if ($file !== null) {
1574
					return $file;
1575
				}
1576
			}
1577
		}
1578
1579
		return null;
1580
	}
1581
1582
	protected function fileExists($name) {
1583
		return is_file($name);
1584
	}
1585
1586
	protected function callBuiltin($name, $args, &$returnValue) {
1587
		// try a lib function
1588
		$name = $this->normalizeName($name);
1589
		$libName = "lib_".$name;
1590
		$f = array($this, $libName);
1591
		$prototype = isset(self::$$libName) ? self::$$libName : null;
1592
1593
		if (is_callable($f)) {
1594
			$sorted = $this->sortArgs($prototype, $args);
1595
			foreach ($sorted as &$val) {
1596
				$val = $this->reduce($val, true);
1597
			}
1598
			$returnValue = call_user_func($f, $sorted, $this);
1599
		} elseif (isset($this->userFunctions[$name])) {
1600
			// see if we can find a user function
1601
			$fn = $this->userFunctions[$name];
1602
1603
			foreach ($args as &$val) {
1604
				$val = $this->reduce($val[1], true);
1605
			}
1606
1607
			$returnValue = call_user_func($fn, $args, $this);
1608
		}
1609
1610
		if (isset($returnValue)) {
1611
			// coerce a php value into a scss one
1612
			if (is_numeric($returnValue)) {
1613
				$returnValue = array('number', $returnValue, "");
1614
			} elseif (is_bool($returnValue)) {
1615
				$returnValue = $returnValue ? self::$true : self::$false;
1616
			} elseif (!is_array($returnValue)) {
1617
				$returnValue = array('keyword', $returnValue);
1618
			}
1619
1620
			return true;
1621
		}
1622
1623
		return false;
1624
	}
1625
1626
	// sorts any keyword arguments
1627
	// TODO: merge with apply arguments
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1628
	protected function sortArgs($prototype, $args) {
1629
		$keyArgs = array();
1630
		$posArgs = array();
1631
1632
		foreach ($args as $arg) {
1633
			list($key, $value) = $arg;
1634
			$key = $key[1];
1635
			if (empty($key)) {
1636
				$posArgs[] = $value;
1637
			} else {
1638
				$keyArgs[$key] = $value;
1639
			}
1640
		}
1641
1642
		if (is_null($prototype)) return $posArgs;
1643
1644
		$finalArgs = array();
1645
		foreach ($prototype as $i => $names) {
1646
			if (isset($posArgs[$i])) {
1647
				$finalArgs[] = $posArgs[$i];
1648
				continue;
1649
			}
1650
1651
			$set = false;
1652
			foreach ((array)$names as $name) {
1653
				if (isset($keyArgs[$name])) {
1654
					$finalArgs[] = $keyArgs[$name];
1655
					$set = true;
1656
					break;
1657
				}
1658
			}
1659
1660
			if (!$set) {
1661
				$finalArgs[] = null;
1662
			}
1663
		}
1664
1665
		return $finalArgs;
1666
	}
1667
1668
	protected function coerceForExpression($value) {
1669
		if ($color = $this->coerceColor($value)) {
1670
			return $color;
1671
		}
1672
1673
		return $value;
1674
	}
1675
1676
	protected function coerceColor($value) {
1677
		switch ($value[0]) {
1678
		case "color": return $value;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1679
		case "keyword":
1680
			$name = $value[1];
1681
			if (isset(self::$cssColors[$name])) {
1682
				@list($r, $g, $b, $a) = explode(',', self::$cssColors[$name]);
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...
1683
				return isset($a)
1684
					? array('color', (int) $r, (int) $g, (int) $b, (int) $a)
1685
					: array('color', (int) $r, (int) $g, (int) $b);
1686
			}
1687
			return null;
1688
		}
1689
1690
		return null;
1691
	}
1692
1693 View Code Duplication
	protected function coerceString($value) {
1694
		switch ($value[0]) {
1695
		case "string":
1696
			return $value;
1697
		case "keyword":
1698
			return array("string", "", array($value[1]));
1699
		}
1700
		return null;
1701
	}
1702
1703
	public function assertList($value) {
1704
		if ($value[0] != "list")
1705
			$this->throwError("expecting list");
1706
		return $value;
1707
	}
1708
1709
	public function assertColor($value) {
1710
		if ($color = $this->coerceColor($value)) return $color;
1711
		$this->throwError("expecting color");
1712
	}
1713
1714
	public function assertNumber($value) {
1715
		if ($value[0] != "number")
1716
			$this->throwError("expecting number");
1717
		return $value[1];
1718
	}
1719
1720
	protected function coercePercent($value) {
1721
		if ($value[0] == "number") {
1722
			if ($value[2] == "%") {
1723
				return $value[1] / 100;
1724
			}
1725
			return $value[1];
1726
		}
1727
		return 0;
1728
	}
1729
1730
	// make sure a color's components don't go out of bounds
1731 View Code Duplication
	protected function fixColor($c) {
1732
		foreach (range(1, 3) as $i) {
1733
			if ($c[$i] < 0) $c[$i] = 0;
1734
			if ($c[$i] > 255) $c[$i] = 255;
1735
		}
1736
1737
		return $c;
1738
	}
1739
1740
	public function toHSL($red, $green, $blue) {
1741
		$r = $red / 255;
1742
		$g = $green / 255;
1743
		$b = $blue / 255;
1744
1745
		$min = min($r, $g, $b);
1746
		$max = max($r, $g, $b);
1747
		$d = $max - $min;
1748
		$l = ($min + $max) / 2;
1749
1750
		if ($min == $max) {
1751
			$s = $h = 0;
1752
		} else {
1753
			if ($l < 0.5)
1754
				$s = $d / (2 * $l);
1755
			else
1756
				$s = $d / (2 - 2 * $l);
1757
1758
			if ($r == $max)
1759
				$h = 60 * ($g - $b) / $d;
1760
			elseif ($g == $max)
1761
				$h = 60 * ($b - $r) / $d + 120;
1762
			elseif ($b == $max)
1763
				$h = 60 * ($r - $g) / $d + 240;
1764
		}
1765
1766
		return array('hsl', fmod($h, 360), $s * 100, $l * 100);
0 ignored issues
show
Bug introduced by
The variable $h does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1767
	}
1768
1769
	public function hueToRGB($m1, $m2, $h) {
1770
		if ($h < 0)
1771
			$h += 1;
1772
		elseif ($h > 1)
1773
			$h -= 1;
1774
1775 View Code Duplication
		if ($h * 6 < 1)
1776
			return $m1 + ($m2 - $m1) * $h * 6;
1777
1778
		if ($h * 2 < 1)
1779
			return $m2;
1780
1781 View Code Duplication
		if ($h * 3 < 2)
1782
			return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
1783
1784
		return $m1;
1785
	}
1786
1787
	// H from 0 to 360, S and L from 0 to 100
1788
	public function toRGB($hue, $saturation, $lightness) {
1789
		if ($hue < 0) {
1790
			$hue += 360;
1791
		}
1792
1793
		$h = $hue / 360;
1794
		$s = min(100, max(0, $saturation)) / 100;
1795
		$l = min(100, max(0, $lightness)) / 100;
1796
1797
		$m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
1798
		$m1 = $l * 2 - $m2;
1799
1800
		$r = $this->hueToRGB($m1, $m2, $h + 1/3) * 255;
1801
		$g = $this->hueToRGB($m1, $m2, $h) * 255;
1802
		$b = $this->hueToRGB($m1, $m2, $h - 1/3) * 255;
1803
1804
		$out = array('color', $r, $g, $b);
1805
		return $out;
1806
	}
1807
1808
	// Built in functions
1809
1810
	protected static $lib_if = array("condition", "if-true", "if-false");
1811
	protected function lib_if($args) {
1812
		list($cond,$t, $f) = $args;
1813
		if ($cond == self::$false) return $f;
1814
		return $t;
1815
	}
1816
1817
	protected static $lib_index = array("list", "value");
1818
	protected function lib_index($args) {
1819
		list($list, $value) = $args;
1820
		$list = $this->assertList($list);
1821
1822
		$values = array();
1823
		foreach ($list[2] as $item) {
1824
			$values[] = $this->normalizeValue($item);
1825
		}
1826
		$key = array_search($this->normalizeValue($value), $values);
1827
1828
		return false === $key ? false : $key + 1;
1829
	}
1830
1831
	protected static $lib_rgb = array("red", "green", "blue");
1832
	protected function lib_rgb($args) {
1833
		list($r,$g,$b) = $args;
1834
		return array("color", $r[1], $g[1], $b[1]);
1835
	}
1836
1837
	protected static $lib_rgba = array(
1838
		array("red", "color"),
1839
		"green", "blue", "alpha");
1840
	protected function lib_rgba($args) {
1841
		if ($color = $this->coerceColor($args[0])) {
1842
			$num = is_null($args[1]) ? $args[3] : $args[1];
1843
			$alpha = $this->assertNumber($num);
1844
			$color[4] = $alpha;
1845
			return $color;
1846
		}
1847
1848
		list($r,$g,$b, $a) = $args;
1849
		return array("color", $r[1], $g[1], $b[1], $a[1]);
1850
	}
1851
1852
	// helper function for adjust_color, change_color, and scale_color
1853
	protected function alter_color($args, $fn) {
1854
		$color = $this->assertColor($args[0]);
1855
1856
		foreach (array(1,2,3,7) as $i) {
1857
			if (!is_null($args[$i])) {
1858
				$val = $this->assertNumber($args[$i]);
1859
				$ii = $i == 7 ? 4 : $i; // alpha
1860
				$color[$ii] =
1861
					$this->$fn(isset($color[$ii]) ? $color[$ii] : 0, $val, $i);
1862
			}
1863
		}
1864
1865
		if (!is_null($args[4]) || !is_null($args[5]) || !is_null($args[6])) {
1866
			$hsl = $this->toHSL($color[1], $color[2], $color[3]);
1867
			foreach (array(4,5,6) as $i) {
1868
				if (!is_null($args[$i])) {
1869
					$val = $this->assertNumber($args[$i]);
1870
					$hsl[$i - 3] = $this->$fn($hsl[$i - 3], $val, $i);
1871
				}
1872
			}
1873
1874
			$rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
1875
			if (isset($color[4])) $rgb[4] = $color[4];
1876
			$color = $rgb;
1877
		}
1878
1879
		return $color;
1880
	}
1881
1882
	protected static $lib_adjust_color = array(
1883
		"color", "red", "green", "blue",
1884
		"hue", "saturation", "lightness", "alpha"
1885
	);
1886
	protected function adjust_color_helper($base, $alter, $i) {
0 ignored issues
show
Unused Code introduced by
The parameter $i 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...
1887
		return $base += $alter;
1888
	}
1889
	protected function lib_adjust_color($args) {
1890
		return $this->alter_color($args, "adjust_color_helper");
1891
	}
1892
1893
	protected static $lib_change_color = array(
1894
		"color", "red", "green", "blue",
1895
		"hue", "saturation", "lightness", "alpha"
1896
	);
1897
	protected function change_color_helper($base, $alter, $i) {
0 ignored issues
show
Unused Code introduced by
The parameter $base 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 $i 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...
1898
		return $alter;
1899
	}
1900
	protected function lib_change_color($args) {
1901
		return $this->alter_color($args, "change_color_helper");
1902
	}
1903
1904
	protected static $lib_scale_color = array(
1905
		"color", "red", "green", "blue",
1906
		"hue", "saturation", "lightness", "alpha"
1907
	);
1908
	protected function scale_color_helper($base, $scale, $i) {
1909
		// 1,2,3 - rgb
1910
		// 4, 5, 6 - hsl
1911
		// 7 - a
1912
		switch ($i) {
1913
		case 1:
1914
		case 2:
1915
		case 3:
1916
			$max = 255; break;
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1917
		case 4:
1918
			$max = 360; break;
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1919
		case 7:
1920
			$max = 1; break;
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1921
		default:
1922
			$max = 100;
1923
		}
1924
1925
		$scale = $scale / 100;
1926
		if ($scale < 0) {
1927
			return $base * $scale + $base;
1928
		} else {
1929
			return ($max - $base) * $scale + $base;
1930
		}
1931
	}
1932
	protected function lib_scale_color($args) {
1933
		return $this->alter_color($args, "scale_color_helper");
1934
	}
1935
1936
	protected static $lib_ie_hex_str = array("color");
1937
	protected function lib_ie_hex_str($args) {
1938
		$color = $this->coerceColor($args[0]);
1939
		$color[4] = isset($color[4]) ? round(255*$color[4]) : 255;
1940
1941
		return sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3]);
1942
	}
1943
1944
	protected static $lib_red = array("color");
1945
	protected function lib_red($args) {
1946
		$color = $this->coerceColor($args[0]);
1947
		return $color[1];
1948
	}
1949
1950
	protected static $lib_green = array("color");
1951
	protected function lib_green($args) {
1952
		$color = $this->coerceColor($args[0]);
1953
		return $color[2];
1954
	}
1955
1956
	protected static $lib_blue = array("color");
1957
	protected function lib_blue($args) {
1958
		$color = $this->coerceColor($args[0]);
1959
		return $color[3];
1960
	}
1961
1962
	protected static $lib_alpha = array("color");
1963
	protected function lib_alpha($args) {
1964
		if ($color = $this->coerceColor($args[0])) {
1965
			return isset($color[4]) ? $color[4] : 1;
1966
		}
1967
1968
		// this might be the IE function, so return value unchanged
1969
		return null;
1970
	}
1971
1972
	protected static $lib_opacity = array("color");
1973
	protected function lib_opacity($args) {
1974
		$value = $args[0];
1975
		if ($value[0] === 'number') return null;
1976
		return $this->lib_alpha($args);
1977
	}
1978
1979
	// mix two colors
1980
	protected static $lib_mix = array("color-1", "color-2", "weight");
1981
	protected function lib_mix($args) {
1982
		list($first, $second, $weight) = $args;
1983
		$first = $this->assertColor($first);
1984
		$second = $this->assertColor($second);
1985
1986
		if (is_null($weight)) {
1987
			$weight = 0.5;
1988
		} else {
1989
			$weight = $this->coercePercent($weight);
1990
		}
1991
1992
		$firstAlpha = isset($first[4]) ? $first[4] : 1;
1993
		$secondAlpha = isset($second[4]) ? $second[4] : 1;
1994
1995
		$w = $weight * 2 - 1;
1996
		$a = $firstAlpha - $secondAlpha;
1997
1998
		$w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
1999
		$w2 = 1.0 - $w1;
2000
2001
		$new = array('color',
2002
			$w1 * $first[1] + $w2 * $second[1],
2003
			$w1 * $first[2] + $w2 * $second[2],
2004
			$w1 * $first[3] + $w2 * $second[3],
2005
		);
2006
2007
		if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
2008
			$new[] = $firstAlpha * $weight + $secondAlpha * ($weight - 1);
2009
		}
2010
2011
		return $this->fixColor($new);
2012
	}
2013
2014
	protected static $lib_hsl = array("hue", "saturation", "lightness");
2015
	protected function lib_hsl($args) {
2016
		list($h, $s, $l) = $args;
2017
		return $this->toRGB($h[1], $s[1], $l[1]);
2018
	}
2019
2020
	protected static $lib_hsla = array("hue", "saturation",
2021
		"lightness", "alpha");
2022
	protected function lib_hsla($args) {
2023
		list($h, $s, $l, $a) = $args;
2024
		$color = $this->toRGB($h[1], $s[1], $l[1]);
2025
		$color[4] = $a[1];
2026
		return $color;
2027
	}
2028
2029
	protected static $lib_hue = array("color");
2030
	protected function lib_hue($args) {
2031
		$color = $this->assertColor($args[0]);
2032
		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
2033
		return array("number", $hsl[1], "deg");
2034
	}
2035
2036
	protected static $lib_saturation = array("color");
2037
	protected function lib_saturation($args) {
2038
		$color = $this->assertColor($args[0]);
2039
		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
2040
		return array("number", $hsl[2], "%");
2041
	}
2042
2043
	protected static $lib_lightness = array("color");
2044
	protected function lib_lightness($args) {
2045
		$color = $this->assertColor($args[0]);
2046
		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
2047
		return array("number", $hsl[3], "%");
2048
	}
2049
2050
	protected function adjustHsl($color, $idx, $amount) {
2051
		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
2052
		$hsl[$idx] += $amount;
2053
		$out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
2054
		if (isset($color[4])) $out[4] = $color[4];
2055
		return $out;
2056
	}
2057
2058
	protected static $lib_adjust_hue = array("color", "degrees");
2059
	protected function lib_adjust_hue($args) {
2060
		$color = $this->assertColor($args[0]);
2061
		$degrees = $this->assertNumber($args[1]);
2062
		return $this->adjustHsl($color, 1, $degrees);
2063
	}
2064
2065
	protected static $lib_lighten = array("color", "amount");
2066
	protected function lib_lighten($args) {
2067
		$color = $this->assertColor($args[0]);
2068
		$amount = 100*$this->coercePercent($args[1]);
2069
		return $this->adjustHsl($color, 3, $amount);
2070
	}
2071
2072
	protected static $lib_darken = array("color", "amount");
2073
	protected function lib_darken($args) {
2074
		$color = $this->assertColor($args[0]);
2075
		$amount = 100*$this->coercePercent($args[1]);
2076
		return $this->adjustHsl($color, 3, -$amount);
2077
	}
2078
2079
	protected static $lib_saturate = array("color", "amount");
2080
	protected function lib_saturate($args) {
2081
		$value = $args[0];
2082
		if ($value[0] === 'number') return null;
2083
		$color = $this->assertColor($value);
2084
		$amount = 100*$this->coercePercent($args[1]);
2085
		return $this->adjustHsl($color, 2, $amount);
2086
	}
2087
2088
	protected static $lib_desaturate = array("color", "amount");
2089
	protected function lib_desaturate($args) {
2090
		$color = $this->assertColor($args[0]);
2091
		$amount = 100*$this->coercePercent($args[1]);
2092
		return $this->adjustHsl($color, 2, -$amount);
2093
	}
2094
2095
	protected static $lib_grayscale = array("color");
2096
	protected function lib_grayscale($args) {
2097
		$value = $args[0];
2098
		if ($value[0] === 'number') return null;
2099
		return $this->adjustHsl($this->assertColor($value), 2, -100);
2100
	}
2101
2102
	protected static $lib_complement = array("color");
2103
	protected function lib_complement($args) {
2104
		return $this->adjustHsl($this->assertColor($args[0]), 1, 180);
2105
	}
2106
2107
	protected static $lib_invert = array("color");
2108
	protected function lib_invert($args) {
2109
		$value = $args[0];
2110
		if ($value[0] === 'number') return null;
2111
		$color = $this->assertColor($value);
2112
		$color[1] = 255 - $color[1];
2113
		$color[2] = 255 - $color[2];
2114
		$color[3] = 255 - $color[3];
2115
		return $color;
2116
	}
2117
2118
	// increases opacity by amount
2119
	protected static $lib_opacify = array("color", "amount");
2120 View Code Duplication
	protected function lib_opacify($args) {
2121
		$color = $this->assertColor($args[0]);
2122
		$amount = $this->coercePercent($args[1]);
2123
2124
		$color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
2125
		$color[4] = min(1, max(0, $color[4]));
2126
		return $color;
2127
	}
2128
2129
	protected static $lib_fade_in = array("color", "amount");
2130
	protected function lib_fade_in($args) {
2131
		return $this->lib_opacify($args);
2132
	}
2133
2134
	// decreases opacity by amount
2135
	protected static $lib_transparentize = array("color", "amount");
2136 View Code Duplication
	protected function lib_transparentize($args) {
2137
		$color = $this->assertColor($args[0]);
2138
		$amount = $this->coercePercent($args[1]);
2139
2140
		$color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
2141
		$color[4] = min(1, max(0, $color[4]));
2142
		return $color;
2143
	}
2144
2145
	protected static $lib_fade_out = array("color", "amount");
2146
	protected function lib_fade_out($args) {
2147
		return $this->lib_transparentize($args);
2148
	}
2149
2150
	protected static $lib_unquote = array("string");
2151
	protected function lib_unquote($args) {
2152
		$str = $args[0];
2153
		if ($str[0] == "string") $str[1] = "";
2154
		return $str;
2155
	}
2156
2157
	protected static $lib_quote = array("string");
2158
	protected function lib_quote($args) {
2159
		$value = $args[0];
2160
		if ($value[0] == "string" && !empty($value[1]))
2161
			return $value;
2162
		return array("string", '"', array($value));
2163
	}
2164
2165
	protected static $lib_percentage = array("value");
2166
	protected function lib_percentage($args) {
2167
		return array("number",
2168
			$this->coercePercent($args[0]) * 100,
2169
			"%");
2170
	}
2171
2172
	protected static $lib_round = array("value");
2173
	protected function lib_round($args) {
2174
		$num = $args[0];
2175
		$num[1] = round($num[1]);
2176
		return $num;
2177
	}
2178
2179
	protected static $lib_floor = array("value");
2180
	protected function lib_floor($args) {
2181
		$num = $args[0];
2182
		$num[1] = floor($num[1]);
2183
		return $num;
2184
	}
2185
2186
	protected static $lib_ceil = array("value");
2187
	protected function lib_ceil($args) {
2188
		$num = $args[0];
2189
		$num[1] = ceil($num[1]);
2190
		return $num;
2191
	}
2192
2193
	protected static $lib_abs = array("value");
2194
	protected function lib_abs($args) {
2195
		$num = $args[0];
2196
		$num[1] = abs($num[1]);
2197
		return $num;
2198
	}
2199
2200 View Code Duplication
	protected function lib_min($args) {
2201
		$numbers = $this->getNormalizedNumbers($args);
2202
		$min = null;
2203
		foreach ($numbers as $key => $number) {
2204
			if (null === $min || $number[1] <= $min[1]) {
2205
				$min = array($key, $number[1]);
2206
			}
2207
		}
2208
2209
		return $args[$min[0]];
2210
	}
2211
2212 View Code Duplication
	protected function lib_max($args) {
2213
		$numbers = $this->getNormalizedNumbers($args);
2214
		$max = null;
2215
		foreach ($numbers as $key => $number) {
2216
			if (null === $max || $number[1] >= $max[1]) {
2217
				$max = array($key, $number[1]);
2218
			}
2219
		}
2220
2221
		return $args[$max[0]];
2222
	}
2223
2224
	protected function getNormalizedNumbers($args) {
2225
		$unit = null;
2226
		$originalUnit = null;
2227
		$numbers = array();
2228
		foreach ($args as $key => $item) {
2229
			if ('number' != $item[0]) {
2230
				$this->throwError("%s is not a number", $item[0]);
2231
			}
2232
			$number = $this->normalizeNumber($item);
2233
2234
			if (null === $unit) {
2235
				$unit = $number[2];
2236
				$originalUnit = $item[2];
2237
			} elseif ($unit !== $number[2]) {
2238
				$this->throwError('Incompatible units: "%s" and "%s".', $originalUnit, $item[2]);
2239
			}
2240
2241
			$numbers[$key] = $number;
2242
		}
2243
2244
		return $numbers;
2245
	}
2246
2247
	protected static $lib_length = array("list");
2248
	protected function lib_length($args) {
2249
		$list = $this->coerceList($args[0]);
2250
		return count($list[2]);
2251
	}
2252
2253
	protected static $lib_nth = array("list", "n");
2254
	protected function lib_nth($args) {
2255
		$list = $this->coerceList($args[0]);
2256
		$n = $this->assertNumber($args[1]) - 1;
2257
		return isset($list[2][$n]) ? $list[2][$n] : self::$defaultValue;
2258
	}
2259
2260
	protected function listSeparatorForJoin($list1, $sep) {
2261
		if (is_null($sep)) return $list1[1];
2262
		switch ($this->compileValue($sep)) {
2263
		case "comma":
2264
			return ",";
2265
		case "space":
2266
			return "";
2267
		default:
2268
			return $list1[1];
2269
		}
2270
	}
2271
2272
	protected static $lib_join = array("list1", "list2", "separator");
2273
	protected function lib_join($args) {
2274
		list($list1, $list2, $sep) = $args;
2275
		$list1 = $this->coerceList($list1, " ");
2276
		$list2 = $this->coerceList($list2, " ");
2277
		$sep = $this->listSeparatorForJoin($list1, $sep);
2278
		return array("list", $sep, array_merge($list1[2], $list2[2]));
2279
	}
2280
2281
	protected static $lib_append = array("list", "val", "separator");
2282
	protected function lib_append($args) {
2283
		list($list1, $value, $sep) = $args;
2284
		$list1 = $this->coerceList($list1, " ");
2285
		$sep = $this->listSeparatorForJoin($list1, $sep);
2286
		return array("list", $sep, array_merge($list1[2], array($value)));
2287
	}
2288
2289
	protected function lib_zip($args) {
2290
		foreach ($args as $arg) {
2291
			$this->assertList($arg);
2292
		}
2293
2294
		$lists = array();
2295
		$firstList = array_shift($args);
2296
		foreach ($firstList[2] as $key => $item) {
2297
			$list = array("list", "", array($item));
2298
			foreach ($args as $arg) {
2299
				if (isset($arg[2][$key])) {
2300
					$list[2][] = $arg[2][$key];
2301
				} else {
2302
					break 2;
2303
				}
2304
			}
2305
			$lists[] = $list;
2306
		}
2307
2308
		return array("list", ",", $lists);
2309
	}
2310
2311
	protected static $lib_type_of = array("value");
2312
	protected function lib_type_of($args) {
2313
		$value = $args[0];
2314
		switch ($value[0]) {
2315
		case "keyword":
2316
			if ($value == self::$true || $value == self::$false) {
2317
				return "bool";
2318
			}
2319
2320
			if ($this->coerceColor($value)) {
2321
				return "color";
2322
			}
2323
2324
			return "string";
2325
		default:
2326
			return $value[0];
2327
		}
2328
	}
2329
2330
	protected static $lib_unit = array("number");
2331
	protected function lib_unit($args) {
2332
		$num = $args[0];
2333
		if ($num[0] == "number") {
2334
			return array("string", '"', array($num[2]));
2335
		}
2336
		return "";
2337
	}
2338
2339
	protected static $lib_unitless = array("number");
2340
	protected function lib_unitless($args) {
2341
		$value = $args[0];
2342
		return $value[0] == "number" && empty($value[2]);
2343
	}
2344
2345
	protected static $lib_comparable = array("number-1", "number-2");
2346
	protected function lib_comparable($args) {
2347
		list($number1, $number2) = $args;
2348
		if (!isset($number1[0]) || $number1[0] != "number" || !isset($number2[0]) || $number2[0] != "number") {
2349
			$this->throwError('Invalid argument(s) for "comparable"');
2350
		}
2351
2352
		$number1 = $this->normalizeNumber($number1);
2353
		$number2 = $this->normalizeNumber($number2);
2354
2355
		return $number1[2] == $number2[2] || $number1[2] == "" || $number2[2] == "";
2356
	}
2357
2358
	/**
2359
	 * Workaround IE7's content counter bug.
2360
	 *
2361
	 * @param array $args
2362
	 */
2363
	protected function lib_counter($args) {
2364
		$list = array_map(array($this, 'compileValue'), $args);
2365
		return array('string', '', array('counter(' . implode(',', $list) . ')'));
2366
	}
2367
2368
	public function throwError($msg = null) {
2369
		if (func_num_args() > 1) {
2370
			$msg = call_user_func_array("sprintf", func_get_args());
2371
		}
2372
2373
		if ($this->sourcePos >= 0 && isset($this->sourceParser)) {
2374
			$this->sourceParser->throwParseError($msg, $this->sourcePos);
0 ignored issues
show
Bug introduced by
The property sourceParser does not seem to exist. Did you mean parser?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2375
		}
2376
2377
		throw new Exception($msg);
2378
	}
2379
2380
	/**
2381
	 * CSS Colors
2382
	 *
2383
	 * @see http://www.w3.org/TR/css3-color
2384
	 */
2385
	static protected $cssColors = array(
2386
		'aliceblue' => '240,248,255',
2387
		'antiquewhite' => '250,235,215',
2388
		'aqua' => '0,255,255',
2389
		'aquamarine' => '127,255,212',
2390
		'azure' => '240,255,255',
2391
		'beige' => '245,245,220',
2392
		'bisque' => '255,228,196',
2393
		'black' => '0,0,0',
2394
		'blanchedalmond' => '255,235,205',
2395
		'blue' => '0,0,255',
2396
		'blueviolet' => '138,43,226',
2397
		'brown' => '165,42,42',
2398
		'burlywood' => '222,184,135',
2399
		'cadetblue' => '95,158,160',
2400
		'chartreuse' => '127,255,0',
2401
		'chocolate' => '210,105,30',
2402
		'coral' => '255,127,80',
2403
		'cornflowerblue' => '100,149,237',
2404
		'cornsilk' => '255,248,220',
2405
		'crimson' => '220,20,60',
2406
		'cyan' => '0,255,255',
2407
		'darkblue' => '0,0,139',
2408
		'darkcyan' => '0,139,139',
2409
		'darkgoldenrod' => '184,134,11',
2410
		'darkgray' => '169,169,169',
2411
		'darkgreen' => '0,100,0',
2412
		'darkgrey' => '169,169,169',
2413
		'darkkhaki' => '189,183,107',
2414
		'darkmagenta' => '139,0,139',
2415
		'darkolivegreen' => '85,107,47',
2416
		'darkorange' => '255,140,0',
2417
		'darkorchid' => '153,50,204',
2418
		'darkred' => '139,0,0',
2419
		'darksalmon' => '233,150,122',
2420
		'darkseagreen' => '143,188,143',
2421
		'darkslateblue' => '72,61,139',
2422
		'darkslategray' => '47,79,79',
2423
		'darkslategrey' => '47,79,79',
2424
		'darkturquoise' => '0,206,209',
2425
		'darkviolet' => '148,0,211',
2426
		'deeppink' => '255,20,147',
2427
		'deepskyblue' => '0,191,255',
2428
		'dimgray' => '105,105,105',
2429
		'dimgrey' => '105,105,105',
2430
		'dodgerblue' => '30,144,255',
2431
		'firebrick' => '178,34,34',
2432
		'floralwhite' => '255,250,240',
2433
		'forestgreen' => '34,139,34',
2434
		'fuchsia' => '255,0,255',
2435
		'gainsboro' => '220,220,220',
2436
		'ghostwhite' => '248,248,255',
2437
		'gold' => '255,215,0',
2438
		'goldenrod' => '218,165,32',
2439
		'gray' => '128,128,128',
2440
		'green' => '0,128,0',
2441
		'greenyellow' => '173,255,47',
2442
		'grey' => '128,128,128',
2443
		'honeydew' => '240,255,240',
2444
		'hotpink' => '255,105,180',
2445
		'indianred' => '205,92,92',
2446
		'indigo' => '75,0,130',
2447
		'ivory' => '255,255,240',
2448
		'khaki' => '240,230,140',
2449
		'lavender' => '230,230,250',
2450
		'lavenderblush' => '255,240,245',
2451
		'lawngreen' => '124,252,0',
2452
		'lemonchiffon' => '255,250,205',
2453
		'lightblue' => '173,216,230',
2454
		'lightcoral' => '240,128,128',
2455
		'lightcyan' => '224,255,255',
2456
		'lightgoldenrodyellow' => '250,250,210',
2457
		'lightgray' => '211,211,211',
2458
		'lightgreen' => '144,238,144',
2459
		'lightgrey' => '211,211,211',
2460
		'lightpink' => '255,182,193',
2461
		'lightsalmon' => '255,160,122',
2462
		'lightseagreen' => '32,178,170',
2463
		'lightskyblue' => '135,206,250',
2464
		'lightslategray' => '119,136,153',
2465
		'lightslategrey' => '119,136,153',
2466
		'lightsteelblue' => '176,196,222',
2467
		'lightyellow' => '255,255,224',
2468
		'lime' => '0,255,0',
2469
		'limegreen' => '50,205,50',
2470
		'linen' => '250,240,230',
2471
		'magenta' => '255,0,255',
2472
		'maroon' => '128,0,0',
2473
		'mediumaquamarine' => '102,205,170',
2474
		'mediumblue' => '0,0,205',
2475
		'mediumorchid' => '186,85,211',
2476
		'mediumpurple' => '147,112,219',
2477
		'mediumseagreen' => '60,179,113',
2478
		'mediumslateblue' => '123,104,238',
2479
		'mediumspringgreen' => '0,250,154',
2480
		'mediumturquoise' => '72,209,204',
2481
		'mediumvioletred' => '199,21,133',
2482
		'midnightblue' => '25,25,112',
2483
		'mintcream' => '245,255,250',
2484
		'mistyrose' => '255,228,225',
2485
		'moccasin' => '255,228,181',
2486
		'navajowhite' => '255,222,173',
2487
		'navy' => '0,0,128',
2488
		'oldlace' => '253,245,230',
2489
		'olive' => '128,128,0',
2490
		'olivedrab' => '107,142,35',
2491
		'orange' => '255,165,0',
2492
		'orangered' => '255,69,0',
2493
		'orchid' => '218,112,214',
2494
		'palegoldenrod' => '238,232,170',
2495
		'palegreen' => '152,251,152',
2496
		'paleturquoise' => '175,238,238',
2497
		'palevioletred' => '219,112,147',
2498
		'papayawhip' => '255,239,213',
2499
		'peachpuff' => '255,218,185',
2500
		'peru' => '205,133,63',
2501
		'pink' => '255,192,203',
2502
		'plum' => '221,160,221',
2503
		'powderblue' => '176,224,230',
2504
		'purple' => '128,0,128',
2505
		'red' => '255,0,0',
2506
		'rosybrown' => '188,143,143',
2507
		'royalblue' => '65,105,225',
2508
		'saddlebrown' => '139,69,19',
2509
		'salmon' => '250,128,114',
2510
		'sandybrown' => '244,164,96',
2511
		'seagreen' => '46,139,87',
2512
		'seashell' => '255,245,238',
2513
		'sienna' => '160,82,45',
2514
		'silver' => '192,192,192',
2515
		'skyblue' => '135,206,235',
2516
		'slateblue' => '106,90,205',
2517
		'slategray' => '112,128,144',
2518
		'slategrey' => '112,128,144',
2519
		'snow' => '255,250,250',
2520
		'springgreen' => '0,255,127',
2521
		'steelblue' => '70,130,180',
2522
		'tan' => '210,180,140',
2523
		'teal' => '0,128,128',
2524
		'thistle' => '216,191,216',
2525
		'tomato' => '255,99,71',
2526
		'transparent' => '0,0,0,0',
2527
		'turquoise' => '64,224,208',
2528
		'violet' => '238,130,238',
2529
		'wheat' => '245,222,179',
2530
		'white' => '255,255,255',
2531
		'whitesmoke' => '245,245,245',
2532
		'yellow' => '255,255,0',
2533
		'yellowgreen' => '154,205,50'
2534
	);
2535
}
2536
2537
/**
2538
 * SCSS parser
2539
 *
2540
 * @author Leaf Corcoran <[email protected]>
2541
 */
2542
class scss_parser {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
2543
	static protected $precedence = array(
2544
		"or" => 0,
2545
		"and" => 1,
2546
2547
		'==' => 2,
2548
		'!=' => 2,
2549
		'<=' => 2,
2550
		'>=' => 2,
2551
		'=' => 2,
2552
		'<' => 3,
2553
		'>' => 2,
2554
2555
		'+' => 3,
2556
		'-' => 3,
2557
		'*' => 4,
2558
		'/' => 4,
2559
		'%' => 4,
2560
	);
2561
2562
	static protected $operators = array("+", "-", "*", "/", "%",
2563
		"==", "!=", "<=", ">=", "<", ">", "and", "or");
2564
2565
	static protected $operatorStr;
2566
	static protected $whitePattern;
2567
	static protected $commentMulti;
2568
2569
	static protected $commentSingle = "//";
2570
	static protected $commentMultiLeft = "/*";
2571
	static protected $commentMultiRight = "*/";
2572
2573
	public function __construct($sourceName = null, $rootParser = true) {
2574
		$this->sourceName = $sourceName;
0 ignored issues
show
Bug introduced by
The property sourceName 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...
2575
		$this->rootParser = $rootParser;
0 ignored issues
show
Bug introduced by
The property rootParser 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...
2576
2577
		if (empty(self::$operatorStr)) {
2578
			self::$operatorStr = $this->makeOperatorStr(self::$operators);
2579
2580
			$commentSingle = $this->preg_quote(self::$commentSingle);
2581
			$commentMultiLeft = $this->preg_quote(self::$commentMultiLeft);
2582
			$commentMultiRight = $this->preg_quote(self::$commentMultiRight);
2583
			self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
2584
			self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
2585
		}
2586
	}
2587
2588
	static protected function makeOperatorStr($operators) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
2589
		return '('.implode('|', array_map(array('scss_parser','preg_quote'),
2590
			$operators)).')';
2591
	}
2592
2593
	public function parse($buffer) {
2594
		$this->count = 0;
0 ignored issues
show
Bug introduced by
The property count 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...
2595
		$this->env = null;
0 ignored issues
show
Bug introduced by
The property env 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...
2596
		$this->inParens = false;
0 ignored issues
show
Bug introduced by
The property inParens 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...
2597
		$this->pushBlock(null); // root block
2598
		$this->eatWhiteDefault = true;
0 ignored issues
show
Bug introduced by
The property eatWhiteDefault 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...
2599
		$this->insertComments = true;
0 ignored issues
show
Bug introduced by
The property insertComments 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...
2600
2601
		$this->buffer = $buffer;
0 ignored issues
show
Bug introduced by
The property buffer 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...
2602
2603
		$this->whitespace();
2604
		while (false !== $this->parseChunk());
2605
2606
		if ($this->count != strlen($this->buffer))
2607
			$this->throwParseError();
2608
2609
		if (!empty($this->env->parent)) {
2610
			$this->throwParseError("unclosed block");
2611
		}
2612
2613
		$this->env->isRoot = true;
2614
		return $this->env;
2615
	}
2616
2617
	/**
2618
	 * Parse a single chunk off the head of the buffer and append it to the
2619
	 * current parse environment.
2620
	 *
2621
	 * Returns false when the buffer is empty, or when there is an error.
2622
	 *
2623
	 * This function is called repeatedly until the entire document is
2624
	 * parsed.
2625
	 *
2626
	 * This parser is most similar to a recursive descent parser. Single
2627
	 * functions represent discrete grammatical rules for the language, and
2628
	 * they are able to capture the text that represents those rules.
2629
	 *
2630
	 * Consider the function scssc::keyword(). (All parse functions are
2631
	 * structured the same.)
2632
	 *
2633
	 * The function takes a single reference argument. When calling the
2634
	 * function it will attempt to match a keyword on the head of the buffer.
2635
	 * If it is successful, it will place the keyword in the referenced
2636
	 * argument, advance the position in the buffer, and return true. If it
2637
	 * fails then it won't advance the buffer and it will return false.
2638
	 *
2639
	 * All of these parse functions are powered by scssc::match(), which behaves
2640
	 * the same way, but takes a literal regular expression. Sometimes it is
2641
	 * more convenient to use match instead of creating a new function.
2642
	 *
2643
	 * Because of the format of the functions, to parse an entire string of
2644
	 * grammatical rules, you can chain them together using &&.
2645
	 *
2646
	 * But, if some of the rules in the chain succeed before one fails, then
2647
	 * the buffer position will be left at an invalid state. In order to
2648
	 * avoid this, scssc::seek() is used to remember and set buffer positions.
2649
	 *
2650
	 * Before parsing a chain, use $s = $this->seek() to remember the current
2651
	 * position into $s. Then if a chain fails, use $this->seek($s) to
2652
	 * go back where we started.
2653
	 *
2654
	 * @return boolean
2655
	 */
2656
	protected function parseChunk() {
2657
		$s = $this->seek();
2658
2659
		// the directives
2660
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
2661
			if ($this->literal("@media") && $this->mediaQueryList($mediaQueryList) && $this->literal("{")) {
2662
				$media = $this->pushSpecialBlock("media");
2663
				$media->queryList = $mediaQueryList[2];
2664
				return true;
2665
			} else {
2666
				$this->seek($s);
2667
			}
2668
2669
			if ($this->literal("@mixin") &&
2670
				$this->keyword($mixinName) &&
2671
				($this->argumentDef($args) || true) &&
2672
				$this->literal("{"))
2673
			{
2674
				$mixin = $this->pushSpecialBlock("mixin");
2675
				$mixin->name = $mixinName;
2676
				$mixin->args = $args;
2677
				return true;
2678
			} else {
2679
				$this->seek($s);
2680
			}
2681
2682
			if ($this->literal("@include") &&
2683
				$this->keyword($mixinName) &&
2684
				($this->literal("(") &&
2685
					($this->argValues($argValues) || true) &&
2686
					$this->literal(")") || true) &&
2687
				($this->end() ||
2688
					$this->literal("{") && $hasBlock = true))
2689
			{
2690
				$child = array("include",
2691
					$mixinName, isset($argValues) ? $argValues : null, null);
2692
2693
				if (!empty($hasBlock)) {
2694
					$include = $this->pushSpecialBlock("include");
2695
					$include->child = $child;
2696
				} else {
2697
					$this->append($child, $s);
2698
				}
2699
2700
				return true;
2701
			} else {
2702
				$this->seek($s);
2703
			}
2704
2705
			if ($this->literal("@import") &&
2706
				$this->valueList($importPath) &&
2707
				$this->end())
2708
			{
2709
				$this->append(array("import", $importPath), $s);
2710
				return true;
2711
			} else {
2712
				$this->seek($s);
2713
			}
2714
2715
			if ($this->literal("@extend") &&
2716
				$this->selectors($selector) &&
2717
				$this->end())
2718
			{
2719
				$this->append(array("extend", $selector), $s);
2720
				return true;
2721
			} else {
2722
				$this->seek($s);
2723
			}
2724
2725 View Code Duplication
			if ($this->literal("@function") &&
2726
				$this->keyword($fnName) &&
2727
				$this->argumentDef($args) &&
2728
				$this->literal("{"))
2729
			{
2730
				$func = $this->pushSpecialBlock("function");
2731
				$func->name = $fnName;
2732
				$func->args = $args;
2733
				return true;
2734
			} else {
2735
				$this->seek($s);
2736
			}
2737
2738
			if ($this->literal("@return") && $this->valueList($retVal) && $this->end()) {
2739
				$this->append(array("return", $retVal), $s);
2740
				return true;
2741
			} else {
2742
				$this->seek($s);
2743
			}
2744
2745
			if ($this->literal("@each") &&
2746
				$this->variable($varName) &&
2747
				$this->literal("in") &&
2748
				$this->valueList($list) &&
2749
				$this->literal("{"))
2750
			{
2751
				$each = $this->pushSpecialBlock("each");
2752
				$each->var = $varName[1];
2753
				$each->list = $list;
2754
				return true;
2755
			} else {
2756
				$this->seek($s);
2757
			}
2758
2759
			if ($this->literal("@while") &&
2760
				$this->expression($cond) &&
2761
				$this->literal("{"))
2762
			{
2763
				$while = $this->pushSpecialBlock("while");
2764
				$while->cond = $cond;
2765
				return true;
2766
			} else {
2767
				$this->seek($s);
2768
			}
2769
2770
			if ($this->literal("@for") &&
2771
				$this->variable($varName) &&
2772
				$this->literal("from") &&
2773
				$this->expression($start) &&
2774
				($this->literal("through") ||
2775
					($forUntil = true && $this->literal("to"))) &&
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $forUntil = (true && $this->literal('to')), Probably Intended Meaning: ($forUntil = true) && $this->literal('to')
Loading history...
2776
				$this->expression($end) &&
2777
				$this->literal("{"))
2778
			{
2779
				$for = $this->pushSpecialBlock("for");
2780
				$for->var = $varName[1];
2781
				$for->start = $start;
2782
				$for->end = $end;
2783
				$for->until = isset($forUntil);
2784
				return true;
2785
			} else {
2786
				$this->seek($s);
2787
			}
2788
2789 View Code Duplication
			if ($this->literal("@if") && $this->valueList($cond) && $this->literal("{")) {
2790
				$if = $this->pushSpecialBlock("if");
2791
				$if->cond = $cond;
2792
				$if->cases = array();
2793
				return true;
2794
			} else {
2795
				$this->seek($s);
2796
			}
2797
2798 View Code Duplication
			if (($this->literal("@debug") || $this->literal("@warn")) &&
2799
				$this->valueList($value) &&
2800
				$this->end()) {
2801
				$this->append(array("debug", $value, $s), $s);
2802
				return true;
2803
			} else {
2804
				$this->seek($s);
2805
			}
2806
2807
			if ($this->literal("@content") && $this->end()) {
2808
				$this->append(array("mixin_content"), $s);
2809
				return true;
2810
			} else {
2811
				$this->seek($s);
2812
			}
2813
2814
			$last = $this->last();
2815
			if (!is_null($last) && $last[0] == "if") {
2816
				list(, $if) = $last;
2817
				if ($this->literal("@else")) {
2818
					if ($this->literal("{")) {
2819
						$else = $this->pushSpecialBlock("else");
2820
					} elseif ($this->literal("if") && $this->valueList($cond) && $this->literal("{")) {
2821
						$else = $this->pushSpecialBlock("elseif");
2822
						$else->cond = $cond;
2823
					}
2824
2825
					if (isset($else)) {
2826
						$else->dontAppend = true;
2827
						$if->cases[] = $else;
2828
						return true;
2829
					}
2830
				}
2831
2832
				$this->seek($s);
2833
			}
2834
2835
			if ($this->literal("@charset") &&
2836
				$this->valueList($charset) && $this->end())
2837
			{
2838
				$this->append(array("charset", $charset), $s);
2839
				return true;
2840
			} else {
2841
				$this->seek($s);
2842
			}
2843
2844
			// doesn't match built in directive, do generic one
2845
			if ($this->literal("@", false) && $this->keyword($dirName) &&
2846
				($this->openString("{", $dirValue) || true) &&
2847
				$this->literal("{"))
2848
			{
2849
				$directive = $this->pushSpecialBlock("directive");
2850
				$directive->name = $dirName;
2851
				if (isset($dirValue)) $directive->value = $dirValue;
2852
				return true;
2853
			}
2854
2855
			$this->seek($s);
2856
			return false;
2857
		}
2858
2859
		// property shortcut
2860
		// captures most properties before having to parse a selector
2861
		if ($this->keyword($name, false) &&
2862
			$this->literal(": ") &&
2863
			$this->valueList($value) &&
2864
			$this->end())
2865
		{
2866
			$name = array("string", "", array($name));
2867
			$this->append(array("assign", $name, $value), $s);
2868
			return true;
2869
		} else {
2870
			$this->seek($s);
2871
		}
2872
2873
		// variable assigns
2874
		if ($this->variable($name) &&
2875
			$this->literal(":") &&
2876
			$this->valueList($value) && $this->end())
2877
		{
2878
			// check for !default
2879
			$defaultVar = $value[0] == "list" && $this->stripDefault($value);
2880
			$this->append(array("assign", $name, $value, $defaultVar), $s);
2881
			return true;
2882
		} else {
2883
			$this->seek($s);
2884
		}
2885
2886
		// misc
2887
		if ($this->literal("-->")) {
2888
			return true;
2889
		}
2890
2891
		// opening css block
2892
		$oldComments = $this->insertComments;
2893
		$this->insertComments = false;
2894 View Code Duplication
		if ($this->selectors($selectors) && $this->literal("{")) {
2895
			$this->pushBlock($selectors);
2896
			$this->insertComments = $oldComments;
2897
			return true;
2898
		} else {
2899
			$this->seek($s);
2900
		}
2901
		$this->insertComments = $oldComments;
2902
2903
		// property assign, or nested assign
2904
		if ($this->propertyName($name) && $this->literal(":")) {
2905
			$foundSomething = false;
2906
			if ($this->valueList($value)) {
2907
				$this->append(array("assign", $name, $value), $s);
2908
				$foundSomething = true;
2909
			}
2910
2911
			if ($this->literal("{")) {
2912
				$propBlock = $this->pushSpecialBlock("nestedprop");
2913
				$propBlock->prefix = $name;
2914
				$foundSomething = true;
2915
			} elseif ($foundSomething) {
2916
				$foundSomething = $this->end();
2917
			}
2918
2919
			if ($foundSomething) {
2920
				return true;
2921
			}
2922
2923
			$this->seek($s);
2924
		} else {
2925
			$this->seek($s);
2926
		}
2927
2928
		// closing a block
2929
		if ($this->literal("}")) {
2930
			$block = $this->popBlock();
2931
			if (isset($block->type) && $block->type == "include") {
2932
				$include = $block->child;
2933
				unset($block->child);
2934
				$include[3] = $block;
2935
				$this->append($include, $s);
2936
			} elseif (empty($block->dontAppend)) {
2937
				$type = isset($block->type) ? $block->type : "block";
2938
				$this->append(array($type, $block), $s);
2939
			}
2940
			return true;
2941
		}
2942
2943
		// extra stuff
2944
		if ($this->literal(";") ||
2945
			$this->literal("<!--"))
2946
		{
2947
			return true;
2948
		}
2949
2950
		return false;
2951
	}
2952
2953
	protected function stripDefault(&$value) {
2954
		$def = end($value[2]);
2955 View Code Duplication
		if ($def[0] == "keyword" && $def[1] == "!default") {
2956
			array_pop($value[2]);
2957
			$value = $this->flattenList($value);
2958
			return true;
2959
		}
2960
2961
		if ($def[0] == "list") {
2962
			return $this->stripDefault($value[2][count($value[2]) - 1]);
2963
		}
2964
2965
		return false;
2966
	}
2967
2968
	protected function literal($what, $eatWhitespace = null) {
2969
		if (is_null($eatWhitespace)) $eatWhitespace = $this->eatWhiteDefault;
2970
2971
		// shortcut on single letter
2972 View Code Duplication
		if (!isset($what[1]) && isset($this->buffer[$this->count])) {
2973
			if ($this->buffer[$this->count] == $what) {
2974
				if (!$eatWhitespace) {
2975
					$this->count++;
2976
					return true;
2977
				}
2978
				// goes below...
2979
			} else {
2980
				return false;
2981
			}
2982
		}
2983
2984
		return $this->match($this->preg_quote($what), $m, $eatWhitespace);
2985
	}
2986
2987
	// tree builders
2988
2989
	protected function pushBlock($selectors) {
2990
		$b = new stdClass;
2991
		$b->parent = $this->env; // not sure if we need this yet
2992
2993
		$b->selectors = $selectors;
2994
		$b->children = array();
2995
2996
		$this->env = $b;
2997
		return $b;
2998
	}
2999
3000
	protected function pushSpecialBlock($type) {
3001
		$block = $this->pushBlock(null);
3002
		$block->type = $type;
3003
		return $block;
3004
	}
3005
3006
	protected function popBlock() {
3007
		if (empty($this->env->parent)) {
3008
			$this->throwParseError("unexpected }");
3009
		}
3010
3011
		$old = $this->env;
3012
		$this->env = $this->env->parent;
3013
		unset($old->parent);
3014
		return $old;
3015
	}
3016
3017
	protected function append($statement, $pos=null) {
3018
		if ($pos !== null) {
3019
			$statement[-1] = $pos;
3020
			if (!$this->rootParser) $statement[-2] = $this;
3021
		}
3022
		$this->env->children[] = $statement;
3023
	}
3024
3025
	// last child that was appended
3026
	protected function last() {
3027
		$i = count($this->env->children) - 1;
3028
		if (isset($this->env->children[$i]))
3029
			return $this->env->children[$i];
3030
	}
3031
3032
	// high level parsers (they return parts of ast)
3033
3034
	protected function mediaQueryList(&$out) {
3035
		return $this->genericList($out, "mediaQuery", ",", false);
3036
	}
3037
3038
	protected function mediaQuery(&$out) {
3039
		$s = $this->seek();
0 ignored issues
show
Unused Code introduced by
$s is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
3040
3041
		$expressions = null;
3042
		$parts = array();
3043
3044
		if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->mixedKeyword($mediaType)) {
3045
			$prop = array("mediaType");
3046
			if (isset($only)) $prop[] = array("keyword", "only");
3047
			if (isset($not)) $prop[] = array("keyword", "not");
3048
			$media = array("list", "", array());
3049
			foreach ((array)$mediaType as $type) {
3050
				if (is_array($type)) {
3051
					$media[2][] = $type;
3052
				} else {
3053
					$media[2][] = array("keyword", $type);
3054
				}
3055
			}
3056
			$prop[] = $media;
3057
			$parts[] = $prop;
3058
		}
3059
3060 View Code Duplication
		if (empty($parts) || $this->literal("and")) {
3061
			$this->genericList($expressions, "mediaExpression", "and", false);
3062
			if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
3063
		}
3064
3065
		$out = $parts;
3066
		return true;
3067
	}
3068
3069
	protected function mediaExpression(&$out) {
3070
		$s = $this->seek();
3071
		$value = null;
3072
		if ($this->literal("(") &&
3073
			$this->expression($feature) &&
3074
			($this->literal(":") && $this->expression($value) || true) &&
3075
			$this->literal(")"))
3076
		{
3077
			$out = array("mediaExp", $feature);
3078
			if ($value) $out[] = $value;
3079
			return true;
3080
		}
3081
3082
		$this->seek($s);
3083
		return false;
3084
	}
3085
3086
	protected function argValues(&$out) {
3087
		if ($this->genericList($list, "argValue", ",", false)) {
3088
			$out = $list[2];
3089
			return true;
3090
		}
3091
		return false;
3092
	}
3093
3094
	protected function argValue(&$out) {
3095
		$s = $this->seek();
3096
3097
		$keyword = null;
3098
		if (!$this->variable($keyword) || !$this->literal(":")) {
3099
			$this->seek($s);
3100
			$keyword = null;
3101
		}
3102
3103
		if ($this->genericList($value, "expression")) {
3104
			$out = array($keyword, $value, false);
3105
			$s = $this->seek();
3106
			if ($this->literal("...")) {
3107
				$out[2] = true;
3108
			} else {
3109
				$this->seek($s);
3110
			}
3111
			return true;
3112
		}
3113
3114
		return false;
3115
	}
3116
3117
3118
	protected function valueList(&$out) {
3119
		return $this->genericList($out, "spaceList", ",");
3120
	}
3121
3122
	protected function spaceList(&$out) {
3123
		return $this->genericList($out, "expression");
3124
	}
3125
3126 View Code Duplication
	protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
3127
		$s = $this->seek();
3128
		$items = array();
3129
		while ($this->$parseItem($value)) {
0 ignored issues
show
Bug introduced by
The variable $value does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
3130
			$items[] = $value;
3131
			if ($delim) {
3132
				if (!$this->literal($delim)) break;
3133
			}
3134
		}
3135
3136
		if (count($items) == 0) {
3137
			$this->seek($s);
3138
			return false;
3139
		}
3140
3141
		if ($flatten && count($items) == 1) {
3142
			$out = $items[0];
3143
		} else {
3144
			$out = array("list", $delim, $items);
3145
		}
3146
3147
		return true;
3148
	}
3149
3150
	protected function expression(&$out) {
3151
		$s = $this->seek();
3152
3153
		if ($this->literal("(")) {
3154
			if ($this->literal(")")) {
3155
				$out = array("list", "", array());
3156
				return true;
3157
			}
3158
3159
			if ($this->valueList($out) && $this->literal(')') && $out[0] == "list") {
3160
				return true;
3161
			}
3162
3163
			$this->seek($s);
3164
		}
3165
3166
		if ($this->value($lhs)) {
3167
			$out = $this->expHelper($lhs, 0);
3168
			return true;
3169
		}
3170
3171
		return false;
3172
	}
3173
3174
	protected function expHelper($lhs, $minP) {
3175
		$opstr = self::$operatorStr;
3176
3177
		$ss = $this->seek();
3178
		$whiteBefore = isset($this->buffer[$this->count - 1]) &&
3179
			ctype_space($this->buffer[$this->count - 1]);
3180
		while ($this->match($opstr, $m) && self::$precedence[$m[1]] >= $minP) {
3181
			$whiteAfter = isset($this->buffer[$this->count - 1]) &&
3182
				ctype_space($this->buffer[$this->count - 1]);
3183
3184
			$op = $m[1];
3185
3186
			// don't turn negative numbers into expressions
3187
			if ($op == "-" && $whiteBefore) {
3188
				if (!$whiteAfter) break;
3189
			}
3190
3191
			if (!$this->value($rhs)) break;
3192
3193
			// peek and see if rhs belongs to next operator
3194
			if ($this->peek($opstr, $next) && self::$precedence[$next[1]] > self::$precedence[$op]) {
3195
				$rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
3196
			}
3197
3198
			$lhs = array("exp", $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter);
3199
			$ss = $this->seek();
3200
			$whiteBefore = isset($this->buffer[$this->count - 1]) &&
3201
				ctype_space($this->buffer[$this->count - 1]);
3202
		}
3203
3204
		$this->seek($ss);
3205
		return $lhs;
3206
	}
3207
3208
	protected function value(&$out) {
3209
		$s = $this->seek();
3210
3211 View Code Duplication
		if ($this->literal("not", false) && $this->whitespace() && $this->value($inner)) {
3212
			$out = array("unary", "not", $inner, $this->inParens);
3213
			return true;
3214
		} else {
3215
			$this->seek($s);
3216
		}
3217
3218 View Code Duplication
		if ($this->literal("+") && $this->value($inner)) {
3219
			$out = array("unary", "+", $inner, $this->inParens);
3220
			return true;
3221
		} else {
3222
			$this->seek($s);
3223
		}
3224
3225
		// negation
3226
		if ($this->literal("-", false) &&
3227
			($this->variable($inner) ||
3228
			$this->unit($inner) ||
3229
			$this->parenValue($inner)))
3230
		{
3231
			$out = array("unary", "-", $inner, $this->inParens);
3232
			return true;
3233
		} else {
3234
			$this->seek($s);
3235
		}
3236
3237
		if ($this->parenValue($out)) return true;
3238
		if ($this->interpolation($out)) return true;
3239
		if ($this->variable($out)) return true;
3240
		if ($this->color($out)) return true;
3241
		if ($this->unit($out)) return true;
3242
		if ($this->string($out)) return true;
3243
		if ($this->func($out)) return true;
3244
		if ($this->progid($out)) return true;
3245
3246
		if ($this->keyword($keyword)) {
3247
			if ($keyword == "null") {
3248
				$out = array("null");
3249
			} else {
3250
				$out = array("keyword", $keyword);
3251
			}
3252
			return true;
3253
		}
3254
3255
		return false;
3256
	}
3257
3258
	// value wrappen in parentheses
3259
	protected function parenValue(&$out) {
3260
		$s = $this->seek();
3261
3262
		$inParens = $this->inParens;
3263 View Code Duplication
		if ($this->literal("(") &&
3264
			($this->inParens = true) && $this->expression($exp) &&
3265
			$this->literal(")"))
3266
		{
3267
			$out = $exp;
3268
			$this->inParens = $inParens;
3269
			return true;
3270
		} else {
3271
			$this->inParens = $inParens;
3272
			$this->seek($s);
3273
		}
3274
3275
		return false;
3276
	}
3277
3278
	protected function progid(&$out) {
3279
		$s = $this->seek();
3280
		if ($this->literal("progid:", false) &&
3281
			$this->openString("(", $fn) &&
3282
			$this->literal("("))
3283
		{
3284
			$this->openString(")", $args, "(");
3285
			if ($this->literal(")")) {
3286
				$out = array("string", "", array(
3287
					"progid:", $fn, "(", $args, ")"
3288
				));
3289
				return true;
3290
			}
3291
		}
3292
3293
		$this->seek($s);
3294
		return false;
3295
	}
3296
3297
	protected function func(&$func) {
3298
		$s = $this->seek();
3299
3300
		if ($this->keyword($name, false) &&
3301
			$this->literal("("))
3302
		{
3303
			if ($name == "alpha" && $this->argumentList($args)) {
3304
				$func = array("function", $name, array("string", "", $args));
3305
				return true;
3306
			}
3307
3308
			if ($name != "expression" && !preg_match("/^(-[a-z]+-)?calc$/", $name)) {
3309
				$ss = $this->seek();
3310
				if ($this->argValues($args) && $this->literal(")")) {
3311
					$func = array("fncall", $name, $args);
3312
					return true;
3313
				}
3314
				$this->seek($ss);
3315
			}
3316
3317
			if (($this->openString(")", $str, "(") || true ) &&
3318
				$this->literal(")"))
3319
			{
3320
				$args = array();
3321
				if (!empty($str)) {
3322
					$args[] = array(null, array("string", "", array($str)));
3323
				}
3324
3325
				$func = array("fncall", $name, $args);
3326
				return true;
3327
			}
3328
		}
3329
3330
		$this->seek($s);
3331
		return false;
3332
	}
3333
3334
	protected function argumentList(&$out) {
3335
		$s = $this->seek();
3336
		$this->literal("(");
3337
3338
		$args = array();
3339
		while ($this->keyword($var)) {
3340
			$ss = $this->seek();
0 ignored issues
show
Unused Code introduced by
$ss is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
3341
3342
			if ($this->literal("=") && $this->expression($exp)) {
3343
				$args[] = array("string", "", array($var."="));
3344
				$arg = $exp;
3345
			} else {
3346
				break;
3347
			}
3348
3349
			$args[] = $arg;
3350
3351
			if (!$this->literal(",")) break;
3352
3353
			$args[] = array("string", "", array(", "));
3354
		}
3355
3356
		if (!$this->literal(")") || !count($args)) {
3357
			$this->seek($s);
3358
			return false;
3359
		}
3360
3361
		$out = $args;
3362
		return true;
3363
	}
3364
3365
	protected function argumentDef(&$out) {
3366
		$s = $this->seek();
3367
		$this->literal("(");
3368
3369
		$args = array();
3370
		while ($this->variable($var)) {
3371
			$arg = array($var[1], null, false);
3372
3373
			$ss = $this->seek();
3374
			if ($this->literal(":") && $this->genericList($defaultVal, "expression")) {
3375
				$arg[1] = $defaultVal;
3376
			} else {
3377
				$this->seek($ss);
3378
			}
3379
3380
			$ss = $this->seek();
3381
			if ($this->literal("...")) {
3382
				$sss = $this->seek();
3383
				if (!$this->literal(")")) {
3384
					$this->throwParseError("... has to be after the final argument");
3385
				}
3386
				$arg[2] = true;
3387
				$this->seek($sss);
3388
			} else {
3389
				$this->seek($ss);
3390
			}
3391
3392
			$args[] = $arg;
3393
			if (!$this->literal(",")) break;
3394
		}
3395
3396
		if (!$this->literal(")")) {
3397
			$this->seek($s);
3398
			return false;
3399
		}
3400
3401
		$out = $args;
3402
		return true;
3403
	}
3404
3405
	protected function color(&$out) {
3406
		$color = array('color');
3407
3408
		if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) {
3409
			if (isset($m[3])) {
3410
				$num = $m[3];
3411
				$width = 16;
3412
			} else {
3413
				$num = $m[2];
3414
				$width = 256;
3415
			}
3416
3417
			$num = hexdec($num);
3418
			foreach (array(3,2,1) as $i) {
3419
				$t = $num % $width;
3420
				$num /= $width;
3421
3422
				$color[$i] = $t * (256/$width) + $t * floor(16/$width);
3423
			}
3424
3425
			$out = $color;
3426
			return true;
3427
		}
3428
3429
		return false;
3430
	}
3431
3432
	protected function unit(&$unit) {
3433 View Code Duplication
		if ($this->match('([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m)) {
3434
			$unit = array("number", $m[1], empty($m[3]) ? "" : $m[3]);
3435
			return true;
3436
		}
3437
		return false;
3438
	}
3439
3440
	protected function string(&$out) {
3441
		$s = $this->seek();
3442 View Code Duplication
		if ($this->literal('"', false)) {
3443
			$delim = '"';
3444
		} elseif ($this->literal("'", false)) {
3445
			$delim = "'";
3446
		} else {
3447
			return false;
3448
		}
3449
3450
		$content = array();
3451
		$oldWhite = $this->eatWhiteDefault;
3452
		$this->eatWhiteDefault = false;
3453
3454 View Code Duplication
		while ($this->matchString($m, $delim)) {
3455
			$content[] = $m[1];
3456
			if ($m[2] == "#{") {
3457
				$this->count -= strlen($m[2]);
3458
				if ($this->interpolation($inter, false)) {
3459
					$content[] = $inter;
3460
				} else {
3461
					$this->count += strlen($m[2]);
3462
					$content[] = "#{"; // ignore it
3463
				}
3464
			} elseif ($m[2] == '\\') {
3465
				$content[] = $m[2];
3466
				if ($this->literal($delim, false)) {
3467
					$content[] = $delim;
3468
				}
3469
			} else {
3470
				$this->count -= strlen($delim);
3471
				break; // delim
3472
			}
3473
		}
3474
3475
		$this->eatWhiteDefault = $oldWhite;
3476
3477 View Code Duplication
		if ($this->literal($delim)) {
3478
			$out = array("string", $delim, $content);
3479
			return true;
3480
		}
3481
3482
		$this->seek($s);
3483
		return false;
3484
	}
3485
3486
	protected function mixedKeyword(&$out) {
3487
		$s = $this->seek();
0 ignored issues
show
Unused Code introduced by
$s is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
3488
3489
		$parts = array();
3490
3491
		$oldWhite = $this->eatWhiteDefault;
3492
		$this->eatWhiteDefault = false;
3493
3494
		while (true) {
3495
			if ($this->keyword($key)) {
3496
				$parts[] = $key;
3497
				continue;
3498
			}
3499
3500
			if ($this->interpolation($inter)) {
3501
				$parts[] = $inter;
3502
				continue;
3503
			}
3504
3505
			break;
3506
		}
3507
3508
		$this->eatWhiteDefault = $oldWhite;
3509
3510
		if (count($parts) == 0) return false;
3511
3512
		if ($this->eatWhiteDefault) {
3513
			$this->whitespace();
3514
		}
3515
3516
		$out = $parts;
3517
		return true;
3518
	}
3519
3520
	// an unbounded string stopped by $end
3521
	protected function openString($end, &$out, $nestingOpen=null) {
3522
		$oldWhite = $this->eatWhiteDefault;
3523
		$this->eatWhiteDefault = false;
3524
3525
		$stop = array("'", '"', "#{", $end);
3526
		$stop = array_map(array($this, "preg_quote"), $stop);
3527
		$stop[] = self::$commentMulti;
3528
3529
		$patt = '(.*?)('.implode("|", $stop).')';
3530
3531
		$nestingLevel = 0;
3532
3533
		$content = array();
3534
		while ($this->match($patt, $m, false)) {
3535
			if (isset($m[1]) && $m[1] !== '') {
3536
				$content[] = $m[1];
3537
				if ($nestingOpen) {
3538
					$nestingLevel += substr_count($m[1], $nestingOpen);
3539
				}
3540
			}
3541
3542
			$tok = $m[2];
3543
3544
			$this->count-= strlen($tok);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 0 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
3545
			if ($tok == $end) {
3546
				if ($nestingLevel == 0) {
3547
					break;
3548
				} else {
3549
					$nestingLevel--;
3550
				}
3551
			}
3552
3553 View Code Duplication
			if (($tok == "'" || $tok == '"') && $this->string($str)) {
3554
				$content[] = $str;
3555
				continue;
3556
			}
3557
3558
			if ($tok == "#{" && $this->interpolation($inter)) {
3559
				$content[] = $inter;
3560
				continue;
3561
			}
3562
3563
			$content[] = $tok;
3564
			$this->count+= strlen($tok);
3565
		}
3566
3567
		$this->eatWhiteDefault = $oldWhite;
3568
3569
		if (count($content) == 0) return false;
3570
3571
		// trim the end
3572 View Code Duplication
		if (is_string(end($content))) {
3573
			$content[count($content) - 1] = rtrim(end($content));
3574
		}
3575
3576
		$out = array("string", "", $content);
3577
		return true;
3578
	}
3579
3580
	// $lookWhite: save information about whitespace before and after
3581
	protected function interpolation(&$out, $lookWhite=true) {
3582
		$oldWhite = $this->eatWhiteDefault;
3583
		$this->eatWhiteDefault = true;
3584
3585
		$s = $this->seek();
3586
		if ($this->literal("#{") && $this->valueList($value) && $this->literal("}", false)) {
3587
3588
			// TODO: don't error if out of bounds
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
3589
3590
			if ($lookWhite) {
3591
				$left = preg_match('/\s/', $this->buffer[$s - 1]) ? " " : "";
3592
				$right = preg_match('/\s/', $this->buffer[$this->count]) ? " ": "";
3593
			} else {
3594
				$left = $right = false;
3595
			}
3596
3597
			$out = array("interpolate", $value, $left, $right);
3598
			$this->eatWhiteDefault = $oldWhite;
3599
			if ($this->eatWhiteDefault) $this->whitespace();
3600
			return true;
3601
		}
3602
3603
		$this->seek($s);
3604
		$this->eatWhiteDefault = $oldWhite;
3605
		return false;
3606
	}
3607
3608
	// low level parsers
3609
3610
	// returns an array of parts or a string
3611
	protected function propertyName(&$out) {
3612
		$s = $this->seek();
0 ignored issues
show
Unused Code introduced by
$s is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
3613
		$parts = array();
3614
3615
		$oldWhite = $this->eatWhiteDefault;
3616
		$this->eatWhiteDefault = false;
3617
3618
		while (true) {
3619
			if ($this->interpolation($inter)) {
3620
				$parts[] = $inter;
3621
			} elseif ($this->keyword($text)) {
3622
				$parts[] = $text;
3623
			} elseif (count($parts) == 0 && $this->match('[:.#]', $m, false)) {
3624
				// css hacks
3625
				$parts[] = $m[0];
3626
			} else {
3627
				break;
3628
			}
3629
		}
3630
3631
		$this->eatWhiteDefault = $oldWhite;
3632
		if (count($parts) == 0) return false;
3633
3634
		// match comment hack
3635
		if (preg_match(self::$whitePattern,
3636
			$this->buffer, $m, null, $this->count))
3637
		{
3638
			if (!empty($m[0])) {
3639
				$parts[] = $m[0];
3640
				$this->count += strlen($m[0]);
3641
			}
3642
		}
3643
3644
		$this->whitespace(); // get any extra whitespace
3645
3646
		$out = array("string", "", $parts);
3647
		return true;
3648
	}
3649
3650
	// comma separated list of selectors
3651 View Code Duplication
	protected function selectors(&$out) {
3652
		$s = $this->seek();
3653
		$selectors = array();
3654
		while ($this->selector($sel)) {
3655
			$selectors[] = $sel;
3656
			if (!$this->literal(",")) break;
3657
			while ($this->literal(",")); // ignore extra
3658
		}
3659
3660
		if (count($selectors) == 0) {
3661
			$this->seek($s);
3662
			return false;
3663
		}
3664
3665
		$out = $selectors;
3666
		return true;
3667
	}
3668
3669
	// whitespace separated list of selectorSingle
3670
	protected function selector(&$out) {
3671
		$selector = array();
3672
3673
		while (true) {
3674
			if ($this->match('[>+~]+', $m)) {
3675
				$selector[] = array($m[0]);
3676
			} elseif ($this->selectorSingle($part)) {
3677
				$selector[] = $part;
3678
				$this->whitespace();
3679
			} elseif ($this->match('\/[^\/]+\/', $m)) {
3680
				$selector[] = array($m[0]);
3681
			} else {
3682
				break;
3683
			}
3684
3685
		}
3686
3687
		if (count($selector) == 0) {
3688
			return false;
3689
		}
3690
3691
		$out = $selector;
3692
		return true;
3693
	}
3694
3695
	// the parts that make up
3696
	// div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
3697
	protected function selectorSingle(&$out) {
3698
		$oldWhite = $this->eatWhiteDefault;
3699
		$this->eatWhiteDefault = false;
3700
3701
		$parts = array();
3702
3703
		if ($this->literal("*", false)) {
3704
			$parts[] = "*";
3705
		}
3706
3707
		while (true) {
3708
			// see if we can stop early
3709
			if ($this->match("\s*[{,]", $m)) {
3710
				$this->count--;
3711
				break;
3712
			}
3713
3714
			$s = $this->seek();
3715
			// self
3716
			if ($this->literal("&", false)) {
3717
				$parts[] = scssc::$selfSelector;
3718
				continue;
3719
			}
3720
3721
			if ($this->literal(".", false)) {
3722
				$parts[] = ".";
3723
				continue;
3724
			}
3725
3726
			if ($this->literal("|", false)) {
3727
				$parts[] = "|";
3728
				continue;
3729
			}
3730
3731
			// for keyframes
3732
			if ($this->unit($unit)) {
3733
				$parts[] = $unit;
3734
				continue;
3735
			}
3736
3737
			if ($this->keyword($name)) {
3738
				$parts[] = $name;
3739
				continue;
3740
			}
3741
3742
			if ($this->interpolation($inter)) {
3743
				$parts[] = $inter;
3744
				continue;
3745
			}
3746
3747
			if ($this->literal('%', false) && $this->placeholder($placeholder)) {
3748
				$parts[] = '%';
3749
				$parts[] = $placeholder;
3750
				continue;
3751
			}
3752
3753
			if ($this->literal("#", false)) {
3754
				$parts[] = "#";
3755
				continue;
3756
			}
3757
3758
			// a pseudo selector
3759
			if ($this->match("::?", $m) && $this->mixedKeyword($nameParts)) {
3760
				$parts[] = $m[0];
3761
				foreach ($nameParts as $sub) {
3762
					$parts[] = $sub;
3763
				}
3764
3765
				$ss = $this->seek();
3766
				if ($this->literal("(") &&
3767
					($this->openString(")", $str, "(") || true ) &&
3768
					$this->literal(")"))
3769
				{
3770
					$parts[] = "(";
3771
					if (!empty($str)) $parts[] = $str;
3772
					$parts[] = ")";
3773
				} else {
3774
					$this->seek($ss);
3775
				}
3776
3777
				continue;
3778
			} else {
3779
				$this->seek($s);
3780
			}
3781
3782
			// attribute selector
3783
			// TODO: replace with open string?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
3784
			if ($this->literal("[", false)) {
3785
				$attrParts = array("[");
3786
				// keyword, string, operator
3787
				while (true) {
3788
					if ($this->literal("]", false)) {
3789
						$this->count--;
3790
						break; // get out early
3791
					}
3792
3793
					if ($this->match('\s+', $m)) {
3794
						$attrParts[] = " ";
3795
						continue;
3796
					}
3797
					if ($this->string($str)) {
3798
						$attrParts[] = $str;
3799
						continue;
3800
					}
3801
3802
					if ($this->keyword($word)) {
3803
						$attrParts[] = $word;
3804
						continue;
3805
					}
3806
3807
					if ($this->interpolation($inter, false)) {
3808
						$attrParts[] = $inter;
3809
						continue;
3810
					}
3811
3812
					// operator, handles attr namespace too
3813
					if ($this->match('[|-~\$\*\^=]+', $m)) {
3814
						$attrParts[] = $m[0];
3815
						continue;
3816
					}
3817
3818
					break;
3819
				}
3820
3821
				if ($this->literal("]", false)) {
3822
					$attrParts[] = "]";
3823
					foreach ($attrParts as $part) {
3824
						$parts[] = $part;
3825
					}
3826
					continue;
3827
				}
3828
				$this->seek($s);
3829
				// should just break here?
3830
			}
3831
3832
			break;
3833
		}
3834
3835
		$this->eatWhiteDefault = $oldWhite;
3836
3837
		if (count($parts) == 0) return false;
3838
3839
		$out = $parts;
3840
		return true;
3841
	}
3842
3843
	protected function variable(&$out) {
3844
		$s = $this->seek();
3845
		if ($this->literal("$", false) && $this->keyword($name)) {
3846
			$out = array("var", $name);
3847
			return true;
3848
		}
3849
		$this->seek($s);
3850
		return false;
3851
	}
3852
3853 View Code Duplication
	protected function keyword(&$word, $eatWhitespace = null) {
3854
		if ($this->match('([\w_\-\*!"\'\\\\][\w\-_"\'\\\\]*)',
3855
			$m, $eatWhitespace))
3856
		{
3857
			$word = $m[1];
3858
			return true;
3859
		}
3860
		return false;
3861
	}
3862
3863 View Code Duplication
	protected function placeholder(&$placeholder) {
3864
		if ($this->match('([\w\-_]+)', $m)) {
3865
			$placeholder = $m[1];
3866
			return true;
3867
		}
3868
		return false;
3869
	}
3870
3871
	// consume an end of statement delimiter
3872 View Code Duplication
	protected function end() {
3873
		if ($this->literal(';')) {
3874
			return true;
3875
		} elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
3876
			// if there is end of file or a closing block next then we don't need a ;
3877
			return true;
3878
		}
3879
		return false;
3880
	}
3881
3882
	// advance counter to next occurrence of $what
3883
	// $until - don't include $what in advance
3884
	// $allowNewline, if string, will be used as valid char set
3885 View Code Duplication
	protected function to($what, &$out, $until = false, $allowNewline = false) {
3886
		if (is_string($allowNewline)) {
3887
			$validChars = $allowNewline;
3888
		} else {
3889
			$validChars = $allowNewline ? "." : "[^\n]";
3890
		}
3891
		if (!$this->match('('.$validChars.'*?)'.$this->preg_quote($what), $m, !$until)) return false;
3892
		if ($until) $this->count -= strlen($what); // give back $what
3893
		$out = $m[1];
3894
		return true;
3895
	}
3896
3897
	public function throwParseError($msg = "parse error", $count = null) {
3898
		$count = is_null($count) ? $this->count : $count;
3899
3900
		$line = $this->getLineNo($count);
3901
3902 View Code Duplication
		if (!empty($this->sourceName)) {
3903
			$loc = "$this->sourceName on line $line";
3904
		} else {
3905
			$loc = "line: $line";
3906
		}
3907
3908 View Code Duplication
		if ($this->peek("(.*?)(\n|$)", $m, $count)) {
3909
			throw new Exception("$msg: failed at `$m[1]` $loc");
3910
		} else {
3911
			throw new Exception("$msg: $loc");
3912
		}
3913
	}
3914
3915
	public function getLineNo($pos) {
3916
		return 1 + substr_count(substr($this->buffer, 0, $pos), "\n");
3917
	}
3918
3919
	/**
3920
	 * Match string looking for either ending delim, escape, or string interpolation
3921
	 *
3922
	 * {@internal This is a workaround for preg_match's 250K string match limit. }}
3923
	 *
3924
	 * @param array  $m     Matches (passed by reference)
3925
	 * @param string $delim Delimeter
3926
	 *
3927
	 * @return boolean True if match; false otherwise
3928
	 */
3929
	protected function matchString(&$m, $delim) {
3930
		$token = null;
3931
3932
		$end = strpos($this->buffer, "\n", $this->count);
3933
		if ($end === false) {
3934
			$end = strlen($this->buffer);
3935
		}
3936
3937
		// look for either ending delim, escape, or string interpolation
3938
		foreach (array('#{', '\\', $delim) as $lookahead) {
3939
			$pos = strpos($this->buffer, $lookahead, $this->count);
3940
			if ($pos !== false && $pos < $end) {
3941
				$end = $pos;
3942
				$token = $lookahead;
3943
			}
3944
		}
3945
3946
		if (!isset($token)) {
3947
			return false;
3948
		}
3949
3950
		$match = substr($this->buffer, $this->count, $end - $this->count);
3951
		$m = array(
3952
			$match . $token,
3953
			$match,
3954
			$token
3955
		);
3956
		$this->count = $end + strlen($token);
3957
3958
		return true;
3959
	}
3960
3961
	// try to match something on head of buffer
3962
	protected function match($regex, &$out, $eatWhitespace = null) {
3963
		if (is_null($eatWhitespace)) $eatWhitespace = $this->eatWhiteDefault;
3964
3965
		$r = '/'.$regex.'/Ais';
3966 View Code Duplication
		if (preg_match($r, $this->buffer, $out, null, $this->count)) {
3967
			$this->count += strlen($out[0]);
3968
			if ($eatWhitespace) $this->whitespace();
3969
			return true;
3970
		}
3971
		return false;
3972
	}
3973
3974
	// match some whitespace
3975
	protected function whitespace() {
3976
		$gotWhite = false;
3977 View Code Duplication
		while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
3978
			if ($this->insertComments) {
3979
				if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
3980
					$this->append(array("comment", $m[1]));
3981
					$this->commentsSeen[$this->count] = true;
0 ignored issues
show
Bug introduced by
The property commentsSeen 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...
3982
				}
3983
			}
3984
			$this->count += strlen($m[0]);
3985
			$gotWhite = true;
3986
		}
3987
		return $gotWhite;
3988
	}
3989
3990 View Code Duplication
	protected function peek($regex, &$out, $from=null) {
3991
		if (is_null($from)) $from = $this->count;
3992
3993
		$r = '/'.$regex.'/Ais';
3994
		$result = preg_match($r, $this->buffer, $out, null, $from);
3995
3996
		return $result;
3997
	}
3998
3999
	protected function seek($where = null) {
4000
		if ($where === null) return $this->count;
4001
		else $this->count = $where;
4002
		return true;
4003
	}
4004
4005
	static function preg_quote($what) {
4006
		return preg_quote($what, '/');
4007
	}
4008
4009
	protected function show() {
4010
		if ($this->peek("(.*?)(\n|$)", $m, $this->count)) {
4011
			return $m[1];
4012
		}
4013
		return "";
4014
	}
4015
4016
	// turn list of length 1 into value type
4017
	protected function flattenList($value) {
4018 View Code Duplication
		if ($value[0] == "list" && count($value[2]) == 1) {
4019
			return $this->flattenList($value[2][0]);
4020
		}
4021
		return $value;
4022
	}
4023
}
4024
4025
/**
4026
 * SCSS base formatter
4027
 *
4028
 * @author Leaf Corcoran <[email protected]>
4029
 */
4030
class scss_formatter {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
4031
	public $indentChar = "  ";
4032
4033
	public $break = "\n";
4034
	public $open = " {";
4035
	public $close = "}";
4036
	public $tagSeparator = ", ";
4037
	public $assignSeparator = ": ";
4038
4039
	public function __construct() {
4040
		$this->indentLevel = 0;
0 ignored issues
show
Bug introduced by
The property indentLevel 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...
4041
	}
4042
4043
	public function indentStr($n = 0) {
4044
		return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
4045
	}
4046
4047
	public function property($name, $value) {
4048
		return $name . $this->assignSeparator . $value . ";";
4049
	}
4050
4051
	protected function block($block) {
4052
		if (empty($block->lines) && empty($block->children)) return;
4053
4054
		$inner = $pre = $this->indentStr();
4055
4056 View Code Duplication
		if (!empty($block->selectors)) {
4057
			echo $pre .
4058
				implode($this->tagSeparator, $block->selectors) .
4059
				$this->open . $this->break;
4060
			$this->indentLevel++;
4061
			$inner = $this->indentStr();
4062
		}
4063
4064 View Code Duplication
		if (!empty($block->lines)) {
4065
			$glue = $this->break.$inner;
4066
			echo $inner . implode($glue, $block->lines);
4067
			if (!empty($block->children)) {
4068
				echo $this->break;
4069
			}
4070
		}
4071
4072
		foreach ($block->children as $child) {
4073
			$this->block($child);
4074
		}
4075
4076
		if (!empty($block->selectors)) {
4077
			$this->indentLevel--;
4078
			if (empty($block->children)) echo $this->break;
4079
			echo $pre . $this->close . $this->break;
4080
		}
4081
	}
4082
4083
	public function format($block) {
4084
		ob_start();
4085
		$this->block($block);
4086
		$out = ob_get_clean();
4087
4088
		return $out;
4089
	}
4090
}
4091
4092
/**
4093
 * SCSS nested formatter
4094
 *
4095
 * @author Leaf Corcoran <[email protected]>
4096
 */
4097
class scss_formatter_nested extends scss_formatter {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
4098
	public $close = " }";
4099
4100
	// adjust the depths of all children, depth first
4101
	public function adjustAllChildren($block) {
4102
		// flatten empty nested blocks
4103
		$children = array();
4104
		foreach ($block->children as $i => $child) {
4105
			if (empty($child->lines) && empty($child->children)) {
4106
				if (isset($block->children[$i + 1])) {
4107
					$block->children[$i + 1]->depth = $child->depth;
4108
				}
4109
				continue;
4110
			}
4111
			$children[] = $child;
4112
		}
4113
4114
		$count = count($children);
4115
		for ($i = 0; $i < $count; $i++) {
4116
			$depth = $children[$i]->depth;
4117
			$j = $i + 1;
4118
			if (isset($children[$j]) && $depth < $children[$j]->depth) {
4119
				$childDepth = $children[$j]->depth;
4120
				for (; $j < $count; $j++) {
4121
					if ($depth < $children[$j]->depth && $childDepth >= $children[$j]->depth) {
4122
						$children[$j]->depth = $depth + 1;
4123
					}
4124
				}
4125
			}
4126
		}
4127
4128
		$block->children = $children;
4129
4130
		// make relative to parent
4131
		foreach ($block->children as $child) {
4132
			$this->adjustAllChildren($child);
4133
			$child->depth = $child->depth - $block->depth;
4134
		}
4135
	}
4136
4137
	protected function block($block) {
4138
		if ($block->type == "root") {
4139
			$this->adjustAllChildren($block);
4140
		}
4141
4142
		$inner = $pre = $this->indentStr($block->depth - 1);
4143 View Code Duplication
		if (!empty($block->selectors)) {
4144
			echo $pre .
4145
				implode($this->tagSeparator, $block->selectors) .
4146
				$this->open . $this->break;
4147
			$this->indentLevel++;
0 ignored issues
show
Bug introduced by
The property indentLevel 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...
4148
			$inner = $this->indentStr($block->depth - 1);
4149
		}
4150
4151 View Code Duplication
		if (!empty($block->lines)) {
4152
			$glue = $this->break.$inner;
4153
			echo $inner . implode($glue, $block->lines);
4154
			if (!empty($block->children)) echo $this->break;
4155
		}
4156
4157
		foreach ($block->children as $i => $child) {
4158
			// echo "*** block: ".$block->depth." child: ".$child->depth."\n";
4159
			$this->block($child);
4160
			if ($i < count($block->children) - 1) {
4161
				echo $this->break;
4162
4163
				if (isset($block->children[$i + 1])) {
4164
					$next = $block->children[$i + 1];
4165
					if ($next->depth == max($block->depth, 1) && $child->depth >= $next->depth) {
4166
						echo $this->break;
4167
					}
4168
				}
4169
			}
4170
		}
4171
4172
		if (!empty($block->selectors)) {
4173
			$this->indentLevel--;
4174
			echo $this->close;
4175
		}
4176
4177
		if ($block->type == "root") {
4178
			echo $this->break;
4179
		}
4180
	}
4181
}
4182
4183
/**
4184
 * SCSS compressed formatter
4185
 *
4186
 * @author Leaf Corcoran <[email protected]>
4187
 */
4188
class scss_formatter_compressed extends scss_formatter {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
4189
	public $open = "{";
4190
	public $tagSeparator = ",";
4191
	public $assignSeparator = ":";
4192
	public $break = "";
4193
4194
	public function indentStr($n = 0) {
4195
		return "";
4196
	}
4197
}
4198
4199
/**
4200
 * SCSS server
4201
 *
4202
 * @author Leaf Corcoran <[email protected]>
4203
 */
4204
class scss_server {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
4205
	/**
4206
	 * Join path components
4207
	 *
4208
	 * @param string $left  Path component, left of the directory separator
4209
	 * @param string $right Path component, right of the directory separator
4210
	 *
4211
	 * @return string
4212
	 */
4213
	protected function join($left, $right) {
4214
		return rtrim($left, '/\\') . DIRECTORY_SEPARATOR . ltrim($right, '/\\');
4215
	}
4216
4217
	/**
4218
	 * Get name of requested .scss file
4219
	 *
4220
	 * @return string|null
4221
	 */
4222
	protected function inputName() {
4223
		switch (true) {
4224
			case isset($_GET['p']):
4225
				return $_GET['p'];
4226
			case isset($_SERVER['PATH_INFO']):
4227
				return $_SERVER['PATH_INFO'];
4228
			case isset($_SERVER['DOCUMENT_URI']):
4229
				return substr($_SERVER['DOCUMENT_URI'], strlen($_SERVER['SCRIPT_NAME']));
4230
		}
4231
	}
4232
4233
	/**
4234
	 * Get path to requested .scss file
4235
	 *
4236
	 * @return string
4237
	 */
4238
	protected function findInput() {
4239
		if (($input = $this->inputName())
4240
			&& strpos($input, '..') === false
4241
			&& substr($input, -5) === '.scss'
4242
		) {
4243
			$name = $this->join($this->dir, $input);
0 ignored issues
show
Bug introduced by
The property dir 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...
4244
4245
			if (is_file($name) && is_readable($name)) {
4246
				return $name;
4247
			}
4248
		}
4249
4250
		return false;
4251
	}
4252
4253
	/**
4254
	 * Get path to cached .css file
4255
	 *
4256
	 * @return string
4257
	 */
4258
	protected function cacheName($fname) {
4259
		return $this->join($this->cacheDir, md5($fname) . '.css');
0 ignored issues
show
Bug introduced by
The property cacheDir 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...
4260
	}
4261
4262
	/**
4263
	 * Get path to cached imports
4264
	 *
4265
	 * @return string
4266
	 */
4267
	protected function importsCacheName($out) {
4268
		return $out . '.imports';
4269
	}
4270
4271
	/**
4272
	 * Determine whether .scss file needs to be re-compiled.
4273
	 *
4274
	 * @param string $in  Input path
4275
	 * @param string $out Output path
4276
	 *
4277
	 * @return boolean True if compile required.
4278
	 */
4279
	protected function needsCompile($in, $out) {
4280
		if (!is_file($out)) return true;
4281
4282
		$mtime = filemtime($out);
4283
		if (filemtime($in) > $mtime) return true;
4284
4285
		// look for modified imports
4286
		$icache = $this->importsCacheName($out);
4287
		if (is_readable($icache)) {
4288
			$imports = unserialize(file_get_contents($icache));
4289
			foreach ($imports as $import) {
4290
				if (filemtime($import) > $mtime) return true;
4291
			}
4292
		}
4293
		return false;
4294
	}
4295
4296
	/**
4297
	 * Compile .scss file
4298
	 *
4299
	 * @param string $in  Input path (.scss)
4300
	 * @param string $out Output path (.css)
4301
	 *
4302
	 * @return string
4303
	 */
4304
	protected function compile($in, $out) {
4305
		$start = microtime(true);
4306
		$css = $this->scss->compile(file_get_contents($in), $in);
0 ignored issues
show
Bug introduced by
The property scss 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...
4307
		$elapsed = round((microtime(true) - $start), 4);
4308
4309
		$v = scssc::$VERSION;
4310
		$t = date('r');
4311
		$css = "/* compiled by scssphp $v on $t (${elapsed}s) */\n\n" . $css;
4312
4313
		file_put_contents($out, $css);
4314
		file_put_contents($this->importsCacheName($out),
4315
			serialize($this->scss->getParsedFiles()));
4316
		return $css;
4317
	}
4318
4319
	/**
4320
	 * Compile requested scss and serve css.  Outputs HTTP response.
4321
	 *
4322
	 * @param string $salt Prefix a string to the filename for creating the cache name hash
4323
	 */
4324
	public function serve($salt = '') {
4325
		if ($input = $this->findInput()) {
4326
			$output = $this->cacheName($salt . $input);
4327
			header('Content-type: text/css');
4328
4329
			if ($this->needsCompile($input, $output)) {
4330
				try {
4331
					echo $this->compile($input, $output);
4332
				} catch (Exception $e) {
0 ignored issues
show
Unused Code introduced by
catch (\Exception $e) { ...->getMessage() . ' '; } does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
4333
					header('HTTP/1.1 500 Internal Server Error');
4334
					echo 'Parse error: ' . $e->getMessage() . "\n";
4335
				}
4336
			} else {
4337
				header('X-SCSS-Cache: true');
4338
				echo file_get_contents($output);
4339
			}
4340
4341
			return;
4342
		}
4343
4344
		header('HTTP/1.0 404 Not Found');
4345
		header('Content-type: text');
4346
		$v = scssc::$VERSION;
4347
		echo "/* INPUT NOT FOUND scss $v */\n";
4348
	}
4349
4350
	/**
4351
	 * Constructor
4352
	 *
4353
	 * @param string      $dir      Root directory to .scss files
4354
	 * @param string      $cacheDir Cache directory
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cacheDir not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
4355
	 * @param \scssc|null $scss     SCSS compiler instance
4356
	 */
4357
	public function __construct($dir, $cacheDir=null, $scss=null) {
4358
		$this->dir = $dir;
4359
4360
		if (is_null($cacheDir)) {
4361
			$cacheDir = $this->join($dir, 'scss_cache');
4362
		}
4363
4364
		$this->cacheDir = $cacheDir;
4365
		if (!is_dir($this->cacheDir)) mkdir($this->cacheDir, 0755, true);
4366
4367
		if (is_null($scss)) {
4368
			$scss = new scssc();
4369
			$scss->setImportPaths($this->dir);
4370
		}
4371
		$this->scss = $scss;
4372
	}
4373
4374
	/**
4375
	 * Helper method to serve compiled scss
4376
	 *
4377
	 * @param string $path Root path
4378
	 */
4379
	static public function serveFrom($path) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
4380
		$server = new self($path);
4381
		$server->serve();
4382
	}
4383
}
4384