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