239 public function __construct( $context =
null, $old = 0, $new = 0, $rcid = 0,
240 $refreshCache =
false, $unhide =
false
256 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'" );
258 $this->mOldid = $old;
259 $this->mNewid = $new;
260 $this->mRefreshCache = $refreshCache;
263 $services = MediaWikiServices::getInstance();
264 $this->linkRenderer = $services->getLinkRenderer();
265 $this->contentHandlerFactory = $services->getContentHandlerFactory();
266 $this->revisionStore = $services->getRevisionStore();
267 $this->hookContainer = $services->getHookContainer();
268 $this->hookRunner =
new HookRunner( $this->hookContainer );
276 if ( $this->isSlotDiffRenderer ) {
277 throw new LogicException( __METHOD__ .
' called in slot diff renderer mode' );
280 if ( $this->slotDiffRenderers ===
null ) {
286 $this->slotDiffRenderers = array_map(
function ( $contents ) {
288 $content = $contents[
'new'] ?: $contents[
'old'];
291 return $content->getContentHandler()->getSlotDiffRenderer(
293 $this->slotDiffOptions
297 return $this->slotDiffRenderers;
307 $this->isSlotDiffRenderer =
true;
316 if ( $this->isContentOverridden ) {
318 SlotRecord::MAIN => [
319 'old' => $this->mOldContent,
320 'new' => $this->mNewContent,
327 $newSlots = $this->mNewRevisionRecord->getSlots()->getSlots();
328 if ( $this->mOldRevisionRecord ) {
329 $oldSlots = $this->mOldRevisionRecord->getSlots()->getSlots();
337 $roles = array_merge( array_keys( $newSlots ), array_keys( $oldSlots ) );
340 foreach ( $roles as $role ) {
342 'old' => isset( $oldSlots[$role] ) ? $oldSlots[$role]->getContent() :
null,
343 'new' => isset( $newSlots[$role] ) ? $newSlots[$role]->getContent() :
null,
347 if ( isset( $slots[SlotRecord::MAIN] ) ) {
348 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
355 return parent::getTitle() ?: Title::makeTitle(
NS_SPECIAL,
'BadTitle/DifferenceEngine' );
365 $this->mReducedLineNumbers = $value;
374 if ( $this->mDiffLang ===
null ) {
375 # Default language in which the diff text is written.
376 $this->mDiffLang = $this->
getTitle()->getPageLanguage();
379 return $this->mDiffLang;
386 return $this->mCacheHit;
399 return $this->mOldid;
411 return $this->mNewid;
421 return $this->mOldRevisionRecord ?:
null;
430 return $this->mNewRevisionRecord;
442 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
443 if ( $permissionManager->userHasRight( $this->getUser(),
'deletedhistory' ) ) {
446 $arQuery =
$revStore->getArchiveQueryInfo();
447 $row =
$dbr->selectRow(
449 array_merge( $arQuery[
'fields'], [
'ar_namespace',
'ar_title' ] ),
450 [
'ar_rev_id' => $id ],
456 $revRecord =
$revStore->newRevisionFromArchiveRow( $row );
457 $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
460 'target' =>
$title->getPrefixedText(),
461 'timestamp' => $revRecord->getTimestamp()
479 return "[$link $id]";
489 if ( $this->mOldRevisionRecord ===
null ||
490 ( $this->mOldRevisionRecord && $this->mOldContent ===
null )
494 if ( $this->mNewRevisionRecord ===
null ||
495 ( $this->mNewRevisionRecord && $this->mNewContent ===
null )
500 $out->setPageTitle( $this->
msg(
'errorpagetitle' ) );
501 $msg = $this->
msg(
'difference-missing-revision' )
502 ->params( $this->
getLanguage()->listToText( $missing ) )
503 ->numParams( count( $missing ) )
505 $out->addHTML( $msg );
516 $this->mNewRevisionRecord &&
517 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
520 $this->mOldRevisionRecord &&
521 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
534 $permManager = MediaWikiServices::getInstance()->getPermissionManager();
535 if ( $this->mNewPage ) {
536 $permErrors = $permManager->getPermissionErrors(
'read', $user, $this->mNewPage );
538 if ( $this->mOldPage ) {
540 $permManager->getPermissionErrors(
'read', $user, $this->mOldPage ) );
552 ( $this->mOldRevisionRecord &&
553 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) ||
554 ( $this->mNewRevisionRecord &&
555 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) )
574 $allowed = !$this->mNewRevisionRecord || RevisionRecord::userCanBitfield(
575 $this->mNewRevisionRecord->getVisibility(),
576 RevisionRecord::DELETED_TEXT,
579 if ( $this->mOldRevisionRecord &&
580 !RevisionRecord::userCanBitfield(
581 $this->mOldRevisionRecord->getVisibility(),
582 RevisionRecord::DELETED_TEXT,
604 # Allow frames except in certain special cases
606 $out->allowClickjacking();
607 $out->setRobotPolicy(
'noindex,nofollow' );
610 $this->hookRunner->onDifferenceEngineShowDiffPage( $out );
613 if ( $this->hookRunner->onDifferenceEngineShowDiffPageMaybeShowMissingRevision( $this ) ) {
621 if ( count( $permErrors ) ) {
627 $query = $this->slotDiffOptions;
628 # Carry over 'diffonly' param via navigation links
629 if ( $diffOnly != $user->getBoolOption(
'diffonly' ) ) {
630 $query[
'diffonly'] = $diffOnly;
632 # Cascade unhide param in links for easy deletion browsing
633 if ( $this->unhide ) {
634 $query[
'unhide'] = 1;
637 # Check if one of the revisions is deleted/suppressed
644 # mOldRevisionRecord is false if the difference engine is called with a "vague" query for
645 # a diff between a version V and its previous version V' AND the version V
646 # is the first version of that article. In that case, V' does not exist.
647 if ( $this->mOldRevisionRecord ===
false ) {
648 if ( $this->mNewPage ) {
649 $out->setPageTitle( $this->
msg(
'difference-title', $this->mNewPage->getPrefixedText() ) );
654 $this->hookRunner->onDifferenceEngineOldHeaderNoOldRev( $oldHeader );
656 $this->hookRunner->onDifferenceEngineViewHeader( $this );
659 if ( $this->hookContainer->isRegistered(
'DiffViewHeader' ) ) {
662 $legacyOldRev = $this->mOldRevisionRecord ?
663 new Revision( $this->mOldRevisionRecord ) :
665 $legacyNewRev = $this->mNewRevisionRecord ?
666 new Revision( $this->mNewRevisionRecord ) :
668 $this->hookRunner->onDiffViewHeader(
675 if ( !$this->mOldPage || !$this->mNewPage ) {
678 } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
679 $out->setPageTitle( $this->
msg(
'difference-title', $this->mNewPage->getPrefixedText() ) );
682 $out->setPageTitle( $this->
msg(
'difference-title-multipage',
683 $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
684 $out->addSubtitle( $this->
msg(
'difference-multipage' ) );
688 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
690 if ( $samePage && $this->mNewPage && $permissionManager->quickUserCan(
691 'edit', $user, $this->mNewPage
693 if ( $this->mNewRevisionRecord->isCurrent() && $permissionManager->quickUserCan(
694 'rollback', $user, $this->mNewPage
697 $this->mNewRevisionRecord,
701 if ( $rollbackLink ) {
702 $out->preventClickjacking();
703 $rollback =
"\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
707 if ( $this->
userCanEdit( $this->mOldRevisionRecord ) &&
710 $undoLink = Html::element(
'a', [
711 'href' => $this->mNewPage->getLocalURL( [
713 'undoafter' => $this->mOldid,
714 'undo' => $this->mNewid
718 $this->msg(
'editundo' )->text()
720 $revisionTools[
'mw-diff-undo'] = $undoLink;
723 # Make "previous revision link"
724 $hasPrevious = $samePage && $this->mOldPage &&
725 $this->revisionStore->getPreviousRevision( $this->mOldRevisionRecord );
726 if ( $hasPrevious ) {
727 $prevlink = $this->linkRenderer->makeKnownLink(
729 $this->
msg(
'previousdiff' )->text(),
730 [
'id' =>
'differences-prevlink' ],
731 [
'diff' =>
'prev',
'oldid' => $this->mOldid ] + $query
734 $prevlink =
"\u{00A0}";
737 if ( $this->mOldRevisionRecord->isMinor() ) {
738 $oldminor = ChangesList::flag(
'minor' );
743 $oldRevRecord = $this->mOldRevisionRecord;
749 $oldHeader =
'<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader .
'</strong></div>' .
750 '<div id="mw-diff-otitle2">' .
752 '<div id="mw-diff-otitle3">' . $oldminor .
754 '<div id="mw-diff-otitle5">' . $oldChangeTags[0] .
'</div>' .
755 '<div id="mw-diff-otitle4">' . $prevlink .
'</div>';
758 $this->hookRunner->onDifferenceEngineOldHeader(
759 $this, $oldHeader, $prevlink, $oldminor, $diffOnly, $ldel, $this->unhide );
762 $out->addJsConfigVars( [
763 'wgDiffOldId' => $this->mOldid,
764 'wgDiffNewId' => $this->mNewid,
767 # Make "next revision link"
768 # Skip next link on the top revision
769 if ( $samePage && $this->mNewPage && !$this->mNewRevisionRecord->isCurrent() ) {
770 $nextlink = $this->linkRenderer->makeKnownLink(
772 $this->
msg(
'nextdiff' )->text(),
773 [
'id' =>
'differences-nextlink' ],
774 [
'diff' =>
'next',
'oldid' => $this->mNewid ] + $query
777 $nextlink =
"\u{00A0}";
780 if ( $this->mNewRevisionRecord->isMinor() ) {
781 $newminor = ChangesList::flag(
'minor' );
786 # Handle RevisionDelete links...
789 # Allow extensions to define their own revision tools
790 $this->hookRunner->onDiffTools(
791 $this->mNewRevisionRecord,
793 $this->mOldRevisionRecord ?:
null,
797 # Hook deprecated since 1.35
798 if ( $this->hookContainer->isRegistered(
'DiffRevisionTools' ) ) {
799 # Only create the Revision objects if they are needed
800 $legacyOldRev = $this->mOldRevisionRecord ?
801 new Revision( $this->mOldRevisionRecord ) :
803 $legacyNewRev = $this->mNewRevisionRecord ?
804 new Revision( $this->mNewRevisionRecord ) :
806 $this->hookRunner->onDiffRevisionTools(
814 $formattedRevisionTools = [];
816 foreach ( $revisionTools as $key => $tool ) {
817 $toolClass = is_string( $key ) ? $key :
'mw-diff-tool';
818 $element = Html::rawElement(
820 [
'class' => $toolClass ],
821 $this->
msg(
'parentheses' )->rawParams( $tool )->escaped()
823 $formattedRevisionTools[] = $element;
826 $newRevRecord = $this->mNewRevisionRecord;
829 ' ' . implode(
' ', $formattedRevisionTools );
832 $newHeader =
'<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader .
'</strong></div>' .
835 '<div id="mw-diff-ntitle3">' . $newminor .
837 '<div id="mw-diff-ntitle5">' . $newChangeTags[0] .
'</div>' .
838 '<div id="mw-diff-ntitle4">' . $nextlink . $this->
markPatrolledLink() .
'</div>';
841 $this->hookRunner->onDifferenceEngineNewHeader( $this, $newHeader,
842 $formattedRevisionTools, $nextlink, $rollback, $newminor, $diffOnly,
843 $rdel, $this->unhide );
845 # If the diff cannot be shown due to a deleted revision, then output
846 # the diff header and links to unhide (if available)...
850 $out->addHTML( $this->
addHeader(
'', $oldHeader, $newHeader, $multi ) );
852 $msg = $suppressed ?
'rev-suppressed-no-diff' :
'rev-deleted-no-diff';
853 # Give explanation for why revision is not visible
854 $out->wrapWikiMsg(
"<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
857 # Give explanation and add a link to view the diff...
858 $query = $this->
getRequest()->appendQueryValue(
'unhide',
'1' );
859 $link = $this->
getTitle()->getFullURL( $query );
860 $msg = $suppressed ?
'rev-suppressed-unhide-diff' :
'rev-deleted-unhide-diff';
862 "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
866 # Otherwise, output a regular diff...
868 # Add deletion notice if the user is viewing deleted content
871 $msg = $suppressed ?
'rev-suppressed-diff-view' :
'rev-deleted-diff-view';
872 $notice =
"<div id='mw-$msg' class='mw-warning plainlinks'>\n" .
873 $this->
msg( $msg )->parse() .
876 $this->
showDiff( $oldHeader, $newHeader, $notice );
894 if ( $this->mMarkPatrolledLink ===
null ) {
897 if ( !$linkInfo || !$this->mNewPage ) {
898 $this->mMarkPatrolledLink =
'';
900 $this->mMarkPatrolledLink =
' <span class="patrollink" data-mw="interface">[' .
901 $this->linkRenderer->makeKnownLink(
903 $this->
msg(
'markaspatrolleddiff' )->text(),
906 'action' =>
'markpatrolled',
907 'rcid' => $linkInfo[
'rcid'],
911 $this->hookRunner->onDifferenceEngineMarkPatrolledLink( $this,
912 $this->mMarkPatrolledLink, $linkInfo[
'rcid'] );
915 return $this->mMarkPatrolledLink;
928 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
933 $config->get(
'UseRCPatrol' ) &&
935 $permissionManager->quickUserCan(
'patrol', $user, $this->mNewPage ) &&
938 RecentChange::isInRCLifespan( $this->mNewRevisionRecord->getTimestamp(), 21600 )
941 $change = RecentChange::newFromConds(
943 'rc_this_oldid' => $this->mNewid,
944 'rc_patrolled' => RecentChange::PRC_UNPATROLLED
949 if ( $change && !$change->getPerformer()->equals( $user ) ) {
950 $rcid = $change->getAttribute(
'rc_id' );
961 $this->hookRunner->onDifferenceEngineMarkPatrolledRCID( $rcid, $this, $change, $user );
965 $this->
getOutput()->preventClickjacking();
966 if ( $permissionManager->userHasRight( $user,
'writeapi' ) ) {
967 $this->
getOutput()->addModules(
'mediawiki.misc-authed-curate' );
991 if ( $link !==
'' ) {
992 $link =
"\u{00A0}\u{00A0}\u{00A0}" . $link .
' ';
1004 if ( $this->isContentOverridden ) {
1008 throw new LogicException(
1010 .
' is not supported after calling setContent(). Use setRevisions() instead.'
1016 # Add "current version as of X" title
1017 $out->addHTML(
"<hr class='diff-hr' id='mw-oldid' />
1018 <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
1019 # Page content may be handled by a hooked call instead...
1020 if ( $this->hookRunner->onArticleContentOnDiff( $this, $out ) ) {
1022 if ( !$this->mNewPage ) {
1029 $out->setRevisionId( $this->mNewid );
1030 $out->setRevisionTimestamp( $this->mNewRevisionRecord->getTimestamp() );
1031 $out->setArticleFlag(
true );
1033 if ( !$this->hookRunner->onArticleRevisionViewCustom(
1034 $this->mNewRevisionRecord, $this->mNewPage, $this->mOldid, $out )
1040 if ( $this->
getTitle()->equals( $this->mNewPage ) ) {
1047 $wikiPage = WikiPage::factory( $this->mNewPage );
1050 $parserOutput = $this->
getParserOutput( $wikiPage, $this->mNewRevisionRecord );
1052 # WikiPage::getParserOutput() should not return false, but just in case
1053 if ( $parserOutput ) {
1055 if ( $this->hookRunner->onDifferenceEngineRenderRevisionAddParserOutput(
1056 $this, $out, $parserOutput, $wikiPage )
1058 $out->addParserOutput( $parserOutput, [
1059 'enableSectionEditLinks' => $this->mNewRevisionRecord->isCurrent()
1060 && MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan(
1063 $this->mNewRevisionRecord->getPageAsLinkTarget()
1072 if ( $this->hookRunner->onDifferenceEngineRenderRevisionShowFinalPatrolLink() ) {
1073 # Add redundant patrol link on bottom...
1085 if ( !$revRecord->
getId() ) {
1095 return $parserOutput;
1108 public function showDiff( $otitle, $ntitle, $notice =
'' ) {
1110 $this->hookRunner->onDifferenceEngineShowDiff( $this );
1112 $diff = $this->
getDiff( $otitle, $ntitle, $notice );
1113 if ( $diff ===
false ) {
1129 if ( !$this->isSlotDiffRenderer ) {
1131 'mediawiki.interface.helpers.styles',
1132 'mediawiki.diff.styles'
1135 $slotDiffRenderer->addModules( $this->
getOutput() );
1149 public function getDiff( $otitle, $ntitle, $notice =
'' ) {
1151 if ( $body ===
false ) {
1157 if ( $body ===
'' ) {
1158 $notice .=
'<div class="mw-diff-empty">' .
1159 $this->
msg(
'diff-empty' )->parse() .
1163 return $this->
addHeader( $body, $otitle, $ntitle, $multi, $notice );
1172 $this->mCacheHit =
true;
1174 if ( !$this->isContentOverridden ) {
1177 } elseif ( $this->mOldRevisionRecord &&
1178 !RevisionRecord::userCanBitfield(
1179 $this->mOldRevisionRecord->getVisibility(),
1180 RevisionRecord::DELETED_TEXT,
1185 } elseif ( $this->mNewRevisionRecord &&
1186 !RevisionRecord::userCanBitfield(
1187 $this->mNewRevisionRecord->getVisibility(),
1188 RevisionRecord::DELETED_TEXT,
1195 if ( $this->mOldRevisionRecord ===
false || (
1196 $this->mOldRevisionRecord &&
1197 $this->mNewRevisionRecord &&
1198 $this->mOldRevisionRecord->getId() &&
1199 $this->mOldRevisionRecord->getId() == $this->mNewRevisionRecord->getId()
1201 if ( $this->hookRunner->onDifferenceEngineShowEmptyOldContent( $this ) ) {
1209 $services = MediaWikiServices::getInstance();
1210 $cache = $services->getMainWANObjectCache();
1211 $stats = $services->getStatsdDataFactory();
1212 if ( $this->mOldid && $this->mNewid ) {
1216 if ( $key ===
null ) {
1221 if ( !$this->mRefreshCache ) {
1222 $difftext =
$cache->get( $key );
1223 if ( is_string( $difftext ) ) {
1224 $stats->updateCount(
'diff_cache.hit', 1 );
1226 $difftext .=
"\n<!-- diff cache key $key -->\n";
1232 $this->mCacheHit =
false;
1244 $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role][
'old'],
1245 $slotContents[$role][
'new'] );
1246 if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1251 $difftext .= $slotDiff;
1255 if ( !$this->hookRunner->onAbortDiffCache( $this ) ) {
1256 $stats->updateCount(
'diff_cache.uncacheable', 1 );
1257 } elseif ( $key !==
false ) {
1258 $stats->updateCount(
'diff_cache.miss', 1 );
1259 $cache->set( $key, $difftext, 7 * 86400 );
1261 $stats->updateCount(
'diff_cache.uncacheable', 1 );
1277 if ( !isset( $diffRenderers[$role] ) ) {
1282 $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role][
'old'],
1283 $slotContents[$role][
'new'] );
1288 if ( $role !== SlotRecord::MAIN ) {
1306 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1308 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-header',
'lang' => $userLang ],
1309 Html::element(
'th', [
'colspan' => $columnCount ], $headerText ) );
1339 if ( !$this->mOldid || !$this->mNewid ) {
1340 throw new MWException(
'mOldid and mNewid must be set to get diff cache key.' );
1346 $engine ===
'php' ? false : $engine,
1348 "old-{$this->mOldid}",
1349 "rev-{$this->mNewid}"
1352 if ( $engine ===
'wikidiff2' ) {
1353 $params[] = phpversion(
'wikidiff2' );
1356 if ( !$this->isSlotDiffRenderer ) {
1358 $params = array_merge( $params, $slotDiffRenderer->getExtraCacheKeys() );
1376 $this->mOldid = 123456789;
1377 $this->mNewid = 987654321;
1381 if ( $cacheString ) {
1382 return [ $cacheString ];
1395 if ( array_slice( $params, 0, count( $standardParams ) ) === $standardParams ) {
1396 $params = array_slice( $params, count( $standardParams ) );
1406 $this->slotDiffOptions = $options;
1426 && $this->isSlotDiffRenderer
1432 throw new Exception( get_class( $this ) .
': could not maintain backwards compatibility. '
1433 .
'Please use a SlotDiffRenderer.' );
1435 return $slotDiffRenderer->getDiff( $old, $new ) . $this->
getDebugString();
1451 $slotDiffRenderer = $this->contentHandlerFactory
1453 ->getSlotDiffRenderer( $this->
getContext() );
1457 throw new Exception(
'The slot diff renderer for text content should be a '
1458 .
'TextSlotDiffRenderer subclass' );
1460 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->
getDebugString();
1470 $diffEngine = MediaWikiServices::getInstance()->getMainConfig()
1471 ->get(
'DiffEngine' );
1472 $externalDiffEngine = MediaWikiServices::getInstance()->getMainConfig()
1473 ->get(
'ExternalDiffEngine' );
1475 if ( $diffEngine ===
null ) {
1476 $engines = [
'external',
'wikidiff2',
'php' ];
1478 $engines = [ $diffEngine ];
1481 $failureReason =
null;
1482 foreach ( $engines as $engine ) {
1483 switch ( $engine ) {
1485 if ( is_string( $externalDiffEngine ) ) {
1486 if ( is_executable( $externalDiffEngine ) ) {
1487 return $externalDiffEngine;
1489 $failureReason =
'ExternalDiffEngine config points to a non-executable';
1490 if ( $diffEngine ===
null ) {
1491 wfDebug(
"$failureReason, ignoring" );
1494 $failureReason =
'ExternalDiffEngine config is set to a non-string value';
1495 if ( $diffEngine ===
null && $externalDiffEngine ) {
1496 wfWarn(
"$failureReason, ignoring" );
1502 if ( function_exists(
'wikidiff2_do_diff' ) ) {
1505 $failureReason =
'wikidiff2 is not available';
1513 throw new DomainException(
'Invalid value for $wgDiffEngine: ' . $engine );
1516 throw new UnexpectedValueException(
"Cannot use diff engine '$engine': $failureReason" );
1532 $slotDiffRenderer = $this->contentHandlerFactory
1534 ->getSlotDiffRenderer( $this->
getContext() );
1538 throw new Exception(
'The slot diff renderer for text content should be a '
1539 .
'TextSlotDiffRenderer subclass' );
1541 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->
getDebugString();
1553 if ( !$this->enableDebugComment ) {
1557 if ( $this->
getConfig()->
get(
'ShowHostnames' ) ) {
1562 return "<!-- diff generator: " .
1563 implode(
" ", array_map(
"htmlspecialchars", $data ) ) .
1568 $engine = self::getEngine();
1569 if ( $engine ===
'wikidiff2' ) {
1570 return $this->
debug(
'wikidiff2' );
1571 } elseif ( $engine ===
'php' ) {
1572 return $this->
debug(
'native PHP' );
1574 return $this->
debug(
"external $engine" );
1586 if ( $this->
getEngine() ===
'wikidiff2' &&
1587 version_compare( phpversion(
'wikidiff2' ),
'1.5.1',
'>=' )
1602 return preg_replace_callback(
1603 '/<!--LINE (\d+)-->/',
1604 [ $this,
'localiseLineNumbersCb' ],
1610 if (
$matches[1] ===
'1' && $this->mReducedLineNumbers ) {
1614 return $this->
msg(
'lineno' )->numParams(
$matches[1] )->escaped();
1624 return preg_replace_callback(
1625 '/class="mw-diff-movedpara-(left|right)"/',
1626 [ $this,
'addLocalisedTitleTooltipsCb' ],
1637 'diff-paragraph-moved-toold' :
1638 'diff-paragraph-moved-tonew';
1639 return $matches[0] .
' title="' . $this->
msg( $key )->escaped() .
'"';
1650 !$this->mOldRevisionRecord || !$this->mNewRevisionRecord
1651 || !$this->mOldPage || !$this->mNewPage
1652 || !$this->mOldPage->equals( $this->mNewPage )
1653 || $this->mOldRevisionRecord->getId() ===
null
1654 || $this->mNewRevisionRecord->getId() ===
null
1656 || $this->mNewPage->getArticleID() !== $this->mOldRevisionRecord->getPageId()
1657 || $this->mNewPage->getArticleID() !== $this->mNewRevisionRecord->getPageId()
1662 if ( $this->mOldRevisionRecord->getTimestamp() > $this->mNewRevisionRecord->getTimestamp() ) {
1663 $oldRevRecord = $this->mNewRevisionRecord;
1664 $newRevRecord = $this->mOldRevisionRecord;
1666 $oldRevRecord = $this->mOldRevisionRecord;
1667 $newRevRecord = $this->mNewRevisionRecord;
1673 $revisionIdList = $this->revisionStore->getRevisionIdsBetween(
1674 $this->mNewPage->getArticleID(),
1680 if ( count( $revisionIdList ) > 0 ) {
1681 foreach ( $revisionIdList as $revisionId ) {
1682 $revision = $this->revisionStore->getRevisionById( $revisionId );
1683 if ( $revision->getUser( RevisionRecord::FOR_THIS_USER, $this->getUser() ) ) {
1688 if ( $nEdits > 0 && $nEdits <= 1000 ) {
1691 $users = $this->revisionStore->getAuthorsBetween(
1692 $this->mNewPage->getArticleID(),
1698 $numUsers = count( $users );
1700 $newRevUser = $newRevRecord->getUser( RevisionRecord::RAW );
1701 $newRevUserText = $newRevUser ? $newRevUser->getName() :
'';
1702 if ( $numUsers == 1 && $users[0]->getName() == $newRevUserText ) {
1705 }
catch ( InvalidArgumentException $e ) {
1709 return self::intermediateEditsMsg( $nEdits, $numUsers, $limit );
1725 if ( $numUsers === 0 ) {
1726 $msg =
'diff-multi-sameuser';
1727 } elseif ( $numUsers > $limit ) {
1728 $msg =
'diff-multi-manyusers';
1731 $msg =
'diff-multi-otherusers';
1734 return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1744 if ( !RevisionRecord::userCanBitfield(
1746 RevisionRecord::DELETED_TEXT,
1766 wfDeprecated( __METHOD__ .
' with a Revision object',
'1.35' );
1767 $rev = $rev->getRevisionRecord();
1772 $revtimestamp = $rev->getTimestamp();
1773 $timestamp =
$lang->userTimeAndDate( $revtimestamp, $user );
1774 $dateofrev =
$lang->userDate( $revtimestamp, $user );
1775 $timeofrev =
$lang->userTime( $revtimestamp, $user );
1778 $rev->isCurrent() ?
'currentrev-asof' :
'revisionasof',
1784 if ( $complete !==
'complete' ) {
1788 $title = $rev->getPageAsLinkTarget();
1791 [
'oldid' => $rev->getId() ] );
1794 $editQuery = [
'action' =>
'edit' ];
1795 if ( !$rev->isCurrent() ) {
1796 $editQuery[
'oldid'] = $rev->getId();
1799 $key = MediaWikiServices::getInstance()->getPermissionManager()
1800 ->quickUserCan(
'edit', $user,
$title ) ?
'editold' :
'viewsourceold';
1801 $msg = $this->
msg( $key )->text();
1802 $editLink = $this->
msg(
'parentheses' )->rawParams(
1803 $this->linkRenderer->makeKnownLink(
$title, $msg, [], $editQuery ) )->escaped();
1804 $header .=
' ' . Html::rawElement(
1806 [
'class' =>
'mw-diff-edit' ],
1809 if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1812 [
'class' =>
'history-deleted' ],
1817 $header = Html::rawElement(
'span', [
'class' =>
'history-deleted' ],
$header );
1835 public function addHeader( $diff, $otitle, $ntitle, $multi =
'', $notice =
'' ) {
1838 $header = Html::openElement(
'table', [
1841 'diff-contentalign-' . $this->
getDiffLang()->alignStart(),
1842 'diff-editfont-' . $this->
getUser()->getOption(
'editfont' )
1844 'data-mw' =>
'interface',
1846 $userLang = htmlspecialchars( $this->
getLanguage()->getHtmlCode() );
1848 if ( !$diff && !$otitle ) {
1850 <tr class=\"diff-title\" lang=\"{$userLang}\">
1851 <td class=\"diff-ntitle\">{$ntitle}</td>
1857 <col class=\"diff-marker\" />
1858 <col class=\"diff-content\" />
1859 <col class=\"diff-marker\" />
1860 <col class=\"diff-content\" />";
1867 if ( $otitle || $ntitle ) {
1869 <tr class=\"diff-title\" lang=\"{$userLang}\">
1870 <td colspan=\"$colspan\" class=\"diff-otitle\">{$otitle}</td>
1871 <td colspan=\"$colspan\" class=\"diff-ntitle\">{$ntitle}</td>
1876 if ( $multi !=
'' ) {
1877 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
1878 "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
1880 if ( $notice !=
'' ) {
1881 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
1882 "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
1885 return $header . $diff .
"</table>";
1896 $this->mOldContent = $oldContent;
1897 $this->mNewContent = $newContent;
1899 $this->mTextLoaded = 2;
1900 $this->mRevisionsLoaded =
true;
1901 $this->isContentOverridden =
true;
1902 $this->slotDiffRenderers =
null;
1913 if ( $oldRevision ) {
1914 $this->mOldRevisionRecord = $oldRevision;
1915 $this->mOldid = $oldRevision->
getId();
1919 $this->mOldContent = $oldRevision->
getContent( SlotRecord::MAIN,
1920 RevisionRecord::FOR_THIS_USER, $this->
getUser() );
1922 $this->mOldPage =
null;
1923 $this->mOldRevisionRecord = $this->mOldid =
false;
1925 $this->mNewRevisionRecord = $newRevision;
1926 $this->mNewid = $newRevision->
getId();
1928 $this->mNewContent = $newRevision->
getContent( SlotRecord::MAIN,
1929 RevisionRecord::FOR_THIS_USER, $this->
getUser() );
1931 $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded =
true;
1932 $this->mTextLoaded = $oldRevision ? 2 : 1;
1933 $this->isContentOverridden =
false;
1934 $this->slotDiffRenderers =
null;
1944 $this->mDiffLang =
$lang;
1960 $rl = MediaWikiServices::getInstance()->getRevisionLookup();
1961 if ( $new ===
'prev' ) {
1963 $newid = intval( $old );
1965 $newRev = $rl->getRevisionById( $newid );
1967 $oldRev = $rl->getPreviousRevision( $newRev );
1969 $oldid = $oldRev->getId();
1972 } elseif ( $new ===
'next' ) {
1974 $oldid = intval( $old );
1976 $oldRev = $rl->getRevisionById( $oldid );
1978 $newRev = $rl->getNextRevision( $oldRev );
1980 $newid = $newRev->getId();
1984 $oldid = intval( $old );
1985 $newid = intval( $new );
1988 return [ $oldid, $newid ];
1995 if ( $this->mRevisionsIdsLoaded ) {
1999 $this->mRevisionsIdsLoaded =
true;
2001 $old = $this->mOldid;
2002 $new = $this->mNewid;
2004 list( $this->mOldid, $this->mNewid ) = self::mapDiffPrevNext( $old, $new );
2005 if ( $new ===
'next' && $this->mNewid ===
false ) {
2006 # if no result, NewId points to the newest old revision. The only newer
2007 # revision is cur, which is "0".
2011 $this->hookRunner->onNewDifferenceEngine(
2012 $this->
getTitle(), $this->mOldid, $this->mNewid, $old, $new );
2029 if ( $this->mRevisionsLoaded ) {
2030 return $this->isContentOverridden ||
2031 ( $this->mOldRevisionRecord !==
null && $this->mNewRevisionRecord !== null );
2035 $this->mRevisionsLoaded =
true;
2040 if ( $this->mNewid ) {
2041 $this->mNewRevisionRecord = $this->revisionStore->getRevisionById( $this->mNewid );
2043 $this->mNewRevisionRecord = $this->revisionStore->getRevisionByTitle( $this->
getTitle() );
2051 $this->mNewid = $this->mNewRevisionRecord->getId();
2052 if ( $this->mNewid ) {
2053 $this->mNewPage = Title::newFromLinkTarget(
2054 $this->mNewRevisionRecord->getPageAsLinkTarget()
2057 $this->mNewPage =
null;
2061 $this->mOldRevisionRecord =
false;
2062 if ( $this->mOldid ) {
2063 $this->mOldRevisionRecord = $this->revisionStore->getRevisionById( $this->mOldid );
2064 } elseif ( $this->mOldid === 0 ) {
2065 $revRecord = $this->revisionStore->getPreviousRevision( $this->mNewRevisionRecord );
2067 $this->mOldid = $revRecord->getId();
2068 $this->mOldRevisionRecord = $revRecord;
2071 $this->mOldid =
false;
2072 $this->mOldRevisionRecord =
false;
2076 if ( $this->mOldRevisionRecord ===
null ) {
2080 if ( $this->mOldRevisionRecord && $this->mOldRevisionRecord->getId() ) {
2081 $this->mOldPage = Title::newFromLinkTarget(
2082 $this->mOldRevisionRecord->getPageAsLinkTarget()
2085 $this->mOldPage =
null;
2090 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
2091 if ( $this->mOldid !==
false ) {
2092 $tagIds =
$dbr->selectFieldValues(
2095 [
'ct_rev_id' => $this->mOldid ],
2099 foreach ( $tagIds as $tagId ) {
2101 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2106 $this->mOldTags = implode(
',', $tags );
2108 $this->mOldTags =
false;
2111 $tagIds =
$dbr->selectFieldValues(
2114 [
'ct_rev_id' => $this->mNewid ],
2118 foreach ( $tagIds as $tagId ) {
2120 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2125 $this->mNewTags = implode(
',', $tags );
2139 if ( $this->mTextLoaded == 2 ) {
2141 ( $this->mOldRevisionRecord ===
false || $this->mOldContent )
2142 && $this->mNewContent;
2146 $this->mTextLoaded = 2;
2152 if ( $this->mOldRevisionRecord ) {
2153 $this->mOldContent = $this->mOldRevisionRecord->getContent(
2155 RevisionRecord::FOR_THIS_USER,
2158 if ( $this->mOldContent ===
null ) {
2163 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2165 RevisionRecord::FOR_THIS_USER,
2168 $this->hookRunner->onDifferenceEngineLoadTextAfterNewContentIsLoaded( $this );
2169 if ( $this->mNewContent ===
null ) {
2182 if ( $this->mTextLoaded >= 1 ) {
2186 $this->mTextLoaded = 1;
2192 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2194 RevisionRecord::FOR_THIS_USER,
2198 $this->hookRunner->onDifferenceEngineAfterLoadNewText( $this );
deprecatePublicProperty( $property, $version, $class=null, $component=null)
Mark a property as deprecated.
trait DeprecationHelper
Use this trait in classes which have properties for which public access is deprecated.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfMergeErrorArrays(... $args)
Merge arrays in the style of PermissionManager::getPermissionErrors, with duplicate removal e....
wfHostname()
Get host name of the current machine, for use in error reporting.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getWikiPage()
Get the WikiPage object.
setContext(IContextSource $context)
B/C adapter for turning a DifferenceEngine into a SlotDiffRenderer.
DifferenceEngine is responsible for rendering the difference between two revisions as HTML.
getDiffBodyCacheKey()
Returns the cache key for diff body text or content.
bool $unhide
Show rev_deleted content if allowed.
bool $isContentOverridden
Was the content overridden via setContent()? If the content was overridden, most internal state (e....
getExtraCacheKeys()
Implements DifferenceEngineSlotDiffRenderer::getExtraCacheKeys().
markAsSlotDiffRenderer()
Mark this DifferenceEngine as a slot renderer (as opposed to a page renderer).
getSlotHeader( $headerText)
Get a slot header for inclusion in a diff body (as a table row).
bool $mRevisionsIdsLoaded
Have the revisions IDs been loaded.
string[] null $mOldTags
Change tags of old revision or null if it does not exist / is not saved.
setSlotDiffOptions( $options)
hasDeletedRevision()
Checks whether one of the given Revisions was deleted.
IContentHandlerFactory $contentHandlerFactory
int $mTextLoaded
How many text blobs have been loaded, 0, 1 or 2?
deletedIdMarker( $id)
Build a wikitext link toward a deleted revision, if viewable.
getDiffBodyForRole( $role)
Get the diff table body for one slot, without header.
RevisionStore $revisionStore
revisionDeleteLink(RevisionRecord $revRecord)
getOldid()
Get the ID of old revision (left pane) of the diff.
setRevisions(?RevisionRecord $oldRevision, RevisionRecord $newRevision)
Use specified text instead of loading from the database.
bool $isSlotDiffRenderer
Temporary hack for B/C while slot diff related methods of DifferenceEngine are being deprecated.
generateTextDiffBody( $otext, $ntext)
Generate a diff, no caching.
loadNewText()
Load the text of the new revision, not the old one.
showDiffPage( $diffOnly=false)
loadText()
Load the text of the revisions, as well as revision data.
int string false null $mNewid
Revision ID for the new revision.
const DIFF_VERSION
Constant to indicate diff cache compatibility.
mapDiffPrevNext( $old, $new)
Maps a revision pair definition as accepted by DifferenceEngine constructor to a pair of actual integ...
Content null $mNewContent
getDiffBody()
Get the diff table body, without header.
string[] null $mNewTags
Change tags of new revision or null if it does not exist / is not saved.
getParserOutput(WikiPage $page, RevisionRecord $revRecord)
loadRevisionData()
Load revision metadata for the specified revisions.
static getEngine()
Process DiffEngine config and get a sane, usable engine.
loadRevisionIds()
Load revision IDs.
bool $mRevisionsLoaded
Have the revisions been loaded.
Content null $mOldContent
getNewRevision()
Get the right side of the diff.
showDiff( $otitle, $ntitle, $notice='')
Get the diff text, send it to the OutputPage object Returns false if the diff could not be generated,...
localiseLineNumbers( $text)
Replace line numbers with the text in the user's language.
getSlotContents()
Get the old and new content objects for all slots.
string $mMarkPatrolledLink
Link to action=markpatrolled.
localiseLineNumbersCb( $matches)
shouldBeHiddenFromUser( $user)
Checks whether the diff should be hidden from the current user This is based on whether the user is a...
deletedLink( $id)
Look up a special:Undelete link to the given deleted revision id, as a workaround for being unable to...
bool $mReducedLineNumbers
If true, line X is not displayed when X is 1, for example to increase readability and conserve space ...
__construct( $context=null, $old=0, $new=0, $rcid=0, $refreshCache=false, $unhide=false)
#-
Title null $mNewPage
Title of new revision or null if the new revision does not exist or does not belong to a page.
bool $mCacheHit
Was the diff fetched from cache?
getMultiNotice()
If there are revisions between the ones being compared, return a note saying so.
int false null $mOldid
Revision ID for the old revision.
debug( $generator="internal")
Generate a debug comment indicating diff generating time, server node, and generator backend.
addHeader( $diff, $otitle, $ntitle, $multi='', $notice='')
Add the header to a diff body.
bool $mRefreshCache
Refresh the diff cache.
LinkRenderer $linkRenderer
getDiffBodyCacheKeyParams()
Get the cache key parameters.
HookContainer $hookContainer
getDiff( $otitle, $ntitle, $notice='')
Get complete diff table, including header.
RevisionRecord null $mNewRevisionRecord
New revision (right pane).
getRevisionHeader( $rev, $complete='')
Get a header for a specified revision.
getNewid()
Get the ID of new revision (right pane) of the diff.
renderNewRevision()
Show the new revision of the page.
addLocalisedTitleTooltips( $text)
Add title attributes for tooltips on moved paragraph indicators.
setContent(Content $oldContent, Content $newContent)
Use specified text instead of loading from the database.
setTextLanguage(Language $lang)
Set the language in which the diff text is written.
generateContentDiffBody(Content $old, Content $new)
Generate a diff, no caching.
localiseDiff( $text)
Localise diff output.
isUserAllowedToSeeRevisions( $user)
Checks whether the current user has permission for accessing the revisions of the diff.
getPermissionErrors(User $user)
Get the permission errors associated with the revisions for the current diff.
getMarkPatrolledLinkInfo()
Returns an array of meta data needed to build a "mark as patrolled" link and adds a JS module to the ...
setReducedLineNumbers( $value=true)
Set reduced line numbers mode.
RevisionRecord null false $mOldRevisionRecord
Old revision (left pane).
textDiff( $otext, $ntext)
Generates diff, to be wrapped internally in a logging/instrumentation.
static intermediateEditsMsg( $numEdits, $numUsers, $limit)
Get a notice about how many intermediate edits and users there are.
Title null $mOldPage
Title of old revision or null if the old revision does not exist or does not belong to a page.
SlotDiffRenderer[] $slotDiffRenderers
DifferenceEngine classes for the slots, keyed by role name.
getDiffLang()
Get the language of the difference engine, defaults to page content language.
showDiffStyle()
Add style sheets for diff display.
addLocalisedTitleTooltipsCb(array $matches)
markPatrolledLink()
Build a link to mark a change as patrolled.
$enableDebugComment
Set this to true to add debug info to the HTML output.
hasSuppressedRevision()
Checks whether one of the given Revisions was suppressed.
getOldRevision()
Get the left side of the diff.
userCanEdit(RevisionRecord $revRecord)
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
static generateRollback( $rev, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
static titleAttrib( $name, $options=null, array $msgParams=[])
Given the id of an interface element, constructs the appropriate title attribute from the system mess...
static revComment( $rev, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
static revUserTools( $rev, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
static getRevDeleteLink(User $user, $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Show an error when a user tries to do something they do not have the necessary permissions for.
Renders a diff for a single slot (that is, a diff between two content objects).
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Renders a slot diff by doing a text diff on the native representation.
Represents a title within MediaWiki.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Class representing a MediaWiki article and history.
makeParserOptions( $context)
Get parser options suitable for rendering the primary article wikitext.
getParserOutput(ParserOptions $parserOptions, $oldid=null, $forceParse=false)
Get a ParserOutput for the given ParserOptions and revision ID.
Base interface for content objects.
getContentHandler()
Convenience method that returns the ContentHandler singleton for handling the content model that this...
Interface for objects which can provide a MediaWiki context on request.
if(!isset( $args[0])) $lang