Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
HasRecommendationFeature
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
5 / 5
7
100.00% covered (success)
100.00%
1 / 1
 doApply
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getKeywords
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parseValue
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 doGetFilterQuery
100.00% covered (success)
100.00%
6 / 6
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
1<?php
2
3namespace CirrusSearch\Query;
4
5use CirrusSearch\Parser\AST\KeywordFeatureNode;
6use CirrusSearch\Query\Builder\QueryBuildingContext;
7use CirrusSearch\Search\Filters;
8use CirrusSearch\Search\SearchContext;
9use CirrusSearch\WarningCollector;
10use CirrusSearch\Wikimedia\WeightedTagsHooks;
11use Elastica\Query\AbstractQuery;
12use Elastica\Query\MatchQuery;
13
14/**
15 * Filters the result set based on the existing article recommendation.
16 * Currently we handle link and image recommendations.
17 *
18 * Examples:
19 *   hasrecommendation:image
20 *   hasrecommendation:link|image
21 */
22class HasRecommendationFeature extends SimpleKeywordFeature implements FilterQueryFeature {
23
24    /**
25     * Limit filtering to 5 recommendation types. Arbitrarily chosen, but should be more
26     * than enough and some sort of limit has to be enforced.
27     */
28    public const QUERY_LIMIT = 5;
29
30    protected function doApply( SearchContext $context, $key, $value, $quotedValue, $negated ) {
31        $parsedValue = $this->parseValue( $key, $value, $quotedValue, '', '', $context );
32        return [ $this->doGetFilterQuery( $parsedValue ), false ];
33    }
34
35    protected function getKeywords() {
36        return [ 'hasrecommendation' ];
37    }
38
39    /**
40     * @param string $key
41     * @param string $value
42     * @param string $quotedValue
43     * @param string $valueDelimiter
44     * @param string $suffix
45     * @param WarningCollector $warningCollector
46     * @return array|false|null
47     */
48    public function parseValue( $key, $value, $quotedValue, $valueDelimiter, $suffix,
49                                WarningCollector $warningCollector ) {
50        $recFlags = explode( "|", $value );
51        if ( count( $recFlags ) > self::QUERY_LIMIT ) {
52            $warningCollector->addWarning(
53                'cirrussearch-feature-too-many-conditions',
54                $key,
55                self::QUERY_LIMIT
56            );
57            $recFlags = array_slice( $recFlags, 0, self::QUERY_LIMIT );
58        }
59        return [ 'recommendationflags' => $recFlags ];
60    }
61
62    /**
63     * @param array[] $parsedValue
64     * @return AbstractQuery|null
65     */
66    private function doGetFilterQuery( array $parsedValue ): ?AbstractQuery {
67        $queries = [];
68        foreach ( $parsedValue['recommendationflags'] as $recFlag ) {
69            $tagValue = "recommendation." . $recFlag . '/exists';
70            $queries[] = ( new MatchQuery() )->setFieldQuery( WeightedTagsHooks::FIELD_NAME, $tagValue );
71        }
72        $query = Filters::booleanOr( $queries, false );
73
74        return $query;
75    }
76
77    /**
78     * @param KeywordFeatureNode $node
79     * @param QueryBuildingContext $context
80     * @return AbstractQuery|null
81     */
82    public function getFilterQuery( KeywordFeatureNode $node, QueryBuildingContext $context ): ?AbstractQuery {
83        return $this->doGetFilterQuery( $node->getParsedValue() );
84    }
85
86}