Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
PropertySanitizer
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
7 / 7
20
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getKnownProperties
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setKnownProperties
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 addKnownProperties
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 getCssWideKeywordsMatcher
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCssWideKeywordsMatcher
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doSanitize
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
6
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 InvalidArgumentException;
12use Wikimedia\CSS\Grammar\Matcher;
13use Wikimedia\CSS\Grammar\NothingMatcher;
14use Wikimedia\CSS\Objects\CSSObject;
15use Wikimedia\CSS\Objects\Declaration;
16use Wikimedia\CSS\Util;
17
18/**
19 * Sanitizes a Declaration
20 */
21class PropertySanitizer extends Sanitizer {
22
23    /** @var Matcher[] Array mapping declaration names (lowercase) to Matchers for the values */
24    private $knownProperties = [];
25
26    /** @var Matcher|null Matcher for CSS-wide keywords */
27    private $cssWideKeywords = null;
28
29    /**
30     * @param Matcher[] $properties Array mapping declaration names (lowercase)
31     *  to Matchers for the values
32     * @param Matcher|null $cssWideKeywordsMatcher Matcher for keywords that should
33     *  be recognized for all known properties.
34     */
35    public function __construct( array $properties = [], ?Matcher $cssWideKeywordsMatcher = null ) {
36        $this->setKnownProperties( $properties );
37        $this->setCssWideKeywordsMatcher( $cssWideKeywordsMatcher ?: new NothingMatcher );
38    }
39
40    /**
41     * Access the list of known properties
42     * @return Matcher[]
43     */
44    public function getKnownProperties() {
45        return $this->knownProperties;
46    }
47
48    /**
49     * Set the list of known properties
50     * @param Matcher[] $properties Array mapping declaration names (lowercase)
51     *  to Matchers for the values
52     */
53    public function setKnownProperties( array $properties ) {
54        foreach ( $properties as $prop => $matcher ) {
55            if ( strtolower( $prop ) !== $prop ) {
56                throw new InvalidArgumentException( "Property name '$prop' must be lowercased" );
57            }
58            if ( !$matcher instanceof Matcher ) {
59                throw new InvalidArgumentException( "Value for '$prop' is not a Matcher" );
60            }
61        }
62        $this->knownProperties = $properties;
63    }
64
65    /**
66     * Merge a list of matchers into the list of known properties
67     * @param Matcher[] $props Array mapping declaration names (lowercase)
68     *  to Matchers for the values
69     * @throws InvalidArgumentException if some property is already defined
70     */
71    public function addKnownProperties( $props ) {
72        $dups = [];
73        foreach ( $props as $k => $v ) {
74            if ( isset( $this->knownProperties[$k] ) && $v !== $this->knownProperties[$k] ) {
75                $dups[] = $k;
76            }
77        }
78        if ( $dups ) {
79            throw new InvalidArgumentException(
80                'Duplicate definitions for properties: ' . implode( ' ', $dups )
81            );
82        }
83        $this->setKnownProperties( $this->knownProperties + $props );
84    }
85
86    /**
87     * Fetch the matcher for keywords that should be recognized for all properties.
88     * @return Matcher
89     */
90    public function getCssWideKeywordsMatcher() {
91        return $this->cssWideKeywords;
92    }
93
94    /**
95     * Set the matcher for keywords that should be recognized for all properties.
96     * @param Matcher $matcher
97     */
98    public function setCssWideKeywordsMatcher( Matcher $matcher ) {
99        $this->cssWideKeywords = $matcher;
100    }
101
102    /** @inheritDoc */
103    protected function doSanitize( CSSObject $object ) {
104        if ( !$object instanceof Declaration ) {
105            $this->sanitizationError( 'expected-declaration', $object );
106            return null;
107        }
108
109        $knownProperties = $this->getKnownProperties();
110        $name = strtolower( $object->getName() );
111        if ( !isset( $knownProperties[$name] ) ) {
112            $this->sanitizationError( 'unrecognized-property', $object );
113            return null;
114        }
115
116        $list = $object->getValue();
117        if ( !$knownProperties[$name]->matchAgainst( $list, [ 'mark-significance' => true ] ) &&
118            !$this->getCssWideKeywordsMatcher()->matchAgainst( $list, [ 'mark-significance' => true ] )
119        ) {
120            $cv = Util::findFirstNonWhitespace( $list );
121            if ( $cv ) {
122                $this->sanitizationError( 'bad-value-for-property', $cv, [ $name ] );
123            } else {
124                $this->sanitizationError( 'missing-value-for-property', $object, [ $name ] );
125            }
126            return null;
127        }
128
129        // @phan-suppress-next-line PhanTypeMismatchReturn generics weakness
130        return $object;
131    }
132}