RCFilters: define consistent interface in ChangesListFilterGroup
[lhc/web/wiklou.git] / includes / changes / ChangesListStringOptionsFilterGroup.php
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 * @license GPL 2+
22 * @author Matthew Flaschen
23 */
24
25 use Wikimedia\Rdbms\IDatabase;
26
27 /**
28 * Represents a filter group with multiple string options. They are passed to the server as
29 * a single form parameter separated by a delimiter. The parameter name is the
30 * group name. E.g. groupname=opt1;opt2 .
31 *
32 * If all options are selected they are replaced by the term "all".
33 *
34 * There is also a single DB query modification for the whole group.
35 *
36 * @since 1.29
37 */
38 class ChangesListStringOptionsFilterGroup extends ChangesListFilterGroup {
39 /**
40 * Type marker, used by JavaScript
41 */
42 const TYPE = 'string_options';
43
44 /**
45 * Delimiter
46 */
47 const SEPARATOR = ';';
48
49 /**
50 * Signifies that all options in the group are selected.
51 */
52 const ALL = 'all';
53
54 /**
55 * Signifies that no options in the group are selected, meaning the group has no effect.
56 *
57 * For full-coverage groups, this is the same as ALL if all filters are allowed.
58 * For others, it is not.
59 */
60 const NONE = '';
61
62 /**
63 * Defaul parameter value
64 *
65 * @var string $defaultValue
66 */
67 protected $defaultValue;
68
69 /**
70 * Callable used to do the actual query modification; see constructor
71 *
72 * @var callable $queryCallable
73 */
74 protected $queryCallable;
75
76 /**
77 * Create a new filter group with the specified configuration
78 *
79 * @param array $groupDefinition Configuration of group
80 * * $groupDefinition['name'] string Group name
81 * * $groupDefinition['title'] string i18n key for title (optional, can be omitted
82 * only if none of the filters in the group display in the structured UI)
83 * * $groupDefinition['priority'] int Priority integer. Higher means higher in the
84 * group list.
85 * * $groupDefinition['filters'] array Numeric array of filter definitions, each of which
86 * is an associative array to be passed to the filter constructor. However,
87 * 'priority' is optional for the filters. Any filter that has priority unset
88 * will be put to the bottom, in the order given.
89 * * $groupDefinition['default'] string Default for group.
90 * * $groupDefinition['isFullCoverage'] bool Whether the group is full coverage;
91 * if true, this means that checking every item in the group means no
92 * changes list entries are filtered out.
93 * * $groupDefinition['queryCallable'] callable Callable accepting parameters:
94 * * string $specialPageClassName Class name of current special page
95 * * IContextSource $context Context, for e.g. user
96 * * IDatabase $dbr Database, for addQuotes, makeList, and similar
97 * * array &$tables Array of tables; see IDatabase::select $table
98 * * array &$fields Array of fields; see IDatabase::select $vars
99 * * array &$conds Array of conditions; see IDatabase::select $conds
100 * * array &$query_options Array of query options; see IDatabase::select $options
101 * * array &$join_conds Array of join conditions; see IDatabase::select $join_conds
102 * * array $selectedValues The allowed and requested values, lower-cased and sorted
103 * * $groupDefinition['whatsThisHeader'] string i18n key for header of "What's
104 * This" popup (optional).
105 * * $groupDefinition['whatsThisBody'] string i18n key for body of "What's This"
106 * popup (optional).
107 * * $groupDefinition['whatsThisUrl'] string URL for main link of "What's This"
108 * popup (optional).
109 * * $groupDefinition['whatsThisLinkText'] string i18n key of text for main link of
110 * "What's This" popup (optional).
111 */
112 public function __construct( array $groupDefinition ) {
113 if ( !isset( $groupDefinition['isFullCoverage'] ) ) {
114 throw new MWException( 'You must specify isFullCoverage' );
115 }
116
117 $groupDefinition['type'] = self::TYPE;
118
119 parent::__construct( $groupDefinition );
120
121 $this->queryCallable = $groupDefinition['queryCallable'];
122
123 if ( isset( $groupDefinition['default'] ) ) {
124 $this->setDefault( $groupDefinition['default'] );
125 } else {
126 throw new MWException( 'You must specify a default' );
127 }
128 }
129
130 /**
131 * Sets default of filter group.
132 *
133 * @param string $defaultValue
134 */
135 public function setDefault( $defaultValue ) {
136 $this->defaultValue = $defaultValue;
137 }
138
139 /**
140 * Gets default of filter group
141 *
142 * @return string $defaultValue
143 */
144 public function getDefault() {
145 return $this->defaultValue;
146 }
147
148 /**
149 * @inheritDoc
150 */
151 protected function createFilter( array $filterDefinition ) {
152 return new ChangesListStringOptionsFilter( $filterDefinition );
153 }
154
155 /**
156 * Registers a filter in this group
157 *
158 * @param ChangesListStringOptionsFilter $filter ChangesListStringOptionsFilter
159 */
160 public function registerFilter( ChangesListStringOptionsFilter $filter ) {
161 $this->filters[$filter->getName()] = $filter;
162 }
163
164 /**
165 * @inheritDoc
166 */
167 public function modifyQuery( IDatabase $dbr, ChangesListSpecialPage $specialPage,
168 &$tables, &$fields, &$conds, &$query_options, &$join_conds,
169 FormOptions $opts, $isStructuredFiltersEnabled
170 ) {
171 if ( !$this->isActive( $isStructuredFiltersEnabled ) ) {
172 return;
173 }
174
175 $value = $opts[ $this->getName() ];
176 $allowedFilterNames = [];
177 foreach ( $this->filters as $filter ) {
178 $allowedFilterNames[] = $filter->getName();
179 }
180
181 if ( $value === self::ALL ) {
182 $selectedValues = $allowedFilterNames;
183 } else {
184 $selectedValues = explode( self::SEPARATOR, strtolower( $value ) );
185
186 // remove values that are not recognized or not currently allowed
187 $selectedValues = array_intersect(
188 $selectedValues,
189 $allowedFilterNames
190 );
191 }
192
193 // If there are now no values, because all are disallowed or invalid (also,
194 // the user may not have selected any), this is a no-op.
195
196 // If everything is unchecked, the group always has no effect, regardless
197 // of full-coverage.
198 if ( count( $selectedValues ) === 0 ) {
199 return;
200 }
201
202 sort( $selectedValues );
203
204 call_user_func_array(
205 $this->queryCallable,
206 [
207 get_class( $specialPage ),
208 $specialPage->getContext(),
209 $dbr,
210 &$tables,
211 &$fields,
212 &$conds,
213 &$query_options,
214 &$join_conds,
215 $selectedValues
216 ]
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
239 /**
240 * Check if this filter group is currently active
241 *
242 * @param {boolean} $isStructuredUI Is structured filters UI current enabled
243 */
244 private function isActive( $isStructuredUI ) {
245 // STRING_OPTIONS filter groups are exclusively active on Structured UI
246 return $isStructuredUI;
247 }
248 }