MediaWiki  master
ChangesListFilterGroup.php
Go to the documentation of this file.
1 <?php
24 // TODO: Might want to make a super-class or trait to share behavior (especially re
25 // conflicts) between ChangesListFilter and ChangesListFilterGroup.
26 // What to call it. FilterStructure? That would also let me make
27 // setUnidirectionalConflict protected.
28 
30 
37 abstract class ChangesListFilterGroup {
43  protected $name;
44 
50  protected $title;
51 
57  protected $whatsThisHeader;
58 
64  protected $whatsThisBody;
65 
71  protected $whatsThisUrl;
72 
78  protected $whatsThisLinkText;
79 
85  protected $type;
86 
93  protected $priority;
94 
100  protected $filters;
101 
108  protected $isFullCoverage;
109 
116  protected $conflictingGroups = [];
117 
124  protected $conflictingFilters = [];
125 
126  const DEFAULT_PRIORITY = -100;
127 
128  const RESERVED_NAME_CHAR = '_';
129 
156  public function __construct( array $groupDefinition ) {
157  if ( strpos( $groupDefinition['name'], self::RESERVED_NAME_CHAR ) !== false ) {
158  throw new MWException( 'Group names may not contain \'' .
159  self::RESERVED_NAME_CHAR .
160  '\'. Use the naming convention: \'camelCase\''
161  );
162  }
163 
164  $this->name = $groupDefinition['name'];
165 
166  if ( isset( $groupDefinition['title'] ) ) {
167  $this->title = $groupDefinition['title'];
168  }
169 
170  if ( isset( $groupDefinition['whatsThisHeader'] ) ) {
171  $this->whatsThisHeader = $groupDefinition['whatsThisHeader'];
172  $this->whatsThisBody = $groupDefinition['whatsThisBody'];
173  $this->whatsThisUrl = $groupDefinition['whatsThisUrl'];
174  $this->whatsThisLinkText = $groupDefinition['whatsThisLinkText'];
175  }
176 
177  $this->type = $groupDefinition['type'];
178  $this->priority = $groupDefinition['priority'] ?? self::DEFAULT_PRIORITY;
179 
180  $this->isFullCoverage = $groupDefinition['isFullCoverage'];
181 
182  $this->filters = [];
183  $lowestSpecifiedPriority = -1;
184  foreach ( $groupDefinition['filters'] as $filterDefinition ) {
185  if ( isset( $filterDefinition['priority'] ) ) {
186  $lowestSpecifiedPriority = min( $lowestSpecifiedPriority, $filterDefinition['priority'] );
187  }
188  }
189 
190  // Convenience feature: If you specify a group (and its filters) all in
191  // one place, you don't have to specify priority. You can just put them
192  // in order. However, if you later add one (e.g. an extension adds a filter
193  // to a core-defined group), you need to specify it.
194  $autoFillPriority = $lowestSpecifiedPriority - 1;
195  foreach ( $groupDefinition['filters'] as $filterDefinition ) {
196  if ( !isset( $filterDefinition['priority'] ) ) {
197  $filterDefinition['priority'] = $autoFillPriority;
198  $autoFillPriority--;
199  }
200  $filterDefinition['group'] = $this;
201 
202  $filter = $this->createFilter( $filterDefinition );
203  $this->registerFilter( $filter );
204  }
205  }
206 
213  abstract protected function createFilter( array $filterDefinition );
214 
229  public function conflictsWith( $other, $globalKey, $forwardKey, $backwardKey ) {
230  if ( $globalKey === null || $forwardKey === null || $backwardKey === null ) {
231  throw new MWException( 'All messages must be specified' );
232  }
233 
235  $other,
236  $globalKey,
237  $forwardKey
238  );
239 
240  $other->setUnidirectionalConflict(
241  $this,
242  $globalKey,
243  $backwardKey
244  );
245  }
246 
258  public function setUnidirectionalConflict( $other, $globalDescription, $contextDescription ) {
259  if ( $other instanceof ChangesListFilterGroup ) {
260  $this->conflictingGroups[] = [
261  'group' => $other->getName(),
262  'groupObject' => $other,
263  'globalDescription' => $globalDescription,
264  'contextDescription' => $contextDescription,
265  ];
266  } elseif ( $other instanceof ChangesListFilter ) {
267  $this->conflictingFilters[] = [
268  'group' => $other->getGroup()->getName(),
269  'filter' => $other->getName(),
270  'filterObject' => $other,
271  'globalDescription' => $globalDescription,
272  'contextDescription' => $contextDescription,
273  ];
274  } else {
275  throw new MWException( 'You can only pass in a ChangesListFilterGroup or a ChangesListFilter' );
276  }
277  }
278 
282  public function getName() {
283  return $this->name;
284  }
285 
289  public function getTitle() {
290  return $this->title;
291  }
292 
296  public function getType() {
297  return $this->type;
298  }
299 
303  public function getPriority() {
304  return $this->priority;
305  }
306 
311  public function getFilters() {
312  return $this->filters;
313  }
314 
321  public function getFilter( $name ) {
322  return $this->filters[$name] ?? null;
323  }
324 
332  public function getJsData() {
333  $output = [
334  'name' => $this->name,
335  'type' => $this->type,
336  'fullCoverage' => $this->isFullCoverage,
337  'filters' => [],
338  'priority' => $this->priority,
339  'conflicts' => [],
340  'messageKeys' => [ $this->title ]
341  ];
342 
343  if ( isset( $this->whatsThisHeader ) ) {
344  $output['whatsThisHeader'] = $this->whatsThisHeader;
345  $output['whatsThisBody'] = $this->whatsThisBody;
346  $output['whatsThisUrl'] = $this->whatsThisUrl;
347  $output['whatsThisLinkText'] = $this->whatsThisLinkText;
348 
349  array_push(
350  $output['messageKeys'],
351  $output['whatsThisHeader'],
352  $output['whatsThisBody'],
353  $output['whatsThisLinkText']
354  );
355  }
356 
357  usort( $this->filters, function ( $a, $b ) {
358  return $b->getPriority() <=> $a->getPriority();
359  } );
360 
361  foreach ( $this->filters as $filterName => $filter ) {
362  if ( $filter->displaysOnStructuredUi() ) {
363  $filterData = $filter->getJsData();
364  $output['messageKeys'] = array_merge(
365  $output['messageKeys'],
366  $filterData['messageKeys']
367  );
368  unset( $filterData['messageKeys'] );
369  $output['filters'][] = $filterData;
370  }
371  }
372 
373  if ( count( $output['filters'] ) === 0 ) {
374  return null;
375  }
376 
377  $output['title'] = $this->title;
378 
379  $conflicts = array_merge(
380  $this->conflictingGroups,
381  $this->conflictingFilters
382  );
383 
384  foreach ( $conflicts as $conflictInfo ) {
385  unset( $conflictInfo['filterObject'] );
386  unset( $conflictInfo['groupObject'] );
387  $output['conflicts'][] = $conflictInfo;
388  array_push(
389  $output['messageKeys'],
390  $conflictInfo['globalDescription'],
391  $conflictInfo['contextDescription']
392  );
393  }
394 
395  return $output;
396  }
397 
403  public function getConflictingGroups() {
404  return array_map(
405  function ( $conflictDesc ) {
406  return $conflictDesc[ 'groupObject' ];
407  },
409  );
410  }
411 
417  public function getConflictingFilters() {
418  return array_map(
419  function ( $conflictDesc ) {
420  return $conflictDesc[ 'filterObject' ];
421  },
423  );
424  }
425 
432  public function anySelected( FormOptions $opts ) {
433  return (bool)count( array_filter(
434  $this->getFilters(),
435  function ( ChangesListFilter $filter ) use ( $opts ) {
436  return $filter->isSelected( $opts );
437  }
438  ) );
439  }
440 
457  abstract public function modifyQuery( IDatabase $dbr, ChangesListSpecialPage $specialPage,
458  &$tables, &$fields, &$conds, &$query_options, &$join_conds,
459  FormOptions $opts, $isStructuredFiltersEnabled );
460 
468  abstract public function addOptions( FormOptions $opts, $allowDefaults,
469  $isStructuredFiltersEnabled );
470 }
string $title
i18n key for title
getFilter( $name)
Get filter by name.
Helper class to keep track of options when mixing links and form elements.
Definition: FormOptions.php:35
isSelected(FormOptions $opts)
Checks whether this filter is selected in the provided options.
anySelected(FormOptions $opts)
Check if any filter in this group is selected.
array $conflictingGroups
Array of associative arrays with conflict information.
string $type
Type, from a TYPE constant of a subclass.
getConflictingGroups()
Get groups conflicting with this filter group.
Represents a filter (used on ChangesListSpecialPage and descendants)
getConflictingFilters()
Get filters conflicting with this filter group.
string null $whatsThisHeader
i18n key for header of What&#39;s This?
__construct(array $groupDefinition)
Create a new filter group with the specified configuration.
string $priority
Priority integer.
Represents a filter group (used on ChangesListSpecialPage and descendants)
setUnidirectionalConflict( $other, $globalDescription, $contextDescription)
Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object...
string null $whatsThisLinkText
i18n key for What&#39;s This? link
array $conflictingFilters
Array of associative arrays with conflict information.
getJsData()
Gets the JS data in the format required by the front-end of the structured UI.
string null $whatsThisUrl
URL of What&#39;s This? link.
conflictsWith( $other, $globalKey, $forwardKey, $backwardKey)
Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object...
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
Special page which uses a ChangesList to show query results.
string null $whatsThisBody
i18n key for body of What&#39;s This?
addOptions(FormOptions $opts, $allowDefaults, $isStructuredFiltersEnabled)
All the options represented by this filter group to $opts.
bool $isFullCoverage
Whether this group is full coverage.
array $filters
Associative array of filters, as ChangesListFilter objects, with filter name as key.
modifyQuery(IDatabase $dbr, ChangesListSpecialPage $specialPage, &$tables, &$fields, &$conds, &$query_options, &$join_conds, FormOptions $opts, $isStructuredFiltersEnabled)
Modifies the query to include the filter group.
createFilter(array $filterDefinition)
Creates a filter of the appropriate type for this group, from the definition.
string $name
Name (internal identifier)