SpecialRedirect: Don't pass null to explode
[mediawiki.git] / includes / Setup.php
blob2b66d96e19b7208c837239b7f292b5f9243ee0a0
1 <?php
2 /**
3 * The setup for all MediaWiki processes (both web-based and CLI).
5 * The entry point (such as WebStart.php and doMaintenance.php) has these responsibilities:
6 * - The entry point MUST:
7 * - define the 'MEDIAWIKI' constant.
8 * - The entry point SHOULD:
9 * - define the 'MW_ENTRY_POINT' constant.
10 * - display an error if MW_CONFIG_CALLBACK is not defined and the
11 * file specified in MW_CONFIG_FILE (or the LocalSettings.php default location)
12 * does not exist. The error should either be sent before and instead
13 * of the Setup.php inclusion, or (if it needs classes and dependencies
14 * from core) the error can be displayed via a MW_CONFIG_CALLBACK,
15 * which must then abort the process to prevent the rest of Setup.php
16 * from executing.
18 * This file does:
19 * - run-time environment checks,
20 * - define MW_INSTALL_PATH, $IP, and $wgBaseDirectory,
21 * - load autoloaders, constants, default settings, and global functions,
22 * - load the site configuration (e.g. LocalSettings.php),
23 * - load the enabled extensions (via ExtensionRegistry),
24 * - trivial expansion of site configuration defaults and shortcuts
25 * (no calls to MediaWikiServices or other parts of MediaWiki),
26 * - initialization of:
27 * - PHP run-time (setlocale, memory limit, default date timezone)
28 * - the debug logger (MWDebug)
29 * - the service container (MediaWikiServices)
30 * - the exception handler (MWExceptionHandler)
31 * - the session manager (SessionManager)
32 * - complex expansion of site configuration defaults (those that require
33 * calling into MediaWikiServices, global functions, or other classes.).
35 * This program is free software; you can redistribute it and/or modify
36 * it under the terms of the GNU General Public License as published by
37 * the Free Software Foundation; either version 2 of the License, or
38 * (at your option) any later version.
40 * This program is distributed in the hope that it will be useful,
41 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
43 * GNU General Public License for more details.
45 * You should have received a copy of the GNU General Public License along
46 * with this program; if not, write to the Free Software Foundation, Inc.,
47 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
48 * http://www.gnu.org/copyleft/gpl.html
50 * @file
53 // phpcs:disable MediaWiki.Usage.DeprecatedGlobalVariables
54 use MediaWiki\HeaderCallback;
55 use MediaWiki\Logger\LoggerFactory;
56 use MediaWiki\MainConfigNames;
57 use MediaWiki\MainConfigSchema;
58 use MediaWiki\MediaWikiServices;
59 use MediaWiki\Settings\Config\GlobalConfigBuilder;
60 use MediaWiki\Settings\Config\PhpIniSink;
61 use MediaWiki\Settings\DynamicDefaultValues;
62 use MediaWiki\Settings\LocalSettingsLoader;
63 use MediaWiki\Settings\SettingsBuilder;
64 use MediaWiki\Settings\Source\PhpSettingsSource;
65 use MediaWiki\Settings\Source\ReflectionSchemaSource;
66 use MediaWiki\Settings\WikiFarmSettingsLoader;
67 use Psr\Log\LoggerInterface;
68 use Wikimedia\RequestTimeout\RequestTimeout;
70 /**
71 * Environment checks
73 * These are inline checks done before we include any source files,
74 * and thus these conditions may be assumed by all source code.
77 // This file must be included from a valid entry point (e.g. WebStart.php, Maintenance.php)
78 if ( !defined( 'MEDIAWIKI' ) ) {
79 exit( 1 );
82 // PHP must not be configured to overload mbstring functions. (T5782, T122807)
83 // This was deprecated by upstream in PHP 7.2, likely to be removed in PHP 8.0.
84 if ( ini_get( 'mbstring.func_overload' ) ) {
85 die( 'MediaWiki does not support installations where mbstring.func_overload is non-zero.' );
88 // The MW_ENTRY_POINT constant must always exists, to make it safe to access.
89 // For compat, we do support older and custom MW entrypoints that don't set this,
90 // in which case we assign a default here.
91 if ( !defined( 'MW_ENTRY_POINT' ) ) {
92 /**
93 * The entry point, which may be either the script filename without the
94 * file extension, or "cli" for maintenance scripts, or "unknown" for any
95 * entry point that does not set the constant.
97 define( 'MW_ENTRY_POINT', 'unknown' );
100 // The $IP variable is defined for use by LocalSettings.php.
101 // It is made available as a global variable for backwards compatibility.
103 // Source code should instead use the MW_INSTALL_PATH constant, or the
104 // MainConfigNames::BaseDirectory setting. The BaseDirectory setting is set further
105 // down in Setup.php to the value of MW_INSTALL_PATH.
106 global $IP;
107 $IP = $IP = wfDetectInstallPath(); // ensure MW_INSTALL_PATH is defined
110 * Pre-config setup: Before loading LocalSettings.php
112 * These are changes and additions to runtime that don't vary on site configuration.
114 require_once MW_INSTALL_PATH . '/includes/AutoLoader.php';
115 require_once MW_INSTALL_PATH . '/includes/Defines.php';
117 // Assert that composer dependencies were successfully loaded
118 if ( !interface_exists( LoggerInterface::class ) ) {
119 $message = (
120 'MediaWiki requires the <a href="https://github.com/php-fig/log">PSR-3 logging ' .
121 "library</a> to be present. This library is not embedded directly in MediaWiki's " .
122 "git repository and must be installed separately by the end user.\n\n" .
123 'Please see the <a href="https://www.mediawiki.org/wiki/Download_from_Git' .
124 '#Fetch_external_libraries">instructions for installing libraries</a> on mediawiki.org ' .
125 'for help on installing the required components.'
127 echo $message;
128 trigger_error( $message, E_USER_ERROR );
131 // Set $wgCommandLineMode to false if it wasn't set to true.
132 $wgCommandLineMode = $wgCommandLineMode ?? false;
135 * $wgConf hold the site configuration.
136 * Not used for much in a default install.
137 * @since 1.5
139 $wgConf = new SiteConfiguration;
141 $wgAutoloadClasses = $wgAutoloadClasses ?? [];
143 $wgSettings = new SettingsBuilder(
144 MW_INSTALL_PATH,
145 ExtensionRegistry::getInstance(),
146 new GlobalConfigBuilder( 'wg' ),
147 new PhpIniSink()
150 if ( defined( 'MW_USE_CONFIG_SCHEMA_CLASS' ) ) {
151 // Load config schema from MainConfigSchema. Useful for running scripts that
152 // generate other representations of the config schema. This is slow, so it
153 // should not be used for serving web traffic.
154 $wgSettings->load( new ReflectionSchemaSource( MainConfigSchema::class ) );
155 } elseif ( getenv( 'MW_USE_LEGACY_DEFAULT_SETTINGS' ) || defined( 'MW_USE_LEGACY_DEFAULT_SETTINGS' ) ) {
156 // Load the old DefaultSettings.php file. Should be removed in 1.39. See T300129.
157 require_once MW_INSTALL_PATH . '/includes/DefaultSettings.php';
159 // This is temporary until we no longer need this mode.
160 // TODO: delete config-merge-strategies.php when this code is removed.
161 $wgSettings->load( new PhpSettingsSource( MW_INSTALL_PATH . '/includes/config-merge-strategies.php' ) );
162 } else {
163 $wgSettings->load( new PhpSettingsSource( MW_INSTALL_PATH . '/includes/config-schema.php' ) );
166 require_once MW_INSTALL_PATH . '/includes/GlobalFunctions.php';
168 HeaderCallback::register();
170 // Set the encoding used by PHP for reading HTTP input, and writing output.
171 // This is also the default for mbstring functions.
172 mb_internal_encoding( 'UTF-8' );
175 * Load LocalSettings.php
178 // Initialize some config settings with dynamic defaults, and
179 // make default settings available in globals for use in LocalSettings.php.
180 $wgSettings->putConfigValues( [
181 MainConfigNames::BaseDirectory => MW_INSTALL_PATH,
182 MainConfigNames::ExtensionDirectory => MW_INSTALL_PATH . '/extensions',
183 MainConfigNames::StyleDirectory => MW_INSTALL_PATH . '/skins',
184 MainConfigNames::ServiceWiringFiles => [ MW_INSTALL_PATH . '/includes/ServiceWiring.php' ],
185 'Version' => MW_VERSION,
186 ] );
187 $wgSettings->apply();
189 // $wgSettings->apply() puts all configuration into global variables.
190 // If we are not in global scope, make all relevant globals available
191 // in this file's scope as well.
192 $wgScopeTest = 'MediaWiki Setup.php scope test';
193 if ( !isset( $GLOBALS['wgScopeTest'] ) || $GLOBALS['wgScopeTest'] !== $wgScopeTest ) {
194 foreach ( $wgSettings->getConfigSchema()->getDefinedKeys() as $key ) {
195 $var = "wg$key";
196 // phpcs:ignore MediaWiki.NamingConventions.ValidGlobalName.allowedPrefix
197 global $$var;
199 unset( $key, $var );
201 unset( $wgScopeTest );
203 if ( defined( 'MW_CONFIG_CALLBACK' ) ) {
204 call_user_func( MW_CONFIG_CALLBACK, $wgSettings );
205 } else {
206 wfDetectLocalSettingsFile( MW_INSTALL_PATH );
208 if ( getenv( 'MW_USE_LOCAL_SETTINGS_LOADER' ) ) {
209 // NOTE: This will not work for configuration variables that use a prefix
210 // other than "wg".
211 $localSettingsLoader = new LocalSettingsLoader( $wgSettings, MW_INSTALL_PATH );
212 $localSettingsLoader->loadLocalSettingsFile( MW_CONFIG_FILE );
213 unset( $localSettingsLoader );
214 } else {
215 if ( str_ends_with( MW_CONFIG_FILE, '.php' ) ) {
216 // make defaults available as globals
217 $wgSettings->apply();
218 require_once MW_CONFIG_FILE;
219 } else {
220 $wgSettings->loadFile( MW_CONFIG_FILE );
225 // Make settings loaded by LocalSettings.php available in globals for use here
226 $wgSettings->apply();
229 * Customization point after all loading (constants, functions, classes,
230 * LocalSettings). Specifically, this is before usage of
231 * settings, before instantiation of Profiler (and other singletons), and
232 * before any setup functions or hooks run.
235 if ( defined( 'MW_SETUP_CALLBACK' ) ) {
236 call_user_func( MW_SETUP_CALLBACK, $wgSettings );
237 // Make any additional settings available in globals for use here
238 $wgSettings->apply();
241 // If in a wiki-farm, load site-specific settings
242 if ( $wgSettings->getConfig()->get( MainConfigNames::WikiFarmSettingsDirectory ) ) {
243 $wikiFarmSettingsLoader = new WikiFarmSettingsLoader( $wgSettings );
244 $wikiFarmSettingsLoader->loadWikiFarmSettings();
245 unset( $wikiFarmSettingsLoader );
248 // Apply dynamic defaults declared in config schema callbacks.
249 $dynamicDefaults = new DynamicDefaultValues( $wgSettings->getConfigSchema() );
250 $dynamicDefaults->applyDynamicDefaults( $wgSettings->getConfigBuilder() );
252 // Make updated config available in global scope.
253 $wgSettings->apply();
255 // Apply dynamic defaults implemented in SetupDynamicConfig.php.
256 // Ideally, all logic in SetupDynamicConfig would be converted to
257 // callbacks in the config schema.
258 require __DIR__ . '/SetupDynamicConfig.php';
260 // All settings should be loaded now.
261 $wgSettings->finalize();
262 if ( $wgBaseDirectory !== MW_INSTALL_PATH ) {
263 throw new FatalError(
264 '$wgBaseDirectory must not be modified in settings files! ' .
265 'Use the MW_INSTALL_PATH environment variable to override the installation root directory.'
269 // Start time limit
270 if ( $wgRequestTimeLimit && !$wgCommandLineMode ) {
271 RequestTimeout::singleton()->setWallTimeLimit( $wgRequestTimeLimit );
275 * Load queued extensions
278 ExtensionRegistry::getInstance()->loadFromQueue();
279 // Don't let any other extensions load
280 ExtensionRegistry::getInstance()->finish();
282 // Set an appropriate locale (T291234)
283 // setlocale() will return the locale name actually set.
284 // The putenv() is meant to propagate the choice of locale to shell commands
285 // so that they will interpret UTF-8 correctly. If you have a problem with a
286 // shell command and need to send a special locale, you can override the locale
287 // with Command::environment().
288 putenv( "LC_ALL=" . setlocale( LC_ALL, 'C.UTF-8', 'C' ) );
290 MWDebug::setup();
292 // Enable the global service locator.
293 // Trivial expansion of site configuration should go before this point.
294 // Any non-trivial expansion that requires calling into MediaWikiServices or other parts of MW.
295 MediaWikiServices::allowGlobalInstance();
297 // Define a constant that indicates that the bootstrapping of the service locator
298 // is complete.
299 define( 'MW_SERVICE_BOOTSTRAP_COMPLETE', 1 );
301 MWExceptionRenderer::setShowExceptionDetails( $wgShowExceptionDetails );
302 MWExceptionHandler::installHandler( $wgLogExceptionBacktrace, $wgPropagateErrors );
304 // Non-trivial validation of: $wgServer
305 // The FatalError page only renders cleanly after MWExceptionHandler is installed.
306 if ( $wgServer === false ) {
307 // T30798: $wgServer must be explicitly set
308 throw new FatalError(
309 '$wgServer must be set in LocalSettings.php. ' .
310 'See <a href="https://www.mediawiki.org/wiki/Manual:$wgServer">' .
311 'https://www.mediawiki.org/wiki/Manual:$wgServer</a>.'
315 // Non-trivial expansion of: $wgCanonicalServer, $wgServerName.
316 // These require calling global functions.
317 // Also here are other settings that further depend on these two.
318 if ( $wgCanonicalServer === false ) {
319 $wgCanonicalServer = wfExpandUrl( $wgServer, PROTO_HTTP );
321 $wgVirtualRestConfig['global']['domain'] = $wgCanonicalServer;
323 $serverParts = wfParseUrl( $wgCanonicalServer );
324 if ( $wgServerName !== false ) {
325 wfWarn( '$wgServerName should be derived from $wgCanonicalServer, '
326 . 'not customized. Overwriting $wgServerName.' );
328 $wgServerName = $serverParts['host'];
329 unset( $serverParts );
331 // $wgEmergencyContact and $wgPasswordSender may be false or empty string (T104142)
332 if ( !$wgEmergencyContact ) {
333 $wgEmergencyContact = 'wikiadmin@' . $wgServerName;
335 if ( !$wgPasswordSender ) {
336 $wgPasswordSender = 'apache@' . $wgServerName;
338 if ( !$wgNoReplyAddress ) {
339 $wgNoReplyAddress = $wgPasswordSender;
342 // Non-trivial expansion of: $wgSecureLogin
343 // (due to calling wfWarn).
344 if ( $wgSecureLogin && substr( $wgServer, 0, 2 ) !== '//' ) {
345 $wgSecureLogin = false;
346 wfWarn( 'Secure login was enabled on a server that only supports '
347 . 'HTTP or HTTPS. Disabling secure login.' );
350 // Now that GlobalFunctions is loaded, set defaults that depend on it.
351 if ( $wgTmpDirectory === false ) {
352 $wgTmpDirectory = wfTempDir();
355 if ( $wgSharedDB && $wgSharedTables ) {
356 // Apply $wgSharedDB table aliases for the local LB (all non-foreign DB connections)
357 MediaWikiServices::getInstance()->getDBLoadBalancer()->setTableAliases(
358 array_fill_keys(
359 $wgSharedTables,
361 'dbname' => $wgSharedDB,
362 'schema' => $wgSharedSchema,
363 'prefix' => $wgSharedPrefix
369 // Raise the memory limit if it's too low
370 // NOTE: This use wfDebug, and must remain after the MWDebug::setup() call.
371 wfMemoryLimit( $wgMemoryLimit );
373 // Explicit globals, so this works with bootstrap.php
374 global $wgRequest, $wgInitialSessionId;
376 // Initialize the request object in $wgRequest
377 $wgRequest = RequestContext::getMain()->getRequest(); // BackCompat
379 // Make sure that object caching does not undermine the ChronologyProtector improvements
380 if ( $wgRequest->getCookie( 'UseDC', '' ) === 'master' ) {
381 // The user is pinned to the primary DC, meaning that they made recent changes which should
382 // be reflected in their subsequent web requests. Avoid the use of interim cache keys because
383 // they use a blind TTL and could be stale if an object changes twice in a short time span.
384 MediaWikiServices::getInstance()->getMainWANObjectCache()->useInterimHoldOffCaching( false );
387 // Useful debug output
388 ( static function () {
389 global $wgCommandLineMode, $wgRequest;
390 $logger = LoggerFactory::getInstance( 'wfDebug' );
391 if ( $wgCommandLineMode ) {
392 $self = $_SERVER['PHP_SELF'] ?? '';
393 $logger->debug( "\n\nStart command line script $self" );
394 } else {
395 $debug = "\n\nStart request {$wgRequest->getMethod()} {$wgRequest->getRequestURL()}\n";
396 $debug .= "IP: " . $wgRequest->getIP() . "\n";
397 $debug .= "HTTP HEADERS:\n";
398 foreach ( $wgRequest->getAllHeaders() as $name => $value ) {
399 $debug .= "$name: $value\n";
401 $debug .= "(end headers)";
402 $logger->debug( $debug );
404 } )();
406 // Most of the config is out, some might want to run hooks here.
407 Hooks::runner()->onSetupAfterCache();
409 // Now that variant lists may be available, parse any action paths and article paths
410 // as query parameters.
412 // Skip title interpolation on API queries where it is useless and sometimes harmful (T18019).
414 // Optimization: Skip on load.php and all other entrypoints besides index.php to save time.
416 // TODO: Figure out if this can be safely done after everything else in Setup.php (e.g. any
417 // hooks or other state that would miss this?). If so, move to wfIndexMain or MediaWiki::run.
418 if ( MW_ENTRY_POINT === 'index' ) {
419 $wgRequest->interpolateTitle();
423 * @var MediaWiki\Session\SessionId|null The persistent session ID (if any) loaded at startup
425 $wgInitialSessionId = null;
426 if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
427 // If session.auto_start is there, we can't touch session name
428 if ( $wgPHPSessionHandling !== 'disable' && !wfIniGetBool( 'session.auto_start' ) ) {
429 HeaderCallback::warnIfHeadersSent();
430 session_name( $wgSessionName ?: $wgCookiePrefix . '_session' );
433 // Create the SessionManager singleton and set up our session handler,
434 // unless we're specifically asked not to.
435 if ( !defined( 'MW_NO_SESSION_HANDLER' ) ) {
436 MediaWiki\Session\PHPSessionHandler::install(
437 MediaWiki\Session\SessionManager::singleton()
441 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
443 // Initialize the session
444 try {
445 $session = MediaWiki\Session\SessionManager::getGlobalSession();
446 } catch ( MediaWiki\Session\SessionOverflowException $ex ) {
447 // The exception is because the request had multiple possible
448 // sessions tied for top priority. Report this to the user.
449 $list = [];
450 foreach ( $ex->getSessionInfos() as $info ) {
451 $list[] = $info->getProvider()->describe( $contLang );
453 $list = $contLang->listToText( $list );
454 throw new HttpError( 400,
455 Message::newFromKey( 'sessionmanager-tie', $list )->inLanguage( $contLang )
459 unset( $contLang );
461 if ( $session->isPersistent() ) {
462 $wgInitialSessionId = $session->getSessionId();
465 $session->renew();
466 if ( MediaWiki\Session\PHPSessionHandler::isEnabled() &&
467 ( $session->isPersistent() || $session->shouldRememberUser() ) &&
468 session_id() !== $session->getId()
470 // Start the PHP-session for backwards compatibility
471 if ( session_id() !== '' ) {
472 wfDebugLog( 'session', 'PHP session {old_id} was already started, changing to {new_id}', 'all', [
473 'old_id' => session_id(),
474 'new_id' => $session->getId(),
475 ] );
476 session_write_close();
478 session_id( $session->getId() );
479 session_start();
482 unset( $session );
483 } else {
484 // Even if we didn't set up a global Session, still install our session
485 // handler unless specifically requested not to.
486 if ( !defined( 'MW_NO_SESSION_HANDLER' ) ) {
487 MediaWiki\Session\PHPSessionHandler::install(
488 MediaWiki\Session\SessionManager::singleton()
493 // Explicit globals, so this works with bootstrap.php
494 global $wgUser, $wgLang, $wgOut, $wgParser, $wgTitle;
497 * @var User $wgUser
498 * @deprecated since 1.35, use an available context source when possible, or, as a backup,
499 * RequestContext::getMain()
501 $wgUser = new StubGlobalUser( RequestContext::getMain()->getUser() ); // BackCompat
502 register_shutdown_function( static function () {
503 StubGlobalUser::$destructorDeprecationDisarmed = true;
504 } );
507 * @var Language|StubUserLang $wgLang
509 $wgLang = new StubUserLang;
512 * @var OutputPage $wgOut
514 $wgOut = RequestContext::getMain()->getOutput(); // BackCompat
517 * @var Parser $wgParser
518 * @deprecated since 1.32, use MediaWikiServices::getInstance()->getParser() instead
520 $wgParser = new DeprecatedGlobal( 'wgParser', static function () {
521 return MediaWikiServices::getInstance()->getParser();
522 }, '1.32' );
525 * @var Title|null $wgTitle
527 $wgTitle = null;
529 // Explicit globals, so this works with bootstrap.php
530 global $wgFullyInitialised, $wgExtensionFunctions;
532 // Extension setup functions
533 // Entries should be added to this variable during the inclusion
534 // of the extension file. This allows the extension to perform
535 // any necessary initialisation in the fully initialised environment
536 foreach ( $wgExtensionFunctions as $func ) {
537 call_user_func( $func );
539 unset( $func ); // no global pollution; destroy reference
541 // If the session user has a 0 id but a valid name, that means we need to
542 // autocreate it.
543 if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
544 $sessionUser = MediaWiki\Session\SessionManager::getGlobalSession()->getUser();
545 if ( $sessionUser->getId() === 0 &&
546 MediaWikiServices::getInstance()->getUserNameUtils()->isValid( $sessionUser->getName() )
548 $res = MediaWikiServices::getInstance()->getAuthManager()->autoCreateUser(
549 $sessionUser,
550 MediaWiki\Auth\AuthManager::AUTOCREATE_SOURCE_SESSION,
551 true
553 \MediaWiki\Logger\LoggerFactory::getInstance( 'authevents' )->info( 'Autocreation attempt', [
554 'event' => 'autocreate',
555 'status' => strval( $res ),
556 ] );
557 unset( $res );
559 unset( $sessionUser );
562 if ( !$wgCommandLineMode ) {
563 Pingback::schedulePingback();
566 // Explicit globals, so this works with bootstrap.php
567 global $wgFullyInitialised;
568 $wgFullyInitialised = true;
570 // T264370
571 if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
572 MediaWiki\Session\SessionManager::singleton()->logPotentialSessionLeakage();