24use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
37use Wikimedia\NonSerializable\NonSerializableTrait;
50 use ProtectedHookAccessorTrait;
51 use NonSerializableTrait;
73 public $mRedirectUrl =
false;
93 protected $viewIsRenderAction =
false;
128 $this->mOldId = $oldId;
131 $services = MediaWikiServices::getInstance();
132 $this->linkRenderer = $services->getLinkRenderer();
133 $this->revisionStore = $services->getRevisionStore();
134 $this->watchlistManager = $services->getWatchlistManager();
135 $this->userNameUtils = $services->getUserNameUtils();
152 $t = Title::newFromID( $id );
153 return $t ==
null ? null :
new static(
$t );
170 Hooks::runner()->onArticleFromTitle(
$title, $page, $context );
172 switch (
$title->getNamespace() ) {
183 $page->setContext( $context );
196 $article = self::newFromTitle( $page->
getTitle(), $context );
197 $article->mPage = $page;
207 return $this->mRedirectedFrom;
216 $this->mRedirectedFrom = $from;
225 return $this->mPage->getTitle();
239 $this->mRedirectedFrom =
null; #
Title object if set
240 $this->mRedirectUrl =
false;
241 $this->mRevisionRecord =
null;
242 $this->fetchResult =
null;
246 $this->mPage->clear();
257 if ( $this->mOldId ===
null ) {
261 return $this->mOldId;
270 $this->mRedirectUrl =
false;
273 $oldid = $request->getIntOrNull(
'oldid' );
275 if ( $oldid ===
null ) {
279 if ( $oldid !== 0 ) {
280 # Load the given revision and check whether the page is another one.
281 # In that case, update this instance to reflect the change.
282 if ( $oldid === $this->mPage->getLatest() ) {
283 $this->mRevisionRecord = $this->mPage->getRevisionRecord();
285 $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
286 if ( $this->mRevisionRecord !==
null ) {
287 $revPageId = $this->mRevisionRecord->getPageId();
289 if ( $this->mPage->getId() != $revPageId ) {
290 $function = get_class( $this->mPage ) .
'::newFromID';
291 $this->mPage = $function( $revPageId );
297 $oldRev = $this->mRevisionRecord;
298 if ( $request->getRawVal(
'direction' ) ===
'next' ) {
301 $nextRev = $this->revisionStore->getNextRevision( $oldRev );
303 $nextid = $nextRev->getId();
308 $this->mRevisionRecord =
null;
310 $this->mRedirectUrl = $this->
getTitle()->getFullURL(
'redirect=no' );
312 } elseif ( $request->getRawVal(
'direction' ) ===
'prev' ) {
315 $prevRev = $this->revisionStore->getPreviousRevision( $oldRev );
317 $previd = $prevRev->getId();
322 $this->mRevisionRecord =
null;
339 if ( $this->fetchResult ) {
340 return $this->mRevisionRecord;
346 if ( !$this->mRevisionRecord ) {
348 $this->mRevisionRecord = $this->mPage->getRevisionRecord();
350 if ( !$this->mRevisionRecord ) {
351 wfDebug( __METHOD__ .
" failed to find page data for title " .
352 $this->
getTitle()->getPrefixedText() );
355 $this->fetchResult = Status::newFatal(
'noarticletext' );
359 $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
361 if ( !$this->mRevisionRecord ) {
362 wfDebug( __METHOD__ .
" failed to load revision, rev_id $oldid" );
364 $this->fetchResult = Status::newFatal(
'missing-revision', $oldid );
370 if ( !$this->mRevisionRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getContext()->getAuthority() ) ) {
371 wfDebug( __METHOD__ .
" failed to retrieve content of revision " . $this->mRevisionRecord->getId() );
375 $this->fetchResult =
new Status;
378 if ( $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
379 $this->fetchResult->
fatal(
'rev-suppressed-text' );
381 $this->fetchResult->fatal(
'rev-deleted-text-permission',
$title );
387 $this->fetchResult = Status::newGood( $this->mRevisionRecord );
388 return $this->mRevisionRecord;
397 # If no oldid, this is the current version.
402 return $this->mPage->exists() &&
403 $this->mRevisionRecord &&
404 $this->mRevisionRecord->isCurrent();
416 if ( $this->fetchResult && $this->fetchResult->isOK() ) {
417 return $this->fetchResult->value->getId();
419 return $this->mPage->getLatest();
430 # Get variables from query string
431 # As side effect this will load the revision and update the title
432 # in a revision ID is passed in the request, so this should remain
433 # the first call of this method even if $oldid is used way below.
437 # Another check in case getOldID() is altering the title
438 $permissionStatus = PermissionStatus::newEmpty();
440 ->authorizeRead(
'read', $this->
getTitle(), $permissionStatus )
442 wfDebug( __METHOD__ .
": denied on secondary read check" );
447 # getOldID() may as well want us to redirect somewhere else
448 if ( $this->mRedirectUrl ) {
449 $outputPage->
redirect( $this->mRedirectUrl );
450 wfDebug( __METHOD__ .
": redirecting due to oldid" );
455 # If we got diff in the query, we want to see a diff page instead of the article.
456 if ( $this->
getContext()->getRequest()->getCheck(
'diff' ) ) {
457 wfDebug( __METHOD__ .
": showing diff page" );
463 # Set page title (may be overridden by DISPLAYTITLE)
467 # Allow frames by default
472 # Allow extensions to vary parser options used for article rendering
473 Hooks::runner()->onArticleParserOptions( $this, $parserOptions );
474 # Render printable version, use printable version cache
477 $poOptions[
'enableSectionEditLinks'] =
false;
480 $outputPage->
msg(
'printableversion-deprecated-warning' )->escaped()
483 } elseif ( $this->viewIsRenderAction || !$this->
isCurrent() ||
486 $poOptions[
'enableSectionEditLinks'] =
false;
489 # Try client and file cache
490 if ( $oldid === 0 && $this->mPage->checkTouched() ) {
491 # Try to stream the output from file cache
493 wfDebug( __METHOD__ .
": done file cache" );
494 # tell wgOut that output is taken care of
496 $this->mPage->doViewUpdates( $user, $oldid );
512 # For the main page, overwrite the <title> element with the con-
513 # tents of 'pagetitle-view-mainpage' instead of the default (if
515 # This message always exists because it is in the i18n files
516 if ( $this->
getTitle()->isMainPage() ) {
517 $msg =
wfMessage(
'pagetitle-view-mainpage' )->inContentLanguage();
518 if ( !$msg->isDisabled() ) {
519 $outputPage->
setHTMLTitle( $msg->page( $this->getTitle() )->text() );
523 # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
524 # This could use getTouched(), but that could be scary for major template edits.
525 $outputPage->
adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
528 $this->mPage->doViewUpdates( $user, $oldid );
530 # Load the postEdit module if the user just saved this revision
531 # See also EditPage::setPostEditCookie
534 $postEdit = $request->getCookie( $cookieKey );
536 # Clear the cookie. This also prevents caching of the response.
537 $request->response()->clearCookie( $cookieKey );
539 $outputPage->
addModules(
'mediawiki.action.view.postEdit' );
562 # Should the parser cache be used?
563 $useParserCache =
true;
565 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
568 $this->getHookRunner()->onArticleViewHeader( $this, $outputDone, $useParserCache );
571 $pOutput = $outputDone;
581 if ( !$this->mPage->exists() ) {
582 wfDebug( __METHOD__ .
": showing missing article" );
583 $this->showMissingArticle();
584 $this->mPage->doViewUpdates( $performer );
590 if ( $useParserCache && !$oldid ) {
591 $pOutput = $parserOutputAccess->getCachedParserOutput(
595 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK
606 if ( !$this->fetchResult->isOK() ) {
608 false,
false, $this->getContext()->getLanguage()
613 # Are we looking at an old revision
618 wfDebug( __METHOD__ .
": cannot view deleted revision" );
625 if ( $useParserCache ) {
626 $pOutput = $parserOutputAccess->getCachedParserOutput(
630 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK
641 # Ensure that UI elements requiring revision ID have
642 # the correct version information.
644 # Preload timestamp to avoid a DB hit
647 # Pages containing custom CSS or JavaScript get special treatment
648 if ( $this->
getTitle()->isSiteConfigPage() || $this->
getTitle()->isUserConfigPage() ) {
649 $dir = $this->
getContext()->getLanguage()->getDir();
653 "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
657 } elseif ( !$this->getHookRunner()->onArticleRevisionViewCustom(
669 # Run the parse, protected by a pool counter
670 wfDebug( __METHOD__ .
": doing uncached parse" );
680 $opt |= ParserOutputAccess::OPT_NO_CHECK_CACHE;
683 $opt |= ParserOutputAccess::OPT_NO_AUDIENCE_CHECK;
685 if ( !$rev->getId() || !$useParserCache ) {
687 $opt |= ParserOutputAccess::OPT_NO_CACHE;
690 $renderStatus = $parserOutputAccess->getParserOutput(
704 if ( !$renderStatus->isOK() ) {
708 $pOutput = $renderStatus->getValue();
718 # Adjust title for main page & pages with displaytitle
720 $this->adjustDisplayTitle( $pOutput );
723 # Check for any __NOINDEX__ tags on the page using $pOutput
724 $policy = $this->getRobotPolicy(
'view', $pOutput ?: null );
728 $this->mParserOutput = $pOutput;
741 # Ensure that UI elements requiring revision ID have
742 # the correct version information.
746 # Preload timestamp to avoid a DB hit
748 if ( $cachedTimestamp !==
null ) {
750 $this->mPage->setTimestamp( $cachedTimestamp );
767 $ok = $renderStatus->
isOK();
769 $pOutput = $ok ? $renderStatus->
getValue() :
null;
772 if ( $ok && $renderStatus->
hasMessage(
'view-pool-dirty-output' ) ) {
775 $staleReason = $renderStatus->
hasMessage(
'view-pool-contention' )
776 ? $this->
getContext()->msg(
'view-pool-contention' )
777 : $this->
getContext()->msg(
'view-pool-timeout' );
778 $outputPage->
addHTML(
"<!-- parser cache is expired, " .
779 "sending anyway due to $staleReason-->\n" );
782 if ( !$renderStatus->
isOK() ) {
784 false,
'view-pool-error', $this->getContext()->getLanguage()
793 if ( $this->getRevisionRedirectTarget( $rev ) ) {
794 $outputPage->
addSubtitle(
"<span id=\"redirectsub\">" .
795 $this->
getContext()->msg(
'redirectpagesub' )->parse() .
"</span>" );
818 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
820 if ( strval( $titleText ) !==
'' ) {
821 $out->setPageTitle( $titleText );
822 $out->setDisplayTitle( $titleText );
833 $diff = $request->getVal(
'diff' );
834 $rcid = $request->getVal(
'rcid' );
835 $diffOnly = $request->getBool(
'diffonly', $user->getOption(
'diffonly' ) );
836 $purge = $request->getRawVal(
'action' ) ===
'purge';
837 $unhide = $request->getInt(
'unhide' ) == 1;
838 $oldid = $this->getOldID();
840 $rev = $this->fetchRevisionRecord();
845 $rev = $this->revisionStore->getRevisionById( $oldid );
852 $msg = $this->
getContext()->msg(
'difference-missing-revision' )
856 $this->
getContext()->getOutput()->addHTML( $msg );
861 $contentHandler = MediaWikiServices::getInstance()
862 ->getContentHandlerFactory()
864 $rev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel()
866 $de = $contentHandler->createDifferenceEngine(
874 $de->setSlotDiffOptions( [
875 'diff-type' => $request->getVal(
'diff-type' )
877 $de->showDiffPage( $diffOnly );
881 list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
883 $this->mPage->doViewUpdates( $user, (
int)$new );
896 $ns = $this->
getTitle()->getNamespace();
898 # Don't index user and user talk pages for blocked users (T13443)
900 $specificTarget =
null;
902 $titleText = $this->
getTitle()->getText();
903 if ( IPUtils::isValid( $titleText ) ) {
904 $vagueTarget = $titleText;
906 $specificTarget = $titleText;
908 if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof
DatabaseBlock ) {
910 'index' =>
'noindex',
911 'follow' =>
'nofollow'
916 if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
917 # Non-articles (special pages etc), and old revisions
919 'index' =>
'noindex',
920 'follow' =>
'nofollow'
922 } elseif ( $this->
getContext()->getOutput()->isPrintable() ) {
923 # Discourage indexing of printable versions, but encourage following
925 'index' =>
'noindex',
928 } elseif ( $this->
getContext()->getRequest()->getInt(
'curid' ) ) {
929 # For ?curid=x urls, disallow indexing
931 'index' =>
'noindex',
936 # Otherwise, construct the policy based on the various config variables.
940 # Honour customised robot policies for this namespace
941 $policy = array_merge(
946 if ( $this->
getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
947 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
948 # a final sanity check that we have really got the parser output.
949 $policy = array_merge(
951 [
'index' => $pOutput->getIndexPolicy() ]
956 # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
957 $policy = array_merge(
974 if ( is_array( $policy ) ) {
976 } elseif ( !$policy ) {
980 $policy = explode(
',', $policy );
981 $policy = array_map(
'trim', $policy );
984 foreach ( $policy as $var ) {
985 if ( in_array( $var, [
'index',
'noindex' ] ) ) {
986 $arr[
'index'] = $var;
987 } elseif ( in_array( $var, [
'follow',
'nofollow' ] ) ) {
988 $arr[
'follow'] = $var;
1007 $request = $context->getRequest();
1008 $rdfrom = $request->getVal(
'rdfrom' );
1011 $query = $request->getValues();
1012 unset( $query[
'rdfrom'] );
1013 unset( $query[
'title'] );
1014 if ( $this->
getTitle()->isRedirect() ) {
1016 $query[
'redirect'] =
'no';
1018 $redirectTargetUrl = $this->
getTitle()->getLinkURL( $query );
1020 if ( isset( $this->mRedirectedFrom ) ) {
1023 if ( $this->getHookRunner()->onArticleViewRedirect( $this ) ) {
1024 $redir = $this->linkRenderer->makeKnownLink(
1025 $this->mRedirectedFrom,
1028 [
'redirect' =>
'no' ]
1031 $outputPage->
addSubtitle(
"<span class=\"mw-redirectedfrom\">" .
1032 $context->msg(
'redirectedfrom' )->rawParams( $redir )->parse()
1038 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1040 $outputPage->
addModules(
'mediawiki.action.view.redirect' );
1050 } elseif ( $rdfrom ) {
1055 $outputPage->
addSubtitle(
"<span class=\"mw-redirectedfrom\">" .
1056 $context->msg(
'redirectedfrom' )->rawParams( $redir )->parse()
1061 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1063 $outputPage->
addModules(
'mediawiki.action.view.redirect' );
1077 if ( $this->
getTitle()->isTalkPage() && !
wfMessage(
'talkpageheader' )->isDisabled() ) {
1078 $this->
getContext()->getOutput()->wrapWikiMsg(
1079 "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1080 [
'talkpageheader' ]
1089 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1091 && IPUtils::isValid( $this->
getTitle()->getText() )
1093 $this->
getContext()->getOutput()->addWikiMsg(
'anontalkpagetext' );
1097 $patrolFooterShown = $this->showPatrolFooter();
1099 $this->getHookRunner()->onArticleViewFooter( $this, $patrolFooterShown );
1116 if ( !$this->getHookRunner()->onArticleShowPatrolFooter( $this ) ) {
1133 if ( $this->mRevisionRecord
1134 && !RecentChange::isInRCLifespan( $this->mRevisionRecord->getTimestamp(), 21600 )
1142 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1143 $key =
$cache->makeKey(
'unpatrollable-page',
$title->getArticleID() );
1144 if (
$cache->get( $key ) ) {
1149 $oldestRevisionTimestamp =
$dbr->selectField(
1151 'MIN( rev_timestamp )',
1152 [
'rev_page' =>
$title->getArticleID() ],
1161 $recentPageCreation =
false;
1162 if ( $oldestRevisionTimestamp
1163 && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1166 $recentPageCreation =
true;
1167 $rc = RecentChange::newFromConds(
1170 'rc_timestamp' => $oldestRevisionTimestamp,
1171 'rc_namespace' =>
$title->getNamespace(),
1172 'rc_cur_id' =>
$title->getArticleID()
1178 $markPatrolledMsg =
wfMessage(
'markaspatrolledtext' );
1186 $recentFileUpload =
false;
1190 $newestUploadTimestamp =
$dbr->selectField(
1192 'MAX( img_timestamp )',
1193 [
'img_name' =>
$title->getDBkey() ],
1196 if ( $newestUploadTimestamp
1197 && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1200 $recentFileUpload =
true;
1201 $rc = RecentChange::newFromConds(
1204 'rc_log_type' =>
'upload',
1205 'rc_timestamp' => $newestUploadTimestamp,
1207 'rc_cur_id' =>
$title->getArticleID()
1213 $markPatrolledMsg =
wfMessage(
'markaspatrolledtext-file' );
1218 if ( !$recentPageCreation && !$recentFileUpload ) {
1223 $cache->set( $key,
'1' );
1235 if ( $rc->getAttribute(
'rc_patrolled' ) ) {
1240 $cache->set( $key,
'1' );
1245 if ( $rc->getPerformerIdentity()->equals( $user ) ) {
1253 $outputPage->
addModules(
'mediawiki.misc-authed-curate' );
1256 $link = $this->linkRenderer->makeKnownLink(
1258 $markPatrolledMsg->text(),
1261 'action' =>
'markpatrolled',
1262 'rcid' => $rc->getAttribute(
'rc_id' ),
1268 "<div class='patrollink' data-mw='interface'>" .
1269 wfMessage(
'markaspatrolledlink' )->rawParams( $link )->escaped() .
1283 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1284 $cache->delete(
$cache->makeKey(
'unpatrollable-page', $articleID ) );
1296 $validUserPage =
false;
1300 $services = MediaWikiServices::getInstance();
1302 $contextUser = $this->
getContext()->getUser();
1304 # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1308 $rootPart = explode(
'/',
$title->getText() )[0];
1310 $ip = $this->userNameUtils->isIP( $rootPart );
1311 $block = DatabaseBlock::newFromTarget( $user, $user );
1313 if ( $user && $user->isRegistered() && $user->isHidden() &&
1314 !$this->getContext()->getAuthority()->isAllowed(
'hideuser' )
1321 if ( !( $user && $user->isRegistered() ) && !$ip ) { #
User does not exist
1322 $outputPage->
wrapWikiMsg(
"<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1326 $block->getType() != DatabaseBlock::TYPE_AUTO &&
1328 $block->isSitewide() ||
1329 $user->isBlockedFrom(
$title,
true )
1334 LogEventsList::showLogExtract(
1337 $services->getNamespaceInfo()->getCanonicalName(
NS_USER ) .
':' .
1338 $block->getTargetName(),
1342 'showIfEmpty' =>
false,
1344 'blocked-notice-logextract',
1345 $user->getName() # Support GENDER in notice
1349 $validUserPage = !
$title->isSubpage();
1351 $validUserPage = !
$title->isSubpage();
1355 $this->getHookRunner()->onShowMissingArticle( $this );
1357 # Show delete and move logs if there were any such events.
1358 # The logging query can DOS the site when bots/crawlers cause 404 floods,
1359 # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1360 $dbCache = ObjectCache::getInstance(
'db-replicated' );
1361 $key = $dbCache->makeKey(
'page-recent-delete', md5(
$title->getPrefixedText() ) );
1362 $isRegistered = $contextUser->isRegistered();
1363 $sessionExists = $this->
getContext()->getRequest()->getSession()->isPersistent();
1365 if ( $isRegistered || $dbCache->get( $key ) || $sessionExists ) {
1366 $logTypes = [
'delete',
'move',
'protect' ];
1370 $conds = [
'log_action != ' .
$dbr->addQuotes(
'revision' ) ];
1372 $this->getHookRunner()->onArticle__MissingArticleConditions( $conds, $logTypes );
1373 LogEventsList::showLogExtract(
1381 'showIfEmpty' =>
false,
1382 'msgKey' => [ $isRegistered || $sessionExists
1383 ?
'moveddeleted-notice'
1384 :
'moveddeleted-notice-recent'
1390 if ( !$this->mPage->hasViewableContent() &&
$wgSend404Code && !$validUserPage ) {
1393 $this->
getContext()->getRequest()->response()->statusHeader( 404 );
1397 $policy = $this->getRobotPolicy(
'view' );
1401 $hookResult = $this->getHookRunner()->onBeforeDisplayNoArticleText( $this );
1403 if ( !$hookResult ) {
1407 # Show error message
1408 $oldid = $this->getOldID();
1410 $text = $this->
getTitle()->getDefaultMessageText() ??
'';
1417 $revRecord = $pa->getArchivedRevisionRecord( $oldid );
1418 if ( $revRecord && $revRecord->userCan(
1419 RevisionRecord::DELETED_TEXT,
1420 $this->getContext()->getAuthority()
1423 'missing-revision-permission', $oldid,
1424 $revRecord->getTimestamp(),
1425 $title->getPrefixedDBkey()
1428 $text =
wfMessage(
'missing-revision', $oldid )->plain();
1433 $message = $isRegistered ?
'noarticletext' :
'noarticletextanon';
1436 $text =
wfMessage(
'noarticletext-nopermission' )->plain();
1439 $dir = $this->
getContext()->getLanguage()->getDir();
1442 'class' =>
"noarticletext mw-content-$dir",
1445 ] ) .
"\n$text\n</div>" );
1469 if ( !$this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1476 $titleText = $this->
getTitle()->getPrefixedDBkey();
1478 if ( !$this->mRevisionRecord->userCan(
1479 RevisionRecord::DELETED_TEXT,
1480 $this->getContext()->getAuthority()
1482 $outputPage->addHtml(
1484 $outputPage->
msg(
'rev-deleted-text-permission', $titleText )->parse(),
1491 } elseif ( $this->
getContext()->getRequest()->getInt(
'unhide' ) != 1 ) {
1492 # Give explanation and add a link to view the revision...
1493 $oldid = intval( $this->getOldID() );
1494 $link = $this->
getTitle()->getFullURL(
"oldid={$oldid}&unhide=1" );
1495 $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1496 'rev-suppressed-text-unhide' :
'rev-deleted-text-unhide';
1497 $outputPage->addHtml(
1499 $outputPage->
msg( $msg, $link )->parse(),
1507 $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
1508 ? [
'rev-suppressed-text-view', $titleText ]
1509 : [
'rev-deleted-text-view', $titleText ];
1510 $outputPage->addHtml(
1512 $outputPage->
msg( $msg[0], $msg[1] )->parse(),
1530 if ( !$this->getHookRunner()->onDisplayOldSubtitle( $this, $oldid ) ) {
1535 $unhide = $context->getRequest()->getInt(
'unhide' ) == 1;
1537 # Cascade unhide param in links for easy deletion browsing
1540 $extraParams[
'unhide'] = 1;
1543 if ( $this->mRevisionRecord && $this->mRevisionRecord->getId() === $oldid ) {
1544 $revisionRecord = $this->mRevisionRecord;
1546 $revisionRecord = $this->revisionStore->getRevisionById( $oldid );
1549 $timestamp = $revisionRecord->getTimestamp();
1551 $current = ( $oldid == $this->mPage->getLatest() );
1552 $language = $context->getLanguage();
1553 $user = $context->getUser();
1555 $td = $language->userTimeAndDate( $timestamp, $user );
1556 $tddate = $language->userDate( $timestamp, $user );
1557 $tdtime = $language->userTime( $timestamp, $user );
1559 # Show user links if allowed to see them. If hidden, then show them only if requested...
1562 $infomsg = $current && !$context->msg(
'revision-info-current' )->isDisabled()
1563 ?
'revision-info-current'
1568 'mediawiki.action.styles',
1569 'mediawiki.interface.helpers.styles'
1572 $revisionUser = $revisionRecord->getUser();
1573 $revisionInfo =
"<div id=\"mw-{$infomsg}\">" .
1574 $context->msg( $infomsg, $td )
1575 ->rawParams( $userlinks )
1577 $revisionRecord->getId(),
1580 $revisionUser ? $revisionUser->getName() :
''
1591 ? $context->msg(
'currentrevisionlink' )->escaped()
1592 : $this->linkRenderer->makeKnownLink(
1594 $context->msg(
'currentrevisionlink' )->text(),
1599 ? $context->msg(
'diff' )->escaped()
1600 : $this->linkRenderer->makeKnownLink(
1602 $context->msg(
'diff' )->text(),
1609 $prevExist = (bool)$this->revisionStore->getPreviousRevision( $revisionRecord );
1610 $prevlink = $prevExist
1611 ? $this->linkRenderer->makeKnownLink(
1613 $context->msg(
'previousrevision' )->text(),
1616 'direction' =>
'prev',
1620 : $context->msg(
'previousrevision' )->escaped();
1621 $prevdiff = $prevExist
1622 ? $this->linkRenderer->makeKnownLink(
1624 $context->msg(
'diff' )->text(),
1631 : $context->msg(
'diff' )->escaped();
1632 $nextlink = $current
1633 ? $context->msg(
'nextrevision' )->escaped()
1634 : $this->linkRenderer->makeKnownLink(
1636 $context->msg(
'nextrevision' )->text(),
1639 'direction' =>
'next',
1643 $nextdiff = $current
1644 ? $context->msg(
'diff' )->escaped()
1645 : $this->linkRenderer->makeKnownLink(
1647 $context->msg(
'diff' )->text(),
1660 if ( $cdel !==
'' ) {
1665 $outputPage->
addSubtitle(
"<div class=\"mw-revision warningbox\">" . $revisionInfo .
1666 "<div id=\"mw-revision-nav\">" . $cdel .
1667 $context->msg(
'revision-nav' )->rawParams(
1668 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1669 )->escaped() .
"</div></div>" );
1685 public function viewRedirect( $target, $appendSubtitle =
true, $forceKnown =
false ) {
1688 if ( $appendSubtitle ) {
1689 $out->addSubtitle(
wfMessage(
'redirectpagesub' ) );
1691 $out->addModuleStyles(
'mediawiki.action.view.redirectPage' );
1692 return static::getRedirectHeaderHtml(
$lang, $target, $forceKnown );
1708 if ( !is_array( $target ) ) {
1709 $target = [ $target ];
1712 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1714 $html =
'<ul class="redirectText">';
1716 foreach ( $target as
$title ) {
1717 if ( $forceKnown ) {
1723 $title->isRedirect() ? [
'redirect' =>
'no' ] : []
1731 $title->isRedirect() ? [
'redirect' =>
'no' ] : []
1734 $html .=
'<li>' . $link .
'</li>';
1738 $redirectToText =
wfMessage(
'redirectto' )->inLanguage(
$lang )->escaped();
1740 return '<div class="redirectMsg">' .
1741 '<p>' . $redirectToText .
'</p>' .
1756 $msg = $out->msg(
'namespace-' . $this->
getTitle()->getNamespace() .
'-helppage' );
1758 if ( !$msg->isDisabled() ) {
1759 $title = Title::newFromText( $msg->plain() );
1761 $out->addHelpLink(
$title->getLocalURL(),
true );
1764 $out->addHelpLink( $to, $overrideBaseUrl );
1772 $this->
getContext()->getRequest()->response()->header(
'X-Robots-Tag: noindex' );
1773 $this->
getContext()->getOutput()->setArticleBodyOnly(
true );
1775 $this->viewIsRenderAction =
true;
1802 public function doDelete( $reason, $suppress =
false, $immediate =
false ) {
1806 $user = $context->getUser();
1807 $status = $this->mPage->doDeleteArticleReal(
1808 $reason, $user, $suppress,
null, $error,
1809 null, [],
'delete', $immediate
1812 if ( $status->isOK() ) {
1813 $deleted = $this->
getTitle()->getPrefixedText();
1818 if ( $status->isGood() ) {
1819 $loglink =
'[[Special:Log/delete|' .
wfMessage(
'deletionlog' )->text() .
']]';
1821 $this->getHookRunner()->onArticleDeleteAfterSuccess( $this->
getTitle(), $outputPage );
1830 $this->
getTitle()->getPrefixedText() )
1833 if ( $error ==
'' ) {
1835 'error mw-error-cannotdelete',
1836 $status->getWikiText(
false,
false, $context->getLanguage() )
1838 $deleteLogPage =
new LogPage(
'delete' );
1839 $outputPage->
addHTML( Xml::element(
'h2',
null, $deleteLogPage->getName()->text() ) );
1841 LogEventsList::showLogExtract(
1847 $outputPage->
addHTML( $error );
1862 static $called =
false;
1865 wfDebug(
"Article::tryFileCache(): called twice!?" );
1870 if ( $this->isFileCacheable() ) {
1872 if (
$cache->isCacheGood( $this->mPage->getTouched() ) ) {
1873 wfDebug(
"Article::tryFileCache(): about to load file" );
1877 wfDebug(
"Article::tryFileCache(): starting buffer" );
1878 ob_start( [ &
$cache,
'saveToFileCache' ] );
1881 wfDebug(
"Article::tryFileCache(): not cacheable" );
1896 $cacheable = $this->mPage->getId()
1897 && !$this->mRedirectedFrom && !$this->
getTitle()->isRedirect();
1900 $cacheable = $this->getHookRunner()->onIsFileCacheable( $this );
1921 if ( $user ===
null ) {
1922 $parserOptions = $this->getParserOptions();
1924 $parserOptions = $this->mPage->makeParserOptions( $user );
1927 return $this->mPage->getParserOutput( $parserOptions, $oldid );
1935 return $this->mPage->makeParserOptions( $this->
getContext() );
1945 $this->mContext = $context;
1956 return $this->mContext;
1958 wfDebug( __METHOD__ .
" called and \$mContext is null. " .
1959 "Return RequestContext::getMain(); for sanity" );
1960 return RequestContext::getMain();
1974 wfDeprecatedMsg(
"Accessing Article::\$$fname is deprecated since MediaWiki 1.35",
1977 if ( property_exists( $this->mPage, $fname ) ) {
1978 return $this->mPage->$fname;
1980 trigger_error(
'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
1992 public function __set( $fname, $fvalue ) {
1993 wfDeprecatedMsg(
"Setting Article::\$$fname is deprecated since MediaWiki 1.35",
1996 if ( property_exists( $this->mPage, $fname ) ) {
1997 $this->mPage->$fname = $fvalue;
1999 } elseif ( !in_array( $fname, [
'mContext',
'mPage' ] ) ) {
2000 $this->mPage->$fname = $fvalue;
2002 trigger_error(
'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2012 return $this->mPage->getActionOverrides();
2022 return $this->mPage->getTimestamp();
$wgArticleRobotPolicies
Robot policies per article.
$wgCdnMaxageStale
Cache timeout when delivering a stale ParserCache response due to PoolCounter contention.
$wgDefaultRobotPolicy
Default robot policy.
$wgSend404Code
Some web hosts attempt to rewrite all responses with a 404 (not found) status code,...
$wgRedirectSources
If local interwikis are set up which allow redirects, set this regexp to restrict URLs which will be ...
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
$wgNamespaceRobotPolicies
Robot policies per namespaces.
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
$wgUseFileCache
This will cache static pages for non-logged-in users to reduce database traffic on public sites.
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.
Class for viewing MediaWiki article and history.
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
UserNameUtils $userNameUtils
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.
RevisionRecord null $mRevisionRecord
Revision to be shown.
RevisionStore $revisionStore
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)
#-
showViewError(string $errortext)
Show error text for errors generated in Article::view().
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
protect()
action=protect handler
generateContentOutput(Authority $performer, ParserOptions $parserOptions, int $oldid, OutputPage $outputPage, array $textOptions)
Determines the desired ParserOutput and passes it to $outputPage.
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.
string bool $mRedirectUrl
URL to redirect to or false if none.
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.
viewRedirect( $target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
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.
Status null $fetchResult
represents the outcome of fetchRevisionRecord().
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.
doOutputMetaData(?ParserOutput $pOutput, OutputPage $outputPage)
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]].
WatchlistManager $watchlistManager
getRevisionRedirectTarget(RevisionRecord $revision)
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
doOutputFromRenderStatus(?RevisionRecord $rev, Status $renderStatus, OutputPage $outputPage, array $textOptions)
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.
doOutputFromParserCache(ParserOutput $pOutput, OutputPage $outputPage, array $textOptions)
setContext( $context)
Sets the context this Article is executed in.
Special handling for category description pages, showing pages, subcategories and file that belong to...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
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.
Class for viewing MediaWiki file description pages.
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
static revComment(RevisionRecord $revRecord, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
static getRevDeleteLink(Authority $performer, RevisionRecord $revRecord, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
static revUserTools(RevisionRecord $revRecord, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Class to simplify the use of log pages.
Service for getting rendered output of a given page.
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.
allowClickjacking()
Turn off frame-breaking.
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()
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.
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
preventClickjacking( $enable=true)
Set a flag which will cause an X-Frame-Options header appropriate for edit pages to be sent.
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,...
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.
enableClientCache( $state)
Use enableClientCache(false) to force it to send nocache headers.
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?
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.
Represents a title within MediaWiki.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
static newFromName( $name, $validate='valid')
Class representing a MediaWiki article and history.
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