MediaWiki REL1_37
MediaWiki.php
Go to the documentation of this file.
1<?php
23use Liuggio\StatsdClient\Sender\SocketSender;
24use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
28use Psr\Log\LoggerInterface;
31use Wikimedia\ScopedCallback;
32
36class MediaWiki {
37 use ProtectedHookAccessorTrait;
38
40 private $context;
42 private $config;
43
45 private $action;
48
52 private const DEFER_SET_LENGTH_AND_FLUSH = 2;
54 private const DEFER_CLI_MODE = 3;
55
59 public function __construct( IContextSource $context = null ) {
60 $this->context = $context ?: RequestContext::getMain();
61 $this->config = $this->context->getConfig();
62
63 if ( $this->config->get( 'CommandLineMode' ) ) {
64 $this->postSendStrategy = self::DEFER_CLI_MODE;
65 } elseif ( function_exists( 'fastcgi_finish_request' ) ) {
66 $this->postSendStrategy = self::DEFER_FASTCGI_FINISH_REQUEST;
67 } else {
68 $this->postSendStrategy = self::DEFER_SET_LENGTH_AND_FLUSH;
69 }
70 }
71
78 private function parseTitle() {
79 $request = $this->context->getRequest();
80 $curid = $request->getInt( 'curid' );
81 $title = $request->getText( 'title' );
82 $action = $request->getRawVal( 'action' );
83
84 if ( $curid ) {
85 // URLs like this are generated by RC, because rc_title isn't always accurate
86 $ret = Title::newFromID( $curid );
87 } else {
88 $ret = Title::newFromURL( $title );
89 if ( $ret !== null ) {
90 // Alias NS_MEDIA page URLs to NS_FILE...we only use NS_MEDIA
91 // in wikitext links to tell Parser to make a direct file link
92 if ( $ret->getNamespace() === NS_MEDIA ) {
93 $ret = Title::makeTitle( NS_FILE, $ret->getDBkey() );
94 }
95 // Check variant links so that interwiki links don't have to worry
96 // about the possible different language variants
98 $languageConverter = $services
99 ->getLanguageConverterFactory()
100 ->getLanguageConverter( $services->getContentLanguage() );
101 if ( $languageConverter->hasVariants() && !$ret->exists() ) {
102 $languageConverter->findVariantLink( $title, $ret );
103 }
104 }
105 }
106
107 // If title is not provided, always allow oldid and diff to set the title.
108 // If title is provided, allow oldid and diff to override the title, unless
109 // we are talking about a special page which might use these parameters for
110 // other purposes.
111 if ( $ret === null || !$ret->isSpecialPage() ) {
112 // We can have urls with just ?diff=,?oldid= or even just ?diff=
113 $oldid = $request->getInt( 'oldid' );
114 $oldid = $oldid ?: $request->getInt( 'diff' );
115 // Allow oldid to override a changed or missing title
116 if ( $oldid ) {
117 $revRecord = MediaWikiServices::getInstance()
118 ->getRevisionLookup()
119 ->getRevisionById( $oldid );
120 if ( $revRecord ) {
121 $ret = Title::newFromLinkTarget(
122 $revRecord->getPageAsLinkTarget()
123 );
124 }
125 }
126 }
127
128 if ( $ret === null && $request->getCheck( 'search' ) ) {
129 // Compatibility with old search URLs which didn't use Special:Search
130 // Just check for presence here, so blank requests still
131 // show the search page when using ugly URLs (T10054).
132 $ret = SpecialPage::getTitleFor( 'Search' );
133 }
134
135 // Use the main page as default title if nothing else has been provided
136 if ( $ret === null
137 && strval( $title ) === ''
138 && !$request->getCheck( 'curid' )
139 && $action !== 'delete'
140 ) {
141 $ret = Title::newMainPage();
142 }
143
144 if ( $ret === null || ( $ret->getDBkey() == '' && !$ret->isExternal() ) ) {
145 // If we get here, we definitely don't have a valid title; throw an exception.
146 // Try to get detailed invalid title exception first, fall back to MalformedTitleException.
147 Title::newFromTextThrow( $title );
148 throw new MalformedTitleException( 'badtitletext', $title );
149 }
150
151 return $ret;
152 }
153
158 public function getTitle() {
159 if ( !$this->context->hasTitle() ) {
160 try {
161 $this->context->setTitle( $this->parseTitle() );
162 } catch ( MalformedTitleException $ex ) {
163 $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
164 }
165 }
166 return $this->context->getTitle();
167 }
168
174 public function getAction(): string {
175 if ( $this->action === null ) {
176 $this->action = Action::getActionName( $this->context );
177 }
178
179 return $this->action;
180 }
181
194 private function performRequest() {
195 global $wgTitle;
196
197 $request = $this->context->getRequest();
198 $requestTitle = $title = $this->context->getTitle();
199 $output = $this->context->getOutput();
200 $user = $this->context->getUser();
201
202 if ( $request->getRawVal( 'printable' ) === 'yes' ) {
203 $output->setPrintable();
204 }
205
206 $this->getHookRunner()->onBeforeInitialize( $title, null, $output, $user, $request, $this );
207
208 // Invalid titles. T23776: The interwikis must redirect even if the page name is empty.
209 if ( $title === null || ( $title->getDBkey() == '' && !$title->isExternal() )
210 || $title->isSpecial( 'Badtitle' )
211 ) {
212 $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
213 try {
214 $this->parseTitle();
215 } catch ( MalformedTitleException $ex ) {
216 throw new BadTitleError( $ex );
217 }
218 throw new BadTitleError();
219 }
220
221 // Check user's permissions to read this page.
222 // We have to check here to catch special pages etc.
223 // We will check again in Article::view().
224 $permissionStatus = PermissionStatus::newEmpty();
225 if ( !$this->context->getAuthority()->authorizeRead( 'read', $title, $permissionStatus ) ) {
226 // T34276: allowing the skin to generate output with $wgTitle or
227 // $this->context->title set to the input title would allow anonymous users to
228 // determine whether a page exists, potentially leaking private data. In fact, the
229 // curid and oldid request parameters would allow page titles to be enumerated even
230 // when they are not guessable. So we reset the title to Special:Badtitle before the
231 // permissions error is displayed.
232
233 // The skin mostly uses $this->context->getTitle() these days, but some extensions
234 // still use $wgTitle.
235 $badTitle = SpecialPage::getTitleFor( 'Badtitle' );
236 $this->context->setTitle( $badTitle );
237 $wgTitle = $badTitle;
238
239 throw new PermissionsError( 'read', $permissionStatus );
240 }
241
242 // Interwiki redirects
243 if ( $title->isExternal() ) {
244 $rdfrom = $request->getVal( 'rdfrom' );
245 if ( $rdfrom ) {
246 $url = $title->getFullURL( [ 'rdfrom' => $rdfrom ] );
247 } else {
248 $query = $request->getValues();
249 unset( $query['title'] );
250 $url = $title->getFullURL( $query );
251 }
252 // Check for a redirect loop
253 if ( !preg_match( '/^' . preg_quote( $this->config->get( 'Server' ), '/' ) . '/', $url )
254 && $title->isLocal()
255 ) {
256 // 301 so google et al report the target as the actual url.
257 $output->redirect( $url, 301 );
258 } else {
259 $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
260 try {
261 $this->parseTitle();
262 } catch ( MalformedTitleException $ex ) {
263 throw new BadTitleError( $ex );
264 }
265 throw new BadTitleError();
266 }
267 // Handle any other redirects.
268 // Redirect loops, titleless URL, $wgUsePathInfo URLs, and URLs with a variant
269 } elseif ( !$this->tryNormaliseRedirect( $title ) ) {
270 // Prevent information leak via Special:MyPage et al (T109724)
271 $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
272 if ( $title->isSpecialPage() ) {
273 $specialPage = $spFactory->getPage( $title->getDBkey() );
274 if ( $specialPage instanceof RedirectSpecialPage ) {
275 $specialPage->setContext( $this->context );
276 if ( $this->config->get( 'HideIdentifiableRedirects' )
277 && $specialPage->personallyIdentifiableTarget()
278 ) {
279 list( , $subpage ) = $spFactory->resolveAlias( $title->getDBkey() );
280 $target = $specialPage->getRedirect( $subpage );
281 // Target can also be true. We let that case fall through to normal processing.
282 if ( $target instanceof Title ) {
283 if ( $target->isExternal() ) {
284 // Handle interwiki redirects
285 $target = SpecialPage::getTitleFor(
286 'GoToInterwiki',
287 'force/' . $target->getPrefixedDBkey()
288 );
289 }
290
291 $query = $specialPage->getRedirectQuery( $subpage ) ?: [];
292 $request = new DerivativeRequest( $this->context->getRequest(), $query );
293 $request->setRequestURL( $this->context->getRequest()->getRequestURL() );
294 $this->context->setRequest( $request );
295 // Do not varnish cache these. May vary even for anons
296 $this->context->getOutput()->lowerCdnMaxage( 0 );
297 $this->context->setTitle( $target );
298 $wgTitle = $target;
299 // Reset action type cache. (Special pages have only view)
300 $this->action = null;
301 $title = $target;
302 $output->addJsConfigVars( [
303 'wgInternalRedirectTargetUrl' => $target->getLinkURL( $query ),
304 ] );
305 $output->addModules( 'mediawiki.action.view.redirect' );
306 }
307 }
308 }
309 }
310
311 // Special pages ($title may have changed since if statement above)
312 if ( $title->isSpecialPage() ) {
313 // Actions that need to be made when we have a special pages
314 $spFactory->executePath( $title, $this->context );
315 } else {
316 // ...otherwise treat it as an article view. The article
317 // may still be a wikipage redirect to another article or URL.
318 $article = $this->initializeArticle();
319 if ( is_object( $article ) ) {
320 $this->performAction( $article, $requestTitle );
321 } elseif ( is_string( $article ) ) {
322 $output->redirect( $article );
323 } else {
324 throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle()"
325 . " returned neither an object nor a URL" );
326 }
327 }
328 $output->considerCacheSettingsFinal();
329 }
330 }
331
354 private function tryNormaliseRedirect( Title $title ) {
355 $request = $this->context->getRequest();
356 $output = $this->context->getOutput();
357
358 if ( $request->getRawVal( 'action', 'view' ) != 'view'
359 || $request->wasPosted()
360 || ( $request->getCheck( 'title' )
361 && $title->getPrefixedDBkey() == $request->getText( 'title' ) )
362 || count( $request->getValueNames( [ 'action', 'title' ] ) )
363 || !$this->getHookRunner()->onTestCanonicalRedirect( $request, $title, $output )
364 ) {
365 return false;
366 }
367
368 if ( $this->config->get( 'MainPageIsDomainRoot' ) && $request->getRequestURL() === '/' ) {
369 return false;
370 }
371
372 if ( $title->isSpecialPage() ) {
373 list( $name, $subpage ) = MediaWikiServices::getInstance()->getSpecialPageFactory()->
374 resolveAlias( $title->getDBkey() );
375 if ( $name ) {
376 $title = SpecialPage::getTitleFor( $name, $subpage );
377 }
378 }
379 // Redirect to canonical url, make it a 301 to allow caching
380 $targetUrl = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
381 if ( $targetUrl == $request->getFullRequestURL() ) {
382 $message = "Redirect loop detected!\n\n" .
383 "This means the wiki got confused about what page was " .
384 "requested; this sometimes happens when moving a wiki " .
385 "to a new server or changing the server configuration.\n\n";
386
387 if ( $this->config->get( 'UsePathInfo' ) ) {
388 $message .= "The wiki is trying to interpret the page " .
389 "title from the URL path portion (PATH_INFO), which " .
390 "sometimes fails depending on the web server. Try " .
391 "setting \"\$wgUsePathInfo = false;\" in your " .
392 "LocalSettings.php, or check that \$wgArticlePath " .
393 "is correct.";
394 } else {
395 $message .= "Your web server was detected as possibly not " .
396 "supporting URL path components (PATH_INFO) correctly; " .
397 "check your LocalSettings.php for a customized " .
398 "\$wgArticlePath setting and/or toggle \$wgUsePathInfo " .
399 "to true.";
400 }
401 throw new HttpError( 500, $message );
402 }
403 $output->setCdnMaxage( 1200 );
404 $output->redirect( $targetUrl, '301' );
405 return true;
406 }
407
414 private function initializeArticle() {
415 $title = $this->context->getTitle();
416 $services = MediaWikiServices::getInstance();
417 if ( $this->context->canUseWikiPage() ) {
418 // Reuse the WikiPage instance from context, as it may already have been initialized
419 // by an earlier this->getAction() call.
420 $page = $this->context->getWikiPage();
421 } else {
422 // This case should not happen, but just in case.
423 // @TODO: remove this or use an exception
424 $page = $services->getWikiPageFactory()->newFromTitle( $title );
425 $this->context->setWikiPage( $page );
426 wfWarn( "RequestContext::canUseWikiPage() returned false" );
427 }
428
429 // Make GUI wrapper for the WikiPage
430 $article = Article::newFromWikiPage( $page, $this->context );
431
432 // Skip some unnecessary code if the content model doesn't support redirects
433 if ( !$services->getContentHandlerFactory()
434 ->getContentHandler( $title->getContentModel() )
435 ->supportsRedirects()
436 ) {
437 return $article;
438 }
439
440 $request = $this->context->getRequest();
441
442 // Namespace might change when using redirects
443 // Check for redirects ...
444 $action = $request->getRawVal( 'action', 'view' );
445 $file = ( $page instanceof WikiFilePage ) ? $page->getFile() : null;
446 if ( ( $action == 'view' || $action == 'render' ) // ... for actions that show content
447 && !$request->getCheck( 'oldid' ) // ... and are not old revisions
448 && !$request->getCheck( 'diff' ) // ... and not when showing diff
449 && $request->getRawVal( 'redirect' ) !== 'no' // ... unless explicitly told not to
450 // ... and the article is not a non-redirect image page with associated file
451 && !( is_object( $file ) && $file->exists() && !$file->getRedirected() )
452 ) {
453 // Give extensions a change to ignore/handle redirects as needed
454 $ignoreRedirect = $target = false;
455
456 $this->getHookRunner()->onInitializeArticleMaybeRedirect( $title, $request,
457 $ignoreRedirect, $target, $article );
458 $page = $article->getPage(); // reflect any hook changes
459
460 // Follow redirects only for... redirects.
461 // If $target is set, then a hook wanted to redirect.
462 if ( !$ignoreRedirect && ( $target || $page->isRedirect() ) ) {
463 // Is the target already set by an extension?
464 $target = $target ?: $page->followRedirect();
465 if ( is_string( $target ) && !$this->config->get( 'DisableHardRedirects' ) ) {
466 // we'll need to redirect
467 return $target;
468 }
469 if ( is_object( $target ) ) {
470 // Rewrite environment to redirected article
471 $rpage = $services->getWikiPageFactory()->newFromTitle( $target );
472 $rpage->loadPageData();
473 if ( $rpage->exists() || ( is_object( $file ) && !$file->isLocal() ) ) {
474 $rarticle = Article::newFromWikiPage( $rpage, $this->context );
475 $rarticle->setRedirectedFrom( $title );
476
477 $article = $rarticle;
478 $this->context->setTitle( $target );
479 $this->context->setWikiPage( $article->getPage() );
480 }
481 }
482 } else {
483 // Article may have been changed by hook
484 $this->context->setTitle( $article->getTitle() );
485 $this->context->setWikiPage( $article->getPage() );
486 }
487 }
488
489 return $article;
490 }
491
498 private function performAction( Article $article, Title $requestTitle ) {
499 $request = $this->context->getRequest();
500 $output = $this->context->getOutput();
501 $title = $this->context->getTitle();
502 $user = $this->context->getUser();
503 $services = MediaWikiServices::getInstance();
504
505 if ( !$this->getHookRunner()->onMediaWikiPerformAction(
506 $output, $article, $title, $user, $request, $this )
507 ) {
508 return;
509 }
510
511 $t = microtime( true );
512 $actionName = $this->getAction();
513 $action = Action::factory( $actionName, $article, $this->context );
514
515 if ( $action instanceof Action ) {
516 // Check read permissions
517 if ( $action->needsReadRights() && !$user->isAllowed( 'read' ) ) {
518 throw new PermissionsError( 'read' );
519 }
520
521 // Narrow DB query expectations for this HTTP request
522 $trxLimits = $this->config->get( 'TrxProfilerLimits' );
523 $trxProfiler = Profiler::instance()->getTransactionProfiler();
524 if ( $request->wasPosted() && !$action->doesWrites() ) {
525 $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
526 $request->markAsSafeRequest();
527 }
528
529 # Let CDN cache things if we can purge them.
530 if ( $this->config->get( 'UseCdn' ) ) {
531 $htmlCacheUpdater = $services->getHtmlCacheUpdater();
532 if ( in_array(
533 // Use PROTO_INTERNAL because that's what HtmlCacheUpdater::getUrls() uses
534 wfExpandUrl( $request->getRequestURL(), PROTO_INTERNAL ),
535 $htmlCacheUpdater->getUrls( $requestTitle )
536 )
537 ) {
538 $output->setCdnMaxage( $this->config->get( 'CdnMaxAge' ) );
539 }
540 }
541
542 $action->show();
543
544 $runTime = microtime( true ) - $t;
545 $services->getStatsdDataFactory()->timing(
546 'action.' . strtr( $actionName, '.', '_' ) . '.executeTiming',
547 1000 * $runTime
548 );
549 return;
550 }
551
552 // If we've not found out which action it is by now, it's unknown
553 $output->setStatusCode( 404 );
554 $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
555 }
556
560 public function run() {
561 try {
562 $this->setDBProfilingAgent();
563 $this->main();
564 } catch ( Exception $e ) {
565 $context = $this->context;
566 $action = $context->getRequest()->getRawVal( 'action', 'view' );
567 if (
568 $e instanceof DBConnectionError &&
569 $context->hasTitle() &&
570 $context->getTitle()->canExist() &&
571 in_array( $action, [ 'view', 'history' ], true ) &&
573 ) {
574 // Try to use any (even stale) file during outages...
575 $cache = new HTMLFileCache( $context->getTitle(), $action );
576 if ( $cache->isCached() ) {
577 $cache->loadFromFileCache( $context, HTMLFileCache::MODE_OUTAGE );
579 exit;
580 }
581 }
582 MWExceptionHandler::handleException( $e, MWExceptionHandler::CAUGHT_BY_ENTRYPOINT );
583 } catch ( Throwable $e ) {
584 // Type errors and such: at least handle it now and clean up the LBFactory state
585 MWExceptionHandler::handleException( $e, MWExceptionHandler::CAUGHT_BY_ENTRYPOINT );
586 }
587
588 $this->doPostOutputShutdown();
589 }
590
594 private function setDBProfilingAgent() {
595 $services = MediaWikiServices::getInstance();
596 $name = $this->context->getUser()->getName();
597 $services->getDBLoadBalancerFactory()->setAgentName(
598 mb_strlen( $name ) > 15 ? mb_substr( $name, 0, 15 ) . '...' : $name
599 );
600 }
601
605 private function schedulePostSendJobs() {
606 $jobRunRate = $this->config->get( 'JobRunRate' );
607 if (
608 // Recursion guard
609 $this->getTitle()->isSpecial( 'RunJobs' ) ||
610 // Short circuit if there is nothing to do
611 ( $jobRunRate <= 0 || wfReadOnly() ) ||
612 // Avoid blocking the client on stock apache; see doPostOutputShutdown()
613 (
614 $this->context->getRequest()->getMethod() === 'HEAD' ||
615 $this->context->getRequest()->getHeader( 'If-Modified-Since' )
616 )
617 ) {
618 return;
619 }
620
621 if ( $jobRunRate < 1 ) {
622 $max = mt_getrandmax();
623 if ( mt_rand( 0, $max ) > $max * $jobRunRate ) {
624 return; // the higher the job run rate, the less likely we return here
625 }
626 $n = 1;
627 } else {
628 $n = intval( $jobRunRate );
629 }
630
631 if ( wfReadOnly() ) {
632 return;
633 }
634
635 // Note that DeferredUpdates will catch and log any errors (T88312)
636 DeferredUpdates::addUpdate( new TransactionRoundDefiningUpdate( function () use ( $n ) {
637 $logger = LoggerFactory::getInstance( 'runJobs' );
638 if ( $this->config->get( 'RunJobsAsync' ) ) {
639 // Send an HTTP request to the job RPC entry point if possible
640 $invokedWithSuccess = $this->triggerAsyncJobs( $n, $logger );
641 if ( !$invokedWithSuccess ) {
642 // Fall back to blocking on running the job(s)
643 $logger->warning( "Jobs switched to blocking; Special:RunJobs disabled" );
644 $this->triggerSyncJobs( $n );
645 }
646 } else {
647 $this->triggerSyncJobs( $n );
648 }
649 }, __METHOD__ ) );
650 }
651
657 public function doPreOutputCommit( callable $postCommitWork = null ) {
658 self::preOutputCommit( $this->context, $postCommitWork );
659 }
660
672 public static function preOutputCommit(
673 IContextSource $context, callable $postCommitWork = null
674 ) {
675 $config = $context->getConfig();
676 $request = $context->getRequest();
677 $output = $context->getOutput();
678 $services = MediaWikiServices::getInstance();
679 $lbFactory = $services->getDBLoadBalancerFactory();
680
681 // Try to make sure that all RDBMs, session, and other storage updates complete
682 ignore_user_abort( true );
683
684 // Commit all RDBMs changes from the main transaction round
685 $lbFactory->commitPrimaryChanges(
686 __METHOD__,
687 // Abort if any transaction was too big
688 [ 'maxWriteDuration' => $config->get( 'MaxUserDBWriteDuration' ) ]
689 );
690 wfDebug( __METHOD__ . ': primary transaction round committed' );
691
692 // Run updates that need to block the client or affect output (this is the last chance)
693 DeferredUpdates::doUpdates( 'run', DeferredUpdates::PRESEND );
694 wfDebug( __METHOD__ . ': pre-send deferred updates completed' );
695
696 // Persist the session to avoid race conditions on subsequent requests by the client
697 $request->getSession()->save(); // T214471
698 wfDebug( __METHOD__ . ': session changes committed' );
699
700 // Subsequent requests by the client should see the DB replication positions written
701 // during the shutdown() call below, even if the position store itself has asynchronous
702 // replication. Setting the cpPosIndex cookie is normally enough. However, this might not
703 // work for cross-domain redirects to foreign wikis, so set the ?cpPoxIndex in that case.
704 $isCrossWikiRedirect = (
705 $output->getRedirect() &&
706 $lbFactory->hasOrMadeRecentPrimaryChanges( INF ) &&
707 self::getUrlDomainDistance( $output->getRedirect() ) === 'remote'
708 );
709
710 // Persist replication positions for DBs modified by this request (at this point).
711 // These help provide "session consistency" for the client on their next requests.
712 $cpIndex = null;
713 $cpClientId = null;
714 $lbFactory->shutdown(
715 $lbFactory::SHUTDOWN_NORMAL,
716 $postCommitWork,
717 $cpIndex,
718 $cpClientId
719 );
720 $now = time();
721
722 $allowHeaders = !( $output->isDisabled() || headers_sent() );
723
724 if ( $cpIndex > 0 ) {
725 if ( $allowHeaders ) {
726 $expires = $now + ChronologyProtector::POSITION_COOKIE_TTL;
727 $options = [ 'prefix' => '' ];
728 $value = $lbFactory::makeCookieValueFromCPIndex( $cpIndex, $now, $cpClientId );
729 $request->response()->setCookie( 'cpPosIndex', $value, $expires, $options );
730 }
731
732 if ( $isCrossWikiRedirect ) {
733 if ( $output->getRedirect() ) { // sanity
734 $safeUrl = $lbFactory->appendShutdownCPIndexAsQuery(
735 $output->getRedirect(),
736 $cpIndex
737 );
738 $output->redirect( $safeUrl );
739 } else {
740 MWExceptionHandler::logException(
741 new LogicException( "No redirect; cannot append cpPosIndex parameter." ),
742 MWExceptionHandler::CAUGHT_BY_ENTRYPOINT
743 );
744 }
745 }
746 }
747
748 if ( $allowHeaders ) {
749 // Set a cookie to tell all CDN edge nodes to "stick" the user to the DC that
750 // handles this POST request (e.g. the "primary" data center). Also have the user
751 // briefly bypass CDN so ChronologyProtector works for cacheable URLs.
752 if ( $request->wasPosted() && $lbFactory->hasOrMadeRecentPrimaryChanges() ) {
753 $expires = $now + max(
754 ChronologyProtector::POSITION_COOKIE_TTL,
755 $config->get( 'DataCenterUpdateStickTTL' )
756 );
757 $options = [ 'prefix' => '' ];
758 $request->response()->setCookie( 'UseDC', 'master', $expires, $options );
759 $request->response()->setCookie( 'UseCDNCache', 'false', $expires, $options );
760 }
761
762 // Avoid letting a few seconds of replica DB lag cause a month of stale data.
763 // This logic is also intimately related to the value of $wgCdnReboundPurgeDelay.
764 if ( $lbFactory->laggedReplicaUsed() ) {
765 $maxAge = $config->get( 'CdnMaxageLagged' );
766 $output->lowerCdnMaxage( $maxAge );
767 $request->response()->header( "X-Database-Lagged: true" );
768 wfDebugLog( 'replication',
769 "Lagged DB used; CDN cache TTL limited to $maxAge seconds" );
770 }
771
772 // Avoid long-term cache pollution due to message cache rebuild timeouts (T133069)
773 if ( $services->getMessageCache()->isDisabled() ) {
774 $maxAge = $config->get( 'CdnMaxageSubstitute' );
775 $output->lowerCdnMaxage( $maxAge );
776 $request->response()->header( "X-Response-Substitute: true" );
777 }
778
779 if ( !$output->couldBePublicCached() || $output->haveCacheVaryCookies() ) {
780 // Autoblocks: If this user is autoblocked (and the cookie block feature is enabled
781 // for autoblocks), then set a cookie to track this block.
782 // This has to be done on all logged-in page loads (not just upon saving edits),
783 // because an autoblocked editor might not edit again from the same IP address.
784 //
785 // IP blocks: For anons, if their IP is blocked (and cookie block feature is enabled
786 // for IP blocks), we also want to set the cookie whenever it is safe to do.
787 // Basically from any url that are definitely not publicly cacheable (like viewing
788 // EditPage), or when the HTTP response is personalised for other reasons (e.g. viewing
789 // articles within the same browsing session after making an edit).
790 $user = $context->getUser();
791 $services->getBlockManager()
792 ->trackBlockWithCookie( $user, $request->response() );
793 }
794 }
795 }
796
801 private static function getUrlDomainDistance( $url ) {
802 $clusterWiki = WikiMap::getWikiFromUrl( $url );
803 if ( WikiMap::isCurrentWikiId( $clusterWiki ) ) {
804 return 'local'; // the current wiki
805 }
806 if ( $clusterWiki !== false ) {
807 return 'remote'; // another wiki in this cluster/farm
808 }
809
810 return 'external';
811 }
812
822 public function doPostOutputShutdown() {
823 // Record backend request timing
824 $timing = $this->context->getTiming();
825 $timing->mark( 'requestShutdown' );
826
827 // Defer everything else if possible...
828 if ( $this->postSendStrategy === self::DEFER_FASTCGI_FINISH_REQUEST ) {
829 // Flush the output to the client, continue processing, and avoid further output
830 fastcgi_finish_request();
831 } elseif ( $this->postSendStrategy === self::DEFER_SET_LENGTH_AND_FLUSH ) {
832 // Flush the output to the client, continue processing, and avoid further output
833 if ( ob_get_level() ) {
834 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
835 @ob_end_flush();
836 }
837 // Flush the web server output buffer to the client/proxy if possible
838 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
839 @flush();
840 }
841
842 // Since the headers and output where already flushed, disable WebResponse setters
843 // during post-send processing to warnings and unexpected behavior (T191537)
844 WebResponse::disableForPostSend();
845 // Run post-send updates while preventing further output for sanity...
846 ob_start( static function () {
847 return ''; // do not output uncaught exceptions
848 } );
849 try {
850 $this->restInPeace();
851 } catch ( Throwable $e ) {
852 MWExceptionHandler::rollbackPrimaryChangesAndLog(
853 $e,
854 MWExceptionHandler::CAUGHT_BY_ENTRYPOINT
855 );
856 }
857 $length = ob_get_length();
858 if ( $length > 0 ) {
859 trigger_error( __METHOD__ . ": suppressed $length byte(s)", E_USER_NOTICE );
860 }
861 ob_end_clean();
862 }
863
867 private function main() {
868 global $wgTitle;
869
870 $output = $this->context->getOutput();
871 $request = $this->context->getRequest();
872
873 // Send Ajax requests to the Ajax dispatcher.
874 if ( $request->getRawVal( 'action' ) === 'ajax' ) {
875 // Set a dummy title, because $wgTitle == null might break things
876 $title = Title::makeTitle( NS_SPECIAL, 'Badtitle/performing an AJAX call in '
877 . __METHOD__
878 );
879 $this->context->setTitle( $title );
881
882 $dispatcher = new AjaxDispatcher( $this->config );
883 $dispatcher->performAction( $this->context->getUser() );
884
885 return;
886 }
887
888 // Get title from request parameters,
889 // is set on the fly by parseTitle the first time.
890 $title = $this->getTitle();
891 $wgTitle = $title;
892
893 // Set DB query expectations for this HTTP request
894 $trxLimits = $this->config->get( 'TrxProfilerLimits' );
895 $trxProfiler = Profiler::instance()->getTransactionProfiler();
896 $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) );
897 if ( $request->hasSafeMethod() ) {
898 $trxProfiler->setExpectations( $trxLimits['GET'], __METHOD__ );
899 } else {
900 $trxProfiler->setExpectations( $trxLimits['POST'], __METHOD__ );
901 }
902
903 if ( $this->maybeDoHttpsRedirect() ) {
904 return;
905 }
906
907 if ( $title->canExist() && HTMLFileCache::useFileCache( $this->context ) ) {
908 // getAction() may trigger DB queries, so avoid eagerly initializing it if possible.
909 // This reduces the cost of requests that exit early due to tryNormaliseRedirect()
910 // or a MediaWikiPerformAction / BeforeInitialize hook handler.
911 $action = $this->getAction();
912 // Try low-level file cache hit
913 $cache = new HTMLFileCache( $title, $action );
914 if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
915 // Check incoming headers to see if client has this cached
916 $timestamp = $cache->cacheTimestamp();
917 if ( !$output->checkLastModified( $timestamp ) ) {
918 $cache->loadFromFileCache( $this->context );
919 }
920 // Do any stats increment/watchlist stuff, assuming user is viewing the
921 // latest revision (which should always be the case for file cache)
922 $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
923 // Tell OutputPage that output is taken care of
924 $output->disable();
925
926 return;
927 }
928 }
929
930 try {
931 // Actually do the work of the request and build up any output
932 $this->performRequest();
933 } catch ( ErrorPageError $e ) {
934 // TODO: Should ErrorPageError::report accept a OutputPage parameter?
936 $output->considerCacheSettingsFinal();
937 // T64091: while exceptions are convenient to bubble up GUI errors,
938 // they are not internal application faults. As with normal requests, this
939 // should commit, print the output, do deferred updates, jobs, and profiling.
940 }
941
942 // GUI-ify and stash the page output in MediaWiki::doPreOutputCommit()
943 $buffer = null;
944 $outputWork = static function () use ( $output, &$buffer ) {
945 if ( $buffer === null ) {
946 $buffer = $output->output( true );
947 }
948
949 return $buffer;
950 };
951
952 // Commit any changes in the current transaction round so that:
953 // a) the transaction is not rolled back after success output was already sent
954 // b) error output is not jumbled together with success output in the response
955 $this->doPreOutputCommit( $outputWork );
956 // If needed, push a deferred update to run jobs after the output is send
957 $this->schedulePostSendJobs();
958 // If no exceptions occurred then send the output since it is safe now
959 $this->outputResponsePayload( $outputWork() );
960 }
961
968 private function shouldDoHttpRedirect() {
969 $request = $this->context->getRequest();
970
971 // Don't redirect if we're already on HTTPS
972 if ( $request->getProtocol() !== 'http' ) {
973 return false;
974 }
975
976 $force = $this->config->get( 'ForceHTTPS' );
977
978 // Don't redirect if $wgServer is explicitly HTTP. We test for this here
979 // by checking whether wfExpandUrl() is able to force HTTPS.
980 if ( !preg_match( '#^https://#', wfExpandUrl( $request->getRequestURL(), PROTO_HTTPS ) ) ) {
981 if ( $force ) {
982 throw new RuntimeException( '$wgForceHTTPS is true but the server is not HTTPS' );
983 }
984 return false;
985 }
986
987 // Configured $wgForceHTTPS overrides the remaining conditions
988 if ( $force ) {
989 return true;
990 }
991
992 // Check if HTTPS is required by the session or user preferences
993 return $request->getSession()->shouldForceHTTPS() ||
994 // Check the cookie manually, for paranoia
995 $request->getCookie( 'forceHTTPS', '' ) ||
996 // Avoid checking the user and groups unless it's enabled.
997 (
998 $this->context->getUser()->isRegistered()
999 && $this->context->getUser()->requiresHTTPS()
1000 );
1001 }
1002
1012 private function maybeDoHttpsRedirect() {
1013 if ( !$this->shouldDoHttpRedirect() ) {
1014 return false;
1015 }
1016
1017 $request = $this->context->getRequest();
1018 $oldUrl = $request->getFullRequestURL();
1019 $redirUrl = preg_replace( '#^http://#', 'https://', $oldUrl );
1020
1021 if ( $request->wasPosted() ) {
1022 // This is weird and we'd hope it almost never happens. This
1023 // means that a POST came in via HTTP and policy requires us
1024 // redirecting to HTTPS. It's likely such a request is going
1025 // to fail due to post data being lost, but let's try anyway
1026 // and just log the instance.
1027
1028 // @todo FIXME: See if we could issue a 307 or 308 here, need
1029 // to see how clients (automated & browser) behave when we do
1030 wfDebugLog( 'RedirectedPosts', "Redirected from HTTP to HTTPS: $oldUrl" );
1031 }
1032 // Setup dummy Title, otherwise OutputPage::redirect will fail
1033 $title = Title::newFromText( 'REDIR', NS_MAIN );
1034 $this->context->setTitle( $title );
1035 // Since we only do this redir to change proto, always send a vary header
1036 $output = $this->context->getOutput();
1037 $output->addVaryHeader( 'X-Forwarded-Proto' );
1038 $output->redirect( $redirUrl );
1039 $output->output();
1040
1041 return true;
1042 }
1043
1053 private function outputResponsePayload( $content ) {
1054 // Append any visible profiling data in a manner appropriate for the Content-Type
1055 ob_start();
1056 try {
1057 Profiler::instance()->logDataPageOutputOnly();
1058 } finally {
1059 $content .= ob_get_clean();
1060 }
1061
1062 // By default, usually one output buffer is active now, either the internal PHP buffer
1063 // started by "output_buffering" in php.ini or the buffer started by MW_SETUP_CALLBACK.
1064 // The MW_SETUP_CALLBACK buffer has an unlimited chunk size, while the internal PHP
1065 // buffer only has an unlimited chunk size if output_buffering="On". If the buffer was
1066 // filled up to the chunk size with printed data, then HTTP headers will have already
1067 // been sent. Also, if the entry point had to stream content to the client, then HTTP
1068 // headers will have already been sent as well, regardless of chunk size.
1069
1070 // Disable mod_deflate compression since it interferes with the output buffer set
1071 // by MW_SETUP_CALLBACK and can also cause the client to wait on deferred updates
1072 if ( function_exists( 'apache_setenv' ) ) {
1073 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1074 @apache_setenv( 'no-gzip', 1 );
1075 }
1076
1077 if (
1078 // "Content-Length" is used to prevent clients from waiting on deferred updates
1079 $this->postSendStrategy === self::DEFER_SET_LENGTH_AND_FLUSH &&
1080 // The HTTP response code clearly allows for a meaningful body
1081 in_array( http_response_code(), [ 200, 404 ], true ) &&
1082 // The queue of (post-send) deferred updates is non-empty
1083 DeferredUpdates::pendingUpdatesCount() &&
1084 // Any buffered output is not spread out accross multiple output buffers
1085 ob_get_level() <= 1 &&
1086 // It is not too late to set additional HTTP headers
1087 !headers_sent()
1088 ) {
1089 $response = $this->context->getRequest()->response();
1090
1091 $obStatus = ob_get_status();
1092 if ( !isset( $obStatus['name'] ) ) {
1093 // No output buffer is active
1094 $response->header( 'Content-Length: ' . strlen( $content ) );
1095 } elseif ( $obStatus['name'] === 'default output handler' ) {
1096 // Internal PHP "output_buffering" output buffer (note that the internal PHP
1097 // "zlib.output_compression" output buffer is named "zlib output compression")
1098 $response->header( 'Content-Length: ' . ( ob_get_length() + strlen( $content ) ) );
1099 }
1100
1101 // The MW_SETUP_CALLBACK output buffer ("MediaWiki\OutputHandler::handle") sets
1102 // "Content-Length" where applicable. Other output buffer types might not set this
1103 // header, and since they might mangle or compress the payload, it is not possible
1104 // to determine the final payload size here.
1105
1106 // Tell the client to immediately end the connection as soon as the response payload
1107 // has been read (informed by any "Content-Length" header). This prevents the client
1108 // from waiting on deferred updates.
1109 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection
1110 if ( ( $_SERVER['SERVER_PROTOCOL'] ?? '' ) === 'HTTP/1.1' ) {
1111 $response->header( 'Connection: close' );
1112 }
1113 }
1114
1115 // Print the content *after* adjusting HTTP headers and disabling mod_deflate since
1116 // calling "print" will send the output to the client if there is no output buffer or
1117 // if the output buffer chunk size is reached
1119 }
1120
1124 public function restInPeace() {
1125 // Either all DB and deferred updates should happen or none.
1126 // The latter should not be cancelled due to client disconnect.
1127 ignore_user_abort( true );
1128
1129 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
1130 // Assure deferred updates are not in the main transaction
1131 $lbFactory->commitPrimaryChanges( __METHOD__ );
1132
1133 // Loosen DB query expectations since the HTTP client is unblocked
1134 $trxProfiler = Profiler::instance()->getTransactionProfiler();
1135 $trxProfiler->redefineExpectations(
1136 $this->context->getRequest()->hasSafeMethod()
1137 ? $this->config->get( 'TrxProfilerLimits' )['PostSend-GET']
1138 : $this->config->get( 'TrxProfilerLimits' )['PostSend-POST'],
1139 __METHOD__
1140 );
1141
1142 // Do any deferred jobs; preferring to run them now if a client will not wait on them
1143 DeferredUpdates::doUpdates( 'run' );
1144
1145 // Log profiling data, e.g. in the database or UDP
1147
1148 // Commit and close up!
1149 $lbFactory->commitPrimaryChanges( __METHOD__ );
1150 $lbFactory->shutdown( $lbFactory::SHUTDOWN_NO_CHRONPROT );
1151
1152 wfDebug( "Request ended normally" );
1153 }
1154
1163 public static function emitBufferedStatsdData(
1164 IBufferingStatsdDataFactory $stats, Config $config
1165 ) {
1166 if ( $config->get( 'StatsdServer' ) && $stats->hasData() ) {
1167 try {
1168 $statsdServer = explode( ':', $config->get( 'StatsdServer' ), 2 );
1169 $statsdHost = $statsdServer[0];
1170 $statsdPort = $statsdServer[1] ?? 8125;
1171 $statsdSender = new SocketSender( $statsdHost, $statsdPort );
1172 $statsdClient = new SamplingStatsdClient( $statsdSender, true, false );
1173 $statsdClient->setSamplingRates( $config->get( 'StatsdSamplingRates' ) );
1174 $statsdClient->send( $stats->getData() );
1175
1176 $stats->clearData(); // empty buffer for the next round
1177 } catch ( Exception $e ) {
1178 MWExceptionHandler::logException( $e, MWExceptionHandler::CAUGHT_BY_ENTRYPOINT );
1179 }
1180 }
1181 }
1182
1186 private function triggerSyncJobs( $n ) {
1187 $scope = Profiler::instance()->getTransactionProfiler()->silenceForScope();
1188 $runner = MediaWikiServices::getInstance()->getJobRunner();
1189 $runner->run( [ 'maxJobs' => $n ] );
1190 ScopedCallback::consume( $scope );
1191 }
1192
1198 private function triggerAsyncJobs( $n, LoggerInterface $runJobsLogger ) {
1199 $services = MediaWikiServices::getInstance();
1200 // Do not send request if there are probably no jobs
1201 $group = $services->getJobQueueGroupFactory()->makeJobQueueGroup();
1202 if ( !$group->queuesHaveJobs( JobQueueGroup::TYPE_DEFAULT ) ) {
1203 return true;
1204 }
1205
1206 $query = [ 'title' => 'Special:RunJobs',
1207 'tasks' => 'jobs', 'maxjobs' => $n, 'sigexpiry' => time() + 5 ];
1208 $query['signature'] = SpecialRunJobs::getQuerySignature(
1209 $query, $this->config->get( 'SecretKey' ) );
1210
1211 $errno = $errstr = null;
1212 $info = wfParseUrl( $this->config->get( 'CanonicalServer' ) );
1213 $host = $info ? $info['host'] : null;
1214 $port = 80;
1215 if ( isset( $info['scheme'] ) && $info['scheme'] == 'https' ) {
1216 $host = "tls://" . $host;
1217 $port = 443;
1218 }
1219 if ( isset( $info['port'] ) ) {
1220 $port = $info['port'];
1221 }
1222
1223 Wikimedia\suppressWarnings();
1224 $sock = $host ? fsockopen(
1225 $host,
1226 $port,
1227 $errno,
1228 $errstr,
1229 // If it takes more than 100ms to connect to ourselves there is a problem...
1230 0.100
1231 ) : false;
1232 Wikimedia\restoreWarnings();
1233
1234 $invokedWithSuccess = true;
1235 if ( $sock ) {
1236 $special = $services->getSpecialPageFactory()->getPage( 'RunJobs' );
1237 $url = $special->getPageTitle()->getCanonicalURL( $query );
1238 $req = (
1239 "POST $url HTTP/1.1\r\n" .
1240 "Host: {$info['host']}\r\n" .
1241 "Connection: Close\r\n" .
1242 "Content-Length: 0\r\n\r\n"
1243 );
1244
1245 $runJobsLogger->info( "Running $n job(s) via '$url'" );
1246 // Send a cron API request to be performed in the background.
1247 // Give up if this takes too long to send (which should be rare).
1248 stream_set_timeout( $sock, 2 );
1249 $bytes = fwrite( $sock, $req );
1250 if ( $bytes !== strlen( $req ) ) {
1251 $invokedWithSuccess = false;
1252 $runJobsLogger->error( "Failed to start cron API (socket write error)" );
1253 } else {
1254 // Do not wait for the response (the script should handle client aborts).
1255 // Make sure that we don't close before that script reaches ignore_user_abort().
1256 $start = microtime( true );
1257 $status = fgets( $sock );
1258 $sec = microtime( true ) - $start;
1259 if ( !preg_match( '#^HTTP/\d\.\d 202 #', $status ) ) {
1260 $invokedWithSuccess = false;
1261 $runJobsLogger->error( "Failed to start cron API: received '$status' ($sec)" );
1262 }
1263 }
1264 fclose( $sock );
1265 } else {
1266 $invokedWithSuccess = false;
1267 $runJobsLogger->error( "Failed to start cron API (socket error $errno): $errstr" );
1268 }
1269
1270 return $invokedWithSuccess;
1271 }
1272}
const PROTO_HTTPS
Definition Defines.php:193
const NS_FILE
Definition Defines.php:70
const PROTO_CURRENT
Definition Defines.php:195
const NS_MAIN
Definition Defines.php:64
const PROTO_INTERNAL
Definition Defines.php:197
const NS_SPECIAL
Definition Defines.php:53
const NS_MEDIA
Definition Defines.php:52
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfParseUrl( $url)
parse_url() work-alike, but non-broken.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfLogProfilingData()
$wgTitle
Definition Setup.php:849
if(ini_get('mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition Setup.php:88
Actions are things which can be done to pages (edit, delete, rollback, etc).
Definition Action.php:43
Object-Oriented Ajax functions.
Class for viewing MediaWiki article and history.
Definition Article.php:49
Show an error page on a badtitle.
Similar to FauxRequest, but only fakes URL parameters and method (POST or GET) and use the base reque...
An error page which can definitely be safely rendered using the OutputPage.
report( $action=self::SEND_OUTPUT)
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.
Show an error that looks like an HTTP server error.
Definition HttpError.php:32
static getHTML(Throwable $e)
If $wgShowExceptionDetails is true, return a HTML message with a backtrace to the error,...
MediaWiki exception.
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
PSR-3 logger instance factory.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static getInstance()
Returns the global default instance of the top level service locator.
A StatusValue for permission errors.
parseTitle()
Parse the request to get the Title object.
Definition MediaWiki.php:78
doPostOutputShutdown()
This function does work that can be done after the user gets the HTTP response so they don't block on...
int $postSendStrategy
Class DEFER_* constant; how non-blocking post-response tasks should run.
Definition MediaWiki.php:47
triggerSyncJobs( $n)
const DEFER_CLI_MODE
Do not try to make post-send updates async (e.g.
Definition MediaWiki.php:54
static emitBufferedStatsdData(IBufferingStatsdDataFactory $stats, Config $config)
Send out any buffered statsd data according to sampling rules.
triggerAsyncJobs( $n, LoggerInterface $runJobsLogger)
initializeArticle()
Initialize the main Article object for "standard" actions (view, etc) Create an Article object for th...
Config $config
Definition MediaWiki.php:42
run()
Run the current MediaWiki instance; index.php just calls this.
maybeDoHttpsRedirect()
If the stars are suitably aligned, do an HTTP->HTTPS redirect.
getTitle()
Get the Title object that we'll be acting on, as specified in the WebRequest.
__construct(IContextSource $context=null)
Definition MediaWiki.php:59
const DEFER_SET_LENGTH_AND_FLUSH
Set Content-Length and call ob_end_flush()/flush() to make post-send updates async.
Definition MediaWiki.php:52
performAction(Article $article, Title $requestTitle)
Perform one of the "standard" actions.
getAction()
Returns the name of the action that will be executed.
string $action
Cache what action this request is.
Definition MediaWiki.php:45
restInPeace()
Ends this task peacefully.
tryNormaliseRedirect(Title $title)
Handle redirects for uncanonical title requests.
schedulePostSendJobs()
If enabled, after everything specific to this request is done, occasionally run jobs.
static preOutputCommit(IContextSource $context, callable $postCommitWork=null)
This function commits all DB and session changes as needed before the client can receive a response (...
outputResponsePayload( $content)
Print a response body to the current buffer (if there is one) or the server (otherwise)
IContextSource $context
Definition MediaWiki.php:40
performRequest()
Performs the request.
static getUrlDomainDistance( $url)
setDBProfilingAgent()
Add a comment to future SQL queries for easy SHOW PROCESSLIST interpretation.
doPreOutputCommit(callable $postCommitWork=null)
main()
Determine and send the response headers and body for this web request.
const DEFER_FASTCGI_FINISH_REQUEST
Call fastcgi_finish_request() to make post-send updates async.
Definition MediaWiki.php:50
shouldDoHttpRedirect()
Check if an HTTP->HTTPS redirect should be done.
Show an error when a user tries to do something they do not have the necessary permissions for.
Shortcut to construct a special page alias.
A statsd client that applies the sampling rate to the data items before sending them.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
static getQuerySignature(array $query, $secretKey)
Represents a title within MediaWiki.
Definition Title.php:48
Deferrable update that must run outside of any explicit LBFactory transaction round.
Special handling for file pages.
Provide a given client with protection against visible database lag.
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:69
Interface for configuration instances.
Definition Config.php:30
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
MediaWiki adaptation of StatsdDataFactory that provides buffering functionality.
hasData()
Check whether this data factory has any buffered data.
clearData()
Clear all buffered data from the factory.
getData()
Return the buffered data from the factory.
Interface for objects which can provide a MediaWiki context on request.
getConfig()
Get the site configuration.
$cache
Definition mcc.php:33
A helper class for throttling authentication attempts.
$content
Definition router.php:76
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42