17use GuzzleHttp\Psr7\Header;
19use InvalidArgumentException;
31use MediaWiki\MainConfigSchema;
44use Wikimedia\AtEase\AtEase;
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();
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 StubGlobalUser::setUser( $user );
528 RequestContext::getMain()->setUser( $user );
534 RequestContext::getMain()->setLanguage( $lang );
535 $wgLang = RequestContext::getMain()->getLanguage();
549 return self::$dbTypes;
568 $this->showMessage(
'config-env-php', PHP_VERSION );
571 foreach ( $this->envChecks as $check ) {
572 $status = $this->$check();
573 if ( $status ===
false ) {
578 $this->setVar(
'_Environment', $good );
580 return $good ? Status::newGood() : Status::newFatal(
'config-env-bad' );
589 public function setVar( $name, $value ) {
590 $this->settings[$name] = $value;
603 public function getVar( $name, $default =
null ) {
604 return $this->settings[$name] ?? $default;
613 return $this->compiledDBs;
624 return '\\MediaWiki\\Installer\\' . ucfirst( $type ) .
'Installer';
636 $type = $this->getVar(
'wgDBtype' );
639 $type = strtolower( $type );
641 if ( !isset( $this->dbInstallers[$type] ) ) {
642 $class = self::getDBInstallerClass( $type );
643 $this->dbInstallers[$type] =
new $class( $this );
646 return $this->dbInstallers[$type];
673 $lsExists = @file_exists( $lsFile );
679 if ( !str_ends_with( $lsFile,
'.php' ) ) {
680 throw new RuntimeException(
681 'The installer cannot yet handle non-php settings files: ' . $lsFile .
'. ' .
682 'Use `php maintenance/run.php update` to update an existing installation.'
688 foreach ( MainConfigSchema::listDefaultValues(
'wg' ) as $var => $value ) {
715 return get_defined_vars();
728 return str_repeat(
'*', strlen( $realPassword ) );
739 if ( !preg_match(
'/^\*+$/', $value ) ) {
740 $this->setVar( $name, $value );
760 public function parse( $text, $lineStart =
false ) {
764 $out = $parser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
767 $html = $pipeline->run( $out, $this->parserOptions, [
768 'enableSectionEditLinks' =>
false,
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 AtEase::suppressWarnings();
1118 foreach ( $scriptTypes as $ext => $contents ) {
1119 foreach ( $contents as
$source ) {
1120 $file =
'exectest.' . $ext;
1122 if ( !file_put_contents( $dir . $file,
$source ) ) {
1127 $text = $httpRequestFactory->get(
1132 }
catch ( Exception ) {
1137 unlink( $dir . $file );
1139 if ( $text ==
'exec' ) {
1140 AtEase::restoreWarnings();
1147 AtEase::restoreWarnings();
1159 if ( function_exists(
'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1164 phpinfo( INFO_MODULES );
1165 $info = ob_get_clean();
1167 return str_contains( $info, $moduleName );
1176 $this->parserOptions->setTargetLanguage( $lang );
1177 $this->parserOptions->setUserLang( $lang );
1186 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1199 switch ( $directory ) {
1201 return $this->findExtensionsByType(
'extension',
'extensions' );
1203 return $this->findExtensionsByType(
'skin',
'skins' );
1205 throw new InvalidArgumentException(
"Invalid extension type" );
1219 if ( $this->getVar(
'IP' ) ===
null ) {
1220 return Status::newGood( [] );
1223 $extDir = $this->getVar(
'IP' ) .
'/' . $directory;
1224 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
1225 return Status::newGood( [] );
1228 $dh = opendir( $extDir );
1232 while ( ( $file = readdir( $dh ) ) !==
false ) {
1234 if ( !is_dir(
"$extDir/$file" ) || $file[0] ===
'.' ) {
1237 $extStatus = $this->getExtensionInfo( $type, $directory, $file );
1238 if ( $extStatus->isOK() ) {
1239 $exts[$file] = $extStatus->value;
1240 } elseif ( $extStatus->hasMessage(
'config-extension-not-found' ) ) {
1242 $status->warning(
'config-extension-not-found', $file );
1244 $status->merge( $extStatus );
1248 uksort( $exts,
'strnatcasecmp' );
1250 $status->value = $exts;
1263 if ( $this->getVar(
'IP' ) ===
null ) {
1264 throw new RuntimeException(
'Cannot find extensions since the IP variable is not yet set' );
1266 if ( $type !==
'extension' && $type !==
'skin' ) {
1267 throw new InvalidArgumentException(
"Invalid extension type" );
1269 $absDir = $this->getVar(
'IP' ) .
"/$parentRelPath/$name";
1270 $relDir =
"../$parentRelPath/$name";
1271 if ( !is_dir( $absDir ) ) {
1272 return Status::newFatal(
'config-extension-not-found', $name );
1274 $jsonFile = $type .
'.json';
1275 $fullJsonFile =
"$absDir/$jsonFile";
1276 $isJson = file_exists( $fullJsonFile );
1280 $fullPhpFile =
"$absDir/$name.php";
1281 $isPhp = file_exists( $fullPhpFile );
1283 if ( !$isJson && !$isPhp ) {
1284 return Status::newFatal(
'config-extension-not-found', $name );
1289 if ( is_dir(
"$absDir/screenshots" ) ) {
1290 $paths = glob(
"$absDir/screenshots/*.png" );
1291 foreach ( $paths as
$path ) {
1292 $info[
'screenshots'][] = str_replace( $absDir, $relDir,
$path );
1297 $jsonStatus = $this->readExtension( $fullJsonFile );
1298 if ( !$jsonStatus->isOK() ) {
1301 $info += $jsonStatus->value;
1304 return Status::newGood( $info );
1315 private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
1320 $extDir = $this->getVar(
'IP' ) .
'/extensions';
1321 foreach ( $extDeps as $dep ) {
1322 $fname =
"$extDir/$dep/extension.json";
1323 if ( !file_exists( $fname ) ) {
1324 return Status::newFatal(
'config-extension-not-found', $dep );
1330 $skinDir = $this->getVar(
'IP' ) .
'/skins';
1331 foreach ( $skinDeps as $dep ) {
1332 $fname =
"$skinDir/$dep/skin.json";
1333 if ( !file_exists( $fname ) ) {
1334 return Status::newFatal(
'config-extension-not-found', $dep );
1339 $registry =
new ExtensionRegistry();
1341 $info = $registry->readFromQueue( $load );
1342 }
catch ( ExtensionDependencyError $e ) {
1343 if ( $e->incompatibleCore || $e->incompatibleSkins
1344 || $e->incompatibleExtensions
1348 return Status::newFatal(
'config-extension-dependency',
1349 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1350 } elseif ( $e->missingExtensions || $e->missingSkins ) {
1353 $status = $this->readExtension(
1355 array_merge( $extDeps, $e->missingExtensions ),
1356 array_merge( $skinDeps, $e->missingSkins )
1358 if ( !$status->isOK() && !$status->hasMessage(
'config-extension-dependency' ) ) {
1359 $status = Status::newFatal(
'config-extension-dependency',
1360 basename( dirname( $fullJsonFile ) ), $status->getMessage() );
1365 return Status::newFatal(
'config-extension-dependency',
1366 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1373 foreach ( $info[
'credits'] as $credit ) {
1379 $type = basename( $credit[
'path'] ) ===
'skin.json' ?
'skins' :
'extensions';
1380 $ret[
'requires'][$type][] = $credit[
'name'];
1382 $credits = array_values( $info[
'credits'] )[0];
1383 if ( isset( $credits[
'url'] ) ) {
1384 $ret[
'url'] = $credits[
'url'];
1386 $ret[
'type'] = $credits[
'type'];
1388 return Status::newGood( $ret );
1400 $defaultSkin = $GLOBALS[
'wgDefaultSkin'];
1402 if ( in_array(
'vector', $skinNames ) ) {
1403 $skinNames[] =
'vector-2022';
1407 if ( in_array(
'minervaneue', $skinNames ) ) {
1408 $minervaNeue = array_search(
'minervaneue', $skinNames );
1409 $skinNames[$minervaNeue] =
'minerva';
1412 if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) {
1413 return $defaultSkin;
1415 return $skinNames[0];
1429 $taskFactory = $this->getTaskFactory();
1430 $taskFactory->registerMainTasks( $taskList, TaskFactory::PROFILE_INSTALLER );
1433 foreach ( $this->extraInstallSteps as $requirement => $steps ) {
1434 foreach ( $steps as $spec ) {
1435 if ( $requirement !==
'BEGINNING' ) {
1436 $spec += [
'after' => $requirement ];
1438 $taskList->add( $taskFactory->create( $spec ) );
1446 if ( $this->taskFactory === null ) {
1448 MediaWikiServices::getInstance()->getObjectFactory(),
1449 $this->getDBInstaller()
1452 return $this->taskFactory;
1464 $tasks = $this->getTaskList();
1466 $taskRunner =
new TaskRunner( $tasks, $this->getTaskFactory(),
1467 TaskFactory::PROFILE_INSTALLER );
1468 $taskRunner->addTaskStartListener( $startCB );
1469 $taskRunner->addTaskEndListener( $endCB );
1471 $status = $taskRunner->execute();
1472 if ( $status->isOK() ) {
1474 'config-install-db-success'
1476 $this->setVar(
'_InstallDone',
true );
1488 if ( !defined(
'MW_NO_SESSION_HANDLER' ) ) {
1489 define(
'MW_NO_SESSION_HANDLER', 1 );
1495 MainConfigNames::UseDatabaseMessages =>
false,
1498 MainConfigNames::LanguageConverterCacheType =>
CACHE_NONE,
1501 MainConfigNames::ShowExceptionDetails =>
true,
1502 MainConfigNames::ShowHostnames =>
true,
1505 MainConfigNames::ExternalLinkTarget =>
'_blank',
1508 MainConfigNames::DisableOutputCompression =>
true,
1511 MainConfigNames::CookiePrefix =>
'mw_installer',
1514 MainConfigNames::MaxShellMemory => 0,
1518 MainConfigNames::SessionProviders => [
1520 'class' => InstallerSessionProvider::class,
1531 MainConfigNames::SessionCacheType =>
CACHE_NONE,
1535 MainConfigNames::Server =>
'https://🌻.invalid',
1547 $this->extraInstallSteps[$findStep][] = $callback;
1555 AtEase::suppressWarnings();
1556 set_time_limit( 0 );
1557 AtEase::restoreWarnings();
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'))
Utility class to find executables in likely places.
static findInDefaultPaths( $names, $versionInfo=false)
Same as locateExecutable(), but checks in getPossibleBinPaths() by default.
A cryptographic random generator class used for generating secret keys.
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()
$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.