211 $refreshCache =
false,
$unhide =
false
229 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'\n" );
231 $this->mOldid = $old;
232 $this->mNewid = $new;
233 $this->mRefreshCache = $refreshCache;
242 if ( $this->isSlotDiffRenderer ) {
243 throw new LogicException( __METHOD__ .
' called in slot diff renderer mode' );
246 if ( $this->slotDiffRenderers ===
null ) {
252 $this->slotDiffRenderers = array_map(
function ( $contents ) {
254 $content = $contents[
'new'] ?: $contents[
'old'];
268 $this->isSlotDiffRenderer =
true;
277 if ( $this->isContentOverridden ) {
279 SlotRecord::MAIN => [
288 $newSlots = $this->mNewRev->getRevisionRecord()->getSlots()->getSlots();
289 if ( $this->mOldRev ) {
290 $oldSlots = $this->mOldRev->getRevisionRecord()->getSlots()->getSlots();
298 $roles = array_merge( array_keys( $newSlots ), array_keys( $oldSlots ) );
301 foreach ( $roles as $role ) {
303 'old' => isset( $oldSlots[$role] ) ? $oldSlots[$role]->getContent() :
null,
304 'new' => isset( $newSlots[$role] ) ? $newSlots[$role]->getContent() :
null,
308 if ( isset( $slots[SlotRecord::MAIN] ) ) {
309 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
316 return parent::getTitle() ?: Title::makeTitle(
NS_SPECIAL,
'BadTitle/DifferenceEngine' );
326 $this->mReducedLineNumbers = $value;
335 if ( $this->mDiffLang ===
null ) {
336 # Default language in which the diff text is written.
337 $this->mDiffLang = $this->
getTitle()->getPageLanguage();
382 return $this->mOldRev ? $this->mOldRev->getRevisionRecord() :
null;
391 return $this->mNewRev ? $this->mNewRev->getRevisionRecord() :
null;
403 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
404 if ( $permissionManager->userHasRight( $this->getUser(),
'deletedhistory' ) ) {
406 $arQuery = Revision::getArchiveQueryInfo();
407 $row =
$dbr->selectRow(
409 array_merge( $arQuery[
'fields'], [
'ar_namespace',
'ar_title' ] ),
410 [
'ar_rev_id' => $id ],
416 $rev = Revision::newFromArchiveRow( $row );
417 $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
419 return SpecialPage::getTitleFor(
'Undelete' )->getFullURL( [
420 'target' =>
$title->getPrefixedText(),
421 'timestamp' => $rev->getTimestamp()
439 return "[$link $id]";
449 if ( $this->mOldRev ===
null ||
450 ( $this->mOldRev && $this->mOldContent ===
null )
454 if ( $this->mNewRev ===
null ||
455 ( $this->mNewRev && $this->mNewContent ===
null )
460 $out->setPageTitle( $this->
msg(
'errorpagetitle' ) );
461 $msg = $this->
msg(
'difference-missing-revision' )
462 ->params( $this->
getLanguage()->listToText( $missing ) )
463 ->numParams( count( $missing ) )
465 $out->addHTML( $msg );
469 # Allow frames except in certain special cases
471 $out->allowClickjacking();
472 $out->setRobotPolicy(
'noindex,nofollow' );
475 Hooks::run(
'DifferenceEngineShowDiffPage', [ $out ] );
478 if ( Hooks::run(
'DifferenceEngineShowDiffPageMaybeShowMissingRevision', [ $this ] ) ) {
486 if ( $this->mNewPage ) {
487 $permErrors = $this->mNewPage->getUserPermissionsErrors(
'read', $user );
489 if ( $this->mOldPage ) {
491 $this->mOldPage->getUserPermissionsErrors(
'read', $user ) );
493 if ( count( $permErrors ) ) {
500 # Carry over 'diffonly' param via navigation links
501 if ( $diffOnly != $user->getBoolOption(
'diffonly' ) ) {
502 $query[
'diffonly'] = $diffOnly;
504 # Cascade unhide param in links for easy deletion browsing
505 if ( $this->unhide ) {
506 $query[
'unhide'] = 1;
509 # Check if one of the revisions is deleted/suppressed
510 $deleted = $suppressed =
false;
511 $allowed = $this->mNewRev->userCan( RevisionRecord::DELETED_TEXT, $user );
515 # mOldRev is false if the difference engine is called with a "vague" query for
516 # a diff between a version V and its previous version V' AND the version V
517 # is the first version of that article. In that case, V' does not exist.
518 if ( $this->mOldRev ===
false ) {
519 if ( $this->mNewPage ) {
520 $out->setPageTitle( $this->
msg(
'difference-title', $this->mNewPage->getPrefixedText() ) );
525 Hooks::run(
'DifferenceEngineOldHeaderNoOldRev', [ &$oldHeader ] );
527 Hooks::run(
'DiffViewHeader', [ $this, $this->mOldRev, $this->mNewRev ] );
529 if ( !$this->mOldPage || !$this->mNewPage ) {
532 } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
533 $out->setPageTitle( $this->
msg(
'difference-title', $this->mNewPage->getPrefixedText() ) );
536 $out->setPageTitle( $this->
msg(
'difference-title-multipage',
537 $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
538 $out->addSubtitle( $this->
msg(
'difference-multipage' ) );
542 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
544 if ( $samePage && $this->mNewPage && $permissionManager->quickUserCan(
545 'edit', $user, $this->mNewPage
547 if ( $this->mNewRev->isCurrent() && $permissionManager->quickUserCan(
548 'rollback', $user, $this->mNewPage
552 if ( $rollbackLink ) {
553 $out->preventClickjacking();
554 $rollback =
"\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
561 $undoLink = Html::element(
'a', [
562 'href' => $this->mNewPage->getLocalURL( [
564 'undoafter' => $this->mOldid,
565 'undo' => $this->mNewid
569 $this->msg(
'editundo' )->text()
571 $revisionTools[
'mw-diff-undo'] = $undoLink;
575 # Make "previous revision link"
576 if ( $samePage && $this->mOldPage && $this->mOldRev->getPrevious() ) {
579 $this->
msg(
'previousdiff' )->escaped(),
580 [
'id' =>
'differences-prevlink' ],
581 [
'diff' =>
'prev',
'oldid' => $this->mOldid ] + $query
584 $prevlink =
"\u{00A0}";
587 if ( $this->mOldRev->isMinor() ) {
597 $oldHeader =
'<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader .
'</strong></div>' .
598 '<div id="mw-diff-otitle2">' .
600 '<div id="mw-diff-otitle3">' . $oldminor .
602 '<div id="mw-diff-otitle5">' . $oldChangeTags[0] .
'</div>' .
603 '<div id="mw-diff-otitle4">' . $prevlink .
'</div>';
606 Hooks::run(
'DifferenceEngineOldHeader', [ $this, &$oldHeader, $prevlink, $oldminor,
607 $diffOnly, $ldel, $this->unhide ] );
609 if ( $this->mOldRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
611 if ( $this->mOldRev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
616 # Check if this user can see the revisions
617 if ( !$this->mOldRev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
622 $out->addJsConfigVars( [
623 'wgDiffOldId' => $this->mOldid,
624 'wgDiffNewId' => $this->mNewid,
627 # Make "next revision link"
628 # Skip next link on the top revision
629 if ( $samePage && $this->mNewPage && !$this->mNewRev->isCurrent() ) {
632 $this->
msg(
'nextdiff' )->escaped(),
633 [
'id' =>
'differences-nextlink' ],
634 [
'diff' =>
'next',
'oldid' => $this->mNewid ] + $query
637 $nextlink =
"\u{00A0}";
640 if ( $this->mNewRev->isMinor() ) {
646 # Handle RevisionDelete links...
649 # Allow extensions to define their own revision tools
650 Hooks::run(
'DiffRevisionTools',
651 [ $this->mNewRev, &$revisionTools, $this->mOldRev, $user ] );
652 $formattedRevisionTools = [];
654 foreach ( $revisionTools as $key => $tool ) {
655 $toolClass = is_string( $key ) ? $key :
'mw-diff-tool';
656 $element = Html::rawElement(
658 [
'class' => $toolClass ],
659 $this->
msg(
'parentheses' )->rawParams( $tool )->escaped()
661 $formattedRevisionTools[] = $element;
664 ' ' . implode(
' ', $formattedRevisionTools );
667 $newHeader =
'<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader .
'</strong></div>' .
670 '<div id="mw-diff-ntitle3">' . $newminor .
672 '<div id="mw-diff-ntitle5">' . $newChangeTags[0] .
'</div>' .
673 '<div id="mw-diff-ntitle4">' . $nextlink . $this->
markPatrolledLink() .
'</div>';
676 Hooks::run(
'DifferenceEngineNewHeader', [ $this, &$newHeader, $formattedRevisionTools,
677 $nextlink, $rollback, $newminor, $diffOnly, $rdel, $this->unhide ] );
679 if ( $this->mNewRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
681 if ( $this->mNewRev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
686 # If the diff cannot be shown due to a deleted revision, then output
687 # the diff header and links to unhide (if available)...
688 if ( $deleted && ( !$this->unhide || !$allowed ) ) {
691 $out->addHTML( $this->
addHeader(
'', $oldHeader, $newHeader, $multi ) );
693 $msg = $suppressed ?
'rev-suppressed-no-diff' :
'rev-deleted-no-diff';
694 # Give explanation for why revision is not visible
695 $out->wrapWikiMsg(
"<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
698 # Give explanation and add a link to view the diff...
699 $query = $this->
getRequest()->appendQueryValue(
'unhide',
'1' );
700 $link = $this->
getTitle()->getFullURL( $query );
701 $msg = $suppressed ?
'rev-suppressed-unhide-diff' :
'rev-deleted-unhide-diff';
703 "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
707 # Otherwise, output a regular diff...
709 # Add deletion notice if the user is viewing deleted content
712 $msg = $suppressed ?
'rev-suppressed-diff-view' :
'rev-deleted-diff-view';
713 $notice =
"<div id='mw-$msg' class='mw-warning plainlinks'>\n" .
714 $this->
msg( $msg )->parse() .
717 $this->
showDiff( $oldHeader, $newHeader, $notice );
734 if ( $this->mMarkPatrolledLink ===
null ) {
737 if ( !$linkInfo || !$this->mNewPage ) {
738 $this->mMarkPatrolledLink =
'';
740 $this->mMarkPatrolledLink =
' <span class="patrollink" data-mw="interface">[' .
743 $this->
msg(
'markaspatrolleddiff' )->escaped(),
746 'action' =>
'markpatrolled',
747 'rcid' => $linkInfo[
'rcid'],
751 Hooks::run(
'DifferenceEngineMarkPatrolledLink', [ $this,
752 &$this->mMarkPatrolledLink, $linkInfo[
'rcid'] ] );
768 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
773 $config->get(
'UseRCPatrol' ) &&
775 $permissionManager->quickUserCan(
'patrol', $user, $this->mNewPage ) &&
778 RecentChange::isInRCLifespan( $this->mNewRev->getTimestamp(), 21600 )
782 $change = RecentChange::newFromConds(
784 'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ),
785 'rc_this_oldid' => $this->mNewid,
786 'rc_patrolled' => RecentChange::PRC_UNPATROLLED
791 if ( $change && !$change->getPerformer()->equals( $user ) ) {
792 $rcid = $change->getAttribute(
'rc_id' );
803 Hooks::run(
'DifferenceEngineMarkPatrolledRCID', [ &$rcid, $this, $change, $user ] );
807 $this->
getOutput()->preventClickjacking();
808 if ( $permissionManager->userHasRight( $user,
'writeapi' ) ) {
809 $this->
getOutput()->addModules(
'mediawiki.page.patrol.ajax' );
829 if ( $link !==
'' ) {
830 $link =
"\u{00A0}\u{00A0}\u{00A0}" . $link .
' ';
842 if ( $this->isContentOverridden ) {
846 throw new LogicException(
848 .
' is not supported after calling setContent(). Use setRevisions() instead.'
854 # Add "current version as of X" title
855 $out->addHTML(
"<hr class='diff-hr' id='mw-oldid' />
856 <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
857 # Page content may be handled by a hooked call instead...
858 if ( Hooks::run(
'ArticleContentOnDiff', [ $this, $out ] ) ) {
860 if ( !$this->mNewPage ) {
867 $out->setRevisionId( $this->mNewid );
868 $out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
869 $out->setArticleFlag(
true );
871 if ( !Hooks::run(
'ArticleRevisionViewCustom',
872 [ $this->mNewRev->getRevisionRecord(), $this->mNewPage, $this->mOldid, $out ] )
876 } elseif ( !Hooks::run(
'ArticleContentViewCustom',
877 [ $this->mNewContent, $this->mNewPage, $out ],
'1.32' )
883 if ( $this->
getTitle()->equals( $this->mNewPage ) ) {
890 $wikiPage = WikiPage::factory( $this->mNewPage );
895 # WikiPage::getParserOutput() should not return false, but just in case
896 if ( $parserOutput ) {
898 if ( Hooks::run(
'DifferenceEngineRenderRevisionAddParserOutput',
899 [ $this, $out, $parserOutput, $wikiPage ] )
901 $out->addParserOutput( $parserOutput, [
902 'enableSectionEditLinks' => $this->mNewRev->isCurrent()
903 && MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan(
906 $this->mNewRev->getTitle()
915 if ( Hooks::run(
'DifferenceEngineRenderRevisionShowFinalPatrolLink' ) ) {
916 # Add redundant patrol link on bottom...
928 if ( !$rev->
getId() ) {
938 return $parserOutput;
951 public function showDiff( $otitle, $ntitle, $notice =
'' ) {
953 Hooks::run(
'DifferenceEngineShowDiff', [ $this ] );
955 $diff = $this->
getDiff( $otitle, $ntitle, $notice );
956 if ( $diff ===
false ) {
972 if ( !$this->isSlotDiffRenderer ) {
974 'mediawiki.interface.helpers.styles',
975 'mediawiki.diff.styles'
978 $slotDiffRenderer->addModules( $this->
getOutput() );
992 public function getDiff( $otitle, $ntitle, $notice =
'' ) {
994 if ( $body ===
false ) {
1000 if ( $body ===
'' ) {
1001 $notice .=
'<div class="mw-diff-empty">' .
1002 $this->
msg(
'diff-empty' )->parse() .
1006 return $this->
addHeader( $body, $otitle, $ntitle, $multi, $notice );
1015 $this->mCacheHit =
true;
1017 if ( !$this->isContentOverridden ) {
1020 } elseif ( $this->mOldRev &&
1021 !$this->mOldRev->userCan( RevisionRecord::DELETED_TEXT, $this->getUser() )
1024 } elseif ( $this->mNewRev &&
1025 !$this->mNewRev->userCan( RevisionRecord::DELETED_TEXT, $this->getUser() )
1030 if ( $this->mOldRev ===
false || ( $this->mOldRev && $this->mNewRev &&
1031 $this->mOldRev->getId() && $this->mOldRev->getId() == $this->mNewRev->getId() )
1033 if ( Hooks::run(
'DifferenceEngineShowEmptyOldContent', [ $this ] ) ) {
1041 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1042 if ( $this->mOldid && $this->mNewid ) {
1046 if ( $key ===
null ) {
1051 if ( !$this->mRefreshCache ) {
1052 $difftext =
$cache->get( $key );
1053 if ( is_string( $difftext ) ) {
1056 $difftext .=
"\n<!-- diff cache key $key -->\n";
1062 $this->mCacheHit =
false;
1074 $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role][
'old'],
1075 $slotContents[$role][
'new'] );
1076 if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1081 $difftext .= $slotDiff;
1085 $diffEngine = $this;
1088 if ( !Hooks::run(
'AbortDiffCache', [ &$diffEngine ] ) ) {
1090 } elseif ( $key !==
false && $difftext !==
false ) {
1092 $cache->set( $key, $difftext, 7 * 86400 );
1097 if ( $difftext !==
false ) {
1112 if ( !isset( $diffRenderers[$role] ) ) {
1117 $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role][
'old'],
1118 $slotContents[$role][
'new'] );
1123 if ( $role !== SlotRecord::MAIN ) {
1141 $columnCount = $this->mOldRev ? 4 : 2;
1143 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-header',
'lang' => $userLang ],
1144 Html::element(
'th', [
'colspan' => $columnCount ], $headerText ) );
1174 if ( !$this->mOldid || !$this->mNewid ) {
1175 throw new MWException(
'mOldid and mNewid must be set to get diff cache key.' );
1181 $engine ===
'php' ? false : $engine,
1183 "old-{$this->mOldid}",
1184 "rev-{$this->mNewid}"
1187 if ( $engine ===
'wikidiff2' ) {
1188 $params[] = phpversion(
'wikidiff2' );
1191 if ( !$this->isSlotDiffRenderer ) {
1193 $params = array_merge( $params, $slotDiffRenderer->getExtraCacheKeys() );
1211 $this->mOldid = 123456789;
1212 $this->mNewid = 987654321;
1216 if ( $cacheString ) {
1217 return [ $cacheString ];
1230 if ( array_slice( $params, 0, count( $standardParams ) ) === $standardParams ) {
1231 $params = array_slice( $params, count( $standardParams ) );
1254 && $this->isSlotDiffRenderer
1260 throw new Exception( get_class( $this ) .
': could not maintain backwards compatibility. '
1261 .
'Please use a SlotDiffRenderer.' );
1263 return $slotDiffRenderer->getDiff( $old, $new ) . $this->
getDebugString();
1280 ->getSlotDiffRenderer( $this->
getContext() );
1284 throw new Exception(
'The slot diff renderer for text content should be a '
1285 .
'TextSlotDiffRenderer subclass' );
1287 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->
getDebugString();
1297 $diffEngine = MediaWikiServices::getInstance()->getMainConfig()
1298 ->get(
'DiffEngine' );
1299 $externalDiffEngine = MediaWikiServices::getInstance()->getMainConfig()
1300 ->get(
'ExternalDiffEngine' );
1302 if ( $diffEngine ===
null ) {
1303 $engines = [
'external',
'wikidiff2',
'php' ];
1305 $engines = [ $diffEngine ];
1308 $failureReason =
null;
1309 foreach ( $engines as $engine ) {
1310 switch ( $engine ) {
1312 if ( is_string( $externalDiffEngine ) ) {
1313 if ( is_executable( $externalDiffEngine ) ) {
1314 return $externalDiffEngine;
1316 $failureReason =
'ExternalDiffEngine config points to a non-executable';
1317 if ( $diffEngine ===
null ) {
1318 wfDebug(
"$failureReason, ignoring" );
1321 $failureReason =
'ExternalDiffEngine config is set to a non-string value';
1322 if ( $diffEngine ===
null && $externalDiffEngine ) {
1323 wfWarn(
"$failureReason, ignoring" );
1329 if ( function_exists(
'wikidiff2_do_diff' ) ) {
1332 $failureReason =
'wikidiff2 is not available';
1340 throw new DomainException(
'Invalid value for $wgDiffEngine: ' . $engine );
1343 throw new UnexpectedValueException(
"Cannot use diff engine '$engine': $failureReason" );
1360 ->getSlotDiffRenderer( $this->
getContext() );
1364 throw new Exception(
'The slot diff renderer for text content should be a '
1365 .
'TextSlotDiffRenderer subclass' );
1367 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->
getDebugString();
1379 if ( !$this->enableDebugComment ) {
1383 if ( $this->
getConfig()->
get(
'ShowHostnames' ) ) {
1388 return "<!-- diff generator: " .
1389 implode(
" ", array_map(
"htmlspecialchars", $data ) ) .
1395 if ( $engine ===
'wikidiff2' ) {
1396 return $this->
debug(
'wikidiff2' );
1397 } elseif ( $engine ===
'php' ) {
1398 return $this->
debug(
'native PHP' );
1400 return $this->
debug(
"external $engine" );
1412 if ( $this->
getEngine() ===
'wikidiff2' &&
1413 version_compare( phpversion(
'wikidiff2' ),
'1.5.1',
'>=' )
1428 return preg_replace_callback(
1429 '/<!--LINE (\d+)-->/',
1430 [ $this,
'localiseLineNumbersCb' ],
1436 if (
$matches[1] ===
'1' && $this->mReducedLineNumbers ) {
1440 return $this->
msg(
'lineno' )->numParams(
$matches[1] )->escaped();
1450 return preg_replace_callback(
1451 '/class="mw-diff-movedpara-(left|right)"/',
1452 [ $this,
'addLocalisedTitleTooltipsCb' ],
1463 'diff-paragraph-moved-toold' :
1464 'diff-paragraph-moved-tonew';
1465 return $matches[0] .
' title="' . $this->
msg( $key )->escaped() .
'"';
1476 !$this->mOldRev || !$this->mNewRev
1477 || !$this->mOldPage || !$this->mNewPage
1478 || !$this->mOldPage->equals( $this->mNewPage )
1483 if ( $this->mOldRev->getTimestamp() > $this->mNewRev->getTimestamp() ) {
1493 $nEdits = $this->mNewPage->countRevisionsBetween( $oldRev, $newRev, 1000 );
1494 if ( $nEdits > 0 && $nEdits <= 1000 ) {
1496 $users = $this->mNewPage->getAuthorsBetween( $oldRev, $newRev, $limit );
1497 $numUsers = count( $users );
1499 if ( $numUsers == 1 && $users[0] == $newRev->getUserText( RevisionRecord::RAW ) ) {
1519 if ( $numUsers === 0 ) {
1520 $msg =
'diff-multi-sameuser';
1521 } elseif ( $numUsers > $limit ) {
1522 $msg =
'diff-multi-manyusers';
1525 $msg =
'diff-multi-otherusers';
1528 return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1538 if ( !$rev->
userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
1558 $timestamp =
$lang->userTimeAndDate( $revtimestamp, $user );
1559 $dateofrev =
$lang->userDate( $revtimestamp, $user );
1560 $timeofrev =
$lang->userTime( $revtimestamp, $user );
1563 $rev->
isCurrent() ?
'currentrev-asof' :
'revisionasof',
1569 if ( $complete !==
'complete' ) {
1576 [
'oldid' => $rev->
getId() ] );
1579 $editQuery = [
'action' =>
'edit' ];
1581 $editQuery[
'oldid'] = $rev->
getId();
1584 $key = MediaWikiServices::getInstance()->getPermissionManager()
1585 ->quickUserCan(
'edit', $user,
$title ) ?
'editold' :
'viewsourceold';
1586 $msg = $this->
msg( $key )->escaped();
1587 $editLink = $this->
msg(
'parentheses' )->rawParams(
1589 $header .=
' ' . Html::rawElement(
1591 [
'class' =>
'mw-diff-edit' ],
1594 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1597 [
'class' =>
'history-deleted' ],
1602 $header = Html::rawElement(
'span', [
'class' =>
'history-deleted' ],
$header );
1620 public function addHeader( $diff, $otitle, $ntitle, $multi =
'', $notice =
'' ) {
1623 $header = Html::openElement(
'table', [
1624 'class' => [
'diff',
'diff-contentalign-' . $this->
getDiffLang()->alignStart() ],
1625 'data-mw' =>
'interface',
1627 $userLang = htmlspecialchars( $this->
getLanguage()->getHtmlCode() );
1629 if ( !$diff && !$otitle ) {
1631 <tr class=\"diff-title\" lang=\"{$userLang}\">
1632 <td class=\"diff-ntitle\">{$ntitle}</td>
1638 <col class=\"diff-marker\" />
1639 <col class=\"diff-content\" />
1640 <col class=\"diff-marker\" />
1641 <col class=\"diff-content\" />";
1648 if ( $otitle || $ntitle ) {
1650 <tr class=\"diff-title\" lang=\"{$userLang}\">
1651 <td colspan=\"$colspan\" class=\"diff-otitle\">{$otitle}</td>
1652 <td colspan=\"$colspan\" class=\"diff-ntitle\">{$ntitle}</td>
1657 if ( $multi !=
'' ) {
1658 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
1659 "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
1661 if ( $notice !=
'' ) {
1662 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
1663 "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
1666 return $header . $diff .
"</table>";
1677 $this->mOldContent = $oldContent;
1678 $this->mNewContent = $newContent;
1680 $this->mTextLoaded = 2;
1681 $this->mRevisionsLoaded =
true;
1682 $this->isContentOverridden =
true;
1683 $this->slotDiffRenderers =
null;
1694 if ( $oldRevision ) {
1695 $this->mOldRev =
new Revision( $oldRevision );
1696 $this->mOldid = $oldRevision->getId();
1697 $this->mOldPage = Title::newFromLinkTarget( $oldRevision->getPageAsLinkTarget() );
1700 $this->mOldContent = $oldRevision->getContent( SlotRecord::MAIN,
1701 RevisionRecord::FOR_THIS_USER, $this->
getUser() );
1703 $this->mOldPage =
null;
1704 $this->mOldRev = $this->mOldid =
false;
1706 $this->mNewRev =
new Revision( $newRevision );
1707 $this->mNewid = $newRevision->
getId();
1709 $this->mNewContent = $newRevision->
getContent( SlotRecord::MAIN,
1710 RevisionRecord::FOR_THIS_USER, $this->
getUser() );
1712 $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded =
true;
1713 $this->mTextLoaded = $oldRevision ? 2 : 1;
1714 $this->isContentOverridden =
false;
1715 $this->slotDiffRenderers =
null;
1725 $this->mDiffLang =
$lang;
1740 $rl = MediaWikiServices::getInstance()->getRevisionLookup();
1741 if ( $new ===
'prev' ) {
1743 $newid = intval( $old );
1745 $newRev = $rl->getRevisionById( $newid );
1747 $oldRev = $rl->getPreviousRevision( $newRev );
1749 $oldid = $oldRev->getId();
1752 } elseif ( $new ===
'next' ) {
1754 $oldid = intval( $old );
1756 $oldRev = $rl->getRevisionById( $oldid );
1758 $newRev = $rl->getNextRevision( $oldRev );
1760 $newid = $newRev->getId();
1764 $oldid = intval( $old );
1765 $newid = intval( $new );
1768 return [ $oldid, $newid ];
1775 if ( $this->mRevisionsIdsLoaded ) {
1779 $this->mRevisionsIdsLoaded =
true;
1785 if ( $new ===
'next' && $this->mNewid ===
false ) {
1786 # if no result, NewId points to the newest old revision. The only newer
1787 # revision is cur, which is "0".
1792 'NewDifferenceEngine',
1793 [ $this->
getTitle(), &$this->mOldid, &$this->mNewid, $old, $new ]
1811 if ( $this->mRevisionsLoaded ) {
1812 return $this->isContentOverridden || ( $this->mOldRev !==
null && $this->mNewRev !== null );
1816 $this->mRevisionsLoaded =
true;
1821 if ( $this->mNewid ) {
1822 $this->mNewRev = Revision::newFromId( $this->mNewid );
1824 $this->mNewRev = Revision::newFromTitle(
1827 Revision::READ_NORMAL
1831 if ( !$this->mNewRev instanceof
Revision ) {
1836 $this->mNewid = $this->mNewRev->getId();
1837 if ( $this->mNewid ) {
1838 $this->mNewPage = $this->mNewRev->getTitle();
1840 $this->mNewPage =
null;
1844 $this->mOldRev =
false;
1845 if ( $this->mOldid ) {
1846 $this->mOldRev = Revision::newFromId( $this->mOldid );
1847 } elseif ( $this->mOldid === 0 ) {
1848 $rev = $this->mNewRev->getPrevious();
1850 $this->mOldid = $rev->getId();
1851 $this->mOldRev = $rev;
1854 $this->mOldid =
false;
1855 $this->mOldRev =
false;
1859 if ( is_null( $this->mOldRev ) ) {
1863 if ( $this->mOldRev && $this->mOldRev->getId() ) {
1864 $this->mOldPage = $this->mOldRev->getTitle();
1866 $this->mOldPage =
null;
1871 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
1872 if ( $this->mOldid !==
false ) {
1873 $tagIds =
$dbr->selectFieldValues(
1876 [
'ct_rev_id' => $this->mOldid ],
1880 foreach ( $tagIds as $tagId ) {
1882 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
1887 $this->mOldTags = implode(
',', $tags );
1889 $this->mOldTags =
false;
1892 $tagIds =
$dbr->selectFieldValues(
1895 [
'ct_rev_id' => $this->mNewid ],
1899 foreach ( $tagIds as $tagId ) {
1901 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
1906 $this->mNewTags = implode(
',', $tags );
1920 if ( $this->mTextLoaded == 2 ) {
1922 && $this->mNewContent;
1926 $this->mTextLoaded = 2;
1932 if ( $this->mOldRev ) {
1933 $this->mOldContent = $this->mOldRev->getContent(
1934 RevisionRecord::FOR_THIS_USER, $this->
getUser()
1936 if ( $this->mOldContent ===
null ) {
1941 $this->mNewContent = $this->mNewRev->getContent(
1942 RevisionRecord::FOR_THIS_USER, $this->
getUser()
1944 Hooks::run(
'DifferenceEngineLoadTextAfterNewContentIsLoaded', [ $this ] );
1945 if ( $this->mNewContent ===
null ) {
1958 if ( $this->mTextLoaded >= 1 ) {
1962 $this->mTextLoaded = 1;
1968 $this->mNewContent = $this->mNewRev->getContent(
1969 RevisionRecord::FOR_THIS_USER, $this->
getUser()
1972 Hooks::run(
'DifferenceEngineAfterLoadNewText', [ $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.
wfIncrStats( $key, $count=1)
Increment a statistics counter.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfMergeErrorArrays(... $args)
Merge arrays in the style of getUserPermissionsErrors, with duplicate removal e.g.
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.
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
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.
getContext()
Get the base IContextSource 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....
Revision null $mNewRev
New revision (right pane).
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 $mOldRev or null if it does not exist / is not saved.
revisionDeleteLink( $rev)
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.
getOldid()
Get the ID of old revision (left pane) of the diff.
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
getParserOutput(WikiPage $page, Revision $rev)
getDiffBody()
Get the diff table body, without header.
string[] null $mNewTags
Change tags of $mNewRev or null if it does not exist / is not saved.
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)
setRevisions(RevisionRecord $oldRevision=null, RevisionRecord $newRevision)
Use specified text instead of loading from the database.
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 ...
getRevisionHeader(Revision $rev, $complete='')
Get a header for a specified revision.
__construct( $context=null, $old=0, $new=0, $rcid=0, $refreshCache=false, $unhide=false)
#-
Title null $mNewPage
Title of $mNewRev 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.
Revision null false $mOldRev
Old revision (left pane).
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.
getDiffBodyCacheKeyParams()
Get the cache key parameters.
getDiff( $otitle, $ntitle, $notice='')
Get complete diff table, including header.
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.
getMarkPatrolledLinkInfo()
Returns an array of meta data needed to build a "mark as patrolled" link and adds the mediawiki....
setReducedLineNumbers( $value=true)
Set reduced line numbers mode.
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 $mOldRev 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)
userCanEdit(Revision $rev)
markPatrolledLink()
Build a link to mark a change as patrolled.
$enableDebugComment
Set this to true to add debug info to the HTML output.
getOldRevision()
Get the left side of the diff.
Internationalisation code.
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 getRevDeleteLink(User $user, Revision $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
static revComment(Revision $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.
Show an error when a user tries to do something they do not have the necessary permissions for.
getTitle()
Returns the title of the page associated with this entry.
userCan( $field, User $user=null)
Determine if the current user is allowed to view a particular field of this revision,...
Renders a diff for a single slot (that is, a diff between two content objects).
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.
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