MediaWiki master
ChangesListFilterGroup.php
Go to the documentation of this file.
1<?php
24
38abstract class ChangesListFilterGroup {
44 protected $name;
45
51 protected $title;
52
59
65 protected $whatsThisBody;
66
72 protected $whatsThisUrl;
73
80
86 protected $type;
87
94 protected $priority;
95
101 protected $filters;
102
110
117 protected $conflictingGroups = [];
118
125 protected $conflictingFilters = [];
126
127 private const DEFAULT_PRIORITY = -100;
128
129 private const RESERVED_NAME_CHAR = '_';
130
157 public function __construct( array $groupDefinition ) {
158 if ( strpos( $groupDefinition['name'], self::RESERVED_NAME_CHAR ) !== false ) {
159 throw new InvalidArgumentException( 'Group names may not contain \'' .
160 self::RESERVED_NAME_CHAR .
161 '\'. Use the naming convention: \'camelCase\''
162 );
163 }
164
165 $this->name = $groupDefinition['name'];
166
167 if ( isset( $groupDefinition['title'] ) ) {
168 $this->title = $groupDefinition['title'];
169 }
170
171 if ( isset( $groupDefinition['whatsThisHeader'] ) ) {
172 $this->whatsThisHeader = $groupDefinition['whatsThisHeader'];
173 $this->whatsThisBody = $groupDefinition['whatsThisBody'];
174 $this->whatsThisUrl = $groupDefinition['whatsThisUrl'];
175 $this->whatsThisLinkText = $groupDefinition['whatsThisLinkText'];
176 }
177
178 $this->type = $groupDefinition['type'];
179 $this->priority = $groupDefinition['priority'] ?? self::DEFAULT_PRIORITY;
180
181 $this->isFullCoverage = $groupDefinition['isFullCoverage'];
182
183 $this->filters = [];
184 $lowestSpecifiedPriority = -1;
185 foreach ( $groupDefinition['filters'] as $filterDefinition ) {
186 if ( isset( $filterDefinition['priority'] ) ) {
187 $lowestSpecifiedPriority = min( $lowestSpecifiedPriority, $filterDefinition['priority'] );
188 }
189 }
190
191 // Convenience feature: If you specify a group (and its filters) all in
192 // one place, you don't have to specify priority. You can just put them
193 // in order. However, if you later add one (e.g. an extension adds a filter
194 // to a core-defined group), you need to specify it.
195 $autoFillPriority = $lowestSpecifiedPriority - 1;
196 foreach ( $groupDefinition['filters'] as $filterDefinition ) {
197 if ( !isset( $filterDefinition['priority'] ) ) {
198 $filterDefinition['priority'] = $autoFillPriority;
199 $autoFillPriority--;
200 }
201 $filterDefinition['group'] = $this;
202
203 $filter = $this->createFilter( $filterDefinition );
204 $this->registerFilter( $filter );
205 }
206 }
207
214 abstract protected function createFilter( array $filterDefinition );
215
230 public function conflictsWith( $other, string $globalKey, string $forwardKey, string $backwardKey ) {
232 $other,
233 $globalKey,
234 $forwardKey
235 );
236
237 $other->setUnidirectionalConflict(
238 $this,
239 $globalKey,
240 $backwardKey
241 );
242 }
243
255 public function setUnidirectionalConflict( $other, $globalDescription, $contextDescription ) {
256 if ( $other instanceof ChangesListFilterGroup ) {
257 $this->conflictingGroups[] = [
258 'group' => $other->getName(),
259 'groupObject' => $other,
260 'globalDescription' => $globalDescription,
261 'contextDescription' => $contextDescription,
262 ];
263 } elseif ( $other instanceof ChangesListFilter ) {
264 $this->conflictingFilters[] = [
265 'group' => $other->getGroup()->getName(),
266 'filter' => $other->getName(),
267 'filterObject' => $other,
268 'globalDescription' => $globalDescription,
269 'contextDescription' => $contextDescription,
270 ];
271 } else {
272 throw new InvalidArgumentException(
273 'You can only pass in a ChangesListFilterGroup or a ChangesListFilter'
274 );
275 }
276 }
277
281 public function getName() {
282 return $this->name;
283 }
284
288 public function getTitle() {
289 return $this->title;
290 }
291
295 public function getType() {
296 return $this->type;
297 }
298
302 public function getPriority() {
303 return $this->priority;
304 }
305
310 public function getFilters() {
311 return $this->filters;
312 }
313
320 public function getFilter( $name ) {
321 return $this->filters[$name] ?? null;
322 }
323
331 public function getJsData() {
332 $output = [
333 'name' => $this->name,
334 'type' => $this->type,
335 'fullCoverage' => $this->isFullCoverage,
336 'filters' => [],
337 'priority' => $this->priority,
338 'conflicts' => [],
339 'messageKeys' => [ $this->title ]
340 ];
341
342 if ( isset( $this->whatsThisHeader ) ) {
343 $output['whatsThisHeader'] = $this->whatsThisHeader;
344 $output['whatsThisBody'] = $this->whatsThisBody;
345 $output['whatsThisUrl'] = $this->whatsThisUrl;
346 $output['whatsThisLinkText'] = $this->whatsThisLinkText;
347
348 array_push(
349 $output['messageKeys'],
350 $output['whatsThisHeader'],
351 $output['whatsThisBody'],
352 $output['whatsThisLinkText']
353 );
354 }
355
356 usort( $this->filters, static function ( ChangesListFilter $a, ChangesListFilter $b ) {
357 return $b->getPriority() <=> $a->getPriority();
358 } );
359
360 foreach ( $this->filters as $filter ) {
361 if ( $filter->displaysOnStructuredUi() ) {
362 $filterData = $filter->getJsData();
363 $output['messageKeys'] = array_merge(
364 $output['messageKeys'],
365 $filterData['messageKeys']
366 );
367 unset( $filterData['messageKeys'] );
368 $output['filters'][] = $filterData;
369 }
370 }
371
372 if ( count( $output['filters'] ) === 0 ) {
373 return null;
374 }
375
376 $output['title'] = $this->title;
377
378 $conflicts = array_merge(
379 $this->conflictingGroups,
380 $this->conflictingFilters
381 );
382
383 foreach ( $conflicts as $conflictInfo ) {
384 unset( $conflictInfo['filterObject'] );
385 unset( $conflictInfo['groupObject'] );
386 $output['conflicts'][] = $conflictInfo;
387 array_push(
388 $output['messageKeys'],
389 $conflictInfo['globalDescription'],
390 $conflictInfo['contextDescription']
391 );
392 }
393
394 return $output;
395 }
396
402 public function getConflictingGroups() {
403 return array_column( $this->conflictingGroups, 'groupObject' );
404 }
405
411 public function getConflictingFilters() {
412 return array_column( $this->conflictingFilters, 'filterObject' );
413 }
414
421 public function anySelected( FormOptions $opts ) {
422 return (bool)count( array_filter(
423 $this->getFilters(),
424 static function ( ChangesListFilter $filter ) use ( $opts ) {
425 return $filter->isSelected( $opts );
426 }
427 ) );
428 }
429
446 abstract public function modifyQuery( IReadableDatabase $dbr, ChangesListSpecialPage $specialPage,
447 &$tables, &$fields, &$conds, &$query_options, &$join_conds,
448 FormOptions $opts, $isStructuredFiltersEnabled );
449
457 abstract public function addOptions( FormOptions $opts, $allowDefaults,
458 $isStructuredFiltersEnabled );
459}
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.
getFilter( $name)
Get filter by name.
ChangesListFilter[] $filters
Associative array of filters, as ChangesListFilter objects, with filter name as key.
conflictsWith( $other, string $globalKey, string $forwardKey, string $backwardKey)
Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object.
getConflictingGroups()
Get groups conflicting with this filter group.
array $conflictingFilters
Array of associative arrays with conflict information.
modifyQuery(IReadableDatabase $dbr, ChangesListSpecialPage $specialPage, &$tables, &$fields, &$conds, &$query_options, &$join_conds, FormOptions $opts, $isStructuredFiltersEnabled)
Modifies the query to include the filter group.
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.
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.
Helper class to keep track of options when mixing links and form elements.
Special page which uses a ChangesList to show query results.
A database connection without write operations.