MediaWiki  master
ChangesListFilter.php
Go to the documentation of this file.
1 <?php
25 
31 abstract 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 ) {
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 
460  public function activelyInConflictWithFilter( ChangesListFilter $filter, FormOptions $opts ) {
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.
const CSS_CLASS_PREFIX
Definition: ChangesList.php:47
Helper class to keep track of options when mixing links and form elements.
Definition: FormOptions.php:41
Utility class for creating new RC entries.
Interface for objects which can provide a MediaWiki context on request.