3 namespace MediaWiki\Actions
;
11 use MediaWiki\Context\RequestContext
;
12 use MediaWiki\Logger\LoggerFactory
;
13 use MediaWiki\MainConfigNames
;
14 use MediaWiki\MediaWikiEntryPoint
;
15 use MediaWiki\Output\OutputPage
;
16 use MediaWiki\Permissions\PermissionStatus
;
17 use MediaWiki\Profiler\ProfilingContext
;
18 use MediaWiki\Request\DerivativeRequest
;
19 use MediaWiki\Request\WebRequest
;
20 use MediaWiki\SpecialPage\RedirectSpecialPage
;
21 use MediaWiki\SpecialPage\SpecialPage
;
22 use MediaWiki\Title\MalformedTitleException
;
23 use MediaWiki\Title\Title
;
24 use MediaWiki\User\User
;
25 use MWExceptionRenderer
;
29 use UnexpectedValueException
;
32 use Wikimedia\Rdbms\DBConnectionError
;
35 * The index.php entry point for web browser navigations, usually routed to
36 * an Action or SpecialPage subclass.
38 * @internal For use in index.php
41 class ActionEntryPoint
extends MediaWikiEntryPoint
{
44 * Overwritten to narrow the return type to RequestContext
46 protected function getContext(): RequestContext
{
47 /** @var RequestContext $context */
48 $context = parent
::getContext();
50 // @phan-suppress-next-line PhanTypeMismatchReturnSuperType see $context in the constructor
54 protected function getOutput(): OutputPage
{
55 return $this->getContext()->getOutput();
58 protected function getUser(): User
{
59 return $this->getContext()->getUser();
62 protected function handleTopLevelError( Throwable
$e ) {
63 $context = $this->getContext();
64 $action = $context->getRequest()->getRawVal( 'action' ) ??
'view';
66 $e instanceof DBConnectionError
&&
67 $context->hasTitle() &&
68 $context->getTitle()->canExist() &&
69 in_array( $action, [ 'view', 'history' ], true ) &&
70 HTMLFileCache
::useFileCache( $context, HTMLFileCache
::MODE_OUTAGE
)
72 // Try to use any (even stale) file during outages...
73 $cache = new HTMLFileCache( $context->getTitle(), $action );
74 if ( $cache->isCached() ) {
75 $cache->loadFromFileCache( $context, HTMLFileCache
::MODE_OUTAGE
);
76 $this->print( MWExceptionRenderer
::getHTML( $e ) );
81 parent
::handleTopLevelError( $e );
85 * Determine and send the response headers and body for this web request
87 protected function execute() {
88 // phpcs:ignore MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgTitle
91 // Get title from request parameters,
92 // is set on the fly by parseTitle the first time.
93 $title = $this->getTitle();
96 $request = $this->getContext()->getRequest();
97 // Set DB query expectations for this HTTP request
98 $trxLimits = $this->getConfig( MainConfigNames
::TrxProfilerLimits
);
99 $trxProfiler = Profiler
::instance()->getTransactionProfiler();
100 $trxProfiler->setLogger( LoggerFactory
::getInstance( 'rdbms' ) );
101 $trxProfiler->setStatsFactory( $this->getStatsFactory() );
102 $trxProfiler->setRequestMethod( $request->getMethod() );
103 if ( $request->hasSafeMethod() ) {
104 $trxProfiler->setExpectations( $trxLimits['GET'], __METHOD__
);
106 $trxProfiler->setExpectations( $trxLimits['POST'], __METHOD__
);
109 if ( $this->maybeDoHttpsRedirect() ) {
113 $context = $this->getContext();
114 $output = $context->getOutput();
116 // NOTE: HTMLFileCache::useFileCache() is not used in WMF production but is
117 // here to provide third-party wikis with a way to enable caching for
118 // "view" and "history" actions. It's triggered by the use of $wgUseFileCache
119 // when set to true in LocalSettings.php.
120 if ( $title->canExist() && HTMLFileCache
::useFileCache( $context ) ) {
121 // getAction() may trigger DB queries, so avoid eagerly initializing it if possible.
122 // This reduces the cost of requests that exit early due to tryNormaliseRedirect()
123 // or a MediaWikiPerformAction / BeforeInitialize hook handler.
124 $action = $this->getAction();
125 // Try low-level file cache hit
126 $cache = new HTMLFileCache( $title, $action );
127 if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
128 // Check incoming headers to see if client has this cached
129 $timestamp = $cache->cacheTimestamp();
130 if ( !$output->checkLastModified( $timestamp ) ) {
131 $cache->loadFromFileCache( $context );
133 // Do any stats increment/watchlist stuff, assuming user is viewing the
134 // latest revision (which should always be the case for file cache)
135 $context->getWikiPage()->doViewUpdates( $context->getAuthority() );
136 // Tell OutputPage that output is taken care of
144 // Actually do the work of the request and build up any output
145 $this->performRequest();
146 } catch ( ErrorPageError
$e ) {
147 // TODO: Should ErrorPageError::report accept a OutputPage parameter?
148 $e->report( ErrorPageError
::STAGE_OUTPUT
);
149 $output->considerCacheSettingsFinal();
150 // T64091: while exceptions are convenient to bubble up GUI errors,
151 // they are not internal application faults. As with normal requests, this
152 // should commit, print the output, do deferred updates, jobs, and profiling.
155 $this->prepareForOutput();
157 // Ask OutputPage/Skin to stage the output (HTTP response body and headers).
158 // Flush the output to the client unless an exception occurred.
159 // Note that the OutputPage object in $context may have been replaced,
160 // so better fetch it again here.
161 $output = $context->getOutput();
162 $this->outputResponsePayload( $output->output( true ) );
166 * If the stars are suitably aligned, do an HTTP->HTTPS redirect
168 * Note: Do this after $wgTitle is setup, otherwise the hooks run from
169 * isRegistered() will do all sorts of weird stuff.
171 * @return bool True if the redirect was done. Handling of the request
172 * should be aborted. False if no redirect was done.
174 protected function maybeDoHttpsRedirect() {
175 if ( !$this->shouldDoHttpRedirect() ) {
179 $context = $this->getContext();
180 $request = $context->getRequest();
181 $oldUrl = $request->getFullRequestURL();
182 $redirUrl = preg_replace( '#^http://#', 'https://', $oldUrl );
184 if ( $request->wasPosted() ) {
185 // This is weird and we'd hope it almost never happens. This
186 // means that a POST came in via HTTP and policy requires us
187 // redirecting to HTTPS. It's likely such a request is going
188 // to fail due to post data being lost, but let's try anyway
189 // and just log the instance.
191 // @todo FIXME: See if we could issue a 307 or 308 here, need
192 // to see how clients (automated & browser) behave when we do
193 wfDebugLog( 'RedirectedPosts', "Redirected from HTTP to HTTPS: $oldUrl" );
195 // Setup dummy Title, otherwise OutputPage::redirect will fail
196 $title = Title
::newFromText( 'REDIR', NS_MAIN
);
197 $context->setTitle( $title );
198 // Since we only do this redir to change proto, always send a vary header
199 $output = $context->getOutput();
200 $output->addVaryHeader( 'X-Forwarded-Proto' );
201 $output->redirect( $redirUrl );
207 protected function doPrepareForOutput() {
208 parent
::doPrepareForOutput();
210 // If needed, push a deferred update to run jobs after the output is sent
211 $this->schedulePostSendJobs();
214 protected function schedulePostSendJobs() {
215 // Recursion guard for $wgRunJobsAsync
216 if ( $this->getTitle()->isSpecial( 'RunJobs' ) ) {
220 parent
::schedulePostSendJobs();
224 * Parse the request to get the Title object
226 * @throws MalformedTitleException If a title has been provided by the user, but is invalid.
227 * @param WebRequest $request
228 * @return Title Title object to be $wgTitle
230 protected function parseTitle( $request ) {
231 $curid = $request->getInt( 'curid' );
232 $title = $request->getText( 'title' );
236 // URLs like this are generated by RC, because rc_title isn't always accurate
237 $ret = Title
::newFromID( $curid );
239 if ( $ret === null ) {
240 $ret = Title
::newFromURL( $title );
241 if ( $ret !== null ) {
242 // Alias NS_MEDIA page URLs to NS_FILE...we only use NS_MEDIA
243 // in wikitext links to tell Parser to make a direct file link
244 if ( $ret->getNamespace() === NS_MEDIA
) {
245 $ret = Title
::makeTitle( NS_FILE
, $ret->getDBkey() );
247 // Check variant links so that interwiki links don't have to worry
248 // about the possible different language variants
249 $services = $this->getServiceContainer();
250 $languageConverter = $services
251 ->getLanguageConverterFactory()
252 ->getLanguageConverter( $services->getContentLanguage() );
253 if ( $languageConverter->hasVariants() && !$ret->exists() ) {
254 $languageConverter->findVariantLink( $title, $ret );
259 // If title is not provided, always allow oldid and diff to set the title.
260 // If title is provided, allow oldid and diff to override the title, unless
261 // we are talking about a special page which might use these parameters for
263 if ( $ret === null ||
!$ret->isSpecialPage() ) {
264 // We can have urls with just ?diff=,?oldid= or even just ?diff=
265 $oldid = $request->getInt( 'oldid' );
266 $oldid = $oldid ?
: $request->getInt( 'diff' );
267 // Allow oldid to override a changed or missing title
269 $revRecord = $this->getServiceContainer()
270 ->getRevisionLookup()
271 ->getRevisionById( $oldid );
273 $ret = Title
::newFromLinkTarget(
274 $revRecord->getPageAsLinkTarget()
280 if ( $ret === null && $request->getCheck( 'search' ) ) {
281 // Compatibility with old search URLs which didn't use Special:Search
282 // Just check for presence here, so blank requests still
283 // show the search page when using ugly URLs (T10054).
284 $ret = SpecialPage
::getTitleFor( 'Search' );
287 if ( $ret === null ||
!$ret->isSpecialPage() ) {
288 // Compatibility with old URLs for Special:RevisionDelete/Special:EditTags (T323338)
289 $actionName = $request->getRawVal( 'action' );
291 $actionName === 'revisiondelete' ||
292 ( $actionName === 'historysubmit' && $request->getBool( 'revisiondelete' ) )
294 $ret = SpecialPage
::getTitleFor( 'Revisiondelete' );
296 $actionName === 'editchangetags' ||
297 ( $actionName === 'historysubmit' && $request->getBool( 'editchangetags' ) )
299 $ret = SpecialPage
::getTitleFor( 'EditTags' );
303 // Use the main page as default title if nothing else has been provided
305 && strval( $title ) === ''
306 && !$request->getCheck( 'curid' )
307 && $request->getRawVal( 'action' ) !== 'delete'
309 $ret = Title
::newMainPage();
312 if ( $ret === null ||
( $ret->getDBkey() == '' && !$ret->isExternal() ) ) {
313 // If we get here, we definitely don't have a valid title; throw an exception.
314 // Try to get detailed invalid title exception first, fall back to MalformedTitleException.
315 Title
::newFromTextThrow( $title );
316 throw new MalformedTitleException( 'badtitletext', $title );
323 * Get the Title object that we'll be acting on, as specified in the WebRequest
326 public function getTitle() {
327 $context = $this->getContext();
329 if ( !$context->hasTitle() ) {
331 $context->setTitle( $this->parseTitle( $context->getRequest() ) );
332 } catch ( MalformedTitleException
$ex ) {
333 $context->setTitle( SpecialPage
::getTitleFor( 'Badtitle' ) );
336 return $context->getTitle();
340 * Returns the name of the action that will be executed.
342 * @note This is public for the benefit of extensions that implement
343 * the BeforeInitialize or MediaWikiPerformAction hooks.
345 * @return string Action
347 public function getAction(): string {
348 return $this->getContext()->getActionName();
352 * Performs the request.
355 * - local interwiki redirects
360 * @throws PermissionsError|BadTitleError|HttpError
363 protected function performRequest() {
364 // phpcs:ignore MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgTitle
367 $context = $this->getContext();
369 $request = $context->getRequest();
370 $output = $context->getOutput();
372 if ( $request->getRawVal( 'printable' ) === 'yes' ) {
373 $output->setPrintable();
376 $user = $context->getUser();
377 $title = $context->getTitle();
378 $requestTitle = $title;
380 $userOptionsLookup = $this->getServiceContainer()->getUserOptionsLookup();
381 if ( $userOptionsLookup->getBoolOption( $user, 'forcesafemode' ) ) {
382 $request->setVal( 'safemode', '1' );
385 $this->getHookRunner()->onBeforeInitialize( $title, null, $output, $user, $request, $this );
387 // Invalid titles. T23776: The interwikis must redirect even if the page name is empty.
388 if ( $title === null ||
( $title->getDBkey() == '' && !$title->isExternal() )
389 ||
$title->isSpecial( 'Badtitle' )
391 $context->setTitle( SpecialPage
::getTitleFor( 'Badtitle' ) );
393 $this->parseTitle( $request );
394 } catch ( MalformedTitleException
$ex ) {
395 throw new BadTitleError( $ex );
397 throw new BadTitleError();
400 // Check user's permissions to read this page.
401 // We have to check here to catch special pages etc.
402 // We will check again in Article::view().
403 $permissionStatus = PermissionStatus
::newEmpty();
404 if ( !$context->getAuthority()->authorizeRead( 'read', $title, $permissionStatus ) ) {
405 // T34276: allowing the skin to generate output with $wgTitle or
406 // $context->title set to the input title would allow anonymous users to
407 // determine whether a page exists, potentially leaking private data. In fact, the
408 // curid and oldid request parameters would allow page titles to be enumerated even
409 // when they are not guessable. So we reset the title to Special:Badtitle before the
410 // permissions error is displayed.
412 // The skin mostly uses $context->getTitle() these days, but some extensions
413 // still use $wgTitle.
414 $badTitle = SpecialPage
::getTitleFor( 'Badtitle' );
415 $context->setTitle( $badTitle );
416 $wgTitle = $badTitle;
418 throw new PermissionsError( 'read', $permissionStatus );
421 // Interwiki redirects
422 if ( $title->isExternal() ) {
423 $rdfrom = $request->getVal( 'rdfrom' );
425 $url = $title->getFullURL( [ 'rdfrom' => $rdfrom ] );
427 $query = $request->getQueryValues();
428 unset( $query['title'] );
429 $url = $title->getFullURL( $query );
431 // Check for a redirect loop
432 if ( $url !== $request->getFullRequestURL() && $title->isLocal() ) {
433 // 301 so google et al report the target as the actual url.
434 $output->redirect( $url, 301 );
436 $context->setTitle( SpecialPage
::getTitleFor( 'Badtitle' ) );
438 $this->parseTitle( $request );
439 } catch ( MalformedTitleException
$ex ) {
440 throw new BadTitleError( $ex );
442 throw new BadTitleError();
444 // Handle any other redirects.
445 // Redirect loops, titleless URL, $wgUsePathInfo URLs, and URLs with a variant
446 } elseif ( !$this->tryNormaliseRedirect( $title ) ) {
447 // Prevent information leak via Special:MyPage et al (T109724)
448 $spFactory = $this->getServiceContainer()->getSpecialPageFactory();
449 if ( $title->isSpecialPage() ) {
450 $specialPage = $spFactory->getPage( $title->getDBkey() );
451 if ( $specialPage instanceof RedirectSpecialPage
) {
452 $specialPage->setContext( $context );
453 if ( $this->getConfig( MainConfigNames
::HideIdentifiableRedirects
)
454 && $specialPage->personallyIdentifiableTarget()
456 [ , $subpage ] = $spFactory->resolveAlias( $title->getDBkey() );
457 $target = $specialPage->getRedirect( $subpage );
458 // Target can also be true. We let that case fall through to normal processing.
459 if ( $target instanceof Title
) {
460 if ( $target->isExternal() ) {
461 // Handle interwiki redirects
462 $target = SpecialPage
::getTitleFor(
464 'force/' . $target->getPrefixedDBkey()
468 $query = $specialPage->getRedirectQuery( $subpage ) ?
: [];
469 $derivateRequest = new DerivativeRequest( $request, $query );
470 $derivateRequest->setRequestURL( $request->getRequestURL() );
471 $context->setRequest( $derivateRequest );
472 // Do not varnish cache these. May vary even for anons
473 $output->lowerCdnMaxage( 0 );
474 // NOTE: This also clears any action cache.
475 // Action should not have been computed yet, but if it was,
476 // we reset it because special pages only support "view".
477 $context->setTitle( $target );
480 $output->addJsConfigVars( [
481 'wgInternalRedirectTargetUrl' => $target->getLinkURL( $query ),
483 $output->addModules( 'mediawiki.action.view.redirect' );
485 // If the title is invalid, redirect but show the correct bad title error - T297407
486 if ( !$target->isValid() ) {
488 $this->getServiceContainer()->getTitleParser()
489 ->parseTitle( $target->getPrefixedText() );
490 } catch ( MalformedTitleException
$ex ) {
491 throw new BadTitleError( $ex );
493 throw new BadTitleError();
500 // Special pages ($title may have changed since if statement above)
501 if ( $title->isSpecialPage() ) {
502 // Actions that need to be made when we have a special pages
503 $spFactory->executePath( $title, $context );
505 // ...otherwise treat it as an article view. The article
506 // may still be a wikipage redirect to another article or URL.
507 $article = $this->initializeArticle();
508 if ( is_object( $article ) ) {
509 $this->performAction( $article, $requestTitle );
510 } elseif ( is_string( $article ) ) {
511 $output->redirect( $article );
513 throw new UnexpectedValueException( "Shouldn't happen: MediaWiki::initializeArticle()"
514 . " returned neither an object nor a URL" );
517 $output->considerCacheSettingsFinal();
522 * Handle redirects for uncanonical title requests.
527 * - $wgUsePathInfo URLs.
528 * - URLs with a variant.
529 * - Other non-standard URLs (as long as they have no extra query parameters).
532 * - Normalise title values:
533 * /wiki/Foo%20Bar -> /wiki/Foo_Bar
534 * - Normalise empty title:
535 * /wiki/ -> /wiki/Main
536 * /w/index.php?title= -> /wiki/Main
537 * - Don't redirect anything with query parameters other than 'title' or 'action=view'.
539 * @param Title $title
540 * @return bool True if a redirect was set.
543 protected function tryNormaliseRedirect( Title
$title ): bool {
544 $request = $this->getRequest();
545 $output = $this->getOutput();
547 if ( ( $request->getRawVal( 'action' ) ??
'view' ) !== 'view'
548 ||
$request->wasPosted()
549 ||
( $request->getCheck( 'title' )
550 && $title->getPrefixedDBkey() == $request->getText( 'title' ) )
551 ||
count( $request->getValueNames( [ 'action', 'title' ] ) )
552 ||
!$this->getHookRunner()->onTestCanonicalRedirect( $request, $title, $output )
557 if ( $this->getConfig( MainConfigNames
::MainPageIsDomainRoot
) && $request->getRequestURL() === '/' ) {
561 $services = $this->getServiceContainer();
563 if ( $title->isSpecialPage() ) {
564 [ $name, $subpage ] = $services->getSpecialPageFactory()
565 ->resolveAlias( $title->getDBkey() );
568 $title = SpecialPage
::getTitleFor( $name, $subpage );
571 // Redirect to canonical url, make it a 301 to allow caching
572 $targetUrl = (string)$services->getUrlUtils()->expand( $title->getFullURL(), PROTO_CURRENT
);
573 if ( $targetUrl == $request->getFullRequestURL() ) {
574 $message = "Redirect loop detected!\n\n" .
575 "This means the wiki got confused about what page was " .
576 "requested; this sometimes happens when moving a wiki " .
577 "to a new server or changing the server configuration.\n\n";
579 if ( $this->getConfig( MainConfigNames
::UsePathInfo
) ) {
580 $message .= "The wiki is trying to interpret the page " .
581 "title from the URL path portion (PATH_INFO), which " .
582 "sometimes fails depending on the web server. Try " .
583 "setting \"\$wgUsePathInfo = false;\" in your " .
584 "LocalSettings.php, or check that \$wgArticlePath " .
587 $message .= "Your web server was detected as possibly not " .
588 "supporting URL path components (PATH_INFO) correctly; " .
589 "check your LocalSettings.php for a customized " .
590 "\$wgArticlePath setting and/or toggle \$wgUsePathInfo " .
593 throw new HttpError( 500, $message );
595 $output->setCdnMaxage( 1200 );
596 $output->redirect( $targetUrl, '301' );
601 * Initialize the main Article object for "standard" actions (view, etc)
602 * Create an Article object for the page, following redirects if needed.
604 * @return Article|string An Article, or a string to redirect to another URL
606 protected function initializeArticle() {
607 $context = $this->getContext();
609 $title = $context->getTitle();
610 $services = $this->getServiceContainer();
611 if ( $context->canUseWikiPage() ) {
612 // Optimization: Reuse the WikiPage instance from context, to avoid
613 // repeat fetching or computation of data already loaded.
614 $page = $context->getWikiPage();
616 // This case should not happen, but just in case.
617 // @TODO: remove this or use an exception
618 $page = $services->getWikiPageFactory()->newFromTitle( $title );
619 $context->setWikiPage( $page );
620 wfWarn( "RequestContext::canUseWikiPage() returned false" );
623 // Make GUI wrapper for the WikiPage
624 $article = Article
::newFromWikiPage( $page, $context );
626 // Skip some unnecessary code if the content model doesn't support redirects
627 // Use the page content model rather than invoking Title::getContentModel()
628 // to avoid querying page data twice (T206498)
629 if ( !$page->getContentHandler()->supportsRedirects() ) {
633 $request = $context->getRequest();
635 // Namespace might change when using redirects
636 // Check for redirects ...
637 $action = $request->getRawVal( 'action' ) ??
'view';
638 $file = ( $page instanceof WikiFilePage
) ?
$page->getFile() : null;
639 if ( ( $action == 'view' ||
$action == 'render' ) // ... for actions that show content
640 && !$request->getCheck( 'oldid' ) // ... and are not old revisions
641 && !$request->getCheck( 'diff' ) // ... and not when showing diff
642 && $request->getRawVal( 'redirect' ) !== 'no' // ... unless explicitly told not to
643 // ... and the article is not a non-redirect image page with associated file
644 && !( is_object( $file ) && $file->exists() && !$file->getRedirected() )
646 // Give extensions a change to ignore/handle redirects as needed
647 $ignoreRedirect = $target = false;
649 $this->getHookRunner()->onInitializeArticleMaybeRedirect( $title, $request,
650 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
651 $ignoreRedirect, $target, $article );
652 $page = $article->getPage(); // reflect any hook changes
654 // Follow redirects only for... redirects.
655 // If $target is set, then a hook wanted to redirect.
656 if ( !$ignoreRedirect && ( $target ||
$page->isRedirect() ) ) {
657 // Is the target already set by an extension?
658 $target = $target ?
: $page->followRedirect();
659 if ( is_string( $target ) && !$this->getConfig( MainConfigNames
::DisableHardRedirects
) ) {
660 // we'll need to redirect
663 if ( is_object( $target ) ) {
664 // Rewrite environment to redirected article
665 $rpage = $services->getWikiPageFactory()->newFromTitle( $target );
666 $rpage->loadPageData();
667 if ( $rpage->exists() ||
( is_object( $file ) && !$file->isLocal() ) ) {
668 $rarticle = Article
::newFromWikiPage( $rpage, $context );
669 $rarticle->setRedirectedFrom( $title );
671 $article = $rarticle;
672 // NOTE: This also clears any action cache
673 $context->setTitle( $target );
674 $context->setWikiPage( $article->getPage() );
684 * Perform one of the "standard" actions
686 * @param Article $article
687 * @param Title $requestTitle The original title, before any redirects were applied
689 protected function performAction( Article
$article, Title
$requestTitle ) {
690 $request = $this->getRequest();
691 $output = $this->getOutput();
692 $title = $this->getTitle();
693 $user = $this->getUser();
695 if ( !$this->getHookRunner()->onMediaWikiPerformAction(
696 $output, $article, $title, $user, $request, $this )
701 $t = microtime( true );
702 $actionName = $this->getAction();
703 $services = $this->getServiceContainer();
705 $action = $services->getActionFactory()->getAction( $actionName, $article, $this->getContext() );
706 if ( $action instanceof Action
) {
707 ProfilingContext
::singleton()->init( MW_ENTRY_POINT
, $actionName );
709 // Check read permissions
710 if ( $action->needsReadRights() && !$user->isAllowed( 'read' ) ) {
711 throw new PermissionsError( 'read' );
714 // Narrow DB query expectations for this HTTP request
715 if ( $request->wasPosted() && !$action->doesWrites() ) {
716 $trxProfiler = Profiler
::instance()->getTransactionProfiler();
717 $trxLimits = $this->getConfig( MainConfigNames
::TrxProfilerLimits
);
718 $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__
);
721 // Let CDN cache things if we can purge them.
722 // Also unconditionally cache page views.
723 if ( $this->getConfig( MainConfigNames
::UseCdn
) ) {
724 $htmlCacheUpdater = $services->getHtmlCacheUpdater();
725 if ( $request->matchURLForCDN( $htmlCacheUpdater->getUrls( $requestTitle ) ) ) {
726 $output->setCdnMaxage( $this->getConfig( MainConfigNames
::CdnMaxAge
) );
727 } elseif ( $action instanceof ViewAction
) {
728 $output->setCdnMaxage( 3600 );
734 $runTime = microtime( true ) - $t;
736 $statAction = strtr( $actionName, '.', '_' );
737 $services->getStatsFactory()->getTiming( 'action_executeTiming_seconds' )
738 ->setLabel( 'action', $statAction )
739 ->copyToStatsdAt( 'action.' . $statAction . '.executeTiming' )
740 ->observe( 1000 * $runTime );
745 // If we've not found out which action it is by now, it's unknown
746 $output->setStatusCode( 404 );
747 $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );