Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
44 / 44
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
SubPageOfFeature
100.00% covered (success)
100.00%
44 / 44
100.00% covered (success)
100.00%
9 / 9
19
100.00% covered (success)
100.00%
1 / 1
 getKeywords
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCrossSearchStrategy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doApply
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 getFilterQuery
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 doGetFilterQuery
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 parseValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doParseValue
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 doGetHLFields
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
3
 buildHighlightFields
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace CirrusSearch\Query;
4
5use CirrusSearch\CrossSearchStrategy;
6use CirrusSearch\Parser\AST\KeywordFeatureNode;
7use CirrusSearch\Query\Builder\QueryBuildingContext;
8use CirrusSearch\Search\Fetch\HighlightedField;
9use CirrusSearch\Search\Fetch\HighlightFieldGenerator;
10use CirrusSearch\Search\SearchContext;
11use CirrusSearch\WarningCollector;
12use Elastica\Query\AbstractQuery;
13use Elastica\Query\MatchQuery;
14use Elastica\Query\MultiMatch;
15
16/**
17 * subpagesof, find subpages of a given page
18 * uses the prefix field, very similar to the prefix except
19 * that it enforces a trailing / and is not a greedy keyword
20 */
21class SubPageOfFeature extends SimpleKeywordFeature implements FilterQueryFeature, HighlightingFeature {
22    /**
23     * @return string[]
24     */
25    protected function getKeywords() {
26        return [ 'subpageof' ];
27    }
28
29    /**
30     * @param KeywordFeatureNode $node
31     * @return CrossSearchStrategy
32     */
33    public function getCrossSearchStrategy( KeywordFeatureNode $node ) {
34        return CrossSearchStrategy::allWikisStrategy();
35    }
36
37    /**
38     * @param SearchContext $context
39     * @param string $key The keyword
40     * @param string $value The value attached to the keyword with quotes stripped
41     * @param string $quotedValue The original value in the search string, including quotes if used
42     * @param bool $negated Is the search negated? Not used to generate the returned AbstractQuery,
43     *  that will be negated as necessary. Used for any other building/context necessary.
44     * @return array Two element array, first an AbstractQuery or null to apply to the
45     *  query. Second a boolean indicating if the quotedValue should be kept in the search
46     *  string.
47     */
48    protected function doApply( SearchContext $context, $key, $value, $quotedValue, $negated ) {
49        $parsedValue = $this->doParseValue( $value );
50        if ( $parsedValue === null ) {
51            return [ null, false ];
52        }
53        $q = $this->doGetFilterQuery( $parsedValue );
54        if ( !$negated ) {
55            foreach ( $this->doGetHLFields( $parsedValue, $context->getFetchPhaseBuilder() ) as $f ) {
56                $context->getFetchPhaseBuilder()->addHLField( $f );
57            }
58        }
59        return [ $q, false ];
60    }
61
62    /**
63     * @param KeywordFeatureNode $node
64     * @param QueryBuildingContext $context
65     * @return AbstractQuery|null
66     */
67    public function getFilterQuery( KeywordFeatureNode $node, QueryBuildingContext $context ) {
68        if ( $node->getParsedValue() === null ) {
69            return null;
70        }
71        return $this->doGetFilterQuery( $node->getParsedValue() );
72    }
73
74    /**
75     * @param array $parsedValue
76     * @return AbstractQuery
77     */
78    private function doGetFilterQuery( array $parsedValue ): AbstractQuery {
79        $query = new MultiMatch();
80        $query->setFields( [ 'title.prefix', 'redirect.title.prefix' ] );
81        $query->setQuery( $parsedValue['prefix'] );
82        return $query;
83    }
84
85    /**
86     * @inheritDoc
87     */
88    public function parseValue( $key, $value, $quotedValue, $valueDelimiter, $suffix, WarningCollector $warningCollector ) {
89        return $this->doParseValue( $value );
90    }
91
92    /**
93     * @param string $value
94     * @return array|null
95     */
96    private function doParseValue( $value ) {
97        if ( $value !== '' ) {
98            $lastC = substr( $value, -1 );
99            if ( $lastC !== '/' && $lastC !== '*' ) {
100                $value .= '/';
101            } elseif ( $lastC === '*' ) {
102                $value = substr( $value, 0, -1 );
103            }
104            return [ 'prefix' => $value ];
105        }
106        return null;
107    }
108
109    /**
110     * @param array $parsedValue
111     * @param HighlightFieldGenerator $highlightFieldGenerator
112     * @return HighlightedField[]
113     */
114    private function doGetHLFields( array $parsedValue, HighlightFieldGenerator $highlightFieldGenerator ) {
115        $hlfields = [];
116        $definition = [
117            HighlightedField::TARGET_TITLE_SNIPPET => 'title.prefix',
118            HighlightedField::TARGET_REDIRECT_SNIPPET => 'redirect.title.prefix',
119        ];
120        $first = true;
121        foreach ( $definition as $target => $esfield ) {
122            $field = $highlightFieldGenerator->newHighlightField( $esfield, $target,
123                 HighlightedField::EXPERT_SYNTAX_PRIORITY );
124            $field->setHighlightQuery( new MatchQuery( $esfield, $parsedValue['prefix'] ) );
125            $field->setNumberOfFragments( 1 );
126            $field->setFragmentSize( 10000 );
127            if ( $first ) {
128                $first = false;
129            } else {
130                $field->skipIfLastMatched();
131            }
132            $hlfields[] = $field;
133        }
134        return $hlfields;
135    }
136
137    /**
138     * @inheritDoc
139     */
140    public function buildHighlightFields( KeywordFeatureNode $node, QueryBuildingContext $context ) {
141        return $this->doGetHLFields( $node->getParsedValue(), $context->getHighlightFieldGenerator() );
142    }
143}