16use GuzzleHttp\Psr7\Header;
18use InvalidArgumentException;
30use MediaWiki\MainConfigSchema;
46use Wikimedia\Services\ServiceDisabledException;
144 'envCheckModSecurity',
150 'envCheckUploadsDirectory',
151 'envCheckUploadsServerResponse',
160 private const DEFAULT_VAR_NAMES = [
199 '_Environment' =>
false,
200 '_RaiseMemory' =>
false,
201 '_UpgradeDone' =>
false,
202 '_InstallDone' =>
false,
204 '_InstallPassword' =>
'',
205 '_SameAccount' =>
true,
206 '_CreateDBAccount' =>
false,
207 '_NamespaceType' =>
'site-name',
209 '_AdminPassword' =>
'',
210 '_AdminPasswordConfirm' =>
'',
212 '_Subscribe' =>
false,
213 '_SkipOptional' =>
'continue',
214 '_RightsProfile' =>
'wiki',
215 '_LicenseCode' =>
'none',
219 '_MemCachedServers' =>
'',
220 '_UpgradeKeySupplied' =>
false,
221 '_ExistingDBSettings' =>
false,
222 '_LogoWordmark' =>
'',
223 '_LogoWordmarkWidth' => 119,
224 '_LogoWordmarkHeight' => 18,
226 '_Logo1x' =>
'$wgResourceBasePath/resources/assets/change-your-logo.svg',
227 '_LogoIcon' =>
'$wgResourceBasePath/resources/assets/change-your-logo-icon.svg',
228 '_LogoTagline' =>
'',
229 '_LogoTaglineWidth' => 117,
230 '_LogoTaglineHeight' => 13,
231 '_WithDevelopmentSettings' =>
false,
232 'wgAuthenticationTokenVersion' => 1,
248 'apcu' =>
'apcu_fetch',
259 '*' => [
'edit' => false ]
263 'createaccount' =>
false,
269 'createaccount' =>
false,
283 'url' =>
'https://creativecommons.org/licenses/by/4.0/',
284 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/cc-by.png',
287 'url' =>
'https://creativecommons.org/licenses/by-sa/4.0/',
288 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/cc-by-sa.png',
291 'url' =>
'https://creativecommons.org/licenses/by-nc-sa/4.0/',
292 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/cc-by-nc-sa.png',
295 'url' =>
'https://creativecommons.org/publicdomain/zero/1.0/',
296 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/cc-0.png',
299 'url' =>
'https://www.gnu.org/copyleft/fdl.html',
300 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/gnu-fdl.png',
316 private $taskFactory;
378 $emptyCache = [
'class' => EmptyBagOStuff::class ];
389 $installerConfig =
new MultiConfig( [ $configOverrides, $baseConfig ] );
393 $configRegistry[
'main'] =
static function () use ( $installerConfig ) {
394 return $installerConfig;
399 return $installerConfig;
410 $lang = $this->
getVar(
'_UserLang',
'en' );
414 $user = RequestContext::getMain()->getUser();
416 $this->parserOptions->setSuppressSectionEditLinks();
418 $this->
setParserLanguage( $services->getLanguageFactory()->getLanguage(
'en' ) );
420 $this->settings = $this->getDefaultSettings();
422 $this->compiledDBs = [];
423 foreach ( self::getDBTypes() as $type ) {
426 if ( !$installer->isCompiled() ) {
429 $this->compiledDBs[] = $type;
432 $this->parserTitle = Title::newFromText(
'Installer' );
435 private function getDefaultSettings(): array {
440 foreach ( self::DEFAULT_VAR_NAMES as $name ) {
442 $ret[$var] = MainConfigSchema::getDefaultValue( $name );
451 if ( $server !==
null ) {
452 $ret[
'wgServer'] = $server;
456 $ret[
'IP'] = MW_INSTALL_PATH;
459 + $this->generateKeys()
488 private function generateKeys() {
491 'wgUpgradeKey' => 16,
495 foreach ( $keyLengths as $name => $length ) {
496 $keys[$name] = MWCryptRand::generateHex( $length );
517 $mwServices->disableStorage();
520 $mwServices->getLocalisationCache()->disableBackend();
525 $user = User::newFromId( 0 );
526 RequestContext::getMain()->setUser( $user );
532 RequestContext::getMain()->setLanguage( $lang );
546 return self::$dbTypes;
565 $this->showMessage(
'config-env-php', PHP_VERSION );
568 foreach ( $this->envChecks as $check ) {
569 $status = $this->$check();
570 if ( $status ===
false ) {
575 $this->setVar(
'_Environment', $good );
577 return $good ? StatusValue::newGood() : StatusValue::newFatal(
'config-env-bad' );
586 public function setVar( $name, $value ) {
587 $this->settings[$name] = $value;
600 public function getVar( $name, $default =
null ) {
601 return $this->settings[$name] ?? $default;
610 return $this->compiledDBs;
621 return '\\MediaWiki\\Installer\\' . ucfirst( $type ) .
'Installer';
633 $type = $this->getVar(
'wgDBtype' );
636 $type = strtolower( $type );
638 if ( !isset( $this->dbInstallers[$type] ) ) {
639 $class = self::getDBInstallerClass( $type );
640 $this->dbInstallers[$type] =
new $class( $this );
643 return $this->dbInstallers[$type];
670 $lsExists = @file_exists( $lsFile );
676 if ( !str_ends_with( $lsFile,
'.php' ) ) {
677 throw new RuntimeException(
678 'The installer cannot yet handle non-php settings files: ' . $lsFile .
'. ' .
679 'Use `php maintenance/run.php update` to update an existing installation.'
685 foreach ( MainConfigSchema::listDefaultValues(
'wg' ) as $var => $value ) {
712 return get_defined_vars();
725 return str_repeat(
'*', strlen( $realPassword ) );
736 if ( !preg_match(
'/^\*+$/', $value ) ) {
737 $this->setVar( $name, $value );
757 public function parse( $text, $lineStart =
false ) {
761 $out = $parser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
764 $html = $pipeline->run( $out, $this->parserOptions, [
766 ] )->getContentHolderText();
767 $html = Parser::stripOuterParagraph( $html );
768 }
catch ( ServiceDisabledException ) {
769 $html =
'<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
779 return $this->parserOptions;
785 $this->parserOptions->setExternalLinkTarget(
false );
801 $dbType = $this->getVar(
'wgDBtype' );
806 foreach ( self::getDBTypes() as $name ) {
807 $allNames[] =
wfMessage(
"config-type-$name" )->text();
810 $databases = $this->getCompiledDBs();
812 $databases = array_flip( $databases );
814 foreach ( $databases as $db => $_ ) {
815 $installer = $this->getDBInstaller( $db );
816 $status = $installer->checkPrerequisites();
817 if ( !$status->isGood() ) {
818 if ( !$this instanceof
WebInstaller && $db === $dbType ) {
824 $this->showStatusMessage( $status );
825 unset( $databases[$db] );
828 $databases = array_flip( $databases );
830 $lang = RequestContext::getMain()->getLanguage();
831 $this->showError(
'config-no-db', $lang->commaList( $allNames ), count( $allNames ) );
850 if ( preg_match(
'/^b.*c$/',
'bÄ…c' ) === 0 ) {
851 $this->showError(
'config-pcre-invalid-newline' );
862 $limit = ini_get(
'memory_limit' );
864 if ( !$limit || $limit == -1 ) {
870 if ( $n < $this->minMemorySize * 1024 * 1024 ) {
871 $newLimit =
"{$this->minMemorySize}M";
873 if ( ini_set(
"memory_limit", $newLimit ) ===
false ) {
874 $this->showMessage(
'config-memory-bad', $limit );
876 $this->showMessage(
'config-memory-raised', $limit, $newLimit );
877 $this->setVar(
'_RaiseMemory',
true );
889 foreach ( $this->objectCaches as $name => $function ) {
890 if ( function_exists( $function ) ) {
891 $caches[$name] =
true;
896 $this->showMessage(
'config-no-cache-apcu' );
899 $this->setVar(
'_Caches', $caches );
907 if ( self::apacheModulePresent(
'mod_security' )
908 || self::apacheModulePresent(
'mod_security2' ) ) {
909 $this->showMessage(
'config-mod-security' );
920 $names = [
"gdiff3",
"diff3" ];
922 $names[] =
'diff3.exe';
924 $versionInfo = [
'--version',
'GNU diffutils' ];
929 $this->setVar(
'wgDiff3', $diff3 );
931 $this->setVar(
'wgDiff3',
false );
932 $this->showMessage(
'config-diff3-bad' );
943 $names =
wfIsWindows() ?
'convert.exe' :
'convert';
944 $versionInfo = [
'-version',
'ImageMagick' ];
947 $this->setVar(
'wgImageMagickConvertCommand',
'' );
949 $this->setVar(
'wgImageMagickConvertCommand', $convert );
950 $this->showMessage(
'config-imagemagick', $convert );
951 } elseif ( function_exists(
'imagejpeg' ) ) {
952 $this->showMessage(
'config-gd' );
954 $this->showMessage(
'config-no-scaling' );
968 $versionInfo = [
'--version',
'git version' ];
973 $this->setVar(
'wgGitBin', $git );
974 $this->showMessage(
'config-git', $git );
976 $this->setVar(
'wgGitBin',
false );
977 $this->showMessage(
'config-git-bad' );
989 $server = $this->envGetDefaultServer();
990 if ( $server !==
null ) {
991 $this->showMessage(
'config-using-server', $server );
1004 $this->getVar(
'wgServer' ),
1005 $this->getVar(
'wgScriptPath' )
1017 $dir =
$IP .
'/images/';
1018 $url = $this->getVar(
'wgServer' ) . $this->getVar(
'wgScriptPath' ) .
'/images/';
1019 $safe = !$this->dirIsExecutable( $dir,
$url );
1022 $this->showWarning(
'config-uploads-not-safe', $dir );
1029 $url = $this->getVar(
'wgServer' ) . $this->getVar(
'wgScriptPath' ) .
'/images/README';
1033 $req = $httpRequestFactory->create(
1038 'followRedirects' =>
true
1043 $status = $req->execute();
1044 }
catch ( Exception ) {
1049 if ( !$status || !$status->isGood() ) {
1050 $this->showWarning(
'config-uploads-security-requesterror',
'X-Content-Type-Options: nosniff' );
1054 $headerValue = $req->getResponseHeader(
'X-Content-Type-Options' ) ??
'';
1055 $responseList = Header::splitList( $headerValue );
1056 if ( !in_array(
'nosniff', $responseList,
true ) ) {
1057 $this->showWarning(
'config-uploads-security-headers',
'X-Content-Type-Options: nosniff' );
1070 if ( PHP_INT_SIZE == 4 ) {
1071 $this->showMessage(
'config-using-32bit' );
1081 $unicodeVersion = implode(
'.', array_slice( IntlChar::getUnicodeVersion(), 0, 3 ) );
1082 $this->showMessage(
'config-env-icu', INTL_ICU_VERSION, $unicodeVersion );
1102 "<?php echo 'exec';",
1103 "#!/var/env php\n<?php echo 'exec';",
1110 $httpRequestFactory = MediaWikiServices::getInstance()->getHttpRequestFactory();
1112 foreach ( $scriptTypes as $ext => $contents ) {
1113 foreach ( $contents as
$source ) {
1114 $file =
'exectest.' . $ext;
1117 if ( !@file_put_contents( $dir . $file,
$source ) ) {
1122 $text = $httpRequestFactory->get(
1127 }
catch ( Exception ) {
1133 @unlink( $dir . $file );
1135 if ( $text ==
'exec' ) {
1151 if ( function_exists(
'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1156 phpinfo( INFO_MODULES );
1157 $info = ob_get_clean();
1159 return str_contains( $info, $moduleName );
1168 $this->parserOptions->setTargetLanguage( $lang );
1169 $this->parserOptions->setUserLang( $lang );
1178 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1191 switch ( $directory ) {
1193 return $this->findExtensionsByType(
'extension',
'extensions' );
1195 return $this->findExtensionsByType(
'skin',
'skins' );
1197 throw new InvalidArgumentException(
"Invalid extension type" );
1211 if ( $this->getVar(
'IP' ) ===
null ) {
1212 return StatusValue::newGood( [] );
1215 $extDir = $this->getVar(
'IP' ) .
'/' . $directory;
1216 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
1217 return StatusValue::newGood( [] );
1220 $dh = opendir( $extDir );
1224 while ( ( $file = readdir( $dh ) ) !==
false ) {
1226 if ( !is_dir(
"$extDir/$file" ) || $file[0] ===
'.' ) {
1229 $extStatus = $this->getExtensionInfo( $type, $directory, $file );
1230 if ( $extStatus->isOK() ) {
1231 $exts[$file] = $extStatus->value;
1232 } elseif ( $extStatus->hasMessage(
'config-extension-not-found' ) ) {
1234 $status->warning(
'config-extension-not-found', $file );
1236 $status->merge( $extStatus );
1240 uksort( $exts,
'strnatcasecmp' );
1242 $status->value = $exts;
1255 if ( $this->getVar(
'IP' ) ===
null ) {
1256 throw new RuntimeException(
'Cannot find extensions since the IP variable is not yet set' );
1258 if ( $type !==
'extension' && $type !==
'skin' ) {
1259 throw new InvalidArgumentException(
"Invalid extension type" );
1261 $absDir = $this->getVar(
'IP' ) .
"/$parentRelPath/$name";
1262 $relDir =
"../$parentRelPath/$name";
1263 if ( !is_dir( $absDir ) ) {
1264 return StatusValue::newFatal(
'config-extension-not-found', $name );
1266 $jsonFile = $type .
'.json';
1267 $fullJsonFile =
"$absDir/$jsonFile";
1268 $isJson = file_exists( $fullJsonFile );
1272 $fullPhpFile =
"$absDir/$name.php";
1273 $isPhp = file_exists( $fullPhpFile );
1275 if ( !$isJson && !$isPhp ) {
1276 return StatusValue::newFatal(
'config-extension-not-found', $name );
1281 if ( is_dir(
"$absDir/screenshots" ) ) {
1282 $paths = glob(
"$absDir/screenshots/*.png" );
1283 foreach ( $paths as
$path ) {
1284 $info[
'screenshots'][] = str_replace( $absDir, $relDir,
$path );
1289 $jsonStatus = $this->readExtension( $fullJsonFile );
1290 if ( !$jsonStatus->isOK() ) {
1293 $info += $jsonStatus->value;
1296 return StatusValue::newGood( $info );
1307 private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
1312 $extDir = $this->getVar(
'IP' ) .
'/extensions';
1313 foreach ( $extDeps as $dep ) {
1314 $fname =
"$extDir/$dep/extension.json";
1315 if ( !file_exists( $fname ) ) {
1316 return StatusValue::newFatal(
'config-extension-not-found', $dep );
1322 $skinDir = $this->getVar(
'IP' ) .
'/skins';
1323 foreach ( $skinDeps as $dep ) {
1324 $fname =
"$skinDir/$dep/skin.json";
1325 if ( !file_exists( $fname ) ) {
1331 $registry =
new ExtensionRegistry();
1333 $info = $registry->readFromQueue( $load );
1334 }
catch ( ExtensionDependencyError $e ) {
1335 if ( $e->incompatibleCore || $e->incompatibleSkins
1336 || $e->incompatibleExtensions
1341 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1342 } elseif ( $e->missingExtensions || $e->missingSkins ) {
1345 $status = $this->readExtension(
1347 array_merge( $extDeps, $e->missingExtensions ),
1348 array_merge( $skinDeps, $e->missingSkins )
1350 if ( !$status->isOK() && !$status->hasMessage(
'config-extension-dependency' ) ) {
1352 basename( dirname( $fullJsonFile ) ), Status::wrap( $status )->getMessage() );
1358 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1365 foreach ( $info[
'credits'] as $credit ) {
1371 $type = basename( $credit[
'path'] ) ===
'skin.json' ?
'skins' :
'extensions';
1372 $ret[
'requires'][$type][] = $credit[
'name'];
1374 $credits = array_values( $info[
'credits'] )[0];
1375 if ( isset( $credits[
'url'] ) ) {
1376 $ret[
'url'] = $credits[
'url'];
1378 $ret[
'type'] = $credits[
'type'];
1392 $defaultSkin = $GLOBALS[
'wgDefaultSkin'];
1394 if ( in_array(
'vector', $skinNames ) ) {
1395 $skinNames[] =
'vector-2022';
1399 if ( in_array(
'minervaneue', $skinNames ) ) {
1400 $minervaNeue = array_search(
'minervaneue', $skinNames );
1401 $skinNames[$minervaNeue] =
'minerva';
1404 if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) {
1405 return $defaultSkin;
1407 return $skinNames[0];
1421 $taskFactory = $this->getTaskFactory();
1422 $taskFactory->registerMainTasks( $taskList, TaskFactory::PROFILE_INSTALLER );
1425 foreach ( $this->extraInstallSteps as $requirement => $steps ) {
1426 foreach ( $steps as $spec ) {
1427 if ( $requirement !==
'BEGINNING' ) {
1428 $spec += [
'after' => $requirement ];
1430 $taskList->add( $taskFactory->create( $spec ) );
1438 if ( $this->taskFactory === null ) {
1440 MediaWikiServices::getInstance()->getObjectFactory(),
1441 $this->getDBInstaller()
1444 return $this->taskFactory;
1456 $tasks = $this->getTaskList();
1458 $taskRunner =
new TaskRunner( $tasks, $this->getTaskFactory(),
1459 TaskFactory::PROFILE_INSTALLER );
1460 $taskRunner->addTaskStartListener( $startCB );
1461 $taskRunner->addTaskEndListener( $endCB );
1463 $status = $taskRunner->execute();
1464 if ( $status->isOK() ) {
1466 'config-install-db-success'
1468 $this->setVar(
'_InstallDone',
true );
1480 if ( !defined(
'MW_NO_SESSION_HANDLER' ) ) {
1481 define(
'MW_NO_SESSION_HANDLER', 1 );
1487 MainConfigNames::UseDatabaseMessages =>
false,
1490 MainConfigNames::LanguageConverterCacheType =>
CACHE_NONE,
1493 MainConfigNames::ShowExceptionDetails =>
true,
1494 MainConfigNames::ShowHostnames =>
true,
1497 MainConfigNames::ExternalLinkTarget =>
'_blank',
1500 MainConfigNames::DisableOutputCompression =>
true,
1503 MainConfigNames::CookiePrefix =>
'mw_installer',
1506 MainConfigNames::MaxShellMemory => 0,
1510 MainConfigNames::SessionProviders => [
1512 'class' => InstallerSessionProvider::class,
1523 MainConfigNames::SessionCacheType =>
CACHE_NONE,
1527 MainConfigNames::Server =>
'https://🌻.invalid',
1539 $this->extraInstallSteps[$findStep][] = $callback;
1548 @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')) $IP
Environment checks.
$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.