24use Psr\Log\LoggerInterface;
29use Liuggio\StatsdClient\Sender\SocketSender;
55 $context = RequestContext::getMain();
59 $this->config =
$context->getConfig();
69 $request = $this->context->getRequest();
70 $curid = $request->getInt(
'curid' );
71 $title = $request->getVal(
'title' );
72 $action = $request->getVal(
'action' );
74 if ( $request->getCheck(
'search' ) ) {
78 $ret = SpecialPage::getTitleFor(
'Search' );
81 $ret = Title::newFromID( $curid );
83 $ret = Title::newFromURL(
$title );
86 if ( !is_null( $ret ) && $ret->getNamespace() ==
NS_MEDIA ) {
87 $ret = Title::makeTitle(
NS_FILE, $ret->getDBkey() );
93 $contLang->hasVariants() && !is_null( $ret ) && $ret->getArticleID() == 0
95 $contLang->findVariantLink(
$title, $ret );
103 if ( $ret ===
null || !$ret->isSpecialPage() ) {
105 $oldid = $request->getInt(
'oldid' );
106 $oldid = $oldid ?: $request->getInt(
'diff' );
109 $rev = Revision::newFromId( $oldid );
110 $ret = $rev ? $rev->getTitle() : $ret;
116 && strval(
$title ) ===
''
117 && !$request->getCheck(
'curid' )
120 $ret = Title::newMainPage();
123 if ( $ret ===
null || ( $ret->getDBkey() ==
'' && !$ret->isExternal() ) ) {
126 Title::newFromTextThrow(
$title );
138 if ( !$this->context->hasTitle() ) {
140 $this->context->setTitle( $this->
parseTitle() );
142 $this->context->setTitle( SpecialPage::getTitleFor(
'Badtitle' ) );
145 return $this->context->getTitle();
154 if ( $this->action ===
null ) {
155 $this->action = Action::getActionName( $this->context );
158 return $this->action;
176 $request = $this->context->getRequest();
177 $requestTitle =
$title = $this->context->getTitle();
178 $output = $this->context->getOutput();
179 $user = $this->context->getUser();
181 if ( $request->getVal(
'printable' ) ===
'yes' ) {
182 $output->setPrintable();
186 Hooks::run(
'BeforeInitialize', [ &
$title, &$unused, &$output, &$user, $request, $this ] );
190 ||
$title->isSpecial(
'Badtitle' )
192 $this->context->setTitle( SpecialPage::getTitleFor(
'Badtitle' ) );
204 $permErrors =
$title->isSpecial(
'RunJobs' )
206 :
$title->getUserPermissionsErrors(
'read', $user );
207 if ( count( $permErrors ) ) {
217 $badTitle = SpecialPage::getTitleFor(
'Badtitle' );
218 $this->context->setTitle( $badTitle );
225 if (
$title->isExternal() ) {
226 $rdfrom = $request->getVal(
'rdfrom' );
228 $url =
$title->getFullURL( [
'rdfrom' => $rdfrom ] );
230 $query = $request->getValues();
231 unset( $query[
'title'] );
232 $url =
$title->getFullURL( $query );
235 if ( !preg_match(
'/^' . preg_quote( $this->config->get(
'Server' ),
'/' ) .
'/', $url )
239 $output->redirect( $url, 301 );
241 $this->context->setTitle( SpecialPage::getTitleFor(
'Badtitle' ) );
254 if (
$title->isSpecialPage() ) {
255 $specialPage = $spFactory->getPage(
$title->getDBkey() );
257 $specialPage->setContext( $this->context );
258 if ( $this->config->get(
'HideIdentifiableRedirects' )
259 && $specialPage->personallyIdentifiableTarget()
261 list( , $subpage ) = $spFactory->resolveAlias(
$title->getDBkey() );
262 $target = $specialPage->getRedirect( $subpage );
264 if ( $target instanceof
Title ) {
265 if ( $target->isExternal() ) {
267 $target = SpecialPage::getTitleFor(
269 'force/' . $target->getPrefixedDBkey()
273 $query = $specialPage->getRedirectQuery( $subpage ) ?: [];
275 $request->setRequestURL( $this->context->getRequest()->getRequestURL() );
276 $this->context->setRequest( $request );
278 $this->context->getOutput()->lowerCdnMaxage( 0 );
279 $this->context->setTitle( $target );
282 $this->action =
null;
284 $output->addJsConfigVars( [
285 'wgInternalRedirectTargetUrl' => $target->getFullURL( $query ),
287 $output->addModules(
'mediawiki.action.view.redirect' );
294 if (
$title->isSpecialPage() ) {
296 $spFactory->executePath(
$title, $this->context );
301 if ( is_object( $article ) ) {
303 } elseif ( is_string( $article ) ) {
304 $output->redirect( $article );
306 throw new MWException(
"Shouldn't happen: MediaWiki::initializeArticle()"
307 .
" returned neither an object nor a URL" );
336 $request = $this->context->getRequest();
337 $output = $this->context->getOutput();
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 ] )
349 if ( $this->config->get(
'MainPageIsDomainRoot' ) && $request->getRequestURL() ===
'/' ) {
353 if (
$title->isSpecialPage() ) {
355 resolveAlias(
$title->getDBkey() );
357 $title = SpecialPage::getTitleFor( $name, $subpage );
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";
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 " .
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 " .
384 $output->setCdnMaxage( 1200 );
385 $output->redirect( $targetUrl,
'301' );
396 $title = $this->context->getTitle();
397 if ( $this->context->canUseWikiPage() ) {
401 $page = $this->context->getWikiPage();
405 $page = WikiPage::factory(
$title );
406 $this->context->setWikiPage( $page );
407 wfWarn(
"RequestContext::canUseWikiPage() returned false" );
414 if ( !ContentHandler::getForTitle(
$title )->supportsRedirects() ) {
418 $request = $this->context->getRequest();
422 $action = $request->getVal(
'action',
'view' );
425 && !$request->getVal(
'oldid' )
426 && !$request->getVal(
'diff' )
427 && $request->getVal(
'redirect' ) !=
'no'
429 && !( is_object(
$file ) &&
$file->exists() && !
$file->getRedirected() )
432 $ignoreRedirect = $target =
false;
434 Hooks::run(
'InitializeArticleMaybeRedirect',
435 [ &
$title, &$request, &$ignoreRedirect, &$target, &$article ] );
436 $page = $article->getPage();
440 if ( !$ignoreRedirect && ( $target || $page->isRedirect() ) ) {
442 $target = $target ?: $page->followRedirect();
443 if ( is_string( $target ) && !$this->config->get(
'DisableHardRedirects' ) ) {
447 if ( is_object( $target ) ) {
449 $rpage = WikiPage::factory( $target );
450 $rpage->loadPageData();
451 if ( $rpage->exists() || ( is_object(
$file ) && !
$file->isLocal() ) ) {
453 $rarticle->setRedirectedFrom(
$title );
455 $article = $rarticle;
456 $this->context->setTitle( $target );
457 $this->context->setWikiPage( $article->getPage() );
462 $this->context->setTitle( $article->getTitle() );
463 $this->context->setWikiPage( $article->getPage() );
477 $request = $this->context->getRequest();
478 $output = $this->context->getOutput();
479 $title = $this->context->getTitle();
480 $user = $this->context->getUser();
482 if ( !Hooks::run(
'MediaWikiPerformAction',
483 [ $output, $page,
$title, $user, $request, $this ] )
489 $action = Action::factory( $act, $page, $this->context );
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();
500 # Let CDN cache things if we can purge them.
501 if ( $this->config->get(
'UseCdn' ) &&
508 $output->setCdnMaxage( $this->config->get(
'CdnMaxAge' ) );
516 $output->setStatusCode( 404 );
517 $output->showErrorPage(
'nosuchaction',
'nosuchactiontext' );
529 $out = $this->context->getOutput();
539 }
catch ( Exception $e ) {
546 in_array(
$action, [
'view',
'history' ],
true ) &&
551 if (
$cache->isCached() ) {
558 MWExceptionHandler::handleException( $e );
559 }
catch ( Error $e ) {
561 MWExceptionHandler::handleException( $e );
570 $name = $this->context->getUser()->getName();
571 $services->getDBLoadBalancerFactory()->setAgentName(
572 mb_strlen( $name ) > 15 ? mb_substr( $name, 0, 15 ) .
'...' : $name
582 self::preOutputCommit( $this->context, $postCommitWork );
605 ignore_user_abort(
true );
608 $lbFactory->commitMasterChanges(
611 [
'maxWriteDuration' =>
$config->
get(
'MaxUserDBWriteDuration' ) ]
613 wfDebug( __METHOD__ .
': primary transaction round committed' );
616 DeferredUpdates::doUpdates(
'run', DeferredUpdates::PRESEND );
617 wfDebug( __METHOD__ .
': pre-send deferred updates completed' );
619 $request->getSession()->save();
620 wfDebug( __METHOD__ .
': session changes committed' );
626 list( $flags, $strategy ) = self::getChronProtStrategy( $lbFactory, $output );
630 $lbFactory->shutdown( $flags, $postCommitWork, $cpIndex, $cpClientId );
631 wfDebug( __METHOD__ .
': LBFactory shutdown completed' );
633 $allowHeaders = !( $output->isDisabled() || headers_sent() );
634 if ( $cpIndex > 0 ) {
635 if ( $allowHeaders ) {
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 );
643 if ( $strategy ===
'cookie+url' ) {
644 if ( $output->getRedirect() ) {
645 $safeUrl = $lbFactory->appendShutdownCPIndexAsQuery(
646 $output->getRedirect(),
649 $output->redirect( $safeUrl );
651 $e =
new LogicException(
"No redirect; cannot append cpPosIndex parameter." );
652 MWExceptionHandler::logException( $e );
657 if ( $allowHeaders ) {
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 );
670 if ( $lbFactory->laggedReplicaUsed() ) {
672 $output->lowerCdnMaxage( $maxAge );
673 $request->response()->header(
"X-Database-Lagged: true" );
675 "Lagged DB used; CDN cache TTL limited to $maxAge seconds" );
680 $maxAge =
$config->
get(
'CdnMaxageSubstitute' );
681 $output->lowerCdnMaxage( $maxAge );
682 $request->response()->header(
"X-Response-Substitute: true" );
698 $flags = $lbFactory::SHUTDOWN_CHRONPROT_SYNC;
699 $strategy =
'cookie+sync';
701 $allowHeaders = !( $output->
isDisabled() || headers_sent() );
706 $domainDistance = self::getUrlDomainDistance( $output->
getRedirect() );
707 if ( $domainDistance ===
'local' && $allowHeaders ) {
708 $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
709 $strategy =
'cookie';
710 } elseif ( $domainDistance ===
'remote' ) {
711 $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
712 $strategy =
'cookie+url';
716 return [ $flags, $strategy ];
724 $clusterWiki = WikiMap::getWikiFromUrl( $url );
725 if ( WikiMap::isCurrentWikiId( $clusterWiki ) ) {
728 if ( $clusterWiki !==
false ) {
747 $timing = $this->context->getTiming();
748 $timing->mark(
'requestShutdown' );
753 Profiler::instance()->logDataPageOutputOnly();
754 }
catch ( Exception $e ) {
756 MWExceptionHandler::logException( $e );
760 WebResponse::disableForPostSend();
762 $blocksHttpClient =
true;
764 $callback =
function () use ( $mode, &$blocksHttpClient ) {
767 }
catch ( Exception $e ) {
769 MWExceptionHandler::rollbackMasterChangesAndLog( $e );
773 if ( function_exists(
'register_postsend_function' ) ) {
775 register_postsend_function( $callback );
777 $blocksHttpClient =
false;
779 if ( function_exists(
'fastcgi_finish_request' ) ) {
780 fastcgi_finish_request();
782 $blocksHttpClient =
false;
786 ignore_user_abort(
true );
796 $output = $this->context->getOutput();
797 $request = $this->context->getRequest();
800 if ( $request->getVal(
'action' ) ===
'ajax' ) {
802 $title = Title::makeTitle(
NS_SPECIAL,
'Badtitle/performing an AJAX call in '
805 $this->context->setTitle(
$title );
809 $dispatcher->performAction( $this->context->getUser() );
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__ );
827 $trxProfiler->setExpectations( $trxLimits[
'POST'], __METHOD__ );
837 if (
$cache->isCacheGood( ) ) {
839 $timestamp =
$cache->cacheTimestamp();
840 if ( !$output->checkLastModified( $timestamp ) ) {
841 $cache->loadFromFileCache( $this->context );
845 $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
859 $outputWork =
function () use ( $output, &$buffer ) {
860 if ( $buffer ===
null ) {
861 $buffer = $output->output(
true );
883 $request = $this->context->getRequest();
886 if ( $request->getProtocol() !==
'http' ) {
890 $force = $this->config->get(
'ForceHTTPS' );
896 throw new RuntimeException(
'$wgForceHTTPS is true but the server is not HTTPS' );
907 return $request->getSession()->shouldForceHTTPS() ||
909 $request->getCookie(
'forceHTTPS',
'' ) ||
912 $this->context->getUser()->isLoggedIn()
913 && $this->context->getUser()->requiresHTTPS()
931 $request = $this->context->getRequest();
932 $oldUrl = $request->getFullRequestURL();
933 $redirUrl = preg_replace(
'#^http://#',
'https://', $oldUrl );
936 if ( !Hooks::run(
'BeforeHttpsRedirect', [ $this->context, &$redirUrl ] ) ) {
940 if ( $request->wasPosted() ) {
949 wfDebugLog(
'RedirectedPosts',
"Redirected from HTTP to HTTPS: $oldUrl" );
953 $this->context->setTitle(
$title );
955 $output = $this->context->getOutput();
956 $output->addVaryHeader(
'X-Forwarded-Proto' );
957 $output->redirect( $redirUrl );
968 public function restInPeace( $mode =
'fast', $blocksHttpClient =
true ) {
971 $lbFactory->commitMasterChanges( __METHOD__ );
974 $trxProfiler = Profiler::instance()->getTransactionProfiler();
975 $trxProfiler->redefineExpectations(
976 $this->context->getRequest()->hasSafeMethod()
977 ? $this->config->get(
'TrxProfilerLimits' )[
'PostSend-GET']
978 : $this->config->get(
'TrxProfilerLimits' )[
'PostSend-POST'],
983 DeferredUpdates::doUpdates( $blocksHttpClient ?
'enqueue' :
'run' );
987 if ( $mode ===
'normal' ) {
995 $lbFactory->commitMasterChanges( __METHOD__ );
996 $lbFactory->shutdown( $lbFactory::SHUTDOWN_NO_CHRONPROT );
998 wfDebug(
"Request ended normally\n" );
1012 if ( $config->
get(
'StatsdServer' ) && $stats->
hasData() ) {
1014 $statsdServer = explode(
':', $config->
get(
'StatsdServer' ), 2 );
1015 $statsdHost = $statsdServer[0];
1016 $statsdPort = $statsdServer[1] ?? 8125;
1017 $statsdSender =
new SocketSender( $statsdHost, $statsdPort );
1019 $statsdClient->setSamplingRates( $config->
get(
'StatsdSamplingRates' ) );
1020 $statsdClient->send( $stats->
getData() );
1023 }
catch ( Exception $ex ) {
1024 MWExceptionHandler::logException( $ex );
1035 $jobRunRate = $this->config->get(
'JobRunRate' );
1036 if ( $this->
getTitle()->isSpecial(
'RunJobs' ) ) {
1038 } elseif ( $jobRunRate <= 0 ||
wfReadOnly() ) {
1042 if ( $jobRunRate < 1 ) {
1043 $max = mt_getrandmax();
1044 if ( mt_rand( 0, $max ) > $max * $jobRunRate ) {
1049 $n = intval( $jobRunRate );
1052 $logger = LoggerFactory::getInstance(
'runJobs' );
1055 if ( $this->config->get(
'RunJobsAsync' ) ) {
1058 if ( !$invokedWithSuccess ) {
1060 $logger->warning(
"Jobs switched to blocking; Special:RunJobs disabled" );
1068 MWExceptionHandler::logException( $e );
1077 $trxProfiler = Profiler::instance()->getTransactionProfiler();
1078 $old = $trxProfiler->setSilenced(
true );
1080 $runner =
new JobRunner( $runJobsLogger );
1081 $runner->run( [
'maxJobs' => $n ] );
1083 $trxProfiler->setSilenced( $old );
1094 $group = JobQueueGroup::singleton();
1095 if ( !$group->queuesHaveJobs( JobQueueGroup::TYPE_DEFAULT ) ) {
1099 $query = [
'title' =>
'Special:RunJobs',
1100 'tasks' =>
'jobs',
'maxjobs' => $n,
'sigexpiry' => time() + 5 ];
1102 $query, $this->config->get(
'SecretKey' ) );
1104 $errno = $errstr =
null;
1105 $info =
wfParseUrl( $this->config->get(
'CanonicalServer' ) );
1106 $host = $info ? $info[
'host'] :
null;
1108 if ( isset( $info[
'scheme'] ) && $info[
'scheme'] ==
'https' ) {
1109 $host =
"tls://" . $host;
1112 if ( isset( $info[
'port'] ) ) {
1113 $port = $info[
'port'];
1116 Wikimedia\suppressWarnings();
1117 $sock = $host ? fsockopen(
1125 Wikimedia\restoreWarnings();
1127 $invokedWithSuccess =
true;
1130 getPage(
'RunJobs' );
1131 $url = $special->getPageTitle()->getCanonicalURL( $query );
1133 "POST $url HTTP/1.1\r\n" .
1134 "Host: {$info['host']}\r\n" .
1135 "Connection: Close\r\n" .
1136 "Content-Length: 0\r\n\r\n"
1139 $runJobsLogger->info(
"Running $n job(s) via '$url'" );
1142 stream_set_timeout( $sock, 2 );
1143 $bytes = fwrite( $sock, $req );
1144 if ( $bytes !== strlen( $req ) ) {
1145 $invokedWithSuccess =
false;
1146 $runJobsLogger->error(
"Failed to start cron API (socket write error)" );
1150 $start = microtime(
true );
1151 $status = fgets( $sock );
1152 $sec = microtime(
true ) - $start;
1153 if ( !preg_match(
'#^HTTP/\d\.\d 202 #', $status ) ) {
1154 $invokedWithSuccess =
false;
1155 $runJobsLogger->error(
"Failed to start cron API: received '$status' ($sec)" );
1160 $invokedWithSuccess =
false;
1161 $runJobsLogger->error(
"Failed to start cron API (socket error $errno): $errstr" );
1164 return $invokedWithSuccess;
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfParseUrl( $url)
parse_url() work-alike, but non-broken.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
if(! $wgRequest->checkUrlExtension()) if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') $wgTitle
Actions are things which can be done to pages (edit, delete, rollback, etc).
Object-Oriented Ajax functions.
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Show an error page on a badtitle.
Similar to FauxRequest, but only fakes URL parameters and method (POST or GET) and use the base reque...
An error page which can definitely be safely rendered using the OutputPage.
report( $action=self::SEND_OUTPUT)
Page view caching in the file system.
static useFileCache(IContextSource $context, $mode=self::MODE_NORMAL)
Check if pages can be cached for this request/user.
Show an error that looks like an HTTP server error.
Job queue runner utility methods.
static getHTML( $e)
If $wgShowExceptionDetails is true, return a HTML message with a backtrace to the error,...
static singleton()
Get the singleton instance of this class.
This is one of the Core classes and should be read at least once by any new developers.
getRedirect()
Get the URL to redirect to, or an empty string if not redirect URL set.
isDisabled()
Return whether the output will be completely disabled.
Show an error when a user tries to do something they do not have the necessary permissions for.
Shortcut to construct a special page alias.
A statsd client that applies the sampling rate to the data items before sending them.
static getQuerySignature(array $query, $secretKey)
Represents a title within MediaWiki.
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Special handling for file pages.
while(( $__line=Maintenance::readconsole()) !==false) print
Interface for configuration instances.
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
MediaWiki adaptation of StatsdDataFactory that provides buffering functionality.
hasData()
Check whether this data factory has any buffered data.
clearData()
Clear all buffered data from the factory.
getData()
Return the buffered data from the factory.
Interface for objects which can provide a MediaWiki context on request.
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.