MediaWiki master
ChangesListFilterGroup.php
Go to the documentation of this file.
1<?php
22
23use InvalidArgumentException;
27
41abstract class ChangesListFilterGroup {
47 protected $name;
48
54 protected $title;
55
62
68 protected $whatsThisBody;
69
75 protected $whatsThisUrl;
76
83
89 protected $type;
90
97 protected $priority;
98
104 protected $filters;
105
113
120 protected $conflictingGroups = [];
121
128 protected $conflictingFilters = [];
129
130 private const DEFAULT_PRIORITY = -100;
131
132 private const RESERVED_NAME_CHAR = '_';
133
160 public function __construct( array $groupDefinition ) {
161 if ( strpos( $groupDefinition['name'], self::RESERVED_NAME_CHAR ) !== false ) {
162 throw new InvalidArgumentException( 'Group names may not contain \'' .
163 self::RESERVED_NAME_CHAR .
164 '\'. Use the naming convention: \'camelCase\''
165 );
166 }
167
168 $this->name = $groupDefinition['name'];
169
170 if ( isset( $groupDefinition['title'] ) ) {
171 $this->title = $groupDefinition['title'];
172 }
173
174 if ( isset( $groupDefinition['whatsThisHeader'] ) ) {
175 $this->whatsThisHeader = $groupDefinition['whatsThisHeader'];
176 $this->whatsThisBody = $groupDefinition['whatsThisBody'];
177 $this->whatsThisUrl = $groupDefinition['whatsThisUrl'];
178 $this->whatsThisLinkText = $groupDefinition['whatsThisLinkText'];
179 }
180
181 $this->type = $groupDefinition['type'];
182 $this->priority = $groupDefinition['priority'] ?? self::DEFAULT_PRIORITY;
183
184 $this->isFullCoverage = $groupDefinition['isFullCoverage'];
185
186 $this->filters = [];
187 $lowestSpecifiedPriority = -1;
188 foreach ( $groupDefinition['filters'] as $filterDefinition ) {
189 if ( isset( $filterDefinition['priority'] ) ) {
190 $lowestSpecifiedPriority = min( $lowestSpecifiedPriority, $filterDefinition['priority'] );
191 }
192 }
193
194 // Convenience feature: If you specify a group (and its filters) all in
195 // one place, you don't have to specify priority. You can just put them
196 // in order. However, if you later add one (e.g. an extension adds a filter
197 // to a core-defined group), you need to specify it.
198 $autoFillPriority = $lowestSpecifiedPriority - 1;
199 foreach ( $groupDefinition['filters'] as $filterDefinition ) {
200 if ( !isset( $filterDefinition['priority'] ) ) {
201 $filterDefinition['priority'] = $autoFillPriority;
202 $autoFillPriority--;
203 }
204 $filterDefinition['group'] = $this;
205
206 $filter = $this->createFilter( $filterDefinition );
207 $this->registerFilter( $filter );
208 }
209 }
210
217 abstract protected function createFilter( array $filterDefinition );
218
233 public function conflictsWith( $other, string $globalKey, string $forwardKey, string $backwardKey ) {
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 InvalidArgumentException(
276 'You can only pass in a ChangesListFilterGroup or a ChangesListFilter'
277 );
278 }
279 }
280
284 public function getName() {
285 return $this->name;
286 }
287
291 public function getTitle() {
292 return $this->title;
293 }
294
298 public function getType() {
299 return $this->type;
300 }
301
305 public function getPriority() {
306 return $this->priority;
307 }
308
313 public function getFilters() {
314 return $this->filters;
315 }
316
323 public function getFilter( $name ) {
324 return $this->filters[$name] ?? null;
325 }
326
334 public function getJsData() {
335 $output = [
336 'name' => $this->name,
337 'type' => $this->type,
338 'fullCoverage' => $this->isFullCoverage,
339 'filters' => [],
340 'priority' => $this->priority,
341 'conflicts' => [],
342 'messageKeys' => [ $this->title ]
343 ];
344
345 if ( $this->whatsThisHeader !== null ) {
346 $output['whatsThisHeader'] = $this->whatsThisHeader;
347 $output['whatsThisBody'] = $this->whatsThisBody;
348 $output['whatsThisUrl'] = $this->whatsThisUrl;
349 $output['whatsThisLinkText'] = $this->whatsThisLinkText;
350
351 array_push(
352 $output['messageKeys'],
353 $output['whatsThisHeader'],
354 $output['whatsThisBody'],
355 $output['whatsThisLinkText']
356 );
357 }
358
359 usort( $this->filters, static function ( ChangesListFilter $a, ChangesListFilter $b ) {
360 return $b->getPriority() <=> $a->getPriority();
361 } );
362
363 foreach ( $this->filters as $filter ) {
364 if ( $filter->displaysOnStructuredUi() ) {
365 $filterData = $filter->getJsData();
366 $output['messageKeys'] = array_merge(
367 $output['messageKeys'],
368 $filterData['messageKeys']
369 );
370 unset( $filterData['messageKeys'] );
371 $output['filters'][] = $filterData;
372 }
373 }
374
375 if ( count( $output['filters'] ) === 0 ) {
376 return null;
377 }
378
379 $output['title'] = $this->title;
380
381 $conflicts = array_merge(
382 $this->conflictingGroups,
383 $this->conflictingFilters
384 );
385
386 foreach ( $conflicts as $conflictInfo ) {
387 unset( $conflictInfo['filterObject'] );
388 unset( $conflictInfo['groupObject'] );
389 $output['conflicts'][] = $conflictInfo;
390 array_push(
391 $output['messageKeys'],
392 $conflictInfo['globalDescription'],
393 $conflictInfo['contextDescription']
394 );
395 }
396
397 return $output;
398 }
399
405 public function getConflictingGroups() {
406 return array_column( $this->conflictingGroups, 'groupObject' );
407 }
408
414 public function getConflictingFilters() {
415 return array_column( $this->conflictingFilters, 'filterObject' );
416 }
417
424 public function anySelected( FormOptions $opts ) {
425 return (bool)count( array_filter(
426 $this->getFilters(),
427 static function ( ChangesListFilter $filter ) use ( $opts ) {
428 return $filter->isSelected( $opts );
429 }
430 ) );
431 }
432
449 abstract public function modifyQuery( IReadableDatabase $dbr, ChangesListSpecialPage $specialPage,
450 &$tables, &$fields, &$conds, &$query_options, &$join_conds,
451 FormOptions $opts, $isStructuredFiltersEnabled );
452
460 abstract public function addOptions( FormOptions $opts, $allowDefaults,
461 $isStructuredFiltersEnabled );
462}
463
465class_alias( ChangesListFilterGroup::class, 'ChangesListFilterGroup' );
Helper class to keep track of options when mixing links and form elements.
Represents a filter group (used on ChangesListSpecialPage and descendants)
string null $whatsThisBody
i18n key for body of What's This?
setUnidirectionalConflict( $other, $globalDescription, $contextDescription)
Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object.
addOptions(FormOptions $opts, $allowDefaults, $isStructuredFiltersEnabled)
All the options represented by this filter group to $opts.
getConflictingGroups()
Get groups conflicting with this filter group.
array $conflictingGroups
Array of associative arrays with conflict information.
getConflictingFilters()
Get filters conflicting with this filter group.
conflictsWith( $other, string $globalKey, string $forwardKey, string $backwardKey)
Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object.
getJsData()
Gets the JS data in the format required by the front-end of the structured UI.
string null $whatsThisLinkText
i18n key for What's This? link
string null $whatsThisUrl
URL of What's This? link.
anySelected(FormOptions $opts)
Check if any filter in this group is selected.
string $type
Type, from a TYPE constant of a subclass.
string null $whatsThisHeader
i18n key for header of What's This?
array $conflictingFilters
Array of associative arrays with conflict information.
ChangesListFilter[] $filters
Associative array of filters, as ChangesListFilter objects, with filter name as key.
__construct(array $groupDefinition)
Create a new filter group with the specified configuration.
bool $isFullCoverage
Whether this group is full coverage.
modifyQuery(IReadableDatabase $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.
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.
A database connection without write operations.