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    /** @inheritDoc */
31    protected function doApply( SearchContext $context, $key, $value, $quotedValue, $negated ) {
32        $parsedValue = $this->parseValue( $key, $value, $quotedValue, '', '', $context );
33        return [ $this->doGetFilterQuery( $parsedValue ), false ];
34    }
35
36    /** @inheritDoc */
37    protected function getKeywords() {
38        return [ 'hasrecommendation' ];
39    }
40
41    /**
42     * @param string $key
43     * @param string $value
44     * @param string $quotedValue
45     * @param string $valueDelimiter
46     * @param string $suffix
47     * @param WarningCollector $warningCollector
48     * @return array|false|null
49     */
50    public function parseValue( $key, $value, $quotedValue, $valueDelimiter, $suffix,
51                                WarningCollector $warningCollector ) {
52        $recFlags = explode( "|", $value );
53        if ( count( $recFlags ) > self::QUERY_LIMIT ) {
54            $warningCollector->addWarning(
55                'cirrussearch-feature-too-many-conditions',
56                $key,
57                self::QUERY_LIMIT
58            );
59            $recFlags = array_slice( $recFlags, 0, self::QUERY_LIMIT );
60        }
61        return [ 'recommendationflags' => $recFlags ];
62    }
63
64    /**
65     * @param array[] $parsedValue
66     * @return AbstractQuery|null
67     */
68    private function doGetFilterQuery( array $parsedValue ): ?AbstractQuery {
69        $queries = [];
70        foreach ( $parsedValue['recommendationflags'] as $recFlag ) {
71            $tagValue = "recommendation." . $recFlag . '/exists';
72            $queries[] = ( new MatchQuery() )->setFieldQuery( WeightedTagsHooks::FIELD_NAME, $tagValue );
73        }
74        $query = Filters::booleanOr( $queries, false );
75
76        return $query;
77    }
78
79    /**
80     * @param KeywordFeatureNode $node
81     * @param QueryBuildingContext $context
82     * @return AbstractQuery|null
83     */
84    public function getFilterQuery( KeywordFeatureNode $node, QueryBuildingContext $context ): ?AbstractQuery {
85        return $this->doGetFilterQuery( $node->getParsedValue() );
86    }
87
88}