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 = [];
238 private $extraQueryParams = [];
257 private $revisionLoadErrors = [];
267 public function __construct( $context =
null, $old = 0, $new = 0, $rcid = 0,
268 $refreshCache =
false, $unhide =
false
274 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'" );
276 $this->mOldid = $old;
277 $this->mNewid = $new;
278 $this->mRefreshCache = $refreshCache;
279 $this->unhide = $unhide;
281 $services = MediaWikiServices::getInstance();
282 $this->linkRenderer = $services->getLinkRenderer();
283 $this->contentHandlerFactory = $services->getContentHandlerFactory();
284 $this->revisionStore = $services->getRevisionStore();
285 $this->archivedRevisionLookup = $services->getArchivedRevisionLookup();
286 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
287 $this->wikiPageFactory = $services->getWikiPageFactory();
288 $this->userOptionsLookup = $services->getUserOptionsLookup();
289 $this->commentFormatter = $services->getCommentFormatter();
290 $this->dbProvider = $services->getConnectionProvider();
291 $this->userGroupManager = $services->getUserGroupManager();
292 $this->userEditTracker = $services->getUserEditTracker();
293 $this->userIdentityUtils = $services->getUserIdentityUtils();
302 if ( $this->isSlotDiffRenderer ) {
303 throw new LogicException( __METHOD__ .
' called in slot diff renderer mode' );
306 if ( $this->slotDiffRenderers ===
null ) {
312 $this->slotDiffRenderers = [];
313 foreach ( $slotContents as $role => $contents ) {
314 if ( $contents[
'new'] && $contents[
'old']
315 && $contents[
'new']->equals( $contents[
'old'] )
320 $handler = ( $contents[
'new'] ?: $contents[
'old'] )->getContentHandler();
321 $this->slotDiffRenderers[$role] = $handler->getSlotDiffRenderer(
323 $this->slotDiffOptions + [
324 'contentLanguage' => $this->
getDiffLang()->getCode(),
331 return $this->slotDiffRenderers;
341 $this->isSlotDiffRenderer =
true;
350 if ( $this->isContentOverridden ) {
352 SlotRecord::MAIN => [
'old' => $this->mOldContent,
'new' => $this->mNewContent ]
358 $newSlots = $this->mNewRevisionRecord->getPrimarySlots()->getSlots();
359 $oldSlots = $this->mOldRevisionRecord ?
360 $this->mOldRevisionRecord->getPrimarySlots()->getSlots() :
366 $roles = array_keys( array_merge( $newSlots, $oldSlots ) );
369 foreach ( $roles as $role ) {
371 'old' => $this->loadSingleSlot(
372 $oldSlots[$role] ??
null,
375 'new' => $this->loadSingleSlot(
376 $newSlots[$role] ??
null,
382 if ( isset( $slots[SlotRecord::MAIN] ) ) {
383 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
395 private function loadSingleSlot( ?
SlotRecord $slot,
string $which ) {
402 $this->addRevisionLoadError( $which );
412 private function addRevisionLoadError( $which ) {
413 $this->revisionLoadErrors[] = $this->
msg( $which ===
'new'
414 ?
'difference-bad-new-revision' :
'difference-bad-old-revision'
425 return $this->revisionLoadErrors;
432 private function hasNewRevisionLoadError() {
433 foreach ( $this->revisionLoadErrors as $error ) {
434 if ( $error->getKey() ===
'difference-bad-new-revision' ) {
444 return parent::getTitle() ?: Title::makeTitle(
NS_SPECIAL,
'BadTitle/DifferenceEngine' );
454 $this->mReducedLineNumbers = $value;
463 # Default language in which the diff text is written.
465 return $this->mDiffLang;
475 return $this->
getTitle()->getPageLanguage();
482 return $this->mCacheHit;
493 $this->loadRevisionIds();
495 return $this->mOldid;
505 $this->loadRevisionIds();
507 return $this->mNewid;
517 return $this->mOldRevisionRecord ?:
null;
526 return $this->mNewRevisionRecord;
538 if ( $this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
539 $revRecord = $this->archivedRevisionLookup->getArchivedRevisionRecord(
null, $id );
541 $title = Title::newFromPageIdentity( $revRecord->
getPage() );
543 return SpecialPage::getTitleFor(
'Undelete' )->getFullURL( [
544 'target' => $title->getPrefixedText(),
563 return "[$link $id]";
569 private function showMissingRevision() {
570 $out = $this->getOutput();
573 if ( $this->mOldid && ( !$this->mOldRevisionRecord || !$this->mOldContent ) ) {
574 $missing[] = $this->deletedIdMarker( $this->mOldid );
576 if ( !$this->mNewRevisionRecord || !$this->mNewContent ) {
580 $out->setPageTitleMsg( $this->
msg(
'errorpagetitle' ) );
581 $msg = $this->
msg(
'difference-missing-revision' )
582 ->params( $this->
getLanguage()->listToText( $missing ) )
583 ->numParams( count( $missing ) )
585 $out->addHTML( $msg );
596 $this->mNewRevisionRecord &&
597 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
600 $this->mOldRevisionRecord &&
601 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
613 $permStatus = PermissionStatus::newEmpty();
614 if ( $this->mNewPage ) {
615 $performer->
authorizeRead(
'read', $this->mNewPage, $permStatus );
617 if ( $this->mOldPage ) {
618 $performer->
authorizeRead(
'read', $this->mOldPage, $permStatus );
620 return $permStatus->toLegacyErrorArray();
630 ( $this->mOldRevisionRecord &&
631 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) ||
632 ( $this->mNewRevisionRecord &&
633 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) )
643 private function getUserEditCount( $user ): string {
644 $editCount = $this->userEditTracker->getUserEditCount( $user );
645 if ( $editCount ===
null ) {
649 return Html::rawElement(
'div', [
650 'class' =>
'mw-diff-usereditcount',
666 if ( !$this->userIdentityUtils->isNamed( $user ) ) {
669 $userGroups = $this->userGroupManager->getUserGroups( $user );
670 $userGroupLinks = [];
671 foreach ( $userGroups as $group ) {
672 $userGroupLinks[] = UserGroupMembership::getLinkHTML( $group, $this->
getContext() );
674 return Html::rawElement(
'div', [
675 'class' =>
'mw-diff-userroles',
676 ], $this->getLanguage()->commaList( $userGroupLinks ) );
685 private function getUserMetaData( ?
UserIdentity $user ) {
689 return Html::rawElement(
'div', [
690 'class' =>
'mw-diff-usermetadata',
691 ], $this->getUserRoles( $user ) . $this->getUserEditCount( $user ) );
706 $this->loadRevisionData();
708 if ( $this->mOldRevisionRecord && !$this->mOldRevisionRecord->userCan(
709 RevisionRecord::DELETED_TEXT,
717 return !$this->mNewRevisionRecord || $this->mNewRevisionRecord->userCan(
718 RevisionRecord::DELETED_TEXT,
731 return $this->hasDeletedRevision() && ( !$this->unhide ||
732 !$this->isUserAllowedToSeeRevisions( $performer ) );
739 # Allow frames except in certain special cases
740 $out = $this->getOutput();
741 $out->setPreventClickjacking(
false );
742 $out->setRobotPolicy(
'noindex,nofollow' );
745 $this->hookRunner->onDifferenceEngineShowDiffPage( $out );
747 if ( !$this->loadRevisionData() ) {
748 if ( $this->hookRunner->onDifferenceEngineShowDiffPageMaybeShowMissingRevision( $this ) ) {
749 $this->showMissingRevision();
754 $user = $this->getUser();
755 $permErrors = $this->getPermissionErrors( $this->getAuthority() );
762 $query = $this->extraQueryParams;
763 # Carry over 'diffonly' param via navigation links
764 if ( $diffOnly != MediaWikiServices::getInstance()
765 ->getUserOptionsLookup()->getBoolOption( $user,
'diffonly' )
767 $query[
'diffonly'] = $diffOnly;
769 # Cascade unhide param in links for easy deletion browsing
770 if ( $this->unhide ) {
771 $query[
'unhide'] = 1;
774 # Check if one of the revisions is deleted/suppressed
775 $deleted = $this->hasDeletedRevision();
776 $suppressed = $this->hasSuppressedRevision();
777 $allowed = $this->isUserAllowedToSeeRevisions( $this->getAuthority() );
782 # mOldRevisionRecord is false if the difference engine is called with a "vague" query for
783 # a diff between a version V and its previous version V' AND the version V
784 # is the first version of that article. In that case, V' does not exist.
785 if ( $this->mOldRevisionRecord ===
false ) {
786 if ( $this->mNewPage ) {
787 $out->setPageTitleMsg(
788 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
794 $this->hookRunner->onDifferenceEngineOldHeaderNoOldRev( $oldHeader );
796 $this->hookRunner->onDifferenceEngineViewHeader( $this );
798 if ( !$this->mOldPage || !$this->mNewPage ) {
801 } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
802 $out->setPageTitleMsg(
803 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
807 $out->setPageTitleMsg( $this->msg(
'difference-title-multipage' )->plaintextParams(
808 $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
809 $out->addSubtitle( $this->msg(
'difference-multipage' ) );
813 if ( $samePage && $this->mNewPage &&
814 $this->getAuthority()->probablyCan(
'edit', $this->mNewPage )
816 if ( $this->mNewRevisionRecord->isCurrent() &&
817 $this->getAuthority()->probablyCan(
'rollback', $this->mNewPage )
819 $rollbackLink = Linker::generateRollback(
820 $this->mNewRevisionRecord,
824 if ( $rollbackLink ) {
825 $out->setPreventClickjacking(
true );
826 $rollback =
"\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
830 if ( $this->userCanEdit( $this->mOldRevisionRecord ) &&
831 $this->userCanEdit( $this->mNewRevisionRecord )
833 $undoLink = $this->linkRenderer->makeKnownLink(
835 $this->msg(
'editundo' )->text(),
836 [
'title' => Linker::titleAttrib(
'undo' ) ],
839 'undoafter' => $this->mOldid,
840 'undo' => $this->mNewid
843 $revisionTools[
'mw-diff-undo'] = $undoLink;
846 # Make "previous revision link"
847 $hasPrevious = $samePage && $this->mOldPage &&
848 $this->revisionStore->getPreviousRevision( $this->mOldRevisionRecord );
849 if ( $hasPrevious ) {
850 $prevlinkQuery = [
'diff' =>
'prev',
'oldid' => $this->mOldid ] + $query;
851 $prevlink = $this->linkRenderer->makeKnownLink(
853 $this->msg(
'previousdiff' )->text(),
854 [
'id' =>
'differences-prevlink' ],
857 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
859 $this->msg(
'previousdiff' )->text(),
861 'class' =>
'mw-diff-revision-history-link-previous'
866 $prevlink =
"\u{00A0}";
869 if ( $this->mOldRevisionRecord->isMinor() ) {
870 $oldminor = ChangesList::flag(
'minor' );
875 $oldRevRecord = $this->mOldRevisionRecord;
877 $ldel = $this->revisionDeleteLink( $oldRevRecord );
878 $oldRevisionHeader = $this->getRevisionHeader( $oldRevRecord,
'complete' );
880 $oldRevComment = $this->commentFormatter
882 $oldRevRecord, $user, !$diffOnly, !$this->unhide,
false
885 if ( $oldRevComment ===
'' ) {
886 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
887 $oldRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
890 $oldHeader =
'<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader .
'</strong></div>' .
891 '<div id="mw-diff-otitle2">' .
892 Linker::revUserTools( $oldRevRecord, !$this->unhide ) .
893 $this->getUserMetaData( $oldRevRecord->getUser() ) .
895 '<div id="mw-diff-otitle3">' . $oldminor . $oldRevComment . $ldel .
'</div>' .
896 '<div id="mw-diff-otitle5">' . $oldChangeTags[0] .
'</div>' .
897 '<div id="mw-diff-otitle4">' . $prevlink .
'</div>';
900 $this->hookRunner->onDifferenceEngineOldHeader(
901 $this, $oldHeader, $prevlink, $oldminor, $diffOnly, $ldel, $this->unhide );
904 $out->addJsConfigVars( [
905 'wgDiffOldId' => $this->mOldid,
906 'wgDiffNewId' => $this->mNewid,
909 # Make "next revision link"
910 # Skip next link on the top revision
911 if ( $samePage && $this->mNewPage && !$this->mNewRevisionRecord->isCurrent() ) {
912 $nextlinkQuery = [
'diff' =>
'next',
'oldid' => $this->mNewid ] + $query;
913 $nextlink = $this->linkRenderer->makeKnownLink(
915 $this->msg(
'nextdiff' )->text(),
916 [
'id' =>
'differences-nextlink' ],
919 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
921 $this->msg(
'nextdiff' )->text(),
923 'class' =>
'mw-diff-revision-history-link-next'
928 $nextlink =
"\u{00A0}";
931 if ( $this->mNewRevisionRecord->isMinor() ) {
932 $newminor = ChangesList::flag(
'minor' );
937 # Handle RevisionDelete links...
938 $rdel = $this->revisionDeleteLink( $this->mNewRevisionRecord );
940 # Allow extensions to define their own revision tools
941 $this->hookRunner->onDiffTools(
942 $this->mNewRevisionRecord,
944 $this->mOldRevisionRecord ?:
null,
948 $formattedRevisionTools = [];
950 foreach ( $revisionTools as $key => $tool ) {
951 $toolClass = is_string( $key ) ? $key :
'mw-diff-tool';
952 $element = Html::rawElement(
954 [
'class' => $toolClass ],
957 $formattedRevisionTools[] = $element;
960 $newRevRecord = $this->mNewRevisionRecord;
962 $newRevisionHeader = $this->getRevisionHeader( $newRevRecord,
'complete' ) .
963 ' ' . implode(
' ', $formattedRevisionTools );
965 $newRevComment = $this->commentFormatter->formatRevision(
966 $newRevRecord, $user, !$diffOnly, !$this->unhide,
false
969 if ( $newRevComment ===
'' ) {
970 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
971 $newRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
974 $newHeader =
'<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader .
'</strong></div>' .
975 '<div id="mw-diff-ntitle2">' . Linker::revUserTools( $newRevRecord, !$this->unhide ) .
977 $this->getUserMetaData( $newRevRecord->getUser() ) .
979 '<div id="mw-diff-ntitle3">' . $newminor . $newRevComment . $rdel .
'</div>' .
980 '<div id="mw-diff-ntitle5">' . $newChangeTags[0] .
'</div>' .
981 '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() .
'</div>';
984 $this->hookRunner->onDifferenceEngineNewHeader( $this, $newHeader,
985 $formattedRevisionTools, $nextlink, $rollback, $newminor, $diffOnly,
986 $rdel, $this->unhide );
989 Html::rawElement(
'div', [
990 'class' =>
'mw-diff-revision-history-links'
993 # If the diff cannot be shown due to a deleted revision, then output
994 # the diff header and links to unhide (if available)...
995 if ( $this->shouldBeHiddenFromUser( $this->getAuthority() ) ) {
996 $this->showDiffStyle();
997 $multi = $this->getMultiNotice();
998 $out->addHTML( $this->addHeader(
'', $oldHeader, $newHeader, $multi ) );
1000 # Give explanation for why revision is not visible
1001 $msg = [ $suppressed ?
'rev-suppressed-no-diff' :
'rev-deleted-no-diff' ];
1003 # Give explanation and add a link to view the diff...
1004 $query = $this->getRequest()->appendQueryValue(
'unhide',
'1' );
1006 $suppressed ?
'rev-suppressed-unhide-diff' :
'rev-deleted-unhide-diff',
1007 $this->
getTitle()->getFullURL( $query )
1010 $out->addHTML( Html::warningBox( $this->msg( ...$msg )->parse(),
'plainlinks' ) );
1011 # Otherwise, output a regular diff...
1013 # Add deletion notice if the user is viewing deleted content
1016 $msg = $suppressed ?
'rev-suppressed-diff-view' :
'rev-deleted-diff-view';
1017 $notice = Html::warningBox( $this->msg( $msg )->parse(),
'plainlinks' );
1020 # Add an error if the content can't be loaded
1021 $this->getSlotContents();
1022 foreach ( $this->getRevisionLoadErrors() as $msg ) {
1023 $notice .= Html::warningBox( $msg->parse() );
1027 if ( $this->getTextDiffer()->hasFormat(
'inline' ) ) {
1031 $this->showTablePrefixes();
1032 $this->showDiff( $oldHeader, $newHeader, $notice );
1034 $this->renderNewRevision();
1038 if ( $this->hookRunner->onDifferenceEngineRenderRevisionShowFinalPatrolLink() ) {
1039 # Add redundant patrol link on bottom...
1040 $out->addHTML( $this->markPatrolledLink() );
1048 private function showTablePrefixes() {
1050 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1051 $parts += $slotDiffRenderer->getTablePrefix( $this->getContext(), $this->mNewPage );
1054 if ( count( array_filter( $parts ) ) > 0 ) {
1055 $language = $this->getLanguage();
1057 'class' =>
'mw-diff-table-prefix',
1058 'dir' => $language->getDir(),
1059 'lang' => $language->getCode(),
1061 $this->getOutput()->addHTML(
1062 Html::rawElement(
'div', $attrs, implode(
'', $parts ) ) );
1077 if ( $this->mMarkPatrolledLink ===
null ) {
1078 $linkInfo = $this->getMarkPatrolledLinkInfo();
1080 if ( !$linkInfo || !$this->mNewPage ) {
1081 $this->mMarkPatrolledLink =
'';
1083 $patrolLinkClass =
'patrollink';
1084 $this->mMarkPatrolledLink =
' <span class="' . $patrolLinkClass .
'" data-mw="interface">[' .
1085 $this->linkRenderer->makeKnownLink(
1087 $this->msg(
'markaspatrolleddiff' )->text(),
1090 'action' =>
'markpatrolled',
1091 'rcid' => $linkInfo[
'rcid'],
1095 $this->hookRunner->onDifferenceEngineMarkPatrolledLink( $this,
1096 $this->mMarkPatrolledLink, $linkInfo[
'rcid'] );
1099 return $this->mMarkPatrolledLink;
1110 $user = $this->getUser();
1111 $config = $this->getConfig();
1116 $config->get( MainConfigNames::UseRCPatrol ) &&
1118 $this->getAuthority()->probablyCan(
'patrol', $this->mNewPage ) &&
1121 RecentChange::isInRCLifespan( $this->mNewRevisionRecord->getTimestamp(), 21600 )
1124 $change = RecentChange::newFromConds(
1126 'rc_this_oldid' => $this->mNewid,
1127 'rc_patrolled' => RecentChange::PRC_UNPATROLLED
1132 if ( $change && !$change->getPerformerIdentity()->equals( $user ) ) {
1133 $rcid = $change->getAttribute(
'rc_id' );
1144 $this->hookRunner->onDifferenceEngineMarkPatrolledRCID( $rcid, $this, $change, $user );
1148 $this->getOutput()->setPreventClickjacking(
true );
1149 $this->getOutput()->addModules(
'mediawiki.misc-authed-curate' );
1151 return [
'rcid' => $rcid ];
1164 private function revisionDeleteLink(
RevisionRecord $revRecord ) {
1165 $link = Linker::getRevDeleteLink(
1166 $this->getAuthority(),
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 );
1349 private function incrementStats(
string $cacheStatus ): void {
1351 $stats->getCounter(
'diff_cache_total' )
1352 ->setLabel(
'status', $cacheStatus )
1353 ->copyToStatsdAt(
'diff_cache.' . $cacheStatus )
1363 $this->mCacheHit =
true;
1365 if ( !$this->isContentOverridden ) {
1366 if ( !$this->loadRevisionData() ) {
1368 } elseif ( $this->mOldRevisionRecord &&
1369 !$this->mOldRevisionRecord->userCan(
1370 RevisionRecord::DELETED_TEXT,
1371 $this->getAuthority()
1375 } elseif ( $this->mNewRevisionRecord &&
1376 !$this->mNewRevisionRecord->userCan(
1377 RevisionRecord::DELETED_TEXT,
1378 $this->getAuthority()
1383 if ( $this->mOldRevisionRecord ===
false || (
1384 $this->mOldRevisionRecord &&
1385 $this->mNewRevisionRecord &&
1386 $this->mOldRevisionRecord->getId() &&
1387 $this->mOldRevisionRecord->getId() == $this->mNewRevisionRecord->getId()
1389 if ( $this->hookRunner->onDifferenceEngineShowEmptyOldContent( $this ) ) {
1397 $services = MediaWikiServices::getInstance();
1398 $cache = $services->getMainWANObjectCache();
1399 $stats = $services->getStatsdDataFactory();
1400 if ( $this->mOldid && $this->mNewid ) {
1401 $key = $cache->makeKey( ...$this->getDiffBodyCacheKeyParams() );
1404 if ( !$this->mRefreshCache ) {
1405 $difftext = $cache->get( $key );
1406 if ( is_string( $difftext ) ) {
1407 $this->incrementStats(
'hit' );
1408 $difftext = $this->localiseDiff( $difftext );
1409 $this->cacheHitKey = $key;
1414 $this->mCacheHit =
false;
1415 $this->cacheHitKey =
null;
1418 if ( !$this->loadText() ) {
1425 $slotContents = $this->getSlotContents();
1426 foreach ( $this->getSlotDiffRenderers() as $role => $slotDiffRenderer ) {
1428 $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role][
'old'],
1429 $slotContents[$role][
'new'] );
1433 if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1436 $difftext .= $this->getSlotHeader( $slotTitle );
1438 $difftext .= $slotDiff;
1442 if ( !$this->hookRunner->onAbortDiffCache( $this ) ) {
1443 $this->incrementStats(
'uncacheable' );
1444 } elseif ( $key !==
false ) {
1445 $this->incrementStats(
'miss' );
1446 $cache->set( $key, $difftext, 7 * 86400 );
1448 $this->incrementStats(
'uncacheable' );
1451 $difftext = $this->localiseDiff( $difftext );
1463 $diffRenderers = $this->getSlotDiffRenderers();
1464 if ( !isset( $diffRenderers[$role] ) ) {
1468 $slotContents = $this->getSlotContents();
1470 $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role][
'old'],
1471 $slotContents[$role][
'new'] );
1475 if ( $slotDiff ===
'' ) {
1479 if ( $role !== SlotRecord::MAIN ) {
1482 $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff;
1485 return $this->localiseDiff( $slotDiff );
1497 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1498 $userLang = $this->getLanguage()->getHtmlCode();
1499 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-header',
'lang' => $userLang ],
1500 Html::element(
'th', [
'colspan' => $columnCount ], $headerText ) );
1511 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1512 $userLang = $this->getLanguage()->getHtmlCode();
1513 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-error',
'lang' => $userLang ],
1514 Html::rawElement(
'td', [
'colspan' => $columnCount ], $errorText ) );
1531 if ( !$this->mOldid || !$this->mNewid ) {
1532 throw new BadMethodCallException(
'mOldid and mNewid must be set to get diff cache key.' );
1538 "old-{$this->mOldid}",
1539 "rev-{$this->mNewid}"
1543 if ( !$this->isSlotDiffRenderer ) {
1544 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1545 $extraKeys = array_merge( $extraKeys, $slotDiffRenderer->getExtraCacheKeys() );
1548 ksort( $extraKeys );
1549 return array_merge(
$params, array_values( $extraKeys ) );
1563 $this->mOldid = 123456789;
1564 $this->mNewid = 987654321;
1567 $params = $this->getDiffBodyCacheKeyParams();
1577 if ( array_slice(
$params, 0, count( $standardParams ) ) === $standardParams ) {
1595 $validatedOptions = [];
1596 if ( isset( $options[
'diff-type'] )
1597 && $this->getTextDiffer()->hasFormat( $options[
'diff-type'] )
1599 $validatedOptions[
'diff-type'] = $options[
'diff-type'];
1601 if ( !empty( $options[
'expand-url'] ) ) {
1602 $validatedOptions[
'expand-url'] =
true;
1604 if ( !empty( $options[
'inline-toggle'] ) ) {
1605 $validatedOptions[
'inline-toggle'] =
true;
1607 $this->slotDiffOptions = $validatedOptions;
1618 $this->extraQueryParams =
$params;
1635 $slotDiffRenderer = $new->getContentHandler()->getSlotDiffRenderer( $this->getContext() );
1638 && $this->isSlotDiffRenderer
1644 throw new LogicException( get_class( $this ) .
': could not maintain backwards compatibility. '
1645 .
'Please use a SlotDiffRenderer.' );
1647 return $slotDiffRenderer->getDiff( $old, $new ) . $this->getDebugString();
1663 $slotDiffRenderer = $this->contentHandlerFactory
1665 ->getSlotDiffRenderer( $this->getContext() );
1669 throw new LogicException(
'The slot diff renderer for text content should be a '
1670 .
'TextSlotDiffRenderer subclass' );
1672 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
1682 $differenceEngine =
new self;
1683 $engine = $differenceEngine->getTextDiffer()->getEngineForFormat(
'table' );
1684 if ( $engine ===
'external' ) {
1685 return MediaWikiServices::getInstance()->getMainConfig()
1686 ->get( MainConfigNames::ExternalDiffEngine );
1700 protected function debug( $generator =
"internal" ) {
1701 if ( !$this->enableDebugComment ) {
1704 $data = [ $generator ];
1705 if ( $this->getConfig()->
get( MainConfigNames::ShowHostnames ) ) {
1710 return "<!-- diff generator: " .
1711 implode(
" ", array_map(
"htmlspecialchars", $data ) ) .
1718 private function getDebugString() {
1719 $engine = self::getEngine();
1720 if ( $engine ===
'wikidiff2' ) {
1721 return $this->debug(
'wikidiff2' );
1722 } elseif ( $engine ===
'php' ) {
1723 return $this->debug(
'native PHP' );
1725 return $this->debug(
"external $engine" );
1735 private function localiseDiff( $text ) {
1736 return $this->getTextDiffer()->localize( $this->getTextDiffFormat(), $text );
1748 return preg_replace_callback(
'/<!--LINE (\d+)-->/',
1750 if (
$matches[1] ===
'1' && $this->mReducedLineNumbers ) {
1753 return $this->msg(
'lineno' )->numParams(
$matches[1] )->escaped();
1765 !$this->mOldRevisionRecord || !$this->mNewRevisionRecord
1766 || !$this->mOldPage || !$this->mNewPage
1767 || !$this->mOldPage->equals( $this->mNewPage )
1768 || $this->mOldRevisionRecord->getId() ===
null
1769 || $this->mNewRevisionRecord->getId() ===
null
1771 || $this->mNewPage->getArticleID() !== $this->mOldRevisionRecord->getPageId()
1772 || $this->mNewPage->getArticleID() !== $this->mNewRevisionRecord->getPageId()
1777 if ( $this->mOldRevisionRecord->getTimestamp() > $this->mNewRevisionRecord->getTimestamp() ) {
1778 $oldRevRecord = $this->mNewRevisionRecord;
1779 $newRevRecord = $this->mOldRevisionRecord;
1781 $oldRevRecord = $this->mOldRevisionRecord;
1782 $newRevRecord = $this->mNewRevisionRecord;
1788 $revisionIdList = $this->revisionStore->getRevisionIdsBetween(
1789 $this->mNewPage->getArticleID(),
1795 if ( count( $revisionIdList ) > 0 ) {
1796 foreach ( $revisionIdList as $revisionId ) {
1797 $revision = $this->revisionStore->getRevisionById( $revisionId );
1798 if ( $revision->getUser( RevisionRecord::FOR_THIS_USER, $this->getAuthority() ) ) {
1803 if ( $nEdits > 0 && $nEdits <= 1000 ) {
1805 $newRevUserForGender =
'[HIDDEN]';
1808 $users = $this->revisionStore->getAuthorsBetween(
1809 $this->mNewPage->getArticleID(),
1815 $numUsers = count( $users );
1817 $newRevUser = $newRevRecord->getUser( RevisionRecord::RAW );
1818 $newRevUserText = $newRevUser ? $newRevUser->getName() :
'';
1819 $newRevUserSafe = $newRevRecord->getUser(
1820 RevisionRecord::FOR_THIS_USER,
1821 $this->getAuthority()
1823 $newRevUserForGender = $newRevUserSafe ? $newRevUserSafe->getName() :
'[HIDDEN]';
1824 if ( $numUsers == 1 && $users[0]->getName() == $newRevUserText ) {
1827 }
catch ( InvalidArgumentException $e ) {
1831 return self::intermediateEditsMsg( $nEdits, $numUsers, $limit, $newRevUserForGender );
1848 if ( $numUsers === 0 ) {
1849 $msg =
'diff-multi-sameuser';
1851 ->numParams( $numEdits, $numUsers )
1852 ->params( $lastUser )
1854 } elseif ( $numUsers > $limit ) {
1855 $msg =
'diff-multi-manyusers';
1858 $msg =
'diff-multi-otherusers';
1861 return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1869 if ( !$revRecord->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1886 $lang = $this->getLanguage();
1887 $user = $this->getUser();
1889 $timestamp = $lang->userTimeAndDate( $revtimestamp, $user );
1890 $dateofrev = $lang->userDate( $revtimestamp, $user );
1891 $timeofrev = $lang->userTime( $revtimestamp, $user );
1894 $rev->
isCurrent() ?
'currentrev-asof' :
'revisionasof',
1900 if ( $complete !==
'complete' ) {
1906 if ( $this->userCanEdit( $rev ) ) {
1907 $header = $this->linkRenderer->makeKnownLink(
1911 [
'oldid' => $rev->
getId() ]
1913 $editQuery = [
'action' =>
'edit' ];
1915 $editQuery[
'oldid'] = $rev->
getId();
1918 $key = $this->getAuthority()->probablyCan(
'edit', $rev->
getPage() ) ?
'editold' :
'viewsourceold';
1919 $msg = $this->msg( $key )->text();
1920 $editLink = $this->linkRenderer->makeKnownLink( $title, $msg, [], $editQuery );
1921 $header .=
' ' . Html::rawElement(
1923 [
'class' =>
'mw-diff-edit' ],
1931 $header .= Html::element(
'span',
1933 'class' =>
'mw-diff-timestamp',
1934 'data-timestamp' =>
wfTimestamp( TS_ISO_8601, $revtimestamp ),
1938 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1939 return Html::rawElement(
1941 [
'class' => Linker::getRevisionDeletedClass( $rev ) ],
1961 public function addHeader( $diff, $otitle, $ntitle, $multi =
'', $notice =
'' ) {
1964 $header = Html::openElement(
'table', [
1970 'diff-type-' . $this->getTextDiffFormat(),
1974 'diff-contentalign-' . $this->getDiffLang()->alignStart(),
1979 'diff-editfont-' . $this->userOptionsLookup->getOption(
1984 'data-mw' =>
'interface',
1986 $userLang = htmlspecialchars( $this->getLanguage()->getHtmlCode() );
1988 if ( !$diff && !$otitle ) {
1990 <tr class=\"diff-title\" lang=\"{$userLang}\">
1991 <td class=\"diff-ntitle\">{$ntitle}</td>
1997 <col class=\"diff-marker\" />
1998 <col class=\"diff-content\" />
1999 <col class=\"diff-marker\" />
2000 <col class=\"diff-content\" />";
2007 if ( $otitle || $ntitle ) {
2009 $deletedClass =
'diff-side-deleted';
2010 $addedClass =
'diff-side-added';
2012 <tr class=\"diff-title\" lang=\"{$userLang}\">
2013 <td colspan=\"$colspan\" class=\"diff-otitle {$deletedClass}\">{$otitle}</td>
2014 <td colspan=\"$colspan\" class=\"diff-ntitle {$addedClass}\">{$ntitle}</td>
2019 if ( $multi !=
'' ) {
2020 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2021 "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
2023 if ( $notice !=
'' ) {
2024 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2025 "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
2028 return $header . $diff .
"</table>";
2038 public function setContent( Content $oldContent, Content $newContent ) {
2039 $this->mOldContent = $oldContent;
2040 $this->mNewContent = $newContent;
2042 $this->mTextLoaded = 2;
2043 $this->mRevisionsLoaded =
true;
2044 $this->isContentOverridden =
true;
2045 $this->slotDiffRenderers =
null;
2056 if ( $oldRevision ) {
2057 $this->mOldRevisionRecord = $oldRevision;
2058 $this->mOldid = $oldRevision->
getId();
2062 $this->mOldContent = $oldRevision->
getContent( SlotRecord::MAIN,
2063 RevisionRecord::FOR_THIS_USER, $this->getAuthority() );
2064 if ( !$this->mOldContent ) {
2065 $this->addRevisionLoadError(
'old' );
2068 $this->mOldPage =
null;
2069 $this->mOldRevisionRecord = $this->mOldid =
false;
2071 $this->mNewRevisionRecord = $newRevision;
2072 $this->mNewid = $newRevision->
getId();
2074 $this->mNewContent = $newRevision->
getContent( SlotRecord::MAIN,
2075 RevisionRecord::FOR_THIS_USER, $this->getAuthority() );
2076 if ( !$this->mNewContent ) {
2077 $this->addRevisionLoadError(
'new' );
2080 $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded =
true;
2081 $this->mTextLoaded = $oldRevision ? 2 : 1;
2082 $this->isContentOverridden =
false;
2083 $this->slotDiffRenderers =
null;
2093 $this->mDiffLang = $lang;
2109 if ( $new ===
'prev' ) {
2111 $newid = intval( $old );
2113 $newRev = $this->revisionStore->getRevisionById( $newid );
2115 $oldRev = $this->revisionStore->getPreviousRevision( $newRev );
2117 $oldid = $oldRev->getId();
2120 } elseif ( $new ===
'next' ) {
2122 $oldid = intval( $old );
2124 $oldRev = $this->revisionStore->getRevisionById( $oldid );
2126 $newRev = $this->revisionStore->getNextRevision( $oldRev );
2128 $newid = $newRev->getId();
2132 $oldid = intval( $old );
2133 $newid = intval( $new );
2137 return [ $oldid, $newid ];
2140 private function loadRevisionIds() {
2141 if ( $this->mRevisionsIdsLoaded ) {
2145 $this->mRevisionsIdsLoaded =
true;
2147 $old = $this->mOldid;
2148 $new = $this->mNewid;
2150 [ $this->mOldid, $this->mNewid ] = self::mapDiffPrevNext( $old, $new );
2151 if ( $new ===
'next' && $this->mNewid ===
false ) {
2152 # if no result, NewId points to the newest old revision. The only newer
2153 # revision is cur, which is "0".
2157 $this->hookRunner->onNewDifferenceEngine(
2159 $this->
getTitle(), $this->mOldid, $this->mNewid, $old, $new );
2176 if ( $this->mRevisionsLoaded ) {
2177 return $this->isContentOverridden ||
2178 ( $this->mOldRevisionRecord !==
null && $this->mNewRevisionRecord !== null );
2182 $this->mRevisionsLoaded =
true;
2184 $this->loadRevisionIds();
2187 if ( $this->mNewid ) {
2188 $this->mNewRevisionRecord = $this->revisionStore->getRevisionById( $this->mNewid );
2190 $this->mNewRevisionRecord = $this->revisionStore->getRevisionByTitle( $this->
getTitle() );
2198 $this->mNewid = $this->mNewRevisionRecord->getId();
2199 $this->mNewPage = $this->mNewid ?
2200 Title::newFromLinkTarget( $this->mNewRevisionRecord->getPageAsLinkTarget() ) :
2204 $this->mOldRevisionRecord =
false;
2205 if ( $this->mOldid ) {
2206 $this->mOldRevisionRecord = $this->revisionStore->getRevisionById( $this->mOldid );
2207 } elseif ( $this->mOldid === 0 ) {
2208 $revRecord = $this->revisionStore->getPreviousRevision( $this->mNewRevisionRecord );
2210 $this->mOldid = $revRecord ? $revRecord->
getId() :
false;
2211 $this->mOldRevisionRecord = $revRecord ??
false;
2214 if ( $this->mOldRevisionRecord ===
null ) {
2218 if ( $this->mOldRevisionRecord && $this->mOldRevisionRecord->getId() ) {
2219 $this->mOldPage = Title::newFromLinkTarget(
2220 $this->mOldRevisionRecord->getPageAsLinkTarget()
2223 $this->mOldPage =
null;
2227 $dbr = $this->dbProvider->getReplicaDatabase();
2228 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
2229 if ( $this->mOldid !==
false ) {
2230 $tagIds = $dbr->newSelectQueryBuilder()
2231 ->select(
'ct_tag_id' )
2232 ->from(
'change_tag' )
2233 ->where( [
'ct_rev_id' => $this->mOldid ] )
2234 ->caller( __METHOD__ )->fetchFieldValues();
2236 foreach ( $tagIds as $tagId ) {
2238 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2243 $this->mOldTags = implode(
',', $tags );
2245 $this->mOldTags =
false;
2248 $tagIds = $dbr->newSelectQueryBuilder()
2249 ->select(
'ct_tag_id' )
2250 ->from(
'change_tag' )
2251 ->where( [
'ct_rev_id' => $this->mNewid ] )
2252 ->caller( __METHOD__ )->fetchFieldValues();
2254 foreach ( $tagIds as $tagId ) {
2256 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2261 $this->mNewTags = implode(
',', $tags );
2275 if ( $this->mTextLoaded == 2 ) {
2276 return $this->loadRevisionData() &&
2277 ( $this->mOldRevisionRecord ===
false || $this->mOldContent )
2278 && $this->mNewContent;
2282 $this->mTextLoaded = 2;
2284 if ( !$this->loadRevisionData() ) {
2288 if ( $this->mOldRevisionRecord ) {
2289 $this->mOldContent = $this->mOldRevisionRecord->getContent(
2291 RevisionRecord::FOR_THIS_USER,
2292 $this->getAuthority()
2294 if ( $this->mOldContent ===
null ) {
2299 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2301 RevisionRecord::FOR_THIS_USER,
2302 $this->getAuthority()
2304 $this->hookRunner->onDifferenceEngineLoadTextAfterNewContentIsLoaded( $this );
2305 if ( $this->mNewContent ===
null ) {
2318 if ( $this->mTextLoaded >= 1 ) {
2319 return $this->loadRevisionData();
2322 $this->mTextLoaded = 1;
2324 if ( !$this->loadRevisionData() ) {
2328 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2330 RevisionRecord::FOR_THIS_USER,
2331 $this->getAuthority()
2334 $this->hookRunner->onDifferenceEngineAfterLoadNewText( $this );
2345 if ( $this->textDiffer ===
null ) {
2347 $this->getContext(),
2348 $this->getDiffLang(),
2349 $this->getConfig()->
get( MainConfigNames::DiffEngine ),
2350 $this->getConfig()->
get( MainConfigNames::ExternalDiffEngine ),
2351 $this->getConfig()->
get( MainConfigNames::Wikidiff2Options )
2354 return $this->textDiffer;
2364 return $this->getTextDiffer()->getFormats();
2374 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.
Interface for objects which can provide a MediaWiki context on request.