79 use DeprecationHelper;
87 private const DIFF_VERSION =
'1.41';
115 private $mOldRevisionRecord;
125 private $mNewRevisionRecord;
156 private $mOldContent;
163 private $mNewContent;
169 private $mRevisionsIdsLoaded =
false;
172 protected $mRevisionsLoaded =
false;
175 protected $mTextLoaded = 0;
185 protected $isContentOverridden =
false;
188 protected $mCacheHit =
false;
191 private $cacheHitKey =
null;
199 public $enableDebugComment =
false;
204 protected $mReducedLineNumbers =
false;
207 protected $mMarkPatrolledLink =
null;
210 protected $unhide =
false;
213 protected $mRefreshCache =
false;
224 protected $isSlotDiffRenderer =
false;
230 private $slotDiffOptions = [];
235 private $extraQueryParams = [];
254 private $revisionLoadErrors = [];
264 public function __construct( $context =
null, $old = 0, $new = 0, $rcid = 0,
265 $refreshCache =
false, $unhide =
false
271 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'" );
273 $this->mOldid = $old;
274 $this->mNewid = $new;
275 $this->mRefreshCache = $refreshCache;
276 $this->unhide = $unhide;
278 $services = MediaWikiServices::getInstance();
279 $this->linkRenderer = $services->getLinkRenderer();
280 $this->contentHandlerFactory = $services->getContentHandlerFactory();
281 $this->revisionStore = $services->getRevisionStore();
282 $this->archivedRevisionLookup = $services->getArchivedRevisionLookup();
283 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
284 $this->wikiPageFactory = $services->getWikiPageFactory();
285 $this->userOptionsLookup = $services->getUserOptionsLookup();
286 $this->commentFormatter = $services->getCommentFormatter();
287 $this->dbProvider = $services->getConnectionProvider();
288 $this->userGroupManager = $services->getUserGroupManager();
289 $this->userEditTracker = $services->getUserEditTracker();
290 $this->userIdentityUtils = $services->getUserIdentityUtils();
299 if ( $this->isSlotDiffRenderer ) {
300 throw new LogicException( __METHOD__ .
' called in slot diff renderer mode' );
303 if ( $this->slotDiffRenderers ===
null ) {
309 $this->slotDiffRenderers = [];
310 foreach ( $slotContents as $role => $contents ) {
311 if ( $contents[
'new'] && $contents[
'old']
312 && $contents[
'new']->equals( $contents[
'old'] )
317 $handler = ( $contents[
'new'] ?: $contents[
'old'] )->getContentHandler();
318 $this->slotDiffRenderers[$role] = $handler->getSlotDiffRenderer(
320 $this->slotDiffOptions + [
321 'contentLanguage' => $this->
getDiffLang()->getCode(),
328 return $this->slotDiffRenderers;
338 $this->isSlotDiffRenderer =
true;
347 if ( $this->isContentOverridden ) {
349 SlotRecord::MAIN => [
'old' => $this->mOldContent,
'new' => $this->mNewContent ]
355 $newSlots = $this->mNewRevisionRecord->getPrimarySlots()->getSlots();
356 $oldSlots = $this->mOldRevisionRecord ?
357 $this->mOldRevisionRecord->getPrimarySlots()->getSlots() :
363 $roles = array_keys( array_merge( $newSlots, $oldSlots ) );
366 foreach ( $roles as $role ) {
368 'old' => $this->loadSingleSlot(
369 $oldSlots[$role] ??
null,
372 'new' => $this->loadSingleSlot(
373 $newSlots[$role] ??
null,
379 if ( isset( $slots[SlotRecord::MAIN] ) ) {
380 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
392 private function loadSingleSlot( ?
SlotRecord $slot,
string $which ) {
399 $this->addRevisionLoadError( $which );
409 private function addRevisionLoadError( $which ) {
410 $this->revisionLoadErrors[] = $this->
msg( $which ===
'new'
411 ?
'difference-bad-new-revision' :
'difference-bad-old-revision'
422 return $this->revisionLoadErrors;
429 private function hasNewRevisionLoadError() {
430 foreach ( $this->revisionLoadErrors as $error ) {
431 if ( $error->getKey() ===
'difference-bad-new-revision' ) {
441 return parent::getTitle() ?: Title::makeTitle(
NS_SPECIAL,
'BadTitle/DifferenceEngine' );
451 $this->mReducedLineNumbers = $value;
460 # Default language in which the diff text is written.
462 return $this->mDiffLang;
472 return $this->
getTitle()->getPageLanguage();
479 return $this->mCacheHit;
490 $this->loadRevisionIds();
492 return $this->mOldid;
502 $this->loadRevisionIds();
504 return $this->mNewid;
514 return $this->mOldRevisionRecord ?:
null;
523 return $this->mNewRevisionRecord;
535 if ( $this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
536 $revRecord = $this->archivedRevisionLookup->getArchivedRevisionRecord(
null, $id );
538 $title = Title::newFromPageIdentity( $revRecord->
getPage() );
540 return SpecialPage::getTitleFor(
'Undelete' )->getFullURL( [
541 'target' => $title->getPrefixedText(),
560 return "[$link $id]";
566 private function showMissingRevision() {
567 $out = $this->getOutput();
570 if ( $this->mOldid && ( !$this->mOldRevisionRecord || !$this->mOldContent ) ) {
571 $missing[] = $this->deletedIdMarker( $this->mOldid );
573 if ( !$this->mNewRevisionRecord || !$this->mNewContent ) {
577 $out->setPageTitleMsg( $this->
msg(
'errorpagetitle' ) );
578 $msg = $this->
msg(
'difference-missing-revision' )
579 ->params( $this->
getLanguage()->listToText( $missing ) )
580 ->numParams( count( $missing ) )
582 $out->addHTML( $msg );
593 $this->mNewRevisionRecord &&
594 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
597 $this->mOldRevisionRecord &&
598 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
610 $permStatus = PermissionStatus::newEmpty();
611 if ( $this->mNewPage ) {
612 $performer->
authorizeRead(
'read', $this->mNewPage, $permStatus );
614 if ( $this->mOldPage ) {
615 $performer->
authorizeRead(
'read', $this->mOldPage, $permStatus );
617 return $permStatus->toLegacyErrorArray();
627 ( $this->mOldRevisionRecord &&
628 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) ||
629 ( $this->mNewRevisionRecord &&
630 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) )
640 private function getUserEditCount( $user ): string {
641 $editCount = $this->userEditTracker->getUserEditCount( $user );
642 if ( $editCount ===
null ) {
646 return Html::rawElement(
'div', [
647 'class' =>
'mw-diff-usereditcount',
663 if ( !$this->userIdentityUtils->isNamed( $user ) ) {
666 $userGroups = $this->userGroupManager->getUserGroups( $user );
667 $userGroupLinks = [];
668 foreach ( $userGroups as $group ) {
669 $userGroupLinks[] = UserGroupMembership::getLinkHTML( $group, $this->
getContext() );
671 return Html::rawElement(
'div', [
672 'class' =>
'mw-diff-userroles',
673 ], $this->getLanguage()->commaList( $userGroupLinks ) );
682 private function getUserMetaData( ?
UserIdentity $user ) {
686 return Html::rawElement(
'div', [
687 'class' =>
'mw-diff-usermetadata',
688 ], $this->getUserRoles( $user ) . $this->getUserEditCount( $user ) );
703 $this->loadRevisionData();
705 if ( $this->mOldRevisionRecord && !$this->mOldRevisionRecord->userCan(
706 RevisionRecord::DELETED_TEXT,
714 return !$this->mNewRevisionRecord || $this->mNewRevisionRecord->userCan(
715 RevisionRecord::DELETED_TEXT,
728 return $this->hasDeletedRevision() && ( !$this->unhide ||
729 !$this->isUserAllowedToSeeRevisions( $performer ) );
736 # Allow frames except in certain special cases
737 $out = $this->getOutput();
738 $out->setPreventClickjacking(
false );
739 $out->setRobotPolicy(
'noindex,nofollow' );
742 $this->hookRunner->onDifferenceEngineShowDiffPage( $out );
744 if ( !$this->loadRevisionData() ) {
745 if ( $this->hookRunner->onDifferenceEngineShowDiffPageMaybeShowMissingRevision( $this ) ) {
746 $this->showMissingRevision();
752 $permErrors = $this->getPermissionErrors( $this->
getAuthority() );
759 $query = $this->extraQueryParams;
760 # Carry over 'diffonly' param via navigation links
761 if ( $diffOnly != MediaWikiServices::getInstance()
762 ->getUserOptionsLookup()->getBoolOption( $user,
'diffonly' )
764 $query[
'diffonly'] = $diffOnly;
766 # Cascade unhide param in links for easy deletion browsing
767 if ( $this->unhide ) {
768 $query[
'unhide'] = 1;
771 # Check if one of the revisions is deleted/suppressed
772 $deleted = $this->hasDeletedRevision();
773 $suppressed = $this->hasSuppressedRevision();
774 $allowed = $this->isUserAllowedToSeeRevisions( $this->
getAuthority() );
779 # mOldRevisionRecord is false if the difference engine is called with a "vague" query for
780 # a diff between a version V and its previous version V' AND the version V
781 # is the first version of that article. In that case, V' does not exist.
782 if ( $this->mOldRevisionRecord ===
false ) {
783 if ( $this->mNewPage ) {
784 $out->setPageTitleMsg(
785 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
791 $this->hookRunner->onDifferenceEngineOldHeaderNoOldRev( $oldHeader );
793 $this->hookRunner->onDifferenceEngineViewHeader( $this );
795 if ( !$this->mOldPage || !$this->mNewPage ) {
798 } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
799 $out->setPageTitleMsg(
800 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
804 $out->setPageTitleMsg( $this->msg(
'difference-title-multipage' )->plaintextParams(
805 $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
806 $out->addSubtitle( $this->msg(
'difference-multipage' ) );
810 if ( $samePage && $this->mNewPage &&
811 $this->
getAuthority()->probablyCan(
'edit', $this->mNewPage )
813 if ( $this->mNewRevisionRecord->isCurrent() &&
814 $this->getAuthority()->probablyCan(
'rollback', $this->mNewPage )
816 $rollbackLink = Linker::generateRollback(
817 $this->mNewRevisionRecord,
821 if ( $rollbackLink ) {
822 $out->setPreventClickjacking(
true );
823 $rollback =
"\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
827 if ( $this->userCanEdit( $this->mOldRevisionRecord ) &&
828 $this->userCanEdit( $this->mNewRevisionRecord )
830 $undoLink = $this->linkRenderer->makeKnownLink(
832 $this->msg(
'editundo' )->text(),
833 [
'title' => Linker::titleAttrib(
'undo' ) ],
836 'undoafter' => $this->mOldid,
837 'undo' => $this->mNewid
840 $revisionTools[
'mw-diff-undo'] = $undoLink;
843 # Make "previous revision link"
844 $hasPrevious = $samePage && $this->mOldPage &&
845 $this->revisionStore->getPreviousRevision( $this->mOldRevisionRecord );
846 if ( $hasPrevious ) {
847 $prevlinkQuery = [
'diff' =>
'prev',
'oldid' => $this->mOldid ] + $query;
848 $prevlink = $this->linkRenderer->makeKnownLink(
850 $this->msg(
'previousdiff' )->text(),
851 [
'id' =>
'differences-prevlink' ],
854 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
856 $this->msg(
'previousdiff' )->text(),
858 'class' =>
'mw-diff-revision-history-link-previous'
863 $prevlink =
"\u{00A0}";
866 if ( $this->mOldRevisionRecord->isMinor() ) {
867 $oldminor = ChangesList::flag(
'minor' );
872 $oldRevRecord = $this->mOldRevisionRecord;
874 $ldel = $this->revisionDeleteLink( $oldRevRecord );
875 $oldRevisionHeader = $this->getRevisionHeader( $oldRevRecord,
'complete' );
877 $oldRevComment = $this->commentFormatter
879 $oldRevRecord, $user, !$diffOnly, !$this->unhide,
false
882 if ( $oldRevComment ===
'' ) {
883 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
884 $oldRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
887 $oldHeader =
'<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader .
'</strong></div>' .
888 '<div id="mw-diff-otitle2">' .
889 Linker::revUserTools( $oldRevRecord, !$this->unhide ) .
890 $this->getUserMetaData( $oldRevRecord->getUser() ) .
892 '<div id="mw-diff-otitle3">' . $oldminor . $oldRevComment . $ldel .
'</div>' .
893 '<div id="mw-diff-otitle5">' . $oldChangeTags[0] .
'</div>' .
894 '<div id="mw-diff-otitle4">' . $prevlink .
'</div>';
897 $this->hookRunner->onDifferenceEngineOldHeader(
898 $this, $oldHeader, $prevlink, $oldminor, $diffOnly, $ldel, $this->unhide );
901 $out->addJsConfigVars( [
902 'wgDiffOldId' => $this->mOldid,
903 'wgDiffNewId' => $this->mNewid,
906 # Make "next revision link"
907 # Skip next link on the top revision
908 if ( $samePage && $this->mNewPage && !$this->mNewRevisionRecord->isCurrent() ) {
909 $nextlinkQuery = [
'diff' =>
'next',
'oldid' => $this->mNewid ] + $query;
910 $nextlink = $this->linkRenderer->makeKnownLink(
912 $this->msg(
'nextdiff' )->text(),
913 [
'id' =>
'differences-nextlink' ],
916 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
918 $this->msg(
'nextdiff' )->text(),
920 'class' =>
'mw-diff-revision-history-link-next'
925 $nextlink =
"\u{00A0}";
928 if ( $this->mNewRevisionRecord->isMinor() ) {
929 $newminor = ChangesList::flag(
'minor' );
934 # Handle RevisionDelete links...
935 $rdel = $this->revisionDeleteLink( $this->mNewRevisionRecord );
937 # Allow extensions to define their own revision tools
938 $this->hookRunner->onDiffTools(
939 $this->mNewRevisionRecord,
941 $this->mOldRevisionRecord ?:
null,
945 $formattedRevisionTools = [];
947 foreach ( $revisionTools as $key => $tool ) {
948 $toolClass = is_string( $key ) ? $key :
'mw-diff-tool';
949 $element = Html::rawElement(
951 [
'class' => $toolClass ],
954 $formattedRevisionTools[] = $element;
957 $newRevRecord = $this->mNewRevisionRecord;
959 $newRevisionHeader = $this->getRevisionHeader( $newRevRecord,
'complete' ) .
960 ' ' . implode(
' ', $formattedRevisionTools );
962 $newRevComment = $this->commentFormatter->formatRevision(
963 $newRevRecord, $user, !$diffOnly, !$this->unhide,
false
966 if ( $newRevComment ===
'' ) {
967 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
968 $newRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
971 $newHeader =
'<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader .
'</strong></div>' .
972 '<div id="mw-diff-ntitle2">' . Linker::revUserTools( $newRevRecord, !$this->unhide ) .
974 $this->getUserMetaData( $newRevRecord->getUser() ) .
976 '<div id="mw-diff-ntitle3">' . $newminor . $newRevComment . $rdel .
'</div>' .
977 '<div id="mw-diff-ntitle5">' . $newChangeTags[0] .
'</div>' .
978 '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() .
'</div>';
981 $this->hookRunner->onDifferenceEngineNewHeader( $this, $newHeader,
982 $formattedRevisionTools, $nextlink, $rollback, $newminor, $diffOnly,
983 $rdel, $this->unhide );
986 Html::rawElement(
'div', [
987 'class' =>
'mw-diff-revision-history-links'
990 # If the diff cannot be shown due to a deleted revision, then output
991 # the diff header and links to unhide (if available)...
992 if ( $this->shouldBeHiddenFromUser( $this->
getAuthority() ) ) {
993 $this->showDiffStyle();
994 $multi = $this->getMultiNotice();
995 $out->addHTML( $this->addHeader(
'', $oldHeader, $newHeader, $multi ) );
997 # Give explanation for why revision is not visible
998 $msg = [ $suppressed ?
'rev-suppressed-no-diff' :
'rev-deleted-no-diff' ];
1000 # Give explanation and add a link to view the diff...
1001 $query = $this->
getRequest()->appendQueryValue(
'unhide',
'1' );
1003 $suppressed ?
'rev-suppressed-unhide-diff' :
'rev-deleted-unhide-diff',
1004 $this->
getTitle()->getFullURL( $query )
1007 $out->addHTML( Html::warningBox( $this->msg( ...$msg )->parse(),
'plainlinks' ) );
1008 # Otherwise, output a regular diff...
1010 # Add deletion notice if the user is viewing deleted content
1013 $msg = $suppressed ?
'rev-suppressed-diff-view' :
'rev-deleted-diff-view';
1014 $notice = Html::warningBox( $this->msg( $msg )->parse(),
'plainlinks' );
1017 # Add an error if the content can't be loaded
1018 $this->getSlotContents();
1019 foreach ( $this->getRevisionLoadErrors() as $msg ) {
1020 $notice .= Html::warningBox( $msg->parse() );
1024 if ( $this->getTextDiffer()->hasFormat(
'inline' ) ) {
1028 $this->showTablePrefixes();
1029 $this->showDiff( $oldHeader, $newHeader, $notice );
1031 $this->renderNewRevision();
1035 if ( $this->hookRunner->onDifferenceEngineRenderRevisionShowFinalPatrolLink() ) {
1036 # Add redundant patrol link on bottom...
1037 $out->addHTML( $this->markPatrolledLink() );
1045 private function showTablePrefixes() {
1047 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1048 $parts += $slotDiffRenderer->getTablePrefix( $this->
getContext(), $this->mNewPage );
1051 if ( count( array_filter( $parts ) ) > 0 ) {
1052 $language = $this->getLanguage();
1054 'class' =>
'mw-diff-table-prefix',
1055 'dir' => $language->getDir(),
1056 'lang' => $language->getCode(),
1058 $this->getOutput()->addHTML(
1059 Html::rawElement(
'div', $attrs, implode(
'', $parts ) ) );
1074 if ( $this->mMarkPatrolledLink ===
null ) {
1075 $linkInfo = $this->getMarkPatrolledLinkInfo();
1077 if ( !$linkInfo || !$this->mNewPage ) {
1078 $this->mMarkPatrolledLink =
'';
1080 $patrolLinkClass =
'patrollink';
1081 $this->mMarkPatrolledLink =
' <span class="' . $patrolLinkClass .
'" data-mw="interface">[' .
1082 $this->linkRenderer->makeKnownLink(
1084 $this->msg(
'markaspatrolleddiff' )->text(),
1087 'action' =>
'markpatrolled',
1088 'rcid' => $linkInfo[
'rcid'],
1092 $this->hookRunner->onDifferenceEngineMarkPatrolledLink( $this,
1093 $this->mMarkPatrolledLink, $linkInfo[
'rcid'] );
1096 return $this->mMarkPatrolledLink;
1108 $config = $this->getConfig();
1113 $config->get( MainConfigNames::UseRCPatrol ) &&
1115 $this->getAuthority()->probablyCan(
'patrol', $this->mNewPage ) &&
1118 RecentChange::isInRCLifespan( $this->mNewRevisionRecord->getTimestamp(), 21600 )
1121 $change = RecentChange::newFromConds(
1123 'rc_this_oldid' => $this->mNewid,
1124 'rc_patrolled' => RecentChange::PRC_UNPATROLLED
1129 if ( $change && !$change->getPerformerIdentity()->equals( $user ) ) {
1130 $rcid = $change->getAttribute(
'rc_id' );
1141 $this->hookRunner->onDifferenceEngineMarkPatrolledRCID( $rcid, $this, $change, $user );
1145 $this->getOutput()->setPreventClickjacking(
true );
1146 if ( $this->
getAuthority()->isAllowed(
'writeapi' ) ) {
1147 $this->getOutput()->addModules(
'mediawiki.misc-authed-curate' );
1150 return [
'rcid' => $rcid ];
1163 private function revisionDeleteLink(
RevisionRecord $revRecord ) {
1164 $link = Linker::getRevDeleteLink(
1169 if ( $link !==
'' ) {
1170 $link =
"\u{00A0}\u{00A0}\u{00A0}" . $link .
' ';
1182 if ( $this->isContentOverridden ) {
1186 throw new LogicException(
1188 .
' is not supported after calling setContent(). Use setRevisions() instead.'
1192 $out = $this->getOutput();
1193 $revHeader = $this->getRevisionHeader( $this->mNewRevisionRecord );
1194 # Add "current version as of X" title
1195 $out->addHTML(
"<hr class='diff-hr' id='mw-oldid' />
1196 <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
1197 # Page content may be handled by a hooked call instead...
1198 if ( $this->hookRunner->onArticleContentOnDiff( $this, $out ) ) {
1199 $this->loadNewText();
1200 if ( !$this->mNewPage ) {
1206 if ( $this->hasNewRevisionLoadError() ) {
1211 $out->setRevisionId( $this->mNewid );
1212 $out->setRevisionIsCurrent( $this->mNewRevisionRecord->isCurrent() );
1213 $out->setRevisionTimestamp( $this->mNewRevisionRecord->getTimestamp() );
1214 $out->setArticleFlag(
true );
1216 if ( !$this->hookRunner->onArticleRevisionViewCustom(
1217 $this->mNewRevisionRecord, $this->mNewPage, $this->mOldid, $out )
1223 if ( $this->
getTitle()->equals( $this->mNewPage ) ) {
1227 $wikiPage = $this->getWikiPage();
1230 $wikiPage = $this->wikiPageFactory->newFromTitle( $this->mNewPage );
1233 $parserOptions = $wikiPage->makeParserOptions( $this->
getContext() );
1234 $parserOptions->setRenderReason(
'diff-page' );
1236 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
1237 $status = $parserOutputAccess->getParserOutput(
1240 $this->mNewRevisionRecord,
1242 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK |
1244 ParserOutputAccess::OPT_LINKS_UPDATE
1246 if ( $status->isOK() ) {
1247 $parserOutput = $status->getValue();
1249 if ( $this->hookRunner->onDifferenceEngineRenderRevisionAddParserOutput(
1250 $this, $out, $parserOutput, $wikiPage )
1252 $out->addParserOutput( $parserOutput, [
1253 'enableSectionEditLinks' => $this->mNewRevisionRecord->isCurrent()
1254 && $this->getAuthority()->probablyCan(
1256 $this->mNewRevisionRecord->getPage()
1258 'absoluteURLs' => $this->slotDiffOptions[
'expand-url'] ??
false
1264 $out->parseAsInterface(
1265 $status->getWikiText(
false,
false, $this->getLanguage() )
1284 public function showDiff( $otitle, $ntitle, $notice =
'' ) {
1286 $this->hookRunner->onDifferenceEngineShowDiff( $this );
1288 $diff = $this->getDiff( $otitle, $ntitle, $notice );
1289 if ( $diff ===
false ) {
1290 $this->showMissingRevision();
1294 $this->showDiffStyle();
1295 if ( $this->slotDiffOptions[
'expand-url'] ??
false ) {
1296 $diff = Linker::expandLocalLinks( $diff );
1298 $this->getOutput()->addHTML( $diff );
1306 if ( !$this->isSlotDiffRenderer ) {
1307 $this->getOutput()->addModules(
'mediawiki.diff' );
1308 $this->getOutput()->addModuleStyles( [
1309 'mediawiki.interface.helpers.styles',
1310 'mediawiki.diff.styles'
1312 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1313 $slotDiffRenderer->addModules( $this->getOutput() );
1327 public function getDiff( $otitle, $ntitle, $notice =
'' ) {
1328 $body = $this->getDiffBody();
1329 if ( $body ===
false ) {
1333 $multi = $this->getMultiNotice();
1335 if ( $body ===
'' ) {
1336 $notice .=
'<div class="mw-diff-empty">' .
1337 $this->msg(
'diff-empty' )->parse() .
1341 if ( $this->cacheHitKey !==
null ) {
1342 $body .=
"\n<!-- diff cache key " . htmlspecialchars( $this->cacheHitKey ) .
" -->\n";
1345 return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
1354 $this->mCacheHit =
true;
1356 if ( !$this->isContentOverridden ) {
1357 if ( !$this->loadRevisionData() ) {
1359 } elseif ( $this->mOldRevisionRecord &&
1360 !$this->mOldRevisionRecord->userCan(
1361 RevisionRecord::DELETED_TEXT,
1362 $this->getAuthority()
1366 } elseif ( $this->mNewRevisionRecord &&
1367 !$this->mNewRevisionRecord->userCan(
1368 RevisionRecord::DELETED_TEXT,
1369 $this->getAuthority()
1374 if ( $this->mOldRevisionRecord ===
false || (
1375 $this->mOldRevisionRecord &&
1376 $this->mNewRevisionRecord &&
1377 $this->mOldRevisionRecord->getId() &&
1378 $this->mOldRevisionRecord->getId() == $this->mNewRevisionRecord->getId()
1380 if ( $this->hookRunner->onDifferenceEngineShowEmptyOldContent( $this ) ) {
1388 $services = MediaWikiServices::getInstance();
1389 $cache = $services->getMainWANObjectCache();
1390 $stats = $services->getStatsdDataFactory();
1391 if ( $this->mOldid && $this->mNewid ) {
1392 $key = $cache->makeKey( ...$this->getDiffBodyCacheKeyParams() );
1395 if ( !$this->mRefreshCache ) {
1396 $difftext = $cache->get( $key );
1397 if ( is_string( $difftext ) ) {
1398 $stats->updateCount(
'diff_cache.hit', 1 );
1399 $difftext = $this->localiseDiff( $difftext );
1400 $this->cacheHitKey = $key;
1405 $this->mCacheHit =
false;
1406 $this->cacheHitKey =
null;
1409 if ( !$this->loadText() ) {
1416 $slotContents = $this->getSlotContents();
1417 foreach ( $this->getSlotDiffRenderers() as $role => $slotDiffRenderer ) {
1419 $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role][
'old'],
1420 $slotContents[$role][
'new'] );
1424 if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1427 $difftext .= $this->getSlotHeader( $slotTitle );
1429 $difftext .= $slotDiff;
1433 if ( !$this->hookRunner->onAbortDiffCache( $this ) ) {
1434 $stats->updateCount(
'diff_cache.uncacheable', 1 );
1435 } elseif ( $key !==
false ) {
1436 $stats->updateCount(
'diff_cache.miss', 1 );
1437 $cache->set( $key, $difftext, 7 * 86400 );
1439 $stats->updateCount(
'diff_cache.uncacheable', 1 );
1442 $difftext = $this->localiseDiff( $difftext );
1454 $diffRenderers = $this->getSlotDiffRenderers();
1455 if ( !isset( $diffRenderers[$role] ) ) {
1459 $slotContents = $this->getSlotContents();
1461 $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role][
'old'],
1462 $slotContents[$role][
'new'] );
1466 if ( $slotDiff ===
'' ) {
1470 if ( $role !== SlotRecord::MAIN ) {
1473 $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff;
1476 return $this->localiseDiff( $slotDiff );
1488 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1489 $userLang = $this->getLanguage()->getHtmlCode();
1490 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-header',
'lang' => $userLang ],
1491 Html::element(
'th', [
'colspan' => $columnCount ], $headerText ) );
1502 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1503 $userLang = $this->getLanguage()->getHtmlCode();
1504 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-error',
'lang' => $userLang ],
1505 Html::rawElement(
'td', [
'colspan' => $columnCount ], $errorText ) );
1522 if ( !$this->mOldid || !$this->mNewid ) {
1523 throw new BadMethodCallException(
'mOldid and mNewid must be set to get diff cache key.' );
1529 "old-{$this->mOldid}",
1530 "rev-{$this->mNewid}"
1534 if ( !$this->isSlotDiffRenderer ) {
1535 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1536 $extraKeys = array_merge( $extraKeys, $slotDiffRenderer->getExtraCacheKeys() );
1539 ksort( $extraKeys );
1540 return array_merge(
$params, array_values( $extraKeys ) );
1554 $this->mOldid = 123456789;
1555 $this->mNewid = 987654321;
1558 $params = $this->getDiffBodyCacheKeyParams();
1568 if ( array_slice(
$params, 0, count( $standardParams ) ) === $standardParams ) {
1586 $validatedOptions = [];
1587 if ( isset( $options[
'diff-type'] )
1588 && $this->getTextDiffer()->hasFormat( $options[
'diff-type'] )
1590 $validatedOptions[
'diff-type'] = $options[
'diff-type'];
1592 if ( !empty( $options[
'expand-url'] ) ) {
1593 $validatedOptions[
'expand-url'] =
true;
1595 if ( !empty( $options[
'inline-toggle'] ) ) {
1596 $validatedOptions[
'inline-toggle'] =
true;
1598 $this->slotDiffOptions = $validatedOptions;
1609 $this->extraQueryParams =
$params;
1629 && $this->isSlotDiffRenderer
1635 throw new LogicException( get_class( $this ) .
': could not maintain backwards compatibility. '
1636 .
'Please use a SlotDiffRenderer.' );
1638 return $slotDiffRenderer->getDiff( $old, $new ) . $this->getDebugString();
1654 $slotDiffRenderer = $this->contentHandlerFactory
1656 ->getSlotDiffRenderer( $this->
getContext() );
1660 throw new LogicException(
'The slot diff renderer for text content should be a '
1661 .
'TextSlotDiffRenderer subclass' );
1663 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
1673 $differenceEngine =
new self;
1674 $engine = $differenceEngine->getTextDiffer()->getEngineForFormat(
'table' );
1675 if ( $engine ===
'external' ) {
1676 return MediaWikiServices::getInstance()->getMainConfig()
1677 ->get( MainConfigNames::ExternalDiffEngine );
1691 protected function debug( $generator =
"internal" ) {
1692 if ( !$this->enableDebugComment ) {
1695 $data = [ $generator ];
1696 if ( $this->getConfig()->
get( MainConfigNames::ShowHostnames ) ) {
1701 return "<!-- diff generator: " .
1702 implode(
" ", array_map(
"htmlspecialchars", $data ) ) .
1709 private function getDebugString() {
1710 $engine = self::getEngine();
1711 if ( $engine ===
'wikidiff2' ) {
1712 return $this->debug(
'wikidiff2' );
1713 } elseif ( $engine ===
'php' ) {
1714 return $this->debug(
'native PHP' );
1716 return $this->debug(
"external $engine" );
1726 private function localiseDiff( $text ) {
1727 return $this->getTextDiffer()->localize( $this->getTextDiffFormat(), $text );
1739 return preg_replace_callback(
'/<!--LINE (\d+)-->/',
1741 if (
$matches[1] ===
'1' && $this->mReducedLineNumbers ) {
1744 return $this->msg(
'lineno' )->numParams(
$matches[1] )->escaped();
1756 !$this->mOldRevisionRecord || !$this->mNewRevisionRecord
1757 || !$this->mOldPage || !$this->mNewPage
1758 || !$this->mOldPage->equals( $this->mNewPage )
1759 || $this->mOldRevisionRecord->getId() ===
null
1760 || $this->mNewRevisionRecord->getId() ===
null
1762 || $this->mNewPage->getArticleID() !== $this->mOldRevisionRecord->getPageId()
1763 || $this->mNewPage->getArticleID() !== $this->mNewRevisionRecord->getPageId()
1768 if ( $this->mOldRevisionRecord->getTimestamp() > $this->mNewRevisionRecord->getTimestamp() ) {
1769 $oldRevRecord = $this->mNewRevisionRecord;
1770 $newRevRecord = $this->mOldRevisionRecord;
1772 $oldRevRecord = $this->mOldRevisionRecord;
1773 $newRevRecord = $this->mNewRevisionRecord;
1779 $revisionIdList = $this->revisionStore->getRevisionIdsBetween(
1780 $this->mNewPage->getArticleID(),
1786 if ( count( $revisionIdList ) > 0 ) {
1787 foreach ( $revisionIdList as $revisionId ) {
1788 $revision = $this->revisionStore->getRevisionById( $revisionId );
1789 if ( $revision->getUser( RevisionRecord::FOR_THIS_USER, $this->getAuthority() ) ) {
1794 if ( $nEdits > 0 && $nEdits <= 1000 ) {
1796 $newRevUserForGender =
'[HIDDEN]';
1799 $users = $this->revisionStore->getAuthorsBetween(
1800 $this->mNewPage->getArticleID(),
1806 $numUsers = count( $users );
1808 $newRevUser = $newRevRecord->getUser( RevisionRecord::RAW );
1809 $newRevUserText = $newRevUser ? $newRevUser->getName() :
'';
1810 $newRevUserSafe = $newRevRecord->getUser(
1811 RevisionRecord::FOR_THIS_USER,
1814 $newRevUserForGender = $newRevUserSafe ? $newRevUserSafe->getName() :
'[HIDDEN]';
1815 if ( $numUsers == 1 && $users[0]->getName() == $newRevUserText ) {
1818 }
catch ( InvalidArgumentException $e ) {
1822 return self::intermediateEditsMsg( $nEdits, $numUsers, $limit, $newRevUserForGender );
1839 if ( $numUsers === 0 ) {
1840 $msg =
'diff-multi-sameuser';
1842 ->numParams( $numEdits, $numUsers )
1843 ->params( $lastUser )
1845 } elseif ( $numUsers > $limit ) {
1846 $msg =
'diff-multi-manyusers';
1849 $msg =
'diff-multi-otherusers';
1852 return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1860 if ( !$revRecord->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1877 $lang = $this->getLanguage();
1880 $timestamp = $lang->userTimeAndDate( $revtimestamp, $user );
1881 $dateofrev = $lang->userDate( $revtimestamp, $user );
1882 $timeofrev = $lang->userTime( $revtimestamp, $user );
1885 $rev->
isCurrent() ?
'currentrev-asof' :
'revisionasof',
1891 if ( $complete !==
'complete' ) {
1897 if ( $this->userCanEdit( $rev ) ) {
1898 $header = $this->linkRenderer->makeKnownLink(
1902 [
'oldid' => $rev->
getId() ]
1904 $editQuery = [
'action' =>
'edit' ];
1906 $editQuery[
'oldid'] = $rev->
getId();
1909 $key = $this->
getAuthority()->probablyCan(
'edit', $rev->
getPage() ) ?
'editold' :
'viewsourceold';
1910 $msg = $this->msg( $key )->text();
1911 $editLink = $this->linkRenderer->makeKnownLink( $title, $msg, [], $editQuery );
1912 $header .=
' ' . Html::rawElement(
1914 [
'class' =>
'mw-diff-edit' ],
1922 $header .= Html::element(
'span',
1924 'class' =>
'mw-diff-timestamp',
1925 'data-timestamp' =>
wfTimestamp( TS_ISO_8601, $revtimestamp ),
1929 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1930 return Html::rawElement(
1932 [
'class' => Linker::getRevisionDeletedClass( $rev ) ],
1952 public function addHeader( $diff, $otitle, $ntitle, $multi =
'', $notice =
'' ) {
1955 $header = Html::openElement(
'table', [
1961 'diff-type-' . $this->getTextDiffFormat(),
1965 'diff-contentalign-' . $this->getDiffLang()->alignStart(),
1970 'diff-editfont-' . $this->userOptionsLookup->getOption(
1975 'data-mw' =>
'interface',
1977 $userLang = htmlspecialchars( $this->getLanguage()->getHtmlCode() );
1979 if ( !$diff && !$otitle ) {
1981 <tr class=\"diff-title\" lang=\"{$userLang}\">
1982 <td class=\"diff-ntitle\">{$ntitle}</td>
1988 <col class=\"diff-marker\" />
1989 <col class=\"diff-content\" />
1990 <col class=\"diff-marker\" />
1991 <col class=\"diff-content\" />";
1998 if ( $otitle || $ntitle ) {
2000 $deletedClass =
'diff-side-deleted';
2001 $addedClass =
'diff-side-added';
2003 <tr class=\"diff-title\" lang=\"{$userLang}\">
2004 <td colspan=\"$colspan\" class=\"diff-otitle {$deletedClass}\">{$otitle}</td>
2005 <td colspan=\"$colspan\" class=\"diff-ntitle {$addedClass}\">{$ntitle}</td>
2010 if ( $multi !=
'' ) {
2011 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2012 "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
2014 if ( $notice !=
'' ) {
2015 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2016 "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
2019 return $header . $diff .
"</table>";
2030 $this->mOldContent = $oldContent;
2031 $this->mNewContent = $newContent;
2033 $this->mTextLoaded = 2;
2034 $this->mRevisionsLoaded =
true;
2035 $this->isContentOverridden =
true;
2036 $this->slotDiffRenderers =
null;
2047 if ( $oldRevision ) {
2048 $this->mOldRevisionRecord = $oldRevision;
2049 $this->mOldid = $oldRevision->
getId();
2053 $this->mOldContent = $oldRevision->
getContent( SlotRecord::MAIN,
2054 RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
2055 if ( !$this->mOldContent ) {
2056 $this->addRevisionLoadError(
'old' );
2059 $this->mOldPage =
null;
2060 $this->mOldRevisionRecord = $this->mOldid =
false;
2062 $this->mNewRevisionRecord = $newRevision;
2063 $this->mNewid = $newRevision->
getId();
2065 $this->mNewContent = $newRevision->
getContent( SlotRecord::MAIN,
2066 RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
2067 if ( !$this->mNewContent ) {
2068 $this->addRevisionLoadError(
'new' );
2071 $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded =
true;
2072 $this->mTextLoaded = $oldRevision ? 2 : 1;
2073 $this->isContentOverridden =
false;
2074 $this->slotDiffRenderers =
null;
2084 $this->mDiffLang = $lang;
2100 if ( $new ===
'prev' ) {
2102 $newid = intval( $old );
2104 $newRev = $this->revisionStore->getRevisionById( $newid );
2106 $oldRev = $this->revisionStore->getPreviousRevision( $newRev );
2108 $oldid = $oldRev->getId();
2111 } elseif ( $new ===
'next' ) {
2113 $oldid = intval( $old );
2115 $oldRev = $this->revisionStore->getRevisionById( $oldid );
2117 $newRev = $this->revisionStore->getNextRevision( $oldRev );
2119 $newid = $newRev->getId();
2123 $oldid = intval( $old );
2124 $newid = intval( $new );
2128 return [ $oldid, $newid ];
2131 private function loadRevisionIds() {
2132 if ( $this->mRevisionsIdsLoaded ) {
2136 $this->mRevisionsIdsLoaded =
true;
2138 $old = $this->mOldid;
2139 $new = $this->mNewid;
2141 [ $this->mOldid, $this->mNewid ] = self::mapDiffPrevNext( $old, $new );
2142 if ( $new ===
'next' && $this->mNewid ===
false ) {
2143 # if no result, NewId points to the newest old revision. The only newer
2144 # revision is cur, which is "0".
2148 $this->hookRunner->onNewDifferenceEngine(
2150 $this->
getTitle(), $this->mOldid, $this->mNewid, $old, $new );
2167 if ( $this->mRevisionsLoaded ) {
2168 return $this->isContentOverridden ||
2169 ( $this->mOldRevisionRecord !==
null && $this->mNewRevisionRecord !== null );
2173 $this->mRevisionsLoaded =
true;
2175 $this->loadRevisionIds();
2178 if ( $this->mNewid ) {
2179 $this->mNewRevisionRecord = $this->revisionStore->getRevisionById( $this->mNewid );
2181 $this->mNewRevisionRecord = $this->revisionStore->getRevisionByTitle( $this->
getTitle() );
2189 $this->mNewid = $this->mNewRevisionRecord->getId();
2190 $this->mNewPage = $this->mNewid ?
2191 Title::newFromLinkTarget( $this->mNewRevisionRecord->getPageAsLinkTarget() ) :
2195 $this->mOldRevisionRecord =
false;
2196 if ( $this->mOldid ) {
2197 $this->mOldRevisionRecord = $this->revisionStore->getRevisionById( $this->mOldid );
2198 } elseif ( $this->mOldid === 0 ) {
2199 $revRecord = $this->revisionStore->getPreviousRevision( $this->mNewRevisionRecord );
2201 $this->mOldid = $revRecord ? $revRecord->
getId() :
false;
2202 $this->mOldRevisionRecord = $revRecord ??
false;
2205 if ( $this->mOldRevisionRecord ===
null ) {
2209 if ( $this->mOldRevisionRecord && $this->mOldRevisionRecord->getId() ) {
2210 $this->mOldPage = Title::newFromLinkTarget(
2211 $this->mOldRevisionRecord->getPageAsLinkTarget()
2214 $this->mOldPage =
null;
2218 $dbr = $this->dbProvider->getReplicaDatabase();
2219 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
2220 if ( $this->mOldid !==
false ) {
2221 $tagIds = $dbr->newSelectQueryBuilder()
2222 ->select(
'ct_tag_id' )
2223 ->from(
'change_tag' )
2224 ->where( [
'ct_rev_id' => $this->mOldid ] )
2225 ->caller( __METHOD__ )->fetchFieldValues();
2227 foreach ( $tagIds as $tagId ) {
2229 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2234 $this->mOldTags = implode(
',', $tags );
2236 $this->mOldTags =
false;
2239 $tagIds = $dbr->newSelectQueryBuilder()
2240 ->select(
'ct_tag_id' )
2241 ->from(
'change_tag' )
2242 ->where( [
'ct_rev_id' => $this->mNewid ] )
2243 ->caller( __METHOD__ )->fetchFieldValues();
2245 foreach ( $tagIds as $tagId ) {
2247 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2252 $this->mNewTags = implode(
',', $tags );
2266 if ( $this->mTextLoaded == 2 ) {
2267 return $this->loadRevisionData() &&
2268 ( $this->mOldRevisionRecord ===
false || $this->mOldContent )
2269 && $this->mNewContent;
2273 $this->mTextLoaded = 2;
2275 if ( !$this->loadRevisionData() ) {
2279 if ( $this->mOldRevisionRecord ) {
2280 $this->mOldContent = $this->mOldRevisionRecord->getContent(
2282 RevisionRecord::FOR_THIS_USER,
2285 if ( $this->mOldContent ===
null ) {
2290 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2292 RevisionRecord::FOR_THIS_USER,
2295 $this->hookRunner->onDifferenceEngineLoadTextAfterNewContentIsLoaded( $this );
2296 if ( $this->mNewContent ===
null ) {
2309 if ( $this->mTextLoaded >= 1 ) {
2310 return $this->loadRevisionData();
2313 $this->mTextLoaded = 1;
2315 if ( !$this->loadRevisionData() ) {
2319 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2321 RevisionRecord::FOR_THIS_USER,
2325 $this->hookRunner->onDifferenceEngineAfterLoadNewText( $this );
2336 if ( $this->textDiffer ===
null ) {
2339 $this->getDiffLang(),
2340 $this->getConfig()->
get( MainConfigNames::DiffEngine ),
2341 $this->getConfig()->
get( MainConfigNames::ExternalDiffEngine ),
2342 $this->getConfig()->
get( MainConfigNames::Wikidiff2Options )
2345 return $this->textDiffer;
2355 return $this->getTextDiffer()->getFormats();
2365 return $this->slotDiffOptions[
'diff-type'] ??
'table';
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
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.
array $params
The job parameters.
B/C adapter for turning a DifferenceEngine into a SlotDiffRenderer.
DifferenceEngine is responsible for rendering the difference between two revisions as HTML.
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).
setSlotDiffOptions( $options)
hasDeletedRevision()
Checks whether one of the given Revisions was deleted.
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.
SlotDiffRenderer[] null $slotDiffRenderers
DifferenceEngine classes for the slots, keyed by role name.
getDiffBodyForRole( $role)
Get the diff table body for one slot, without header.
getTextDiffer()
Get the TextDiffer which will be used for rendering text.
getDefaultLanguage()
Get the language to use if none has been set by setTextLanguage().
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.
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.
getDiffBody()
Get the diff table body, without header.
getTitle()
1.18 to override Title|null
loadRevisionData()
Load revision metadata for the specified revisions.
static getEngine()
Process DiffEngine config and get a sensible, usable engine.
bool $mRevisionsLoaded
Have the revisions been loaded.
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,...
getTextDiffFormat()
Get the selected text diff format.
localiseLineNumbers( $text)
Replace a common convention for language-independent line numbers with the text in the user's languag...
getSlotContents()
Get the old and new content objects for all slots.
string $mMarkPatrolledLink
Link to action=markpatrolled.
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.
setExtraQueryParams( $params)
Set query parameters to append to diff page links.
static intermediateEditsMsg( $numEdits, $numUsers, $limit, $lastUser='[HIDDEN]')
Get a notice about how many intermediate edits and users there are.
getNewid()
Get the ID of new revision (right pane) of the diff.
renderNewRevision()
Show the new revision of the page.
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.
getSlotError( $errorText)
Get an error message for inclusion in a diff body (as a table row).
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.
getSupportedFormats()
Get the list of supported text diff formats.
Title null $mOldPage
Title of old revision or null if the old revision does not exist or does not belong to a page.
getDiffLang()
Get the language in which the diff text is written.
showDiffStyle()
Add style sheets for diff display.
markPatrolledLink()
Build a link to mark a change as patrolled.
getRevisionLoadErrors()
If errors were encountered while loading the revision contents, this will return an array of Messages...
hasSuppressedRevision()
Checks whether one of the given Revisions was suppressed.
getOldRevision()
Get the left side of the diff.
Exception thrown when trying to render a diff between two content types which cannot be compared (thi...
Base class for language-specific code.
getMessageObject()
Return a Message object for this exception.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
setContext(IContextSource $context)
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
A TextDiffer which acts as a container for other TextDiffers, and dispatches requests to them.
A class containing constants representing the names of configuration variables.
Service for getting rendered output of a given page.
Service for creating WikiPage objects.
Parent class for all special pages.
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).
Renders a slot diff by doing a text diff on the native representation.
Base interface for representing page content.
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.