Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
UrlMatcher
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
3 / 3
20
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
3
 anyModifierMatcher
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 generateMatches
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
16
1<?php
2/**
3 * @file
4 * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
5 */
6
7namespace Wikimedia\CSS\Grammar;
8
9use InvalidArgumentException;
10use Wikimedia\CSS\Objects\ComponentValueList;
11use Wikimedia\CSS\Objects\CSSFunction;
12use Wikimedia\CSS\Objects\Token;
13
14/**
15 * Matcher that matches a CSSFunction for a URL function or a T_URL token
16 */
17class UrlMatcher extends FunctionMatcher {
18    /** @var callable|null */
19    protected $urlCheck;
20
21    /**
22     * @param callable|null $urlCheck Function to check that the URL is really valid.
23     *  Prototype is bool func( string $url, ComponentValue[] $modifiers )
24     * @param array $options Additional options:
25     *  - modifierMatcher: (Matcher) Matcher for URL modifiers. The default is
26     *    a NothingMatcher.
27     */
28    public function __construct( ?callable $urlCheck = null, array $options = [] ) {
29        if ( isset( $options['modifierMatcher'] ) ) {
30            $modifierMatcher = $options['modifierMatcher'];
31            if ( !$modifierMatcher instanceof Matcher ) {
32                throw new InvalidArgumentException( 'modifierMatcher must be a Matcher' );
33            }
34        } else {
35            $modifierMatcher = new NothingMatcher;
36        }
37
38        $funcContents = new Juxtaposition( [
39            TokenMatcher::create( Token::T_STRING )->capture( 'url' ),
40            Quantifier::star( $modifierMatcher->capture( 'modifier' ) ),
41        ] );
42
43        $this->urlCheck = $urlCheck;
44        parent::__construct(
45            static function ( $name ) {
46                return in_array( strtolower( $name ), [ 'url', 'src' ], true );
47            },
48            $funcContents
49        );
50    }
51
52    /**
53     * Return a Matcher for any grammatically-correct modifier
54     * @return Matcher
55     */
56    public static function anyModifierMatcher() {
57        return Alternative::create( [
58            new TokenMatcher( Token::T_IDENT ),
59            new FunctionMatcher( null, new AnythingMatcher( [ 'quantifier' => '*' ] ) ),
60        ] );
61    }
62
63    /** @inheritDoc */
64    protected function generateMatches( ComponentValueList $values, $start, array $options ) {
65        // First, is it a URL token?
66        $cv = $values[$start] ?? null;
67        if ( $cv instanceof Token && $cv->type() === Token::T_URL ) {
68            $url = $cv->value();
69            if ( !$this->urlCheck || ( $this->urlCheck )( $url, [] ) ) {
70                $match = new GrammarMatch( $values, $start, 1, 'url' );
71                yield $this->makeMatch( $values, $start, $this->next( $values, $start, $options ), $match );
72            }
73            return;
74        }
75
76        // Nope. Try it as a FunctionMatcher and extract the URL and modifiers
77        // for each match.
78        foreach ( parent::generateMatches( $values, $start, $options ) as $match ) {
79            $url = null;
80            $modifiers = [];
81            foreach ( $match->getCapturedMatches() as $submatch ) {
82                $cvs = $submatch->getValues();
83                if ( $cvs[0] instanceof Token && $submatch->getName() === 'url' ) {
84                    $url = $cvs[0]->value();
85                } elseif ( $submatch->getName() === 'modifier' ) {
86                    if ( $cvs[0] instanceof CSSFunction ) {
87                        $modifiers[] = $cvs[0];
88                    } elseif ( $cvs[0] instanceof Token && $cvs[0]->type() === Token::T_IDENT ) {
89                        $modifiers[] = $cvs[0];
90                    }
91                }
92            }
93            if ( $url && ( !$this->urlCheck || ( $this->urlCheck )( $url, $modifiers ) ) ) {
94                yield $match;
95            }
96        }
97    }
98}