29use MediaWiki\Debug\DeprecationHelper;
83 use DeprecationHelper;
91 private const DIFF_VERSION =
'1.41';
119 private $mOldRevisionRecord;
129 private $mNewRevisionRecord;
160 private $mOldContent;
167 private $mNewContent;
173 private $mRevisionsIdsLoaded =
false;
176 protected $mRevisionsLoaded =
false;
179 protected $mTextLoaded = 0;
189 protected $isContentOverridden =
false;
192 protected $mCacheHit =
false;
195 private $cacheHitKey =
null;
203 public $enableDebugComment =
false;
208 protected $mReducedLineNumbers =
false;
211 protected $mMarkPatrolledLink =
null;
214 protected $unhide =
false;
217 protected $mRefreshCache =
false;
228 protected $isSlotDiffRenderer =
false;
234 private $slotDiffOptions = [];
240 private $extraQueryParams = [];
259 private $revisionLoadErrors = [];
269 public function __construct( $context =
null, $old = 0, $new = 0, $rcid = 0,
270 $refreshCache =
false, $unhide =
false
276 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'" );
278 $this->mOldid = $old;
279 $this->mNewid = $new;
280 $this->mRefreshCache = $refreshCache;
281 $this->unhide = $unhide;
283 $services = MediaWikiServices::getInstance();
284 $this->linkRenderer = $services->getLinkRenderer();
285 $this->contentHandlerFactory = $services->getContentHandlerFactory();
286 $this->revisionStore = $services->getRevisionStore();
287 $this->archivedRevisionLookup = $services->getArchivedRevisionLookup();
288 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
289 $this->wikiPageFactory = $services->getWikiPageFactory();
290 $this->userOptionsLookup = $services->getUserOptionsLookup();
291 $this->commentFormatter = $services->getCommentFormatter();
292 $this->dbProvider = $services->getConnectionProvider();
293 $this->userGroupManager = $services->getUserGroupManager();
294 $this->userEditTracker = $services->getUserEditTracker();
295 $this->userIdentityUtils = $services->getUserIdentityUtils();
304 if ( $this->isSlotDiffRenderer ) {
305 throw new LogicException( __METHOD__ .
' called in slot diff renderer mode' );
308 if ( $this->slotDiffRenderers ===
null ) {
314 $this->slotDiffRenderers = [];
315 foreach ( $slotContents as $role => $contents ) {
316 if ( $contents[
'new'] && $contents[
'old']
317 && $contents[
'new']->equals( $contents[
'old'] )
322 $handler = ( $contents[
'new'] ?: $contents[
'old'] )->getContentHandler();
323 $this->slotDiffRenderers[$role] = $handler->getSlotDiffRenderer(
325 $this->slotDiffOptions + [
326 'contentLanguage' => $this->
getDiffLang()->getCode(),
333 return $this->slotDiffRenderers;
343 $this->isSlotDiffRenderer =
true;
352 if ( $this->isContentOverridden ) {
354 SlotRecord::MAIN => [
'old' => $this->mOldContent,
'new' => $this->mNewContent ]
360 $newSlots = $this->mNewRevisionRecord->getPrimarySlots()->getSlots();
361 $oldSlots = $this->mOldRevisionRecord ?
362 $this->mOldRevisionRecord->getPrimarySlots()->getSlots() :
368 $roles = array_keys( array_merge( $newSlots, $oldSlots ) );
371 foreach ( $roles as $role ) {
373 'old' => $this->loadSingleSlot(
374 $oldSlots[$role] ??
null,
377 'new' => $this->loadSingleSlot(
378 $newSlots[$role] ??
null,
384 if ( isset( $slots[SlotRecord::MAIN] ) ) {
385 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
397 private function loadSingleSlot( ?
SlotRecord $slot,
string $which ) {
404 $this->addRevisionLoadError( $which );
414 private function addRevisionLoadError( $which ) {
415 $this->revisionLoadErrors[] = $this->
msg( $which ===
'new'
416 ?
'difference-bad-new-revision' :
'difference-bad-old-revision'
427 return $this->revisionLoadErrors;
434 private function hasNewRevisionLoadError() {
435 foreach ( $this->revisionLoadErrors as $error ) {
436 if ( $error->getKey() ===
'difference-bad-new-revision' ) {
446 return parent::getTitle() ?: Title::makeTitle(
NS_SPECIAL,
'BadTitle/DifferenceEngine' );
456 $this->mReducedLineNumbers = $value;
465 # Default language in which the diff text is written.
467 return $this->mDiffLang;
477 return $this->
getTitle()->getPageLanguage();
484 return $this->mCacheHit;
495 $this->loadRevisionIds();
497 return $this->mOldid;
507 $this->loadRevisionIds();
509 return $this->mNewid;
519 return $this->mOldRevisionRecord ?:
null;
528 return $this->mNewRevisionRecord;
540 if ( $this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
541 $revRecord = $this->archivedRevisionLookup->getArchivedRevisionRecord(
null, $id );
543 $title = Title::newFromPageIdentity( $revRecord->
getPage() );
545 return SpecialPage::getTitleFor(
'Undelete' )->getFullURL( [
546 'target' => $title->getPrefixedText(),
565 return "[$link $id]";
571 private function showMissingRevision() {
572 $out = $this->getOutput();
575 if ( $this->mOldid && ( !$this->mOldRevisionRecord || !$this->mOldContent ) ) {
576 $missing[] = $this->deletedIdMarker( $this->mOldid );
578 if ( !$this->mNewRevisionRecord || !$this->mNewContent ) {
582 $out->setPageTitleMsg( $this->
msg(
'errorpagetitle' ) );
583 $msg = $this->
msg(
'difference-missing-revision' )
584 ->params( $this->
getLanguage()->listToText( $missing ) )
585 ->numParams( count( $missing ) )
587 $out->addHTML( $msg );
598 $this->mNewRevisionRecord &&
599 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
602 $this->mOldRevisionRecord &&
603 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
615 $permStatus = PermissionStatus::newEmpty();
616 if ( $this->mNewPage ) {
617 $performer->
authorizeRead(
'read', $this->mNewPage, $permStatus );
619 if ( $this->mOldPage ) {
620 $performer->
authorizeRead(
'read', $this->mOldPage, $permStatus );
622 return $permStatus->toLegacyErrorArray();
632 ( $this->mOldRevisionRecord &&
633 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) ||
634 ( $this->mNewRevisionRecord &&
635 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) )
645 private function getUserEditCount( $user ): string {
646 $editCount = $this->userEditTracker->getUserEditCount( $user );
647 if ( $editCount ===
null ) {
651 return Html::rawElement(
'div', [
652 'class' =>
'mw-diff-usereditcount',
668 if ( !$this->userIdentityUtils->isNamed( $user ) ) {
671 $userGroups = $this->userGroupManager->getUserGroups( $user );
672 $userGroupLinks = [];
673 foreach ( $userGroups as $group ) {
674 $userGroupLinks[] = UserGroupMembership::getLinkHTML( $group, $this->
getContext() );
676 return Html::rawElement(
'div', [
677 'class' =>
'mw-diff-userroles',
678 ], $this->getLanguage()->commaList( $userGroupLinks ) );
687 private function getUserMetaData( ?
UserIdentity $user ) {
691 return Html::rawElement(
'div', [
692 'class' =>
'mw-diff-usermetadata',
693 ], $this->getUserRoles( $user ) . $this->getUserEditCount( $user ) );
708 $this->loadRevisionData();
710 if ( $this->mOldRevisionRecord && !$this->mOldRevisionRecord->userCan(
711 RevisionRecord::DELETED_TEXT,
719 return !$this->mNewRevisionRecord || $this->mNewRevisionRecord->userCan(
720 RevisionRecord::DELETED_TEXT,
733 return $this->hasDeletedRevision() && ( !$this->unhide ||
734 !$this->isUserAllowedToSeeRevisions( $performer ) );
741 # Allow frames except in certain special cases
742 $out = $this->getOutput();
743 $out->getMetadata()->setPreventClickjacking(
false );
744 $out->setRobotPolicy(
'noindex,nofollow' );
747 $this->hookRunner->onDifferenceEngineShowDiffPage( $out );
749 if ( !$this->loadRevisionData() ) {
750 if ( $this->hookRunner->onDifferenceEngineShowDiffPageMaybeShowMissingRevision( $this ) ) {
751 $this->showMissingRevision();
756 $user = $this->getUser();
757 $permErrors = $this->getPermissionErrors( $this->getAuthority() );
764 $query = $this->extraQueryParams;
765 # Carry over 'diffonly' param via navigation links
766 if ( $diffOnly != MediaWikiServices::getInstance()
767 ->getUserOptionsLookup()->getBoolOption( $user,
'diffonly' )
769 $query[
'diffonly'] = $diffOnly;
771 # Cascade unhide param in links for easy deletion browsing
772 if ( $this->unhide ) {
773 $query[
'unhide'] = 1;
776 # Check if one of the revisions is deleted/suppressed
777 $deleted = $this->hasDeletedRevision();
778 $suppressed = $this->hasSuppressedRevision();
779 $allowed = $this->isUserAllowedToSeeRevisions( $this->getAuthority() );
784 # mOldRevisionRecord is false if the difference engine is called with a "vague" query for
785 # a diff between a version V and its previous version V' AND the version V
786 # is the first version of that article. In that case, V' does not exist.
787 if ( $this->mOldRevisionRecord ===
false ) {
788 if ( $this->mNewPage ) {
789 $out->setPageTitleMsg(
790 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
796 $this->hookRunner->onDifferenceEngineOldHeaderNoOldRev( $oldHeader );
798 $this->hookRunner->onDifferenceEngineViewHeader( $this );
800 if ( !$this->mOldPage || !$this->mNewPage ) {
803 } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
804 $out->setPageTitleMsg(
805 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
809 $out->setPageTitleMsg( $this->msg(
'difference-title-multipage' )->plaintextParams(
810 $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
811 $out->addSubtitle( $this->msg(
'difference-multipage' ) );
815 if ( $samePage && $this->mNewPage &&
816 $this->getAuthority()->probablyCan(
'edit', $this->mNewPage )
818 if ( $this->mNewRevisionRecord->isCurrent() &&
819 $this->getAuthority()->probablyCan(
'rollback', $this->mNewPage )
821 $rollbackLink = Linker::generateRollback(
822 $this->mNewRevisionRecord,
826 if ( $rollbackLink ) {
827 $out->getMetadata()->setPreventClickjacking(
true );
828 $rollback =
"\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
832 if ( $this->userCanEdit( $this->mOldRevisionRecord ) &&
833 $this->userCanEdit( $this->mNewRevisionRecord )
835 $undoLink = $this->linkRenderer->makeKnownLink(
837 $this->msg(
'editundo' )->text(),
838 [
'title' => Linker::titleAttrib(
'undo' ) ],
841 'undoafter' => $this->mOldid,
842 'undo' => $this->mNewid
845 $revisionTools[
'mw-diff-undo'] = $undoLink;
848 # Make "previous revision link"
849 $hasPrevious = $samePage && $this->mOldPage &&
850 $this->revisionStore->getPreviousRevision( $this->mOldRevisionRecord );
851 if ( $hasPrevious ) {
852 $prevlinkQuery = [
'diff' =>
'prev',
'oldid' => $this->mOldid ] + $query;
853 $prevlink = $this->linkRenderer->makeKnownLink(
855 $this->msg(
'previousdiff' )->text(),
856 [
'id' =>
'differences-prevlink' ],
859 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
861 $this->msg(
'previousdiff' )->text(),
863 'class' =>
'mw-diff-revision-history-link-previous'
868 $prevlink =
"\u{00A0}";
871 if ( $this->mOldRevisionRecord->isMinor() ) {
872 $oldminor = ChangesList::flag(
'minor' );
877 $oldRevRecord = $this->mOldRevisionRecord;
879 $ldel = $this->revisionDeleteLink( $oldRevRecord );
880 $oldRevisionHeader = $this->getRevisionHeader( $oldRevRecord,
'complete' );
882 $oldRevComment = $this->commentFormatter
884 $oldRevRecord, $user, !$diffOnly, !$this->unhide,
false
887 if ( $oldRevComment ===
'' ) {
888 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
889 $oldRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
892 $oldHeader =
'<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader .
'</strong></div>' .
893 '<div id="mw-diff-otitle2">' .
894 Linker::revUserTools( $oldRevRecord, !$this->unhide ) .
895 $this->getUserMetaData( $oldRevRecord->getUser() ) .
897 '<div id="mw-diff-otitle3">' . $oldminor . $oldRevComment . $ldel .
'</div>' .
898 '<div id="mw-diff-otitle5">' . $oldChangeTags[0] .
'</div>' .
899 '<div id="mw-diff-otitle4">' . $prevlink .
'</div>';
902 $this->hookRunner->onDifferenceEngineOldHeader(
903 $this, $oldHeader, $prevlink, $oldminor, $diffOnly, $ldel, $this->unhide );
906 $out->addJsConfigVars( [
907 'wgDiffOldId' => $this->mOldid,
908 'wgDiffNewId' => $this->mNewid,
911 # Make "next revision link"
912 # Skip next link on the top revision
913 if ( $samePage && $this->mNewPage && !$this->mNewRevisionRecord->isCurrent() ) {
914 $nextlinkQuery = [
'diff' =>
'next',
'oldid' => $this->mNewid ] + $query;
915 $nextlink = $this->linkRenderer->makeKnownLink(
917 $this->msg(
'nextdiff' )->text(),
918 [
'id' =>
'differences-nextlink' ],
921 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
923 $this->msg(
'nextdiff' )->text(),
925 'class' =>
'mw-diff-revision-history-link-next'
930 $nextlink =
"\u{00A0}";
933 if ( $this->mNewRevisionRecord->isMinor() ) {
934 $newminor = ChangesList::flag(
'minor' );
939 # Handle RevisionDelete links...
940 $rdel = $this->revisionDeleteLink( $this->mNewRevisionRecord );
942 # Allow extensions to define their own revision tools
943 $this->hookRunner->onDiffTools(
944 $this->mNewRevisionRecord,
946 $this->mOldRevisionRecord ?:
null,
950 $formattedRevisionTools = [];
952 foreach ( $revisionTools as $key => $tool ) {
953 $toolClass = is_string( $key ) ? $key :
'mw-diff-tool';
954 $element = Html::rawElement(
956 [
'class' => $toolClass ],
959 $formattedRevisionTools[] = $element;
962 $newRevRecord = $this->mNewRevisionRecord;
964 $newRevisionHeader = $this->getRevisionHeader( $newRevRecord,
'complete' ) .
965 ' ' . implode(
' ', $formattedRevisionTools );
967 $newRevComment = $this->commentFormatter->formatRevision(
968 $newRevRecord, $user, !$diffOnly, !$this->unhide,
false
971 if ( $newRevComment ===
'' ) {
972 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
973 $newRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
976 $newHeader =
'<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader .
'</strong></div>' .
977 '<div id="mw-diff-ntitle2">' . Linker::revUserTools( $newRevRecord, !$this->unhide ) .
979 $this->getUserMetaData( $newRevRecord->getUser() ) .
981 '<div id="mw-diff-ntitle3">' . $newminor . $newRevComment . $rdel .
'</div>' .
982 '<div id="mw-diff-ntitle5">' . $newChangeTags[0] .
'</div>' .
983 '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() .
'</div>';
986 $this->hookRunner->onDifferenceEngineNewHeader( $this, $newHeader,
987 $formattedRevisionTools, $nextlink, $rollback, $newminor, $diffOnly,
988 $rdel, $this->unhide );
991 Html::rawElement(
'div', [
992 'class' =>
'mw-diff-revision-history-links'
995 $addMessageBoxStyles =
false;
996 # If the diff cannot be shown due to a deleted revision, then output
997 # the diff header and links to unhide (if available)...
998 if ( $this->shouldBeHiddenFromUser( $this->getAuthority() ) ) {
999 $this->showDiffStyle();
1000 $multi = $this->getMultiNotice();
1001 $out->addHTML( $this->addHeader(
'', $oldHeader, $newHeader, $multi ) );
1003 # Give explanation for why revision is not visible
1004 $msg = [ $suppressed ?
'rev-suppressed-no-diff' :
'rev-deleted-no-diff' ];
1006 # Give explanation and add a link to view the diff...
1007 $query = $this->getRequest()->appendQueryValue(
'unhide',
'1' );
1009 $suppressed ?
'rev-suppressed-unhide-diff' :
'rev-deleted-unhide-diff',
1010 $this->
getTitle()->getFullURL( $query )
1013 $out->addHTML( Html::warningBox( $this->msg( ...$msg )->parse(),
'plainlinks' ) );
1014 $addMessageBoxStyles =
true;
1015 # Otherwise, output a regular diff...
1017 # Add deletion notice if the user is viewing deleted content
1020 $msg = $suppressed ?
'rev-suppressed-diff-view' :
'rev-deleted-diff-view';
1021 $notice = Html::warningBox( $this->msg( $msg )->parse(),
'plainlinks' );
1022 $addMessageBoxStyles =
true;
1025 # Add an error if the content can't be loaded
1026 $this->getSlotContents();
1027 foreach ( $this->getRevisionLoadErrors() as $msg ) {
1028 $notice .= Html::warningBox( $msg->parse() );
1029 $addMessageBoxStyles =
true;
1033 if ( $this->getTextDiffer()->hasFormat(
'inline' ) ) {
1037 $this->showTablePrefixes();
1038 $this->showDiff( $oldHeader, $newHeader, $notice );
1040 $this->renderNewRevision();
1044 if ( $this->hookRunner->onDifferenceEngineRenderRevisionShowFinalPatrolLink() ) {
1045 # Add redundant patrol link on bottom...
1046 $out->addHTML( $this->markPatrolledLink() );
1049 if ( $addMessageBoxStyles ) {
1050 $out->addModuleStyles(
'mediawiki.codex.messagebox.styles' );
1057 private function showTablePrefixes() {
1059 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1060 $parts += $slotDiffRenderer->getTablePrefix( $this->getContext(), $this->mNewPage );
1063 if ( count( array_filter( $parts ) ) > 0 ) {
1064 $language = $this->getLanguage();
1066 'class' =>
'mw-diff-table-prefix',
1067 'dir' => $language->getDir(),
1068 'lang' => $language->getCode(),
1070 $this->getOutput()->addHTML(
1071 Html::rawElement(
'div', $attrs, implode(
'', $parts ) ) );
1087 if ( $this->mMarkPatrolledLink ===
null ) {
1088 $linkInfo = $this->getMarkPatrolledLinkInfo();
1090 if ( !$linkInfo || !$this->mNewPage ) {
1091 $this->mMarkPatrolledLink =
'';
1093 $patrolLinkClass =
'patrollink';
1094 $this->mMarkPatrolledLink =
' <span class="' . $patrolLinkClass .
'" data-mw="interface">[' .
1095 $this->linkRenderer->makeKnownLink(
1097 $this->msg(
'markaspatrolleddiff' )->text(),
1100 'action' =>
'markpatrolled',
1101 'rcid' => $linkInfo[
'rcid'],
1105 $this->hookRunner->onDifferenceEngineMarkPatrolledLink( $this,
1106 $this->mMarkPatrolledLink, $linkInfo[
'rcid'] );
1109 return $this->mMarkPatrolledLink;
1120 $user = $this->getUser();
1121 $config = $this->getConfig();
1126 $config->get( MainConfigNames::UseRCPatrol ) &&
1128 $this->getAuthority()->probablyCan(
'patrol', $this->mNewPage ) &&
1131 RecentChange::isInRCLifespan( $this->mNewRevisionRecord->getTimestamp(), 21600 )
1134 $change = RecentChange::newFromConds(
1136 'rc_this_oldid' => $this->mNewid,
1137 'rc_patrolled' => RecentChange::PRC_UNPATROLLED
1142 if ( $change && !$change->getPerformerIdentity()->equals( $user ) ) {
1143 $rcid = $change->getAttribute(
'rc_id' );
1154 $this->hookRunner->onDifferenceEngineMarkPatrolledRCID( $rcid, $this, $change, $user );
1158 $this->getOutput()->getMetadata()->setPreventClickjacking(
true );
1159 $this->getOutput()->addModules(
'mediawiki.misc-authed-curate' );
1161 return [
'rcid' => $rcid ];
1174 private function revisionDeleteLink(
RevisionRecord $revRecord ) {
1175 $link = Linker::getRevDeleteLink(
1176 $this->getAuthority(),
1180 if ( $link !==
'' ) {
1181 $link =
"\u{00A0}\u{00A0}\u{00A0}" . $link .
' ';
1193 if ( $this->isContentOverridden ) {
1197 throw new LogicException(
1199 .
' is not supported after calling setContent(). Use setRevisions() instead.'
1203 $out = $this->getOutput();
1204 $revHeader = $this->getRevisionHeader( $this->mNewRevisionRecord );
1205 # Add "current version as of X" title
1206 $out->addHTML(
"<hr class='diff-hr' id='mw-oldid' />
1207 <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
1208 # Page content may be handled by a hooked call instead...
1209 if ( $this->hookRunner->onArticleContentOnDiff( $this, $out ) ) {
1210 $this->loadNewText();
1211 if ( !$this->mNewPage ) {
1217 if ( $this->hasNewRevisionLoadError() ) {
1222 $out->setRevisionId( $this->mNewid );
1223 $out->setRevisionIsCurrent( $this->mNewRevisionRecord->isCurrent() );
1224 $out->setRevisionTimestamp( $this->mNewRevisionRecord->getTimestamp() );
1225 $out->setArticleFlag(
true );
1227 if ( !$this->hookRunner->onArticleRevisionViewCustom(
1228 $this->mNewRevisionRecord, $this->mNewPage, $this->mOldid, $out )
1234 if ( $this->
getTitle()->equals( $this->mNewPage ) ) {
1238 $wikiPage = $this->getWikiPage();
1241 $wikiPage = $this->wikiPageFactory->newFromTitle( $this->mNewPage );
1244 $parserOptions = $wikiPage->makeParserOptions( $this->getContext() );
1245 $parserOptions->setRenderReason(
'diff-page' );
1247 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
1248 $status = $parserOutputAccess->getParserOutput(
1251 $this->mNewRevisionRecord,
1253 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK |
1255 ParserOutputAccess::OPT_LINKS_UPDATE
1257 if ( $status->isOK() ) {
1258 $parserOutput = $status->getValue();
1260 if ( $this->hookRunner->onDifferenceEngineRenderRevisionAddParserOutput(
1261 $this, $out, $parserOutput, $wikiPage )
1263 $out->addParserOutput( $parserOutput, [
1264 'enableSectionEditLinks' => $this->mNewRevisionRecord->isCurrent()
1265 && $this->getAuthority()->probablyCan(
1267 $this->mNewRevisionRecord->getPage()
1269 'absoluteURLs' => $this->slotDiffOptions[
'expand-url'] ??
false
1273 $out->addModuleStyles(
'mediawiki.codex.messagebox.styles' );
1274 foreach ( $status->getMessages() as $msg ) {
1275 $out->addHTML( Html::errorBox(
1276 $this->msg( $msg )->parse()
1294 public function showDiff( $otitle, $ntitle, $notice =
'' ) {
1296 $this->hookRunner->onDifferenceEngineShowDiff( $this );
1298 $diff = $this->getDiff( $otitle, $ntitle, $notice );
1299 if ( $diff ===
false ) {
1300 $this->showMissingRevision();
1304 $this->showDiffStyle();
1305 if ( $this->slotDiffOptions[
'expand-url'] ??
false ) {
1306 $diff = Linker::expandLocalLinks( $diff );
1308 $this->getOutput()->addHTML( $diff );
1316 if ( !$this->isSlotDiffRenderer ) {
1317 $this->getOutput()->addModules(
'mediawiki.diff' );
1318 $this->getOutput()->addModuleStyles( [
1319 'mediawiki.interface.helpers.styles',
1320 'mediawiki.diff.styles'
1322 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1323 $slotDiffRenderer->addModules( $this->getOutput() );
1337 public function getDiff( $otitle, $ntitle, $notice =
'' ) {
1338 $body = $this->getDiffBody();
1339 if ( $body ===
false ) {
1343 $multi = $this->getMultiNotice();
1345 if ( $body ===
'' ) {
1346 $notice .=
'<div class="mw-diff-empty">' .
1347 $this->msg(
'diff-empty' )->parse() .
1351 if ( $this->cacheHitKey !==
null ) {
1352 $body .=
"\n<!-- diff cache key " . htmlspecialchars( $this->cacheHitKey ) .
" -->\n";
1355 return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
1358 private function incrementStats(
string $cacheStatus ): void {
1360 $stats->getCounter(
'diff_cache_total' )
1361 ->setLabel(
'status', $cacheStatus )
1362 ->copyToStatsdAt(
'diff_cache.' . $cacheStatus )
1372 $this->mCacheHit =
true;
1374 if ( !$this->isContentOverridden ) {
1375 if ( !$this->loadRevisionData() ) {
1377 } elseif ( $this->mOldRevisionRecord &&
1378 !$this->mOldRevisionRecord->userCan(
1379 RevisionRecord::DELETED_TEXT,
1380 $this->getAuthority()
1384 } elseif ( $this->mNewRevisionRecord &&
1385 !$this->mNewRevisionRecord->userCan(
1386 RevisionRecord::DELETED_TEXT,
1387 $this->getAuthority()
1392 if ( $this->mOldRevisionRecord ===
false || (
1393 $this->mOldRevisionRecord &&
1394 $this->mNewRevisionRecord &&
1395 $this->mOldRevisionRecord->getId() &&
1396 $this->mOldRevisionRecord->getId() == $this->mNewRevisionRecord->getId()
1398 if ( $this->hookRunner->onDifferenceEngineShowEmptyOldContent( $this ) ) {
1406 $services = MediaWikiServices::getInstance();
1407 $cache = $services->getMainWANObjectCache();
1408 $stats = $services->getStatsdDataFactory();
1409 if ( $this->mOldid && $this->mNewid ) {
1410 $key = $cache->makeKey( ...$this->getDiffBodyCacheKeyParams() );
1413 if ( !$this->mRefreshCache ) {
1414 $difftext = $cache->get( $key );
1415 if ( is_string( $difftext ) ) {
1416 $this->incrementStats(
'hit' );
1417 $difftext = $this->localiseDiff( $difftext );
1418 $this->cacheHitKey = $key;
1423 $this->mCacheHit =
false;
1424 $this->cacheHitKey =
null;
1427 if ( !$this->loadText() ) {
1434 $slotContents = $this->getSlotContents();
1435 foreach ( $this->getSlotDiffRenderers() as $role => $slotDiffRenderer ) {
1437 $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role][
'old'],
1438 $slotContents[$role][
'new'] );
1442 if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1445 $difftext .= $this->getSlotHeader( $slotTitle );
1447 $difftext .= $slotDiff;
1451 if ( !$this->hookRunner->onAbortDiffCache( $this ) ) {
1452 $this->incrementStats(
'uncacheable' );
1453 } elseif ( $key !==
false ) {
1454 $this->incrementStats(
'miss' );
1455 $cache->set( $key, $difftext, 7 * 86400 );
1457 $this->incrementStats(
'uncacheable' );
1460 $difftext = $this->localiseDiff( $difftext );
1472 $diffRenderers = $this->getSlotDiffRenderers();
1473 if ( !isset( $diffRenderers[$role] ) ) {
1477 $slotContents = $this->getSlotContents();
1479 $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role][
'old'],
1480 $slotContents[$role][
'new'] );
1484 if ( $slotDiff ===
'' ) {
1488 if ( $role !== SlotRecord::MAIN ) {
1491 $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff;
1494 return $this->localiseDiff( $slotDiff );
1506 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1507 $userLang = $this->getLanguage()->getHtmlCode();
1508 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-header',
'lang' => $userLang ],
1509 Html::element(
'th', [
'colspan' => $columnCount ], $headerText ) );
1520 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1521 $userLang = $this->getLanguage()->getHtmlCode();
1522 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-error',
'lang' => $userLang ],
1523 Html::rawElement(
'td', [
'colspan' => $columnCount ], $errorText ) );
1540 if ( !$this->mOldid || !$this->mNewid ) {
1541 throw new BadMethodCallException(
'mOldid and mNewid must be set to get diff cache key.' );
1547 "old-{$this->mOldid}",
1548 "rev-{$this->mNewid}"
1552 if ( !$this->isSlotDiffRenderer ) {
1553 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1554 $extraKeys = array_merge( $extraKeys, $slotDiffRenderer->getExtraCacheKeys() );
1557 ksort( $extraKeys );
1558 return array_merge(
$params, array_values( $extraKeys ) );
1572 $this->mOldid = 123456789;
1573 $this->mNewid = 987654321;
1576 $params = $this->getDiffBodyCacheKeyParams();
1586 if ( array_slice(
$params, 0, count( $standardParams ) ) === $standardParams ) {
1604 $validatedOptions = [];
1605 if ( isset( $options[
'diff-type'] )
1606 && $this->getTextDiffer()->hasFormat( $options[
'diff-type'] )
1608 $validatedOptions[
'diff-type'] = $options[
'diff-type'];
1610 if ( !empty( $options[
'expand-url'] ) ) {
1611 $validatedOptions[
'expand-url'] =
true;
1613 if ( !empty( $options[
'inline-toggle'] ) ) {
1614 $validatedOptions[
'inline-toggle'] =
true;
1616 $this->slotDiffOptions = $validatedOptions;
1627 $this->extraQueryParams =
$params;
1644 $slotDiffRenderer = $new->
getContentHandler()->getSlotDiffRenderer( $this->getContext() );
1647 && $this->isSlotDiffRenderer
1653 throw new LogicException( get_class( $this ) .
': could not maintain backwards compatibility. '
1654 .
'Please use a SlotDiffRenderer.' );
1656 return $slotDiffRenderer->getDiff( $old, $new ) . $this->getDebugString();
1672 $slotDiffRenderer = $this->contentHandlerFactory
1674 ->getSlotDiffRenderer( $this->getContext() );
1678 throw new LogicException(
'The slot diff renderer for text content should be a '
1679 .
'TextSlotDiffRenderer subclass' );
1681 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
1691 $differenceEngine =
new self;
1692 $engine = $differenceEngine->getTextDiffer()->getEngineForFormat(
'table' );
1693 if ( $engine ===
'external' ) {
1694 return MediaWikiServices::getInstance()->getMainConfig()
1695 ->get( MainConfigNames::ExternalDiffEngine );
1709 protected function debug( $generator =
"internal" ) {
1710 if ( !$this->enableDebugComment ) {
1713 $data = [ $generator ];
1714 if ( $this->getConfig()->
get( MainConfigNames::ShowHostnames ) ) {
1719 return "<!-- diff generator: " .
1720 implode(
" ", array_map(
"htmlspecialchars", $data ) ) .
1727 private function getDebugString() {
1728 $engine = self::getEngine();
1729 if ( $engine ===
'wikidiff2' ) {
1730 return $this->debug(
'wikidiff2' );
1731 } elseif ( $engine ===
'php' ) {
1732 return $this->debug(
'native PHP' );
1734 return $this->debug(
"external $engine" );
1744 private function localiseDiff( $text ) {
1745 return $this->getTextDiffer()->localize( $this->getTextDiffFormat(), $text );
1757 return preg_replace_callback(
'/<!--LINE (\d+)-->/',
1759 if (
$matches[1] ===
'1' && $this->mReducedLineNumbers ) {
1762 return $this->msg(
'lineno' )->numParams(
$matches[1] )->escaped();
1774 !$this->mOldRevisionRecord || !$this->mNewRevisionRecord
1775 || !$this->mOldPage || !$this->mNewPage
1776 || !$this->mOldPage->equals( $this->mNewPage )
1777 || $this->mOldRevisionRecord->getId() ===
null
1778 || $this->mNewRevisionRecord->getId() ===
null
1780 || $this->mNewPage->getArticleID() !== $this->mOldRevisionRecord->getPageId()
1781 || $this->mNewPage->getArticleID() !== $this->mNewRevisionRecord->getPageId()
1786 if ( $this->mOldRevisionRecord->getTimestamp() > $this->mNewRevisionRecord->getTimestamp() ) {
1787 $oldRevRecord = $this->mNewRevisionRecord;
1788 $newRevRecord = $this->mOldRevisionRecord;
1790 $oldRevRecord = $this->mOldRevisionRecord;
1791 $newRevRecord = $this->mNewRevisionRecord;
1797 $revisionIdList = $this->revisionStore->getRevisionIdsBetween(
1798 $this->mNewPage->getArticleID(),
1804 if ( count( $revisionIdList ) > 0 ) {
1805 foreach ( $revisionIdList as $revisionId ) {
1806 $revision = $this->revisionStore->getRevisionById( $revisionId );
1807 if ( $revision->getUser( RevisionRecord::FOR_THIS_USER, $this->getAuthority() ) ) {
1812 if ( $nEdits > 0 && $nEdits <= 1000 ) {
1814 $newRevUserForGender =
'[HIDDEN]';
1817 $users = $this->revisionStore->getAuthorsBetween(
1818 $this->mNewPage->getArticleID(),
1824 $numUsers = count( $users );
1826 $newRevUser = $newRevRecord->getUser( RevisionRecord::RAW );
1827 $newRevUserText = $newRevUser ? $newRevUser->getName() :
'';
1828 $newRevUserSafe = $newRevRecord->getUser(
1829 RevisionRecord::FOR_THIS_USER,
1830 $this->getAuthority()
1832 $newRevUserForGender = $newRevUserSafe ? $newRevUserSafe->getName() :
'[HIDDEN]';
1833 if ( $numUsers == 1 && $users[0]->getName() == $newRevUserText ) {
1836 }
catch ( InvalidArgumentException $e ) {
1840 return self::intermediateEditsMsg( $nEdits, $numUsers, $limit, $newRevUserForGender );
1857 if ( $numUsers === 0 ) {
1858 $msg =
'diff-multi-sameuser';
1860 ->numParams( $numEdits, $numUsers )
1861 ->params( $lastUser )
1863 } elseif ( $numUsers > $limit ) {
1864 $msg =
'diff-multi-manyusers';
1867 $msg =
'diff-multi-otherusers';
1870 return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1878 if ( !$revRecord->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1895 $lang = $this->getLanguage();
1896 $user = $this->getUser();
1898 $timestamp = $lang->userTimeAndDate( $revtimestamp, $user );
1899 $dateofrev = $lang->userDate( $revtimestamp, $user );
1900 $timeofrev = $lang->userTime( $revtimestamp, $user );
1903 $rev->
isCurrent() ?
'currentrev-asof' :
'revisionasof',
1909 if ( $complete !==
'complete' ) {
1915 if ( $this->userCanEdit( $rev ) ) {
1916 $header = $this->linkRenderer->makeKnownLink(
1920 [
'oldid' => $rev->
getId() ]
1922 $editQuery = [
'action' =>
'edit' ];
1924 $editQuery[
'oldid'] = $rev->
getId();
1927 $key = $this->getAuthority()->probablyCan(
'edit', $rev->
getPage() ) ?
'editold' :
'viewsourceold';
1928 $msg = $this->msg( $key )->text();
1929 $editLink = $this->linkRenderer->makeKnownLink( $title, $msg, [], $editQuery );
1930 $header .=
' ' . Html::rawElement(
1932 [
'class' =>
'mw-diff-edit' ],
1940 $header .= Html::element(
'span',
1942 'class' =>
'mw-diff-timestamp',
1943 'data-timestamp' =>
wfTimestamp( TS_ISO_8601, $revtimestamp ),
1947 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1948 return Html::rawElement(
1950 [
'class' => Linker::getRevisionDeletedClass( $rev ) ],
1970 public function addHeader( $diff, $otitle, $ntitle, $multi =
'', $notice =
'' ) {
1973 $header = Html::openElement(
'table', [
1979 'diff-type-' . $this->getTextDiffFormat(),
1983 'diff-contentalign-' . $this->getDiffLang()->alignStart(),
1988 'diff-editfont-' . $this->userOptionsLookup->getOption(
1993 'data-mw' =>
'interface',
1995 $userLang = htmlspecialchars( $this->getLanguage()->getHtmlCode() );
1997 if ( !$diff && !$otitle ) {
1999 <tr class=\"diff-title\" lang=\"{$userLang}\">
2000 <td class=\"diff-ntitle\">{$ntitle}</td>
2006 <col class=\"diff-marker\" />
2007 <col class=\"diff-content\" />
2008 <col class=\"diff-marker\" />
2009 <col class=\"diff-content\" />";
2016 if ( $otitle || $ntitle ) {
2018 $deletedClass =
'diff-side-deleted';
2019 $addedClass =
'diff-side-added';
2021 <tr class=\"diff-title\" lang=\"{$userLang}\">
2022 <td colspan=\"$colspan\" class=\"diff-otitle {$deletedClass}\">{$otitle}</td>
2023 <td colspan=\"$colspan\" class=\"diff-ntitle {$addedClass}\">{$ntitle}</td>
2028 if ( $multi !=
'' ) {
2029 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2030 "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
2032 if ( $notice !=
'' ) {
2033 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2034 "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
2037 return $header . $diff .
"</table>";
2048 $this->mOldContent = $oldContent;
2049 $this->mNewContent = $newContent;
2051 $this->mTextLoaded = 2;
2052 $this->mRevisionsLoaded =
true;
2053 $this->isContentOverridden =
true;
2054 $this->slotDiffRenderers =
null;
2065 if ( $oldRevision ) {
2066 $this->mOldRevisionRecord = $oldRevision;
2067 $this->mOldid = $oldRevision->
getId();
2071 $this->mOldContent = $oldRevision->
getContent( SlotRecord::MAIN,
2072 RevisionRecord::FOR_THIS_USER, $this->getAuthority() );
2073 if ( !$this->mOldContent ) {
2074 $this->addRevisionLoadError(
'old' );
2077 $this->mOldPage =
null;
2078 $this->mOldRevisionRecord = $this->mOldid =
false;
2080 $this->mNewRevisionRecord = $newRevision;
2081 $this->mNewid = $newRevision->
getId();
2083 $this->mNewContent = $newRevision->
getContent( SlotRecord::MAIN,
2084 RevisionRecord::FOR_THIS_USER, $this->getAuthority() );
2085 if ( !$this->mNewContent ) {
2086 $this->addRevisionLoadError(
'new' );
2089 $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded =
true;
2090 $this->mTextLoaded = $oldRevision ? 2 : 1;
2091 $this->isContentOverridden =
false;
2092 $this->slotDiffRenderers =
null;
2102 $this->mDiffLang = $lang;
2118 if ( $new ===
'prev' ) {
2120 $newid = intval( $old );
2122 $newRev = $this->revisionStore->getRevisionById( $newid );
2124 $oldRev = $this->revisionStore->getPreviousRevision( $newRev );
2126 $oldid = $oldRev->getId();
2129 } elseif ( $new ===
'next' ) {
2131 $oldid = intval( $old );
2133 $oldRev = $this->revisionStore->getRevisionById( $oldid );
2135 $newRev = $this->revisionStore->getNextRevision( $oldRev );
2137 $newid = $newRev->getId();
2141 $oldid = intval( $old );
2142 $newid = intval( $new );
2146 return [ $oldid, $newid ];
2149 private function loadRevisionIds() {
2150 if ( $this->mRevisionsIdsLoaded ) {
2154 $this->mRevisionsIdsLoaded =
true;
2156 $old = $this->mOldid;
2157 $new = $this->mNewid;
2159 [ $this->mOldid, $this->mNewid ] = self::mapDiffPrevNext( $old, $new );
2160 if ( $new ===
'next' && $this->mNewid ===
false ) {
2161 # if no result, NewId points to the newest old revision. The only newer
2162 # revision is cur, which is "0".
2166 $this->hookRunner->onNewDifferenceEngine(
2168 $this->
getTitle(), $this->mOldid, $this->mNewid, $old, $new );
2185 if ( $this->mRevisionsLoaded ) {
2186 return $this->isContentOverridden ||
2187 ( $this->mOldRevisionRecord !==
null && $this->mNewRevisionRecord !== null );
2191 $this->mRevisionsLoaded =
true;
2193 $this->loadRevisionIds();
2196 if ( $this->mNewid ) {
2197 $this->mNewRevisionRecord = $this->revisionStore->getRevisionById( $this->mNewid );
2199 $this->mNewRevisionRecord = $this->revisionStore->getRevisionByTitle( $this->
getTitle() );
2207 $this->mNewid = $this->mNewRevisionRecord->getId();
2208 $this->mNewPage = $this->mNewid ?
2209 Title::newFromLinkTarget( $this->mNewRevisionRecord->getPageAsLinkTarget() ) :
2213 $this->mOldRevisionRecord =
false;
2214 if ( $this->mOldid ) {
2215 $this->mOldRevisionRecord = $this->revisionStore->getRevisionById( $this->mOldid );
2216 } elseif ( $this->mOldid === 0 ) {
2217 $revRecord = $this->revisionStore->getPreviousRevision( $this->mNewRevisionRecord );
2219 $this->mOldid = $revRecord ? $revRecord->
getId() :
false;
2220 $this->mOldRevisionRecord = $revRecord ??
false;
2223 if ( $this->mOldRevisionRecord ===
null ) {
2227 if ( $this->mOldRevisionRecord && $this->mOldRevisionRecord->getId() ) {
2228 $this->mOldPage = Title::newFromLinkTarget(
2229 $this->mOldRevisionRecord->getPageAsLinkTarget()
2232 $this->mOldPage =
null;
2236 $dbr = $this->dbProvider->getReplicaDatabase();
2237 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
2238 if ( $this->mOldid !==
false ) {
2239 $tagIds = $dbr->newSelectQueryBuilder()
2240 ->select(
'ct_tag_id' )
2241 ->from(
'change_tag' )
2242 ->where( [
'ct_rev_id' => $this->mOldid ] )
2243 ->caller( __METHOD__ )->fetchFieldValues();
2245 foreach ( $tagIds as $tagId ) {
2247 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2252 $this->mOldTags = implode(
',', $tags );
2254 $this->mOldTags =
false;
2257 $tagIds = $dbr->newSelectQueryBuilder()
2258 ->select(
'ct_tag_id' )
2259 ->from(
'change_tag' )
2260 ->where( [
'ct_rev_id' => $this->mNewid ] )
2261 ->caller( __METHOD__ )->fetchFieldValues();
2263 foreach ( $tagIds as $tagId ) {
2265 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2270 $this->mNewTags = implode(
',', $tags );
2284 if ( $this->mTextLoaded == 2 ) {
2285 return $this->loadRevisionData() &&
2286 ( $this->mOldRevisionRecord ===
false || $this->mOldContent )
2287 && $this->mNewContent;
2291 $this->mTextLoaded = 2;
2293 if ( !$this->loadRevisionData() ) {
2297 if ( $this->mOldRevisionRecord ) {
2298 $this->mOldContent = $this->mOldRevisionRecord->getContent(
2300 RevisionRecord::FOR_THIS_USER,
2301 $this->getAuthority()
2303 if ( $this->mOldContent ===
null ) {
2308 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2310 RevisionRecord::FOR_THIS_USER,
2311 $this->getAuthority()
2313 $this->hookRunner->onDifferenceEngineLoadTextAfterNewContentIsLoaded( $this );
2314 if ( $this->mNewContent ===
null ) {
2327 if ( $this->mTextLoaded >= 1 ) {
2328 return $this->loadRevisionData();
2331 $this->mTextLoaded = 1;
2333 if ( !$this->loadRevisionData() ) {
2337 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2339 RevisionRecord::FOR_THIS_USER,
2340 $this->getAuthority()
2343 $this->hookRunner->onDifferenceEngineAfterLoadNewText( $this );
2354 if ( $this->textDiffer ===
null ) {
2356 $this->getContext(),
2357 $this->getDiffLang(),
2358 $this->getConfig()->
get( MainConfigNames::DiffEngine ),
2359 $this->getConfig()->
get( MainConfigNames::ExternalDiffEngine ),
2360 $this->getConfig()->
get( MainConfigNames::Wikidiff2Options )
2363 return $this->textDiffer;
2373 return $this->getTextDiffer()->getFormats();
2383 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...
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()
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.
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.