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 Wikimedia\CSS\Grammar\Matcher;
10use Wikimedia\CSS\Objects\AtRule;
11use Wikimedia\CSS\Objects\CSSObject;
12use Wikimedia\CSS\Objects\DeclarationList;
13use Wikimedia\CSS\Objects\Rule;
14use Wikimedia\CSS\Parser\Parser;
15use Wikimedia\CSS\Util;
16
17/**
18 * Sanitizes a feature-value at-rule inside a CSS \@font-feature-values rule
19 * @see https://www.w3.org/TR/2018/CR-css-fonts-3-20180315/#at-font-feature-values-rule
20 */
21class FontFeatureValueAtRuleSanitizer extends RuleSanitizer {
22
23	/** @var string */
24	protected $name;
25
26	/** @var Matcher */
27	protected $valueMatcher;
28
29	/**
30	 * @param string $name
31	 * @param Matcher $valueMatcher
32	 */
33	public function __construct( $name, Matcher $valueMatcher ) {
34		$this->name = $name;
35		$this->valueMatcher = $valueMatcher;
36	}
37
38	/** @inheritDoc */
39	public function handlesRule( Rule $rule ) {
40		return $rule instanceof AtRule && !strcasecmp( $rule->getName(), $this->name );
41	}
42
43	/** @inheritDoc */
44	protected function doSanitize( CSSObject $object ) {
45		if ( !$object instanceof AtRule || !$this->handlesRule( $object ) ) {
46			$this->sanitizationError( 'expected-at-rule', $object, [ $this->name ] );
47			return null;
48		}
49
50		if ( $object->getBlock() === null ) {
51			$this->sanitizationError( 'at-rule-block-required', $object, [ $this->name ] );
52			return null;
53		}
54
55		// No non-whitespace prelude allowed
56		if ( Util::findFirstNonWhitespace( $object->getPrelude() ) ) {
57			$this->sanitizationError( 'invalid-font-feature-value', $object, [ $this->name ] );
58			return null;
59		}
60
61		$ret = clone $object;
62		$this->fixPreludeWhitespace( $ret, false );
63
64		// Parse the block's contents into a list of declarations, sanitize it,
65		// and put it back into the block.
66		$blockContents = $ret->getBlock()->getValue();
67		$parser = Parser::newFromTokens( $blockContents->toTokenArray() );
68		$oldDeclarations = $parser->parseDeclarationList();
69		$this->sanitizationErrors = array_merge( $this->sanitizationErrors, $parser->getParseErrors() );
70		$newDeclarations = new DeclarationList();
71		foreach ( $oldDeclarations as $declaration ) {
72			if ( $this->valueMatcher->matchAgainst( $declaration->getValue(), [ 'mark-significance' => true ] ) ) {
73				$newDeclarations->add( $declaration );
74			} else {
75				$this->sanitizationError( 'invalid-font-feature-value-declaration', $declaration,
76					[ $this->name ] );
77			}
78		}
79		$blockContents->clear();
80		$blockContents->add( $newDeclarations->toComponentValueArray() );
81
82		return $ret;
83	}
84}
85