MediaWiki  master
ContribsPager.php
Go to the documentation of this file.
1 <?php
32 
34 
38  private $messages;
39 
43  private $target;
44 
48  private $namespace = '';
49 
53  private $tagFilter;
54 
58  private $nsInvert;
59 
64  private $associated;
65 
69  private $deletedOnly;
70 
74  private $topOnly;
75 
79  private $newOnly;
80 
84  private $hideMinor;
85 
86  private $preventClickjacking = false;
87 
89  private $mDbSecondary;
90 
94  private $mParentLens;
95 
99  private $templateParser;
100 
101  public function __construct( IContextSource $context, array $options,
103  ) {
104  // Set ->target before calling parent::__construct() so
105  // parent can call $this->getIndexField() and get the right result. Set
106  // the rest too just to keep things simple.
107  $this->target = $options['target'] ?? '';
108  $this->namespace = $options['namespace'] ?? '';
109  $this->tagFilter = $options['tagfilter'] ?? false;
110  $this->nsInvert = $options['nsInvert'] ?? false;
111  $this->associated = $options['associated'] ?? false;
112 
113  $this->deletedOnly = !empty( $options['deletedOnly'] );
114  $this->topOnly = !empty( $options['topOnly'] );
115  $this->newOnly = !empty( $options['newOnly'] );
116  $this->hideMinor = !empty( $options['hideMinor'] );
117 
118  parent::__construct( $context, $linkRenderer );
119 
120  $msgs = [
121  'diff',
122  'hist',
123  'pipe-separator',
124  'uctop'
125  ];
126 
127  foreach ( $msgs as $msg ) {
128  $this->messages[$msg] = $this->msg( $msg )->escaped();
129  }
130 
131  // Date filtering: use timestamp if available
132  $startTimestamp = '';
133  $endTimestamp = '';
134  if ( $options['start'] ) {
135  $startTimestamp = $options['start'] . ' 00:00:00';
136  }
137  if ( $options['end'] ) {
138  $endTimestamp = $options['end'] . ' 23:59:59';
139  }
140  $this->getDateRangeCond( $startTimestamp, $endTimestamp );
141 
142  // Most of this code will use the 'contributions' group DB, which can map to replica DBs
143  // with extra user based indexes or partioning by user. The additional metadata
144  // queries should use a regular replica DB since the lookup pattern is not all by user.
145  $this->mDbSecondary = wfGetDB( DB_REPLICA ); // any random replica DB
146  $this->mDb = wfGetDB( DB_REPLICA, 'contributions' );
147  $this->templateParser = new TemplateParser();
148  }
149 
150  function getDefaultQuery() {
151  $query = parent::getDefaultQuery();
152  $query['target'] = $this->target;
153 
154  return $query;
155  }
156 
164  function getNavigationBar() {
165  return Html::rawElement( 'p', [ 'class' => 'mw-pager-navigation-bar' ],
166  parent::getNavigationBar()
167  );
168  }
169 
179  function reallyDoQuery( $offset, $limit, $order ) {
180  list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo(
181  $offset,
182  $limit,
183  $order
184  );
185 
186  /*
187  * This hook will allow extensions to add in additional queries, so they can get their data
188  * in My Contributions as well. Extensions should append their results to the $data array.
189  *
190  * Extension queries have to implement the navbar requirement as well. They should
191  * - have a column aliased as $pager->getIndexField()
192  * - have LIMIT set
193  * - have a WHERE-clause that compares the $pager->getIndexField()-equivalent column to the offset
194  * - have the ORDER BY specified based upon the details provided by the navbar
195  *
196  * See includes/Pager.php buildQueryInfo() method on how to build LIMIT, WHERE & ORDER BY
197  *
198  * &$data: an array of results of all contribs queries
199  * $pager: the ContribsPager object hooked into
200  * $offset: see phpdoc above
201  * $limit: see phpdoc above
202  * $descending: see phpdoc above
203  */
204  $data = [ $this->mDb->select(
205  $tables, $fields, $conds, $fname, $options, $join_conds
206  ) ];
207  Hooks::run(
208  'ContribsPager::reallyDoQuery',
209  [ &$data, $this, $offset, $limit, $order ]
210  );
211 
212  $result = [];
213 
214  // loop all results and collect them in an array
215  foreach ( $data as $query ) {
216  foreach ( $query as $i => $row ) {
217  // use index column as key, allowing us to easily sort in PHP
218  $result[$row->{$this->getIndexField()} . "-$i"] = $row;
219  }
220  }
221 
222  // sort results
223  if ( $order === self::QUERY_ASCENDING ) {
224  ksort( $result );
225  } else {
226  krsort( $result );
227  }
228 
229  // enforce limit
230  $result = array_slice( $result, 0, $limit );
231 
232  // get rid of array keys
233  $result = array_values( $result );
234 
235  return new FakeResultWrapper( $result );
236  }
237 
247  private function getTargetTable() {
248  $user = User::newFromName( $this->target, false );
249  $ipRangeConds = $user->isAnon() ? $this->getIpRangeConds( $this->mDb, $this->target ) : null;
250  if ( $ipRangeConds ) {
251  return 'ip_changes';
252  } else {
253  $conds = ActorMigration::newMigration()->getWhere( $this->mDb, 'rev_user', $user );
254  if ( isset( $conds['orconds']['actor'] ) ) {
255  // @todo: This will need changing when revision_actor_temp goes away
256  return 'revision_actor_temp';
257  }
258  }
259 
260  return 'revision';
261  }
262 
263  function getQueryInfo() {
264  $revQuery = Revision::getQueryInfo( [ 'page', 'user' ] );
265  $queryInfo = [
266  'tables' => $revQuery['tables'],
267  'fields' => array_merge( $revQuery['fields'], [ 'page_is_new' ] ),
268  'conds' => [],
269  'options' => [],
270  'join_conds' => $revQuery['joins'],
271  ];
272  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
273 
274  // WARNING: Keep this in sync with getTargetTable()!
275  $user = User::newFromName( $this->target, false );
276  $ipRangeConds = $user->isAnon() ? $this->getIpRangeConds( $this->mDb, $this->target ) : null;
277  if ( $ipRangeConds ) {
278  $queryInfo['tables'][] = 'ip_changes';
279  $queryInfo['join_conds']['ip_changes'] = [
280  'LEFT JOIN', [ 'ipc_rev_id = rev_id' ]
281  ];
282  $queryInfo['conds'][] = $ipRangeConds;
283  } else {
284  // tables and joins are already handled by Revision::getQueryInfo()
285  $conds = ActorMigration::newMigration()->getWhere( $this->mDb, 'rev_user', $user );
286  $queryInfo['conds'][] = $conds['conds'];
287  // Force the appropriate index to avoid bad query plans (T189026)
288  if ( isset( $conds['orconds']['actor'] ) ) {
289  // @todo: This will need changing when revision_actor_temp goes away
290  $queryInfo['options']['USE INDEX']['temp_rev_user'] = 'actor_timestamp';
291  } else {
292  $queryInfo['options']['USE INDEX']['revision'] =
293  isset( $conds['orconds']['userid'] ) ? 'user_timestamp' : 'usertext_timestamp';
294  }
295  }
296 
297  if ( $this->deletedOnly ) {
298  $queryInfo['conds'][] = 'rev_deleted != 0';
299  }
300 
301  if ( $this->topOnly ) {
302  $queryInfo['conds'][] = 'rev_id = page_latest';
303  }
304 
305  if ( $this->newOnly ) {
306  $queryInfo['conds'][] = 'rev_parent_id = 0';
307  }
308 
309  if ( $this->hideMinor ) {
310  $queryInfo['conds'][] = 'rev_minor_edit = 0';
311  }
312 
313  $user = $this->getUser();
314  $queryInfo['conds'] = array_merge( $queryInfo['conds'], $this->getNamespaceCond() );
315 
316  // Paranoia: avoid brute force searches (T19342)
317  if ( !$permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
318  $queryInfo['conds'][] = $this->mDb->bitAnd(
319  'rev_deleted', RevisionRecord::DELETED_USER
320  ) . ' = 0';
321  } elseif ( !$permissionManager->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) ) {
322  $queryInfo['conds'][] = $this->mDb->bitAnd(
323  'rev_deleted', RevisionRecord::SUPPRESSED_USER
324  ) . ' != ' . RevisionRecord::SUPPRESSED_USER;
325  }
326 
327  // $this->getIndexField() must be in the result rows, as reallyDoQuery() tries to access it.
328  $indexField = $this->getIndexField();
329  if ( $indexField !== 'rev_timestamp' ) {
330  $queryInfo['fields'][] = $indexField;
331  }
332 
334  $queryInfo['tables'],
335  $queryInfo['fields'],
336  $queryInfo['conds'],
337  $queryInfo['join_conds'],
338  $queryInfo['options'],
339  $this->tagFilter
340  );
341 
342  // Avoid PHP 7.1 warning from passing $this by reference
343  $pager = $this;
344  Hooks::run( 'ContribsPager::getQueryInfo', [ &$pager, &$queryInfo ] );
345 
346  return $queryInfo;
347  }
348 
349  function getNamespaceCond() {
350  if ( $this->namespace !== '' ) {
351  $selectedNS = $this->mDb->addQuotes( $this->namespace );
352  $eq_op = $this->nsInvert ? '!=' : '=';
353  $bool_op = $this->nsInvert ? 'AND' : 'OR';
354 
355  if ( !$this->associated ) {
356  return [ "page_namespace $eq_op $selectedNS" ];
357  }
358 
359  $associatedNS = $this->mDb->addQuotes(
360  MediaWikiServices::getInstance()->getNamespaceInfo()->getAssociated( $this->namespace )
361  );
362 
363  return [
364  "page_namespace $eq_op $selectedNS " .
365  $bool_op .
366  " page_namespace $eq_op $associatedNS"
367  ];
368  }
369 
370  return [];
371  }
372 
379  private function getIpRangeConds( $db, $ip ) {
380  // First make sure it is a valid range and they are not outside the CIDR limit
381  if ( !$this->isQueryableRange( $ip ) ) {
382  return false;
383  }
384 
385  list( $start, $end ) = IP::parseRange( $ip );
386 
387  return 'ipc_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end );
388  }
389 
397  public function isQueryableRange( $ipRange ) {
398  $limits = $this->getConfig()->get( 'RangeContributionsCIDRLimit' );
399 
400  $bits = IP::parseCIDR( $ipRange )[1];
401  if (
402  ( $bits === false ) ||
403  ( IP::isIPv4( $ipRange ) && $bits < $limits['IPv4'] ) ||
404  ( IP::isIPv6( $ipRange ) && $bits < $limits['IPv6'] )
405  ) {
406  return false;
407  }
408 
409  return true;
410  }
411 
415  public function getIndexField() {
416  // The returned column is used for sorting and continuation, so we need to
417  // make sure to use the right denormalized column depending on which table is
418  // being targeted by the query to avoid bad query plans.
419  // See T200259, T204669, T220991, and T221380.
420  $target = $this->getTargetTable();
421  switch ( $target ) {
422  case 'revision':
423  return 'rev_timestamp';
424  case 'ip_changes':
425  return 'ipc_rev_timestamp';
426  case 'revision_actor_temp':
427  return 'revactor_timestamp';
428  default:
429  wfWarn(
430  __METHOD__ . ": Unknown value '$target' from " . static::class . '::getTargetTable()', 0
431  );
432  return 'rev_timestamp';
433  }
434  }
435 
439  public function getTagFilter() {
440  return $this->tagFilter;
441  }
442 
446  public function getTarget() {
447  return $this->target;
448  }
449 
453  public function isNewOnly() {
454  return $this->newOnly;
455  }
456 
460  public function getNamespace() {
461  return $this->namespace;
462  }
463 
467  protected function getExtraSortFields() {
468  // The returned columns are used for sorting, so we need to make sure
469  // to use the right denormalized column depending on which table is
470  // being targeted by the query to avoid bad query plans.
471  // See T200259, T204669, T220991, and T221380.
472  $target = $this->getTargetTable();
473  switch ( $target ) {
474  case 'revision':
475  return [ 'rev_id' ];
476  case 'ip_changes':
477  return [ 'ipc_rev_id' ];
478  case 'revision_actor_temp':
479  return [ 'revactor_rev' ];
480  default:
481  wfWarn(
482  __METHOD__ . ": Unknown value '$target' from " . static::class . '::getTargetTable()', 0
483  );
484  return [ 'rev_id' ];
485  }
486  }
487 
488  protected function doBatchLookups() {
489  # Do a link batch query
490  $this->mResult->seek( 0 );
491  $parentRevIds = [];
492  $this->mParentLens = [];
493  $batch = new LinkBatch();
494  $isIpRange = $this->isQueryableRange( $this->target );
495  # Give some pointers to make (last) links
496  foreach ( $this->mResult as $row ) {
497  if ( isset( $row->rev_parent_id ) && $row->rev_parent_id ) {
498  $parentRevIds[] = $row->rev_parent_id;
499  }
500  if ( isset( $row->rev_id ) ) {
501  $this->mParentLens[$row->rev_id] = $row->rev_len;
502  if ( $isIpRange ) {
503  // If this is an IP range, batch the IP's talk page
504  $batch->add( NS_USER_TALK, $row->rev_user_text );
505  }
506  $batch->add( $row->page_namespace, $row->page_title );
507  }
508  }
509  # Fetch rev_len for revisions not already scanned above
510  $this->mParentLens += Revision::getParentLengths(
511  $this->mDbSecondary,
512  array_diff( $parentRevIds, array_keys( $this->mParentLens ) )
513  );
514  $batch->execute();
515  $this->mResult->seek( 0 );
516  }
517 
521  protected function getStartBody() {
522  return "<ul class=\"mw-contributions-list\">\n";
523  }
524 
528  protected function getEndBody() {
529  return "</ul>\n";
530  }
531 
540  public function tryToCreateValidRevision( $row, $title = null ) {
541  /*
542  * There may be more than just revision rows. To make sure that we'll only be processing
543  * revisions here, let's _try_ to build a revision out of our row (without displaying
544  * notices though) and then trying to grab data from the built object. If we succeed,
545  * we're definitely dealing with revision data and we may proceed, if not, we'll leave it
546  * to extensions to subscribe to the hook to parse the row.
547  */
548  Wikimedia\suppressWarnings();
549  try {
550  $rev = new Revision( $row, 0, $title );
551  $validRevision = (bool)$rev->getId();
552  } catch ( Exception $e ) {
553  $validRevision = false;
554  }
555  Wikimedia\restoreWarnings();
556  return $validRevision ? $rev : null;
557  }
558 
571  function formatRow( $row ) {
572  $ret = '';
573  $classes = [];
574  $attribs = [];
575 
576  $linkRenderer = $this->getLinkRenderer();
577  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
578 
579  $page = null;
580  // Create a title for the revision if possible
581  // Rows from the hook may not include title information
582  if ( isset( $row->page_namespace ) && isset( $row->page_title ) ) {
583  $page = Title::newFromRow( $row );
584  }
585  $rev = $this->tryToCreateValidRevision( $row, $page );
586  if ( $rev ) {
587  $attribs['data-mw-revid'] = $rev->getId();
588 
589  $link = $linkRenderer->makeLink(
590  $page,
591  $page->getPrefixedText(),
592  [ 'class' => 'mw-contributions-title' ],
593  $page->isRedirect() ? [ 'redirect' => 'no' ] : []
594  );
595  # Mark current revisions
596  $topmarktext = '';
597  $user = $this->getUser();
598 
599  if ( $row->rev_id === $row->page_latest ) {
600  $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
601  $classes[] = 'mw-contributions-current';
602  # Add rollback link
603  if ( !$row->page_is_new &&
604  $permissionManager->quickUserCan( 'rollback', $user, $page ) &&
605  $permissionManager->quickUserCan( 'edit', $user, $page )
606  ) {
607  $this->preventClickjacking();
608  $topmarktext .= ' ' . Linker::generateRollback( $rev, $this->getContext(),
609  [ 'noBrackets' ] );
610  }
611  }
612  # Is there a visible previous revision?
613  if ( $rev->userCan( RevisionRecord::DELETED_TEXT, $user ) && $rev->getParentId() !== 0 ) {
614  $difftext = $linkRenderer->makeKnownLink(
615  $page,
616  new HtmlArmor( $this->messages['diff'] ),
617  [ 'class' => 'mw-changeslist-diff' ],
618  [
619  'diff' => 'prev',
620  'oldid' => $row->rev_id
621  ]
622  );
623  } else {
624  $difftext = $this->messages['diff'];
625  }
626  $histlink = $linkRenderer->makeKnownLink(
627  $page,
628  new HtmlArmor( $this->messages['hist'] ),
629  [ 'class' => 'mw-changeslist-history' ],
630  [ 'action' => 'history' ]
631  );
632 
633  if ( $row->rev_parent_id === null ) {
634  // For some reason rev_parent_id isn't populated for this row.
635  // Its rumoured this is true on wikipedia for some revisions (T36922).
636  // Next best thing is to have the total number of bytes.
637  $chardiff = ' <span class="mw-changeslist-separator"></span> ';
638  $chardiff .= Linker::formatRevisionSize( $row->rev_len );
639  $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
640  } else {
641  $parentLen = 0;
642  if ( isset( $this->mParentLens[$row->rev_parent_id] ) ) {
643  $parentLen = $this->mParentLens[$row->rev_parent_id];
644  }
645 
646  $chardiff = ' <span class="mw-changeslist-separator"></span> ';
648  $parentLen,
649  $row->rev_len,
650  $this->getContext()
651  );
652  $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
653  }
654 
655  $lang = $this->getLanguage();
656  $comment = $lang->getDirMark() . Linker::revComment( $rev, false, true, false );
657  $d = ChangesList::revDateLink( $rev, $user, $lang, $page );
658 
659  # When querying for an IP range, we want to always show user and user talk links.
660  $userlink = '';
661  if ( $this->isQueryableRange( $this->target ) ) {
662  $userlink = ' <span class="mw-changeslist-separator"></span> '
663  . $lang->getDirMark()
664  . Linker::userLink( $rev->getUser(), $rev->getUserText() );
665  $userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
666  Linker::userTalkLink( $rev->getUser(), $rev->getUserText() ) )->escaped() . ' ';
667  }
668 
669  $flags = [];
670  if ( $rev->getParentId() === 0 ) {
671  $flags[] = ChangesList::flag( 'newpage' );
672  }
673 
674  if ( $rev->isMinor() ) {
675  $flags[] = ChangesList::flag( 'minor' );
676  }
677 
678  $del = Linker::getRevDeleteLink( $user, $rev, $page );
679  if ( $del !== '' ) {
680  $del .= ' ';
681  }
682 
683  // While it might be tempting to use a list here
684  // this would result in clutter and slows down navigating the content
685  // in assistive technology.
686  // See https://phabricator.wikimedia.org/T205581#4734812
687  $diffHistLinks = Html::rawElement( 'span',
688  [ 'class' => 'mw-changeslist-links' ],
689  // The spans are needed to ensure the dividing '|' elements are not
690  // themselves styled as links.
691  Html::rawElement( 'span', [], $difftext ) .
692  ' ' . // Space needed for separating two words.
693  Html::rawElement( 'span', [], $histlink )
694  );
695 
696  # Tags, if any.
697  list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow(
698  $row->ts_tags,
699  'contributions',
700  $this->getContext()
701  );
702  $classes = array_merge( $classes, $newClasses );
703 
704  Hooks::run( 'SpecialContributions::formatRow::flags', [ $this->getContext(), $row, &$flags ] );
705 
706  $templateParams = [
707  'del' => $del,
708  'timestamp' => $d,
709  'diffHistLinks' => $diffHistLinks,
710  'charDifference' => $chardiff,
711  'flags' => $flags,
712  'articleLink' => $link,
713  'userlink' => $userlink,
714  'logText' => $comment,
715  'topmarktext' => $topmarktext,
716  'tagSummary' => $tagSummary,
717  ];
718 
719  # Denote if username is redacted for this edit
720  if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
721  $templateParams['rev-deleted-user-contribs'] =
722  $this->msg( 'rev-deleted-user-contribs' )->escaped();
723  }
724 
725  $ret = $this->templateParser->processTemplate(
726  'SpecialContributionsLine',
727  $templateParams
728  );
729  }
730 
731  // Let extensions add data
732  Hooks::run( 'ContributionsLineEnding', [ $this, &$ret, $row, &$classes, &$attribs ] );
733  $attribs = array_filter( $attribs,
734  [ Sanitizer::class, 'isReservedDataAttribute' ],
735  ARRAY_FILTER_USE_KEY
736  );
737 
738  // TODO: Handle exceptions in the catch block above. Do any extensions rely on
739  // receiving empty rows?
740 
741  if ( $classes === [] && $attribs === [] && $ret === '' ) {
742  wfDebug( "Dropping Special:Contribution row that could not be formatted\n" );
743  return "<!-- Could not format Special:Contribution row. -->\n";
744  }
745  $attribs['class'] = $classes;
746 
747  // FIXME: The signature of the ContributionsLineEnding hook makes it
748  // very awkward to move this LI wrapper into the template.
749  return Html::rawElement( 'li', $attribs, $ret ) . "\n";
750  }
751 
756  function getSqlComment() {
757  if ( $this->namespace || $this->deletedOnly ) {
758  // potentially slow, see CR r58153
759  return 'contributions page filtered for namespace or RevisionDeleted edits';
760  } else {
761  return 'contributions page unfiltered';
762  }
763  }
764 
765  protected function preventClickjacking() {
766  $this->preventClickjacking = true;
767  }
768 
772  public function getPreventClickjacking() {
774  }
775 
782  public static function processDateFilter( array $opts ) {
783  $start = $opts['start'] ?? '';
784  $end = $opts['end'] ?? '';
785  $year = $opts['year'] ?? '';
786  $month = $opts['month'] ?? '';
787 
788  if ( $start !== '' && $end !== '' && $start > $end ) {
789  $temp = $start;
790  $start = $end;
791  $end = $temp;
792  }
793 
794  // If year/month legacy filtering options are set, convert them to display the new stamp
795  if ( $year !== '' || $month !== '' ) {
796  // Reuse getDateCond logic, but subtract a day because
797  // the endpoints of our date range appear inclusive
798  // but the internal end offsets are always exclusive
799  $legacyTimestamp = ReverseChronologicalPager::getOffsetDate( $year, $month );
800  $legacyDateTime = new DateTime( $legacyTimestamp->getTimestamp( TS_ISO_8601 ) );
801  $legacyDateTime = $legacyDateTime->modify( '-1 day' );
802 
803  // Clear the new timestamp range options if used and
804  // replace with the converted legacy timestamp
805  $start = '';
806  $end = $legacyDateTime->format( 'Y-m-d' );
807  }
808 
809  $opts['start'] = $start;
810  $opts['end'] = $end;
811 
812  return $opts;
813  }
814 }
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
Definition: ChangeTags.php:772
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
isQueryableRange( $ipRange)
Is the given IP a range and within the CIDR limit?
bool $deletedOnly
Set to true to show only deleted revisions.
static processDateFilter(array $opts)
Set up date filter options, given request data.
static parseRange( $range)
Given a string range in a number of formats, return the start and end of the range in hexadecimal...
Definition: IP.php:500
static formatRevisionSize( $size)
Definition: Linker.php:1597
bool $hideMinor
Set to true to hide edits marked as minor by the user.
if(!isset( $args[0])) $lang
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
bool $associated
Set to true to show both the subject and talk namespace, no matter which got selected.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
static getParentLengths( $db, array $revIds)
Do a batched query to get the parent revision lengths.
Definition: Revision.php:342
LinkRenderer $linkRenderer
Definition: IndexPager.php:162
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:518
static generateRollback( $rev, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
Definition: Linker.php:1806
bool $nsInvert
Set to true to invert the namespace selection.
IContextSource $context
tryToCreateValidRevision( $row, $title=null)
Check whether the revision associated is valid for formatting.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:34
__construct(IContextSource $context, array $options, LinkRenderer $linkRenderer=null)
Class that generates HTML links for pages.
static newMigration()
Static constructor.
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
reallyDoQuery( $offset, $limit, $order)
This method basically executes the exact same code as the parent class, though with a hook added...
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getDateRangeCond( $startStamp, $endStamp)
Set and return a date range condition using timestamps provided by the user.
string $target
User name, or a string describing an IP address range.
getContext()
Get the base IContextSource object.
static revDateLink(Revision $rev, User $user, Language $lang, $title=null)
Render the date and time of a revision in the current user language based on whether the user is able...
getTargetTable()
Return the table targeted for ordering and continuation.
makeKnownLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
string false $tagFilter
Name of tag to filter, or false to ignore tags.
static getQueryInfo( $options=[])
Return the tables, fields, and join conditions to be selected to create a new revision object...
Definition: Revision.php:315
formatRow( $row)
Generates each row in the contributions list.
TemplateParser $templateParser
static showCharacterDifference( $old, $new, IContextSource $context=null)
Show formatted char difference.
getSqlComment()
Overwrite Pager function and return a helpful comment.
string int $namespace
A single namespace number, or an empty string for all namespaces.
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition: Linker.php:898
buildQueryInfo( $offset, $limit, $order)
Build variables to use by the database wrapper.
static isIPv6( $ip)
Given a string, determine if it as valid IP in IPv6 only.
Definition: IP.php:88
static getRevDeleteLink(User $user, Revision $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2105
static userTalkLink( $userId, $userText)
Definition: Linker.php:1036
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Overloads the relevant methods of the real ResultsWrapper so it doesn&#39;t go anywhere near an actual da...
getNavigationBar()
Wrap the navigation bar in a p element with identifying class.
static getOffsetDate( $year, $month, $day=-1)
Core logic of determining the mOffset timestamp such that we can get all items with a timestamp up to...
string [] $messages
Local cache for escaped messages.
$revQuery
static isIPv4( $ip)
Given a string, determine if it as valid IP in IPv4 only.
Definition: IP.php:99
makeLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:94
static revComment(Revision $rev, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision&#39;s comment block, if the current user is allowed to view it...
Definition: Linker.php:1572
const DB_REPLICA
Definition: defines.php:25
Pager for filtering by a range of dates.
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:515
static parseCIDR( $range)
Convert a network specification in CIDR notation to an integer network and a number of bits...
Definition: IP.php:457
const NS_USER_TALK
Definition: Defines.php:63
bool $topOnly
Set to true to show only latest (a.k.a.
getIpRangeConds( $db, $ip)
Get SQL conditions for an IP range, if applicable.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
IDatabase $mDbSecondary
bool $newOnly
Set to true to show only new pages.