MediaWiki  1.31.0
ChangesListSpecialPage.php
Go to the documentation of this file.
1 <?php
28 
35 abstract class ChangesListSpecialPage extends SpecialPage {
41 
46  protected static $savedQueriesPreferenceName;
47 
52  protected static $daysPreferenceName;
53 
58  protected static $limitPreferenceName;
59 
61  protected $rcSubpage;
62 
64  protected $rcOptions;
65 
67  protected $customFilters;
68 
69  // Order of both groups and filters is significant; first is top-most priority,
70  // descending from there.
71  // 'showHideSuffix' is a shortcut to and avoid spelling out
72  // details specific to subclasses here.
87 
88  // Same format as filterGroupDefinitions, but for a single group (reviewStatus)
89  // that is registered conditionally.
91 
92  // Single filter group registered conditionally
94 
95  // Single filter group registered conditionally
97 
104  protected $filterGroups = [];
105 
106  public function __construct( $name, $restriction ) {
107  parent::__construct( $name, $restriction );
108 
109  $nonRevisionTypes = [ RC_LOG ];
110  Hooks::run( 'SpecialWatchlistGetNonRevisionTypes', [ &$nonRevisionTypes ] );
111 
112  $this->filterGroupDefinitions = [
113  [
114  'name' => 'registration',
115  'title' => 'rcfilters-filtergroup-registration',
117  'filters' => [
118  [
119  'name' => 'hideliu',
120  // rcshowhideliu-show, rcshowhideliu-hide,
121  // wlshowhideliu
122  'showHideSuffix' => 'showhideliu',
123  'default' => false,
124  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
125  &$query_options, &$join_conds
126  ) {
127  $actorMigration = ActorMigration::newMigration();
128  $actorQuery = $actorMigration->getJoin( 'rc_user' );
129  $tables += $actorQuery['tables'];
130  $join_conds += $actorQuery['joins'];
131  $conds[] = $actorMigration->isAnon( $actorQuery['fields']['rc_user'] );
132  },
133  'isReplacedInStructuredUi' => true,
134 
135  ],
136  [
137  'name' => 'hideanons',
138  // rcshowhideanons-show, rcshowhideanons-hide,
139  // wlshowhideanons
140  'showHideSuffix' => 'showhideanons',
141  'default' => false,
142  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
143  &$query_options, &$join_conds
144  ) {
145  $actorMigration = ActorMigration::newMigration();
146  $actorQuery = $actorMigration->getJoin( 'rc_user' );
147  $tables += $actorQuery['tables'];
148  $join_conds += $actorQuery['joins'];
149  $conds[] = $actorMigration->isNotAnon( $actorQuery['fields']['rc_user'] );
150  },
151  'isReplacedInStructuredUi' => true,
152  ]
153  ],
154  ],
155 
156  [
157  'name' => 'userExpLevel',
158  'title' => 'rcfilters-filtergroup-userExpLevel',
160  'isFullCoverage' => true,
161  'filters' => [
162  [
163  'name' => 'unregistered',
164  'label' => 'rcfilters-filter-user-experience-level-unregistered-label',
165  'description' => 'rcfilters-filter-user-experience-level-unregistered-description',
166  'cssClassSuffix' => 'user-unregistered',
167  'isRowApplicableCallable' => function ( $ctx, $rc ) {
168  return !$rc->getAttribute( 'rc_user' );
169  }
170  ],
171  [
172  'name' => 'registered',
173  'label' => 'rcfilters-filter-user-experience-level-registered-label',
174  'description' => 'rcfilters-filter-user-experience-level-registered-description',
175  'cssClassSuffix' => 'user-registered',
176  'isRowApplicableCallable' => function ( $ctx, $rc ) {
177  return $rc->getAttribute( 'rc_user' );
178  }
179  ],
180  [
181  'name' => 'newcomer',
182  'label' => 'rcfilters-filter-user-experience-level-newcomer-label',
183  'description' => 'rcfilters-filter-user-experience-level-newcomer-description',
184  'cssClassSuffix' => 'user-newcomer',
185  'isRowApplicableCallable' => function ( $ctx, $rc ) {
186  $performer = $rc->getPerformer();
187  return $performer && $performer->isLoggedIn() &&
188  $performer->getExperienceLevel() === 'newcomer';
189  }
190  ],
191  [
192  'name' => 'learner',
193  'label' => 'rcfilters-filter-user-experience-level-learner-label',
194  'description' => 'rcfilters-filter-user-experience-level-learner-description',
195  'cssClassSuffix' => 'user-learner',
196  'isRowApplicableCallable' => function ( $ctx, $rc ) {
197  $performer = $rc->getPerformer();
198  return $performer && $performer->isLoggedIn() &&
199  $performer->getExperienceLevel() === 'learner';
200  },
201  ],
202  [
203  'name' => 'experienced',
204  'label' => 'rcfilters-filter-user-experience-level-experienced-label',
205  'description' => 'rcfilters-filter-user-experience-level-experienced-description',
206  'cssClassSuffix' => 'user-experienced',
207  'isRowApplicableCallable' => function ( $ctx, $rc ) {
208  $performer = $rc->getPerformer();
209  return $performer && $performer->isLoggedIn() &&
210  $performer->getExperienceLevel() === 'experienced';
211  },
212  ]
213  ],
215  'queryCallable' => [ $this, 'filterOnUserExperienceLevel' ],
216  ],
217 
218  [
219  'name' => 'authorship',
220  'title' => 'rcfilters-filtergroup-authorship',
222  'filters' => [
223  [
224  'name' => 'hidemyself',
225  'label' => 'rcfilters-filter-editsbyself-label',
226  'description' => 'rcfilters-filter-editsbyself-description',
227  // rcshowhidemine-show, rcshowhidemine-hide,
228  // wlshowhidemine
229  'showHideSuffix' => 'showhidemine',
230  'default' => false,
231  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
232  &$query_options, &$join_conds
233  ) {
234  $actorQuery = ActorMigration::newMigration()->getWhere( $dbr, 'rc_user', $ctx->getUser() );
235  $tables += $actorQuery['tables'];
236  $join_conds += $actorQuery['joins'];
237  $conds[] = 'NOT(' . $actorQuery['conds'] . ')';
238  },
239  'cssClassSuffix' => 'self',
240  'isRowApplicableCallable' => function ( $ctx, $rc ) {
241  return $ctx->getUser()->equals( $rc->getPerformer() );
242  },
243  ],
244  [
245  'name' => 'hidebyothers',
246  'label' => 'rcfilters-filter-editsbyother-label',
247  'description' => 'rcfilters-filter-editsbyother-description',
248  'default' => false,
249  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
250  &$query_options, &$join_conds
251  ) {
252  $actorQuery = ActorMigration::newMigration()
253  ->getWhere( $dbr, 'rc_user', $ctx->getUser(), false );
254  $tables += $actorQuery['tables'];
255  $join_conds += $actorQuery['joins'];
256  $conds[] = $actorQuery['conds'];
257  },
258  'cssClassSuffix' => 'others',
259  'isRowApplicableCallable' => function ( $ctx, $rc ) {
260  return !$ctx->getUser()->equals( $rc->getPerformer() );
261  },
262  ]
263  ]
264  ],
265 
266  [
267  'name' => 'automated',
268  'title' => 'rcfilters-filtergroup-automated',
270  'filters' => [
271  [
272  'name' => 'hidebots',
273  'label' => 'rcfilters-filter-bots-label',
274  'description' => 'rcfilters-filter-bots-description',
275  // rcshowhidebots-show, rcshowhidebots-hide,
276  // wlshowhidebots
277  'showHideSuffix' => 'showhidebots',
278  'default' => false,
279  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
280  &$query_options, &$join_conds
281  ) {
282  $conds['rc_bot'] = 0;
283  },
284  'cssClassSuffix' => 'bot',
285  'isRowApplicableCallable' => function ( $ctx, $rc ) {
286  return $rc->getAttribute( 'rc_bot' );
287  },
288  ],
289  [
290  'name' => 'hidehumans',
291  'label' => 'rcfilters-filter-humans-label',
292  'description' => 'rcfilters-filter-humans-description',
293  'default' => false,
294  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
295  &$query_options, &$join_conds
296  ) {
297  $conds['rc_bot'] = 1;
298  },
299  'cssClassSuffix' => 'human',
300  'isRowApplicableCallable' => function ( $ctx, $rc ) {
301  return !$rc->getAttribute( 'rc_bot' );
302  },
303  ]
304  ]
305  ],
306 
307  // significance (conditional)
308 
309  [
310  'name' => 'significance',
311  'title' => 'rcfilters-filtergroup-significance',
313  'priority' => -6,
314  'filters' => [
315  [
316  'name' => 'hideminor',
317  'label' => 'rcfilters-filter-minor-label',
318  'description' => 'rcfilters-filter-minor-description',
319  // rcshowhideminor-show, rcshowhideminor-hide,
320  // wlshowhideminor
321  'showHideSuffix' => 'showhideminor',
322  'default' => false,
323  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
324  &$query_options, &$join_conds
325  ) {
326  $conds[] = 'rc_minor = 0';
327  },
328  'cssClassSuffix' => 'minor',
329  'isRowApplicableCallable' => function ( $ctx, $rc ) {
330  return $rc->getAttribute( 'rc_minor' );
331  }
332  ],
333  [
334  'name' => 'hidemajor',
335  'label' => 'rcfilters-filter-major-label',
336  'description' => 'rcfilters-filter-major-description',
337  'default' => false,
338  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
339  &$query_options, &$join_conds
340  ) {
341  $conds[] = 'rc_minor = 1';
342  },
343  'cssClassSuffix' => 'major',
344  'isRowApplicableCallable' => function ( $ctx, $rc ) {
345  return !$rc->getAttribute( 'rc_minor' );
346  }
347  ]
348  ]
349  ],
350 
351  [
352  'name' => 'lastRevision',
353  'title' => 'rcfilters-filtergroup-lastRevision',
355  'priority' => -7,
356  'filters' => [
357  [
358  'name' => 'hidelastrevision',
359  'label' => 'rcfilters-filter-lastrevision-label',
360  'description' => 'rcfilters-filter-lastrevision-description',
361  'default' => false,
362  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
363  &$query_options, &$join_conds ) use ( $nonRevisionTypes ) {
364  $conds[] = $dbr->makeList(
365  [
366  'rc_this_oldid <> page_latest',
367  'rc_type' => $nonRevisionTypes,
368  ],
369  LIST_OR
370  );
371  },
372  'cssClassSuffix' => 'last',
373  'isRowApplicableCallable' => function ( $ctx, $rc ) {
374  return $rc->getAttribute( 'rc_this_oldid' ) === $rc->getAttribute( 'page_latest' );
375  }
376  ],
377  [
378  'name' => 'hidepreviousrevisions',
379  'label' => 'rcfilters-filter-previousrevision-label',
380  'description' => 'rcfilters-filter-previousrevision-description',
381  'default' => false,
382  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
383  &$query_options, &$join_conds ) use ( $nonRevisionTypes ) {
384  $conds[] = $dbr->makeList(
385  [
386  'rc_this_oldid = page_latest',
387  'rc_type' => $nonRevisionTypes,
388  ],
389  LIST_OR
390  );
391  },
392  'cssClassSuffix' => 'previous',
393  'isRowApplicableCallable' => function ( $ctx, $rc ) {
394  return $rc->getAttribute( 'rc_this_oldid' ) !== $rc->getAttribute( 'page_latest' );
395  }
396  ]
397  ]
398  ],
399 
400  // With extensions, there can be change types that will not be hidden by any of these.
401  [
402  'name' => 'changeType',
403  'title' => 'rcfilters-filtergroup-changetype',
405  'priority' => -8,
406  'filters' => [
407  [
408  'name' => 'hidepageedits',
409  'label' => 'rcfilters-filter-pageedits-label',
410  'description' => 'rcfilters-filter-pageedits-description',
411  'default' => false,
412  'priority' => -2,
413  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
414  &$query_options, &$join_conds
415  ) {
416  $conds[] = 'rc_type != ' . $dbr->addQuotes( RC_EDIT );
417  },
418  'cssClassSuffix' => 'src-mw-edit',
419  'isRowApplicableCallable' => function ( $ctx, $rc ) {
420  return $rc->getAttribute( 'rc_source' ) === RecentChange::SRC_EDIT;
421  },
422  ],
423  [
424  'name' => 'hidenewpages',
425  'label' => 'rcfilters-filter-newpages-label',
426  'description' => 'rcfilters-filter-newpages-description',
427  'default' => false,
428  'priority' => -3,
429  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
430  &$query_options, &$join_conds
431  ) {
432  $conds[] = 'rc_type != ' . $dbr->addQuotes( RC_NEW );
433  },
434  'cssClassSuffix' => 'src-mw-new',
435  'isRowApplicableCallable' => function ( $ctx, $rc ) {
436  return $rc->getAttribute( 'rc_source' ) === RecentChange::SRC_NEW;
437  },
438  ],
439 
440  // hidecategorization
441 
442  [
443  'name' => 'hidelog',
444  'label' => 'rcfilters-filter-logactions-label',
445  'description' => 'rcfilters-filter-logactions-description',
446  'default' => false,
447  'priority' => -5,
448  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
449  &$query_options, &$join_conds
450  ) {
451  $conds[] = 'rc_type != ' . $dbr->addQuotes( RC_LOG );
452  },
453  'cssClassSuffix' => 'src-mw-log',
454  'isRowApplicableCallable' => function ( $ctx, $rc ) {
455  return $rc->getAttribute( 'rc_source' ) === RecentChange::SRC_LOG;
456  }
457  ],
458  ],
459  ],
460 
461  ];
462 
463  $this->legacyReviewStatusFilterGroupDefinition = [
464  [
465  'name' => 'legacyReviewStatus',
466  'title' => 'rcfilters-filtergroup-reviewstatus',
468  'filters' => [
469  [
470  'name' => 'hidepatrolled',
471  // rcshowhidepatr-show, rcshowhidepatr-hide
472  // wlshowhidepatr
473  'showHideSuffix' => 'showhidepatr',
474  'default' => false,
475  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
476  &$query_options, &$join_conds
477  ) {
478  $conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
479  },
480  'isReplacedInStructuredUi' => true,
481  ],
482  [
483  'name' => 'hideunpatrolled',
484  'default' => false,
485  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
486  &$query_options, &$join_conds
487  ) {
488  $conds[] = 'rc_patrolled != ' . RecentChange::PRC_UNPATROLLED;
489  },
490  'isReplacedInStructuredUi' => true,
491  ],
492  ],
493  ]
494  ];
495 
496  $this->reviewStatusFilterGroupDefinition = [
497  [
498  'name' => 'reviewStatus',
499  'title' => 'rcfilters-filtergroup-reviewstatus',
501  'isFullCoverage' => true,
502  'priority' => -5,
503  'filters' => [
504  [
505  'name' => 'unpatrolled',
506  'label' => 'rcfilters-filter-reviewstatus-unpatrolled-label',
507  'description' => 'rcfilters-filter-reviewstatus-unpatrolled-description',
508  'cssClassSuffix' => 'reviewstatus-unpatrolled',
509  'isRowApplicableCallable' => function ( $ctx, $rc ) {
510  return $rc->getAttribute( 'rc_patrolled' ) == RecentChange::PRC_UNPATROLLED;
511  },
512  ],
513  [
514  'name' => 'manual',
515  'label' => 'rcfilters-filter-reviewstatus-manual-label',
516  'description' => 'rcfilters-filter-reviewstatus-manual-description',
517  'cssClassSuffix' => 'reviewstatus-manual',
518  'isRowApplicableCallable' => function ( $ctx, $rc ) {
519  return $rc->getAttribute( 'rc_patrolled' ) == RecentChange::PRC_PATROLLED;
520  },
521  ],
522  [
523  'name' => 'auto',
524  'label' => 'rcfilters-filter-reviewstatus-auto-label',
525  'description' => 'rcfilters-filter-reviewstatus-auto-description',
526  'cssClassSuffix' => 'reviewstatus-auto',
527  'isRowApplicableCallable' => function ( $ctx, $rc ) {
528  return $rc->getAttribute( 'rc_patrolled' ) == RecentChange::PRC_AUTOPATROLLED;
529  },
530  ],
531  ],
533  'queryCallable' => function ( $specialPageClassName, $ctx, $dbr,
534  &$tables, &$fields, &$conds, &$query_options, &$join_conds, $selected
535  ) {
536  if ( $selected === [] ) {
537  return;
538  }
539  $rcPatrolledValues = [
540  'unpatrolled' => RecentChange::PRC_UNPATROLLED,
541  'manual' => RecentChange::PRC_PATROLLED,
543  ];
544  // e.g. rc_patrolled IN (0, 2)
545  $conds['rc_patrolled'] = array_map( function ( $s ) use ( $rcPatrolledValues ) {
546  return $rcPatrolledValues[ $s ];
547  }, $selected );
548  }
549  ]
550  ];
551 
552  $this->hideCategorizationFilterDefinition = [
553  'name' => 'hidecategorization',
554  'label' => 'rcfilters-filter-categorization-label',
555  'description' => 'rcfilters-filter-categorization-description',
556  // rcshowhidecategorization-show, rcshowhidecategorization-hide.
557  // wlshowhidecategorization
558  'showHideSuffix' => 'showhidecategorization',
559  'default' => false,
560  'priority' => -4,
561  'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
562  &$query_options, &$join_conds
563  ) {
564  $conds[] = 'rc_type != ' . $dbr->addQuotes( RC_CATEGORIZE );
565  },
566  'cssClassSuffix' => 'src-mw-categorize',
567  'isRowApplicableCallable' => function ( $ctx, $rc ) {
568  return $rc->getAttribute( 'rc_source' ) === RecentChange::SRC_CATEGORIZE;
569  },
570  ];
571  }
572 
578  protected function areFiltersInConflict() {
579  $opts = $this->getOptions();
581  foreach ( $this->getFilterGroups() as $group ) {
582  if ( $group->getConflictingGroups() ) {
583  wfLogWarning(
584  $group->getName() .
585  " specifies conflicts with other groups but these are not supported yet."
586  );
587  }
588 
590  foreach ( $group->getConflictingFilters() as $conflictingFilter ) {
591  if ( $conflictingFilter->activelyInConflictWithGroup( $group, $opts ) ) {
592  return true;
593  }
594  }
595 
597  foreach ( $group->getFilters() as $filter ) {
599  foreach ( $filter->getConflictingFilters() as $conflictingFilter ) {
600  if (
601  $conflictingFilter->activelyInConflictWithFilter( $filter, $opts ) &&
602  $filter->activelyInConflictWithFilter( $conflictingFilter, $opts )
603  ) {
604  return true;
605  }
606  }
607 
608  }
609 
610  }
611 
612  return false;
613  }
614 
620  public function execute( $subpage ) {
621  $this->rcSubpage = $subpage;
622 
623  $this->considerActionsForDefaultSavedQuery( $subpage );
624 
625  $opts = $this->getOptions();
626  try {
627  $rows = $this->getRows();
628  if ( $rows === false ) {
629  $rows = new FakeResultWrapper( [] );
630  }
631 
632  // Used by Structured UI app to get results without MW chrome
633  if ( $this->getRequest()->getVal( 'action' ) === 'render' ) {
634  $this->getOutput()->setArticleBodyOnly( true );
635  }
636 
637  // Used by "live update" and "view newest" to check
638  // if there's new changes with minimal data transfer
639  if ( $this->getRequest()->getBool( 'peek' ) ) {
640  $code = $rows->numRows() > 0 ? 200 : 204;
641  $this->getOutput()->setStatusCode( $code );
642 
643  if ( $this->getUser()->isAnon() !==
644  $this->getRequest()->getFuzzyBool( 'isAnon' )
645  ) {
646  $this->getOutput()->setStatusCode( 205 );
647  }
648 
649  return;
650  }
651 
652  $batch = new LinkBatch;
653  foreach ( $rows as $row ) {
654  $batch->add( NS_USER, $row->rc_user_text );
655  $batch->add( NS_USER_TALK, $row->rc_user_text );
656  $batch->add( $row->rc_namespace, $row->rc_title );
657  if ( $row->rc_source === RecentChange::SRC_LOG ) {
658  $formatter = LogFormatter::newFromRow( $row );
659  foreach ( $formatter->getPreloadTitles() as $title ) {
660  $batch->addObj( $title );
661  }
662  }
663  }
664  $batch->execute();
665 
666  $this->setHeaders();
667  $this->outputHeader();
668  $this->addModules();
669  $this->webOutput( $rows, $opts );
670 
671  $rows->free();
672  } catch ( DBQueryTimeoutError $timeoutException ) {
673  MWExceptionHandler::logException( $timeoutException );
674 
675  $this->setHeaders();
676  $this->outputHeader();
677  $this->addModules();
678 
679  $this->getOutput()->setStatusCode( 500 );
680  $this->webOutputHeader( 0, $opts );
681  $this->outputTimeout();
682  }
683 
684  if ( $this->getConfig()->get( 'EnableWANCacheReaper' ) ) {
685  // Clean up any bad page entries for titles showing up in RC
687  $this->getDB(),
688  LoggerFactory::getInstance( 'objectcache' )
689  ) );
690  }
691 
692  $this->includeRcFiltersApp();
693  }
694 
702  protected function considerActionsForDefaultSavedQuery( $subpage ) {
703  if ( !$this->isStructuredFilterUiEnabled() || $this->including() ) {
704  return;
705  }
706 
707  $knownParams = call_user_func_array(
708  [ $this->getRequest(), 'getValues' ],
709  array_keys( $this->getOptions()->getAllValues() )
710  );
711 
712  // HACK: Temporarily until we can properly define "sticky" filters and parameters,
713  // we need to exclude several parameters we know should not be counted towards preventing
714  // the loading of defaults.
715  $excludedParams = [ 'limit' => '', 'days' => '', 'enhanced' => '', 'from' => '' ];
716  $knownParams = array_diff_key( $knownParams, $excludedParams );
717 
718  if (
719  // If there are NO known parameters in the URL request
720  // (that are not excluded) then we need to check into loading
721  // the default saved query
722  count( $knownParams ) === 0
723  ) {
724  // Get the saved queries data and parse it
725  $savedQueries = FormatJson::decode(
726  $this->getUser()->getOption( static::$savedQueriesPreferenceName ),
727  true
728  );
729 
730  if ( $savedQueries && isset( $savedQueries[ 'default' ] ) ) {
731  // Only load queries that are 'version' 2, since those
732  // have parameter representation
733  if ( isset( $savedQueries[ 'version' ] ) && $savedQueries[ 'version' ] === '2' ) {
734  $savedQueryDefaultID = $savedQueries[ 'default' ];
735  $defaultQuery = $savedQueries[ 'queries' ][ $savedQueryDefaultID ][ 'data' ];
736 
737  // Build the entire parameter list
738  $query = array_merge(
739  $defaultQuery[ 'params' ],
740  $defaultQuery[ 'highlights' ],
741  [
742  'urlversion' => '2',
743  ]
744  );
745  // Add to the query any parameters that we may have ignored before
746  // but are still valid and requested in the URL
747  $query = array_merge( $this->getRequest()->getValues(), $query );
748  unset( $query[ 'title' ] );
749  $this->getOutput()->redirect( $this->getPageTitle( $subpage )->getCanonicalURL( $query ) );
750  } else {
751  // There's a default, but the version is not 2, and the server can't
752  // actually recognize the query itself. This happens if it is before
753  // the conversion, so we need to tell the UI to reload saved query as
754  // it does the conversion to version 2
755  $this->getOutput()->addJsConfigVars(
756  'wgStructuredChangeFiltersDefaultSavedQueryExists',
757  true
758  );
759 
760  // Add the class that tells the frontend it is still loading
761  // another query
762  $this->getOutput()->addBodyClasses( 'mw-rcfilters-ui-loading' );
763  }
764  }
765  }
766  }
767 
774  protected function includeRcFiltersApp() {
775  $out = $this->getOutput();
776  if ( $this->isStructuredFilterUiEnabled() && !$this->including() ) {
777  $jsData = $this->getStructuredFilterJsData();
778 
779  $messages = [];
780  foreach ( $jsData['messageKeys'] as $key ) {
781  $messages[$key] = $this->msg( $key )->plain();
782  }
783 
784  $out->addBodyClasses( 'mw-rcfilters-enabled' );
785 
786  $out->addHTML(
787  ResourceLoader::makeInlineScript(
788  ResourceLoader::makeMessageSetScript( $messages )
789  )
790  );
791 
792  $out->addJsConfigVars( 'wgStructuredChangeFilters', $jsData['groups'] );
793 
794  $out->addJsConfigVars(
795  'wgRCFiltersChangeTags',
796  $this->getChangeTagList()
797  );
798  $out->addJsConfigVars(
799  'StructuredChangeFiltersDisplayConfig',
800  [
801  'maxDays' => (int)$this->getConfig()->get( 'RCMaxAge' ) / ( 24 * 3600 ), // Translate to days
802  'limitArray' => $this->getConfig()->get( 'RCLinkLimits' ),
803  'limitDefault' => $this->getDefaultLimit(),
804  'daysArray' => $this->getConfig()->get( 'RCLinkDays' ),
805  'daysDefault' => $this->getDefaultDays(),
806  ]
807  );
808 
809  $out->addJsConfigVars(
810  'wgStructuredChangeFiltersSavedQueriesPreferenceName',
811  static::$savedQueriesPreferenceName
812  );
813  $out->addJsConfigVars(
814  'wgStructuredChangeFiltersLimitPreferenceName',
815  static::$limitPreferenceName
816  );
817  $out->addJsConfigVars(
818  'wgStructuredChangeFiltersDaysPreferenceName',
819  static::$daysPreferenceName
820  );
821 
822  $out->addJsConfigVars(
823  'StructuredChangeFiltersLiveUpdatePollingRate',
824  $this->getConfig()->get( 'StructuredChangeFiltersLiveUpdatePollingRate' )
825  );
826  } else {
827  $out->addBodyClasses( 'mw-rcfilters-disabled' );
828  }
829  }
830 
836  protected function getChangeTagList() {
838  $context = $this->getContext();
839  return $cache->getWithSetCallback(
840  $cache->makeKey( 'changeslistspecialpage-changetags', $context->getLanguage()->getCode() ),
841  $cache::TTL_MINUTE * 10,
842  function () use ( $context ) {
843  $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
844  $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
845 
846  // Hit counts disabled for perf reasons, see T169997
847  /*
848  $tagStats = ChangeTags::tagUsageStatistics();
849  $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags, $tagStats );
850 
851  // Sort by hits
852  arsort( $tagHitCounts );
853  */
854  $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags );
855 
856  // Build the list and data
857  $result = [];
858  foreach ( $tagHitCounts as $tagName => $hits ) {
859  if (
860  // Only get active tags
861  isset( $explicitlyDefinedTags[ $tagName ] ) ||
862  isset( $softwareActivatedTags[ $tagName ] )
863  ) {
864  $result[] = [
865  'name' => $tagName,
866  'label' => Sanitizer::stripAllTags(
868  ),
869  'description' =>
871  $tagName, self::TAG_DESC_CHARACTER_LIMIT, $context
872  ),
873  'cssClass' => Sanitizer::escapeClass( 'mw-tag-' . $tagName ),
874  'hits' => $hits,
875  ];
876  }
877  }
878 
879  // Instead of sorting by hit count (disabled, see above), sort by display name
880  usort( $result, function ( $a, $b ) {
881  return strcasecmp( $a['label'], $b['label'] );
882  } );
883 
884  return $result;
885  },
886  [
887  'lockTSE' => 30
888  ]
889  );
890  }
891 
895  protected function outputNoResults() {
896  $this->getOutput()->addHTML(
897  '<div class="mw-changeslist-empty">' .
898  $this->msg( 'recentchanges-noresult' )->parse() .
899  '</div>'
900  );
901  }
902 
906  protected function outputTimeout() {
907  $this->getOutput()->addHTML(
908  '<div class="mw-changeslist-empty mw-changeslist-timeout">' .
909  $this->msg( 'recentchanges-timeout' )->parse() .
910  '</div>'
911  );
912  }
913 
919  public function getRows() {
920  $opts = $this->getOptions();
921 
922  $tables = [];
923  $fields = [];
924  $conds = [];
925  $query_options = [];
926  $join_conds = [];
927  $this->buildQuery( $tables, $fields, $conds, $query_options, $join_conds, $opts );
928 
929  return $this->doMainQuery( $tables, $fields, $conds, $query_options, $join_conds, $opts );
930  }
931 
937  public function getOptions() {
938  if ( $this->rcOptions === null ) {
939  $this->rcOptions = $this->setup( $this->rcSubpage );
940  }
941 
942  return $this->rcOptions;
943  }
944 
954  protected function registerFilters() {
955  $this->registerFiltersFromDefinitions( $this->filterGroupDefinitions );
956 
957  // Make sure this is not being transcluded (we don't want to show this
958  // information to all users just because the user that saves the edit can
959  // patrol or is logged in)
960  if ( !$this->including() && $this->getUser()->useRCPatrol() ) {
961  $this->registerFiltersFromDefinitions( $this->legacyReviewStatusFilterGroupDefinition );
962  $this->registerFiltersFromDefinitions( $this->reviewStatusFilterGroupDefinition );
963  }
964 
965  $changeTypeGroup = $this->getFilterGroup( 'changeType' );
966 
967  if ( $this->getConfig()->get( 'RCWatchCategoryMembership' ) ) {
968  $transformedHideCategorizationDef = $this->transformFilterDefinition(
969  $this->hideCategorizationFilterDefinition
970  );
971 
972  $transformedHideCategorizationDef['group'] = $changeTypeGroup;
973 
974  $hideCategorization = new ChangesListBooleanFilter(
975  $transformedHideCategorizationDef
976  );
977  }
978 
979  Hooks::run( 'ChangesListSpecialPageStructuredFilters', [ $this ] );
980 
981  $unstructuredGroupDefinition =
983  $this->getCustomFilters()
984  );
985  $this->registerFiltersFromDefinitions( [ $unstructuredGroupDefinition ] );
986 
987  $userExperienceLevel = $this->getFilterGroup( 'userExpLevel' );
988  $registered = $userExperienceLevel->getFilter( 'registered' );
989  $registered->setAsSupersetOf( $userExperienceLevel->getFilter( 'newcomer' ) );
990  $registered->setAsSupersetOf( $userExperienceLevel->getFilter( 'learner' ) );
991  $registered->setAsSupersetOf( $userExperienceLevel->getFilter( 'experienced' ) );
992 
993  $categoryFilter = $changeTypeGroup->getFilter( 'hidecategorization' );
994  $logactionsFilter = $changeTypeGroup->getFilter( 'hidelog' );
995  $pagecreationFilter = $changeTypeGroup->getFilter( 'hidenewpages' );
996 
997  $significanceTypeGroup = $this->getFilterGroup( 'significance' );
998  $hideMinorFilter = $significanceTypeGroup->getFilter( 'hideminor' );
999 
1000  // categoryFilter is conditional; see registerFilters
1001  if ( $categoryFilter !== null ) {
1002  $hideMinorFilter->conflictsWith(
1003  $categoryFilter,
1004  'rcfilters-hideminor-conflicts-typeofchange-global',
1005  'rcfilters-hideminor-conflicts-typeofchange',
1006  'rcfilters-typeofchange-conflicts-hideminor'
1007  );
1008  }
1009  $hideMinorFilter->conflictsWith(
1010  $logactionsFilter,
1011  'rcfilters-hideminor-conflicts-typeofchange-global',
1012  'rcfilters-hideminor-conflicts-typeofchange',
1013  'rcfilters-typeofchange-conflicts-hideminor'
1014  );
1015  $hideMinorFilter->conflictsWith(
1016  $pagecreationFilter,
1017  'rcfilters-hideminor-conflicts-typeofchange-global',
1018  'rcfilters-hideminor-conflicts-typeofchange',
1019  'rcfilters-typeofchange-conflicts-hideminor'
1020  );
1021  }
1022 
1032  protected function transformFilterDefinition( array $filterDefinition ) {
1033  return $filterDefinition;
1034  }
1035 
1045  protected function registerFiltersFromDefinitions( array $definition ) {
1046  $autoFillPriority = -1;
1047  foreach ( $definition as $groupDefinition ) {
1048  if ( !isset( $groupDefinition['priority'] ) ) {
1049  $groupDefinition['priority'] = $autoFillPriority;
1050  } else {
1051  // If it's explicitly specified, start over the auto-fill
1052  $autoFillPriority = $groupDefinition['priority'];
1053  }
1054 
1055  $autoFillPriority--;
1056 
1057  $className = $groupDefinition['class'];
1058  unset( $groupDefinition['class'] );
1059 
1060  foreach ( $groupDefinition['filters'] as &$filterDefinition ) {
1061  $filterDefinition = $this->transformFilterDefinition( $filterDefinition );
1062  }
1063 
1064  $this->registerFilterGroup( new $className( $groupDefinition ) );
1065  }
1066  }
1067 
1075  // Special internal unstructured group
1076  $unstructuredGroupDefinition = [
1077  'name' => 'unstructured',
1079  'priority' => -1, // Won't display in structured
1080  'filters' => [],
1081  ];
1082 
1083  foreach ( $customFilters as $name => $params ) {
1084  $unstructuredGroupDefinition['filters'][] = [
1085  'name' => $name,
1086  'showHide' => $params['msg'],
1087  'default' => $params['default'],
1088  ];
1089  }
1090 
1091  return $unstructuredGroupDefinition;
1092  }
1093 
1097  protected function getLegacyShowHideFilters() {
1098  $filters = [];
1099  foreach ( $this->filterGroups as $group ) {
1100  if ( $group instanceof ChangesListBooleanFilterGroup ) {
1101  foreach ( $group->getFilters() as $key => $filter ) {
1102  if ( $filter->displaysOnUnstructuredUi( $this ) ) {
1103  $filters[ $key ] = $filter;
1104  }
1105  }
1106  }
1107  }
1108  return $filters;
1109  }
1110 
1119  public function setup( $parameters ) {
1120  $this->registerFilters();
1121 
1122  $opts = $this->getDefaultOptions();
1123 
1124  $opts = $this->fetchOptionsFromRequest( $opts );
1125 
1126  // Give precedence to subpage syntax
1127  if ( $parameters !== null ) {
1128  $this->parseParameters( $parameters, $opts );
1129  }
1130 
1131  $this->validateOptions( $opts );
1132 
1133  return $opts;
1134  }
1135 
1145  public function getDefaultOptions() {
1146  $opts = new FormOptions();
1147  $structuredUI = $this->isStructuredFilterUiEnabled();
1148  // If urlversion=2 is set, ignore the filter defaults and set them all to false/empty
1149  $useDefaults = $this->getRequest()->getInt( 'urlversion' ) !== 2;
1150 
1152  foreach ( $this->filterGroups as $filterGroup ) {
1153  $filterGroup->addOptions( $opts, $useDefaults, $structuredUI );
1154  }
1155 
1156  $opts->add( 'namespace', '', FormOptions::STRING );
1157  $opts->add( 'invert', false );
1158  $opts->add( 'associated', false );
1159  $opts->add( 'urlversion', 1 );
1160  $opts->add( 'tagfilter', '' );
1161 
1162  $opts->add( 'days', $this->getDefaultDays(), FormOptions::FLOAT );
1163  $opts->add( 'limit', $this->getDefaultLimit(), FormOptions::INT );
1164 
1165  $opts->add( 'from', '' );
1166 
1167  return $opts;
1168  }
1169 
1175  public function registerFilterGroup( ChangesListFilterGroup $group ) {
1176  $groupName = $group->getName();
1177 
1178  $this->filterGroups[$groupName] = $group;
1179  }
1180 
1186  protected function getFilterGroups() {
1187  return $this->filterGroups;
1188  }
1189 
1197  public function getFilterGroup( $groupName ) {
1198  return isset( $this->filterGroups[$groupName] ) ?
1199  $this->filterGroups[$groupName] :
1200  null;
1201  }
1202 
1203  // Currently, this intentionally only includes filters that display
1204  // in the structured UI. This can be changed easily, though, if we want
1205  // to include data on filters that use the unstructured UI. messageKeys is a
1206  // special top-level value, with the value being an array of the message keys to
1207  // send to the client.
1215  public function getStructuredFilterJsData() {
1216  $output = [
1217  'groups' => [],
1218  'messageKeys' => [],
1219  ];
1220 
1221  usort( $this->filterGroups, function ( $a, $b ) {
1222  return $b->getPriority() - $a->getPriority();
1223  } );
1224 
1225  foreach ( $this->filterGroups as $groupName => $group ) {
1226  $groupOutput = $group->getJsData( $this );
1227  if ( $groupOutput !== null ) {
1228  $output['messageKeys'] = array_merge(
1229  $output['messageKeys'],
1230  $groupOutput['messageKeys']
1231  );
1232 
1233  unset( $groupOutput['messageKeys'] );
1234  $output['groups'][] = $groupOutput;
1235  }
1236  }
1237 
1238  return $output;
1239  }
1240 
1247  protected function getCustomFilters() {
1248  if ( $this->customFilters === null ) {
1249  $this->customFilters = [];
1250  Hooks::run( 'ChangesListSpecialPageFilters', [ $this, &$this->customFilters ], '1.29' );
1251  }
1252 
1253  return $this->customFilters;
1254  }
1255 
1264  protected function fetchOptionsFromRequest( $opts ) {
1265  $opts->fetchValuesFromRequest( $this->getRequest() );
1266 
1267  return $opts;
1268  }
1269 
1276  public function parseParameters( $par, FormOptions $opts ) {
1277  $stringParameterNameSet = [];
1278  $hideParameterNameSet = [];
1279 
1280  // URL parameters can be per-group, like 'userExpLevel',
1281  // or per-filter, like 'hideminor'.
1282 
1283  foreach ( $this->filterGroups as $filterGroup ) {
1284  if ( $filterGroup instanceof ChangesListStringOptionsFilterGroup ) {
1285  $stringParameterNameSet[$filterGroup->getName()] = true;
1286  } elseif ( $filterGroup instanceof ChangesListBooleanFilterGroup ) {
1287  foreach ( $filterGroup->getFilters() as $filter ) {
1288  $hideParameterNameSet[$filter->getName()] = true;
1289  }
1290  }
1291  }
1292 
1293  $bits = preg_split( '/\s*,\s*/', trim( $par ) );
1294  foreach ( $bits as $bit ) {
1295  $m = [];
1296  if ( isset( $hideParameterNameSet[$bit] ) ) {
1297  // hidefoo => hidefoo=true
1298  $opts[$bit] = true;
1299  } elseif ( isset( $hideParameterNameSet["hide$bit"] ) ) {
1300  // foo => hidefoo=false
1301  $opts["hide$bit"] = false;
1302  } elseif ( preg_match( '/^(.*)=(.*)$/', $bit, $m ) ) {
1303  if ( isset( $stringParameterNameSet[$m[1]] ) ) {
1304  $opts[$m[1]] = $m[2];
1305  }
1306  }
1307  }
1308  }
1309 
1315  public function validateOptions( FormOptions $opts ) {
1316  $isContradictory = $this->fixContradictoryOptions( $opts );
1317  $isReplaced = $this->replaceOldOptions( $opts );
1318 
1319  if ( $isContradictory || $isReplaced ) {
1320  $query = wfArrayToCgi( $this->convertParamsForLink( $opts->getChangedValues() ) );
1321  $this->getOutput()->redirect( $this->getPageTitle()->getCanonicalURL( $query ) );
1322  }
1323 
1324  $opts->validateIntBounds( 'limit', 0, 5000 );
1325  $opts->validateBounds( 'days', 0, $this->getConfig()->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
1326  }
1327 
1334  private function fixContradictoryOptions( FormOptions $opts ) {
1335  $fixed = $this->fixBackwardsCompatibilityOptions( $opts );
1336 
1337  foreach ( $this->filterGroups as $filterGroup ) {
1338  if ( $filterGroup instanceof ChangesListBooleanFilterGroup ) {
1339  $filters = $filterGroup->getFilters();
1340 
1341  if ( count( $filters ) === 1 ) {
1342  // legacy boolean filters should not be considered
1343  continue;
1344  }
1345 
1346  $allInGroupEnabled = array_reduce(
1347  $filters,
1348  function ( $carry, $filter ) use ( $opts ) {
1349  return $carry && $opts[ $filter->getName() ];
1350  },
1351  /* initialValue */ count( $filters ) > 0
1352  );
1353 
1354  if ( $allInGroupEnabled ) {
1355  foreach ( $filters as $filter ) {
1356  $opts[ $filter->getName() ] = false;
1357  }
1358 
1359  $fixed = true;
1360  }
1361  }
1362  }
1363 
1364  return $fixed;
1365  }
1366 
1376  private function fixBackwardsCompatibilityOptions( FormOptions $opts ) {
1377  if ( $opts['hideanons'] && $opts['hideliu'] ) {
1378  $opts->reset( 'hideanons' );
1379  if ( !$opts['hidebots'] ) {
1380  $opts->reset( 'hideliu' );
1381  $opts['hidehumans'] = 1;
1382  }
1383 
1384  return true;
1385  }
1386 
1387  return false;
1388  }
1389 
1396  public function replaceOldOptions( FormOptions $opts ) {
1397  if ( !$this->isStructuredFilterUiEnabled() ) {
1398  return false;
1399  }
1400 
1401  $changed = false;
1402 
1403  // At this point 'hideanons' and 'hideliu' cannot be both true,
1404  // because fixBackwardsCompatibilityOptions resets (at least) 'hideanons' in such case
1405  if ( $opts[ 'hideanons' ] ) {
1406  $opts->reset( 'hideanons' );
1407  $opts[ 'userExpLevel' ] = 'registered';
1408  $changed = true;
1409  }
1410 
1411  if ( $opts[ 'hideliu' ] ) {
1412  $opts->reset( 'hideliu' );
1413  $opts[ 'userExpLevel' ] = 'unregistered';
1414  $changed = true;
1415  }
1416 
1417  if ( $this->getFilterGroup( 'legacyReviewStatus' ) ) {
1418  if ( $opts[ 'hidepatrolled' ] ) {
1419  $opts->reset( 'hidepatrolled' );
1420  $opts[ 'reviewStatus' ] = 'unpatrolled';
1421  $changed = true;
1422  }
1423 
1424  if ( $opts[ 'hideunpatrolled' ] ) {
1425  $opts->reset( 'hideunpatrolled' );
1426  $opts[ 'reviewStatus' ] = implode(
1428  [ 'manual', 'auto' ]
1429  );
1430  $changed = true;
1431  }
1432  }
1433 
1434  return $changed;
1435  }
1436 
1445  protected function convertParamsForLink( $params ) {
1446  foreach ( $params as &$value ) {
1447  if ( $value === false ) {
1448  $value = '0';
1449  }
1450  }
1451  unset( $value );
1452  return $params;
1453  }
1454 
1466  protected function buildQuery( &$tables, &$fields, &$conds, &$query_options,
1467  &$join_conds, FormOptions $opts
1468  ) {
1469  $dbr = $this->getDB();
1470  $isStructuredUI = $this->isStructuredFilterUiEnabled();
1471 
1473  foreach ( $this->filterGroups as $filterGroup ) {
1474  $filterGroup->modifyQuery( $dbr, $this, $tables, $fields, $conds,
1475  $query_options, $join_conds, $opts, $isStructuredUI );
1476  }
1477 
1478  // Namespace filtering
1479  if ( $opts[ 'namespace' ] !== '' ) {
1480  $namespaces = explode( ';', $opts[ 'namespace' ] );
1481 
1482  if ( $opts[ 'associated' ] ) {
1483  $associatedNamespaces = array_map(
1484  function ( $ns ) {
1485  return MWNamespace::getAssociated( $ns );
1486  },
1487  $namespaces
1488  );
1489  $namespaces = array_unique( array_merge( $namespaces, $associatedNamespaces ) );
1490  }
1491 
1492  if ( count( $namespaces ) === 1 ) {
1493  $operator = $opts[ 'invert' ] ? '!=' : '=';
1494  $value = $dbr->addQuotes( reset( $namespaces ) );
1495  } else {
1496  $operator = $opts[ 'invert' ] ? 'NOT IN' : 'IN';
1497  sort( $namespaces );
1498  $value = '(' . $dbr->makeList( $namespaces ) . ')';
1499  }
1500  $conds[] = "rc_namespace $operator $value";
1501  }
1502 
1503  // Calculate cutoff
1504  $cutoff_unixtime = time() - $opts['days'] * 3600 * 24;
1505  $cutoff = $dbr->timestamp( $cutoff_unixtime );
1506 
1507  $fromValid = preg_match( '/^[0-9]{14}$/', $opts['from'] );
1508  if ( $fromValid && $opts['from'] > wfTimestamp( TS_MW, $cutoff ) ) {
1509  $cutoff = $dbr->timestamp( $opts['from'] );
1510  } else {
1511  $opts->reset( 'from' );
1512  }
1513 
1514  $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff );
1515  }
1516 
1528  protected function doMainQuery( $tables, $fields, $conds,
1529  $query_options, $join_conds, FormOptions $opts
1530  ) {
1531  $rcQuery = RecentChange::getQueryInfo();
1532  $tables = array_merge( $tables, $rcQuery['tables'] );
1533  $fields = array_merge( $rcQuery['fields'], $fields );
1534  $join_conds = array_merge( $join_conds, $rcQuery['joins'] );
1535 
1537  $tables,
1538  $fields,
1539  $conds,
1540  $join_conds,
1541  $query_options,
1542  ''
1543  );
1544 
1545  if ( !$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds,
1546  $opts )
1547  ) {
1548  return false;
1549  }
1550 
1551  $dbr = $this->getDB();
1552 
1553  return $dbr->select(
1554  $tables,
1555  $fields,
1556  $conds,
1557  __METHOD__,
1558  $query_options,
1559  $join_conds
1560  );
1561  }
1562 
1563  protected function runMainQueryHook( &$tables, &$fields, &$conds,
1564  &$query_options, &$join_conds, $opts
1565  ) {
1566  return Hooks::run(
1567  'ChangesListSpecialPageQuery',
1568  [ $this->getName(), &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ]
1569  );
1570  }
1571 
1577  protected function getDB() {
1578  return wfGetDB( DB_REPLICA );
1579  }
1580 
1587  private function webOutputHeader( $rowCount, $opts ) {
1588  if ( !$this->including() ) {
1589  $this->outputFeedLinks();
1590  $this->doHeader( $opts, $rowCount );
1591  }
1592  }
1593 
1600  public function webOutput( $rows, $opts ) {
1601  $this->webOutputHeader( $rows->numRows(), $opts );
1602 
1603  $this->outputChangesList( $rows, $opts );
1604  }
1605 
1609  public function outputFeedLinks() {
1610  // nothing by default
1611  }
1612 
1619  abstract public function outputChangesList( $rows, $opts );
1620 
1627  public function doHeader( $opts, $numRows ) {
1628  $this->setTopText( $opts );
1629 
1630  // @todo Lots of stuff should be done here.
1631 
1632  $this->setBottomText( $opts );
1633  }
1634 
1641  public function setTopText( FormOptions $opts ) {
1642  // nothing by default
1643  }
1644 
1651  public function setBottomText( FormOptions $opts ) {
1652  // nothing by default
1653  }
1654 
1664  public function getExtraOptions( $opts ) {
1665  return [];
1666  }
1667 
1673  public function makeLegend() {
1674  $context = $this->getContext();
1675  $user = $context->getUser();
1676  # The legend showing what the letters and stuff mean
1677  $legend = Html::openElement( 'dl' ) . "\n";
1678  # Iterates through them and gets the messages for both letter and tooltip
1679  $legendItems = $context->getConfig()->get( 'RecentChangesFlags' );
1680  if ( !( $user->useRCPatrol() || $user->useNPPatrol() ) ) {
1681  unset( $legendItems['unpatrolled'] );
1682  }
1683  foreach ( $legendItems as $key => $item ) { # generate items of the legend
1684  $label = isset( $item['legend'] ) ? $item['legend'] : $item['title'];
1685  $letter = $item['letter'];
1686  $cssClass = isset( $item['class'] ) ? $item['class'] : $key;
1687 
1688  $legend .= Html::element( 'dt',
1689  [ 'class' => $cssClass ], $context->msg( $letter )->text()
1690  ) . "\n" .
1691  Html::rawElement( 'dd',
1692  [ 'class' => Sanitizer::escapeClass( 'mw-changeslist-legend-' . $key ) ],
1693  $context->msg( $label )->parse()
1694  ) . "\n";
1695  }
1696  # (+-123)
1697  $legend .= Html::rawElement( 'dt',
1698  [ 'class' => 'mw-plusminus-pos' ],
1699  $context->msg( 'recentchanges-legend-plusminus' )->parse()
1700  ) . "\n";
1701  $legend .= Html::element(
1702  'dd',
1703  [ 'class' => 'mw-changeslist-legend-plusminus' ],
1704  $context->msg( 'recentchanges-label-plusminus' )->text()
1705  ) . "\n";
1706  $legend .= Html::closeElement( 'dl' ) . "\n";
1707 
1708  $legendHeading = $this->isStructuredFilterUiEnabled() ?
1709  $context->msg( 'rcfilters-legend-heading' )->parse() :
1710  $context->msg( 'recentchanges-legend-heading' )->parse();
1711 
1712  # Collapsible
1713  $collapsedState = $this->getRequest()->getCookie( 'changeslist-state' );
1714  $collapsedClass = $collapsedState === 'collapsed' ? ' mw-collapsed' : '';
1715 
1716  $legend =
1717  '<div class="mw-changeslist-legend mw-collapsible' . $collapsedClass . '">' .
1718  $legendHeading .
1719  '<div class="mw-collapsible-content">' . $legend . '</div>' .
1720  '</div>';
1721 
1722  return $legend;
1723  }
1724 
1728  protected function addModules() {
1729  $out = $this->getOutput();
1730  // Styles and behavior for the legend box (see makeLegend())
1731  $out->addModuleStyles( [
1732  'mediawiki.special.changeslist.legend',
1733  'mediawiki.special.changeslist',
1734  ] );
1735  $out->addModules( 'mediawiki.special.changeslist.legend.js' );
1736 
1737  if ( $this->isStructuredFilterUiEnabled() && !$this->including() ) {
1738  $out->addModules( 'mediawiki.rcfilters.filters.ui' );
1739  $out->addModuleStyles( 'mediawiki.rcfilters.filters.base.styles' );
1740  }
1741  }
1742 
1743  protected function getGroupName() {
1744  return 'changes';
1745  }
1746 
1763  public function filterOnUserExperienceLevel( $specialPageClassName, $context, $dbr,
1764  &$tables, &$fields, &$conds, &$query_options, &$join_conds, $selectedExpLevels, $now = 0
1765  ) {
1770 
1771  $LEVEL_COUNT = 5;
1772 
1773  // If all levels are selected, don't filter
1774  if ( count( $selectedExpLevels ) === $LEVEL_COUNT ) {
1775  return;
1776  }
1777 
1778  // both 'registered' and 'unregistered', experience levels, if any, are included in 'registered'
1779  if (
1780  in_array( 'registered', $selectedExpLevels ) &&
1781  in_array( 'unregistered', $selectedExpLevels )
1782  ) {
1783  return;
1784  }
1785 
1786  $actorMigration = ActorMigration::newMigration();
1787  $actorQuery = $actorMigration->getJoin( 'rc_user' );
1788  $tables += $actorQuery['tables'];
1789  $join_conds += $actorQuery['joins'];
1790 
1791  // 'registered' but not 'unregistered', experience levels, if any, are included in 'registered'
1792  if (
1793  in_array( 'registered', $selectedExpLevels ) &&
1794  !in_array( 'unregistered', $selectedExpLevels )
1795  ) {
1796  $conds[] = $actorMigration->isNotAnon( $actorQuery['fields']['rc_user'] );
1797  return;
1798  }
1799 
1800  if ( $selectedExpLevels === [ 'unregistered' ] ) {
1801  $conds[] = $actorMigration->isAnon( $actorQuery['fields']['rc_user'] );
1802  return;
1803  }
1804 
1805  $tables[] = 'user';
1806  $join_conds['user'] = [ 'LEFT JOIN', $actorQuery['fields']['rc_user'] . ' = user_id' ];
1807 
1808  if ( $now === 0 ) {
1809  $now = time();
1810  }
1811  $secondsPerDay = 86400;
1812  $learnerCutoff = $now - $wgLearnerMemberSince * $secondsPerDay;
1813  $experiencedUserCutoff = $now - $wgExperiencedUserMemberSince * $secondsPerDay;
1814 
1815  $aboveNewcomer = $dbr->makeList(
1816  [
1817  'user_editcount >= ' . intval( $wgLearnerEdits ),
1818  'user_registration <= ' . $dbr->addQuotes( $dbr->timestamp( $learnerCutoff ) ),
1819  ],
1821  );
1822 
1823  $aboveLearner = $dbr->makeList(
1824  [
1825  'user_editcount >= ' . intval( $wgExperiencedUserEdits ),
1826  'user_registration <= ' .
1827  $dbr->addQuotes( $dbr->timestamp( $experiencedUserCutoff ) ),
1828  ],
1830  );
1831 
1832  $conditions = [];
1833 
1834  if ( in_array( 'unregistered', $selectedExpLevels ) ) {
1835  $selectedExpLevels = array_diff( $selectedExpLevels, [ 'unregistered' ] );
1836  $conditions[] = $actorMigration->isAnon( $actorQuery['fields']['rc_user'] );
1837  }
1838 
1839  if ( $selectedExpLevels === [ 'newcomer' ] ) {
1840  $conditions[] = "NOT ( $aboveNewcomer )";
1841  } elseif ( $selectedExpLevels === [ 'learner' ] ) {
1842  $conditions[] = $dbr->makeList(
1843  [ $aboveNewcomer, "NOT ( $aboveLearner )" ],
1845  );
1846  } elseif ( $selectedExpLevels === [ 'experienced' ] ) {
1847  $conditions[] = $aboveLearner;
1848  } elseif ( $selectedExpLevels === [ 'learner', 'newcomer' ] ) {
1849  $conditions[] = "NOT ( $aboveLearner )";
1850  } elseif ( $selectedExpLevels === [ 'experienced', 'newcomer' ] ) {
1851  $conditions[] = $dbr->makeList(
1852  [ "NOT ( $aboveNewcomer )", $aboveLearner ],
1854  );
1855  } elseif ( $selectedExpLevels === [ 'experienced', 'learner' ] ) {
1856  $conditions[] = $aboveNewcomer;
1857  } elseif ( $selectedExpLevels === [ 'experienced', 'learner', 'newcomer' ] ) {
1858  $conditions[] = $actorMigration->isNotAnon( $actorQuery['fields']['rc_user'] );
1859  }
1860 
1861  if ( count( $conditions ) > 1 ) {
1862  $conds[] = $dbr->makeList( $conditions, IDatabase::LIST_OR );
1863  } elseif ( count( $conditions ) === 1 ) {
1864  $conds[] = reset( $conditions );
1865  }
1866  }
1867 
1873  public function isStructuredFilterUiEnabled() {
1874  if ( $this->getRequest()->getBool( 'rcfilters' ) ) {
1875  return true;
1876  }
1877 
1878  return static::checkStructuredFilterUiEnabled(
1879  $this->getConfig(),
1880  $this->getUser()
1881  );
1882  }
1883 
1891  if ( $this->getConfig()->get( 'StructuredChangeFiltersShowPreference' ) ) {
1892  return !$this->getUser()->getDefaultOption( 'rcenhancedfilters-disable' );
1893  } else {
1894  return $this->getUser()->getDefaultOption( 'rcenhancedfilters' );
1895  }
1896  }
1897 
1906  public static function checkStructuredFilterUiEnabled( Config $config, User $user ) {
1907  if ( $config->get( 'StructuredChangeFiltersShowPreference' ) ) {
1908  return !$user->getOption( 'rcenhancedfilters-disable' );
1909  } else {
1910  return $user->getOption( 'rcenhancedfilters' );
1911  }
1912  }
1913 
1921  public function getDefaultLimit() {
1922  return $this->getUser()->getIntOption( static::$limitPreferenceName );
1923  }
1924 
1933  public function getDefaultDays() {
1934  return floatval( $this->getUser()->getOption( static::$daysPreferenceName ) );
1935  }
1936 }
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:629
RecentChange\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new recentchanges object.
Definition: RecentChange.php:268
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:244
ChangesListFilterGroup\getName
getName()
Definition: ChangesListFilterGroup.php:285
ChangesListSpecialPage\getExtraOptions
getExtraOptions( $opts)
Get options to be displayed in a form.
Definition: ChangesListSpecialPage.php:1664
ChangesListSpecialPage\checkStructuredFilterUiEnabled
static checkStructuredFilterUiEnabled(Config $config, User $user)
Static method to check whether StructuredFilter UI is enabled for the given user.
Definition: ChangesListSpecialPage.php:1906
ChangesListSpecialPage\__construct
__construct( $name, $restriction)
Definition: ChangesListSpecialPage.php:106
SpecialPage\msg
msg( $key)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:747
ChangesListSpecialPage\considerActionsForDefaultSavedQuery
considerActionsForDefaultSavedQuery( $subpage)
Check whether or not the page should load defaults, and if so, whether a default saved query is relev...
Definition: ChangesListSpecialPage.php:702
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
$tables
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition: hooks.txt:990
$context
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2604
LinkBatch
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:34
FormOptions\FLOAT
const FLOAT
Float type, maps guessType() to WebRequest::getFloat()
Definition: FormOptions.php:48
ChangesListSpecialPage\includeRcFiltersApp
includeRcFiltersApp()
Include the modules and configuration for the RCFilters app.
Definition: ChangesListSpecialPage.php:774
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:676
$wgExperiencedUserMemberSince
$wgExperiencedUserMemberSince
Name of the external diff engine to use.
Definition: DefaultSettings.php:8789
captcha-old.count
count
Definition: captcha-old.py:249
ChangesListSpecialPage\makeLegend
makeLegend()
Return the legend displayed within the fieldset.
Definition: ChangesListSpecialPage.php:1673
ChangesListSpecialPage\execute
execute( $subpage)
Main execution point.
Definition: ChangesListSpecialPage.php:620
ChangesListSpecialPage\webOutputHeader
webOutputHeader( $rowCount, $opts)
Send header output to the OutputPage object, only called if not using feeds.
Definition: ChangesListSpecialPage.php:1587
ChangesListSpecialPage\fixContradictoryOptions
fixContradictoryOptions(FormOptions $opts)
Fix invalid options by resetting pairs that should never appear together.
Definition: ChangesListSpecialPage.php:1334
ChangesListSpecialPage\parseParameters
parseParameters( $par, FormOptions $opts)
Process $par and put options found in $opts.
Definition: ChangesListSpecialPage.php:1276
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1985
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1968
ChangesListSpecialPage\doMainQuery
doMainQuery( $tables, $fields, $conds, $query_options, $join_conds, FormOptions $opts)
Process the query.
Definition: ChangesListSpecialPage.php:1528
$namespaces
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:934
RC_LOG
const RC_LOG
Definition: Defines.php:145
ChangesListSpecialPage\fixBackwardsCompatibilityOptions
fixBackwardsCompatibilityOptions(FormOptions $opts)
Fix a special case (hideanons=1 and hideliu=1) in a special way, for backwards compatibility.
Definition: ChangesListSpecialPage.php:1376
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
ChangesListSpecialPage
Special page which uses a ChangesList to show query results.
Definition: ChangesListSpecialPage.php:35
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Definition: DeferredUpdates.php:76
ChangesListBooleanFilter
Represents a hide-based boolean filter (used on ChangesListSpecialPage and descendants)
Definition: ChangesListBooleanFilter.php:31
$params
$params
Definition: styleTest.css.php:40
RC_EDIT
const RC_EDIT
Definition: Defines.php:143
WANCacheReapUpdate
Class for fixing stale WANObjectCache keys using a purge event source.
Definition: WANCacheReapUpdate.php:24
ChangesListSpecialPage\getDefaultDays
getDefaultDays()
Get the default value of the number of days to display when loading the result set.
Definition: ChangesListSpecialPage.php:1933
$s
$s
Definition: mergeMessageFileList.php:187
FormOptions\reset
reset( $name)
Delete the option value.
Definition: FormOptions.php:205
ChangesListSpecialPage\fetchOptionsFromRequest
fetchOptionsFromRequest( $opts)
Fetch values for a FormOptions object from the WebRequest associated with this instance.
Definition: ChangesListSpecialPage.php:1264
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1138
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
FormOptions\validateIntBounds
validateIntBounds( $name, $min, $max)
Definition: FormOptions.php:253
RecentChange\SRC_CATEGORIZE
const SRC_CATEGORIZE
Definition: RecentChange.php:75
SpecialPage\getName
getName()
Get the name of this Special Page.
Definition: SpecialPage.php:150
Wikimedia\Rdbms\FakeResultWrapper
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
Definition: FakeResultWrapper.php:11
$messages
$messages
Definition: LogTests.i18n.php:8
ChangesListFilterGroup
Represents a filter group (used on ChangesListSpecialPage and descendants)
Definition: ChangesListFilterGroup.php:36
RecentChange\SRC_LOG
const SRC_LOG
Definition: RecentChange.php:73
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:89
ChangesListSpecialPage\$filterGroups
$filterGroups
Filter groups, and their contained filters This is an associative array (with group name as key) of C...
Definition: ChangesListSpecialPage.php:104
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
LIST_AND
const LIST_AND
Definition: Defines.php:44
ChangesListSpecialPage\outputNoResults
outputNoResults()
Add the "no results" message to the output.
Definition: ChangesListSpecialPage.php:895
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
ChangesListSpecialPage\doHeader
doHeader( $opts, $numRows)
Set the text to be displayed above the changes.
Definition: ChangesListSpecialPage.php:1627
$dbr
$dbr
Definition: testCompression.php:50
ChangeTags\truncateTagDescription
static truncateTagDescription( $tag, $length, IContextSource $context)
Get truncated message for the tag's long description.
Definition: ChangeTags.php:193
$wgExperiencedUserEdits
$wgExperiencedUserEdits
Name of the external diff engine to use.
Definition: DefaultSettings.php:8788
Html\closeElement
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:309
ChangesListSpecialPage\webOutput
webOutput( $rows, $opts)
Send output to the OutputPage object, only called if not used feeds.
Definition: ChangesListSpecialPage.php:1600
ChangeTags\modifyDisplayQuery
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
Definition: ChangeTags.php:723
$query
null for the wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1591
Config
Interface for configuration instances.
Definition: Config.php:28
ChangesListSpecialPage\TAG_DESC_CHARACTER_LIMIT
const TAG_DESC_CHARACTER_LIMIT
Maximum length of a tag description in UTF-8 characters.
Definition: ChangesListSpecialPage.php:40
FormatJson\decode
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:187
LIST_OR
const LIST_OR
Definition: Defines.php:47
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:715
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:934
ChangesListSpecialPage\$filterGroupDefinitions
$filterGroupDefinitions
Definition information for the filters and their groups.
Definition: ChangesListSpecialPage.php:86
ChangesListSpecialPage\$reviewStatusFilterGroupDefinition
$reviewStatusFilterGroupDefinition
Definition: ChangesListSpecialPage.php:93
LogFormatter\newFromRow
static newFromRow( $row)
Handy shortcut for constructing a formatter directly from database row.
Definition: LogFormatter.php:76
Wikimedia\Rdbms\DBQueryTimeoutError
Error thrown when a query times out.
Definition: DBQueryTimeoutError.php:29
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:24
Config\get
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
ChangesListSpecialPage\convertParamsForLink
convertParamsForLink( $params)
Convert parameters values from true/false to 1/0 so they are not omitted by wfArrayToCgi() Bug 36524.
Definition: ChangesListSpecialPage.php:1445
ChangesListSpecialPage\getRows
getRows()
Get the database result for this special page instance.
Definition: ChangesListSpecialPage.php:919
ChangesListSpecialPage\outputFeedLinks
outputFeedLinks()
Output feed links.
Definition: ChangesListSpecialPage.php:1609
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2800
ChangesListSpecialPage\runMainQueryHook
runMainQueryHook(&$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts)
Definition: ChangesListSpecialPage.php:1563
ChangesListSpecialPage\isStructuredFilterUiEnabled
isStructuredFilterUiEnabled()
Check whether the structured filter UI is enabled.
Definition: ChangesListSpecialPage.php:1873
ChangesListSpecialPage\$daysPreferenceName
static string $daysPreferenceName
Preference name for 'days'.
Definition: ChangesListSpecialPage.php:52
ChangesListSpecialPage\$hideCategorizationFilterDefinition
$hideCategorizationFilterDefinition
Definition: ChangesListSpecialPage.php:96
ChangesListSpecialPage\getStructuredFilterJsData
getStructuredFilterJsData()
Gets structured filter information needed by JS.
Definition: ChangesListSpecialPage.php:1215
ChangesListStringOptionsFilterGroup\SEPARATOR
const SEPARATOR
Delimiter.
Definition: ChangesListStringOptionsFilterGroup.php:46
ChangesListSpecialPage\outputChangesList
outputChangesList( $rows, $opts)
Build and output the actual changes list.
ChangesListSpecialPage\getFilterGroups
getFilterGroups()
Gets the currently registered filters groups.
Definition: ChangesListSpecialPage.php:1186
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
Definition: SpecialPage.php:484
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:686
RecentChange\SRC_EDIT
const SRC_EDIT
Definition: RecentChange.php:71
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
$output
$output
Definition: SyntaxHighlight.php:338
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
RecentChange\SRC_NEW
const SRC_NEW
Definition: RecentChange.php:72
ChangesListSpecialPage\buildQuery
buildQuery(&$tables, &$fields, &$conds, &$query_options, &$join_conds, FormOptions $opts)
Sets appropriate tables, fields, conditions, etc.
Definition: ChangesListSpecialPage.php:1466
ChangesListSpecialPage\getDefaultLimit
getDefaultLimit()
Get the default value of the number of changes to display when loading the result set.
Definition: ChangesListSpecialPage.php:1921
SpecialPage\getContext
getContext()
Gets the context this SpecialPage is executed in.
Definition: SpecialPage.php:649
RecentChange\PRC_PATROLLED
const PRC_PATROLLED
Definition: RecentChange.php:78
ChangeTags\listExplicitlyDefinedTags
static listExplicitlyDefinedTags()
Lists tags explicitly defined in the valid_tag table of the database.
Definition: ChangeTags.php:1320
ChangesListSpecialPage\$rcOptions
FormOptions $rcOptions
Definition: ChangesListSpecialPage.php:64
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:68
$value
$value
Definition: styleTest.css.php:45
ChangesListSpecialPage\registerFilters
registerFilters()
Register all filters and their groups (including those from hooks), plus handle conflicts and default...
Definition: ChangesListSpecialPage.php:954
ChangesListSpecialPage\$savedQueriesPreferenceName
static string $savedQueriesPreferenceName
Preference name for saved queries.
Definition: ChangesListSpecialPage.php:46
FormOptions\validateBounds
validateBounds( $name, $min, $max)
Constrain a numeric value for a given option to a given range.
Definition: FormOptions.php:268
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:36
ChangesListSpecialPage\setup
setup( $parameters)
Register all the filters, including legacy hook-driven ones.
Definition: ChangesListSpecialPage.php:1119
ChangesListSpecialPage\getFilterGroup
getFilterGroup( $groupName)
Gets a specified ChangesListFilterGroup by name.
Definition: ChangesListSpecialPage.php:1197
ChangesListSpecialPage\outputTimeout
outputTimeout()
Add the "timeout" message to the output.
Definition: ChangesListSpecialPage.php:906
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:666
ChangesListStringOptionsFilterGroup\NONE
const NONE
Signifies that no options in the group are selected, meaning the group has no effect.
Definition: ChangesListStringOptionsFilterGroup.php:59
RC_NEW
const RC_NEW
Definition: Defines.php:144
ChangesListSpecialPage\getCustomFilters
getCustomFilters()
Get custom show/hide filters using deprecated ChangesListSpecialPageFilters hook.
Definition: ChangesListSpecialPage.php:1247
ChangeTags\tagDescription
static tagDescription( $tag, IContextSource $context)
Get a short description for a tag.
Definition: ChangeTags.php:143
RecentChange\PRC_AUTOPATROLLED
const PRC_AUTOPATROLLED
Definition: RecentChange.php:79
ChangeTags\listSoftwareActivatedTags
static listSoftwareActivatedTags()
Lists those tags which core or extensions report as being "active".
Definition: ChangeTags.php:1273
$wgLearnerEdits
$wgLearnerEdits
The following variables define 3 user experience levels:
Definition: DefaultSettings.php:8786
ChangesListSpecialPage\addModules
addModules()
Add page-specific modules.
Definition: ChangesListSpecialPage.php:1728
FormOptions\STRING
const STRING
String type, maps guessType() to WebRequest::getText()
Definition: FormOptions.php:43
ChangesListSpecialPage\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: ChangesListSpecialPage.php:1743
ChangesListSpecialPage\areFiltersInConflict
areFiltersInConflict()
Check if filters are in conflict and guaranteed to return no results.
Definition: ChangesListSpecialPage.php:578
ChangesListSpecialPage\getDefaultOptions
getDefaultOptions()
Get a FormOptions object containing the default options.
Definition: ChangesListSpecialPage.php:1145
ChangesListSpecialPage\transformFilterDefinition
transformFilterDefinition(array $filterDefinition)
Transforms filter definition to prepare it for constructor.
Definition: ChangesListSpecialPage.php:1032
ChangesListBooleanFilterGroup
If the group is active, any unchecked filters will translate to hide parameters in the URL.
Definition: ChangesListBooleanFilterGroup.php:12
$cache
$cache
Definition: mcc.php:33
$rows
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2604
FormOptions\INT
const INT
Integer type, maps guessType() to WebRequest::getInt()
Definition: FormOptions.php:45
ObjectCache\getMainWANInstance
static getMainWANInstance()
Get the main WAN cache object.
Definition: ObjectCache.php:380
ChangesListSpecialPage\getDB
getDB()
Return a IDatabase object for reading.
Definition: ChangesListSpecialPage.php:1577
$code
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:783
ChangesListSpecialPage\getChangeTagList
getChangeTagList()
Fetch the change tags list for the front end.
Definition: ChangesListSpecialPage.php:836
ChangesListSpecialPage\getFilterGroupDefinitionFromLegacyCustomFilters
getFilterGroupDefinitionFromLegacyCustomFilters(array $customFilters)
Get filter group definition from legacy custom filters.
Definition: ChangesListSpecialPage.php:1074
RecentChange\PRC_UNPATROLLED
const PRC_UNPATROLLED
Definition: RecentChange.php:77
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
Html\openElement
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:251
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
NS_USER
const NS_USER
Definition: Defines.php:67
$wgLearnerMemberSince
$wgLearnerMemberSince
Name of the external diff engine to use.
Definition: DefaultSettings.php:8787
LoggerFactory
MediaWiki Logger LoggerFactory implements a PSR[0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method. MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances. The "Spi" in MediaWiki\Logger\Spi stands for "service provider interface". An SPI is an API intended to be implemented or extended by a third party. This software design pattern is intended to enable framework extension and replaceable components. It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki. The service provider interface allows the backend logging library to be implemented in multiple ways. The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime. This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance. Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
ChangesListSpecialPage\$rcSubpage
string $rcSubpage
Definition: ChangesListSpecialPage.php:61
$batch
$batch
Definition: linkcache.txt:23
of
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
Definition: globals.txt:10
ChangesListSpecialPage\filterOnUserExperienceLevel
filterOnUserExperienceLevel( $specialPageClassName, $context, $dbr, &$tables, &$fields, &$conds, &$query_options, &$join_conds, $selectedExpLevels, $now=0)
Filter on users' experience levels; this will not be called if nothing is selected.
Definition: ChangesListSpecialPage.php:1763
ChangesListSpecialPage\replaceOldOptions
replaceOldOptions(FormOptions $opts)
Replace old options with their structured UI equivalents.
Definition: ChangesListSpecialPage.php:1396
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
RC_CATEGORIZE
const RC_CATEGORIZE
Definition: Defines.php:147
FormOptions
Helper class to keep track of options when mixing links and form elements.
Definition: FormOptions.php:35
ChangesListSpecialPage\registerFiltersFromDefinitions
registerFiltersFromDefinitions(array $definition)
Register filters from a definition object.
Definition: ChangesListSpecialPage.php:1045
ChangesListSpecialPage\isStructuredFilterUiEnabledByDefault
isStructuredFilterUiEnabledByDefault()
Check whether the structured filter UI is enabled by default (regardless of this particular user's se...
Definition: ChangesListSpecialPage.php:1890
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
FormOptions\getChangedValues
getChangedValues()
Return options modified as an array ( name => value )
Definition: FormOptions.php:306
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:53
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
ChangesListSpecialPage\setBottomText
setBottomText(FormOptions $opts)
Send the text to be displayed after the options.
Definition: ChangesListSpecialPage.php:1651
ChangesListSpecialPage\validateOptions
validateOptions(FormOptions $opts)
Validate a FormOptions object generated by getDefaultOptions() with values already populated.
Definition: ChangesListSpecialPage.php:1315
ChangesListSpecialPage\registerFilterGroup
registerFilterGroup(ChangesListFilterGroup $group)
Register a structured changes list filter group.
Definition: ChangesListSpecialPage.php:1175
ChangesListStringOptionsFilterGroup
Represents a filter group with multiple string options.
Definition: ChangesListStringOptionsFilterGroup.php:37
ChangesListSpecialPage\getOptions
getOptions()
Get the current FormOptions for this request.
Definition: ChangesListSpecialPage.php:937
MWNamespace\getAssociated
static getAssociated( $index)
Get the associated namespace.
Definition: MWNamespace.php:162
SpecialPage\outputHeader
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
Definition: SpecialPage.php:583
SpecialPage\including
including( $x=null)
Whether the special page is being evaluated via transclusion.
Definition: SpecialPage.php:226
ChangesListSpecialPage\$customFilters
array $customFilters
Definition: ChangesListSpecialPage.php:67
ChangesListSpecialPage\$limitPreferenceName
static string $limitPreferenceName
Preference name for 'limit'.
Definition: ChangesListSpecialPage.php:58
array
the array() calling protocol came about after MediaWiki 1.4rc1.
ChangesListSpecialPage\getLegacyShowHideFilters
getLegacyShowHideFilters()
Definition: ChangesListSpecialPage.php:1097
MWExceptionHandler\logException
static logException( $e, $catcher=self::CAUGHT_BY_OTHER)
Log an exception to the exception log (if enabled).
Definition: MWExceptionHandler.php:645
ChangesListSpecialPage\$legacyReviewStatusFilterGroupDefinition
$legacyReviewStatusFilterGroupDefinition
Definition: ChangesListSpecialPage.php:90
wfArrayToCgi
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
Definition: GlobalFunctions.php:377
ChangesListSpecialPage\setTopText
setTopText(FormOptions $opts)
Send the text to be displayed before the options.
Definition: ChangesListSpecialPage.php:1641
$out
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:783