28use MediaWiki\Debug\DeprecationHelper;
80 use DeprecationHelper;
88 private const DIFF_VERSION =
'1.41';
116 private $mOldRevisionRecord;
126 private $mNewRevisionRecord;
157 private $mOldContent;
164 private $mNewContent;
170 private $mRevisionsIdsLoaded =
false;
173 protected $mRevisionsLoaded =
false;
176 protected $mTextLoaded = 0;
186 protected $isContentOverridden =
false;
189 protected $mCacheHit =
false;
192 private $cacheHitKey =
null;
200 public $enableDebugComment =
false;
205 protected $mReducedLineNumbers =
false;
208 protected $mMarkPatrolledLink =
null;
211 protected $unhide =
false;
214 protected $mRefreshCache =
false;
225 protected $isSlotDiffRenderer =
false;
231 private $slotDiffOptions = [];
236 private $extraQueryParams = [];
255 private $revisionLoadErrors = [];
265 public function __construct( $context =
null, $old = 0, $new = 0, $rcid = 0,
266 $refreshCache =
false, $unhide =
false
272 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'" );
274 $this->mOldid = $old;
275 $this->mNewid = $new;
276 $this->mRefreshCache = $refreshCache;
277 $this->unhide = $unhide;
279 $services = MediaWikiServices::getInstance();
280 $this->linkRenderer = $services->getLinkRenderer();
281 $this->contentHandlerFactory = $services->getContentHandlerFactory();
282 $this->revisionStore = $services->getRevisionStore();
283 $this->archivedRevisionLookup = $services->getArchivedRevisionLookup();
284 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
285 $this->wikiPageFactory = $services->getWikiPageFactory();
286 $this->userOptionsLookup = $services->getUserOptionsLookup();
287 $this->commentFormatter = $services->getCommentFormatter();
288 $this->dbProvider = $services->getConnectionProvider();
289 $this->userGroupManager = $services->getUserGroupManager();
290 $this->userEditTracker = $services->getUserEditTracker();
291 $this->userIdentityUtils = $services->getUserIdentityUtils();
300 if ( $this->isSlotDiffRenderer ) {
301 throw new LogicException( __METHOD__ .
' called in slot diff renderer mode' );
304 if ( $this->slotDiffRenderers ===
null ) {
310 $this->slotDiffRenderers = [];
311 foreach ( $slotContents as $role => $contents ) {
312 if ( $contents[
'new'] && $contents[
'old']
313 && $contents[
'new']->equals( $contents[
'old'] )
318 $handler = ( $contents[
'new'] ?: $contents[
'old'] )->getContentHandler();
319 $this->slotDiffRenderers[$role] = $handler->getSlotDiffRenderer(
321 $this->slotDiffOptions + [
322 'contentLanguage' => $this->
getDiffLang()->getCode(),
329 return $this->slotDiffRenderers;
339 $this->isSlotDiffRenderer =
true;
348 if ( $this->isContentOverridden ) {
350 SlotRecord::MAIN => [
'old' => $this->mOldContent,
'new' => $this->mNewContent ]
356 $newSlots = $this->mNewRevisionRecord->getPrimarySlots()->getSlots();
357 $oldSlots = $this->mOldRevisionRecord ?
358 $this->mOldRevisionRecord->getPrimarySlots()->getSlots() :
364 $roles = array_keys( array_merge( $newSlots, $oldSlots ) );
367 foreach ( $roles as $role ) {
369 'old' => $this->loadSingleSlot(
370 $oldSlots[$role] ??
null,
373 'new' => $this->loadSingleSlot(
374 $newSlots[$role] ??
null,
380 if ( isset( $slots[SlotRecord::MAIN] ) ) {
381 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
393 private function loadSingleSlot( ?
SlotRecord $slot,
string $which ) {
400 $this->addRevisionLoadError( $which );
410 private function addRevisionLoadError( $which ) {
411 $this->revisionLoadErrors[] = $this->
msg( $which ===
'new'
412 ?
'difference-bad-new-revision' :
'difference-bad-old-revision'
423 return $this->revisionLoadErrors;
430 private function hasNewRevisionLoadError() {
431 foreach ( $this->revisionLoadErrors as $error ) {
432 if ( $error->getKey() ===
'difference-bad-new-revision' ) {
442 return parent::getTitle() ?: Title::makeTitle(
NS_SPECIAL,
'BadTitle/DifferenceEngine' );
452 $this->mReducedLineNumbers = $value;
461 # Default language in which the diff text is written.
463 return $this->mDiffLang;
473 return $this->
getTitle()->getPageLanguage();
480 return $this->mCacheHit;
491 $this->loadRevisionIds();
493 return $this->mOldid;
503 $this->loadRevisionIds();
505 return $this->mNewid;
515 return $this->mOldRevisionRecord ?:
null;
524 return $this->mNewRevisionRecord;
536 if ( $this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
537 $revRecord = $this->archivedRevisionLookup->getArchivedRevisionRecord(
null, $id );
539 $title = Title::newFromPageIdentity( $revRecord->
getPage() );
541 return SpecialPage::getTitleFor(
'Undelete' )->getFullURL( [
542 'target' => $title->getPrefixedText(),
561 return "[$link $id]";
567 private function showMissingRevision() {
568 $out = $this->getOutput();
571 if ( $this->mOldid && ( !$this->mOldRevisionRecord || !$this->mOldContent ) ) {
572 $missing[] = $this->deletedIdMarker( $this->mOldid );
574 if ( !$this->mNewRevisionRecord || !$this->mNewContent ) {
578 $out->setPageTitleMsg( $this->
msg(
'errorpagetitle' ) );
579 $msg = $this->
msg(
'difference-missing-revision' )
580 ->params( $this->
getLanguage()->listToText( $missing ) )
581 ->numParams( count( $missing ) )
583 $out->addHTML( $msg );
594 $this->mNewRevisionRecord &&
595 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
598 $this->mOldRevisionRecord &&
599 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
611 $permStatus = PermissionStatus::newEmpty();
612 if ( $this->mNewPage ) {
613 $performer->
authorizeRead(
'read', $this->mNewPage, $permStatus );
615 if ( $this->mOldPage ) {
616 $performer->
authorizeRead(
'read', $this->mOldPage, $permStatus );
618 return $permStatus->toLegacyErrorArray();
628 ( $this->mOldRevisionRecord &&
629 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) ||
630 ( $this->mNewRevisionRecord &&
631 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) )
641 private function getUserEditCount( $user ): string {
642 $editCount = $this->userEditTracker->getUserEditCount( $user );
643 if ( $editCount ===
null ) {
647 return Html::rawElement(
'div', [
648 'class' =>
'mw-diff-usereditcount',
664 if ( !$this->userIdentityUtils->isNamed( $user ) ) {
667 $userGroups = $this->userGroupManager->getUserGroups( $user );
668 $userGroupLinks = [];
669 foreach ( $userGroups as $group ) {
670 $userGroupLinks[] = UserGroupMembership::getLinkHTML( $group, $this->
getContext() );
672 return Html::rawElement(
'div', [
673 'class' =>
'mw-diff-userroles',
674 ], $this->getLanguage()->commaList( $userGroupLinks ) );
683 private function getUserMetaData( ?
UserIdentity $user ) {
687 return Html::rawElement(
'div', [
688 'class' =>
'mw-diff-usermetadata',
689 ], $this->getUserRoles( $user ) . $this->getUserEditCount( $user ) );
704 $this->loadRevisionData();
706 if ( $this->mOldRevisionRecord && !$this->mOldRevisionRecord->userCan(
707 RevisionRecord::DELETED_TEXT,
715 return !$this->mNewRevisionRecord || $this->mNewRevisionRecord->userCan(
716 RevisionRecord::DELETED_TEXT,
729 return $this->hasDeletedRevision() && ( !$this->unhide ||
730 !$this->isUserAllowedToSeeRevisions( $performer ) );
737 # Allow frames except in certain special cases
738 $out = $this->getOutput();
739 $out->setPreventClickjacking(
false );
740 $out->setRobotPolicy(
'noindex,nofollow' );
743 $this->hookRunner->onDifferenceEngineShowDiffPage( $out );
745 if ( !$this->loadRevisionData() ) {
746 if ( $this->hookRunner->onDifferenceEngineShowDiffPageMaybeShowMissingRevision( $this ) ) {
747 $this->showMissingRevision();
753 $permErrors = $this->getPermissionErrors( $this->
getAuthority() );
760 $query = $this->extraQueryParams;
761 # Carry over 'diffonly' param via navigation links
762 if ( $diffOnly != MediaWikiServices::getInstance()
763 ->getUserOptionsLookup()->getBoolOption( $user,
'diffonly' )
765 $query[
'diffonly'] = $diffOnly;
767 # Cascade unhide param in links for easy deletion browsing
768 if ( $this->unhide ) {
769 $query[
'unhide'] = 1;
772 # Check if one of the revisions is deleted/suppressed
773 $deleted = $this->hasDeletedRevision();
774 $suppressed = $this->hasSuppressedRevision();
775 $allowed = $this->isUserAllowedToSeeRevisions( $this->
getAuthority() );
780 # mOldRevisionRecord is false if the difference engine is called with a "vague" query for
781 # a diff between a version V and its previous version V' AND the version V
782 # is the first version of that article. In that case, V' does not exist.
783 if ( $this->mOldRevisionRecord ===
false ) {
784 if ( $this->mNewPage ) {
785 $out->setPageTitleMsg(
786 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
792 $this->hookRunner->onDifferenceEngineOldHeaderNoOldRev( $oldHeader );
794 $this->hookRunner->onDifferenceEngineViewHeader( $this );
796 if ( !$this->mOldPage || !$this->mNewPage ) {
799 } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
800 $out->setPageTitleMsg(
801 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
805 $out->setPageTitleMsg( $this->msg(
'difference-title-multipage' )->plaintextParams(
806 $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
807 $out->addSubtitle( $this->msg(
'difference-multipage' ) );
811 if ( $samePage && $this->mNewPage &&
812 $this->
getAuthority()->probablyCan(
'edit', $this->mNewPage )
814 if ( $this->mNewRevisionRecord->isCurrent() &&
815 $this->getAuthority()->probablyCan(
'rollback', $this->mNewPage )
817 $rollbackLink = Linker::generateRollback(
818 $this->mNewRevisionRecord,
822 if ( $rollbackLink ) {
823 $out->setPreventClickjacking(
true );
824 $rollback =
"\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
828 if ( $this->userCanEdit( $this->mOldRevisionRecord ) &&
829 $this->userCanEdit( $this->mNewRevisionRecord )
831 $undoLink = $this->linkRenderer->makeKnownLink(
833 $this->msg(
'editundo' )->text(),
834 [
'title' => Linker::titleAttrib(
'undo' ) ],
837 'undoafter' => $this->mOldid,
838 'undo' => $this->mNewid
841 $revisionTools[
'mw-diff-undo'] = $undoLink;
844 # Make "previous revision link"
845 $hasPrevious = $samePage && $this->mOldPage &&
846 $this->revisionStore->getPreviousRevision( $this->mOldRevisionRecord );
847 if ( $hasPrevious ) {
848 $prevlinkQuery = [
'diff' =>
'prev',
'oldid' => $this->mOldid ] + $query;
849 $prevlink = $this->linkRenderer->makeKnownLink(
851 $this->msg(
'previousdiff' )->text(),
852 [
'id' =>
'differences-prevlink' ],
855 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
857 $this->msg(
'previousdiff' )->text(),
859 'class' =>
'mw-diff-revision-history-link-previous'
864 $prevlink =
"\u{00A0}";
867 if ( $this->mOldRevisionRecord->isMinor() ) {
868 $oldminor = ChangesList::flag(
'minor' );
873 $oldRevRecord = $this->mOldRevisionRecord;
875 $ldel = $this->revisionDeleteLink( $oldRevRecord );
876 $oldRevisionHeader = $this->getRevisionHeader( $oldRevRecord,
'complete' );
878 $oldRevComment = $this->commentFormatter
880 $oldRevRecord, $user, !$diffOnly, !$this->unhide,
false
883 if ( $oldRevComment ===
'' ) {
884 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
885 $oldRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
888 $oldHeader =
'<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader .
'</strong></div>' .
889 '<div id="mw-diff-otitle2">' .
890 Linker::revUserTools( $oldRevRecord, !$this->unhide ) .
891 $this->getUserMetaData( $oldRevRecord->getUser() ) .
893 '<div id="mw-diff-otitle3">' . $oldminor . $oldRevComment . $ldel .
'</div>' .
894 '<div id="mw-diff-otitle5">' . $oldChangeTags[0] .
'</div>' .
895 '<div id="mw-diff-otitle4">' . $prevlink .
'</div>';
898 $this->hookRunner->onDifferenceEngineOldHeader(
899 $this, $oldHeader, $prevlink, $oldminor, $diffOnly, $ldel, $this->unhide );
902 $out->addJsConfigVars( [
903 'wgDiffOldId' => $this->mOldid,
904 'wgDiffNewId' => $this->mNewid,
907 # Make "next revision link"
908 # Skip next link on the top revision
909 if ( $samePage && $this->mNewPage && !$this->mNewRevisionRecord->isCurrent() ) {
910 $nextlinkQuery = [
'diff' =>
'next',
'oldid' => $this->mNewid ] + $query;
911 $nextlink = $this->linkRenderer->makeKnownLink(
913 $this->msg(
'nextdiff' )->text(),
914 [
'id' =>
'differences-nextlink' ],
917 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
919 $this->msg(
'nextdiff' )->text(),
921 'class' =>
'mw-diff-revision-history-link-next'
926 $nextlink =
"\u{00A0}";
929 if ( $this->mNewRevisionRecord->isMinor() ) {
930 $newminor = ChangesList::flag(
'minor' );
935 # Handle RevisionDelete links...
936 $rdel = $this->revisionDeleteLink( $this->mNewRevisionRecord );
938 # Allow extensions to define their own revision tools
939 $this->hookRunner->onDiffTools(
940 $this->mNewRevisionRecord,
942 $this->mOldRevisionRecord ?:
null,
946 $formattedRevisionTools = [];
948 foreach ( $revisionTools as $key => $tool ) {
949 $toolClass = is_string( $key ) ? $key :
'mw-diff-tool';
950 $element = Html::rawElement(
952 [
'class' => $toolClass ],
955 $formattedRevisionTools[] = $element;
958 $newRevRecord = $this->mNewRevisionRecord;
960 $newRevisionHeader = $this->getRevisionHeader( $newRevRecord,
'complete' ) .
961 ' ' . implode(
' ', $formattedRevisionTools );
963 $newRevComment = $this->commentFormatter->formatRevision(
964 $newRevRecord, $user, !$diffOnly, !$this->unhide,
false
967 if ( $newRevComment ===
'' ) {
968 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
969 $newRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
972 $newHeader =
'<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader .
'</strong></div>' .
973 '<div id="mw-diff-ntitle2">' . Linker::revUserTools( $newRevRecord, !$this->unhide ) .
975 $this->getUserMetaData( $newRevRecord->getUser() ) .
977 '<div id="mw-diff-ntitle3">' . $newminor . $newRevComment . $rdel .
'</div>' .
978 '<div id="mw-diff-ntitle5">' . $newChangeTags[0] .
'</div>' .
979 '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() .
'</div>';
982 $this->hookRunner->onDifferenceEngineNewHeader( $this, $newHeader,
983 $formattedRevisionTools, $nextlink, $rollback, $newminor, $diffOnly,
984 $rdel, $this->unhide );
987 Html::rawElement(
'div', [
988 'class' =>
'mw-diff-revision-history-links'
991 # If the diff cannot be shown due to a deleted revision, then output
992 # the diff header and links to unhide (if available)...
993 if ( $this->shouldBeHiddenFromUser( $this->
getAuthority() ) ) {
994 $this->showDiffStyle();
995 $multi = $this->getMultiNotice();
996 $out->addHTML( $this->addHeader(
'', $oldHeader, $newHeader, $multi ) );
998 # Give explanation for why revision is not visible
999 $msg = [ $suppressed ?
'rev-suppressed-no-diff' :
'rev-deleted-no-diff' ];
1001 # Give explanation and add a link to view the diff...
1002 $query = $this->
getRequest()->appendQueryValue(
'unhide',
'1' );
1004 $suppressed ?
'rev-suppressed-unhide-diff' :
'rev-deleted-unhide-diff',
1005 $this->
getTitle()->getFullURL( $query )
1008 $out->addHTML( Html::warningBox( $this->msg( ...$msg )->parse(),
'plainlinks' ) );
1009 # Otherwise, output a regular diff...
1011 # Add deletion notice if the user is viewing deleted content
1014 $msg = $suppressed ?
'rev-suppressed-diff-view' :
'rev-deleted-diff-view';
1015 $notice = Html::warningBox( $this->msg( $msg )->parse(),
'plainlinks' );
1018 # Add an error if the content can't be loaded
1019 $this->getSlotContents();
1020 foreach ( $this->getRevisionLoadErrors() as $msg ) {
1021 $notice .= Html::warningBox( $msg->parse() );
1025 if ( $this->getTextDiffer()->hasFormat(
'inline' ) ) {
1029 $this->showTablePrefixes();
1030 $this->showDiff( $oldHeader, $newHeader, $notice );
1032 $this->renderNewRevision();
1036 if ( $this->hookRunner->onDifferenceEngineRenderRevisionShowFinalPatrolLink() ) {
1037 # Add redundant patrol link on bottom...
1038 $out->addHTML( $this->markPatrolledLink() );
1046 private function showTablePrefixes() {
1048 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1049 $parts += $slotDiffRenderer->getTablePrefix( $this->
getContext(), $this->mNewPage );
1052 if ( count( array_filter( $parts ) ) > 0 ) {
1053 $language = $this->getLanguage();
1055 'class' =>
'mw-diff-table-prefix',
1056 'dir' => $language->getDir(),
1057 'lang' => $language->getCode(),
1059 $this->getOutput()->addHTML(
1060 Html::rawElement(
'div', $attrs, implode(
'', $parts ) ) );
1075 if ( $this->mMarkPatrolledLink ===
null ) {
1076 $linkInfo = $this->getMarkPatrolledLinkInfo();
1078 if ( !$linkInfo || !$this->mNewPage ) {
1079 $this->mMarkPatrolledLink =
'';
1081 $patrolLinkClass =
'patrollink';
1082 $this->mMarkPatrolledLink =
' <span class="' . $patrolLinkClass .
'" data-mw="interface">[' .
1083 $this->linkRenderer->makeKnownLink(
1085 $this->msg(
'markaspatrolleddiff' )->text(),
1088 'action' =>
'markpatrolled',
1089 'rcid' => $linkInfo[
'rcid'],
1093 $this->hookRunner->onDifferenceEngineMarkPatrolledLink( $this,
1094 $this->mMarkPatrolledLink, $linkInfo[
'rcid'] );
1097 return $this->mMarkPatrolledLink;
1109 $config = $this->getConfig();
1114 $config->get( MainConfigNames::UseRCPatrol ) &&
1116 $this->getAuthority()->probablyCan(
'patrol', $this->mNewPage ) &&
1119 RecentChange::isInRCLifespan( $this->mNewRevisionRecord->getTimestamp(), 21600 )
1122 $change = RecentChange::newFromConds(
1124 'rc_this_oldid' => $this->mNewid,
1125 'rc_patrolled' => RecentChange::PRC_UNPATROLLED
1130 if ( $change && !$change->getPerformerIdentity()->equals( $user ) ) {
1131 $rcid = $change->getAttribute(
'rc_id' );
1142 $this->hookRunner->onDifferenceEngineMarkPatrolledRCID( $rcid, $this, $change, $user );
1146 $this->getOutput()->setPreventClickjacking(
true );
1147 if ( $this->
getAuthority()->isAllowed(
'writeapi' ) ) {
1148 $this->getOutput()->addModules(
'mediawiki.misc-authed-curate' );
1151 return [
'rcid' => $rcid ];
1164 private function revisionDeleteLink(
RevisionRecord $revRecord ) {
1165 $link = Linker::getRevDeleteLink(
1170 if ( $link !==
'' ) {
1171 $link =
"\u{00A0}\u{00A0}\u{00A0}" . $link .
' ';
1183 if ( $this->isContentOverridden ) {
1187 throw new LogicException(
1189 .
' is not supported after calling setContent(). Use setRevisions() instead.'
1193 $out = $this->getOutput();
1194 $revHeader = $this->getRevisionHeader( $this->mNewRevisionRecord );
1195 # Add "current version as of X" title
1196 $out->addHTML(
"<hr class='diff-hr' id='mw-oldid' />
1197 <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
1198 # Page content may be handled by a hooked call instead...
1199 if ( $this->hookRunner->onArticleContentOnDiff( $this, $out ) ) {
1200 $this->loadNewText();
1201 if ( !$this->mNewPage ) {
1207 if ( $this->hasNewRevisionLoadError() ) {
1212 $out->setRevisionId( $this->mNewid );
1213 $out->setRevisionIsCurrent( $this->mNewRevisionRecord->isCurrent() );
1214 $out->setRevisionTimestamp( $this->mNewRevisionRecord->getTimestamp() );
1215 $out->setArticleFlag(
true );
1217 if ( !$this->hookRunner->onArticleRevisionViewCustom(
1218 $this->mNewRevisionRecord, $this->mNewPage, $this->mOldid, $out )
1224 if ( $this->
getTitle()->equals( $this->mNewPage ) ) {
1228 $wikiPage = $this->getWikiPage();
1231 $wikiPage = $this->wikiPageFactory->newFromTitle( $this->mNewPage );
1234 $parserOptions = $wikiPage->makeParserOptions( $this->
getContext() );
1235 $parserOptions->setRenderReason(
'diff-page' );
1237 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
1238 $status = $parserOutputAccess->getParserOutput(
1241 $this->mNewRevisionRecord,
1243 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK |
1245 ParserOutputAccess::OPT_LINKS_UPDATE
1247 if ( $status->isOK() ) {
1248 $parserOutput = $status->getValue();
1250 if ( $this->hookRunner->onDifferenceEngineRenderRevisionAddParserOutput(
1251 $this, $out, $parserOutput, $wikiPage )
1253 $out->addParserOutput( $parserOutput, [
1254 'enableSectionEditLinks' => $this->mNewRevisionRecord->isCurrent()
1255 && $this->getAuthority()->probablyCan(
1257 $this->mNewRevisionRecord->getPage()
1259 'absoluteURLs' => $this->slotDiffOptions[
'expand-url'] ??
false
1265 $out->parseAsInterface(
1266 $status->getWikiText(
false,
false, $this->getLanguage() )
1285 public function showDiff( $otitle, $ntitle, $notice =
'' ) {
1287 $this->hookRunner->onDifferenceEngineShowDiff( $this );
1289 $diff = $this->getDiff( $otitle, $ntitle, $notice );
1290 if ( $diff ===
false ) {
1291 $this->showMissingRevision();
1295 $this->showDiffStyle();
1296 if ( $this->slotDiffOptions[
'expand-url'] ??
false ) {
1297 $diff = Linker::expandLocalLinks( $diff );
1299 $this->getOutput()->addHTML( $diff );
1307 if ( !$this->isSlotDiffRenderer ) {
1308 $this->getOutput()->addModules(
'mediawiki.diff' );
1309 $this->getOutput()->addModuleStyles( [
1310 'mediawiki.interface.helpers.styles',
1311 'mediawiki.diff.styles'
1313 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1314 $slotDiffRenderer->addModules( $this->getOutput() );
1328 public function getDiff( $otitle, $ntitle, $notice =
'' ) {
1329 $body = $this->getDiffBody();
1330 if ( $body ===
false ) {
1334 $multi = $this->getMultiNotice();
1336 if ( $body ===
'' ) {
1337 $notice .=
'<div class="mw-diff-empty">' .
1338 $this->msg(
'diff-empty' )->parse() .
1342 if ( $this->cacheHitKey !==
null ) {
1343 $body .=
"\n<!-- diff cache key " . htmlspecialchars( $this->cacheHitKey ) .
" -->\n";
1346 return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
1355 $this->mCacheHit =
true;
1357 if ( !$this->isContentOverridden ) {
1358 if ( !$this->loadRevisionData() ) {
1360 } elseif ( $this->mOldRevisionRecord &&
1361 !$this->mOldRevisionRecord->userCan(
1362 RevisionRecord::DELETED_TEXT,
1363 $this->getAuthority()
1367 } elseif ( $this->mNewRevisionRecord &&
1368 !$this->mNewRevisionRecord->userCan(
1369 RevisionRecord::DELETED_TEXT,
1370 $this->getAuthority()
1375 if ( $this->mOldRevisionRecord ===
false || (
1376 $this->mOldRevisionRecord &&
1377 $this->mNewRevisionRecord &&
1378 $this->mOldRevisionRecord->getId() &&
1379 $this->mOldRevisionRecord->getId() == $this->mNewRevisionRecord->getId()
1381 if ( $this->hookRunner->onDifferenceEngineShowEmptyOldContent( $this ) ) {
1389 $services = MediaWikiServices::getInstance();
1390 $cache = $services->getMainWANObjectCache();
1391 $stats = $services->getStatsdDataFactory();
1392 if ( $this->mOldid && $this->mNewid ) {
1393 $key = $cache->makeKey( ...$this->getDiffBodyCacheKeyParams() );
1396 if ( !$this->mRefreshCache ) {
1397 $difftext = $cache->get( $key );
1398 if ( is_string( $difftext ) ) {
1399 $stats->updateCount(
'diff_cache.hit', 1 );
1400 $difftext = $this->localiseDiff( $difftext );
1401 $this->cacheHitKey = $key;
1406 $this->mCacheHit =
false;
1407 $this->cacheHitKey =
null;
1410 if ( !$this->loadText() ) {
1417 $slotContents = $this->getSlotContents();
1418 foreach ( $this->getSlotDiffRenderers() as $role => $slotDiffRenderer ) {
1420 $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role][
'old'],
1421 $slotContents[$role][
'new'] );
1425 if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1428 $difftext .= $this->getSlotHeader( $slotTitle );
1430 $difftext .= $slotDiff;
1434 if ( !$this->hookRunner->onAbortDiffCache( $this ) ) {
1435 $stats->updateCount(
'diff_cache.uncacheable', 1 );
1436 } elseif ( $key !==
false ) {
1437 $stats->updateCount(
'diff_cache.miss', 1 );
1438 $cache->set( $key, $difftext, 7 * 86400 );
1440 $stats->updateCount(
'diff_cache.uncacheable', 1 );
1443 $difftext = $this->localiseDiff( $difftext );
1455 $diffRenderers = $this->getSlotDiffRenderers();
1456 if ( !isset( $diffRenderers[$role] ) ) {
1460 $slotContents = $this->getSlotContents();
1462 $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role][
'old'],
1463 $slotContents[$role][
'new'] );
1467 if ( $slotDiff ===
'' ) {
1471 if ( $role !== SlotRecord::MAIN ) {
1474 $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff;
1477 return $this->localiseDiff( $slotDiff );
1489 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1490 $userLang = $this->getLanguage()->getHtmlCode();
1491 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-header',
'lang' => $userLang ],
1492 Html::element(
'th', [
'colspan' => $columnCount ], $headerText ) );
1503 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1504 $userLang = $this->getLanguage()->getHtmlCode();
1505 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-error',
'lang' => $userLang ],
1506 Html::rawElement(
'td', [
'colspan' => $columnCount ], $errorText ) );
1523 if ( !$this->mOldid || !$this->mNewid ) {
1524 throw new BadMethodCallException(
'mOldid and mNewid must be set to get diff cache key.' );
1530 "old-{$this->mOldid}",
1531 "rev-{$this->mNewid}"
1535 if ( !$this->isSlotDiffRenderer ) {
1536 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1537 $extraKeys = array_merge( $extraKeys, $slotDiffRenderer->getExtraCacheKeys() );
1540 ksort( $extraKeys );
1541 return array_merge(
$params, array_values( $extraKeys ) );
1555 $this->mOldid = 123456789;
1556 $this->mNewid = 987654321;
1559 $params = $this->getDiffBodyCacheKeyParams();
1569 if ( array_slice(
$params, 0, count( $standardParams ) ) === $standardParams ) {
1587 $validatedOptions = [];
1588 if ( isset( $options[
'diff-type'] )
1589 && $this->getTextDiffer()->hasFormat( $options[
'diff-type'] )
1591 $validatedOptions[
'diff-type'] = $options[
'diff-type'];
1593 if ( !empty( $options[
'expand-url'] ) ) {
1594 $validatedOptions[
'expand-url'] =
true;
1596 if ( !empty( $options[
'inline-toggle'] ) ) {
1597 $validatedOptions[
'inline-toggle'] =
true;
1599 $this->slotDiffOptions = $validatedOptions;
1610 $this->extraQueryParams =
$params;
1630 && $this->isSlotDiffRenderer
1636 throw new LogicException( get_class( $this ) .
': could not maintain backwards compatibility. '
1637 .
'Please use a SlotDiffRenderer.' );
1639 return $slotDiffRenderer->getDiff( $old, $new ) . $this->getDebugString();
1655 $slotDiffRenderer = $this->contentHandlerFactory
1657 ->getSlotDiffRenderer( $this->
getContext() );
1661 throw new LogicException(
'The slot diff renderer for text content should be a '
1662 .
'TextSlotDiffRenderer subclass' );
1664 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
1674 $differenceEngine =
new self;
1675 $engine = $differenceEngine->getTextDiffer()->getEngineForFormat(
'table' );
1676 if ( $engine ===
'external' ) {
1677 return MediaWikiServices::getInstance()->getMainConfig()
1678 ->get( MainConfigNames::ExternalDiffEngine );
1692 protected function debug( $generator =
"internal" ) {
1693 if ( !$this->enableDebugComment ) {
1696 $data = [ $generator ];
1697 if ( $this->getConfig()->
get( MainConfigNames::ShowHostnames ) ) {
1702 return "<!-- diff generator: " .
1703 implode(
" ", array_map(
"htmlspecialchars", $data ) ) .
1710 private function getDebugString() {
1711 $engine = self::getEngine();
1712 if ( $engine ===
'wikidiff2' ) {
1713 return $this->debug(
'wikidiff2' );
1714 } elseif ( $engine ===
'php' ) {
1715 return $this->debug(
'native PHP' );
1717 return $this->debug(
"external $engine" );
1727 private function localiseDiff( $text ) {
1728 return $this->getTextDiffer()->localize( $this->getTextDiffFormat(), $text );
1740 return preg_replace_callback(
'/<!--LINE (\d+)-->/',
1742 if (
$matches[1] ===
'1' && $this->mReducedLineNumbers ) {
1745 return $this->msg(
'lineno' )->numParams(
$matches[1] )->escaped();
1757 !$this->mOldRevisionRecord || !$this->mNewRevisionRecord
1758 || !$this->mOldPage || !$this->mNewPage
1759 || !$this->mOldPage->equals( $this->mNewPage )
1760 || $this->mOldRevisionRecord->getId() ===
null
1761 || $this->mNewRevisionRecord->getId() ===
null
1763 || $this->mNewPage->getArticleID() !== $this->mOldRevisionRecord->getPageId()
1764 || $this->mNewPage->getArticleID() !== $this->mNewRevisionRecord->getPageId()
1769 if ( $this->mOldRevisionRecord->getTimestamp() > $this->mNewRevisionRecord->getTimestamp() ) {
1770 $oldRevRecord = $this->mNewRevisionRecord;
1771 $newRevRecord = $this->mOldRevisionRecord;
1773 $oldRevRecord = $this->mOldRevisionRecord;
1774 $newRevRecord = $this->mNewRevisionRecord;
1780 $revisionIdList = $this->revisionStore->getRevisionIdsBetween(
1781 $this->mNewPage->getArticleID(),
1787 if ( count( $revisionIdList ) > 0 ) {
1788 foreach ( $revisionIdList as $revisionId ) {
1789 $revision = $this->revisionStore->getRevisionById( $revisionId );
1790 if ( $revision->getUser( RevisionRecord::FOR_THIS_USER, $this->getAuthority() ) ) {
1795 if ( $nEdits > 0 && $nEdits <= 1000 ) {
1797 $newRevUserForGender =
'[HIDDEN]';
1800 $users = $this->revisionStore->getAuthorsBetween(
1801 $this->mNewPage->getArticleID(),
1807 $numUsers = count( $users );
1809 $newRevUser = $newRevRecord->getUser( RevisionRecord::RAW );
1810 $newRevUserText = $newRevUser ? $newRevUser->getName() :
'';
1811 $newRevUserSafe = $newRevRecord->getUser(
1812 RevisionRecord::FOR_THIS_USER,
1815 $newRevUserForGender = $newRevUserSafe ? $newRevUserSafe->getName() :
'[HIDDEN]';
1816 if ( $numUsers == 1 && $users[0]->getName() == $newRevUserText ) {
1819 }
catch ( InvalidArgumentException $e ) {
1823 return self::intermediateEditsMsg( $nEdits, $numUsers, $limit, $newRevUserForGender );
1840 if ( $numUsers === 0 ) {
1841 $msg =
'diff-multi-sameuser';
1843 ->numParams( $numEdits, $numUsers )
1844 ->params( $lastUser )
1846 } elseif ( $numUsers > $limit ) {
1847 $msg =
'diff-multi-manyusers';
1850 $msg =
'diff-multi-otherusers';
1853 return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1861 if ( !$revRecord->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1878 $lang = $this->getLanguage();
1881 $timestamp = $lang->userTimeAndDate( $revtimestamp, $user );
1882 $dateofrev = $lang->userDate( $revtimestamp, $user );
1883 $timeofrev = $lang->userTime( $revtimestamp, $user );
1886 $rev->
isCurrent() ?
'currentrev-asof' :
'revisionasof',
1892 if ( $complete !==
'complete' ) {
1898 if ( $this->userCanEdit( $rev ) ) {
1899 $header = $this->linkRenderer->makeKnownLink(
1903 [
'oldid' => $rev->
getId() ]
1905 $editQuery = [
'action' =>
'edit' ];
1907 $editQuery[
'oldid'] = $rev->
getId();
1910 $key = $this->
getAuthority()->probablyCan(
'edit', $rev->
getPage() ) ?
'editold' :
'viewsourceold';
1911 $msg = $this->msg( $key )->text();
1912 $editLink = $this->linkRenderer->makeKnownLink( $title, $msg, [], $editQuery );
1913 $header .=
' ' . Html::rawElement(
1915 [
'class' =>
'mw-diff-edit' ],
1923 $header .= Html::element(
'span',
1925 'class' =>
'mw-diff-timestamp',
1926 'data-timestamp' =>
wfTimestamp( TS_ISO_8601, $revtimestamp ),
1930 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1931 return Html::rawElement(
1933 [
'class' => Linker::getRevisionDeletedClass( $rev ) ],
1953 public function addHeader( $diff, $otitle, $ntitle, $multi =
'', $notice =
'' ) {
1956 $header = Html::openElement(
'table', [
1962 'diff-type-' . $this->getTextDiffFormat(),
1966 'diff-contentalign-' . $this->getDiffLang()->alignStart(),
1971 'diff-editfont-' . $this->userOptionsLookup->getOption(
1976 'data-mw' =>
'interface',
1978 $userLang = htmlspecialchars( $this->getLanguage()->getHtmlCode() );
1980 if ( !$diff && !$otitle ) {
1982 <tr class=\"diff-title\" lang=\"{$userLang}\">
1983 <td class=\"diff-ntitle\">{$ntitle}</td>
1989 <col class=\"diff-marker\" />
1990 <col class=\"diff-content\" />
1991 <col class=\"diff-marker\" />
1992 <col class=\"diff-content\" />";
1999 if ( $otitle || $ntitle ) {
2001 $deletedClass =
'diff-side-deleted';
2002 $addedClass =
'diff-side-added';
2004 <tr class=\"diff-title\" lang=\"{$userLang}\">
2005 <td colspan=\"$colspan\" class=\"diff-otitle {$deletedClass}\">{$otitle}</td>
2006 <td colspan=\"$colspan\" class=\"diff-ntitle {$addedClass}\">{$ntitle}</td>
2011 if ( $multi !=
'' ) {
2012 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2013 "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
2015 if ( $notice !=
'' ) {
2016 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2017 "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
2020 return $header . $diff .
"</table>";
2031 $this->mOldContent = $oldContent;
2032 $this->mNewContent = $newContent;
2034 $this->mTextLoaded = 2;
2035 $this->mRevisionsLoaded =
true;
2036 $this->isContentOverridden =
true;
2037 $this->slotDiffRenderers =
null;
2048 if ( $oldRevision ) {
2049 $this->mOldRevisionRecord = $oldRevision;
2050 $this->mOldid = $oldRevision->
getId();
2054 $this->mOldContent = $oldRevision->
getContent( SlotRecord::MAIN,
2055 RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
2056 if ( !$this->mOldContent ) {
2057 $this->addRevisionLoadError(
'old' );
2060 $this->mOldPage =
null;
2061 $this->mOldRevisionRecord = $this->mOldid =
false;
2063 $this->mNewRevisionRecord = $newRevision;
2064 $this->mNewid = $newRevision->
getId();
2066 $this->mNewContent = $newRevision->
getContent( SlotRecord::MAIN,
2067 RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
2068 if ( !$this->mNewContent ) {
2069 $this->addRevisionLoadError(
'new' );
2072 $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded =
true;
2073 $this->mTextLoaded = $oldRevision ? 2 : 1;
2074 $this->isContentOverridden =
false;
2075 $this->slotDiffRenderers =
null;
2085 $this->mDiffLang = $lang;
2101 if ( $new ===
'prev' ) {
2103 $newid = intval( $old );
2105 $newRev = $this->revisionStore->getRevisionById( $newid );
2107 $oldRev = $this->revisionStore->getPreviousRevision( $newRev );
2109 $oldid = $oldRev->getId();
2112 } elseif ( $new ===
'next' ) {
2114 $oldid = intval( $old );
2116 $oldRev = $this->revisionStore->getRevisionById( $oldid );
2118 $newRev = $this->revisionStore->getNextRevision( $oldRev );
2120 $newid = $newRev->getId();
2124 $oldid = intval( $old );
2125 $newid = intval( $new );
2129 return [ $oldid, $newid ];
2132 private function loadRevisionIds() {
2133 if ( $this->mRevisionsIdsLoaded ) {
2137 $this->mRevisionsIdsLoaded =
true;
2139 $old = $this->mOldid;
2140 $new = $this->mNewid;
2142 [ $this->mOldid, $this->mNewid ] = self::mapDiffPrevNext( $old, $new );
2143 if ( $new ===
'next' && $this->mNewid ===
false ) {
2144 # if no result, NewId points to the newest old revision. The only newer
2145 # revision is cur, which is "0".
2149 $this->hookRunner->onNewDifferenceEngine(
2151 $this->
getTitle(), $this->mOldid, $this->mNewid, $old, $new );
2168 if ( $this->mRevisionsLoaded ) {
2169 return $this->isContentOverridden ||
2170 ( $this->mOldRevisionRecord !==
null && $this->mNewRevisionRecord !== null );
2174 $this->mRevisionsLoaded =
true;
2176 $this->loadRevisionIds();
2179 if ( $this->mNewid ) {
2180 $this->mNewRevisionRecord = $this->revisionStore->getRevisionById( $this->mNewid );
2182 $this->mNewRevisionRecord = $this->revisionStore->getRevisionByTitle( $this->
getTitle() );
2190 $this->mNewid = $this->mNewRevisionRecord->getId();
2191 $this->mNewPage = $this->mNewid ?
2192 Title::newFromLinkTarget( $this->mNewRevisionRecord->getPageAsLinkTarget() ) :
2196 $this->mOldRevisionRecord =
false;
2197 if ( $this->mOldid ) {
2198 $this->mOldRevisionRecord = $this->revisionStore->getRevisionById( $this->mOldid );
2199 } elseif ( $this->mOldid === 0 ) {
2200 $revRecord = $this->revisionStore->getPreviousRevision( $this->mNewRevisionRecord );
2202 $this->mOldid = $revRecord ? $revRecord->
getId() :
false;
2203 $this->mOldRevisionRecord = $revRecord ??
false;
2206 if ( $this->mOldRevisionRecord ===
null ) {
2210 if ( $this->mOldRevisionRecord && $this->mOldRevisionRecord->getId() ) {
2211 $this->mOldPage = Title::newFromLinkTarget(
2212 $this->mOldRevisionRecord->getPageAsLinkTarget()
2215 $this->mOldPage =
null;
2219 $dbr = $this->dbProvider->getReplicaDatabase();
2220 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
2221 if ( $this->mOldid !==
false ) {
2222 $tagIds = $dbr->newSelectQueryBuilder()
2223 ->select(
'ct_tag_id' )
2224 ->from(
'change_tag' )
2225 ->where( [
'ct_rev_id' => $this->mOldid ] )
2226 ->caller( __METHOD__ )->fetchFieldValues();
2228 foreach ( $tagIds as $tagId ) {
2230 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2235 $this->mOldTags = implode(
',', $tags );
2237 $this->mOldTags =
false;
2240 $tagIds = $dbr->newSelectQueryBuilder()
2241 ->select(
'ct_tag_id' )
2242 ->from(
'change_tag' )
2243 ->where( [
'ct_rev_id' => $this->mNewid ] )
2244 ->caller( __METHOD__ )->fetchFieldValues();
2246 foreach ( $tagIds as $tagId ) {
2248 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2253 $this->mNewTags = implode(
',', $tags );
2267 if ( $this->mTextLoaded == 2 ) {
2268 return $this->loadRevisionData() &&
2269 ( $this->mOldRevisionRecord ===
false || $this->mOldContent )
2270 && $this->mNewContent;
2274 $this->mTextLoaded = 2;
2276 if ( !$this->loadRevisionData() ) {
2280 if ( $this->mOldRevisionRecord ) {
2281 $this->mOldContent = $this->mOldRevisionRecord->getContent(
2283 RevisionRecord::FOR_THIS_USER,
2286 if ( $this->mOldContent ===
null ) {
2291 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2293 RevisionRecord::FOR_THIS_USER,
2296 $this->hookRunner->onDifferenceEngineLoadTextAfterNewContentIsLoaded( $this );
2297 if ( $this->mNewContent ===
null ) {
2310 if ( $this->mTextLoaded >= 1 ) {
2311 return $this->loadRevisionData();
2314 $this->mTextLoaded = 1;
2316 if ( !$this->loadRevisionData() ) {
2320 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2322 RevisionRecord::FOR_THIS_USER,
2326 $this->hookRunner->onDifferenceEngineAfterLoadNewText( $this );
2337 if ( $this->textDiffer ===
null ) {
2340 $this->getDiffLang(),
2341 $this->getConfig()->
get( MainConfigNames::DiffEngine ),
2342 $this->getConfig()->
get( MainConfigNames::ExternalDiffEngine ),
2343 $this->getConfig()->
get( MainConfigNames::Wikidiff2Options )
2346 return $this->textDiffer;
2356 return $this->getTextDiffer()->getFormats();
2366 return $this->slotDiffOptions[
'diff-type'] ??
'table';
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfHostname()
Get host name of the current machine, for use in error reporting.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
array $params
The job parameters.
B/C adapter for turning a DifferenceEngine into a SlotDiffRenderer.
DifferenceEngine is responsible for rendering the difference between two revisions as HTML.
bool $enableDebugComment
Set this to true to add debug info to the HTML output.
bool $unhide
Show rev_deleted content if allowed.
bool $isContentOverridden
Was the content overridden via setContent()? If the content was overridden, most internal state (e....
getExtraCacheKeys()
Implements DifferenceEngineSlotDiffRenderer::getExtraCacheKeys().
markAsSlotDiffRenderer()
Mark this DifferenceEngine as a slot renderer (as opposed to a page renderer).
getSlotHeader( $headerText)
Get a slot header for inclusion in a diff body (as a table row).
setSlotDiffOptions( $options)
hasDeletedRevision()
Checks whether one of the given Revisions was deleted.
int $mTextLoaded
How many text blobs have been loaded, 0, 1 or 2?
deletedIdMarker( $id)
Build a wikitext link toward a deleted revision, if viewable.
SlotDiffRenderer[] null $slotDiffRenderers
DifferenceEngine classes for the slots, keyed by role name.
getDiffBodyForRole( $role)
Get the diff table body for one slot, without header.
getTextDiffer()
Get the TextDiffer which will be used for rendering text.
getDefaultLanguage()
Get the language to use if none has been set by setTextLanguage().
getOldid()
Get the ID of old revision (left pane) of the diff.
setRevisions(?RevisionRecord $oldRevision, RevisionRecord $newRevision)
Use specified text instead of loading from the database.
bool $isSlotDiffRenderer
Temporary hack for B/C while slot diff related methods of DifferenceEngine are being deprecated.
generateTextDiffBody( $otext, $ntext)
Generate a diff, no caching.
loadNewText()
Load the text of the new revision, not the old one.
showDiffPage( $diffOnly=false)
loadText()
Load the text of the revisions, as well as revision data.
int string false null $mNewid
Revision ID for the new revision.
mapDiffPrevNext( $old, $new)
Maps a revision pair definition as accepted by DifferenceEngine constructor to a pair of actual integ...
getPermissionErrors(Authority $performer)
Get the permission errors associated with the revisions for the current diff.
getDiffBody()
Get the diff table body, without header.
getTitle()
1.18 to override Title|null
loadRevisionData()
Load revision metadata for the specified revisions.
static getEngine()
Process DiffEngine config and get a sensible, usable engine.
bool $mRevisionsLoaded
Have the revisions been loaded.
getNewRevision()
Get the right side of the diff.
showDiff( $otitle, $ntitle, $notice='')
Get the diff text, send it to the OutputPage object Returns false if the diff could not be generated,...
getTextDiffFormat()
Get the selected text diff format.
localiseLineNumbers( $text)
Replace a common convention for language-independent line numbers with the text in the user's languag...
getSlotContents()
Get the old and new content objects for all slots.
string $mMarkPatrolledLink
Link to action=markpatrolled.
deletedLink( $id)
Look up a special:Undelete link to the given deleted revision id, as a workaround for being unable to...
bool $mReducedLineNumbers
If true, line X is not displayed when X is 1, for example to increase readability and conserve space ...
__construct( $context=null, $old=0, $new=0, $rcid=0, $refreshCache=false, $unhide=false)
Title null $mNewPage
Title of new revision or null if the new revision does not exist or does not belong to a page.
bool $mCacheHit
Was the diff fetched from cache?
getMultiNotice()
If there are revisions between the ones being compared, return a note saying so.
isUserAllowedToSeeRevisions(Authority $performer)
Checks whether the current user has permission for accessing the revisions of the diff.
int false null $mOldid
Revision ID for the old revision.
debug( $generator="internal")
Generate a debug comment indicating diff generating time, server node, and generator backend.
addHeader( $diff, $otitle, $ntitle, $multi='', $notice='')
Add the header to a diff body.
bool $mRefreshCache
Refresh the diff cache.
LinkRenderer $linkRenderer
getDiffBodyCacheKeyParams()
Get the cache key parameters.
getDiff( $otitle, $ntitle, $notice='')
Get complete diff table, including header.
setExtraQueryParams( $params)
Set query parameters to append to diff page links.
static intermediateEditsMsg( $numEdits, $numUsers, $limit, $lastUser='[HIDDEN]')
Get a notice about how many intermediate edits and users there are.
getNewid()
Get the ID of new revision (right pane) of the diff.
renderNewRevision()
Show the new revision of the page.
setContent(Content $oldContent, Content $newContent)
Use specified text instead of loading from the database.
setTextLanguage(Language $lang)
Set the language in which the diff text is written.
generateContentDiffBody(Content $old, Content $new)
Generate a diff, no caching.
getSlotError( $errorText)
Get an error message for inclusion in a diff body (as a table row).
shouldBeHiddenFromUser(Authority $performer)
Checks whether the diff should be hidden from the current user This is based on whether the user is a...
getRevisionHeader(RevisionRecord $rev, $complete='')
Get a header for a specified revision.
getMarkPatrolledLinkInfo()
Returns an array of meta data needed to build a "mark as patrolled" link and adds a JS module to the ...
setReducedLineNumbers( $value=true)
Set reduced line numbers mode.
getSupportedFormats()
Get the list of supported text diff formats.
Title null $mOldPage
Title of old revision or null if the old revision does not exist or does not belong to a page.
getDiffLang()
Get the language in which the diff text is written.
showDiffStyle()
Add style sheets for diff display.
markPatrolledLink()
Build a link to mark a change as patrolled.
getRevisionLoadErrors()
If errors were encountered while loading the revision contents, this will return an array of Messages...
hasSuppressedRevision()
Checks whether one of the given Revisions was suppressed.
getOldRevision()
Get the left side of the diff.
Exception thrown when trying to render a diff between two content types which cannot be compared (thi...
Base class for language-specific code.
getMessageObject()
Return a Message object for this exception.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
setContext(IContextSource $context)
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
A TextDiffer which acts as a container for other TextDiffers, and dispatches requests to them.
A class containing constants representing the names of configuration variables.
Service for getting rendered output of a given page.
Service for creating WikiPage objects.
Parent class for all special pages.
Show an error when a user tries to do something they do not have the necessary permissions for.
Renders a diff for a single slot (that is, a diff between two content objects).
Renders a slot diff by doing a text diff on the native representation.
Base interface for representing page content.
getContentHandler()
Convenience method that returns the ContentHandler singleton for handling the content model that this...
Interface for objects which can provide a MediaWiki context on request.