16use GuzzleHttp\Psr7\Header;
18use InvalidArgumentException;
30use MediaWiki\MainConfigSchema;
47use Wikimedia\Services\ServiceDisabledException;
145 'envCheckModSecurity',
151 'envCheckUploadsDirectory',
152 'envCheckUploadsServerResponse',
161 private const DEFAULT_VAR_NAMES = [
200 '_Environment' =>
false,
201 '_RaiseMemory' =>
false,
202 '_UpgradeDone' =>
false,
203 '_InstallDone' =>
false,
205 '_InstallPassword' =>
'',
206 '_SameAccount' =>
true,
207 '_CreateDBAccount' =>
false,
208 '_NamespaceType' =>
'site-name',
210 '_AdminPassword' =>
'',
211 '_AdminPasswordConfirm' =>
'',
213 '_Subscribe' =>
false,
214 '_SkipOptional' =>
'continue',
215 '_RightsProfile' =>
'wiki',
216 '_LicenseCode' =>
'none',
220 '_MemCachedServers' =>
'',
221 '_UpgradeKeySupplied' =>
false,
222 '_ExistingDBSettings' =>
false,
223 '_LogoWordmark' =>
'',
224 '_LogoWordmarkWidth' => 119,
225 '_LogoWordmarkHeight' => 18,
227 '_Logo1x' =>
'$wgResourceBasePath/resources/assets/change-your-logo.svg',
228 '_LogoIcon' =>
'$wgResourceBasePath/resources/assets/change-your-logo-icon.svg',
229 '_LogoTagline' =>
'',
230 '_LogoTaglineWidth' => 117,
231 '_LogoTaglineHeight' => 13,
232 '_WithDevelopmentSettings' =>
false,
233 'wgAuthenticationTokenVersion' => 1,
249 'apcu' =>
'apcu_fetch',
260 '*' => [
'edit' => false ]
264 'createaccount' =>
false,
270 'createaccount' =>
false,
284 'url' =>
'https://creativecommons.org/licenses/by/4.0/',
285 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/cc-by.png',
288 'url' =>
'https://creativecommons.org/licenses/by-sa/4.0/',
289 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/cc-by-sa.png',
292 'url' =>
'https://creativecommons.org/licenses/by-nc-sa/4.0/',
293 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/cc-by-nc-sa.png',
296 'url' =>
'https://creativecommons.org/publicdomain/zero/1.0/',
297 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/cc-0.png',
300 'url' =>
'https://www.gnu.org/copyleft/fdl.html',
301 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/gnu-fdl.png',
317 private $taskFactory;
379 $emptyCache = [
'class' => EmptyBagOStuff::class ];
390 $installerConfig =
new MultiConfig( [ $configOverrides, $baseConfig ] );
394 $configRegistry[
'main'] =
static function () use ( $installerConfig ) {
395 return $installerConfig;
400 return $installerConfig;
411 $lang = $this->
getVar(
'_UserLang',
'en' );
415 $user = RequestContext::getMain()->getUser();
417 $this->parserOptions->setSuppressSectionEditLinks();
419 $this->
setParserLanguage( $services->getLanguageFactory()->getLanguage(
'en' ) );
421 $this->settings = $this->getDefaultSettings();
423 $this->compiledDBs = [];
424 foreach ( self::getDBTypes() as $type ) {
427 if ( !$installer->isCompiled() ) {
430 $this->compiledDBs[] = $type;
433 $this->parserTitle = Title::newFromText(
'Installer' );
436 private function getDefaultSettings(): array {
441 foreach ( self::DEFAULT_VAR_NAMES as $name ) {
443 $ret[$var] = MainConfigSchema::getDefaultValue( $name );
452 if ( $server !==
null ) {
453 $ret[
'wgServer'] = $server;
457 $ret[
'IP'] = MW_INSTALL_PATH;
460 + $this->generateKeys()
489 private function generateKeys() {
492 'wgUpgradeKey' => 16,
496 foreach ( $keyLengths as $name => $length ) {
497 $keys[$name] = MWCryptRand::generateHex( $length );
518 $mwServices->disableStorage();
521 $mwServices->getLocalisationCache()->disableBackend();
526 $user = User::newFromId( 0 );
527 StubGlobalUser::setUser( $user );
529 RequestContext::getMain()->setUser( $user );
535 RequestContext::getMain()->setLanguage( $lang );
536 $wgLang = RequestContext::getMain()->getLanguage();
550 return self::$dbTypes;
569 $this->showMessage(
'config-env-php', PHP_VERSION );
572 foreach ( $this->envChecks as $check ) {
573 $status = $this->$check();
574 if ( $status ===
false ) {
579 $this->setVar(
'_Environment', $good );
581 return $good ? StatusValue::newGood() : StatusValue::newFatal(
'config-env-bad' );
590 public function setVar( $name, $value ) {
591 $this->settings[$name] = $value;
604 public function getVar( $name, $default =
null ) {
605 return $this->settings[$name] ?? $default;
614 return $this->compiledDBs;
625 return '\\MediaWiki\\Installer\\' . ucfirst( $type ) .
'Installer';
637 $type = $this->getVar(
'wgDBtype' );
640 $type = strtolower( $type );
642 if ( !isset( $this->dbInstallers[$type] ) ) {
643 $class = self::getDBInstallerClass( $type );
644 $this->dbInstallers[$type] =
new $class( $this );
647 return $this->dbInstallers[$type];
674 $lsExists = @file_exists( $lsFile );
680 if ( !str_ends_with( $lsFile,
'.php' ) ) {
681 throw new RuntimeException(
682 'The installer cannot yet handle non-php settings files: ' . $lsFile .
'. ' .
683 'Use `php maintenance/run.php update` to update an existing installation.'
689 foreach ( MainConfigSchema::listDefaultValues(
'wg' ) as $var => $value ) {
716 return get_defined_vars();
729 return str_repeat(
'*', strlen( $realPassword ) );
740 if ( !preg_match(
'/^\*+$/', $value ) ) {
741 $this->setVar( $name, $value );
761 public function parse( $text, $lineStart =
false ) {
765 $out = $parser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
768 $html = $pipeline->run( $out, $this->parserOptions, [
770 ] )->getContentHolderText();
771 $html = Parser::stripOuterParagraph( $html );
772 }
catch ( ServiceDisabledException ) {
773 $html =
'<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
783 return $this->parserOptions;
789 $this->parserOptions->setExternalLinkTarget(
false );
806 $dbType = $this->getVar(
'wgDBtype' );
811 foreach ( self::getDBTypes() as $name ) {
812 $allNames[] =
wfMessage(
"config-type-$name" )->text();
815 $databases = $this->getCompiledDBs();
817 $databases = array_flip( $databases );
819 foreach ( $databases as $db => $_ ) {
820 $installer = $this->getDBInstaller( $db );
821 $status = $installer->checkPrerequisites();
822 if ( !$status->isGood() ) {
823 if ( !$this instanceof
WebInstaller && $db === $dbType ) {
829 $this->showStatusMessage( $status );
830 unset( $databases[$db] );
833 $databases = array_flip( $databases );
835 $this->showError(
'config-no-db',
$wgLang->commaList( $allNames ), count( $allNames ) );
854 if ( preg_match(
'/^b.*c$/',
'bÄ…c' ) === 0 ) {
855 $this->showError(
'config-pcre-invalid-newline' );
866 $limit = ini_get(
'memory_limit' );
868 if ( !$limit || $limit == -1 ) {
874 if ( $n < $this->minMemorySize * 1024 * 1024 ) {
875 $newLimit =
"{$this->minMemorySize}M";
877 if ( ini_set(
"memory_limit", $newLimit ) ===
false ) {
878 $this->showMessage(
'config-memory-bad', $limit );
880 $this->showMessage(
'config-memory-raised', $limit, $newLimit );
881 $this->setVar(
'_RaiseMemory',
true );
893 foreach ( $this->objectCaches as $name => $function ) {
894 if ( function_exists( $function ) ) {
895 $caches[$name] =
true;
900 $this->showMessage(
'config-no-cache-apcu' );
903 $this->setVar(
'_Caches', $caches );
911 if ( self::apacheModulePresent(
'mod_security' )
912 || self::apacheModulePresent(
'mod_security2' ) ) {
913 $this->showMessage(
'config-mod-security' );
924 $names = [
"gdiff3",
"diff3" ];
926 $names[] =
'diff3.exe';
928 $versionInfo = [
'--version',
'GNU diffutils' ];
933 $this->setVar(
'wgDiff3', $diff3 );
935 $this->setVar(
'wgDiff3',
false );
936 $this->showMessage(
'config-diff3-bad' );
947 $names =
wfIsWindows() ?
'convert.exe' :
'convert';
948 $versionInfo = [
'-version',
'ImageMagick' ];
951 $this->setVar(
'wgImageMagickConvertCommand',
'' );
953 $this->setVar(
'wgImageMagickConvertCommand', $convert );
954 $this->showMessage(
'config-imagemagick', $convert );
955 } elseif ( function_exists(
'imagejpeg' ) ) {
956 $this->showMessage(
'config-gd' );
958 $this->showMessage(
'config-no-scaling' );
972 $versionInfo = [
'--version',
'git version' ];
977 $this->setVar(
'wgGitBin', $git );
978 $this->showMessage(
'config-git', $git );
980 $this->setVar(
'wgGitBin',
false );
981 $this->showMessage(
'config-git-bad' );
993 $server = $this->envGetDefaultServer();
994 if ( $server !==
null ) {
995 $this->showMessage(
'config-using-server', $server );
1008 $this->getVar(
'wgServer' ),
1009 $this->getVar(
'wgScriptPath' )
1021 $dir =
$IP .
'/images/';
1022 $url = $this->getVar(
'wgServer' ) . $this->getVar(
'wgScriptPath' ) .
'/images/';
1023 $safe = !$this->dirIsExecutable( $dir,
$url );
1026 $this->showWarning(
'config-uploads-not-safe', $dir );
1033 $url = $this->getVar(
'wgServer' ) . $this->getVar(
'wgScriptPath' ) .
'/images/README';
1037 $req = $httpRequestFactory->create(
1042 'followRedirects' =>
true
1047 $status = $req->execute();
1048 }
catch ( Exception ) {
1053 if ( !$status || !$status->isGood() ) {
1054 $this->showWarning(
'config-uploads-security-requesterror',
'X-Content-Type-Options: nosniff' );
1058 $headerValue = $req->getResponseHeader(
'X-Content-Type-Options' ) ??
'';
1059 $responseList = Header::splitList( $headerValue );
1060 if ( !in_array(
'nosniff', $responseList,
true ) ) {
1061 $this->showWarning(
'config-uploads-security-headers',
'X-Content-Type-Options: nosniff' );
1074 if ( PHP_INT_SIZE == 4 ) {
1075 $this->showMessage(
'config-using-32bit' );
1085 $unicodeVersion = implode(
'.', array_slice( IntlChar::getUnicodeVersion(), 0, 3 ) );
1086 $this->showMessage(
'config-env-icu', INTL_ICU_VERSION, $unicodeVersion );
1106 "<?php echo 'exec';",
1107 "#!/var/env php\n<?php echo 'exec';",
1114 $httpRequestFactory = MediaWikiServices::getInstance()->getHttpRequestFactory();
1116 foreach ( $scriptTypes as $ext => $contents ) {
1117 foreach ( $contents as
$source ) {
1118 $file =
'exectest.' . $ext;
1121 if ( !@file_put_contents( $dir . $file,
$source ) ) {
1126 $text = $httpRequestFactory->get(
1131 }
catch ( Exception ) {
1137 @unlink( $dir . $file );
1139 if ( $text ==
'exec' ) {
1155 if ( function_exists(
'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1160 phpinfo( INFO_MODULES );
1161 $info = ob_get_clean();
1163 return str_contains( $info, $moduleName );
1172 $this->parserOptions->setTargetLanguage( $lang );
1173 $this->parserOptions->setUserLang( $lang );
1182 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1195 switch ( $directory ) {
1197 return $this->findExtensionsByType(
'extension',
'extensions' );
1199 return $this->findExtensionsByType(
'skin',
'skins' );
1201 throw new InvalidArgumentException(
"Invalid extension type" );
1215 if ( $this->getVar(
'IP' ) ===
null ) {
1216 return StatusValue::newGood( [] );
1219 $extDir = $this->getVar(
'IP' ) .
'/' . $directory;
1220 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
1221 return StatusValue::newGood( [] );
1224 $dh = opendir( $extDir );
1228 while ( ( $file = readdir( $dh ) ) !==
false ) {
1230 if ( !is_dir(
"$extDir/$file" ) || $file[0] ===
'.' ) {
1233 $extStatus = $this->getExtensionInfo( $type, $directory, $file );
1234 if ( $extStatus->isOK() ) {
1235 $exts[$file] = $extStatus->value;
1236 } elseif ( $extStatus->hasMessage(
'config-extension-not-found' ) ) {
1238 $status->warning(
'config-extension-not-found', $file );
1240 $status->merge( $extStatus );
1244 uksort( $exts,
'strnatcasecmp' );
1246 $status->value = $exts;
1259 if ( $this->getVar(
'IP' ) ===
null ) {
1260 throw new RuntimeException(
'Cannot find extensions since the IP variable is not yet set' );
1262 if ( $type !==
'extension' && $type !==
'skin' ) {
1263 throw new InvalidArgumentException(
"Invalid extension type" );
1265 $absDir = $this->getVar(
'IP' ) .
"/$parentRelPath/$name";
1266 $relDir =
"../$parentRelPath/$name";
1267 if ( !is_dir( $absDir ) ) {
1268 return StatusValue::newFatal(
'config-extension-not-found', $name );
1270 $jsonFile = $type .
'.json';
1271 $fullJsonFile =
"$absDir/$jsonFile";
1272 $isJson = file_exists( $fullJsonFile );
1276 $fullPhpFile =
"$absDir/$name.php";
1277 $isPhp = file_exists( $fullPhpFile );
1279 if ( !$isJson && !$isPhp ) {
1280 return StatusValue::newFatal(
'config-extension-not-found', $name );
1285 if ( is_dir(
"$absDir/screenshots" ) ) {
1286 $paths = glob(
"$absDir/screenshots/*.png" );
1287 foreach ( $paths as
$path ) {
1288 $info[
'screenshots'][] = str_replace( $absDir, $relDir,
$path );
1293 $jsonStatus = $this->readExtension( $fullJsonFile );
1294 if ( !$jsonStatus->isOK() ) {
1297 $info += $jsonStatus->value;
1300 return StatusValue::newGood( $info );
1311 private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
1316 $extDir = $this->getVar(
'IP' ) .
'/extensions';
1317 foreach ( $extDeps as $dep ) {
1318 $fname =
"$extDir/$dep/extension.json";
1319 if ( !file_exists( $fname ) ) {
1320 return StatusValue::newFatal(
'config-extension-not-found', $dep );
1326 $skinDir = $this->getVar(
'IP' ) .
'/skins';
1327 foreach ( $skinDeps as $dep ) {
1328 $fname =
"$skinDir/$dep/skin.json";
1329 if ( !file_exists( $fname ) ) {
1335 $registry =
new ExtensionRegistry();
1337 $info = $registry->readFromQueue( $load );
1338 }
catch ( ExtensionDependencyError $e ) {
1339 if ( $e->incompatibleCore || $e->incompatibleSkins
1340 || $e->incompatibleExtensions
1345 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1346 } elseif ( $e->missingExtensions || $e->missingSkins ) {
1349 $status = $this->readExtension(
1351 array_merge( $extDeps, $e->missingExtensions ),
1352 array_merge( $skinDeps, $e->missingSkins )
1354 if ( !$status->isOK() && !$status->hasMessage(
'config-extension-dependency' ) ) {
1356 basename( dirname( $fullJsonFile ) ), Status::wrap( $status )->getMessage() );
1362 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1369 foreach ( $info[
'credits'] as $credit ) {
1375 $type = basename( $credit[
'path'] ) ===
'skin.json' ?
'skins' :
'extensions';
1376 $ret[
'requires'][$type][] = $credit[
'name'];
1378 $credits = array_values( $info[
'credits'] )[0];
1379 if ( isset( $credits[
'url'] ) ) {
1380 $ret[
'url'] = $credits[
'url'];
1382 $ret[
'type'] = $credits[
'type'];
1396 $defaultSkin = $GLOBALS[
'wgDefaultSkin'];
1398 if ( in_array(
'vector', $skinNames ) ) {
1399 $skinNames[] =
'vector-2022';
1403 if ( in_array(
'minervaneue', $skinNames ) ) {
1404 $minervaNeue = array_search(
'minervaneue', $skinNames );
1405 $skinNames[$minervaNeue] =
'minerva';
1408 if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) {
1409 return $defaultSkin;
1411 return $skinNames[0];
1425 $taskFactory = $this->getTaskFactory();
1426 $taskFactory->registerMainTasks( $taskList, TaskFactory::PROFILE_INSTALLER );
1429 foreach ( $this->extraInstallSteps as $requirement => $steps ) {
1430 foreach ( $steps as $spec ) {
1431 if ( $requirement !==
'BEGINNING' ) {
1432 $spec += [
'after' => $requirement ];
1434 $taskList->add( $taskFactory->create( $spec ) );
1442 if ( $this->taskFactory === null ) {
1444 MediaWikiServices::getInstance()->getObjectFactory(),
1445 $this->getDBInstaller()
1448 return $this->taskFactory;
1460 $tasks = $this->getTaskList();
1462 $taskRunner =
new TaskRunner( $tasks, $this->getTaskFactory(),
1463 TaskFactory::PROFILE_INSTALLER );
1464 $taskRunner->addTaskStartListener( $startCB );
1465 $taskRunner->addTaskEndListener( $endCB );
1467 $status = $taskRunner->execute();
1468 if ( $status->isOK() ) {
1470 'config-install-db-success'
1472 $this->setVar(
'_InstallDone',
true );
1484 if ( !defined(
'MW_NO_SESSION_HANDLER' ) ) {
1485 define(
'MW_NO_SESSION_HANDLER', 1 );
1491 MainConfigNames::UseDatabaseMessages =>
false,
1494 MainConfigNames::LanguageConverterCacheType =>
CACHE_NONE,
1497 MainConfigNames::ShowExceptionDetails =>
true,
1498 MainConfigNames::ShowHostnames =>
true,
1501 MainConfigNames::ExternalLinkTarget =>
'_blank',
1504 MainConfigNames::DisableOutputCompression =>
true,
1507 MainConfigNames::CookiePrefix =>
'mw_installer',
1510 MainConfigNames::MaxShellMemory => 0,
1514 MainConfigNames::SessionProviders => [
1516 'class' => InstallerSessionProvider::class,
1527 MainConfigNames::SessionCacheType =>
CACHE_NONE,
1531 MainConfigNames::Server =>
'https://🌻.invalid',
1543 $this->extraInstallSteps[$findStep][] = $callback;
1552 @set_time_limit( 0 );
wfDetectLocalSettingsFile(?string $installationPath=null)
Decide and remember where to load LocalSettings from.
wfIsWindows()
Check if the operating system is Windows.
wfDetectInstallPath()
Decide and remember where mediawiki is installed.
wfShorthandToInteger(?string $string='', int $default=-1)
Converts shorthand byte notation to integer form.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(!defined('MEDIAWIKI')) if(!defined( 'MW_ENTRY_POINT')) global $IP
Environment checks.
if(MW_ENTRY_POINT==='index') if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgLang
$wgConf
$wgConf hold the site configuration.
if(!interface_exists(LoggerInterface::class)) $wgCommandLineMode
Pre-config setup: Before loading LocalSettings.php.
if(!defined('MW_SETUP_CALLBACK'))
Group all the pieces relevant to the context of a request into one instance.
A class containing constants representing the names of configuration variables.
const EnotifWatchlist
Name constant for the EnotifWatchlist setting, for use with Config::get()
const DefaultSkin
Name constant for the DefaultSkin setting, for use with Config::get()
const DBtype
Name constant for the DBtype setting, for use with Config::get()
const EnableUserEmail
Name constant for the EnableUserEmail setting, for use with Config::get()
const ImageMagickConvertCommand
Name constant for the ImageMagickConvertCommand setting, for use with Config::get()
const Localtimezone
Name constant for the Localtimezone setting, for use with Config::get()
const DeletedDirectory
Name constant for the DeletedDirectory setting, for use with Config::get()
const DBname
Name constant for the DBname setting, for use with Config::get()
const MetaNamespace
Name constant for the MetaNamespace setting, for use with Config::get()
const RightsIcon
Name constant for the RightsIcon setting, for use with Config::get()
const RightsText
Name constant for the RightsText setting, for use with Config::get()
const ObjectCaches
Name constant for the ObjectCaches setting, for use with Config::get()
const Pingback
Name constant for the Pingback setting, for use with Config::get()
const Sitename
Name constant for the Sitename setting, for use with Config::get()
const EnableEmail
Name constant for the EnableEmail setting, for use with Config::get()
const EnableUploads
Name constant for the EnableUploads setting, for use with Config::get()
const RightsUrl
Name constant for the RightsUrl setting, for use with Config::get()
const EnotifUserTalk
Name constant for the EnotifUserTalk setting, for use with Config::get()
const ConfigRegistry
Name constant for the ConfigRegistry setting, for use with Config::get()
const ScriptPath
Name constant for the ScriptPath setting, for use with Config::get()
const UseInstantCommons
Name constant for the UseInstantCommons setting, for use with Config::get()
const LanguageCode
Name constant for the LanguageCode setting, for use with Config::get()
const InstallerInitialPages
Name constant for the InstallerInitialPages setting, for use with Config::get()
const UpgradeKey
Name constant for the UpgradeKey setting, for use with Config::get()
const GitBin
Name constant for the GitBin setting, for use with Config::get()
const PasswordSender
Name constant for the PasswordSender setting, for use with Config::get()
const SecretKey
Name constant for the SecretKey setting, for use with Config::get()
const Diff3
Name constant for the Diff3 setting, for use with Config::get()
const EmailAuthentication
Name constant for the EmailAuthentication setting, for use with Config::get()
Generic operation result class Has warning/error list, boolean status and arbitrary value.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.
$wgObjectCaches
Config variable stub for the ObjectCaches setting, for use by phpdoc and IDEs.
$wgStyleDirectory
Config variable stub for the StyleDirectory setting, for use by phpdoc and IDEs.
$wgLocaltimezone
Config variable stub for the Localtimezone setting, for use by phpdoc and IDEs.
$wgExtensionDirectory
Config variable stub for the ExtensionDirectory setting, for use by phpdoc and IDEs.
$wgExternalLinkTarget
Config variable stub for the ExternalLinkTarget setting, for use by phpdoc and IDEs.