MediaWiki REL1_35
ContribsPager.php
Go to the documentation of this file.
1<?php
29use Wikimedia\IPUtils;
33
35
39 private $messages;
40
44 private $target;
45
49 private $namespace = '';
50
54 private $tagFilter;
55
59 private $nsInvert;
60
65 private $associated;
66
70 private $deletedOnly;
71
75 private $topOnly;
76
80 private $newOnly;
81
85 private $hideMinor;
86
87 private $preventClickjacking = false;
88
92 private $mParentLens;
93
98
99 public function __construct( IContextSource $context, array $options,
100 LinkRenderer $linkRenderer = null
101 ) {
102 // Set ->target before calling parent::__construct() so
103 // parent can call $this->getIndexField() and get the right result. Set
104 // the rest too just to keep things simple.
105 $this->target = $options['target'] ?? '';
106 $this->namespace = $options['namespace'] ?? '';
107 $this->tagFilter = $options['tagfilter'] ?? false;
108 $this->nsInvert = $options['nsInvert'] ?? false;
109 $this->associated = $options['associated'] ?? false;
110
111 $this->deletedOnly = !empty( $options['deletedOnly'] );
112 $this->topOnly = !empty( $options['topOnly'] );
113 $this->newOnly = !empty( $options['newOnly'] );
114 $this->hideMinor = !empty( $options['hideMinor'] );
115
116 parent::__construct( $context, $linkRenderer );
117
118 $msgs = [
119 'diff',
120 'hist',
121 'pipe-separator',
122 'uctop'
123 ];
124
125 foreach ( $msgs as $msg ) {
126 $this->messages[$msg] = $this->msg( $msg )->escaped();
127 }
128
129 // Date filtering: use timestamp if available
130 $startTimestamp = '';
131 $endTimestamp = '';
132 if ( isset( $options['start'] ) && $options['start'] ) {
133 $startTimestamp = $options['start'] . ' 00:00:00';
134 }
135 if ( isset( $options['end'] ) && $options['end'] ) {
136 $endTimestamp = $options['end'] . ' 23:59:59';
137 }
138 $this->getDateRangeCond( $startTimestamp, $endTimestamp );
139
140 // Most of this code will use the 'contributions' group DB, which can map to replica DBs
141 // with extra user based indexes or partioning by user.
142 $this->mDb = wfGetDB( DB_REPLICA, 'contributions' );
143 $this->templateParser = new TemplateParser();
144 }
145
146 public function getDefaultQuery() {
147 $query = parent::getDefaultQuery();
148 $query['target'] = $this->target;
149
150 return $query;
151 }
152
160 public function getNavigationBar() {
161 return Html::rawElement( 'p', [ 'class' => 'mw-pager-navigation-bar' ],
162 parent::getNavigationBar()
163 );
164 }
165
175 public function reallyDoQuery( $offset, $limit, $order ) {
176 list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo(
177 $offset,
178 $limit,
179 $order
180 );
181
182 $options['MAX_EXECUTION_TIME'] = $this->getConfig()->get( 'MaxExecutionTimeForExpensiveQueries' );
183 /*
184 * This hook will allow extensions to add in additional queries, so they can get their data
185 * in My Contributions as well. Extensions should append their results to the $data array.
186 *
187 * Extension queries have to implement the navbar requirement as well. They should
188 * - have a column aliased as $pager->getIndexField()
189 * - have LIMIT set
190 * - have a WHERE-clause that compares the $pager->getIndexField()-equivalent column to the offset
191 * - have the ORDER BY specified based upon the details provided by the navbar
192 *
193 * See includes/Pager.php buildQueryInfo() method on how to build LIMIT, WHERE & ORDER BY
194 *
195 * &$data: an array of results of all contribs queries
196 * $pager: the ContribsPager object hooked into
197 * $offset: see phpdoc above
198 * $limit: see phpdoc above
199 * $descending: see phpdoc above
200 */
201 $data = [ $this->mDb->select(
202 $tables, $fields, $conds, $fname, $options, $join_conds
203 ) ];
204 $this->getHookRunner()->onContribsPager__reallyDoQuery(
205 $data, $this, $offset, $limit, $order );
206
207 $result = [];
208
209 // loop all results and collect them in an array
210 foreach ( $data as $query ) {
211 foreach ( $query as $i => $row ) {
212 // If the query results are in descending order, the indexes must also be in descending order
213 $index = $order === self::QUERY_ASCENDING ? $i : $limit - 1 - $i;
214 // Left-pad with zeroes, because these values will be sorted as strings
215 $index = str_pad( $index, strlen( $limit ), '0', STR_PAD_LEFT );
216 // use index column as key, allowing us to easily sort in PHP
217 $result[$row->{$this->getIndexField()} . "-$index"] = $row;
218 }
219 }
220
221 // sort results
222 if ( $order === self::QUERY_ASCENDING ) {
223 ksort( $result );
224 } else {
225 krsort( $result );
226 }
227
228 // enforce limit
229 $result = array_slice( $result, 0, $limit );
230
231 // get rid of array keys
232 $result = array_values( $result );
233
234 return new FakeResultWrapper( $result );
235 }
236
246 private function getTargetTable() {
247 $user = User::newFromName( $this->target, false );
248 $ipRangeConds = $user->isAnon() ? $this->getIpRangeConds( $this->mDb, $this->target ) : null;
249 if ( $ipRangeConds ) {
250 return 'ip_changes';
251 } else {
252 $conds = ActorMigration::newMigration()->getWhere( $this->mDb, 'rev_user', $user );
253 if ( isset( $conds['orconds']['actor'] ) ) {
254 // @todo: This will need changing when revision_actor_temp goes away
255 return 'revision_actor_temp';
256 }
257 }
258
259 return 'revision';
260 }
261
262 public function getQueryInfo() {
263 $revQuery = MediaWikiServices::getInstance()
264 ->getRevisionStore()
265 ->getQueryInfo( [ 'page', 'user' ] );
266 $queryInfo = [
267 'tables' => $revQuery['tables'],
268 'fields' => array_merge( $revQuery['fields'], [ 'page_is_new' ] ),
269 'conds' => [],
270 'options' => [],
271 'join_conds' => $revQuery['joins'],
272 ];
273 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
274
275 // WARNING: Keep this in sync with getTargetTable()!
276 $user = User::newFromName( $this->target, false );
277 $ipRangeConds = $user->isAnon() ? $this->getIpRangeConds( $this->mDb, $this->target ) : null;
278 if ( $ipRangeConds ) {
279 $queryInfo['tables'][] = 'ip_changes';
280 $queryInfo['join_conds']['ip_changes'] = [
281 'LEFT JOIN', [ 'ipc_rev_id = rev_id' ]
282 ];
283 $queryInfo['conds'][] = $ipRangeConds;
284 } else {
285 // tables and joins are already handled by Revision::getQueryInfo()
286 $conds = ActorMigration::newMigration()->getWhere( $this->mDb, 'rev_user', $user );
287 $queryInfo['conds'][] = $conds['conds'];
288 // Force the appropriate index to avoid bad query plans (T189026)
289 if ( isset( $conds['orconds']['actor'] ) ) {
290 // @todo: This will need changing when revision_actor_temp goes away
291 $queryInfo['options']['USE INDEX']['temp_rev_user'] = 'actor_timestamp';
292 }
293 }
294
295 if ( $this->deletedOnly ) {
296 $queryInfo['conds'][] = 'rev_deleted != 0';
297 }
298
299 if ( $this->topOnly ) {
300 $queryInfo['conds'][] = 'rev_id = page_latest';
301 }
302
303 if ( $this->newOnly ) {
304 $queryInfo['conds'][] = 'rev_parent_id = 0';
305 }
306
307 if ( $this->hideMinor ) {
308 $queryInfo['conds'][] = 'rev_minor_edit = 0';
309 }
310
311 $user = $this->getUser();
312 $queryInfo['conds'] = array_merge( $queryInfo['conds'], $this->getNamespaceCond() );
313
314 // Paranoia: avoid brute force searches (T19342)
315 if ( !$permissionManager->userHasRight( $user, 'deletedhistory' ) ) {
316 $queryInfo['conds'][] = $this->mDb->bitAnd(
317 'rev_deleted', RevisionRecord::DELETED_USER
318 ) . ' = 0';
319 } elseif ( !$permissionManager->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' ) ) {
320 $queryInfo['conds'][] = $this->mDb->bitAnd(
321 'rev_deleted', RevisionRecord::SUPPRESSED_USER
322 ) . ' != ' . RevisionRecord::SUPPRESSED_USER;
323 }
324
325 // $this->getIndexField() must be in the result rows, as reallyDoQuery() tries to access it.
326 $indexField = $this->getIndexField();
327 if ( $indexField !== 'rev_timestamp' ) {
328 $queryInfo['fields'][] = $indexField;
329 }
330
332 $queryInfo['tables'],
333 $queryInfo['fields'],
334 $queryInfo['conds'],
335 $queryInfo['join_conds'],
336 $queryInfo['options'],
337 $this->tagFilter
338 );
339
340 $this->getHookRunner()->onContribsPager__getQueryInfo( $this, $queryInfo );
341
342 return $queryInfo;
343 }
344
345 protected function getNamespaceCond() {
346 if ( $this->namespace !== '' ) {
347 $selectedNS = $this->mDb->addQuotes( $this->namespace );
348 $eq_op = $this->nsInvert ? '!=' : '=';
349 $bool_op = $this->nsInvert ? 'AND' : 'OR';
350
351 if ( !$this->associated ) {
352 return [ "page_namespace $eq_op $selectedNS" ];
353 }
354
355 $associatedNS = $this->mDb->addQuotes(
356 MediaWikiServices::getInstance()->getNamespaceInfo()->getAssociated( $this->namespace )
357 );
358
359 return [
360 "page_namespace $eq_op $selectedNS " .
361 $bool_op .
362 " page_namespace $eq_op $associatedNS"
363 ];
364 }
365
366 return [];
367 }
368
375 private function getIpRangeConds( $db, $ip ) {
376 // First make sure it is a valid range and they are not outside the CIDR limit
377 if ( !$this->isQueryableRange( $ip ) ) {
378 return false;
379 }
380
381 list( $start, $end ) = IPUtils::parseRange( $ip );
382
383 return 'ipc_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end );
384 }
385
393 public function isQueryableRange( $ipRange ) {
394 $limits = $this->getConfig()->get( 'RangeContributionsCIDRLimit' );
395
396 $bits = IPUtils::parseCIDR( $ipRange )[1];
397 if (
398 ( $bits === false ) ||
399 ( IPUtils::isIPv4( $ipRange ) && $bits < $limits['IPv4'] ) ||
400 ( IPUtils::isIPv6( $ipRange ) && $bits < $limits['IPv6'] )
401 ) {
402 return false;
403 }
404
405 return true;
406 }
407
411 public function getIndexField() {
412 // The returned column is used for sorting and continuation, so we need to
413 // make sure to use the right denormalized column depending on which table is
414 // being targeted by the query to avoid bad query plans.
415 // See T200259, T204669, T220991, and T221380.
416 $target = $this->getTargetTable();
417 switch ( $target ) {
418 case 'revision':
419 return 'rev_timestamp';
420 case 'ip_changes':
421 return 'ipc_rev_timestamp';
422 case 'revision_actor_temp':
423 return 'revactor_timestamp';
424 default:
425 wfWarn(
426 __METHOD__ . ": Unknown value '$target' from " . static::class . '::getTargetTable()', 0
427 );
428 return 'rev_timestamp';
429 }
430 }
431
435 public function getTagFilter() {
436 return $this->tagFilter;
437 }
438
442 public function getTarget() {
443 return $this->target;
444 }
445
449 public function isNewOnly() {
450 return $this->newOnly;
451 }
452
456 public function getNamespace() {
457 return $this->namespace;
458 }
459
463 protected function getExtraSortFields() {
464 // The returned columns are used for sorting, so we need to make sure
465 // to use the right denormalized column depending on which table is
466 // being targeted by the query to avoid bad query plans.
467 // See T200259, T204669, T220991, and T221380.
468 $target = $this->getTargetTable();
469 switch ( $target ) {
470 case 'revision':
471 return [ 'rev_id' ];
472 case 'ip_changes':
473 return [ 'ipc_rev_id' ];
474 case 'revision_actor_temp':
475 return [ 'revactor_rev' ];
476 default:
477 wfWarn(
478 __METHOD__ . ": Unknown value '$target' from " . static::class . '::getTargetTable()', 0
479 );
480 return [ 'rev_id' ];
481 }
482 }
483
484 protected function doBatchLookups() {
485 # Do a link batch query
486 $this->mResult->seek( 0 );
487 $parentRevIds = [];
488 $this->mParentLens = [];
489 $batch = new LinkBatch();
490 $isIpRange = $this->isQueryableRange( $this->target );
491 # Give some pointers to make (last) links
492 foreach ( $this->mResult as $row ) {
493 if ( isset( $row->rev_parent_id ) && $row->rev_parent_id ) {
494 $parentRevIds[] = $row->rev_parent_id;
495 }
496 if ( isset( $row->rev_id ) ) {
497 $this->mParentLens[$row->rev_id] = $row->rev_len;
498 if ( $isIpRange ) {
499 // If this is an IP range, batch the IP's talk page
500 $batch->add( NS_USER_TALK, $row->rev_user_text );
501 }
502 $batch->add( $row->page_namespace, $row->page_title );
503 }
504 }
505 # Fetch rev_len for revisions not already scanned above
506 $this->mParentLens += MediaWikiServices::getInstance()
507 ->getRevisionStore()
508 ->getRevisionSizes( array_diff( $parentRevIds, array_keys( $this->mParentLens ) ) );
509 $batch->execute();
510 $this->mResult->seek( 0 );
511 }
512
516 protected function getStartBody() {
517 return "<ul class=\"mw-contributions-list\">\n";
518 }
519
523 protected function getEndBody() {
524 return "</ul>\n";
525 }
526
537 public function tryToCreateValidRevision( $row, $title = null ) {
538 wfDeprecated( __METHOD__, '1.35' );
539 $potentialRevRecord = $this->tryCreatingRevisionRecord( $row, $title );
540 return $potentialRevRecord ? new Revision( $potentialRevRecord ) : null;
541 }
542
553 public function tryCreatingRevisionRecord( $row, $title = null ) {
554 $revFactory = MediaWikiServices::getInstance()->getRevisionFactory();
555 /*
556 * There may be more than just revision rows. To make sure that we'll only be processing
557 * revisions here, let's _try_ to build a revision out of our row (without displaying
558 * notices though) and then trying to grab data from the built object. If we succeed,
559 * we're definitely dealing with revision data and we may proceed, if not, we'll leave it
560 * to extensions to subscribe to the hook to parse the row.
561 */
562 Wikimedia\suppressWarnings();
563 try {
564 $revRecord = $revFactory->newRevisionFromRow( $row, 0, $title );
565 $validRevision = (bool)$revRecord->getId();
566 } catch ( Exception $e ) {
567 $validRevision = false;
568 }
569 Wikimedia\restoreWarnings();
570 return $validRevision ? $revRecord : null;
571 }
572
585 public function formatRow( $row ) {
586 $ret = '';
587 $classes = [];
588 $attribs = [];
589
590 $linkRenderer = $this->getLinkRenderer();
591 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
592
593 $page = null;
594 // Create a title for the revision if possible
595 // Rows from the hook may not include title information
596 if ( isset( $row->page_namespace ) && isset( $row->page_title ) ) {
597 $page = Title::newFromRow( $row );
598 }
599 $revRecord = $this->tryCreatingRevisionRecord( $row, $page );
600 if ( $revRecord ) {
601 $attribs['data-mw-revid'] = $revRecord->getId();
602
603 $link = $linkRenderer->makeLink(
604 $page,
605 $page->getPrefixedText(),
606 [ 'class' => 'mw-contributions-title' ],
607 $page->isRedirect() ? [ 'redirect' => 'no' ] : []
608 );
609 # Mark current revisions
610 $topmarktext = '';
611 $user = $this->getUser();
612
613 if ( $row->rev_id === $row->page_latest ) {
614 $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
615 $classes[] = 'mw-contributions-current';
616 # Add rollback link
617 if ( !$row->page_is_new &&
618 $permissionManager->quickUserCan( 'rollback', $user, $page ) &&
619 $permissionManager->quickUserCan( 'edit', $user, $page )
620 ) {
621 $this->preventClickjacking();
622 $topmarktext .= ' ' . Linker::generateRollback(
623 $revRecord,
624 $this->getContext(),
625 [ 'noBrackets' ]
626 );
627 }
628 }
629 # Is there a visible previous revision?
630 if ( $revRecord->getParentId() !== 0 &&
631 RevisionRecord::userCanBitfield(
632 $revRecord->getVisibility(),
633 RevisionRecord::DELETED_TEXT,
634 $user
635 )
636 ) {
637 $difftext = $linkRenderer->makeKnownLink(
638 $page,
639 new HtmlArmor( $this->messages['diff'] ),
640 [ 'class' => 'mw-changeslist-diff' ],
641 [
642 'diff' => 'prev',
643 'oldid' => $row->rev_id
644 ]
645 );
646 } else {
647 $difftext = $this->messages['diff'];
648 }
649 $histlink = $linkRenderer->makeKnownLink(
650 $page,
651 new HtmlArmor( $this->messages['hist'] ),
652 [ 'class' => 'mw-changeslist-history' ],
653 [ 'action' => 'history' ]
654 );
655
656 if ( $row->rev_parent_id === null ) {
657 // For some reason rev_parent_id isn't populated for this row.
658 // Its rumoured this is true on wikipedia for some revisions (T36922).
659 // Next best thing is to have the total number of bytes.
660 $chardiff = ' <span class="mw-changeslist-separator"></span> ';
661 $chardiff .= Linker::formatRevisionSize( $row->rev_len );
662 $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
663 } else {
664 $parentLen = 0;
665 if ( isset( $this->mParentLens[$row->rev_parent_id] ) ) {
666 $parentLen = $this->mParentLens[$row->rev_parent_id];
667 }
668
669 $chardiff = ' <span class="mw-changeslist-separator"></span> ';
670 $chardiff .= ChangesList::showCharacterDifference(
671 $parentLen,
672 $row->rev_len,
673 $this->getContext()
674 );
675 $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
676 }
677
678 $lang = $this->getLanguage();
679 $comment = $lang->getDirMark() . Linker::revComment( $revRecord, false, true, false );
680 $d = ChangesList::revDateLink( $revRecord, $user, $lang, $page );
681
682 # When querying for an IP range, we want to always show user and user talk links.
683 $userlink = '';
684 $revUser = $revRecord->getUser();
685 $revUserId = $revUser ? $revUser->getId() : 0;
686 $revUserText = $revUser ? $revUser->getName() : '';
687 if ( $this->isQueryableRange( $this->target ) ) {
688 $userlink = ' <span class="mw-changeslist-separator"></span> '
689 . $lang->getDirMark()
690 . Linker::userLink( $revUserId, $revUserText );
691 $userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
692 Linker::userTalkLink( $revUserId, $revUserText ) )->escaped() . ' ';
693 }
694
695 $flags = [];
696 if ( $revRecord->getParentId() === 0 ) {
697 $flags[] = ChangesList::flag( 'newpage' );
698 }
699
700 if ( $revRecord->isMinor() ) {
701 $flags[] = ChangesList::flag( 'minor' );
702 }
703
704 $del = Linker::getRevDeleteLink( $user, $revRecord, $page );
705 if ( $del !== '' ) {
706 $del .= ' ';
707 }
708
709 // While it might be tempting to use a list here
710 // this would result in clutter and slows down navigating the content
711 // in assistive technology.
712 // See https://phabricator.wikimedia.org/T205581#4734812
713 $diffHistLinks = Html::rawElement( 'span',
714 [ 'class' => 'mw-changeslist-links' ],
715 // The spans are needed to ensure the dividing '|' elements are not
716 // themselves styled as links.
717 Html::rawElement( 'span', [], $difftext ) .
718 ' ' . // Space needed for separating two words.
719 Html::rawElement( 'span', [], $histlink )
720 );
721
722 # Tags, if any.
723 list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow(
724 $row->ts_tags,
725 'contributions',
726 $this->getContext()
727 );
728 $classes = array_merge( $classes, $newClasses );
729
730 $this->getHookRunner()->onSpecialContributions__formatRow__flags(
731 $this->getContext(), $row, $flags );
732
733 $templateParams = [
734 'del' => $del,
735 'timestamp' => $d,
736 'diffHistLinks' => $diffHistLinks,
737 'charDifference' => $chardiff,
738 'flags' => $flags,
739 'articleLink' => $link,
740 'userlink' => $userlink,
741 'logText' => $comment,
742 'topmarktext' => $topmarktext,
743 'tagSummary' => $tagSummary,
744 ];
745
746 # Denote if username is redacted for this edit
747 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
748 $templateParams['rev-deleted-user-contribs'] =
749 $this->msg( 'rev-deleted-user-contribs' )->escaped();
750 }
751
752 $ret = $this->templateParser->processTemplate(
753 'SpecialContributionsLine',
754 $templateParams
755 );
756 }
757
758 // Let extensions add data
759 $this->getHookRunner()->onContributionsLineEnding( $this, $ret, $row, $classes, $attribs );
760 $attribs = array_filter( $attribs,
761 [ Sanitizer::class, 'isReservedDataAttribute' ],
762 ARRAY_FILTER_USE_KEY
763 );
764
765 // TODO: Handle exceptions in the catch block above. Do any extensions rely on
766 // receiving empty rows?
767
768 if ( $classes === [] && $attribs === [] && $ret === '' ) {
769 wfDebug( "Dropping Special:Contribution row that could not be formatted" );
770 return "<!-- Could not format Special:Contribution row. -->\n";
771 }
772 $attribs['class'] = $classes;
773
774 // FIXME: The signature of the ContributionsLineEnding hook makes it
775 // very awkward to move this LI wrapper into the template.
776 return Html::rawElement( 'li', $attribs, $ret ) . "\n";
777 }
778
783 protected function getSqlComment() {
784 if ( $this->namespace || $this->deletedOnly ) {
785 // potentially slow, see CR r58153
786 return 'contributions page filtered for namespace or RevisionDeleted edits';
787 } else {
788 return 'contributions page unfiltered';
789 }
790 }
791
792 protected function preventClickjacking() {
793 $this->preventClickjacking = true;
794 }
795
799 public function getPreventClickjacking() {
800 return $this->preventClickjacking;
801 }
802
809 public static function processDateFilter( array $opts ) {
810 $start = $opts['start'] ?? '';
811 $end = $opts['end'] ?? '';
812 $year = $opts['year'] ?? '';
813 $month = $opts['month'] ?? '';
814
815 if ( $start !== '' && $end !== '' && $start > $end ) {
816 $temp = $start;
817 $start = $end;
818 $end = $temp;
819 }
820
821 // If year/month legacy filtering options are set, convert them to display the new stamp
822 if ( $year !== '' || $month !== '' ) {
823 // Reuse getDateCond logic, but subtract a day because
824 // the endpoints of our date range appear inclusive
825 // but the internal end offsets are always exclusive
826 $legacyTimestamp = ReverseChronologicalPager::getOffsetDate( $year, $month );
827 $legacyDateTime = new DateTime( $legacyTimestamp->getTimestamp( TS_ISO_8601 ) );
828 $legacyDateTime = $legacyDateTime->modify( '-1 day' );
829
830 // Clear the new timestamp range options if used and
831 // replace with the converted legacy timestamp
832 $start = '';
833 $end = $legacyDateTime->format( 'Y-m-d' );
834 }
835
836 $opts['start'] = $start;
837 $opts['end'] = $end;
838
839 return $opts;
840 }
841}
getUser()
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
getContext()
static formatSummaryRow( $tags, $page, MessageLocalizer $localizer=null)
Creates HTML for the given tags.
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='')
Applies all tags-related changes to a query.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
TemplateParser $templateParser
string $target
User name, or a string describing an IP address range.
string int $namespace
A single namespace number, or an empty string for all namespaces.
static processDateFilter(array $opts)
Set up date filter options, given request data.
getTargetTable()
Return the table targeted for ordering and continuation.
string false $tagFilter
Name of tag to filter, or false to ignore tags.
string[] $messages
Local cache for escaped messages.
tryToCreateValidRevision( $row, $title=null)
Check whether the revision associated is valid for formatting.
tryCreatingRevisionRecord( $row, $title=null)
Check whether the revision associated is valid for formatting.
bool $associated
Set to true to show both the subject and talk namespace, no matter which got selected.
bool $nsInvert
Set to true to invert the namespace selection.
getQueryInfo()
Provides all parameters needed for the main paged query.
getNavigationBar()
Wrap the navigation bar in a p element with identifying class.
bool $topOnly
Set to true to show only latest (a.k.a.
getDefaultQuery()
Get an array of query parameters that should be put into self-links.
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
isQueryableRange( $ipRange)
Is the given IP a range and within the CIDR limit?
getSqlComment()
Overwrite Pager function and return a helpful comment.
formatRow( $row)
Generates each row in the contributions list.
__construct(IContextSource $context, array $options, LinkRenderer $linkRenderer=null)
reallyDoQuery( $offset, $limit, $order)
This method basically executes the exact same code as the parent class, though with a hook added,...
bool $deletedOnly
Set to true to show only deleted revisions.
bool $newOnly
Set to true to show only new pages.
getIpRangeConds( $db, $ip)
Get SQL conditions for an IP range, if applicable.
bool $hideMinor
Set to true to hide edits marked as minor by the user.
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
LinkRenderer $linkRenderer
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:35
static generateRollback( $rev, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
Definition Linker.php:1871
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition Linker.php:906
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:1615
static formatRevisionSize( $size)
Definition Linker.php:1660
static userTalkLink( $userId, $userText)
Definition Linker.php:1045
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:2205
Class that generates HTML links for pages.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Page revision base class.
Pager for filtering by a range of dates.
buildQueryInfo( $offset, $limit, $order)
Build variables to use by the database wrapper.
getDateRangeCond( $startStamp, $endStamp)
Set and return a date range condition using timestamps provided by the user.
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...
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:541
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
const NS_USER_TALK
Definition Defines.php:73
Interface for objects which can provide a MediaWiki context on request.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
Result wrapper for grabbing data queried from an IDatabase object.
const DB_REPLICA
Definition defines.php:25
if(!isset( $args[0])) $lang