62 private $notificationTimestamp;
65 private $revisionStore;
68 private $watchlistManager;
71 private $linkBatchFactory;
74 private $commentFormatter;
82 private $revisions = [];
87 private $formattedComments = [];
115 $this->tagFilter = $tagFilter;
119 $services = MediaWikiServices::getInstance();
120 $this->revisionStore = $services->getRevisionStore();
121 $this->linkBatchFactory = $linkBatchFactory ?? $services->getLinkBatchFactory();
122 $this->watchlistManager = $watchlistManager
123 ?? $services->getWatchlistManager();
124 $this->commentFormatter = $commentFormatter ?? $services->getCommentFormatter();
125 $this->hookRunner =
new HookRunner( $hookContainer ?? $services->getHookContainer() );
126 $this->notificationTimestamp = $this->
getConfig()->get( MainConfigNames::ShowUpdatedMarker )
127 ? $this->watchlistManager->getTitleNotificationTimestamp( $this->
getUser(), $this->
getTitle() )
133 return $this->historyPage->getArticle();
137 if ( $this->conds ) {
138 return 'history page filtered';
140 return 'history page unfiltered';
145 $revQuery = $this->revisionStore->getQueryInfo( [
'user' ] );
150 'conds' => array_merge(
153 'options' => [
'USE INDEX' => [
'revision' =>
'rev_page_timestamp' ] ],
157 $queryInfo[
'tables'],
158 $queryInfo[
'fields'],
160 $queryInfo[
'join_conds'],
161 $queryInfo[
'options'],
165 $this->hookRunner->onPageHistoryPager__getQueryInfo( $this, $queryInfo );
171 return [ [
'rev_timestamp',
'rev_id' ] ];
175 if ( !$this->hookRunner->onPageHistoryPager__doBatchLookups( $this, $this->mResult ) ) {
179 # Do a link batch query
180 $batch = $this->linkBatchFactory->newLinkBatch();
183 foreach ( $this->mResult as $row ) {
184 if ( $row->rev_parent_id ) {
185 $revIds[] = (int)$row->rev_parent_id;
187 if ( $row->user_name !==
null ) {
188 $batch->add(
NS_USER, $row->user_name );
190 }
else { #
for anons or usernames of imported revisions
191 $batch->add(
NS_USER, $row->rev_user_text );
194 $this->revisions[] = $this->revisionStore->newRevisionFromRow(
196 RevisionStore::READ_NORMAL,
200 $this->parentLens = $this->revisionStore->getRevisionSizes( $revIds );
203 # The keys of $this->formattedComments will be the same as the keys of $this->revisions
204 $this->formattedComments = $this->commentFormatter->createRevisionBatch()
205 ->revisions( $this->revisions )
208 ->hideIfDeleted(
true )
209 ->useParentheses(
false )
212 $this->mResult->seek( 0 );
220 return $this->
msg(
'history-empty' )->escaped();
229 $this->oldIdChecked = 0;
234 $this->
getOutput()->wrapWikiMsg(
"<div class='mw-history-legend'>\n$1\n</div>",
'histlegend' );
236 $s = Html::openElement(
'form', [
238 'id' =>
'mw-history-compare'
240 $s .= Html::hidden(
'title', $this->
getTitle()->getPrefixedDBkey() ) .
"\n";
241 $s .= Html::hidden(
'type',
'revision', [
'form' =>
'mw-history-revisionactions' ] ) .
"\n";
243 $this->buttons .= Html::openElement(
244 'div', [
'class' =>
'mw-history-compareselectedversions' ] );
245 $className =
'historysubmit mw-history-compareselectedversions-button mw-ui-button';
246 $attrs = [
'class' => $className ]
247 + Linker::tooltipAndAccesskeyAttribs(
'compareselectedversions' );
248 $this->buttons .= $this->submitButton( $this->
msg(
'compareselectedversions' )->text(),
253 if ( $this->
getAuthority()->isAllowed(
'deleterevision' ) ) {
254 $actionButtons .= $this->getRevisionButton(
255 'revisiondelete',
'showhideselectedversions' );
257 if ( $this->showTagEditUI ) {
258 $actionButtons .= $this->getRevisionButton(
259 'editchangetags',
'history-edit-tags' );
261 if ( $actionButtons ) {
268 $s = Html::rawElement(
'form', [
270 'id' =>
'mw-history-revisionactions',
271 ], Html::hidden(
'title', $this->
getTitle()->getPrefixedDBkey() ) ) .
"\n" .
$s;
273 $this->buttons .=
Xml::tags(
'div', [
'class' =>
274 'mw-history-revisionactions' ], $actionButtons );
277 if ( $this->
getAuthority()->isAllowed(
'deleterevision' ) || $this->showTagEditUI ) {
281 $this->buttons .=
'</div>';
286 $s .=
'<section id="pagehistory" class="mw-pager-body">';
291 private function getRevisionButton( $name, $msg ) {
292 $this->preventClickjacking =
true;
293 $element = Html::element(
299 'class' =>
"historysubmit mw-history-$name-button mw-ui-button",
300 'form' =>
'mw-history-revisionactions',
302 $this->
msg( $msg )->text()
312 # Add second buttons only if there is more than one rev
328 private function submitButton( $message, $attributes = [] ) {
329 # Disable submit button if history has 1 revision only
331 return Html::submitButton( $message, $attributes );
345 $numRows = min( $this->mResult->numRows(), $this->mLimit );
347 $firstInList = $resultOffset === ( $this->mIsBackwards ? $numRows - 1 : 0 );
349 $nextResultOffset = $resultOffset + ( $this->mIsBackwards ? -1 : 1 );
351 $revRecord = $this->revisions[$resultOffset];
353 $previousRevRecord = $this->revisions[$nextResultOffset] ??
null;
355 $latest = $revRecord->getId() === $this->
getWikiPage()->getLatest();
356 $curlink = $this->curLink( $revRecord );
357 if ( $previousRevRecord ) {
359 $lastlink = $this->lastLink( $revRecord, $previousRevRecord );
360 } elseif ( $this->mIsBackwards && $this->mOffset !==
'' ) {
366 $lastlink = $this->lastLink( $revRecord,
null );
369 $lastlink = $this->historyPage->message[
'last'];
371 $curLastlinks = Html::rawElement(
'span', [], $curlink ) .
372 Html::rawElement(
'span', [], $lastlink );
373 $histLinks = Html::rawElement(
375 [
'class' =>
'mw-history-histlinks mw-changeslist-links' ],
379 $diffButtons = $this->diffButtons( $revRecord, $firstInList );
380 $s = $histLinks . $diffButtons;
382 $link = $this->revLink( $revRecord );
386 $canRevDelete = $this->
getAuthority()->isAllowed(
'deleterevision' );
389 if ( $canRevDelete || $this->showTagEditUI ) {
390 $this->preventClickjacking =
true;
393 if ( !$this->showTagEditUI
394 && !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $this->getAuthority() )
396 $del =
Xml::check(
'deleterevisions',
false, [
'disabled' =>
'disabled' ] );
399 $del =
Xml::check(
'showhiderevisions',
false,
400 [
'name' =>
'ids[' . $revRecord->getId() .
']',
'form' =>
'mw-history-revisionactions' ] );
403 } elseif ( $revRecord->getVisibility() && $this->getAuthority()->isAllowed(
'deletedhistory' ) ) {
405 if ( !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $this->getAuthority() ) ) {
406 $del = Linker::revDeleteLinkDisabled(
false );
410 'type' =>
'revision',
411 'target' => $this->
getTitle()->getPrefixedDBkey(),
412 'ids' => $revRecord->getId()
414 $del .= Linker::revDeleteLink(
416 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
426 $dirmark =
$lang->getDirMark();
430 $s .=
" <span class='history-user'>" .
431 Linker::revUserTools( $revRecord,
true,
false ) .
"</span>";
434 if ( $revRecord->isMinor() ) {
438 # Sometimes rev_len isn't populated
439 if ( $revRecord->getSize() !==
null ) {
440 # Size is always public data
441 $prevSize = $this->parentLens[$row->rev_parent_id] ?? 0;
443 $fSize = Linker::formatRevisionSize( $revRecord->getSize() );
444 $s .=
' <span class="mw-changeslist-separator"></span> ' .
"$fSize $sDiff";
447 # Text following the character difference is added just before running hooks
448 $s2 = $this->formattedComments[$resultOffset];
451 $defaultComment = $this->
msg(
'changeslist-nocomment' )->escaped();
452 $s2 =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
455 if ( $this->notificationTimestamp && $row->rev_timestamp >= $this->notificationTimestamp ) {
456 $s2 .=
' <span class="updatedmarker">' . $this->
msg(
'updatedmarker' )->escaped() .
'</span>';
457 $classes[] =
'mw-history-line-updated';
463 $latest && $previousRevRecord,
469 if ( $pagerTools->shouldPreventClickjacking() ) {
470 $this->preventClickjacking =
true;
472 $s2 .= $pagerTools->toHTML();
480 $classes = array_merge( $classes, $newClasses );
481 if ( $tagSummary !==
'' ) {
482 $s2 .=
" $tagSummary";
485 # Include separator between character difference and following text
486 $s .=
' <span class="mw-changeslist-separator"></span> ' . $s2;
488 $attribs = [
'data-mw-revid' => $revRecord->getId() ];
490 $this->hookRunner->onPageHistoryLineEnding( $this, $row,
$s, $classes, $attribs );
491 $attribs = array_filter( $attribs,
492 [ Sanitizer::class,
'isReservedDataAttribute' ],
497 $attribs[
'class'] = implode(
' ', $classes );
521 $cur = $this->historyPage->message[
'cur'];
523 if ( $latest === $rev->
getId()
524 || !$rev->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() )
532 'title' => $this->historyPage->message[
'tooltip-cur']
536 'oldid' => $rev->
getId()
551 $last = $this->historyPage->message[
'last'];
553 if ( !$prevRev->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ||
554 ( $nextRev && !$nextRev->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) )
563 'title' => $this->historyPage->message[
'tooltip-last']
567 'oldid' => $prevRev->
getId()
580 private function diffButtons(
RevisionRecord $rev, $firstInList ) {
583 $radio = [
'type' =>
'radio',
'value' => $id ];
585 if ( $firstInList ) {
587 array_merge( $radio, [
592 'id' =>
'mw-oldid-null' ] )
594 $checkmark = [
'checked' =>
'checked' ];
596 # Check visibility of old revisions
597 if ( !$rev->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
598 $radio[
'disabled'] =
'disabled';
600 } elseif ( !$this->oldIdChecked ) {
601 $checkmark = [
'checked' =>
'checked' ];
602 $this->oldIdChecked = $id;
607 array_merge( $radio, $checkmark, [
609 'id' =>
"mw-oldid-$id" ] ) );
613 array_merge( $radio, $checkmark, [
615 'id' =>
"mw-diff-$id" ] ) );
617 return $first . $second;
632 return parent::isNavigationBarShown();
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
static revDateLink(RevisionRecord $rev, Authority $performer, Language $lang, $title=null)
Render the date and time of a revision in the current user language based on whether the user is able...
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.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getWikiPage()
Get the WikiPage object.
getContext()
Get the base IContextSource object.
This class handles printing the history page for an article.
Marks HTML that shouldn't be escaped.
A class containing constants representing the names of configuration variables.
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
if(!isset( $args[0])) $lang