MediaWiki REL1_39
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
37abstract class ChangesListFilterGroup {
43 protected $name;
44
50 protected $title;
51
58
64 protected $whatsThisBody;
65
71 protected $whatsThisUrl;
72
79
85 protected $type;
86
93 protected $priority;
94
100 protected $filters;
101
109
116 protected $conflictingGroups = [];
117
124 protected $conflictingFilters = [];
125
126 private const DEFAULT_PRIORITY = -100;
127
128 private 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, static function ( ChangesListFilter $a, ChangesListFilter $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_column( $this->conflictingGroups, 'groupObject' );
405 }
406
412 public function getConflictingFilters() {
413 return array_column( $this->conflictingFilters, 'filterObject' );
414 }
415
422 public function anySelected( FormOptions $opts ) {
423 return (bool)count( array_filter(
424 $this->getFilters(),
425 static function ( ChangesListFilter $filter ) use ( $opts ) {
426 return $filter->isSelected( $opts );
427 }
428 ) );
429 }
430
447 abstract public function modifyQuery( IDatabase $dbr, ChangesListSpecialPage $specialPage,
448 &$tables, &$fields, &$conds, &$query_options, &$join_conds,
449 FormOptions $opts, $isStructuredFiltersEnabled );
450
458 abstract public function addOptions( FormOptions $opts, $allowDefaults,
459 $isStructuredFiltersEnabled );
460}
Represents a filter group (used on ChangesListSpecialPage and descendants)
int $priority
Priority integer.
string $name
Name (internal identifier)
string null $whatsThisBody
i18n key for body of What's This?
string $title
i18n key for title
anySelected(FormOptions $opts)
Check if any filter in this group is selected.
string null $whatsThisUrl
URL of What's This? link.
bool $isFullCoverage
Whether this group is full coverage.
modifyQuery(IDatabase $dbr, ChangesListSpecialPage $specialPage, &$tables, &$fields, &$conds, &$query_options, &$join_conds, FormOptions $opts, $isStructuredFiltersEnabled)
Modifies the query to include the filter group.
getFilter( $name)
Get filter by name.
ChangesListFilter[] $filters
Associative array of filters, as ChangesListFilter objects, with filter name as key.
getConflictingGroups()
Get groups conflicting with this filter group.
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.
__construct(array $groupDefinition)
Create a new filter group with the specified configuration.
string null $whatsThisHeader
i18n key for header of What's This?
addOptions(FormOptions $opts, $allowDefaults, $isStructuredFiltersEnabled)
All the options represented by this filter group to $opts.
getConflictingFilters()
Get filters conflicting with this filter group.
array $conflictingGroups
Array of associative arrays with conflict information.
setUnidirectionalConflict( $other, $globalDescription, $contextDescription)
Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object.
createFilter(array $filterDefinition)
Creates a filter of the appropriate type for this group, from the definition.
string $type
Type, from a TYPE constant of a subclass.
conflictsWith( $other, $globalKey, $forwardKey, $backwardKey)
Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object.
string null $whatsThisLinkText
i18n key for What's This? link
Represents a filter (used on ChangesListSpecialPage and descendants)
isSelected(FormOptions $opts)
Checks whether this filter is selected in the provided options.
Special page which uses a ChangesList to show query results.
Helper class to keep track of options when mixing links and form elements.
MediaWiki exception.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:39