Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
75 / 75 |
|
100.00% |
3 / 3 |
CRAP | |
100.00% |
1 / 1 |
PageAtRuleSanitizer | |
100.00% |
75 / 75 |
|
100.00% |
3 / 3 |
14 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
42 / 42 |
|
100.00% |
1 / 1 |
1 | |||
handlesRule | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
doSanitize | |
100.00% |
32 / 32 |
|
100.00% |
1 / 1 |
11 |
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\Alternative; |
10 | use Wikimedia\CSS\Grammar\Juxtaposition; |
11 | use Wikimedia\CSS\Grammar\KeywordMatcher; |
12 | use Wikimedia\CSS\Grammar\Matcher; |
13 | use Wikimedia\CSS\Grammar\MatcherFactory; |
14 | use Wikimedia\CSS\Grammar\Quantifier; |
15 | use Wikimedia\CSS\Grammar\TokenMatcher; |
16 | use Wikimedia\CSS\Grammar\UnorderedGroup; |
17 | use Wikimedia\CSS\Objects\AtRule; |
18 | use Wikimedia\CSS\Objects\CSSObject; |
19 | use Wikimedia\CSS\Objects\Declaration; |
20 | use Wikimedia\CSS\Objects\DeclarationOrAtRuleList; |
21 | use Wikimedia\CSS\Objects\Rule; |
22 | use Wikimedia\CSS\Objects\Token; |
23 | use Wikimedia\CSS\Parser\Parser; |
24 | use Wikimedia\CSS\Util; |
25 | |
26 | /** |
27 | * Sanitizes a CSS \@page rule |
28 | * @see https://www.w3.org/TR/2018/WD-css-page-3-20181018/ |
29 | */ |
30 | class PageAtRuleSanitizer extends RuleSanitizer { |
31 | |
32 | /** @var Matcher */ |
33 | protected $pageSelectorMatcher; |
34 | |
35 | /** @var PropertySanitizer */ |
36 | protected $propertySanitizer; |
37 | |
38 | /** @var MarginAtRuleSanitizer */ |
39 | protected $ruleSanitizer; |
40 | |
41 | /** |
42 | * @param MatcherFactory $matcherFactory |
43 | * @param PropertySanitizer $propertySanitizer Sanitizer for declarations |
44 | */ |
45 | public function __construct( |
46 | MatcherFactory $matcherFactory, PropertySanitizer $propertySanitizer |
47 | ) { |
48 | $ows = $matcherFactory->optionalWhitespace(); |
49 | $pseudoPage = new Juxtaposition( [ |
50 | new TokenMatcher( Token::T_COLON ), |
51 | new KeywordMatcher( [ 'left', 'right', 'first', 'blank' ] ), |
52 | ] ); |
53 | $this->pageSelectorMatcher = new Alternative( [ |
54 | Quantifier::hash( new Juxtaposition( [ |
55 | $ows, |
56 | new Alternative( [ |
57 | Quantifier::plus( $pseudoPage ), |
58 | new Juxtaposition( [ $matcherFactory->ident(), Quantifier::star( $pseudoPage ) ] ), |
59 | ] ), |
60 | $ows, |
61 | ] ) ), |
62 | $ows |
63 | ] ); |
64 | $this->pageSelectorMatcher->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
65 | |
66 | // Clone the $propertySanitizer and inject the special properties |
67 | $this->propertySanitizer = clone $propertySanitizer; |
68 | $this->propertySanitizer->addKnownProperties( [ |
69 | 'size' => new Alternative( [ |
70 | Quantifier::count( $matcherFactory->length(), 1, 2 ), |
71 | new KeywordMatcher( 'auto' ), |
72 | UnorderedGroup::someOf( [ |
73 | new KeywordMatcher( [ |
74 | 'A5', 'A4', 'A3', 'B5', 'B4', 'JIS-B5', 'JIS-B4', 'letter', 'legal', 'ledger', |
75 | ] ), |
76 | new KeywordMatcher( [ 'portrait', 'landscape' ] ), |
77 | ] ), |
78 | ] ), |
79 | 'marks' => new Alternative( [ |
80 | new KeywordMatcher( 'none' ), |
81 | UnorderedGroup::someOf( [ |
82 | new KeywordMatcher( 'crop' ), |
83 | new KeywordMatcher( 'cross' ), |
84 | ] ), |
85 | ] ), |
86 | 'bleed' => new Alternative( [ |
87 | new KeywordMatcher( 'auto' ), |
88 | $matcherFactory->length(), |
89 | ] ), |
90 | ] ); |
91 | |
92 | $this->ruleSanitizer = new MarginAtRuleSanitizer( $propertySanitizer ); |
93 | } |
94 | |
95 | /** @inheritDoc */ |
96 | public function handlesRule( Rule $rule ) { |
97 | return $rule instanceof AtRule && !strcasecmp( $rule->getName(), 'page' ); |
98 | } |
99 | |
100 | /** @inheritDoc */ |
101 | protected function doSanitize( CSSObject $object ) { |
102 | if ( !$object instanceof AtRule || !$this->handlesRule( $object ) ) { |
103 | $this->sanitizationError( 'expected-at-rule', $object, [ 'page' ] ); |
104 | return null; |
105 | } |
106 | |
107 | if ( $object->getBlock() === null ) { |
108 | $this->sanitizationError( 'at-rule-block-required', $object, [ 'page' ] ); |
109 | return null; |
110 | } |
111 | |
112 | // Test the page selector |
113 | $match = $this->pageSelectorMatcher->matchAgainst( |
114 | $object->getPrelude(), [ 'mark-significance' => true ] |
115 | ); |
116 | if ( !$match ) { |
117 | $cv = Util::findFirstNonWhitespace( $object->getPrelude() ) ?: $object->getPrelude(); |
118 | $this->sanitizationError( 'invalid-page-selector', $cv ); |
119 | return null; |
120 | } |
121 | |
122 | $ret = clone $object; |
123 | $this->fixPreludeWhitespace( $ret, false ); |
124 | |
125 | // Parse the block's contents into a list of declarations and at-rules, |
126 | // sanitize it, and put it back into the block. |
127 | $blockContents = $ret->getBlock()->getValue(); |
128 | $parser = Parser::newFromTokens( $blockContents->toTokenArray() ); |
129 | $oldList = $parser->parseDeclarationOrAtRuleList(); |
130 | $this->sanitizationErrors = array_merge( $this->sanitizationErrors, $parser->getParseErrors() ); |
131 | $newList = new DeclarationOrAtRuleList(); |
132 | foreach ( $oldList as $thing ) { |
133 | if ( $thing instanceof Declaration ) { |
134 | $thing = $this->sanitizeObj( $this->propertySanitizer, $thing ); |
135 | } elseif ( $thing instanceof AtRule && $this->ruleSanitizer->handlesRule( $thing ) ) { |
136 | $thing = $this->sanitizeObj( $this->ruleSanitizer, $thing ); |
137 | } else { |
138 | $this->sanitizationError( 'invalid-page-rule-content', $thing ); |
139 | $thing = null; |
140 | } |
141 | if ( $thing ) { |
142 | $newList->add( $thing ); |
143 | } |
144 | } |
145 | $blockContents->clear(); |
146 | $blockContents->add( $newList->toComponentValueArray() ); |
147 | |
148 | return $ret; |
149 | } |
150 | } |