84 private const DIFF_VERSION =
'1.41';
112 private $mOldRevisionRecord;
122 private $mNewRevisionRecord;
153 private $mOldContent;
160 private $mNewContent;
166 private $mRevisionsIdsLoaded =
false;
169 protected $mRevisionsLoaded =
false;
172 protected $mTextLoaded = 0;
182 protected $isContentOverridden =
false;
185 protected $mCacheHit =
false;
188 private $cacheHitKey =
null;
196 public $enableDebugComment =
false;
201 protected $mReducedLineNumbers =
false;
204 protected $mMarkPatrolledLink =
null;
207 protected $unhide =
false;
210 protected $mRefreshCache =
false;
221 protected $isSlotDiffRenderer =
false;
227 private $slotDiffOptions = [];
232 private $extraQueryParams = [];
251 private $revisionLoadErrors = [];
261 public function __construct( $context =
null, $old = 0, $new = 0, $rcid = 0,
262 $refreshCache =
false, $unhide =
false
268 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'" );
270 $this->mOldid = $old;
271 $this->mNewid = $new;
272 $this->mRefreshCache = $refreshCache;
273 $this->unhide = $unhide;
275 $services = MediaWikiServices::getInstance();
276 $this->linkRenderer = $services->getLinkRenderer();
277 $this->contentHandlerFactory = $services->getContentHandlerFactory();
278 $this->revisionStore = $services->getRevisionStore();
279 $this->archivedRevisionLookup = $services->getArchivedRevisionLookup();
280 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
281 $this->wikiPageFactory = $services->getWikiPageFactory();
282 $this->userOptionsLookup = $services->getUserOptionsLookup();
283 $this->commentFormatter = $services->getCommentFormatter();
284 $this->dbProvider = $services->getDBLoadBalancerFactory();
285 $this->userGroupManager = $services->getUserGroupManager();
286 $this->userEditTracker = $services->getUserEditTracker();
287 $this->userIdentityUtils = $services->getUserIdentityUtils();
296 if ( $this->isSlotDiffRenderer ) {
297 throw new LogicException( __METHOD__ .
' called in slot diff renderer mode' );
300 if ( $this->slotDiffRenderers ===
null ) {
306 $this->slotDiffRenderers = [];
307 foreach ( $slotContents as $role => $contents ) {
308 if ( $contents[
'new'] && $contents[
'old']
309 && $contents[
'new']->equals( $contents[
'old'] )
314 $handler = ( $contents[
'new'] ?: $contents[
'old'] )->getContentHandler();
315 $this->slotDiffRenderers[$role] = $handler->getSlotDiffRenderer(
317 $this->slotDiffOptions + [
318 'contentLanguage' => $this->
getDiffLang()->getCode(),
325 return $this->slotDiffRenderers;
335 $this->isSlotDiffRenderer =
true;
344 if ( $this->isContentOverridden ) {
346 SlotRecord::MAIN => [
'old' => $this->mOldContent,
'new' => $this->mNewContent ]
352 $newSlots = $this->mNewRevisionRecord->getPrimarySlots()->getSlots();
353 $oldSlots = $this->mOldRevisionRecord ?
354 $this->mOldRevisionRecord->getPrimarySlots()->getSlots() :
360 $roles = array_keys( array_merge( $newSlots, $oldSlots ) );
363 foreach ( $roles as $role ) {
365 'old' => $this->loadSingleSlot(
366 $oldSlots[$role] ??
null,
369 'new' => $this->loadSingleSlot(
370 $newSlots[$role] ??
null,
376 if ( isset( $slots[SlotRecord::MAIN] ) ) {
377 $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
389 private function loadSingleSlot( ?
SlotRecord $slot,
string $which ) {
396 $this->addRevisionLoadError( $which );
406 private function addRevisionLoadError( $which ) {
407 $this->revisionLoadErrors[] = $this->
msg( $which ===
'new'
408 ?
'difference-bad-new-revision' :
'difference-bad-old-revision'
419 return $this->revisionLoadErrors;
426 private function hasNewRevisionLoadError() {
427 foreach ( $this->revisionLoadErrors as $error ) {
428 if ( $error->getKey() ===
'difference-bad-new-revision' ) {
438 return parent::getTitle() ?: Title::makeTitle(
NS_SPECIAL,
'BadTitle/DifferenceEngine' );
448 $this->mReducedLineNumbers = $value;
457 # Default language in which the diff text is written.
459 return $this->mDiffLang;
469 return $this->
getTitle()->getPageLanguage();
476 return $this->mCacheHit;
487 $this->loadRevisionIds();
489 return $this->mOldid;
499 $this->loadRevisionIds();
501 return $this->mNewid;
511 return $this->mOldRevisionRecord ?:
null;
520 return $this->mNewRevisionRecord;
532 if ( $this->
getAuthority()->isAllowed(
'deletedhistory' ) ) {
533 $revRecord = $this->archivedRevisionLookup->getArchivedRevisionRecord(
null, $id );
535 $title = Title::newFromPageIdentity( $revRecord->
getPage() );
537 return SpecialPage::getTitleFor(
'Undelete' )->getFullURL( [
538 'target' => $title->getPrefixedText(),
557 return "[$link $id]";
563 private function showMissingRevision() {
564 $out = $this->getOutput();
567 if ( $this->mOldid && ( !$this->mOldRevisionRecord || !$this->mOldContent ) ) {
568 $missing[] = $this->deletedIdMarker( $this->mOldid );
570 if ( !$this->mNewRevisionRecord || !$this->mNewContent ) {
574 $out->setPageTitleMsg( $this->
msg(
'errorpagetitle' ) );
575 $msg = $this->
msg(
'difference-missing-revision' )
576 ->params( $this->
getLanguage()->listToText( $missing ) )
577 ->numParams( count( $missing ) )
579 $out->addHTML( $msg );
590 $this->mNewRevisionRecord &&
591 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
594 $this->mOldRevisionRecord &&
595 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT )
607 $permStatus = PermissionStatus::newEmpty();
608 if ( $this->mNewPage ) {
609 $performer->
authorizeRead(
'read', $this->mNewPage, $permStatus );
611 if ( $this->mOldPage ) {
612 $performer->
authorizeRead(
'read', $this->mOldPage, $permStatus );
614 return $permStatus->toLegacyErrorArray();
624 ( $this->mOldRevisionRecord &&
625 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) ||
626 ( $this->mNewRevisionRecord &&
627 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) )
637 private function getUserEditCount( $user ): string {
638 $editCount = $this->userEditTracker->getUserEditCount( $user );
639 if ( $editCount ===
null ) {
643 return Html::rawElement(
'div', [
644 'class' =>
'mw-diff-usereditcount',
660 if ( !$this->userIdentityUtils->isNamed( $user ) ) {
663 $userGroups = $this->userGroupManager->getUserGroups( $user );
664 $userGroupLinks = [];
665 foreach ( $userGroups as $group ) {
666 $userGroupLinks[] = UserGroupMembership::getLinkHTML( $group, $this->
getContext() );
668 return Html::rawElement(
'div', [
669 'class' =>
'mw-diff-userroles',
670 ], $this->getLanguage()->commaList( $userGroupLinks ) );
679 private function getUserMetaData( ?
UserIdentity $user ) {
683 return Html::rawElement(
'div', [
684 'class' =>
'mw-diff-usermetadata',
685 ], $this->getUserRoles( $user ) . $this->getUserEditCount( $user ) );
700 $this->loadRevisionData();
702 if ( $this->mOldRevisionRecord && !$this->mOldRevisionRecord->userCan(
703 RevisionRecord::DELETED_TEXT,
711 return !$this->mNewRevisionRecord || $this->mNewRevisionRecord->userCan(
712 RevisionRecord::DELETED_TEXT,
725 return $this->hasDeletedRevision() && ( !$this->unhide ||
726 !$this->isUserAllowedToSeeRevisions( $performer ) );
733 # Allow frames except in certain special cases
734 $out = $this->getOutput();
735 $out->setPreventClickjacking(
false );
736 $out->setRobotPolicy(
'noindex,nofollow' );
739 $this->hookRunner->onDifferenceEngineShowDiffPage( $out );
741 if ( !$this->loadRevisionData() ) {
742 if ( $this->hookRunner->onDifferenceEngineShowDiffPageMaybeShowMissingRevision( $this ) ) {
743 $this->showMissingRevision();
749 $permErrors = $this->getPermissionErrors( $this->
getAuthority() );
756 $query = $this->extraQueryParams;
757 # Carry over 'diffonly' param via navigation links
758 if ( $diffOnly != MediaWikiServices::getInstance()
759 ->getUserOptionsLookup()->getBoolOption( $user,
'diffonly' )
761 $query[
'diffonly'] = $diffOnly;
763 # Cascade unhide param in links for easy deletion browsing
764 if ( $this->unhide ) {
765 $query[
'unhide'] = 1;
768 # Check if one of the revisions is deleted/suppressed
769 $deleted = $this->hasDeletedRevision();
770 $suppressed = $this->hasSuppressedRevision();
771 $allowed = $this->isUserAllowedToSeeRevisions( $this->
getAuthority() );
776 # mOldRevisionRecord is false if the difference engine is called with a "vague" query for
777 # a diff between a version V and its previous version V' AND the version V
778 # is the first version of that article. In that case, V' does not exist.
779 if ( $this->mOldRevisionRecord ===
false ) {
780 if ( $this->mNewPage ) {
781 $out->setPageTitleMsg(
782 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
788 $this->hookRunner->onDifferenceEngineOldHeaderNoOldRev( $oldHeader );
790 $this->hookRunner->onDifferenceEngineViewHeader( $this );
792 if ( !$this->mOldPage || !$this->mNewPage ) {
795 } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
796 $out->setPageTitleMsg(
797 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
801 $out->setPageTitleMsg( $this->msg(
'difference-title-multipage' )->plaintextParams(
802 $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
803 $out->addSubtitle( $this->msg(
'difference-multipage' ) );
807 if ( $samePage && $this->mNewPage &&
808 $this->
getAuthority()->probablyCan(
'edit', $this->mNewPage )
810 if ( $this->mNewRevisionRecord->isCurrent() &&
811 $this->getAuthority()->probablyCan(
'rollback', $this->mNewPage )
813 $rollbackLink = Linker::generateRollback(
814 $this->mNewRevisionRecord,
818 if ( $rollbackLink ) {
819 $out->setPreventClickjacking(
true );
820 $rollback =
"\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
824 if ( $this->userCanEdit( $this->mOldRevisionRecord ) &&
825 $this->userCanEdit( $this->mNewRevisionRecord )
827 $undoLink = $this->linkRenderer->makeKnownLink(
829 $this->msg(
'editundo' )->text(),
830 [
'title' => Linker::titleAttrib(
'undo' ) ],
833 'undoafter' => $this->mOldid,
834 'undo' => $this->mNewid
837 $revisionTools[
'mw-diff-undo'] = $undoLink;
840 # Make "previous revision link"
841 $hasPrevious = $samePage && $this->mOldPage &&
842 $this->revisionStore->getPreviousRevision( $this->mOldRevisionRecord );
843 if ( $hasPrevious ) {
844 $prevlinkQuery = [
'diff' =>
'prev',
'oldid' => $this->mOldid ] + $query;
845 $prevlink = $this->linkRenderer->makeKnownLink(
847 $this->msg(
'previousdiff' )->text(),
848 [
'id' =>
'differences-prevlink' ],
851 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
853 $this->msg(
'previousdiff' )->text(),
855 'class' =>
'mw-diff-revision-history-link-previous'
860 $prevlink =
"\u{00A0}";
863 if ( $this->mOldRevisionRecord->isMinor() ) {
864 $oldminor = ChangesList::flag(
'minor' );
869 $oldRevRecord = $this->mOldRevisionRecord;
871 $ldel = $this->revisionDeleteLink( $oldRevRecord );
872 $oldRevisionHeader = $this->getRevisionHeader( $oldRevRecord,
'complete' );
874 $oldRevComment = $this->commentFormatter
876 $oldRevRecord, $user, !$diffOnly, !$this->unhide,
false
879 if ( $oldRevComment ===
'' ) {
880 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
881 $oldRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
884 $oldHeader =
'<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader .
'</strong></div>' .
885 '<div id="mw-diff-otitle2">' .
886 Linker::revUserTools( $oldRevRecord, !$this->unhide ) .
887 $this->getUserMetaData( $oldRevRecord->getUser() ) .
889 '<div id="mw-diff-otitle3">' . $oldminor . $oldRevComment . $ldel .
'</div>' .
890 '<div id="mw-diff-otitle5">' . $oldChangeTags[0] .
'</div>' .
891 '<div id="mw-diff-otitle4">' . $prevlink .
'</div>';
894 $this->hookRunner->onDifferenceEngineOldHeader(
895 $this, $oldHeader, $prevlink, $oldminor, $diffOnly, $ldel, $this->unhide );
898 $out->addJsConfigVars( [
899 'wgDiffOldId' => $this->mOldid,
900 'wgDiffNewId' => $this->mNewid,
903 # Make "next revision link"
904 # Skip next link on the top revision
905 if ( $samePage && $this->mNewPage && !$this->mNewRevisionRecord->isCurrent() ) {
906 $nextlinkQuery = [
'diff' =>
'next',
'oldid' => $this->mNewid ] + $query;
907 $nextlink = $this->linkRenderer->makeKnownLink(
909 $this->msg(
'nextdiff' )->text(),
910 [
'id' =>
'differences-nextlink' ],
913 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
915 $this->msg(
'nextdiff' )->text(),
917 'class' =>
'mw-diff-revision-history-link-next'
922 $nextlink =
"\u{00A0}";
925 if ( $this->mNewRevisionRecord->isMinor() ) {
926 $newminor = ChangesList::flag(
'minor' );
931 # Handle RevisionDelete links...
932 $rdel = $this->revisionDeleteLink( $this->mNewRevisionRecord );
934 # Allow extensions to define their own revision tools
935 $this->hookRunner->onDiffTools(
936 $this->mNewRevisionRecord,
938 $this->mOldRevisionRecord ?:
null,
942 $formattedRevisionTools = [];
944 foreach ( $revisionTools as $key => $tool ) {
945 $toolClass = is_string( $key ) ? $key :
'mw-diff-tool';
946 $element = Html::rawElement(
948 [
'class' => $toolClass ],
951 $formattedRevisionTools[] = $element;
954 $newRevRecord = $this->mNewRevisionRecord;
956 $newRevisionHeader = $this->getRevisionHeader( $newRevRecord,
'complete' ) .
957 ' ' . implode(
' ', $formattedRevisionTools );
959 $newRevComment = $this->commentFormatter->formatRevision(
960 $newRevRecord, $user, !$diffOnly, !$this->unhide,
false
963 if ( $newRevComment ===
'' ) {
964 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
965 $newRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
968 $newHeader =
'<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader .
'</strong></div>' .
969 '<div id="mw-diff-ntitle2">' . Linker::revUserTools( $newRevRecord, !$this->unhide ) .
971 $this->getUserMetaData( $newRevRecord->getUser() ) .
973 '<div id="mw-diff-ntitle3">' . $newminor . $newRevComment . $rdel .
'</div>' .
974 '<div id="mw-diff-ntitle5">' . $newChangeTags[0] .
'</div>' .
975 '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() .
'</div>';
978 $this->hookRunner->onDifferenceEngineNewHeader( $this, $newHeader,
979 $formattedRevisionTools, $nextlink, $rollback, $newminor, $diffOnly,
980 $rdel, $this->unhide );
983 Html::rawElement(
'div', [
984 'class' =>
'mw-diff-revision-history-links'
987 # If the diff cannot be shown due to a deleted revision, then output
988 # the diff header and links to unhide (if available)...
989 if ( $this->shouldBeHiddenFromUser( $this->
getAuthority() ) ) {
990 $this->showDiffStyle();
991 $multi = $this->getMultiNotice();
992 $out->addHTML( $this->addHeader(
'', $oldHeader, $newHeader, $multi ) );
994 # Give explanation for why revision is not visible
995 $msg = [ $suppressed ?
'rev-suppressed-no-diff' :
'rev-deleted-no-diff' ];
997 # Give explanation and add a link to view the diff...
998 $query = $this->
getRequest()->appendQueryValue(
'unhide',
'1' );
1000 $suppressed ?
'rev-suppressed-unhide-diff' :
'rev-deleted-unhide-diff',
1001 $this->
getTitle()->getFullURL( $query )
1004 $out->addHTML( Html::warningBox( $this->msg( ...$msg )->parse(),
'plainlinks' ) );
1005 # Otherwise, output a regular diff...
1007 # Add deletion notice if the user is viewing deleted content
1010 $msg = $suppressed ?
'rev-suppressed-diff-view' :
'rev-deleted-diff-view';
1011 $notice = Html::warningBox( $this->msg( $msg )->parse(),
'plainlinks' );
1014 # Add an error if the content can't be loaded
1015 $this->getSlotContents();
1016 foreach ( $this->getRevisionLoadErrors() as $msg ) {
1017 $notice .= Html::warningBox( $msg->parse() );
1021 if ( $this->getTextDiffer()->hasFormat(
'inline' ) ) {
1025 $this->showTablePrefixes();
1026 $this->showDiff( $oldHeader, $newHeader, $notice );
1028 $this->renderNewRevision();
1036 private function showTablePrefixes() {
1038 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1039 $parts += $slotDiffRenderer->getTablePrefix( $this->
getContext(), $this->mNewPage );
1042 if ( count( array_filter( $parts ) ) > 0 ) {
1043 $language = $this->getLanguage();
1045 'class' =>
'mw-diff-table-prefix',
1046 'dir' => $language->getDir(),
1047 'lang' => $language->getCode(),
1049 $this->getOutput()->addHTML(
1050 Html::rawElement(
'div', $attrs, implode(
'', $parts ) ) );
1065 if ( $this->mMarkPatrolledLink ===
null ) {
1066 $linkInfo = $this->getMarkPatrolledLinkInfo();
1068 if ( !$linkInfo || !$this->mNewPage ) {
1069 $this->mMarkPatrolledLink =
'';
1071 $this->mMarkPatrolledLink =
' <span class="patrollink" data-mw="interface">[' .
1072 $this->linkRenderer->makeKnownLink(
1074 $this->msg(
'markaspatrolleddiff' )->text(),
1077 'action' =>
'markpatrolled',
1078 'rcid' => $linkInfo[
'rcid'],
1082 $this->hookRunner->onDifferenceEngineMarkPatrolledLink( $this,
1083 $this->mMarkPatrolledLink, $linkInfo[
'rcid'] );
1086 return $this->mMarkPatrolledLink;
1098 $config = $this->getConfig();
1103 $config->get( MainConfigNames::UseRCPatrol ) &&
1105 $this->getAuthority()->probablyCan(
'patrol', $this->mNewPage ) &&
1108 RecentChange::isInRCLifespan( $this->mNewRevisionRecord->getTimestamp(), 21600 )
1111 $change = RecentChange::newFromConds(
1113 'rc_this_oldid' => $this->mNewid,
1114 'rc_patrolled' => RecentChange::PRC_UNPATROLLED
1119 if ( $change && !$change->getPerformerIdentity()->equals( $user ) ) {
1120 $rcid = $change->getAttribute(
'rc_id' );
1131 $this->hookRunner->onDifferenceEngineMarkPatrolledRCID( $rcid, $this, $change, $user );
1135 $this->getOutput()->setPreventClickjacking(
true );
1136 if ( $this->
getAuthority()->isAllowed(
'writeapi' ) ) {
1137 $this->getOutput()->addModules(
'mediawiki.misc-authed-curate' );
1140 return [
'rcid' => $rcid ];
1153 private function revisionDeleteLink(
RevisionRecord $revRecord ) {
1154 $link = Linker::getRevDeleteLink(
1159 if ( $link !==
'' ) {
1160 $link =
"\u{00A0}\u{00A0}\u{00A0}" . $link .
' ';
1172 if ( $this->isContentOverridden ) {
1176 throw new LogicException(
1178 .
' is not supported after calling setContent(). Use setRevisions() instead.'
1182 $out = $this->getOutput();
1183 $revHeader = $this->getRevisionHeader( $this->mNewRevisionRecord );
1184 # Add "current version as of X" title
1185 $out->addHTML(
"<hr class='diff-hr' id='mw-oldid' />
1186 <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
1187 # Page content may be handled by a hooked call instead...
1188 if ( $this->hookRunner->onArticleContentOnDiff( $this, $out ) ) {
1189 $this->loadNewText();
1190 if ( !$this->mNewPage ) {
1196 if ( $this->hasNewRevisionLoadError() ) {
1201 $out->setRevisionId( $this->mNewid );
1202 $out->setRevisionIsCurrent( $this->mNewRevisionRecord->isCurrent() );
1203 $out->setRevisionTimestamp( $this->mNewRevisionRecord->getTimestamp() );
1204 $out->setArticleFlag(
true );
1206 if ( !$this->hookRunner->onArticleRevisionViewCustom(
1207 $this->mNewRevisionRecord, $this->mNewPage, $this->mOldid, $out )
1213 if ( $this->
getTitle()->equals( $this->mNewPage ) ) {
1217 $wikiPage = $this->getWikiPage();
1220 $wikiPage = $this->wikiPageFactory->newFromTitle( $this->mNewPage );
1223 $parserOptions = $wikiPage->makeParserOptions( $this->
getContext() );
1224 $parserOptions->setRenderReason(
'diff-page' );
1226 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
1227 $status = $parserOutputAccess->getParserOutput(
1230 $this->mNewRevisionRecord,
1232 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK |
1234 ParserOutputAccess::OPT_LINKS_UPDATE
1236 if ( $status->isOK() ) {
1237 $parserOutput = $status->getValue();
1239 if ( $this->hookRunner->onDifferenceEngineRenderRevisionAddParserOutput(
1240 $this, $out, $parserOutput, $wikiPage )
1242 $out->addParserOutput( $parserOutput, [
1243 'enableSectionEditLinks' => $this->mNewRevisionRecord->isCurrent()
1244 && $this->getAuthority()->probablyCan(
1246 $this->mNewRevisionRecord->getPage()
1248 'absoluteURLs' => $this->slotDiffOptions[
'expand-url'] ??
false
1254 $out->parseAsInterface(
1255 $status->getWikiText(
false,
false, $this->getLanguage() )
1264 if ( $this->hookRunner->onDifferenceEngineRenderRevisionShowFinalPatrolLink() ) {
1265 # Add redundant patrol link on bottom...
1266 $out->addHTML( $this->markPatrolledLink() );
1280 public function showDiff( $otitle, $ntitle, $notice =
'' ) {
1282 $this->hookRunner->onDifferenceEngineShowDiff( $this );
1284 $diff = $this->getDiff( $otitle, $ntitle, $notice );
1285 if ( $diff ===
false ) {
1286 $this->showMissingRevision();
1290 $this->showDiffStyle();
1291 if ( $this->slotDiffOptions[
'expand-url'] ??
false ) {
1292 $diff = Linker::expandLocalLinks( $diff );
1294 $this->getOutput()->addHTML( $diff );
1302 if ( !$this->isSlotDiffRenderer ) {
1303 $this->getOutput()->addModules(
'mediawiki.diff' );
1304 $this->getOutput()->addModuleStyles( [
1305 'mediawiki.interface.helpers.styles',
1306 'mediawiki.diff.styles'
1308 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1309 $slotDiffRenderer->addModules( $this->getOutput() );
1323 public function getDiff( $otitle, $ntitle, $notice =
'' ) {
1324 $body = $this->getDiffBody();
1325 if ( $body ===
false ) {
1329 $multi = $this->getMultiNotice();
1331 if ( $body ===
'' ) {
1332 $notice .=
'<div class="mw-diff-empty">' .
1333 $this->msg(
'diff-empty' )->parse() .
1337 if ( $this->cacheHitKey !==
null ) {
1338 $body .=
"\n<!-- diff cache key " . htmlspecialchars( $this->cacheHitKey ) .
" -->\n";
1341 return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
1350 $this->mCacheHit =
true;
1352 if ( !$this->isContentOverridden ) {
1353 if ( !$this->loadRevisionData() ) {
1355 } elseif ( $this->mOldRevisionRecord &&
1356 !$this->mOldRevisionRecord->userCan(
1357 RevisionRecord::DELETED_TEXT,
1358 $this->getAuthority()
1362 } elseif ( $this->mNewRevisionRecord &&
1363 !$this->mNewRevisionRecord->userCan(
1364 RevisionRecord::DELETED_TEXT,
1365 $this->getAuthority()
1370 if ( $this->mOldRevisionRecord ===
false || (
1371 $this->mOldRevisionRecord &&
1372 $this->mNewRevisionRecord &&
1373 $this->mOldRevisionRecord->getId() &&
1374 $this->mOldRevisionRecord->getId() == $this->mNewRevisionRecord->getId()
1376 if ( $this->hookRunner->onDifferenceEngineShowEmptyOldContent( $this ) ) {
1384 $services = MediaWikiServices::getInstance();
1385 $cache = $services->getMainWANObjectCache();
1386 $stats = $services->getStatsdDataFactory();
1387 if ( $this->mOldid && $this->mNewid ) {
1388 $key = $cache->makeKey( ...$this->getDiffBodyCacheKeyParams() );
1391 if ( !$this->mRefreshCache ) {
1392 $difftext = $cache->get( $key );
1393 if ( is_string( $difftext ) ) {
1394 $stats->updateCount(
'diff_cache.hit', 1 );
1395 $difftext = $this->localiseDiff( $difftext );
1396 $this->cacheHitKey = $key;
1401 $this->mCacheHit =
false;
1402 $this->cacheHitKey =
null;
1405 if ( !$this->loadText() ) {
1412 $slotContents = $this->getSlotContents();
1413 foreach ( $this->getSlotDiffRenderers() as $role => $slotDiffRenderer ) {
1415 $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role][
'old'],
1416 $slotContents[$role][
'new'] );
1420 if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1423 $difftext .= $this->getSlotHeader( $slotTitle );
1425 $difftext .= $slotDiff;
1429 if ( !$this->hookRunner->onAbortDiffCache( $this ) ) {
1430 $stats->updateCount(
'diff_cache.uncacheable', 1 );
1431 } elseif ( $key !==
false ) {
1432 $stats->updateCount(
'diff_cache.miss', 1 );
1433 $cache->set( $key, $difftext, 7 * 86400 );
1435 $stats->updateCount(
'diff_cache.uncacheable', 1 );
1438 $difftext = $this->localiseDiff( $difftext );
1450 $diffRenderers = $this->getSlotDiffRenderers();
1451 if ( !isset( $diffRenderers[$role] ) ) {
1455 $slotContents = $this->getSlotContents();
1457 $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role][
'old'],
1458 $slotContents[$role][
'new'] );
1462 if ( $slotDiff ===
'' ) {
1466 if ( $role !== SlotRecord::MAIN ) {
1469 $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff;
1472 return $this->localiseDiff( $slotDiff );
1484 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1485 $userLang = $this->getLanguage()->getHtmlCode();
1486 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-header',
'lang' => $userLang ],
1487 Html::element(
'th', [
'colspan' => $columnCount ], $headerText ) );
1498 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1499 $userLang = $this->getLanguage()->getHtmlCode();
1500 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-error',
'lang' => $userLang ],
1501 Html::rawElement(
'td', [
'colspan' => $columnCount ], $errorText ) );
1518 if ( !$this->mOldid || !$this->mNewid ) {
1519 throw new BadMethodCallException(
'mOldid and mNewid must be set to get diff cache key.' );
1525 "old-{$this->mOldid}",
1526 "rev-{$this->mNewid}"
1530 if ( !$this->isSlotDiffRenderer ) {
1531 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1532 $extraKeys = array_merge( $extraKeys, $slotDiffRenderer->getExtraCacheKeys() );
1535 ksort( $extraKeys );
1536 return array_merge( $params, array_values( $extraKeys ) );
1550 $this->mOldid = 123456789;
1551 $this->mNewid = 987654321;
1554 $params = $this->getDiffBodyCacheKeyParams();
1564 if ( array_slice( $params, 0, count( $standardParams ) ) === $standardParams ) {
1565 $params = array_slice( $params, count( $standardParams ) );
1582 $validatedOptions = [];
1583 if ( isset( $options[
'diff-type'] )
1584 && $this->getTextDiffer()->hasFormat( $options[
'diff-type'] )
1586 $validatedOptions[
'diff-type'] = $options[
'diff-type'];
1588 if ( !empty( $options[
'expand-url'] ) ) {
1589 $validatedOptions[
'expand-url'] =
true;
1591 if ( !empty( $options[
'inline-toggle'] ) ) {
1592 $validatedOptions[
'inline-toggle'] =
true;
1594 $this->slotDiffOptions = $validatedOptions;
1605 $this->extraQueryParams = $params;
1625 && $this->isSlotDiffRenderer
1631 throw new Exception( get_class( $this ) .
': could not maintain backwards compatibility. '
1632 .
'Please use a SlotDiffRenderer.' );
1634 return $slotDiffRenderer->getDiff( $old, $new ) . $this->getDebugString();
1650 $slotDiffRenderer = $this->contentHandlerFactory
1652 ->getSlotDiffRenderer( $this->
getContext() );
1656 throw new Exception(
'The slot diff renderer for text content should be a '
1657 .
'TextSlotDiffRenderer subclass' );
1659 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
1669 $differenceEngine =
new self;
1670 $engine = $differenceEngine->getTextDiffer()->getEngineForFormat(
'table' );
1671 if ( $engine ===
'external' ) {
1672 return MediaWikiServices::getInstance()->getMainConfig()
1673 ->get( MainConfigNames::ExternalDiffEngine );
1687 protected function debug( $generator =
"internal" ) {
1688 if ( !$this->enableDebugComment ) {
1691 $data = [ $generator ];
1692 if ( $this->getConfig()->
get( MainConfigNames::ShowHostnames ) ) {
1697 return "<!-- diff generator: " .
1698 implode(
" ", array_map(
"htmlspecialchars", $data ) ) .
1705 private function getDebugString() {
1706 $engine = self::getEngine();
1707 if ( $engine ===
'wikidiff2' ) {
1708 return $this->debug(
'wikidiff2' );
1709 } elseif ( $engine ===
'php' ) {
1710 return $this->debug(
'native PHP' );
1712 return $this->debug(
"external $engine" );
1722 private function localiseDiff( $text ) {
1723 return $this->getTextDiffer()->localize( $this->getTextDiffFormat(), $text );
1735 return preg_replace_callback(
'/<!--LINE (\d+)-->/',
1737 if (
$matches[1] ===
'1' && $this->mReducedLineNumbers ) {
1740 return $this->msg(
'lineno' )->numParams(
$matches[1] )->escaped();
1752 !$this->mOldRevisionRecord || !$this->mNewRevisionRecord
1753 || !$this->mOldPage || !$this->mNewPage
1754 || !$this->mOldPage->equals( $this->mNewPage )
1755 || $this->mOldRevisionRecord->getId() ===
null
1756 || $this->mNewRevisionRecord->getId() ===
null
1758 || $this->mNewPage->getArticleID() !== $this->mOldRevisionRecord->getPageId()
1759 || $this->mNewPage->getArticleID() !== $this->mNewRevisionRecord->getPageId()
1764 if ( $this->mOldRevisionRecord->getTimestamp() > $this->mNewRevisionRecord->getTimestamp() ) {
1765 $oldRevRecord = $this->mNewRevisionRecord;
1766 $newRevRecord = $this->mOldRevisionRecord;
1768 $oldRevRecord = $this->mOldRevisionRecord;
1769 $newRevRecord = $this->mNewRevisionRecord;
1775 $revisionIdList = $this->revisionStore->getRevisionIdsBetween(
1776 $this->mNewPage->getArticleID(),
1782 if ( count( $revisionIdList ) > 0 ) {
1783 foreach ( $revisionIdList as $revisionId ) {
1784 $revision = $this->revisionStore->getRevisionById( $revisionId );
1785 if ( $revision->getUser( RevisionRecord::FOR_THIS_USER, $this->getAuthority() ) ) {
1790 if ( $nEdits > 0 && $nEdits <= 1000 ) {
1792 $newRevUserForGender =
'[HIDDEN]';
1795 $users = $this->revisionStore->getAuthorsBetween(
1796 $this->mNewPage->getArticleID(),
1802 $numUsers = count( $users );
1804 $newRevUser = $newRevRecord->getUser( RevisionRecord::RAW );
1805 $newRevUserText = $newRevUser ? $newRevUser->getName() :
'';
1806 $newRevUserSafe = $newRevRecord->getUser(
1807 RevisionRecord::FOR_THIS_USER,
1810 $newRevUserForGender = $newRevUserSafe ? $newRevUserSafe->getName() :
'[HIDDEN]';
1811 if ( $numUsers == 1 && $users[0]->getName() == $newRevUserText ) {
1814 }
catch ( InvalidArgumentException $e ) {
1818 return self::intermediateEditsMsg( $nEdits, $numUsers, $limit, $newRevUserForGender );
1835 if ( $numUsers === 0 ) {
1836 $msg =
'diff-multi-sameuser';
1838 ->numParams( $numEdits, $numUsers )
1839 ->params( $lastUser )
1841 } elseif ( $numUsers > $limit ) {
1842 $msg =
'diff-multi-manyusers';
1845 $msg =
'diff-multi-otherusers';
1848 return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1856 if ( !$revRecord->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1873 $lang = $this->getLanguage();
1876 $timestamp = $lang->userTimeAndDate( $revtimestamp, $user );
1877 $dateofrev = $lang->userDate( $revtimestamp, $user );
1878 $timeofrev = $lang->userTime( $revtimestamp, $user );
1881 $rev->
isCurrent() ?
'currentrev-asof' :
'revisionasof',
1887 if ( $complete !==
'complete' ) {
1893 if ( $this->userCanEdit( $rev ) ) {
1894 $header = $this->linkRenderer->makeKnownLink(
1898 [
'oldid' => $rev->
getId() ]
1900 $editQuery = [
'action' =>
'edit' ];
1902 $editQuery[
'oldid'] = $rev->
getId();
1905 $key = $this->
getAuthority()->probablyCan(
'edit', $rev->
getPage() ) ?
'editold' :
'viewsourceold';
1906 $msg = $this->msg( $key )->text();
1907 $editLink = $this->linkRenderer->makeKnownLink( $title, $msg, [], $editQuery );
1908 $header .=
' ' . Html::rawElement(
1910 [
'class' =>
'mw-diff-edit' ],
1918 $header .= Html::element(
'span',
1920 'class' =>
'mw-diff-timestamp',
1921 'data-timestamp' =>
wfTimestamp( TS_ISO_8601, $revtimestamp ),
1925 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1926 return Html::rawElement(
1928 [
'class' => Linker::getRevisionDeletedClass( $rev ) ],
1948 public function addHeader( $diff, $otitle, $ntitle, $multi =
'', $notice =
'' ) {
1951 $header = Html::openElement(
'table', [
1957 'diff-type-' . $this->getTextDiffFormat(),
1961 'diff-contentalign-' . $this->getDiffLang()->alignStart(),
1966 'diff-editfont-' . $this->userOptionsLookup->getOption(
1971 'data-mw' =>
'interface',
1973 $userLang = htmlspecialchars( $this->getLanguage()->getHtmlCode() );
1975 if ( !$diff && !$otitle ) {
1977 <tr class=\"diff-title\" lang=\"{$userLang}\">
1978 <td class=\"diff-ntitle\">{$ntitle}</td>
1984 <col class=\"diff-marker\" />
1985 <col class=\"diff-content\" />
1986 <col class=\"diff-marker\" />
1987 <col class=\"diff-content\" />";
1994 if ( $otitle || $ntitle ) {
1996 $deletedClass =
'diff-side-deleted';
1997 $addedClass =
'diff-side-added';
1999 <tr class=\"diff-title\" lang=\"{$userLang}\">
2000 <td colspan=\"$colspan\" class=\"diff-otitle {$deletedClass}\">{$otitle}</td>
2001 <td colspan=\"$colspan\" class=\"diff-ntitle {$addedClass}\">{$ntitle}</td>
2006 if ( $multi !=
'' ) {
2007 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2008 "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
2010 if ( $notice !=
'' ) {
2011 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2012 "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
2015 return $header . $diff .
"</table>";
2026 $this->mOldContent = $oldContent;
2027 $this->mNewContent = $newContent;
2029 $this->mTextLoaded = 2;
2030 $this->mRevisionsLoaded =
true;
2031 $this->isContentOverridden =
true;
2032 $this->slotDiffRenderers =
null;
2043 if ( $oldRevision ) {
2044 $this->mOldRevisionRecord = $oldRevision;
2045 $this->mOldid = $oldRevision->
getId();
2049 $this->mOldContent = $oldRevision->
getContent( SlotRecord::MAIN,
2050 RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
2051 if ( !$this->mOldContent ) {
2052 $this->addRevisionLoadError(
'old' );
2055 $this->mOldPage =
null;
2056 $this->mOldRevisionRecord = $this->mOldid =
false;
2058 $this->mNewRevisionRecord = $newRevision;
2059 $this->mNewid = $newRevision->
getId();
2061 $this->mNewContent = $newRevision->
getContent( SlotRecord::MAIN,
2062 RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
2063 if ( !$this->mNewContent ) {
2064 $this->addRevisionLoadError(
'new' );
2067 $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded =
true;
2068 $this->mTextLoaded = $oldRevision ? 2 : 1;
2069 $this->isContentOverridden =
false;
2070 $this->slotDiffRenderers =
null;
2080 $this->mDiffLang = $lang;
2096 if ( $new ===
'prev' ) {
2098 $newid = intval( $old );
2100 $newRev = $this->revisionStore->getRevisionById( $newid );
2102 $oldRev = $this->revisionStore->getPreviousRevision( $newRev );
2104 $oldid = $oldRev->getId();
2107 } elseif ( $new ===
'next' ) {
2109 $oldid = intval( $old );
2111 $oldRev = $this->revisionStore->getRevisionById( $oldid );
2113 $newRev = $this->revisionStore->getNextRevision( $oldRev );
2115 $newid = $newRev->getId();
2119 $oldid = intval( $old );
2120 $newid = intval( $new );
2124 return [ $oldid, $newid ];
2127 private function loadRevisionIds() {
2128 if ( $this->mRevisionsIdsLoaded ) {
2132 $this->mRevisionsIdsLoaded =
true;
2134 $old = $this->mOldid;
2135 $new = $this->mNewid;
2137 [ $this->mOldid, $this->mNewid ] = self::mapDiffPrevNext( $old, $new );
2138 if ( $new ===
'next' && $this->mNewid ===
false ) {
2139 # if no result, NewId points to the newest old revision. The only newer
2140 # revision is cur, which is "0".
2144 $this->hookRunner->onNewDifferenceEngine(
2146 $this->
getTitle(), $this->mOldid, $this->mNewid, $old, $new );
2163 if ( $this->mRevisionsLoaded ) {
2164 return $this->isContentOverridden ||
2165 ( $this->mOldRevisionRecord !==
null && $this->mNewRevisionRecord !== null );
2169 $this->mRevisionsLoaded =
true;
2171 $this->loadRevisionIds();
2174 if ( $this->mNewid ) {
2175 $this->mNewRevisionRecord = $this->revisionStore->getRevisionById( $this->mNewid );
2177 $this->mNewRevisionRecord = $this->revisionStore->getRevisionByTitle( $this->
getTitle() );
2185 $this->mNewid = $this->mNewRevisionRecord->getId();
2186 $this->mNewPage = $this->mNewid ?
2187 Title::newFromLinkTarget( $this->mNewRevisionRecord->getPageAsLinkTarget() ) :
2191 $this->mOldRevisionRecord =
false;
2192 if ( $this->mOldid ) {
2193 $this->mOldRevisionRecord = $this->revisionStore->getRevisionById( $this->mOldid );
2194 } elseif ( $this->mOldid === 0 ) {
2195 $revRecord = $this->revisionStore->getPreviousRevision( $this->mNewRevisionRecord );
2197 $this->mOldid = $revRecord ? $revRecord->
getId() :
false;
2198 $this->mOldRevisionRecord = $revRecord ??
false;
2201 if ( $this->mOldRevisionRecord ===
null ) {
2205 if ( $this->mOldRevisionRecord && $this->mOldRevisionRecord->getId() ) {
2206 $this->mOldPage = Title::newFromLinkTarget(
2207 $this->mOldRevisionRecord->getPageAsLinkTarget()
2210 $this->mOldPage =
null;
2214 $dbr = $this->dbProvider->getReplicaDatabase();
2215 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
2216 if ( $this->mOldid !==
false ) {
2217 $tagIds = $dbr->newSelectQueryBuilder()
2218 ->select(
'ct_tag_id' )
2219 ->from(
'change_tag' )
2220 ->where( [
'ct_rev_id' => $this->mOldid ] )
2221 ->caller( __METHOD__ )->fetchFieldValues();
2223 foreach ( $tagIds as $tagId ) {
2225 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2230 $this->mOldTags = implode(
',', $tags );
2232 $this->mOldTags =
false;
2235 $tagIds = $dbr->newSelectQueryBuilder()
2236 ->select(
'ct_tag_id' )
2237 ->from(
'change_tag' )
2238 ->where( [
'ct_rev_id' => $this->mNewid ] )
2239 ->caller( __METHOD__ )->fetchFieldValues();
2241 foreach ( $tagIds as $tagId ) {
2243 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2248 $this->mNewTags = implode(
',', $tags );
2262 if ( $this->mTextLoaded == 2 ) {
2263 return $this->loadRevisionData() &&
2264 ( $this->mOldRevisionRecord ===
false || $this->mOldContent )
2265 && $this->mNewContent;
2269 $this->mTextLoaded = 2;
2271 if ( !$this->loadRevisionData() ) {
2275 if ( $this->mOldRevisionRecord ) {
2276 $this->mOldContent = $this->mOldRevisionRecord->getContent(
2278 RevisionRecord::FOR_THIS_USER,
2281 if ( $this->mOldContent ===
null ) {
2286 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2288 RevisionRecord::FOR_THIS_USER,
2291 $this->hookRunner->onDifferenceEngineLoadTextAfterNewContentIsLoaded( $this );
2292 if ( $this->mNewContent ===
null ) {
2305 if ( $this->mTextLoaded >= 1 ) {
2306 return $this->loadRevisionData();
2309 $this->mTextLoaded = 1;
2311 if ( !$this->loadRevisionData() ) {
2315 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2317 RevisionRecord::FOR_THIS_USER,
2321 $this->hookRunner->onDifferenceEngineAfterLoadNewText( $this );
2332 if ( $this->textDiffer ===
null ) {
2335 $this->getDiffLang(),
2336 $this->getConfig()->
get( MainConfigNames::DiffEngine ),
2337 $this->getConfig()->
get( MainConfigNames::ExternalDiffEngine ),
2338 $this->getConfig()->
get( MainConfigNames::Wikidiff2Options )
2341 return $this->textDiffer;
2351 return $this->getTextDiffer()->getFormats();
2361 return $this->slotDiffOptions[
'diff-type'] ??
'table';
trait DeprecationHelper
Trait for issuing warnings on deprecated access.
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.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
setContext(IContextSource $context)
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.
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.
The Message class deals with fetching and processing of interface message into a variety of formats.
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.