MediaWiki REL1_39
ChangesListFilter.php
Go to the documentation of this file.
1<?php
29abstract class ChangesListFilter {
35 protected $name;
36
45 protected $cssClassSuffix;
46
53
59 protected $group;
60
66 protected $label;
67
73 protected $description;
74
81 protected $conflictingGroups = [];
82
89 protected $conflictingFilters = [];
90
96 protected $subsetFilters = [];
97
103 protected $priority;
104
109
110 private const RESERVED_NAME_CHAR = '_';
111
143 public function __construct( array $filterDefinition ) {
144 if ( isset( $filterDefinition['group'] ) ) {
145 $this->group = $filterDefinition['group'];
146 } else {
147 throw new MWException( 'You must use \'group\' to specify the ' .
148 'ChangesListFilterGroup this filter belongs to' );
149 }
150
151 if ( strpos( $filterDefinition['name'], self::RESERVED_NAME_CHAR ) !== false ) {
152 throw new MWException( 'Filter names may not contain \'' .
153 self::RESERVED_NAME_CHAR .
154 '\'. Use the naming convention: \'lowercase\''
155 );
156 }
157
158 if ( $this->group->getFilter( $filterDefinition['name'] ) ) {
159 throw new MWException( 'Two filters in a group cannot have the ' .
160 "same name: '{$filterDefinition['name']}'" );
161 }
162
163 $this->name = $filterDefinition['name'];
164
165 if ( isset( $filterDefinition['cssClassSuffix'] ) ) {
166 $this->cssClassSuffix = $filterDefinition['cssClassSuffix'];
167 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset Documented as required
168 $this->isRowApplicableCallable = $filterDefinition['isRowApplicableCallable'];
169 }
170
171 if ( isset( $filterDefinition['label'] ) ) {
172 $this->label = $filterDefinition['label'];
173 $this->description = $filterDefinition['description'];
174 }
175
176 $this->priority = $filterDefinition['priority'];
177
178 $this->group->registerFilter( $this );
179 }
180
195 public function conflictsWith( $other, $globalKey, $forwardKey, $backwardKey ) {
196 if ( $globalKey === null || $forwardKey === null || $backwardKey === null ) {
197 throw new MWException( 'All messages must be specified' );
198 }
199
201 $other,
202 $globalKey,
203 $forwardKey
204 );
205
206 $other->setUnidirectionalConflict(
207 $this,
208 $globalKey,
209 $backwardKey
210 );
211 }
212
224 public function setUnidirectionalConflict( $other, $globalDescription, $contextDescription ) {
225 if ( $other instanceof ChangesListFilterGroup ) {
226 $this->conflictingGroups[] = [
227 'group' => $other->getName(),
228 'groupObject' => $other,
229 'globalDescription' => $globalDescription,
230 'contextDescription' => $contextDescription,
231 ];
232 } elseif ( $other instanceof ChangesListFilter ) {
233 $this->conflictingFilters[] = [
234 'group' => $other->getGroup()->getName(),
235 'filter' => $other->getName(),
236 'filterObject' => $other,
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
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
270 public function getName() {
271 return $this->name;
272 }
273
277 public function getGroup() {
278 return $this->group;
279 }
280
284 public function getLabel() {
285 return $this->label;
286 }
287
291 public function getDescription() {
292 return $this->description;
293 }
294
300 abstract public function displaysOnUnstructuredUi();
301
308 public function displaysOnStructuredUi() {
309 return $this->label !== null;
310 }
311
320 return $this->displaysOnStructuredUi();
321 }
322
326 public function getPriority() {
327 return $this->priority;
328 }
329
335 protected function getCssClass() {
336 if ( $this->cssClassSuffix !== null ) {
337 return ChangesList::CSS_CLASS_PREFIX . $this->cssClassSuffix;
338 } else {
339 return null;
340 }
341 }
342
350 public function applyCssClassIfNeeded( IContextSource $ctx, RecentChange $rc, array &$classes ) {
351 if ( $this->isRowApplicableCallable === null ) {
352 return;
353 }
354
355 if ( call_user_func( $this->isRowApplicableCallable, $ctx, $rc ) ) {
356 $classes[] = $this->getCssClass();
357 }
358 }
359
367 public function getJsData() {
368 $output = [
369 'name' => $this->getName(),
370 'label' => $this->getLabel(),
371 'description' => $this->getDescription(),
372 'cssClass' => $this->getCssClass(),
373 'priority' => $this->priority,
374 'subset' => $this->subsetFilters,
375 'conflicts' => [],
376 'defaultHighlightColor' => $this->defaultHighlightColor
377 ];
378
379 $output['messageKeys'] = [
380 $this->getLabel(),
381 $this->getDescription(),
382 ];
383
384 $conflicts = array_merge(
385 $this->conflictingGroups,
386 $this->conflictingFilters
387 );
388
389 foreach ( $conflicts as $conflictInfo ) {
390 unset( $conflictInfo['filterObject'] );
391 unset( $conflictInfo['groupObject'] );
392 $output['conflicts'][] = $conflictInfo;
393 array_push(
394 $output['messageKeys'],
395 $conflictInfo['globalDescription'],
396 $conflictInfo['contextDescription']
397 );
398 }
399
400 return $output;
401 }
402
409 abstract public function isSelected( FormOptions $opts );
410
416 public function getConflictingGroups() {
417 return array_column( $this->conflictingGroups, 'groupObject' );
418 }
419
425 public function getConflictingFilters() {
426 return array_column( $this->conflictingFilters, 'filterObject' );
427 }
428
437 if ( $group->anySelected( $opts ) && $this->isSelected( $opts ) ) {
439 foreach ( $this->getSiblings() as $siblingFilter ) {
440 if ( $siblingFilter->isSelected( $opts ) && !$siblingFilter->hasConflictWithGroup( $group ) ) {
441 return false;
442 }
443 }
444 return true;
445 }
446 return false;
447 }
448
449 private function hasConflictWithGroup( ChangesListFilterGroup $group ) {
450 return in_array( $group, $this->getConflictingGroups() );
451 }
452
461 if ( $this->isSelected( $opts ) && $filter->isSelected( $opts ) ) {
463 foreach ( $this->getSiblings() as $siblingFilter ) {
464 if (
465 $siblingFilter->isSelected( $opts ) &&
466 !$siblingFilter->hasConflictWithFilter( $filter )
467 ) {
468 return false;
469 }
470 }
471 return true;
472 }
473 return false;
474 }
475
476 private function hasConflictWithFilter( ChangesListFilter $filter ) {
477 return in_array( $filter, $this->getConflictingFilters() );
478 }
479
485 protected function getSiblings() {
486 return array_filter(
487 $this->getGroup()->getFilters(),
488 function ( $filter ) {
489 return $filter !== $this;
490 }
491 );
492 }
493
498 $this->defaultHighlightColor = $defaultHighlightColor;
499 }
500}
Represents a filter group (used on ChangesListSpecialPage and descendants)
anySelected(FormOptions $opts)
Check if any filter in this group is selected.
Represents a filter (used on ChangesListSpecialPage and descendants)
displaysOnUnstructuredUi()
Checks whether the filter should display on the unstructured UI.
string null $cssClassSuffix
CSS class suffix used for attribution, e.g.
getConflictingFilters()
Get filters conflicting with this filter.
ChangesListFilterGroup $group
Group.
getJsData()
Gets the JS data required by the front-end of the structured UI.
activelyInConflictWithGroup(ChangesListFilterGroup $group, FormOptions $opts)
Check if the conflict with a group is currently "active".
array $conflictingGroups
Array of associative arrays with conflict information.
int $priority
Priority integer.
array $conflictingFilters
Array of associative arrays with conflict information.
displaysOnStructuredUi()
Checks whether the filter should display on the structured UI This refers to the exact filter.
string $label
i18n key of label for structured UI
setUnidirectionalConflict( $other, $globalDescription, $contextDescription)
Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object.
string $name
Filter name.
isFeatureAvailableOnStructuredUi()
Checks whether an equivalent feature for this filter is available on the structured UI.
applyCssClassIfNeeded(IContextSource $ctx, RecentChange $rc, array &$classes)
Add CSS class if needed.
setAsSupersetOf(ChangesListFilter $other)
Marks that the current instance is (also) a superset of the filter passed in.
__construct(array $filterDefinition)
Creates a new filter with the specified configuration, and registers it to the specified group.
array $subsetFilters
Array of associative arrays with subset information.
isSelected(FormOptions $opts)
Checks whether this filter is selected in the provided options.
setDefaultHighlightColor( $defaultHighlightColor)
string $description
i18n key of description for structured UI
getConflictingGroups()
Get groups conflicting with this filter.
getCssClass()
Gets the CSS class.
getSiblings()
Get filters in the same group.
activelyInConflictWithFilter(ChangesListFilter $filter, FormOptions $opts)
Check if the conflict with a filter is currently "active".
callable $isRowApplicableCallable
Callable that returns true if and only if a row is attributed to this filter.
conflictsWith( $other, $globalKey, $forwardKey, $backwardKey)
Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object.
Helper class to keep track of options when mixing links and form elements.
MediaWiki exception.
Utility class for creating new RC entries.
Interface for objects which can provide a MediaWiki context on request.