243 public function __construct( $context =
null, $old = 0, $new = 0, $rcid = 0,
244 $refreshCache =
false, $unhide =
false
260 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'" );
262 $this->mOldid = $old;
263 $this->mNewid = $new;
264 $this->mRefreshCache = $refreshCache;
267 $services = MediaWikiServices::getInstance();
268 $this->linkRenderer = $services->getLinkRenderer();
269 $this->contentHandlerFactory = $services->getContentHandlerFactory();
270 $this->revisionStore = $services->getRevisionStore();
271 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
272 $this->wikiPageFactory = $services->getWikiPageFactory();
280 if ( $this->isSlotDiffRenderer ) {
281 throw new LogicException( __METHOD__ .
' called in slot diff renderer mode' );
284 if ( $this->slotDiffRenderers ===
null ) {
290 $this->slotDiffRenderers = array_map(
function ( $contents ) {
292 $content = $contents[
'new'] ?: $contents[
'old'];
295 return $content->getContentHandler()->getSlotDiffRenderer(
297 $this->slotDiffOptions
301 return $this->slotDiffRenderers;
311 $this->isSlotDiffRenderer =
true;
320 if ( $this->isContentOverridden ) {
322 SlotRecord::MAIN => [
323 'old' => $this->mOldContent,
324 'new' => $this->mNewContent,
331 $newSlots = $this->mNewRevisionRecord->getPrimarySlots()->getSlots();
332 if ( $this->mOldRevisionRecord ) {
333 $oldSlots = $this->mOldRevisionRecord->getPrimarySlots()->getSlots();
341 $roles = array_merge( array_keys( $newSlots ), array_keys( $oldSlots ) );
344 foreach ( $roles as $role ) {
346 'old' => isset( $oldSlots[$role] ) ? $oldSlots[$role]->getContent() :
null,
347 'new' => isset( $newSlots[$role] ) ? $newSlots[$role]->getContent() :
null,
351 if ( isset( $slots[SlotRecord::MAIN] ) ) {
352 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
360 return parent::getTitle() ?: Title::makeTitle(
NS_SPECIAL,
'BadTitle/DifferenceEngine' );
370 $this->mReducedLineNumbers = $value;
379 if ( $this->mDiffLang ===
null ) {
380 # Default language in which the diff text is written.
381 $this->mDiffLang = $this->
getTitle()->getPageLanguage();
384 return $this->mDiffLang;
391 return $this->mCacheHit;
404 return $this->mOldid;
416 return $this->mNewid;
426 return $this->mOldRevisionRecord ?:
null;
435 return $this->mNewRevisionRecord;
447 if ( $this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
450 $arQuery =
$revStore->getArchiveQueryInfo();
451 $row =
$dbr->selectRow(
453 array_merge( $arQuery[
'fields'], [
'ar_namespace',
'ar_title' ] ),
454 [
'ar_rev_id' => $id ],
460 $revRecord =
$revStore->newRevisionFromArchiveRow( $row );
461 $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
464 'target' =>
$title->getPrefixedText(),
465 'timestamp' => $revRecord->getTimestamp()
483 return "[$link $id]";
493 if ( $this->mOldRevisionRecord ===
null ||
494 ( $this->mOldRevisionRecord && $this->mOldContent ===
null )
498 if ( $this->mNewRevisionRecord ===
null ||
499 ( $this->mNewRevisionRecord && $this->mNewContent ===
null )
504 $out->setPageTitle( $this->
msg(
'errorpagetitle' ) );
505 $msg = $this->
msg(
'difference-missing-revision' )
506 ->params( $this->
getLanguage()->listToText( $missing ) )
507 ->numParams( count( $missing ) )
509 $out->addHTML( $msg );
520 $this->mNewRevisionRecord &&
521 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
524 $this->mOldRevisionRecord &&
525 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
537 $permStatus = PermissionStatus::newEmpty();
538 if ( $this->mNewPage ) {
539 $performer->
authorizeRead(
'read', $this->mNewPage, $permStatus );
541 if ( $this->mOldPage ) {
542 $performer->
authorizeRead(
'read', $this->mOldPage, $permStatus );
544 return $permStatus->toLegacyErrorArray();
554 ( $this->mOldRevisionRecord &&
555 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) ||
556 ( $this->mNewRevisionRecord &&
557 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) )
576 $allowed = !$this->mNewRevisionRecord || $this->mNewRevisionRecord->userCan(
577 RevisionRecord::DELETED_TEXT,
580 if ( $this->mOldRevisionRecord &&
581 !$this->mOldRevisionRecord->userCan(
582 RevisionRecord::DELETED_TEXT,
607 # Allow frames except in certain special cases
609 $out->allowClickjacking();
610 $out->setRobotPolicy(
'noindex,nofollow' );
613 $this->hookRunner->onDifferenceEngineShowDiffPage( $out );
616 if ( $this->hookRunner->onDifferenceEngineShowDiffPageMaybeShowMissingRevision( $this ) ) {
624 if ( count( $permErrors ) ) {
630 $query = $this->slotDiffOptions;
631 # Carry over 'diffonly' param via navigation links
632 if ( $diffOnly != MediaWikiServices::getInstance()
633 ->getUserOptionsLookup()->getBoolOption( $user,
'diffonly' )
635 $query[
'diffonly'] = $diffOnly;
637 # Cascade unhide param in links for easy deletion browsing
638 if ( $this->unhide ) {
639 $query[
'unhide'] = 1;
642 # Check if one of the revisions is deleted/suppressed
649 # mOldRevisionRecord is false if the difference engine is called with a "vague" query for
650 # a diff between a version V and its previous version V' AND the version V
651 # is the first version of that article. In that case, V' does not exist.
652 if ( $this->mOldRevisionRecord ===
false ) {
653 if ( $this->mNewPage ) {
654 $out->setPageTitle( $this->
msg(
'difference-title', $this->mNewPage->getPrefixedText() ) );
659 $this->hookRunner->onDifferenceEngineOldHeaderNoOldRev( $oldHeader );
661 $this->hookRunner->onDifferenceEngineViewHeader( $this );
663 if ( !$this->mOldPage || !$this->mNewPage ) {
666 } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
667 $out->setPageTitle( $this->
msg(
'difference-title', $this->mNewPage->getPrefixedText() ) );
670 $out->setPageTitle( $this->
msg(
'difference-title-multipage',
671 $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
672 $out->addSubtitle( $this->
msg(
'difference-multipage' ) );
676 if ( $samePage && $this->mNewPage &&
677 $this->
getAuthority()->probablyCan(
'edit', $this->mNewPage )
679 if ( $this->mNewRevisionRecord->isCurrent() &&
680 $this->getAuthority()->probablyCan(
'rollback', $this->mNewPage )
683 $this->mNewRevisionRecord,
687 if ( $rollbackLink ) {
688 $out->preventClickjacking();
689 $rollback =
"\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
693 if ( $this->
userCanEdit( $this->mOldRevisionRecord ) &&
696 $undoLink = Html::element(
'a', [
697 'href' => $this->mNewPage->getLocalURL( [
699 'undoafter' => $this->mOldid,
700 'undo' => $this->mNewid
704 $this->msg(
'editundo' )->text()
706 $revisionTools[
'mw-diff-undo'] = $undoLink;
709 # Make "previous revision link"
710 $hasPrevious = $samePage && $this->mOldPage &&
711 $this->revisionStore->getPreviousRevision( $this->mOldRevisionRecord );
712 if ( $hasPrevious ) {
713 $prevlink = $this->linkRenderer->makeKnownLink(
715 $this->
msg(
'previousdiff' )->text(),
716 [
'id' =>
'differences-prevlink' ],
717 [
'diff' =>
'prev',
'oldid' => $this->mOldid ] + $query
720 $prevlink =
"\u{00A0}";
723 if ( $this->mOldRevisionRecord->isMinor() ) {
724 $oldminor = ChangesList::flag(
'minor' );
729 $oldRevRecord = $this->mOldRevisionRecord;
735 $oldHeader =
'<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader .
'</strong></div>' .
736 '<div id="mw-diff-otitle2">' .
738 '<div id="mw-diff-otitle3">' . $oldminor .
740 '<div id="mw-diff-otitle5">' . $oldChangeTags[0] .
'</div>' .
741 '<div id="mw-diff-otitle4">' . $prevlink .
'</div>';
744 $this->hookRunner->onDifferenceEngineOldHeader(
745 $this, $oldHeader, $prevlink, $oldminor, $diffOnly, $ldel, $this->unhide );
748 $out->addJsConfigVars( [
749 'wgDiffOldId' => $this->mOldid,
750 'wgDiffNewId' => $this->mNewid,
753 # Make "next revision link"
754 # Skip next link on the top revision
755 if ( $samePage && $this->mNewPage && !$this->mNewRevisionRecord->isCurrent() ) {
756 $nextlink = $this->linkRenderer->makeKnownLink(
758 $this->
msg(
'nextdiff' )->text(),
759 [
'id' =>
'differences-nextlink' ],
760 [
'diff' =>
'next',
'oldid' => $this->mNewid ] + $query
763 $nextlink =
"\u{00A0}";
766 if ( $this->mNewRevisionRecord->isMinor() ) {
767 $newminor = ChangesList::flag(
'minor' );
772 # Handle RevisionDelete links...
775 # Allow extensions to define their own revision tools
776 $this->hookRunner->onDiffTools(
777 $this->mNewRevisionRecord,
779 $this->mOldRevisionRecord ?:
null,
783 $formattedRevisionTools = [];
785 foreach ( $revisionTools as $key => $tool ) {
786 $toolClass = is_string( $key ) ? $key :
'mw-diff-tool';
787 $element = Html::rawElement(
789 [
'class' => $toolClass ],
790 $this->
msg(
'parentheses' )->rawParams( $tool )->escaped()
792 $formattedRevisionTools[] = $element;
795 $newRevRecord = $this->mNewRevisionRecord;
798 ' ' . implode(
' ', $formattedRevisionTools );
801 $newHeader =
'<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader .
'</strong></div>' .
804 '<div id="mw-diff-ntitle3">' . $newminor .
806 '<div id="mw-diff-ntitle5">' . $newChangeTags[0] .
'</div>' .
807 '<div id="mw-diff-ntitle4">' . $nextlink . $this->
markPatrolledLink() .
'</div>';
810 $this->hookRunner->onDifferenceEngineNewHeader( $this, $newHeader,
811 $formattedRevisionTools, $nextlink, $rollback, $newminor, $diffOnly,
812 $rdel, $this->unhide );
814 # If the diff cannot be shown due to a deleted revision, then output
815 # the diff header and links to unhide (if available)...
819 $out->addHTML( $this->
addHeader(
'', $oldHeader, $newHeader, $multi ) );
821 $msg = $suppressed ?
'rev-suppressed-no-diff' :
'rev-deleted-no-diff';
822 # Give explanation for why revision is not visible
825 $this->
msg( $msg )->parse(),
830 # Give explanation and add a link to view the diff...
831 $query = $this->
getRequest()->appendQueryValue(
'unhide',
'1' );
832 $link = $this->
getTitle()->getFullURL( $query );
833 $msg = $suppressed ?
'rev-suppressed-unhide-diff' :
'rev-deleted-unhide-diff';
836 $this->
msg( $msg, $link )->parse(),
841 # Otherwise, output a regular diff...
843 # Add deletion notice if the user is viewing deleted content
846 $msg = $suppressed ?
'rev-suppressed-diff-view' :
'rev-deleted-diff-view';
847 $notice = Html::warningBox(
848 $this->
msg( $msg )->parse(),
852 $this->
showDiff( $oldHeader, $newHeader, $notice );
870 if ( $this->mMarkPatrolledLink ===
null ) {
873 if ( !$linkInfo || !$this->mNewPage ) {
874 $this->mMarkPatrolledLink =
'';
876 $this->mMarkPatrolledLink =
' <span class="patrollink" data-mw="interface">[' .
877 $this->linkRenderer->makeKnownLink(
879 $this->
msg(
'markaspatrolleddiff' )->text(),
882 'action' =>
'markpatrolled',
883 'rcid' => $linkInfo[
'rcid'],
887 $this->hookRunner->onDifferenceEngineMarkPatrolledLink( $this,
888 $this->mMarkPatrolledLink, $linkInfo[
'rcid'] );
891 return $this->mMarkPatrolledLink;
908 $config->get(
'UseRCPatrol' ) &&
910 $this->getAuthority()->probablyCan(
'patrol', $this->mNewPage ) &&
913 RecentChange::isInRCLifespan( $this->mNewRevisionRecord->getTimestamp(), 21600 )
916 $change = RecentChange::newFromConds(
918 'rc_this_oldid' => $this->mNewid,
919 'rc_patrolled' => RecentChange::PRC_UNPATROLLED
924 if ( $change && !$change->getPerformerIdentity()->equals( $user ) ) {
925 $rcid = $change->getAttribute(
'rc_id' );
936 $this->hookRunner->onDifferenceEngineMarkPatrolledRCID( $rcid, $this, $change, $user );
940 $this->
getOutput()->preventClickjacking();
942 $this->
getOutput()->addModules(
'mediawiki.misc-authed-curate' );
966 if ( $link !==
'' ) {
967 $link =
"\u{00A0}\u{00A0}\u{00A0}" . $link .
' ';
979 if ( $this->isContentOverridden ) {
983 throw new LogicException(
985 .
' is not supported after calling setContent(). Use setRevisions() instead.'
991 # Add "current version as of X" title
992 $out->addHTML(
"<hr class='diff-hr' id='mw-oldid' />
993 <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
994 # Page content may be handled by a hooked call instead...
995 if ( $this->hookRunner->onArticleContentOnDiff( $this, $out ) ) {
997 if ( !$this->mNewPage ) {
1004 $out->setRevisionId( $this->mNewid );
1005 $out->setRevisionTimestamp( $this->mNewRevisionRecord->getTimestamp() );
1006 $out->setArticleFlag(
true );
1008 if ( !$this->hookRunner->onArticleRevisionViewCustom(
1009 $this->mNewRevisionRecord, $this->mNewPage, $this->mOldid, $out )
1015 if ( $this->
getTitle()->equals( $this->mNewPage ) ) {
1022 $wikiPage = $this->wikiPageFactory->newFromTitle( $this->mNewPage );
1025 $parserOutput = $this->
getParserOutput( $wikiPage, $this->mNewRevisionRecord );
1027 # WikiPage::getParserOutput() should not return false, but just in case
1028 if ( $parserOutput ) {
1030 if ( $this->hookRunner->onDifferenceEngineRenderRevisionAddParserOutput(
1031 $this, $out, $parserOutput, $wikiPage )
1033 $out->addParserOutput( $parserOutput, [
1034 'enableSectionEditLinks' => $this->mNewRevisionRecord->isCurrent()
1035 && $this->getAuthority()->probablyCan(
1037 $this->mNewRevisionRecord->getPage()
1046 if ( $this->hookRunner->onDifferenceEngineRenderRevisionShowFinalPatrolLink() ) {
1047 # Add redundant patrol link on bottom...
1059 if ( !$revRecord->
getId() ) {
1069 return $parserOutput;
1082 public function showDiff( $otitle, $ntitle, $notice =
'' ) {
1084 $this->hookRunner->onDifferenceEngineShowDiff( $this );
1086 $diff = $this->
getDiff( $otitle, $ntitle, $notice );
1087 if ( $diff ===
false ) {
1103 if ( !$this->isSlotDiffRenderer ) {
1104 $this->
getOutput()->addModules(
'mediawiki.diff' );
1106 'mediawiki.interface.helpers.styles',
1107 'mediawiki.diff.styles'
1110 $slotDiffRenderer->addModules( $this->
getOutput() );
1124 public function getDiff( $otitle, $ntitle, $notice =
'' ) {
1126 if ( $body ===
false ) {
1132 if ( $body ===
'' ) {
1133 $notice .=
'<div class="mw-diff-empty">' .
1134 $this->
msg(
'diff-empty' )->parse() .
1138 return $this->
addHeader( $body, $otitle, $ntitle, $multi, $notice );
1147 $this->mCacheHit =
true;
1149 if ( !$this->isContentOverridden ) {
1152 } elseif ( $this->mOldRevisionRecord &&
1153 !$this->mOldRevisionRecord->userCan(
1154 RevisionRecord::DELETED_TEXT,
1155 $this->getAuthority()
1159 } elseif ( $this->mNewRevisionRecord &&
1160 !$this->mNewRevisionRecord->userCan(
1161 RevisionRecord::DELETED_TEXT,
1162 $this->getAuthority()
1167 if ( $this->mOldRevisionRecord ===
false || (
1168 $this->mOldRevisionRecord &&
1169 $this->mNewRevisionRecord &&
1170 $this->mOldRevisionRecord->getId() &&
1171 $this->mOldRevisionRecord->getId() == $this->mNewRevisionRecord->getId()
1173 if ( $this->hookRunner->onDifferenceEngineShowEmptyOldContent( $this ) ) {
1181 $services = MediaWikiServices::getInstance();
1182 $cache = $services->getMainWANObjectCache();
1183 $stats = $services->getStatsdDataFactory();
1184 if ( $this->mOldid && $this->mNewid ) {
1188 if ( $key ===
null ) {
1193 if ( !$this->mRefreshCache ) {
1194 $difftext =
$cache->get( $key );
1195 if ( is_string( $difftext ) ) {
1196 $stats->updateCount(
'diff_cache.hit', 1 );
1198 $difftext .=
"\n<!-- diff cache key $key -->\n";
1204 $this->mCacheHit =
false;
1216 $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role][
'old'],
1217 $slotContents[$role][
'new'] );
1218 if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1223 $difftext .= $slotDiff;
1227 if ( !$this->hookRunner->onAbortDiffCache( $this ) ) {
1228 $stats->updateCount(
'diff_cache.uncacheable', 1 );
1229 } elseif ( $key !==
false ) {
1230 $stats->updateCount(
'diff_cache.miss', 1 );
1231 $cache->set( $key, $difftext, 7 * 86400 );
1233 $stats->updateCount(
'diff_cache.uncacheable', 1 );
1249 if ( !isset( $diffRenderers[$role] ) ) {
1254 $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role][
'old'],
1255 $slotContents[$role][
'new'] );
1260 if ( $role !== SlotRecord::MAIN ) {
1278 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1280 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-header',
'lang' => $userLang ],
1281 Html::element(
'th', [
'colspan' => $columnCount ], $headerText ) );
1311 if ( !$this->mOldid || !$this->mNewid ) {
1312 throw new MWException(
'mOldid and mNewid must be set to get diff cache key.' );
1318 $engine ===
'php' ? false : $engine,
1320 "old-{$this->mOldid}",
1321 "rev-{$this->mNewid}"
1324 if ( $engine ===
'wikidiff2' ) {
1325 $params[] = phpversion(
'wikidiff2' );
1328 if ( !$this->isSlotDiffRenderer ) {
1330 $params = array_merge( $params, $slotDiffRenderer->getExtraCacheKeys() );
1348 $this->mOldid = 123456789;
1349 $this->mNewid = 987654321;
1353 if ( $cacheString ) {
1354 return [ $cacheString ];
1367 if ( array_slice( $params, 0, count( $standardParams ) ) === $standardParams ) {
1368 $params = array_slice( $params, count( $standardParams ) );
1378 $this->slotDiffOptions = $options;
1398 && $this->isSlotDiffRenderer
1404 throw new Exception( get_class( $this ) .
': could not maintain backwards compatibility. '
1405 .
'Please use a SlotDiffRenderer.' );
1407 return $slotDiffRenderer->getDiff( $old, $new ) . $this->
getDebugString();
1423 $slotDiffRenderer = $this->contentHandlerFactory
1425 ->getSlotDiffRenderer( $this->
getContext() );
1429 throw new Exception(
'The slot diff renderer for text content should be a '
1430 .
'TextSlotDiffRenderer subclass' );
1432 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->
getDebugString();
1442 $diffEngine = MediaWikiServices::getInstance()->getMainConfig()
1443 ->get(
'DiffEngine' );
1444 $externalDiffEngine = MediaWikiServices::getInstance()->getMainConfig()
1445 ->get(
'ExternalDiffEngine' );
1447 if ( $diffEngine ===
null ) {
1448 $engines = [
'external',
'wikidiff2',
'php' ];
1450 $engines = [ $diffEngine ];
1453 $failureReason =
null;
1454 foreach ( $engines as $engine ) {
1455 switch ( $engine ) {
1457 if ( is_string( $externalDiffEngine ) ) {
1458 if ( is_executable( $externalDiffEngine ) ) {
1459 return $externalDiffEngine;
1461 $failureReason =
'ExternalDiffEngine config points to a non-executable';
1462 if ( $diffEngine ===
null ) {
1463 wfDebug(
"$failureReason, ignoring" );
1466 $failureReason =
'ExternalDiffEngine config is set to a non-string value';
1467 if ( $diffEngine ===
null && $externalDiffEngine ) {
1468 wfWarn(
"$failureReason, ignoring" );
1474 if ( function_exists(
'wikidiff2_do_diff' ) ) {
1477 $failureReason =
'wikidiff2 is not available';
1485 throw new DomainException(
'Invalid value for $wgDiffEngine: ' . $engine );
1488 throw new UnexpectedValueException(
"Cannot use diff engine '$engine': $failureReason" );
1504 $slotDiffRenderer = $this->contentHandlerFactory
1506 ->getSlotDiffRenderer( $this->
getContext() );
1510 throw new Exception(
'The slot diff renderer for text content should be a '
1511 .
'TextSlotDiffRenderer subclass' );
1513 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->
getDebugString();
1525 if ( !$this->enableDebugComment ) {
1529 if ( $this->
getConfig()->
get(
'ShowHostnames' ) ) {
1534 return "<!-- diff generator: " .
1535 implode(
" ", array_map(
"htmlspecialchars", $data ) ) .
1543 $engine = self::getEngine();
1544 if ( $engine ===
'wikidiff2' ) {
1545 return $this->
debug(
'wikidiff2' );
1546 } elseif ( $engine ===
'php' ) {
1547 return $this->
debug(
'native PHP' );
1549 return $this->
debug(
"external $engine" );
1561 if ( $this->
getEngine() ===
'wikidiff2' &&
1562 version_compare( phpversion(
'wikidiff2' ),
'1.5.1',
'>=' )
1577 return preg_replace_callback(
1578 '/<!--LINE (\d+)-->/',
1579 [ $this,
'localiseLineNumbersCb' ],
1589 if (
$matches[1] ===
'1' && $this->mReducedLineNumbers ) {
1593 return $this->
msg(
'lineno' )->numParams(
$matches[1] )->escaped();
1603 return preg_replace_callback(
1604 '/class="mw-diff-movedpara-(left|right)"/',
1605 [ $this,
'addLocalisedTitleTooltipsCb' ],
1616 'diff-paragraph-moved-toold' :
1617 'diff-paragraph-moved-tonew';
1618 return $matches[0] .
' title="' . $this->
msg( $key )->escaped() .
'"';
1629 !$this->mOldRevisionRecord || !$this->mNewRevisionRecord
1630 || !$this->mOldPage || !$this->mNewPage
1631 || !$this->mOldPage->equals( $this->mNewPage )
1632 || $this->mOldRevisionRecord->getId() ===
null
1633 || $this->mNewRevisionRecord->getId() ===
null
1635 || $this->mNewPage->getArticleID() !== $this->mOldRevisionRecord->getPageId()
1636 || $this->mNewPage->getArticleID() !== $this->mNewRevisionRecord->getPageId()
1641 if ( $this->mOldRevisionRecord->getTimestamp() > $this->mNewRevisionRecord->getTimestamp() ) {
1642 $oldRevRecord = $this->mNewRevisionRecord;
1643 $newRevRecord = $this->mOldRevisionRecord;
1645 $oldRevRecord = $this->mOldRevisionRecord;
1646 $newRevRecord = $this->mNewRevisionRecord;
1651 $nEdits = $this->revisionStore->countRevisionsBetween(
1652 $this->mNewPage->getArticleID(),
1657 if ( $nEdits > 0 && $nEdits <= 1000 ) {
1660 $users = $this->revisionStore->getAuthorsBetween(
1661 $this->mNewPage->getArticleID(),
1667 $numUsers = count( $users );
1669 $newRevUser = $newRevRecord->getUser( RevisionRecord::RAW );
1670 $newRevUserText = $newRevUser ? $newRevUser->getName() :
'';
1671 if ( $numUsers == 1 && $users[0]->getName() == $newRevUserText ) {
1674 }
catch ( InvalidArgumentException $e ) {
1678 return self::intermediateEditsMsg( $nEdits, $numUsers, $limit );
1694 if ( $numUsers === 0 ) {
1695 $msg =
'diff-multi-sameuser';
1696 } elseif ( $numUsers > $limit ) {
1697 $msg =
'diff-multi-manyusers';
1700 $msg =
'diff-multi-otherusers';
1703 return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1711 if ( !$revRecord->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1731 $timestamp =
$lang->userTimeAndDate( $revtimestamp, $user );
1732 $dateofrev =
$lang->userDate( $revtimestamp, $user );
1733 $timeofrev =
$lang->userTime( $revtimestamp, $user );
1736 $rev->
isCurrent() ?
'currentrev-asof' :
'revisionasof',
1742 if ( $complete !==
'complete' ) {
1749 [
'oldid' => $rev->
getId() ] );
1752 $editQuery = [
'action' =>
'edit' ];
1754 $editQuery[
'oldid'] = $rev->
getId();
1757 $key = $this->
getAuthority()->probablyCan(
'edit', $rev->
getPage() ) ?
'editold' :
'viewsourceold';
1758 $msg = $this->
msg( $key )->text();
1759 $editLink = $this->
msg(
'parentheses' )->rawParams(
1760 $this->linkRenderer->makeKnownLink(
$title, $msg, [], $editQuery ) )->escaped();
1761 $header .=
' ' . Html::rawElement(
1763 [
'class' =>
'mw-diff-edit' ],
1766 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1774 $header = Html::rawElement(
'span', [
'class' =>
'history-deleted' ],
$header );
1792 public function addHeader( $diff, $otitle, $ntitle, $multi =
'', $notice =
'' ) {
1795 $header = Html::openElement(
'table', [
1798 'diff-contentalign-' . $this->
getDiffLang()->alignStart(),
1799 'diff-editfont-' . $this->
getUser()->getOption(
'editfont' )
1801 'data-mw' =>
'interface',
1803 $userLang = htmlspecialchars( $this->
getLanguage()->getHtmlCode() );
1805 if ( !$diff && !$otitle ) {
1807 <tr class=\"diff-title\" lang=\"{$userLang}\">
1808 <td class=\"diff-ntitle\">{$ntitle}</td>
1814 <col class=\"diff-marker\" />
1815 <col class=\"diff-content\" />
1816 <col class=\"diff-marker\" />
1817 <col class=\"diff-content\" />";
1824 if ( $otitle || $ntitle ) {
1826 $deletedClass =
'diff-side-deleted';
1827 $addedClass =
'diff-side-added';
1829 <tr class=\"diff-title\" lang=\"{$userLang}\">
1830 <td colspan=\"$colspan\" class=\"diff-otitle {$deletedClass}\">{$otitle}</td>
1831 <td colspan=\"$colspan\" class=\"diff-ntitle {$addedClass}\">{$ntitle}</td>
1836 if ( $multi !=
'' ) {
1837 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
1838 "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
1840 if ( $notice !=
'' ) {
1841 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
1842 "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
1845 return $header . $diff .
"</table>";
1856 $this->mOldContent = $oldContent;
1857 $this->mNewContent = $newContent;
1859 $this->mTextLoaded = 2;
1860 $this->mRevisionsLoaded =
true;
1861 $this->isContentOverridden =
true;
1862 $this->slotDiffRenderers =
null;
1873 if ( $oldRevision ) {
1874 $this->mOldRevisionRecord = $oldRevision;
1875 $this->mOldid = $oldRevision->
getId();
1879 $this->mOldContent = $oldRevision->
getContent( SlotRecord::MAIN,
1880 RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
1882 $this->mOldPage =
null;
1883 $this->mOldRevisionRecord = $this->mOldid =
false;
1885 $this->mNewRevisionRecord = $newRevision;
1886 $this->mNewid = $newRevision->
getId();
1888 $this->mNewContent = $newRevision->
getContent( SlotRecord::MAIN,
1889 RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
1891 $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded =
true;
1892 $this->mTextLoaded = $oldRevision ? 2 : 1;
1893 $this->isContentOverridden =
false;
1894 $this->slotDiffRenderers =
null;
1904 $this->mDiffLang =
$lang;
1920 if ( $new ===
'prev' ) {
1922 $newid = intval( $old );
1924 $newRev = $this->revisionStore->getRevisionById( $newid );
1926 $oldRev = $this->revisionStore->getPreviousRevision( $newRev );
1928 $oldid = $oldRev->getId();
1931 } elseif ( $new ===
'next' ) {
1933 $oldid = intval( $old );
1935 $oldRev = $this->revisionStore->getRevisionById( $oldid );
1937 $newRev = $this->revisionStore->getNextRevision( $oldRev );
1939 $newid = $newRev->getId();
1943 $oldid = intval( $old );
1944 $newid = intval( $new );
1947 return [ $oldid, $newid ];
1951 if ( $this->mRevisionsIdsLoaded ) {
1955 $this->mRevisionsIdsLoaded =
true;
1957 $old = $this->mOldid;
1958 $new = $this->mNewid;
1960 list( $this->mOldid, $this->mNewid ) = self::mapDiffPrevNext( $old, $new );
1961 if ( $new ===
'next' && $this->mNewid ===
false ) {
1962 # if no result, NewId points to the newest old revision. The only newer
1963 # revision is cur, which is "0".
1967 $this->hookRunner->onNewDifferenceEngine(
1968 $this->
getTitle(), $this->mOldid, $this->mNewid, $old, $new );
1985 if ( $this->mRevisionsLoaded ) {
1986 return $this->isContentOverridden ||
1987 ( $this->mOldRevisionRecord !==
null && $this->mNewRevisionRecord !== null );
1991 $this->mRevisionsLoaded =
true;
1996 if ( $this->mNewid ) {
1997 $this->mNewRevisionRecord = $this->revisionStore->getRevisionById( $this->mNewid );
1999 $this->mNewRevisionRecord = $this->revisionStore->getRevisionByTitle( $this->
getTitle() );
2007 $this->mNewid = $this->mNewRevisionRecord->getId();
2008 if ( $this->mNewid ) {
2009 $this->mNewPage = Title::newFromLinkTarget(
2010 $this->mNewRevisionRecord->getPageAsLinkTarget()
2013 $this->mNewPage =
null;
2017 $this->mOldRevisionRecord =
false;
2018 if ( $this->mOldid ) {
2019 $this->mOldRevisionRecord = $this->revisionStore->getRevisionById( $this->mOldid );
2020 } elseif ( $this->mOldid === 0 ) {
2021 $revRecord = $this->revisionStore->getPreviousRevision( $this->mNewRevisionRecord );
2023 $this->mOldid = $revRecord->getId();
2024 $this->mOldRevisionRecord = $revRecord;
2027 $this->mOldid =
false;
2028 $this->mOldRevisionRecord =
false;
2032 if ( $this->mOldRevisionRecord ===
null ) {
2036 if ( $this->mOldRevisionRecord && $this->mOldRevisionRecord->getId() ) {
2037 $this->mOldPage = Title::newFromLinkTarget(
2038 $this->mOldRevisionRecord->getPageAsLinkTarget()
2041 $this->mOldPage =
null;
2046 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
2047 if ( $this->mOldid !==
false ) {
2048 $tagIds =
$dbr->selectFieldValues(
2051 [
'ct_rev_id' => $this->mOldid ],
2055 foreach ( $tagIds as $tagId ) {
2057 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2062 $this->mOldTags = implode(
',', $tags );
2064 $this->mOldTags =
false;
2067 $tagIds =
$dbr->selectFieldValues(
2070 [
'ct_rev_id' => $this->mNewid ],
2074 foreach ( $tagIds as $tagId ) {
2076 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2081 $this->mNewTags = implode(
',', $tags );
2095 if ( $this->mTextLoaded == 2 ) {
2097 ( $this->mOldRevisionRecord ===
false || $this->mOldContent )
2098 && $this->mNewContent;
2102 $this->mTextLoaded = 2;
2108 if ( $this->mOldRevisionRecord ) {
2109 $this->mOldContent = $this->mOldRevisionRecord->getContent(
2111 RevisionRecord::FOR_THIS_USER,
2114 if ( $this->mOldContent ===
null ) {
2119 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2121 RevisionRecord::FOR_THIS_USER,
2124 $this->hookRunner->onDifferenceEngineLoadTextAfterNewContentIsLoaded( $this );
2125 if ( $this->mNewContent ===
null ) {
2138 if ( $this->mTextLoaded >= 1 ) {
2142 $this->mTextLoaded = 1;
2148 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2150 RevisionRecord::FOR_THIS_USER,
2154 $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 or implementati...
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.
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.
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 $enableDebugComment
Set this to true to add debug info to the HTML output.
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.
WikiPageFactory $wikiPageFactory
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...
getPermissionErrors(Authority $performer)
Get the permission errors associated with the revisions for the current diff.
Content null $mNewContent
getDiffBody()
Get the diff table body, without header.
getTitle()
1.18 to override Title|null
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.
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)
array $slotDiffOptions
A set of options that will be passed to the SlotDiffRenderer upon creation.
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.
isUserAllowedToSeeRevisions(Authority $performer)
Checks whether the current user has permission for accessing the revisions of the diff.
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.
getDiff( $otitle, $ntitle, $notice='')
Get complete diff table, including header.
RevisionRecord null $mNewRevisionRecord
New revision (right pane).
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.
shouldBeHiddenFromUser(Authority $performer)
Checks whether the diff should be hidden from the current user This is based on whether the user is a...
getRevisionHeader(RevisionRecord $rev, $complete='')
Get a header for a specified revision.
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.
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 titleAttrib( $name, $options=null, array $msgParams=[])
Given the id of an interface element, constructs the appropriate title attribute from the system mess...
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
static revComment(RevisionRecord $revRecord, $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 getRevDeleteLink(Authority $performer, RevisionRecord $revRecord, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
static generateRollback(RevisionRecord $revRecord, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
static revUserTools(RevisionRecord $revRecord, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
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.
Class representing a MediaWiki article and history.
getParserOutput(ParserOptions $parserOptions, $oldid=null, $noCache=false)
Get a ParserOutput for the given ParserOptions and revision ID.
makeParserOptions( $context)
Get parser options suitable for rendering the primary article wikitext.
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