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