24use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
45use Wikimedia\NonSerializable\NonSerializableTrait;
57 use ProtectedHookAccessorTrait;
58 use NonSerializableTrait;
86 private $fetchResult =
null;
100 protected $viewIsRenderAction =
false;
110 private $revisionStore;
115 private $watchlistManager;
120 private $userNameUtils;
125 private $userOptionsLookup;
128 private $commentFormatter;
131 private $wikiPageFactory;
134 private $jobQueueGroup;
142 private $mRevisionRecord =
null;
149 $this->mOldId = $oldId;
152 $services = MediaWikiServices::getInstance();
153 $this->linkRenderer = $services->getLinkRenderer();
154 $this->revisionStore = $services->getRevisionStore();
155 $this->watchlistManager = $services->getWatchlistManager();
156 $this->userNameUtils = $services->getUserNameUtils();
157 $this->userOptionsLookup = $services->getUserOptionsLookup();
158 $this->commentFormatter = $services->getCommentFormatter();
159 $this->wikiPageFactory = $services->getWikiPageFactory();
160 $this->jobQueueGroup = $services->getJobQueueGroup();
177 $t = Title::newFromID( $id );
178 return $t ==
null ? null :
new static(
$t );
196 Hooks::runner()->onArticleFromTitle(
$title, $page, $context );
198 switch (
$title->getNamespace() ) {
209 $page->setContext( $context );
222 $article = self::newFromTitle( $page->
getTitle(), $context );
223 $article->mPage = $page;
233 return $this->mRedirectedFrom;
242 $this->mRedirectedFrom = $from;
251 return $this->mPage->getTitle();
265 $this->mRedirectedFrom =
null; #
Title object if set
266 $this->mRedirectUrl =
false;
267 $this->mRevisionRecord =
null;
268 $this->fetchResult =
null;
272 $this->mPage->clear();
283 if ( $this->mOldId ===
null ) {
287 return $this->mOldId;
296 $this->mRedirectUrl =
false;
299 $oldid = $request->getIntOrNull(
'oldid' );
301 if ( $oldid ===
null ) {
305 if ( $oldid !== 0 ) {
306 # Load the given revision and check whether the page is another one.
307 # In that case, update this instance to reflect the change.
308 if ( $oldid === $this->mPage->getLatest() ) {
309 $this->mRevisionRecord = $this->mPage->getRevisionRecord();
311 $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
312 if ( $this->mRevisionRecord !==
null ) {
313 $revPageId = $this->mRevisionRecord->getPageId();
315 if ( $this->mPage->getId() != $revPageId ) {
316 $this->mPage = $this->wikiPageFactory->newFromID( $revPageId );
322 $oldRev = $this->mRevisionRecord;
323 if ( $request->getRawVal(
'direction' ) ===
'next' ) {
326 $nextRev = $this->revisionStore->getNextRevision( $oldRev );
328 $nextid = $nextRev->getId();
333 $this->mRevisionRecord =
null;
335 $this->mRedirectUrl = $this->
getTitle()->getFullURL(
'redirect=no' );
337 } elseif ( $request->getRawVal(
'direction' ) ===
'prev' ) {
340 $prevRev = $this->revisionStore->getPreviousRevision( $oldRev );
342 $previd = $prevRev->getId();
347 $this->mRevisionRecord =
null;
364 if ( $this->fetchResult ) {
365 return $this->mRevisionRecord;
371 if ( !$this->mRevisionRecord ) {
373 $this->mRevisionRecord = $this->mPage->getRevisionRecord();
375 if ( !$this->mRevisionRecord ) {
376 wfDebug( __METHOD__ .
" failed to find page data for title " .
377 $this->
getTitle()->getPrefixedText() );
380 $this->fetchResult = Status::newFatal(
'noarticletext' );
384 $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
386 if ( !$this->mRevisionRecord ) {
387 wfDebug( __METHOD__ .
" failed to load revision, rev_id $oldid" );
389 $this->fetchResult = Status::newFatal(
'missing-revision', $oldid );
395 if ( !$this->mRevisionRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getContext()->getAuthority() ) ) {
396 wfDebug( __METHOD__ .
" failed to retrieve content of revision " . $this->mRevisionRecord->getId() );
400 $this->fetchResult =
new Status;
403 if ( $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
404 $this->fetchResult->
fatal(
'rev-suppressed-text' );
406 $this->fetchResult->fatal(
'rev-deleted-text-permission',
$title );
412 $this->fetchResult = Status::newGood( $this->mRevisionRecord );
413 return $this->mRevisionRecord;
422 # If no oldid, this is the current version.
427 return $this->mPage->exists() &&
428 $this->mRevisionRecord &&
429 $this->mRevisionRecord->isCurrent();
441 if ( $this->fetchResult && $this->fetchResult->isOK() ) {
443 $rev = $this->fetchResult->getValue();
444 return $rev->
getId();
446 return $this->mPage->getLatest();
456 $useFileCache = $context->getConfig()->get( MainConfigNames::UseFileCache );
458 # Get variables from query string
459 # As side effect this will load the revision and update the title
460 # in a revision ID is passed in the request, so this should remain
461 # the first call of this method even if $oldid is used way below.
464 $authority = $context->getAuthority();
465 # Another check in case getOldID() is altering the title
466 $permissionStatus = PermissionStatus::newEmpty();
468 ->authorizeRead(
'read', $this->
getTitle(), $permissionStatus )
470 wfDebug( __METHOD__ .
": denied on secondary read check" );
475 # getOldID() may as well want us to redirect somewhere else
476 if ( $this->mRedirectUrl ) {
477 $outputPage->
redirect( $this->mRedirectUrl );
478 wfDebug( __METHOD__ .
": redirecting due to oldid" );
483 # If we got diff in the query, we want to see a diff page instead of the article.
484 if ( $context->getRequest()->getCheck(
'diff' ) ) {
485 wfDebug( __METHOD__ .
": showing diff page" );
491 # Set page title (may be overridden from ParserOutput if title conversion is enabled or DISPLAYTITLE is used)
493 str_replace(
'_',
' ', $this->
getTitle()->getNsText() ),
499 # Allow frames by default
505 # Allow extensions to vary parser options used for article rendering
506 Hooks::runner()->onArticleParserOptions( $this, $parserOptions );
507 # Render printable version, use printable version cache
510 $poOptions[
'enableSectionEditLinks'] =
false;
513 $outputPage->
msg(
'printableversion-deprecated-warning' )->escaped()
516 } elseif ( $this->viewIsRenderAction || !$this->
isCurrent() ||
517 !$authority->probablyCan(
'edit', $this->getTitle() )
519 $poOptions[
'enableSectionEditLinks'] =
false;
522 # Try client and file cache
523 if ( $oldid === 0 && $this->mPage->checkTouched() ) {
524 # Try to stream the output from file cache
526 wfDebug( __METHOD__ .
": done file cache" );
527 # tell wgOut that output is taken care of
529 $this->mPage->doViewUpdates( $authority, $oldid );
538 if ( $this->viewIsRenderAction ) {
539 $poOptions += [
'absoluteURLs' =>
true ];
541 $poOptions += [
'includeDebugInfo' =>
true ];
545 $this->generateContentOutput( $authority, $parserOptions, $oldid, $outputPage, $poOptions );
548 $this->showViewError(
wfMessage(
'badrevision' )->text() );
555 # For the main page, overwrite the <title> element with the con-
556 # tents of 'pagetitle-view-mainpage' instead of the default (if
558 # This message always exists because it is in the i18n files
559 if ( $this->
getTitle()->isMainPage() ) {
560 $msg = $context->msg(
'pagetitle-view-mainpage' )->inContentLanguage();
561 if ( !$msg->isDisabled() ) {
566 # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
567 # This could use getTouched(), but that could be scary for major template edits.
568 $outputPage->
adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
573 # Load the postEdit module if the user just saved this revision
574 # See also EditPage::setPostEditCookie
575 $request = $context->getRequest();
576 $cookieKey = EditPage::POST_EDIT_COOKIE_KEY_PREFIX . $this->
getRevIdFetched();
577 $postEdit = $request->getCookie( $cookieKey );
579 # Clear the cookie. This also prevents caching of the response.
580 $request->response()->clearCookie( $cookieKey );
582 $outputPage->
addModules(
'mediawiki.action.view.postEdit' );
598 private function generateContentOutput(
605 # Should the parser cache be used?
606 $useParserCache =
true;
608 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
611 $this->getHookRunner()->onArticleViewHeader( $this, $outputDone, $useParserCache );
614 $pOutput = $outputDone;
618 $this->doOutputMetaData( $pOutput, $outputPage );
624 if ( !$this->mPage->exists() ) {
625 wfDebug( __METHOD__ .
": showing missing article" );
627 $this->mPage->doViewUpdates( $performer );
633 if ( $useParserCache && !$oldid ) {
634 $pOutput = $parserOutputAccess->getCachedParserOutput(
638 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK
642 $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
643 $this->doOutputMetaData( $pOutput, $outputPage );
649 if ( !$this->fetchResult->isOK() ) {
650 $this->showViewError( $this->fetchResult->getWikiText(
651 false,
false, $this->getContext()->getLanguage()
656 # Are we looking at an old revision
661 wfDebug( __METHOD__ .
": cannot view deleted revision" );
668 if ( $useParserCache ) {
669 $pOutput = $parserOutputAccess->getCachedParserOutput(
673 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK
677 $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
678 $this->doOutputMetaData( $pOutput, $outputPage );
684 # Ensure that UI elements requiring revision ID have
685 # the correct version information.
688 # Preload timestamp to avoid a DB hit
691 # Pages containing custom CSS or JavaScript get special treatment
692 if ( $this->
getTitle()->isSiteConfigPage() || $this->
getTitle()->isUserConfigPage() ) {
693 $dir = $this->
getContext()->getLanguage()->getDir();
697 "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
701 } elseif ( !$this->getHookRunner()->onArticleRevisionViewCustom(
709 $this->doOutputMetaData( $pOutput, $outputPage );
713 # Run the parse, protected by a pool counter
714 wfDebug( __METHOD__ .
": doing uncached parse" );
724 $opt |= ParserOutputAccess::OPT_NO_CHECK_CACHE;
727 $opt |= ParserOutputAccess::OPT_NO_AUDIENCE_CHECK;
729 if ( !$rev->
getId() || !$useParserCache ) {
731 $opt |= ParserOutputAccess::OPT_NO_CACHE;
734 $renderStatus = $parserOutputAccess->getParserOutput(
756 if ( $oldid === 0 || $oldid === $this->
getPage()->getLatest() ) {
757 $parsoidCacheWarmingEnabled = $this->
getContext()->getConfig()
758 ->get( MainConfigNames::ParsoidCacheConfig )[
'WarmParsoidParserCache'];
760 if ( $parsoidCacheWarmingEnabled ) {
763 $this->getPage()->toPageRecord(),
764 [
'causeAction' =>
'view' ]
766 $this->jobQueueGroup->lazyPush( $parsoidJobSpec );
770 $this->doOutputFromRenderStatus(
777 if ( !$renderStatus->
isOK() ) {
781 $pOutput = $renderStatus->
getValue();
782 $this->doOutputMetaData( $pOutput, $outputPage );
791 # Adjust title for main page & pages with displaytitle
793 $this->adjustDisplayTitle( $pOutput );
796 # Check for any __NOINDEX__ tags on the page using $pOutput
797 $policy = $this->getRobotPolicy(
'view', $pOutput ?: null );
801 $this->mParserOutput = $pOutput;
809 private function doOutputFromParserCache(
814 # Ensure that UI elements requiring revision ID have
815 # the correct version information.
820 # Preload timestamp to avoid a DB hit
822 if ( $cachedTimestamp !==
null ) {
824 $this->mPage->setTimestamp( $cachedTimestamp );
834 private function doOutputFromRenderStatus(
841 $cdnMaxageStale = $context->getConfig()->get( MainConfigNames::CdnMaxageStale );
842 $ok = $renderStatus->
isOK();
844 $pOutput = $ok ? $renderStatus->
getValue() :
null;
847 if ( $ok && $renderStatus->
hasMessage(
'view-pool-dirty-output' ) ) {
850 $staleReason = $renderStatus->
hasMessage(
'view-pool-contention' )
851 ? $context->msg(
'view-pool-contention' )
852 : $context->msg(
'view-pool-timeout' );
853 $outputPage->
addHTML(
"<!-- parser cache is expired, " .
854 "sending anyway due to $staleReason-->\n" );
857 if ( !$renderStatus->
isOK() ) {
859 false,
'view-pool-error', $context->getLanguage()
868 if ( $this->getRevisionRedirectTarget( $rev ) ) {
869 $outputPage->
addSubtitle(
"<span id=\"redirectsub\">" .
870 $context->msg(
'redirectpagesub' )->parse() .
"</span>" );
878 private function getRevisionRedirectTarget(
RevisionRecord $revision ) {
893 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
895 if ( strval( $titleText ) !==
'' ) {
896 $out->setPageTitle( $titleText );
897 $out->setDisplayTitle( $titleText );
907 $request = $context->getRequest();
908 $diff = $request->getVal(
'diff' );
909 $rcid = $request->getInt(
'rcid' );
910 $purge = $request->getRawVal(
'action' ) ===
'purge';
911 $unhide = $request->getInt(
'unhide' ) == 1;
912 $oldid = $this->getOldID();
914 $rev = $this->fetchRevisionRecord();
919 $rev = $this->revisionStore->getRevisionById( $oldid );
925 $context->getOutput()->setPageTitle( $context->msg(
'errorpagetitle' ) );
926 $msg = $context->msg(
'difference-missing-revision' )
930 $context->getOutput()->addHTML( $msg );
935 $contentHandler = MediaWikiServices::getInstance()
936 ->getContentHandlerFactory()
938 $rev->
getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel()
940 $de = $contentHandler->createDifferenceEngine(
948 $de->setSlotDiffOptions( [
949 'diff-type' => $request->getVal(
'diff-type' ),
950 'expand-url' => $this->viewIsRenderAction
952 $de->showDiffPage( $this->isDiffOnlyView() );
956 [ , $new ] = $de->mapDiffPrevNext( $oldid, $diff );
958 $this->mPage->doViewUpdates( $context->getAuthority(), (
int)$new );
961 $context->getOutput()->addHelpLink(
'Help:Diff' );
965 return $this->
getContext()->getRequest()->getBool(
967 $this->userOptionsLookup->getBoolOption( $this->getContext()->getUser(),
'diffonly' )
980 $mainConfig = $context->getConfig();
981 $articleRobotPolicies = $mainConfig->get( MainConfigNames::ArticleRobotPolicies );
982 $namespaceRobotPolicies = $mainConfig->get( MainConfigNames::NamespaceRobotPolicies );
983 $defaultRobotPolicy = $mainConfig->get( MainConfigNames::DefaultRobotPolicy );
985 $ns =
$title->getNamespace();
987 # Don't index user and user talk pages for blocked users (T13443)
989 $specificTarget =
null;
991 $titleText =
$title->getText();
992 if ( IPUtils::isValid( $titleText ) ) {
993 $vagueTarget = $titleText;
995 $specificTarget = $titleText;
997 if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof
DatabaseBlock ) {
999 'index' =>
'noindex',
1000 'follow' =>
'nofollow'
1005 if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
1006 # Non-articles (special pages etc), and old revisions
1008 'index' =>
'noindex',
1009 'follow' =>
'nofollow'
1011 } elseif ( $context->getOutput()->isPrintable() ) {
1012 # Discourage indexing of printable versions, but encourage following
1014 'index' =>
'noindex',
1015 'follow' =>
'follow'
1017 } elseif ( $context->getRequest()->getInt(
'curid' ) ) {
1018 # For ?curid=x urls, disallow indexing
1020 'index' =>
'noindex',
1021 'follow' =>
'follow'
1025 # Otherwise, construct the policy based on the various config variables.
1026 $policy = self::formatRobotPolicy( $defaultRobotPolicy );
1028 if ( isset( $namespaceRobotPolicies[$ns] ) ) {
1029 # Honour customised robot policies for this namespace
1030 $policy = array_merge(
1032 self::formatRobotPolicy( $namespaceRobotPolicies[$ns] )
1036 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
1037 # a final check that we have really got the parser output.
1038 $policy = array_merge(
1044 if ( isset( $articleRobotPolicies[
$title->getPrefixedText()] ) ) {
1045 # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
1046 $policy = array_merge(
1048 self::formatRobotPolicy( $articleRobotPolicies[
$title->getPrefixedText()] )
1063 if ( is_array( $policy ) ) {
1065 } elseif ( !$policy ) {
1070 foreach ( explode(
',', $policy ) as $var ) {
1071 $var = trim( $var );
1072 if ( $var ===
'index' || $var ===
'noindex' ) {
1073 $arr[
'index'] = $var;
1074 } elseif ( $var ===
'follow' || $var ===
'nofollow' ) {
1075 $arr[
'follow'] = $var;
1091 $redirectSources = $context->getConfig()->get( MainConfigNames::RedirectSources );
1093 $request = $context->getRequest();
1094 $rdfrom = $request->getVal(
'rdfrom' );
1097 $query = $request->getValues();
1098 unset( $query[
'rdfrom'] );
1099 unset( $query[
'title'] );
1100 if ( $this->
getTitle()->isRedirect() ) {
1102 $query[
'redirect'] =
'no';
1104 $redirectTargetUrl = $this->
getTitle()->getLinkURL( $query );
1106 if ( isset( $this->mRedirectedFrom ) ) {
1109 if ( $this->getHookRunner()->onArticleViewRedirect( $this ) ) {
1110 $redir = $this->linkRenderer->makeKnownLink(
1111 $this->mRedirectedFrom,
1114 [
'redirect' =>
'no' ]
1117 $outputPage->
addSubtitle(
"<span class=\"mw-redirectedfrom\">" .
1118 $context->msg(
'redirectedfrom' )->rawParams( $redir )->parse()
1124 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1126 $outputPage->
addModules(
'mediawiki.action.view.redirect' );
1136 } elseif ( $rdfrom ) {
1139 if ( $redirectSources && preg_match( $redirectSources, $rdfrom ) ) {
1140 $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1141 $outputPage->
addSubtitle(
"<span class=\"mw-redirectedfrom\">" .
1142 $context->msg(
'redirectedfrom' )->rawParams( $redir )->parse()
1147 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1149 $outputPage->
addModules(
'mediawiki.action.view.redirect' );
1163 if ( $this->
getTitle()->isTalkPage() && !$this->
getContext()->msg(
'talkpageheader' )->isDisabled() ) {
1164 $this->
getContext()->getOutput()->wrapWikiMsg(
1165 "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1166 [
'talkpageheader' ]
1175 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1177 && IPUtils::isValid( $this->
getTitle()->getText() )
1179 $this->
getContext()->getOutput()->addWikiMsg(
'anontalkpagetext' );
1183 $patrolFooterShown = $this->showPatrolFooter();
1185 $this->getHookRunner()->onArticleViewFooter( $this, $patrolFooterShown );
1200 $mainConfig = $context->getConfig();
1201 $useNPPatrol = $mainConfig->get( MainConfigNames::UseNPPatrol );
1202 $useRCPatrol = $mainConfig->get( MainConfigNames::UseRCPatrol );
1203 $useFilePatrol = $mainConfig->get( MainConfigNames::UseFilePatrol );
1205 if ( !$this->getHookRunner()->onArticleShowPatrolFooter( $this ) ) {
1210 $user = $context->getUser();
1214 if ( !$context->getAuthority()->probablyCan(
'patrol',
$title )
1215 || !( $useRCPatrol || $useNPPatrol
1222 if ( $this->mRevisionRecord
1223 && !RecentChange::isInRCLifespan( $this->mRevisionRecord->getTimestamp(), 21600 )
1231 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1232 $key = $cache->makeKey(
'unpatrollable-page',
$title->getArticleID() );
1233 if ( $cache->get( $key ) ) {
1238 $oldestRevisionRow =
$dbr->selectRow(
1240 [
'rev_id',
'rev_timestamp' ],
1241 [
'rev_page' =>
$title->getArticleID() ],
1243 [
'ORDER BY' => [
'rev_timestamp',
'rev_id' ] ]
1245 $oldestRevisionTimestamp = $oldestRevisionRow ? $oldestRevisionRow->rev_timestamp :
false;
1251 $recentPageCreation =
false;
1252 if ( $oldestRevisionTimestamp
1253 && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1256 $recentPageCreation =
true;
1257 $rc = RecentChange::newFromConds(
1259 'rc_this_oldid' => intval( $oldestRevisionRow->rev_id ),
1267 $markPatrolledMsg = $context->msg(
'markaspatrolledtext' );
1275 $recentFileUpload =
false;
1276 if ( ( !$rc || $rc->getAttribute(
'rc_patrolled' ) ) && $useFilePatrol
1279 $newestUploadTimestamp =
$dbr->selectField(
1282 [
'img_name' =>
$title->getDBkey() ],
1285 if ( $newestUploadTimestamp
1286 && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1289 $recentFileUpload =
true;
1290 $rc = RecentChange::newFromConds(
1293 'rc_log_type' =>
'upload',
1294 'rc_timestamp' => $newestUploadTimestamp,
1296 'rc_cur_id' =>
$title->getArticleID()
1302 $markPatrolledMsg = $context->msg(
'markaspatrolledtext-file' );
1307 if ( !$recentPageCreation && !$recentFileUpload ) {
1312 $cache->set( $key,
'1' );
1324 if ( $rc->getAttribute(
'rc_patrolled' ) ) {
1329 $cache->set( $key,
'1' );
1334 if ( $rc->getPerformerIdentity()->equals( $user ) ) {
1341 if ( $context->getAuthority()->isAllowed(
'writeapi' ) ) {
1342 $outputPage->
addModules(
'mediawiki.misc-authed-curate' );
1345 $link = $this->linkRenderer->makeKnownLink(
1348 $markPatrolledMsg->text(),
1351 'action' =>
'markpatrolled',
1352 'rcid' => $rc->getAttribute(
'rc_id' ),
1358 "<div class='patrollink' data-mw='interface'>" .
1359 $context->msg(
'markaspatrolledlink' )->rawParams( $link )->escaped() .
1373 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1374 $cache->delete( $cache->makeKey(
'unpatrollable-page', $articleID ) );
1383 $send404Code = $context->getConfig()->get( MainConfigNames::Send404Code );
1387 $validUserPage =
false;
1391 $services = MediaWikiServices::getInstance();
1393 $contextUser = $context->getUser();
1395 # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1399 $rootPart = explode(
'/',
$title->getText() )[0];
1401 $ip = $this->userNameUtils->isIP( $rootPart );
1402 $block = DatabaseBlock::newFromTarget( $user, $user );
1404 if ( $user && $user->isRegistered() && $user->isHidden() &&
1405 !$context->getAuthority()->isAllowed(
'hideuser' )
1412 if ( !( $user && $user->isRegistered() ) && !$ip ) {
1414 $outputPage->
addHTML( Html::warningBox(
1415 $context->msg(
'userpage-userdoesnotexist-view',
wfEscapeWikiText( $rootPart ) )->parse(),
1416 'mw-userpage-userdoesnotexist'
1420 LogEventsList::showLogExtract(
1423 Title::makeTitleSafe(
NS_USER, $rootPart ),
1427 'showIfEmpty' =>
false,
1428 'msgKey' => [
'renameuser-renamed-notice',
$title->getBaseText() ]
1433 $block->getType() != DatabaseBlock::TYPE_AUTO &&
1435 $block->isSitewide() ||
1436 $services->getPermissionManager()->isBlockedFrom( $user,
$title,
true )
1441 LogEventsList::showLogExtract(
1444 $services->getNamespaceInfo()->getCanonicalName(
NS_USER ) .
':' .
1445 $block->getTargetName(),
1449 'showIfEmpty' =>
false,
1451 'blocked-notice-logextract',
1452 $user->getName() # Support GENDER in notice
1456 $validUserPage = !
$title->isSubpage();
1458 $validUserPage = !
$title->isSubpage();
1462 $this->getHookRunner()->onShowMissingArticle( $this );
1464 # Show delete and move logs if there were any such events.
1465 # The logging query can DOS the site when bots/crawlers cause 404 floods,
1466 # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1467 $dbCache = MediaWikiServices::getInstance()->getMainObjectStash();
1468 $key = $dbCache->makeKey(
'page-recent-delete', md5(
$title->getPrefixedText() ) );
1469 $isRegistered = $contextUser->isRegistered();
1470 $sessionExists = $context->getRequest()->getSession()->isPersistent();
1472 if ( $isRegistered || $dbCache->get( $key ) || $sessionExists ) {
1473 $logTypes = [
'delete',
'move',
'protect' ];
1477 $conds = [
'log_action != ' .
$dbr->addQuotes(
'revision' ) ];
1479 $this->getHookRunner()->onArticle__MissingArticleConditions( $conds, $logTypes );
1480 LogEventsList::showLogExtract(
1488 'showIfEmpty' =>
false,
1489 'msgKey' => [ $isRegistered || $sessionExists
1490 ?
'moveddeleted-notice'
1491 :
'moveddeleted-notice-recent'
1497 if ( !$this->mPage->hasViewableContent() && $send404Code && !$validUserPage ) {
1500 $context->getRequest()->response()->statusHeader( 404 );
1504 $policy = $this->getRobotPolicy(
'view' );
1508 $hookResult = $this->getHookRunner()->onBeforeDisplayNoArticleText( $this );
1510 if ( !$hookResult ) {
1514 # Show error message
1515 $oldid = $this->getOldID();
1517 $text = $this->
getTitle()->getDefaultMessageText() ??
'';
1524 $revRecord = $pa->getArchivedRevisionRecord( $oldid );
1525 if ( $revRecord && $revRecord->userCan(
1526 RevisionRecord::DELETED_TEXT,
1527 $context->getAuthority()
1528 ) && $context->getAuthority()->isAllowedAny(
'deletedtext',
'undelete' ) ) {
1529 $text = $context->msg(
1530 'missing-revision-permission', $oldid,
1531 $revRecord->getTimestamp(),
1532 $title->getPrefixedDBkey()
1535 $text = $context->msg(
'missing-revision', $oldid )->plain();
1538 } elseif ( $context->getAuthority()->probablyCan(
'edit',
$title ) ) {
1539 $message = $isRegistered ?
'noarticletext' :
'noarticletextanon';
1540 $text = $context->msg( $message )->plain();
1542 $text = $context->msg(
'noarticletext-nopermission' )->plain();
1545 $dir = $context->getLanguage()->getDir();
1546 $lang = $context->getLanguage()->getHtmlCode();
1548 'class' =>
"noarticletext mw-content-$dir",
1551 ] ) .
"\n$text\n</div>" );
1559 private function showViewError(
string $errortext ) {
1575 if ( !$this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1581 $titleText = $this->
getTitle()->getPrefixedDBkey();
1583 if ( !$this->mRevisionRecord->userCan(
1584 RevisionRecord::DELETED_TEXT,
1585 $this->getContext()->getAuthority()
1589 $outputPage->
msg(
'rev-deleted-text-permission', $titleText )->parse(),
1596 } elseif ( $this->
getContext()->getRequest()->getInt(
'unhide' ) != 1 ) {
1597 # Give explanation and add a link to view the revision...
1598 $oldid = intval( $this->getOldID() );
1599 $link = $this->
getTitle()->getFullURL(
"oldid={$oldid}&unhide=1" );
1600 $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1601 'rev-suppressed-text-unhide' :
'rev-deleted-text-unhide';
1604 $outputPage->
msg( $msg, $link )->parse(),
1612 $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
1613 ? [
'rev-suppressed-text-view', $titleText ]
1614 : [
'rev-deleted-text-view', $titleText ];
1617 $outputPage->
msg( $msg[0], $msg[1] )->parse(),
1635 if ( !$this->getHookRunner()->onDisplayOldSubtitle( $this, $oldid ) ) {
1640 $unhide = $context->getRequest()->getInt(
'unhide' ) == 1;
1642 # Cascade unhide param in links for easy deletion browsing
1645 $extraParams[
'unhide'] = 1;
1648 if ( $this->mRevisionRecord && $this->mRevisionRecord->getId() === $oldid ) {
1649 $revisionRecord = $this->mRevisionRecord;
1651 $revisionRecord = $this->revisionStore->getRevisionById( $oldid );
1654 $timestamp = $revisionRecord->getTimestamp();
1656 $current = ( $oldid == $this->mPage->getLatest() );
1657 $language = $context->getLanguage();
1658 $user = $context->getUser();
1660 $td = $language->userTimeAndDate( $timestamp, $user );
1661 $tddate = $language->userDate( $timestamp, $user );
1662 $tdtime = $language->userTime( $timestamp, $user );
1664 # Show user links if allowed to see them. If hidden, then show them only if requested...
1666 $userlinks = Linker::revUserTools( $revisionRecord, !$unhide );
1668 $infomsg = $current && !$context->msg(
'revision-info-current' )->isDisabled()
1669 ?
'revision-info-current'
1674 'mediawiki.action.styles',
1675 'mediawiki.interface.helpers.styles'
1678 $revisionUser = $revisionRecord->getUser();
1679 $revisionInfo =
"<div id=\"mw-{$infomsg}\">" .
1680 $context->msg( $infomsg, $td )
1681 ->rawParams( $userlinks )
1683 $revisionRecord->getId(),
1686 $revisionUser ? $revisionUser->getName() :
''
1688 ->rawParams( $this->commentFormatter->formatRevision(
1699 ? $context->msg(
'currentrevisionlink' )->escaped()
1700 : $this->linkRenderer->makeKnownLink(
1702 $context->msg(
'currentrevisionlink' )->text(),
1707 ? $context->msg(
'diff' )->escaped()
1708 : $this->linkRenderer->makeKnownLink(
1710 $context->msg(
'diff' )->text(),
1718 $prevExist = (bool)$this->revisionStore->getPreviousRevision( $revisionRecord );
1719 $prevlink = $prevExist
1720 ? $this->linkRenderer->makeKnownLink(
1722 $context->msg(
'previousrevision' )->text(),
1725 'direction' =>
'prev',
1729 : $context->msg(
'previousrevision' )->escaped();
1730 $prevdiff = $prevExist
1731 ? $this->linkRenderer->makeKnownLink(
1733 $context->msg(
'diff' )->text(),
1740 : $context->msg(
'diff' )->escaped();
1741 $nextlink = $current
1742 ? $context->msg(
'nextrevision' )->escaped()
1743 : $this->linkRenderer->makeKnownLink(
1745 $context->msg(
'nextrevision' )->text(),
1748 'direction' =>
'next',
1752 $nextdiff = $current
1753 ? $context->msg(
'diff' )->escaped()
1754 : $this->linkRenderer->makeKnownLink(
1756 $context->msg(
'diff' )->text(),
1764 $cdel = Linker::getRevDeleteLink(
1765 $context->getAuthority(),
1770 if ( $cdel !==
'' ) {
1778 "<div id=\"mw-revision-nav\">" . $cdel .
1779 $context->msg(
'revision-nav' )->rawParams(
1780 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1781 )->escaped() .
"</div>",
1800 if ( is_array( $target ) ) {
1802 wfDeprecatedMsg(
'The $target parameter can no longer be an array',
'1.39' );
1803 $target = reset( $target );
1806 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1808 $html =
'<ul class="redirectText">';
1809 if ( $forceKnown ) {
1812 $target->getFullText(),
1815 $target->isRedirect() ? [
'redirect' =>
'no' ] : []
1820 $target->getFullText(),
1823 $target->isRedirect() ? [
'redirect' =>
'no' ] : []
1826 $html .=
'<li>' . $link .
'</li>';
1829 $redirectToText =
wfMessage(
'redirectto' )->inLanguage(
$lang )->escaped();
1831 return '<div class="redirectMsg">' .
1832 '<p>' . $redirectToText .
'</p>' .
1847 $msg = $out->msg(
'namespace-' . $this->
getTitle()->getNamespace() .
'-helppage' );
1849 if ( !$msg->isDisabled() ) {
1850 $title = Title::newFromText( $msg->plain() );
1852 $out->addHelpLink(
$title->getLocalURL(),
true );
1855 $out->addHelpLink( $to, $overrideBaseUrl );
1863 $this->
getContext()->getRequest()->response()->header(
'X-Robots-Tag: noindex' );
1864 $this->
getContext()->getOutput()->setArticleBodyOnly(
true );
1866 $this->viewIsRenderAction =
true;
1898 public function doDelete( $reason, $suppress =
false, $immediate =
false ) {
1903 $user = $context->getUser();
1904 $status = $this->mPage->doDeleteArticleReal(
1905 $reason, $user, $suppress,
null, $error,
1906 null, [],
'delete', $immediate
1909 if ( $status->isOK() ) {
1910 $deleted = $this->
getTitle()->getPrefixedText();
1912 $outputPage->
setPageTitle( $context->msg(
'actioncomplete' ) );
1915 if ( $status->isGood() ) {
1916 $loglink =
'[[Special:Log/delete|' . $context->msg(
'deletionlog' )->text() .
']]';
1918 $this->getHookRunner()->onArticleDeleteAfterSuccess( $this->
getTitle(), $outputPage );
1926 $context->msg(
'cannotdelete-title',
1927 $this->getTitle()->getPrefixedText() )
1930 if ( $error ==
'' ) {
1932 'error mw-error-cannotdelete',
1933 $status->getWikiText(
false,
false, $context->getLanguage() )
1935 $deleteLogPage =
new LogPage(
'delete' );
1936 $outputPage->
addHTML( Xml::element(
'h2',
null, $deleteLogPage->getName()->text() ) );
1938 LogEventsList::showLogExtract(
1944 $outputPage->
addHTML( $error );
1959 static $called =
false;
1962 wfDebug(
"Article::tryFileCache(): called twice!?" );
1967 if ( $this->isFileCacheable() ) {
1969 if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1970 wfDebug(
"Article::tryFileCache(): about to load file" );
1971 $cache->loadFromFileCache( $this->
getContext() );
1974 wfDebug(
"Article::tryFileCache(): starting buffer" );
1975 ob_start( [ &$cache,
'saveToFileCache' ] );
1978 wfDebug(
"Article::tryFileCache(): not cacheable" );
1993 $cacheable = $this->mPage->getId()
1994 && !$this->mRedirectedFrom && !$this->
getTitle()->isRedirect();
1997 $cacheable = $this->getHookRunner()->onIsFileCacheable( $this ) ??
false;
2018 if ( $user ===
null ) {
2019 $parserOptions = $this->getParserOptions();
2021 $parserOptions = $this->mPage->makeParserOptions( $user );
2025 return $this->mPage->getParserOutput( $parserOptions, $oldid );
2033 $parserOptions = $this->mPage->makeParserOptions( $this->
getContext() );
2035 return $parserOptions;
2045 $this->mContext = $context;
2056 return $this->mContext;
2058 wfDebug( __METHOD__ .
" called and \$mContext is null. " .
2059 "Return RequestContext::getMain()" );
2060 return RequestContext::getMain();
2074 wfDeprecatedMsg(
"Accessing Article::\$$fname is deprecated since MediaWiki 1.35",
2077 if ( property_exists( $this->mPage, $fname ) ) {
2078 return $this->mPage->$fname;
2080 trigger_error(
'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2092 public function __set( $fname, $fvalue ) {
2093 wfDeprecatedMsg(
"Setting Article::\$$fname is deprecated since MediaWiki 1.35",
2096 if ( property_exists( $this->mPage, $fname ) ) {
2097 $this->mPage->$fname = $fvalue;
2099 } elseif ( !in_array( $fname, [
'mContext',
'mPage' ] ) ) {
2100 $this->mPage->$fname = $fvalue;
2102 trigger_error(
'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2112 return $this->mPage->getActionOverrides();
2122 return $this->mPage->getTimestamp();
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,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
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,...
doDelete( $reason, $suppress=false, $immediate=false)
Perform a deletion and output success or failure messages.
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.
getParserOutput( $oldid=null, UserIdentity $user=null)
#-
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
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.
Handle enqueueing of background jobs.
Base class for language-specific code.
Class to simplify the use of log pages.
The HTML user interface for page editing.
A class containing constants representing the names of configuration variables.
Service for getting rendered output of a given page.
Handles the page protection UI and backend.
Service for creating WikiPage objects.
This is one of the Core classes and should be read at least once by any new developers.
disable()
Disable output completely, i.e.
addWikiMsg(... $args)
Add a wikitext-formatted message to the output.
wrapWikiTextAsInterface( $wrapperClass, $text)
Convert wikitext in the user interface language to HTML and add it to the buffer with a <div class="$...
setArticleFlag( $newVal)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
setRobotPolicy( $policy)
Set the robot policy for the page: http://www.robotstxt.org/meta.html
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
setPageTitle( $name)
"Page title" means the contents of <h1>.
redirect( $url, $responsecode='302')
Redirect to $url rather than displaying the normal page.
setLastModified( $timestamp)
Override the last modified timestamp.
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] and pass it to lowerCdnMaxage()
setRevisionIsCurrent(bool $isCurrent)
Set whether the revision displayed (as set in ::setRevisionId()) is the latest revision of the page.
wrapWikiMsg( $wrap,... $msgSpecs)
This function takes a number of message/argument specifications, wraps them in some overall structure...
setCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
parseAsContent( $text, $linestart=true)
Parse wikitext in the page content language and return the HTML.
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
addWikiTextAsInterface( $text, $linestart=true, PageReference $title=null)
Convert wikitext in the user interface language to HTML and add it to the buffer.
setFollowPolicy( $policy)
Set the follow policy for the page, but leave the index policy un- touched.
isPrintable()
Return whether the page is "printable".
addModuleStyles( $modules)
Load the styles of one or more style-only ResourceLoader modules on this page.
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
returnToMain( $unused=null, $returnto=null, $returntoquery=null)
Add a "return to" link pointing to a specified title, or the title indicated in the request,...
disableClientCache()
Force the page to send nocache headers.
addSubtitle( $str)
Add $str to the subtitle.
setRevisionId( $revid)
Set the revision ID which will be seen by the wiki text parser for things such as embedded {{REVISION...
clearHTML()
Clear the body HTML.
setPreventClickjacking(bool $enable)
Set the prevent-clickjacking flag.
addHTML( $text)
Append $text to the body HTML.
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
setCanonicalUrl( $url)
Set the URL to be used for the <link rel=canonical>>.
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
setRedirectedFrom(PageReference $t)
Set $mRedirectedFrom, the page which redirected us to the current page.
prependHTML( $text)
Prepend $text to the body HTML.
addModules( $modules)
Load one or more ResourceLoader modules on this page.
addWikiTextAsContent( $text, $linestart=true, PageReference $title=null)
Convert wikitext in the page content language to HTML and add it to the buffer.
Used to show archived pages and eventually restore them.
Set options of the Parser.
setIsPrintable( $x)
Parsing the printable version of the page?
setRenderReason(string $renderReason)
Sets reason for rendering the content.
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, $page, array $params=[])
Show an error when a user tries to do something they do not have the necessary permissions for.
hasMessage( $message)
Returns true if the specified message is present as a warning or error.
isOK()
Returns whether the operation completed.
fatal( $message,... $parameters)
Add an error and set OK to false, indicating that the operation as a whole was fatal.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
getWikiText( $shortContext=false, $longContext=false, $lang=null)
Get the error list as a wikitext formatted list.
static newFromName( $name, $validate='valid')
Base representation for an editable wiki page.
getTitle()
Get the title object of the article.
Interface for objects which can provide a MediaWiki context on request.
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
if(!isset( $args[0])) $lang