Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.11% covered (success)
92.11%
35 / 38
85.71% covered (warning)
85.71%
6 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
HasTemplateFeature
92.11% covered (success)
92.11%
35 / 38
85.71% covered (warning)
85.71%
6 / 7
13.08
0.00% covered (danger)
0.00%
0 / 1
 getKeywords
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doApplyExtended
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 parseValue
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
6
 getCrossSearchStrategy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doGetFilterQuery
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 getFilterQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doApply
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace CirrusSearch\Query;
4
5use CirrusSearch\CrossSearchStrategy;
6use CirrusSearch\Parser\AST\KeywordFeatureNode;
7use CirrusSearch\Query\Builder\QueryBuildingContext;
8use CirrusSearch\Search\Filters;
9use CirrusSearch\Search\SearchContext;
10use CirrusSearch\WarningCollector;
11use Elastica\Query\AbstractQuery;
12use MediaWiki\Title\Title;
13
14/**
15 * We emulate template syntax here as best as possible, so things in NS_MAIN
16 * are prefixed with ":" and things in NS_TEMPATE don't have a prefix at all.
17 * Since we don't actually index templates like that, munge the query here.
18 */
19class HasTemplateFeature extends SimpleKeywordFeature implements FilterQueryFeature {
20    public const MAX_CONDITIONS = 256;
21
22    /**
23     * @return string[]
24     */
25    protected function getKeywords() {
26        return [ 'hastemplate' ];
27    }
28
29    /**
30     * @param SearchContext $context
31     * @param string $key
32     * @param string $value
33     * @param string $quotedValue
34     * @param bool $negated
35     * @param string $delimiter
36     * @param string $suffix
37     * @return array
38     */
39    public function doApplyExtended( SearchContext $context, $key, $value, $quotedValue, $negated,
40        $delimiter, $suffix
41    ) {
42        $filter = $this->doGetFilterQuery(
43            $this->parseValue( $key, $value, $quotedValue, $delimiter, $suffix, $context ) );
44        return [ $filter, false ];
45    }
46
47    /**
48     * @param string $key
49     * @param string $value
50     * @param string $quotedValue
51     * @param string $valueDelimiter
52     * @param string $suffix
53     * @param WarningCollector $warningCollector
54     * @return array|false|null
55     */
56    public function parseValue( $key, $value, $quotedValue, $valueDelimiter, $suffix, WarningCollector $warningCollector ) {
57        $values = explode( '|', $value, self::MAX_CONDITIONS + 1 );
58        if ( count( $values ) > self::MAX_CONDITIONS ) {
59            $warningCollector->addWarning(
60                'cirrussearch-feature-too-many-conditions',
61                $key,
62                self::MAX_CONDITIONS
63            );
64            $values = array_slice(
65                $values,
66                0,
67                self::MAX_CONDITIONS
68            );
69        }
70        $templates = [];
71        foreach ( $values as $template ) {
72            if ( strpos( $template, ':' ) === 0 ) {
73                $template = substr( $template, 1 );
74            } else {
75                $title = Title::newFromText( $template );
76                if ( $title && $title->getNamespace() === NS_MAIN ) {
77                    $template = Title::makeTitle( NS_TEMPLATE, $title->getDBkey() )
78                        ->getPrefixedText();
79                }
80            }
81            $templates[] = $template;
82        }
83        return [ 'templates' => $templates, 'case_sensitive' => $valueDelimiter == '"' ];
84    }
85
86    /**
87     * @param KeywordFeatureNode $node
88     * @return CrossSearchStrategy
89     */
90    public function getCrossSearchStrategy( KeywordFeatureNode $node ) {
91        return CrossSearchStrategy::allWikisStrategy();
92    }
93
94    /**
95     * @param string[][] $parsedValue
96     * @return AbstractQuery
97     */
98    protected function doGetFilterQuery( array $parsedValue ) {
99        $caseSensitive = $parsedValue['case_sensitive'];
100
101        return Filters::booleanOr( array_map(
102            static function ( $v ) use ( $caseSensitive ) {
103                return QueryHelper::matchPage( $caseSensitive ? 'template.keyword' : 'template', $v );
104            },
105            $parsedValue['templates']
106        ) );
107    }
108
109    /**
110     * @param KeywordFeatureNode $node
111     * @param QueryBuildingContext $context
112     * @return AbstractQuery|null
113     */
114    public function getFilterQuery( KeywordFeatureNode $node, QueryBuildingContext $context ) {
115        return $this->doGetFilterQuery( $node->getParsedValue() );
116    }
117
118    /**
119     * Applies the detected keyword from the search term. May apply changes
120     * either to $context directly, or return a filter to be added.
121     *
122     * @param SearchContext $context
123     * @param string $key The keyword
124     * @param string $value The value attached to the keyword with quotes stripped and escaped
125     *  quotes un-escaped.
126     * @param string $quotedValue The original value in the search string, including quotes if used
127     * @param bool $negated Is the search negated? Not used to generate the returned AbstractQuery,
128     *  that will be negated as necessary. Used for any other building/context necessary.
129     * @return array Two element array, first an AbstractQuery or null to apply to the
130     *  query. Second a boolean indicating if the quotedValue should be kept in the search
131     *  string.
132     */
133    protected function doApply( SearchContext $context, $key, $value, $quotedValue, $negated ) {
134        // not used
135        $filter = $this->doGetFilterQuery(
136            $this->parseValue( $key, $value, $quotedValue, '', '', $context ) );
137        return [ $filter, false ];
138    }
139}