Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
StylesheetSanitizer
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
5 / 5
8
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newDefault
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 getRuleSanitizers
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRuleSanitizers
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 doSanitize
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2declare( strict_types = 1 );
3
4/**
5 * @file
6 * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
7 */
8
9namespace Wikimedia\CSS\Sanitizer;
10
11use Wikimedia\CSS\Grammar\MatcherFactory;
12use Wikimedia\CSS\Objects\CSSObject;
13use Wikimedia\CSS\Objects\RuleList;
14use Wikimedia\CSS\Objects\Stylesheet;
15use Wikimedia\CSS\Util;
16
17/**
18 * Sanitizes a CSS stylesheet or rule list
19 * @see https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/#css-stylesheets
20 */
21class StylesheetSanitizer extends Sanitizer {
22
23    /** @var RuleSanitizer[] */
24    protected $ruleSanitizers;
25
26    /**
27     * @param RuleSanitizer[] $ruleSanitizers Sanitizers to test rules. For
28     *  each rule in the sheet, the first sanitizer that handles that rule gets
29     *  to sanitize it.
30     */
31    public function __construct( array $ruleSanitizers = [] ) {
32        $this->setRuleSanitizers( $ruleSanitizers );
33    }
34
35    /**
36     * Create and return a default StylesheetSanitizer.
37     * @note This method exists more to be an example of how to put everything
38     *  together than to be used directly.
39     * @return StylesheetSanitizer
40     */
41    public static function newDefault() {
42        // First, we need a matcher factory for the stuff all the sanitizers
43        // will need.
44        $matcherFactory = MatcherFactory::singleton();
45
46        // This is the sanitizer for a single "property: value", that gets used by
47        // StyleRuleSanitizer and various others.
48        $propertySanitizer = new StylePropertySanitizer( $matcherFactory );
49
50        // These are sanitizers for different types of rules that can appear in
51        // stylesheets and can be nested inside @media and @supports blocks.
52        // The keys in the array aren't used for anything by the library, but
53        // may help humans reading it.
54        $ruleSanitizers = [
55            'style' => new StyleRuleSanitizer( $matcherFactory->cssSelectorList(), $propertySanitizer ),
56            '@font-face' => new FontFaceAtRuleSanitizer( $matcherFactory ),
57            '@keyframes' => new KeyframesAtRuleSanitizer( $matcherFactory, $propertySanitizer ),
58            '@page' => new PageAtRuleSanitizer( $matcherFactory, $propertySanitizer ),
59            '@media' => new MediaAtRuleSanitizer( $matcherFactory->cssMediaQueryList() ),
60            '@supports' => new SupportsAtRuleSanitizer( $matcherFactory, [
61                'declarationSanitizer' => $propertySanitizer,
62            ] ),
63        ];
64
65        // Inject the above list into the @media and @supports sanitizers.
66        $ruleSanitizers['@media']->setRuleSanitizers( $ruleSanitizers );
67        $ruleSanitizers['@supports']->setRuleSanitizers( $ruleSanitizers );
68
69        // Now we can put together the StylesheetSanitizer
70        return new StylesheetSanitizer( $ruleSanitizers + [
71            // Note there's intentionally no "@charset" sanitizer, as that at-rule
72            // was removed in the Editor's Draft in favor of special handling
73            // in the parser.
74            '@import' => new ImportAtRuleSanitizer( $matcherFactory, [
75                'declarationSanitizer' => $propertySanitizer,
76            ] ),
77            '@namespace' => new NamespaceAtRuleSanitizer( $matcherFactory ),
78        ] );
79    }
80
81    /**
82     * Access the list of rule sanitizers
83     * @return RuleSanitizer[]
84     */
85    public function getRuleSanitizers() {
86        return $this->ruleSanitizers;
87    }
88
89    /**
90     * Set the list of rule sanitizers
91     * @param RuleSanitizer[] $ruleSanitizers
92     */
93    public function setRuleSanitizers( array $ruleSanitizers ) {
94        Util::assertAllInstanceOf( $ruleSanitizers, RuleSanitizer::class, '$ruleSanitizers' );
95        $this->ruleSanitizers = $ruleSanitizers;
96    }
97
98    /** @inheritDoc */
99    protected function doSanitize( CSSObject $object ) {
100        $isSheet = $object instanceof Stylesheet;
101        if ( $isSheet ) {
102            '@phan-var Stylesheet $object';
103            $object = $object->getRuleList();
104        }
105        if ( !$object instanceof RuleList ) {
106            '@phan-var CSSObject $object';
107            $this->sanitizationError( 'expected-stylesheet', $object );
108            return null;
109        }
110
111        $ret = $this->sanitizeRules( $this->ruleSanitizers, $object );
112        if ( $isSheet ) {
113            $ret = new Stylesheet( $ret );
114        }
115
116        // @phan-suppress-next-line PhanTypeMismatchReturn generics weakness
117        return $ret;
118    }
119}