MediaWiki master
ChangesListFilter.php
Go to the documentation of this file.
1<?php
23
31abstract class ChangesListFilter {
37 protected $name;
38
47 protected $cssClassSuffix;
48
55
61 protected $group;
62
68 protected $label;
69
75 protected $description;
76
83 protected $conflictingGroups = [];
84
91 protected $conflictingFilters = [];
92
98 protected $subsetFilters = [];
99
105 protected $priority;
106
111
112 private const RESERVED_NAME_CHAR = '_';
113
145 public function __construct( array $filterDefinition ) {
146 if ( isset( $filterDefinition['group'] ) ) {
147 $this->group = $filterDefinition['group'];
148 } else {
149 throw new InvalidArgumentException( 'You must use \'group\' to specify the ' .
150 'ChangesListFilterGroup this filter belongs to' );
151 }
152
153 if ( strpos( $filterDefinition['name'], self::RESERVED_NAME_CHAR ) !== false ) {
154 throw new InvalidArgumentException( 'Filter names may not contain \'' .
155 self::RESERVED_NAME_CHAR .
156 '\'. Use the naming convention: \'lowercase\''
157 );
158 }
159
160 if ( $this->group->getFilter( $filterDefinition['name'] ) ) {
161 throw new InvalidArgumentException( 'Two filters in a group cannot have the ' .
162 "same name: '{$filterDefinition['name']}'" );
163 }
164
165 $this->name = $filterDefinition['name'];
166
167 if ( isset( $filterDefinition['cssClassSuffix'] ) ) {
168 $this->cssClassSuffix = $filterDefinition['cssClassSuffix'];
169 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset Documented as required
170 $this->isRowApplicableCallable = $filterDefinition['isRowApplicableCallable'];
171 }
172
173 if ( isset( $filterDefinition['label'] ) ) {
174 $this->label = $filterDefinition['label'];
175 $this->description = $filterDefinition['description'];
176 }
177
178 $this->priority = $filterDefinition['priority'];
179
180 $this->group->registerFilter( $this );
181 }
182
197 public function conflictsWith( $other, string $globalKey, string $forwardKey, string $backwardKey ) {
199 $other,
200 $globalKey,
201 $forwardKey
202 );
203
204 $other->setUnidirectionalConflict(
205 $this,
206 $globalKey,
207 $backwardKey
208 );
209 }
210
222 public function setUnidirectionalConflict( $other, $globalDescription, $contextDescription ) {
223 if ( $other instanceof ChangesListFilterGroup ) {
224 $this->conflictingGroups[] = [
225 'group' => $other->getName(),
226 'groupObject' => $other,
227 'globalDescription' => $globalDescription,
228 'contextDescription' => $contextDescription,
229 ];
230 } elseif ( $other instanceof ChangesListFilter ) {
231 $this->conflictingFilters[] = [
232 'group' => $other->getGroup()->getName(),
233 'filter' => $other->getName(),
234 'filterObject' => $other,
235 'globalDescription' => $globalDescription,
236 'contextDescription' => $contextDescription,
237 ];
238 } else {
239 throw new InvalidArgumentException(
240 'You can only pass in a ChangesListFilterGroup or a ChangesListFilter'
241 );
242 }
243 }
244
254 public function setAsSupersetOf( ChangesListFilter $other ) {
255 if ( $other->getGroup() !== $this->getGroup() ) {
256 throw new InvalidArgumentException( '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.
conflictsWith( $other, string $globalKey, string $forwardKey, string $backwardKey)
Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object.
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.
Helper class to keep track of options when mixing links and form elements.
Utility class for creating and reading rows in the recentchanges table.
Interface for objects which can provide a MediaWiki context on request.