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