28use MediaWiki\Debug\DeprecationHelper;
81 use DeprecationHelper;
89 private const DIFF_VERSION =
'1.41';
117 private $mOldRevisionRecord;
127 private $mNewRevisionRecord;
158 private $mOldContent;
165 private $mNewContent;
171 private $mRevisionsIdsLoaded =
false;
174 protected $mRevisionsLoaded =
false;
177 protected $mTextLoaded = 0;
187 protected $isContentOverridden =
false;
190 protected $mCacheHit =
false;
193 private $cacheHitKey =
null;
201 public $enableDebugComment =
false;
206 protected $mReducedLineNumbers =
false;
209 protected $mMarkPatrolledLink =
null;
212 protected $unhide =
false;
215 protected $mRefreshCache =
false;
226 protected $isSlotDiffRenderer =
false;
232 private $slotDiffOptions = [];
237 private $extraQueryParams = [];
256 private $revisionLoadErrors = [];
266 public function __construct( $context =
null, $old = 0, $new = 0, $rcid = 0,
267 $refreshCache =
false, $unhide =
false
273 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'" );
275 $this->mOldid = $old;
276 $this->mNewid = $new;
277 $this->mRefreshCache = $refreshCache;
278 $this->unhide = $unhide;
280 $services = MediaWikiServices::getInstance();
281 $this->linkRenderer = $services->getLinkRenderer();
282 $this->contentHandlerFactory = $services->getContentHandlerFactory();
283 $this->revisionStore = $services->getRevisionStore();
284 $this->archivedRevisionLookup = $services->getArchivedRevisionLookup();
285 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
286 $this->wikiPageFactory = $services->getWikiPageFactory();
287 $this->userOptionsLookup = $services->getUserOptionsLookup();
288 $this->commentFormatter = $services->getCommentFormatter();
289 $this->dbProvider = $services->getConnectionProvider();
290 $this->userGroupManager = $services->getUserGroupManager();
291 $this->userEditTracker = $services->getUserEditTracker();
292 $this->userIdentityUtils = $services->getUserIdentityUtils();
301 if ( $this->isSlotDiffRenderer ) {
302 throw new LogicException( __METHOD__ .
' called in slot diff renderer mode' );
305 if ( $this->slotDiffRenderers ===
null ) {
311 $this->slotDiffRenderers = [];
312 foreach ( $slotContents as $role => $contents ) {
313 if ( $contents[
'new'] && $contents[
'old']
314 && $contents[
'new']->equals( $contents[
'old'] )
319 $handler = ( $contents[
'new'] ?: $contents[
'old'] )->getContentHandler();
320 $this->slotDiffRenderers[$role] = $handler->getSlotDiffRenderer(
322 $this->slotDiffOptions + [
323 'contentLanguage' => $this->
getDiffLang()->getCode(),
330 return $this->slotDiffRenderers;
340 $this->isSlotDiffRenderer =
true;
349 if ( $this->isContentOverridden ) {
351 SlotRecord::MAIN => [
'old' => $this->mOldContent,
'new' => $this->mNewContent ]
357 $newSlots = $this->mNewRevisionRecord->getPrimarySlots()->getSlots();
358 $oldSlots = $this->mOldRevisionRecord ?
359 $this->mOldRevisionRecord->getPrimarySlots()->getSlots() :
365 $roles = array_keys( array_merge( $newSlots, $oldSlots ) );
368 foreach ( $roles as $role ) {
370 'old' => $this->loadSingleSlot(
371 $oldSlots[$role] ??
null,
374 'new' => $this->loadSingleSlot(
375 $newSlots[$role] ??
null,
381 if ( isset( $slots[SlotRecord::MAIN] ) ) {
382 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
394 private function loadSingleSlot( ?
SlotRecord $slot,
string $which ) {
401 $this->addRevisionLoadError( $which );
411 private function addRevisionLoadError( $which ) {
412 $this->revisionLoadErrors[] = $this->
msg( $which ===
'new'
413 ?
'difference-bad-new-revision' :
'difference-bad-old-revision'
424 return $this->revisionLoadErrors;
431 private function hasNewRevisionLoadError() {
432 foreach ( $this->revisionLoadErrors as $error ) {
433 if ( $error->getKey() ===
'difference-bad-new-revision' ) {
443 return parent::getTitle() ?: Title::makeTitle(
NS_SPECIAL,
'BadTitle/DifferenceEngine' );
453 $this->mReducedLineNumbers = $value;
462 # Default language in which the diff text is written.
464 return $this->mDiffLang;
474 return $this->
getTitle()->getPageLanguage();
481 return $this->mCacheHit;
492 $this->loadRevisionIds();
494 return $this->mOldid;
504 $this->loadRevisionIds();
506 return $this->mNewid;
516 return $this->mOldRevisionRecord ?:
null;
525 return $this->mNewRevisionRecord;
537 if ( $this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
538 $revRecord = $this->archivedRevisionLookup->getArchivedRevisionRecord(
null, $id );
540 $title = Title::newFromPageIdentity( $revRecord->
getPage() );
542 return SpecialPage::getTitleFor(
'Undelete' )->getFullURL( [
543 'target' => $title->getPrefixedText(),
562 return "[$link $id]";
568 private function showMissingRevision() {
569 $out = $this->getOutput();
572 if ( $this->mOldid && ( !$this->mOldRevisionRecord || !$this->mOldContent ) ) {
573 $missing[] = $this->deletedIdMarker( $this->mOldid );
575 if ( !$this->mNewRevisionRecord || !$this->mNewContent ) {
579 $out->setPageTitleMsg( $this->
msg(
'errorpagetitle' ) );
580 $msg = $this->
msg(
'difference-missing-revision' )
581 ->params( $this->
getLanguage()->listToText( $missing ) )
582 ->numParams( count( $missing ) )
584 $out->addHTML( $msg );
595 $this->mNewRevisionRecord &&
596 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
599 $this->mOldRevisionRecord &&
600 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
612 $permStatus = PermissionStatus::newEmpty();
613 if ( $this->mNewPage ) {
614 $performer->
authorizeRead(
'read', $this->mNewPage, $permStatus );
616 if ( $this->mOldPage ) {
617 $performer->
authorizeRead(
'read', $this->mOldPage, $permStatus );
619 return $permStatus->toLegacyErrorArray();
629 ( $this->mOldRevisionRecord &&
630 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) ||
631 ( $this->mNewRevisionRecord &&
632 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) )
642 private function getUserEditCount( $user ): string {
643 $editCount = $this->userEditTracker->getUserEditCount( $user );
644 if ( $editCount ===
null ) {
648 return Html::rawElement(
'div', [
649 'class' =>
'mw-diff-usereditcount',
665 if ( !$this->userIdentityUtils->isNamed( $user ) ) {
668 $userGroups = $this->userGroupManager->getUserGroups( $user );
669 $userGroupLinks = [];
670 foreach ( $userGroups as $group ) {
671 $userGroupLinks[] = UserGroupMembership::getLinkHTML( $group, $this->
getContext() );
673 return Html::rawElement(
'div', [
674 'class' =>
'mw-diff-userroles',
675 ], $this->getLanguage()->commaList( $userGroupLinks ) );
684 private function getUserMetaData( ?
UserIdentity $user ) {
688 return Html::rawElement(
'div', [
689 'class' =>
'mw-diff-usermetadata',
690 ], $this->getUserRoles( $user ) . $this->getUserEditCount( $user ) );
705 $this->loadRevisionData();
707 if ( $this->mOldRevisionRecord && !$this->mOldRevisionRecord->userCan(
708 RevisionRecord::DELETED_TEXT,
716 return !$this->mNewRevisionRecord || $this->mNewRevisionRecord->userCan(
717 RevisionRecord::DELETED_TEXT,
730 return $this->hasDeletedRevision() && ( !$this->unhide ||
731 !$this->isUserAllowedToSeeRevisions( $performer ) );
738 # Allow frames except in certain special cases
739 $out = $this->getOutput();
740 $out->setPreventClickjacking(
false );
741 $out->setRobotPolicy(
'noindex,nofollow' );
744 $this->hookRunner->onDifferenceEngineShowDiffPage( $out );
746 if ( !$this->loadRevisionData() ) {
747 if ( $this->hookRunner->onDifferenceEngineShowDiffPageMaybeShowMissingRevision( $this ) ) {
748 $this->showMissingRevision();
754 $permErrors = $this->getPermissionErrors( $this->
getAuthority() );
761 $query = $this->extraQueryParams;
762 # Carry over 'diffonly' param via navigation links
763 if ( $diffOnly != MediaWikiServices::getInstance()
764 ->getUserOptionsLookup()->getBoolOption( $user,
'diffonly' )
766 $query[
'diffonly'] = $diffOnly;
768 # Cascade unhide param in links for easy deletion browsing
769 if ( $this->unhide ) {
770 $query[
'unhide'] = 1;
773 # Check if one of the revisions is deleted/suppressed
774 $deleted = $this->hasDeletedRevision();
775 $suppressed = $this->hasSuppressedRevision();
776 $allowed = $this->isUserAllowedToSeeRevisions( $this->
getAuthority() );
781 # mOldRevisionRecord is false if the difference engine is called with a "vague" query for
782 # a diff between a version V and its previous version V' AND the version V
783 # is the first version of that article. In that case, V' does not exist.
784 if ( $this->mOldRevisionRecord ===
false ) {
785 if ( $this->mNewPage ) {
786 $out->setPageTitleMsg(
787 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
793 $this->hookRunner->onDifferenceEngineOldHeaderNoOldRev( $oldHeader );
795 $this->hookRunner->onDifferenceEngineViewHeader( $this );
797 if ( !$this->mOldPage || !$this->mNewPage ) {
800 } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
801 $out->setPageTitleMsg(
802 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
806 $out->setPageTitleMsg( $this->msg(
'difference-title-multipage' )->plaintextParams(
807 $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
808 $out->addSubtitle( $this->msg(
'difference-multipage' ) );
812 if ( $samePage && $this->mNewPage &&
813 $this->
getAuthority()->probablyCan(
'edit', $this->mNewPage )
815 if ( $this->mNewRevisionRecord->isCurrent() &&
816 $this->getAuthority()->probablyCan(
'rollback', $this->mNewPage )
818 $rollbackLink = Linker::generateRollback(
819 $this->mNewRevisionRecord,
823 if ( $rollbackLink ) {
824 $out->setPreventClickjacking(
true );
825 $rollback =
"\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
829 if ( $this->userCanEdit( $this->mOldRevisionRecord ) &&
830 $this->userCanEdit( $this->mNewRevisionRecord )
832 $undoLink = $this->linkRenderer->makeKnownLink(
834 $this->msg(
'editundo' )->text(),
835 [
'title' => Linker::titleAttrib(
'undo' ) ],
838 'undoafter' => $this->mOldid,
839 'undo' => $this->mNewid
842 $revisionTools[
'mw-diff-undo'] = $undoLink;
845 # Make "previous revision link"
846 $hasPrevious = $samePage && $this->mOldPage &&
847 $this->revisionStore->getPreviousRevision( $this->mOldRevisionRecord );
848 if ( $hasPrevious ) {
849 $prevlinkQuery = [
'diff' =>
'prev',
'oldid' => $this->mOldid ] + $query;
850 $prevlink = $this->linkRenderer->makeKnownLink(
852 $this->msg(
'previousdiff' )->text(),
853 [
'id' =>
'differences-prevlink' ],
856 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
858 $this->msg(
'previousdiff' )->text(),
860 'class' =>
'mw-diff-revision-history-link-previous'
865 $prevlink =
"\u{00A0}";
868 if ( $this->mOldRevisionRecord->isMinor() ) {
869 $oldminor = ChangesList::flag(
'minor' );
874 $oldRevRecord = $this->mOldRevisionRecord;
876 $ldel = $this->revisionDeleteLink( $oldRevRecord );
877 $oldRevisionHeader = $this->getRevisionHeader( $oldRevRecord,
'complete' );
879 $oldRevComment = $this->commentFormatter
881 $oldRevRecord, $user, !$diffOnly, !$this->unhide,
false
884 if ( $oldRevComment ===
'' ) {
885 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
886 $oldRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
889 $oldHeader =
'<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader .
'</strong></div>' .
890 '<div id="mw-diff-otitle2">' .
891 Linker::revUserTools( $oldRevRecord, !$this->unhide ) .
892 $this->getUserMetaData( $oldRevRecord->getUser() ) .
894 '<div id="mw-diff-otitle3">' . $oldminor . $oldRevComment . $ldel .
'</div>' .
895 '<div id="mw-diff-otitle5">' . $oldChangeTags[0] .
'</div>' .
896 '<div id="mw-diff-otitle4">' . $prevlink .
'</div>';
899 $this->hookRunner->onDifferenceEngineOldHeader(
900 $this, $oldHeader, $prevlink, $oldminor, $diffOnly, $ldel, $this->unhide );
903 $out->addJsConfigVars( [
904 'wgDiffOldId' => $this->mOldid,
905 'wgDiffNewId' => $this->mNewid,
908 # Make "next revision link"
909 # Skip next link on the top revision
910 if ( $samePage && $this->mNewPage && !$this->mNewRevisionRecord->isCurrent() ) {
911 $nextlinkQuery = [
'diff' =>
'next',
'oldid' => $this->mNewid ] + $query;
912 $nextlink = $this->linkRenderer->makeKnownLink(
914 $this->msg(
'nextdiff' )->text(),
915 [
'id' =>
'differences-nextlink' ],
918 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
920 $this->msg(
'nextdiff' )->text(),
922 'class' =>
'mw-diff-revision-history-link-next'
927 $nextlink =
"\u{00A0}";
930 if ( $this->mNewRevisionRecord->isMinor() ) {
931 $newminor = ChangesList::flag(
'minor' );
936 # Handle RevisionDelete links...
937 $rdel = $this->revisionDeleteLink( $this->mNewRevisionRecord );
939 # Allow extensions to define their own revision tools
940 $this->hookRunner->onDiffTools(
941 $this->mNewRevisionRecord,
943 $this->mOldRevisionRecord ?:
null,
947 $formattedRevisionTools = [];
949 foreach ( $revisionTools as $key => $tool ) {
950 $toolClass = is_string( $key ) ? $key :
'mw-diff-tool';
951 $element = Html::rawElement(
953 [
'class' => $toolClass ],
956 $formattedRevisionTools[] = $element;
959 $newRevRecord = $this->mNewRevisionRecord;
961 $newRevisionHeader = $this->getRevisionHeader( $newRevRecord,
'complete' ) .
962 ' ' . implode(
' ', $formattedRevisionTools );
964 $newRevComment = $this->commentFormatter->formatRevision(
965 $newRevRecord, $user, !$diffOnly, !$this->unhide,
false
968 if ( $newRevComment ===
'' ) {
969 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
970 $newRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
973 $newHeader =
'<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader .
'</strong></div>' .
974 '<div id="mw-diff-ntitle2">' . Linker::revUserTools( $newRevRecord, !$this->unhide ) .
976 $this->getUserMetaData( $newRevRecord->getUser() ) .
978 '<div id="mw-diff-ntitle3">' . $newminor . $newRevComment . $rdel .
'</div>' .
979 '<div id="mw-diff-ntitle5">' . $newChangeTags[0] .
'</div>' .
980 '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() .
'</div>';
983 $this->hookRunner->onDifferenceEngineNewHeader( $this, $newHeader,
984 $formattedRevisionTools, $nextlink, $rollback, $newminor, $diffOnly,
985 $rdel, $this->unhide );
988 Html::rawElement(
'div', [
989 'class' =>
'mw-diff-revision-history-links'
992 # If the diff cannot be shown due to a deleted revision, then output
993 # the diff header and links to unhide (if available)...
994 if ( $this->shouldBeHiddenFromUser( $this->
getAuthority() ) ) {
995 $this->showDiffStyle();
996 $multi = $this->getMultiNotice();
997 $out->addHTML( $this->addHeader(
'', $oldHeader, $newHeader, $multi ) );
999 # Give explanation for why revision is not visible
1000 $msg = [ $suppressed ?
'rev-suppressed-no-diff' :
'rev-deleted-no-diff' ];
1002 # Give explanation and add a link to view the diff...
1003 $query = $this->
getRequest()->appendQueryValue(
'unhide',
'1' );
1005 $suppressed ?
'rev-suppressed-unhide-diff' :
'rev-deleted-unhide-diff',
1006 $this->
getTitle()->getFullURL( $query )
1009 $out->addHTML( Html::warningBox( $this->msg( ...$msg )->parse(),
'plainlinks' ) );
1010 # Otherwise, output a regular diff...
1012 # Add deletion notice if the user is viewing deleted content
1015 $msg = $suppressed ?
'rev-suppressed-diff-view' :
'rev-deleted-diff-view';
1016 $notice = Html::warningBox( $this->msg( $msg )->parse(),
'plainlinks' );
1019 # Add an error if the content can't be loaded
1020 $this->getSlotContents();
1021 foreach ( $this->getRevisionLoadErrors() as $msg ) {
1022 $notice .= Html::warningBox( $msg->parse() );
1026 if ( $this->getTextDiffer()->hasFormat(
'inline' ) ) {
1030 $this->showTablePrefixes();
1031 $this->showDiff( $oldHeader, $newHeader, $notice );
1033 $this->renderNewRevision();
1037 if ( $this->hookRunner->onDifferenceEngineRenderRevisionShowFinalPatrolLink() ) {
1038 # Add redundant patrol link on bottom...
1039 $out->addHTML( $this->markPatrolledLink() );
1047 private function showTablePrefixes() {
1049 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1050 $parts += $slotDiffRenderer->getTablePrefix( $this->
getContext(), $this->mNewPage );
1053 if ( count( array_filter( $parts ) ) > 0 ) {
1054 $language = $this->getLanguage();
1056 'class' =>
'mw-diff-table-prefix',
1057 'dir' => $language->getDir(),
1058 'lang' => $language->getCode(),
1060 $this->getOutput()->addHTML(
1061 Html::rawElement(
'div', $attrs, implode(
'', $parts ) ) );
1076 if ( $this->mMarkPatrolledLink ===
null ) {
1077 $linkInfo = $this->getMarkPatrolledLinkInfo();
1079 if ( !$linkInfo || !$this->mNewPage ) {
1080 $this->mMarkPatrolledLink =
'';
1082 $patrolLinkClass =
'patrollink';
1083 $this->mMarkPatrolledLink =
' <span class="' . $patrolLinkClass .
'" data-mw="interface">[' .
1084 $this->linkRenderer->makeKnownLink(
1086 $this->msg(
'markaspatrolleddiff' )->text(),
1089 'action' =>
'markpatrolled',
1090 'rcid' => $linkInfo[
'rcid'],
1094 $this->hookRunner->onDifferenceEngineMarkPatrolledLink( $this,
1095 $this->mMarkPatrolledLink, $linkInfo[
'rcid'] );
1098 return $this->mMarkPatrolledLink;
1110 $config = $this->getConfig();
1115 $config->get( MainConfigNames::UseRCPatrol ) &&
1117 $this->getAuthority()->probablyCan(
'patrol', $this->mNewPage ) &&
1120 RecentChange::isInRCLifespan( $this->mNewRevisionRecord->getTimestamp(), 21600 )
1123 $change = RecentChange::newFromConds(
1125 'rc_this_oldid' => $this->mNewid,
1126 'rc_patrolled' => RecentChange::PRC_UNPATROLLED
1131 if ( $change && !$change->getPerformerIdentity()->equals( $user ) ) {
1132 $rcid = $change->getAttribute(
'rc_id' );
1143 $this->hookRunner->onDifferenceEngineMarkPatrolledRCID( $rcid, $this, $change, $user );
1147 $this->getOutput()->setPreventClickjacking(
true );
1148 if ( $this->
getAuthority()->isAllowed(
'writeapi' ) ) {
1149 $this->getOutput()->addModules(
'mediawiki.misc-authed-curate' );
1152 return [
'rcid' => $rcid ];
1165 private function revisionDeleteLink(
RevisionRecord $revRecord ) {
1166 $link = Linker::getRevDeleteLink(
1171 if ( $link !==
'' ) {
1172 $link =
"\u{00A0}\u{00A0}\u{00A0}" . $link .
' ';
1184 if ( $this->isContentOverridden ) {
1188 throw new LogicException(
1190 .
' is not supported after calling setContent(). Use setRevisions() instead.'
1194 $out = $this->getOutput();
1195 $revHeader = $this->getRevisionHeader( $this->mNewRevisionRecord );
1196 # Add "current version as of X" title
1197 $out->addHTML(
"<hr class='diff-hr' id='mw-oldid' />
1198 <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
1199 # Page content may be handled by a hooked call instead...
1200 if ( $this->hookRunner->onArticleContentOnDiff( $this, $out ) ) {
1201 $this->loadNewText();
1202 if ( !$this->mNewPage ) {
1208 if ( $this->hasNewRevisionLoadError() ) {
1213 $out->setRevisionId( $this->mNewid );
1214 $out->setRevisionIsCurrent( $this->mNewRevisionRecord->isCurrent() );
1215 $out->setRevisionTimestamp( $this->mNewRevisionRecord->getTimestamp() );
1216 $out->setArticleFlag(
true );
1218 if ( !$this->hookRunner->onArticleRevisionViewCustom(
1219 $this->mNewRevisionRecord, $this->mNewPage, $this->mOldid, $out )
1225 if ( $this->
getTitle()->equals( $this->mNewPage ) ) {
1229 $wikiPage = $this->getWikiPage();
1232 $wikiPage = $this->wikiPageFactory->newFromTitle( $this->mNewPage );
1235 $parserOptions = $wikiPage->makeParserOptions( $this->
getContext() );
1236 $parserOptions->setRenderReason(
'diff-page' );
1238 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
1239 $status = $parserOutputAccess->getParserOutput(
1242 $this->mNewRevisionRecord,
1244 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK |
1246 ParserOutputAccess::OPT_LINKS_UPDATE
1248 if ( $status->isOK() ) {
1249 $parserOutput = $status->getValue();
1251 if ( $this->hookRunner->onDifferenceEngineRenderRevisionAddParserOutput(
1252 $this, $out, $parserOutput, $wikiPage )
1254 $out->addParserOutput( $parserOutput, [
1255 'enableSectionEditLinks' => $this->mNewRevisionRecord->isCurrent()
1256 && $this->getAuthority()->probablyCan(
1258 $this->mNewRevisionRecord->getPage()
1260 'absoluteURLs' => $this->slotDiffOptions[
'expand-url'] ??
false
1266 $out->parseAsInterface(
1267 $status->getWikiText(
false,
false, $this->getLanguage() )
1286 public function showDiff( $otitle, $ntitle, $notice =
'' ) {
1288 $this->hookRunner->onDifferenceEngineShowDiff( $this );
1290 $diff = $this->getDiff( $otitle, $ntitle, $notice );
1291 if ( $diff ===
false ) {
1292 $this->showMissingRevision();
1296 $this->showDiffStyle();
1297 if ( $this->slotDiffOptions[
'expand-url'] ??
false ) {
1298 $diff = Linker::expandLocalLinks( $diff );
1300 $this->getOutput()->addHTML( $diff );
1308 if ( !$this->isSlotDiffRenderer ) {
1309 $this->getOutput()->addModules(
'mediawiki.diff' );
1310 $this->getOutput()->addModuleStyles( [
1311 'mediawiki.interface.helpers.styles',
1312 'mediawiki.diff.styles'
1314 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1315 $slotDiffRenderer->addModules( $this->getOutput() );
1329 public function getDiff( $otitle, $ntitle, $notice =
'' ) {
1330 $body = $this->getDiffBody();
1331 if ( $body ===
false ) {
1335 $multi = $this->getMultiNotice();
1337 if ( $body ===
'' ) {
1338 $notice .=
'<div class="mw-diff-empty">' .
1339 $this->msg(
'diff-empty' )->parse() .
1343 if ( $this->cacheHitKey !==
null ) {
1344 $body .=
"\n<!-- diff cache key " . htmlspecialchars( $this->cacheHitKey ) .
" -->\n";
1347 return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
1350 private function incrementStats(
string $cacheStatus ): void {
1352 $stats->getCounter(
'diff_cache_total' )
1353 ->setLabel(
'status', $cacheStatus )
1354 ->copyToStatsdAt(
'diff_cache.' . $cacheStatus )
1364 $this->mCacheHit =
true;
1366 if ( !$this->isContentOverridden ) {
1367 if ( !$this->loadRevisionData() ) {
1369 } elseif ( $this->mOldRevisionRecord &&
1370 !$this->mOldRevisionRecord->userCan(
1371 RevisionRecord::DELETED_TEXT,
1372 $this->getAuthority()
1376 } elseif ( $this->mNewRevisionRecord &&
1377 !$this->mNewRevisionRecord->userCan(
1378 RevisionRecord::DELETED_TEXT,
1379 $this->getAuthority()
1384 if ( $this->mOldRevisionRecord ===
false || (
1385 $this->mOldRevisionRecord &&
1386 $this->mNewRevisionRecord &&
1387 $this->mOldRevisionRecord->getId() &&
1388 $this->mOldRevisionRecord->getId() == $this->mNewRevisionRecord->getId()
1390 if ( $this->hookRunner->onDifferenceEngineShowEmptyOldContent( $this ) ) {
1398 $services = MediaWikiServices::getInstance();
1399 $cache = $services->getMainWANObjectCache();
1400 $stats = $services->getStatsdDataFactory();
1401 if ( $this->mOldid && $this->mNewid ) {
1402 $key = $cache->makeKey( ...$this->getDiffBodyCacheKeyParams() );
1405 if ( !$this->mRefreshCache ) {
1406 $difftext = $cache->get( $key );
1407 if ( is_string( $difftext ) ) {
1408 $this->incrementStats(
'hit' );
1409 $difftext = $this->localiseDiff( $difftext );
1410 $this->cacheHitKey = $key;
1415 $this->mCacheHit =
false;
1416 $this->cacheHitKey =
null;
1419 if ( !$this->loadText() ) {
1426 $slotContents = $this->getSlotContents();
1427 foreach ( $this->getSlotDiffRenderers() as $role => $slotDiffRenderer ) {
1429 $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role][
'old'],
1430 $slotContents[$role][
'new'] );
1434 if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1437 $difftext .= $this->getSlotHeader( $slotTitle );
1439 $difftext .= $slotDiff;
1443 if ( !$this->hookRunner->onAbortDiffCache( $this ) ) {
1444 $this->incrementStats(
'uncacheable' );
1445 } elseif ( $key !==
false ) {
1446 $this->incrementStats(
'miss' );
1447 $cache->set( $key, $difftext, 7 * 86400 );
1449 $this->incrementStats(
'uncacheable' );
1452 $difftext = $this->localiseDiff( $difftext );
1464 $diffRenderers = $this->getSlotDiffRenderers();
1465 if ( !isset( $diffRenderers[$role] ) ) {
1469 $slotContents = $this->getSlotContents();
1471 $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role][
'old'],
1472 $slotContents[$role][
'new'] );
1476 if ( $slotDiff ===
'' ) {
1480 if ( $role !== SlotRecord::MAIN ) {
1483 $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff;
1486 return $this->localiseDiff( $slotDiff );
1498 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1499 $userLang = $this->getLanguage()->getHtmlCode();
1500 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-header',
'lang' => $userLang ],
1501 Html::element(
'th', [
'colspan' => $columnCount ], $headerText ) );
1512 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1513 $userLang = $this->getLanguage()->getHtmlCode();
1514 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-error',
'lang' => $userLang ],
1515 Html::rawElement(
'td', [
'colspan' => $columnCount ], $errorText ) );
1532 if ( !$this->mOldid || !$this->mNewid ) {
1533 throw new BadMethodCallException(
'mOldid and mNewid must be set to get diff cache key.' );
1539 "old-{$this->mOldid}",
1540 "rev-{$this->mNewid}"
1544 if ( !$this->isSlotDiffRenderer ) {
1545 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1546 $extraKeys = array_merge( $extraKeys, $slotDiffRenderer->getExtraCacheKeys() );
1549 ksort( $extraKeys );
1550 return array_merge(
$params, array_values( $extraKeys ) );
1564 $this->mOldid = 123456789;
1565 $this->mNewid = 987654321;
1568 $params = $this->getDiffBodyCacheKeyParams();
1578 if ( array_slice(
$params, 0, count( $standardParams ) ) === $standardParams ) {
1596 $validatedOptions = [];
1597 if ( isset( $options[
'diff-type'] )
1598 && $this->getTextDiffer()->hasFormat( $options[
'diff-type'] )
1600 $validatedOptions[
'diff-type'] = $options[
'diff-type'];
1602 if ( !empty( $options[
'expand-url'] ) ) {
1603 $validatedOptions[
'expand-url'] =
true;
1605 if ( !empty( $options[
'inline-toggle'] ) ) {
1606 $validatedOptions[
'inline-toggle'] =
true;
1608 $this->slotDiffOptions = $validatedOptions;
1619 $this->extraQueryParams =
$params;
1639 && $this->isSlotDiffRenderer
1645 throw new LogicException( get_class( $this ) .
': could not maintain backwards compatibility. '
1646 .
'Please use a SlotDiffRenderer.' );
1648 return $slotDiffRenderer->getDiff( $old, $new ) . $this->getDebugString();
1664 $slotDiffRenderer = $this->contentHandlerFactory
1666 ->getSlotDiffRenderer( $this->
getContext() );
1670 throw new LogicException(
'The slot diff renderer for text content should be a '
1671 .
'TextSlotDiffRenderer subclass' );
1673 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
1683 $differenceEngine =
new self;
1684 $engine = $differenceEngine->getTextDiffer()->getEngineForFormat(
'table' );
1685 if ( $engine ===
'external' ) {
1686 return MediaWikiServices::getInstance()->getMainConfig()
1687 ->get( MainConfigNames::ExternalDiffEngine );
1701 protected function debug( $generator =
"internal" ) {
1702 if ( !$this->enableDebugComment ) {
1705 $data = [ $generator ];
1706 if ( $this->getConfig()->
get( MainConfigNames::ShowHostnames ) ) {
1711 return "<!-- diff generator: " .
1712 implode(
" ", array_map(
"htmlspecialchars", $data ) ) .
1719 private function getDebugString() {
1720 $engine = self::getEngine();
1721 if ( $engine ===
'wikidiff2' ) {
1722 return $this->debug(
'wikidiff2' );
1723 } elseif ( $engine ===
'php' ) {
1724 return $this->debug(
'native PHP' );
1726 return $this->debug(
"external $engine" );
1736 private function localiseDiff( $text ) {
1737 return $this->getTextDiffer()->localize( $this->getTextDiffFormat(), $text );
1749 return preg_replace_callback(
'/<!--LINE (\d+)-->/',
1751 if (
$matches[1] ===
'1' && $this->mReducedLineNumbers ) {
1754 return $this->msg(
'lineno' )->numParams(
$matches[1] )->escaped();
1766 !$this->mOldRevisionRecord || !$this->mNewRevisionRecord
1767 || !$this->mOldPage || !$this->mNewPage
1768 || !$this->mOldPage->equals( $this->mNewPage )
1769 || $this->mOldRevisionRecord->getId() ===
null
1770 || $this->mNewRevisionRecord->getId() ===
null
1772 || $this->mNewPage->getArticleID() !== $this->mOldRevisionRecord->getPageId()
1773 || $this->mNewPage->getArticleID() !== $this->mNewRevisionRecord->getPageId()
1778 if ( $this->mOldRevisionRecord->getTimestamp() > $this->mNewRevisionRecord->getTimestamp() ) {
1779 $oldRevRecord = $this->mNewRevisionRecord;
1780 $newRevRecord = $this->mOldRevisionRecord;
1782 $oldRevRecord = $this->mOldRevisionRecord;
1783 $newRevRecord = $this->mNewRevisionRecord;
1789 $revisionIdList = $this->revisionStore->getRevisionIdsBetween(
1790 $this->mNewPage->getArticleID(),
1796 if ( count( $revisionIdList ) > 0 ) {
1797 foreach ( $revisionIdList as $revisionId ) {
1798 $revision = $this->revisionStore->getRevisionById( $revisionId );
1799 if ( $revision->getUser( RevisionRecord::FOR_THIS_USER, $this->getAuthority() ) ) {
1804 if ( $nEdits > 0 && $nEdits <= 1000 ) {
1806 $newRevUserForGender =
'[HIDDEN]';
1809 $users = $this->revisionStore->getAuthorsBetween(
1810 $this->mNewPage->getArticleID(),
1816 $numUsers = count( $users );
1818 $newRevUser = $newRevRecord->getUser( RevisionRecord::RAW );
1819 $newRevUserText = $newRevUser ? $newRevUser->getName() :
'';
1820 $newRevUserSafe = $newRevRecord->getUser(
1821 RevisionRecord::FOR_THIS_USER,
1824 $newRevUserForGender = $newRevUserSafe ? $newRevUserSafe->getName() :
'[HIDDEN]';
1825 if ( $numUsers == 1 && $users[0]->getName() == $newRevUserText ) {
1828 }
catch ( InvalidArgumentException $e ) {
1832 return self::intermediateEditsMsg( $nEdits, $numUsers, $limit, $newRevUserForGender );
1849 if ( $numUsers === 0 ) {
1850 $msg =
'diff-multi-sameuser';
1852 ->numParams( $numEdits, $numUsers )
1853 ->params( $lastUser )
1855 } elseif ( $numUsers > $limit ) {
1856 $msg =
'diff-multi-manyusers';
1859 $msg =
'diff-multi-otherusers';
1862 return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1870 if ( !$revRecord->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1887 $lang = $this->getLanguage();
1890 $timestamp = $lang->userTimeAndDate( $revtimestamp, $user );
1891 $dateofrev = $lang->userDate( $revtimestamp, $user );
1892 $timeofrev = $lang->userTime( $revtimestamp, $user );
1895 $rev->
isCurrent() ?
'currentrev-asof' :
'revisionasof',
1901 if ( $complete !==
'complete' ) {
1907 if ( $this->userCanEdit( $rev ) ) {
1908 $header = $this->linkRenderer->makeKnownLink(
1912 [
'oldid' => $rev->
getId() ]
1914 $editQuery = [
'action' =>
'edit' ];
1916 $editQuery[
'oldid'] = $rev->
getId();
1919 $key = $this->
getAuthority()->probablyCan(
'edit', $rev->
getPage() ) ?
'editold' :
'viewsourceold';
1920 $msg = $this->msg( $key )->text();
1921 $editLink = $this->linkRenderer->makeKnownLink( $title, $msg, [], $editQuery );
1922 $header .=
' ' . Html::rawElement(
1924 [
'class' =>
'mw-diff-edit' ],
1932 $header .= Html::element(
'span',
1934 'class' =>
'mw-diff-timestamp',
1935 'data-timestamp' =>
wfTimestamp( TS_ISO_8601, $revtimestamp ),
1939 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1940 return Html::rawElement(
1942 [
'class' => Linker::getRevisionDeletedClass( $rev ) ],
1962 public function addHeader( $diff, $otitle, $ntitle, $multi =
'', $notice =
'' ) {
1965 $header = Html::openElement(
'table', [
1971 'diff-type-' . $this->getTextDiffFormat(),
1975 'diff-contentalign-' . $this->getDiffLang()->alignStart(),
1980 'diff-editfont-' . $this->userOptionsLookup->getOption(
1985 'data-mw' =>
'interface',
1987 $userLang = htmlspecialchars( $this->getLanguage()->getHtmlCode() );
1989 if ( !$diff && !$otitle ) {
1991 <tr class=\"diff-title\" lang=\"{$userLang}\">
1992 <td class=\"diff-ntitle\">{$ntitle}</td>
1998 <col class=\"diff-marker\" />
1999 <col class=\"diff-content\" />
2000 <col class=\"diff-marker\" />
2001 <col class=\"diff-content\" />";
2008 if ( $otitle || $ntitle ) {
2010 $deletedClass =
'diff-side-deleted';
2011 $addedClass =
'diff-side-added';
2013 <tr class=\"diff-title\" lang=\"{$userLang}\">
2014 <td colspan=\"$colspan\" class=\"diff-otitle {$deletedClass}\">{$otitle}</td>
2015 <td colspan=\"$colspan\" class=\"diff-ntitle {$addedClass}\">{$ntitle}</td>
2020 if ( $multi !=
'' ) {
2021 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2022 "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
2024 if ( $notice !=
'' ) {
2025 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2026 "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
2029 return $header . $diff .
"</table>";
2040 $this->mOldContent = $oldContent;
2041 $this->mNewContent = $newContent;
2043 $this->mTextLoaded = 2;
2044 $this->mRevisionsLoaded =
true;
2045 $this->isContentOverridden =
true;
2046 $this->slotDiffRenderers =
null;
2057 if ( $oldRevision ) {
2058 $this->mOldRevisionRecord = $oldRevision;
2059 $this->mOldid = $oldRevision->
getId();
2063 $this->mOldContent = $oldRevision->
getContent( SlotRecord::MAIN,
2064 RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
2065 if ( !$this->mOldContent ) {
2066 $this->addRevisionLoadError(
'old' );
2069 $this->mOldPage =
null;
2070 $this->mOldRevisionRecord = $this->mOldid =
false;
2072 $this->mNewRevisionRecord = $newRevision;
2073 $this->mNewid = $newRevision->
getId();
2075 $this->mNewContent = $newRevision->
getContent( SlotRecord::MAIN,
2076 RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
2077 if ( !$this->mNewContent ) {
2078 $this->addRevisionLoadError(
'new' );
2081 $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded =
true;
2082 $this->mTextLoaded = $oldRevision ? 2 : 1;
2083 $this->isContentOverridden =
false;
2084 $this->slotDiffRenderers =
null;
2094 $this->mDiffLang = $lang;
2110 if ( $new ===
'prev' ) {
2112 $newid = intval( $old );
2114 $newRev = $this->revisionStore->getRevisionById( $newid );
2116 $oldRev = $this->revisionStore->getPreviousRevision( $newRev );
2118 $oldid = $oldRev->getId();
2121 } elseif ( $new ===
'next' ) {
2123 $oldid = intval( $old );
2125 $oldRev = $this->revisionStore->getRevisionById( $oldid );
2127 $newRev = $this->revisionStore->getNextRevision( $oldRev );
2129 $newid = $newRev->getId();
2133 $oldid = intval( $old );
2134 $newid = intval( $new );
2138 return [ $oldid, $newid ];
2141 private function loadRevisionIds() {
2142 if ( $this->mRevisionsIdsLoaded ) {
2146 $this->mRevisionsIdsLoaded =
true;
2148 $old = $this->mOldid;
2149 $new = $this->mNewid;
2151 [ $this->mOldid, $this->mNewid ] = self::mapDiffPrevNext( $old, $new );
2152 if ( $new ===
'next' && $this->mNewid ===
false ) {
2153 # if no result, NewId points to the newest old revision. The only newer
2154 # revision is cur, which is "0".
2158 $this->hookRunner->onNewDifferenceEngine(
2160 $this->
getTitle(), $this->mOldid, $this->mNewid, $old, $new );
2177 if ( $this->mRevisionsLoaded ) {
2178 return $this->isContentOverridden ||
2179 ( $this->mOldRevisionRecord !==
null && $this->mNewRevisionRecord !== null );
2183 $this->mRevisionsLoaded =
true;
2185 $this->loadRevisionIds();
2188 if ( $this->mNewid ) {
2189 $this->mNewRevisionRecord = $this->revisionStore->getRevisionById( $this->mNewid );
2191 $this->mNewRevisionRecord = $this->revisionStore->getRevisionByTitle( $this->
getTitle() );
2199 $this->mNewid = $this->mNewRevisionRecord->getId();
2200 $this->mNewPage = $this->mNewid ?
2201 Title::newFromLinkTarget( $this->mNewRevisionRecord->getPageAsLinkTarget() ) :
2205 $this->mOldRevisionRecord =
false;
2206 if ( $this->mOldid ) {
2207 $this->mOldRevisionRecord = $this->revisionStore->getRevisionById( $this->mOldid );
2208 } elseif ( $this->mOldid === 0 ) {
2209 $revRecord = $this->revisionStore->getPreviousRevision( $this->mNewRevisionRecord );
2211 $this->mOldid = $revRecord ? $revRecord->
getId() :
false;
2212 $this->mOldRevisionRecord = $revRecord ??
false;
2215 if ( $this->mOldRevisionRecord ===
null ) {
2219 if ( $this->mOldRevisionRecord && $this->mOldRevisionRecord->getId() ) {
2220 $this->mOldPage = Title::newFromLinkTarget(
2221 $this->mOldRevisionRecord->getPageAsLinkTarget()
2224 $this->mOldPage =
null;
2228 $dbr = $this->dbProvider->getReplicaDatabase();
2229 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
2230 if ( $this->mOldid !==
false ) {
2231 $tagIds = $dbr->newSelectQueryBuilder()
2232 ->select(
'ct_tag_id' )
2233 ->from(
'change_tag' )
2234 ->where( [
'ct_rev_id' => $this->mOldid ] )
2235 ->caller( __METHOD__ )->fetchFieldValues();
2237 foreach ( $tagIds as $tagId ) {
2239 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2244 $this->mOldTags = implode(
',', $tags );
2246 $this->mOldTags =
false;
2249 $tagIds = $dbr->newSelectQueryBuilder()
2250 ->select(
'ct_tag_id' )
2251 ->from(
'change_tag' )
2252 ->where( [
'ct_rev_id' => $this->mNewid ] )
2253 ->caller( __METHOD__ )->fetchFieldValues();
2255 foreach ( $tagIds as $tagId ) {
2257 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2262 $this->mNewTags = implode(
',', $tags );
2276 if ( $this->mTextLoaded == 2 ) {
2277 return $this->loadRevisionData() &&
2278 ( $this->mOldRevisionRecord ===
false || $this->mOldContent )
2279 && $this->mNewContent;
2283 $this->mTextLoaded = 2;
2285 if ( !$this->loadRevisionData() ) {
2289 if ( $this->mOldRevisionRecord ) {
2290 $this->mOldContent = $this->mOldRevisionRecord->getContent(
2292 RevisionRecord::FOR_THIS_USER,
2295 if ( $this->mOldContent ===
null ) {
2300 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2302 RevisionRecord::FOR_THIS_USER,
2305 $this->hookRunner->onDifferenceEngineLoadTextAfterNewContentIsLoaded( $this );
2306 if ( $this->mNewContent ===
null ) {
2319 if ( $this->mTextLoaded >= 1 ) {
2320 return $this->loadRevisionData();
2323 $this->mTextLoaded = 1;
2325 if ( !$this->loadRevisionData() ) {
2329 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2331 RevisionRecord::FOR_THIS_USER,
2335 $this->hookRunner->onDifferenceEngineAfterLoadNewText( $this );
2346 if ( $this->textDiffer ===
null ) {
2349 $this->getDiffLang(),
2350 $this->getConfig()->
get( MainConfigNames::DiffEngine ),
2351 $this->getConfig()->
get( MainConfigNames::ExternalDiffEngine ),
2352 $this->getConfig()->
get( MainConfigNames::Wikidiff2Options )
2355 return $this->textDiffer;
2365 return $this->getTextDiffer()->getFormats();
2375 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.