MediaWiki  master
MediaWiki.php
Go to the documentation of this file.
1 <?php
23 use Liuggio\StatsdClient\Sender\SocketSender;
24 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
28 use Psr\Log\LoggerInterface;
31 use Wikimedia\ScopedCallback;
32 
36 class 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;
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
97  $services = MediaWikiServices::getInstance();
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 ) {
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.
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  // Try to use request context wiki page, as there
419  // is already data from db saved in per process
420  // cache there from this->getAction() call.
421  $page = $this->context->getWikiPage();
422  } else {
423  // This case should not happen, but just in case.
424  // @TODO: remove this or use an exception
425  $page = $services->getWikiPageFactory()->newFromTitle( $title );
426  $this->context->setWikiPage( $page );
427  wfWarn( "RequestContext::canUseWikiPage() returned false" );
428  }
429 
430  // Make GUI wrapper for the WikiPage
431  $article = Article::newFromWikiPage( $page, $this->context );
432 
433  // Skip some unnecessary code if the content model doesn't support redirects
434  if ( !$services->getContentHandlerFactory()
435  ->getContentHandler( $title->getContentModel() )
436  ->supportsRedirects()
437  ) {
438  return $article;
439  }
440 
441  $request = $this->context->getRequest();
442 
443  // Namespace might change when using redirects
444  // Check for redirects ...
445  $action = $request->getRawVal( 'action', 'view' );
446  $file = ( $page instanceof WikiFilePage ) ? $page->getFile() : null;
447  if ( ( $action == 'view' || $action == 'render' ) // ... for actions that show content
448  && !$request->getCheck( 'oldid' ) // ... and are not old revisions
449  && !$request->getCheck( 'diff' ) // ... and not when showing diff
450  && $request->getRawVal( 'redirect' ) !== 'no' // ... unless explicitly told not to
451  // ... and the article is not a non-redirect image page with associated file
452  && !( is_object( $file ) && $file->exists() && !$file->getRedirected() )
453  ) {
454  // Give extensions a change to ignore/handle redirects as needed
455  $ignoreRedirect = $target = false;
456 
457  $this->getHookRunner()->onInitializeArticleMaybeRedirect( $title, $request,
458  $ignoreRedirect, $target, $article );
459  $page = $article->getPage(); // reflect any hook changes
460 
461  // Follow redirects only for... redirects.
462  // If $target is set, then a hook wanted to redirect.
463  if ( !$ignoreRedirect && ( $target || $page->isRedirect() ) ) {
464  // Is the target already set by an extension?
465  $target = $target ?: $page->followRedirect();
466  if ( is_string( $target ) && !$this->config->get( 'DisableHardRedirects' ) ) {
467  // we'll need to redirect
468  return $target;
469  }
470  if ( is_object( $target ) ) {
471  // Rewrite environment to redirected article
472  $rpage = $services->getWikiPageFactory()->newFromTitle( $target );
473  $rpage->loadPageData();
474  if ( $rpage->exists() || ( is_object( $file ) && !$file->isLocal() ) ) {
475  $rarticle = Article::newFromWikiPage( $rpage, $this->context );
476  $rarticle->setRedirectedFrom( $title );
477 
478  $article = $rarticle;
479  $this->context->setTitle( $target );
480  $this->context->setWikiPage( $article->getPage() );
481  }
482  }
483  } else {
484  // Article may have been changed by hook
485  $this->context->setTitle( $article->getTitle() );
486  $this->context->setWikiPage( $article->getPage() );
487  }
488  }
489 
490  return $article;
491  }
492 
499  private function performAction( Article $article, Title $requestTitle ) {
500  $request = $this->context->getRequest();
501  $output = $this->context->getOutput();
502  $title = $this->context->getTitle();
503  $user = $this->context->getUser();
504  $services = MediaWikiServices::getInstance();
505 
506  if ( !$this->getHookRunner()->onMediaWikiPerformAction(
507  $output, $article, $title, $user, $request, $this )
508  ) {
509  return;
510  }
511 
512  $t = microtime( true );
513  $actionName = $this->getAction();
514  $action = Action::factory( $actionName, $article, $this->context );
515 
516  if ( $action instanceof Action ) {
517  // Check read permissions
518  if ( $action->needsReadRights() && !$user->isAllowed( 'read' ) ) {
519  throw new PermissionsError( 'read' );
520  }
521 
522  // Narrow DB query expectations for this HTTP request
523  $trxLimits = $this->config->get( 'TrxProfilerLimits' );
524  $trxProfiler = Profiler::instance()->getTransactionProfiler();
525  if ( $request->wasPosted() && !$action->doesWrites() ) {
526  $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
527  $request->markAsSafeRequest();
528  }
529 
530  # Let CDN cache things if we can purge them.
531  if ( $this->config->get( 'UseCdn' ) ) {
532  $htmlCacheUpdater = $services->getHtmlCacheUpdater();
533  if ( in_array(
534  // Use PROTO_INTERNAL because that's what HtmlCacheUpdater::getUrls() uses
535  wfExpandUrl( $request->getRequestURL(), PROTO_INTERNAL ),
536  $htmlCacheUpdater->getUrls( $requestTitle )
537  )
538  ) {
539  $output->setCdnMaxage( $this->config->get( 'CdnMaxAge' ) );
540  }
541  }
542 
543  $action->show();
544 
545  $runTime = microtime( true ) - $t;
546  $services->getStatsdDataFactory()->timing(
547  'action.' . strtr( $actionName, '.', '_' ) . '.executeTiming',
548  1000 * $runTime
549  );
550  return;
551  }
552 
553  // If we've not found out which action it is by now, it's unknown
554  $output->setStatusCode( 404 );
555  $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
556  }
557 
561  public function run() {
562  try {
563  $this->main();
564  } catch ( Exception $e ) {
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...
576  if ( $cache->isCached() ) {
577  $cache->loadFromFileCache( $context, HTMLFileCache::MODE_OUTAGE );
578  print MWExceptionRenderer::getHTML( $e );
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 schedulePostSendJobs() {
595  $jobRunRate = $this->config->get( 'JobRunRate' );
596  if (
597  // Recursion guard
598  $this->getTitle()->isSpecial( 'RunJobs' ) ||
599  // Short circuit if there is nothing to do
600  ( $jobRunRate <= 0 || wfReadOnly() ) ||
601  // Avoid blocking the client on stock apache; see doPostOutputShutdown()
602  (
603  $this->context->getRequest()->getMethod() === 'HEAD' ||
604  $this->context->getRequest()->getHeader( 'If-Modified-Since' )
605  )
606  ) {
607  return;
608  }
609 
610  if ( $jobRunRate < 1 ) {
611  $max = mt_getrandmax();
612  if ( mt_rand( 0, $max ) > $max * $jobRunRate ) {
613  return; // the higher the job run rate, the less likely we return here
614  }
615  $n = 1;
616  } else {
617  $n = intval( $jobRunRate );
618  }
619 
620  if ( wfReadOnly() ) {
621  return;
622  }
623 
624  // Note that DeferredUpdates will catch and log any errors (T88312)
625  DeferredUpdates::addUpdate( new TransactionRoundDefiningUpdate( function () use ( $n ) {
626  $logger = LoggerFactory::getInstance( 'runJobs' );
627  if ( $this->config->get( 'RunJobsAsync' ) ) {
628  // Send an HTTP request to the job RPC entry point if possible
629  $invokedWithSuccess = $this->triggerAsyncJobs( $n, $logger );
630  if ( !$invokedWithSuccess ) {
631  // Fall back to blocking on running the job(s)
632  $logger->warning( "Jobs switched to blocking; Special:RunJobs disabled" );
633  $this->triggerSyncJobs( $n );
634  }
635  } else {
636  $this->triggerSyncJobs( $n );
637  }
638  }, __METHOD__ ) );
639  }
640 
646  public function doPreOutputCommit( callable $postCommitWork = null ) {
647  self::preOutputCommit( $this->context, $postCommitWork );
648  }
649 
661  public static function preOutputCommit(
662  IContextSource $context, callable $postCommitWork = null
663  ) {
665  $request = $context->getRequest();
666  $output = $context->getOutput();
667  $services = MediaWikiServices::getInstance();
668  $lbFactory = $services->getDBLoadBalancerFactory();
669 
670  // Try to make sure that all RDBMs, session, and other storage updates complete
671  ignore_user_abort( true );
672 
673  // Commit all RDBMs changes from the main transaction round
674  $lbFactory->commitPrimaryChanges(
675  __METHOD__,
676  // Abort if any transaction was too big
677  [ 'maxWriteDuration' => $config->get( 'MaxUserDBWriteDuration' ) ]
678  );
679  wfDebug( __METHOD__ . ': primary transaction round committed' );
680 
681  // Run updates that need to block the client or affect output (this is the last chance)
682  DeferredUpdates::doUpdates( 'run', DeferredUpdates::PRESEND );
683  wfDebug( __METHOD__ . ': pre-send deferred updates completed' );
684 
685  // Persist the session to avoid race conditions on subsequent requests by the client
686  $request->getSession()->save(); // T214471
687  wfDebug( __METHOD__ . ': session changes committed' );
688 
689  // Subsequent requests by the client should see the DB replication positions written
690  // during the shutdown() call below, even if the position store itself has asynchronous
691  // replication. Setting the cpPosIndex cookie is normally enough. However, this might not
692  // work for cross-domain redirects to foreign wikis, so set the ?cpPoxIndex in that case.
693  $isCrossWikiRedirect = (
694  $output->getRedirect() &&
695  $lbFactory->hasOrMadeRecentPrimaryChanges( INF ) &&
696  self::getUrlDomainDistance( $output->getRedirect() ) === 'remote'
697  );
698 
699  // Persist replication positions for DBs modified by this request (at this point).
700  // These help provide "session consistency" for the client on their next requests.
701  $cpIndex = null;
702  $cpClientId = null;
703  $lbFactory->shutdown(
704  $lbFactory::SHUTDOWN_NORMAL,
705  $postCommitWork,
706  $cpIndex,
707  $cpClientId
708  );
709  $now = time();
710 
711  $allowHeaders = !( $output->isDisabled() || headers_sent() );
712 
713  if ( $cpIndex > 0 ) {
714  if ( $allowHeaders ) {
715  $expires = $now + ChronologyProtector::POSITION_COOKIE_TTL;
716  $options = [ 'prefix' => '' ];
717  $value = $lbFactory::makeCookieValueFromCPIndex( $cpIndex, $now, $cpClientId );
718  $request->response()->setCookie( 'cpPosIndex', $value, $expires, $options );
719  }
720 
721  if ( $isCrossWikiRedirect ) {
722  if ( $output->getRedirect() ) {
723  $safeUrl = $lbFactory->appendShutdownCPIndexAsQuery(
724  $output->getRedirect(),
725  $cpIndex
726  );
727  $output->redirect( $safeUrl );
728  } else {
730  new LogicException( "No redirect; cannot append cpPosIndex parameter." ),
731  MWExceptionHandler::CAUGHT_BY_ENTRYPOINT
732  );
733  }
734  }
735  }
736 
737  if ( $allowHeaders ) {
738  // Set a cookie to tell all CDN edge nodes to "stick" the user to the DC that
739  // handles this POST request (e.g. the "primary" data center). Also have the user
740  // briefly bypass CDN so ChronologyProtector works for cacheable URLs.
741  if ( $request->wasPosted() && $lbFactory->hasOrMadeRecentPrimaryChanges() ) {
742  $expires = $now + max(
743  ChronologyProtector::POSITION_COOKIE_TTL,
744  $config->get( 'DataCenterUpdateStickTTL' )
745  );
746  $options = [ 'prefix' => '' ];
747  $request->response()->setCookie( 'UseDC', 'master', $expires, $options );
748  $request->response()->setCookie( 'UseCDNCache', 'false', $expires, $options );
749  }
750 
751  // Avoid letting a few seconds of replica DB lag cause a month of stale data.
752  // This logic is also intimately related to the value of $wgCdnReboundPurgeDelay.
753  if ( $lbFactory->laggedReplicaUsed() ) {
754  $maxAge = $config->get( 'CdnMaxageLagged' );
755  $output->lowerCdnMaxage( $maxAge );
756  $request->response()->header( "X-Database-Lagged: true" );
757  wfDebugLog( 'replication',
758  "Lagged DB used; CDN cache TTL limited to $maxAge seconds" );
759  }
760 
761  // Avoid long-term cache pollution due to message cache rebuild timeouts (T133069)
762  if ( $services->getMessageCache()->isDisabled() ) {
763  $maxAge = $config->get( 'CdnMaxageSubstitute' );
764  $output->lowerCdnMaxage( $maxAge );
765  $request->response()->header( "X-Response-Substitute: true" );
766  }
767 
768  if ( !$output->couldBePublicCached() || $output->haveCacheVaryCookies() ) {
769  // Autoblocks: If this user is autoblocked (and the cookie block feature is enabled
770  // for autoblocks), then set a cookie to track this block.
771  // This has to be done on all logged-in page loads (not just upon saving edits),
772  // because an autoblocked editor might not edit again from the same IP address.
773  //
774  // IP blocks: For anons, if their IP is blocked (and cookie block feature is enabled
775  // for IP blocks), we also want to set the cookie whenever it is safe to do.
776  // Basically from any url that are definitely not publicly cacheable (like viewing
777  // EditPage), or when the HTTP response is personalised for other reasons (e.g. viewing
778  // articles within the same browsing session after making an edit).
779  $user = $context->getUser();
780  $services->getBlockManager()
781  ->trackBlockWithCookie( $user, $request->response() );
782  }
783  }
784  }
785 
790  private static function getUrlDomainDistance( $url ) {
791  $clusterWiki = WikiMap::getWikiFromUrl( $url );
792  if ( WikiMap::isCurrentWikiId( $clusterWiki ) ) {
793  return 'local'; // the current wiki
794  }
795  if ( $clusterWiki !== false ) {
796  return 'remote'; // another wiki in this cluster/farm
797  }
798 
799  return 'external';
800  }
801 
811  public function doPostOutputShutdown() {
812  // Record backend request timing
813  $timing = $this->context->getTiming();
814  $timing->mark( 'requestShutdown' );
815 
816  // Defer everything else if possible...
817  if ( $this->postSendStrategy === self::DEFER_FASTCGI_FINISH_REQUEST ) {
818  // Flush the output to the client, continue processing, and avoid further output
819  fastcgi_finish_request();
820  } elseif ( $this->postSendStrategy === self::DEFER_SET_LENGTH_AND_FLUSH ) {
821  // Flush the output to the client, continue processing, and avoid further output
822  if ( ob_get_level() ) {
823  // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
824  @ob_end_flush();
825  }
826  // Flush the web server output buffer to the client/proxy if possible
827  // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
828  @flush();
829  }
830 
831  // Since the headers and output where already flushed, disable WebResponse setters
832  // during post-send processing to warnings and unexpected behavior (T191537)
834  // Run post-send updates while preventing further output...
835  ob_start( static function () {
836  return ''; // do not output uncaught exceptions
837  } );
838  try {
839  $this->restInPeace();
840  } catch ( Throwable $e ) {
842  $e,
843  MWExceptionHandler::CAUGHT_BY_ENTRYPOINT
844  );
845  }
846  $length = ob_get_length();
847  if ( $length > 0 ) {
848  trigger_error( __METHOD__ . ": suppressed $length byte(s)", E_USER_NOTICE );
849  }
850  ob_end_clean();
851  }
852 
856  private function main() {
857  global $wgTitle;
858 
859  $output = $this->context->getOutput();
860  $request = $this->context->getRequest();
861 
862  // Get title from request parameters,
863  // is set on the fly by parseTitle the first time.
864  $title = $this->getTitle();
865  $action = $this->getAction();
866  $wgTitle = $title;
867 
868  // Set DB query expectations for this HTTP request
869  $trxLimits = $this->config->get( 'TrxProfilerLimits' );
870  $trxProfiler = Profiler::instance()->getTransactionProfiler();
871  $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) );
872  if ( $request->hasSafeMethod() ) {
873  $trxProfiler->setExpectations( $trxLimits['GET'], __METHOD__ );
874  } else {
875  $trxProfiler->setExpectations( $trxLimits['POST'], __METHOD__ );
876  }
877 
878  if ( $this->maybeDoHttpsRedirect() ) {
879  return;
880  }
881 
882  if ( $title->canExist() && HTMLFileCache::useFileCache( $this->context ) ) {
883  // Try low-level file cache hit
885  if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
886  // Check incoming headers to see if client has this cached
887  $timestamp = $cache->cacheTimestamp();
888  if ( !$output->checkLastModified( $timestamp ) ) {
889  $cache->loadFromFileCache( $this->context );
890  }
891  // Do any stats increment/watchlist stuff, assuming user is viewing the
892  // latest revision (which should always be the case for file cache)
893  $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
894  // Tell OutputPage that output is taken care of
895  $output->disable();
896 
897  return;
898  }
899  }
900 
901  try {
902  // Actually do the work of the request and build up any output
903  $this->performRequest();
904  } catch ( ErrorPageError $e ) {
905  // TODO: Should ErrorPageError::report accept a OutputPage parameter?
907  $output->considerCacheSettingsFinal();
908  // T64091: while exceptions are convenient to bubble up GUI errors,
909  // they are not internal application faults. As with normal requests, this
910  // should commit, print the output, do deferred updates, jobs, and profiling.
911  }
912 
913  // GUI-ify and stash the page output in MediaWiki::doPreOutputCommit()
914  $buffer = null;
915  $outputWork = static function () use ( $output, &$buffer ) {
916  if ( $buffer === null ) {
917  $buffer = $output->output( true );
918  }
919 
920  return $buffer;
921  };
922 
923  // Commit any changes in the current transaction round so that:
924  // a) the transaction is not rolled back after success output was already sent
925  // b) error output is not jumbled together with success output in the response
926  $this->doPreOutputCommit( $outputWork );
927  // If needed, push a deferred update to run jobs after the output is send
928  $this->schedulePostSendJobs();
929  // If no exceptions occurred then send the output since it is safe now
930  $this->outputResponsePayload( $outputWork() );
931  }
932 
939  private function shouldDoHttpRedirect() {
940  $request = $this->context->getRequest();
941 
942  // Don't redirect if we're already on HTTPS
943  if ( $request->getProtocol() !== 'http' ) {
944  return false;
945  }
946 
947  $force = $this->config->get( 'ForceHTTPS' );
948 
949  // Don't redirect if $wgServer is explicitly HTTP. We test for this here
950  // by checking whether wfExpandUrl() is able to force HTTPS.
951  if ( !preg_match( '#^https://#', wfExpandUrl( $request->getRequestURL(), PROTO_HTTPS ) ) ) {
952  if ( $force ) {
953  throw new RuntimeException( '$wgForceHTTPS is true but the server is not HTTPS' );
954  }
955  return false;
956  }
957 
958  // Configured $wgForceHTTPS overrides the remaining conditions
959  if ( $force ) {
960  return true;
961  }
962 
963  // Check if HTTPS is required by the session or user preferences
964  return $request->getSession()->shouldForceHTTPS() ||
965  // Check the cookie manually, for paranoia
966  $request->getCookie( 'forceHTTPS', '' ) ||
967  // Avoid checking the user and groups unless it's enabled.
968  (
969  $this->context->getUser()->isRegistered()
970  && $this->context->getUser()->requiresHTTPS()
971  );
972  }
973 
983  private function maybeDoHttpsRedirect() {
984  if ( !$this->shouldDoHttpRedirect() ) {
985  return false;
986  }
987 
988  $request = $this->context->getRequest();
989  $oldUrl = $request->getFullRequestURL();
990  $redirUrl = preg_replace( '#^http://#', 'https://', $oldUrl );
991 
992  if ( $request->wasPosted() ) {
993  // This is weird and we'd hope it almost never happens. This
994  // means that a POST came in via HTTP and policy requires us
995  // redirecting to HTTPS. It's likely such a request is going
996  // to fail due to post data being lost, but let's try anyway
997  // and just log the instance.
998 
999  // @todo FIXME: See if we could issue a 307 or 308 here, need
1000  // to see how clients (automated & browser) behave when we do
1001  wfDebugLog( 'RedirectedPosts', "Redirected from HTTP to HTTPS: $oldUrl" );
1002  }
1003  // Setup dummy Title, otherwise OutputPage::redirect will fail
1004  $title = Title::newFromText( 'REDIR', NS_MAIN );
1005  $this->context->setTitle( $title );
1006  // Since we only do this redir to change proto, always send a vary header
1007  $output = $this->context->getOutput();
1008  $output->addVaryHeader( 'X-Forwarded-Proto' );
1009  $output->redirect( $redirUrl );
1010  $output->output();
1011 
1012  return true;
1013  }
1014 
1024  private function outputResponsePayload( $content ) {
1025  // Append any visible profiling data in a manner appropriate for the Content-Type
1026  ob_start();
1027  try {
1028  Profiler::instance()->logDataPageOutputOnly();
1029  } finally {
1030  $content .= ob_get_clean();
1031  }
1032 
1033  // By default, usually one output buffer is active now, either the internal PHP buffer
1034  // started by "output_buffering" in php.ini or the buffer started by MW_SETUP_CALLBACK.
1035  // The MW_SETUP_CALLBACK buffer has an unlimited chunk size, while the internal PHP
1036  // buffer only has an unlimited chunk size if output_buffering="On". If the buffer was
1037  // filled up to the chunk size with printed data, then HTTP headers will have already
1038  // been sent. Also, if the entry point had to stream content to the client, then HTTP
1039  // headers will have already been sent as well, regardless of chunk size.
1040 
1041  // Disable mod_deflate compression since it interferes with the output buffer set
1042  // by MW_SETUP_CALLBACK and can also cause the client to wait on deferred updates
1043  if ( function_exists( 'apache_setenv' ) ) {
1044  // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1045  @apache_setenv( 'no-gzip', 1 );
1046  }
1047 
1048  if (
1049  // "Content-Length" is used to prevent clients from waiting on deferred updates
1050  $this->postSendStrategy === self::DEFER_SET_LENGTH_AND_FLUSH &&
1051  // The HTTP response code clearly allows for a meaningful body
1052  in_array( http_response_code(), [ 200, 404 ], true ) &&
1053  // The queue of (post-send) deferred updates is non-empty
1055  // Any buffered output is not spread out across multiple output buffers
1056  ob_get_level() <= 1 &&
1057  // It is not too late to set additional HTTP headers
1058  !headers_sent()
1059  ) {
1060  $response = $this->context->getRequest()->response();
1061 
1062  $obStatus = ob_get_status();
1063  if ( !isset( $obStatus['name'] ) ) {
1064  // No output buffer is active
1065  $response->header( 'Content-Length: ' . strlen( $content ) );
1066  } elseif ( $obStatus['name'] === 'default output handler' ) {
1067  // Internal PHP "output_buffering" output buffer (note that the internal PHP
1068  // "zlib.output_compression" output buffer is named "zlib output compression")
1069  $response->header( 'Content-Length: ' . ( ob_get_length() + strlen( $content ) ) );
1070  }
1071 
1072  // The MW_SETUP_CALLBACK output buffer ("MediaWiki\OutputHandler::handle") sets
1073  // "Content-Length" where applicable. Other output buffer types might not set this
1074  // header, and since they might mangle or compress the payload, it is not possible
1075  // to determine the final payload size here.
1076 
1077  // Tell the client to immediately end the connection as soon as the response payload
1078  // has been read (informed by any "Content-Length" header). This prevents the client
1079  // from waiting on deferred updates.
1080  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection
1081  if ( ( $_SERVER['SERVER_PROTOCOL'] ?? '' ) === 'HTTP/1.1' ) {
1082  $response->header( 'Connection: close' );
1083  }
1084  }
1085 
1086  // Print the content *after* adjusting HTTP headers and disabling mod_deflate since
1087  // calling "print" will send the output to the client if there is no output buffer or
1088  // if the output buffer chunk size is reached
1089  print $content;
1090  }
1091 
1095  public function restInPeace() {
1096  // Either all DB and deferred updates should happen or none.
1097  // The latter should not be cancelled due to client disconnect.
1098  ignore_user_abort( true );
1099 
1100  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
1101  // Assure deferred updates are not in the main transaction
1102  $lbFactory->commitPrimaryChanges( __METHOD__ );
1103 
1104  // Loosen DB query expectations since the HTTP client is unblocked
1105  $trxProfiler = Profiler::instance()->getTransactionProfiler();
1106  $trxProfiler->redefineExpectations(
1107  $this->context->getRequest()->hasSafeMethod()
1108  ? $this->config->get( 'TrxProfilerLimits' )['PostSend-GET']
1109  : $this->config->get( 'TrxProfilerLimits' )['PostSend-POST'],
1110  __METHOD__
1111  );
1112 
1113  // Do any deferred jobs; preferring to run them now if a client will not wait on them
1115 
1116  // Handle external profiler outputs.
1117  // Any embedded profiler outputs were already processed in outputResponsePayload().
1118  $profiler = Profiler::instance();
1119  $profiler->logData();
1120 
1122  MediaWikiServices::getInstance()->getStatsdDataFactory(),
1123  $this->config
1124  );
1125 
1126  // Send metrics gathered by MetricsFactory
1127  MediaWikiServices::getInstance()->getMetricsFactory()->flush();
1128 
1129  // Commit and close up!
1130  $lbFactory->commitPrimaryChanges( __METHOD__ );
1131  $lbFactory->shutdown( $lbFactory::SHUTDOWN_NO_CHRONPROT );
1132 
1133  wfDebug( "Request ended normally" );
1134  }
1135 
1163  public static function emitBufferedStatsdData(
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 }
Action\getActionName
static getActionName(IContextSource $context)
Get the action that will be executed, not necessarily the one passed passed through the "action" requ...
Definition: Action.php:108
DerivativeRequest
Similar to FauxRequest, but only fakes URL parameters and method (POST or GET) and use the base reque...
Definition: DerivativeRequest.php:36
IBufferingStatsdDataFactory\clearData
clearData()
Clear all buffered data from the factory.
HTMLFileCache\useFileCache
static useFileCache(IContextSource $context, $mode=self::MODE_NORMAL)
Check if pages can be cached for this request/user.
Definition: HTMLFileCache.php:94
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:377
MediaWiki\performAction
performAction(Article $article, Title $requestTitle)
Perform one of the "standard" actions.
Definition: MediaWiki.php:499
MediaWiki\emitBufferedStatsdData
static emitBufferedStatsdData(IBufferingStatsdDataFactory $stats, Config $config)
Send out any buffered statsd data according to sampling rules.
Definition: MediaWiki.php:1163
MWExceptionRenderer\getHTML
static getHTML(Throwable $e)
If $wgShowExceptionDetails is true, return a HTML message with a backtrace to the error,...
Definition: MWExceptionRenderer.php:185
PROTO_HTTPS
const PROTO_HTTPS
Definition: Defines.php:193
Action\factory
static factory(string $action, Article $article, IContextSource $context=null)
Get an appropriate Action subclass for the given action.
Definition: Action.php:85
Profiler\instance
static instance()
Singleton.
Definition: Profiler.php:69
WikiMap\isCurrentWikiId
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:321
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:203
HTMLFileCache
Page view caching in the file system.
Definition: HTMLFileCache.php:35
MediaWiki\run
run()
Run the current MediaWiki instance; index.php just calls this.
Definition: MediaWiki.php:561
MediaWiki\Logger\LoggerFactory\getInstance
static getInstance( $channel)
Get a named logger instance from the currently configured logger factory.
Definition: LoggerFactory.php:92
JobQueueGroup\TYPE_DEFAULT
const TYPE_DEFAULT
Definition: JobQueueGroup.php:64
MediaWiki\restInPeace
restInPeace()
Ends this task peacefully.
Definition: MediaWiki.php:1095
HTMLFileCache\MODE_OUTAGE
const MODE_OUTAGE
Definition: HTMLFileCache.php:37
MediaWiki\getTitle
getTitle()
Get the Title object that we'll be acting on, as specified in the WebRequest.
Definition: MediaWiki.php:158
WikiMap\getWikiFromUrl
static getWikiFromUrl( $url)
Definition: WikiMap.php:224
MediaWiki\main
main()
Determine and send the response headers and body for this web request.
Definition: MediaWiki.php:856
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
Definition: DeferredUpdates.php:119
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
SpecialRunJobs\getQuerySignature
static getQuerySignature(array $query, $secretKey)
Definition: SpecialRunJobs.php:137
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1082
SpecialPage\getTitleFor
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,...
Definition: SpecialPage.php:131
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:32
Title\newMainPage
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:710
RedirectSpecialPage
Shortcut to construct a special page alias.
Definition: RedirectSpecialPage.php:31
IBufferingStatsdDataFactory\hasData
hasData()
Check whether this data factory has any buffered data.
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:958
HttpError
Show an error that looks like an HTTP server error.
Definition: HttpError.php:32
ErrorPageError\report
report( $action=self::SEND_OUTPUT)
Definition: ErrorPageError.php:77
NS_MAIN
const NS_MAIN
Definition: Defines.php:64
Action
Actions are things which can be done to pages (edit, delete, rollback, etc).
Definition: Action.php:43
MediaWiki\MediaWikiServices\getInstance
static getInstance()
Returns the global default instance of the top level service locator.
Definition: MediaWikiServices.php:264
MWExceptionHandler\logException
static logException(Throwable $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log a throwable to the exception log (if enabled).
Definition: MWExceptionHandler.php:700
MediaWiki\maybeDoHttpsRedirect
maybeDoHttpsRedirect()
If the stars are suitably aligned, do an HTTP->HTTPS redirect.
Definition: MediaWiki.php:983
ErrorPageError\STAGE_OUTPUT
const STAGE_OUTPUT
Definition: ErrorPageError.php:32
Config
Interface for configuration instances.
Definition: Config.php:30
MediaWiki\$postSendStrategy
int $postSendStrategy
Class DEFER_* constant; how non-blocking post-response tasks should run.
Definition: MediaWiki.php:47
wfParseUrl
wfParseUrl( $url)
parse_url() work-alike, but non-broken.
Definition: GlobalFunctions.php:776
Article\newFromWikiPage
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:195
IBufferingStatsdDataFactory\getData
getData()
Return the buffered data from the factory.
MWException
MediaWiki exception.
Definition: MWException.php:29
MWExceptionHandler\handleException
static handleException(Throwable $e, $catcher=self::CAUGHT_BY_OTHER)
Exception handler which simulates the appropriate catch() handling:
Definition: MWExceptionHandler.php:200
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
Config\get
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
MediaWiki\triggerSyncJobs
triggerSyncJobs( $n)
Definition: MediaWiki.php:1186
MediaWiki\parseTitle
parseTitle()
Parse the request to get the Title object.
Definition: MediaWiki.php:78
MediaWiki\schedulePostSendJobs
schedulePostSendJobs()
If enabled, after everything specific to this request is done, occasionally run jobs.
Definition: MediaWiki.php:594
MediaWiki
A helper class for throttling authentication attempts.
MediaWiki\DEFER_FASTCGI_FINISH_REQUEST
const DEFER_FASTCGI_FINISH_REQUEST
Call fastcgi_finish_request() to make post-send updates async.
Definition: MediaWiki.php:50
SamplingStatsdClient
A statsd client that applies the sampling rate to the data items before sending them.
Definition: SamplingStatsdClient.php:32
$title
$title
Definition: testCompression.php:38
MediaWiki\doPreOutputCommit
doPreOutputCommit(callable $postCommitWork=null)
Definition: MediaWiki.php:646
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:648
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:894
MediaWiki\triggerAsyncJobs
triggerAsyncJobs( $n, LoggerInterface $runJobsLogger)
Definition: MediaWiki.php:1198
$wgTitle
$wgTitle
Definition: Setup.php:879
Title\newFromTextThrow
static newFromTextThrow( $text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid,...
Definition: Title.php:412
PROTO_CURRENT
const PROTO_CURRENT
Definition: Defines.php:195
WebResponse\disableForPostSend
static disableForPostSend()
Disable setters for post-send processing.
Definition: WebResponse.php:50
$content
$content
Definition: router.php:76
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:52
MediaWiki\preOutputCommit
static preOutputCommit(IContextSource $context, callable $postCommitWork=null)
This function commits all DB and session changes as needed before the client can receive a response (...
Definition: MediaWiki.php:661
MediaWiki\tryNormaliseRedirect
tryNormaliseRedirect(Title $title)
Handle redirects for uncanonical title requests.
Definition: MediaWiki.php:354
MediaWiki\__construct
__construct(IContextSource $context=null)
Definition: MediaWiki.php:59
Title\newFromURL
static newFromURL( $url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition: Title.php:474
IContextSource\getUser
getUser()
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:484
IContextSource\getTitle
getTitle()
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:289
PROTO_INTERNAL
const PROTO_INTERNAL
Definition: Defines.php:197
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:58
DeferredUpdates\doUpdates
static doUpdates( $mode='run', $stage=self::ALL)
Consume and execute all pending updates.
Definition: DeferredUpdates.php:173
MediaWiki\Permissions\PermissionStatus
A StatusValue for permission errors.
Definition: PermissionStatus.php:35
MWExceptionHandler\rollbackPrimaryChangesAndLog
static rollbackPrimaryChangesAndLog(Throwable $e, $catcher=self::CAUGHT_BY_OTHER)
Roll back any open database transactions and log the stack trace of the throwable.
Definition: MWExceptionHandler.php:126
DeferredUpdates\pendingUpdatesCount
static pendingUpdatesCount()
Get the number of pending updates for the current execution context.
Definition: DeferredUpdates.php:322
IBufferingStatsdDataFactory
MediaWiki adaptation of StatsdDataFactory that provides buffering functionality.
Definition: IBufferingStatsdDataFactory.php:13
MediaWiki\performRequest
performRequest()
Performs the request.
Definition: MediaWiki.php:194
MediaWiki\$action
string $action
Cache what action this request is.
Definition: MediaWiki.php:45
MediaWiki\getAction
getAction()
Returns the name of the action that will be executed.
Definition: MediaWiki.php:174
Title
Represents a title within MediaWiki.
Definition: Title.php:47
MediaWiki\initializeArticle
initializeArticle()
Initialize the main Article object for "standard" actions (view, etc) Create an Article object for th...
Definition: MediaWiki.php:414
$cache
$cache
Definition: mcc.php:33
MalformedTitleException
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Definition: MalformedTitleException.php:26
Wikimedia\Rdbms\ChronologyProtector
Provide a given client with protection against visible database lag.
Definition: ChronologyProtector.php:136
IContextSource\getConfig
getConfig()
Get the site configuration.
MediaWiki\$config
Config $config
Definition: MediaWiki.php:42
MediaWiki\DEFER_CLI_MODE
const DEFER_CLI_MODE
Do not try to make post-send updates async (e.g.
Definition: MediaWiki.php:54
TransactionRoundDefiningUpdate
Deferrable update that must run outside of any explicit LBFactory transaction round.
Definition: TransactionRoundDefiningUpdate.php:10
Wikimedia\Rdbms\DBConnectionError
Definition: DBConnectionError.php:27
IContextSource\getRequest
getRequest()
WikiFilePage
Special handling for file pages.
Definition: WikiFilePage.php:32
wfWarn
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Definition: GlobalFunctions.php:1043
$t
$t
Definition: testCompression.php:74
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:49
NS_FILE
const NS_FILE
Definition: Defines.php:70
MediaWiki\shouldDoHttpRedirect
shouldDoHttpRedirect()
Check if an HTTP->HTTPS redirect should be done.
Definition: MediaWiki.php:939
MediaWiki\$context
IContextSource $context
Definition: MediaWiki.php:40
MediaWiki\doPostOutputShutdown
doPostOutputShutdown()
This function does work that can be done after the user gets the HTTP response so they don't block on...
Definition: MediaWiki.php:811
ErrorPageError
An error page which can definitely be safely rendered using the OutputPage.
Definition: ErrorPageError.php:30
MediaWiki\outputResponsePayload
outputResponsePayload( $content)
Print a response body to the current buffer (if there is one) or the server (otherwise)
Definition: MediaWiki.php:1024
MediaWiki\getUrlDomainDistance
static getUrlDomainDistance( $url)
Definition: MediaWiki.php:790
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:523
IContextSource\getOutput
getOutput()
BadTitleError
Show an error page on a badtitle.
Definition: BadTitleError.php:31
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:474
MediaWiki\DEFER_SET_LENGTH_AND_FLUSH
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