MediaWiki  master
SpecialRecentChanges.php
Go to the documentation of this file.
1 <?php
29 
36 
38 
41 
43  private $messageCache;
44 
46  private $loadBalancer;
47 
50 
52  private $db;
53 
55  public $denseRcSizeThreshold = 10000;
56 
63  public function __construct(
68  ) {
69  parent::__construct( 'Recentchanges', '' );
70  // This class is extended and therefor fallback to global state - T265310
71  $services = MediaWikiServices::getInstance();
72  $this->watchedItemStore = $watchedItemStore ?? $services->getWatchedItemStore();
73  $this->messageCache = $messageCache ?? $services->getMessageCache();
74  $this->loadBalancer = $loadBalancer ?? $services->getDBLoadBalancer();
75  $this->userOptionsLookup = $userOptionsLookup ?? $services->getUserOptionsLookup();
76 
77  $this->watchlistFilterGroupDefinition = [
78  'name' => 'watchlist',
79  'title' => 'rcfilters-filtergroup-watchlist',
80  'class' => ChangesListStringOptionsFilterGroup::class,
81  'priority' => -9,
82  'isFullCoverage' => true,
83  'filters' => [
84  [
85  'name' => 'watched',
86  'label' => 'rcfilters-filter-watchlist-watched-label',
87  'description' => 'rcfilters-filter-watchlist-watched-description',
88  'cssClassSuffix' => 'watched',
89  'isRowApplicableCallable' => static function ( IContextSource $ctx, RecentChange $rc ) {
90  return $rc->getAttribute( 'wl_user' );
91  }
92  ],
93  [
94  'name' => 'watchednew',
95  'label' => 'rcfilters-filter-watchlist-watchednew-label',
96  'description' => 'rcfilters-filter-watchlist-watchednew-description',
97  'cssClassSuffix' => 'watchednew',
98  'isRowApplicableCallable' => static function ( IContextSource $ctx, RecentChange $rc ) {
99  return $rc->getAttribute( 'wl_user' ) &&
100  $rc->getAttribute( 'rc_timestamp' ) &&
101  $rc->getAttribute( 'wl_notificationtimestamp' ) &&
102  $rc->getAttribute( 'rc_timestamp' ) >= $rc->getAttribute( 'wl_notificationtimestamp' );
103  },
104  ],
105  [
106  'name' => 'notwatched',
107  'label' => 'rcfilters-filter-watchlist-notwatched-label',
108  'description' => 'rcfilters-filter-watchlist-notwatched-description',
109  'cssClassSuffix' => 'notwatched',
110  'isRowApplicableCallable' => static function ( IContextSource $ctx, RecentChange $rc ) {
111  return $rc->getAttribute( 'wl_user' ) === null;
112  },
113  ]
114  ],
116  'queryCallable' => function ( string $specialClassName, IContextSource $ctx,
117  IDatabase $dbr, &$tables, &$fields, &$conds, &$query_options, &$join_conds, $selectedValues
118  ) {
119  sort( $selectedValues );
120  $notwatchedCond = 'wl_user IS NULL';
121  $watchedCond = 'wl_user IS NOT NULL';
122  if ( $this->getConfig()->get( 'WatchlistExpiry' ) ) {
123  // Expired watchlist items stay in the DB after their expiry time until they're purged,
124  // so it's not enough to only check for wl_user.
125  $quotedNow = $dbr->addQuotes( $dbr->timestamp() );
126  $notwatchedCond = "wl_user IS NULL OR ( we_expiry IS NOT NULL AND we_expiry < $quotedNow )";
127  $watchedCond = "wl_user IS NOT NULL AND ( we_expiry IS NULL OR we_expiry >= $quotedNow )";
128  }
129  $newCond = 'rc_timestamp >= wl_notificationtimestamp';
130 
131  if ( $selectedValues === [ 'notwatched' ] ) {
132  $conds[] = $notwatchedCond;
133  return;
134  }
135 
136  if ( $selectedValues === [ 'watched' ] ) {
137  $conds[] = $watchedCond;
138  return;
139  }
140 
141  if ( $selectedValues === [ 'watchednew' ] ) {
142  $conds[] = $dbr->makeList( [
143  $watchedCond,
144  $newCond
145  ], LIST_AND );
146  return;
147  }
148 
149  if ( $selectedValues === [ 'notwatched', 'watched' ] ) {
150  // no filters
151  return;
152  }
153 
154  if ( $selectedValues === [ 'notwatched', 'watchednew' ] ) {
155  $conds[] = $dbr->makeList( [
156  $notwatchedCond,
157  $dbr->makeList( [
158  $watchedCond,
159  $newCond
160  ], LIST_AND )
161  ], LIST_OR );
162  return;
163  }
164 
165  if ( $selectedValues === [ 'watched', 'watchednew' ] ) {
166  $conds[] = $watchedCond;
167  return;
168  }
169 
170  if ( $selectedValues === [ 'notwatched', 'watched', 'watchednew' ] ) {
171  // no filters
172  return;
173  }
174  }
175  ];
176  }
177 
181  public function execute( $subpage ) {
182  // Backwards-compatibility: redirect to new feed URLs
183  $feedFormat = $this->getRequest()->getVal( 'feed' );
184  if ( !$this->including() && $feedFormat ) {
185  $query = $this->getFeedQuery();
186  $query['feedformat'] = $feedFormat === 'atom' ? 'atom' : 'rss';
187  $this->getOutput()->redirect( wfAppendQuery( wfScript( 'api' ), $query ) );
188 
189  return;
190  }
191 
192  // 10 seconds server-side caching max
193  $out = $this->getOutput();
194  $out->setCdnMaxage( 10 );
195  // Check if the client has a cached version
196  $lastmod = $this->checkLastModified();
197  if ( $lastmod === false ) {
198  return;
199  }
200 
201  $this->addHelpLink(
202  'https://meta.wikimedia.org/wiki/Special:MyLanguage/Help:Recent_changes',
203  true
204  );
205  parent::execute( $subpage );
206  }
207 
211  protected function transformFilterDefinition( array $filterDefinition ) {
212  if ( isset( $filterDefinition['showHideSuffix'] ) ) {
213  $filterDefinition['showHide'] = 'rc' . $filterDefinition['showHideSuffix'];
214  }
215 
216  return $filterDefinition;
217  }
218 
225  private function needsWatchlistFeatures(): bool {
226  return !$this->including()
227  && $this->getUser()->isRegistered()
228  && $this->getAuthority()->isAllowed( 'viewmywatchlist' );
229  }
230 
234  protected function registerFilters() {
235  parent::registerFilters();
236 
237  if ( $this->needsWatchlistFeatures() ) {
238  $this->registerFiltersFromDefinitions( [ $this->watchlistFilterGroupDefinition ] );
239  $watchlistGroup = $this->getFilterGroup( 'watchlist' );
240  $watchlistGroup->getFilter( 'watched' )->setAsSupersetOf(
241  $watchlistGroup->getFilter( 'watchednew' )
242  );
243  }
244 
245  $user = $this->getUser();
246 
247  $significance = $this->getFilterGroup( 'significance' );
249  $hideMinor = $significance->getFilter( 'hideminor' );
250  '@phan-var ChangesListBooleanFilter $hideMinor';
251  $hideMinor->setDefault( $this->userOptionsLookup->getBoolOption( $user, 'hideminor' ) );
252 
253  $automated = $this->getFilterGroup( 'automated' );
255  $hideBots = $automated->getFilter( 'hidebots' );
256  '@phan-var ChangesListBooleanFilter $hideBots';
257  $hideBots->setDefault( true );
258 
260  $reviewStatus = $this->getFilterGroup( 'reviewStatus' );
261  '@phan-var ChangesListStringOptionsFilterGroup|null $reviewStatus';
262  if ( $reviewStatus !== null ) {
263  // Conditional on feature being available and rights
264  if ( $this->userOptionsLookup->getBoolOption( $user, 'hidepatrolled' ) ) {
265  $reviewStatus->setDefault( 'unpatrolled' );
266  $legacyReviewStatus = $this->getFilterGroup( 'legacyReviewStatus' );
268  $legacyHidePatrolled = $legacyReviewStatus->getFilter( 'hidepatrolled' );
269  '@phan-var ChangesListBooleanFilter $legacyHidePatrolled';
270  $legacyHidePatrolled->setDefault( true );
271  }
272  }
273 
274  $changeType = $this->getFilterGroup( 'changeType' );
276  $hideCategorization = $changeType->getFilter( 'hidecategorization' );
277  '@phan-var ChangesListBooleanFilter $hideCategorization';
278  if ( $hideCategorization !== null ) {
279  // Conditional on feature being available
280  $hideCategorization->setDefault( $this->userOptionsLookup->getBoolOption( $user, 'hidecategorization' ) );
281  }
282  }
283 
290  public function parseParameters( $par, FormOptions $opts ) {
291  parent::parseParameters( $par, $opts );
292 
293  $bits = preg_split( '/\s*,\s*/', trim( $par ) );
294  foreach ( $bits as $bit ) {
295  if ( is_numeric( $bit ) ) {
296  $opts['limit'] = $bit;
297  }
298 
299  $m = [];
300  if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) {
301  $opts['limit'] = $m[1];
302  }
303  if ( preg_match( '/^days=(\d+(?:\.\d+)?)$/', $bit, $m ) ) {
304  $opts['days'] = $m[1];
305  }
306  if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) {
307  $opts['namespace'] = $m[1];
308  }
309  if ( preg_match( '/^tagfilter=(.*)$/', $bit, $m ) ) {
310  $opts['tagfilter'] = $m[1];
311  }
312  }
313  }
314 
325  protected function addWatchlistJoins( IDatabase $dbr, &$tables, &$fields, &$joinConds, &$conds ) {
326  if ( !$this->needsWatchlistFeatures() ) {
327  return;
328  }
329 
330  // Join on watchlist table.
331  $tables[] = 'watchlist';
332  $fields[] = 'wl_user';
333  $fields[] = 'wl_notificationtimestamp';
334  $joinConds['watchlist'] = [ 'LEFT JOIN', [
335  'wl_user' => $this->getUser()->getId(),
336  'wl_title=rc_title',
337  'wl_namespace=rc_namespace'
338  ] ];
339 
340  // Exclude expired watchlist items.
341  if ( $this->getConfig()->get( 'WatchlistExpiry' ) ) {
342  $tables[] = 'watchlist_expiry';
343  $fields[] = 'we_expiry';
344  $joinConds['watchlist_expiry'] = [ 'LEFT JOIN', 'wl_id = we_item' ];
345  }
346  }
347 
351  protected function doMainQuery( $tables, $fields, $conds, $query_options,
352  $join_conds, FormOptions $opts
353  ) {
354  $dbr = $this->getDB();
355 
356  $rcQuery = RecentChange::getQueryInfo();
357  $tables = array_merge( $rcQuery['tables'], $tables );
358  $fields = array_merge( $rcQuery['fields'], $fields );
359  $join_conds = array_merge( $rcQuery['joins'], $join_conds );
360 
361  // Join with watchlist and watchlist_expiry tables to highlight watched rows.
362  $this->addWatchlistJoins( $dbr, $tables, $fields, $join_conds, $conds );
363 
364  // JOIN on page, used for 'last revision' filter highlight
365  $tables[] = 'page';
366  $fields[] = 'page_latest';
367  $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
368 
369  $tagFilter = $opts['tagfilter'] ? explode( '|', $opts['tagfilter'] ) : [];
371  $tables,
372  $fields,
373  $conds,
374  $join_conds,
375  $query_options,
376  $tagFilter
377  );
378 
379  if ( !$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds,
380  $opts )
381  ) {
382  return false;
383  }
384 
385  if ( $this->areFiltersInConflict() ) {
386  return false;
387  }
388 
389  $orderByAndLimit = [
390  'ORDER BY' => 'rc_timestamp DESC',
391  'LIMIT' => $opts['limit']
392  ];
393 
394  // Workaround for T298225: MySQL's lack of awareness of LIMIT when
395  // choosing the join order.
396  $ctTableName = ChangeTags::getDisplayTableName();
397  if ( isset( $join_conds[$ctTableName] )
398  && $this->isDenseTagFilter( $conds['ct_tag_id'] ?? [], $opts['limit'] )
399  ) {
400  $join_conds[$ctTableName][0] = 'STRAIGHT_JOIN';
401  }
402 
403  if ( in_array( 'DISTINCT', $query_options ) ) {
404  // ChangeTags::modifyDisplayQuery() adds DISTINCT when filtering on multiple tags.
405  // In order to prevent DISTINCT from causing query performance problems,
406  // we have to GROUP BY the primary key. This in turn requires us to add
407  // the primary key to the end of the ORDER BY, and the old ORDER BY to the
408  // start of the GROUP BY
409  $orderByAndLimit['ORDER BY'] = 'rc_timestamp DESC, rc_id DESC';
410  $orderByAndLimit['GROUP BY'] = 'rc_timestamp, rc_id';
411  }
412 
413  // rc_new is not an ENUM, but adding a redundant rc_new IN (0,1) gives mysql enough
414  // knowledge to use an index merge if it wants (it may use some other index though).
415  $conds += [ 'rc_new' => [ 0, 1 ] ];
416 
417  // array_merge() is used intentionally here so that hooks can, should
418  // they so desire, override the ORDER BY / LIMIT condition(s); prior to
419  // MediaWiki 1.26 this used to use the plus operator instead, which meant
420  // that extensions weren't able to change these conditions
421  $query_options = array_merge( $orderByAndLimit, $query_options );
422  $query_options['MAX_EXECUTION_TIME'] = $this->getConfig()->get( 'MaxExecutionTimeForExpensiveQueries' );
423  $rows = $dbr->select(
424  $tables,
425  $fields,
426  $conds,
427  __METHOD__,
428  $query_options,
429  $join_conds
430  );
431 
432  return $rows;
433  }
434 
446  protected function isDenseTagFilter( $tagIds, $limit ) {
447  global $wgMiserMode;
448 
449  $dbr = $this->getDB();
450  if ( !$tagIds
451  // This is a MySQL-specific hack
452  || $dbr->getType() !== 'mysql'
453  // Unnecessary for small wikis
454  || !$wgMiserMode
455  ) {
456  return false;
457  }
458 
459  $rcInfo = $dbr->newSelectQueryBuilder()
460  ->select( [
461  'min_id' => 'MIN(rc_id)',
462  'max_id' => 'MAX(rc_id)',
463  ] )
464  ->from( 'recentchanges' )
465  ->caller( __METHOD__ )
466  ->fetchRow();
467  if ( !$rcInfo ) {
468  return false;
469  }
470  $rcSize = $rcInfo->max_id - $rcInfo->min_id;
471  if ( $rcSize < $this->denseRcSizeThreshold ) {
472  // RC is too small to worry about
473  return false;
474  }
475  $tagCount = $dbr->newSelectQueryBuilder()
476  ->table( 'change_tag' )
477  ->where( [
478  'ct_rc_id >= ' . $dbr->addQuotes( $rcInfo->min_id ),
479  'ct_tag_id' => $tagIds
480  ] )
481  ->caller( __METHOD__ )
482  ->estimateRowCount();
483 
484  // If we scan recentchanges first, the number of rows examined will be
485  // approximately the limit divided by the proportion of tagged rows,
486  // i.e. $limit / ( $tagCount / $rcSize ). If that's less than $tagCount,
487  // use a straight join. The inequality below is rearranged for
488  // simplicity and to avoid division by zero.
489  $isDense = $limit * $rcSize < $tagCount * $tagCount;
490 
491  wfDebug( __METHOD__ . ": rcSize = $rcSize, tagCount = $tagCount, limit = $limit => " .
492  ( $isDense ? 'dense' : 'sparse' ) );
493  return $isDense;
494  }
495 
496  protected function getDB() {
497  if ( !$this->db ) {
498  $this->db = $this->loadBalancer->getConnectionRef(
499  ILoadBalancer::DB_REPLICA, 'recentchanges' );
500  }
501  return $this->db;
502  }
503 
504  public function outputFeedLinks() {
505  $this->addFeedLinks( $this->getFeedQuery() );
506  }
507 
513  protected function getFeedQuery() {
514  $query = array_filter( $this->getOptions()->getAllValues(), static function ( $value ) {
515  // API handles empty parameters in a different way
516  return $value !== '';
517  } );
518  $query['action'] = 'feedrecentchanges';
519  $feedLimit = $this->getConfig()->get( 'FeedLimit' );
520  if ( $query['limit'] > $feedLimit ) {
521  $query['limit'] = $feedLimit;
522  }
523 
524  return $query;
525  }
526 
533  public function outputChangesList( $rows, $opts ) {
534  $limit = $opts['limit'];
535 
536  $showWatcherCount = $this->getConfig()->get( 'RCShowWatchingUsers' )
537  && $this->userOptionsLookup->getBoolOption( $this->getUser(), 'shownumberswatching' );
538  $watcherCache = [];
539 
540  $counter = 1;
541  $list = ChangesList::newFromContext( $this->getContext(), $this->filterGroups );
542  $list->initChangesListRows( $rows );
543 
544  $userShowHiddenCats = $this->userOptionsLookup->getBoolOption( $this->getUser(), 'showhiddencats' );
545  $rclistOutput = $list->beginRecentChangesList();
546  if ( $this->isStructuredFilterUiEnabled() ) {
547  $rclistOutput .= $this->makeLegend();
548  }
549 
550  foreach ( $rows as $obj ) {
551  if ( $limit == 0 ) {
552  break;
553  }
554  $rc = RecentChange::newFromRow( $obj );
555 
556  # Skip CatWatch entries for hidden cats based on user preference
557  if (
558  $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE &&
559  !$userShowHiddenCats &&
560  $rc->getParam( 'hidden-cat' )
561  ) {
562  continue;
563  }
564 
565  $rc->counter = $counter++;
566  # Check if the page has been updated since the last visit
567  if ( $this->getConfig()->get( 'ShowUpdatedMarker' )
568  && !empty( $obj->wl_notificationtimestamp )
569  ) {
570  $rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp );
571  } else {
572  $rc->notificationtimestamp = false; // Default
573  }
574  # Check the number of users watching the page
575  $rc->numberofWatchingusers = 0; // Default
576  if ( $showWatcherCount && $obj->rc_namespace >= 0 ) {
577  if ( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) {
578  $watcherCache[$obj->rc_namespace][$obj->rc_title] =
579  $this->watchedItemStore->countWatchers(
580  new TitleValue( (int)$obj->rc_namespace, $obj->rc_title )
581  );
582  }
583  $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title];
584  }
585 
586  $watched = !empty( $obj->wl_user );
587  if ( $watched && $this->getConfig()->get( 'WatchlistExpiry' ) ) {
588  $notExpired = $obj->we_expiry === null
589  || MWTimestamp::convert( TS_UNIX, $obj->we_expiry ) > wfTimestamp();
590  $watched = $watched && $notExpired;
591  }
592  $changeLine = $list->recentChangesLine( $rc, $watched, $counter );
593  if ( $changeLine !== false ) {
594  $rclistOutput .= $changeLine;
595  --$limit;
596  }
597  }
598  $rclistOutput .= $list->endRecentChangesList();
599 
600  if ( $rows->numRows() === 0 ) {
601  $this->outputNoResults();
602  if ( !$this->including() ) {
603  $this->getOutput()->setStatusCode( 404 );
604  }
605  } else {
606  $this->getOutput()->addHTML( $rclistOutput );
607  }
608  }
609 
616  public function doHeader( $opts, $numRows ) {
617  $this->setTopText( $opts );
618 
619  $defaults = $opts->getAllValues();
620  $nondefaults = $opts->getChangedValues();
621 
622  $panel = [];
623  if ( !$this->isStructuredFilterUiEnabled() ) {
624  $panel[] = $this->makeLegend();
625  }
626  $panel[] = $this->optionsPanel( $defaults, $nondefaults, $numRows );
627  $panel[] = '<hr />';
628 
629  $extraOpts = $this->getExtraOptions( $opts );
630  $extraOptsCount = count( $extraOpts );
631  $count = 0;
632  $submit = ' ' . Xml::submitButton( $this->msg( 'recentchanges-submit' )->text() );
633 
634  $out = Xml::openElement( 'table', [ 'class' => 'mw-recentchanges-table' ] );
635  foreach ( $extraOpts as $name => $optionRow ) {
636  # Add submit button to the last row only
637  ++$count;
638  $addSubmit = ( $count === $extraOptsCount ) ? $submit : '';
639 
640  $out .= Xml::openElement( 'tr', [ 'class' => $name . 'Form' ] );
641  if ( is_array( $optionRow ) ) {
642  $out .= Xml::tags(
643  'td',
644  [ 'class' => 'mw-label mw-' . $name . '-label' ],
645  $optionRow[0]
646  );
647  $out .= Xml::tags(
648  'td',
649  [ 'class' => 'mw-input' ],
650  $optionRow[1] . $addSubmit
651  );
652  } else {
653  $out .= Xml::tags(
654  'td',
655  [ 'class' => 'mw-input', 'colspan' => 2 ],
656  $optionRow . $addSubmit
657  );
658  }
659  $out .= Xml::closeElement( 'tr' );
660  }
661  $out .= Xml::closeElement( 'table' );
662 
663  $unconsumed = $opts->getUnconsumedValues();
664  foreach ( $unconsumed as $key => $value ) {
665  $out .= Html::hidden( $key, $value );
666  }
667 
668  $t = $this->getPageTitle();
669  $out .= Html::hidden( 'title', $t->getPrefixedText() );
670  $form = Xml::tags( 'form', [ 'action' => wfScript() ], $out );
671  $panel[] = $form;
672  $panelString = implode( "\n", $panel );
673 
674  $rcoptions = Xml::fieldset(
675  $this->msg( 'recentchanges-legend' )->text(),
676  $panelString,
677  [ 'class' => 'rcoptions cloptions' ]
678  );
679 
680  // Insert a placeholder for RCFilters
681  if ( $this->isStructuredFilterUiEnabled() ) {
682  $rcfilterContainer = Html::element(
683  'div',
684  // TODO: Remove deprecated rcfilters-container class
685  [ 'class' => 'rcfilters-container mw-rcfilters-container' ]
686  );
687 
688  $loadingContainer = Html::rawElement(
689  'div',
690  [ 'class' => 'mw-rcfilters-spinner' ],
692  'div',
693  [ 'class' => 'mw-rcfilters-spinner-bounce' ]
694  )
695  );
696 
697  // Wrap both with rcfilters-head
698  $this->getOutput()->addHTML(
700  'div',
701  // TODO: Remove deprecated rcfilters-head class
702  [ 'class' => 'rcfilters-head mw-rcfilters-head' ],
703  $rcfilterContainer . $rcoptions
704  )
705  );
706 
707  // Add spinner
708  $this->getOutput()->addHTML( $loadingContainer );
709  } else {
710  $this->getOutput()->addHTML( $rcoptions );
711  }
712 
713  $this->setBottomText( $opts );
714  }
715 
721  public function setTopText( FormOptions $opts ) {
722  $message = $this->msg( 'recentchangestext' )->inContentLanguage();
723  if ( !$message->isDisabled() ) {
724  $contLang = $this->getContentLanguage();
725  // Parse the message in this weird ugly way to preserve the ability to include interlanguage
726  // links in it (T172461). In the future when T66969 is resolved, perhaps we can just use
727  // $message->parse() instead. This code is copied from Message::parseText().
728  $parserOutput = $this->messageCache->parse(
729  $message->plain(),
730  $this->getPageTitle(),
731  /*linestart*/true,
732  // Message class sets the interface flag to false when parsing in a language different than
733  // user language, and this is wiki content language
734  /*interface*/false,
735  $contLang
736  );
737  $content = $parserOutput->getText( [
738  'enableSectionEditLinks' => false,
739  ] );
740  // Add only metadata here (including the language links), text is added below
741  $this->getOutput()->addParserOutputMetadata( $parserOutput );
742 
743  $langAttributes = [
744  'lang' => $contLang->getHtmlCode(),
745  'dir' => $contLang->getDir(),
746  ];
747 
748  $topLinksAttributes = [ 'class' => 'mw-recentchanges-toplinks' ];
749 
750  if ( $this->isStructuredFilterUiEnabled() ) {
751  // Check whether the widget is already collapsed or expanded
752  $collapsedState = $this->getRequest()->getCookie( 'rcfilters-toplinks-collapsed-state' );
753  // Note that an empty/unset cookie means collapsed, so check for !== 'expanded'
754  $topLinksAttributes[ 'class' ] .= $collapsedState !== 'expanded' ?
755  ' mw-recentchanges-toplinks-collapsed' : '';
756 
757  $this->getOutput()->enableOOUI();
758  $contentTitle = new OOUI\ButtonWidget( [
759  'classes' => [ 'mw-recentchanges-toplinks-title' ],
760  'label' => new OOUI\HtmlSnippet( $this->msg( 'rcfilters-other-review-tools' )->parse() ),
761  'framed' => false,
762  'indicator' => $collapsedState !== 'expanded' ? 'down' : 'up',
763  'flags' => [ 'progressive' ],
764  ] );
765 
766  $contentWrapper = Html::rawElement( 'div',
767  array_merge(
768  [ 'class' => 'mw-recentchanges-toplinks-content mw-collapsible-content' ],
769  $langAttributes
770  ),
771  $content
772  );
773  $content = $contentTitle . $contentWrapper;
774  } else {
775  // Language direction should be on the top div only
776  // if the title is not there. If it is there, it's
777  // interface direction, and the language/dir attributes
778  // should be on the content itself
779  $topLinksAttributes = array_merge( $topLinksAttributes, $langAttributes );
780  }
781 
782  $this->getOutput()->addHTML(
783  Html::rawElement( 'div', $topLinksAttributes, $content )
784  );
785  }
786  }
787 
794  public function getExtraOptions( $opts ) {
795  $opts->consumeValues( [
796  'namespace', 'invert', 'associated', 'tagfilter'
797  ] );
798 
799  $extraOpts = [];
800  $extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
801 
803  $opts['tagfilter'], false, $this->getContext() );
804  if ( count( $tagFilter ) ) {
805  $extraOpts['tagfilter'] = $tagFilter;
806  }
807 
808  // Don't fire the hook for subclasses. (Or should we?)
809  if ( $this->getName() === 'Recentchanges' ) {
810  $this->getHookRunner()->onSpecialRecentChangesPanel( $extraOpts, $opts );
811  }
812 
813  return $extraOpts;
814  }
815 
819  protected function addModules() {
820  parent::addModules();
821  $out = $this->getOutput();
822  $out->addModules( 'mediawiki.special.recentchanges' );
823  }
824 
832  public function checkLastModified() {
833  $dbr = $this->getDB();
834  $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', '', __METHOD__ );
835 
836  return $lastmod;
837  }
838 
845  protected function namespaceFilterForm( FormOptions $opts ) {
846  $nsSelect = Html::namespaceSelector(
847  [ 'selected' => $opts['namespace'], 'all' => '', 'in-user-lang' => true ],
848  [ 'name' => 'namespace', 'id' => 'namespace' ]
849  );
850  $nsLabel = Xml::label( $this->msg( 'namespace' )->text(), 'namespace' );
851  $attribs = [ 'class' => [ 'mw-input-with-label' ] ];
852  // Hide the checkboxes when the namespace filter is set to 'all'.
853  if ( $opts['namespace'] === '' ) {
854  $attribs['class'][] = 'mw-input-hidden';
855  }
856  $invert = Html::rawElement( 'span', $attribs, Xml::checkLabel(
857  $this->msg( 'invert' )->text(), 'invert', 'nsinvert',
858  $opts['invert'],
859  [ 'title' => $this->msg( 'tooltip-invert' )->text() ]
860  ) );
861  $associated = Html::rawElement( 'span', $attribs, Xml::checkLabel(
862  $this->msg( 'namespace_association' )->text(), 'associated', 'nsassociated',
863  $opts['associated'],
864  [ 'title' => $this->msg( 'tooltip-namespace_association' )->text() ]
865  ) );
866 
867  return [ $nsLabel, "$nsSelect $invert $associated" ];
868  }
869 
881  private function makeOptionsLink( $title, $override, $options, $active = false ) {
882  $params = $this->convertParamsForLink( $override + $options );
883 
884  if ( $active ) {
885  $title = new HtmlArmor( '<strong>' . htmlspecialchars( $title ) . '</strong>' );
886  }
887 
888  return $this->getLinkRenderer()->makeKnownLink( $this->getPageTitle(), $title, [
889  'data-params' => json_encode( $override ),
890  'data-keys' => implode( ',', array_keys( $override ) ),
891  ], $params );
892  }
893 
902  private function optionsPanel( $defaults, $nondefaults, $numRows ) {
903  $options = $nondefaults + $defaults;
904 
905  $note = '';
906  $msg = $this->msg( 'rclegend' );
907  if ( !$msg->isDisabled() ) {
908  $note .= Html::rawElement(
909  'div',
910  [ 'class' => 'mw-rclegend' ],
911  $msg->parse()
912  );
913  }
914 
915  $lang = $this->getLanguage();
916  $user = $this->getUser();
917  $config = $this->getConfig();
918  if ( $options['from'] ) {
919  $resetLink = $this->makeOptionsLink( $this->msg( 'rclistfromreset' )->text(),
920  [ 'from' => '' ], $nondefaults );
921 
922  $noteFromMsg = $this->msg( 'rcnotefrom' )
923  ->numParams( $options['limit'] )
924  ->params(
925  $lang->userTimeAndDate( $options['from'], $user ),
926  $lang->userDate( $options['from'], $user ),
927  $lang->userTime( $options['from'], $user )
928  )
929  ->numParams( $numRows );
930  $note .= Html::rawElement(
931  'span',
932  [ 'class' => 'rcnotefrom' ],
933  $noteFromMsg->parse()
934  ) .
935  ' ' .
937  'span',
938  [ 'class' => 'rcoptions-listfromreset' ],
939  $this->msg( 'parentheses' )->rawParams( $resetLink )->parse()
940  ) .
941  '<br />';
942  }
943 
944  # Sort data for display and make sure it's unique after we've added user data.
945  $linkLimits = $config->get( 'RCLinkLimits' );
946  $linkLimits[] = $options['limit'];
947  sort( $linkLimits );
948  $linkLimits = array_unique( $linkLimits );
949 
950  $linkDays = $this->getLinkDays();
951  $linkDays[] = $options['days'];
952  sort( $linkDays );
953  $linkDays = array_unique( $linkDays );
954 
955  // limit links
956  $cl = [];
957  foreach ( $linkLimits as $value ) {
958  $cl[] = $this->makeOptionsLink( $lang->formatNum( $value ),
959  [ 'limit' => $value ], $nondefaults, $value == $options['limit'] );
960  }
961  $cl = $lang->pipeList( $cl );
962 
963  // day links, reset 'from' to none
964  $dl = [];
965  foreach ( $linkDays as $value ) {
966  $dl[] = $this->makeOptionsLink( $lang->formatNum( $value ),
967  [ 'days' => $value, 'from' => '' ], $nondefaults, $value == $options['days'] );
968  }
969  $dl = $lang->pipeList( $dl );
970 
971  $showhide = [ 'show', 'hide' ];
972 
973  $links = [];
974 
975  foreach ( $this->getLegacyShowHideFilters() as $key => $filter ) {
976  $msg = $filter->getShowHide();
977  $linkMessage = $this->msg( $msg . '-' . $showhide[1 - $options[$key]] );
978  // Extensions can define additional filters, but don't need to define the corresponding
979  // messages. If they don't exist, just fall back to 'show' and 'hide'.
980  if ( !$linkMessage->exists() ) {
981  $linkMessage = $this->msg( $showhide[1 - $options[$key]] );
982  }
983 
984  $link = $this->makeOptionsLink( $linkMessage->text(),
985  [ $key => 1 - $options[$key] ], $nondefaults );
986 
987  $attribs = [
988  'class' => "$msg rcshowhideoption clshowhideoption",
989  'data-filter-name' => $filter->getName(),
990  ];
991 
992  if ( $filter->isFeatureAvailableOnStructuredUi() ) {
993  $attribs['data-feature-in-structured-ui'] = true;
994  }
995 
996  $links[] = Html::rawElement(
997  'span',
998  $attribs,
999  $this->msg( $msg )->rawParams( $link )->parse()
1000  );
1001  }
1002 
1003  // show from this onward link
1004  $timestamp = wfTimestampNow();
1005  $now = $lang->userTimeAndDate( $timestamp, $user );
1006  $timenow = $lang->userTime( $timestamp, $user );
1007  $datenow = $lang->userDate( $timestamp, $user );
1008  $pipedLinks = '<span class="rcshowhide">' . $lang->pipeList( $links ) . '</span>';
1009 
1010  $rclinks = Html::rawElement(
1011  'span',
1012  [ 'class' => 'rclinks' ],
1013  $this->msg( 'rclinks' )->rawParams( $cl, $dl, '' )->parse()
1014  );
1015 
1016  $rclistfrom = Html::rawElement(
1017  'span',
1018  [ 'class' => 'rclistfrom' ],
1019  $this->makeOptionsLink(
1020  $this->msg( 'rclistfrom' )->plaintextParams( $now, $timenow, $datenow )->text(),
1021  [ 'from' => $timestamp, 'fromFormatted' => $now ],
1022  $nondefaults
1023  )
1024  );
1025 
1026  return "{$note}$rclinks<br />$pipedLinks<br />$rclistfrom";
1027  }
1028 
1029  public function isIncludable() {
1030  return true;
1031  }
1032 
1033  protected function getCacheTTL() {
1034  return 60 * 5;
1035  }
1036 
1037  public function getDefaultLimit() {
1038  $systemPrefValue = $this->userOptionsLookup->getIntOption( $this->getUser(), 'rclimit' );
1039  // Prefer the RCFilters-specific preference if RCFilters is enabled
1040  if ( $this->isStructuredFilterUiEnabled() ) {
1041  return $this->userOptionsLookup->getIntOption(
1042  $this->getUser(), $this->getLimitPreferenceName(), $systemPrefValue
1043  );
1044  }
1045 
1046  // Otherwise, use the system rclimit preference value
1047  return $systemPrefValue;
1048  }
1049 
1053  protected function getLimitPreferenceName(): string {
1054  return 'rcfilters-limit'; // Use RCFilters-specific preference
1055  }
1056 
1060  protected function getSavedQueriesPreferenceName(): string {
1061  return 'rcfilters-saved-queries';
1062  }
1063 
1067  protected function getDefaultDaysPreferenceName(): string {
1068  return 'rcdays'; // Use general RecentChanges preference
1069  }
1070 
1074  protected function getCollapsedPreferenceName(): string {
1075  return 'rcfilters-rc-collapsed';
1076  }
1077 
1078 }
LIST_OR
const LIST_OR
Definition: Defines.php:46
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:768
RecentChange\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new recentchanges object.
Definition: RecentChange.php:258
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:936
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
SpecialRecentChanges\parseParameters
parseParameters( $par, FormOptions $opts)
Process $par and put options found in $opts.
Definition: SpecialRecentChanges.php:290
SpecialRecentChanges\doMainQuery
doMainQuery( $tables, $fields, $conds, $query_options, $join_conds, FormOptions $opts)
Process the query.Array of tables; see IDatabase::select $table Array of fields; see IDatabase::selec...
Definition: SpecialRecentChanges.php:351
SpecialRecentChanges\$db
IDatabase $db
Definition: SpecialRecentChanges.php:52
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:814
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:203
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
SpecialRecentChanges\namespaceFilterForm
namespaceFilterForm(FormOptions $opts)
Creates the choose namespace selection.
Definition: SpecialRecentChanges.php:845
Xml\label
static label( $label, $id, $attribs=[])
Convenience function to build an HTML form label.
Definition: Xml.php:365
ChangesListSpecialPage\makeLegend
makeLegend()
Return the legend displayed within the fieldset.
Definition: ChangesListSpecialPage.php:1722
RecentChange
Utility class for creating new RC entries.
Definition: RecentChange.php:81
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1649
SpecialRecentChanges\needsWatchlistFeatures
needsWatchlistFeatures()
Whether or not the current query needs to use watchlist data: check that the current user can use the...
Definition: SpecialRecentChanges.php:225
SpecialRecentChanges\$userOptionsLookup
UserOptionsLookup $userOptionsLookup
Definition: SpecialRecentChanges.php:49
LIST_AND
const LIST_AND
Definition: Defines.php:43
SpecialRecentChanges\getSavedQueriesPreferenceName
getSavedQueriesPreferenceName()
Definition: SpecialRecentChanges.php:1060
ChangesListSpecialPage
Special page which uses a ChangesList to show query results.
Definition: ChangesListSpecialPage.php:39
SpecialRecentChanges\outputChangesList
outputChangesList( $rows, $opts)
Build and output the actual changes list.
Definition: SpecialRecentChanges.php:533
SpecialRecentChanges\getCacheTTL
getCacheTTL()
Definition: SpecialRecentChanges.php:1033
SpecialRecentChanges\$watchlistFilterGroupDefinition
$watchlistFilterGroupDefinition
Definition: SpecialRecentChanges.php:37
SpecialRecentChanges\addWatchlistJoins
addWatchlistJoins(IDatabase $dbr, &$tables, &$fields, &$joinConds, &$conds)
Add required values to a query's $tables, $fields, $joinConds, and $conds arrays to join to the watch...
Definition: SpecialRecentChanges.php:325
SpecialRecentChanges\registerFilters
registerFilters()
Register all filters and their groups (including those from hooks), plus handle conflicts and default...
Definition: SpecialRecentChanges.php:234
SpecialPage\getAuthority
getAuthority()
Shortcut to get the Authority executing this instance.
Definition: SpecialPage.php:834
ChangeTags\buildTagFilterSelector
static buildTagFilterSelector( $selected='', $ooui=false, IContextSource $context=null)
Build a text box to select a change tag.
Definition: ChangeTags.php:1036
SpecialPage\getLanguage
getLanguage()
Shortcut to get user's language.
Definition: SpecialPage.php:854
SpecialRecentChanges\isIncludable
isIncludable()
Whether it's allowed to transclude the special page via {{Special:Foo/params}}.
Definition: SpecialRecentChanges.php:1029
SpecialRecentChanges\$loadBalancer
ILoadBalancer $loadBalancer
Definition: SpecialRecentChanges.php:46
SpecialPage\getName
getName()
Get the name of this Special Page.
Definition: SpecialPage.php:203
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:111
SpecialRecentChanges\getLimitPreferenceName
getLimitPreferenceName()
Definition: SpecialRecentChanges.php:1053
ChangesListSpecialPage\outputNoResults
outputNoResults()
Add the "no results" message to the output.
Definition: ChangesListSpecialPage.php:982
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
$dbr
$dbr
Definition: testCompression.php:54
Xml\fieldset
static fieldset( $legend=false, $content=false, $attribs=[])
Shortcut for creating fieldsets.
Definition: Xml.php:624
wfAppendQuery
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
Definition: GlobalFunctions.php:422
ChangeTags\modifyDisplayQuery
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
Definition: ChangeTags.php:896
SpecialRecentChanges\addModules
addModules()
Add page-specific modules.
Definition: SpecialRecentChanges.php:819
SpecialPage\addHelpLink
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: SpecialPage.php:972
SpecialPage\getHookRunner
getHookRunner()
Definition: SpecialPage.php:1119
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:902
SpecialPage\addFeedLinks
addFeedLinks( $params)
Adds RSS/atom links.
Definition: SpecialPage.php:954
wfScript
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
Definition: GlobalFunctions.php:2268
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:26
ChangesListSpecialPage\convertParamsForLink
convertParamsForLink( $params)
Convert parameters values from true/false to 1/0 so they are not omitted by wfArrayToCgi() T38524.
Definition: ChangesListSpecialPage.php:1489
ChangesListSpecialPage\runMainQueryHook
runMainQueryHook(&$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts)
Definition: ChangesListSpecialPage.php:1615
ChangesListSpecialPage\isStructuredFilterUiEnabled
isStructuredFilterUiEnabled()
Check whether the structured filter UI is enabled.
Definition: ChangesListSpecialPage.php:1944
SpecialRecentChanges\transformFilterDefinition
transformFilterDefinition(array $filterDefinition)
Transforms filter definition to prepare it for constructor.See overrides of this method as well....
Definition: SpecialRecentChanges.php:211
ChangeTags\getDisplayTableName
static getDisplayTableName()
Get the name of the change_tag table to use for modifyDisplayQuery().
Definition: ChangeTags.php:959
RecentChange\newFromRow
static newFromRow( $row)
Definition: RecentChange.php:157
SpecialRecentChanges\__construct
__construct(WatchedItemStoreInterface $watchedItemStore=null, MessageCache $messageCache=null, ILoadBalancer $loadBalancer=null, UserOptionsLookup $userOptionsLookup=null)
Definition: SpecialRecentChanges.php:63
SpecialRecentChanges\getFeedQuery
getFeedQuery()
Get URL query parameters for action=feedrecentchanges API feed of current recent changes view.
Definition: SpecialRecentChanges.php:513
$title
$title
Definition: testCompression.php:38
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:824
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1678
SpecialRecentChanges\execute
execute( $subpage)
Definition: SpecialRecentChanges.php:181
SpecialRecentChanges\getDB
getDB()
Return a IDatabase object for reading.
Definition: SpecialRecentChanges.php:496
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:894
SpecialPage\getContext
getContext()
Gets the context this SpecialPage is executed in.
Definition: SpecialPage.php:788
ChangesList\newFromContext
static newFromContext(IContextSource $context, array $groups=[])
Fetch an appropriate changes list class for the specified context Some users might want to use an enh...
Definition: ChangesList.php:98
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:834
$content
$content
Definition: router.php:76
Html\namespaceSelector
static namespaceSelector(array $params=[], array $selectAttribs=[])
Build a drop-down box for selecting a namespace.
Definition: Html.php:926
Xml\tags
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:133
SpecialRecentChanges\$watchedItemStore
WatchedItemStoreInterface $watchedItemStore
Definition: SpecialRecentChanges.php:40
ChangesListSpecialPage\getFilterGroup
getFilterGroup( $groupName)
Gets a specified ChangesListFilterGroup by name.
Definition: ChangesListSpecialPage.php:1257
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:804
ChangesListStringOptionsFilterGroup\NONE
const NONE
Signifies that no options in the group are selected, meaning the group has no effect.
Definition: ChangesListStringOptionsFilterGroup.php:59
SpecialRecentChanges
A special page that lists last changes made to the wiki.
Definition: SpecialRecentChanges.php:35
SpecialRecentChanges\$messageCache
MessageCache $messageCache
Definition: SpecialRecentChanges.php:43
MediaWiki\User\UserOptionsLookup
Provides access to user options.
Definition: UserOptionsLookup.php:29
SpecialRecentChanges\$denseRcSizeThreshold
int $denseRcSizeThreshold
Definition: SpecialRecentChanges.php:55
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:58
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:1052
SpecialRecentChanges\optionsPanel
optionsPanel( $defaults, $nondefaults, $numRows)
Creates the options panel.
Definition: SpecialRecentChanges.php:902
ChangesListSpecialPage\areFiltersInConflict
areFiltersInConflict()
Check if filters are in conflict and guaranteed to return no results.
Definition: ChangesListSpecialPage.php:564
SpecialRecentChanges\setTopText
setTopText(FormOptions $opts)
Send the text to be displayed above the options.
Definition: SpecialRecentChanges.php:721
SpecialRecentChanges\doHeader
doHeader( $opts, $numRows)
Set the text to be displayed above the changes.
Definition: SpecialRecentChanges.php:616
$wgMiserMode
$wgMiserMode
Disable database-intensive features.
Definition: DefaultSettings.php:2642
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:120
SpecialRecentChanges\getExtraOptions
getExtraOptions( $opts)
Get options to be displayed in a form.
Definition: SpecialRecentChanges.php:794
RC_CATEGORIZE
const RC_CATEGORIZE
Definition: Defines.php:119
SpecialRecentChanges\outputFeedLinks
outputFeedLinks()
Definition: SpecialRecentChanges.php:504
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:213
SpecialRecentChanges\isDenseTagFilter
isDenseTagFilter( $tagIds, $limit)
Determine whether a tag filter matches a high proportion of the rows in recentchanges.
Definition: SpecialRecentChanges.php:446
FormOptions
Helper class to keep track of options when mixing links and form elements.
Definition: FormOptions.php:35
SpecialPage\getContentLanguage
getContentLanguage()
Shortcut to get content language.
Definition: SpecialPage.php:864
$t
$t
Definition: testCompression.php:74
ChangesListSpecialPage\registerFiltersFromDefinitions
registerFiltersFromDefinitions(array $definition)
Register filters from a definition object.
Definition: ChangesListSpecialPage.php:1131
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:235
WatchedItemStoreInterface
Definition: WatchedItemStoreInterface.php:31
MessageCache
Cache of messages that are defined by MediaWiki namespace pages or by hooks.
Definition: MessageCache.php:52
ChangesListSpecialPage\setBottomText
setBottomText(FormOptions $opts)
Send the text to be displayed after the options.
Definition: ChangesListSpecialPage.php:1700
SpecialRecentChanges\getDefaultLimit
getDefaultLimit()
Get the default value of the number of changes to display when loading the result set.
Definition: SpecialRecentChanges.php:1037
ChangesListSpecialPage\getOptions
getOptions()
Get the current FormOptions for this request.
Definition: ChangesListSpecialPage.php:1026
ChangesListSpecialPage\getLinkDays
getLinkDays()
Definition: ChangesListSpecialPage.php:765
SpecialPage\including
including( $x=null)
Whether the special page is being evaluated via transclusion.
Definition: SpecialPage.php:290
SpecialRecentChanges\getDefaultDaysPreferenceName
getDefaultDaysPreferenceName()
Definition: SpecialRecentChanges.php:1067
SpecialRecentChanges\checkLastModified
checkLastModified()
Get last modified date, for client caching Don't use this if we are using the patrol feature,...
Definition: SpecialRecentChanges.php:832
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
SpecialRecentChanges\makeOptionsLink
makeOptionsLink( $title, $override, $options, $active=false)
Makes change an option link which carries all the other options.
Definition: SpecialRecentChanges.php:881
ChangesListSpecialPage\getLegacyShowHideFilters
getLegacyShowHideFilters()
Definition: ChangesListSpecialPage.php:1157
Xml\submitButton
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button When $wgUseMediaWikiUIEverywhere is true it will ...
Definition: Xml.php:466
Xml\checkLabel
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition: Xml.php:426
SpecialRecentChanges\getCollapsedPreferenceName
getCollapsedPreferenceName()
Definition: SpecialRecentChanges.php:1074
TitleValue
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:40