17use MediaWiki\Debug\DeprecationHelper;
75 use DeprecationHelper;
83 private const DIFF_VERSION =
'1.41';
111 private $mOldRevisionRecord;
121 private $mNewRevisionRecord;
152 private $mOldContent;
159 private $mNewContent;
165 private $mRevisionsIdsLoaded =
false;
168 protected $mRevisionsLoaded =
false;
171 protected $mTextLoaded = 0;
181 protected $isContentOverridden =
false;
184 protected $mCacheHit =
false;
187 private $cacheHitKey =
null;
195 public $enableDebugComment =
false;
200 protected $mReducedLineNumbers =
false;
203 protected $mMarkPatrolledLink =
null;
206 protected $unhide =
false;
209 protected $mRefreshCache =
false;
220 protected $isSlotDiffRenderer =
false;
226 private $slotDiffOptions = [];
232 private $extraQueryParams = [];
252 private $revisionLoadErrors = [];
262 public function __construct( $context =
null, $old = 0, $new = 0, $rcid = 0,
263 $refreshCache =
false, $unhide =
false
269 wfDebug(
"DifferenceEngine old '$old' new '$new' rcid '$rcid'" );
271 $this->mOldid = $old;
272 $this->mNewid = $new;
273 $this->mRefreshCache = $refreshCache;
274 $this->unhide = $unhide;
276 $services = MediaWikiServices::getInstance();
277 $this->linkRenderer = $services->getLinkRenderer();
278 $this->contentHandlerFactory = $services->getContentHandlerFactory();
279 $this->revisionStore = $services->getRevisionStore();
280 $this->archivedRevisionLookup = $services->getArchivedRevisionLookup();
281 $this->hookRunner =
new HookRunner( $services->getHookContainer() );
282 $this->wikiPageFactory = $services->getWikiPageFactory();
283 $this->userOptionsLookup = $services->getUserOptionsLookup();
284 $this->commentFormatter = $services->getCommentFormatter();
285 $this->dbProvider = $services->getConnectionProvider();
286 $this->userGroupManager = $services->getUserGroupManager();
287 $this->userEditTracker = $services->getUserEditTracker();
288 $this->userIdentityUtils = $services->getUserIdentityUtils();
289 $this->recentChangeLookup = $services->getRecentChangeLookup();
298 if ( $this->isSlotDiffRenderer ) {
299 throw new LogicException( __METHOD__ .
' called in slot diff renderer mode' );
302 if ( $this->slotDiffRenderers ===
null ) {
308 $this->slotDiffRenderers = [];
309 foreach ( $slotContents as $role => $contents ) {
310 if ( $contents[
'new'] && $contents[
'old']
311 && $contents[
'new']->equals( $contents[
'old'] )
316 if ( !$contents[
'new'] && !$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 )
614 $permStatus = PermissionStatus::newEmpty();
615 if ( $this->mNewPage ) {
616 $performer->
authorizeRead(
'read', $this->mNewPage, $permStatus );
618 if ( $this->mOldPage ) {
619 $performer->
authorizeRead(
'read', $this->mOldPage, $permStatus );
630 return $this->hasDeletedRevision() && (
631 ( $this->mOldRevisionRecord &&
632 $this->mOldRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) ||
633 ( $this->mNewRevisionRecord &&
634 $this->mNewRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) )
644 private function getUserEditCount( $user ): string {
645 $editCount = $this->userEditTracker->getUserEditCount( $user );
646 if ( $editCount ===
null ) {
650 return Html::rawElement(
'div', [
651 'class' =>
'mw-diff-usereditcount',
655 $this->getLanguage()->formatNum( $editCount )
667 if ( !$this->userIdentityUtils->isNamed( $user ) ) {
670 $userGroups = $this->userGroupManager->getUserGroups( $user );
671 $userGroupLinks = [];
672 foreach ( $userGroups as $group ) {
673 $userGroupLinks[] = UserGroupMembership::getLinkHTML( $group, $this->
getContext() );
675 return Html::rawElement(
'div', [
676 'class' =>
'mw-diff-userroles',
677 ], $this->getLanguage()->commaList( $userGroupLinks ) );
686 private function getUserMetaData( ?
UserIdentity $user ) {
690 return Html::rawElement(
'div', [
691 'class' =>
'mw-diff-usermetadata',
692 ], $this->getUserRoles( $user ) . $this->getUserEditCount( $user ) );
707 $this->loadRevisionData();
709 if ( $this->mOldRevisionRecord && !$this->mOldRevisionRecord->userCan(
710 RevisionRecord::DELETED_TEXT,
718 return !$this->mNewRevisionRecord || $this->mNewRevisionRecord->userCan(
719 RevisionRecord::DELETED_TEXT,
732 return $this->hasDeletedRevision() && ( !$this->unhide ||
733 !$this->isUserAllowedToSeeRevisions( $performer ) );
740 # Allow frames except in certain special cases
741 $out = $this->getOutput();
742 $out->getMetadata()->setPreventClickjacking(
false );
743 $out->setRobotPolicy(
'noindex,nofollow' );
746 $this->hookRunner->onDifferenceEngineShowDiffPage( $out );
748 if ( !$this->loadRevisionData() ) {
749 if ( $this->hookRunner->onDifferenceEngineShowDiffPageMaybeShowMissingRevision( $this ) ) {
750 $this->showMissingRevision();
755 $user = $this->getUser();
756 $permStatus = $this->authorizeView( $this->getAuthority() );
757 if ( !$permStatus->isGood() ) {
763 $query = $this->extraQueryParams;
764 # Carry over 'diffonly' param via navigation links
765 if ( $diffOnly != MediaWikiServices::getInstance()
766 ->getUserOptionsLookup()->getBoolOption( $user,
'diffonly' )
768 $query[
'diffonly'] = $diffOnly;
770 # Cascade unhide param in links for easy deletion browsing
771 if ( $this->unhide ) {
772 $query[
'unhide'] = 1;
775 # Check if one of the revisions is deleted/suppressed
776 $deleted = $this->hasDeletedRevision();
777 $suppressed = $this->hasSuppressedRevision();
778 $allowed = $this->isUserAllowedToSeeRevisions( $this->getAuthority() );
782 $newMobileFooter =
'';
784 # mOldRevisionRecord is false if the difference engine is called with a "vague" query for
785 # a diff between a version V and its previous version V' AND the version V
786 # is the first version of that article. In that case, V' does not exist.
787 if ( $this->mOldRevisionRecord ===
false ) {
788 if ( $this->mNewPage ) {
789 $out->setPageTitleMsg(
790 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
796 $this->hookRunner->onDifferenceEngineOldHeaderNoOldRev( $oldHeader );
798 $this->hookRunner->onDifferenceEngineViewHeader( $this );
800 if ( !$this->mOldPage || !$this->mNewPage ) {
803 } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
804 $out->setPageTitleMsg(
805 $this->msg(
'difference-title' )->plaintextParams( $this->mNewPage->getPrefixedText() )
809 $out->setPageTitleMsg( $this->msg(
'difference-title-multipage' )->plaintextParams(
810 $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
811 $out->addSubtitle( $this->msg(
'difference-multipage' ) );
815 if ( $samePage && $this->mNewPage &&
816 $this->getAuthority()->probablyCan(
'edit', $this->mNewPage )
818 if ( $this->mNewRevisionRecord->isCurrent() &&
819 $this->getAuthority()->probablyCan(
'rollback', $this->mNewPage )
821 $rollbackLink = Linker::generateRollback(
822 $this->mNewRevisionRecord,
826 if ( $rollbackLink ) {
827 $out->getMetadata()->setPreventClickjacking(
true );
828 $rollback =
"\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
829 $newMobileFooter .= $rollback;
833 if ( $this->userCanEdit( $this->mOldRevisionRecord ) &&
834 $this->userCanEdit( $this->mNewRevisionRecord )
836 $undoLink = $this->linkRenderer->makeKnownLink(
838 $this->msg(
'editundo' )->text(),
839 [
'title' => Linker::titleAttrib(
'undo' ) ],
842 'undoafter' => $this->mOldid,
843 'undo' => $this->mNewid
846 $revisionTools[
'mw-diff-undo'] = $undoLink;
849 # Make "previous revision link"
850 $hasPrevious = $samePage && $this->mOldPage &&
851 $this->revisionStore->getPreviousRevision( $this->mOldRevisionRecord );
852 if ( $hasPrevious ) {
853 $prevlinkQuery = [
'diff' =>
'prev',
'oldid' => $this->mOldid ] + $query;
854 $prevlink = $this->linkRenderer->makeKnownLink(
856 $this->msg(
'previousdiff' )->text(),
857 [
'id' =>
'differences-prevlink' ],
860 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
862 $this->msg(
'previousdiff' )->text(),
864 'class' =>
'mw-diff-revision-history-link-previous'
869 $prevlink =
"\u{00A0}";
872 if ( $this->mOldRevisionRecord->isMinor() ) {
873 $oldminor = ChangesList::flag(
'minor' );
878 $oldRevRecord = $this->mOldRevisionRecord;
880 $ldel = $this->revisionDeleteLink( $oldRevRecord );
881 $oldRevisionHeader = $this->getRevisionHeader( $oldRevRecord,
'complete' );
882 $oldChangeTags = ChangeTags::formatSummaryRow( $this->mOldTags,
'diff', $this->getContext() );
883 $oldRevComment = $this->commentFormatter
885 $oldRevRecord, $user, !$diffOnly, !$this->unhide,
false
888 if ( $oldRevComment ===
'' ) {
889 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
890 $oldRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
893 $oldHeader =
'<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader .
'</strong></div>' .
894 '<div id="mw-diff-otitle2">' .
895 Linker::revUserTools( $oldRevRecord, !$this->unhide ) .
896 $this->getUserMetaData( $oldRevRecord->getUser() ) .
898 '<div id="mw-diff-otitle3">' . $oldminor . $oldRevComment . $ldel .
'</div>' .
899 '<div id="mw-diff-otitle5">' . $oldChangeTags[0] .
'</div>' .
900 '<div id="mw-diff-otitle4">' . $prevlink .
'</div>';
903 $this->hookRunner->onDifferenceEngineOldHeader(
904 $this, $oldHeader, $prevlink, $oldminor, $diffOnly, $ldel, $this->unhide );
907 $out->addJsConfigVars( [
908 'wgDiffOldId' => $this->mOldid,
909 'wgDiffNewId' => $this->mNewid,
912 # Make "next revision link"
913 # Skip next link on the top revision
914 if ( $samePage && $this->mNewPage && !$this->mNewRevisionRecord->isCurrent() ) {
915 $nextlinkQuery = [
'diff' =>
'next',
'oldid' => $this->mNewid ] + $query;
916 $nextlink = $this->linkRenderer->makeKnownLink(
918 $this->msg(
'nextdiff' )->text(),
919 [
'id' =>
'differences-nextlink' ],
922 $breadCrumbs .= $this->linkRenderer->makeKnownLink(
924 $this->msg(
'nextdiff' )->text(),
926 'class' =>
'mw-diff-revision-history-link-next'
931 $nextlink =
"\u{00A0}";
934 if ( $this->mNewRevisionRecord->isMinor() ) {
935 $newminor = ChangesList::flag(
'minor' );
940 # Handle RevisionDelete links...
941 $rdel = $this->revisionDeleteLink( $this->mNewRevisionRecord );
943 # Allow extensions to define their own revision tools
944 $this->hookRunner->onDiffTools(
945 $this->mNewRevisionRecord,
947 $this->mOldRevisionRecord ?:
null,
951 $formattedRevisionTools = [];
953 foreach ( $revisionTools as $key => $tool ) {
954 $toolClass = is_string( $key ) ? $key :
'mw-diff-tool';
955 $element = Html::rawElement(
957 [
'class' => $toolClass ],
960 $formattedRevisionTools[] = $element;
961 $newMobileFooter .= $element;
964 $newRevRecord = $this->mNewRevisionRecord;
966 $newRevisionHeader = $this->getRevisionHeader( $newRevRecord,
'complete' ) .
967 ' ' . implode(
' ', $formattedRevisionTools );
968 $newChangeTags = ChangeTags::formatSummaryRow( $this->mNewTags,
'diff', $this->getContext() );
969 $newRevComment = $this->commentFormatter->formatRevision(
970 $newRevRecord, $user, !$diffOnly, !$this->unhide,
false
973 if ( $newRevComment ===
'' ) {
974 $defaultComment = $this->msg(
'changeslist-nocomment' )->escaped();
975 $newRevComment =
"<span class=\"comment mw-comment-none\">$defaultComment</span>";
978 $newMobileFooter .= Linker::revUserTools( $newRevRecord, !$this->unhide ) .
979 $this->getUserMetaData( $newRevRecord->getUser() );
981 $newHeader =
'<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader .
'</strong></div>' .
982 '<div id="mw-diff-ntitle2">' . Linker::revUserTools( $newRevRecord, !$this->unhide ) .
984 $this->getUserMetaData( $newRevRecord->getUser() ) .
986 '<div id="mw-diff-ntitle3">' . $newminor . $newRevComment . $rdel .
'</div>' .
987 '<div id="mw-diff-ntitle5">' . $newChangeTags[0] .
'</div>' .
988 '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() .
'</div>';
991 $this->hookRunner->onDifferenceEngineNewHeader( $this, $newHeader,
992 $formattedRevisionTools, $nextlink, $rollback, $newminor, $diffOnly,
993 $rdel, $this->unhide );
996 Html::rawElement(
'div', [
997 'class' =>
'mw-diff-revision-history-links'
1002 Html::rawElement(
'div', [
1003 'class' =>
'mw-diff-mobile-footer'
1004 ], $newMobileFooter )
1006 $addMessageBoxStyles =
false;
1007 # If the diff cannot be shown due to a deleted revision, then output
1008 # the diff header and links to unhide (if available)...
1009 if ( $this->shouldBeHiddenFromUser( $this->getAuthority() ) ) {
1010 $this->showDiffStyle();
1011 $multi = $this->getMultiNotice();
1012 $out->addHTML( $this->addHeader(
'', $oldHeader, $newHeader, $multi ) );
1014 # Give explanation for why revision is not visible
1015 $msg = [ $suppressed ?
'rev-suppressed-no-diff' :
'rev-deleted-no-diff' ];
1017 # Give explanation and add a link to view the diff...
1018 $query = $this->getRequest()->appendQueryValue(
'unhide',
'1' );
1020 $suppressed ?
'rev-suppressed-unhide-diff' :
'rev-deleted-unhide-diff',
1021 $this->getTitle()->getFullURL( $query )
1024 $out->addHTML( Html::warningBox( $this->msg( ...$msg )->parse(),
'plainlinks' ) );
1025 $addMessageBoxStyles =
true;
1026 # Otherwise, output a regular diff...
1028 # Add deletion notice if the user is viewing deleted content
1031 $msg = $suppressed ?
'rev-suppressed-diff-view' :
'rev-deleted-diff-view';
1032 $notice = Html::warningBox( $this->msg( $msg )->parse(),
'plainlinks' );
1033 $addMessageBoxStyles =
true;
1036 # Add an error if the content can't be loaded
1037 $this->getSlotContents();
1038 foreach ( $this->getRevisionLoadErrors() as $msg ) {
1039 $notice .= Html::warningBox( $msg->parse() );
1040 $addMessageBoxStyles =
true;
1044 if ( $this->getTextDiffer()->hasFormat(
'inline' ) ) {
1048 $this->showTablePrefixes();
1049 $this->showDiff( $oldHeader, $newHeader, $notice );
1051 $this->renderNewRevision();
1055 if ( $this->hookRunner->onDifferenceEngineRenderRevisionShowFinalPatrolLink() ) {
1056 # Add redundant patrol link on bottom...
1057 $out->addHTML( $this->markPatrolledLink() );
1060 if ( $addMessageBoxStyles ) {
1061 $out->addModuleStyles(
'mediawiki.codex.messagebox.styles' );
1068 private function showTablePrefixes() {
1070 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1071 $parts += $slotDiffRenderer->getTablePrefix( $this->getContext(), $this->mNewPage );
1074 if ( count( array_filter( $parts ) ) > 0 ) {
1075 $language = $this->getLanguage();
1077 'class' =>
'mw-diff-table-prefix',
1078 'dir' => $language->getDir(),
1079 'lang' => $language->getCode(),
1081 $this->getOutput()->addHTML(
1082 Html::rawElement(
'div', $attrs, implode(
'', $parts ) ) );
1098 if ( $this->mMarkPatrolledLink ===
null ) {
1099 $linkInfo = $this->getMarkPatrolledLinkInfo();
1101 if ( !$linkInfo || !$this->mNewPage ) {
1102 $this->mMarkPatrolledLink =
'';
1104 $patrolLinkClass =
'patrollink';
1105 $this->mMarkPatrolledLink =
' <span class="' . $patrolLinkClass .
'" data-mw="interface">[' .
1106 $this->linkRenderer->makeKnownLink(
1108 $this->msg(
'markaspatrolleddiff' )->text(),
1111 'action' =>
'markpatrolled',
1112 'rcid' => $linkInfo[
'rcid'],
1116 $this->hookRunner->onDifferenceEngineMarkPatrolledLink( $this,
1117 $this->mMarkPatrolledLink, $linkInfo[
'rcid'] );
1120 return $this->mMarkPatrolledLink;
1131 $user = $this->getUser();
1132 $config = $this->getConfig();
1137 $config->get( MainConfigNames::UseRCPatrol ) &&
1139 $this->getAuthority()->probablyCan(
'patrol', $this->mNewPage ) &&
1142 RecentChange::isInRCLifespan( $this->mNewRevisionRecord->getTimestamp(), 21600 )
1145 $change = $this->recentChangeLookup->getRecentChangeByConds(
1147 'rc_this_oldid' => $this->mNewid,
1148 'rc_patrolled' => RecentChange::PRC_UNPATROLLED
1153 if ( $change && !$change->getPerformerIdentity()->equals( $user ) ) {
1154 $rcid = $change->getAttribute(
'rc_id' );
1165 $this->hookRunner->onDifferenceEngineMarkPatrolledRCID( $rcid, $this, $change, $user );
1169 $this->getOutput()->getMetadata()->setPreventClickjacking(
true );
1170 $this->getOutput()->addModules(
'mediawiki.misc-authed-curate' );
1172 return [
'rcid' => $rcid ];
1185 private function revisionDeleteLink(
RevisionRecord $revRecord ) {
1186 $link = Linker::getRevDeleteLink(
1187 $this->getAuthority(),
1191 if ( $link !==
'' ) {
1192 $link =
"\u{00A0}\u{00A0}\u{00A0}" . $link .
' ';
1204 if ( $this->isContentOverridden ) {
1208 throw new LogicException(
1210 .
' is not supported after calling setContent(). Use setRevisions() instead.'
1214 $out = $this->getOutput();
1215 $revHeader = $this->getRevisionHeader( $this->mNewRevisionRecord );
1216 # Add "current version as of X" title
1217 $out->addHTML(
"<hr class='diff-hr' id='mw-oldid' />
1218 <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
1219 # Page content may be handled by a hooked call instead...
1220 if ( $this->hookRunner->onArticleContentOnDiff( $this, $out ) ) {
1221 $this->loadNewText();
1222 if ( !$this->mNewPage ) {
1228 if ( $this->hasNewRevisionLoadError() ) {
1233 $out->setRevisionId( $this->mNewid );
1234 $out->setRevisionIsCurrent( $this->mNewRevisionRecord->isCurrent() );
1235 $out->getMetadata()->setRevisionTimestamp( $this->mNewRevisionRecord->getTimestamp() );
1236 $out->setArticleFlag(
true );
1238 if ( !$this->hookRunner->onArticleRevisionViewCustom(
1239 $this->mNewRevisionRecord, $this->mNewPage, $this->mOldid, $out )
1245 if ( $this->getTitle()->equals( $this->mNewPage ) ) {
1249 $wikiPage = $this->getWikiPage();
1252 $wikiPage = $this->wikiPageFactory->newFromTitle( $this->mNewPage );
1255 $parserOptions = $wikiPage->makeParserOptions( $this->getContext() );
1256 $parserOptions->setRenderReason(
'diff-page' );
1258 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
1259 $status = $parserOutputAccess->getParserOutput(
1262 $this->mNewRevisionRecord,
1264 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK |
1266 ParserOutputAccess::OPT_LINKS_UPDATE
1268 if ( $status->isOK() ) {
1269 $parserOutput = $status->getValue();
1271 if ( $this->hookRunner->onDifferenceEngineRenderRevisionAddParserOutput(
1272 $this, $out, $parserOutput, $wikiPage )
1274 $out->addParserOutput( $parserOutput, $parserOptions, [
1275 'enableSectionEditLinks' => $this->mNewRevisionRecord->isCurrent()
1276 && $this->getAuthority()->probablyCan(
1278 $this->mNewRevisionRecord->getPage()
1280 'absoluteURLs' => $this->slotDiffOptions[
'expand-url'] ??
false
1284 $out->addModuleStyles(
'mediawiki.codex.messagebox.styles' );
1285 foreach ( $status->getMessages() as $msg ) {
1286 $out->addHTML( Html::errorBox(
1287 $this->msg( $msg )->parse()
1305 public function showDiff( $otitle, $ntitle, $notice =
'' ) {
1307 $this->hookRunner->onDifferenceEngineShowDiff( $this );
1309 $diff = $this->getDiff( $otitle, $ntitle, $notice );
1310 if ( $diff ===
false ) {
1311 $this->showMissingRevision();
1315 $this->showDiffStyle();
1316 if ( $this->slotDiffOptions[
'expand-url'] ??
false ) {
1317 $diff = Linker::expandLocalLinks( $diff );
1319 $this->getOutput()->addHTML( $diff );
1327 if ( !$this->isSlotDiffRenderer ) {
1328 $this->getOutput()->addModules(
'mediawiki.diff' );
1329 $this->getOutput()->addModuleStyles( [
1330 'mediawiki.interface.helpers.styles',
1331 'mediawiki.diff.styles'
1333 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1334 $slotDiffRenderer->addModules( $this->getOutput() );
1348 public function getDiff( $otitle, $ntitle, $notice =
'' ) {
1349 $body = $this->getDiffBody();
1350 if ( $body ===
false ) {
1354 $multi = $this->getMultiNotice();
1356 if ( $body ===
'' ) {
1357 $notice .=
'<div class="mw-diff-empty">' .
1358 $this->msg(
'diff-empty' )->parse() .
1362 if ( $this->cacheHitKey !==
null ) {
1363 $body .=
"\n<!-- diff cache key " . htmlspecialchars( $this->cacheHitKey ) .
" -->\n";
1366 return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
1369 private function incrementStats(
string $cacheStatus ): void {
1371 $stats->getCounter(
'diff_cache_total' )
1372 ->setLabel(
'status', $cacheStatus )
1373 ->copyToStatsdAt(
'diff_cache.' . $cacheStatus )
1383 $this->mCacheHit =
true;
1385 if ( !$this->isContentOverridden ) {
1386 if ( !$this->loadRevisionData() ) {
1388 } elseif ( $this->mOldRevisionRecord &&
1389 !$this->mOldRevisionRecord->userCan(
1390 RevisionRecord::DELETED_TEXT,
1391 $this->getAuthority()
1395 } elseif ( $this->mNewRevisionRecord &&
1396 !$this->mNewRevisionRecord->userCan(
1397 RevisionRecord::DELETED_TEXT,
1398 $this->getAuthority()
1403 if ( $this->mOldRevisionRecord ===
false || (
1404 $this->mOldRevisionRecord &&
1405 $this->mNewRevisionRecord &&
1406 $this->mOldRevisionRecord->getId() &&
1407 $this->mOldRevisionRecord->getId() == $this->mNewRevisionRecord->getId()
1409 if ( $this->hookRunner->onDifferenceEngineShowEmptyOldContent( $this ) ) {
1417 $services = MediaWikiServices::getInstance();
1418 $cache = $services->getMainWANObjectCache();
1419 $stats = $services->getStatsdDataFactory();
1420 if ( $this->mOldid && $this->mNewid ) {
1421 $key = $cache->makeKey( ...$this->getDiffBodyCacheKeyParams() );
1424 if ( !$this->mRefreshCache ) {
1425 $difftext = $cache->get( $key );
1426 if ( is_string( $difftext ) ) {
1427 $this->incrementStats(
'hit' );
1428 $difftext = $this->localiseDiff( $difftext );
1429 $this->cacheHitKey = $key;
1434 $this->mCacheHit =
false;
1435 $this->cacheHitKey =
null;
1438 if ( !$this->loadText() ) {
1445 $slotContents = $this->getSlotContents();
1446 foreach ( $this->getSlotDiffRenderers() as $role => $slotDiffRenderer ) {
1448 $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role][
'old'],
1449 $slotContents[$role][
'new'] );
1453 if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1456 $difftext .= $this->getSlotHeader( $slotTitle );
1458 $difftext .= $slotDiff;
1462 if ( !$this->hookRunner->onAbortDiffCache( $this ) ) {
1463 $this->incrementStats(
'uncacheable' );
1464 } elseif ( $key !==
false ) {
1465 $this->incrementStats(
'miss' );
1466 $cache->set( $key, $difftext, 7 * 86400 );
1468 $this->incrementStats(
'uncacheable' );
1471 $difftext = $this->localiseDiff( $difftext );
1483 $diffRenderers = $this->getSlotDiffRenderers();
1484 if ( !isset( $diffRenderers[$role] ) ) {
1488 $slotContents = $this->getSlotContents();
1490 $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role][
'old'],
1491 $slotContents[$role][
'new'] );
1495 if ( $slotDiff ===
'' ) {
1499 if ( $role !== SlotRecord::MAIN ) {
1502 $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff;
1505 return $this->localiseDiff( $slotDiff );
1516 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1517 $userLang = $this->getLanguage()->getHtmlCode();
1518 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-header',
'lang' => $userLang ],
1519 Html::element(
'th', [
'colspan' => $columnCount ], $headerText ) );
1530 $columnCount = $this->mOldRevisionRecord ? 4 : 2;
1531 $userLang = $this->getLanguage()->getHtmlCode();
1532 return Html::rawElement(
'tr', [
'class' =>
'mw-diff-slot-error',
'lang' => $userLang ],
1533 Html::rawElement(
'td', [
'colspan' => $columnCount ], $errorText ) );
1550 if ( !$this->mOldid || !$this->mNewid ) {
1551 throw new BadMethodCallException(
'mOldid and mNewid must be set to get diff cache key.' );
1557 "old-{$this->mOldid}",
1558 "rev-{$this->mNewid}"
1562 if ( !$this->isSlotDiffRenderer ) {
1563 foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1564 $extraKeys = array_merge( $extraKeys, $slotDiffRenderer->getExtraCacheKeys() );
1567 ksort( $extraKeys );
1568 return array_merge( $params, array_values( $extraKeys ) );
1582 $this->mOldid = 123456789;
1583 $this->mNewid = 987654321;
1586 $params = $this->getDiffBodyCacheKeyParams();
1596 if ( array_slice( $params, 0, count( $standardParams ) ) === $standardParams ) {
1597 $params = array_slice( $params, count( $standardParams ) );
1614 $validatedOptions = [];
1615 if ( isset( $options[
'diff-type'] )
1616 && $this->getTextDiffer()->hasFormat( $options[
'diff-type'] )
1618 $validatedOptions[
'diff-type'] = $options[
'diff-type'];
1620 if ( !empty( $options[
'expand-url'] ) ) {
1621 $validatedOptions[
'expand-url'] =
true;
1623 if ( !empty( $options[
'inline-toggle'] ) ) {
1624 $validatedOptions[
'inline-toggle'] =
true;
1626 $this->slotDiffOptions = $validatedOptions;
1637 $this->extraQueryParams = $params;
1654 $slotDiffRenderer = $new->
getContentHandler()->getSlotDiffRenderer( $this->getContext() );
1657 && $this->isSlotDiffRenderer
1663 throw new LogicException( get_class( $this ) .
': could not maintain backwards compatibility. '
1664 .
'Please use a SlotDiffRenderer.' );
1666 return $slotDiffRenderer->getDiff( $old, $new ) . $this->getDebugString();
1682 $slotDiffRenderer = $this->contentHandlerFactory
1684 ->getSlotDiffRenderer( $this->getContext() );
1688 throw new LogicException(
'The slot diff renderer for text content should be a '
1689 .
'TextSlotDiffRenderer subclass' );
1691 return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
1701 $differenceEngine =
new self;
1702 $engine = $differenceEngine->getTextDiffer()->getEngineForFormat(
'table' );
1703 if ( $engine ===
'external' ) {
1704 return MediaWikiServices::getInstance()->getMainConfig()
1705 ->get( MainConfigNames::ExternalDiffEngine );
1719 protected function debug( $generator =
"internal" ) {
1720 if ( !$this->enableDebugComment ) {
1723 $data = [ $generator ];
1724 if ( $this->getConfig()->
get( MainConfigNames::ShowHostnames ) ) {
1729 return "<!-- diff generator: " .
1730 implode(
" ", array_map(
"htmlspecialchars", $data ) ) .
1737 private function getDebugString() {
1738 $engine = self::getEngine();
1739 if ( $engine ===
'wikidiff2' ) {
1740 return $this->debug(
'wikidiff2' );
1741 } elseif ( $engine ===
'php' ) {
1742 return $this->debug(
'native PHP' );
1744 return $this->debug(
"external $engine" );
1754 private function localiseDiff( $text ) {
1755 return $this->getTextDiffer()->localize( $this->getTextDiffFormat(), $text );
1767 return preg_replace_callback(
'/<!--LINE (\d+)-->/',
1769 if (
$matches[1] ===
'1' && $this->mReducedLineNumbers ) {
1772 return $this->msg(
'lineno' )->numParams(
$matches[1] )->escaped();
1784 !$this->mOldRevisionRecord || !$this->mNewRevisionRecord
1785 || !$this->mOldPage || !$this->mNewPage
1786 || !$this->mOldPage->equals( $this->mNewPage )
1787 || $this->mOldRevisionRecord->getId() ===
null
1788 || $this->mNewRevisionRecord->getId() ===
null
1790 || $this->mNewPage->getArticleID() !== $this->mOldRevisionRecord->getPageId()
1791 || $this->mNewPage->getArticleID() !== $this->mNewRevisionRecord->getPageId()
1796 if ( $this->mOldRevisionRecord->getTimestamp() > $this->mNewRevisionRecord->getTimestamp() ) {
1797 $oldRevRecord = $this->mNewRevisionRecord;
1798 $newRevRecord = $this->mOldRevisionRecord;
1800 $oldRevRecord = $this->mOldRevisionRecord;
1801 $newRevRecord = $this->mNewRevisionRecord;
1807 $revisionIdList = $this->revisionStore->getRevisionIdsBetween(
1808 $this->mNewPage->getArticleID(),
1814 if ( count( $revisionIdList ) > 0 ) {
1815 foreach ( $revisionIdList as $revisionId ) {
1816 $revision = $this->revisionStore->getRevisionById( $revisionId );
1817 if ( $revision->getUser( RevisionRecord::FOR_THIS_USER, $this->getAuthority() ) ) {
1822 if ( $nEdits > 0 && $nEdits <= 1000 ) {
1824 $newRevUserForGender =
'[HIDDEN]';
1827 $users = $this->revisionStore->getAuthorsBetween(
1828 $this->mNewPage->getArticleID(),
1834 $numUsers = count( $users );
1836 $newRevUser = $newRevRecord->getUser( RevisionRecord::RAW );
1837 $newRevUserText = $newRevUser ? $newRevUser->getName() :
'';
1838 $newRevUserSafe = $newRevRecord->getUser(
1839 RevisionRecord::FOR_THIS_USER,
1840 $this->getAuthority()
1842 $newRevUserForGender = $newRevUserSafe ? $newRevUserSafe->getName() :
'[HIDDEN]';
1843 if ( $numUsers == 1 && $users[0]->getName() == $newRevUserText ) {
1846 }
catch ( InvalidArgumentException ) {
1850 return self::intermediateEditsMsg( $nEdits, $numUsers, $limit, $newRevUserForGender );
1867 if ( $numUsers === 0 ) {
1868 $msg =
'diff-multi-sameuser';
1870 ->numParams( $numEdits, $numUsers )
1871 ->params( $lastUser )
1873 } elseif ( $numUsers > $limit ) {
1874 $msg =
'diff-multi-manyusers';
1877 $msg =
'diff-multi-otherusers';
1880 return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1888 if ( !$revRecord->
userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
1905 $lang = $this->getLanguage();
1906 $user = $this->getUser();
1908 $timestamp = $lang->userTimeAndDate( $revtimestamp, $user );
1909 $dateofrev = $lang->userDate( $revtimestamp, $user );
1910 $timeofrev = $lang->userTime( $revtimestamp, $user );
1912 $header = $this->msg(
1913 $rev->
isCurrent() ?
'currentrev-asof' :
'revisionasof',
1919 if ( $complete !==
'complete' ) {
1920 return $header->escaped();
1925 if ( $this->userCanEdit( $rev ) ) {
1926 $header = $this->linkRenderer->makeKnownLink(
1930 [
'oldid' => $rev->
getId() ]
1932 $editQuery = [
'action' =>
'edit' ];
1934 $editQuery[
'oldid'] = $rev->
getId();
1937 $key = $this->getAuthority()->probablyCan(
'edit', $rev->
getPage() ) ?
'editold' :
'viewsourceold';
1938 $msg = $this->msg( $key )->text();
1939 $editLink = $this->linkRenderer->makeKnownLink( $title, $msg, [], $editQuery );
1940 $header .=
' ' . Html::rawElement(
1942 [
'class' =>
'mw-diff-edit' ],
1946 $header = $header->escaped();
1950 $header .= Html::element(
'span',
1952 'class' =>
'mw-diff-timestamp',
1953 'data-timestamp' =>
wfTimestamp( TS_ISO_8601, $revtimestamp ),
1957 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1958 return Html::rawElement(
1960 [
'class' => Linker::getRevisionDeletedClass( $rev ) ],
1980 public function addHeader( $diff, $otitle, $ntitle, $multi =
'', $notice =
'' ) {
1983 $header = Html::openElement(
'table', [
1989 'diff-type-' . $this->getTextDiffFormat(),
1993 'diff-contentalign-' . $this->getDiffLang()->alignStart(),
1998 'diff-editfont-' . $this->userOptionsLookup->getOption(
2003 'data-mw' =>
'interface',
2005 $userLang = htmlspecialchars( $this->getLanguage()->getHtmlCode() );
2007 if ( !$diff && !$otitle ) {
2009 <tr class=\"diff-title\" lang=\"{$userLang}\">
2010 <td class=\"diff-ntitle\">{$ntitle}</td>
2016 <col class=\"diff-marker\" />
2017 <col class=\"diff-content\" />
2018 <col class=\"diff-marker\" />
2019 <col class=\"diff-content\" />";
2026 if ( $otitle || $ntitle ) {
2028 $deletedClass =
'diff-side-deleted';
2029 $addedClass =
'diff-side-added';
2031 <tr class=\"diff-title\" lang=\"{$userLang}\">
2032 <td colspan=\"$colspan\" class=\"diff-otitle {$deletedClass}\">{$otitle}</td>
2033 <td colspan=\"$colspan\" class=\"diff-ntitle {$addedClass}\">{$ntitle}</td>
2038 if ( $multi !=
'' ) {
2039 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2040 "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
2042 if ( $notice !=
'' ) {
2043 $header .=
"<tr><td colspan=\"{$multiColspan}\" " .
2044 "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
2047 return $header . $diff .
"</table>";
2058 $this->mOldContent = $oldContent;
2059 $this->mNewContent = $newContent;
2061 $this->mTextLoaded = 2;
2062 $this->mRevisionsLoaded =
true;
2063 $this->isContentOverridden =
true;
2064 $this->slotDiffRenderers =
null;
2075 if ( $oldRevision ) {
2076 $this->mOldRevisionRecord = $oldRevision;
2077 $this->mOldid = $oldRevision->
getId();
2078 $this->mOldPage = Title::newFromPageIdentity( $oldRevision->
getPage() );
2081 $this->mOldContent = $oldRevision->
getContent( SlotRecord::MAIN,
2082 RevisionRecord::FOR_THIS_USER, $this->getAuthority() );
2083 if ( !$this->mOldContent ) {
2084 $this->addRevisionLoadError(
'old' );
2087 $this->mOldPage =
null;
2088 $this->mOldRevisionRecord = $this->mOldid =
false;
2090 $this->mNewRevisionRecord = $newRevision;
2091 $this->mNewid = $newRevision->
getId();
2092 $this->mNewPage = Title::newFromPageIdentity( $newRevision->
getPage() );
2093 $this->mNewContent = $newRevision->
getContent( SlotRecord::MAIN,
2094 RevisionRecord::FOR_THIS_USER, $this->getAuthority() );
2095 if ( !$this->mNewContent ) {
2096 $this->addRevisionLoadError(
'new' );
2099 $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded =
true;
2100 $this->mTextLoaded = $oldRevision ? 2 : 1;
2101 $this->isContentOverridden =
false;
2102 $this->slotDiffRenderers =
null;
2112 $this->mDiffLang = $lang;
2128 if ( $new ===
'prev' ) {
2130 $newid = intval( $old );
2132 $newRev = $this->revisionStore->getRevisionById( $newid );
2134 $oldRev = $this->revisionStore->getPreviousRevision( $newRev );
2136 $oldid = $oldRev->getId();
2139 } elseif ( $new ===
'next' ) {
2141 $oldid = intval( $old );
2143 $oldRev = $this->revisionStore->getRevisionById( $oldid );
2145 $newRev = $this->revisionStore->getNextRevision( $oldRev );
2147 $newid = $newRev->getId();
2151 $oldid = intval( $old );
2152 $newid = intval( $new );
2156 return [ $oldid, $newid ];
2159 private function loadRevisionIds() {
2160 if ( $this->mRevisionsIdsLoaded ) {
2164 $this->mRevisionsIdsLoaded =
true;
2166 $old = $this->mOldid;
2167 $new = $this->mNewid;
2169 [ $this->mOldid, $this->mNewid ] = self::mapDiffPrevNext( $old, $new );
2170 if ( $new ===
'next' && $this->mNewid ===
false ) {
2171 # if no result, NewId points to the newest old revision. The only newer
2172 # revision is cur, which is "0".
2176 $this->hookRunner->onNewDifferenceEngine(
2178 $this->getTitle(), $this->mOldid, $this->mNewid, $old, $new );
2195 if ( $this->mRevisionsLoaded ) {
2196 return $this->isContentOverridden ||
2197 ( $this->mOldRevisionRecord !==
null && $this->mNewRevisionRecord !== null );
2201 $this->mRevisionsLoaded =
true;
2203 $this->loadRevisionIds();
2206 if ( $this->mNewid ) {
2207 $this->mNewRevisionRecord = $this->revisionStore->getRevisionById( $this->mNewid );
2209 $this->mNewRevisionRecord = $this->revisionStore->getRevisionByTitle( $this->getTitle() );
2217 $this->mNewid = $this->mNewRevisionRecord->getId();
2218 $this->mNewPage = $this->mNewid ?
2219 Title::newFromPageIdentity( $this->mNewRevisionRecord->getPage() ) :
2223 $this->mOldRevisionRecord =
false;
2224 if ( $this->mOldid ) {
2225 $this->mOldRevisionRecord = $this->revisionStore->getRevisionById( $this->mOldid );
2226 } elseif ( $this->mOldid === 0 ) {
2227 $revRecord = $this->revisionStore->getPreviousRevision( $this->mNewRevisionRecord );
2229 $this->mOldid = $revRecord ? $revRecord->
getId() :
false;
2230 $this->mOldRevisionRecord = $revRecord ??
false;
2233 if ( $this->mOldRevisionRecord ===
null ) {
2237 if ( $this->mOldRevisionRecord && $this->mOldRevisionRecord->getId() ) {
2238 $this->mOldPage = Title::newFromPageIdentity( $this->mOldRevisionRecord->getPage() );
2240 $this->mOldPage =
null;
2244 $dbr = $this->dbProvider->getReplicaDatabase();
2245 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
2246 if ( $this->mOldid !==
false ) {
2247 $tagIds = $dbr->newSelectQueryBuilder()
2248 ->select(
'ct_tag_id' )
2249 ->from(
'change_tag' )
2250 ->where( [
'ct_rev_id' => $this->mOldid ] )
2251 ->caller( __METHOD__ )->fetchFieldValues();
2253 foreach ( $tagIds as $tagId ) {
2255 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2260 $this->mOldTags = implode(
',', $tags );
2262 $this->mOldTags =
false;
2265 $tagIds = $dbr->newSelectQueryBuilder()
2266 ->select(
'ct_tag_id' )
2267 ->from(
'change_tag' )
2268 ->where( [
'ct_rev_id' => $this->mNewid ] )
2269 ->caller( __METHOD__ )->fetchFieldValues();
2271 foreach ( $tagIds as $tagId ) {
2273 $tags[] = $changeTagDefStore->getName( (
int)$tagId );
2278 $this->mNewTags = implode(
',', $tags );
2291 if ( $this->mTextLoaded == 2 ) {
2292 return $this->loadRevisionData() &&
2293 ( $this->mOldRevisionRecord ===
false || $this->mOldContent )
2294 && $this->mNewContent;
2298 $this->mTextLoaded = 2;
2300 if ( !$this->loadRevisionData() ) {
2304 if ( $this->mOldRevisionRecord ) {
2305 $this->mOldContent = $this->mOldRevisionRecord->getContent(
2307 RevisionRecord::FOR_THIS_USER,
2308 $this->getAuthority()
2310 if ( $this->mOldContent ===
null ) {
2315 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2317 RevisionRecord::FOR_THIS_USER,
2318 $this->getAuthority()
2320 $this->hookRunner->onDifferenceEngineLoadTextAfterNewContentIsLoaded( $this );
2321 if ( $this->mNewContent ===
null ) {
2334 if ( $this->mTextLoaded >= 1 ) {
2335 return $this->loadRevisionData();
2338 $this->mTextLoaded = 1;
2340 if ( !$this->loadRevisionData() ) {
2344 $this->mNewContent = $this->mNewRevisionRecord->getContent(
2346 RevisionRecord::FOR_THIS_USER,
2347 $this->getAuthority()
2350 $this->hookRunner->onDifferenceEngineAfterLoadNewText( $this );
2361 if ( $this->textDiffer ===
null ) {
2363 $this->getContext(),
2364 $this->getDiffLang(),
2365 $this->getConfig()->
get( MainConfigNames::DiffEngine ),
2366 $this->getConfig()->
get( MainConfigNames::ExternalDiffEngine ),
2367 $this->getConfig()->
get( MainConfigNames::Wikidiff2Options )
2370 return $this->textDiffer;
2380 return $this->getTextDiffer()->getFormats();
2390 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.
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...
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?
authorizeView(Authority $performer)
Check whether the user can read both of the pages for the current diff.
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...
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.
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.
Content objects represent page content, e.g.
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.