MediaWiki  master
ContribsPager.php
Go to the documentation of this file.
1 <?php
29 use Wikimedia\IPUtils;
34 
40 
44  private $messages;
45 
49  private $target;
50 
54  private $namespace;
55 
59  private $tagFilter;
60 
64  private $nsInvert;
65 
70  private $associated;
71 
75  private $deletedOnly;
76 
80  private $topOnly;
81 
85  private $newOnly;
86 
90  private $hideMinor;
91 
96  private $revisionsOnly;
97 
98  private $preventClickjacking = false;
99 
103  private $mParentLens;
104 
109 
112 
114  private $hookRunner;
115 
118 
120  private $revisionStore;
121 
123  private $namespaceInfo;
124 
136  public function __construct(
138  array $options,
141  HookContainer $hookContainer = null,
142  ILoadBalancer $loadBalancer = null,
146  ) {
147  // Class is used directly in extensions - T266484
148  $services = MediaWikiServices::getInstance();
149  $loadBalancer = $loadBalancer ?? $services->getDBLoadBalancer();
150  // Set ->target before calling parent::__construct() so
151  // parent can call $this->getIndexField() and get the right result. Set
152  // the rest too just to keep things simple.
153  $this->target = $options['target'] ?? '';
154  $this->namespace = $options['namespace'] ?? '';
155  $this->tagFilter = $options['tagfilter'] ?? false;
156  $this->nsInvert = $options['nsInvert'] ?? false;
157  $this->associated = $options['associated'] ?? false;
158 
159  $this->deletedOnly = !empty( $options['deletedOnly'] );
160  $this->topOnly = !empty( $options['topOnly'] );
161  $this->newOnly = !empty( $options['newOnly'] );
162  $this->hideMinor = !empty( $options['hideMinor'] );
163  $this->revisionsOnly = !empty( $options['revisionsOnly'] );
164 
165  // Most of this code will use the 'contributions' group DB, which can map to replica DBs
166  // with extra user based indexes or partioning by user.
167  // Set database before parent constructor to avoid setting it there with wfGetDB
168  $this->mDb = $loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA, 'contributions' );
169  // Needed by call to getIndexField -> getTargetTable from parent constructor
170  $this->actorMigration = $actorMigration ?? $services->getActorMigration();
171  parent::__construct( $context, $linkRenderer ?? $services->getLinkRenderer() );
172 
173  $msgs = [
174  'diff',
175  'hist',
176  'pipe-separator',
177  'uctop'
178  ];
179 
180  foreach ( $msgs as $msg ) {
181  $this->messages[$msg] = $this->msg( $msg )->escaped();
182  }
183 
184  // Date filtering: use timestamp if available
185  $startTimestamp = '';
186  $endTimestamp = '';
187  if ( isset( $options['start'] ) && $options['start'] ) {
188  $startTimestamp = $options['start'] . ' 00:00:00';
189  }
190  if ( isset( $options['end'] ) && $options['end'] ) {
191  $endTimestamp = $options['end'] . ' 23:59:59';
192  }
193  $this->getDateRangeCond( $startTimestamp, $endTimestamp );
194 
195  $this->templateParser = new TemplateParser();
196  $this->linkBatchFactory = $linkBatchFactory ?? $services->getLinkBatchFactory();
197  $this->hookRunner = new HookRunner( $hookContainer ?? $services->getHookContainer() );
198  $this->revisionStore = $revisionStore ?? $services->getRevisionStore();
199  $this->namespaceInfo = $namespaceInfo ?? $services->getNamespaceInfo();
200  }
201 
202  public function getDefaultQuery() {
203  $query = parent::getDefaultQuery();
204  $query['target'] = $this->target;
205 
206  return $query;
207  }
208 
216  public function getNavigationBar() {
217  return Html::rawElement( 'p', [ 'class' => 'mw-pager-navigation-bar' ],
218  parent::getNavigationBar()
219  );
220  }
221 
231  public function reallyDoQuery( $offset, $limit, $order ) {
232  list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo(
233  $offset,
234  $limit,
235  $order
236  );
237 
238  /*
239  * This hook will allow extensions to add in additional queries, so they can get their data
240  * in My Contributions as well. Extensions should append their results to the $data array.
241  *
242  * Extension queries have to implement the navbar requirement as well. They should
243  * - have a column aliased as $pager->getIndexField()
244  * - have LIMIT set
245  * - have a WHERE-clause that compares the $pager->getIndexField()-equivalent column to the offset
246  * - have the ORDER BY specified based upon the details provided by the navbar
247  *
248  * See includes/Pager.php buildQueryInfo() method on how to build LIMIT, WHERE & ORDER BY
249  *
250  * &$data: an array of results of all contribs queries
251  * $pager: the ContribsPager object hooked into
252  * $offset: see phpdoc above
253  * $limit: see phpdoc above
254  * $descending: see phpdoc above
255  */
256  $dbr = $this->getDatabase();
257  $data = [ $dbr->select(
258  $tables, $fields, $conds, $fname, $options, $join_conds
259  ) ];
260  if ( !$this->revisionsOnly ) {
261  $this->hookRunner->onContribsPager__reallyDoQuery(
262  $data, $this, $offset, $limit, $order );
263  }
264 
265  $result = [];
266 
267  // loop all results and collect them in an array
268  foreach ( $data as $query ) {
269  foreach ( $query as $i => $row ) {
270  // If the query results are in descending order, the indexes must also be in descending order
271  $index = $order === self::QUERY_ASCENDING ? $i : $limit - 1 - $i;
272  // Left-pad with zeroes, because these values will be sorted as strings
273  $index = str_pad( $index, strlen( $limit ), '0', STR_PAD_LEFT );
274  // use index column as key, allowing us to easily sort in PHP
275  $result[$row->{$this->getIndexField()} . "-$index"] = $row;
276  }
277  }
278 
279  // sort results
280  if ( $order === self::QUERY_ASCENDING ) {
281  ksort( $result );
282  } else {
283  krsort( $result );
284  }
285 
286  // enforce limit
287  $result = array_slice( $result, 0, $limit );
288 
289  // get rid of array keys
290  $result = array_values( $result );
291 
292  return new FakeResultWrapper( $result );
293  }
294 
304  private function getTargetTable() {
305  $dbr = $this->getDatabase();
306  $user = User::newFromName( $this->target, false );
307  $ipRangeConds = $user->isAnon() ? $this->getIpRangeConds( $dbr, $this->target ) : null;
308  if ( $ipRangeConds ) {
309  return 'ip_changes';
310  } else {
311  $conds = $this->actorMigration->getWhere( $dbr, 'rev_user', $user );
312  if ( isset( $conds['orconds']['actor'] ) ) {
313  return 'revision_actor_temp';
314  }
315  }
316 
317  return 'revision';
318  }
319 
320  public function getQueryInfo() {
321  $revQuery = $this->revisionStore->getQueryInfo( [ 'page', 'user' ] );
322  $queryInfo = [
323  'tables' => $revQuery['tables'],
324  'fields' => array_merge( $revQuery['fields'], [ 'page_is_new' ] ),
325  'conds' => [],
326  'options' => [],
327  'join_conds' => $revQuery['joins'],
328  ];
329 
330  // WARNING: Keep this in sync with getTargetTable()!
331  $user = User::newFromName( $this->target, false );
332  $dbr = $this->getDatabase();
333  $ipRangeConds = $user->isAnon() ? $this->getIpRangeConds( $dbr, $this->target ) : null;
334  if ( $ipRangeConds ) {
335  // Put ip_changes first (T284419)
336  array_unshift( $queryInfo['tables'], 'ip_changes' );
337  $queryInfo['join_conds']['revision'] = [
338  'JOIN', [ 'rev_id = ipc_rev_id' ]
339  ];
340  $queryInfo['conds'][] = $ipRangeConds;
341  } else {
342  // tables and joins are already handled by RevisionStore::getQueryInfo()
343  $conds = $this->actorMigration->getWhere( $dbr, 'rev_user', $user );
344  $queryInfo['conds'][] = $conds['conds'];
345  // Force the appropriate index to avoid bad query plans (T189026)
346  if ( isset( $conds['orconds']['actor'] ) ) {
347  $queryInfo['options']['USE INDEX']['temp_rev_user'] = 'actor_timestamp';
348  }
349  }
350 
351  if ( $this->deletedOnly ) {
352  $queryInfo['conds'][] = 'rev_deleted != 0';
353  }
354 
355  if ( $this->topOnly ) {
356  $queryInfo['conds'][] = 'rev_id = page_latest';
357  }
358 
359  if ( $this->newOnly ) {
360  $queryInfo['conds'][] = 'rev_parent_id = 0';
361  }
362 
363  if ( $this->hideMinor ) {
364  $queryInfo['conds'][] = 'rev_minor_edit = 0';
365  }
366 
367  $queryInfo['conds'] = array_merge( $queryInfo['conds'], $this->getNamespaceCond() );
368 
369  // Paranoia: avoid brute force searches (T19342)
370  if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
371  $queryInfo['conds'][] = $dbr->bitAnd(
372  'rev_deleted', RevisionRecord::DELETED_USER
373  ) . ' = 0';
374  } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
375  $queryInfo['conds'][] = $dbr->bitAnd(
376  'rev_deleted', RevisionRecord::SUPPRESSED_USER
377  ) . ' != ' . RevisionRecord::SUPPRESSED_USER;
378  }
379 
380  // $this->getIndexField() must be in the result rows, as reallyDoQuery() tries to access it.
381  $indexField = $this->getIndexField();
382  if ( $indexField !== 'rev_timestamp' ) {
383  $queryInfo['fields'][] = $indexField;
384  }
385 
387  $queryInfo['tables'],
388  $queryInfo['fields'],
389  $queryInfo['conds'],
390  $queryInfo['join_conds'],
391  $queryInfo['options'],
392  $this->tagFilter
393  );
394 
395  $this->hookRunner->onContribsPager__getQueryInfo( $this, $queryInfo );
396 
397  return $queryInfo;
398  }
399 
400  protected function getNamespaceCond() {
401  if ( $this->namespace !== '' ) {
402  $dbr = $this->getDatabase();
403  $selectedNS = $dbr->addQuotes( $this->namespace );
404  $eq_op = $this->nsInvert ? '!=' : '=';
405  $bool_op = $this->nsInvert ? 'AND' : 'OR';
406 
407  if ( !$this->associated ) {
408  return [ "page_namespace $eq_op $selectedNS" ];
409  }
410 
411  $associatedNS = $dbr->addQuotes( $this->namespaceInfo->getAssociated( $this->namespace ) );
412 
413  return [
414  "page_namespace $eq_op $selectedNS " .
415  $bool_op .
416  " page_namespace $eq_op $associatedNS"
417  ];
418  }
419 
420  return [];
421  }
422 
429  private function getIpRangeConds( $db, $ip ) {
430  // First make sure it is a valid range and they are not outside the CIDR limit
431  if ( !$this->isQueryableRange( $ip ) ) {
432  return false;
433  }
434 
435  list( $start, $end ) = IPUtils::parseRange( $ip );
436 
437  return 'ipc_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end );
438  }
439 
447  public function isQueryableRange( $ipRange ) {
448  $limits = $this->getConfig()->get( 'RangeContributionsCIDRLimit' );
449 
450  $bits = IPUtils::parseCIDR( $ipRange )[1];
451  if (
452  ( $bits === false ) ||
453  ( IPUtils::isIPv4( $ipRange ) && $bits < $limits['IPv4'] ) ||
454  ( IPUtils::isIPv6( $ipRange ) && $bits < $limits['IPv6'] )
455  ) {
456  return false;
457  }
458 
459  return true;
460  }
461 
465  public function getIndexField() {
466  // The returned column is used for sorting and continuation, so we need to
467  // make sure to use the right denormalized column depending on which table is
468  // being targeted by the query to avoid bad query plans.
469  // See T200259, T204669, T220991, and T221380.
470  $target = $this->getTargetTable();
471  switch ( $target ) {
472  case 'revision':
473  return 'rev_timestamp';
474  case 'ip_changes':
475  return 'ipc_rev_timestamp';
476  case 'revision_actor_temp':
477  return 'revactor_timestamp';
478  default:
479  wfWarn(
480  __METHOD__ . ": Unknown value '$target' from " . static::class . '::getTargetTable()', 0
481  );
482  return 'rev_timestamp';
483  }
484  }
485 
489  public function getTagFilter() {
490  return $this->tagFilter;
491  }
492 
496  public function getTarget() {
497  return $this->target;
498  }
499 
503  public function isNewOnly() {
504  return $this->newOnly;
505  }
506 
510  public function getNamespace() {
511  return $this->namespace;
512  }
513 
517  protected function getExtraSortFields() {
518  // The returned columns are used for sorting, so we need to make sure
519  // to use the right denormalized column depending on which table is
520  // being targeted by the query to avoid bad query plans.
521  // See T200259, T204669, T220991, and T221380.
522  $target = $this->getTargetTable();
523  switch ( $target ) {
524  case 'revision':
525  return [ 'rev_id' ];
526  case 'ip_changes':
527  return [ 'ipc_rev_id' ];
528  case 'revision_actor_temp':
529  return [ 'revactor_rev' ];
530  default:
531  wfWarn(
532  __METHOD__ . ": Unknown value '$target' from " . static::class . '::getTargetTable()', 0
533  );
534  return [ 'rev_id' ];
535  }
536  }
537 
538  protected function doBatchLookups() {
539  # Do a link batch query
540  $this->mResult->seek( 0 );
541  $parentRevIds = [];
542  $this->mParentLens = [];
543  $batch = $this->linkBatchFactory->newLinkBatch();
544  $isIpRange = $this->isQueryableRange( $this->target );
545  # Give some pointers to make (last) links
546  foreach ( $this->mResult as $row ) {
547  if ( isset( $row->rev_parent_id ) && $row->rev_parent_id ) {
548  $parentRevIds[] = $row->rev_parent_id;
549  }
550  if ( isset( $row->rev_id ) ) {
551  $this->mParentLens[$row->rev_id] = $row->rev_len;
552  if ( $isIpRange ) {
553  // If this is an IP range, batch the IP's talk page
554  $batch->add( NS_USER_TALK, $row->rev_user_text );
555  }
556  $batch->add( $row->page_namespace, $row->page_title );
557  }
558  }
559  # Fetch rev_len for revisions not already scanned above
560  $this->mParentLens += $this->revisionStore->getRevisionSizes(
561  array_diff( $parentRevIds, array_keys( $this->mParentLens ) )
562  );
563  $batch->execute();
564  $this->mResult->seek( 0 );
565  }
566 
570  protected function getStartBody() {
571  return "<ul class=\"mw-contributions-list\">\n";
572  }
573 
577  protected function getEndBody() {
578  return "</ul>\n";
579  }
580 
591  public function tryCreatingRevisionRecord( $row, $title = null ) {
592  /*
593  * There may be more than just revision rows. To make sure that we'll only be processing
594  * revisions here, let's _try_ to build a revision out of our row (without displaying
595  * notices though) and then trying to grab data from the built object. If we succeed,
596  * we're definitely dealing with revision data and we may proceed, if not, we'll leave it
597  * to extensions to subscribe to the hook to parse the row.
598  */
599  Wikimedia\suppressWarnings();
600  try {
601  $revRecord = $this->revisionStore->newRevisionFromRow( $row, 0, $title );
602  return $revRecord->getId() ? $revRecord : null;
603  } catch ( Exception $e ) {
604  return null;
605  } finally {
606  Wikimedia\restoreWarnings();
607  }
608  }
609 
622  public function formatRow( $row ) {
623  $ret = '';
624  $classes = [];
625  $attribs = [];
626 
627  $linkRenderer = $this->getLinkRenderer();
628 
629  $page = null;
630  // Create a title for the revision if possible
631  // Rows from the hook may not include title information
632  if ( isset( $row->page_namespace ) && isset( $row->page_title ) ) {
633  $page = Title::newFromRow( $row );
634  }
635  $revRecord = $this->tryCreatingRevisionRecord( $row, $page );
636  if ( $revRecord ) {
637  $attribs['data-mw-revid'] = $revRecord->getId();
638 
639  $link = $linkRenderer->makeLink(
640  $page,
641  $page->getPrefixedText(),
642  [ 'class' => 'mw-contributions-title' ],
643  $page->isRedirect() ? [ 'redirect' => 'no' ] : []
644  );
645  # Mark current revisions
646  $topmarktext = '';
647  $user = $this->getUser();
648 
649  if ( $row->rev_id === $row->page_latest ) {
650  $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
651  $classes[] = 'mw-contributions-current';
652  # Add rollback link
653  if ( !$row->page_is_new &&
654  $this->getAuthority()->probablyCan( 'rollback', $page ) &&
655  $this->getAuthority()->probablyCan( 'edit', $page )
656  ) {
657  $this->preventClickjacking();
658  $topmarktext .= ' ' . Linker::generateRollback(
659  $revRecord,
660  $this->getContext(),
661  [ 'noBrackets' ]
662  );
663  }
664  }
665  # Is there a visible previous revision?
666  if ( $revRecord->getParentId() !== 0 &&
667  $revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() )
668  ) {
669  $difftext = $linkRenderer->makeKnownLink(
670  $page,
671  new HtmlArmor( $this->messages['diff'] ),
672  [ 'class' => 'mw-changeslist-diff' ],
673  [
674  'diff' => 'prev',
675  'oldid' => $row->rev_id
676  ]
677  );
678  } else {
679  $difftext = $this->messages['diff'];
680  }
681  $histlink = $linkRenderer->makeKnownLink(
682  $page,
683  new HtmlArmor( $this->messages['hist'] ),
684  [ 'class' => 'mw-changeslist-history' ],
685  [ 'action' => 'history' ]
686  );
687 
688  if ( $row->rev_parent_id === null ) {
689  // For some reason rev_parent_id isn't populated for this row.
690  // Its rumoured this is true on wikipedia for some revisions (T36922).
691  // Next best thing is to have the total number of bytes.
692  $chardiff = ' <span class="mw-changeslist-separator"></span> ';
693  $chardiff .= Linker::formatRevisionSize( $row->rev_len );
694  $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
695  } else {
696  $parentLen = 0;
697  if ( isset( $this->mParentLens[$row->rev_parent_id] ) ) {
698  $parentLen = $this->mParentLens[$row->rev_parent_id];
699  }
700 
701  $chardiff = ' <span class="mw-changeslist-separator"></span> ';
703  $parentLen,
704  $row->rev_len,
705  $this->getContext()
706  );
707  $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
708  }
709 
710  $lang = $this->getLanguage();
711  $comment = $lang->getDirMark() . Linker::revComment( $revRecord, false, true, false );
712  $d = ChangesList::revDateLink( $revRecord, $user, $lang, $page );
713 
714  # When querying for an IP range, we want to always show user and user talk links.
715  $userlink = '';
716  $revUser = $revRecord->getUser();
717  $revUserId = $revUser ? $revUser->getId() : 0;
718  $revUserText = $revUser ? $revUser->getName() : '';
719  if ( $this->isQueryableRange( $this->target ) ) {
720  $userlink = ' <span class="mw-changeslist-separator"></span> '
721  . $lang->getDirMark()
722  . Linker::userLink( $revUserId, $revUserText );
723  $userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
724  Linker::userTalkLink( $revUserId, $revUserText ) )->escaped() . ' ';
725  }
726 
727  $flags = [];
728  if ( $revRecord->getParentId() === 0 ) {
729  $flags[] = ChangesList::flag( 'newpage' );
730  }
731 
732  if ( $revRecord->isMinor() ) {
733  $flags[] = ChangesList::flag( 'minor' );
734  }
735 
736  $del = Linker::getRevDeleteLink( $user, $revRecord, $page );
737  if ( $del !== '' ) {
738  $del .= ' ';
739  }
740 
741  // While it might be tempting to use a list here
742  // this would result in clutter and slows down navigating the content
743  // in assistive technology.
744  // See https://phabricator.wikimedia.org/T205581#4734812
745  $diffHistLinks = Html::rawElement( 'span',
746  [ 'class' => 'mw-changeslist-links' ],
747  // The spans are needed to ensure the dividing '|' elements are not
748  // themselves styled as links.
749  Html::rawElement( 'span', [], $difftext ) .
750  ' ' . // Space needed for separating two words.
751  Html::rawElement( 'span', [], $histlink )
752  );
753 
754  # Tags, if any.
755  list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow(
756  $row->ts_tags,
757  'contributions',
758  $this->getContext()
759  );
760  $classes = array_merge( $classes, $newClasses );
761 
762  $this->hookRunner->onSpecialContributions__formatRow__flags(
763  $this->getContext(), $row, $flags );
764 
765  $templateParams = [
766  'del' => $del,
767  'timestamp' => $d,
768  'diffHistLinks' => $diffHistLinks,
769  'charDifference' => $chardiff,
770  'flags' => $flags,
771  'articleLink' => $link,
772  'userlink' => $userlink,
773  'logText' => $comment,
774  'topmarktext' => $topmarktext,
775  'tagSummary' => $tagSummary,
776  ];
777 
778  # Denote if username is redacted for this edit
779  if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
780  $templateParams['rev-deleted-user-contribs'] =
781  $this->msg( 'rev-deleted-user-contribs' )->escaped();
782  }
783 
784  $ret = $this->templateParser->processTemplate(
785  'SpecialContributionsLine',
786  $templateParams
787  );
788  }
789 
790  // Let extensions add data
791  $this->hookRunner->onContributionsLineEnding( $this, $ret, $row, $classes, $attribs );
792  $attribs = array_filter( $attribs,
793  [ Sanitizer::class, 'isReservedDataAttribute' ],
794  ARRAY_FILTER_USE_KEY
795  );
796 
797  // TODO: Handle exceptions in the catch block above. Do any extensions rely on
798  // receiving empty rows?
799 
800  if ( $classes === [] && $attribs === [] && $ret === '' ) {
801  wfDebug( "Dropping Special:Contribution row that could not be formatted" );
802  return "<!-- Could not format Special:Contribution row. -->\n";
803  }
804  $attribs['class'] = $classes;
805 
806  // FIXME: The signature of the ContributionsLineEnding hook makes it
807  // very awkward to move this LI wrapper into the template.
808  return Html::rawElement( 'li', $attribs, $ret ) . "\n";
809  }
810 
815  protected function getSqlComment() {
816  if ( $this->namespace || $this->deletedOnly ) {
817  // potentially slow, see CR r58153
818  return 'contributions page filtered for namespace or RevisionDeleted edits';
819  } else {
820  return 'contributions page unfiltered';
821  }
822  }
823 
824  protected function preventClickjacking() {
825  $this->preventClickjacking = true;
826  }
827 
831  public function getPreventClickjacking() {
832  return $this->preventClickjacking;
833  }
834 
841  public static function processDateFilter( array $opts ) {
842  $start = $opts['start'] ?? '';
843  $end = $opts['end'] ?? '';
844  $year = $opts['year'] ?? '';
845  $month = $opts['month'] ?? '';
846 
847  if ( $start !== '' && $end !== '' && $start > $end ) {
848  $temp = $start;
849  $start = $end;
850  $end = $temp;
851  }
852 
853  // If year/month legacy filtering options are set, convert them to display the new stamp
854  if ( $year !== '' || $month !== '' ) {
855  // Reuse getDateCond logic, but subtract a day because
856  // the endpoints of our date range appear inclusive
857  // but the internal end offsets are always exclusive
858  $legacyTimestamp = ReverseChronologicalPager::getOffsetDate( $year, $month );
859  $legacyDateTime = new DateTime( $legacyTimestamp->getTimestamp( TS_ISO_8601 ) );
860  $legacyDateTime = $legacyDateTime->modify( '-1 day' );
861 
862  // Clear the new timestamp range options if used and
863  // replace with the converted legacy timestamp
864  $start = '';
865  $end = $legacyDateTime->format( 'Y-m-d' );
866  }
867 
868  $opts['start'] = $start;
869  $opts['end'] = $end;
870 
871  return $opts;
872  }
873 }
ReverseChronologicalPager\getOffsetDate
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...
Definition: ReverseChronologicalPager.php:129
ContextSource\$context
IContextSource $context
Definition: ContextSource.php:39
ContribsPager\getNamespace
getNamespace()
Definition: ContribsPager.php:510
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
Linker\userTalkLink
static userTalkLink( $userId, $userText)
Definition: Linker.php:1206
Linker\userLink
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition: Linker.php:1068
ContribsPager\$topOnly
bool $topOnly
Set to true to show only latest (a.k.a.
Definition: ContribsPager.php:80
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:186
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
MediaWiki\Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:88
MediaWiki\Linker\LinkRenderer
Class that generates HTML links for pages.
Definition: LinkRenderer.php:43
ContribsPager\getDefaultQuery
getDefaultQuery()
Get an array of query parameters that should be put into self-links.
Definition: ContribsPager.php:202
IndexPager\getDatabase
getDatabase()
Get the Database object in use.
Definition: IndexPager.php:244
getAuthority
getAuthority()
ContribsPager\getSqlComment
getSqlComment()
Overwrite Pager function and return a helpful comment.
Definition: ContribsPager.php:815
ContribsPager\formatRow
formatRow( $row)
Generates each row in the contributions list.
Definition: ContribsPager.php:622
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:602
ContribsPager\$hookRunner
HookRunner $hookRunner
Definition: ContribsPager.php:114
IndexPager\$linkRenderer
LinkRenderer $linkRenderer
Definition: IndexPager.php:167
ContribsPager\reallyDoQuery
reallyDoQuery( $offset, $limit, $order)
This method basically executes the exact same code as the parent class, though with a hook added,...
Definition: ContribsPager.php:231
ContribsPager\tryCreatingRevisionRecord
tryCreatingRevisionRecord( $row, $title=null)
Check whether the revision associated is valid for formatting.
Definition: ContribsPager.php:591
ActorMigration
This is not intended to be a long-term part of MediaWiki; it will be deprecated and removed once acto...
Definition: ActorMigration.php:15
Wikimedia\Rdbms\FakeResultWrapper
Overloads the relevant methods of the real ResultWrapper so it doesn't go anywhere near an actual dat...
Definition: FakeResultWrapper.php:12
ContribsPager\$target
string $target
User name, or a string describing an IP address range.
Definition: ContribsPager.php:49
getContext
getContext()
ContribsPager\doBatchLookups
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
Definition: ContribsPager.php:538
$revQuery
$revQuery
Definition: testCompression.php:56
ContribsPager\processDateFilter
static processDateFilter(array $opts)
Set up date filter options, given request data.
Definition: ContribsPager.php:841
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
$dbr
$dbr
Definition: testCompression.php:54
ContribsPager\$namespaceInfo
NamespaceInfo $namespaceInfo
Definition: ContribsPager.php:123
ContribsPager\getPreventClickjacking
getPreventClickjacking()
Definition: ContribsPager.php:831
ChangeTags\modifyDisplayQuery
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
Definition: ChangeTags.php:891
ContribsPager\$linkBatchFactory
LinkBatchFactory $linkBatchFactory
Definition: ContribsPager.php:111
ContribsPager\getNamespaceCond
getNamespaceCond()
Definition: ContribsPager.php:400
ContribsPager\__construct
__construct(IContextSource $context, array $options, LinkRenderer $linkRenderer=null, LinkBatchFactory $linkBatchFactory=null, HookContainer $hookContainer=null, ILoadBalancer $loadBalancer=null, ActorMigration $actorMigration=null, RevisionStore $revisionStore=null, NamespaceInfo $namespaceInfo=null)
Definition: ContribsPager.php:136
ContribsPager\getTarget
getTarget()
Definition: ContribsPager.php:496
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:25
ContribsPager\getStartBody
getStartBody()
Definition: ContribsPager.php:570
Title\newFromRow
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:612
RangeChronologicalPager\getDateRangeCond
getDateRangeCond( $startStamp, $endStamp)
Set and return a date range condition using timestamps provided by the user.
Definition: RangeChronologicalPager.php:46
MediaWiki\Cache\LinkBatchFactory
Definition: LinkBatchFactory.php:39
ContribsPager\$deletedOnly
bool $deletedOnly
Set to true to show only deleted revisions.
Definition: ContribsPager.php:75
ChangesList\flag
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
Definition: ChangesList.php:272
$title
$title
Definition: testCompression.php:38
ContribsPager\isNewOnly
isNewOnly()
Definition: ContribsPager.php:503
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
ContribsPager\$actorMigration
ActorMigration $actorMigration
Definition: ContribsPager.php:117
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
RangeChronologicalPager
Pager for filtering by a range of dates.
Definition: RangeChronologicalPager.php:28
Linker\generateRollback
static generateRollback(RevisionRecord $revRecord, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
Definition: Linker.php:2033
ContextSource\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:197
ContribsPager
Pager for Special:Contributions.
Definition: ContribsPager.php:39
Linker\revComment
static revComment(RevisionRecord $revRecord, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition: Linker.php:1784
ContribsPager\isQueryableRange
isQueryableRange( $ipRange)
Is the given IP a range and within the CIDR limit?
Definition: ContribsPager.php:447
ContribsPager\getNavigationBar
getNavigationBar()
Wrap the navigation bar in a p element with identifying class.
Definition: ContribsPager.php:216
ContribsPager\$preventClickjacking
$preventClickjacking
Definition: ContribsPager.php:98
ContribsPager\getExtraSortFields
getExtraSortFields()
Definition: ContribsPager.php:517
ContribsPager\$templateParser
TemplateParser $templateParser
Definition: ContribsPager.php:108
Linker\formatRevisionSize
static formatRevisionSize( $size)
Definition: Linker.php:1822
ChangesList\showCharacterDifference
static showCharacterDifference( $old, $new, IContextSource $context=null)
Show formatted char difference.
Definition: ChangesList.php:336
ContribsPager\$tagFilter
string false $tagFilter
Name of tag to filter, or false to ignore tags.
Definition: ContribsPager.php:59
RangeChronologicalPager\buildQueryInfo
buildQueryInfo( $offset, $limit, $order)
Build variables to use by the database wrapper.
Definition: RangeChronologicalPager.php:107
ChangesList\revDateLink
static revDateLink(RevisionRecord $rev, Authority $performer, Language $lang, $title=null)
Render the date and time of a revision in the current user language based on whether the user is able...
Definition: ChangesList.php:428
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:58
ContribsPager\$newOnly
bool $newOnly
Set to true to show only new pages.
Definition: ContribsPager.php:85
ContribsPager\$hideMinor
bool $hideMinor
Set to true to hide edits marked as minor by the user.
Definition: ContribsPager.php:90
ContribsPager\$associated
bool $associated
Set to true to show both the subject and talk namespace, no matter which got selected.
Definition: ContribsPager.php:70
ContribsPager\getTargetTable
getTargetTable()
Return the table targeted for ordering and continuation.
Definition: ContribsPager.php:304
ContribsPager\$revisionsOnly
bool $revisionsOnly
Set to true to only include mediawiki revisions.
Definition: ContribsPager.php:96
ContribsPager\$revisionStore
RevisionStore $revisionStore
Definition: ContribsPager.php:120
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:67
ContribsPager\getIpRangeConds
getIpRangeConds( $db, $ip)
Get SQL conditions for an IP range, if applicable.
Definition: ContribsPager.php:429
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
wfWarn
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Definition: GlobalFunctions.php:1043
NamespaceInfo
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Definition: NamespaceInfo.php:35
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:555
ContribsPager\getEndBody
getEndBody()
Definition: ContribsPager.php:577
ContribsPager\getQueryInfo
getQueryInfo()
Provides all parameters needed for the main paged query.
Definition: ContribsPager.php:320
ContribsPager\$messages
string[] $messages
Local cache for escaped messages.
Definition: ContribsPager.php:44
ContribsPager\$nsInvert
bool $nsInvert
Set to true to invert the namespace selection.
Definition: ContribsPager.php:64
ContribsPager\$namespace
string int $namespace
A single namespace number, or an empty string for all namespaces.
Definition: ContribsPager.php:54
Linker\getRevDeleteLink
static getRevDeleteLink(Authority $performer, RevisionRecord $revRecord, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2362
ContribsPager\preventClickjacking
preventClickjacking()
Definition: ContribsPager.php:824
TemplateParser
Definition: TemplateParser.php:27
ContribsPager\getIndexField
getIndexField()
Definition: ContribsPager.php:465
ChangeTags\formatSummaryRow
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:194
ContribsPager\$mParentLens
array $mParentLens
Definition: ContribsPager.php:103
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
ContribsPager\getTagFilter
getTagFilter()
Definition: ContribsPager.php:489