MediaWiki master
ContributionsPager.php
Go to the documentation of this file.
1<?php
22namespace MediaWiki\Pager;
23
24use ChangesList;
25use ChangeTags;
26use HtmlArmor;
27use InvalidArgumentException;
28use MapCacheLRU;
48use stdClass;
51
57
58 public $mGroupByDate = true;
59
63 private $messages;
64
68 protected $isArchive;
69
73 protected $target;
74
78 private $namespace;
79
83 private $tagFilter;
84
88 private $tagInvert;
89
93 private $nsInvert;
94
99 private $associated;
100
104 private $deletedOnly;
105
109 private $topOnly;
110
114 private $newOnly;
115
119 private $hideMinor;
120
125 private $revisionsOnly;
126
127 private $preventClickjacking = false;
128
132 private $mParentLens;
133
135 protected $targetUser;
136
137 private TemplateParser $templateParser;
138 private CommentFormatter $commentFormatter;
139 private HookRunner $hookRunner;
140 private LinkBatchFactory $linkBatchFactory;
141 private NamespaceInfo $namespaceInfo;
143
145 private $formattedComments = [];
146
148 private $revisions = [];
149
151 private $tagsCache;
152
157 protected string $revisionIdField = 'rev_id';
158 protected string $revisionParentIdField = 'rev_parent_id';
159 protected string $revisionTimestampField = 'rev_timestamp';
160 protected string $revisionLengthField = 'rev_len';
161 protected string $revisionDeletedField = 'rev_deleted';
162 protected string $revisionMinorField = 'rev_minor_edit';
163 protected string $userNameField = 'rev_user_text';
164 protected string $pageNamespaceField = 'page_namespace';
165 protected string $pageTitleField = 'page_title';
166
179 public function __construct(
180 LinkRenderer $linkRenderer,
181 LinkBatchFactory $linkBatchFactory,
182 HookContainer $hookContainer,
184 NamespaceInfo $namespaceInfo,
185 CommentFormatter $commentFormatter,
186 UserFactory $userFactory,
187 IContextSource $context,
188 array $options,
190 ) {
191 $this->isArchive = $options['isArchive'] ?? false;
192
193 // Set ->target before calling parent::__construct() so
194 // parent can call $this->getIndexField() and get the right result. Set
195 // the rest too just to keep things simple.
196 if ( $targetUser ) {
197 $this->target = $options['target'] ?? $targetUser->getName();
198 $this->targetUser = $targetUser;
199 } else {
200 // Use target option
201 // It's possible for the target to be empty. This is used by
202 // ContribsPagerTest and does not cause newFromName() to return
203 // false. It's probably not used by any production code.
204 $this->target = $options['target'] ?? '';
205 // @phan-suppress-next-line PhanPossiblyNullTypeMismatchProperty RIGOR_NONE never returns null
206 $this->targetUser = $userFactory->newFromName(
207 $this->target, UserRigorOptions::RIGOR_NONE
208 );
209 if ( !$this->targetUser ) {
210 // This can happen if the target contained "#". Callers
211 // typically pass user input through title normalization to
212 // avoid it.
213 throw new InvalidArgumentException( __METHOD__ . ': the user name is too ' .
214 'broken to use even with validation disabled.' );
215 }
216 }
217
218 $this->namespace = $options['namespace'] ?? '';
219 $this->tagFilter = $options['tagfilter'] ?? false;
220 $this->tagInvert = $options['tagInvert'] ?? false;
221 $this->nsInvert = $options['nsInvert'] ?? false;
222 $this->associated = $options['associated'] ?? false;
223
224 $this->deletedOnly = !empty( $options['deletedOnly'] );
225 $this->topOnly = !empty( $options['topOnly'] );
226 $this->newOnly = !empty( $options['newOnly'] );
227 $this->hideMinor = !empty( $options['hideMinor'] );
228 $this->revisionsOnly = !empty( $options['revisionsOnly'] );
229
230 parent::__construct( $context, $linkRenderer );
231
232 $msgs = [
233 'diff',
234 'hist',
235 'pipe-separator',
236 'uctop',
237 'changeslist-nocomment',
238 'undeleteviewlink',
239 'undeleteviewlink',
240 'deletionlog',
241 ];
242
243 foreach ( $msgs as $msg ) {
244 $this->messages[$msg] = $this->msg( $msg )->escaped();
245 }
246
247 // Date filtering: use timestamp if available
248 $startTimestamp = '';
249 $endTimestamp = '';
250 if ( isset( $options['start'] ) && $options['start'] ) {
251 $startTimestamp = $options['start'] . ' 00:00:00';
252 }
253 if ( isset( $options['end'] ) && $options['end'] ) {
254 $endTimestamp = $options['end'] . ' 23:59:59';
255 }
256 $this->getDateRangeCond( $startTimestamp, $endTimestamp );
257
258 $this->templateParser = new TemplateParser();
259 $this->linkBatchFactory = $linkBatchFactory;
260 $this->hookRunner = new HookRunner( $hookContainer );
261 $this->revisionStore = $revisionStore;
262 $this->namespaceInfo = $namespaceInfo;
263 $this->commentFormatter = $commentFormatter;
264 $this->tagsCache = new MapCacheLRU( 50 );
265 }
266
267 public function getDefaultQuery() {
268 $query = parent::getDefaultQuery();
269 $query['target'] = $this->target;
270
271 return $query;
272 }
273
283 public function reallyDoQuery( $offset, $limit, $order ) {
284 [ $tables, $fields, $conds, $fname, $options, $join_conds ] = $this->buildQueryInfo(
285 $offset,
286 $limit,
287 $order
288 );
289
290 $options['MAX_EXECUTION_TIME'] =
292 /*
293 * This hook will allow extensions to add in additional queries, so they can get their data
294 * in My Contributions as well. Extensions should append their results to the $data array.
295 *
296 * Extension queries have to implement the navbar requirement as well. They should
297 * - have a column aliased as $pager->getIndexField()
298 * - have LIMIT set
299 * - have a WHERE-clause that compares the $pager->getIndexField()-equivalent column to the offset
300 * - have the ORDER BY specified based upon the details provided by the navbar
301 *
302 * See includes/Pager.php buildQueryInfo() method on how to build LIMIT, WHERE & ORDER BY
303 *
304 * &$data: an array of results of all contribs queries
305 * $pager: the ContribsPager object hooked into
306 * $offset: see phpdoc above
307 * $limit: see phpdoc above
308 * $descending: see phpdoc above
309 */
310 $dbr = $this->getDatabase();
311 $data = [ $dbr->newSelectQueryBuilder()
312 ->tables( is_array( $tables ) ? $tables : [ $tables ] )
313 ->fields( $fields )
314 ->conds( $conds )
315 ->caller( $fname )
316 ->options( $options )
317 ->joinConds( $join_conds )
318 ->setMaxExecutionTime( $this->getConfig()->get( MainConfigNames::MaxExecutionTimeForExpensiveQueries ) )
319 ->fetchResultSet() ];
320 if ( !$this->revisionsOnly && !$this->isArchive ) {
321 // TODO: Range offsets are fairly important and all handlers should take care of it.
322 // If this hook will be replaced (e.g. unified with the DeletedContribsPager one),
323 // please consider passing [ $this->endOffset, $this->startOffset ] to it (T167577).
324 $this->hookRunner->onContribsPager__reallyDoQuery(
325 $data, $this, $offset, $limit, $order );
326 }
327
328 $result = [];
329
330 // loop all results and collect them in an array
331 foreach ( $data as $query ) {
332 foreach ( $query as $i => $row ) {
333 // If the query results are in descending order, the indexes must also be in descending order
334 $index = $order === self::QUERY_ASCENDING ? $i : $limit - 1 - $i;
335 // Left-pad with zeroes, because these values will be sorted as strings
336 $index = str_pad( (string)$index, strlen( (string)$limit ), '0', STR_PAD_LEFT );
337 // use index column as key, allowing us to easily sort in PHP
338 $result[$row->{$this->getIndexField()} . "-$index"] = $row;
339 }
340 }
341
342 // sort results
343 if ( $order === self::QUERY_ASCENDING ) {
344 ksort( $result );
345 } else {
346 krsort( $result );
347 }
348
349 // enforce limit
350 $result = array_slice( $result, 0, $limit );
351
352 // get rid of array keys
353 $result = array_values( $result );
354
355 return new FakeResultWrapper( $result );
356 }
357
364 abstract protected function getRevisionQuery();
365
366 public function getQueryInfo() {
367 $queryInfo = $this->getRevisionQuery();
368
369 if ( $this->deletedOnly ) {
370 $queryInfo['conds'][] = $this->revisionDeletedField . ' != 0';
371 }
372
373 if ( $this->topOnly ) {
374 $queryInfo['conds'][] = $this->revisionIdField . ' = page_latest';
375 }
376
377 if ( $this->newOnly ) {
378 $queryInfo['conds'][] = $this->revisionParentIdField . ' = 0';
379 }
380
381 if ( $this->hideMinor ) {
382 $queryInfo['conds'][] = $this->revisionMinorField . ' = 0';
383 }
384
385 $queryInfo['conds'] = array_merge( $queryInfo['conds'], $this->getNamespaceCond() );
386
387 // Paranoia: avoid brute force searches (T19342)
388 $dbr = $this->getDatabase();
389 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
390 $queryInfo['conds'][] = $dbr->bitAnd(
391 $this->revisionDeletedField, RevisionRecord::DELETED_USER
392 ) . ' = 0';
393 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
394 $queryInfo['conds'][] = $dbr->bitAnd(
395 $this->revisionDeletedField, RevisionRecord::SUPPRESSED_USER
396 ) . ' != ' . RevisionRecord::SUPPRESSED_USER;
397 }
398
399 // $this->getIndexField() must be in the result rows, as reallyDoQuery() tries to access it.
400 $indexField = $this->getIndexField();
401 if ( $indexField !== $this->revisionTimestampField ) {
402 $queryInfo['fields'][] = $indexField;
403 }
404
406 $queryInfo['tables'],
407 $queryInfo['fields'],
408 $queryInfo['conds'],
409 $queryInfo['join_conds'],
410 $queryInfo['options'],
411 $this->tagFilter,
412 $this->tagInvert,
413 );
414
415 if ( !$this->isArchive ) {
416 $this->hookRunner->onContribsPager__getQueryInfo( $this, $queryInfo );
417 }
418
419 return $queryInfo;
420 }
421
422 protected function getNamespaceCond() {
423 if ( $this->namespace !== '' ) {
424 $dbr = $this->getDatabase();
425 $selectedNS = $dbr->addQuotes( $this->namespace );
426 $eq_op = $this->nsInvert ? '!=' : '=';
427 $bool_op = $this->nsInvert ? 'AND' : 'OR';
428
429 if ( !$this->associated ) {
430 return [ $this->pageNamespaceField . " $eq_op $selectedNS" ];
431 }
432
433 $associatedNS = $dbr->addQuotes( $this->namespaceInfo->getAssociated( $this->namespace ) );
434
435 return [
436 $this->pageNamespaceField . " $eq_op $selectedNS " .
437 $bool_op .
438 " " . $this->pageNamespaceField . " $eq_op $associatedNS"
439 ];
440 }
441
442 return [];
443 }
444
448 public function getTagFilter() {
449 return $this->tagFilter;
450 }
451
455 public function getTagInvert() {
456 return $this->tagInvert;
457 }
458
462 public function getTarget() {
463 return $this->target;
464 }
465
469 public function isNewOnly() {
470 return $this->newOnly;
471 }
472
476 public function getNamespace() {
477 return $this->namespace;
478 }
479
480 protected function doBatchLookups() {
481 # Do a link batch query
482 $this->mResult->seek( 0 );
483 $parentRevIds = [];
484 $this->mParentLens = [];
485 $revisions = [];
486 $linkBatch = $this->linkBatchFactory->newLinkBatch();
487 # Give some pointers to make (last) links
488 foreach ( $this->mResult as $row ) {
489 if ( isset( $row->{$this->revisionParentIdField} ) && $row->{$this->revisionParentIdField} ) {
490 $parentRevIds[] = (int)$row->{$this->revisionParentIdField};
491 }
492 if ( $this->revisionStore->isRevisionRow( $row, $this->isArchive ? 'archive' : 'revision' ) ) {
493 $this->mParentLens[(int)$row->{$this->revisionIdField}] = $row->{$this->revisionLengthField};
494 if ( $this->target !== $row->{$this->userNameField} ) {
495 // If the target does not match the author, batch the author's talk page
496 $linkBatch->add( NS_USER_TALK, $row->{$this->userNameField} );
497 }
498 $linkBatch->add( $row->{$this->pageNamespaceField}, $row->{$this->pageTitleField} );
499 $revisions[$row->{$this->revisionIdField}] = $this->createRevisionRecord( $row );
500 }
501 }
502 # Fetch rev_len for revisions not already scanned above
503 $this->mParentLens += $this->revisionStore->getRevisionSizes(
504 array_diff( $parentRevIds, array_keys( $this->mParentLens ) )
505 );
506 $linkBatch->execute();
507
508 $this->formattedComments = $this->commentFormatter->createRevisionBatch()
509 ->authority( $this->getAuthority() )
510 ->revisions( $revisions )
511 ->hideIfDeleted()
512 ->execute();
513
514 # For performance, save the revision objects for later.
515 # The array is indexed by rev_id. doBatchLookups() may be called
516 # multiple times with different results, so merge the revisions array,
517 # ignoring any duplicates.
518 $this->revisions += $revisions;
519 }
520
524 protected function getStartBody() {
525 return "<section class='mw-pager-body'>\n";
526 }
527
531 protected function getEndBody() {
532 return "</section>\n";
533 }
534
545 public function tryCreatingRevisionRecord( $row, $title = null ) {
546 if ( $row instanceof stdClass && isset( $row->{$this->revisionIdField} )
547 && isset( $this->revisions[$row->{$this->revisionIdField}] )
548 ) {
549 return $this->revisions[$row->{$this->revisionIdField}];
550 }
551
552 if (
553 $this->isArchive &&
554 $this->revisionStore->isRevisionRow( $row, 'archive' )
555 ) {
556 return $this->revisionStore->newRevisionFromArchiveRow( $row, 0, $title );
557 }
558
559 if (
560 !$this->isArchive &&
561 $this->revisionStore->isRevisionRow( $row )
562 ) {
563 return $this->revisionStore->newRevisionFromRow( $row, 0, $title );
564 }
565
566 return null;
567 }
568
576 public function createRevisionRecord( $row, $title = null ) {
577 if ( $this->isArchive ) {
578 return $this->revisionStore->newRevisionFromArchiveRow( $row, 0, $title );
579 }
580
581 return $this->revisionStore->newRevisionFromRow( $row, 0, $title );
582 }
583
596 public function formatRow( $row ) {
597 $ret = '';
598 $classes = [];
599 $attribs = [];
600
601 $linkRenderer = $this->getLinkRenderer();
602
603 $page = null;
604 // Create a title for the revision if possible
605 // Rows from the hook may not include title information
606 if ( isset( $row->{$this->pageNamespaceField} ) && isset( $row->{$this->pageTitleField} ) ) {
607 $page = Title::makeTitle( $row->{$this->pageNamespaceField}, $row->{$this->pageTitleField} );
608 }
609
610 // Flow overrides the ContribsPager::reallyDoQuery hook, causing this
611 // function to be called with a special object for $row. It expects us
612 // skip formatting so that the row can be formatted by the
613 // ContributionsLineEnding hook below.
614 // FIXME: have some better way for extensions to provide formatted rows.
615 $revRecord = $this->tryCreatingRevisionRecord( $row, $page );
616 if ( $revRecord && $page ) {
617 $revRecord = $this->createRevisionRecord( $row, $page );
618 $attribs['data-mw-revid'] = $revRecord->getId();
619
620 $link = $linkRenderer->makeLink(
621 $page,
622 $page->getPrefixedText(),
623 [ 'class' => 'mw-contributions-title' ],
624 $page->isRedirect() ? [ 'redirect' => 'no' ] : []
625 );
626 # Mark current revisions
627 $topmarktext = '';
628
629 // Add links for seeing history, diff, etc.
630 if ( $this->isArchive ) {
631 // Add the same links as DeletedContribsPager::formatRevisionRow
632 $undelete = SpecialPage::getTitleFor( 'Undelete' );
633 if ( $this->getAuthority()->isAllowed( 'deletedtext' ) ) {
634 $last = $linkRenderer->makeKnownLink(
635 $undelete,
636 new HtmlArmor( $this->messages['diff'] ),
637 [],
638 [
639 'target' => $page->getPrefixedText(),
640 'timestamp' => $revRecord->getTimestamp(),
641 'diff' => 'prev'
642 ]
643 );
644 } else {
645 $last = $this->messages['diff'];
646 }
647
648 $logs = SpecialPage::getTitleFor( 'Log' );
649 $dellog = $linkRenderer->makeKnownLink(
650 $logs,
651 new HtmlArmor( $this->messages['deletionlog'] ),
652 [],
653 [
654 'type' => 'delete',
655 'page' => $page->getPrefixedText()
656 ]
657 );
658
659 $reviewlink = $linkRenderer->makeKnownLink(
660 SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
661 new HtmlArmor( $this->messages['undeleteviewlink'] )
662 );
663
664 $diffHistLinks = Html::rawElement(
665 'span',
666 [ 'class' => 'mw-deletedcontribs-tools' ],
667 $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList(
668 [ $last, $dellog, $reviewlink ] ) )->escaped()
669 );
670
671 } else {
672 $pagerTools = new PagerTools(
673 $revRecord,
674 null,
675 $row->{$this->revisionIdField} === $row->page_latest && !$row->page_is_new,
676 $this->hookRunner,
677 $page,
678 $this->getContext(),
679 $this->getLinkRenderer()
680 );
681 if ( $row->{$this->revisionIdField} === $row->page_latest ) {
682 $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
683 $classes[] = 'mw-contributions-current';
684 }
685 if ( $pagerTools->shouldPreventClickjacking() ) {
686 $this->setPreventClickjacking( true );
687 }
688 $topmarktext .= $pagerTools->toHTML();
689 # Is there a visible previous revision?
690 if ( $revRecord->getParentId() !== 0 &&
691 $revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() )
692 ) {
693 $difftext = $linkRenderer->makeKnownLink(
694 $page,
695 new HtmlArmor( $this->messages['diff'] ),
696 [ 'class' => 'mw-changeslist-diff' ],
697 [
698 'diff' => 'prev',
699 'oldid' => $row->{$this->revisionIdField},
700 ]
701 );
702 } else {
703 $difftext = $this->messages['diff'];
704 }
705 $histlink = $linkRenderer->makeKnownLink(
706 $page,
707 new HtmlArmor( $this->messages['hist'] ),
708 [ 'class' => 'mw-changeslist-history' ],
709 [ 'action' => 'history' ]
710 );
711
712 // While it might be tempting to use a list here
713 // this would result in clutter and slows down navigating the content
714 // in assistive technology.
715 // See https://phabricator.wikimedia.org/T205581#4734812
716 $diffHistLinks = Html::rawElement( 'span',
717 [ 'class' => 'mw-changeslist-links' ],
718 // The spans are needed to ensure the dividing '|' elements are not
719 // themselves styled as links.
720 Html::rawElement( 'span', [], $difftext ) .
721 ' ' . // Space needed for separating two words.
722 Html::rawElement( 'span', [], $histlink )
723 );
724 }
725
726 if ( $row->{$this->revisionParentIdField} === null ) {
727 // For some reason rev_parent_id isn't populated for this row.
728 // Its rumoured this is true on wikipedia for some revisions (T36922).
729 // Next best thing is to have the total number of bytes.
730 $chardiff = ' <span class="mw-changeslist-separator"></span> ';
731 $chardiff .= Linker::formatRevisionSize( $row->{$this->revisionLengthField} );
732 $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
733 } else {
734 $parentLen = 0;
735 if ( isset( $this->mParentLens[$row->{$this->revisionParentIdField}] ) ) {
736 $parentLen = $this->mParentLens[$row->{$this->revisionParentIdField}];
737 }
738
739 $chardiff = ' <span class="mw-changeslist-separator"></span> ';
741 $parentLen,
742 $row->{$this->revisionLengthField},
743 $this->getContext()
744 );
745 $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
746 }
747
748 $lang = $this->getLanguage();
749
750 $comment = $this->formattedComments[$row->{$this->revisionIdField}];
751
752 if ( $comment === '' ) {
753 $defaultComment = $this->messages['changeslist-nocomment'];
754 $comment = "<span class=\"comment mw-comment-none\">$defaultComment</span>";
755 }
756
757 $comment = $lang->getDirMark() . $comment;
758
759 $authority = $this->getAuthority();
760 $d = ChangesList::revDateLink( $revRecord, $authority, $lang, $page );
761
762 // When the author is different from the target, always show user and user talk links
763 $userlink = '';
764 $revUser = $revRecord->getUser();
765 $revUserId = $revUser ? $revUser->getId() : 0;
766 $revUserText = $revUser ? $revUser->getName() : '';
767 if ( $this->target !== $revUserText ) {
768 $userlink = ' <span class="mw-changeslist-separator"></span> '
769 . $lang->getDirMark()
770 . Linker::userLink( $revUserId, $revUserText );
771 $userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
772 Linker::userTalkLink( $revUserId, $revUserText ) )->escaped() . ' ';
773 }
774
775 $flags = [];
776 if ( $revRecord->getParentId() === 0 ) {
777 $flags[] = ChangesList::flag( 'newpage' );
778 }
779
780 if ( $revRecord->isMinor() ) {
781 $flags[] = ChangesList::flag( 'minor' );
782 }
783
784 $del = Linker::getRevDeleteLink( $authority, $revRecord, $page );
785 if ( $del !== '' ) {
786 $del .= ' ';
787 }
788
789 # Tags, if any. Save some time using a cache.
790 [ $tagSummary, $newClasses ] = $this->tagsCache->getWithSetCallback(
791 $this->tagsCache->makeKey(
792 $row->ts_tags ?? '',
793 $this->getUser()->getName(),
794 $lang->getCode()
795 ),
797 $row->ts_tags,
798 null,
799 $this->getContext()
800 )
801 );
802 $classes = array_merge( $classes, $newClasses );
803
804 if ( !$this->isArchive ) {
805 $this->hookRunner->onSpecialContributions__formatRow__flags(
806 $this->getContext(), $row, $flags );
807 }
808
809 $templateParams = [
810 'del' => $del,
811 'timestamp' => $d,
812 'diffHistLinks' => $diffHistLinks,
813 'charDifference' => $chardiff,
814 'flags' => $flags,
815 'articleLink' => $link,
816 'userlink' => $userlink,
817 'logText' => $comment,
818 'topmarktext' => $topmarktext,
819 'tagSummary' => $tagSummary,
820 ];
821
822 # Denote if username is redacted for this edit
823 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
824 $templateParams['rev-deleted-user-contribs'] =
825 $this->msg( 'rev-deleted-user-contribs' )->escaped();
826 }
827
828 $ret = $this->templateParser->processTemplate(
829 'SpecialContributionsLine',
830 $templateParams
831 );
832 }
833
834 if ( !$this->isArchive ) {
835 // Let extensions add data
836 $this->hookRunner->onContributionsLineEnding( $this, $ret, $row, $classes, $attribs );
837 $attribs = array_filter( $attribs,
838 [ Sanitizer::class, 'isReservedDataAttribute' ],
839 ARRAY_FILTER_USE_KEY
840 );
841 }
842
843 // TODO: Handle exceptions in the catch block above. Do any extensions rely on
844 // receiving empty rows?
845
846 if ( $classes === [] && $attribs === [] && $ret === '' ) {
847 wfDebug( "Dropping Special:Contribution row that could not be formatted" );
848 return "<!-- Could not format Special:Contribution row. -->\n";
849 }
850 $attribs['class'] = $classes;
851
852 // FIXME: The signature of the ContributionsLineEnding hook makes it
853 // very awkward to move this LI wrapper into the template.
854 return Html::rawElement( 'li', $attribs, $ret ) . "\n";
855 }
856
861 protected function getSqlComment() {
862 if ( $this->namespace || $this->deletedOnly ) {
863 // potentially slow, see CR r58153
864 return 'contributions page filtered for namespace or RevisionDeleted edits';
865 } else {
866 return 'contributions page unfiltered';
867 }
868 }
869
873 protected function preventClickjacking() {
874 $this->setPreventClickjacking( true );
875 }
876
881 protected function setPreventClickjacking( bool $enable ) {
882 $this->preventClickjacking = $enable;
883 }
884
888 public function getPreventClickjacking() {
889 return $this->preventClickjacking;
890 }
891
892}
getAuthority()
const NS_USER_TALK
Definition Defines.php:68
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getContext()
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag='', bool $exclude=false)
Applies all tags-related changes to a query.
static formatSummaryRow( $tags, $unused, MessageLocalizer $localizer=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(RevisionRecord $rev, Authority $performer, Language $lang, $title=null, $className='')
Render the date and time of a revision in the current user language based on whether the user is able...
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
Store key-value entries in a size-limited in-memory LRU cache.
This is the main service interface for converting single-line comments from various DB comment fields...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
Class that generates HTML for internal links.
Some internal bits split of from Skin.php.
Definition Linker.php:63
A class containing constants representing the names of configuration variables.
const MaxExecutionTimeForExpensiveQueries
Name constant for the MaxExecutionTimeForExpensiveQueries setting, for use with Config::get()
Pager for Special:Contributions.
getRevisionQuery()
Get queryInfo for the main query selecting revisions, not including filtering on namespace,...
getQueryInfo()
Provides all parameters needed for the main paged query.
string $revisionIdField
Field names for various attributes.
reallyDoQuery( $offset, $limit, $order)
This method basically executes the exact same code as the parent class, though with a hook added,...
getEndBody()
Hook into getBody() for the end of the list.to overridestring
createRevisionRecord( $row, $title=null)
Create a revision record from a $row that models a revision.
string $target
User name, or a string describing an IP address range.
getSqlComment()
Overwrite Pager function and return a helpful comment.
getDefaultQuery()
Get an array of query parameters that should be put into self-links.
tryCreatingRevisionRecord( $row, $title=null)
If the object looks like a revision row, or corresponds to a previously cached revision,...
bool $isArchive
Get revisions from the archive table (if true) or the revision table (if false)
__construct(LinkRenderer $linkRenderer, LinkBatchFactory $linkBatchFactory, HookContainer $hookContainer, RevisionStore $revisionStore, NamespaceInfo $namespaceInfo, CommentFormatter $commentFormatter, UserFactory $userFactory, IContextSource $context, array $options, ?UserIdentity $targetUser)
formatRow( $row)
Generates each row in the contributions list.
doBatchLookups()
Called from getBody(), before getStartBody() is called and after doQuery() was called.
getStartBody()
Hook into getBody(), allows text to be inserted at the start.This will be called even if there are no...
getDatabase()
Get the Database object in use.
getIndexField()
Returns the name of the index field.
Pager for filtering by a range of dates.
getDateRangeCond( $startTime, $endTime)
Set and return a date range condition using timestamps provided by the user.
buildQueryInfo( $offset, $limit, $order)
Build variables to use by the database wrapper.For b/c, query direction is true for ascending and fal...
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:46
Page revision base class.
Service for looking up page revisions.
Parent class for all special pages.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition Title.php:79
Creates User objects.
newFromName(string $name, string $validate=self::RIGOR_VALID)
Factory method for creating users by name, replacing static User::newFromName.
Overloads the relevant methods of the real ResultWrapper so it doesn't go anywhere near an actual dat...
Interface for objects which can provide a MediaWiki context on request.
Interface for objects representing user identity.
Shared interface for rigor levels when dealing with User methods.
Result wrapper for grabbing data queried from an IDatabase object.