131 $this->mOldId = $oldId;
132 $this->mPage = $this->
newPage( $title );
150 return $t ==
null ? null :
new static(
$t );
169 switch (
$title->getNamespace() ) {
194 $article->mPage = $page;
213 $this->mRedirectedFrom = $from;
222 return $this->mPage->getTitle();
239 $this->mContentLoaded =
false;
241 $this->mRedirectedFrom =
null; #
Title object if set
242 $this->mRevIdFetched = 0;
243 $this->mRedirectUrl =
false;
244 $this->mRevision =
null;
245 $this->mContentObject =
null;
246 $this->fetchResult =
null;
250 $this->mPage->clear();
271 if ( $this->mPage->getId() === 0 ) {
287 # If this is a MediaWiki:x message, then load the messages
288 # and return the message value for x.
290 $text = $this->
getTitle()->getDefaultMessageText();
291 if ( $text ===
false ) {
297 $message = $this->
getContext()->getUser()->isLoggedIn() ?
'noarticletext' :
'noarticletextanon';
327 if ( is_null( $this->mOldId ) ) {
340 $this->mRedirectUrl =
false;
343 $oldid = $request->getIntOrNull(
'oldid' );
345 if ( $oldid ===
null ) {
349 if ( $oldid !== 0 ) {
350 # Load the given revision and check whether the page is another one.
351 # In that case, update this instance to reflect the change.
352 if ( $oldid === $this->mPage->getLatest() ) {
353 $this->mRevision = $this->mPage->getRevision();
356 if ( $this->mRevision !==
null ) {
358 if ( $this->mPage->getId() != $this->mRevision->getPage() ) {
359 $function = get_class( $this->mPage ) .
'::newFromID';
360 $this->mPage = $function( $this->mRevision->getPage() );
366 $rl = MediaWikiServices::getInstance()->getRevisionLookup();
367 $oldRev = $this->mRevision ? $this->mRevision->getRevisionRecord() :
null;
368 if ( $request->getVal(
'direction' ) ==
'next' ) {
371 $nextRev = $rl->getNextRevision( $oldRev );
373 $nextid = $nextRev->getId();
378 $this->mRevision =
null;
380 $this->mRedirectUrl = $this->
getTitle()->getFullURL(
'redirect=no' );
382 } elseif ( $request->getVal(
'direction' ) ==
'prev' ) {
385 $prevRev = $rl->getPreviousRevision( $oldRev );
387 $previd = $prevRev->getId();
392 $this->mRevision =
null;
396 $this->mRevIdFetched = $this->mRevision ? $this->mRevision->getId() : 0;
415 if ( !$this->mContentLoaded ) {
432 if ( $this->fetchResult ) {
433 return $this->mRevision ? $this->mRevision->getRevisionRecord() :
null;
436 $this->mContentLoaded =
true;
437 $this->mContentObject =
null;
442 if ( !$this->mRevision ) {
444 $this->mRevision = $this->mPage->getRevision();
446 if ( !$this->mRevision ) {
447 wfDebug( __METHOD__ .
" failed to find page data for title " .
448 $this->
getTitle()->getPrefixedText() .
"\n" );
458 if ( !$this->mRevision ) {
459 wfDebug( __METHOD__ .
" failed to load revision, rev_id $oldid\n" );
468 $this->mRevIdFetched = $this->mRevision->getId();
472 !$this->mRevision->userCan( RevisionRecord::DELETED_TEXT, $this->getContext()->getUser() )
474 wfDebug( __METHOD__ .
" failed to retrieve content of revision " .
475 $this->mRevision->getId() .
"\n" );
484 $contentObject = $this->mRevision->getContent(
485 RevisionRecord::FOR_THIS_USER,
489 $hookContentObject = $contentObject;
492 $articlePage = $this;
495 'ArticleAfterFetchContentObject',
496 [ &$articlePage, &$hookContentObject ],
500 if ( $hookContentObject !== $contentObject ) {
507 $this->mContentObject = $this->mRevision->getContent(
508 RevisionRecord::FOR_THIS_USER,
512 return $this->mRevision->getRevisionRecord();
522 if ( !$this->fetchResult || $this->fetchResult->isOK() ) {
544 $rev->setContent( SlotRecord::MAIN, $override );
546 $this->mRevision =
new Revision( $rev );
549 $this->mContentObject = $override;
558 # If no oldid, this is the current version.
563 return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
578 if ( $this->fetchResult->isOK() ) {
592 if ( $this->fetchResult && $this->fetchResult->isOK() ) {
593 return $this->fetchResult->value->getId();
595 return $this->mPage->getLatest();
606 # Get variables from query string
607 # As side effect this will load the revision and update the title
608 # in a revision ID is passed in the request, so this should remain
609 # the first call of this method even if $oldid is used way below.
613 # Another whitelist check in case getOldID() is altering the title
614 $permErrors = $this->
getTitle()->getUserPermissionsErrors(
'read', $user );
615 if ( count( $permErrors ) ) {
616 wfDebug( __METHOD__ .
": denied on secondary read check\n" );
620 $outputPage = $this->
getContext()->getOutput();
621 # getOldID() may as well want us to redirect somewhere else
622 if ( $this->mRedirectUrl ) {
623 $outputPage->redirect( $this->mRedirectUrl );
624 wfDebug( __METHOD__ .
": redirecting due to oldid\n" );
629 # If we got diff in the query, we want to see a diff page instead of the article.
630 if ( $this->
getContext()->getRequest()->getCheck(
'diff' ) ) {
631 wfDebug( __METHOD__ .
": showing diff page\n" );
637 # Set page title (may be overridden by DISPLAYTITLE)
638 $outputPage->setPageTitle( $this->
getTitle()->getPrefixedText() );
640 $outputPage->setArticleFlag(
true );
641 # Allow frames by default
642 $outputPage->allowClickjacking();
644 $parserCache = MediaWikiServices::getInstance()->getParserCache();
648 # Render printable version, use printable version cache
649 if ( $outputPage->isPrintable() ) {
650 $parserOptions->setIsPrintable(
true );
651 $poOptions[
'enableSectionEditLinks'] =
false;
652 } elseif ( $this->viewIsRenderAction || !$this->
isCurrent() ||
654 ->quickUserCan(
'edit', $user, $this->
getTitle() )
656 $poOptions[
'enableSectionEditLinks'] =
false;
659 # Try client and file cache
660 if ( $oldid === 0 && $this->mPage->checkTouched() ) {
661 # Try to stream the output from file cache
663 wfDebug( __METHOD__ .
": done file cache\n" );
664 # tell wgOut that output is taken care of
665 $outputPage->disable();
666 $this->mPage->doViewUpdates( $user, $oldid );
672 # Should the parser cache be used?
673 $useParserCache = $this->mPage->shouldCheckParserCache( $parserOptions, $oldid );
674 wfDebug(
'Article::view using parser cache: ' . ( $useParserCache ?
'yes' :
'no' ) .
"\n" );
675 if ( $user->getStubThreshold() ) {
676 MediaWikiServices::getInstance()->getStatsdDataFactory()->increment(
'pcache_miss_stub' );
682 # Iterate through the possible ways of constructing the output text.
683 # Keep going until $outputDone is set, or we run out of things to do.
686 $this->mParserOutput =
false;
688 while ( !$outputDone && ++$pass ) {
692 $articlePage = $this;
693 Hooks::run(
'ArticleViewHeader', [ &$articlePage, &$outputDone, &$useParserCache ] );
696 # Early abort if the page doesn't exist
697 if ( !$this->mPage->exists() ) {
698 wfDebug( __METHOD__ .
": showing missing article\n" );
700 $this->mPage->doViewUpdates( $user );
704 # Try the parser cache
705 if ( $useParserCache ) {
706 $this->mParserOutput = $parserCache->get( $this->mPage, $parserOptions );
708 if ( $this->mParserOutput !==
false ) {
710 wfDebug( __METHOD__ .
": showing parser cache contents for current rev permalink\n" );
713 wfDebug( __METHOD__ .
": showing parser cache contents\n" );
715 $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
716 # Ensure that UI elements requiring revision ID have
717 # the correct version information.
718 $outputPage->setRevisionId( $this->mPage->getLatest() );
719 # Preload timestamp to avoid a DB hit
720 $cachedTimestamp = $this->mParserOutput->getTimestamp();
721 if ( $cachedTimestamp !==
null ) {
722 $outputPage->setRevisionTimestamp( $cachedTimestamp );
723 $this->mPage->setTimestamp( $cachedTimestamp );
730 # Are we looking at an old revision
732 if ( $oldid && $this->fetchResult->isOK() ) {
736 wfDebug( __METHOD__ .
": cannot view deleted revision\n" );
741 # Ensure that UI elements requiring revision ID have
742 # the correct version information.
744 # Preload timestamp to avoid a DB hit
745 $outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
747 # Pages containing custom CSS or JavaScript get special treatment
748 if ( $this->
getTitle()->isSiteConfigPage() || $this->
getTitle()->isUserConfigPage() ) {
749 $dir = $this->
getContext()->getLanguage()->getDir();
752 $outputPage->wrapWikiMsg(
753 "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
756 } elseif ( !
Hooks::run(
'ArticleRevisionViewCustom', [
766 } elseif ( !
Hooks::run(
'ArticleContentViewCustom',
775 # Run the parse, protected by a pool counter
776 wfDebug( __METHOD__ .
": doing uncached parse\n" );
791 $ok = $poolArticleView->execute();
792 $error = $poolArticleView->getError();
793 $this->mParserOutput = $poolArticleView->getParserOutput() ?:
null;
795 # Don't cache a dirty ParserOutput object
796 if ( $poolArticleView->getIsDirty() ) {
797 $outputPage->setCdnMaxage( 0 );
798 $outputPage->addHTML(
"<!-- parser cache is expired, " .
799 "sending anyway due to pool overload-->\n" );
807 $outputPage->clearHTML();
808 $outputPage->enableClientCache(
false );
809 $outputPage->setRobotPolicy(
'noindex,nofollow' );
811 $errortext = $error->getWikiText(
812 false,
'view-pool-error', $this->
getContext()->getLanguage()
814 $outputPage->wrapWikiTextAsInterface(
'errorbox', $errortext );
816 # Connection or timeout error
820 if ( $this->mParserOutput ) {
821 $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
825 $outputPage->addSubtitle(
"<span id=\"redirectsub\">" .
826 $this->
getContext()->msg(
'redirectpagesub' )->parse() .
"</span>" );
831 # Should be unreachable, but just in case...
843 : ( $this->mParserOutput ?:
null );
845 # Adjust title for main page & pages with displaytitle
850 # For the main page, overwrite the <title> element with the con-
851 # tents of 'pagetitle-view-mainpage' instead of the default (if
853 # This message always exists because it is in the i18n files
854 if ( $this->
getTitle()->isMainPage() ) {
855 $msg =
wfMessage(
'pagetitle-view-mainpage' )->inContentLanguage();
856 if ( !$msg->isDisabled() ) {
857 $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
861 # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
862 # This could use getTouched(), but that could be scary for major template edits.
865 # Check for any __NOINDEX__ tags on the page using $pOutput
867 $outputPage->setIndexPolicy( $policy[
'index'] );
868 $outputPage->setFollowPolicy( $policy[
'follow'] );
871 $this->mPage->doViewUpdates( $user, $oldid );
873 # Load the postEdit module if the user just saved this revision
874 # See also EditPage::setPostEditCookie
877 $postEdit = $request->getCookie( $cookieKey );
879 # Clear the cookie. This also prevents caching of the response.
880 $request->response()->clearCookie( $cookieKey );
881 $outputPage->addJsConfigVars(
'wgPostEdit', $postEdit );
882 $outputPage->addModules(
'mediawiki.action.view.postEdit' );
905 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
907 if ( strval( $titleText ) !==
'' ) {
908 $out->setPageTitle( $titleText );
909 $out->setDisplayTitle( $titleText );
920 $diff = $request->getVal(
'diff' );
921 $rcid = $request->getVal(
'rcid' );
922 $diffOnly = $request->getBool(
'diffonly', $user->getOption(
'diffonly' ) );
923 $purge = $request->getVal(
'action' ) ==
'purge';
924 $unhide = $request->getInt(
'unhide' ) == 1;
931 $msg = $this->
getContext()->msg(
'difference-missing-revision' )
935 $this->
getContext()->getOutput()->addHTML( $msg );
939 $contentHandler = $rev->getContentHandler();
940 $de = $contentHandler->createDifferenceEngine(
950 $this->mRevIdFetched = $de->getNewid();
951 $de->showDiffPage( $diffOnly );
955 list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
957 $this->mPage->doViewUpdates( $user, (
int)$new );
970 $ns = $this->
getTitle()->getNamespace();
972 # Don't index user and user talk pages for blocked users (T13443)
974 $specificTarget =
null;
976 $titleText = $this->
getTitle()->getText();
978 $vagueTarget = $titleText;
980 $specificTarget = $titleText;
982 if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof
DatabaseBlock ) {
984 'index' =>
'noindex',
985 'follow' =>
'nofollow'
990 if ( $this->mPage->getId() === 0 || $this->
getOldID() ) {
991 # Non-articles (special pages etc), and old revisions
993 'index' =>
'noindex',
994 'follow' =>
'nofollow'
996 } elseif ( $this->
getContext()->getOutput()->isPrintable() ) {
997 # Discourage indexing of printable versions, but encourage following
999 'index' =>
'noindex',
1000 'follow' =>
'follow'
1002 } elseif ( $this->
getContext()->getRequest()->getInt(
'curid' ) ) {
1003 # For ?curid=x urls, disallow indexing
1005 'index' =>
'noindex',
1006 'follow' =>
'follow'
1010 # Otherwise, construct the policy based on the various config variables.
1014 # Honour customised robot policies for this namespace
1015 $policy = array_merge(
1020 if ( $this->
getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
1021 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
1022 # a final sanity check that we have really got the parser output.
1023 $policy = array_merge(
1025 [
'index' => $pOutput->getIndexPolicy() ]
1030 # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
1031 $policy = array_merge(
1048 if ( is_array( $policy ) ) {
1050 } elseif ( !$policy ) {
1054 $policy = explode(
',', $policy );
1055 $policy = array_map(
'trim', $policy );
1058 foreach ( $policy as $var ) {
1059 if ( in_array( $var, [
'index',
'noindex' ] ) ) {
1060 $arr[
'index'] = $var;
1061 } elseif ( in_array( $var, [
'follow',
'nofollow' ] ) ) {
1062 $arr[
'follow'] = $var;
1080 $outputPage =
$context->getOutput();
1082 $rdfrom = $request->getVal(
'rdfrom' );
1085 $query = $request->getValues();
1086 unset( $query[
'rdfrom'] );
1087 unset( $query[
'title'] );
1090 $query[
'redirect'] =
'no';
1092 $redirectTargetUrl = $this->
getTitle()->getLinkURL( $query );
1094 if ( isset( $this->mRedirectedFrom ) ) {
1096 $articlePage = $this;
1100 if (
Hooks::run(
'ArticleViewRedirect', [ &$articlePage ] ) ) {
1102 $this->mRedirectedFrom,
1105 [
'redirect' =>
'no' ]
1108 $outputPage->addSubtitle(
"<span class=\"mw-redirectedfrom\">" .
1109 $context->msg(
'redirectedfrom' )->rawParams( $redir )->parse()
1114 $outputPage->addJsConfigVars( [
1115 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1117 $outputPage->addModules(
'mediawiki.action.view.redirect' );
1120 $outputPage->setCanonicalUrl( $this->
getTitle()->getCanonicalURL() );
1123 $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1127 } elseif ( $rdfrom ) {
1132 $outputPage->addSubtitle(
"<span class=\"mw-redirectedfrom\">" .
1133 $context->msg(
'redirectedfrom' )->rawParams( $redir )->parse()
1137 $outputPage->addJsConfigVars( [
1138 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1140 $outputPage->addModules(
'mediawiki.action.view.redirect' );
1154 if ( $this->
getTitle()->isTalkPage() && !
wfMessage(
'talkpageheader' )->isDisabled() ) {
1155 $this->
getContext()->getOutput()->wrapWikiMsg(
1156 "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1157 [
'talkpageheader' ]
1166 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1170 $this->
getContext()->getOutput()->addWikiMsg(
'anontalkpagetext' );
1176 Hooks::run(
'ArticleViewFooter', [ $this, $patrolFooterShown ] );
1192 if ( !
Hooks::run(
'ArticleShowPatrolFooter', [ $this ] ) ) {
1196 $outputPage = $this->
getContext()->getOutput();
1202 ->quickUserCan(
'patrol', $user,
$title )
1210 if ( $this->mRevision
1219 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1220 $key =
$cache->makeKey(
'unpatrollable-page',
$title->getArticleID() );
1221 if (
$cache->get( $key ) ) {
1226 $oldestRevisionTimestamp =
$dbr->selectField(
1228 'MIN( rev_timestamp )',
1229 [
'rev_page' =>
$title->getArticleID() ],
1238 $recentPageCreation =
false;
1239 if ( $oldestRevisionTimestamp
1243 $recentPageCreation =
true;
1247 'rc_timestamp' => $oldestRevisionTimestamp,
1248 'rc_namespace' =>
$title->getNamespace(),
1249 'rc_cur_id' =>
$title->getArticleID()
1255 $markPatrolledMsg =
wfMessage(
'markaspatrolledtext' );
1263 $recentFileUpload =
false;
1267 $newestUploadTimestamp =
$dbr->selectField(
1269 'MAX( img_timestamp )',
1270 [
'img_name' =>
$title->getDBkey() ],
1273 if ( $newestUploadTimestamp
1277 $recentFileUpload =
true;
1281 'rc_log_type' =>
'upload',
1282 'rc_timestamp' => $newestUploadTimestamp,
1284 'rc_cur_id' =>
$title->getArticleID()
1290 $markPatrolledMsg =
wfMessage(
'markaspatrolledtext-file' );
1295 if ( !$recentPageCreation && !$recentFileUpload ) {
1300 $cache->set( $key,
'1' );
1312 if ( $rc->getAttribute(
'rc_patrolled' ) ) {
1317 $cache->set( $key,
'1' );
1322 if ( $rc->getPerformer()->equals( $user ) ) {
1328 $outputPage->preventClickjacking();
1329 if ( MediaWikiServices::getInstance()
1331 ->userHasRight( $user,
'writeapi' )
1333 $outputPage->addModules(
'mediawiki.page.patrol.ajax' );
1338 $markPatrolledMsg->escaped(),
1341 'action' =>
'markpatrolled',
1342 'rcid' => $rc->getAttribute(
'rc_id' ),
1346 $outputPage->addHTML(
1347 "<div class='patrollink' data-mw='interface'>" .
1348 wfMessage(
'markaspatrolledlink' )->rawParams( $link )->escaped() .
1362 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1363 $cache->delete(
$cache->makeKey(
'unpatrollable-page', $articleID ) );
1373 $outputPage = $this->
getContext()->getOutput();
1375 $validUserPage =
false;
1379 $services = MediaWikiServices::getInstance();
1381 # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1385 $rootPart = explode(
'/',
$title->getText() )[0];
1388 $block = DatabaseBlock::newFromTarget( $user, $user );
1390 if ( !( $user && $user->isLoggedIn() ) && !$ip ) { #
User does not exist
1391 $outputPage->wrapWikiMsg(
"<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1394 !is_null( $block ) &&
1395 $block->getType() != DatabaseBlock::TYPE_AUTO &&
1396 ( $block->isSitewide() || $user->isBlockedFrom(
$title ) )
1403 $services->getNamespaceInfo()->getCanonicalName(
NS_USER ) .
':' .
1404 $block->getTarget(),
1408 'showIfEmpty' =>
false,
1410 'blocked-notice-logextract',
1411 $user->getName() # Support GENDER in notice
1415 $validUserPage = !
$title->isSubpage();
1417 $validUserPage = !
$title->isSubpage();
1421 Hooks::run(
'ShowMissingArticle', [ $this ] );
1423 # Show delete and move logs if there were any such events.
1424 # The logging query can DOS the site when bots/crawlers cause 404 floods,
1425 # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1427 $key = $dbCache->makeKey(
'page-recent-delete', md5(
$title->getPrefixedText() ) );
1428 $loggedIn = $this->
getContext()->getUser()->isLoggedIn();
1429 $sessionExists = $this->
getContext()->getRequest()->getSession()->isPersistent();
1430 if ( $loggedIn || $dbCache->get( $key ) || $sessionExists ) {
1431 $logTypes = [
'delete',
'move',
'protect' ];
1435 $conds = [
'log_action != ' .
$dbr->addQuotes(
'revision' ) ];
1437 Hooks::run(
'Article::MissingArticleConditions', [ &$conds, $logTypes ] );
1446 'showIfEmpty' =>
false,
1447 'msgKey' => [ $loggedIn || $sessionExists
1448 ?
'moveddeleted-notice'
1449 :
'moveddeleted-notice-recent'
1455 if ( !$this->mPage->hasViewableContent() &&
$wgSend404Code && !$validUserPage ) {
1458 $this->
getContext()->getRequest()->response()->statusHeader( 404 );
1463 $outputPage->setIndexPolicy( $policy[
'index'] );
1464 $outputPage->setFollowPolicy( $policy[
'follow'] );
1466 $hookResult =
Hooks::run(
'BeforeDisplayNoArticleText', [ $this ] );
1468 if ( !$hookResult ) {
1472 # Show error message
1474 $pm = MediaWikiServices::getInstance()->getPermissionManager();
1481 $text =
wfMessage(
'missing-revision', $oldid )->plain();
1482 } elseif ( $pm->quickUserCan(
'create', $this->getContext()->getUser(),
$title ) &&
1483 $pm->quickUserCan(
'edit', $this->getContext()->getUser(),
$title )
1485 $message = $this->
getContext()->getUser()->isLoggedIn() ?
'noarticletext' :
'noarticletextanon';
1488 $text =
wfMessage(
'noarticletext-nopermission' )->plain();
1491 $dir = $this->
getContext()->getLanguage()->getDir();
1494 'class' =>
"noarticletext mw-content-$dir",
1497 ] ) .
"\n$text\n</div>" );
1508 if ( !$this->mRevision->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1513 $outputPage = $this->
getContext()->getOutput();
1516 if ( !$this->mRevision->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
1517 $outputPage->wrapWikiMsg(
"<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1518 'rev-deleted-text-permission' );
1522 } elseif ( $this->
getContext()->getRequest()->getInt(
'unhide' ) != 1 ) {
1523 # Give explanation and add a link to view the revision...
1524 $oldid = intval( $this->
getOldID() );
1525 $link = $this->
getTitle()->getFullURL(
"oldid={$oldid}&unhide=1" );
1526 $msg = $this->mRevision->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1527 'rev-suppressed-text-unhide' :
'rev-deleted-text-unhide';
1528 $outputPage->wrapWikiMsg(
"<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1534 $msg = $this->mRevision->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1535 'rev-suppressed-text-view' :
'rev-deleted-text-view';
1536 $outputPage->wrapWikiMsg(
"<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
1552 $articlePage = $this;
1554 if ( !
Hooks::run(
'DisplayOldSubtitle', [ &$articlePage, &$oldid ] ) ) {
1559 $unhide =
$context->getRequest()->getInt(
'unhide' ) == 1;
1561 # Cascade unhide param in links for easy deletion browsing
1564 $extraParams[
'unhide'] = 1;
1567 if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
1573 $timestamp = $revision->getTimestamp();
1575 $current = ( $oldid == $this->mPage->getLatest() );
1576 $language =
$context->getLanguage();
1579 $td = $language->userTimeAndDate( $timestamp, $user );
1580 $tddate = $language->userDate( $timestamp, $user );
1581 $tdtime = $language->userTime( $timestamp, $user );
1583 # Show user links if allowed to see them. If hidden, then show them only if requested...
1586 $infomsg = $current && !
$context->msg(
'revision-info-current' )->isDisabled()
1587 ?
'revision-info-current'
1590 $outputPage =
$context->getOutput();
1591 $revisionInfo =
"<div id=\"mw-{$infomsg}\">" .
1593 ->rawParams( $userlinks )
1594 ->params( $revision->getId(), $tddate, $tdtime, $revision->getUserText() )
1600 ?
$context->msg(
'currentrevisionlink' )->escaped()
1603 $context->msg(
'currentrevisionlink' )->escaped(),
1608 ?
$context->msg(
'diff' )->escaped()
1611 $context->msg(
'diff' )->escaped(),
1618 $rl = MediaWikiServices::getInstance()->getRevisionLookup();
1619 $prevExist = (bool)$rl->getPreviousRevision( $revision->getRevisionRecord() );
1620 $prevlink = $prevExist
1623 $context->msg(
'previousrevision' )->escaped(),
1626 'direction' =>
'prev',
1630 :
$context->msg(
'previousrevision' )->escaped();
1631 $prevdiff = $prevExist
1634 $context->msg(
'diff' )->escaped(),
1641 :
$context->msg(
'diff' )->escaped();
1642 $nextlink = $current
1643 ?
$context->msg(
'nextrevision' )->escaped()
1646 $context->msg(
'nextrevision' )->escaped(),
1649 'direction' =>
'next',
1653 $nextdiff = $current
1654 ?
$context->msg(
'diff' )->escaped()
1657 $context->msg(
'diff' )->escaped(),
1666 if ( $cdel !==
'' ) {
1671 $outputPage->addSubtitle(
"<div class=\"mw-revision warningbox\">" . $revisionInfo .
1672 "<div id=\"mw-revision-nav\">" . $cdel .
1673 $context->msg(
'revision-nav' )->rawParams(
1674 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1675 )->escaped() .
"</div></div>" );
1691 public function viewRedirect( $target, $appendSubtitle =
true, $forceKnown =
false ) {
1694 if ( $appendSubtitle ) {
1695 $out->addSubtitle(
wfMessage(
'redirectpagesub' ) );
1697 $out->addModuleStyles(
'mediawiki.action.view.redirectPage' );
1698 return static::getRedirectHeaderHtml(
$lang, $target, $forceKnown );
1714 if ( !is_array( $target ) ) {
1715 $target = [ $target ];
1718 $html =
'<ul class="redirectText">';
1720 foreach ( $target as
$title ) {
1723 htmlspecialchars(
$title->getFullText() ),
1726 $title->isRedirect() ? [
'redirect' =>
'no' ] : [],
1727 ( $forceKnown ? [
'known',
'noclasses' ] : [] )
1732 $redirectToText =
wfMessage(
'redirectto' )->inLanguage(
$lang )->escaped();
1734 return '<div class="redirectMsg">' .
1735 '<p>' . $redirectToText .
'</p>' .
1750 'namespace-' . $this->
getTitle()->getNamespace() .
'-helppage'
1754 if ( !$msg->isDisabled() ) {
1756 $out->addHelpLink( $helpUrl,
true );
1758 $out->addHelpLink( $to, $overrideBaseUrl );
1766 $this->
getContext()->getRequest()->response()->header(
'X-Robots-Tag: noindex' );
1767 $this->
getContext()->getOutput()->setArticleBodyOnly(
true );
1769 $this->viewIsRenderAction =
true;
1791 public function delete() {
1792 # This code desperately needs to be totally rewritten
1800 $permissionErrors =
$title->getUserPermissionsErrors(
'delete', $user );
1801 if ( count( $permissionErrors ) ) {
1805 # Read-only check...
1810 # Better double-check that it hasn't been deleted yet!
1811 $this->mPage->loadPageData(
1812 $request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL
1814 if ( !$this->mPage->exists() ) {
1815 $deleteLogPage =
new LogPage(
'delete' );
1816 $outputPage =
$context->getOutput();
1817 $outputPage->setPageTitle(
$context->msg(
'cannotdelete-title',
$title->getPrefixedText() ) );
1818 $outputPage->wrapWikiMsg(
"<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
1821 $outputPage->addHTML(
1822 Xml::element(
'h2',
null, $deleteLogPage->getName()->text() )
1833 $deleteReasonList = $request->getText(
'wpDeleteReasonList',
'other' );
1834 $deleteReason = $request->getText(
'wpReason' );
1836 if ( $deleteReasonList ==
'other' ) {
1837 $reason = $deleteReason;
1838 } elseif ( $deleteReason !=
'' ) {
1840 $colonseparator =
wfMessage(
'colon-separator' )->inContentLanguage()->text();
1841 $reason = $deleteReasonList . $colonseparator . $deleteReason;
1843 $reason = $deleteReasonList;
1846 if ( $request->wasPosted() && $user->matchEditToken( $request->getVal(
'wpEditToken' ),
1847 [
'delete', $this->
getTitle()->getPrefixedText() ] )
1849 # Flag to hide all contents of the archived revisions
1851 $suppress = $request->getCheck(
'wpSuppress' ) && MediaWikiServices::getInstance()
1852 ->getPermissionManager()
1853 ->userHasRight( $user,
'suppressrevision' );
1855 $this->
doDelete( $reason, $suppress );
1863 $hasHistory =
false;
1867 }
catch ( Exception $e ) {
1868 # if a page is horribly broken, we still want to be able to
1869 # delete it. So be lenient about errors here.
1870 wfDebug(
"Error while building auto delete summary: $e" );
1876 if ( $hasHistory ) {
1885 $revisions = $edits = (int)
$dbr->selectField(
1888 [
'rev_page' =>
$title->getArticleID() ],
1894 '<strong class="mw-delete-warning-revisions">' .
1895 $context->msg(
'historywarning' )->numParams( $revisions )->parse() .
1897 $context->msg(
'history' )->escaped(),
1899 [
'action' =>
'history' ] ) .
1903 if (
$title->isBigDeletion() ) {
1905 $context->getOutput()->wrapWikiMsg(
"<div class='error'>\n$1\n</div>\n",
1907 'delete-warning-toobig',
1923 wfDebug(
"Article::confirmDelete\n" );
1927 $outputPage = $ctx->getOutput();
1928 $outputPage->setPageTitle(
wfMessage(
'delete-confirm',
$title->getPrefixedText() ) );
1929 $outputPage->addBacklinkSubtitle(
$title );
1930 $outputPage->setRobotPolicy(
'noindex,nofollow' );
1931 $outputPage->addModules(
'mediawiki.action.delete' );
1933 $backlinkCache =
$title->getBacklinkCache();
1934 if ( $backlinkCache->hasLinks(
'pagelinks' ) || $backlinkCache->hasLinks(
'templatelinks' ) ) {
1935 $outputPage->wrapWikiMsg(
"<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1936 'deleting-backlinks-warning' );
1939 $subpageQueryLimit = 51;
1940 $subpages =
$title->getSubpages( $subpageQueryLimit );
1941 $subpageCount = count( $subpages );
1942 if ( $subpageCount > 0 ) {
1943 $outputPage->wrapWikiMsg(
"<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1944 [
'deleting-subpages-warning', Message::numParam( $subpageCount ) ] );
1946 $outputPage->addWikiMsg(
'confirmdeletetext' );
1948 Hooks::run(
'ArticleConfirmDelete', [ $this, $outputPage, &$reason ] );
1951 $checkWatch = $user->getBoolOption(
'watchdeletion' ) || $user->isWatched(
$title );
1953 $outputPage->enableOOUI();
1958 $ctx->msg(
'deletereason-dropdown' )->inContentLanguage()->text(),
1959 [
'other' => $ctx->msg(
'deletereasonotherlist' )->inContentLanguage()->text() ]
1963 $fields[] =
new OOUI\FieldLayout(
1964 new OOUI\DropdownInputWidget( [
1965 'name' =>
'wpDeleteReasonList',
1966 'inputId' =>
'wpDeleteReasonList',
1968 'infusable' =>
true,
1970 'options' => $options
1973 'label' => $ctx->msg(
'deletecomment' )->text(),
1981 $fields[] =
new OOUI\FieldLayout(
1982 new OOUI\TextInputWidget( [
1983 'name' =>
'wpReason',
1984 'inputId' =>
'wpReason',
1987 'infusable' =>
true,
1989 'autofocus' =>
true,
1992 'label' => $ctx->msg(
'deleteotherreason' )->text(),
1997 if ( $user->isLoggedIn() ) {
1998 $fields[] =
new OOUI\FieldLayout(
1999 new OOUI\CheckboxInputWidget( [
2000 'name' =>
'wpWatch',
2001 'inputId' =>
'wpWatch',
2003 'selected' => $checkWatch,
2006 'label' => $ctx->msg(
'watchthis' )->text(),
2007 'align' =>
'inline',
2008 'infusable' =>
true,
2012 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
2013 if ( $permissionManager->userHasRight( $user,
'suppressrevision' ) ) {
2014 $fields[] =
new OOUI\FieldLayout(
2015 new OOUI\CheckboxInputWidget( [
2016 'name' =>
'wpSuppress',
2017 'inputId' =>
'wpSuppress',
2021 'label' => $ctx->msg(
'revdelete-suppress' )->text(),
2022 'align' =>
'inline',
2023 'infusable' =>
true,
2028 $fields[] =
new OOUI\FieldLayout(
2029 new OOUI\ButtonInputWidget( [
2030 'name' =>
'wpConfirmB',
2031 'inputId' =>
'wpConfirmB',
2033 'value' => $ctx->msg(
'deletepage' )->text(),
2034 'label' => $ctx->msg(
'deletepage' )->text(),
2035 'flags' => [
'primary',
'destructive' ],
2043 $fieldset =
new OOUI\FieldsetLayout( [
2044 'label' => $ctx->msg(
'delete-legend' )->text(),
2045 'id' =>
'mw-delete-table',
2049 $form =
new OOUI\FormLayout( [
2051 'action' =>
$title->getLocalURL(
'action=delete' ),
2052 'id' =>
'deleteconfirm',
2054 $form->appendContent(
2056 new OOUI\HtmlSnippet(
2057 Html::hidden(
'wpEditToken', $user->getEditToken( [
'delete',
$title->getPrefixedText() ] ) )
2061 $outputPage->addHTML(
2062 new OOUI\PanelLayout( [
2063 'classes' => [
'deletepage-wrapper' ],
2064 'expanded' =>
false,
2071 if ( $permissionManager->userHasRight( $user,
'editinterface' ) ) {
2073 $ctx->msg(
'deletereason-dropdown' )->inContentLanguage()->getTitle(),
2074 wfMessage(
'delete-edit-reasonlist' )->escaped(),
2076 [
'action' =>
'edit' ]
2078 $outputPage->addHTML(
'<p class="mw-delete-editreasons">' . $link .
'</p>' );
2081 $deleteLogPage =
new LogPage(
'delete' );
2082 $outputPage->addHTML(
Xml::element(
'h2',
null, $deleteLogPage->getName()->text() ) );
2094 public function doDelete( $reason, $suppress =
false, $immediate =
false ) {
2097 $outputPage =
$context->getOutput();
2099 $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0,
true, $error, $user,
2100 [],
'delete', $immediate );
2103 $deleted = $this->
getTitle()->getPrefixedText();
2105 $outputPage->setPageTitle(
wfMessage(
'actioncomplete' ) );
2106 $outputPage->setRobotPolicy(
'noindex,nofollow' );
2109 $loglink =
'[[Special:Log/delete|' .
wfMessage(
'deletionlog' )->text() .
']]';
2110 $outputPage->addWikiMsg(
'deletedtext',
wfEscapeWikiText( $deleted ), $loglink );
2113 $outputPage->addWikiMsg(
'delete-scheduled',
wfEscapeWikiText( $deleted ) );
2116 $outputPage->returnToMain(
false );
2118 $outputPage->setPageTitle(
2120 $this->
getTitle()->getPrefixedText() )
2123 if ( $error ==
'' ) {
2124 $outputPage->wrapWikiTextAsInterface(
2125 'error mw-error-cannotdelete',
2128 $deleteLogPage =
new LogPage(
'delete' );
2129 $outputPage->addHTML(
Xml::element(
'h2',
null, $deleteLogPage->getName()->text() ) );
2137 $outputPage->addHTML( $error );
2152 static $called =
false;
2155 wfDebug(
"Article::tryFileCache(): called twice!?\n" );
2162 if (
$cache->isCacheGood( $this->mPage->getTouched() ) ) {
2163 wfDebug(
"Article::tryFileCache(): about to load file\n" );
2167 wfDebug(
"Article::tryFileCache(): starting buffer\n" );
2168 ob_start( [ &
$cache,
'saveToFileCache' ] );
2171 wfDebug(
"Article::tryFileCache(): not cacheable\n" );
2186 $cacheable = $this->mPage->getId()
2187 && !$this->mRedirectedFrom && !$this->
getTitle()->isRedirect();
2191 $articlePage = $this;
2192 $cacheable =
Hooks::run(
'IsFileCacheable', [ &$articlePage ] );
2215 if ( $user ===
null ) {
2218 $parserOptions = $this->mPage->makeParserOptions( $user );
2221 return $this->mPage->getParserOutput( $parserOptions, $oldid );
2231 if ( $this->mParserOptions ) {
2232 throw new MWException(
"can't change parser options after they have already been set" );
2236 $this->mParserOptions = clone $options;
2244 if ( !$this->mParserOptions ) {
2245 $this->mParserOptions = $this->mPage->makeParserOptions( $this->
getContext() );
2271 wfDebug( __METHOD__ .
" called and \$mContext is null. " .
2272 "Return RequestContext::getMain(); for sanity\n" );
2285 if ( property_exists( $this->mPage, $fname ) ) {
2286 # wfWarn( "Access to raw $fname field " . __CLASS__ );
2287 return $this->mPage->$fname;
2289 trigger_error(
'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2299 public function __set( $fname, $fvalue ) {
2300 if ( property_exists( $this->mPage, $fname ) ) {
2301 # wfWarn( "Access to raw $fname field of " . __CLASS__ );
2302 $this->mPage->$fname = $fvalue;
2304 } elseif ( !in_array( $fname, [
'mContext',
'mPage' ] ) ) {
2305 $this->mPage->$fname = $fvalue;
2307 trigger_error(
'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2316 return $this->mPage->checkFlags( $flags );
2324 return $this->mPage->checkTouched();
2332 $this->mPage->clearPreparedEdit();
2340 $reason, $suppress =
false, $u1 =
null, $u2 =
null, &$error =
'',
User $user =
null,
2341 $tags = [], $immediate =
false
2343 return $this->mPage->doDeleteArticleReal(
2344 $reason, $suppress, $u1, $u2, $error, $user, $tags,
'delete', $immediate
2358 $this->mPage->doDeleteUpdates( $id,
$content, $revision, $user );
2367 User $user =
null, $serialFormat =
null
2370 return $this->mPage->doEditContent(
$content, $summary, $flags, $originalRevId,
2371 $user, $serialFormat
2380 return $this->mPage->doEditUpdates( $revision, $user, $options );
2390 return $this->mPage->doPurge();
2398 $this->mPage->doViewUpdates( $user, $oldid );
2406 return $this->mPage->exists();
2414 return $this->mPage->followRedirect();
2422 return $this->mPage->getActionOverrides();
2430 return $this->mPage->getAutoDeleteReason( $hasHistory );
2438 return $this->mPage->getCategories();
2445 public function getComment( $audience = RevisionRecord::FOR_PUBLIC,
User $user =
null ) {
2446 return $this->mPage->getComment( $audience, $user );
2454 return $this->mPage->getContentHandler();
2462 return $this->mPage->getContentModel();
2470 return $this->mPage->getContributors();
2477 public function getCreator( $audience = RevisionRecord::FOR_PUBLIC,
User $user =
null ) {
2478 return $this->mPage->getCreator( $audience, $user );
2486 return $this->mPage->getDeletionUpdates(
$content );
2494 return $this->mPage->getHiddenCategories();
2502 return $this->mPage->getId();
2510 return $this->mPage->getLatest();
2518 return $this->mPage->getLinksTimestamp();
2526 return $this->mPage->getMinorEdit();
2534 return $this->mPage->getOldestRevision();
2542 return $this->mPage->getRedirectTarget();
2550 return $this->mPage->getRedirectURL( $rt );
2558 return $this->mPage->getRevision();
2566 return $this->mPage->getTimestamp();
2574 return $this->mPage->getTouched();
2582 return $this->mPage->getUndoContent( $undo, $undoafter );
2589 public function getUser( $audience = RevisionRecord::FOR_PUBLIC,
User $user =
null ) {
2590 return $this->mPage->getUser( $audience, $user );
2597 public function getUserText( $audience = RevisionRecord::FOR_PUBLIC,
User $user =
null ) {
2598 return $this->mPage->getUserText( $audience, $user );
2606 return $this->mPage->hasViewableContent();
2614 return $this->mPage->insertOn( $dbw, $pageId );
2622 array $expiry, $cascade, $reason, $user =
null
2624 return $this->mPage->insertProtectNullRevision( $revCommentMsg, $limit,
2625 $expiry, $cascade, $reason, $user
2634 return $this->mPage->insertRedirect();
2642 return $this->mPage->insertRedirectEntry( $rt, $oldLatest );
2650 return $this->mPage->isCountable( $editInfo );
2658 return $this->mPage->isRedirect();
2666 return $this->mPage->loadFromRow( $data, $from );
2674 $this->mPage->loadPageData( $from );
2682 return $this->mPage->lockAndGetLatest();
2690 return $this->mPage->makeParserOptions(
$context );
2698 return $this->mPage->pageDataFromId(
$dbr, $id, $options );
2706 return $this->mPage->pageDataFromTitle(
$dbr,
$title, $options );
2715 $serialFormat =
null, $useCache =
true
2717 return $this->mPage->prepareContentForEdit(
2719 $serialFormat, $useCache
2728 return $this->mPage->protectDescription( $limit, $expiry );
2736 return $this->mPage->protectDescriptionLog( $limit, $expiry );
2744 $sectionTitle =
'', $baseRevId =
null
2746 return $this->mPage->replaceSectionAtRev( $sectionId, $sectionContent,
2747 $sectionTitle, $baseRevId
2756 $sectionId,
Content $sectionContent, $sectionTitle =
'', $edittime =
null
2758 return $this->mPage->replaceSectionContent(
2759 $sectionId, $sectionContent, $sectionTitle, $edittime
2768 $this->mPage->setTimestamp( $ts );
2776 return $this->mPage->shouldCheckParserCache( $parserOptions, $oldId );
2784 return $this->mPage->supportsSections();
2792 return $this->mPage->triggerOpportunisticLinksUpdate( $parserOutput );
2800 return $this->mPage->updateCategoryCounts( $added, $deleted, $id );
2808 return $this->mPage->updateIfNewerOn( $dbw, $revision );
2816 return $this->mPage->updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect );
2824 $lastRevIsRedirect =
null
2826 return $this->mPage->updateRevisionOn( $dbw, $revision, $lastRevision,
2842 return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
2853 &$cascade = 0, $expiry = []
2855 return $this->mPage->doUpdateRestrictions(
2876 $reason, $suppress =
false, $u1 =
null, $u2 =
null, &$error =
'', $immediate =
false
2878 return $this->mPage->doDeleteArticle( $reason, $suppress, $u1, $u2, $error,
2891 public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails,
User $user =
null ) {
2896 return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
2912 return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
2920 $title = $this->mPage->getTitle();
2922 return $handler->getAutoDeleteReason(
$title, $hasHistory );