MediaWiki REL1_35
MediaWiki.php
Go to the documentation of this file.
1<?php
23use Liuggio\StatsdClient\Sender\SocketSender;
24use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
27use Psr\Log\LoggerInterface;
28use Wikimedia\AtEase;
32
36class MediaWiki {
37 use ProtectedHookAccessorTrait;
38
40 private $context;
42 private $config;
43
45 private $action;
48
50 private const DEFER_FASTCGI_FINISH_REQUEST = 1;
52 private const DEFER_SET_LENGTH_AND_FLUSH = 2;
53
57 public function __construct( IContextSource $context = null ) {
58 $this->context = $context ?: RequestContext::getMain();
59 $this->config = $this->context->getConfig();
60 if ( function_exists( 'fastcgi_finish_request' ) ) {
61 $this->postSendStrategy = self::DEFER_FASTCGI_FINISH_REQUEST;
62 } else {
63 $this->postSendStrategy = self::DEFER_SET_LENGTH_AND_FLUSH;
64 }
65 }
66
73 private function parseTitle() {
74 $request = $this->context->getRequest();
75 $curid = $request->getInt( 'curid' );
76 $title = $request->getVal( 'title' );
77 $action = $request->getVal( 'action' );
78
79 if ( $request->getCheck( 'search' ) ) {
80 // Compatibility with old search URLs which didn't use Special:Search
81 // Just check for presence here, so blank requests still
82 // show the search page when using ugly URLs (T10054).
83 $ret = SpecialPage::getTitleFor( 'Search' );
84 } elseif ( $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 // Alias NS_MEDIA page URLs to NS_FILE...we only use NS_MEDIA
90 // in wikitext links to tell Parser to make a direct file link
91 if ( $ret !== null && $ret->getNamespace() == NS_MEDIA ) {
92 $ret = Title::makeTitle( NS_FILE, $ret->getDBkey() );
93 }
94 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
95 // Check variant links so that interwiki links don't have to worry
96 // about the possible different language variants
97 if (
98 $contLang->hasVariants() && $ret !== null && $ret->getArticleID() == 0
99 ) {
100 $contLang->findVariantLink( $title, $ret );
101 }
102 }
103
104 // If title is not provided, always allow oldid and diff to set the title.
105 // If title is provided, allow oldid and diff to override the title, unless
106 // we are talking about a special page which might use these parameters for
107 // other purposes.
108 if ( $ret === null || !$ret->isSpecialPage() ) {
109 // We can have urls with just ?diff=,?oldid= or even just ?diff=
110 $oldid = $request->getInt( 'oldid' );
111 $oldid = $oldid ?: $request->getInt( 'diff' );
112 // Allow oldid to override a changed or missing title
113 if ( $oldid ) {
114 $revRecord = MediaWikiServices::getInstance()
115 ->getRevisionLookup()
116 ->getRevisionById( $oldid );
117 if ( $revRecord ) {
118 $ret = Title::newFromLinkTarget(
119 $revRecord->getPageAsLinkTarget()
120 );
121 }
122 }
123 }
124
125 // Use the main page as default title if nothing else has been provided
126 if ( $ret === null
127 && strval( $title ) === ''
128 && !$request->getCheck( 'curid' )
129 && $action !== 'delete'
130 ) {
131 $ret = Title::newMainPage();
132 }
133
134 if ( $ret === null || ( $ret->getDBkey() == '' && !$ret->isExternal() ) ) {
135 // If we get here, we definitely don't have a valid title; throw an exception.
136 // Try to get detailed invalid title exception first, fall back to MalformedTitleException.
137 Title::newFromTextThrow( $title );
138 throw new MalformedTitleException( 'badtitletext', $title );
139 }
140
141 return $ret;
142 }
143
148 public function getTitle() {
149 if ( !$this->context->hasTitle() ) {
150 try {
151 $this->context->setTitle( $this->parseTitle() );
152 } catch ( MalformedTitleException $ex ) {
153 $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
154 }
155 }
156 return $this->context->getTitle();
157 }
158
164 public function getAction() : string {
165 if ( $this->action === null ) {
166 $this->action = Action::getActionName( $this->context );
167 }
168
169 return $this->action;
170 }
171
184 private function performRequest() {
185 global $wgTitle;
186
187 $request = $this->context->getRequest();
188 $requestTitle = $title = $this->context->getTitle();
189 $output = $this->context->getOutput();
190 $user = $this->context->getUser();
191
192 if ( $request->getVal( 'printable' ) === 'yes' ) {
193 $output->setPrintable();
194 }
195
196 $this->getHookRunner()->onBeforeInitialize( $title, null, $output, $user, $request, $this );
197
198 // Invalid titles. T23776: The interwikis must redirect even if the page name is empty.
199 if ( $title === null || ( $title->getDBkey() == '' && !$title->isExternal() )
200 || $title->isSpecial( 'Badtitle' )
201 ) {
202 $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
203 try {
204 $this->parseTitle();
205 } catch ( MalformedTitleException $ex ) {
206 throw new BadTitleError( $ex );
207 }
208 throw new BadTitleError();
209 }
210
211 // Check user's permissions to read this page.
212 // We have to check here to catch special pages etc.
213 // We will check again in Article::view().
214 $permErrors = $title->isSpecial( 'RunJobs' )
215 ? [] // relies on HMAC key signature alone
216 : MediaWikiServices::getInstance()->getPermissionManager()
217 ->getPermissionErrors( 'read', $user, $title );
218 if ( count( $permErrors ) ) {
219 // T34276: allowing the skin to generate output with $wgTitle or
220 // $this->context->title set to the input title would allow anonymous users to
221 // determine whether a page exists, potentially leaking private data. In fact, the
222 // curid and oldid request parameters would allow page titles to be enumerated even
223 // when they are not guessable. So we reset the title to Special:Badtitle before the
224 // permissions error is displayed.
225
226 // The skin mostly uses $this->context->getTitle() these days, but some extensions
227 // still use $wgTitle.
228 $badTitle = SpecialPage::getTitleFor( 'Badtitle' );
229 $this->context->setTitle( $badTitle );
230 $wgTitle = $badTitle;
231
232 throw new PermissionsError( 'read', $permErrors );
233 }
234
235 // Interwiki redirects
236 if ( $title->isExternal() ) {
237 $rdfrom = $request->getVal( 'rdfrom' );
238 if ( $rdfrom ) {
239 $url = $title->getFullURL( [ 'rdfrom' => $rdfrom ] );
240 } else {
241 $query = $request->getValues();
242 unset( $query['title'] );
243 $url = $title->getFullURL( $query );
244 }
245 // Check for a redirect loop
246 if ( !preg_match( '/^' . preg_quote( $this->config->get( 'Server' ), '/' ) . '/', $url )
247 && $title->isLocal()
248 ) {
249 // 301 so google et al report the target as the actual url.
250 $output->redirect( $url, 301 );
251 } else {
252 $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
253 try {
254 $this->parseTitle();
255 } catch ( MalformedTitleException $ex ) {
256 throw new BadTitleError( $ex );
257 }
258 throw new BadTitleError();
259 }
260 // Handle any other redirects.
261 // Redirect loops, titleless URL, $wgUsePathInfo URLs, and URLs with a variant
262 } elseif ( !$this->tryNormaliseRedirect( $title ) ) {
263 // Prevent information leak via Special:MyPage et al (T109724)
264 $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
265 if ( $title->isSpecialPage() ) {
266 $specialPage = $spFactory->getPage( $title->getDBkey() );
267 if ( $specialPage instanceof RedirectSpecialPage ) {
268 $specialPage->setContext( $this->context );
269 if ( $this->config->get( 'HideIdentifiableRedirects' )
270 && $specialPage->personallyIdentifiableTarget()
271 ) {
272 list( , $subpage ) = $spFactory->resolveAlias( $title->getDBkey() );
273 $target = $specialPage->getRedirect( $subpage );
274 // Target can also be true. We let that case fall through to normal processing.
275 if ( $target instanceof Title ) {
276 if ( $target->isExternal() ) {
277 // Handle interwiki redirects
278 $target = SpecialPage::getTitleFor(
279 'GoToInterwiki',
280 'force/' . $target->getPrefixedDBkey()
281 );
282 }
283
284 $query = $specialPage->getRedirectQuery( $subpage ) ?: [];
285 $request = new DerivativeRequest( $this->context->getRequest(), $query );
286 $request->setRequestURL( $this->context->getRequest()->getRequestURL() );
287 $this->context->setRequest( $request );
288 // Do not varnish cache these. May vary even for anons
289 $this->context->getOutput()->lowerCdnMaxage( 0 );
290 $this->context->setTitle( $target );
291 $wgTitle = $target;
292 // Reset action type cache. (Special pages have only view)
293 $this->action = null;
294 $title = $target;
295 $output->addJsConfigVars( [
296 'wgInternalRedirectTargetUrl' => $target->getLinkURL( $query ),
297 ] );
298 $output->addModules( 'mediawiki.action.view.redirect' );
299 }
300 }
301 }
302 }
303
304 // Special pages ($title may have changed since if statement above)
305 if ( $title->isSpecialPage() ) {
306 // Actions that need to be made when we have a special pages
307 $spFactory->executePath( $title, $this->context );
308 } else {
309 // ...otherwise treat it as an article view. The article
310 // may still be a wikipage redirect to another article or URL.
311 $article = $this->initializeArticle();
312 if ( is_object( $article ) ) {
313 $this->performAction( $article, $requestTitle );
314 } elseif ( is_string( $article ) ) {
315 $output->redirect( $article );
316 } else {
317 throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle()"
318 . " returned neither an object nor a URL" );
319 }
320 }
321 $output->considerCacheSettingsFinal();
322 }
323 }
324
347 private function tryNormaliseRedirect( Title $title ) {
348 $request = $this->context->getRequest();
349 $output = $this->context->getOutput();
350
351 if ( $request->getVal( 'action', 'view' ) != 'view'
352 || $request->wasPosted()
353 || ( $request->getCheck( 'title' )
354 && $title->getPrefixedDBkey() == $request->getVal( 'title' ) )
355 || count( $request->getValueNames( [ 'action', 'title' ] ) )
356 || !$this->getHookRunner()->onTestCanonicalRedirect( $request, $title, $output )
357 ) {
358 return false;
359 }
360
361 if ( $this->config->get( 'MainPageIsDomainRoot' ) && $request->getRequestURL() === '/' ) {
362 return false;
363 }
364
365 if ( $title->isSpecialPage() ) {
366 list( $name, $subpage ) = MediaWikiServices::getInstance()->getSpecialPageFactory()->
367 resolveAlias( $title->getDBkey() );
368 if ( $name ) {
369 $title = SpecialPage::getTitleFor( $name, $subpage );
370 }
371 }
372 // Redirect to canonical url, make it a 301 to allow caching
373 $targetUrl = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
374 if ( $targetUrl == $request->getFullRequestURL() ) {
375 $message = "Redirect loop detected!\n\n" .
376 "This means the wiki got confused about what page was " .
377 "requested; this sometimes happens when moving a wiki " .
378 "to a new server or changing the server configuration.\n\n";
379
380 if ( $this->config->get( 'UsePathInfo' ) ) {
381 $message .= "The wiki is trying to interpret the page " .
382 "title from the URL path portion (PATH_INFO), which " .
383 "sometimes fails depending on the web server. Try " .
384 "setting \"\$wgUsePathInfo = false;\" in your " .
385 "LocalSettings.php, or check that \$wgArticlePath " .
386 "is correct.";
387 } else {
388 $message .= "Your web server was detected as possibly not " .
389 "supporting URL path components (PATH_INFO) correctly; " .
390 "check your LocalSettings.php for a customized " .
391 "\$wgArticlePath setting and/or toggle \$wgUsePathInfo " .
392 "to true.";
393 }
394 throw new HttpError( 500, $message );
395 }
396 $output->setCdnMaxage( 1200 );
397 $output->redirect( $targetUrl, '301' );
398 return true;
399 }
400
407 private function initializeArticle() {
408 $title = $this->context->getTitle();
409 if ( $this->context->canUseWikiPage() ) {
410 // Reuse the WikiPage instance from context, as it may already have been initialized
411 // by an earlier this->getAction() call.
412 $page = $this->context->getWikiPage();
413 } else {
414 // This case should not happen, but just in case.
415 // @TODO: remove this or use an exception
416 $page = WikiPage::factory( $title );
417 $this->context->setWikiPage( $page );
418 wfWarn( "RequestContext::canUseWikiPage() returned false" );
419 }
420
421 // Make GUI wrapper for the WikiPage
422 $article = Article::newFromWikiPage( $page, $this->context );
423
424 // Skip some unnecessary code if the content model doesn't support redirects
425 if ( !MediaWikiServices::getInstance()
426 ->getContentHandlerFactory()
427 ->getContentHandler( $title->getContentModel() )
428 ->supportsRedirects()
429 ) {
430 return $article;
431 }
432
433 $request = $this->context->getRequest();
434
435 // Namespace might change when using redirects
436 // Check for redirects ...
437 $action = $request->getVal( 'action', 'view' );
438 $file = ( $page instanceof WikiFilePage ) ? $page->getFile() : null;
439 if ( ( $action == 'view' || $action == 'render' ) // ... for actions that show content
440 && !$request->getVal( 'oldid' ) // ... and are not old revisions
441 && !$request->getVal( 'diff' ) // ... and not when showing diff
442 && $request->getVal( 'redirect' ) != 'no' // ... unless explicitly told not to
443 // ... and the article is not a non-redirect image page with associated file
444 && !( is_object( $file ) && $file->exists() && !$file->getRedirected() )
445 ) {
446 // Give extensions a change to ignore/handle redirects as needed
447 $ignoreRedirect = $target = false;
448
449 $this->getHookRunner()->onInitializeArticleMaybeRedirect( $title, $request,
450 $ignoreRedirect, $target, $article );
451 $page = $article->getPage(); // reflect any hook changes
452
453 // Follow redirects only for... redirects.
454 // If $target is set, then a hook wanted to redirect.
455 if ( !$ignoreRedirect && ( $target || $page->isRedirect() ) ) {
456 // Is the target already set by an extension?
457 $target = $target ?: $page->followRedirect();
458 if ( is_string( $target ) && !$this->config->get( 'DisableHardRedirects' ) ) {
459 // we'll need to redirect
460 return $target;
461 }
462 if ( is_object( $target ) ) {
463 // Rewrite environment to redirected article
464 $rpage = WikiPage::factory( $target );
465 $rpage->loadPageData();
466 if ( $rpage->exists() || ( is_object( $file ) && !$file->isLocal() ) ) {
467 $rarticle = Article::newFromWikiPage( $rpage, $this->context );
468 $rarticle->setRedirectedFrom( $title );
469
470 $article = $rarticle;
471 $this->context->setTitle( $target );
472 $this->context->setWikiPage( $article->getPage() );
473 }
474 }
475 } else {
476 // Article may have been changed by hook
477 $this->context->setTitle( $article->getTitle() );
478 $this->context->setWikiPage( $article->getPage() );
479 }
480 }
481
482 return $article;
483 }
484
491 private function performAction( Article $article, Title $requestTitle ) {
492 $request = $this->context->getRequest();
493 $output = $this->context->getOutput();
494 $title = $this->context->getTitle();
495 $user = $this->context->getUser();
496
497 if ( !$this->getHookRunner()->onMediaWikiPerformAction(
498 $output, $article, $title, $user, $request, $this )
499 ) {
500 return;
501 }
502
503 $act = $this->getAction();
504 $action = Action::factory( $act, $article, $this->context );
505
506 if ( $action instanceof Action ) {
507 // Check read permissions
508 if ( $action->needsReadRights() && !$user->isAllowed( 'read' ) ) {
509 throw new PermissionsError( 'read' );
510 }
511
512 // Narrow DB query expectations for this HTTP request
513 $trxLimits = $this->config->get( 'TrxProfilerLimits' );
514 $trxProfiler = Profiler::instance()->getTransactionProfiler();
515 if ( $request->wasPosted() && !$action->doesWrites() ) {
516 $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
517 $request->markAsSafeRequest();
518 }
519
520 # Let CDN cache things if we can purge them.
521 if ( $this->config->get( 'UseCdn' ) &&
522 in_array(
523 // Use PROTO_INTERNAL because that's what getCdnUrls() uses
524 wfExpandUrl( $request->getRequestURL(), PROTO_INTERNAL ),
525 $requestTitle->getCdnUrls()
526 )
527 ) {
528 $output->setCdnMaxage( $this->config->get( 'CdnMaxAge' ) );
529 }
530
531 $action->show();
532 return;
533 }
534
535 // If we've not found out which action it is by now, it's unknown
536 $output->setStatusCode( 404 );
537 $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
538 }
539
543 public function run() {
544 try {
545 $this->setDBProfilingAgent();
546 try {
547 $this->main();
548 } catch ( ErrorPageError $e ) {
549 $out = $this->context->getOutput();
550 // TODO: Should ErrorPageError::report accept a OutputPage parameter?
552 $out->considerCacheSettingsFinal();
553
554 // T64091: while exceptions are convenient to bubble up GUI errors,
555 // they are not internal application faults. As with normal requests, this
556 // should commit, print the output, do deferred updates, jobs, and profiling.
557 $this->doPreOutputCommit();
558 $out->output(); // display the GUI error
559 }
560 } catch ( Exception $e ) {
561 $context = $this->context;
562 $action = $context->getRequest()->getVal( 'action', 'view' );
563 if (
564 $e instanceof DBConnectionError &&
565 $context->hasTitle() &&
566 $context->getTitle()->canExist() &&
567 in_array( $action, [ 'view', 'history' ], true ) &&
569 ) {
570 // Try to use any (even stale) file during outages...
571 $cache = new HTMLFileCache( $context->getTitle(), $action );
572 if ( $cache->isCached() ) {
573 $cache->loadFromFileCache( $context, HTMLFileCache::MODE_OUTAGE );
575 exit;
576 }
577 }
578 // GUI-ify and stash the page output in MediaWiki::doPreOutputCommit() while
579 // ChronologyProtector synchronizes DB positions or replicas across all datacenters.
580 MWExceptionHandler::handleException( $e, MWExceptionHandler::CAUGHT_BY_ENTRYPOINT );
581 } catch ( Throwable $e ) {
582 // Type errors and such: at least handle it now and clean up the LBFactory state
583 MWExceptionHandler::handleException( $e, MWExceptionHandler::CAUGHT_BY_ENTRYPOINT );
584 }
585
586 $this->doPostOutputShutdown();
587 }
588
592 private function setDBProfilingAgent() {
593 $services = MediaWikiServices::getInstance();
594 $name = $this->context->getUser()->getName();
595 $services->getDBLoadBalancerFactory()->setAgentName(
596 mb_strlen( $name ) > 15 ? mb_substr( $name, 0, 15 ) . '...' : $name
597 );
598 }
599
603 private function schedulePostSendJobs() {
604 $jobRunRate = $this->config->get( 'JobRunRate' );
605 if (
606 // Recursion guard
607 $this->getTitle()->isSpecial( 'RunJobs' ) ||
608 // Short circuit if there is nothing to do
609 ( $jobRunRate <= 0 || wfReadOnly() ) ||
610 // Avoid blocking the client on stock apache; see doPostOutputShutdown()
611 (
612 $this->context->getRequest()->getMethod() === 'HEAD' ||
613 $this->context->getRequest()->getHeader( 'If-Modified-Since' )
614 )
615 ) {
616 return;
617 }
618
619 if ( $jobRunRate < 1 ) {
620 $max = mt_getrandmax();
621 if ( mt_rand( 0, $max ) > $max * $jobRunRate ) {
622 return; // the higher the job run rate, the less likely we return here
623 }
624 $n = 1;
625 } else {
626 $n = intval( $jobRunRate );
627 }
628
629 // Note that DeferredUpdates will catch and log an errors (T88312)
630 DeferredUpdates::addUpdate( new TransactionRoundDefiningUpdate( function () use ( $n ) {
631 $logger = LoggerFactory::getInstance( 'runJobs' );
632 if ( $this->config->get( 'RunJobsAsync' ) ) {
633 // Send an HTTP request to the job RPC entry point if possible
634 $invokedWithSuccess = $this->triggerAsyncJobs( $n, $logger );
635 if ( !$invokedWithSuccess ) {
636 // Fall back to blocking on running the job(s)
637 $logger->warning( "Jobs switched to blocking; Special:RunJobs disabled" );
638 $this->triggerSyncJobs( $n );
639 }
640 } else {
641 $this->triggerSyncJobs( $n );
642 }
643 }, __METHOD__ ) );
644 }
645
651 public function doPreOutputCommit( callable $postCommitWork = null ) {
652 self::preOutputCommit( $this->context, $postCommitWork );
653 }
654
666 public static function preOutputCommit(
667 IContextSource $context, callable $postCommitWork = null
668 ) {
669 $config = $context->getConfig();
670 $request = $context->getRequest();
671 $output = $context->getOutput();
672 $services = MediaWikiServices::getInstance();
673 $lbFactory = $services->getDBLoadBalancerFactory();
674
675 // Try to make sure that all RDBMs, session, and other storage updates complete
676 ignore_user_abort( true );
677
678 // Commit all RDBMs changes from the main transaction round
679 $lbFactory->commitMasterChanges(
680 __METHOD__,
681 // Abort if any transaction was too big
682 [ 'maxWriteDuration' => $config->get( 'MaxUserDBWriteDuration' ) ]
683 );
684 wfDebug( __METHOD__ . ': primary transaction round committed' );
685
686 // Run updates that need to block the client or affect output (this is the last chance)
687 DeferredUpdates::doUpdates( 'run', DeferredUpdates::PRESEND );
688 wfDebug( __METHOD__ . ': pre-send deferred updates completed' );
689 // Persist the session to avoid race conditions on subsequent requests by the client
690 $request->getSession()->save(); // T214471
691 wfDebug( __METHOD__ . ': session changes committed' );
692
693 // Figure out whether to wait for DB replication now or to use some method that assures
694 // that subsequent requests by the client will use the DB replication positions written
695 // during the shutdown() call below; the later requires working around replication lag
696 // of the store containing DB replication positions (e.g. dynomite, mcrouter).
697 list( $flags, $strategy ) = self::getChronProtStrategy( $lbFactory, $output );
698 // Record ChronologyProtector positions for DBs affected in this request at this point
699 $cpIndex = null;
700 $cpClientId = null;
701 $lbFactory->shutdown( $flags, $postCommitWork, $cpIndex, $cpClientId );
702 wfDebug( __METHOD__ . ': LBFactory shutdown completed' );
703
704 $allowHeaders = !( $output->isDisabled() || headers_sent() );
705 if ( $cpIndex > 0 ) {
706 if ( $allowHeaders ) {
707 $now = time();
708 $expires = $now + ChronologyProtector::POSITION_COOKIE_TTL;
709 $options = [ 'prefix' => '' ];
710 $value = $lbFactory::makeCookieValueFromCPIndex( $cpIndex, $now, $cpClientId );
711 $request->response()->setCookie( 'cpPosIndex', $value, $expires, $options );
712 }
713
714 if ( $strategy === 'cookie+url' ) {
715 if ( $output->getRedirect() ) { // sanity
716 $safeUrl = $lbFactory->appendShutdownCPIndexAsQuery(
717 $output->getRedirect(),
718 $cpIndex
719 );
720 $output->redirect( $safeUrl );
721 } else {
722 MWExceptionHandler::logException(
723 new LogicException( "No redirect; cannot append cpPosIndex parameter." ),
724 MWExceptionHandler::CAUGHT_BY_ENTRYPOINT
725 );
726 }
727 }
728 }
729
730 if ( $allowHeaders ) {
731 // Set a cookie to tell all CDN edge nodes to "stick" the user to the DC that
732 // handles this POST request (e.g. the "master" data center). Also have the user
733 // briefly bypass CDN so ChronologyProtector works for cacheable URLs.
734 if ( $request->wasPosted() && $lbFactory->hasOrMadeRecentMasterChanges() ) {
735 $expires = time() + $config->get( 'DataCenterUpdateStickTTL' );
736 $options = [ 'prefix' => '' ];
737 $request->response()->setCookie( 'UseDC', 'master', $expires, $options );
738 $request->response()->setCookie( 'UseCDNCache', 'false', $expires, $options );
739 }
740
741 // Avoid letting a few seconds of replica DB lag cause a month of stale data.
742 // This logic is also intimately related to the value of $wgCdnReboundPurgeDelay.
743 if ( $lbFactory->laggedReplicaUsed() ) {
744 $maxAge = $config->get( 'CdnMaxageLagged' );
745 $output->lowerCdnMaxage( $maxAge );
746 $request->response()->header( "X-Database-Lagged: true" );
747 wfDebugLog( 'replication',
748 "Lagged DB used; CDN cache TTL limited to $maxAge seconds" );
749 }
750
751 // Avoid long-term cache pollution due to message cache rebuild timeouts (T133069)
752 if ( $services->getMessageCache()->isDisabled() ) {
753 $maxAge = $config->get( 'CdnMaxageSubstitute' );
754 $output->lowerCdnMaxage( $maxAge );
755 $request->response()->header( "X-Response-Substitute: true" );
756 }
757
758 if ( !$output->couldBePublicCached() || $output->haveCacheVaryCookies() ) {
759 // Autoblocks: If this user is autoblocked (and the cookie block feature is enabled
760 // for autoblocks), then set a cookie to track this block.
761 // This has to be done on all logged-in page loads (not just upon saving edits),
762 // because an autoblocked editor might not edit again from the same IP address.
763 //
764 // IP blocks: For anons, if their IP is blocked (and cookie block feature is enabled
765 // for IP blocks), we also want to set the cookie whenever it is safe to do.
766 // Basically from any url that are definitely not publicly cacheable (like viewing
767 // EditPage), or when the HTTP response is personalised for other reasons (e.g. viewing
768 // articles within the same browsing session after making an edit).
769 $user = $context->getUser();
770 $services->getBlockManager()
771 ->trackBlockWithCookie( $user, $request->response() );
772 }
773 }
774 }
775
781 private static function getChronProtStrategy( ILBFactory $lbFactory, OutputPage $output ) {
782 // Should the client return, their request should observe the new ChronologyProtector
783 // DB positions. This request might be on a foreign wiki domain, so synchronously update
784 // the DB positions in all datacenters to be safe. If this output is not a redirect,
785 // then OutputPage::output() will be relatively slow, meaning that running it in
786 // $postCommitWork should help mask the latency of those updates.
787 $flags = $lbFactory::SHUTDOWN_CHRONPROT_SYNC;
788 $strategy = 'cookie+sync';
789
790 $allowHeaders = !( $output->isDisabled() || headers_sent() );
791 if ( $output->getRedirect() && $lbFactory->hasOrMadeRecentMasterChanges( INF ) ) {
792 // OutputPage::output() will be fast, so $postCommitWork is useless for masking
793 // the latency of synchronously updating the DB positions in all datacenters.
794 // Try to make use of the time the client spends following redirects instead.
795 $domainDistance = self::getUrlDomainDistance( $output->getRedirect() );
796 if ( $domainDistance === 'local' && $allowHeaders ) {
797 $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
798 $strategy = 'cookie'; // use same-domain cookie and keep the URL uncluttered
799 } elseif ( $domainDistance === 'remote' ) {
800 $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
801 $strategy = 'cookie+url'; // cross-domain cookie might not work
802 }
803 }
804
805 return [ $flags, $strategy ];
806 }
807
812 private static function getUrlDomainDistance( $url ) {
813 $clusterWiki = WikiMap::getWikiFromUrl( $url );
814 if ( WikiMap::isCurrentWikiId( $clusterWiki ) ) {
815 return 'local'; // the current wiki
816 }
817 if ( $clusterWiki !== false ) {
818 return 'remote'; // another wiki in this cluster/farm
819 }
820
821 return 'external';
822 }
823
833 public function doPostOutputShutdown() {
834 // Record backend request timing
835 $timing = $this->context->getTiming();
836 $timing->mark( 'requestShutdown' );
837
838 // Perform the last synchronous operations...
839 try {
840 // Show visible profiling data if enabled (which cannot be post-send)
841 Profiler::instance()->logDataPageOutputOnly();
842 } catch ( Throwable $e ) {
843 // An error may already have been shown in run(), so just log it to be safe
844 MWExceptionHandler::logException( $e, MWExceptionHandler::CAUGHT_BY_ENTRYPOINT );
845 }
846
847 // Disable WebResponse setters for post-send processing (T191537).
848 WebResponse::disableForPostSend();
849
850 // Defer everything else if possible...
851 $callback = function () {
852 try {
853 $this->restInPeace();
854 } catch ( Throwable $e ) {
855 // If this is post-send, then displaying errors can cause broken HTML
856 MWExceptionHandler::rollbackMasterChangesAndLog(
857 $e,
858 MWExceptionHandler::CAUGHT_BY_ENTRYPOINT
859 );
860 }
861 };
862
863 if ( $this->postSendStrategy === self::DEFER_FASTCGI_FINISH_REQUEST ) {
864 fastcgi_finish_request();
865 $callback();
866 } else {
867 // Flush PHP and web server output buffers
868 if ( !$this->config->get( 'CommandLineMode' ) ) {
869 AtEase\AtEase::suppressWarnings();
870 if ( ob_get_status() ) {
871 ob_end_flush();
872 }
873 flush();
874 AtEase\AtEase::restoreWarnings();
875 }
876 $callback();
877 }
878 }
879
883 private function main() {
884 global $wgTitle;
885
886 $output = $this->context->getOutput();
887 $request = $this->context->getRequest();
888
889 // Send Ajax requests to the Ajax dispatcher.
890 if ( $request->getVal( 'action' ) === 'ajax' ) {
891 // Set a dummy title, because $wgTitle == null might break things
892 $title = Title::makeTitle( NS_SPECIAL, 'Badtitle/performing an AJAX call in '
893 . __METHOD__
894 );
895 $this->context->setTitle( $title );
897
898 $dispatcher = new AjaxDispatcher( $this->config );
899 $dispatcher->performAction( $this->context->getUser() );
900
901 return;
902 }
903
904 // Get title from request parameters,
905 // is set on the fly by parseTitle the first time.
906 $title = $this->getTitle();
907 $wgTitle = $title;
908
909 // Set DB query expectations for this HTTP request
910 $trxLimits = $this->config->get( 'TrxProfilerLimits' );
911 $trxProfiler = Profiler::instance()->getTransactionProfiler();
912 $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) );
913 if ( $request->hasSafeMethod() ) {
914 $trxProfiler->setExpectations( $trxLimits['GET'], __METHOD__ );
915 } else {
916 $trxProfiler->setExpectations( $trxLimits['POST'], __METHOD__ );
917 }
918
919 if ( $this->maybeDoHttpsRedirect() ) {
920 return;
921 }
922
923 if ( $title->canExist() && HTMLFileCache::useFileCache( $this->context ) ) {
924 // getAction() may trigger DB queries, so avoid eagerly initializing it if possible.
925 // This reduces the cost of requests that exit early due to tryNormaliseRedirect()
926 // or a MediaWikiPerformAction / BeforeInitialize hook handler.
927 $action = $this->getAction();
928 // Try low-level file cache hit
929 $cache = new HTMLFileCache( $title, $action );
930 if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
931 // Check incoming headers to see if client has this cached
932 $timestamp = $cache->cacheTimestamp();
933 if ( !$output->checkLastModified( $timestamp ) ) {
934 $cache->loadFromFileCache( $this->context );
935 }
936 // Do any stats increment/watchlist stuff, assuming user is viewing the
937 // latest revision (which should always be the case for file cache)
938 $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
939 // Tell OutputPage that output is taken care of
940 $output->disable();
941
942 return;
943 }
944 }
945
946 // Actually do the work of the request and build up any output
947 $this->performRequest();
948
949 // GUI-ify and stash the page output in MediaWiki::doPreOutputCommit() while
950 // ChronologyProtector synchronizes DB positions or replicas across all datacenters.
951 $buffer = null;
952 $outputWork = function () use ( $output, &$buffer ) {
953 if ( $buffer === null ) {
954 $buffer = $output->output( true );
955 }
956
957 return $buffer;
958 };
959
960 // Commit any changes in the current transaction round so that:
961 // a) the transaction is not rolled back after success output was already sent
962 // b) error output is not jumbled together with success output in the response
963 $this->doPreOutputCommit( $outputWork );
964 // If needed, push a deferred update to run jobs after the output is send
965 $this->schedulePostSendJobs();
966 // If no exceptions occurred then send the output since it is safe now
967 $this->outputResponsePayload( $outputWork() );
968 }
969
976 private function shouldDoHttpRedirect() {
977 $request = $this->context->getRequest();
978
979 // Don't redirect if we're already on HTTPS
980 if ( $request->getProtocol() !== 'http' ) {
981 return false;
982 }
983
984 $force = $this->config->get( 'ForceHTTPS' );
985
986 // Don't redirect if $wgServer is explicitly HTTP. We test for this here
987 // by checking whether wfExpandUrl() is able to force HTTPS.
988 if ( !preg_match( '#^https://#', wfExpandUrl( $request->getRequestURL(), PROTO_HTTPS ) ) ) {
989 if ( $force ) {
990 throw new RuntimeException( '$wgForceHTTPS is true but the server is not HTTPS' );
991 }
992 return false;
993 }
994
995 // Configured $wgForceHTTPS overrides the remaining conditions
996 if ( $force ) {
997 return true;
998 }
999
1000 // Check if HTTPS is required by the session or user preferences
1001 return $request->getSession()->shouldForceHTTPS() ||
1002 // Check the cookie manually, for paranoia
1003 $request->getCookie( 'forceHTTPS', '' ) ||
1004 // Avoid checking the user and groups unless it's enabled.
1005 (
1006 $this->context->getUser()->isLoggedIn()
1007 && $this->context->getUser()->requiresHTTPS()
1008 );
1009 }
1010
1020 private function maybeDoHttpsRedirect() {
1021 if ( !$this->shouldDoHttpRedirect() ) {
1022 return false;
1023 }
1024
1025 $request = $this->context->getRequest();
1026 $oldUrl = $request->getFullRequestURL();
1027 $redirUrl = preg_replace( '#^http://#', 'https://', $oldUrl );
1028
1029 // ATTENTION: This hook is likely to be removed soon due to overall design of the system.
1030 if ( !$this->getHookRunner()->onBeforeHttpsRedirect( $this->context, $redirUrl ) ) {
1031 return false;
1032 }
1033
1034 if ( $request->wasPosted() ) {
1035 // This is weird and we'd hope it almost never happens. This
1036 // means that a POST came in via HTTP and policy requires us
1037 // redirecting to HTTPS. It's likely such a request is going
1038 // to fail due to post data being lost, but let's try anyway
1039 // and just log the instance.
1040
1041 // @todo FIXME: See if we could issue a 307 or 308 here, need
1042 // to see how clients (automated & browser) behave when we do
1043 wfDebugLog( 'RedirectedPosts', "Redirected from HTTP to HTTPS: $oldUrl" );
1044 }
1045 // Setup dummy Title, otherwise OutputPage::redirect will fail
1046 $title = Title::newFromText( 'REDIR', NS_MAIN );
1047 $this->context->setTitle( $title );
1048 // Since we only do this redir to change proto, always send a vary header
1049 $output = $this->context->getOutput();
1050 $output->addVaryHeader( 'X-Forwarded-Proto' );
1051 $output->redirect( $redirUrl );
1052 $output->output();
1053
1054 return true;
1055 }
1056
1062 private function outputResponsePayload( $content ) {
1063 // @TODO: respect DEFER_SET_LENGTH_AND_FLUSH
1065 }
1066
1070 public function restInPeace() {
1071 // Either all DB and deferred updates should happen or none.
1072 // The latter should not be cancelled due to client disconnect.
1073 ignore_user_abort( true );
1074
1075 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
1076 // Assure deferred updates are not in the main transaction
1077 $lbFactory->commitMasterChanges( __METHOD__ );
1078
1079 // Loosen DB query expectations since the HTTP client is unblocked
1080 $trxProfiler = Profiler::instance()->getTransactionProfiler();
1081 $trxProfiler->redefineExpectations(
1082 $this->context->getRequest()->hasSafeMethod()
1083 ? $this->config->get( 'TrxProfilerLimits' )['PostSend-GET']
1084 : $this->config->get( 'TrxProfilerLimits' )['PostSend-POST'],
1085 __METHOD__
1086 );
1087
1088 // Do any deferred jobs; preferring to run them now if a client will not wait on them
1089 DeferredUpdates::doUpdates( 'run' );
1090
1091 // Log profiling data, e.g. in the database or UDP
1093
1094 // Commit and close up!
1095 $lbFactory->commitMasterChanges( __METHOD__ );
1096 $lbFactory->shutdown( $lbFactory::SHUTDOWN_NO_CHRONPROT );
1097
1098 wfDebug( "Request ended normally" );
1099 }
1100
1109 public static function emitBufferedStatsdData(
1110 IBufferingStatsdDataFactory $stats, Config $config
1111 ) {
1112 if ( $config->get( 'StatsdServer' ) && $stats->hasData() ) {
1113 try {
1114 $statsdServer = explode( ':', $config->get( 'StatsdServer' ), 2 );
1115 $statsdHost = $statsdServer[0];
1116 $statsdPort = $statsdServer[1] ?? 8125;
1117 $statsdSender = new SocketSender( $statsdHost, $statsdPort );
1118 $statsdClient = new SamplingStatsdClient( $statsdSender, true, false );
1119 $statsdClient->setSamplingRates( $config->get( 'StatsdSamplingRates' ) );
1120 $statsdClient->send( $stats->getData() );
1121
1122 $stats->clearData(); // empty buffer for the next round
1123 } catch ( Exception $e ) {
1124 MWExceptionHandler::logException( $e, MWExceptionHandler::CAUGHT_BY_ENTRYPOINT );
1125 }
1126 }
1127 }
1128
1135 public function triggerJobs() {
1136 $jobRunRate = $this->config->get( 'JobRunRate' );
1137 if ( $this->getTitle()->isSpecial( 'RunJobs' ) ) {
1138 return; // recursion guard
1139 } elseif ( $jobRunRate <= 0 || wfReadOnly() ) {
1140 return;
1141 }
1142
1143 if ( $jobRunRate < 1 ) {
1144 $max = mt_getrandmax();
1145 if ( mt_rand( 0, $max ) > $max * $jobRunRate ) {
1146 return; // the higher the job run rate, the less likely we return here
1147 }
1148 $n = 1;
1149 } else {
1150 $n = intval( $jobRunRate );
1151 }
1152
1153 $logger = LoggerFactory::getInstance( 'runJobs' );
1154
1155 try {
1156 if ( $this->config->get( 'RunJobsAsync' ) ) {
1157 // Send an HTTP request to the job RPC entry point if possible
1158 $invokedWithSuccess = $this->triggerAsyncJobs( $n, $logger );
1159 if ( !$invokedWithSuccess ) {
1160 // Fall back to blocking on running the job(s)
1161 $logger->warning( "Jobs switched to blocking; Special:RunJobs disabled" );
1162 $this->triggerSyncJobs( $n );
1163 }
1164 } else {
1165 $this->triggerSyncJobs( $n );
1166 }
1167 } catch ( JobQueueError $e ) {
1168 // Do not make the site unavailable (T88312)
1169 MWExceptionHandler::logException( $e, MWExceptionHandler::CAUGHT_BY_ENTRYPOINT );
1170 }
1171 }
1172
1176 private function triggerSyncJobs( $n ) {
1177 $trxProfiler = Profiler::instance()->getTransactionProfiler();
1178 $old = $trxProfiler->setSilenced( true );
1179 try {
1180 $runner = MediaWikiServices::getInstance()->getJobRunner();
1181 $runner->run( [ 'maxJobs' => $n ] );
1182 } finally {
1183 $trxProfiler->setSilenced( $old );
1184 }
1185 }
1186
1192 private function triggerAsyncJobs( $n, LoggerInterface $runJobsLogger ) {
1193 // Do not send request if there are probably no jobs
1194 $group = JobQueueGroup::singleton();
1195 if ( !$group->queuesHaveJobs( JobQueueGroup::TYPE_DEFAULT ) ) {
1196 return true;
1197 }
1198
1199 $query = [ 'title' => 'Special:RunJobs',
1200 'tasks' => 'jobs', 'maxjobs' => $n, 'sigexpiry' => time() + 5 ];
1201 $query['signature'] = SpecialRunJobs::getQuerySignature(
1202 $query, $this->config->get( 'SecretKey' ) );
1203
1204 $errno = $errstr = null;
1205 $info = wfParseUrl( $this->config->get( 'CanonicalServer' ) );
1206 $host = $info ? $info['host'] : null;
1207 $port = 80;
1208 if ( isset( $info['scheme'] ) && $info['scheme'] == 'https' ) {
1209 $host = "tls://" . $host;
1210 $port = 443;
1211 }
1212 if ( isset( $info['port'] ) ) {
1213 $port = $info['port'];
1214 }
1215
1216 Wikimedia\suppressWarnings();
1217 $sock = $host ? fsockopen(
1218 $host,
1219 $port,
1220 $errno,
1221 $errstr,
1222 // If it takes more than 100ms to connect to ourselves there is a problem...
1223 0.100
1224 ) : false;
1225 Wikimedia\restoreWarnings();
1226
1227 $invokedWithSuccess = true;
1228 if ( $sock ) {
1229 $special = MediaWikiServices::getInstance()->getSpecialPageFactory()->
1230 getPage( 'RunJobs' );
1231 $url = $special->getPageTitle()->getCanonicalURL( $query );
1232 $req = (
1233 "POST $url HTTP/1.1\r\n" .
1234 "Host: {$info['host']}\r\n" .
1235 "Connection: Close\r\n" .
1236 "Content-Length: 0\r\n\r\n"
1237 );
1238
1239 $runJobsLogger->info( "Running $n job(s) via '$url'" );
1240 // Send a cron API request to be performed in the background.
1241 // Give up if this takes too long to send (which should be rare).
1242 stream_set_timeout( $sock, 2 );
1243 $bytes = fwrite( $sock, $req );
1244 if ( $bytes !== strlen( $req ) ) {
1245 $invokedWithSuccess = false;
1246 $runJobsLogger->error( "Failed to start cron API (socket write error)" );
1247 } else {
1248 // Do not wait for the response (the script should handle client aborts).
1249 // Make sure that we don't close before that script reaches ignore_user_abort().
1250 $start = microtime( true );
1251 $status = fgets( $sock );
1252 $sec = microtime( true ) - $start;
1253 if ( !preg_match( '#^HTTP/\d\.\d 202 #', $status ) ) {
1254 $invokedWithSuccess = false;
1255 $runJobsLogger->error( "Failed to start cron API: received '$status' ($sec)" );
1256 }
1257 }
1258 fclose( $sock );
1259 } else {
1260 $invokedWithSuccess = false;
1261 $runJobsLogger->error( "Failed to start cron API (socket error $errno): $errstr" );
1262 }
1263
1264 return $invokedWithSuccess;
1265 }
1266}
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:799
if(ini_get('mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition Setup.php:85
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:46
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)
Stable to override.
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.
parseTitle()
Parse the request to get the Title object.
Definition MediaWiki.php:73
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)
static emitBufferedStatsdData(IBufferingStatsdDataFactory $stats, Config $config)
Send out any buffered statsd data according to sampling rules.
static getChronProtStrategy(ILBFactory $lbFactory, OutputPage $output)
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:57
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 the actual output to the buffer (or webserver if there is none)
IContextSource $context
Definition MediaWiki.php:40
triggerJobs()
Potentially open a socket and sent an HTTP request back to the server to run a specified number of jo...
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.
shouldDoHttpRedirect()
Check if an HTTP->HTTPS redirect should be done.
This is one of the Core classes and should be read at least once by any new developers.
getRedirect()
Get the URL to redirect to, or an empty string if not redirect URL set.
isDisabled()
Return whether the output will be completely disabled.
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:42
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition Title.php:3616
Deferrable update that must run outside of any explicit LBFactory transaction round.
Special handling for file pages.
Helper class for mitigating DB replication lag in order to provide "session consistency".
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:64
const PROTO_HTTPS
Definition Defines.php:210
const NS_FILE
Definition Defines.php:76
const PROTO_CURRENT
Definition Defines.php:212
const NS_MAIN
Definition Defines.php:70
const PROTO_INTERNAL
Definition Defines.php:214
const NS_SPECIAL
Definition Defines.php:59
const NS_MEDIA
Definition Defines.php:58
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.
An interface for generating database load balancers.
hasOrMadeRecentMasterChanges( $age=null)
Determine if any master connection has pending/written changes from this request.
$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