]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - vendor/wikimedia/css-sanitizer/src/Sanitizer/StyleRuleSanitizer.php
MediaWiki 1.30.2
[autoinstalls/mediawiki.git] / vendor / wikimedia / css-sanitizer / src / Sanitizer / StyleRuleSanitizer.php
1 <?php
2 /**
3  * @file
4  * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
5  */
6
7 namespace Wikimedia\CSS\Sanitizer;
8
9 use Wikimedia\CSS\Grammar\Juxtaposition;
10 use Wikimedia\CSS\Grammar\Matcher;
11 use Wikimedia\CSS\Grammar\MatcherFactory;
12 use Wikimedia\CSS\Objects\CSSObject;
13 use Wikimedia\CSS\Objects\ComponentValue;
14 use Wikimedia\CSS\Objects\QualifiedRule;
15 use Wikimedia\CSS\Objects\Rule;
16 use Wikimedia\CSS\Objects\Token;
17 use Wikimedia\CSS\Util;
18
19 /**
20  * Sanitizes a CSS style rule
21  * @see https://www.w3.org/TR/2014/CR-css-syntax-3-20140220/#style-rules
22  */
23 class StyleRuleSanitizer extends RuleSanitizer {
24
25         /** @var Matcher */
26         protected $selectorMatcher;
27
28         /** @var ComponentValue[] */
29         protected $prependSelectors;
30
31         /** @var PropertySanitizer */
32         protected $propertySanitizer;
33
34         /**
35          * @param Matcher $selectorMatcher Matcher for valid selectors.
36          *  Probably from MatcherFactory::cssSelectorList().
37          * @param PropertySanitizer $propertySanitizer Sanitizer to test property declarations.
38          *  Probably an instance of StylePropertySanitizer.
39          * @param array $options Additional options
40          *  - prependSelectors: (ComponentValue[]) Prepend this to all selectors.
41          *    Include trailing whitespace if necessary. Note $selectorMatcher must
42          *    capture each selector with the name 'selector'.
43          */
44         public function __construct(
45                 Matcher $selectorMatcher, PropertySanitizer $propertySanitizer, array $options = []
46         ) {
47                 $options += [
48                         'prependSelectors' => [],
49                 ];
50
51                 // Add optional whitespace around the selector-matcher, because
52                 // selector-matchers don't usually have it.
53                 if ( !$selectorMatcher->getDefaultOptions()['skip-whitespace'] ) {
54                         $ows = MatcherFactory::singleton()->optionalWhitespace();
55                         $this->selectorMatcher = new Juxtaposition( [
56                                 $ows,
57                                 $selectorMatcher,
58                                 $ows->capture( 'trailingWS' ),
59                         ] );
60                         $this->selectorMatcher->setDefaultOptions( $selectorMatcher->getDefaultOptions() );
61                 } else {
62                         $this->selectorMatcher = $selectorMatcher;
63                 }
64
65                 $this->propertySanitizer = $propertySanitizer;
66                 $this->prependSelectors = $options['prependSelectors'];
67         }
68
69         public function handlesRule( Rule $rule ) {
70                 return $rule instanceof QualifiedRule;
71         }
72
73         protected function doSanitize( CSSObject $object ) {
74                 if ( !$object instanceof QualifiedRule ) {
75                         $this->sanitizationError( 'expected-qualified-rule', $object );
76                         return null;
77                 }
78
79                 // Test that the prelude is a valid selector list
80                 $match = $this->selectorMatcher->match( $object->getPrelude(), [ 'mark-significance' => true ] );
81                 if ( !$match ) {
82                         $cv = Util::findFirstNonWhitespace( $object->getPrelude() );
83                         if ( $cv ) {
84                                 $this->sanitizationError( 'invalid-selector-list', $cv );
85                         } else {
86                                 $this->sanitizationError( 'missing-selector-list', $object );
87                         }
88                         return null;
89                 }
90
91                 $ret = clone( $object );
92
93                 // If necessary, munge the selector list
94                 if ( $this->prependSelectors ) {
95                         $prelude = $ret->getPrelude();
96                         $comma = [
97                                 new Token( Token::T_COMMA ),
98                                 new Token( Token::T_WHITESPACE, [ 'significant' => false ] )
99                         ];
100                         $oldPrelude = $object->getPrelude();
101                         $prelude->clear();
102                         foreach ( $match->getCapturedMatches() as $m ) {
103                                 if ( $m->getName() === 'selector' ) {
104                                         if ( $prelude->count() ) {
105                                                 $prelude->add( $comma );
106                                         }
107                                         $prelude->add( $this->prependSelectors );
108                                         $prelude->add( $m->getValues() );
109                                 } elseif ( $m->getName() === 'trailingWS' && $m->getLength() > 0 ) {
110                                         $prelude->add( $m->getValues() );
111                                 }
112                         }
113                 }
114
115                 $this->sanitizeDeclarationBlock( $ret->getBlock(), $this->propertySanitizer );
116
117                 return $ret;
118         }
119 }