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 $namespaces = [ $this->namespace ];
426 $eq_op = $this->nsInvert ? '!=' : '=';
427 if ( $this->associated ) {
428 $namespaces[] = $this->namespaceInfo->getAssociated( $this->namespace );
429 }
430 return [ $dbr->expr( $this->pageNamespaceField, $eq_op, $namespaces ) ];
431 }
432
433 return [];
434 }
435
439 public function getTagFilter() {
440 return $this->tagFilter;
441 }
442
446 public function getTagInvert() {
447 return $this->tagInvert;
448 }
449
453 public function getTarget() {
454 return $this->target;
455 }
456
460 public function isNewOnly() {
461 return $this->newOnly;
462 }
463
467 public function getNamespace() {
468 return $this->namespace;
469 }
470
471 protected function doBatchLookups() {
472 # Do a link batch query
473 $this->mResult->seek( 0 );
474 $parentRevIds = [];
475 $this->mParentLens = [];
476 $revisions = [];
477 $linkBatch = $this->linkBatchFactory->newLinkBatch();
478 # Give some pointers to make (last) links
479 foreach ( $this->mResult as $row ) {
480 if ( isset( $row->{$this->revisionParentIdField} ) && $row->{$this->revisionParentIdField} ) {
481 $parentRevIds[] = (int)$row->{$this->revisionParentIdField};
482 }
483 if ( $this->revisionStore->isRevisionRow( $row, $this->isArchive ? 'archive' : 'revision' ) ) {
484 $this->mParentLens[(int)$row->{$this->revisionIdField}] = $row->{$this->revisionLengthField};
485 if ( $this->target !== $row->{$this->userNameField} ) {
486 // If the target does not match the author, batch the author's talk page
487 $linkBatch->add( NS_USER_TALK, $row->{$this->userNameField} );
488 }
489 $linkBatch->add( $row->{$this->pageNamespaceField}, $row->{$this->pageTitleField} );
490 $revisions[$row->{$this->revisionIdField}] = $this->createRevisionRecord( $row );
491 }
492 }
493 # Fetch rev_len for revisions not already scanned above
494 $this->mParentLens += $this->revisionStore->getRevisionSizes(
495 array_diff( $parentRevIds, array_keys( $this->mParentLens ) )
496 );
497 $linkBatch->execute();
498
499 $this->formattedComments = $this->commentFormatter->createRevisionBatch()
500 ->authority( $this->getAuthority() )
501 ->revisions( $revisions )
502 ->hideIfDeleted()
503 ->execute();
504
505 # For performance, save the revision objects for later.
506 # The array is indexed by rev_id. doBatchLookups() may be called
507 # multiple times with different results, so merge the revisions array,
508 # ignoring any duplicates.
509 $this->revisions += $revisions;
510 }
511
515 protected function getStartBody() {
516 return "<section class='mw-pager-body'>\n";
517 }
518
522 protected function getEndBody() {
523 return "</section>\n";
524 }
525
536 public function tryCreatingRevisionRecord( $row, $title = null ) {
537 if ( $row instanceof stdClass && isset( $row->{$this->revisionIdField} )
538 && isset( $this->revisions[$row->{$this->revisionIdField}] )
539 ) {
540 return $this->revisions[$row->{$this->revisionIdField}];
541 }
542
543 if (
544 $this->isArchive &&
545 $this->revisionStore->isRevisionRow( $row, 'archive' )
546 ) {
547 return $this->revisionStore->newRevisionFromArchiveRow( $row, 0, $title );
548 }
549
550 if (
551 !$this->isArchive &&
552 $this->revisionStore->isRevisionRow( $row )
553 ) {
554 return $this->revisionStore->newRevisionFromRow( $row, 0, $title );
555 }
556
557 return null;
558 }
559
567 public function createRevisionRecord( $row, $title = null ) {
568 if ( $this->isArchive ) {
569 return $this->revisionStore->newRevisionFromArchiveRow( $row, 0, $title );
570 }
571
572 return $this->revisionStore->newRevisionFromRow( $row, 0, $title );
573 }
574
587 public function formatRow( $row ) {
588 $ret = '';
589 $classes = [];
590 $attribs = [];
591
592 $linkRenderer = $this->getLinkRenderer();
593
594 $page = null;
595 // Create a title for the revision if possible
596 // Rows from the hook may not include title information
597 if ( isset( $row->{$this->pageNamespaceField} ) && isset( $row->{$this->pageTitleField} ) ) {
598 $page = Title::makeTitle( $row->{$this->pageNamespaceField}, $row->{$this->pageTitleField} );
599 }
600
601 // Flow overrides the ContribsPager::reallyDoQuery hook, causing this
602 // function to be called with a special object for $row. It expects us
603 // skip formatting so that the row can be formatted by the
604 // ContributionsLineEnding hook below.
605 // FIXME: have some better way for extensions to provide formatted rows.
606 $revRecord = $this->tryCreatingRevisionRecord( $row, $page );
607 if ( $revRecord && $page ) {
608 $revRecord = $this->createRevisionRecord( $row, $page );
609 $attribs['data-mw-revid'] = $revRecord->getId();
610
611 $link = $linkRenderer->makeLink(
612 $page,
613 $page->getPrefixedText(),
614 [ 'class' => 'mw-contributions-title' ],
615 $page->isRedirect() ? [ 'redirect' => 'no' ] : []
616 );
617 # Mark current revisions
618 $topmarktext = '';
619
620 // Add links for seeing history, diff, etc.
621 if ( $this->isArchive ) {
622 // Add the same links as DeletedContribsPager::formatRevisionRow
623 $undelete = SpecialPage::getTitleFor( 'Undelete' );
624 if ( $this->getAuthority()->isAllowed( 'deletedtext' ) ) {
625 $last = $linkRenderer->makeKnownLink(
626 $undelete,
627 new HtmlArmor( $this->messages['diff'] ),
628 [],
629 [
630 'target' => $page->getPrefixedText(),
631 'timestamp' => $revRecord->getTimestamp(),
632 'diff' => 'prev'
633 ]
634 );
635 } else {
636 $last = $this->messages['diff'];
637 }
638
639 $logs = SpecialPage::getTitleFor( 'Log' );
640 $dellog = $linkRenderer->makeKnownLink(
641 $logs,
642 new HtmlArmor( $this->messages['deletionlog'] ),
643 [],
644 [
645 'type' => 'delete',
646 'page' => $page->getPrefixedText()
647 ]
648 );
649
650 $reviewlink = $linkRenderer->makeKnownLink(
651 SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
652 new HtmlArmor( $this->messages['undeleteviewlink'] )
653 );
654
655 $diffHistLinks = Html::rawElement(
656 'span',
657 [ 'class' => 'mw-deletedcontribs-tools' ],
658 $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList(
659 [ $last, $dellog, $reviewlink ] ) )->escaped()
660 );
661
662 } else {
663 $pagerTools = new PagerTools(
664 $revRecord,
665 null,
666 $row->{$this->revisionIdField} === $row->page_latest && !$row->page_is_new,
667 $this->hookRunner,
668 $page,
669 $this->getContext(),
670 $this->getLinkRenderer()
671 );
672 if ( $row->{$this->revisionIdField} === $row->page_latest ) {
673 $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
674 $classes[] = 'mw-contributions-current';
675 }
676 if ( $pagerTools->shouldPreventClickjacking() ) {
677 $this->setPreventClickjacking( true );
678 }
679 $topmarktext .= $pagerTools->toHTML();
680 # Is there a visible previous revision?
681 if ( $revRecord->getParentId() !== 0 &&
682 $revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() )
683 ) {
684 $difftext = $linkRenderer->makeKnownLink(
685 $page,
686 new HtmlArmor( $this->messages['diff'] ),
687 [ 'class' => 'mw-changeslist-diff' ],
688 [
689 'diff' => 'prev',
690 'oldid' => $row->{$this->revisionIdField},
691 ]
692 );
693 } else {
694 $difftext = $this->messages['diff'];
695 }
696 $histlink = $linkRenderer->makeKnownLink(
697 $page,
698 new HtmlArmor( $this->messages['hist'] ),
699 [ 'class' => 'mw-changeslist-history' ],
700 [ 'action' => 'history' ]
701 );
702
703 // While it might be tempting to use a list here
704 // this would result in clutter and slows down navigating the content
705 // in assistive technology.
706 // See https://phabricator.wikimedia.org/T205581#4734812
707 $diffHistLinks = Html::rawElement( 'span',
708 [ 'class' => 'mw-changeslist-links' ],
709 // The spans are needed to ensure the dividing '|' elements are not
710 // themselves styled as links.
711 Html::rawElement( 'span', [], $difftext ) .
712 ' ' . // Space needed for separating two words.
713 Html::rawElement( 'span', [], $histlink )
714 );
715 }
716
717 if ( $row->{$this->revisionParentIdField} === null ) {
718 // For some reason rev_parent_id isn't populated for this row.
719 // Its rumoured this is true on wikipedia for some revisions (T36922).
720 // Next best thing is to have the total number of bytes.
721 $chardiff = ' <span class="mw-changeslist-separator"></span> ';
722 $chardiff .= Linker::formatRevisionSize( $row->{$this->revisionLengthField} );
723 $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
724 } else {
725 $parentLen = 0;
726 if ( isset( $this->mParentLens[$row->{$this->revisionParentIdField}] ) ) {
727 $parentLen = $this->mParentLens[$row->{$this->revisionParentIdField}];
728 }
729
730 $chardiff = ' <span class="mw-changeslist-separator"></span> ';
732 $parentLen,
733 $row->{$this->revisionLengthField},
734 $this->getContext()
735 );
736 $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
737 }
738
739 $lang = $this->getLanguage();
740
741 $comment = $this->formattedComments[$row->{$this->revisionIdField}];
742
743 if ( $comment === '' ) {
744 $defaultComment = $this->messages['changeslist-nocomment'];
745 $comment = "<span class=\"comment mw-comment-none\">$defaultComment</span>";
746 }
747
748 $comment = $lang->getDirMark() . $comment;
749
750 $authority = $this->getAuthority();
751 $d = ChangesList::revDateLink( $revRecord, $authority, $lang, $page );
752
753 // When the author is different from the target, always show user and user talk links
754 $userlink = '';
755 $revUser = $revRecord->getUser();
756 $revUserId = $revUser ? $revUser->getId() : 0;
757 $revUserText = $revUser ? $revUser->getName() : '';
758 if ( $this->target !== $revUserText ) {
759 $userlink = ' <span class="mw-changeslist-separator"></span> '
760 . $lang->getDirMark()
761 . Linker::userLink( $revUserId, $revUserText );
762 $userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
763 Linker::userTalkLink( $revUserId, $revUserText ) )->escaped() . ' ';
764 }
765
766 $flags = [];
767 if ( $revRecord->getParentId() === 0 ) {
768 $flags[] = ChangesList::flag( 'newpage' );
769 }
770
771 if ( $revRecord->isMinor() ) {
772 $flags[] = ChangesList::flag( 'minor' );
773 }
774
775 $del = Linker::getRevDeleteLink( $authority, $revRecord, $page );
776 if ( $del !== '' ) {
777 $del .= ' ';
778 }
779
780 # Tags, if any. Save some time using a cache.
781 [ $tagSummary, $newClasses ] = $this->tagsCache->getWithSetCallback(
782 $this->tagsCache->makeKey(
783 $row->ts_tags ?? '',
784 $this->getUser()->getName(),
785 $lang->getCode()
786 ),
788 $row->ts_tags,
789 null,
790 $this->getContext()
791 )
792 );
793 $classes = array_merge( $classes, $newClasses );
794
795 if ( !$this->isArchive ) {
796 $this->hookRunner->onSpecialContributions__formatRow__flags(
797 $this->getContext(), $row, $flags );
798 }
799
800 $templateParams = [
801 'del' => $del,
802 'timestamp' => $d,
803 'diffHistLinks' => $diffHistLinks,
804 'charDifference' => $chardiff,
805 'flags' => $flags,
806 'articleLink' => $link,
807 'userlink' => $userlink,
808 'logText' => $comment,
809 'topmarktext' => $topmarktext,
810 'tagSummary' => $tagSummary,
811 ];
812
813 # Denote if username is redacted for this edit
814 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
815 $templateParams['rev-deleted-user-contribs'] =
816 $this->msg( 'rev-deleted-user-contribs' )->escaped();
817 }
818
819 $ret = $this->templateParser->processTemplate(
820 'SpecialContributionsLine',
821 $templateParams
822 );
823 }
824
825 if ( !$this->isArchive ) {
826 // Let extensions add data
827 $this->hookRunner->onContributionsLineEnding( $this, $ret, $row, $classes, $attribs );
828 $attribs = array_filter( $attribs,
829 [ Sanitizer::class, 'isReservedDataAttribute' ],
830 ARRAY_FILTER_USE_KEY
831 );
832 }
833
834 // TODO: Handle exceptions in the catch block above. Do any extensions rely on
835 // receiving empty rows?
836
837 if ( $classes === [] && $attribs === [] && $ret === '' ) {
838 wfDebug( "Dropping Special:Contribution row that could not be formatted" );
839 return "<!-- Could not format Special:Contribution row. -->\n";
840 }
841 $attribs['class'] = $classes;
842
843 // FIXME: The signature of the ContributionsLineEnding hook makes it
844 // very awkward to move this LI wrapper into the template.
845 return Html::rawElement( 'li', $attribs, $ret ) . "\n";
846 }
847
852 protected function getSqlComment() {
853 if ( $this->namespace || $this->deletedOnly ) {
854 // potentially slow, see CR r58153
855 return 'contributions page filtered for namespace or RevisionDeleted edits';
856 } else {
857 return 'contributions page unfiltered';
858 }
859 }
860
864 protected function preventClickjacking() {
865 $this->setPreventClickjacking( true );
866 }
867
872 protected function setPreventClickjacking( bool $enable ) {
873 $this->preventClickjacking = $enable;
874 }
875
879 public function getPreventClickjacking() {
880 return $this->preventClickjacking;
881 }
882
883}
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.