Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
76 / 76 |
|
100.00% |
3 / 3 |
CRAP | |
100.00% |
1 / 1 |
| PageAtRuleSanitizer | |
100.00% |
76 / 76 |
|
100.00% |
3 / 3 |
14 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
43 / 43 |
|
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/2023/WD-css-page-3-20230914/ |
| 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 | 'page-orientation' => new KeywordMatcher( [ 'upright', 'rotate-left', 'rotate-right' ] ), |
| 80 | 'marks' => new Alternative( [ |
| 81 | new KeywordMatcher( 'none' ), |
| 82 | UnorderedGroup::someOf( [ |
| 83 | new KeywordMatcher( 'crop' ), |
| 84 | new KeywordMatcher( 'cross' ), |
| 85 | ] ), |
| 86 | ] ), |
| 87 | 'bleed' => new Alternative( [ |
| 88 | new KeywordMatcher( 'auto' ), |
| 89 | $matcherFactory->length(), |
| 90 | ] ), |
| 91 | ] ); |
| 92 | |
| 93 | $this->ruleSanitizer = new MarginAtRuleSanitizer( $propertySanitizer ); |
| 94 | } |
| 95 | |
| 96 | /** @inheritDoc */ |
| 97 | public function handlesRule( Rule $rule ) { |
| 98 | return $rule instanceof AtRule && !strcasecmp( $rule->getName(), 'page' ); |
| 99 | } |
| 100 | |
| 101 | /** @inheritDoc */ |
| 102 | protected function doSanitize( CSSObject $object ) { |
| 103 | if ( !$object instanceof AtRule || !$this->handlesRule( $object ) ) { |
| 104 | $this->sanitizationError( 'expected-at-rule', $object, [ 'page' ] ); |
| 105 | return null; |
| 106 | } |
| 107 | |
| 108 | if ( $object->getBlock() === null ) { |
| 109 | $this->sanitizationError( 'at-rule-block-required', $object, [ 'page' ] ); |
| 110 | return null; |
| 111 | } |
| 112 | |
| 113 | // Test the page selector |
| 114 | $match = $this->pageSelectorMatcher->matchAgainst( |
| 115 | $object->getPrelude(), [ 'mark-significance' => true ] |
| 116 | ); |
| 117 | if ( !$match ) { |
| 118 | $cv = Util::findFirstNonWhitespace( $object->getPrelude() ) ?: $object->getPrelude(); |
| 119 | $this->sanitizationError( 'invalid-page-selector', $cv ); |
| 120 | return null; |
| 121 | } |
| 122 | |
| 123 | $ret = clone $object; |
| 124 | $this->fixPreludeWhitespace( $ret, false ); |
| 125 | |
| 126 | // Parse the block's contents into a list of declarations and at-rules, |
| 127 | // sanitize it, and put it back into the block. |
| 128 | $blockContents = $ret->getBlock()->getValue(); |
| 129 | $parser = Parser::newFromTokens( $blockContents->toTokenArray() ); |
| 130 | $oldList = $parser->parseDeclarationOrAtRuleList(); |
| 131 | $this->sanitizationErrors = array_merge( $this->sanitizationErrors, $parser->getParseErrors() ); |
| 132 | $newList = new DeclarationOrAtRuleList(); |
| 133 | foreach ( $oldList as $thing ) { |
| 134 | if ( $thing instanceof Declaration ) { |
| 135 | $thing = $this->sanitizeObj( $this->propertySanitizer, $thing ); |
| 136 | } elseif ( $thing instanceof AtRule && $this->ruleSanitizer->handlesRule( $thing ) ) { |
| 137 | $thing = $this->sanitizeObj( $this->ruleSanitizer, $thing ); |
| 138 | } else { |
| 139 | $this->sanitizationError( 'invalid-page-rule-content', $thing ); |
| 140 | $thing = null; |
| 141 | } |
| 142 | if ( $thing ) { |
| 143 | $newList->add( $thing ); |
| 144 | } |
| 145 | } |
| 146 | $blockContents->clear(); |
| 147 | $blockContents->add( $newList->toComponentValueArray() ); |
| 148 | |
| 149 | return $ret; |
| 150 | } |
| 151 | } |