Add 3D filetype for STL files
[lhc/web/wiklou.git] / includes / changes / ChangesListFilter.php
1 <?php
2 /**
3 * Represents a filter (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 /**
26 * Represents a filter (used on ChangesListSpecialPage and descendants)
27 *
28 * @since 1.29
29 */
30 abstract class ChangesListFilter {
31 /**
32 * Filter name
33 *
34 * @var string $name
35 */
36 protected $name;
37
38 /**
39 * CSS class suffix used for attribution, e.g. 'bot'.
40 *
41 * In this example, if bot actions are included in the result set, this CSS class
42 * will then be included in all bot-flagged actions.
43 *
44 * @var string|null $cssClassSuffix
45 */
46 protected $cssClassSuffix;
47
48 /**
49 * Callable that returns true if and only if a row is attributed to this filter
50 *
51 * @var callable $isRowApplicableCallable
52 */
53 protected $isRowApplicableCallable;
54
55 /**
56 * Group. ChangesListFilterGroup this belongs to
57 *
58 * @var ChangesListFilterGroup $group
59 */
60 protected $group;
61
62 /**
63 * i18n key of label for structured UI
64 *
65 * @var string $label
66 */
67 protected $label;
68
69 /**
70 * i18n key of description for structured UI
71 *
72 * @var string $description
73 */
74 protected $description;
75
76 /**
77 * List of conflicting groups
78 *
79 * @var array $conflictingGroups Array of associative arrays with conflict
80 * information. See setUnidirectionalConflict
81 */
82 protected $conflictingGroups = [];
83
84 /**
85 * List of conflicting filters
86 *
87 * @var array $conflictingFilters Array of associative arrays with conflict
88 * information. See setUnidirectionalConflict
89 */
90 protected $conflictingFilters = [];
91
92 /**
93 * List of filters that are a subset of the current filter
94 *
95 * @var array $subsetFilters Array of associative arrays with subset information
96 */
97 protected $subsetFilters = [];
98
99 /**
100 * Priority integer. Higher value means higher up in the group's filter list.
101 *
102 * @var string $priority
103 */
104 protected $priority;
105
106 const RESERVED_NAME_CHAR = '_';
107
108 /**
109 * Creates a new filter with the specified configuration, and registers it to the
110 * specified group.
111 *
112 * It infers which UI (it can be either or both) to display the filter on based on
113 * which messages are provided.
114 *
115 * If 'label' is provided, it will be displayed on the structured UI. Thus,
116 * 'label', 'description', and sub-class parameters are optional depending on which
117 * UI it's for.
118 *
119 * @param array $filterDefinition ChangesListFilter definition
120 *
121 * $filterDefinition['name'] string Name of filter; use lowercase with no
122 * punctuation
123 * $filterDefinition['cssClassSuffix'] string CSS class suffix, used to mark
124 * that a particular row belongs to this filter (when a row is included by the
125 * filter) (optional)
126 * $filterDefinition['isRowApplicableCallable'] Callable taking two parameters, the
127 * IContextSource, and the RecentChange object for the row, and returning true if
128 * the row is attributed to this filter. The above CSS class will then be
129 * automatically added (optional, required if cssClassSuffix is used).
130 * $filterDefinition['group'] ChangesListFilterGroup Group. Filter group this
131 * belongs to.
132 * $filterDefinition['label'] string i18n key of label for structured UI.
133 * $filterDefinition['description'] string i18n key of description for structured
134 * UI.
135 * $filterDefinition['priority'] int Priority integer. Higher value means higher
136 * up in the group's filter list.
137 */
138 public function __construct( array $filterDefinition ) {
139 if ( isset( $filterDefinition['group'] ) ) {
140 $this->group = $filterDefinition['group'];
141 } else {
142 throw new MWException( 'You must use \'group\' to specify the ' .
143 'ChangesListFilterGroup this filter belongs to' );
144 }
145
146 if ( strpos( $filterDefinition['name'], self::RESERVED_NAME_CHAR ) !== false ) {
147 throw new MWException( 'Filter names may not contain \'' .
148 self::RESERVED_NAME_CHAR .
149 '\'. Use the naming convention: \'lowercase\''
150 );
151 }
152
153 if ( $this->group->getFilter( $filterDefinition['name'] ) ) {
154 throw new MWException( 'Two filters in a group cannot have the ' .
155 "same name: '{$filterDefinition['name']}'" );
156 }
157
158 $this->name = $filterDefinition['name'];
159
160 if ( isset( $filterDefinition['cssClassSuffix'] ) ) {
161 $this->cssClassSuffix = $filterDefinition['cssClassSuffix'];
162 $this->isRowApplicableCallable = $filterDefinition['isRowApplicableCallable'];
163 }
164
165 if ( isset( $filterDefinition['label'] ) ) {
166 $this->label = $filterDefinition['label'];
167 $this->description = $filterDefinition['description'];
168 }
169
170 $this->priority = $filterDefinition['priority'];
171
172 $this->group->registerFilter( $this );
173 }
174
175 /**
176 * Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object.
177 *
178 * WARNING: This means there is a conflict when both things are *shown*
179 * (not filtered out), even for the hide-based filters. So e.g. conflicting with
180 * 'hideanons' means there is a conflict if only anonymous users are *shown*.
181 *
182 * @param ChangesListFilterGroup|ChangesListFilter $other Other
183 * ChangesListFilterGroup or ChangesListFilter
184 * @param string $globalKey i18n key for top-level conflict message
185 * @param string $forwardKey i18n key for conflict message in this
186 * direction (when in UI context of $this object)
187 * @param string $backwardKey i18n key for conflict message in reverse
188 * direction (when in UI context of $other object)
189 */
190 public function conflictsWith( $other, $globalKey, $forwardKey,
191 $backwardKey ) {
192
193 if ( $globalKey === null || $forwardKey === null ||
194 $backwardKey === null ) {
195
196 throw new MWException( 'All messages must be specified' );
197 }
198
199 $this->setUnidirectionalConflict(
200 $other,
201 $globalKey,
202 $forwardKey
203 );
204
205 $other->setUnidirectionalConflict(
206 $this,
207 $globalKey,
208 $backwardKey
209 );
210 }
211
212 /**
213 * Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with
214 * this object.
215 *
216 * Internal use ONLY.
217 *
218 * @param ChangesListFilterGroup|ChangesListFilter $other Other
219 * ChangesListFilterGroup or ChangesListFilter
220 * @param string $globalDescription i18n key for top-level conflict message
221 * @param string $contextDescription i18n key for conflict message in this
222 * direction (when in UI context of $this object)
223 */
224 public function setUnidirectionalConflict( $other, $globalDescription,
225 $contextDescription ) {
226
227 if ( $other instanceof ChangesListFilterGroup ) {
228 $this->conflictingGroups[] = [
229 'group' => $other->getName(),
230 'globalDescription' => $globalDescription,
231 'contextDescription' => $contextDescription,
232 ];
233 } elseif ( $other instanceof ChangesListFilter ) {
234 $this->conflictingFilters[] = [
235 'group' => $other->getGroup()->getName(),
236 'filter' => $other->getName(),
237 'globalDescription' => $globalDescription,
238 'contextDescription' => $contextDescription,
239 ];
240 } else {
241 throw new MWException( 'You can only pass in a ChangesListFilterGroup or a ChangesListFilter' );
242 }
243 }
244
245 /**
246 * Marks that the current instance is (also) a superset of the filter passed in.
247 * This can be called more than once.
248 *
249 * This means that anything in the results for the other filter is also in the
250 * results for this one.
251 *
252 * @param ChangesListFilter The filter the current instance is a superset of
253 */
254 public function setAsSupersetOf( ChangesListFilter $other ) {
255 if ( $other->getGroup() !== $this->getGroup() ) {
256 throw new MWException( 'Supersets can only be defined for filters in the same group' );
257 }
258
259 $this->subsetFilters[] = [
260 // It's always the same group, but this makes the representation
261 // more consistent with conflicts.
262 'group' => $other->getGroup()->getName(),
263 'filter' => $other->getName(),
264 ];
265 }
266
267 /**
268 * @return string Name, e.g. hideanons
269 */
270 public function getName() {
271 return $this->name;
272 }
273
274 /**
275 * @return ChangesListFilterGroup Group this belongs to
276 */
277 public function getGroup() {
278 return $this->group;
279 }
280
281 /**
282 * @return string i18n key of label for structured UI
283 */
284 public function getLabel() {
285 return $this->label;
286 }
287
288 /**
289 * @return string i18n key of description for structured UI
290 */
291 public function getDescription() {
292 return $this->description;
293 }
294
295 /**
296 * Checks whether the filter should display on the unstructured UI
297 *
298 * @return bool Whether to display
299 */
300 abstract public function displaysOnUnstructuredUi();
301
302 /**
303 * Checks whether the filter should display on the structured UI
304 * This refers to the exact filter. See also isFeatureAvailableOnStructuredUi.
305 *
306 * @return bool Whether to display
307 */
308 public function displaysOnStructuredUi() {
309 return $this->label !== null;
310 }
311
312 /**
313 * Checks whether an equivalent feature for this filter is available on the
314 * structured UI.
315 *
316 * This can either be the exact filter, or a new filter that replaces it.
317 */
318 public function isFeatureAvailableOnStructuredUi() {
319 return $this->displaysOnStructuredUi();
320 }
321
322 /**
323 * @return int Priority. Higher value means higher up in the group list
324 */
325 public function getPriority() {
326 return $this->priority;
327 }
328
329 /**
330 * Gets the CSS class
331 *
332 * @return string|null CSS class, or null if not defined
333 */
334 protected function getCssClass() {
335 if ( $this->cssClassSuffix !== null ) {
336 return ChangesList::CSS_CLASS_PREFIX . $this->cssClassSuffix;
337 } else {
338 return null;
339 }
340 }
341
342 /**
343 * Add CSS class if needed
344 *
345 * @param IContextSource $ctx Context source
346 * @param RecentChange $rc Recent changes object
347 * @param Non-associative array of CSS class names; appended to if needed
348 */
349 public function applyCssClassIfNeeded( IContextSource $ctx, RecentChange $rc, array &$classes ) {
350 if ( $this->isRowApplicableCallable === null ) {
351 return;
352 }
353
354 if ( call_user_func( $this->isRowApplicableCallable, $ctx, $rc ) ) {
355 $classes[] = $this->getCssClass();
356 }
357 }
358
359 /**
360 * Gets the JS data required by the front-end of the structured UI
361 *
362 * @return array Associative array Data required by the front-end. messageKeys is
363 * a special top-level value, with the value being an array of the message keys to
364 * send to the client.
365 */
366 public function getJsData() {
367 $output = [
368 'name' => $this->getName(),
369 'label' => $this->getLabel(),
370 'description' => $this->getDescription(),
371 'cssClass' => $this->getCssClass(),
372 'priority' => $this->priority,
373 'subset' => $this->subsetFilters,
374 'conflicts' => [],
375 ];
376
377 $output['messageKeys'] = [
378 $this->getLabel(),
379 $this->getDescription(),
380 ];
381
382 $conflicts = array_merge(
383 $this->conflictingGroups,
384 $this->conflictingFilters
385 );
386
387 foreach ( $conflicts as $conflictInfo ) {
388 $output['conflicts'][] = $conflictInfo;
389 array_push(
390 $output['messageKeys'],
391 $conflictInfo['globalDescription'],
392 $conflictInfo['contextDescription']
393 );
394 }
395
396 return $output;
397 }
398 }