Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
FileTypeFeature
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
7 / 7
11
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
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
 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%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 parseValue
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
4
 matchFileType
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getFilterQuery
100.00% covered (success)
100.00%
2 / 2
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\Filters;
9use CirrusSearch\Search\SearchContext;
10use CirrusSearch\WarningCollector;
11use Elastica\Query;
12use Elastica\Query\AbstractQuery;
13use MediaWiki\Config\Config;
14
15/**
16 * File type features:
17 *  filetype:bitmap
18 * Types can be OR'd together:
19 *  filetype:bitmap|png
20 * Selects only files of these specified features.
21 */
22class FileTypeFeature extends SimpleKeywordFeature implements FilterQueryFeature {
23    public const MAX_CONDITIONS = 20;
24
25    /** @var string[] Map of aliases from user provided term to term to search for */
26    private $aliases;
27
28    public function __construct( Config $config ) {
29        $this->aliases = $config->get( 'CirrusSearchFiletypeAliases' );
30    }
31
32    /**
33     * @return string[]
34     */
35    protected function getKeywords() {
36        return [ 'filetype' ];
37    }
38
39    /**
40     * @param KeywordFeatureNode $node
41     * @return CrossSearchStrategy
42     */
43    public function getCrossSearchStrategy( KeywordFeatureNode $node ) {
44        return CrossSearchStrategy::allWikisStrategy();
45    }
46
47    /**
48     * @param SearchContext $context
49     * @param string $key The keyword
50     * @param string $value The value attached to the keyword with quotes stripped
51     * @param string $quotedValue The original value in the search string, including quotes
52     *     if used
53     * @param bool $negated Is the search negated? Not used to generate the returned
54     *     AbstractQuery, that will be negated as necessary. Used for any other building/context
55     *     necessary.
56     * @return array Two element array, first an AbstractQuery or null to apply to the
57     *  query. Second a boolean indicating if the quotedValue should be kept in the search
58     *  string.
59     */
60    protected function doApply( SearchContext $context, $key, $value, $quotedValue, $negated ) {
61        $parsed = $this->parseValue( $key, $value, $quotedValue, '', '', $context );
62        '@phan-var array $parsed';
63        $query = $this->matchFileType( array_merge( $parsed['aliased'], $parsed['user_types'] ) );
64
65        return [ $query, false ];
66    }
67
68    /**
69     * @param string $key
70     * @param string $value
71     * @param string $quotedValue
72     * @param string $valueDelimiter
73     * @param string $suffix
74     * @param WarningCollector $warningCollector
75     * @return array|false|null
76     */
77    public function parseValue( $key, $value, $quotedValue, $valueDelimiter, $suffix, WarningCollector $warningCollector ) {
78        // TODO: The explode/count/warn is repeated elsewhere and should be generalized?
79        $types = explode( '|', $value );
80        if ( count( $types ) > self::MAX_CONDITIONS ) {
81            $warningCollector->addWarning(
82                'cirrussearch-feature-too-many-conditions',
83                $key,
84                self::MAX_CONDITIONS
85            );
86            $types = array_slice(
87                $types,
88                0,
89                self::MAX_CONDITIONS
90            );
91        }
92
93        $aliased = [];
94        foreach ( $types as $type ) {
95            $lcType = mb_strtolower( $type );
96            if ( isset( $this->aliases[$lcType] ) ) {
97                $aliased[] = $this->aliases[$lcType];
98            }
99        }
100
101        return [
102            'user_types' => $types,
103            'aliased' => $aliased,
104        ];
105    }
106
107    /**
108     * @param string[] $types
109     * @return Query\BoolQuery|Query\MatchQuery|null
110     */
111    protected function matchFileType( $types ) {
112        $queries = [];
113        foreach ( $types as $type ) {
114            $queries[] = new Query\MatchQuery( 'file_media_type', [ 'query' => $type ] );
115        }
116
117        return Filters::booleanOr( $queries, false );
118    }
119
120    /**
121     * @param KeywordFeatureNode $node
122     * @param QueryBuildingContext $context
123     * @return AbstractQuery|null
124     */
125    public function getFilterQuery( KeywordFeatureNode $node, QueryBuildingContext $context ) {
126        $parsed = $node->getParsedValue();
127        '@phan-var array $parsed';
128        return $this->matchFileType( array_merge( $parsed['aliased'], $parsed['user_types'] ) );
129    }
130}