25 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
48 use Wikimedia\IPUtils;
50 use Wikimedia\NonSerializable\NonSerializableTrait;
62 use ProtectedHookAccessorTrait;
63 use NonSerializableTrait;
91 private $fetchResult =
null;
115 private $revisionStore;
120 private $userNameUtils;
125 private $userOptionsLookup;
128 private $commentFormatter;
131 private $wikiPageFactory;
134 private $jobQueueGroup;
137 private $archivedRevisionLookup;
145 private $mRevisionRecord =
null;
152 $this->mOldId = $oldId;
153 $this->mPage = $this->
newPage( $title );
155 $services = MediaWikiServices::getInstance();
156 $this->linkRenderer = $services->getLinkRenderer();
157 $this->revisionStore = $services->getRevisionStore();
158 $this->userNameUtils = $services->getUserNameUtils();
159 $this->userOptionsLookup = $services->getUserOptionsLookup();
160 $this->commentFormatter = $services->getCommentFormatter();
161 $this->wikiPageFactory = $services->getWikiPageFactory();
162 $this->jobQueueGroup = $services->getJobQueueGroup();
163 $this->archivedRevisionLookup = $services->getArchivedRevisionLookup();
180 $t = Title::newFromID( $id );
181 return $t ===
null ? null :
new static(
$t );
194 $title = Title::makeTitle(
NS_FILE, $title->getDBkey() );
198 (
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
200 ->onArticleFromTitle( $title, $page, $context );
202 switch ( $title->getNamespace() ) {
213 $page->setContext( $context );
226 $article = self::newFromTitle( $page->
getTitle(), $context );
227 $article->mPage = $page;
237 return $this->mRedirectedFrom;
246 $this->mRedirectedFrom = $from;
255 return $this->mPage->getTitle();
269 $this->mRedirectedFrom =
null; #
Title object if set
270 $this->mRedirectUrl =
false;
271 $this->mRevisionRecord =
null;
272 $this->fetchResult =
null;
276 $this->mPage->clear();
287 if ( $this->mOldId ===
null ) {
288 $this->mOldId = $this->getOldIDFromRequest();
291 return $this->mOldId;
300 $this->mRedirectUrl =
false;
303 $oldid = $request->getIntOrNull(
'oldid' );
305 if ( $oldid ===
null ) {
309 if ( $oldid !== 0 ) {
310 # Load the given revision and check whether the page is another one.
311 # In that case, update this instance to reflect the change.
312 if ( $oldid === $this->mPage->getLatest() ) {
313 $this->mRevisionRecord = $this->mPage->getRevisionRecord();
315 $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
316 if ( $this->mRevisionRecord !==
null ) {
317 $revPageId = $this->mRevisionRecord->getPageId();
319 if ( $this->mPage->getId() !== $revPageId ) {
320 $this->mPage = $this->wikiPageFactory->newFromID( $revPageId );
326 $oldRev = $this->mRevisionRecord;
327 if ( $request->getRawVal(
'direction' ) ===
'next' ) {
330 $nextRev = $this->revisionStore->getNextRevision( $oldRev );
332 $nextid = $nextRev->getId();
337 $this->mRevisionRecord =
null;
339 $this->mRedirectUrl = $this->
getTitle()->getFullURL(
'redirect=no' );
341 } elseif ( $request->getRawVal(
'direction' ) ===
'prev' ) {
344 $prevRev = $this->revisionStore->getPreviousRevision( $oldRev );
346 $previd = $prevRev->getId();
351 $this->mRevisionRecord =
null;
368 if ( $this->fetchResult ) {
369 return $this->mRevisionRecord;
372 $oldid = $this->getOldID();
375 if ( !$this->mRevisionRecord ) {
377 $this->mRevisionRecord = $this->mPage->getRevisionRecord();
379 if ( !$this->mRevisionRecord ) {
380 wfDebug( __METHOD__ .
" failed to find page data for title " .
381 $this->
getTitle()->getPrefixedText() );
384 $this->fetchResult = Status::newFatal(
'noarticletext' );
388 $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
390 if ( !$this->mRevisionRecord ) {
391 wfDebug( __METHOD__ .
" failed to load revision, rev_id $oldid" );
393 $this->fetchResult = Status::newFatal(
'missing-revision', $oldid );
399 if ( !$this->mRevisionRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getContext()->getAuthority() ) ) {
400 wfDebug( __METHOD__ .
" failed to retrieve content of revision " . $this->mRevisionRecord->getId() );
404 $this->fetchResult =
new Status;
405 $title = $this->
getTitle()->getPrefixedDBkey();
407 if ( $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
408 $this->fetchResult->fatal(
'rev-suppressed-text' );
410 $this->fetchResult->fatal(
'rev-deleted-text-permission', $title );
416 $this->fetchResult = Status::newGood( $this->mRevisionRecord );
417 return $this->mRevisionRecord;
426 # If no oldid, this is the current version.
427 if ( $this->getOldID() == 0 ) {
431 return $this->mPage->exists() &&
432 $this->mRevisionRecord &&
433 $this->mRevisionRecord->isCurrent();
445 if ( $this->fetchResult && $this->fetchResult->isOK() ) {
447 $rev = $this->fetchResult->getValue();
448 return $rev->
getId();
450 return $this->mPage->getLatest();
460 $useFileCache = $context->getConfig()->get( MainConfigNames::UseFileCache );
462 # Get variables from query string
463 # As side effect this will load the revision and update the title
464 # in a revision ID is passed in the request, so this should remain
465 # the first call of this method even if $oldid is used way below.
466 $oldid = $this->getOldID();
468 $authority = $context->getAuthority();
469 # Another check in case getOldID() is altering the title
470 $permissionStatus = PermissionStatus::newEmpty();
472 ->authorizeRead(
'read', $this->
getTitle(), $permissionStatus )
474 wfDebug( __METHOD__ .
": denied on secondary read check" );
479 # getOldID() may as well want us to redirect somewhere else
480 if ( $this->mRedirectUrl ) {
481 $outputPage->
redirect( $this->mRedirectUrl );
482 wfDebug( __METHOD__ .
": redirecting due to oldid" );
487 # If we got diff in the query, we want to see a diff page instead of the article.
488 if ( $context->getRequest()->getCheck(
'diff' ) ) {
489 wfDebug( __METHOD__ .
": showing diff page" );
490 $this->showDiffPage();
495 # Set page title (may be overridden from ParserOutput if title conversion is enabled or DISPLAYTITLE is used)
497 str_replace(
'_',
' ', $this->
getTitle()->getNsText() ),
503 # Allow frames by default
506 $parserOptions = $this->getParserOptions();
509 # Allow extensions to vary parser options used for article rendering
510 (
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
511 ->onArticleParserOptions( $this, $parserOptions );
512 # Render printable version, use printable version cache
515 $poOptions[
'enableSectionEditLinks'] =
false;
518 $outputPage->
msg(
'printableversion-deprecated-warning' )->escaped()
521 } elseif ( $this->viewIsRenderAction || !$this->isCurrent() ||
522 !$authority->probablyCan(
'edit', $this->getTitle() )
524 $poOptions[
'enableSectionEditLinks'] =
false;
527 # Try client and file cache
528 if ( $oldid === 0 && $this->mPage->checkTouched() ) {
529 # Try to stream the output from file cache
530 if ( $useFileCache && $this->tryFileCache() ) {
531 wfDebug( __METHOD__ .
": done file cache" );
532 # tell wgOut that output is taken care of
534 $this->mPage->doViewUpdates( $authority, $oldid );
540 $this->showRedirectedFromHeader();
541 $this->showNamespaceHeader();
543 if ( $this->viewIsRenderAction ) {
544 $poOptions += [
'absoluteURLs' =>
true ];
546 $poOptions += [
'includeDebugInfo' =>
true ];
550 $this->generateContentOutput( $authority, $parserOptions, $oldid, $outputPage, $poOptions );
553 $this->showViewError(
wfMessage(
'badrevision' )->text() );
561 $outputPage->
addModules(
'mediawiki.skinning.content.parsoid' );
564 # For the main page, overwrite the <title> element with the con-
565 # tents of 'pagetitle-view-mainpage' instead of the default (if
567 # This message always exists because it is in the i18n files
568 if ( $this->
getTitle()->isMainPage() ) {
569 $msg = $context->msg(
'pagetitle-view-mainpage' )->inContentLanguage();
570 if ( !$msg->isDisabled() ) {
575 # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
576 # This could use getTouched(), but that could be scary for major template edits.
577 $outputPage->
adaptCdnTTL( $this->mPage->getTimestamp(), ExpirationAwareness::TTL_DAY );
579 $this->showViewFooter();
580 $this->mPage->doViewUpdates( $authority, $oldid, $this->fetchRevisionRecord() );
582 # Load the postEdit module if the user just saved this revision
583 # See also EditPage::setPostEditCookie
584 $request = $context->getRequest();
585 $cookieKey = EditPage::POST_EDIT_COOKIE_KEY_PREFIX . $this->getRevIdFetched();
586 $postEdit = $request->getCookie( $cookieKey );
588 # Clear the cookie. This also prevents caching of the response.
589 $request->response()->clearCookie( $cookieKey );
591 $outputPage->
addModules(
'mediawiki.action.view.postEdit' );
592 if ( $this->
getContext()->getConfig()->
get(
'EnableEditRecovery' ) ) {
593 $outputPage->
addModules(
'mediawiki.editRecovery.postEdit' );
610 private function generateContentOutput(
617 # Should the parser cache be used?
618 $useParserCache =
true;
620 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
623 $this->getHookRunner()->onArticleViewHeader( $this, $outputDone, $useParserCache );
626 $pOutput = $outputDone;
630 $this->doOutputMetaData( $pOutput, $outputPage );
636 if ( !$this->mPage->exists() ) {
637 wfDebug( __METHOD__ .
": showing missing article" );
638 $this->showMissingArticle();
639 $this->mPage->doViewUpdates( $performer );
645 if ( $useParserCache && !$oldid ) {
646 $pOutput = $parserOutputAccess->getCachedParserOutput(
650 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK
654 $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
655 $this->doOutputMetaData( $pOutput, $outputPage );
660 $rev = $this->fetchRevisionRecord();
661 if ( !$this->fetchResult->isOK() ) {
662 $this->showViewError( $this->fetchResult->getWikiText(
663 false,
false, $this->getContext()->getLanguage()
668 # Are we looking at an old revision
670 $this->setOldSubtitle( $oldid );
672 if ( !$this->showDeletedRevisionHeader() ) {
673 wfDebug( __METHOD__ .
": cannot view deleted revision" );
680 if ( $useParserCache ) {
681 $pOutput = $parserOutputAccess->getCachedParserOutput(
685 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK
689 $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
690 $this->doOutputMetaData( $pOutput, $outputPage );
696 # Ensure that UI elements requiring revision ID have
697 # the correct version information. (This may be overwritten after creation of ParserOutput)
700 # Preload timestamp to avoid a DB hit
703 # Pages containing custom CSS or JavaScript get special treatment
704 if ( $this->
getTitle()->isSiteConfigPage() || $this->
getTitle()->isUserConfigPage() ) {
705 $dir = $this->
getContext()->getLanguage()->getDir();
706 $lang = $this->
getContext()->getLanguage()->getHtmlCode();
709 "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
713 } elseif ( !$this->getHookRunner()->onArticleRevisionViewCustom(
721 $this->doOutputMetaData( $pOutput, $outputPage );
725 # Run the parse, protected by a pool counter
726 wfDebug( __METHOD__ .
": doing uncached parse" );
731 $opt |= ParserOutputAccess::OPT_NO_CHECK_CACHE;
734 $opt |= ParserOutputAccess::OPT_NO_AUDIENCE_CHECK;
739 $opt |= ParserOutputAccess::OPT_LINKS_UPDATE;
741 if ( !$rev->
getId() || !$useParserCache ) {
743 $opt |= ParserOutputAccess::OPT_NO_CACHE;
746 $renderStatus = $parserOutputAccess->getParserOutput(
768 if ( $oldid === 0 || $oldid === $this->getPage()->getLatest() ) {
769 $parsoidCacheWarmingEnabled = $this->
getContext()->getConfig()
770 ->get( MainConfigNames::ParsoidCacheConfig )[
'WarmParsoidParserCache'];
772 if ( $parsoidCacheWarmingEnabled ) {
775 $this->getPage()->toPageRecord(),
776 [
'causeAction' =>
'view' ]
778 $this->jobQueueGroup->lazyPush( $parsoidJobSpec );
782 $this->doOutputFromRenderStatus(
789 if ( !$renderStatus->
isOK() ) {
793 $pOutput = $renderStatus->
getValue();
794 $this->doOutputMetaData( $pOutput, $outputPage );
803 # Adjust title for main page & pages with displaytitle
805 $this->adjustDisplayTitle( $pOutput );
808 # Check for any __NOINDEX__ tags on the page using $pOutput
809 $policy = $this->getRobotPolicy(
'view', $pOutput ?:
null );
813 $this->mParserOutput = $pOutput;
821 private function doOutputFromParserCache(
826 # Ensure that UI elements requiring revision ID have
827 # the correct version information.
832 # Preload timestamp to avoid a DB hit
834 if ( $cachedTimestamp !==
null ) {
836 $this->mPage->setTimestamp( $cachedTimestamp );
846 private function doOutputFromRenderStatus(
853 if ( !$renderStatus->
isOK() ) {
855 false,
'view-pool-error', $context->getLanguage()
860 $pOutput = $renderStatus->
getValue();
863 if ( $renderStatus->
hasMessage(
'view-pool-dirty-output' ) ) {
864 $outputPage->
setCdnMaxage( $context->getConfig()->get( MainConfigNames::CdnMaxageStale ) );
866 $staleReason = $renderStatus->
hasMessage(
'view-pool-contention' )
867 ? $context->msg(
'view-pool-contention' )->escaped()
868 : $context->msg(
'view-pool-timeout' )->escaped();
869 $outputPage->
addHTML(
"<!-- parser cache is expired, " .
870 "sending anyway due to $staleReason-->\n" );
874 if ( $cachedId !==
null ) {
882 if ( $this->getRevisionRedirectTarget( $rev ) ) {
883 $outputPage->
addSubtitle(
"<span id=\"redirectsub\">" .
884 $context->msg(
'redirectpagesub' )->parse() .
"</span>" );
892 private function getRevisionRedirectTarget(
RevisionRecord $revision ) {
907 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
909 if ( strval( $titleText ) !==
'' ) {
910 $out->setPageTitle( $titleText );
911 $out->setDisplayTitle( $titleText );
921 $request = $context->getRequest();
922 $diff = $request->getVal(
'diff' );
923 $rcid = $request->getInt(
'rcid' );
924 $purge = $request->getRawVal(
'action' ) ===
'purge';
925 $unhide = $request->getInt(
'unhide' ) === 1;
926 $oldid = $this->getOldID();
928 $rev = $this->fetchRevisionRecord();
933 $rev = $this->revisionStore->getRevisionById( $oldid );
939 $context->getOutput()->setPageTitleMsg( $context->msg(
'errorpagetitle' ) );
940 $msg = $context->msg(
'difference-missing-revision' )
944 $context->getOutput()->addHTML( $msg );
949 $services = MediaWikiServices::getInstance();
951 $contentHandler = $services
952 ->getContentHandlerFactory()
954 $rev->
getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel()
956 $de = $contentHandler->createDifferenceEngine(
965 $diffType = $request->getVal(
'diff-type' );
967 if ( $diffType ===
null ) {
968 $diffType = $this->userOptionsLookup
969 ->getOption( $context->getUser(),
'diff-type' );
971 $de->setExtraQueryParams( [
'diff-type' => $diffType ] );
974 $de->setSlotDiffOptions( [
975 'diff-type' => $diffType,
976 'expand-url' => $this->viewIsRenderAction,
977 'inline-toggle' =>
true,
979 $de->showDiffPage( $this->isDiffOnlyView() );
983 [ , $new ] = $de->mapDiffPrevNext( $oldid, $diff );
985 $this->mPage->doViewUpdates( $context->getAuthority(), (
int)$new );
988 $context->getOutput()->addHelpLink(
'Help:Diff' );
992 return $this->
getContext()->getRequest()->getBool(
994 $this->userOptionsLookup->getBoolOption( $this->getContext()->getUser(),
'diffonly' )
1007 $mainConfig = $context->getConfig();
1008 $articleRobotPolicies = $mainConfig->get( MainConfigNames::ArticleRobotPolicies );
1009 $namespaceRobotPolicies = $mainConfig->get( MainConfigNames::NamespaceRobotPolicies );
1010 $defaultRobotPolicy = $mainConfig->get( MainConfigNames::DefaultRobotPolicy );
1012 $ns = $title->getNamespace();
1014 # Don't index user and user talk pages for blocked users (T13443)
1016 $specificTarget =
null;
1017 $vagueTarget =
null;
1018 $titleText = $title->getText();
1019 if ( IPUtils::isValid( $titleText ) ) {
1020 $vagueTarget = $titleText;
1022 $specificTarget = $titleText;
1024 if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof
DatabaseBlock ) {
1026 'index' =>
'noindex',
1027 'follow' =>
'nofollow'
1032 if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
1033 # Non-articles (special pages etc), and old revisions
1035 'index' =>
'noindex',
1036 'follow' =>
'nofollow'
1038 } elseif ( $context->getOutput()->isPrintable() ) {
1039 # Discourage indexing of printable versions, but encourage following
1041 'index' =>
'noindex',
1042 'follow' =>
'follow'
1044 } elseif ( $context->getRequest()->getInt(
'curid' ) ) {
1045 # For ?curid=x urls, disallow indexing
1047 'index' =>
'noindex',
1048 'follow' =>
'follow'
1052 # Otherwise, construct the policy based on the various config variables.
1053 $policy = self::formatRobotPolicy( $defaultRobotPolicy );
1055 if ( isset( $namespaceRobotPolicies[$ns] ) ) {
1056 # Honour customised robot policies for this namespace
1057 $policy = array_merge(
1059 self::formatRobotPolicy( $namespaceRobotPolicies[$ns] )
1062 if ( $title->canUseNoindex() && $pOutput && $pOutput->
getIndexPolicy() ) {
1063 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
1064 # a final check that we have really got the parser output.
1065 $policy = array_merge(
1071 if ( isset( $articleRobotPolicies[$title->getPrefixedText()] ) ) {
1072 # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
1073 $policy = array_merge(
1075 self::formatRobotPolicy( $articleRobotPolicies[$title->getPrefixedText()] )
1090 if ( is_array( $policy ) ) {
1092 } elseif ( !$policy ) {
1097 foreach ( explode(
',', $policy ) as $var ) {
1098 $var = trim( $var );
1099 if ( $var ===
'index' || $var ===
'noindex' ) {
1100 $arr[
'index'] = $var;
1101 } elseif ( $var ===
'follow' || $var ===
'nofollow' ) {
1102 $arr[
'follow'] = $var;
1118 $redirectSources = $context->getConfig()->get( MainConfigNames::RedirectSources );
1120 $request = $context->getRequest();
1121 $rdfrom = $request->getVal(
'rdfrom' );
1124 $query = $request->getValues();
1125 unset( $query[
'rdfrom'] );
1126 unset( $query[
'title'] );
1127 if ( $this->
getTitle()->isRedirect() ) {
1129 $query[
'redirect'] =
'no';
1131 $redirectTargetUrl = $this->
getTitle()->getLinkURL( $query );
1133 if ( isset( $this->mRedirectedFrom ) ) {
1136 if ( $this->getHookRunner()->onArticleViewRedirect( $this ) ) {
1137 $redir = $this->linkRenderer->makeKnownLink(
1138 $this->mRedirectedFrom,
1141 [
'redirect' =>
'no' ]
1144 $outputPage->
addSubtitle(
"<span class=\"mw-redirectedfrom\">" .
1145 $context->msg(
'redirectedfrom' )->rawParams( $redir )->parse()
1151 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1153 $outputPage->
addModules(
'mediawiki.action.view.redirect' );
1163 } elseif ( $rdfrom ) {
1166 if ( $redirectSources && preg_match( $redirectSources, $rdfrom ) ) {
1167 $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1168 $outputPage->
addSubtitle(
"<span class=\"mw-redirectedfrom\">" .
1169 $context->msg(
'redirectedfrom' )->rawParams( $redir )->parse()
1174 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1176 $outputPage->
addModules(
'mediawiki.action.view.redirect' );
1190 if ( $this->
getTitle()->isTalkPage() && !$this->
getContext()->msg(
'talkpageheader' )->isDisabled() ) {
1191 $this->
getContext()->getOutput()->wrapWikiMsg(
1192 "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1193 [
'talkpageheader' ]
1202 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1204 && IPUtils::isValid( $this->
getTitle()->getText() )
1206 $this->
getContext()->getOutput()->addWikiMsg(
'anontalkpagetext' );
1210 $patrolFooterShown = $this->showPatrolFooter();
1212 $this->getHookRunner()->onArticleViewFooter( $this, $patrolFooterShown );
1227 $mainConfig = $context->getConfig();
1228 $useNPPatrol = $mainConfig->get( MainConfigNames::UseNPPatrol );
1229 $useRCPatrol = $mainConfig->get( MainConfigNames::UseRCPatrol );
1230 $useFilePatrol = $mainConfig->get( MainConfigNames::UseFilePatrol );
1232 if ( !$this->getHookRunner()->onArticleShowPatrolFooter( $this ) ) {
1237 $user = $context->getUser();
1241 if ( !$context->getAuthority()->probablyCan(
'patrol', $title )
1242 || !( $useRCPatrol || $useNPPatrol
1243 || ( $useFilePatrol && $title->inNamespace(
NS_FILE ) ) )
1249 if ( $this->mRevisionRecord
1258 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1259 $key = $cache->makeKey(
'unpatrollable-page', $title->getArticleID() );
1260 if ( $cache->get( $key ) ) {
1265 $oldestRevisionRow = $dbr->newSelectQueryBuilder()
1266 ->select( [
'rev_id',
'rev_timestamp' ] )
1267 ->from(
'revision' )
1268 ->where( [
'rev_page' => $title->getArticleID() ] )
1269 ->orderBy( [
'rev_timestamp',
'rev_id' ] )
1270 ->caller( __METHOD__ )->fetchRow();
1271 $oldestRevisionTimestamp = $oldestRevisionRow ? $oldestRevisionRow->rev_timestamp :
false;
1277 $recentPageCreation =
false;
1278 if ( $oldestRevisionTimestamp
1282 $recentPageCreation =
true;
1285 'rc_this_oldid' => intval( $oldestRevisionRow->rev_id ),
1293 $markPatrolledMsg = $context->msg(
'markaspatrolledtext' );
1301 $recentFileUpload =
false;
1302 if ( ( !$rc || $rc->getAttribute(
'rc_patrolled' ) ) && $useFilePatrol
1303 && $title->getNamespace() ===
NS_FILE ) {
1305 $newestUploadTimestamp = $dbr->newSelectQueryBuilder()
1306 ->select(
'img_timestamp' )
1308 ->where( [
'img_name' => $title->getDBkey() ] )
1309 ->caller( __METHOD__ )->fetchField();
1310 if ( $newestUploadTimestamp
1314 $recentFileUpload =
true;
1318 'rc_log_type' =>
'upload',
1319 'rc_timestamp' => $newestUploadTimestamp,
1321 'rc_cur_id' => $title->getArticleID()
1327 $markPatrolledMsg = $context->msg(
'markaspatrolledtext-file' );
1332 if ( !$recentPageCreation && !$recentFileUpload ) {
1337 $cache->set( $key,
'1' );
1349 if ( $rc->getAttribute(
'rc_patrolled' ) ) {
1354 $cache->set( $key,
'1' );
1359 if ( $rc->getPerformerIdentity()->equals( $user ) ) {
1366 if ( $context->getAuthority()->isAllowed(
'writeapi' ) ) {
1367 $outputPage->
addModules(
'mediawiki.misc-authed-curate' );
1370 $link = $this->linkRenderer->makeKnownLink(
1373 $markPatrolledMsg->text(),
1376 'action' =>
'markpatrolled',
1377 'rcid' => $rc->getAttribute(
'rc_id' ),
1383 "<div class='patrollink' data-mw='interface'>" .
1384 $context->msg(
'markaspatrolledlink' )->rawParams( $link )->escaped() .
1398 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1399 $cache->delete( $cache->makeKey(
'unpatrollable-page', $articleID ) );
1408 $send404Code = $context->getConfig()->get( MainConfigNames::Send404Code );
1412 $validUserPage =
false;
1416 $services = MediaWikiServices::getInstance();
1418 $contextUser = $context->getUser();
1420 # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1421 if ( $title->getNamespace() ===
NS_USER
1424 $rootPart = $title->getRootText();
1425 $user = User::newFromName( $rootPart,
false );
1426 $ip = $this->userNameUtils->isIP( $rootPart );
1427 $block = DatabaseBlock::newFromTarget( $user, $user );
1429 if ( $user && $user->isRegistered() && $user->isHidden() &&
1430 !$context->getAuthority()->isAllowed(
'hideuser' )
1437 if ( !( $user && $user->isRegistered() ) && !$ip ) {
1439 $outputPage->
addHTML( Html::warningBox(
1440 $context->msg(
'userpage-userdoesnotexist-view',
wfEscapeWikiText( $rootPart ) )->parse(),
1441 'mw-userpage-userdoesnotexist'
1448 Title::makeTitleSafe(
NS_USER, $rootPart ),
1452 'showIfEmpty' =>
false,
1453 'msgKey' => [
'renameuser-renamed-notice', $title->getBaseText() ]
1458 $block->getType() != DatabaseBlock::TYPE_AUTO &&
1460 $block->isSitewide() ||
1461 $services->getPermissionManager()->isBlockedFrom( $user, $title,
true )
1469 $services->getNamespaceInfo()->getCanonicalName(
NS_USER ) .
':' .
1470 $block->getTargetName(),
1474 'showIfEmpty' =>
false,
1476 'blocked-notice-logextract',
1477 $user->getName() # Support GENDER in notice
1481 $validUserPage = !$title->isSubpage();
1483 $validUserPage = !$title->isSubpage();
1487 $this->getHookRunner()->onShowMissingArticle( $this );
1489 # Show delete and move logs if there were any such events.
1490 # The logging query can DOS the site when bots/crawlers cause 404 floods,
1491 # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1492 $dbCache = MediaWikiServices::getInstance()->getMainObjectStash();
1493 $key = $dbCache->makeKey(
'page-recent-delete', md5( $title->getPrefixedText() ) );
1494 $isRegistered = $contextUser->isRegistered();
1495 $sessionExists = $context->getRequest()->getSession()->isPersistent();
1497 if ( $isRegistered || $dbCache->get( $key ) || $sessionExists ) {
1498 $logTypes = [
'delete',
'move',
'protect' ];
1502 $conds = [
'log_action != ' . $dbr->addQuotes(
'revision' ) ];
1504 $this->getHookRunner()->onArticle__MissingArticleConditions( $conds, $logTypes );
1513 'showIfEmpty' =>
false,
1514 'msgKey' => [ $isRegistered || $sessionExists
1515 ?
'moveddeleted-notice'
1516 :
'moveddeleted-notice-recent'
1522 if ( !$this->mPage->hasViewableContent() && $send404Code && !$validUserPage ) {
1525 $context->getRequest()->response()->statusHeader( 404 );
1529 $policy = $this->getRobotPolicy(
'view' );
1533 $hookResult = $this->getHookRunner()->onBeforeDisplayNoArticleText( $this );
1535 if ( !$hookResult ) {
1539 # Show error message
1540 $oldid = $this->getOldID();
1541 if ( !$oldid && $title->getNamespace() ===
NS_MEDIAWIKI && $title->hasSourceText() ) {
1542 $text = $this->
getTitle()->getDefaultMessageText() ??
'';
1548 $revRecord = $this->archivedRevisionLookup->getArchivedRevisionRecord( $title, $oldid );
1549 if ( $revRecord && $revRecord->userCan(
1550 RevisionRecord::DELETED_TEXT,
1551 $context->getAuthority()
1552 ) && $context->getAuthority()->isAllowedAny(
'deletedtext',
'undelete' ) ) {
1553 $text = $context->msg(
1554 'missing-revision-permission', $oldid,
1555 $revRecord->getTimestamp(),
1556 $title->getPrefixedDBkey()
1559 $text = $context->msg(
'missing-revision', $oldid )->plain();
1562 } elseif ( $context->getAuthority()->probablyCan(
'edit', $title ) ) {
1563 $message = $isRegistered ?
'noarticletext' :
'noarticletextanon';
1564 $text = $context->msg( $message )->plain();
1566 $text = $context->msg(
'noarticletext-nopermission' )->plain();
1569 $dir = $context->getLanguage()->getDir();
1570 $lang = $context->getLanguage()->getHtmlCode();
1572 'class' =>
"noarticletext mw-content-$dir",
1575 ] ) .
"\n$text\n</div>" );
1583 private function showViewError(
string $errortext ) {
1584 $outputPage = $this->
getContext()->getOutput();
1599 if ( !$this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1603 $outputPage = $this->
getContext()->getOutput();
1605 $titleText = $this->
getTitle()->getPrefixedDBkey();
1607 if ( !$this->mRevisionRecord->userCan(
1608 RevisionRecord::DELETED_TEXT,
1609 $this->getContext()->getAuthority()
1613 $outputPage->
msg(
'rev-deleted-text-permission', $titleText )->parse(),
1620 } elseif ( $this->
getContext()->getRequest()->getInt(
'unhide' ) !== 1 ) {
1621 # Give explanation and add a link to view the revision...
1622 $oldid = intval( $this->getOldID() );
1623 $link = $this->
getTitle()->getFullURL(
"oldid={$oldid}&unhide=1" );
1624 $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1625 'rev-suppressed-text-unhide' :
'rev-deleted-text-unhide';
1628 $outputPage->
msg( $msg, $link )->parse(),
1636 $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
1637 ? [
'rev-suppressed-text-view', $titleText ]
1638 : [
'rev-deleted-text-view', $titleText ];
1641 $outputPage->
msg( $msg[0], $msg[1] )->parse(),
1659 if ( !$this->getHookRunner()->onDisplayOldSubtitle( $this, $oldid ) ) {
1664 $unhide = $context->getRequest()->getInt(
'unhide' ) === 1;
1666 # Cascade unhide param in links for easy deletion browsing
1669 $extraParams[
'unhide'] = 1;
1672 if ( $this->mRevisionRecord && $this->mRevisionRecord->getId() === $oldid ) {
1673 $revisionRecord = $this->mRevisionRecord;
1675 $revisionRecord = $this->revisionStore->getRevisionById( $oldid );
1677 if ( !$revisionRecord ) {
1678 throw new LogicException(
'There should be a revision record at this point.' );
1681 $timestamp = $revisionRecord->getTimestamp();
1683 $current = ( $oldid == $this->mPage->getLatest() );
1684 $language = $context->getLanguage();
1685 $user = $context->getUser();
1687 $td = $language->userTimeAndDate( $timestamp, $user );
1688 $tddate = $language->userDate( $timestamp, $user );
1689 $tdtime = $language->userTime( $timestamp, $user );
1691 # Show user links if allowed to see them. If hidden, then show them only if requested...
1692 $userlinks = Linker::revUserTools( $revisionRecord, !$unhide );
1694 $infomsg = $current && !$context->msg(
'revision-info-current' )->isDisabled()
1695 ?
'revision-info-current'
1700 'mediawiki.action.styles',
1701 'mediawiki.interface.helpers.styles'
1704 $revisionUser = $revisionRecord->getUser();
1705 $revisionInfo =
"<div id=\"mw-{$infomsg}\">" .
1706 $context->msg( $infomsg, $td )
1707 ->rawParams( $userlinks )
1709 $revisionRecord->getId(),
1712 $revisionUser ? $revisionUser->getName() :
''
1714 ->rawParams( $this->commentFormatter->formatRevision(
1724 ? $context->msg(
'currentrevisionlink' )->escaped()
1725 : $this->linkRenderer->makeKnownLink(
1727 $context->msg(
'currentrevisionlink' )->text(),
1732 ? $context->msg(
'diff' )->escaped()
1733 : $this->linkRenderer->makeKnownLink(
1735 $context->msg(
'diff' )->text(),
1742 $prevExist = (bool)$this->revisionStore->getPreviousRevision( $revisionRecord );
1743 $prevlink = $prevExist
1744 ? $this->linkRenderer->makeKnownLink(
1746 $context->msg(
'previousrevision' )->text(),
1749 'direction' =>
'prev',
1753 : $context->msg(
'previousrevision' )->escaped();
1754 $prevdiff = $prevExist
1755 ? $this->linkRenderer->makeKnownLink(
1757 $context->msg(
'diff' )->text(),
1764 : $context->msg(
'diff' )->escaped();
1765 $nextlink = $current
1766 ? $context->msg(
'nextrevision' )->escaped()
1767 : $this->linkRenderer->makeKnownLink(
1769 $context->msg(
'nextrevision' )->text(),
1772 'direction' =>
'next',
1776 $nextdiff = $current
1777 ? $context->msg(
'diff' )->escaped()
1778 : $this->linkRenderer->makeKnownLink(
1780 $context->msg(
'diff' )->text(),
1788 $cdel = Linker::getRevDeleteLink(
1789 $context->getAuthority(),
1793 if ( $cdel !==
'' ) {
1801 "<div id=\"mw-revision-nav\">" . $cdel .
1802 $context->msg(
'revision-nav' )->rawParams(
1803 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1804 )->escaped() .
"</div>",
1823 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1825 $html =
'<ul class="redirectText">';
1826 if ( $forceKnown ) {
1832 $target->
isRedirect() ? [
'redirect' =>
'no' ] : []
1840 $target->
isRedirect() ? [
'redirect' =>
'no' ] : []
1843 $html .=
'<li>' . $link .
'</li>';
1846 $redirectToText =
wfMessage(
'redirectto' )->inLanguage( $lang )->escaped();
1848 return '<div class="redirectMsg">' .
1849 '<p>' . $redirectToText .
'</p>' .
1864 $msg = $out->msg(
'namespace-' . $this->
getTitle()->getNamespace() .
'-helppage' );
1866 if ( !$msg->isDisabled() ) {
1867 $title = Title::newFromText( $msg->plain() );
1868 if ( $title instanceof
Title ) {
1869 $out->addHelpLink( $title->getLocalURL(),
true );
1872 $out->addHelpLink( $to, $overrideBaseUrl );
1880 $this->
getContext()->getRequest()->response()->header(
'X-Robots-Tag: noindex' );
1881 $this->
getContext()->getOutput()->setArticleBodyOnly(
true );
1883 $this->viewIsRenderAction =
true;
1912 static $called =
false;
1915 wfDebug(
"Article::tryFileCache(): called twice!?" );
1920 if ( $this->isFileCacheable() ) {
1922 if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1923 wfDebug(
"Article::tryFileCache(): about to load file" );
1924 $cache->loadFromFileCache( $this->
getContext() );
1927 wfDebug(
"Article::tryFileCache(): starting buffer" );
1928 ob_start( [ &$cache,
'saveToFileCache' ] );
1931 wfDebug(
"Article::tryFileCache(): not cacheable" );
1946 $cacheable = $this->mPage->getId()
1947 && !$this->mRedirectedFrom && !$this->
getTitle()->isRedirect();
1950 $cacheable = $this->getHookRunner()->onIsFileCacheable( $this ) ??
false;
1971 if ( $user ===
null ) {
1972 $parserOptions = $this->getParserOptions();
1974 $parserOptions = $this->mPage->makeParserOptions( $user );
1978 return $this->mPage->getParserOutput( $parserOptions, $oldid );
1986 $parserOptions = $this->mPage->makeParserOptions( $this->
getContext() );
1988 return $parserOptions;
1998 $this->mContext = $context;
2009 return $this->mContext;
2011 wfDebug( __METHOD__ .
" called and \$mContext is null. " .
2012 "Return RequestContext::getMain()" );
2027 wfDeprecatedMsg(
"Accessing Article::\$$fname is deprecated since MediaWiki 1.35",
2030 if ( property_exists( $this->mPage, $fname ) ) {
2031 return $this->mPage->$fname;
2033 trigger_error(
'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2045 public function __set( $fname, $fvalue ) {
2046 wfDeprecatedMsg(
"Setting Article::\$$fname is deprecated since MediaWiki 1.35",
2049 if ( property_exists( $this->mPage, $fname ) ) {
2050 $this->mPage->$fname = $fvalue;
2052 } elseif ( !in_array( $fname, [
'mContext',
'mPage' ] ) ) {
2053 $this->mPage->$fname = $fvalue;
2055 trigger_error(
'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2065 return $this->mPage->getActionOverrides();
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
if(!defined('MW_SETUP_CALLBACK'))
Legacy class representing an editable page and handling UI for some page actions.
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
getContext()
Gets the context this Article is executed in.
getOldIDFromRequest()
Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect.
getRedirectedFrom()
Get the page this view was redirected from.
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
bool $viewIsRenderAction
Whether render() was called.
view()
This is the default action of the index.php entry point: just view the page of the given title.
__construct(Title $title, $oldId=null)
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
LinkRenderer $linkRenderer
getTitle()
Get the title object of the article.
getActionOverrides()
Call to WikiPage function for backwards compatibility.
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
IContextSource null $mContext
The context this Article is executed in.
static getRedirectHeaderHtml(Language $lang, Title $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
getParserOutput( $oldid=null, UserIdentity $user=null)
#-
protect()
action=protect handler
string false $mRedirectUrl
URL to redirect to or false if none.
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists).
showMissingArticle()
Show the error text for a missing article.
unprotect()
action=unprotect handler (alias)
getPage()
Get the WikiPage object of this instance.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
static newFromID( $id)
Constructor from a page id.
int null $mOldId
The oldid of the article that was requested to be shown, 0 for the current revision.
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
fetchRevisionRecord()
Fetches the revision to work on.
showPatrolFooter()
If patrol is possible, output a patrol UI box.
setOldSubtitle( $oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
showViewFooter()
Show the footer section of an ordinary page view.
WikiPage $mPage
The WikiPage object of this instance.
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki.
isFileCacheable( $mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
getRevIdFetched()
Use this to fetch the rev ID used on page views.
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
showDiffPage()
Show a diff page according to current request variables.
render()
Handle action=render.
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
setContext( $context)
Sets the context this Article is executed in.
Special handling for category description pages.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Page view caching in the file system.
static useFileCache(IContextSource $context, $mode=self::MODE_NORMAL)
Check if pages can be cached for this request/user.
Rendering of file description pages.
Base class for language-specific code.
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
The HTML user interface for page editing.
A class containing constants representing the names of configuration variables.
This is one of the Core classes and should be read at least once by any new developers.
parseAsContent( $text, $linestart=true)
Parse wikitext in the page content language and return the HTML.
disableClientCache()
Force the page to send nocache headers.
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
wrapWikiMsg( $wrap,... $msgSpecs)
This function takes a number of message/argument specifications, wraps them in some overall structure...
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
setPreventClickjacking(bool $enable)
Set the prevent-clickjacking flag.
disable()
Disable output completely, i.e.
setRedirectedFrom(PageReference $t)
Set $mRedirectedFrom, the page which redirected us to the current page.
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] and pass it to lowerCdnMaxage()
setCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
setLastModified( $timestamp)
Override the last modified timestamp.
setRevisionIsCurrent(bool $isCurrent)
Set whether the revision displayed (as set in ::setRevisionId()) is the latest revision of the page.
setFollowPolicy( $policy)
Set the follow policy for the page, but leave the index policy un- touched.
clearHTML()
Clear the body HTML.
setPageTitle( $name)
"Page title" means the contents of <h1>.
setPageTitleMsg(Message $msg)
"Page title" means the contents of <h1>.
addModules( $modules)
Load one or more ResourceLoader modules on this page.
redirect( $url, $responsecode='302')
Redirect to $url rather than displaying the normal page.
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
prependHTML( $text)
Prepend $text to the body HTML.
setRobotPolicy( $policy)
Set the robot policy for the page: http://www.robotstxt.org/meta.html
setCanonicalUrl( $url)
Set the URL to be used for the <link rel=canonical>>.
addWikiTextAsContent( $text, $linestart=true, PageReference $title=null)
Convert wikitext in the page content language to HTML and add it to the buffer.
addHTML( $text)
Append $text to the body HTML.
setRevisionId( $revid)
Set the revision ID which will be seen by the wiki text parser for things such as embedded {{REVISION...
addSubtitle( $str)
Add $str to the subtitle.
addModuleStyles( $modules)
Load the styles of one or more style-only ResourceLoader modules on this page.
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
isPrintable()
Return whether the page is "printable".
setArticleFlag( $newVal)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
addWikiTextAsInterface( $text, $linestart=true, PageReference $title=null)
Convert wikitext in the user interface language to HTML and add it to the buffer.
Service for getting rendered output of a given page.
Handles the page protection UI and backend.
Service for creating WikiPage objects.
Set options of the Parser.
setIsPrintable( $x)
Parsing the printable version of the page?
setRenderReason(string $renderReason)
Sets reason for rendering the content.
getUseParsoid()
Parsoid-format HTML output, or legacy wikitext parser HTML?
static formatPageTitle( $nsText, $nsSeparator, $mainText)
Add HTML tags marking the parts of a page title, to be displayed in the first heading of the page.
static newSpec(int $revisionId, PageRecord $page, array $params=[])
Show an error when a user tries to do something they do not have the necessary permissions for.
static isInRCLifespan( $timestamp, $tolerance=0)
Check whether the given timestamp is new enough to have a RC row with a given tolerance as the recent...
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
static getMain()
Get the RequestContext object associated with the main request.
hasMessage( $message)
Returns true if the specified message is present as a warning or error.
isOK()
Returns whether the operation completed.
Base representation for an editable wiki page.
getTitle()
Get the title object of the article.
static openElement( $element, $attribs=null)
This opens an XML element.
Interface for objects which can provide a MediaWiki context on request.
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)