58 private $revisionStore;
61 private $watchlistManager;
64 private $linkBatchFactory;
67 private $commentFormatter;
72 private $revisions = [];
77 private $formattedComments = [];
101 parent::__construct( $historyPage->
getContext() );
102 $this->historyPage = $historyPage;
103 $this->tagFilter = $tagFilter;
105 $this->conds = $conds;
107 $services = MediaWikiServices::getInstance();
108 $this->revisionStore = $services->getRevisionStore();
109 $this->linkBatchFactory = $linkBatchFactory ?? $services->getLinkBatchFactory();
110 $this->watchlistManager = $watchlistManager
111 ?? $services->getWatchlistManager();
112 $this->commentFormatter = $commentFormatter ?? $services->getCommentFormatter();
117 return $this->historyPage->getArticle();
121 if ( $this->conds ) {
122 return 'history page filtered';
124 return 'history page unfiltered';
129 $revQuery = $this->revisionStore->getQueryInfo( [
'user' ] );
134 'conds' => array_merge(
137 'options' => [
'USE INDEX' => [
'revision' =>
'rev_page_timestamp' ] ],
141 $queryInfo[
'tables'],
142 $queryInfo[
'fields'],
144 $queryInfo[
'join_conds'],
145 $queryInfo[
'options'],
149 $this->getHookRunner()->onPageHistoryPager__getQueryInfo( $this, $queryInfo );
155 return [ [
'rev_timestamp',
'rev_id' ] ];
163 $notifTimestamp = $this->
getConfig()->get( MainConfigNames::ShowUpdatedMarker )
164 ? $this->watchlistManager
168 return $this->historyLine( $row, $notifTimestamp, $this->
getResultOffset() );
172 if ( !$this->getHookRunner()->onPageHistoryPager__doBatchLookups( $this, $this->mResult ) ) {
176 # Do a link batch query
177 $batch = $this->linkBatchFactory->newLinkBatch();
180 foreach ( $this->mResult as $row ) {
181 if ( $row->rev_parent_id ) {
182 $revIds[] = (int)$row->rev_parent_id;
184 if ( $row->user_name !==
null ) {
185 $batch->add(
NS_USER, $row->user_name );
187 }
else { #
for anons or usernames of imported revisions
188 $batch->add(
NS_USER, $row->rev_user_text );
191 $this->revisions[] = $this->revisionStore->newRevisionFromRow(
193 RevisionStore::READ_NORMAL,
197 $this->parentLens = $this->revisionStore->getRevisionSizes( $revIds );
200 # The keys of $this->formattedComments will be the same as the keys of $this->revisions
201 $this->formattedComments = $this->commentFormatter->createRevisionBatch()
202 ->revisions( $this->revisions )
205 ->hideIfDeleted(
true )
206 ->useParentheses(
false )
209 $this->mResult->seek( 0 );
217 return $this->
msg(
'history-empty' )->escaped();
226 $this->oldIdChecked = 0;
231 $this->
getOutput()->wrapWikiMsg(
"<div class='mw-history-legend'>\n$1\n</div>",
'histlegend' );
232 $s = Html::openElement(
'form', [
234 'id' =>
'mw-history-compare'
236 $s .= Html::hidden(
'title', $this->
getTitle()->getPrefixedDBkey() ) .
"\n";
237 $s .= Html::hidden(
'action',
'historysubmit' ) .
"\n";
238 $s .= Html::hidden(
'type',
'revision' ) .
"\n";
240 $this->buttons .= Html::openElement(
241 'div', [
'class' =>
'mw-history-compareselectedversions' ] );
242 $className =
'historysubmit mw-history-compareselectedversions-button mw-ui-button';
243 $attrs = [
'class' => $className ]
245 $this->buttons .= $this->submitButton( $this->
msg(
'compareselectedversions' )->text(),
250 if ( $this->
getAuthority()->isAllowed(
'deleterevision' ) ) {
251 $actionButtons .= $this->getRevisionButton(
252 'revisiondelete',
'showhideselectedversions' );
254 if ( $this->showTagEditUI ) {
255 $actionButtons .= $this->getRevisionButton(
256 'editchangetags',
'history-edit-tags' );
258 if ( $actionButtons ) {
259 $this->buttons .= Xml::tags(
'div', [
'class' =>
260 'mw-history-revisionactions' ], $actionButtons );
263 if ( $this->
getAuthority()->isAllowed(
'deleterevision' ) || $this->showTagEditUI ) {
267 $this->buttons .=
'</div>';
269 $s .= $this->buttons;
272 $s .=
'<section id="pagehistory" class="mw-pager-body">';
277 private function getRevisionButton( $name, $msg ) {
278 $this->setPreventClickjacking(
true );
279 $element = Html::element(
285 'class' =>
"historysubmit mw-history-$name-button mw-ui-button",
287 $this->msg( $msg )->text()
297 # Add second buttons only if there is more than one rev
299 $s .= $this->buttons;
313 private function submitButton( $message, $attributes = [] ) {
314 # Disable submit button if history has 1 revision only
315 if ( $this->getNumRows() > 1 ) {
316 return Html::submitButton( $message, $attributes );
332 private function historyLine( $row, $notificationtimestamp, $resultOffset ) {
333 $numRows = min( $this->mResult->numRows(), $this->mLimit );
335 $firstInList = $resultOffset === ( $this->mIsBackwards ? $numRows - 1 : 0 );
337 $nextResultOffset = $resultOffset + ( $this->mIsBackwards ? -1 : 1 );
339 $revRecord = $this->revisions[$resultOffset];
341 $previousRevRecord = $this->revisions[$nextResultOffset] ??
null;
343 $latest = $revRecord->getId() === $this->
getWikiPage()->getLatest();
344 $curlink = $this->curLink( $revRecord );
345 if ( $previousRevRecord ) {
347 $lastlink = $this->lastLink( $revRecord, $previousRevRecord );
348 } elseif ( $this->mIsBackwards && $this->mOffset !==
'' ) {
354 $lastlink = $this->lastLink( $revRecord,
null );
357 $lastlink = $this->historyPage->message[
'last'];
363 [
'class' =>
'mw-history-histlinks mw-changeslist-links' ],
367 $diffButtons = $this->diffButtons( $revRecord, $firstInList );
368 $s = $histLinks . $diffButtons;
370 $link = $this->revLink( $revRecord );
375 $canRevDelete = $this->
getAuthority()->isAllowed(
'deleterevision' );
378 if ( $canRevDelete || $this->showTagEditUI ) {
379 $this->setPreventClickjacking(
true );
382 if ( !$this->showTagEditUI
383 && !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $this->getAuthority() )
385 $del =
Xml::check(
'deleterevisions',
false, [
'disabled' =>
'disabled' ] );
388 $del =
Xml::check(
'showhiderevisions',
false,
389 [
'name' =>
'ids[' . $revRecord->getId() .
']' ] );
392 } elseif ( $revRecord->getVisibility() && $this->getAuthority()->isAllowed(
'deletedhistory' ) ) {
394 if ( !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $this->getAuthority() ) ) {
399 'type' =>
'revision',
400 'target' => $this->
getTitle()->getPrefixedDBkey(),
401 'ids' => $revRecord->getId()
405 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
415 $dirmark =
$lang->getDirMark();
419 $s .=
" <span class='history-user'>" .
423 if ( $revRecord->isMinor() ) {
427 # Sometimes rev_len isn't populated
428 if ( $revRecord->getSize() !==
null ) {
429 # Size is always public data
430 $prevSize = $this->parentLens[$row->rev_parent_id] ?? 0;
433 $s .=
' <span class="mw-changeslist-separator"></span> ' .
"$fSize $sDiff";
436 # Text following the character difference is added just before running hooks
437 $s2 = $this->formattedComments[$resultOffset];
440 $defaultComment = $this->
msg(
'changeslist-nocomment' )->escaped();
441 $s2 =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
444 if ( $notificationtimestamp && ( $row->rev_timestamp >= $notificationtimestamp ) ) {
445 $s2 .=
' <span class="updatedmarker">' . $this->
msg(
'updatedmarker' )->escaped() .
'</span>';
446 $classes[] =
'mw-history-line-updated';
451 # Rollback and undo links
460 [
'verify',
'noBrackets' ]
462 if ( $rollbackLink ) {
463 $this->setPreventClickjacking(
true );
464 $tools[
'mw-rollback'] = $rollbackLink;
468 if ( !$revRecord->isDeleted( RevisionRecord::DELETED_TEXT )
469 && !$previousRevRecord->isDeleted( RevisionRecord::DELETED_TEXT )
471 # Create undo tooltip for the first (=latest) line only
472 $undoTooltip = $latest
473 ? [
'title' => $this->
msg(
'tooltip-undo' )->text() ]
477 $this->
msg(
'editundo' )->text(),
481 'undoafter' => $previousRevRecord->getId(),
482 'undo' => $revRecord->getId()
485 $tools[
'mw-undo'] =
"<span class=\"mw-history-undo\">{$undolink}</span>";
489 $this->getHookRunner()->onHistoryTools(
498 foreach ( $tools as $tool ) {
510 $classes = array_merge( $classes, $newClasses );
511 if ( $tagSummary !==
'' ) {
512 $s2 .=
" $tagSummary";
515 # Include separator between character difference and following text
516 $s .=
' <span class="mw-changeslist-separator"></span> ' . $s2;
518 $attribs = [
'data-mw-revid' => $revRecord->getId() ];
520 $this->getHookRunner()->onPageHistoryLineEnding( $this, $row,
$s, $classes, $attribs );
521 $attribs = array_filter( $attribs,
522 [ Sanitizer::class,
'isReservedDataAttribute' ],
527 $attribs[
'class'] = implode(
' ', $classes );
551 $cur = $this->historyPage->message[
'cur'];
553 if ( $latest === $rev->
getId()
554 || !$rev->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() )
562 'title' => $this->historyPage->message[
'tooltip-cur']
566 'oldid' => $rev->
getId()
581 $last = $this->historyPage->message[
'last'];
583 if ( !$prevRev->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ||
584 ( $nextRev && !$nextRev->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) )
593 'title' => $this->historyPage->message[
'tooltip-last']
596 'diff' => $prevRev->
getId(),
597 'oldid' => $nextRev ? $nextRev->
getId() :
'prev'
610 private function diffButtons(
RevisionRecord $rev, $firstInList ) {
613 $radio = [
'type' =>
'radio',
'value' => $id ];
615 if ( $firstInList ) {
617 array_merge( $radio, [
622 'id' =>
'mw-oldid-null' ] )
624 $checkmark = [
'checked' =>
'checked' ];
626 # Check visibility of old revisions
627 if ( !$rev->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
628 $radio[
'disabled'] =
'disabled';
630 } elseif ( !$this->oldIdChecked ) {
631 $checkmark = [
'checked' =>
'checked' ];
632 $this->oldIdChecked = $id;
637 array_merge( $radio, $checkmark, [
639 'id' =>
"mw-oldid-$id" ] ) );
643 array_merge( $radio, $checkmark, [
645 'id' =>
"mw-diff-$id" ] ) );
647 return $first . $second;
662 return parent::isNavigationBarShown();
669 parent::getDefaultQuery();
670 unset( $this->mDefaultQuery[
'date-range-to'] );
671 return $this->mDefaultQuery;
679 private function setPreventClickjacking(
bool $flag ) {
680 $this->preventClickjacking = $flag;
688 return $this->preventClickjacking;
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
This class handles printing the history page for an article.