Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
GlobalCustomFilter
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
10 / 10
23
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
 setRequiredPlugins
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setFallbackFilter
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setMustFollowFilters
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setDenyList
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setApplyToAnalyzers
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getApplyToAnalyzers
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 pluginsAvailable
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 enableGlobalCustomFilters
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
7
 insertGlobalCustomFilter
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2
3namespace CirrusSearch\Maintenance;
4
5class GlobalCustomFilter {
6    /** @var string filter type, probably 'filter' or 'char_filter'; 'filter' by default */
7    private $type;
8
9    /** @var string[] plugins that must be present to use the filter */
10    private $requiredPlugins = [];
11
12    /** @var string local filter to use instead if requiredPlugins are not available */
13    private $fallbackFilter = '';
14
15    /** @var string[] filters this one must come after. see T268730 */
16    private $mustFollowFilters = [];
17
18    /** @var string[] languages where this filter should not be used, by language codes */
19    private $denyList = [];
20
21    /** @var string[] which analyzers to apply to; 'text' and 'text_search' by default */
22    private $applyToAnalyzers = [ 'text', 'text_search' ];
23
24    public function __construct( string $type = 'filter' ) {
25        $this->type = $type;
26    }
27
28    /**
29     * @param string[] $requiredPlugins
30     * @return self
31     */
32    public function setRequiredPlugins( array $requiredPlugins ): self {
33        $this->requiredPlugins = $requiredPlugins;
34        return $this;
35    }
36
37    /**
38     * @param string $fallbackFilter
39     * @return self
40     */
41    public function setFallbackFilter( string $fallbackFilter ): self {
42        $this->fallbackFilter = $fallbackFilter;
43        return $this;
44    }
45
46    /**
47     * @param string[] $mustFollowFilters
48     * @return self
49     */
50    public function setMustFollowFilters( array $mustFollowFilters ): self {
51        $this->mustFollowFilters = $mustFollowFilters;
52        return $this;
53    }
54
55    /**
56     * @param string[] $denyList
57     * @return self
58     */
59    public function setDenyList( array $denyList ): self {
60        $this->denyList = $denyList;
61        return $this;
62    }
63
64    /**
65     * @param string[] $applyToAnalyzers
66     * @return self
67     */
68    public function setApplyToAnalyzers( array $applyToAnalyzers ): self {
69        $this->applyToAnalyzers = $applyToAnalyzers;
70        return $this;
71    }
72
73    public function getApplyToAnalyzers() {
74        return $this->applyToAnalyzers;
75    }
76
77    /**
78     * check to see if the filter is compatible with the set of installed plugins
79     *
80     * @param string[] $installedPlugins
81     * @return bool
82     */
83    public function pluginsAvailable( array $installedPlugins ): bool {
84        foreach ( $this->requiredPlugins as $reqPlugin ) {
85            if ( !in_array( $reqPlugin, $installedPlugins ) ) {
86                return false;
87            }
88        }
89        return true;
90    }
91
92    /**
93     * update languages with global custom filters (e.g., homoglyph & nnbsp filters)
94     *
95     * @param mixed[] $config
96     * @param string $language
97     * @param GlobalCustomFilter[] $customFilters list of filters and info
98     * @param string[] $installedPlugins
99     * @return mixed[] updated config
100     */
101    public static function enableGlobalCustomFilters( array $config, string $language,
102            array $customFilters, array $installedPlugins ) {
103        foreach ( $customFilters as $gcf => $gcfInfo ) {
104            $filterName = $gcf;
105
106            if ( !in_array( $language, $gcfInfo->denyList ) ) {
107                $filterIsUsable = $gcfInfo->pluginsAvailable( $installedPlugins );
108
109                if ( !$filterIsUsable && $gcfInfo->fallbackFilter ) {
110                    $filterName = $gcfInfo->fallbackFilter;
111                    $filterIsUsable = true;
112                }
113
114                if ( $filterIsUsable ) {
115                    foreach ( $gcfInfo->getApplyToAnalyzers() as $analyzer ) {
116                        $config = $gcfInfo->insertGlobalCustomFilter( $config, $analyzer,
117                            $filterName, $gcfInfo );
118                    }
119                }
120            }
121        }
122
123        return $config;
124    }
125
126    /**
127     * insert one of the global custom filters into the right spot in the analysis chain
128     * @param mixed[] $config the analysis config we are modifying
129     * @param string $analyzer the specifc analyzer we are modifying
130     * @param string $filterName filter to add
131     * @param GlobalCustomFilter $filterInfo includes filter type & incompatible filters
132     * @return mixed[] updated config
133     */
134    public static function insertGlobalCustomFilter( array $config, string $analyzer,
135        string $filterName, GlobalCustomFilter $filterInfo ) {
136        if ( !array_key_exists( $analyzer, $config['analyzer'] ) ) {
137            return $config;
138        }
139
140        if ( $config['analyzer'][$analyzer]['type'] == 'custom' ) {
141            $filters = $config['analyzer'][$analyzer][$filterInfo->type] ?? [];
142
143            $lastMustFollow = -1;
144            foreach ( $filterInfo->mustFollowFilters as $mustFollow ) {
145                $mustFollowIdx = array_keys( $filters, $mustFollow );
146                $mustFollowIdx = end( $mustFollowIdx );
147                if ( $mustFollowIdx !== false && $mustFollowIdx > $lastMustFollow ) {
148                    $lastMustFollow = $mustFollowIdx;
149                }
150            }
151            array_splice( $filters, $lastMustFollow + 1, 0, $filterName );
152
153            $config['analyzer'][$analyzer][$filterInfo->type] = $filters;
154        }
155        return $config;
156    }
157
158}