31use GuzzleHttp\Psr7\Header;
33use InvalidArgumentException;
58use Wikimedia\AtEase\AtEase;
61use Wikimedia\Services\ServiceDisabledException;
159 'envCheckModSecurity',
165 'envCheckUploadsDirectory',
166 'envCheckUploadsServerResponse',
175 private const DEFAULT_VAR_NAMES = [
214 '_Environment' =>
false,
215 '_RaiseMemory' =>
false,
216 '_UpgradeDone' =>
false,
217 '_InstallDone' =>
false,
219 '_InstallPassword' =>
'',
220 '_SameAccount' =>
true,
221 '_CreateDBAccount' =>
false,
222 '_NamespaceType' =>
'site-name',
224 '_AdminPassword' =>
'',
225 '_AdminPasswordConfirm' =>
'',
227 '_Subscribe' =>
false,
228 '_SkipOptional' =>
'continue',
229 '_RightsProfile' =>
'wiki',
230 '_LicenseCode' =>
'none',
234 '_MemCachedServers' =>
'',
235 '_UpgradeKeySupplied' =>
false,
236 '_ExistingDBSettings' =>
false,
237 '_LogoWordmark' =>
'',
238 '_LogoWordmarkWidth' => 119,
239 '_LogoWordmarkHeight' => 18,
241 '_Logo1x' =>
'$wgResourceBasePath/resources/assets/change-your-logo.svg',
242 '_LogoIcon' =>
'$wgResourceBasePath/resources/assets/change-your-logo-icon.svg',
243 '_LogoTagline' =>
'',
244 '_LogoTaglineWidth' => 117,
245 '_LogoTaglineHeight' => 13,
246 '_WithDevelopmentSettings' =>
false,
247 'wgAuthenticationTokenVersion' => 1,
263 'apcu' =>
'apcu_fetch',
274 '*' => [
'edit' => false ]
278 'createaccount' =>
false,
284 'createaccount' =>
false,
298 'url' =>
'https://creativecommons.org/licenses/by/4.0/',
299 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/cc-by.png',
302 'url' =>
'https://creativecommons.org/licenses/by-sa/4.0/',
303 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/cc-by-sa.png',
306 'url' =>
'https://creativecommons.org/licenses/by-nc-sa/4.0/',
307 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/cc-by-nc-sa.png',
310 'url' =>
'https://creativecommons.org/publicdomain/zero/1.0/',
311 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/cc-0.png',
314 'url' =>
'https://www.gnu.org/copyleft/fdl.html',
315 'icon' =>
'$wgResourceBasePath/resources/assets/licenses/gnu-fdl.png',
331 private $taskFactory;
393 $emptyCache = [
'class' => EmptyBagOStuff::class ];
404 $installerConfig =
new MultiConfig( [ $configOverrides, $baseConfig ] );
408 $configRegistry[
'main'] =
static function () use ( $installerConfig ) {
409 return $installerConfig;
414 return $installerConfig;
425 $lang = $this->
getVar(
'_UserLang',
'en' );
429 $user = RequestContext::getMain()->getUser();
432 $this->
setParserLanguage( $services->getLanguageFactory()->getLanguage(
'en' ) );
434 $this->settings = $this->getDefaultSettings();
436 $this->compiledDBs = [];
437 foreach ( self::getDBTypes() as $type ) {
440 if ( !$installer->isCompiled() ) {
443 $this->compiledDBs[] = $type;
446 $this->parserTitle = Title::newFromText(
'Installer' );
449 private function getDefaultSettings(): array {
454 foreach ( self::DEFAULT_VAR_NAMES as $name ) {
465 if ( $server !==
null ) {
466 $ret[
'wgServer'] = $server;
470 $ret[
'IP'] = MW_INSTALL_PATH;
473 + $this->generateKeys()
502 private function generateKeys() {
505 'wgUpgradeKey' => 16,
509 foreach ( $keyLengths as $name => $length ) {
510 $keys[$name] = MWCryptRand::generateHex( $length );
531 $mwServices->disableStorage();
534 $mwServices->getLocalisationCache()->disableBackend();
539 $user = User::newFromId( 0 );
540 StubGlobalUser::setUser( $user );
542 RequestContext::getMain()->setUser( $user );
548 RequestContext::getMain()->setLanguage( $lang );
549 $wgLang = RequestContext::getMain()->getLanguage();
563 return self::$dbTypes;
582 $this->showMessage(
'config-env-php', PHP_VERSION );
585 foreach ( $this->envChecks as $check ) {
586 $status = $this->$check();
587 if ( $status ===
false ) {
592 $this->setVar(
'_Environment', $good );
594 return $good ? Status::newGood() : Status::newFatal(
'config-env-bad' );
603 public function setVar( $name, $value ) {
604 $this->settings[$name] = $value;
617 public function getVar( $name, $default =
null ) {
618 return $this->settings[$name] ?? $default;
627 return $this->compiledDBs;
638 return '\\MediaWiki\\Installer\\' . ucfirst( $type ) .
'Installer';
650 $type = $this->getVar(
'wgDBtype' );
653 $type = strtolower( $type );
655 if ( !isset( $this->dbInstallers[$type] ) ) {
656 $class = self::getDBInstallerClass( $type );
657 $this->dbInstallers[$type] =
new $class( $this );
660 return $this->dbInstallers[$type];
687 $lsExists = @file_exists( $lsFile );
693 if ( !str_ends_with( $lsFile,
'.php' ) ) {
694 throw new RuntimeException(
695 'The installer cannot yet handle non-php settings files: ' . $lsFile .
'. ' .
696 'Use `php maintenance/run.php update` to update an existing installation.'
722 return get_defined_vars();
735 return str_repeat(
'*', strlen( $realPassword ) );
746 if ( !preg_match(
'/^\*+$/', $value ) ) {
747 $this->setVar( $name, $value );
767 public function parse( $text, $lineStart =
false ) {
771 $out = $parser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
774 $html = $pipeline->run( $out, $this->parserOptions, [
775 'enableSectionEditLinks' =>
false,
777 ] )->getContentHolderText();
778 $html = Parser::stripOuterParagraph( $html );
779 }
catch ( ServiceDisabledException $e ) {
780 $html =
'<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
790 return $this->parserOptions;
796 $this->parserOptions->setExternalLinkTarget(
false );
813 $dbType = $this->getVar(
'wgDBtype' );
818 foreach ( self::getDBTypes() as $name ) {
819 $allNames[] =
wfMessage(
"config-type-$name" )->text();
822 $databases = $this->getCompiledDBs();
824 $databases = array_flip( $databases );
826 foreach ( $databases as $db => $_ ) {
827 $installer = $this->getDBInstaller( $db );
828 $status = $installer->checkPrerequisites();
829 if ( !$status->isGood() ) {
830 if ( !$this instanceof
WebInstaller && $db === $dbType ) {
836 $this->showStatusMessage( $status );
837 unset( $databases[$db] );
840 $databases = array_flip( $databases );
842 $this->showError(
'config-no-db',
$wgLang->commaList( $allNames ), count( $allNames ) );
861 if ( preg_match(
'/^b.*c$/',
'bÄ…c' ) === 0 ) {
862 $this->showError(
'config-pcre-invalid-newline' );
873 $limit = ini_get(
'memory_limit' );
875 if ( !$limit || $limit == -1 ) {
881 if ( $n < $this->minMemorySize * 1024 * 1024 ) {
882 $newLimit =
"{$this->minMemorySize}M";
884 if ( ini_set(
"memory_limit", $newLimit ) ===
false ) {
885 $this->showMessage(
'config-memory-bad', $limit );
887 $this->showMessage(
'config-memory-raised', $limit, $newLimit );
888 $this->setVar(
'_RaiseMemory',
true );
900 foreach ( $this->objectCaches as $name => $function ) {
901 if ( function_exists( $function ) ) {
902 $caches[$name] =
true;
907 $this->showMessage(
'config-no-cache-apcu' );
910 $this->setVar(
'_Caches', $caches );
918 if ( self::apacheModulePresent(
'mod_security' )
919 || self::apacheModulePresent(
'mod_security2' ) ) {
920 $this->showMessage(
'config-mod-security' );
931 $names = [
"gdiff3",
"diff3" ];
933 $names[] =
'diff3.exe';
935 $versionInfo = [
'--version',
'GNU diffutils' ];
940 $this->setVar(
'wgDiff3', $diff3 );
942 $this->setVar(
'wgDiff3',
false );
943 $this->showMessage(
'config-diff3-bad' );
954 $names =
wfIsWindows() ?
'convert.exe' :
'convert';
955 $versionInfo = [
'-version',
'ImageMagick' ];
958 $this->setVar(
'wgImageMagickConvertCommand',
'' );
960 $this->setVar(
'wgImageMagickConvertCommand', $convert );
961 $this->showMessage(
'config-imagemagick', $convert );
962 } elseif ( function_exists(
'imagejpeg' ) ) {
963 $this->showMessage(
'config-gd' );
965 $this->showMessage(
'config-no-scaling' );
979 $versionInfo = [
'--version',
'git version' ];
984 $this->setVar(
'wgGitBin', $git );
985 $this->showMessage(
'config-git', $git );
987 $this->setVar(
'wgGitBin',
false );
988 $this->showMessage(
'config-git-bad' );
1000 $server = $this->envGetDefaultServer();
1001 if ( $server !==
null ) {
1002 $this->showMessage(
'config-using-server', $server );
1015 $this->getVar(
'wgServer' ),
1016 $this->getVar(
'wgScriptPath' )
1028 $dir =
$IP .
'/images/';
1029 $url = $this->getVar(
'wgServer' ) . $this->getVar(
'wgScriptPath' ) .
'/images/';
1030 $safe = !$this->dirIsExecutable( $dir,
$url );
1033 $this->showWarning(
'config-uploads-not-safe', $dir );
1040 $url = $this->getVar(
'wgServer' ) . $this->getVar(
'wgScriptPath' ) .
'/images/README';
1044 $req = $httpRequestFactory->create(
1049 'followRedirects' =>
true
1054 $status = $req->execute();
1055 }
catch ( Exception $e ) {
1060 if ( !$status || !$status->isGood() ) {
1061 $this->showWarning(
'config-uploads-security-requesterror',
'X-Content-Type-Options: nosniff' );
1065 $headerValue = $req->getResponseHeader(
'X-Content-Type-Options' ) ??
'';
1066 $responseList = Header::splitList( $headerValue );
1067 if ( !in_array(
'nosniff', $responseList,
true ) ) {
1068 $this->showWarning(
'config-uploads-security-headers',
'X-Content-Type-Options: nosniff' );
1081 if ( PHP_INT_SIZE == 4 ) {
1082 $this->showMessage(
'config-using-32bit' );
1092 $unicodeVersion = implode(
'.', array_slice( IntlChar::getUnicodeVersion(), 0, 3 ) );
1093 $this->showMessage(
'config-env-icu', INTL_ICU_VERSION, $unicodeVersion );
1113 "<?php echo 'exec';",
1114 "#!/var/env php\n<?php echo 'exec';",
1123 AtEase::suppressWarnings();
1125 foreach ( $scriptTypes as $ext => $contents ) {
1126 foreach ( $contents as
$source ) {
1127 $file =
'exectest.' . $ext;
1129 if ( !file_put_contents( $dir . $file,
$source ) ) {
1134 $text = $httpRequestFactory->get(
1139 }
catch ( Exception $e ) {
1144 unlink( $dir . $file );
1146 if ( $text ==
'exec' ) {
1147 AtEase::restoreWarnings();
1154 AtEase::restoreWarnings();
1166 if ( function_exists(
'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1171 phpinfo( INFO_MODULES );
1172 $info = ob_get_clean();
1174 return strpos( $info, $moduleName ) !==
false;
1183 $this->parserOptions->setTargetLanguage( $lang );
1184 $this->parserOptions->setUserLang( $lang );
1193 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1206 switch ( $directory ) {
1208 return $this->findExtensionsByType(
'extension',
'extensions' );
1210 return $this->findExtensionsByType(
'skin',
'skins' );
1212 throw new InvalidArgumentException(
"Invalid extension type" );
1226 if ( $this->getVar(
'IP' ) ===
null ) {
1227 return Status::newGood( [] );
1230 $extDir = $this->getVar(
'IP' ) .
'/' . $directory;
1231 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
1232 return Status::newGood( [] );
1235 $dh = opendir( $extDir );
1239 while ( ( $file = readdir( $dh ) ) !==
false ) {
1241 if ( !is_dir(
"$extDir/$file" ) || $file[0] ===
'.' ) {
1244 $extStatus = $this->getExtensionInfo( $type, $directory, $file );
1245 if ( $extStatus->isOK() ) {
1246 $exts[$file] = $extStatus->value;
1247 } elseif ( $extStatus->hasMessage(
'config-extension-not-found' ) ) {
1249 $status->warning(
'config-extension-not-found', $file );
1251 $status->merge( $extStatus );
1255 uksort( $exts,
'strnatcasecmp' );
1257 $status->value = $exts;
1270 if ( $this->getVar(
'IP' ) ===
null ) {
1271 throw new RuntimeException(
'Cannot find extensions since the IP variable is not yet set' );
1273 if ( $type !==
'extension' && $type !==
'skin' ) {
1274 throw new InvalidArgumentException(
"Invalid extension type" );
1276 $absDir = $this->getVar(
'IP' ) .
"/$parentRelPath/$name";
1277 $relDir =
"../$parentRelPath/$name";
1278 if ( !is_dir( $absDir ) ) {
1279 return Status::newFatal(
'config-extension-not-found', $name );
1281 $jsonFile = $type .
'.json';
1282 $fullJsonFile =
"$absDir/$jsonFile";
1283 $isJson = file_exists( $fullJsonFile );
1287 $fullPhpFile =
"$absDir/$name.php";
1288 $isPhp = file_exists( $fullPhpFile );
1290 if ( !$isJson && !$isPhp ) {
1291 return Status::newFatal(
'config-extension-not-found', $name );
1296 if ( is_dir(
"$absDir/screenshots" ) ) {
1297 $paths = glob(
"$absDir/screenshots/*.png" );
1298 foreach ( $paths as
$path ) {
1299 $info[
'screenshots'][] = str_replace( $absDir, $relDir,
$path );
1304 $jsonStatus = $this->readExtension( $fullJsonFile );
1305 if ( !$jsonStatus->isOK() ) {
1308 $info += $jsonStatus->value;
1311 return Status::newGood( $info );
1322 private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
1327 $extDir = $this->getVar(
'IP' ) .
'/extensions';
1328 foreach ( $extDeps as $dep ) {
1329 $fname =
"$extDir/$dep/extension.json";
1330 if ( !file_exists( $fname ) ) {
1331 return Status::newFatal(
'config-extension-not-found', $dep );
1337 $skinDir = $this->getVar(
'IP' ) .
'/skins';
1338 foreach ( $skinDeps as $dep ) {
1339 $fname =
"$skinDir/$dep/skin.json";
1340 if ( !file_exists( $fname ) ) {
1341 return Status::newFatal(
'config-extension-not-found', $dep );
1346 $registry =
new ExtensionRegistry();
1348 $info = $registry->readFromQueue( $load );
1349 }
catch ( ExtensionDependencyError $e ) {
1350 if ( $e->incompatibleCore || $e->incompatibleSkins
1351 || $e->incompatibleExtensions
1355 return Status::newFatal(
'config-extension-dependency',
1356 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1357 } elseif ( $e->missingExtensions || $e->missingSkins ) {
1360 $status = $this->readExtension(
1362 array_merge( $extDeps, $e->missingExtensions ),
1363 array_merge( $skinDeps, $e->missingSkins )
1365 if ( !$status->isOK() && !$status->hasMessage(
'config-extension-dependency' ) ) {
1366 $status = Status::newFatal(
'config-extension-dependency',
1367 basename( dirname( $fullJsonFile ) ), $status->getMessage() );
1372 return Status::newFatal(
'config-extension-dependency',
1373 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1380 foreach ( $info[
'credits'] as $credit ) {
1386 $type = basename( $credit[
'path'] ) ===
'skin.json' ?
'skins' :
'extensions';
1387 $ret[
'requires'][$type][] = $credit[
'name'];
1389 $credits = array_values( $info[
'credits'] )[0];
1390 if ( isset( $credits[
'url'] ) ) {
1391 $ret[
'url'] = $credits[
'url'];
1393 $ret[
'type'] = $credits[
'type'];
1395 return Status::newGood( $ret );
1407 $defaultSkin = $GLOBALS[
'wgDefaultSkin'];
1409 if ( in_array(
'vector', $skinNames ) ) {
1410 $skinNames[] =
'vector-2022';
1414 if ( in_array(
'minervaneue', $skinNames ) ) {
1415 $minervaNeue = array_search(
'minervaneue', $skinNames );
1416 $skinNames[$minervaNeue] =
'minerva';
1419 if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) {
1420 return $defaultSkin;
1422 return $skinNames[0];
1436 $taskFactory = $this->getTaskFactory();
1437 $taskFactory->registerMainTasks( $taskList, TaskFactory::PROFILE_INSTALLER );
1440 foreach ( $this->extraInstallSteps as $requirement => $steps ) {
1441 foreach ( $steps as $spec ) {
1442 if ( $requirement !==
'BEGINNING' ) {
1443 $spec += [
'after' => $requirement ];
1445 $taskList->add( $taskFactory->create( $spec ) );
1453 if ( $this->taskFactory ===
null ) {
1456 $this->getDBInstaller()
1459 return $this->taskFactory;
1471 $tasks = $this->getTaskList();
1473 $taskRunner =
new TaskRunner( $tasks, $this->getTaskFactory(),
1474 TaskFactory::PROFILE_INSTALLER );
1475 $taskRunner->addTaskStartListener( $startCB );
1476 $taskRunner->addTaskEndListener( $endCB );
1478 $status = $taskRunner->execute();
1479 if ( $status->isOK() ) {
1481 'config-install-db-success'
1483 $this->setVar(
'_InstallDone',
true );
1495 if ( !defined(
'MW_NO_SESSION_HANDLER' ) ) {
1496 define(
'MW_NO_SESSION_HANDLER', 1 );
1527 'class' => InstallerSessionProvider::class,
1554 $this->extraInstallSteps[$findStep][] = $callback;
1562 AtEase::suppressWarnings();
1563 set_time_limit( 0 );
1564 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(ini_get('mbstring.func_overload')) if(!defined( 'MW_ENTRY_POINT')) global $IP
Environment checks.
if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgLang
Utility class to find executables in likely places.
static findInDefaultPaths( $names, $versionInfo=false)
Same as locateExecutable(), but checks in getPossibleBinPaths() by default.
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 ExternalLinkTarget
Name constant for the ExternalLinkTarget setting, for use with Config::get()
const EnotifWatchlist
Name constant for the EnotifWatchlist setting, for use with Config::get()
const MainStash
Name constant for the MainStash setting, for use with Config::get()
const DefaultSkin
Name constant for the DefaultSkin setting, for use with Config::get()
const MaxShellMemory
Name constant for the MaxShellMemory 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 Server
Name constant for the Server 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 UseDatabaseMessages
Name constant for the UseDatabaseMessages setting, for use with Config::get()
const ShowExceptionDetails
Name constant for the ShowExceptionDetails 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 LanguageConverterCacheType
Name constant for the LanguageConverterCacheType setting, for use with Config::get()
const Pingback
Name constant for the Pingback setting, for use with Config::get()
const SessionCacheType
Name constant for the SessionCacheType 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 SessionProviders
Name constant for the SessionProviders 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 DisableOutputCompression
Name constant for the DisableOutputCompression 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 CookiePrefix
Name constant for the CookiePrefix 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()
const ShowHostnames
Name constant for the ShowHostnames setting, for use with Config::get()
This class contains schema declarations for all configuration variables known to MediaWiki core.
static getDefaultValue(string $name)
Returns the default value of the given config setting.
static listDefaultValues(string $prefix='')
Returns a generator for iterating over all config settings and their default values.
$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.