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