30use MediaWiki\Debug\DeprecationHelper;
87 use DeprecationHelper;
95 private const DIFF_VERSION =
'1.41';
123 private $mOldRevisionRecord;
133 private $mNewRevisionRecord;
164 private $mOldContent;
171 private $mNewContent;
177 private $mRevisionsIdsLoaded =
false;
180 protected $mRevisionsLoaded =
false;
183 protected $mTextLoaded = 0;
193 protected $isContentOverridden =
false;
196 protected $mCacheHit =
false;
199 private $cacheHitKey =
null;
207 public $enableDebugComment =
false;
212 protected $mReducedLineNumbers =
false;
215 protected $mMarkPatrolledLink =
null;
218 protected $unhide =
false;
221 protected $mRefreshCache =
false;
232 protected $isSlotDiffRenderer =
false;
238 private $slotDiffOptions = [];
244 private $extraQueryParams = [];
263 private $revisionLoadErrors = [];
273 public function __construct( $context =
null, $old = 0, $new = 0, $rcid = 0,
274 $refreshCache =
false, $unhide =
false
280 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'" );
282 $this->mOldid = $old;
283 $this->mNewid = $new;
284 $this->mRefreshCache = $refreshCache;
285 $this->unhide = $unhide;
287 $services = MediaWikiServices::getInstance();
288 $this->linkRenderer = $services->getLinkRenderer();
289 $this->contentHandlerFactory = $services->getContentHandlerFactory();
290 $this->revisionStore = $services->getRevisionStore();
291 $this->archivedRevisionLookup = $services->getArchivedRevisionLookup();
292 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
293 $this->wikiPageFactory = $services->getWikiPageFactory();
294 $this->userOptionsLookup = $services->getUserOptionsLookup();
295 $this->commentFormatter = $services->getCommentFormatter();
296 $this->dbProvider = $services->getConnectionProvider();
297 $this->userGroupManager = $services->getUserGroupManager();
298 $this->userEditTracker = $services->getUserEditTracker();
299 $this->userIdentityUtils = $services->getUserIdentityUtils();
308 if ( $this->isSlotDiffRenderer ) {
309 throw new LogicException( __METHOD__ .
' called in slot diff renderer mode' );
312 if ( $this->slotDiffRenderers ===
null ) {
318 $this->slotDiffRenderers = [];
319 foreach ( $slotContents as $role => $contents ) {
320 if ( $contents[
'new'] && $contents[
'old']
321 && $contents[
'new']->equals( $contents[
'old'] )
326 if ( !$contents[
'new'] && !$contents[
'old'] ) {
330 $handler = ( $contents[
'new'] ?: $contents[
'old'] )->getContentHandler();
331 $this->slotDiffRenderers[$role] = $handler->getSlotDiffRenderer(
333 $this->slotDiffOptions + [
334 'contentLanguage' => $this->
getDiffLang()->getCode(),
341 return $this->slotDiffRenderers;
351 $this->isSlotDiffRenderer =
true;
360 if ( $this->isContentOverridden ) {
362 SlotRecord::MAIN => [
'old' => $this->mOldContent,
'new' => $this->mNewContent ]
368 $newSlots = $this->mNewRevisionRecord->getPrimarySlots()->getSlots();
369 $oldSlots = $this->mOldRevisionRecord ?
370 $this->mOldRevisionRecord->getPrimarySlots()->getSlots() :
376 $roles = array_keys( array_merge( $newSlots, $oldSlots ) );
379 foreach ( $roles as $role ) {
381 'old' => $this->loadSingleSlot(
382 $oldSlots[$role] ??
null,
385 'new' => $this->loadSingleSlot(
386 $newSlots[$role] ??
null,
392 if ( isset( $slots[SlotRecord::MAIN] ) ) {
393 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
405 private function loadSingleSlot( ?
SlotRecord $slot,
string $which ) {
412 $this->addRevisionLoadError( $which );
422 private function addRevisionLoadError( $which ) {
423 $this->revisionLoadErrors[] = $this->
msg( $which ===
'new'
424 ?
'difference-bad-new-revision' :
'difference-bad-old-revision'
435 return $this->revisionLoadErrors;
442 private function hasNewRevisionLoadError() {
443 foreach ( $this->revisionLoadErrors as $error ) {
444 if ( $error->getKey() ===
'difference-bad-new-revision' ) {
454 return parent::getTitle() ?: Title::makeTitle(
NS_SPECIAL,
'BadTitle/DifferenceEngine' );
464 $this->mReducedLineNumbers = $value;
473 # Default language in which the diff text is written.
475 return $this->mDiffLang;
485 return $this->
getTitle()->getPageLanguage();
492 return $this->mCacheHit;
503 $this->loadRevisionIds();
505 return $this->mOldid;
515 $this->loadRevisionIds();
517 return $this->mNewid;
527 return $this->mOldRevisionRecord ?:
null;
536 return $this->mNewRevisionRecord;
548 if ( $this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
549 $revRecord = $this->archivedRevisionLookup->getArchivedRevisionRecord(
null, $id );
551 $title = Title::newFromPageIdentity( $revRecord->
getPage() );
553 return SpecialPage::getTitleFor(
'Undelete' )->getFullURL( [
554 'target' => $title->getPrefixedText(),
573 return "[$link $id]";
579 private function showMissingRevision() {
580 $out = $this->getOutput();
583 if ( $this->mOldid && ( !$this->mOldRevisionRecord || !$this->mOldContent ) ) {
584 $missing[] = $this->deletedIdMarker( $this->mOldid );
586 if ( !$this->mNewRevisionRecord || !$this->mNewContent ) {
590 $out->setPageTitleMsg( $this->
msg(
'errorpagetitle' ) );
591 $msg = $this->
msg(
'difference-missing-revision' )
592 ->params( $this->
getLanguage()->listToText( $missing ) )
593 ->numParams( count( $missing ) )
595 $out->addHTML( $msg );
606 $this->mNewRevisionRecord &&
607 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
610 $this->mOldRevisionRecord &&
611 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
624 return $this->
authorizeView( $performer )->toLegacyErrorArray();
636 $permStatus = PermissionStatus::newEmpty();
637 if ( $this->mNewPage ) {
638 $performer->
authorizeRead(
'read', $this->mNewPage, $permStatus );
640 if ( $this->mOldPage ) {
641 $performer->
authorizeRead(
'read', $this->mOldPage, $permStatus );
652 return $this->hasDeletedRevision() && (
653 ( $this->mOldRevisionRecord &&
654 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) ||
655 ( $this->mNewRevisionRecord &&
656 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) )
666 private function getUserEditCount( $user ): string {
667 $editCount = $this->userEditTracker->getUserEditCount( $user );
668 if ( $editCount ===
null ) {
672 return Html::rawElement(
'div', [
673 'class' =>
'mw-diff-usereditcount',
677 $this->getLanguage()->formatNum( $editCount )
689 if ( !$this->userIdentityUtils->isNamed( $user ) ) {
692 $userGroups = $this->userGroupManager->getUserGroups( $user );
693 $userGroupLinks = [];
694 foreach ( $userGroups as $group ) {
695 $userGroupLinks[] = UserGroupMembership::getLinkHTML( $group, $this->
getContext() );
697 return Html::rawElement(
'div', [
698 'class' =>
'mw-diff-userroles',
699 ], $this->getLanguage()->commaList( $userGroupLinks ) );
708 private function getUserMetaData( ?
UserIdentity $user ) {
712 return Html::rawElement(
'div', [
713 'class' =>
'mw-diff-usermetadata',
714 ], $this->getUserRoles( $user ) . $this->getUserEditCount( $user ) );
729 $this->loadRevisionData();
731 if ( $this->mOldRevisionRecord && !$this->mOldRevisionRecord->userCan(
732 RevisionRecord::DELETED_TEXT,
740 return !$this->mNewRevisionRecord || $this->mNewRevisionRecord->userCan(
741 RevisionRecord::DELETED_TEXT,
754 return $this->hasDeletedRevision() && ( !$this->unhide ||
755 !$this->isUserAllowedToSeeRevisions( $performer ) );
762 # Allow frames except in certain special cases
763 $out = $this->getOutput();
764 $out->getMetadata()->setPreventClickjacking(
false );
765 $out->setRobotPolicy(
'noindex,nofollow' );
768 $this->hookRunner->onDifferenceEngineShowDiffPage( $out );
770 if ( !$this->loadRevisionData() ) {
771 if ( $this->hookRunner->onDifferenceEngineShowDiffPageMaybeShowMissingRevision( $this ) ) {
772 $this->showMissingRevision();
777 $user = $this->getUser();
778 $permStatus = $this->authorizeView( $this->getAuthority() );
779 if ( !$permStatus->isGood() ) {
785 $query = $this->extraQueryParams;
786 # Carry over 'diffonly' param via navigation links
787 if ( $diffOnly != MediaWikiServices::getInstance()
788 ->getUserOptionsLookup()->getBoolOption( $user,
'diffonly' )
790 $query[
'diffonly'] = $diffOnly;
792 # Cascade unhide param in links for easy deletion browsing
793 if ( $this->unhide ) {
794 $query[
'unhide'] = 1;
797 # Check if one of the revisions is deleted/suppressed
798 $deleted = $this->hasDeletedRevision();
799 $suppressed = $this->hasSuppressedRevision();
800 $allowed = $this->isUserAllowedToSeeRevisions( $this->getAuthority() );
805 # mOldRevisionRecord is false if the difference engine is called with a "vague" query for
806 # a diff between a version V and its previous version V' AND the version V
807 # is the first version of that article. In that case, V' does not exist.
808 if ( $this->mOldRevisionRecord ===
false ) {
809 if ( $this->mNewPage ) {
810 $out->setPageTitleMsg(
811 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
817 $this->hookRunner->onDifferenceEngineOldHeaderNoOldRev( $oldHeader );
819 $this->hookRunner->onDifferenceEngineViewHeader( $this );
821 if ( !$this->mOldPage || !$this->mNewPage ) {
824 } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
825 $out->setPageTitleMsg(
826 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
830 $out->setPageTitleMsg( $this->msg(
'difference-title-multipage' )->plaintextParams(
831 $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
832 $out->addSubtitle( $this->msg(
'difference-multipage' ) );
836 if ( $samePage && $this->mNewPage &&
837 $this->getAuthority()->probablyCan(
'edit', $this->mNewPage )
839 if ( $this->mNewRevisionRecord->isCurrent() &&
840 $this->getAuthority()->probablyCan(
'rollback', $this->mNewPage )
842 $rollbackLink = Linker::generateRollback(
843 $this->mNewRevisionRecord,
847 if ( $rollbackLink ) {
848 $out->getMetadata()->setPreventClickjacking(
true );
849 $rollback =
"\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
853 if ( $this->userCanEdit( $this->mOldRevisionRecord ) &&
854 $this->userCanEdit( $this->mNewRevisionRecord )
856 $undoLink = $this->linkRenderer->makeKnownLink(
858 $this->msg(
'editundo' )->text(),
859 [
'title' => Linker::titleAttrib(
'undo' ) ],
862 'undoafter' => $this->mOldid,
863 'undo' => $this->mNewid
866 $revisionTools[
'mw-diff-undo'] = $undoLink;
869 # Make "previous revision link"
870 $hasPrevious = $samePage && $this->mOldPage &&
871 $this->revisionStore->getPreviousRevision( $this->mOldRevisionRecord );
872 if ( $hasPrevious ) {
873 $prevlinkQuery = [
'diff' =>
'prev',
'oldid' => $this->mOldid ] + $query;
874 $prevlink = $this->linkRenderer->makeKnownLink(
876 $this->msg(
'previousdiff' )->text(),
877 [
'id' =>
'differences-prevlink' ],
880 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
882 $this->msg(
'previousdiff' )->text(),
884 'class' =>
'mw-diff-revision-history-link-previous'
889 $prevlink =
"\u{00A0}";
892 if ( $this->mOldRevisionRecord->isMinor() ) {
893 $oldminor = ChangesList::flag(
'minor' );
898 $oldRevRecord = $this->mOldRevisionRecord;
900 $ldel = $this->revisionDeleteLink( $oldRevRecord );
901 $oldRevisionHeader = $this->getRevisionHeader( $oldRevRecord,
'complete' );
902 $oldChangeTags = ChangeTags::formatSummaryRow( $this->mOldTags,
'diff', $this->getContext() );
903 $oldRevComment = $this->commentFormatter
905 $oldRevRecord, $user, !$diffOnly, !$this->unhide,
false
908 if ( $oldRevComment ===
'' ) {
909 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
910 $oldRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
913 $oldHeader =
'<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader .
'</strong></div>' .
914 '<div id="mw-diff-otitle2">' .
915 Linker::revUserTools( $oldRevRecord, !$this->unhide ) .
916 $this->getUserMetaData( $oldRevRecord->getUser() ) .
918 '<div id="mw-diff-otitle3">' . $oldminor . $oldRevComment . $ldel .
'</div>' .
919 '<div id="mw-diff-otitle5">' . $oldChangeTags[0] .
'</div>' .
920 '<div id="mw-diff-otitle4">' . $prevlink .
'</div>';
923 $this->hookRunner->onDifferenceEngineOldHeader(
924 $this, $oldHeader, $prevlink, $oldminor, $diffOnly, $ldel, $this->unhide );
927 $out->addJsConfigVars( [
928 'wgDiffOldId' => $this->mOldid,
929 'wgDiffNewId' => $this->mNewid,
932 # Make "next revision link"
933 # Skip next link on the top revision
934 if ( $samePage && $this->mNewPage && !$this->mNewRevisionRecord->isCurrent() ) {
935 $nextlinkQuery = [
'diff' =>
'next',
'oldid' => $this->mNewid ] + $query;
936 $nextlink = $this->linkRenderer->makeKnownLink(
938 $this->msg(
'nextdiff' )->text(),
939 [
'id' =>
'differences-nextlink' ],
942 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
944 $this->msg(
'nextdiff' )->text(),
946 'class' =>
'mw-diff-revision-history-link-next'
951 $nextlink =
"\u{00A0}";
954 if ( $this->mNewRevisionRecord->isMinor() ) {
955 $newminor = ChangesList::flag(
'minor' );
960 # Handle RevisionDelete links...
961 $rdel = $this->revisionDeleteLink( $this->mNewRevisionRecord );
963 # Allow extensions to define their own revision tools
964 $this->hookRunner->onDiffTools(
965 $this->mNewRevisionRecord,
967 $this->mOldRevisionRecord ?:
null,
971 $formattedRevisionTools = [];
973 foreach ( $revisionTools as $key => $tool ) {
974 $toolClass = is_string( $key ) ? $key :
'mw-diff-tool';
975 $element = Html::rawElement(
977 [
'class' => $toolClass ],
980 $formattedRevisionTools[] = $element;
983 $newRevRecord = $this->mNewRevisionRecord;
985 $newRevisionHeader = $this->getRevisionHeader( $newRevRecord,
'complete' ) .
986 ' ' . implode(
' ', $formattedRevisionTools );
987 $newChangeTags = ChangeTags::formatSummaryRow( $this->mNewTags,
'diff', $this->getContext() );
988 $newRevComment = $this->commentFormatter->formatRevision(
989 $newRevRecord, $user, !$diffOnly, !$this->unhide,
false
992 if ( $newRevComment ===
'' ) {
993 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
994 $newRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
997 $newHeader =
'<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader .
'</strong></div>' .
998 '<div id="mw-diff-ntitle2">' . Linker::revUserTools( $newRevRecord, !$this->unhide ) .
1000 $this->getUserMetaData( $newRevRecord->getUser() ) .
1002 '<div id="mw-diff-ntitle3">' . $newminor . $newRevComment . $rdel .
'</div>' .
1003 '<div id="mw-diff-ntitle5">' . $newChangeTags[0] .
'</div>' .
1004 '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() .
'</div>';
1007 $this->hookRunner->onDifferenceEngineNewHeader( $this, $newHeader,
1008 $formattedRevisionTools, $nextlink, $rollback, $newminor, $diffOnly,
1009 $rdel, $this->unhide );
1012 Html::rawElement(
'div', [
1013 'class' =>
'mw-diff-revision-history-links'
1016 $addMessageBoxStyles =
false;
1017 # If the diff cannot be shown due to a deleted revision, then output
1018 # the diff header and links to unhide (if available)...
1019 if ( $this->shouldBeHiddenFromUser( $this->getAuthority() ) ) {
1020 $this->showDiffStyle();
1021 $multi = $this->getMultiNotice();
1022 $out->addHTML( $this->addHeader(
'', $oldHeader, $newHeader, $multi ) );
1024 # Give explanation for why revision is not visible
1025 $msg = [ $suppressed ?
'rev-suppressed-no-diff' :
'rev-deleted-no-diff' ];
1027 # Give explanation and add a link to view the diff...
1028 $query = $this->getRequest()->appendQueryValue(
'unhide',
'1' );
1030 $suppressed ?
'rev-suppressed-unhide-diff' :
'rev-deleted-unhide-diff',
1031 $this->
getTitle()->getFullURL( $query )
1034 $out->addHTML( Html::warningBox( $this->msg( ...$msg )->parse(),
'plainlinks' ) );
1035 $addMessageBoxStyles =
true;
1036 # Otherwise, output a regular diff...
1038 # Add deletion notice if the user is viewing deleted content
1041 $msg = $suppressed ?
'rev-suppressed-diff-view' :
'rev-deleted-diff-view';
1042 $notice = Html::warningBox( $this->msg( $msg )->parse(),
'plainlinks' );
1043 $addMessageBoxStyles =
true;
1046 # Add an error if the content can't be loaded
1047 $this->getSlotContents();
1048 foreach ( $this->getRevisionLoadErrors() as $msg ) {
1049 $notice .= Html::warningBox( $msg->parse() );
1050 $addMessageBoxStyles =
true;
1054 if ( $this->getTextDiffer()->hasFormat(
'inline' ) ) {
1058 $this->showTablePrefixes();
1059 $this->showDiff( $oldHeader, $newHeader, $notice );
1061 $this->renderNewRevision();
1065 if ( $this->hookRunner->onDifferenceEngineRenderRevisionShowFinalPatrolLink() ) {
1066 # Add redundant patrol link on bottom...
1067 $out->addHTML( $this->markPatrolledLink() );
1070 if ( $addMessageBoxStyles ) {
1071 $out->addModuleStyles(
'mediawiki.codex.messagebox.styles' );
1078 private function showTablePrefixes() {
1080 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1081 $parts += $slotDiffRenderer->getTablePrefix( $this->getContext(), $this->mNewPage );
1084 if ( count( array_filter( $parts ) ) > 0 ) {
1085 $language = $this->getLanguage();
1087 'class' =>
'mw-diff-table-prefix',
1088 'dir' => $language->getDir(),
1089 'lang' => $language->getCode(),
1091 $this->getOutput()->addHTML(
1092 Html::rawElement(
'div', $attrs, implode(
'', $parts ) ) );
1108 if ( $this->mMarkPatrolledLink ===
null ) {
1109 $linkInfo = $this->getMarkPatrolledLinkInfo();
1111 if ( !$linkInfo || !$this->mNewPage ) {
1112 $this->mMarkPatrolledLink =
'';
1114 $patrolLinkClass =
'patrollink';
1115 $this->mMarkPatrolledLink =
' <span class="' . $patrolLinkClass .
'" data-mw="interface">[' .
1116 $this->linkRenderer->makeKnownLink(
1118 $this->msg(
'markaspatrolleddiff' )->text(),
1121 'action' =>
'markpatrolled',
1122 'rcid' => $linkInfo[
'rcid'],
1126 $this->hookRunner->onDifferenceEngineMarkPatrolledLink( $this,
1127 $this->mMarkPatrolledLink, $linkInfo[
'rcid'] );
1130 return $this->mMarkPatrolledLink;
1141 $user = $this->getUser();
1142 $config = $this->getConfig();
1147 $config->get( MainConfigNames::UseRCPatrol ) &&
1149 $this->getAuthority()->probablyCan(
'patrol', $this->mNewPage ) &&
1152 RecentChange::isInRCLifespan( $this->mNewRevisionRecord->getTimestamp(), 21600 )
1155 $change = RecentChange::newFromConds(
1157 'rc_this_oldid' => $this->mNewid,
1158 'rc_patrolled' => RecentChange::PRC_UNPATROLLED
1163 if ( $change && !$change->getPerformerIdentity()->equals( $user ) ) {
1164 $rcid = $change->getAttribute(
'rc_id' );
1175 $this->hookRunner->onDifferenceEngineMarkPatrolledRCID( $rcid, $this, $change, $user );
1179 $this->getOutput()->getMetadata()->setPreventClickjacking(
true );
1180 $this->getOutput()->addModules(
'mediawiki.misc-authed-curate' );
1182 return [
'rcid' => $rcid ];
1195 private function revisionDeleteLink(
RevisionRecord $revRecord ) {
1196 $link = Linker::getRevDeleteLink(
1197 $this->getAuthority(),
1201 if ( $link !==
'' ) {
1202 $link =
"\u{00A0}\u{00A0}\u{00A0}" . $link .
' ';
1214 if ( $this->isContentOverridden ) {
1218 throw new LogicException(
1220 .
' is not supported after calling setContent(). Use setRevisions() instead.'
1224 $out = $this->getOutput();
1225 $revHeader = $this->getRevisionHeader( $this->mNewRevisionRecord );
1226 # Add "current version as of X" title
1227 $out->addHTML(
"<hr class='diff-hr' id='mw-oldid' />
1228 <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
1229 # Page content may be handled by a hooked call instead...
1230 if ( $this->hookRunner->onArticleContentOnDiff( $this, $out ) ) {
1231 $this->loadNewText();
1232 if ( !$this->mNewPage ) {
1238 if ( $this->hasNewRevisionLoadError() ) {
1243 $out->setRevisionId( $this->mNewid );
1244 $out->setRevisionIsCurrent( $this->mNewRevisionRecord->isCurrent() );
1245 $out->getMetadata()->setRevisionTimestamp( $this->mNewRevisionRecord->getTimestamp() );
1246 $out->setArticleFlag(
true );
1248 if ( !$this->hookRunner->onArticleRevisionViewCustom(
1249 $this->mNewRevisionRecord, $this->mNewPage, $this->mOldid, $out )
1255 if ( $this->
getTitle()->equals( $this->mNewPage ) ) {
1259 $wikiPage = $this->getWikiPage();
1262 $wikiPage = $this->wikiPageFactory->newFromTitle( $this->mNewPage );
1265 $parserOptions = $wikiPage->makeParserOptions( $this->getContext() );
1266 $parserOptions->setRenderReason(
'diff-page' );
1268 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
1269 $status = $parserOutputAccess->getParserOutput(
1272 $this->mNewRevisionRecord,
1274 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK |
1276 ParserOutputAccess::OPT_LINKS_UPDATE
1278 if ( $status->isOK() ) {
1279 $parserOutput = $status->getValue();
1281 if ( $this->hookRunner->onDifferenceEngineRenderRevisionAddParserOutput(
1282 $this, $out, $parserOutput, $wikiPage )
1284 $out->addParserOutput( $parserOutput, $parserOptions, [
1285 'enableSectionEditLinks' => $this->mNewRevisionRecord->isCurrent()
1286 && $this->getAuthority()->probablyCan(
1288 $this->mNewRevisionRecord->getPage()
1290 'absoluteURLs' => $this->slotDiffOptions[
'expand-url'] ??
false
1294 $out->addModuleStyles(
'mediawiki.codex.messagebox.styles' );
1295 foreach ( $status->getMessages() as $msg ) {
1296 $out->addHTML( Html::errorBox(
1297 $this->msg( $msg )->parse()
1315 public function showDiff( $otitle, $ntitle, $notice =
'' ) {
1317 $this->hookRunner->onDifferenceEngineShowDiff( $this );
1319 $diff = $this->getDiff( $otitle, $ntitle, $notice );
1320 if ( $diff ===
false ) {
1321 $this->showMissingRevision();
1325 $this->showDiffStyle();
1326 if ( $this->slotDiffOptions[
'expand-url'] ??
false ) {
1327 $diff = Linker::expandLocalLinks( $diff );
1329 $this->getOutput()->addHTML( $diff );
1337 if ( !$this->isSlotDiffRenderer ) {
1338 $this->getOutput()->addModules(
'mediawiki.diff' );
1339 $this->getOutput()->addModuleStyles( [
1340 'mediawiki.interface.helpers.styles',
1341 'mediawiki.diff.styles'
1343 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1344 $slotDiffRenderer->addModules( $this->getOutput() );
1358 public function getDiff( $otitle, $ntitle, $notice =
'' ) {
1359 $body = $this->getDiffBody();
1360 if ( $body ===
false ) {
1364 $multi = $this->getMultiNotice();
1366 if ( $body ===
'' ) {
1367 $notice .=
'<div class="mw-diff-empty">' .
1368 $this->msg(
'diff-empty' )->parse() .
1372 if ( $this->cacheHitKey !==
null ) {
1373 $body .=
"\n<!-- diff cache key " . htmlspecialchars( $this->cacheHitKey ) .
" -->\n";
1376 return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
1379 private function incrementStats(
string $cacheStatus ): void {
1381 $stats->getCounter(
'diff_cache_total' )
1382 ->setLabel(
'status', $cacheStatus )
1383 ->copyToStatsdAt(
'diff_cache.' . $cacheStatus )
1393 $this->mCacheHit =
true;
1395 if ( !$this->isContentOverridden ) {
1396 if ( !$this->loadRevisionData() ) {
1398 } elseif ( $this->mOldRevisionRecord &&
1399 !$this->mOldRevisionRecord->userCan(
1400 RevisionRecord::DELETED_TEXT,
1401 $this->getAuthority()
1405 } elseif ( $this->mNewRevisionRecord &&
1406 !$this->mNewRevisionRecord->userCan(
1407 RevisionRecord::DELETED_TEXT,
1408 $this->getAuthority()
1413 if ( $this->mOldRevisionRecord ===
false || (
1414 $this->mOldRevisionRecord &&
1415 $this->mNewRevisionRecord &&
1416 $this->mOldRevisionRecord->getId() &&
1417 $this->mOldRevisionRecord->getId() == $this->mNewRevisionRecord->getId()
1419 if ( $this->hookRunner->onDifferenceEngineShowEmptyOldContent( $this ) ) {
1427 $services = MediaWikiServices::getInstance();
1428 $cache = $services->getMainWANObjectCache();
1429 $stats = $services->getStatsdDataFactory();
1430 if ( $this->mOldid && $this->mNewid ) {
1431 $key = $cache->makeKey( ...$this->getDiffBodyCacheKeyParams() );
1434 if ( !$this->mRefreshCache ) {
1435 $difftext = $cache->get( $key );
1436 if ( is_string( $difftext ) ) {
1437 $this->incrementStats(
'hit' );
1438 $difftext = $this->localiseDiff( $difftext );
1439 $this->cacheHitKey = $key;
1444 $this->mCacheHit =
false;
1445 $this->cacheHitKey =
null;
1448 if ( !$this->loadText() ) {
1455 $slotContents = $this->getSlotContents();
1456 foreach ( $this->getSlotDiffRenderers() as $role => $slotDiffRenderer ) {
1458 $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role][
'old'],
1459 $slotContents[$role][
'new'] );
1463 if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1466 $difftext .= $this->getSlotHeader( $slotTitle );
1468 $difftext .= $slotDiff;
1472 if ( !$this->hookRunner->onAbortDiffCache( $this ) ) {
1473 $this->incrementStats(
'uncacheable' );
1474 } elseif ( $key !==
false ) {
1475 $this->incrementStats(
'miss' );
1476 $cache->set( $key, $difftext, 7 * 86400 );
1478 $this->incrementStats(
'uncacheable' );
1481 $difftext = $this->localiseDiff( $difftext );
1493 $diffRenderers = $this->getSlotDiffRenderers();
1494 if ( !isset( $diffRenderers[$role] ) ) {
1498 $slotContents = $this->getSlotContents();
1500 $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role][
'old'],
1501 $slotContents[$role][
'new'] );
1505 if ( $slotDiff ===
'' ) {
1509 if ( $role !== SlotRecord::MAIN ) {
1512 $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff;
1515 return $this->localiseDiff( $slotDiff );
1527 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1528 $userLang = $this->getLanguage()->getHtmlCode();
1529 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-header',
'lang' => $userLang ],
1530 Html::element(
'th', [
'colspan' => $columnCount ], $headerText ) );
1541 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1542 $userLang = $this->getLanguage()->getHtmlCode();
1543 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-error',
'lang' => $userLang ],
1544 Html::rawElement(
'td', [
'colspan' => $columnCount ], $errorText ) );
1561 if ( !$this->mOldid || !$this->mNewid ) {
1562 throw new BadMethodCallException(
'mOldid and mNewid must be set to get diff cache key.' );
1568 "old-{$this->mOldid}",
1569 "rev-{$this->mNewid}"
1573 if ( !$this->isSlotDiffRenderer ) {
1574 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1575 $extraKeys = array_merge( $extraKeys, $slotDiffRenderer->getExtraCacheKeys() );
1578 ksort( $extraKeys );
1579 return array_merge( $params, array_values( $extraKeys ) );
1593 $this->mOldid = 123456789;
1594 $this->mNewid = 987654321;
1597 $params = $this->getDiffBodyCacheKeyParams();
1607 if ( array_slice( $params, 0, count( $standardParams ) ) === $standardParams ) {
1608 $params = array_slice( $params, count( $standardParams ) );
1625 $validatedOptions = [];
1626 if ( isset( $options[
'diff-type'] )
1627 && $this->getTextDiffer()->hasFormat( $options[
'diff-type'] )
1629 $validatedOptions[
'diff-type'] = $options[
'diff-type'];
1631 if ( !empty( $options[
'expand-url'] ) ) {
1632 $validatedOptions[
'expand-url'] =
true;
1634 if ( !empty( $options[
'inline-toggle'] ) ) {
1635 $validatedOptions[
'inline-toggle'] =
true;
1637 $this->slotDiffOptions = $validatedOptions;
1648 $this->extraQueryParams = $params;
1665 $slotDiffRenderer = $new->
getContentHandler()->getSlotDiffRenderer( $this->getContext() );
1668 && $this->isSlotDiffRenderer
1674 throw new LogicException( get_class( $this ) .
': could not maintain backwards compatibility. '
1675 .
'Please use a SlotDiffRenderer.' );
1677 return $slotDiffRenderer->getDiff( $old, $new ) . $this->getDebugString();
1693 $slotDiffRenderer = $this->contentHandlerFactory
1695 ->getSlotDiffRenderer( $this->getContext() );
1699 throw new LogicException(
'The slot diff renderer for text content should be a '
1700 .
'TextSlotDiffRenderer subclass' );
1702 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
1712 $differenceEngine =
new self;
1713 $engine = $differenceEngine->getTextDiffer()->getEngineForFormat(
'table' );
1714 if ( $engine ===
'external' ) {
1715 return MediaWikiServices::getInstance()->getMainConfig()
1716 ->get( MainConfigNames::ExternalDiffEngine );
1730 protected function debug( $generator =
"internal" ) {
1731 if ( !$this->enableDebugComment ) {
1734 $data = [ $generator ];
1735 if ( $this->getConfig()->
get( MainConfigNames::ShowHostnames ) ) {
1740 return "<!-- diff generator: " .
1741 implode(
" ", array_map(
"htmlspecialchars", $data ) ) .
1748 private function getDebugString() {
1749 $engine = self::getEngine();
1750 if ( $engine ===
'wikidiff2' ) {
1751 return $this->debug(
'wikidiff2' );
1752 } elseif ( $engine ===
'php' ) {
1753 return $this->debug(
'native PHP' );
1755 return $this->debug(
"external $engine" );
1765 private function localiseDiff( $text ) {
1766 return $this->getTextDiffer()->localize( $this->getTextDiffFormat(), $text );
1778 return preg_replace_callback(
'/<!--LINE (\d+)-->/',
1780 if (
$matches[1] ===
'1' && $this->mReducedLineNumbers ) {
1783 return $this->msg(
'lineno' )->numParams(
$matches[1] )->escaped();
1795 !$this->mOldRevisionRecord || !$this->mNewRevisionRecord
1796 || !$this->mOldPage || !$this->mNewPage
1797 || !$this->mOldPage->equals( $this->mNewPage )
1798 || $this->mOldRevisionRecord->getId() ===
null
1799 || $this->mNewRevisionRecord->getId() ===
null
1801 || $this->mNewPage->getArticleID() !== $this->mOldRevisionRecord->getPageId()
1802 || $this->mNewPage->getArticleID() !== $this->mNewRevisionRecord->getPageId()
1807 if ( $this->mOldRevisionRecord->getTimestamp() > $this->mNewRevisionRecord->getTimestamp() ) {
1808 $oldRevRecord = $this->mNewRevisionRecord;
1809 $newRevRecord = $this->mOldRevisionRecord;
1811 $oldRevRecord = $this->mOldRevisionRecord;
1812 $newRevRecord = $this->mNewRevisionRecord;
1818 $revisionIdList = $this->revisionStore->getRevisionIdsBetween(
1819 $this->mNewPage->getArticleID(),
1825 if ( count( $revisionIdList ) > 0 ) {
1826 foreach ( $revisionIdList as $revisionId ) {
1827 $revision = $this->revisionStore->getRevisionById( $revisionId );
1828 if ( $revision->getUser( RevisionRecord::FOR_THIS_USER, $this->getAuthority() ) ) {
1833 if ( $nEdits > 0 && $nEdits <= 1000 ) {
1835 $newRevUserForGender =
'[HIDDEN]';
1838 $users = $this->revisionStore->getAuthorsBetween(
1839 $this->mNewPage->getArticleID(),
1845 $numUsers = count( $users );
1847 $newRevUser = $newRevRecord->getUser( RevisionRecord::RAW );
1848 $newRevUserText = $newRevUser ? $newRevUser->getName() :
'';
1849 $newRevUserSafe = $newRevRecord->getUser(
1850 RevisionRecord::FOR_THIS_USER,
1851 $this->getAuthority()
1853 $newRevUserForGender = $newRevUserSafe ? $newRevUserSafe->getName() :
'[HIDDEN]';
1854 if ( $numUsers == 1 && $users[0]->getName() == $newRevUserText ) {
1857 }
catch ( InvalidArgumentException ) {
1861 return self::intermediateEditsMsg( $nEdits, $numUsers, $limit, $newRevUserForGender );
1878 if ( $numUsers === 0 ) {
1879 $msg =
'diff-multi-sameuser';
1881 ->numParams( $numEdits, $numUsers )
1882 ->params( $lastUser )
1884 } elseif ( $numUsers > $limit ) {
1885 $msg =
'diff-multi-manyusers';
1888 $msg =
'diff-multi-otherusers';
1891 return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1899 if ( !$revRecord->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1916 $lang = $this->getLanguage();
1917 $user = $this->getUser();
1919 $timestamp = $lang->userTimeAndDate( $revtimestamp, $user );
1920 $dateofrev = $lang->userDate( $revtimestamp, $user );
1921 $timeofrev = $lang->userTime( $revtimestamp, $user );
1923 $header = $this->msg(
1924 $rev->
isCurrent() ?
'currentrev-asof' :
'revisionasof',
1930 if ( $complete !==
'complete' ) {
1931 return $header->escaped();
1936 if ( $this->userCanEdit( $rev ) ) {
1937 $header = $this->linkRenderer->makeKnownLink(
1941 [
'oldid' => $rev->
getId() ]
1943 $editQuery = [
'action' =>
'edit' ];
1945 $editQuery[
'oldid'] = $rev->
getId();
1948 $key = $this->getAuthority()->probablyCan(
'edit', $rev->
getPage() ) ?
'editold' :
'viewsourceold';
1949 $msg = $this->msg( $key )->text();
1950 $editLink = $this->linkRenderer->makeKnownLink( $title, $msg, [], $editQuery );
1951 $header .=
' ' . Html::rawElement(
1953 [
'class' =>
'mw-diff-edit' ],
1957 $header = $header->escaped();
1961 $header .= Html::element(
'span',
1963 'class' =>
'mw-diff-timestamp',
1964 'data-timestamp' =>
wfTimestamp( TS_ISO_8601, $revtimestamp ),
1968 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1969 return Html::rawElement(
1971 [
'class' => Linker::getRevisionDeletedClass( $rev ) ],
1991 public function addHeader( $diff, $otitle, $ntitle, $multi =
'', $notice =
'' ) {
1994 $header = Html::openElement(
'table', [
2000 'diff-type-' . $this->getTextDiffFormat(),
2004 'diff-contentalign-' . $this->getDiffLang()->alignStart(),
2009 'diff-editfont-' . $this->userOptionsLookup->getOption(
2014 'data-mw' =>
'interface',
2016 $userLang = htmlspecialchars( $this->getLanguage()->getHtmlCode() );
2018 if ( !$diff && !$otitle ) {
2020 <tr class=\"diff-title\" lang=\"{$userLang}\">
2021 <td class=\"diff-ntitle\">{$ntitle}</td>
2027 <col class=\"diff-marker\" />
2028 <col class=\"diff-content\" />
2029 <col class=\"diff-marker\" />
2030 <col class=\"diff-content\" />";
2037 if ( $otitle || $ntitle ) {
2039 $deletedClass =
'diff-side-deleted';
2040 $addedClass =
'diff-side-added';
2042 <tr class=\"diff-title\" lang=\"{$userLang}\">
2043 <td colspan=\"$colspan\" class=\"diff-otitle {$deletedClass}\">{$otitle}</td>
2044 <td colspan=\"$colspan\" class=\"diff-ntitle {$addedClass}\">{$ntitle}</td>
2049 if ( $multi !=
'' ) {
2050 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2051 "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
2053 if ( $notice !=
'' ) {
2054 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2055 "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
2058 return $header . $diff .
"</table>";
2069 $this->mOldContent = $oldContent;
2070 $this->mNewContent = $newContent;
2072 $this->mTextLoaded = 2;
2073 $this->mRevisionsLoaded =
true;
2074 $this->isContentOverridden =
true;
2075 $this->slotDiffRenderers =
null;
2086 if ( $oldRevision ) {
2087 $this->mOldRevisionRecord = $oldRevision;
2088 $this->mOldid = $oldRevision->
getId();
2089 $this->mOldPage = Title::newFromPageIdentity( $oldRevision->
getPage() );
2092 $this->mOldContent = $oldRevision->
getContent( SlotRecord::MAIN,
2093 RevisionRecord::FOR_THIS_USER, $this->getAuthority() );
2094 if ( !$this->mOldContent ) {
2095 $this->addRevisionLoadError(
'old' );
2098 $this->mOldPage =
null;
2099 $this->mOldRevisionRecord = $this->mOldid =
false;
2101 $this->mNewRevisionRecord = $newRevision;
2102 $this->mNewid = $newRevision->
getId();
2103 $this->mNewPage = Title::newFromPageIdentity( $newRevision->
getPage() );
2104 $this->mNewContent = $newRevision->
getContent( SlotRecord::MAIN,
2105 RevisionRecord::FOR_THIS_USER, $this->getAuthority() );
2106 if ( !$this->mNewContent ) {
2107 $this->addRevisionLoadError(
'new' );
2110 $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded =
true;
2111 $this->mTextLoaded = $oldRevision ? 2 : 1;
2112 $this->isContentOverridden =
false;
2113 $this->slotDiffRenderers =
null;
2123 $this->mDiffLang = $lang;
2139 if ( $new ===
'prev' ) {
2141 $newid = intval( $old );
2143 $newRev = $this->revisionStore->getRevisionById( $newid );
2145 $oldRev = $this->revisionStore->getPreviousRevision( $newRev );
2147 $oldid = $oldRev->getId();
2150 } elseif ( $new ===
'next' ) {
2152 $oldid = intval( $old );
2154 $oldRev = $this->revisionStore->getRevisionById( $oldid );
2156 $newRev = $this->revisionStore->getNextRevision( $oldRev );
2158 $newid = $newRev->getId();
2162 $oldid = intval( $old );
2163 $newid = intval( $new );
2167 return [ $oldid, $newid ];
2170 private function loadRevisionIds() {
2171 if ( $this->mRevisionsIdsLoaded ) {
2175 $this->mRevisionsIdsLoaded =
true;
2177 $old = $this->mOldid;
2178 $new = $this->mNewid;
2180 [ $this->mOldid, $this->mNewid ] = self::mapDiffPrevNext( $old, $new );
2181 if ( $new ===
'next' && $this->mNewid ===
false ) {
2182 # if no result, NewId points to the newest old revision. The only newer
2183 # revision is cur, which is "0".
2187 $this->hookRunner->onNewDifferenceEngine(
2189 $this->
getTitle(), $this->mOldid, $this->mNewid, $old, $new );
2206 if ( $this->mRevisionsLoaded ) {
2207 return $this->isContentOverridden ||
2208 ( $this->mOldRevisionRecord !==
null && $this->mNewRevisionRecord !== null );
2212 $this->mRevisionsLoaded =
true;
2214 $this->loadRevisionIds();
2217 if ( $this->mNewid ) {
2218 $this->mNewRevisionRecord = $this->revisionStore->getRevisionById( $this->mNewid );
2220 $this->mNewRevisionRecord = $this->revisionStore->getRevisionByTitle( $this->
getTitle() );
2228 $this->mNewid = $this->mNewRevisionRecord->getId();
2229 $this->mNewPage = $this->mNewid ?
2230 Title::newFromPageIdentity( $this->mNewRevisionRecord->getPage() ) :
2234 $this->mOldRevisionRecord =
false;
2235 if ( $this->mOldid ) {
2236 $this->mOldRevisionRecord = $this->revisionStore->getRevisionById( $this->mOldid );
2237 } elseif ( $this->mOldid === 0 ) {
2238 $revRecord = $this->revisionStore->getPreviousRevision( $this->mNewRevisionRecord );
2240 $this->mOldid = $revRecord ? $revRecord->
getId() :
false;
2241 $this->mOldRevisionRecord = $revRecord ??
false;
2244 if ( $this->mOldRevisionRecord ===
null ) {
2248 if ( $this->mOldRevisionRecord && $this->mOldRevisionRecord->getId() ) {
2249 $this->mOldPage = Title::newFromPageIdentity( $this->mOldRevisionRecord->getPage() );
2251 $this->mOldPage =
null;
2255 $dbr = $this->dbProvider->getReplicaDatabase();
2256 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
2257 if ( $this->mOldid !==
false ) {
2258 $tagIds = $dbr->newSelectQueryBuilder()
2259 ->select(
'ct_tag_id' )
2260 ->from(
'change_tag' )
2261 ->where( [
'ct_rev_id' => $this->mOldid ] )
2262 ->caller( __METHOD__ )->fetchFieldValues();
2264 foreach ( $tagIds as $tagId ) {
2266 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2271 $this->mOldTags = implode(
',', $tags );
2273 $this->mOldTags =
false;
2276 $tagIds = $dbr->newSelectQueryBuilder()
2277 ->select(
'ct_tag_id' )
2278 ->from(
'change_tag' )
2279 ->where( [
'ct_rev_id' => $this->mNewid ] )
2280 ->caller( __METHOD__ )->fetchFieldValues();
2282 foreach ( $tagIds as $tagId ) {
2284 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2289 $this->mNewTags = implode(
',', $tags );
2303 if ( $this->mTextLoaded == 2 ) {
2304 return $this->loadRevisionData() &&
2305 ( $this->mOldRevisionRecord ===
false || $this->mOldContent )
2306 && $this->mNewContent;
2310 $this->mTextLoaded = 2;
2312 if ( !$this->loadRevisionData() ) {
2316 if ( $this->mOldRevisionRecord ) {
2317 $this->mOldContent = $this->mOldRevisionRecord->getContent(
2319 RevisionRecord::FOR_THIS_USER,
2320 $this->getAuthority()
2322 if ( $this->mOldContent ===
null ) {
2327 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2329 RevisionRecord::FOR_THIS_USER,
2330 $this->getAuthority()
2332 $this->hookRunner->onDifferenceEngineLoadTextAfterNewContentIsLoaded( $this );
2333 if ( $this->mNewContent ===
null ) {
2346 if ( $this->mTextLoaded >= 1 ) {
2347 return $this->loadRevisionData();
2350 $this->mTextLoaded = 1;
2352 if ( !$this->loadRevisionData() ) {
2356 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2358 RevisionRecord::FOR_THIS_USER,
2359 $this->getAuthority()
2362 $this->hookRunner->onDifferenceEngineAfterLoadNewText( $this );
2373 if ( $this->textDiffer ===
null ) {
2375 $this->getContext(),
2376 $this->getDiffLang(),
2377 $this->getConfig()->
get( MainConfigNames::DiffEngine ),
2378 $this->getConfig()->
get( MainConfigNames::ExternalDiffEngine ),
2379 $this->getConfig()->
get( MainConfigNames::Wikidiff2Options )
2382 return $this->textDiffer;
2392 return $this->getTextDiffer()->getFormats();
2402 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.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
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?
authorizeView(Authority $performer)
Check whether the user can read both of the pages for the current diff.
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...
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()
getContext()
Get the base IContextSource object.
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.
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.
Content objects represent page content, e.g.
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.