Completed
Push — master ( 6706d8...93737b )
by
unknown
07:43
created

Stylesheet::_parse_properties()   F

Complexity

Conditions 14
Paths 244

Size

Total Lines 80
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 80
rs 3.9286
c 0
b 0
f 0
cc 14
eloc 30
nc 244
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @package dompdf
4
 * @link    http://dompdf.github.com/
5
 * @author  Benj Carson <[email protected]>
6
 * @author  Helmut Tischer <[email protected]>
7
 * @author  Fabien Ménager <[email protected]>
8
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
9
 */
10
namespace Dompdf\Css;
11
12
use DOMXPath;
13
use Dompdf\Dompdf;
14
use Dompdf\Helpers;
15
use Dompdf\Exception;
16
use Dompdf\FontMetrics;
17
use Dompdf\Frame\FrameTree;
18
19
/**
20
 * The master stylesheet class
21
 *
22
 * The Stylesheet class is responsible for parsing stylesheets and style
23
 * tags/attributes.  It also acts as a registry of the individual Style
24
 * objects generated by the current set of loaded CSS files and style
25
 * elements.
26
 *
27
 * @see Style
28
 * @package dompdf
29
 */
30
class Stylesheet
31
{
32
    /**
33
     * The location of the default built-in CSS file.
34
     */
35
    const DEFAULT_STYLESHEET = "/lib/res/html.css";
36
37
    /**
38
     * User agent stylesheet origin
39
     *
40
     * @var int
41
     */
42
    const ORIG_UA = 1;
43
44
    /**
45
     * User normal stylesheet origin
46
     *
47
     * @var int
48
     */
49
    const ORIG_USER = 2;
50
51
    /**
52
     * Author normal stylesheet origin
53
     *
54
     * @var int
55
     */
56
    const ORIG_AUTHOR = 3;
57
58
    /*
59
     * The highest possible specificity is 0x01000000 (and that is only for author
60
     * stylesheets, as it is for inline styles). Origin precedence can be achieved by
61
     * adding multiples of 0x10000000 to the actual specificity. Important
62
     * declarations are handled in Style; though technically they should be handled
63
     * here so that user important declarations can be made to take precedence over
64
     * user important declarations, this doesn't matter in practice as Dompdf does
65
     * not support user stylesheets, and user agent stylesheets can not include
66
     * important declarations.
67
     */
68
    private static $_stylesheet_origins = array(
69
        self::ORIG_UA => 0x00000000, // user agent declarations
70
        self::ORIG_USER => 0x10000000, // user normal declarations
71
        self::ORIG_AUTHOR => 0x30000000, // author normal declarations
72
    );
73
74
    /*
75
     * Non-CSS presentational hints (i.e. HTML 4 attributes) are handled as if added
76
     * to the beginning of an author stylesheet, i.e. anything in author stylesheets
77
     * should override them.
78
     */
79
    const SPEC_NON_CSS = 0x20000000;
80
81
    /**
82
     * Current dompdf instance
83
     *
84
     * @var Dompdf
85
     */
86
    private $_dompdf;
87
88
    /**
89
     * Array of currently defined styles
90
     *
91
     * @var Style[]
92
     */
93
    private $_styles;
94
95
    /**
96
     * Base protocol of the document being parsed
97
     * Used to handle relative urls.
98
     *
99
     * @var string
100
     */
101
    private $_protocol;
102
103
    /**
104
     * Base hostname of the document being parsed
105
     * Used to handle relative urls.
106
     *
107
     * @var string
108
     */
109
    private $_base_host;
110
111
    /**
112
     * Base path of the document being parsed
113
     * Used to handle relative urls.
114
     *
115
     * @var string
116
     */
117
    private $_base_path;
118
119
    /**
120
     * The styles defined by @page rules
121
     *
122
     * @var array<Style>
123
     */
124
    private $_page_styles;
125
126
    /**
127
     * List of loaded files, used to prevent recursion
128
     *
129
     * @var array
130
     */
131
    private $_loaded_files;
132
133
    /**
134
     * Current stylesheet origin
135
     *
136
     * @var int
137
     */
138
    private $_current_origin = self::ORIG_UA;
139
140
    /**
141
     * Accepted CSS media types
142
     * List of types and parsing rules for future extensions:
143
     * http://www.w3.org/TR/REC-html40/types.html
144
     *   screen, tty, tv, projection, handheld, print, braille, aural, all
145
     * The following are non standard extensions for undocumented specific environments.
146
     *   static, visual, bitmap, paged, dompdf
147
     * Note, even though the generated pdf file is intended for print output,
148
     * the desired content might be different (e.g. screen or projection view of html file).
149
     * Therefore allow specification of content by dompdf setting Options::defaultMediaType.
150
     * If given, replace media "print" by Options::defaultMediaType.
151
     * (Previous version $ACCEPTED_MEDIA_TYPES = $ACCEPTED_GENERIC_MEDIA_TYPES + $ACCEPTED_DEFAULT_MEDIA_TYPE)
152
     */
153
    static $ACCEPTED_DEFAULT_MEDIA_TYPE = "print";
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $ACCEPTED_DEFAULT_MEDIA_TYPE.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
154
    static $ACCEPTED_GENERIC_MEDIA_TYPES = array("all", "static", "visual", "bitmap", "paged", "dompdf");
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $ACCEPTED_GENERIC_MEDIA_TYPES.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
155
    static $VALID_MEDIA_TYPES = array("all", "aural", "bitmap", "braille", "dompdf", "embossed", "handheld", "paged", "print", "projection", "screen", "speech", "static", "tty", "tv", "visual");
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $VALID_MEDIA_TYPES.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
156
157
    /**
158
     * @var FontMetrics
159
     */
160
    private $fontMetrics;
161
162
    /**
163
     * The class constructor.
164
     *
165
     * The base protocol, host & path are initialized to those of
166
     * the current script.
167
     */
168
    function __construct(Dompdf $dompdf)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
Coding Style introduced by
__construct uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
169
    {
170
        $this->_dompdf = $dompdf;
171
        $this->setFontMetrics($dompdf->getFontMetrics());
172
        $this->_styles = array();
173
        $this->_loaded_files = array();
174
        list($this->_protocol, $this->_base_host, $this->_base_path) = Helpers::explode_url($_SERVER["SCRIPT_FILENAME"]);
175
        $this->_page_styles = array("base" => null);
0 ignored issues
show
Documentation Bug introduced by
It seems like array('base' => null) of type array<string,null,{"base":"null"}> is incompatible with the declared type array<integer,object<Dompdf\Css\Style>> of property $_page_styles.

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...
176
    }
177
178
    /**
179
     * Set the base protocol
180
     *
181
     * @param string $protocol
182
     */
183
    function set_protocol($protocol)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
184
    {
185
        $this->_protocol = $protocol;
186
    }
187
188
    /**
189
     * Set the base host
190
     *
191
     * @param string $host
192
     */
193
    function set_host($host)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
194
    {
195
        $this->_base_host = $host;
196
    }
197
198
    /**
199
     * Set the base path
200
     *
201
     * @param string $path
202
     */
203
    function set_base_path($path)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
204
    {
205
        $this->_base_path = $path;
206
    }
207
208
    /**
209
     * Return the Dompdf object
210
     *
211
     * @return Dompdf
212
     */
213
    function get_dompdf()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
214
    {
215
        return $this->_dompdf;
216
    }
217
218
    /**
219
     * Return the base protocol for this stylesheet
220
     *
221
     * @return string
222
     */
223
    function get_protocol()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
224
    {
225
        return $this->_protocol;
226
    }
227
228
    /**
229
     * Return the base host for this stylesheet
230
     *
231
     * @return string
232
     */
233
    function get_host()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
234
    {
235
        return $this->_base_host;
236
    }
237
238
    /**
239
     * Return the base path for this stylesheet
240
     *
241
     * @return string
242
     */
243
    function get_base_path()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
244
    {
245
        return $this->_base_path;
246
    }
247
248
    /**
249
     * Return the array of page styles
250
     *
251
     * @return Style[]
252
     */
253
    function get_page_styles()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
254
    {
255
        return $this->_page_styles;
256
    }
257
258
    /**
259
     * Add a new Style object to the stylesheet
260
     * add_style() adds a new Style object to the current stylesheet, or
261
     * merges a new Style with an existing one.
262
     *
263
     * @param string $key the Style's selector
264
     * @param Style $style the Style to be added
265
     *
266
     * @throws \Dompdf\Exception
267
     */
268
    function add_style($key, Style $style)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
269
    {
270
        if (!is_string($key)) {
271
            throw new Exception("CSS rule must be keyed by a string.");
272
        }
273
274
        if (!isset($this->_styles[$key])) {
275
            $this->_styles[$key] = array();
276
        }
277
        $new_style = clone $style;
278
        $new_style->set_origin($this->_current_origin);
279
        $this->_styles[$key][] = $new_style;
280
    }
281
282
    /**
283
     * lookup a specifc Style collection
284
     *
285
     * lookup() returns the Style collection specified by $key, or null if the Style is
286
     * not found.
287
     *
288
     * @param string $key the selector of the requested Style
289
     * @return Style
290
     *
291
     * @Fixme _styles is a two dimensional array. It should produce wrong results
292
     */
293
    function lookup($key)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
294
    {
295
        if (!isset($this->_styles[$key])) {
296
            return null;
297
        }
298
299
        return $this->_styles[$key];
300
    }
301
302
    /**
303
     * create a new Style object associated with this stylesheet
304
     *
305
     * @param Style $parent The style of this style's parent in the DOM tree
306
     * @return Style
307
     */
308
    function create_style(Style $parent = null)
0 ignored issues
show
Unused Code introduced by
The parameter $parent 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...
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
309
    {
310
        return new Style($this, $this->_current_origin);
311
    }
312
313
    /**
314
     * load and parse a CSS string
315
     *
316
     * @param string $css
317
     * @param int $origin
318
     */
319
    function load_css(&$css, $origin = self::ORIG_AUTHOR)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
320
    {
321
        if ($origin) {
322
            $this->_current_origin = $origin;
323
        }
324
        $this->_parse_css($css);
325
    }
326
327
328
    /**
329
     * load and parse a CSS file
330
     *
331
     * @param string $file
332
     * @param int $origin
333
     */
334
    function load_css_file($file, $origin = self::ORIG_AUTHOR)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
335
    {
336
        if ($origin) {
337
            $this->_current_origin = $origin;
338
        }
339
340
        // Prevent circular references
341
        if (isset($this->_loaded_files[$file])) {
342
            return;
343
        }
344
345
        $this->_loaded_files[$file] = true;
346
347
        if (strpos($file, "data:") === 0) {
348
            $parsed = Helpers::parse_data_uri($file);
349
            $css = $parsed["data"];
350
        } else {
351
            $parsed_url = Helpers::explode_url($file);
352
353
            list($this->_protocol, $this->_base_host, $this->_base_path, $filename) = $parsed_url;
354
355
            // Fix submitted by Nick Oostveen for aliased directory support:
356
            if ($this->_protocol == "") {
357
                $file = $this->_base_path . $filename;
358
            } else {
359
                $file = Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $filename);
360
            }
361
362
            list($css, $http_response_header) = Helpers::getFileContent($file, $this->_dompdf->getHttpContext());
363
364
            $good_mime_type = true;
365
366
            // See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/
367
            if (isset($http_response_header) && !$this->_dompdf->getQuirksmode()) {
368
                foreach ($http_response_header as $_header) {
369
                    if (preg_match("@Content-Type:\s*([\w/]+)@i", $_header, $matches) &&
370
                        ($matches[1] !== "text/css")
371
                    ) {
372
                        $good_mime_type = false;
373
                    }
374
                }
375
            }
376
377
            if (!$good_mime_type || $css == "") {
378
                Helpers::record_warnings(E_USER_WARNING, "Unable to load css file $file", __FILE__, __LINE__);
379
                return;
380
            }
381
        }
382
383
        $this->_parse_css($css);
384
    }
385
386
    /**
387
     * @link http://www.w3.org/TR/CSS21/cascade.html#specificity
388
     *
389
     * @param string $selector
390
     * @param int $origin :
391
     *    - Stylesheet::ORIG_UA: user agent style sheet
392
     *    - Stylesheet::ORIG_USER: user style sheet
393
     *    - Stylesheet::ORIG_AUTHOR: author style sheet
394
     *
395
     * @return int
396
     */
397
    private function _specificity($selector, $origin = self::ORIG_AUTHOR)
398
    {
399
        // http://www.w3.org/TR/CSS21/cascade.html#specificity
400
        // ignoring the ":" pseudoclass modifiers
401
        // also ignored in _css_selector_to_xpath
402
403
        $a = ($selector === "!attr") ? 1 : 0;
404
405
        $b = min(mb_substr_count($selector, "#"), 255);
406
407
        $c = min(mb_substr_count($selector, ".") +
408
            mb_substr_count($selector, "["), 255);
409
410
        $d = min(mb_substr_count($selector, " ") +
411
            mb_substr_count($selector, ">") +
412
            mb_substr_count($selector, "+"), 255);
413
414
        //If a normal element name is at the beginning of the string,
415
        //a leading whitespace might have been removed on whitespace collapsing and removal
416
        //therefore there might be one whitespace less as selected element names
417
        //this can lead to a too small specificity
418
        //see _css_selector_to_xpath
419
420
        if (!in_array($selector[0], array(" ", ">", ".", "#", "+", ":", "[")) && $selector !== "*") {
421
            $d++;
422
        }
423
424
        if ($this->_dompdf->getOptions()->getDebugCss()) {
425
            /*DEBUGCSS*/
426
            print "<pre>\n";
427
            /*DEBUGCSS*/
428
            printf("_specificity(): 0x%08x \"%s\"\n", self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d)), $selector);
429
            /*DEBUGCSS*/
430
            print "</pre>";
431
        }
432
433
        return self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d));
434
    }
435
436
    /**
437
     * Converts a CSS selector to an XPath query.
438
     *
439
     * @param string $selector
440
     * @param bool $first_pass
441
     *
442
     * @throws Exception
443
     * @return string
444
     */
445
    private function _css_selector_to_xpath($selector, $first_pass = false)
446
    {
447
448
        // Collapse white space and strip whitespace around delimiters
449
        //$search = array("/\\s+/", "/\\s+([.>#+:])\\s+/");
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
450
        //$replace = array(" ", "\\1");
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
451
        //$selector = preg_replace($search, $replace, trim($selector));
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
452
453
        // Initial query (non-absolute)
454
        $query = "//";
455
456
        // Will contain :before and :after
457
        $pseudo_elements = array();
458
459
        // Will contain :link, etc
460
        $pseudo_classes = array();
461
462
        // Parse the selector
463
        //$s = preg_split("/([ :>.#+])/", $selector, -1, PREG_SPLIT_DELIM_CAPTURE);
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
464
465
        $delimiters = array(" ", ">", ".", "#", "+", ":", "[", "(");
466
467
        // Add an implicit * at the beginning of the selector
468
        // if it begins with an attribute selector
469
        if ($selector[0] === "[") {
470
            $selector = "*$selector";
471
        }
472
473
        // Add an implicit space at the beginning of the selector if there is no
474
        // delimiter there already.
475
        if (!in_array($selector[0], $delimiters)) {
476
            $selector = " $selector";
477
        }
478
479
        $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
480
        $len = mb_strlen($selector);
481
        $i = 0;
482
483
        while ($i < $len) {
484
485
            $s = $selector[$i];
486
            $i++;
487
488
            // Eat characters up to the next delimiter
489
            $tok = "";
490
            $in_attr = false;
491
            $in_func = false;
492
493
            while ($i < $len) {
494
                $c = $selector[$i];
495
                $c_prev = $selector[$i - 1];
496
497
                if (!$in_func && !$in_attr && in_array($c, $delimiters) && !(($c == $c_prev) == ":")) {
498
                    break;
499
                }
500
501
                if ($c_prev === "[") {
502
                    $in_attr = true;
503
                }
504
                if ($c_prev === "(") {
505
                    $in_func = true;
506
                }
507
508
                $tok .= $selector[$i++];
509
510
                if ($in_attr && $c === "]") {
511
                    $in_attr = false;
0 ignored issues
show
Unused Code introduced by
$in_attr 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...
512
                    break;
513
                }
514
                if ($in_func && $c === ")") {
515
                    $in_func = false;
0 ignored issues
show
Unused Code introduced by
$in_func 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...
516
                    break;
517
                }
518
            }
519
520
            switch ($s) {
521
522
                case " ":
523
                case ">":
524
                    // All elements matching the next token that are direct children of
525
                    // the current token
526
                    $expr = $s === " " ? "descendant" : "child";
527
528
                    if (mb_substr($query, -1, 1) !== "/") {
529
                        $query .= "/";
530
                    }
531
532
                    // Tag names are case-insensitive
533
                    $tok = strtolower($tok);
534
535
                    if (!$tok) {
536
                        $tok = "*";
537
                    }
538
539
                    $query .= "$expr::$tok";
540
                    $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
541
                    break;
542
543
                case ".":
544
                case "#":
545
                    // All elements matching the current token with a class/id equal to
546
                    // the _next_ token.
547
548
                    $attr = $s === "." ? "class" : "id";
549
550
                    // empty class/id == *
551
                    if (mb_substr($query, -1, 1) === "/") {
552
                        $query .= "*";
553
                    }
554
555
                    // Match multiple classes: $tok contains the current selected
556
                    // class.  Search for class attributes with class="$tok",
557
                    // class=".* $tok .*" and class=".* $tok"
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
558
559
                    // This doesn't work because libxml only supports XPath 1.0...
560
                    //$query .= "[matches(@$attr,\"^${tok}\$|^${tok}[ ]+|[ ]+${tok}\$|[ ]+${tok}[ ]+\")]";
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
561
562
                    // Query improvement by Michael Sheakoski <[email protected]>:
563
                    $query .= "[contains(concat(' ', @$attr, ' '), concat(' ', '$tok', ' '))]";
564
                    $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
565
                    break;
566
567
                case "+":
568
                    // All sibling elements that folow the current token
569
                    if (mb_substr($query, -1, 1) !== "/") {
570
                        $query .= "/";
571
                    }
572
573
                    $query .= "following-sibling::$tok";
574
                    $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
575
                    break;
576
577
                case ":":
578
                    $i2 = $i - strlen($tok) - 2; // the char before ":"
579
                    if (($i2 < 0 || !isset($selector[$i2]) || (in_array($selector[$i2], $delimiters) && $selector[$i2] != ":")) && substr($query, -1) != "*") {
580
                        $query .= "*";
581
                    }
582
583
                    $last = false;
584
585
                    // Pseudo-classes
586
                    switch ($tok) {
587
588
                        case "first-child":
589
                            $query .= "[1]";
590
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
591
                            break;
592
593
                        case "last-child":
594
                            $query .= "[not(following-sibling::*)]";
595
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
596
                            break;
597
598
                        case "first-of-type":
599
                            $query .= "[position() = 1]";
600
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
601
                            break;
602
603
                        case "last-of-type":
604
                            $query .= "[position() = last()]";
605
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
606
                            break;
607
608
                        // an+b, n, odd, and even
609
                        /** @noinspection PhpMissingBreakStatementInspection */
610
                        case "nth-last-of-type":
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...
611
                            $last = true;
612
                        case "nth-of-type":
613
                            //FIXME: this fix-up is pretty ugly, would parsing the selector in reverse work better generally?
614
                            $descendant_delimeter = strrpos($query, "::");
615
                            $isChild = substr($query, $descendant_delimeter-5, 5) == "child";
616
                            $el = substr($query, $descendant_delimeter+2);
617
                            $query = substr($query, 0, strrpos($query, "/")) . ($isChild ? "/" : "//") . $el;
618
619
                            $pseudo_classes[$tok] = true;
620
                            $p = $i + 1;
621
                            $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
622
623
                            // 1
624 View Code Duplication
                            if (preg_match("/^\d+$/", $nth)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
625
                                $condition = "position() = $nth";
626
                            } // odd
627
                            elseif ($nth === "odd") {
628
                                $condition = "(position() mod 2) = 1";
629
                            } // even
630
                            elseif ($nth === "even") {
631
                                $condition = "(position() mod 2) = 0";
632
                            } // an+b
633
                            else {
634
                                $condition = $this->_selector_an_plus_b($nth, $last);
635
                            }
636
637
                            $query .= "[$condition]";
638
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
639
                            break;
640
                        /** @noinspection PhpMissingBreakStatementInspection */
641
                        case "nth-last-child":
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...
642
                            $last = true;
643
                        case "nth-child":
644
                            //FIXME: this fix-up is pretty ugly, would parsing the selector in reverse work better generally?
645
                            $descendant_delimeter = strrpos($query, "::");
646
                            $isChild = substr($query, $descendant_delimeter-5, 5) == "child";
647
                            $el = substr($query, $descendant_delimeter+2);
648
                            $query = substr($query, 0, strrpos($query, "/")) . ($isChild ? "/" : "//") . "*";
649
650
                            $pseudo_classes[$tok] = true;
651
                            $p = $i + 1;
652
                            $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
653
654
                            // 1
655 View Code Duplication
                            if (preg_match("/^\d+$/", $nth)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
656
                                $condition = "position() = $nth";
657
                            } // odd
658
                            elseif ($nth === "odd") {
659
                                $condition = "(position() mod 2) = 1";
660
                            } // even
661
                            elseif ($nth === "even") {
662
                                $condition = "(position() mod 2) = 0";
663
                            } // an+b
664
                            else {
665
                                $condition = $this->_selector_an_plus_b($nth, $last);
666
                            }
667
668
                            $query .= "[$condition]";
669
                            if ($el != "*") {
670
                                $query .= "[name() = '$el']";
671
                            }
672
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
673
                            break;
674
675
                        //TODO: bit of a hack attempt at matches support, currently only matches against elements
676
                        case "matches":
677
                            $pseudo_classes[$tok] = true;
678
                            $p = $i + 1;
679
                            $matchList = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
680
681
                            // Tag names are case-insensitive
682
                            $elements = array_map("trim", explode(",", strtolower($matchList)));
683
                            foreach ($elements as &$element) {
684
                                $element = "name() = '$element'";
685
                            }
686
687
                            $query .= "[" . implode(" or ", $elements) . "]";
688
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
689
                            break;
690
691
                        case "link":
692
                            $query .= "[@href]";
693
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
694
                            break;
695
696
                        case "first-line":
697
                        case ":first-line":
698
                        case "first-letter":
699
                        case ":first-letter":
700
                            // TODO
701
                            $el = trim($tok, ":");
702
                            $pseudo_elements[$el] = true;
703
                            break;
704
705
                            // N/A
706
                        case "focus":
707
                        case "active":
708
                        case "hover":
709
                        case "visited":
710
                            $query .= "[false()]";
711
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
712
                            break;
713
714
                        /* Pseudo-elements */
715
                        case "before":
716
                        case ":before":
717
                        case "after":
718
                        case ":after":
719
                            $pos = trim($tok, ":");
720
                            $pseudo_elements[$pos] = true;
721
                            if (!$first_pass) {
722
                                $query .= "/*[@$pos]";
723
                            }
724
725
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
726
                            break;
727
728
                        case "empty":
729
                            $query .= "[not(*) and not(normalize-space())]";
730
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
731
                            break;
732
733
                        case "disabled":
734
                        case "checked":
735
                            $query .= "[@$tok]";
736
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
737
                            break;
738
739
                        case "enabled":
740
                            $query .= "[not(@disabled)]";
741
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
742
                            break;
743
744
                        // the selector is not handled, until we support all possible selectors force an empty set (silent failure)
745
                        default:
746
                            $query = "/..";
747
                            $tok = "";
0 ignored issues
show
Unused Code introduced by
$tok 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...
748
                            break;
749
                    }
750
751
                    break;
752
753
                case "[":
754
                    // Attribute selectors.  All with an attribute matching the following token(s)
755
                    $attr_delimiters = array("=", "]", "~", "|", "$", "^", "*");
756
                    $tok_len = mb_strlen($tok);
757
                    $j = 0;
758
759
                    $attr = "";
760
                    $op = "";
761
                    $value = "";
762
763
                    while ($j < $tok_len) {
764
                        if (in_array($tok[$j], $attr_delimiters)) {
765
                            break;
766
                        }
767
                        $attr .= $tok[$j++];
768
                    }
769
770
                    switch ($tok[$j]) {
771
772
                        case "~":
773
                        case "|":
774
                        case "$":
775
                        case "^":
776
                        case "*":
777
                            $op .= $tok[$j++];
778
779
                            if ($tok[$j] !== "=") {
780
                                throw new Exception("Invalid CSS selector syntax: invalid attribute selector: $selector");
781
                            }
782
783
                            $op .= $tok[$j];
784
                            break;
785
786
                        case "=":
787
                            $op = "=";
788
                            break;
789
790
                    }
791
792
                    // Read the attribute value, if required
793
                    if ($op != "") {
794
                        $j++;
795
                        while ($j < $tok_len) {
796
                            if ($tok[$j] === "]") {
797
                                break;
798
                            }
799
                            $value .= $tok[$j++];
800
                        }
801
                    }
802
803
                    if ($attr == "") {
804
                        throw new Exception("Invalid CSS selector syntax: missing attribute name");
805
                    }
806
807
                    $value = trim($value, "\"'");
808
809
                    switch ($op) {
810
811
                        case "":
812
                            $query .= "[@$attr]";
813
                            break;
814
815
                        case "=":
816
                            $query .= "[@$attr=\"$value\"]";
817
                            break;
818
819 View Code Duplication
                        case "~=":
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
820
                            // FIXME: this will break if $value contains quoted strings
821
                            // (e.g. [type~="a b c" "d e f"])
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
822
                            $values = explode(" ", $value);
823
                            $query .= "[";
824
825
                            foreach ($values as $val) {
826
                                $query .= "@$attr=\"$val\" or ";
827
                            }
828
829
                            $query = rtrim($query, " or ") . "]";
830
                            break;
831
832 View Code Duplication
                        case "|=":
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
833
                            $values = explode("-", $value);
834
                            $query .= "[";
835
836
                            foreach ($values as $val) {
837
                                $query .= "starts-with(@$attr, \"$val\") or ";
838
                            }
839
840
                            $query = rtrim($query, " or ") . "]";
841
                            break;
842
843
                        case "$=":
844
                            $query .= "[substring(@$attr, string-length(@$attr)-" . (strlen($value) - 1) . ")=\"$value\"]";
845
                            break;
846
847
                        case "^=":
848
                            $query .= "[starts-with(@$attr,\"$value\")]";
849
                            break;
850
851
                        case "*=":
852
                            $query .= "[contains(@$attr,\"$value\")]";
853
                            break;
854
                    }
855
856
                    break;
857
            }
858
        }
859
        $i++;
860
861
//       case ":":
862
//         // Pseudo selectors: ignore for now.  Partially handled directly
863
//         // below.
864
865
//         // Skip until the next special character, leaving the token as-is
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
866
//         while ( $i < $len ) {
867
//           if ( in_array($selector[$i], $delimiters) )
868
//             break;
869
//           $i++;
870
//         }
871
//         break;
872
873
//       default:
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
874
//         // Add the character to the token
875
//         $tok .= $selector[$i++];
876
//         break;
877
//       }
878
879
//    }
880
881
882
        // Trim the trailing '/' from the query
883
        if (mb_strlen($query) > 2) {
884
            $query = rtrim($query, "/");
885
        }
886
887
        return array("query" => $query, "pseudo_elements" => $pseudo_elements);
888
    }
889
890
    /**
891
     * https://github.com/tenderlove/nokogiri/blob/master/lib/nokogiri/css/xpath_visitor.rb
892
     *
893
     * @param $expr
894
     * @param bool $last
895
     * @return string
896
     */
897
    protected function _selector_an_plus_b($expr, $last = false)
898
    {
899
        $expr = preg_replace("/\s/", "", $expr);
900
        if (!preg_match("/^(?P<a>-?[0-9]*)?n(?P<b>[-+]?[0-9]+)?$/", $expr, $matches)) {
901
            return "false()";
902
        }
903
904
        $a = ((isset($matches["a"]) && $matches["a"] !== "") ? intval($matches["a"]) : 1);
905
        $b = ((isset($matches["b"]) && $matches["b"] !== "") ? intval($matches["b"]) : 0);
906
907
        $position = ($last ? "(last()-position()+1)" : "position()");
908
909
        if ($b == 0) {
910
            return "($position mod $a) = 0";
911
        } else {
912
            $compare = (($a < 0) ? "<=" : ">=");
913
            $b2 = -$b;
914
            if ($b2 >= 0) {
915
                $b2 = "+$b2";
916
            }
917
            return "($position $compare $b) and ((($position $b2) mod " . abs($a) . ") = 0)";
918
        }
919
    }
920
921
    /**
922
     * applies all current styles to a particular document tree
923
     *
924
     * apply_styles() applies all currently loaded styles to the provided
925
     * {@link FrameTree}.  Aside from parsing CSS, this is the main purpose
926
     * of this class.
927
     *
928
     * @param \Dompdf\Frame\FrameTree $tree
929
     */
930
    function apply_styles(FrameTree $tree)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
931
    {
932
        // Use XPath to select nodes.  This would be easier if we could attach
933
        // Frame objects directly to DOMNodes using the setUserData() method, but
934
        // we can't do that just yet.  Instead, we set a _node attribute_ in
935
        // Frame->set_id() and use that as a handle on the Frame object via
936
        // FrameTree::$_registry.
937
938
        // We create a scratch array of styles indexed by frame id.  Once all
939
        // styles have been assigned, we order the cached styles by specificity
940
        // and create a final style object to assign to the frame.
941
942
        // FIXME: this is not particularly robust...
943
944
        $styles = array();
945
        $xp = new DOMXPath($tree->get_dom());
946
        $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
947
948
        // Add generated content
949
        foreach ($this->_styles as $selector => $selector_styles) {
950
            /** @var Style $style */
951
            foreach ($selector_styles as $style) {
0 ignored issues
show
Bug introduced by
The expression $selector_styles of type object<Dompdf\Css\Style> is not traversable.
Loading history...
952
                if (strpos($selector, ":before") === false && strpos($selector, ":after") === false) {
953
                    continue;
954
                }
955
956
                $query = $this->_css_selector_to_xpath($selector, true);
957
958
                // Retrieve the nodes, limit to body for generated content
959
                //TODO: If we use a context node can we remove the leading dot?
960
                $nodes = @$xp->query('.' . $query["query"]);
961
                if ($nodes == null) {
962
                    Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__);
963
                    continue;
964
                }
965
966
                /** @var \DOMElement $node */
967
                foreach ($nodes as $node) {
968
                    foreach (array_keys($query["pseudo_elements"], true, true) as $pos) {
969
                        // Do not add a new pseudo element if another one already matched
970
                        if ($node->hasAttribute("dompdf_{$pos}_frame_id")) {
971
                            continue;
972
                        }
973
974
                        if (($src = $this->_image($style->get_prop('content'))) !== "none") {
975
                            $new_node = $node->ownerDocument->createElement("img_generated");
976
                            $new_node->setAttribute("src", $src);
977
                        } else {
978
                            $new_node = $node->ownerDocument->createElement("dompdf_generated");
979
                        }
980
981
                        $new_node->setAttribute($pos, $pos);
982
                        $new_frame_id = $tree->insert_node($node, $new_node, $pos);
983
                        $node->setAttribute("dompdf_{$pos}_frame_id", $new_frame_id);
984
                    }
985
                }
986
            }
987
        }
988
989
        // Apply all styles in stylesheet
990
        foreach ($this->_styles as $selector => $selector_styles) {
991
            /** @var Style $style */
992
            foreach ($selector_styles as $style) {
0 ignored issues
show
Bug introduced by
The expression $selector_styles of type object<Dompdf\Css\Style> is not traversable.
Loading history...
993
                $query = $this->_css_selector_to_xpath($selector);
994
995
                // Retrieve the nodes
996
                $nodes = @$xp->query($query["query"]);
997
                if ($nodes == null) {
998
                    Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__);
999
                    continue;
1000
                }
1001
1002
                $spec = $this->_specificity($selector, $style->get_origin());
1003
1004
                foreach ($nodes as $node) {
1005
                    // Retrieve the node id
1006
                    // Only DOMElements get styles
1007
                    if ($node->nodeType != XML_ELEMENT_NODE) {
1008
                        continue;
1009
                    }
1010
1011
                    $id = $node->getAttribute("frame_id");
1012
1013
                    // Assign the current style to the scratch array
1014
                    $styles[$id][$spec][] = $style;
1015
                }
1016
            }
1017
        }
1018
1019
        // Set the page width, height, and orientation based on the canvas paper size
1020
        $canvas = $this->_dompdf->getCanvas();
1021
        $paper_width = $canvas->get_width();
1022
        $paper_height = $canvas->get_height();
1023
        $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
1024
1025
        if ($this->_page_styles["base"] && is_array($this->_page_styles["base"]->size)) {
0 ignored issues
show
Documentation introduced by
The property size does not exist on object<Dompdf\Css\Style>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1026
            $paper_width = $this->_page_styles['base']->size[0];
0 ignored issues
show
Documentation introduced by
The property size does not exist on object<Dompdf\Css\Style>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1027
            $paper_height = $this->_page_styles['base']->size[1];
0 ignored issues
show
Documentation introduced by
The property size does not exist on object<Dompdf\Css\Style>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1028
            $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
1029
        }
1030
1031
        // Now create the styles and assign them to the appropriate frames. (We
1032
        // iterate over the tree using an implicit FrameTree iterator.)
1033
        $root_flg = false;
1034
        foreach ($tree->get_frames() as $frame) {
1035
            // Helpers::pre_r($frame->get_node()->nodeName . ":");
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1036
            if (!$root_flg && $this->_page_styles["base"]) {
1037
                $style = $this->_page_styles["base"];
1038
            } else {
1039
                $style = $this->create_style();
1040
            }
1041
1042
            // Find nearest DOMElement parent
1043
            $p = $frame;
1044
            while ($p = $p->get_parent()) {
1045
                if ($p->get_node()->nodeType == XML_ELEMENT_NODE) {
1046
                    break;
1047
                }
1048
            }
1049
1050
            // Styles can only be applied directly to DOMElements; anonymous
1051
            // frames inherit from their parent
1052
            if ($frame->get_node()->nodeType != XML_ELEMENT_NODE) {
1053
                if ($p) {
1054
                    $style->inherit($p->get_style());
1055
                }
1056
1057
                $frame->set_style($style);
1058
                continue;
1059
            }
1060
1061
            $id = $frame->get_id();
1062
1063
            // Handle HTML 4.0 attributes
1064
            AttributeTranslator::translate_attributes($frame);
1065
            if (($str = $frame->get_node()->getAttribute(AttributeTranslator::$_style_attr)) !== "") {
0 ignored issues
show
Bug introduced by
The property _style_attr cannot be accessed from this context as it is declared private in class Dompdf\Css\AttributeTranslator.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
1066
                $styles[$id][self::SPEC_NON_CSS][] = $this->_parse_properties($str);
1067
            }
1068
1069
            // Locate any additional style attributes
1070
            if (($str = $frame->get_node()->getAttribute("style")) !== "") {
1071
                // Destroy CSS comments
1072
                $str = preg_replace("'/\*.*?\*/'si", "", $str);
1073
1074
                $spec = $this->_specificity("!attr", self::ORIG_AUTHOR);
1075
                $styles[$id][$spec][] = $this->_parse_properties($str);
1076
            }
1077
1078
            // Grab the applicable styles
1079
            if (isset($styles[$id])) {
1080
1081
                /** @var array[][] $applied_styles */
1082
                $applied_styles = $styles[$frame->get_id()];
1083
1084
                // Sort by specificity
1085
                ksort($applied_styles);
1086
1087
                if ($DEBUGCSS) {
1088
                    $debug_nodename = $frame->get_node()->nodeName;
1089
                    print "<pre>\n[$debug_nodename\n";
1090
                    foreach ($applied_styles as $spec => $arr) {
1091
                        printf("specificity: 0x%08x\n", $spec);
1092
                        /** @var Style $s */
1093
                        foreach ($arr as $s) {
1094
                            print "[\n";
1095
                            $s->debug_print();
1096
                            print "]\n";
1097
                        }
1098
                    }
1099
                }
1100
1101
                // Merge the new styles with the inherited styles
1102
                $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
1103
                $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
1104
                foreach ($applied_styles as $arr) {
1105
                    /** @var Style $s */
1106
                    foreach ($arr as $s) {
1107
                        $media_queries = $s->get_media_queries();
1108
                        foreach ($media_queries as $media_query) {
1109
                            list($media_query_feature, $media_query_value) = $media_query;
1110
                            // if any of the Style's media queries fail then do not apply the style
1111
                            //TODO: When the media query logic is fully developed we should not apply the Style when any of the media queries fail or are bad, per https://www.w3.org/TR/css3-mediaqueries/#error-handling
1112
                            if (in_array($media_query_feature, self::$VALID_MEDIA_TYPES)) {
1113
                                if ((strlen($media_query_feature) === 0 && !in_array($media_query, $acceptedmedia)) || (in_array($media_query, $acceptedmedia) && $media_query_value == "not")) {
1114
                                    continue (3);
1115
                                }
1116
                            } else {
1117
                                switch ($media_query_feature) {
1118
                                    case "height":
1119
                                        if ($paper_height !== (float)$style->length_in_pt($media_query_value)) {
1120
                                            continue (3);
1121
                                        }
1122
                                        break;
1123
                                    case "min-height":
1124
                                        if ($paper_height < (float)$style->length_in_pt($media_query_value)) {
1125
                                            continue (3);
1126
                                        }
1127
                                        break;
1128
                                    case "max-height":
1129
                                        if ($paper_height > (float)$style->length_in_pt($media_query_value)) {
1130
                                            continue (3);
1131
                                        }
1132
                                        break;
1133
                                    case "width":
1134
                                        if ($paper_width !== (float)$style->length_in_pt($media_query_value)) {
1135
                                            continue (3);
1136
                                        }
1137
                                        break;
1138
                                    case "min-width":
1139
                                        //if (min($paper_width, $media_query_width) === $paper_width) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1140
                                        if ($paper_width < (float)$style->length_in_pt($media_query_value)) {
1141
                                            continue (3);
1142
                                        }
1143
                                        break;
1144
                                    case "max-width":
1145
                                        //if (max($paper_width, $media_query_width) === $paper_width) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1146
                                        if ($paper_width > (float)$style->length_in_pt($media_query_value)) {
1147
                                            continue (3);
1148
                                        }
1149
                                        break;
1150
                                    case "orientation":
1151
                                        if ($paper_orientation !== $media_query_value) {
1152
                                            continue (3);
1153
                                        }
1154
                                        break;
1155
                                    default:
1156
                                        Helpers::record_warnings(E_USER_WARNING, "Unknown media query: $media_query_feature", __FILE__, __LINE__);
1157
                                        break;
1158
                                }
1159
                            }
1160
                        }
1161
1162
                        $style->merge($s);
1163
                    }
1164
                }
1165
            }
1166
1167
            // Inherit parent's styles if required
1168
            if ($p) {
1169
1170
                if ($DEBUGCSS) {
1171
                    print "inherit:\n";
1172
                    print "[\n";
1173
                    $p->get_style()->debug_print();
1174
                    print "]\n";
1175
                }
1176
1177
                $style->inherit($p->get_style());
1178
            }
1179
1180
            if ($DEBUGCSS) {
1181
                print "DomElementStyle:\n";
1182
                print "[\n";
1183
                $style->debug_print();
1184
                print "]\n";
1185
                print "/$debug_nodename]\n</pre>";
0 ignored issues
show
Bug introduced by
The variable $debug_nodename 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...
1186
            }
1187
1188
            /*DEBUGCSS print: see below different print debugging method
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1189
            Helpers::pre_r($frame->get_node()->nodeName . ":");
1190
            echo "<pre>";
1191
            echo $style;
1192
            echo "</pre>";*/
1193
            $frame->set_style($style);
1194
1195
            if (!$root_flg && $this->_page_styles["base"]) {
1196
                $root_flg = true;
1197
1198
                // set the page width, height, and orientation based on the parsed page style
1199
                if ($style->size !== "auto") {
0 ignored issues
show
Documentation introduced by
The property size does not exist on object<Dompdf\Css\Style>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1200
                    list($paper_width, $paper_height) = $style->size;
0 ignored issues
show
Documentation introduced by
The property size does not exist on object<Dompdf\Css\Style>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1201
                }
1202
                $paper_width = $paper_width - (float)$style->length_in_pt($style->margin_left) - (float)$style->length_in_pt($style->margin_right);
0 ignored issues
show
Documentation introduced by
The property margin_left does not exist on object<Dompdf\Css\Style>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property margin_right does not exist on object<Dompdf\Css\Style>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1203
                $paper_height = $paper_height - (float)$style->length_in_pt($style->margin_top) - (float)$style->length_in_pt($style->margin_bottom);
0 ignored issues
show
Documentation introduced by
The property margin_top does not exist on object<Dompdf\Css\Style>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property margin_bottom does not exist on object<Dompdf\Css\Style>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1204
                $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
1205
            }
1206
        }
1207
1208
        // We're done!  Clean out the registry of all styles since we
1209
        // won't be needing this later.
1210
        foreach (array_keys($this->_styles) as $key) {
1211
            $this->_styles[$key] = null;
1212
            unset($this->_styles[$key]);
1213
        }
1214
1215
    }
1216
1217
    /**
1218
     * parse a CSS string using a regex parser
1219
     * Called by {@link Stylesheet::parse_css()}
1220
     *
1221
     * @param string $str
1222
     *
1223
     * @throws Exception
1224
     */
1225
    private function _parse_css($str)
1226
    {
1227
1228
        $str = trim($str);
1229
1230
        // Destroy comments and remove HTML comments
1231
        $css = preg_replace(array(
1232
            "'/\*.*?\*/'si",
1233
            "/^<!--/",
1234
            "/-->$/"
1235
        ), "", $str);
1236
1237
        // FIXME: handle '{' within strings, e.g. [attr="string {}"]
1238
1239
        // Something more legible:
1240
        $re =
1241
            "/\s*                                   # Skip leading whitespace                             \n" .
1242
            "( @([^\s{]+)\s*([^{;]*) (?:;|({)) )?   # Match @rules followed by ';' or '{'                 \n" .
1243
            "(?(1)                                  # Only parse sub-sections if we're in an @rule...     \n" .
1244
            "  (?(4)                                # ...and if there was a leading '{'                   \n" .
1245
            "    \s*( (?:(?>[^{}]+) ({)?            # Parse rulesets and individual @page rules           \n" .
1246
            "            (?(6) (?>[^}]*) }) \s*)+?                                                        \n" .
1247
            "       )                                                                                     \n" .
1248
            "   })                                  # Balancing '}'                                       \n" .
1249
            "|                                      # Branch to match regular rules (not preceded by '@') \n" .
1250
            "([^{]*{[^}]*}))                        # Parse normal rulesets                               \n" .
1251
            "/xs";
1252
1253
        if (preg_match_all($re, $css, $matches, PREG_SET_ORDER) === false) {
1254
            // An error occurred
1255
            throw new Exception("Error parsing css file: preg_match_all() failed.");
1256
        }
1257
1258
        // After matching, the array indicies are set as follows:
1259
        //
1260
        // [0] => complete text of match
1261
        // [1] => contains '@import ...;' or '@media {' if applicable
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1262
        // [2] => text following @ for cases where [1] is set
1263
        // [3] => media types or full text following '@import ...;'
1264
        // [4] => '{', if present
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1265
        // [5] => rulesets within media rules
1266
        // [6] => '{', within media rules
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1267
        // [7] => individual rules, outside of media rules
1268
        //
1269
1270
        $media_query_regex = "/(?:((only|not)?\s*(" . implode("|", self::$VALID_MEDIA_TYPES) . "))|(\s*\(\s*((?:(min|max)-)?([\w\-]+))\s*(?:\:\s*(.*?)\s*)?\)))/isx";
1271
1272
        //Helpers::pre_r($matches);
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1273
        foreach ($matches as $match) {
1274
            $match[2] = trim($match[2]);
1275
1276
            if ($match[2] !== "") {
1277
                // Handle @rules
1278
                switch ($match[2]) {
1279
1280
                    case "import":
1281
                        $this->_parse_import($match[3]);
1282
                        break;
1283
1284
                    case "media":
1285
                        $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
1286
                        $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
1287
1288
                        $media_queries = preg_split("/\s*,\s*/", mb_strtolower(trim($match[3])));
1289
                        foreach ($media_queries as $media_query) {
1290
                            if (in_array($media_query, $acceptedmedia)) {
1291
                                //if we have a media type match go ahead and parse the stylesheet
1292
                                $this->_parse_sections($match[5]);
1293
                                break;
1294
                            } elseif (!in_array($media_query, self::$VALID_MEDIA_TYPES)) {
1295
                                // otherwise conditionally parse the stylesheet assuming there are parseable media queries
1296
                                if (preg_match_all($media_query_regex, $media_query, $media_query_matches, PREG_SET_ORDER) !== false) {
1297
                                    $mq = array();
1298
                                    foreach ($media_query_matches as $media_query_match) {
1299
                                        if (empty($media_query_match[1]) === false) {
1300
                                            $media_query_feature = strtolower($media_query_match[3]);
1301
                                            $media_query_value = strtolower($media_query_match[2]);
1302
                                            $mq[] = array($media_query_feature, $media_query_value);
1303
                                        } else if (empty($media_query_match[4]) === false) {
1304
                                            $media_query_feature = strtolower($media_query_match[5]);
1305
                                            $media_query_value = (array_key_exists(8, $media_query_match) ? strtolower($media_query_match[8]) : null);
1306
                                            $mq[] = array($media_query_feature, $media_query_value);
1307
                                        }
1308
                                    }
1309
                                    $this->_parse_sections($match[5], $mq);
1310
                                    break;
1311
                                }
1312
                            }
1313
                        }
1314
                        break;
1315
1316
                    case "page":
1317
                        //This handles @page to be applied to page oriented media
1318
                        //Note: This has a reduced syntax:
1319
                        //@page { margin:1cm; color:blue; }
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1320
                        //Not a sequence of styles like a full.css, but only the properties
1321
                        //of a single style, which is applied to the very first "root" frame before
1322
                        //processing other styles of the frame.
1323
                        //Working properties:
1324
                        // margin (for margin around edge of paper)
1325
                        // font-family (default font of pages)
1326
                        // color (default text color of pages)
1327
                        //Non working properties:
1328
                        // border
1329
                        // padding
1330
                        // background-color
1331
                        //Todo:Reason is unknown
1332
                        //Other properties (like further font or border attributes) not tested.
1333
                        //If a border or background color around each paper sheet is desired,
1334
                        //assign it to the <body> tag, possibly only for the css of the correct media type.
1335
1336
                        // If the page has a name, skip the style.
1337
                        $page_selector = trim($match[3]);
1338
1339
                        $key = null;
1340
                        switch ($page_selector) {
1341
                            case "":
1342
                                $key = "base";
1343
                                break;
1344
1345
                            case ":left":
1346
                            case ":right":
1347
                            case ":odd":
1348
                            case ":even":
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...
1349
                            /** @noinspection PhpMissingBreakStatementInspection */
1350
                            case ":first":
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...
1351
                                $key = $page_selector;
1352
1353
                            default:
1354
                                continue;
1355
                        }
1356
1357
                        // Store the style for later...
1358
                        if (empty($this->_page_styles[$key])) {
1359
                            $this->_page_styles[$key] = $this->_parse_properties($match[5]);
1360
                        } else {
1361
                            $this->_page_styles[$key]->merge($this->_parse_properties($match[5]));
1362
                        }
1363
                        break;
1364
1365
                    case "font-face":
1366
                        $this->_parse_font_face($match[5]);
1367
                        break;
1368
1369
                    default:
1370
                        // ignore everything else
1371
                        break;
1372
                }
1373
1374
                continue;
1375
            }
1376
1377
            if ($match[7] !== "") {
1378
                $this->_parse_sections($match[7]);
1379
            }
1380
1381
        }
1382
    }
1383
1384
    /**
1385
     * See also style.cls Style::_image(), refactoring?, works also for imported css files
1386
     *
1387
     * @param $val
1388
     * @return string
1389
     */
1390
    protected function _image($val)
0 ignored issues
show
Coding Style introduced by
_image uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1391
    {
1392
        $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
1393
        $parsed_url = "none";
1394
1395
        if (mb_strpos($val, "url") === false) {
1396
            $path = "none"; //Don't resolve no image -> otherwise would prefix path and no longer recognize as none
1397
        } else {
1398
            $val = preg_replace("/url\(\s*['\"]?([^'\")]+)['\"]?\s*\)/", "\\1", trim($val));
1399
1400
            // Resolve the url now in the context of the current stylesheet
1401
            $parsed_url = Helpers::explode_url($val);
1402
            if ($parsed_url["protocol"] == "" && $this->get_protocol() == "") {
1403 View Code Duplication
                if ($parsed_url["path"][0] === '/' || $parsed_url["path"][0] === '\\') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1404
                    $path = $_SERVER["DOCUMENT_ROOT"] . '/';
1405
                } else {
1406
                    $path = $this->get_base_path();
1407
                }
1408
1409
                $path .= $parsed_url["path"] . $parsed_url["file"];
1410
                $path = realpath($path);
1411
                // If realpath returns FALSE then specifically state that there is no background image
1412
                // FIXME: Is this causing problems for imported CSS files? There are some './none' references when running the test cases.
1413
                if (!$path) {
1414
                    $path = 'none';
1415
                }
1416
            } else {
1417
                $path = Helpers::build_url($this->get_protocol(),
1418
                    $this->get_host(),
1419
                    $this->get_base_path(),
1420
                    $val);
1421
            }
1422
        }
1423
1424 View Code Duplication
        if ($DEBUGCSS) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1425
            print "<pre>[_image\n";
1426
            print_r($parsed_url);
1427
            print $this->get_protocol() . "\n" . $this->get_base_path() . "\n" . $path . "\n";
1428
            print "_image]</pre>";;
1429
        }
1430
1431
        return $path;
1432
    }
1433
1434
    /**
1435
     * parse @import{} sections
1436
     *
1437
     * @param string $url the url of the imported CSS file
1438
     */
1439
    private function _parse_import($url)
1440
    {
1441
        $arr = preg_split("/[\s\n,]/", $url, -1, PREG_SPLIT_NO_EMPTY);
1442
        $url = array_shift($arr);
1443
        $accept = false;
1444
1445
        if (count($arr) > 0) {
1446
            $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
1447
            $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
1448
1449
            // @import url media_type [media_type...]
1450 View Code Duplication
            foreach ($arr as $type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1451
                if (in_array(mb_strtolower(trim($type)), $acceptedmedia)) {
1452
                    $accept = true;
1453
                    break;
1454
                }
1455
            }
1456
1457
        } else {
1458
            // unconditional import
1459
            $accept = true;
1460
        }
1461
1462
        if ($accept) {
1463
            // Store our current base url properties in case the new url is elsewhere
1464
            $protocol = $this->_protocol;
1465
            $host = $this->_base_host;
1466
            $path = $this->_base_path;
1467
1468
            // $url = str_replace(array('"',"url", "(", ")"), "", $url);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1469
            // If the protocol is php, assume that we will import using file://
1470
            // $url = Helpers::build_url($protocol == "php://" ? "file://" : $protocol, $host, $path, $url);
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1471
            // Above does not work for subfolders and absolute urls.
1472
            // Todo: As above, do we need to replace php or file to an empty protocol for local files?
1473
1474
            $url = $this->_image($url);
1475
1476
            $this->load_css_file($url);
1477
1478
            // Restore the current base url
1479
            $this->_protocol = $protocol;
1480
            $this->_base_host = $host;
1481
            $this->_base_path = $path;
1482
        }
1483
1484
    }
1485
1486
    /**
1487
     * parse @font-face{} sections
1488
     * http://www.w3.org/TR/css3-fonts/#the-font-face-rule
1489
     *
1490
     * @param string $str CSS @font-face rules
1491
     */
1492
    private function _parse_font_face($str)
1493
    {
1494
        $descriptors = $this->_parse_properties($str);
1495
1496
        preg_match_all("/(url|local)\s*\([\"\']?([^\"\'\)]+)[\"\']?\)\s*(format\s*\([\"\']?([^\"\'\)]+)[\"\']?\))?/i", $descriptors->src, $src);
0 ignored issues
show
Documentation introduced by
The property src does not exist on object<Dompdf\Css\Style>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1497
1498
        $sources = array();
1499
        $valid_sources = array();
1500
1501
        foreach ($src[0] as $i => $value) {
1502
            $source = array(
1503
                "local" => strtolower($src[1][$i]) === "local",
1504
                "uri" => $src[2][$i],
1505
                "format" => strtolower($src[4][$i]),
1506
                "path" => Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $src[2][$i]),
1507
            );
1508
1509
            if (!$source["local"] && in_array($source["format"], array("", "truetype"))) {
1510
                $valid_sources[] = $source;
1511
            }
1512
1513
            $sources[] = $source;
1514
        }
1515
1516
        // No valid sources
1517
        if (empty($valid_sources)) {
1518
            return;
1519
        }
1520
1521
        $style = array(
1522
            "family" => $descriptors->get_font_family_raw(),
1523
            "weight" => $descriptors->font_weight,
0 ignored issues
show
Documentation introduced by
The property font_weight does not exist on object<Dompdf\Css\Style>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1524
            "style" => $descriptors->font_style,
0 ignored issues
show
Documentation introduced by
The property font_style does not exist on object<Dompdf\Css\Style>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1525
        );
1526
1527
        $this->getFontMetrics()->registerFont($style, $valid_sources[0]["path"], $this->_dompdf->getHttpContext());
1528
    }
1529
1530
    /**
1531
     * parse regular CSS blocks
1532
     *
1533
     * _parse_properties() creates a new Style object based on the provided
1534
     * CSS rules.
1535
     *
1536
     * @param string $str CSS rules
1537
     * @return Style
1538
     */
1539
    private function _parse_properties($str)
1540
    {
1541
        $properties = preg_split("/;(?=(?:[^\(]*\([^\)]*\))*(?![^\)]*\)))/", $str);
1542
        $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
1543
1544
        if ($DEBUGCSS) {
1545
            print '[_parse_properties';
1546
        }
1547
1548
        // Create the style
1549
        $style = new Style($this, Stylesheet::ORIG_AUTHOR);
1550
1551
        foreach ($properties as $prop) {
1552
            // If the $prop contains an url, the regex may be wrong
1553
            // @todo: fix the regex so that it works everytime
1554
            /*if (strpos($prop, "url(") === false) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1555
              if (preg_match("/([a-z-]+)\s*:\s*[^:]+$/i", $prop, $m))
1556
                $prop = $m[0];
1557
            }*/
1558
            //A css property can have " ! important" appended (whitespace optional)
1559
            //strip this off to decode core of the property correctly.
1560
            //Pass on in the style to allow proper handling:
1561
            //!important properties can only be overridden by other !important ones.
1562
            //$style->$prop_name = is a shortcut of $style->__set($prop_name,$value);.
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1563
            //If no specific set function available, set _props["prop_name"]
1564
            //style is always copied completely, or $_props handled separately
1565
            //Therefore set a _important_props["prop_name"]=true to indicate the modifier
1566
1567
            /* Instead of short code, prefer the typical case with fast code
0 ignored issues
show
Unused Code Comprehensibility introduced by
41% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1568
          $important = preg_match("/(.*?)!\s*important/",$prop,$match);
1569
            if ( $important ) {
1570
              $prop = $match[1];
1571
            }
1572
            $prop = trim($prop);
1573
            */
1574
            if ($DEBUGCSS) print '(';
1575
1576
            $important = false;
1577
            $prop = trim($prop);
1578
1579
            if (substr($prop, -9) === 'important') {
1580
                $prop_tmp = rtrim(substr($prop, 0, -9));
1581
1582
                if (substr($prop_tmp, -1) === '!') {
1583
                    $prop = rtrim(substr($prop_tmp, 0, -1));
1584
                    $important = true;
1585
                }
1586
            }
1587
1588
            if ($prop === "") {
1589
                if ($DEBUGCSS) print 'empty)';
1590
                continue;
1591
            }
1592
1593
            $i = mb_strpos($prop, ":");
1594
            if ($i === false) {
1595
                if ($DEBUGCSS) print 'novalue' . $prop . ')';
1596
                continue;
1597
            }
1598
1599
            $prop_name = rtrim(mb_strtolower(mb_substr($prop, 0, $i)));
1600
            $value = ltrim(mb_substr($prop, $i + 1));
1601
            if ($DEBUGCSS) print $prop_name . ':=' . $value . ($important ? '!IMPORTANT' : '') . ')';
1602
            //New style, anyway empty
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1603
            //if ($important || !$style->important_get($prop_name) ) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1604
            //$style->$prop_name = array($value,$important);
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1605
            //assignment might be replaced by overloading through __set,
1606
            //and overloaded functions might check _important_props,
1607
            //therefore set _important_props first.
1608
            if ($important) {
1609
                $style->important_set($prop_name);
1610
            }
1611
            //For easier debugging, don't use overloading of assignments with __set
1612
            $style->$prop_name = $value;
1613
            //$style->props_set($prop_name, $value);
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1614
        }
1615
        if ($DEBUGCSS) print '_parse_properties]';
1616
1617
        return $style;
1618
    }
1619
1620
    /**
1621
     * parse selector + rulesets
1622
     *
1623
     * @param string $str CSS selectors and rulesets
1624
     * @param array $media_queries
1625
     */
1626
    private function _parse_sections($str, $media_queries = array())
1627
    {
1628
        // Pre-process: collapse all whitespace and strip whitespace around '>',
1629
        // '.', ':', '+', '#'
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1630
1631
        $patterns = array("/[\\s\n]+/", "/\\s+([>.:+#])\\s+/");
1632
        $replacements = array(" ", "\\1");
1633
        $str = preg_replace($patterns, $replacements, $str);
1634
        $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
1635
1636
        $sections = explode("}", $str);
1637
        if ($DEBUGCSS) print '[_parse_sections';
1638
        foreach ($sections as $sect) {
1639
            $i = mb_strpos($sect, "{");
1640
            if ($i === false) { continue; }
1641
1642
            //$selectors = explode(",", mb_substr($sect, 0, $i));
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1643
            $selectors = preg_split("/,(?![^\(]*\))/", mb_substr($sect, 0, $i),0, PREG_SPLIT_NO_EMPTY);
1644
            if ($DEBUGCSS) print '[section';
1645
1646
            $style = $this->_parse_properties(trim(mb_substr($sect, $i + 1)));
1647
1648
            // Assign it to the selected elements
1649
            foreach ($selectors as $selector) {
1650
                $selector = trim($selector);
1651
1652
                if ($selector == "") {
1653
                    if ($DEBUGCSS) print '#empty#';
1654
                    continue;
1655
                }
1656
                if ($DEBUGCSS) print '#' . $selector . '#';
1657
                //if ($DEBUGCSS) { if (strpos($selector,'p') !== false) print '!!!p!!!#'; }
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1658
1659
                //FIXME: tag the selector with a hash of the media query to separate it from non-conditional styles (?), xpath comments are probably not what we want to do here
1660
                if (count($media_queries) > 0) {
1661
                    $style->set_media_queries($media_queries);
1662
                }
1663
                $this->add_style($selector, $style);
1664
            }
1665
1666
            if ($DEBUGCSS) {
1667
                print 'section]';
1668
            }
1669
        }
1670
1671
        if ($DEBUGCSS) {
1672
            print '_parse_sections]';
1673
        }
1674
    }
1675
1676
    /**
1677
     * @return string
1678
     */
1679
    public static function getDefaultStylesheet()
1680
    {
1681
        $dir = realpath(__DIR__ . "/../..");
1682
        return $dir . self::DEFAULT_STYLESHEET;
1683
    }
1684
1685
    /**
1686
     * @param FontMetrics $fontMetrics
1687
     * @return $this
1688
     */
1689
    public function setFontMetrics(FontMetrics $fontMetrics)
1690
    {
1691
        $this->fontMetrics = $fontMetrics;
1692
        return $this;
1693
    }
1694
1695
    /**
1696
     * @return FontMetrics
1697
     */
1698
    public function getFontMetrics()
1699
    {
1700
        return $this->fontMetrics;
1701
    }
1702
1703
    /**
1704
     * dumps the entire stylesheet as a string
1705
     *
1706
     * Generates a string of each selector and associated style in the
1707
     * Stylesheet.  Useful for debugging.
1708
     *
1709
     * @return string
1710
     */
1711
    function __toString()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1712
    {
1713
        $str = "";
1714
        foreach ($this->_styles as $selector => $selector_styles) {
1715
            /** @var Style $style */
1716
            foreach ($selector_styles as $style) {
0 ignored issues
show
Bug introduced by
The expression $selector_styles of type object<Dompdf\Css\Style> is not traversable.
Loading history...
1717
                $str .= "$selector => " . $style->__toString() . "\n";
1718
            }
1719
        }
1720
1721
        return $str;
1722
    }
1723
}