MediaWiki  1.34.0
MediaWiki.php
Go to the documentation of this file.
1 <?php
24 use Psr\Log\LoggerInterface;
29 use Liuggio\StatsdClient\Sender\SocketSender;
30 
34 class MediaWiki {
38  private $context;
39 
43  private $config;
44 
48  private $action;
49 
53  public function __construct( IContextSource $context = null ) {
54  if ( !$context ) {
56  }
57 
58  $this->context = $context;
59  $this->config = $context->getConfig();
60  }
61 
68  private function parseTitle() {
69  $request = $this->context->getRequest();
70  $curid = $request->getInt( 'curid' );
71  $title = $request->getVal( 'title' );
72  $action = $request->getVal( 'action' );
73 
74  if ( $request->getCheck( 'search' ) ) {
75  // Compatibility with old search URLs which didn't use Special:Search
76  // Just check for presence here, so blank requests still
77  // show the search page when using ugly URLs (T10054).
78  $ret = SpecialPage::getTitleFor( 'Search' );
79  } elseif ( $curid ) {
80  // URLs like this are generated by RC, because rc_title isn't always accurate
81  $ret = Title::newFromID( $curid );
82  } else {
83  $ret = Title::newFromURL( $title );
84  // Alias NS_MEDIA page URLs to NS_FILE...we only use NS_MEDIA
85  // in wikitext links to tell Parser to make a direct file link
86  if ( !is_null( $ret ) && $ret->getNamespace() == NS_MEDIA ) {
87  $ret = Title::makeTitle( NS_FILE, $ret->getDBkey() );
88  }
89  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
90  // Check variant links so that interwiki links don't have to worry
91  // about the possible different language variants
92  if (
93  $contLang->hasVariants() && !is_null( $ret ) && $ret->getArticleID() == 0
94  ) {
95  $contLang->findVariantLink( $title, $ret );
96  }
97  }
98 
99  // If title is not provided, always allow oldid and diff to set the title.
100  // If title is provided, allow oldid and diff to override the title, unless
101  // we are talking about a special page which might use these parameters for
102  // other purposes.
103  if ( $ret === null || !$ret->isSpecialPage() ) {
104  // We can have urls with just ?diff=,?oldid= or even just ?diff=
105  $oldid = $request->getInt( 'oldid' );
106  $oldid = $oldid ?: $request->getInt( 'diff' );
107  // Allow oldid to override a changed or missing title
108  if ( $oldid ) {
109  $rev = Revision::newFromId( $oldid );
110  $ret = $rev ? $rev->getTitle() : $ret;
111  }
112  }
113 
114  // Use the main page as default title if nothing else has been provided
115  if ( $ret === null
116  && strval( $title ) === ''
117  && !$request->getCheck( 'curid' )
118  && $action !== 'delete'
119  ) {
120  $ret = Title::newMainPage();
121  }
122 
123  if ( $ret === null || ( $ret->getDBkey() == '' && !$ret->isExternal() ) ) {
124  // If we get here, we definitely don't have a valid title; throw an exception.
125  // Try to get detailed invalid title exception first, fall back to MalformedTitleException.
127  throw new MalformedTitleException( 'badtitletext', $title );
128  }
129 
130  return $ret;
131  }
132 
137  public function getTitle() {
138  if ( !$this->context->hasTitle() ) {
139  try {
140  $this->context->setTitle( $this->parseTitle() );
141  } catch ( MalformedTitleException $ex ) {
142  $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
143  }
144  }
145  return $this->context->getTitle();
146  }
147 
153  public function getAction() {
154  if ( $this->action === null ) {
155  $this->action = Action::getActionName( $this->context );
156  }
157 
158  return $this->action;
159  }
160 
173  private function performRequest() {
174  global $wgTitle;
175 
176  $request = $this->context->getRequest();
177  $requestTitle = $title = $this->context->getTitle();
178  $output = $this->context->getOutput();
179  $user = $this->context->getUser();
180 
181  if ( $request->getVal( 'printable' ) === 'yes' ) {
182  $output->setPrintable();
183  }
184 
185  $unused = null; // To pass it by reference
186  Hooks::run( 'BeforeInitialize', [ &$title, &$unused, &$output, &$user, $request, $this ] );
187 
188  // Invalid titles. T23776: The interwikis must redirect even if the page name is empty.
189  if ( is_null( $title ) || ( $title->getDBkey() == '' && !$title->isExternal() )
190  || $title->isSpecial( 'Badtitle' )
191  ) {
192  $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
193  try {
194  $this->parseTitle();
195  } catch ( MalformedTitleException $ex ) {
196  throw new BadTitleError( $ex );
197  }
198  throw new BadTitleError();
199  }
200 
201  // Check user's permissions to read this page.
202  // We have to check here to catch special pages etc.
203  // We will check again in Article::view().
204  $permErrors = $title->isSpecial( 'RunJobs' )
205  ? [] // relies on HMAC key signature alone
206  : $title->getUserPermissionsErrors( 'read', $user );
207  if ( count( $permErrors ) ) {
208  // T34276: allowing the skin to generate output with $wgTitle or
209  // $this->context->title set to the input title would allow anonymous users to
210  // determine whether a page exists, potentially leaking private data. In fact, the
211  // curid and oldid request parameters would allow page titles to be enumerated even
212  // when they are not guessable. So we reset the title to Special:Badtitle before the
213  // permissions error is displayed.
214 
215  // The skin mostly uses $this->context->getTitle() these days, but some extensions
216  // still use $wgTitle.
217  $badTitle = SpecialPage::getTitleFor( 'Badtitle' );
218  $this->context->setTitle( $badTitle );
219  $wgTitle = $badTitle;
220 
221  throw new PermissionsError( 'read', $permErrors );
222  }
223 
224  // Interwiki redirects
225  if ( $title->isExternal() ) {
226  $rdfrom = $request->getVal( 'rdfrom' );
227  if ( $rdfrom ) {
228  $url = $title->getFullURL( [ 'rdfrom' => $rdfrom ] );
229  } else {
230  $query = $request->getValues();
231  unset( $query['title'] );
232  $url = $title->getFullURL( $query );
233  }
234  // Check for a redirect loop
235  if ( !preg_match( '/^' . preg_quote( $this->config->get( 'Server' ), '/' ) . '/', $url )
236  && $title->isLocal()
237  ) {
238  // 301 so google et al report the target as the actual url.
239  $output->redirect( $url, 301 );
240  } else {
241  $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
242  try {
243  $this->parseTitle();
244  } catch ( MalformedTitleException $ex ) {
245  throw new BadTitleError( $ex );
246  }
247  throw new BadTitleError();
248  }
249  // Handle any other redirects.
250  // Redirect loops, titleless URL, $wgUsePathInfo URLs, and URLs with a variant
251  } elseif ( !$this->tryNormaliseRedirect( $title ) ) {
252  // Prevent information leak via Special:MyPage et al (T109724)
253  $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
254  if ( $title->isSpecialPage() ) {
255  $specialPage = $spFactory->getPage( $title->getDBkey() );
256  if ( $specialPage instanceof RedirectSpecialPage ) {
257  $specialPage->setContext( $this->context );
258  if ( $this->config->get( 'HideIdentifiableRedirects' )
259  && $specialPage->personallyIdentifiableTarget()
260  ) {
261  list( , $subpage ) = $spFactory->resolveAlias( $title->getDBkey() );
262  $target = $specialPage->getRedirect( $subpage );
263  // Target can also be true. We let that case fall through to normal processing.
264  if ( $target instanceof Title ) {
265  if ( $target->isExternal() ) {
266  // Handle interwiki redirects
267  $target = SpecialPage::getTitleFor(
268  'GoToInterwiki',
269  'force/' . $target->getPrefixedDBkey()
270  );
271  }
272 
273  $query = $specialPage->getRedirectQuery( $subpage ) ?: [];
274  $request = new DerivativeRequest( $this->context->getRequest(), $query );
275  $request->setRequestURL( $this->context->getRequest()->getRequestURL() );
276  $this->context->setRequest( $request );
277  // Do not varnish cache these. May vary even for anons
278  $this->context->getOutput()->lowerCdnMaxage( 0 );
279  $this->context->setTitle( $target );
280  $wgTitle = $target;
281  // Reset action type cache. (Special pages have only view)
282  $this->action = null;
283  $title = $target;
284  $output->addJsConfigVars( [
285  'wgInternalRedirectTargetUrl' => $target->getFullURL( $query ),
286  ] );
287  $output->addModules( 'mediawiki.action.view.redirect' );
288  }
289  }
290  }
291  }
292 
293  // Special pages ($title may have changed since if statement above)
294  if ( $title->isSpecialPage() ) {
295  // Actions that need to be made when we have a special pages
296  $spFactory->executePath( $title, $this->context );
297  } else {
298  // ...otherwise treat it as an article view. The article
299  // may still be a wikipage redirect to another article or URL.
300  $article = $this->initializeArticle();
301  if ( is_object( $article ) ) {
302  $this->performAction( $article, $requestTitle );
303  } elseif ( is_string( $article ) ) {
304  $output->redirect( $article );
305  } else {
306  throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle()"
307  . " returned neither an object nor a URL" );
308  }
309  }
310  }
311  }
312 
335  private function tryNormaliseRedirect( Title $title ) {
336  $request = $this->context->getRequest();
337  $output = $this->context->getOutput();
338 
339  if ( $request->getVal( 'action', 'view' ) != 'view'
340  || $request->wasPosted()
341  || ( $request->getCheck( 'title' )
342  && $title->getPrefixedDBkey() == $request->getVal( 'title' ) )
343  || count( $request->getValueNames( [ 'action', 'title' ] ) )
344  || !Hooks::run( 'TestCanonicalRedirect', [ $request, $title, $output ] )
345  ) {
346  return false;
347  }
348 
349  if ( $this->config->get( 'MainPageIsDomainRoot' ) && $request->getRequestURL() === '/' ) {
350  return false;
351  }
352 
353  if ( $title->isSpecialPage() ) {
354  list( $name, $subpage ) = MediaWikiServices::getInstance()->getSpecialPageFactory()->
355  resolveAlias( $title->getDBkey() );
356  if ( $name ) {
357  $title = SpecialPage::getTitleFor( $name, $subpage );
358  }
359  }
360  // Redirect to canonical url, make it a 301 to allow caching
361  $targetUrl = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
362  if ( $targetUrl == $request->getFullRequestURL() ) {
363  $message = "Redirect loop detected!\n\n" .
364  "This means the wiki got confused about what page was " .
365  "requested; this sometimes happens when moving a wiki " .
366  "to a new server or changing the server configuration.\n\n";
367 
368  if ( $this->config->get( 'UsePathInfo' ) ) {
369  $message .= "The wiki is trying to interpret the page " .
370  "title from the URL path portion (PATH_INFO), which " .
371  "sometimes fails depending on the web server. Try " .
372  "setting \"\$wgUsePathInfo = false;\" in your " .
373  "LocalSettings.php, or check that \$wgArticlePath " .
374  "is correct.";
375  } else {
376  $message .= "Your web server was detected as possibly not " .
377  "supporting URL path components (PATH_INFO) correctly; " .
378  "check your LocalSettings.php for a customized " .
379  "\$wgArticlePath setting and/or toggle \$wgUsePathInfo " .
380  "to true.";
381  }
382  throw new HttpError( 500, $message );
383  }
384  $output->setCdnMaxage( 1200 );
385  $output->redirect( $targetUrl, '301' );
386  return true;
387  }
388 
395  private function initializeArticle() {
396  $title = $this->context->getTitle();
397  if ( $this->context->canUseWikiPage() ) {
398  // Try to use request context wiki page, as there
399  // is already data from db saved in per process
400  // cache there from this->getAction() call.
401  $page = $this->context->getWikiPage();
402  } else {
403  // This case should not happen, but just in case.
404  // @TODO: remove this or use an exception
405  $page = WikiPage::factory( $title );
406  $this->context->setWikiPage( $page );
407  wfWarn( "RequestContext::canUseWikiPage() returned false" );
408  }
409 
410  // Make GUI wrapper for the WikiPage
411  $article = Article::newFromWikiPage( $page, $this->context );
412 
413  // Skip some unnecessary code if the content model doesn't support redirects
414  if ( !ContentHandler::getForTitle( $title )->supportsRedirects() ) {
415  return $article;
416  }
417 
418  $request = $this->context->getRequest();
419 
420  // Namespace might change when using redirects
421  // Check for redirects ...
422  $action = $request->getVal( 'action', 'view' );
423  $file = ( $page instanceof WikiFilePage ) ? $page->getFile() : null;
424  if ( ( $action == 'view' || $action == 'render' ) // ... for actions that show content
425  && !$request->getVal( 'oldid' ) // ... and are not old revisions
426  && !$request->getVal( 'diff' ) // ... and not when showing diff
427  && $request->getVal( 'redirect' ) != 'no' // ... unless explicitly told not to
428  // ... and the article is not a non-redirect image page with associated file
429  && !( is_object( $file ) && $file->exists() && !$file->getRedirected() )
430  ) {
431  // Give extensions a change to ignore/handle redirects as needed
432  $ignoreRedirect = $target = false;
433 
434  Hooks::run( 'InitializeArticleMaybeRedirect',
435  [ &$title, &$request, &$ignoreRedirect, &$target, &$article ] );
436  $page = $article->getPage(); // reflect any hook changes
437 
438  // Follow redirects only for... redirects.
439  // If $target is set, then a hook wanted to redirect.
440  if ( !$ignoreRedirect && ( $target || $page->isRedirect() ) ) {
441  // Is the target already set by an extension?
442  $target = $target ?: $page->followRedirect();
443  if ( is_string( $target ) && !$this->config->get( 'DisableHardRedirects' ) ) {
444  // we'll need to redirect
445  return $target;
446  }
447  if ( is_object( $target ) ) {
448  // Rewrite environment to redirected article
449  $rpage = WikiPage::factory( $target );
450  $rpage->loadPageData();
451  if ( $rpage->exists() || ( is_object( $file ) && !$file->isLocal() ) ) {
452  $rarticle = Article::newFromWikiPage( $rpage, $this->context );
453  $rarticle->setRedirectedFrom( $title );
454 
455  $article = $rarticle;
456  $this->context->setTitle( $target );
457  $this->context->setWikiPage( $article->getPage() );
458  }
459  }
460  } else {
461  // Article may have been changed by hook
462  $this->context->setTitle( $article->getTitle() );
463  $this->context->setWikiPage( $article->getPage() );
464  }
465  }
466 
467  return $article;
468  }
469 
476  private function performAction( Page $page, Title $requestTitle ) {
477  $request = $this->context->getRequest();
478  $output = $this->context->getOutput();
479  $title = $this->context->getTitle();
480  $user = $this->context->getUser();
481 
482  if ( !Hooks::run( 'MediaWikiPerformAction',
483  [ $output, $page, $title, $user, $request, $this ] )
484  ) {
485  return;
486  }
487 
488  $act = $this->getAction();
489  $action = Action::factory( $act, $page, $this->context );
490 
491  if ( $action instanceof Action ) {
492  // Narrow DB query expectations for this HTTP request
493  $trxLimits = $this->config->get( 'TrxProfilerLimits' );
494  $trxProfiler = Profiler::instance()->getTransactionProfiler();
495  if ( $request->wasPosted() && !$action->doesWrites() ) {
496  $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
497  $request->markAsSafeRequest();
498  }
499 
500  # Let CDN cache things if we can purge them.
501  if ( $this->config->get( 'UseCdn' ) &&
502  in_array(
503  // Use PROTO_INTERNAL because that's what getCdnUrls() uses
504  wfExpandUrl( $request->getRequestURL(), PROTO_INTERNAL ),
505  $requestTitle->getCdnUrls()
506  )
507  ) {
508  $output->setCdnMaxage( $this->config->get( 'CdnMaxAge' ) );
509  }
510 
511  $action->show();
512  return;
513  }
514 
515  // If we've not found out which action it is by now, it's unknown
516  $output->setStatusCode( 404 );
517  $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
518  }
519 
523  public function run() {
524  try {
525  $this->setDBProfilingAgent();
526  try {
527  $this->main();
528  } catch ( ErrorPageError $e ) {
529  $out = $this->context->getOutput();
530  // TODO: Should ErrorPageError::report accept a OutputPage parameter?
532 
533  // T64091: while exceptions are convenient to bubble up GUI errors,
534  // they are not internal application faults. As with normal requests, this
535  // should commit, print the output, do deferred updates, jobs, and profiling.
536  $this->doPreOutputCommit();
537  $out->output(); // display the GUI error
538  }
539  } catch ( Exception $e ) {
541  $action = $context->getRequest()->getVal( 'action', 'view' );
542  if (
543  $e instanceof DBConnectionError &&
544  $context->hasTitle() &&
545  $context->getTitle()->canExist() &&
546  in_array( $action, [ 'view', 'history' ], true ) &&
548  ) {
549  // Try to use any (even stale) file during outages...
551  if ( $cache->isCached() ) {
552  $cache->loadFromFileCache( $context, HTMLFileCache::MODE_OUTAGE );
553  print MWExceptionRenderer::getHTML( $e );
554  exit;
555  }
556  }
557 
559  } catch ( Error $e ) {
560  // Type errors and such: at least handle it now and clean up the LBFactory state
562  }
563 
564  $this->doPostOutputShutdown( 'normal' );
565  }
566 
567  private function setDBProfilingAgent() {
568  $services = MediaWikiServices::getInstance();
569  // Add a comment for easy SHOW PROCESSLIST interpretation
570  $name = $this->context->getUser()->getName();
571  $services->getDBLoadBalancerFactory()->setAgentName(
572  mb_strlen( $name ) > 15 ? mb_substr( $name, 0, 15 ) . '...' : $name
573  );
574  }
575 
581  public function doPreOutputCommit( callable $postCommitWork = null ) {
582  self::preOutputCommit( $this->context, $postCommitWork );
583  }
584 
596  public static function preOutputCommit(
597  IContextSource $context, callable $postCommitWork = null
598  ) {
600  $request = $context->getRequest();
602  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
603 
604  // Try to make sure that all RDBMs, session, and other storage updates complete
605  ignore_user_abort( true );
606 
607  // Commit all RDBMs changes from the main transaction round
608  $lbFactory->commitMasterChanges(
609  __METHOD__,
610  // Abort if any transaction was too big
611  [ 'maxWriteDuration' => $config->get( 'MaxUserDBWriteDuration' ) ]
612  );
613  wfDebug( __METHOD__ . ': primary transaction round committed' );
614 
615  // Run updates that need to block the client or affect output (this is the last chance)
617  wfDebug( __METHOD__ . ': pre-send deferred updates completed' );
618  // Persist the session to avoid race conditions on subsequent requests by the client
619  $request->getSession()->save(); // T214471
620  wfDebug( __METHOD__ . ': session changes committed' );
621 
622  // Figure out whether to wait for DB replication now or to use some method that assures
623  // that subsequent requests by the client will use the DB replication positions written
624  // during the shutdown() call below; the later requires working around replication lag
625  // of the store containing DB replication positions (e.g. dynomite, mcrouter).
626  list( $flags, $strategy ) = self::getChronProtStrategy( $lbFactory, $output );
627  // Record ChronologyProtector positions for DBs affected in this request at this point
628  $cpIndex = null;
629  $cpClientId = null;
630  $lbFactory->shutdown( $flags, $postCommitWork, $cpIndex, $cpClientId );
631  wfDebug( __METHOD__ . ': LBFactory shutdown completed' );
632 
633  $allowHeaders = !( $output->isDisabled() || headers_sent() );
634  if ( $cpIndex > 0 ) {
635  if ( $allowHeaders ) {
636  $now = time();
637  $expires = $now + ChronologyProtector::POSITION_COOKIE_TTL;
638  $options = [ 'prefix' => '' ];
639  $value = $lbFactory::makeCookieValueFromCPIndex( $cpIndex, $now, $cpClientId );
640  $request->response()->setCookie( 'cpPosIndex', $value, $expires, $options );
641  }
642 
643  if ( $strategy === 'cookie+url' ) {
644  if ( $output->getRedirect() ) { // sanity
645  $safeUrl = $lbFactory->appendShutdownCPIndexAsQuery(
646  $output->getRedirect(),
647  $cpIndex
648  );
649  $output->redirect( $safeUrl );
650  } else {
651  $e = new LogicException( "No redirect; cannot append cpPosIndex parameter." );
653  }
654  }
655  }
656 
657  if ( $allowHeaders ) {
658  // Set a cookie to tell all CDN edge nodes to "stick" the user to the DC that
659  // handles this POST request (e.g. the "master" data center). Also have the user
660  // briefly bypass CDN so ChronologyProtector works for cacheable URLs.
661  if ( $request->wasPosted() && $lbFactory->hasOrMadeRecentMasterChanges() ) {
662  $expires = time() + $config->get( 'DataCenterUpdateStickTTL' );
663  $options = [ 'prefix' => '' ];
664  $request->response()->setCookie( 'UseDC', 'master', $expires, $options );
665  $request->response()->setCookie( 'UseCDNCache', 'false', $expires, $options );
666  }
667 
668  // Avoid letting a few seconds of replica DB lag cause a month of stale data.
669  // This logic is also intimately related to the value of $wgCdnReboundPurgeDelay.
670  if ( $lbFactory->laggedReplicaUsed() ) {
671  $maxAge = $config->get( 'CdnMaxageLagged' );
672  $output->lowerCdnMaxage( $maxAge );
673  $request->response()->header( "X-Database-Lagged: true" );
674  wfDebugLog( 'replication',
675  "Lagged DB used; CDN cache TTL limited to $maxAge seconds" );
676  }
677 
678  // Avoid long-term cache pollution due to message cache rebuild timeouts (T133069)
679  if ( MessageCache::singleton()->isDisabled() ) {
680  $maxAge = $config->get( 'CdnMaxageSubstitute' );
681  $output->lowerCdnMaxage( $maxAge );
682  $request->response()->header( "X-Response-Substitute: true" );
683  }
684  }
685  }
686 
692  private static function getChronProtStrategy( ILBFactory $lbFactory, OutputPage $output ) {
693  // Should the client return, their request should observe the new ChronologyProtector
694  // DB positions. This request might be on a foreign wiki domain, so synchronously update
695  // the DB positions in all datacenters to be safe. If this output is not a redirect,
696  // then OutputPage::output() will be relatively slow, meaning that running it in
697  // $postCommitWork should help mask the latency of those updates.
698  $flags = $lbFactory::SHUTDOWN_CHRONPROT_SYNC;
699  $strategy = 'cookie+sync';
700 
701  $allowHeaders = !( $output->isDisabled() || headers_sent() );
702  if ( $output->getRedirect() && $lbFactory->hasOrMadeRecentMasterChanges( INF ) ) {
703  // OutputPage::output() will be fast, so $postCommitWork is useless for masking
704  // the latency of synchronously updating the DB positions in all datacenters.
705  // Try to make use of the time the client spends following redirects instead.
706  $domainDistance = self::getUrlDomainDistance( $output->getRedirect() );
707  if ( $domainDistance === 'local' && $allowHeaders ) {
708  $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
709  $strategy = 'cookie'; // use same-domain cookie and keep the URL uncluttered
710  } elseif ( $domainDistance === 'remote' ) {
711  $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
712  $strategy = 'cookie+url'; // cross-domain cookie might not work
713  }
714  }
715 
716  return [ $flags, $strategy ];
717  }
718 
723  private static function getUrlDomainDistance( $url ) {
724  $clusterWiki = WikiMap::getWikiFromUrl( $url );
725  if ( WikiMap::isCurrentWikiId( $clusterWiki ) ) {
726  return 'local'; // the current wiki
727  }
728  if ( $clusterWiki !== false ) {
729  return 'remote'; // another wiki in this cluster/farm
730  }
731 
732  return 'external';
733  }
734 
745  public function doPostOutputShutdown( $mode = 'normal' ) {
746  // Record backend request timing
747  $timing = $this->context->getTiming();
748  $timing->mark( 'requestShutdown' );
749 
750  // Perform the last synchronous operations...
751  try {
752  // Show visible profiling data if enabled (which cannot be post-send)
753  Profiler::instance()->logDataPageOutputOnly();
754  } catch ( Exception $e ) {
755  // An error may already have been shown in run(), so just log it to be safe
757  }
758 
759  // Disable WebResponse setters for post-send processing (T191537).
761 
762  $blocksHttpClient = true;
763  // Defer everything else if possible...
764  $callback = function () use ( $mode, &$blocksHttpClient ) {
765  try {
766  $this->restInPeace( $mode, $blocksHttpClient );
767  } catch ( Exception $e ) {
768  // If this is post-send, then displaying errors can cause broken HTML
770  }
771  };
772 
773  if ( function_exists( 'register_postsend_function' ) ) {
774  // https://github.com/facebook/hhvm/issues/1230
775  register_postsend_function( $callback );
777  $blocksHttpClient = false;
778  } else {
779  if ( function_exists( 'fastcgi_finish_request' ) ) {
780  fastcgi_finish_request();
782  $blocksHttpClient = false;
783  } else {
784  // Either all DB and deferred updates should happen or none.
785  // The latter should not be cancelled due to client disconnect.
786  ignore_user_abort( true );
787  }
788 
789  $callback();
790  }
791  }
792 
793  private function main() {
794  global $wgTitle;
795 
796  $output = $this->context->getOutput();
797  $request = $this->context->getRequest();
798 
799  // Send Ajax requests to the Ajax dispatcher.
800  if ( $request->getVal( 'action' ) === 'ajax' ) {
801  // Set a dummy title, because $wgTitle == null might break things
802  $title = Title::makeTitle( NS_SPECIAL, 'Badtitle/performing an AJAX call in '
803  . __METHOD__
804  );
805  $this->context->setTitle( $title );
806  $wgTitle = $title;
807 
808  $dispatcher = new AjaxDispatcher( $this->config );
809  $dispatcher->performAction( $this->context->getUser() );
810 
811  return;
812  }
813 
814  // Get title from request parameters,
815  // is set on the fly by parseTitle the first time.
816  $title = $this->getTitle();
817  $action = $this->getAction();
818  $wgTitle = $title;
819 
820  // Set DB query expectations for this HTTP request
821  $trxLimits = $this->config->get( 'TrxProfilerLimits' );
822  $trxProfiler = Profiler::instance()->getTransactionProfiler();
823  $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) );
824  if ( $request->hasSafeMethod() ) {
825  $trxProfiler->setExpectations( $trxLimits['GET'], __METHOD__ );
826  } else {
827  $trxProfiler->setExpectations( $trxLimits['POST'], __METHOD__ );
828  }
829 
830  // If the user has forceHTTPS set to true, or if the user
831  // is in a group requiring HTTPS, or if they have the HTTPS
832  // preference set, redirect them to HTTPS.
833  // Note: Do this after $wgTitle is setup, otherwise the hooks run from
834  // isLoggedIn() will do all sorts of weird stuff.
835  if (
836  $request->getProtocol() == 'http' &&
837  // switch to HTTPS only when supported by the server
838  preg_match( '#^https://#', wfExpandUrl( $request->getRequestURL(), PROTO_HTTPS ) ) &&
839  (
840  $request->getSession()->shouldForceHTTPS() ||
841  // Check the cookie manually, for paranoia
842  $request->getCookie( 'forceHTTPS', '' ) ||
843  // check for prefixed version that was used for a time in older MW versions
844  $request->getCookie( 'forceHTTPS' ) ||
845  // Avoid checking the user and groups unless it's enabled.
846  (
847  $this->context->getUser()->isLoggedIn()
848  && $this->context->getUser()->requiresHTTPS()
849  )
850  )
851  ) {
852  $oldUrl = $request->getFullRequestURL();
853  $redirUrl = preg_replace( '#^http://#', 'https://', $oldUrl );
854 
855  // ATTENTION: This hook is likely to be removed soon due to overall design of the system.
856  if ( Hooks::run( 'BeforeHttpsRedirect', [ $this->context, &$redirUrl ] ) ) {
857  if ( $request->wasPosted() ) {
858  // This is weird and we'd hope it almost never happens. This
859  // means that a POST came in via HTTP and policy requires us
860  // redirecting to HTTPS. It's likely such a request is going
861  // to fail due to post data being lost, but let's try anyway
862  // and just log the instance.
863 
864  // @todo FIXME: See if we could issue a 307 or 308 here, need
865  // to see how clients (automated & browser) behave when we do
866  wfDebugLog( 'RedirectedPosts', "Redirected from HTTP to HTTPS: $oldUrl" );
867  }
868  // Setup dummy Title, otherwise OutputPage::redirect will fail
869  $title = Title::newFromText( 'REDIR', NS_MAIN );
870  $this->context->setTitle( $title );
871  // Since we only do this redir to change proto, always send a vary header
872  $output->addVaryHeader( 'X-Forwarded-Proto' );
873  $output->redirect( $redirUrl );
874  $output->output();
875 
876  return;
877  }
878  }
879 
880  if ( $title->canExist() && HTMLFileCache::useFileCache( $this->context ) ) {
881  // Try low-level file cache hit
883  if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
884  // Check incoming headers to see if client has this cached
885  $timestamp = $cache->cacheTimestamp();
886  if ( !$output->checkLastModified( $timestamp ) ) {
887  $cache->loadFromFileCache( $this->context );
888  }
889  // Do any stats increment/watchlist stuff, assuming user is viewing the
890  // latest revision (which should always be the case for file cache)
891  $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
892  // Tell OutputPage that output is taken care of
893  $output->disable();
894 
895  return;
896  }
897  }
898 
899  // Actually do the work of the request and build up any output
900  $this->performRequest();
901 
902  // GUI-ify and stash the page output in MediaWiki::doPreOutputCommit() while
903  // ChronologyProtector synchronizes DB positions or replicas across all datacenters.
904  $buffer = null;
905  $outputWork = function () use ( $output, &$buffer ) {
906  if ( $buffer === null ) {
907  $buffer = $output->output( true );
908  }
909 
910  return $buffer;
911  };
912 
913  // Now commit any transactions, so that unreported errors after
914  // output() don't roll back the whole DB transaction and so that
915  // we avoid having both success and error text in the response
916  $this->doPreOutputCommit( $outputWork );
917 
918  // Now send the actual output
919  print $outputWork();
920  }
921 
927  public function restInPeace( $mode = 'fast', $blocksHttpClient = true ) {
928  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
929  // Assure deferred updates are not in the main transaction
930  $lbFactory->commitMasterChanges( __METHOD__ );
931 
932  // Loosen DB query expectations since the HTTP client is unblocked
933  $trxProfiler = Profiler::instance()->getTransactionProfiler();
934  $trxProfiler->redefineExpectations(
935  $this->context->getRequest()->hasSafeMethod()
936  ? $this->config->get( 'TrxProfilerLimits' )['PostSend-GET']
937  : $this->config->get( 'TrxProfilerLimits' )['PostSend-POST'],
938  __METHOD__
939  );
940 
941  // Do any deferred jobs; preferring to run them now if a client will not wait on them
942  DeferredUpdates::doUpdates( $blocksHttpClient ? 'enqueue' : 'run' );
943 
944  // Now that everything specific to this request is done,
945  // try to occasionally run jobs (if enabled) from the queues
946  if ( $mode === 'normal' ) {
947  $this->triggerJobs();
948  }
949 
950  // Log profiling data, e.g. in the database or UDP
952 
953  // Commit and close up!
954  $lbFactory->commitMasterChanges( __METHOD__ );
955  $lbFactory->shutdown( $lbFactory::SHUTDOWN_NO_CHRONPROT );
956 
957  wfDebug( "Request ended normally\n" );
958  }
959 
968  public static function emitBufferedStatsdData(
970  ) {
971  if ( $config->get( 'StatsdServer' ) && $stats->hasData() ) {
972  try {
973  $statsdServer = explode( ':', $config->get( 'StatsdServer' ), 2 );
974  $statsdHost = $statsdServer[0];
975  $statsdPort = $statsdServer[1] ?? 8125;
976  $statsdSender = new SocketSender( $statsdHost, $statsdPort );
977  $statsdClient = new SamplingStatsdClient( $statsdSender, true, false );
978  $statsdClient->setSamplingRates( $config->get( 'StatsdSamplingRates' ) );
979  $statsdClient->send( $stats->getData() );
980 
981  $stats->clearData(); // empty buffer for the next round
982  } catch ( Exception $ex ) {
984  }
985  }
986  }
987 
993  public function triggerJobs() {
994  $jobRunRate = $this->config->get( 'JobRunRate' );
995  if ( $this->getTitle()->isSpecial( 'RunJobs' ) ) {
996  return; // recursion guard
997  } elseif ( $jobRunRate <= 0 || wfReadOnly() ) {
998  return;
999  }
1000 
1001  if ( $jobRunRate < 1 ) {
1002  $max = mt_getrandmax();
1003  if ( mt_rand( 0, $max ) > $max * $jobRunRate ) {
1004  return; // the higher the job run rate, the less likely we return here
1005  }
1006  $n = 1;
1007  } else {
1008  $n = intval( $jobRunRate );
1009  }
1010 
1011  $logger = LoggerFactory::getInstance( 'runJobs' );
1012 
1013  try {
1014  if ( $this->config->get( 'RunJobsAsync' ) ) {
1015  // Send an HTTP request to the job RPC entry point if possible
1016  $invokedWithSuccess = $this->triggerAsyncJobs( $n, $logger );
1017  if ( !$invokedWithSuccess ) {
1018  // Fall back to blocking on running the job(s)
1019  $logger->warning( "Jobs switched to blocking; Special:RunJobs disabled" );
1020  $this->triggerSyncJobs( $n, $logger );
1021  }
1022  } else {
1023  $this->triggerSyncJobs( $n, $logger );
1024  }
1025  } catch ( JobQueueError $e ) {
1026  // Do not make the site unavailable (T88312)
1028  }
1029  }
1030 
1035  private function triggerSyncJobs( $n, LoggerInterface $runJobsLogger ) {
1036  $trxProfiler = Profiler::instance()->getTransactionProfiler();
1037  $old = $trxProfiler->setSilenced( true );
1038  try {
1039  $runner = new JobRunner( $runJobsLogger );
1040  $runner->run( [ 'maxJobs' => $n ] );
1041  } finally {
1042  $trxProfiler->setSilenced( $old );
1043  }
1044  }
1045 
1051  private function triggerAsyncJobs( $n, LoggerInterface $runJobsLogger ) {
1052  // Do not send request if there are probably no jobs
1053  $group = JobQueueGroup::singleton();
1054  if ( !$group->queuesHaveJobs( JobQueueGroup::TYPE_DEFAULT ) ) {
1055  return true;
1056  }
1057 
1058  $query = [ 'title' => 'Special:RunJobs',
1059  'tasks' => 'jobs', 'maxjobs' => $n, 'sigexpiry' => time() + 5 ];
1060  $query['signature'] = SpecialRunJobs::getQuerySignature(
1061  $query, $this->config->get( 'SecretKey' ) );
1062 
1063  $errno = $errstr = null;
1064  $info = wfParseUrl( $this->config->get( 'CanonicalServer' ) );
1065  $host = $info ? $info['host'] : null;
1066  $port = 80;
1067  if ( isset( $info['scheme'] ) && $info['scheme'] == 'https' ) {
1068  $host = "tls://" . $host;
1069  $port = 443;
1070  }
1071  if ( isset( $info['port'] ) ) {
1072  $port = $info['port'];
1073  }
1074 
1075  Wikimedia\suppressWarnings();
1076  $sock = $host ? fsockopen(
1077  $host,
1078  $port,
1079  $errno,
1080  $errstr,
1081  // If it takes more than 100ms to connect to ourselves there is a problem...
1082  0.100
1083  ) : false;
1084  Wikimedia\restoreWarnings();
1085 
1086  $invokedWithSuccess = true;
1087  if ( $sock ) {
1088  $special = MediaWikiServices::getInstance()->getSpecialPageFactory()->
1089  getPage( 'RunJobs' );
1090  $url = $special->getPageTitle()->getCanonicalURL( $query );
1091  $req = (
1092  "POST $url HTTP/1.1\r\n" .
1093  "Host: {$info['host']}\r\n" .
1094  "Connection: Close\r\n" .
1095  "Content-Length: 0\r\n\r\n"
1096  );
1097 
1098  $runJobsLogger->info( "Running $n job(s) via '$url'" );
1099  // Send a cron API request to be performed in the background.
1100  // Give up if this takes too long to send (which should be rare).
1101  stream_set_timeout( $sock, 2 );
1102  $bytes = fwrite( $sock, $req );
1103  if ( $bytes !== strlen( $req ) ) {
1104  $invokedWithSuccess = false;
1105  $runJobsLogger->error( "Failed to start cron API (socket write error)" );
1106  } else {
1107  // Do not wait for the response (the script should handle client aborts).
1108  // Make sure that we don't close before that script reaches ignore_user_abort().
1109  $start = microtime( true );
1110  $status = fgets( $sock );
1111  $sec = microtime( true ) - $start;
1112  if ( !preg_match( '#^HTTP/\d\.\d 202 #', $status ) ) {
1113  $invokedWithSuccess = false;
1114  $runJobsLogger->error( "Failed to start cron API: received '$status' ($sec)" );
1115  }
1116  }
1117  fclose( $sock );
1118  } else {
1119  $invokedWithSuccess = false;
1120  $runJobsLogger->error( "Failed to start cron API (socket error $errno): $errstr" );
1121  }
1122 
1123  return $invokedWithSuccess;
1124  }
1125 }
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:123
DerivativeRequest
Similar to FauxRequest, but only fakes URL parameters and method (POST or GET) and use the base reque...
Definition: DerivativeRequest.php:34
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()
Definition: MediaWiki.php:567
Page
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition: Page.php:29
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:316
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:119
MediaWiki\emitBufferedStatsdData
static emitBufferedStatsdData(IBufferingStatsdDataFactory $stats, Config $config)
Send out any buffered statsd data according to sampling rules.
Definition: MediaWiki.php:968
PROTO_INTERNAL
const PROTO_INTERNAL
Definition: Defines.php:204
Profiler\instance
static instance()
Singleton.
Definition: Profiler.php:63
WikiMap\isCurrentWikiId
static isCurrentWikiId( $wikiId)
Definition: WikiMap.php:312
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:117
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:523
JobQueueGroup\TYPE_DEFAULT
const TYPE_DEFAULT
Definition: JobQueueGroup.php:47
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:137
WikiMap\getWikiFromUrl
static getWikiFromUrl( $url)
Definition: WikiMap.php:223
MediaWiki\main
main()
Definition: MediaWiki.php:793
NS_FILE
const NS_FILE
Definition: Defines.php:66
$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:124
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1171
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:83
MediaWiki\restInPeace
restInPeace( $mode='fast', $blocksHttpClient=true)
Ends this task peacefully.
Definition: MediaWiki.php:927
ContentHandler\getForTitle
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
Definition: ContentHandler.php:201
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') $wgTitle
Definition: api.php:58
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:30
Title\newMainPage
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:649
RedirectSpecialPage
Shortcut to construct a special page alias.
Definition: RedirectSpecialPage.php:29
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:1007
HttpError
Show an error that looks like an HTTP server error.
Definition: HttpError.php:30
ErrorPageError\report
report( $action=self::SEND_OUTPUT)
Definition: ErrorPageError.php:65
Action
Actions are things which can be done to pages (edit, delete, rollback, etc).
Definition: Action.php:39
MediaWiki\getChronProtStrategy
static getChronProtStrategy(ILBFactory $lbFactory, OutputPage $output)
Definition: MediaWiki.php:692
NS_MAIN
const NS_MAIN
Definition: Defines.php:60
MediaWiki\MediaWikiServices\getInstance
static getInstance()
Returns the global default instance of the top level service locator.
Definition: MediaWikiServices.php:138
FauxRequest\setRequestURL
setRequestURL( $url)
Definition: FauxRequest.php:150
ErrorPageError\STAGE_OUTPUT
const STAGE_OUTPUT
Definition: ErrorPageError.php:29
Config
Interface for configuration instances.
Definition: Config.php:28
Title\getCdnUrls
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition: Title.php:3504
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:49
wfParseUrl
wfParseUrl( $url)
parse_url() work-alike, but non-broken.
Definition: GlobalFunctions.php:793
Article\newFromWikiPage
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:192
IBufferingStatsdDataFactory\getData
getData()
Return the buffered data from the factory.
MWException
MediaWiki exception.
Definition: MWException.php:26
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:142
MediaWiki\performAction
performAction(Page $page, Title $requestTitle)
Perform one of the "standard" actions.
Definition: MediaWiki.php:476
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.".
MWExceptionHandler\handleException
static handleException( $e)
Exception handler which simulates the appropriate catch() handling:
Definition: MWExceptionHandler.php:193
MediaWiki\parseTitle
parseTitle()
Parse the request to get the Title object.
Definition: MediaWiki.php:68
MWExceptionHandler\rollbackMasterChangesAndLog
static rollbackMasterChangesAndLog( $e)
Roll back any open database transactions and log the stack trace of the exception.
Definition: MWExceptionHandler.php:139
MediaWiki
This class serves as a utility class for this extension.
PROTO_CURRENT
const PROTO_CURRENT
Definition: Defines.php:202
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:34
MediaWiki\triggerSyncJobs
triggerSyncJobs( $n, LoggerInterface $runJobsLogger)
Definition: MediaWiki.php:1035
MediaWiki\doPreOutputCommit
doPreOutputCommit(callable $postCommitWork=null)
Definition: MediaWiki.php:581
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
$output
$output
Definition: SyntaxHighlight.php:335
JobQueueError
Definition: JobQueueError.php:28
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:913
MediaWiki\triggerAsyncJobs
triggerAsyncJobs( $n, LoggerInterface $runJobsLogger)
Definition: MediaWiki.php:1051
PROTO_HTTPS
const PROTO_HTTPS
Definition: Defines.php:200
MessageCache\singleton
static singleton()
Get the singleton instance of this class.
Definition: MessageCache.php:114
Title\newFromTextThrow
static newFromTextThrow( $text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid,...
Definition: Title.php:353
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:993
WebResponse\disableForPostSend
static disableForPostSend()
Disable setters for post-send processing.
Definition: WebResponse.php:47
AjaxDispatcher
Object-Oriented Ajax functions.
Definition: AjaxDispatcher.php:37
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:596
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:48
MediaWiki\tryNormaliseRedirect
tryNormaliseRedirect(Title $title)
Handle redirects for uncanonical title requests.
Definition: MediaWiki.php:335
MediaWiki\__construct
__construct(IContextSource $context=null)
Definition: MediaWiki.php:53
Title\newFromURL
static newFromURL( $url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition: Title.php:404
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:431
IContextSource\getTitle
getTitle()
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
DeferredUpdates\doUpdates
static doUpdates( $mode='run', $stage=self::ALL)
Do any deferred updates and clear the list.
Definition: DeferredUpdates.php:139
IBufferingStatsdDataFactory
MediaWiki adaptation of StatsdDataFactory that provides buffering functionality.
Definition: IBufferingStatsdDataFactory.php:11
MediaWiki\performRequest
performRequest()
Performs the request.
Definition: MediaWiki.php:173
MediaWiki\$action
string $action
Cache what action this request is.
Definition: MediaWiki.php:48
MediaWiki\getAction
getAction()
Returns the name of the action that will be executed.
Definition: MediaWiki.php:153
Title
Represents a title within MediaWiki.
Definition: Title.php:42
$status
return $status
Definition: SyntaxHighlight.php:347
MediaWiki\initializeArticle
initializeArticle()
Initialize the main Article object for "standard" actions (view, etc) Create an Article object for th...
Definition: MediaWiki.php:395
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:25
DeferredUpdates\PRESEND
const PRESEND
Definition: DeferredUpdates.php:69
Wikimedia\Rdbms\ChronologyProtector
Helper class for mitigating DB replication lag in order to provide "session consistency".
Definition: ChronologyProtector.php:39
IContextSource\getConfig
getConfig()
Get the site configuration.
MediaWiki\$config
Config $config
Definition: MediaWiki.php:43
Wikimedia\Rdbms\DBConnectionError
Definition: DBConnectionError.php:26
MWExceptionRenderer\getHTML
static getHTML( $e)
If $wgShowExceptionDetails is true, return a HTML message with a backtrace to the error,...
Definition: MWExceptionRenderer.php:161
MediaWiki\doPostOutputShutdown
doPostOutputShutdown( $mode='normal')
This function does work that can be done after the user gets the HTTP response so they don't block on...
Definition: MediaWiki.php:745
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:1065
MediaWiki\$context
IContextSource $context
Definition: MediaWiki.php:38
JobRunner
Job queue runner utility methods.
Definition: JobRunner.php:39
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:27
MediaWiki\getUrlDomainDistance
static getUrlDomainDistance( $url)
Definition: MediaWiki.php:723
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:467
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
IContextSource\getOutput
getOutput()
Action\factory
static factory( $action, Page $page, IContextSource $context=null)
Get an appropriate Action subclass for the given action.
Definition: Action.php:97
BadTitleError
Show an error page on a badtitle.
Definition: BadTitleError.php:30
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:491
Wikimedia\Rdbms\ILBFactory
An interface for generating database load balancers.
Definition: ILBFactory.php:33
wfLogProfilingData
wfLogProfilingData()
Definition: GlobalFunctions.php:1086
MWExceptionHandler\logException
static logException( $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log an exception to the exception log (if enabled).
Definition: MWExceptionHandler.php:691