Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.18% covered (success)
93.18%
41 / 44
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ChangesListStringOptionsFilterGroup
93.18% covered (success)
93.18%
41 / 44
62.50% covered (warning)
62.50%
5 / 8
15.07
0.00% covered (danger)
0.00%
0 / 1
 __construct
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
 setDefault
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDefault
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createFilter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 registerFilter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 modifyQuery
96.30% covered (success)
96.30%
26 / 27
0.00% covered (danger)
0.00%
0 / 1
5
 getJsData
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 addOptions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Represents a filter group (used on ChangesListSpecialPage and descendants)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @author Matthew Flaschen
22 */
23
24use MediaWiki\Html\FormOptions;
25use MediaWiki\SpecialPage\ChangesListSpecialPage;
26use Wikimedia\Rdbms\IReadableDatabase;
27
28/**
29 * Represents a filter group with multiple string options. They are passed to the server as
30 * a single form parameter separated by a delimiter.  The parameter name is the
31 * group name.  E.g. groupname=opt1;opt2 .
32 *
33 * If all options are selected they are replaced by the term "all".
34 *
35 * There is also a single DB query modification for the whole group.
36 *
37 * @since 1.29
38 */
39class ChangesListStringOptionsFilterGroup extends ChangesListFilterGroup {
40    /**
41     * Type marker, used by JavaScript
42     */
43    public const TYPE = 'string_options';
44
45    /**
46     * Delimiter
47     */
48    public const SEPARATOR = ';';
49
50    /**
51     * Signifies that all options in the group are selected.
52     */
53    public const ALL = 'all';
54
55    /**
56     * Signifies that no options in the group are selected, meaning the group has no effect.
57     *
58     * For full-coverage groups, this is the same as ALL if all filters are allowed.
59     * For others, it is not.
60     */
61    public const NONE = '';
62
63    /**
64     * Default parameter value
65     *
66     * @var string
67     */
68    protected $defaultValue;
69
70    /**
71     * Callable used to do the actual query modification; see constructor
72     *
73     * @var callable
74     */
75    protected $queryCallable;
76
77    /**
78     * Create a new filter group with the specified configuration
79     *
80     * @param array $groupDefinition Configuration of group
81     * * $groupDefinition['name'] string Group name
82     * * $groupDefinition['title'] string i18n key for title (optional, can be omitted
83     *     only if none of the filters in the group display in the structured UI)
84     * * $groupDefinition['priority'] int Priority integer.  Higher means higher in the
85     *     group list.
86     * * $groupDefinition['filters'] array Numeric array of filter definitions, each of which
87     *     is an associative array to be passed to the filter constructor.  However,
88     *     'priority' is optional for the filters.  Any filter that has priority unset
89     *     will be put to the bottom, in the order given.
90     * * $groupDefinition['default'] string Default for group.
91     * * $groupDefinition['isFullCoverage'] bool Whether the group is full coverage;
92     *     if true, this means that checking every item in the group means no
93     *     changes list entries are filtered out.
94     * * $groupDefinition['queryCallable'] callable Callable accepting parameters:
95     *     * string $specialPageClassName Class name of current special page
96     *     * IContextSource $context Context, for e.g. user
97     *     * IDatabase $dbr Database, for addQuotes, makeList, and similar
98     *     * array &$tables Array of tables; see IDatabase::select $table
99     *     * array &$fields Array of fields; see IDatabase::select $vars
100     *     * array &$conds Array of conditions; see IDatabase::select $conds
101     *     * array &$query_options Array of query options; see IDatabase::select $options
102     *     * array &$join_conds Array of join conditions; see IDatabase::select $join_conds
103     *     * array $selectedValues The allowed and requested values, lower-cased and sorted
104     * * $groupDefinition['whatsThisHeader'] string i18n key for header of "What's
105     *     This" popup (optional).
106     * * $groupDefinition['whatsThisBody'] string i18n key for body of "What's This"
107     *     popup (optional).
108     * * $groupDefinition['whatsThisUrl'] string URL for main link of "What's This"
109     *     popup (optional).
110     * * $groupDefinition['whatsThisLinkText'] string i18n key of text for main link of
111     *     "What's This" popup (optional).
112     */
113    public function __construct( array $groupDefinition ) {
114        if ( !isset( $groupDefinition['isFullCoverage'] ) ) {
115            throw new InvalidArgumentException( 'You must specify isFullCoverage' );
116        }
117
118        $groupDefinition['type'] = self::TYPE;
119
120        parent::__construct( $groupDefinition );
121
122        $this->queryCallable = $groupDefinition['queryCallable'];
123
124        if ( isset( $groupDefinition['default'] ) ) {
125            $this->setDefault( $groupDefinition['default'] );
126        } else {
127            throw new InvalidArgumentException( 'You must specify a default' );
128        }
129    }
130
131    /**
132     * Sets default of filter group.
133     *
134     * @param string $defaultValue
135     */
136    public function setDefault( $defaultValue ) {
137        $this->defaultValue = $defaultValue;
138    }
139
140    /**
141     * Gets default of filter group
142     *
143     * @return string
144     */
145    public function getDefault() {
146        return $this->defaultValue;
147    }
148
149    /**
150     * @inheritDoc
151     */
152    protected function createFilter( array $filterDefinition ) {
153        return new ChangesListStringOptionsFilter( $filterDefinition );
154    }
155
156    /**
157     * Registers a filter in this group
158     *
159     * @param ChangesListStringOptionsFilter $filter
160     * @suppress PhanParamSignaturePHPDocMismatchHasParamType,PhanParamSignatureMismatch
161     */
162    public function registerFilter( ChangesListStringOptionsFilter $filter ) {
163        $this->filters[$filter->getName()] = $filter;
164    }
165
166    /**
167     * @inheritDoc
168     */
169    public function modifyQuery( IReadableDatabase $dbr, ChangesListSpecialPage $specialPage,
170        &$tables, &$fields, &$conds, &$query_options, &$join_conds,
171        FormOptions $opts, $isStructuredFiltersEnabled
172    ) {
173        // STRING_OPTIONS filter groups are exclusively active on Structured UI
174        if ( !$isStructuredFiltersEnabled ) {
175            return;
176        }
177
178        $value = $opts[ $this->getName() ];
179        $allowedFilterNames = [];
180        foreach ( $this->filters as $filter ) {
181            $allowedFilterNames[] = $filter->getName();
182        }
183
184        if ( $value === self::ALL ) {
185            $selectedValues = $allowedFilterNames;
186        } else {
187            $selectedValues = explode( self::SEPARATOR, strtolower( $value ) );
188
189            // remove values that are not recognized or not currently allowed
190            $selectedValues = array_intersect(
191                $selectedValues,
192                $allowedFilterNames
193            );
194        }
195
196        // If there are now no values, because all are disallowed or invalid (also,
197        // the user may not have selected any), this is a no-op.
198
199        // If everything is unchecked, the group always has no effect, regardless
200        // of full-coverage.
201        if ( count( $selectedValues ) === 0 ) {
202            return;
203        }
204
205        sort( $selectedValues );
206
207        ( $this->queryCallable )(
208            get_class( $specialPage ),
209            $specialPage->getContext(),
210            $dbr,
211            $tables,
212            $fields,
213            $conds,
214            $query_options,
215            $join_conds,
216            $selectedValues
217        );
218    }
219
220    /**
221     * @inheritDoc
222     */
223    public function getJsData() {
224        $output = parent::getJsData();
225
226        $output['separator'] = self::SEPARATOR;
227        $output['default'] = $this->getDefault();
228
229        return $output;
230    }
231
232    /**
233     * @inheritDoc
234     */
235    public function addOptions( FormOptions $opts, $allowDefaults, $isStructuredFiltersEnabled ) {
236        $opts->add( $this->getName(), $allowDefaults ? $this->getDefault() : '' );
237    }
238}