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