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