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  // @todo: This will need changing when revision_actor_temp goes away
314  return 'revision_actor_temp';
315  }
316  }
317 
318  return 'revision';
319  }
320 
321  public function getQueryInfo() {
322  $revQuery = $this->revisionStore->getQueryInfo( [ 'page', 'user' ] );
323  $queryInfo = [
324  'tables' => $revQuery['tables'],
325  'fields' => array_merge( $revQuery['fields'], [ 'page_is_new' ] ),
326  'conds' => [],
327  'options' => [],
328  'join_conds' => $revQuery['joins'],
329  ];
330 
331  // WARNING: Keep this in sync with getTargetTable()!
332  $user = User::newFromName( $this->target, false );
333  $dbr = $this->getDatabase();
334  $ipRangeConds = $user->isAnon() ? $this->getIpRangeConds( $dbr, $this->target ) : null;
335  if ( $ipRangeConds ) {
336  $queryInfo['tables'][] = 'ip_changes';
337  $queryInfo['join_conds']['ip_changes'] = [
338  'LEFT JOIN', [ 'ipc_rev_id = rev_id' ]
339  ];
340  $queryInfo['conds'][] = $ipRangeConds;
341  } else {
342  // tables and joins are already handled by Revision::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  // @todo: This will need changing when revision_actor_temp goes away
348  $queryInfo['options']['USE INDEX']['temp_rev_user'] = 'actor_timestamp';
349  }
350  }
351 
352  if ( $this->deletedOnly ) {
353  $queryInfo['conds'][] = 'rev_deleted != 0';
354  }
355 
356  if ( $this->topOnly ) {
357  $queryInfo['conds'][] = 'rev_id = page_latest';
358  }
359 
360  if ( $this->newOnly ) {
361  $queryInfo['conds'][] = 'rev_parent_id = 0';
362  }
363 
364  if ( $this->hideMinor ) {
365  $queryInfo['conds'][] = 'rev_minor_edit = 0';
366  }
367 
368  $queryInfo['conds'] = array_merge( $queryInfo['conds'], $this->getNamespaceCond() );
369 
370  // Paranoia: avoid brute force searches (T19342)
371  if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
372  $queryInfo['conds'][] = $dbr->bitAnd(
373  'rev_deleted', RevisionRecord::DELETED_USER
374  ) . ' = 0';
375  } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
376  $queryInfo['conds'][] = $dbr->bitAnd(
377  'rev_deleted', RevisionRecord::SUPPRESSED_USER
378  ) . ' != ' . RevisionRecord::SUPPRESSED_USER;
379  }
380 
381  // $this->getIndexField() must be in the result rows, as reallyDoQuery() tries to access it.
382  $indexField = $this->getIndexField();
383  if ( $indexField !== 'rev_timestamp' ) {
384  $queryInfo['fields'][] = $indexField;
385  }
386 
388  $queryInfo['tables'],
389  $queryInfo['fields'],
390  $queryInfo['conds'],
391  $queryInfo['join_conds'],
392  $queryInfo['options'],
393  $this->tagFilter
394  );
395 
396  $this->hookRunner->onContribsPager__getQueryInfo( $this, $queryInfo );
397 
398  return $queryInfo;
399  }
400 
401  protected function getNamespaceCond() {
402  if ( $this->namespace !== '' ) {
403  $dbr = $this->getDatabase();
404  $selectedNS = $dbr->addQuotes( $this->namespace );
405  $eq_op = $this->nsInvert ? '!=' : '=';
406  $bool_op = $this->nsInvert ? 'AND' : 'OR';
407 
408  if ( !$this->associated ) {
409  return [ "page_namespace $eq_op $selectedNS" ];
410  }
411 
412  $associatedNS = $dbr->addQuotes( $this->namespaceInfo->getAssociated( $this->namespace ) );
413 
414  return [
415  "page_namespace $eq_op $selectedNS " .
416  $bool_op .
417  " page_namespace $eq_op $associatedNS"
418  ];
419  }
420 
421  return [];
422  }
423 
430  private function getIpRangeConds( $db, $ip ) {
431  // First make sure it is a valid range and they are not outside the CIDR limit
432  if ( !$this->isQueryableRange( $ip ) ) {
433  return false;
434  }
435 
436  list( $start, $end ) = IPUtils::parseRange( $ip );
437 
438  return 'ipc_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end );
439  }
440 
448  public function isQueryableRange( $ipRange ) {
449  $limits = $this->getConfig()->get( 'RangeContributionsCIDRLimit' );
450 
451  $bits = IPUtils::parseCIDR( $ipRange )[1];
452  if (
453  ( $bits === false ) ||
454  ( IPUtils::isIPv4( $ipRange ) && $bits < $limits['IPv4'] ) ||
455  ( IPUtils::isIPv6( $ipRange ) && $bits < $limits['IPv6'] )
456  ) {
457  return false;
458  }
459 
460  return true;
461  }
462 
466  public function getIndexField() {
467  // The returned column is used for sorting and continuation, so we need to
468  // make sure to use the right denormalized column depending on which table is
469  // being targeted by the query to avoid bad query plans.
470  // See T200259, T204669, T220991, and T221380.
471  $target = $this->getTargetTable();
472  switch ( $target ) {
473  case 'revision':
474  return 'rev_timestamp';
475  case 'ip_changes':
476  return 'ipc_rev_timestamp';
477  case 'revision_actor_temp':
478  return 'revactor_timestamp';
479  default:
480  wfWarn(
481  __METHOD__ . ": Unknown value '$target' from " . static::class . '::getTargetTable()', 0
482  );
483  return 'rev_timestamp';
484  }
485  }
486 
490  public function getTagFilter() {
491  return $this->tagFilter;
492  }
493 
497  public function getTarget() {
498  return $this->target;
499  }
500 
504  public function isNewOnly() {
505  return $this->newOnly;
506  }
507 
511  public function getNamespace() {
512  return $this->namespace;
513  }
514 
518  protected function getExtraSortFields() {
519  // The returned columns are used for sorting, so we need to make sure
520  // to use the right denormalized column depending on which table is
521  // being targeted by the query to avoid bad query plans.
522  // See T200259, T204669, T220991, and T221380.
523  $target = $this->getTargetTable();
524  switch ( $target ) {
525  case 'revision':
526  return [ 'rev_id' ];
527  case 'ip_changes':
528  return [ 'ipc_rev_id' ];
529  case 'revision_actor_temp':
530  return [ 'revactor_rev' ];
531  default:
532  wfWarn(
533  __METHOD__ . ": Unknown value '$target' from " . static::class . '::getTargetTable()', 0
534  );
535  return [ 'rev_id' ];
536  }
537  }
538 
539  protected function doBatchLookups() {
540  # Do a link batch query
541  $this->mResult->seek( 0 );
542  $parentRevIds = [];
543  $this->mParentLens = [];
544  $batch = $this->linkBatchFactory->newLinkBatch();
545  $isIpRange = $this->isQueryableRange( $this->target );
546  # Give some pointers to make (last) links
547  foreach ( $this->mResult as $row ) {
548  if ( isset( $row->rev_parent_id ) && $row->rev_parent_id ) {
549  $parentRevIds[] = $row->rev_parent_id;
550  }
551  if ( isset( $row->rev_id ) ) {
552  $this->mParentLens[$row->rev_id] = $row->rev_len;
553  if ( $isIpRange ) {
554  // If this is an IP range, batch the IP's talk page
555  $batch->add( NS_USER_TALK, $row->rev_user_text );
556  }
557  $batch->add( $row->page_namespace, $row->page_title );
558  }
559  }
560  # Fetch rev_len for revisions not already scanned above
561  $this->mParentLens += $this->revisionStore->getRevisionSizes(
562  array_diff( $parentRevIds, array_keys( $this->mParentLens ) )
563  );
564  $batch->execute();
565  $this->mResult->seek( 0 );
566  }
567 
571  protected function getStartBody() {
572  return "<ul class=\"mw-contributions-list\">\n";
573  }
574 
578  protected function getEndBody() {
579  return "</ul>\n";
580  }
581 
592  public function tryToCreateValidRevision( $row, $title = null ) {
593  wfDeprecated( __METHOD__, '1.35' );
594  $potentialRevRecord = $this->tryCreatingRevisionRecord( $row, $title );
595  return $potentialRevRecord ? new Revision( $potentialRevRecord ) : null;
596  }
597 
608  public function tryCreatingRevisionRecord( $row, $title = null ) {
609  /*
610  * There may be more than just revision rows. To make sure that we'll only be processing
611  * revisions here, let's _try_ to build a revision out of our row (without displaying
612  * notices though) and then trying to grab data from the built object. If we succeed,
613  * we're definitely dealing with revision data and we may proceed, if not, we'll leave it
614  * to extensions to subscribe to the hook to parse the row.
615  */
616  Wikimedia\suppressWarnings();
617  try {
618  $revRecord = $this->revisionStore->newRevisionFromRow( $row, 0, $title );
619  return $revRecord->getId() ? $revRecord : null;
620  } catch ( Exception $e ) {
621  return null;
622  } finally {
623  Wikimedia\restoreWarnings();
624  }
625  }
626 
639  public function formatRow( $row ) {
640  $ret = '';
641  $classes = [];
642  $attribs = [];
643 
644  $linkRenderer = $this->getLinkRenderer();
645 
646  $page = null;
647  // Create a title for the revision if possible
648  // Rows from the hook may not include title information
649  if ( isset( $row->page_namespace ) && isset( $row->page_title ) ) {
650  $page = Title::newFromRow( $row );
651  }
652  $revRecord = $this->tryCreatingRevisionRecord( $row, $page );
653  if ( $revRecord ) {
654  $attribs['data-mw-revid'] = $revRecord->getId();
655 
656  $link = $linkRenderer->makeLink(
657  $page,
658  $page->getPrefixedText(),
659  [ 'class' => 'mw-contributions-title' ],
660  $page->isRedirect() ? [ 'redirect' => 'no' ] : []
661  );
662  # Mark current revisions
663  $topmarktext = '';
664  $user = $this->getUser();
665 
666  if ( $row->rev_id === $row->page_latest ) {
667  $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
668  $classes[] = 'mw-contributions-current';
669  # Add rollback link
670  if ( !$row->page_is_new &&
671  $this->getAuthority()->probablyCan( 'rollback', $page ) &&
672  $this->getAuthority()->probablyCan( 'edit', $page )
673  ) {
674  $this->preventClickjacking();
675  $topmarktext .= ' ' . Linker::generateRollback(
676  $revRecord,
677  $this->getContext(),
678  [ 'noBrackets' ]
679  );
680  }
681  }
682  # Is there a visible previous revision?
683  if ( $revRecord->getParentId() !== 0 &&
684  RevisionRecord::userCanBitfield(
685  $revRecord->getVisibility(),
686  RevisionRecord::DELETED_TEXT,
687  $user
688  )
689  ) {
690  $difftext = $linkRenderer->makeKnownLink(
691  $page,
692  new HtmlArmor( $this->messages['diff'] ),
693  [ 'class' => 'mw-changeslist-diff' ],
694  [
695  'diff' => 'prev',
696  'oldid' => $row->rev_id
697  ]
698  );
699  } else {
700  $difftext = $this->messages['diff'];
701  }
702  $histlink = $linkRenderer->makeKnownLink(
703  $page,
704  new HtmlArmor( $this->messages['hist'] ),
705  [ 'class' => 'mw-changeslist-history' ],
706  [ 'action' => 'history' ]
707  );
708 
709  if ( $row->rev_parent_id === null ) {
710  // For some reason rev_parent_id isn't populated for this row.
711  // Its rumoured this is true on wikipedia for some revisions (T36922).
712  // Next best thing is to have the total number of bytes.
713  $chardiff = ' <span class="mw-changeslist-separator"></span> ';
714  $chardiff .= Linker::formatRevisionSize( $row->rev_len );
715  $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
716  } else {
717  $parentLen = 0;
718  if ( isset( $this->mParentLens[$row->rev_parent_id] ) ) {
719  $parentLen = $this->mParentLens[$row->rev_parent_id];
720  }
721 
722  $chardiff = ' <span class="mw-changeslist-separator"></span> ';
724  $parentLen,
725  $row->rev_len,
726  $this->getContext()
727  );
728  $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
729  }
730 
731  $lang = $this->getLanguage();
732  $comment = $lang->getDirMark() . Linker::revComment( $revRecord, false, true, false );
733  $d = ChangesList::revDateLink( $revRecord, $user, $lang, $page );
734 
735  # When querying for an IP range, we want to always show user and user talk links.
736  $userlink = '';
737  $revUser = $revRecord->getUser();
738  $revUserId = $revUser ? $revUser->getId() : 0;
739  $revUserText = $revUser ? $revUser->getName() : '';
740  if ( $this->isQueryableRange( $this->target ) ) {
741  $userlink = ' <span class="mw-changeslist-separator"></span> '
742  . $lang->getDirMark()
743  . Linker::userLink( $revUserId, $revUserText );
744  $userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
745  Linker::userTalkLink( $revUserId, $revUserText ) )->escaped() . ' ';
746  }
747 
748  $flags = [];
749  if ( $revRecord->getParentId() === 0 ) {
750  $flags[] = ChangesList::flag( 'newpage' );
751  }
752 
753  if ( $revRecord->isMinor() ) {
754  $flags[] = ChangesList::flag( 'minor' );
755  }
756 
757  $del = Linker::getRevDeleteLink( $user, $revRecord, $page );
758  if ( $del !== '' ) {
759  $del .= ' ';
760  }
761 
762  // While it might be tempting to use a list here
763  // this would result in clutter and slows down navigating the content
764  // in assistive technology.
765  // See https://phabricator.wikimedia.org/T205581#4734812
766  $diffHistLinks = Html::rawElement( 'span',
767  [ 'class' => 'mw-changeslist-links' ],
768  // The spans are needed to ensure the dividing '|' elements are not
769  // themselves styled as links.
770  Html::rawElement( 'span', [], $difftext ) .
771  ' ' . // Space needed for separating two words.
772  Html::rawElement( 'span', [], $histlink )
773  );
774 
775  # Tags, if any.
776  list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow(
777  $row->ts_tags,
778  'contributions',
779  $this->getContext()
780  );
781  $classes = array_merge( $classes, $newClasses );
782 
783  $this->hookRunner->onSpecialContributions__formatRow__flags(
784  $this->getContext(), $row, $flags );
785 
786  $templateParams = [
787  'del' => $del,
788  'timestamp' => $d,
789  'diffHistLinks' => $diffHistLinks,
790  'charDifference' => $chardiff,
791  'flags' => $flags,
792  'articleLink' => $link,
793  'userlink' => $userlink,
794  'logText' => $comment,
795  'topmarktext' => $topmarktext,
796  'tagSummary' => $tagSummary,
797  ];
798 
799  # Denote if username is redacted for this edit
800  if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
801  $templateParams['rev-deleted-user-contribs'] =
802  $this->msg( 'rev-deleted-user-contribs' )->escaped();
803  }
804 
805  $ret = $this->templateParser->processTemplate(
806  'SpecialContributionsLine',
807  $templateParams
808  );
809  }
810 
811  // Let extensions add data
812  $this->hookRunner->onContributionsLineEnding( $this, $ret, $row, $classes, $attribs );
813  $attribs = array_filter( $attribs,
814  [ Sanitizer::class, 'isReservedDataAttribute' ],
815  ARRAY_FILTER_USE_KEY
816  );
817 
818  // TODO: Handle exceptions in the catch block above. Do any extensions rely on
819  // receiving empty rows?
820 
821  if ( $classes === [] && $attribs === [] && $ret === '' ) {
822  wfDebug( "Dropping Special:Contribution row that could not be formatted" );
823  return "<!-- Could not format Special:Contribution row. -->\n";
824  }
825  $attribs['class'] = $classes;
826 
827  // FIXME: The signature of the ContributionsLineEnding hook makes it
828  // very awkward to move this LI wrapper into the template.
829  return Html::rawElement( 'li', $attribs, $ret ) . "\n";
830  }
831 
836  protected function getSqlComment() {
837  if ( $this->namespace || $this->deletedOnly ) {
838  // potentially slow, see CR r58153
839  return 'contributions page filtered for namespace or RevisionDeleted edits';
840  } else {
841  return 'contributions page unfiltered';
842  }
843  }
844 
845  protected function preventClickjacking() {
846  $this->preventClickjacking = true;
847  }
848 
852  public function getPreventClickjacking() {
853  return $this->preventClickjacking;
854  }
855 
862  public static function processDateFilter( array $opts ) {
863  $start = $opts['start'] ?? '';
864  $end = $opts['end'] ?? '';
865  $year = $opts['year'] ?? '';
866  $month = $opts['month'] ?? '';
867 
868  if ( $start !== '' && $end !== '' && $start > $end ) {
869  $temp = $start;
870  $start = $end;
871  $end = $temp;
872  }
873 
874  // If year/month legacy filtering options are set, convert them to display the new stamp
875  if ( $year !== '' || $month !== '' ) {
876  // Reuse getDateCond logic, but subtract a day because
877  // the endpoints of our date range appear inclusive
878  // but the internal end offsets are always exclusive
879  $legacyTimestamp = ReverseChronologicalPager::getOffsetDate( $year, $month );
880  $legacyDateTime = new DateTime( $legacyTimestamp->getTimestamp( TS_ISO_8601 ) );
881  $legacyDateTime = $legacyDateTime->modify( '-1 day' );
882 
883  // Clear the new timestamp range options if used and
884  // replace with the converted legacy timestamp
885  $start = '';
886  $end = $legacyDateTime->format( 'Y-m-d' );
887  }
888 
889  $opts['start'] = $start;
890  $opts['end'] = $end;
891 
892  return $opts;
893  }
894 }
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:38
ContribsPager\getNamespace
getNamespace()
Definition: ContribsPager.php:511
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:45
Linker\userTalkLink
static userTalkLink( $userId, $userText)
Definition: Linker.php:1034
Linker\userLink
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition: Linker.php:895
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:166
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:81
MediaWiki\Linker\LinkRenderer
Class that generates HTML links for pages.
Definition: LinkRenderer.php:41
getUser
getUser()
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
Linker\revComment
static revComment( $rev, $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:1604
Linker\getRevDeleteLink
static getRevDeleteLink(User $user, $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2201
ContribsPager\getSqlComment
getSqlComment()
Overwrite Pager function and return a helpful comment.
Definition: ContribsPager.php:836
ContribsPager\formatRow
formatRow( $row)
Generates each row in the contributions list.
Definition: ContribsPager.php:639
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:558
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:608
ContribsPager\tryToCreateValidRevision
tryToCreateValidRevision( $row, $title=null)
Check whether the revision associated is valid for formatting.
Definition: ContribsPager.php:592
ActorMigration
This class handles the logic for the actor table migration and should always be used in lieu of direc...
Definition: ActorMigration.php:41
Wikimedia\Rdbms\FakeResultWrapper
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
Definition: FakeResultWrapper.php:11
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:539
$revQuery
$revQuery
Definition: testCompression.php:56
ContribsPager\processDateFilter
static processDateFilter(array $opts)
Set up date filter options, given request data.
Definition: ContribsPager.php:862
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
Revision
Definition: Revision.php:40
ContribsPager\getPreventClickjacking
getPreventClickjacking()
Definition: ContribsPager.php:852
ChangeTags\modifyDisplayQuery
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
Definition: ChangeTags.php:851
ContribsPager\$linkBatchFactory
LinkBatchFactory $linkBatchFactory
Definition: ContribsPager.php:111
ContribsPager\getNamespaceCond
getNamespaceCond()
Definition: ContribsPager.php:401
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1033
Linker\generateRollback
static generateRollback( $rev, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
Definition: Linker.php:1860
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:497
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:24
ContribsPager\getStartBody
getStartBody()
Definition: ContribsPager.php:571
Title\newFromRow
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:556
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:38
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:269
$title
$title
Definition: testCompression.php:38
ContribsPager\isNewOnly
isNewOnly()
Definition: ContribsPager.php:504
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:914
RangeChronologicalPager
Pager for filtering by a range of dates.
Definition: RangeChronologicalPager.php:28
ContextSource\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:195
ContribsPager
Pager for Special:Contributions.
Definition: ContribsPager.php:39
ContribsPager\isQueryableRange
isQueryableRange( $ipRange)
Is the given IP a range and within the CIDR limit?
Definition: ContribsPager.php:448
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:518
ContribsPager\$templateParser
TemplateParser $templateParser
Definition: ContribsPager.php:108
Linker\formatRevisionSize
static formatRevisionSize( $size)
Definition: Linker.php:1649
ChangesList\showCharacterDifference
static showCharacterDifference( $old, $new, IContextSource $context=null)
Show formatted char difference.
Definition: ChangesList.php:333
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
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:57
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:212
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:66
ContribsPager\getIpRangeConds
getIpRangeConds( $db, $ip)
Get SQL conditions for an IP range, if applicable.
Definition: ContribsPager.php:430
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:1080
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:571
ContribsPager\getEndBody
getEndBody()
Definition: ContribsPager.php:578
ContribsPager\getQueryInfo
getQueryInfo()
Provides all parameters needed for the main paged query.
Definition: ContribsPager.php:321
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
ChangesList\revDateLink
static revDateLink(RevisionRecord $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...
Definition: ChangesList.php:425
ContribsPager\$namespace
string int $namespace
A single namespace number, or an empty string for all namespaces.
Definition: ContribsPager.php:54
ContribsPager\preventClickjacking
preventClickjacking()
Definition: ContribsPager.php:845
TemplateParser
Definition: TemplateParser.php:27
ContribsPager\getIndexField
getIndexField()
Definition: ContribsPager.php:466
ChangeTags\formatSummaryRow
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:174
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:490