MediaWiki master
ChangesListFilter.php
Go to the documentation of this file.
1<?php
26
32abstract class ChangesListFilter {
38 protected $name;
39
48 protected $cssClassSuffix;
49
56
62 protected $group;
63
69 protected $label;
70
76 protected $description;
77
84 protected $conflictingGroups = [];
85
92 protected $conflictingFilters = [];
93
99 protected $subsetFilters = [];
100
106 protected $priority;
107
112
113 private const RESERVED_NAME_CHAR = '_';
114
146 public function __construct( array $filterDefinition ) {
147 if ( isset( $filterDefinition['group'] ) ) {
148 $this->group = $filterDefinition['group'];
149 } else {
150 throw new InvalidArgumentException( 'You must use \'group\' to specify the ' .
151 'ChangesListFilterGroup this filter belongs to' );
152 }
153
154 if ( strpos( $filterDefinition['name'], self::RESERVED_NAME_CHAR ) !== false ) {
155 throw new InvalidArgumentException( 'Filter names may not contain \'' .
156 self::RESERVED_NAME_CHAR .
157 '\'. Use the naming convention: \'lowercase\''
158 );
159 }
160
161 if ( $this->group->getFilter( $filterDefinition['name'] ) ) {
162 throw new InvalidArgumentException( 'Two filters in a group cannot have the ' .
163 "same name: '{$filterDefinition['name']}'" );
164 }
165
166 $this->name = $filterDefinition['name'];
167
168 if ( isset( $filterDefinition['cssClassSuffix'] ) ) {
169 $this->cssClassSuffix = $filterDefinition['cssClassSuffix'];
170 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset Documented as required
171 $this->isRowApplicableCallable = $filterDefinition['isRowApplicableCallable'];
172 }
173
174 if ( isset( $filterDefinition['label'] ) ) {
175 $this->label = $filterDefinition['label'];
176 $this->description = $filterDefinition['description'];
177 }
178
179 $this->priority = $filterDefinition['priority'];
180
181 $this->group->registerFilter( $this );
182 }
183
198 public function conflictsWith( $other, string $globalKey, string $forwardKey, string $backwardKey ) {
200 $other,
201 $globalKey,
202 $forwardKey
203 );
204
205 $other->setUnidirectionalConflict(
206 $this,
207 $globalKey,
208 $backwardKey
209 );
210 }
211
223 public function setUnidirectionalConflict( $other, $globalDescription, $contextDescription ) {
224 if ( $other instanceof ChangesListFilterGroup ) {
225 $this->conflictingGroups[] = [
226 'group' => $other->getName(),
227 'groupObject' => $other,
228 'globalDescription' => $globalDescription,
229 'contextDescription' => $contextDescription,
230 ];
231 } elseif ( $other instanceof ChangesListFilter ) {
232 $this->conflictingFilters[] = [
233 'group' => $other->getGroup()->getName(),
234 'filter' => $other->getName(),
235 'filterObject' => $other,
236 'globalDescription' => $globalDescription,
237 'contextDescription' => $contextDescription,
238 ];
239 } else {
240 throw new InvalidArgumentException(
241 'You can only pass in a ChangesListFilterGroup or a ChangesListFilter'
242 );
243 }
244 }
245
255 public function setAsSupersetOf( ChangesListFilter $other ) {
256 if ( $other->getGroup() !== $this->getGroup() ) {
257 throw new InvalidArgumentException( 'Supersets can only be defined for filters in the same group' );
258 }
259
260 $this->subsetFilters[] = [
261 // It's always the same group, but this makes the representation
262 // more consistent with conflicts.
263 'group' => $other->getGroup()->getName(),
264 'filter' => $other->getName(),
265 ];
266 }
267
271 public function getName() {
272 return $this->name;
273 }
274
278 public function getGroup() {
279 return $this->group;
280 }
281
285 public function getLabel() {
286 return $this->label;
287 }
288
292 public function getDescription() {
293 return $this->description;
294 }
295
301 abstract public function displaysOnUnstructuredUi();
302
309 public function displaysOnStructuredUi() {
310 return $this->label !== null;
311 }
312
321 return $this->displaysOnStructuredUi();
322 }
323
327 public function getPriority() {
328 return $this->priority;
329 }
330
336 protected function getCssClass() {
337 if ( $this->cssClassSuffix !== null ) {
338 return ChangesList::CSS_CLASS_PREFIX . $this->cssClassSuffix;
339 } else {
340 return null;
341 }
342 }
343
351 public function applyCssClassIfNeeded( IContextSource $ctx, RecentChange $rc, array &$classes ) {
352 if ( $this->isRowApplicableCallable === null ) {
353 return;
354 }
355
356 if ( call_user_func( $this->isRowApplicableCallable, $ctx, $rc ) ) {
357 $classes[] = $this->getCssClass();
358 }
359 }
360
368 public function getJsData() {
369 $output = [
370 'name' => $this->getName(),
371 'label' => $this->getLabel(),
372 'description' => $this->getDescription(),
373 'cssClass' => $this->getCssClass(),
374 'priority' => $this->priority,
375 'subset' => $this->subsetFilters,
376 'conflicts' => [],
377 'defaultHighlightColor' => $this->defaultHighlightColor
378 ];
379
380 $output['messageKeys'] = [
381 $this->getLabel(),
382 $this->getDescription(),
383 ];
384
385 $conflicts = array_merge(
386 $this->conflictingGroups,
387 $this->conflictingFilters
388 );
389
390 foreach ( $conflicts as $conflictInfo ) {
391 unset( $conflictInfo['filterObject'] );
392 unset( $conflictInfo['groupObject'] );
393 $output['conflicts'][] = $conflictInfo;
394 array_push(
395 $output['messageKeys'],
396 $conflictInfo['globalDescription'],
397 $conflictInfo['contextDescription']
398 );
399 }
400
401 return $output;
402 }
403
410 abstract public function isSelected( FormOptions $opts );
411
417 public function getConflictingGroups() {
418 return array_column( $this->conflictingGroups, 'groupObject' );
419 }
420
426 public function getConflictingFilters() {
427 return array_column( $this->conflictingFilters, 'filterObject' );
428 }
429
438 if ( $group->anySelected( $opts ) && $this->isSelected( $opts ) ) {
440 foreach ( $this->getSiblings() as $siblingFilter ) {
441 if ( $siblingFilter->isSelected( $opts ) && !$siblingFilter->hasConflictWithGroup( $group ) ) {
442 return false;
443 }
444 }
445 return true;
446 }
447 return false;
448 }
449
450 private function hasConflictWithGroup( ChangesListFilterGroup $group ) {
451 return in_array( $group, $this->getConflictingGroups() );
452 }
453
462 if ( $this->isSelected( $opts ) && $filter->isSelected( $opts ) ) {
464 foreach ( $this->getSiblings() as $siblingFilter ) {
465 if (
466 $siblingFilter->isSelected( $opts ) &&
467 !$siblingFilter->hasConflictWithFilter( $filter )
468 ) {
469 return false;
470 }
471 }
472 return true;
473 }
474 return false;
475 }
476
477 private function hasConflictWithFilter( ChangesListFilter $filter ) {
478 return in_array( $filter, $this->getConflictingFilters() );
479 }
480
486 protected function getSiblings() {
487 return array_filter(
488 $this->getGroup()->getFilters(),
489 function ( $filter ) {
490 return $filter !== $this;
491 }
492 );
493 }
494
499 $this->defaultHighlightColor = $defaultHighlightColor;
500 }
501}
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 new RC entries.
Interface for objects which can provide a MediaWiki context on request.