246 $refreshCache =
false,
$unhide =
false
262 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'" );
264 $this->mOldid = $old;
265 $this->mNewid = $new;
266 $this->mRefreshCache = $refreshCache;
269 $services = MediaWikiServices::getInstance();
270 $this->linkRenderer = $services->getLinkRenderer();
271 $this->contentHandlerFactory = $services->getContentHandlerFactory();
272 $this->revisionStore = $services->getRevisionStore();
273 $this->hookContainer = $services->getHookContainer();
274 $this->hookRunner =
new HookRunner( $this->hookContainer );
275 $this->wikiPageFactory = $services->getWikiPageFactory();
283 if ( $this->isSlotDiffRenderer ) {
284 throw new LogicException( __METHOD__ .
' called in slot diff renderer mode' );
287 if ( $this->slotDiffRenderers ===
null ) {
293 $this->slotDiffRenderers = array_map(
function ( $contents ) {
295 $content = $contents[
'new'] ?: $contents[
'old'];
298 return $content->getContentHandler()->getSlotDiffRenderer(
300 $this->slotDiffOptions
314 $this->isSlotDiffRenderer =
true;
323 if ( $this->isContentOverridden ) {
325 SlotRecord::MAIN => [
334 $newSlots = $this->mNewRevisionRecord->getSlots()->getSlots();
335 if ( $this->mOldRevisionRecord ) {
336 $oldSlots = $this->mOldRevisionRecord->getSlots()->getSlots();
344 $roles = array_merge( array_keys( $newSlots ), array_keys( $oldSlots ) );
347 foreach ( $roles as $role ) {
349 'old' => isset( $oldSlots[$role] ) ? $oldSlots[$role]->getContent() :
null,
350 'new' => isset( $newSlots[$role] ) ? $newSlots[$role]->getContent() :
null,
354 if ( isset( $slots[SlotRecord::MAIN] ) ) {
355 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
373 $this->mReducedLineNumbers = $value;
382 if ( $this->mDiffLang ===
null ) {
383 # Default language in which the diff text is written.
384 $this->mDiffLang = $this->
getTitle()->getPageLanguage();
429 return $this->mOldRevisionRecord ?:
null;
450 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
451 if ( $permissionManager->userHasRight( $this->getUser(),
'deletedhistory' ) ) {
454 $arQuery =
$revStore->getArchiveQueryInfo();
455 $row =
$dbr->selectRow(
457 array_merge( $arQuery[
'fields'], [
'ar_namespace',
'ar_title' ] ),
458 [
'ar_rev_id' => $id ],
464 $revRecord =
$revStore->newRevisionFromArchiveRow( $row );
468 'target' =>
$title->getPrefixedText(),
469 'timestamp' => $revRecord->getTimestamp()
487 return "[$link $id]";
497 if ( $this->mOldRevisionRecord ===
null ||
498 ( $this->mOldRevisionRecord && $this->mOldContent ===
null )
502 if ( $this->mNewRevisionRecord ===
null ||
503 ( $this->mNewRevisionRecord && $this->mNewContent ===
null )
508 $out->setPageTitle( $this->
msg(
'errorpagetitle' ) );
509 $msg = $this->
msg(
'difference-missing-revision' )
510 ->params( $this->
getLanguage()->listToText( $missing ) )
511 ->numParams( count( $missing ) )
513 $out->addHTML( $msg );
524 $this->mNewRevisionRecord &&
525 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
528 $this->mOldRevisionRecord &&
529 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
542 $permManager = MediaWikiServices::getInstance()->getPermissionManager();
543 if ( $this->mNewPage ) {
544 $permErrors = $permManager->getPermissionErrors(
'read', $user, $this->mNewPage );
546 if ( $this->mOldPage ) {
548 $permManager->getPermissionErrors(
'read', $user, $this->mOldPage ) );
560 ( $this->mOldRevisionRecord &&
561 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) ||
562 ( $this->mNewRevisionRecord &&
563 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) )
582 $allowed = !$this->mNewRevisionRecord || RevisionRecord::userCanBitfield(
583 $this->mNewRevisionRecord->getVisibility(),
584 RevisionRecord::DELETED_TEXT,
587 if ( $this->mOldRevisionRecord &&
588 !RevisionRecord::userCanBitfield(
589 $this->mOldRevisionRecord->getVisibility(),
590 RevisionRecord::DELETED_TEXT,
615 # Allow frames except in certain special cases
617 $out->allowClickjacking();
618 $out->setRobotPolicy(
'noindex,nofollow' );
621 $this->hookRunner->onDifferenceEngineShowDiffPage( $out );
624 if ( $this->hookRunner->onDifferenceEngineShowDiffPageMaybeShowMissingRevision( $this ) ) {
632 if ( count( $permErrors ) ) {
639 # Carry over 'diffonly' param via navigation links
640 if ( $diffOnly != $user->getBoolOption(
'diffonly' ) ) {
641 $query[
'diffonly'] = $diffOnly;
643 # Cascade unhide param in links for easy deletion browsing
644 if ( $this->unhide ) {
645 $query[
'unhide'] = 1;
648 # Check if one of the revisions is deleted/suppressed
655 # mOldRevisionRecord is false if the difference engine is called with a "vague" query for
656 # a diff between a version V and its previous version V' AND the version V
657 # is the first version of that article. In that case, V' does not exist.
658 if ( $this->mOldRevisionRecord ===
false ) {
659 if ( $this->mNewPage ) {
660 $out->setPageTitle( $this->
msg(
'difference-title', $this->mNewPage->getPrefixedText() ) );
665 $this->hookRunner->onDifferenceEngineOldHeaderNoOldRev( $oldHeader );
667 $this->hookRunner->onDifferenceEngineViewHeader( $this );
670 if ( $this->hookContainer->isRegistered(
'DiffViewHeader' ) ) {
673 $legacyOldRev = $this->mOldRevisionRecord ?
674 new Revision( $this->mOldRevisionRecord ) :
676 $legacyNewRev = $this->mNewRevisionRecord ?
677 new Revision( $this->mNewRevisionRecord ) :
679 $this->hookRunner->onDiffViewHeader(
686 if ( !$this->mOldPage || !$this->mNewPage ) {
689 } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
690 $out->setPageTitle( $this->
msg(
'difference-title', $this->mNewPage->getPrefixedText() ) );
693 $out->setPageTitle( $this->
msg(
'difference-title-multipage',
694 $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
695 $out->addSubtitle( $this->
msg(
'difference-multipage' ) );
699 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
701 if ( $samePage && $this->mNewPage && $permissionManager->quickUserCan(
702 'edit', $user, $this->mNewPage
704 if ( $this->mNewRevisionRecord->isCurrent() && $permissionManager->quickUserCan(
705 'rollback', $user, $this->mNewPage
708 $this->mNewRevisionRecord,
712 if ( $rollbackLink ) {
713 $out->preventClickjacking();
714 $rollback =
"\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
718 if ( $this->
userCanEdit( $this->mOldRevisionRecord ) &&
722 'href' => $this->mNewPage->getLocalURL( [
724 'undoafter' => $this->mOldid,
725 'undo' => $this->mNewid
729 $this->msg(
'editundo' )->text()
731 $revisionTools[
'mw-diff-undo'] = $undoLink;
734 # Make "previous revision link"
735 $hasPrevious = $samePage && $this->mOldPage &&
736 $this->revisionStore->getPreviousRevision( $this->mOldRevisionRecord );
737 if ( $hasPrevious ) {
738 $prevlink = $this->linkRenderer->makeKnownLink(
740 $this->
msg(
'previousdiff' )->text(),
741 [
'id' =>
'differences-prevlink' ],
742 [
'diff' =>
'prev',
'oldid' => $this->mOldid ] + $query
745 $prevlink =
"\u{00A0}";
748 if ( $this->mOldRevisionRecord->isMinor() ) {
760 $oldHeader =
'<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader .
'</strong></div>' .
761 '<div id="mw-diff-otitle2">' .
763 '<div id="mw-diff-otitle3">' . $oldminor .
765 '<div id="mw-diff-otitle5">' . $oldChangeTags[0] .
'</div>' .
766 '<div id="mw-diff-otitle4">' . $prevlink .
'</div>';
769 $this->hookRunner->onDifferenceEngineOldHeader(
770 $this, $oldHeader, $prevlink, $oldminor, $diffOnly, $ldel, $this->unhide );
773 $out->addJsConfigVars( [
774 'wgDiffOldId' => $this->mOldid,
775 'wgDiffNewId' => $this->mNewid,
778 # Make "next revision link"
779 # Skip next link on the top revision
780 if ( $samePage && $this->mNewPage && !$this->mNewRevisionRecord->isCurrent() ) {
781 $nextlink = $this->linkRenderer->makeKnownLink(
783 $this->
msg(
'nextdiff' )->text(),
784 [
'id' =>
'differences-nextlink' ],
785 [
'diff' =>
'next',
'oldid' => $this->mNewid ] + $query
788 $nextlink =
"\u{00A0}";
791 if ( $this->mNewRevisionRecord->isMinor() ) {
797 # Handle RevisionDelete links...
800 # Allow extensions to define their own revision tools
801 $this->hookRunner->onDiffTools(
802 $this->mNewRevisionRecord,
804 $this->mOldRevisionRecord ?:
null,
808 # Hook deprecated since 1.35
809 if ( $this->hookContainer->isRegistered(
'DiffRevisionTools' ) ) {
810 # Only create the Revision objects if they are needed
811 $legacyOldRev = $this->mOldRevisionRecord ?
812 new Revision( $this->mOldRevisionRecord ) :
814 $legacyNewRev = $this->mNewRevisionRecord ?
815 new Revision( $this->mNewRevisionRecord ) :
817 $this->hookRunner->onDiffRevisionTools(
825 $formattedRevisionTools = [];
827 foreach ( $revisionTools as $key => $tool ) {
828 $toolClass = is_string( $key ) ? $key :
'mw-diff-tool';
831 [
'class' => $toolClass ],
832 $this->
msg(
'parentheses' )->rawParams( $tool )->escaped()
834 $formattedRevisionTools[] = $element;
840 ' ' . implode(
' ', $formattedRevisionTools );
843 $newHeader =
'<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader .
'</strong></div>' .
846 '<div id="mw-diff-ntitle3">' . $newminor .
848 '<div id="mw-diff-ntitle5">' . $newChangeTags[0] .
'</div>' .
849 '<div id="mw-diff-ntitle4">' . $nextlink . $this->
markPatrolledLink() .
'</div>';
852 $this->hookRunner->onDifferenceEngineNewHeader( $this, $newHeader,
853 $formattedRevisionTools, $nextlink, $rollback, $newminor, $diffOnly,
854 $rdel, $this->unhide );
856 # If the diff cannot be shown due to a deleted revision, then output
857 # the diff header and links to unhide (if available)...
861 $out->addHTML( $this->
addHeader(
'', $oldHeader, $newHeader, $multi ) );
863 $msg = $suppressed ?
'rev-suppressed-no-diff' :
'rev-deleted-no-diff';
864 # Give explanation for why revision is not visible
867 $this->
msg( $msg )->parse(),
872 # Give explanation and add a link to view the diff...
873 $query = $this->
getRequest()->appendQueryValue(
'unhide',
'1' );
874 $link = $this->
getTitle()->getFullURL( $query );
875 $msg = $suppressed ?
'rev-suppressed-unhide-diff' :
'rev-deleted-unhide-diff';
878 $this->
msg( $msg, $link )->parse(),
883 # Otherwise, output a regular diff...
885 # Add deletion notice if the user is viewing deleted content
888 $msg = $suppressed ?
'rev-suppressed-diff-view' :
'rev-deleted-diff-view';
890 $this->
msg( $msg )->parse(),
894 $this->
showDiff( $oldHeader, $newHeader, $notice );
912 if ( $this->mMarkPatrolledLink ===
null ) {
915 if ( !$linkInfo || !$this->mNewPage ) {
916 $this->mMarkPatrolledLink =
'';
918 $this->mMarkPatrolledLink =
' <span class="patrollink" data-mw="interface">[' .
919 $this->linkRenderer->makeKnownLink(
921 $this->
msg(
'markaspatrolleddiff' )->text(),
924 'action' =>
'markpatrolled',
925 'rcid' => $linkInfo[
'rcid'],
929 $this->hookRunner->onDifferenceEngineMarkPatrolledLink( $this,
930 $this->mMarkPatrolledLink, $linkInfo[
'rcid'] );
946 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
951 $config->get(
'UseRCPatrol' ) &&
953 $permissionManager->quickUserCan(
'patrol', $user, $this->mNewPage ) &&
961 'rc_this_oldid' => $this->mNewid,
967 if ( $change && !$change->getPerformer()->equals( $user ) ) {
968 $rcid = $change->getAttribute(
'rc_id' );
979 $this->hookRunner->onDifferenceEngineMarkPatrolledRCID( $rcid, $this, $change, $user );
983 $this->
getOutput()->preventClickjacking();
984 if ( $permissionManager->userHasRight( $user,
'writeapi' ) ) {
985 $this->
getOutput()->addModules(
'mediawiki.misc-authed-curate' );
1009 if ( $link !==
'' ) {
1010 $link =
"\u{00A0}\u{00A0}\u{00A0}" . $link .
' ';
1022 if ( $this->isContentOverridden ) {
1026 throw new LogicException(
1028 .
' is not supported after calling setContent(). Use setRevisions() instead.'
1034 # Add "current version as of X" title
1035 $out->addHTML(
"<hr class='diff-hr' id='mw-oldid' />
1036 <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
1037 # Page content may be handled by a hooked call instead...
1038 if ( $this->hookRunner->onArticleContentOnDiff( $this, $out ) ) {
1040 if ( !$this->mNewPage ) {
1047 $out->setRevisionId( $this->mNewid );
1048 $out->setRevisionTimestamp( $this->mNewRevisionRecord->getTimestamp() );
1049 $out->setArticleFlag(
true );
1051 if ( !$this->hookRunner->onArticleRevisionViewCustom(
1052 $this->mNewRevisionRecord, $this->mNewPage, $this->mOldid, $out )
1058 if ( $this->
getTitle()->equals( $this->mNewPage ) ) {
1065 $wikiPage = $this->wikiPageFactory->newFromTitle( $this->mNewPage );
1068 $parserOutput = $this->
getParserOutput( $wikiPage, $this->mNewRevisionRecord );
1070 # WikiPage::getParserOutput() should not return false, but just in case
1071 if ( $parserOutput ) {
1073 if ( $this->hookRunner->onDifferenceEngineRenderRevisionAddParserOutput(
1074 $this, $out, $parserOutput, $wikiPage )
1076 $out->addParserOutput( $parserOutput, [
1077 'enableSectionEditLinks' => $this->mNewRevisionRecord->isCurrent()
1078 && MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan(
1081 $this->mNewRevisionRecord->getPageAsLinkTarget()
1090 if ( $this->hookRunner->onDifferenceEngineRenderRevisionShowFinalPatrolLink() ) {
1091 # Add redundant patrol link on bottom...
1103 if ( !$revRecord->
getId() ) {
1113 return $parserOutput;
1126 public function showDiff( $otitle, $ntitle, $notice =
'' ) {
1128 $this->hookRunner->onDifferenceEngineShowDiff( $this );
1130 $diff = $this->
getDiff( $otitle, $ntitle, $notice );
1131 if ( $diff ===
false ) {
1147 if ( !$this->isSlotDiffRenderer ) {
1149 'mediawiki.interface.helpers.styles',
1150 'mediawiki.diff.styles'
1153 $slotDiffRenderer->addModules( $this->
getOutput() );
1167 public function getDiff( $otitle, $ntitle, $notice =
'' ) {
1169 if ( $body ===
false ) {
1175 if ( $body ===
'' ) {
1176 $notice .=
'<div class="mw-diff-empty">' .
1177 $this->
msg(
'diff-empty' )->parse() .
1181 return $this->
addHeader( $body, $otitle, $ntitle, $multi, $notice );
1190 $this->mCacheHit =
true;
1192 if ( !$this->isContentOverridden ) {
1195 } elseif ( $this->mOldRevisionRecord &&
1196 !RevisionRecord::userCanBitfield(
1197 $this->mOldRevisionRecord->getVisibility(),
1198 RevisionRecord::DELETED_TEXT,
1203 } elseif ( $this->mNewRevisionRecord &&
1204 !RevisionRecord::userCanBitfield(
1205 $this->mNewRevisionRecord->getVisibility(),
1206 RevisionRecord::DELETED_TEXT,
1213 if ( $this->mOldRevisionRecord ===
false || (
1214 $this->mOldRevisionRecord &&
1215 $this->mNewRevisionRecord &&
1216 $this->mOldRevisionRecord->getId() &&
1217 $this->mOldRevisionRecord->getId() == $this->mNewRevisionRecord->getId()
1219 if ( $this->hookRunner->onDifferenceEngineShowEmptyOldContent( $this ) ) {
1227 $services = MediaWikiServices::getInstance();
1228 $cache = $services->getMainWANObjectCache();
1229 $stats = $services->getStatsdDataFactory();
1230 if ( $this->mOldid && $this->mNewid ) {
1234 if ( $key ===
null ) {
1239 if ( !$this->mRefreshCache ) {
1240 $difftext =
$cache->get( $key );
1241 if ( is_string( $difftext ) ) {
1242 $stats->updateCount(
'diff_cache.hit', 1 );
1244 $difftext .=
"\n<!-- diff cache key $key -->\n";
1250 $this->mCacheHit =
false;
1262 $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role][
'old'],
1263 $slotContents[$role][
'new'] );
1264 if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1269 $difftext .= $slotDiff;
1273 if ( !$this->hookRunner->onAbortDiffCache( $this ) ) {
1274 $stats->updateCount(
'diff_cache.uncacheable', 1 );
1275 } elseif ( $key !==
false ) {
1276 $stats->updateCount(
'diff_cache.miss', 1 );
1277 $cache->set( $key, $difftext, 7 * 86400 );
1279 $stats->updateCount(
'diff_cache.uncacheable', 1 );
1295 if ( !isset( $diffRenderers[$role] ) ) {
1300 $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role][
'old'],
1301 $slotContents[$role][
'new'] );
1306 if ( $role !== SlotRecord::MAIN ) {
1324 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1326 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-header',
'lang' => $userLang ],
1327 Html::element(
'th', [
'colspan' => $columnCount ], $headerText ) );
1357 if ( !$this->mOldid || !$this->mNewid ) {
1358 throw new MWException(
'mOldid and mNewid must be set to get diff cache key.' );
1364 $engine ===
'php' ? false : $engine,
1366 "old-{$this->mOldid}",
1367 "rev-{$this->mNewid}"
1370 if ( $engine ===
'wikidiff2' ) {
1371 $params[] = phpversion(
'wikidiff2' );
1374 if ( !$this->isSlotDiffRenderer ) {
1376 $params = array_merge( $params, $slotDiffRenderer->getExtraCacheKeys() );
1394 $this->mOldid = 123456789;
1395 $this->mNewid = 987654321;
1399 if ( $cacheString ) {
1400 return [ $cacheString ];
1413 if ( array_slice( $params, 0, count( $standardParams ) ) === $standardParams ) {
1414 $params = array_slice( $params, count( $standardParams ) );
1424 $this->slotDiffOptions = $options;
1444 && $this->isSlotDiffRenderer
1450 throw new Exception( get_class( $this ) .
': could not maintain backwards compatibility. '
1451 .
'Please use a SlotDiffRenderer.' );
1453 return $slotDiffRenderer->getDiff( $old, $new ) . $this->
getDebugString();
1469 $slotDiffRenderer = $this->contentHandlerFactory
1471 ->getSlotDiffRenderer( $this->
getContext() );
1475 throw new Exception(
'The slot diff renderer for text content should be a '
1476 .
'TextSlotDiffRenderer subclass' );
1478 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->
getDebugString();
1488 $diffEngine = MediaWikiServices::getInstance()->getMainConfig()
1489 ->get(
'DiffEngine' );
1490 $externalDiffEngine = MediaWikiServices::getInstance()->getMainConfig()
1491 ->get(
'ExternalDiffEngine' );
1493 if ( $diffEngine ===
null ) {
1494 $engines = [
'external',
'wikidiff2',
'php' ];
1496 $engines = [ $diffEngine ];
1499 $failureReason =
null;
1500 foreach ( $engines as $engine ) {
1501 switch ( $engine ) {
1503 if ( is_string( $externalDiffEngine ) ) {
1504 if ( is_executable( $externalDiffEngine ) ) {
1505 return $externalDiffEngine;
1507 $failureReason =
'ExternalDiffEngine config points to a non-executable';
1508 if ( $diffEngine ===
null ) {
1509 wfDebug(
"$failureReason, ignoring" );
1512 $failureReason =
'ExternalDiffEngine config is set to a non-string value';
1513 if ( $diffEngine ===
null && $externalDiffEngine ) {
1514 wfWarn(
"$failureReason, ignoring" );
1520 if ( function_exists(
'wikidiff2_do_diff' ) ) {
1523 $failureReason =
'wikidiff2 is not available';
1531 throw new DomainException(
'Invalid value for $wgDiffEngine: ' . $engine );
1534 throw new UnexpectedValueException(
"Cannot use diff engine '$engine': $failureReason" );
1550 $slotDiffRenderer = $this->contentHandlerFactory
1552 ->getSlotDiffRenderer( $this->
getContext() );
1556 throw new Exception(
'The slot diff renderer for text content should be a '
1557 .
'TextSlotDiffRenderer subclass' );
1559 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->
getDebugString();
1571 if ( !$this->enableDebugComment ) {
1575 if ( $this->
getConfig()->
get(
'ShowHostnames' ) ) {
1580 return "<!-- diff generator: " .
1581 implode(
" ", array_map(
"htmlspecialchars", $data ) ) .
1590 if ( $engine ===
'wikidiff2' ) {
1591 return $this->
debug(
'wikidiff2' );
1592 } elseif ( $engine ===
'php' ) {
1593 return $this->
debug(
'native PHP' );
1595 return $this->
debug(
"external $engine" );
1607 if ( $this->
getEngine() ===
'wikidiff2' &&
1608 version_compare( phpversion(
'wikidiff2' ),
'1.5.1',
'>=' )
1623 return preg_replace_callback(
1624 '/<!--LINE (\d+)-->/',
1625 [ $this,
'localiseLineNumbersCb' ],
1635 if (
$matches[1] ===
'1' && $this->mReducedLineNumbers ) {
1639 return $this->
msg(
'lineno' )->numParams(
$matches[1] )->escaped();
1649 return preg_replace_callback(
1650 '/class="mw-diff-movedpara-(left|right)"/',
1651 [ $this,
'addLocalisedTitleTooltipsCb' ],
1662 'diff-paragraph-moved-toold' :
1663 'diff-paragraph-moved-tonew';
1664 return $matches[0] .
' title="' . $this->
msg( $key )->escaped() .
'"';
1675 !$this->mOldRevisionRecord || !$this->mNewRevisionRecord
1676 || !$this->mOldPage || !$this->mNewPage
1677 || !$this->mOldPage->equals( $this->mNewPage )
1678 || $this->mOldRevisionRecord->getId() ===
null
1679 || $this->mNewRevisionRecord->getId() ===
null
1681 || $this->mNewPage->getArticleID() !== $this->mOldRevisionRecord->getPageId()
1682 || $this->mNewPage->getArticleID() !== $this->mNewRevisionRecord->getPageId()
1687 if ( $this->mOldRevisionRecord->getTimestamp() > $this->mNewRevisionRecord->getTimestamp() ) {
1697 $nEdits = $this->revisionStore->countRevisionsBetween(
1698 $this->mNewPage->getArticleID(),
1703 if ( $nEdits > 0 && $nEdits <= 1000 ) {
1706 $users = $this->revisionStore->getAuthorsBetween(
1707 $this->mNewPage->getArticleID(),
1713 $numUsers = count( $users );
1715 $newRevUser = $newRevRecord->getUser( RevisionRecord::RAW );
1716 $newRevUserText = $newRevUser ? $newRevUser->getName() :
'';
1717 if ( $numUsers == 1 && $users[0]->getName() == $newRevUserText ) {
1720 }
catch ( InvalidArgumentException $e ) {
1740 if ( $numUsers === 0 ) {
1741 $msg =
'diff-multi-sameuser';
1742 } elseif ( $numUsers > $limit ) {
1743 $msg =
'diff-multi-manyusers';
1746 $msg =
'diff-multi-otherusers';
1749 return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1759 if ( !RevisionRecord::userCanBitfield(
1761 RevisionRecord::DELETED_TEXT,
1781 wfDeprecated( __METHOD__ .
' with a Revision object',
'1.35' );
1782 $rev = $rev->getRevisionRecord();
1787 $revtimestamp = $rev->getTimestamp();
1788 $timestamp =
$lang->userTimeAndDate( $revtimestamp, $user );
1789 $dateofrev =
$lang->userDate( $revtimestamp, $user );
1790 $timeofrev =
$lang->userTime( $revtimestamp, $user );
1793 $rev->isCurrent() ?
'currentrev-asof' :
'revisionasof',
1799 if ( $complete !==
'complete' ) {
1803 $title = $rev->getPageAsLinkTarget();
1806 [
'oldid' => $rev->getId() ] );
1809 $editQuery = [
'action' =>
'edit' ];
1810 if ( !$rev->isCurrent() ) {
1811 $editQuery[
'oldid'] = $rev->getId();
1814 $key = MediaWikiServices::getInstance()->getPermissionManager()
1815 ->quickUserCan(
'edit', $user,
$title ) ?
'editold' :
'viewsourceold';
1816 $msg = $this->
msg( $key )->text();
1817 $editLink = $this->
msg(
'parentheses' )->rawParams(
1818 $this->linkRenderer->makeKnownLink(
$title, $msg, [], $editQuery ) )->escaped();
1821 [
'class' =>
'mw-diff-edit' ],
1824 if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1827 [
'class' =>
'history-deleted' ],
1850 public function addHeader( $diff, $otitle, $ntitle, $multi =
'', $notice =
'' ) {
1856 'diff-contentalign-' . $this->
getDiffLang()->alignStart(),
1857 'diff-editfont-' . $this->
getUser()->getOption(
'editfont' )
1859 'data-mw' =>
'interface',
1861 $userLang = htmlspecialchars( $this->
getLanguage()->getHtmlCode() );
1863 if ( !$diff && !$otitle ) {
1865 <tr class=\"diff-title\" lang=\"{$userLang}\">
1866 <td class=\"diff-ntitle\">{$ntitle}</td>
1872 <col class=\"diff-marker\" />
1873 <col class=\"diff-content\" />
1874 <col class=\"diff-marker\" />
1875 <col class=\"diff-content\" />";
1882 if ( $otitle || $ntitle ) {
1884 <tr class=\"diff-title\" lang=\"{$userLang}\">
1885 <td colspan=\"$colspan\" class=\"diff-otitle\">{$otitle}</td>
1886 <td colspan=\"$colspan\" class=\"diff-ntitle\">{$ntitle}</td>
1891 if ( $multi !=
'' ) {
1892 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
1893 "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
1895 if ( $notice !=
'' ) {
1896 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
1897 "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
1900 return $header . $diff .
"</table>";
1911 $this->mOldContent = $oldContent;
1912 $this->mNewContent = $newContent;
1914 $this->mTextLoaded = 2;
1915 $this->mRevisionsLoaded =
true;
1916 $this->isContentOverridden =
true;
1917 $this->slotDiffRenderers =
null;
1928 if ( $oldRevision ) {
1929 $this->mOldRevisionRecord = $oldRevision;
1930 $this->mOldid = $oldRevision->
getId();
1934 $this->mOldContent = $oldRevision->
getContent( SlotRecord::MAIN,
1935 RevisionRecord::FOR_THIS_USER, $this->
getUser() );
1937 $this->mOldPage =
null;
1938 $this->mOldRevisionRecord = $this->mOldid =
false;
1940 $this->mNewRevisionRecord = $newRevision;
1941 $this->mNewid = $newRevision->
getId();
1943 $this->mNewContent = $newRevision->
getContent( SlotRecord::MAIN,
1944 RevisionRecord::FOR_THIS_USER, $this->
getUser() );
1946 $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded =
true;
1947 $this->mTextLoaded = $oldRevision ? 2 : 1;
1948 $this->isContentOverridden =
false;
1949 $this->slotDiffRenderers =
null;
1959 $this->mDiffLang =
$lang;
1975 if ( $new ===
'prev' ) {
1977 $newid = intval( $old );
1979 $newRev = $this->revisionStore->getRevisionById( $newid );
1981 $oldRev = $this->revisionStore->getPreviousRevision( $newRev );
1983 $oldid = $oldRev->getId();
1986 } elseif ( $new ===
'next' ) {
1988 $oldid = intval( $old );
1990 $oldRev = $this->revisionStore->getRevisionById( $oldid );
1992 $newRev = $this->revisionStore->getNextRevision( $oldRev );
1994 $newid = $newRev->getId();
1998 $oldid = intval( $old );
1999 $newid = intval( $new );
2002 return [ $oldid, $newid ];
2006 if ( $this->mRevisionsIdsLoaded ) {
2010 $this->mRevisionsIdsLoaded =
true;
2016 if ( $new ===
'next' && $this->mNewid ===
false ) {
2017 # if no result, NewId points to the newest old revision. The only newer
2018 # revision is cur, which is "0".
2022 $this->hookRunner->onNewDifferenceEngine(
2023 $this->
getTitle(), $this->mOldid, $this->mNewid, $old, $new );
2040 if ( $this->mRevisionsLoaded ) {
2041 return $this->isContentOverridden ||
2042 ( $this->mOldRevisionRecord !==
null && $this->mNewRevisionRecord !== null );
2046 $this->mRevisionsLoaded =
true;
2051 if ( $this->mNewid ) {
2052 $this->mNewRevisionRecord = $this->revisionStore->getRevisionById( $this->mNewid );
2054 $this->mNewRevisionRecord = $this->revisionStore->getRevisionByTitle( $this->
getTitle() );
2062 $this->mNewid = $this->mNewRevisionRecord->getId();
2063 if ( $this->mNewid ) {
2065 $this->mNewRevisionRecord->getPageAsLinkTarget()
2068 $this->mNewPage =
null;
2072 $this->mOldRevisionRecord =
false;
2073 if ( $this->mOldid ) {
2074 $this->mOldRevisionRecord = $this->revisionStore->getRevisionById( $this->mOldid );
2075 } elseif ( $this->mOldid === 0 ) {
2076 $revRecord = $this->revisionStore->getPreviousRevision( $this->mNewRevisionRecord );
2078 $this->mOldid = $revRecord->getId();
2079 $this->mOldRevisionRecord = $revRecord;
2082 $this->mOldid =
false;
2083 $this->mOldRevisionRecord =
false;
2087 if ( $this->mOldRevisionRecord ===
null ) {
2091 if ( $this->mOldRevisionRecord && $this->mOldRevisionRecord->getId() ) {
2093 $this->mOldRevisionRecord->getPageAsLinkTarget()
2096 $this->mOldPage =
null;
2101 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
2102 if ( $this->mOldid !==
false ) {
2103 $tagIds =
$dbr->selectFieldValues(
2106 [
'ct_rev_id' => $this->mOldid ],
2110 foreach ( $tagIds as $tagId ) {
2112 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2117 $this->mOldTags = implode(
',', $tags );
2119 $this->mOldTags =
false;
2122 $tagIds =
$dbr->selectFieldValues(
2125 [
'ct_rev_id' => $this->mNewid ],
2129 foreach ( $tagIds as $tagId ) {
2131 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2136 $this->mNewTags = implode(
',', $tags );
2150 if ( $this->mTextLoaded == 2 ) {
2153 && $this->mNewContent;
2157 $this->mTextLoaded = 2;
2163 if ( $this->mOldRevisionRecord ) {
2164 $this->mOldContent = $this->mOldRevisionRecord->getContent(
2166 RevisionRecord::FOR_THIS_USER,
2169 if ( $this->mOldContent ===
null ) {
2174 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2176 RevisionRecord::FOR_THIS_USER,
2179 $this->hookRunner->onDifferenceEngineLoadTextAfterNewContentIsLoaded( $this );
2180 if ( $this->mNewContent ===
null ) {
2193 if ( $this->mTextLoaded >= 1 ) {
2197 $this->mTextLoaded = 1;
2203 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2205 RevisionRecord::FOR_THIS_USER,
2209 $this->hookRunner->onDifferenceEngineAfterLoadNewText( $this );