MediaWiki REL1_35
Installer.php
Go to the documentation of this file.
1<?php
32
54abstract class Installer {
55
62 public const MINIMUM_PCRE_VERSION = '7.2';
63
67 private const MEDIAWIKI_ANNOUNCE_URL =
68 'https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/';
69
73 protected $settings;
74
80 protected $compiledDBs;
81
87 protected $dbInstallers = [];
88
94 protected $minMemorySize = 50;
95
101 protected $parserTitle;
102
108 protected $parserOptions;
109
119 protected static $dbTypes = [
120 'mysql',
121 'postgres',
122 'sqlite',
123 ];
124
136 protected $envChecks = [
137 'envCheckDB',
138 'envCheckPCRE',
139 'envCheckMemory',
140 'envCheckCache',
141 'envCheckModSecurity',
142 'envCheckDiff3',
143 'envCheckGraphics',
144 'envCheckGit',
145 'envCheckServer',
146 'envCheckPath',
147 'envCheckShellLocale',
148 'envCheckUploadsDirectory',
149 'envCheckLibicu',
150 'envCheckSuhosinMaxValueLength',
151 'envCheck64Bit',
152 ];
153
159 protected $envPreps = [
160 'envPrepServer',
161 'envPrepPath',
162 ];
163
171 protected $defaultVarNames = [
172 'wgSitename',
173 'wgPasswordSender',
174 'wgLanguageCode',
175 'wgRightsIcon',
176 'wgRightsText',
177 'wgRightsUrl',
178 'wgEnableEmail',
179 'wgEnableUserEmail',
180 'wgEnotifUserTalk',
181 'wgEnotifWatchlist',
182 'wgEmailAuthentication',
183 'wgDBname',
184 'wgDBtype',
185 'wgDiff3',
186 'wgImageMagickConvertCommand',
187 'wgGitBin',
188 'IP',
189 'wgScriptPath',
190 'wgMetaNamespace',
191 'wgDeletedDirectory',
192 'wgEnableUploads',
193 'wgShellLocale',
194 'wgSecretKey',
195 'wgUseInstantCommons',
196 'wgUpgradeKey',
197 'wgDefaultSkin',
198 'wgPingback',
199 ];
200
208 protected $internalDefaults = [
209 '_UserLang' => 'en',
210 '_Environment' => false,
211 '_RaiseMemory' => false,
212 '_UpgradeDone' => false,
213 '_InstallDone' => false,
214 '_Caches' => [],
215 '_InstallPassword' => '',
216 '_SameAccount' => true,
217 '_CreateDBAccount' => false,
218 '_NamespaceType' => 'site-name',
219 '_AdminName' => '', // will be set later, when the user selects language
220 '_AdminPassword' => '',
221 '_AdminPasswordConfirm' => '',
222 '_AdminEmail' => '',
223 '_Subscribe' => false,
224 '_SkipOptional' => 'continue',
225 '_RightsProfile' => 'wiki',
226 '_LicenseCode' => 'none',
227 '_CCDone' => false,
228 '_Extensions' => [],
229 '_Skins' => [],
230 '_MemCachedServers' => '',
231 '_UpgradeKeySupplied' => false,
232 '_ExistingDBSettings' => false,
233 // Single quotes are intentional, LocalSettingsGenerator must output this unescaped.
234 '_Logo' => '$wgResourceBasePath/resources/assets/wiki.png',
235
236 'wgAuthenticationTokenVersion' => 1,
237 ];
238
245 private $installSteps = [];
246
252 protected $extraInstallSteps = [];
253
259 protected $objectCaches = [
260 'apcu' => 'apcu_fetch',
261 'wincache' => 'wincache_ucache_get'
262 ];
263
270 'wiki' => [],
271 'no-anon' => [
272 '*' => [ 'edit' => false ]
273 ],
274 'fishbowl' => [
275 '*' => [
276 'createaccount' => false,
277 'edit' => false,
278 ],
279 ],
280 'private' => [
281 '*' => [
282 'createaccount' => false,
283 'edit' => false,
284 'read' => false,
285 ],
286 ],
287 ];
288
294 public $licenses = [
295 'cc-by' => [
296 'url' => 'https://creativecommons.org/licenses/by/4.0/',
297 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by.png',
298 ],
299 'cc-by-sa' => [
300 'url' => 'https://creativecommons.org/licenses/by-sa/4.0/',
301 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-sa.png',
302 ],
303 'cc-by-nc-sa' => [
304 'url' => 'https://creativecommons.org/licenses/by-nc-sa/4.0/',
305 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-nc-sa.png',
306 ],
307 'cc-0' => [
308 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
309 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-0.png',
310 ],
311 'gfdl' => [
312 'url' => 'https://www.gnu.org/copyleft/fdl.html',
313 'icon' => '$wgResourceBasePath/resources/assets/licenses/gnu-fdl.png',
314 ],
315 'none' => [
316 'url' => '',
317 'icon' => '',
318 'text' => ''
319 ],
320 'cc-choose' => [
321 // Details will be filled in by the selector.
322 'url' => '',
323 'icon' => '',
324 'text' => '',
325 ],
326 ];
327
332
341 abstract public function showMessage( $msg, ...$params );
342
348 abstract public function showError( $msg, ...$params );
349
354 abstract public function showStatusMessage( Status $status );
355
366 public static function getInstallerConfig( Config $baseConfig ) {
367 $configOverrides = new HashConfig();
368
369 // disable (problematic) object cache types explicitly, preserving all other (working) ones
370 // bug T113843
371 $emptyCache = [ 'class' => EmptyBagOStuff::class ];
372
373 $objectCaches = [
374 CACHE_NONE => $emptyCache,
375 CACHE_DB => $emptyCache,
376 CACHE_ANYTHING => $emptyCache,
377 CACHE_MEMCACHED => $emptyCache,
378 ] + $baseConfig->get( 'ObjectCaches' );
379
380 $configOverrides->set( 'ObjectCaches', $objectCaches );
381
382 // Load the installer's i18n.
383 $messageDirs = $baseConfig->get( 'MessagesDirs' );
384 $messageDirs['MediawikiInstaller'] = __DIR__ . '/i18n';
385
386 $configOverrides->set( 'MessagesDirs', $messageDirs );
387
388 $installerConfig = new MultiConfig( [ $configOverrides, $baseConfig ] );
389
390 // make sure we use the installer config as the main config
391 $configRegistry = $baseConfig->get( 'ConfigRegistry' );
392 $configRegistry['main'] = function () use ( $installerConfig ) {
393 return $installerConfig;
394 };
395
396 $configOverrides->set( 'ConfigRegistry', $configRegistry );
397
398 return $installerConfig;
399 }
400
404 public function __construct() {
405 $defaultConfig = new GlobalVarConfig(); // all the stuff from DefaultSettings.php
406 $installerConfig = self::getInstallerConfig( $defaultConfig );
407
408 $this->resetMediaWikiServices( $installerConfig );
409
410 // Disable all storage services, since we don't have any configuration yet!
411 MediaWikiServices::disableStorageBackend();
412
413 $this->settings = $this->internalDefaults;
414
415 foreach ( $this->defaultVarNames as $var ) {
416 $this->settings[$var] = $GLOBALS[$var];
417 }
418
419 $this->doEnvironmentPreps();
420
421 $this->compiledDBs = [];
422 foreach ( self::getDBTypes() as $type ) {
423 $installer = $this->getDBInstaller( $type );
424
425 if ( !$installer->isCompiled() ) {
426 continue;
427 }
428 $this->compiledDBs[] = $type;
429 }
430
431 $this->parserTitle = Title::newFromText( 'Installer' );
432 }
433
448 public function resetMediaWikiServices( Config $installerConfig = null, $serviceOverrides = [] ) {
449 global $wgMemc, $wgUser, $wgObjectCaches, $wgLang;
450
451 $serviceOverrides += [
452 // Disable interwiki lookup, to avoid database access during parses
453 'InterwikiLookup' => function () {
454 return new NullInterwikiLookup();
455 },
456
457 // Disable user options database fetching, only rely on default options.
458 'UserOptionsLookup' => function ( MediaWikiServices $services ) {
459 return $services->get( '_DefaultOptionsLookup' );
460 }
461 ];
462
463 $lang = $this->getVar( '_UserLang', 'en' );
464
465 // Reset all services and inject config overrides
466 MediaWikiServices::resetGlobalInstance( $installerConfig );
467
468 $mwServices = MediaWikiServices::getInstance();
469
470 foreach ( $serviceOverrides as $name => $callback ) {
471 // Skip if the caller set $callback to null
472 // to suppress default overrides.
473 if ( $callback ) {
474 $mwServices->redefineService( $name, $callback );
475 }
476 }
477
478 // Disable i18n cache
479 $mwServices->getLocalisationCache()->disableBackend();
480
481 // Clear language cache so the old i18n cache doesn't sneak back in
482 Language::$mLangObjCache = [];
483
484 // Set a fake user.
485 // Note that this will reset the context's language,
486 // so set the user before setting the language.
487 $user = User::newFromId( 0 );
488 $wgUser = $user;
489
490 RequestContext::getMain()->setUser( $user );
491
492 // Don't attempt to load user language options (T126177)
493 // This will be overridden in the web installer with the user-specified language
494 // Ensure $wgLang does not have a reference to a stale LocalisationCache instance
495 // (T241638, T261081)
496 RequestContext::getMain()->setLanguage( $lang );
497 $wgLang = RequestContext::getMain()->getLanguage();
498
499 // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
500 // SqlBagOStuff will then throw since we just disabled wfGetDB)
501 $wgObjectCaches = $mwServices->getMainConfig()->get( 'ObjectCaches' );
502 $wgMemc = ObjectCache::getInstance( CACHE_NONE );
503
504 $this->parserOptions = new ParserOptions( $user ); // language will be wrong :(
505 // Don't try to access DB before user language is initialised
506 $this->setParserLanguage( $mwServices->getLanguageFactory()->getLanguage( 'en' ) );
507
508 return $mwServices;
509 }
510
516 public static function getDBTypes() {
517 return self::$dbTypes;
518 }
519
533 public function doEnvironmentChecks() {
534 // PHP version has already been checked by entry scripts
535 // Show message here for information purposes
536 $this->showMessage( 'config-env-php', PHP_VERSION );
537
538 $good = true;
539 // Must go here because an old version of PCRE can prevent other checks from completing
540 $pcreVersion = explode( ' ', PCRE_VERSION, 2 )[0];
541 if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) {
542 $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion );
543 $good = false;
544 } else {
545 foreach ( $this->envChecks as $check ) {
546 $status = $this->$check();
547 if ( $status === false ) {
548 $good = false;
549 }
550 }
551 }
552
553 $this->setVar( '_Environment', $good );
554
555 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
556 }
557
558 public function doEnvironmentPreps() {
559 foreach ( $this->envPreps as $prep ) {
560 $this->$prep();
561 }
562 }
563
570 public function setVar( $name, $value ) {
571 $this->settings[$name] = $value;
572 }
573
584 public function getVar( $name, $default = null ) {
585 return $this->settings[$name] ?? $default;
586 }
587
593 public function getCompiledDBs() {
594 return $this->compiledDBs;
595 }
596
604 public static function getDBInstallerClass( $type ) {
605 return ucfirst( $type ) . 'Installer';
606 }
607
615 public function getDBInstaller( $type = false ) {
616 if ( !$type ) {
617 $type = $this->getVar( 'wgDBtype' );
618 }
619
620 $type = strtolower( $type );
621
622 if ( !isset( $this->dbInstallers[$type] ) ) {
624 $this->dbInstallers[$type] = new $class( $this );
625 }
626
627 return $this->dbInstallers[$type];
628 }
629
635 public static function getExistingLocalSettings() {
636 global $IP;
637
638 // You might be wondering why this is here. Well if you don't do this
639 // then some poorly-formed extensions try to call their own classes
640 // after immediately registering them. We really need to get extension
641 // registration out of the global scope and into a real format.
642 // @see https://phabricator.wikimedia.org/T69440
643 global $wgAutoloadClasses;
645
646 // LocalSettings.php should not call functions, except wfLoadSkin/wfLoadExtensions
647 // Define the required globals here, to ensure, the functions can do it work correctly.
648 // phpcs:ignore MediaWiki.VariableAnalysis.UnusedGlobalVariables
650
651 Wikimedia\suppressWarnings();
652 $_lsExists = file_exists( "$IP/LocalSettings.php" );
653 Wikimedia\restoreWarnings();
654
655 if ( !$_lsExists ) {
656 return false;
657 }
658 unset( $_lsExists );
659
660 require "$IP/includes/DefaultSettings.php";
661 require "$IP/LocalSettings.php";
662
663 return get_defined_vars();
664 }
665
675 public function getFakePassword( $realPassword ) {
676 return str_repeat( '*', strlen( $realPassword ) );
677 }
678
686 public function setPassword( $name, $value ) {
687 if ( !preg_match( '/^\*+$/', $value ) ) {
688 $this->setVar( $name, $value );
689 }
690 }
691
703 public static function maybeGetWebserverPrimaryGroup() {
704 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
705 # I don't know this, this isn't UNIX.
706 return null;
707 }
708
709 # posix_getegid() *not* getmygid() because we want the group of the webserver,
710 # not whoever owns the current script.
711 $gid = posix_getegid();
712 return posix_getpwuid( $gid )['name'] ?? null;
713 }
714
731 public function parse( $text, $lineStart = false ) {
732 $parser = MediaWikiServices::getInstance()->getParser();
733
734 try {
735 $out = $parser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
736 $html = $out->getText( [
737 'enableSectionEditLinks' => false,
738 'unwrap' => true,
739 ] );
740 $html = Parser::stripOuterParagraph( $html );
741 } catch ( Wikimedia\Services\ServiceDisabledException $e ) {
742 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
743 }
744
745 return $html;
746 }
747
751 public function getParserOptions() {
753 }
754
755 public function disableLinkPopups() {
756 $this->parserOptions->setExternalLinkTarget( false );
757 }
758
759 public function restoreLinkPopups() {
761 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
762 }
763
772 public function populateSiteStats( DatabaseInstaller $installer ) {
773 $status = $installer->getConnection();
774 if ( !$status->isOK() ) {
775 return $status;
776 }
777 // @phan-suppress-next-line PhanUndeclaredMethod
778 $status->value->insert(
779 'site_stats',
780 [
781 'ss_row_id' => 1,
782 'ss_total_edits' => 0,
783 'ss_good_articles' => 0,
784 'ss_total_pages' => 0,
785 'ss_users' => 0,
786 'ss_active_users' => 0,
787 'ss_images' => 0
788 ],
789 __METHOD__,
790 'IGNORE'
791 );
792
793 return Status::newGood();
794 }
795
800 protected function envCheckDB() {
801 global $wgLang;
803 $dbType = $this->getVar( 'wgDBtype' );
804
805 $allNames = [];
806
807 // Messages: config-type-mysql, config-type-postgres, config-type-sqlite
808 foreach ( self::getDBTypes() as $name ) {
809 $allNames[] = wfMessage( "config-type-$name" )->text();
810 }
811
812 $databases = $this->getCompiledDBs();
813
814 $databases = array_flip( $databases );
815 $ok = true;
816 foreach ( array_keys( $databases ) as $db ) {
817 $installer = $this->getDBInstaller( $db );
818 $status = $installer->checkPrerequisites();
819 if ( !$status->isGood() ) {
820 if ( !$this instanceof WebInstaller && $db === $dbType ) {
821 // Strictly check the key database type instead of just outputting message
822 // Note: No perform this check run from the web installer, since this method always called by
823 // the welcome page under web installation, so $dbType will always be 'mysql'
824 $ok = false;
825 }
826 $this->showStatusMessage( $status );
827 unset( $databases[$db] );
828 }
829 }
830 $databases = array_flip( $databases );
831 if ( !$databases ) {
832 $this->showError( 'config-no-db', $wgLang->commaList( $allNames ), count( $allNames ) );
833 return false;
834 }
835 return $ok;
836 }
837
846 protected function envCheckPCRE() {
847 Wikimedia\suppressWarnings();
848 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
849 // Need to check for \p support too, as PCRE can be compiled
850 // with utf8 support, but not unicode property support.
851 // check that \p{Zs} (space separators) matches
852 // U+3000 (Ideographic space)
853 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\u{3000}-" );
854 Wikimedia\restoreWarnings();
855 if ( $regexd != '--' || $regexprop != '--' ) {
856 $this->showError( 'config-pcre-no-utf8' );
857
858 return false;
859 }
860
861 // PCRE must be compiled using PCRE_CONFIG_NEWLINE other than -1 (any)
862 // otherwise it will misidentify some unicode characters containing 0x85
863 // code with break lines
864 if ( preg_match( '/^b.*c$/', 'bąc' ) === 0 ) {
865 $this->showError( 'config-pcre-invalid-newline' );
866
867 return false;
868 }
869
870 return true;
871 }
872
877 protected function envCheckMemory() {
878 $limit = ini_get( 'memory_limit' );
879
880 if ( !$limit || $limit == -1 ) {
881 return true;
882 }
883
884 $n = wfShorthandToInteger( $limit );
885
886 if ( $n < $this->minMemorySize * 1024 * 1024 ) {
887 $newLimit = "{$this->minMemorySize}M";
888
889 if ( ini_set( "memory_limit", $newLimit ) === false ) {
890 $this->showMessage( 'config-memory-bad', $limit );
891 } else {
892 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
893 $this->setVar( '_RaiseMemory', true );
894 }
895 }
896
897 return true;
898 }
899
903 protected function envCheckCache() {
904 $caches = [];
905 foreach ( $this->objectCaches as $name => $function ) {
906 if ( function_exists( $function ) ) {
907 $caches[$name] = true;
908 }
909 }
910
911 if ( !$caches ) {
912 $this->showMessage( 'config-no-cache-apcu' );
913 }
914
915 $this->setVar( '_Caches', $caches );
916 }
917
922 protected function envCheckModSecurity() {
923 if ( self::apacheModulePresent( 'mod_security' )
924 || self::apacheModulePresent( 'mod_security2' ) ) {
925 $this->showMessage( 'config-mod-security' );
926 }
927
928 return true;
929 }
930
935 protected function envCheckDiff3() {
936 $names = [ "gdiff3", "diff3" ];
937 if ( wfIsWindows() ) {
938 $names[] = 'diff3.exe';
939 }
940 $versionInfo = [ '--version', 'GNU diffutils' ];
941
942 $diff3 = ExecutableFinder::findInDefaultPaths( $names, $versionInfo );
943
944 if ( $diff3 ) {
945 $this->setVar( 'wgDiff3', $diff3 );
946 } else {
947 $this->setVar( 'wgDiff3', false );
948 $this->showMessage( 'config-diff3-bad' );
949 }
950
951 return true;
952 }
953
958 protected function envCheckGraphics() {
959 $names = wfIsWindows() ? 'convert.exe' : 'convert';
960 $versionInfo = [ '-version', 'ImageMagick' ];
961 $convert = ExecutableFinder::findInDefaultPaths( $names, $versionInfo );
962
963 $this->setVar( 'wgImageMagickConvertCommand', '' );
964 if ( $convert ) {
965 $this->setVar( 'wgImageMagickConvertCommand', $convert );
966 $this->showMessage( 'config-imagemagick', $convert );
967 } elseif ( function_exists( 'imagejpeg' ) ) {
968 $this->showMessage( 'config-gd' );
969 } else {
970 $this->showMessage( 'config-no-scaling' );
971 }
972
973 return true;
974 }
975
982 protected function envCheckGit() {
983 $names = wfIsWindows() ? 'git.exe' : 'git';
984 $versionInfo = [ '--version', 'git version' ];
985
986 $git = ExecutableFinder::findInDefaultPaths( $names, $versionInfo );
987
988 if ( $git ) {
989 $this->setVar( 'wgGitBin', $git );
990 $this->showMessage( 'config-git', $git );
991 } else {
992 $this->setVar( 'wgGitBin', false );
993 $this->showMessage( 'config-git-bad' );
994 }
995
996 return true;
997 }
998
1004 protected function envCheckServer() {
1005 $server = $this->envGetDefaultServer();
1006 if ( $server !== null ) {
1007 $this->showMessage( 'config-using-server', $server );
1008 }
1009 return true;
1010 }
1011
1017 protected function envCheckPath() {
1018 $this->showMessage(
1019 'config-using-uri',
1020 $this->getVar( 'wgServer' ),
1021 $this->getVar( 'wgScriptPath' )
1022 );
1023 return true;
1024 }
1025
1030 protected function envCheckShellLocale() {
1031 $os = php_uname( 's' );
1032 $supported = [ 'Linux', 'SunOS', 'HP-UX', 'Darwin' ]; # Tested these
1033
1034 if ( !in_array( $os, $supported ) ) {
1035 return true;
1036 }
1037
1038 if ( Shell::isDisabled() ) {
1039 return true;
1040 }
1041
1042 # Get a list of available locales.
1043 $result = Shell::command( '/usr/bin/locale', '-a' )->execute();
1044
1045 if ( $result->getExitCode() != 0 ) {
1046 return true;
1047 }
1048
1049 $lines = $result->getStdout();
1050 $lines = array_map( 'trim', explode( "\n", $lines ) );
1051 $candidatesByLocale = [];
1052 $candidatesByLang = [];
1053 foreach ( $lines as $line ) {
1054 if ( $line === '' ) {
1055 continue;
1056 }
1057
1058 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
1059 continue;
1060 }
1061
1062 list( , $lang, , , ) = $m;
1063
1064 $candidatesByLocale[$m[0]] = $m;
1065 $candidatesByLang[$lang][] = $m;
1066 }
1067
1068 # Try the current value of LANG.
1069 if ( isset( $candidatesByLocale[getenv( 'LANG' )] ) ) {
1070 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
1071
1072 return true;
1073 }
1074
1075 # Try the most common ones.
1076 $commonLocales = [ 'C.UTF-8', 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' ];
1077 foreach ( $commonLocales as $commonLocale ) {
1078 if ( isset( $candidatesByLocale[$commonLocale] ) ) {
1079 $this->setVar( 'wgShellLocale', $commonLocale );
1080
1081 return true;
1082 }
1083 }
1084
1085 # Is there an available locale in the Wiki's language?
1086 $wikiLang = $this->getVar( 'wgLanguageCode' );
1087
1088 if ( isset( $candidatesByLang[$wikiLang] ) ) {
1089 $m = reset( $candidatesByLang[$wikiLang] );
1090 $this->setVar( 'wgShellLocale', $m[0] );
1091
1092 return true;
1093 }
1094
1095 # Are there any at all?
1096 if ( count( $candidatesByLocale ) ) {
1097 $m = reset( $candidatesByLocale );
1098 $this->setVar( 'wgShellLocale', $m[0] );
1099
1100 return true;
1101 }
1102
1103 # Give up.
1104 return true;
1105 }
1106
1111 protected function envCheckUploadsDirectory() {
1112 global $IP;
1113
1114 $dir = $IP . '/images/';
1115 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
1116 $safe = !$this->dirIsExecutable( $dir, $url );
1117
1118 if ( !$safe ) {
1119 $this->showMessage( 'config-uploads-not-safe', $dir );
1120 }
1121
1122 return true;
1123 }
1124
1130 protected function envCheckSuhosinMaxValueLength() {
1131 $currentValue = ini_get( 'suhosin.get.max_value_length' );
1132 $minRequired = 2000;
1133 $recommended = 5000;
1134 if ( $currentValue > 0 && $currentValue < $minRequired ) {
1135 $this->showError( 'config-suhosin-max-value-length', $currentValue, $minRequired, $recommended );
1136 return false;
1137 }
1138
1139 return true;
1140 }
1141
1148 protected function envCheck64Bit() {
1149 if ( PHP_INT_SIZE == 4 ) {
1150 $this->showMessage( 'config-using-32bit' );
1151 }
1152
1153 return true;
1154 }
1155
1159 protected function envCheckLibicu() {
1167 $not_normal_c = "\u{FA6C}";
1168 $normal_c = "\u{242EE}";
1169
1170 $useNormalizer = 'php';
1171 $needsUpdate = false;
1172
1173 if ( function_exists( 'normalizer_normalize' ) ) {
1174 $useNormalizer = 'intl';
1175 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
1176 if ( $intl !== $normal_c ) {
1177 $needsUpdate = true;
1178 }
1179 }
1180
1181 // Uses messages 'config-unicode-using-php' and 'config-unicode-using-intl'
1182 if ( $useNormalizer === 'php' ) {
1183 $this->showMessage( 'config-unicode-pure-php-warning' );
1184 } else {
1185 $this->showMessage( 'config-unicode-using-' . $useNormalizer );
1186 if ( $needsUpdate ) {
1187 $this->showMessage( 'config-unicode-update-warning' );
1188 }
1189 }
1190 }
1191
1195 protected function envPrepServer() {
1196 $server = $this->envGetDefaultServer();
1197 if ( $server !== null ) {
1198 $this->setVar( 'wgServer', $server );
1199 }
1200 }
1201
1206 abstract protected function envGetDefaultServer();
1207
1211 protected function envPrepPath() {
1212 global $IP;
1213 $IP = dirname( dirname( __DIR__ ) );
1214 $this->setVar( 'IP', $IP );
1215 }
1216
1225 public function dirIsExecutable( $dir, $url ) {
1226 $scriptTypes = [
1227 'php' => [
1228 "<?php echo 'exec';",
1229 "#!/var/env php\n<?php echo 'exec';",
1230 ],
1231 ];
1232
1233 // it would be good to check other popular languages here, but it'll be slow.
1234 // TODO no need to have a loop if there is going to only be one script type
1235
1236 $httpRequestFactory = MediaWikiServices::getInstance()->getHttpRequestFactory();
1237
1238 Wikimedia\suppressWarnings();
1239
1240 foreach ( $scriptTypes as $ext => $contents ) {
1241 foreach ( $contents as $source ) {
1242 $file = 'exectest.' . $ext;
1243
1244 if ( !file_put_contents( $dir . $file, $source ) ) {
1245 break;
1246 }
1247
1248 try {
1249 $text = $httpRequestFactory->get(
1250 $url . $file,
1251 [ 'timeout' => 3 ],
1252 __METHOD__
1253 );
1254 } catch ( Exception $e ) {
1255 // HttpRequestFactory::get can throw with allow_url_fopen = false and no curl
1256 // extension.
1257 $text = null;
1258 }
1259 unlink( $dir . $file );
1260
1261 if ( $text == 'exec' ) {
1262 Wikimedia\restoreWarnings();
1263
1264 return $ext;
1265 }
1266 }
1267 }
1268
1269 Wikimedia\restoreWarnings();
1270
1271 return false;
1272 }
1273
1280 public static function apacheModulePresent( $moduleName ) {
1281 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1282 return true;
1283 }
1284 // try it the hard way
1285 ob_start();
1286 phpinfo( INFO_MODULES );
1287 $info = ob_get_clean();
1288
1289 return strpos( $info, $moduleName ) !== false;
1290 }
1291
1297 public function setParserLanguage( $lang ) {
1298 $this->parserOptions->setTargetLanguage( $lang );
1299 $this->parserOptions->setUserLang( $lang );
1300 }
1301
1307 protected function getDocUrl( $page ) {
1308 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1309 }
1310
1320 public function findExtensions( $directory = 'extensions' ) {
1321 switch ( $directory ) {
1322 case 'extensions':
1323 return $this->findExtensionsByType( 'extension', 'extensions' );
1324 case 'skins':
1325 return $this->findExtensionsByType( 'skin', 'skins' );
1326 default:
1327 throw new InvalidArgumentException( "Invalid extension type" );
1328 }
1329 }
1330
1340 protected function findExtensionsByType( $type = 'extension', $directory = 'extensions' ) {
1341 if ( $this->getVar( 'IP' ) === null ) {
1342 return Status::newGood( [] );
1343 }
1344
1345 $extDir = $this->getVar( 'IP' ) . '/' . $directory;
1346 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
1347 return Status::newGood( [] );
1348 }
1349
1350 $dh = opendir( $extDir );
1351 $exts = [];
1352 $status = new Status;
1353 while ( ( $file = readdir( $dh ) ) !== false ) {
1354 // skip non-dirs and hidden directories
1355 if ( !is_dir( "$extDir/$file" ) || $file[0] === '.' ) {
1356 continue;
1357 }
1358 $extStatus = $this->getExtensionInfo( $type, $directory, $file );
1359 if ( $extStatus->isOK() ) {
1360 $exts[$file] = $extStatus->value;
1361 } elseif ( $extStatus->hasMessage( 'config-extension-not-found' ) ) {
1362 // (T225512) The directory is not actually an extension. Downgrade to warning.
1363 $status->warning( 'config-extension-not-found', $file );
1364 } else {
1365 $status->merge( $extStatus );
1366 }
1367 }
1368 closedir( $dh );
1369 uksort( $exts, 'strnatcasecmp' );
1370
1371 $status->value = $exts;
1372
1373 return $status;
1374 }
1375
1383 protected function getExtensionInfo( $type, $parentRelPath, $name ) {
1384 if ( $this->getVar( 'IP' ) === null ) {
1385 throw new Exception( 'Cannot find extensions since the IP variable is not yet set' );
1386 }
1387 if ( $type !== 'extension' && $type !== 'skin' ) {
1388 throw new InvalidArgumentException( "Invalid extension type" );
1389 }
1390 $absDir = $this->getVar( 'IP' ) . "/$parentRelPath/$name";
1391 $relDir = "../$parentRelPath/$name";
1392 if ( !is_dir( $absDir ) ) {
1393 return Status::newFatal( 'config-extension-not-found', $name );
1394 }
1395 $jsonFile = $type . '.json';
1396 $fullJsonFile = "$absDir/$jsonFile";
1397 $isJson = file_exists( $fullJsonFile );
1398 $isPhp = false;
1399 if ( !$isJson ) {
1400 // Only fallback to PHP file if JSON doesn't exist
1401 $fullPhpFile = "$absDir/$name.php";
1402 $isPhp = file_exists( $fullPhpFile );
1403 }
1404 if ( !$isJson && !$isPhp ) {
1405 return Status::newFatal( 'config-extension-not-found', $name );
1406 }
1407
1408 // Extension exists. Now see if there are screenshots
1409 $info = [];
1410 if ( is_dir( "$absDir/screenshots" ) ) {
1411 $paths = glob( "$absDir/screenshots/*.png" );
1412 foreach ( $paths as $path ) {
1413 $info['screenshots'][] = str_replace( $absDir, $relDir, $path );
1414 }
1415 }
1416
1417 if ( $isJson ) {
1418 $jsonStatus = $this->readExtension( $fullJsonFile );
1419 if ( !$jsonStatus->isOK() ) {
1420 return $jsonStatus;
1421 }
1422 $info += $jsonStatus->value;
1423 }
1424
1425 return Status::newGood( $info );
1426 }
1427
1436 private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
1437 $load = [
1438 $fullJsonFile => 1
1439 ];
1440 if ( $extDeps ) {
1441 $extDir = $this->getVar( 'IP' ) . '/extensions';
1442 foreach ( $extDeps as $dep ) {
1443 $fname = "$extDir/$dep/extension.json";
1444 if ( !file_exists( $fname ) ) {
1445 return Status::newFatal( 'config-extension-not-found', $dep );
1446 }
1447 $load[$fname] = 1;
1448 }
1449 }
1450 if ( $skinDeps ) {
1451 $skinDir = $this->getVar( 'IP' ) . '/skins';
1452 foreach ( $skinDeps as $dep ) {
1453 $fname = "$skinDir/$dep/skin.json";
1454 if ( !file_exists( $fname ) ) {
1455 return Status::newFatal( 'config-extension-not-found', $dep );
1456 }
1457 $load[$fname] = 1;
1458 }
1459 }
1460 $registry = new ExtensionRegistry();
1461 try {
1462 $info = $registry->readFromQueue( $load );
1463 } catch ( ExtensionDependencyError $e ) {
1464 if ( $e->incompatibleCore || $e->incompatibleSkins
1465 || $e->incompatibleExtensions
1466 ) {
1467 // If something is incompatible with a dependency, we have no real
1468 // option besides skipping it
1469 return Status::newFatal( 'config-extension-dependency',
1470 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1471 } elseif ( $e->missingExtensions || $e->missingSkins ) {
1472 // There's an extension missing in the dependency tree,
1473 // so add those to the dependency list and try again
1474 $status = $this->readExtension(
1475 $fullJsonFile,
1476 array_merge( $extDeps, $e->missingExtensions ),
1477 array_merge( $skinDeps, $e->missingSkins )
1478 );
1479 if ( !$status->isOK() && !$status->hasMessage( 'config-extension-dependency' ) ) {
1480 $status = Status::newFatal( 'config-extension-dependency',
1481 basename( dirname( $fullJsonFile ) ), $status->getMessage() );
1482 }
1483 return $status;
1484 }
1485 // Some other kind of dependency error?
1486 return Status::newFatal( 'config-extension-dependency',
1487 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1488 }
1489 $ret = [];
1490 // The order of credits will be the order of $load,
1491 // so the first extension is the one we want to load,
1492 // everything else is a dependency
1493 $i = 0;
1494 foreach ( $info['credits'] as $name => $credit ) {
1495 $i++;
1496 if ( $i == 1 ) {
1497 // Extension we want to load
1498 continue;
1499 }
1500 $type = basename( $credit['path'] ) === 'skin.json' ? 'skins' : 'extensions';
1501 $ret['requires'][$type][] = $credit['name'];
1502 }
1503 $credits = array_values( $info['credits'] )[0];
1504 if ( isset( $credits['url'] ) ) {
1505 $ret['url'] = $credits['url'];
1506 }
1507 $ret['type'] = $credits['type'];
1508
1509 return Status::newGood( $ret );
1510 }
1511
1520 public function getDefaultSkin( array $skinNames ) {
1521 $defaultSkin = $GLOBALS['wgDefaultSkin'];
1522 if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) {
1523 return $defaultSkin;
1524 } else {
1525 return $skinNames[0];
1526 }
1527 }
1528
1535 protected function includeExtensions() {
1536 // Marker for DatabaseUpdater::loadExtensions so we don't
1537 // double load extensions
1538 define( 'MW_EXTENSIONS_LOADED', true );
1539
1540 $legacySchemaHooks = $this->getAutoExtensionLegacyHooks();
1541 $data = $this->getAutoExtensionData();
1542 if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
1543 $legacySchemaHooks = array_merge( $legacySchemaHooks,
1544 $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] );
1545 }
1546 $extDeprecatedHooks = $data['attributes']['DeprecatedHooks'] ?? [];
1547
1548 $this->autoExtensionHookContainer = new HookContainer(
1550 [ 'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
1551 $data['attributes']['Hooks'] ?? [],
1552 $extDeprecatedHooks
1553 ),
1554 MediaWikiServices::getInstance()->getObjectFactory()
1555 );
1556
1557 return Status::newGood();
1558 }
1559
1567 protected function getAutoExtensionLegacyHooks() {
1568 $exts = $this->getVar( '_Extensions' );
1569 $installPath = $this->getVar( 'IP' );
1570 $files = [];
1571 foreach ( $exts as $e ) {
1572 if ( file_exists( "$installPath/extensions/$e/$e.php" ) ) {
1573 $files[] = "$installPath/extensions/$e/$e.php";
1574 }
1575 }
1576
1577 if ( $files ) {
1578 return $this->includeExtensionFiles( $files );
1579 } else {
1580 return [];
1581 }
1582 }
1583
1591 protected function includeExtensionFiles( $files ) {
1592 global $IP;
1593 $IP = $this->getVar( 'IP' );
1594
1603 require "$IP/includes/DefaultSettings.php";
1604
1605 // phpcs:ignore MediaWiki.VariableAnalysis.UnusedGlobalVariables
1606 global $wgAutoloadClasses;
1607 foreach ( $files as $file ) {
1608 require_once $file;
1609 }
1610
1611 // @phan-suppress-next-line PhanUndeclaredVariable,PhanCoalescingAlwaysNull $wgHooks is set by DefaultSettings
1612 $hooksWeWant = $wgHooks['LoadExtensionSchemaUpdates'] ?? [];
1613
1614 // Ignore everyone else's hooks. Lord knows what someone might be doing
1615 // in ParserFirstCallInit (see T29171)
1616 return [ 'LoadExtensionSchemaUpdates' => $hooksWeWant ];
1617 }
1618
1625 protected function getAutoExtensionData() {
1626 $exts = $this->getVar( '_Extensions' );
1627 $installPath = $this->getVar( 'IP' );
1628 $queue = [];
1629 foreach ( $exts as $e ) {
1630 if ( file_exists( "$installPath/extensions/$e/extension.json" ) ) {
1631 $queue["$installPath/extensions/$e/extension.json"] = 1;
1632 }
1633 }
1634
1635 $registry = new ExtensionRegistry();
1636 $data = $registry->readFromQueue( $queue );
1637 global $wgAutoloadClasses;
1638 $wgAutoloadClasses += $data['globals']['wgAutoloadClasses'];
1639 return $data;
1640 }
1641
1650 if ( !$this->autoExtensionHookContainer ) {
1651 throw new \Exception( __METHOD__ .
1652 ': includeExtensions() has not been called' );
1653 }
1655 }
1656
1670 protected function getInstallSteps( DatabaseInstaller $installer ) {
1671 $coreInstallSteps = [
1672 [ 'name' => 'database', 'callback' => [ $installer, 'setupDatabase' ] ],
1673 [ 'name' => 'tables', 'callback' => [ $installer, 'createTables' ] ],
1674 [ 'name' => 'tables-manual', 'callback' => [ $installer, 'createManualTables' ] ],
1675 [ 'name' => 'interwiki', 'callback' => [ $installer, 'populateInterwikiTable' ] ],
1676 [ 'name' => 'stats', 'callback' => [ $this, 'populateSiteStats' ] ],
1677 [ 'name' => 'keys', 'callback' => [ $this, 'generateKeys' ] ],
1678 [ 'name' => 'updates', 'callback' => [ $installer, 'insertUpdateKeys' ] ],
1679 [ 'name' => 'restore-services', 'callback' => [ $this, 'restoreServices' ] ],
1680 [ 'name' => 'sysop', 'callback' => [ $this, 'createSysop' ] ],
1681 [ 'name' => 'mainpage', 'callback' => [ $this, 'createMainpage' ] ],
1682 ];
1683
1684 // Build the array of install steps starting from the core install list,
1685 // then adding any callbacks that wanted to attach after a given step
1686 foreach ( $coreInstallSteps as $step ) {
1687 $this->installSteps[] = $step;
1688 if ( isset( $this->extraInstallSteps[$step['name']] ) ) {
1689 $this->installSteps = array_merge(
1690 $this->installSteps,
1691 $this->extraInstallSteps[$step['name']]
1692 );
1693 }
1694 }
1695
1696 // Prepend any steps that want to be at the beginning
1697 if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1698 $this->installSteps = array_merge(
1699 $this->extraInstallSteps['BEGINNING'],
1700 $this->installSteps
1701 );
1702 }
1703
1704 // Extensions should always go first, chance to tie into hooks and such
1705 if ( count( $this->getVar( '_Extensions' ) ) ) {
1706 array_unshift( $this->installSteps,
1707 [ 'name' => 'extensions', 'callback' => [ $this, 'includeExtensions' ] ]
1708 );
1709 $this->installSteps[] = [
1710 'name' => 'extension-tables',
1711 'callback' => [ $installer, 'createExtensionTables' ]
1712 ];
1713 }
1714
1715 return $this->installSteps;
1716 }
1717
1726 public function performInstallation( $startCB, $endCB ) {
1727 $installResults = [];
1728 $installer = $this->getDBInstaller();
1729 $installer->preInstall();
1730 $steps = $this->getInstallSteps( $installer );
1731 foreach ( $steps as $stepObj ) {
1732 $name = $stepObj['name'];
1733 call_user_func_array( $startCB, [ $name ] );
1734
1735 // Perform the callback step
1736 $status = call_user_func( $stepObj['callback'], $installer );
1737
1738 // Output and save the results
1739 call_user_func( $endCB, $name, $status );
1740 $installResults[$name] = $status;
1741
1742 // If we've hit some sort of fatal, we need to bail.
1743 // Callback already had a chance to do output above.
1744 if ( !$status->isOK() ) {
1745 break;
1746 }
1747 }
1748 if ( $status->isOK() ) {
1749 $this->showMessage(
1750 'config-install-db-success'
1751 );
1752 $this->setVar( '_InstallDone', true );
1753 }
1754
1755 return $installResults;
1756 }
1757
1763 public function generateKeys() {
1764 $keys = [ 'wgSecretKey' => 64 ];
1765 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1766 $keys['wgUpgradeKey'] = 16;
1767 }
1768
1769 return $this->doGenerateKeys( $keys );
1770 }
1771
1776 public function restoreServices() {
1777 $this->resetMediaWikiServices( null, [
1778 'UserOptionsLookup' => function ( MediaWikiServices $services ) {
1779 return $services->get( 'UserOptionsManager' );
1780 }
1781 ] );
1782 return Status::newGood();
1783 }
1784
1791 protected function doGenerateKeys( $keys ) {
1792 foreach ( $keys as $name => $length ) {
1793 $secretKey = MWCryptRand::generateHex( $length );
1794 $this->setVar( $name, $secretKey );
1795 }
1796 return Status::newGood();
1797 }
1798
1804 protected function createSysop() {
1805 $name = $this->getVar( '_AdminName' );
1806 $user = User::newFromName( $name );
1807
1808 if ( !$user ) {
1809 // We should've validated this earlier anyway!
1810 return Status::newFatal( 'config-admin-error-user', $name );
1811 }
1812
1813 if ( $user->idForName() == 0 ) {
1814 $user->addToDatabase();
1815
1816 $password = $this->getVar( '_AdminPassword' );
1817 $status = $user->changeAuthenticationData( [
1818 'username' => $user->getName(),
1819 'password' => $password,
1820 'retype' => $password,
1821 ] );
1822 if ( !$status->isGood() ) {
1823 return Status::newFatal( 'config-admin-error-password',
1824 $name, $status->getWikiText( null, null, $this->getVar( '_UserLang' ) ) );
1825 }
1826
1827 $user->addGroup( 'sysop' );
1828 $user->addGroup( 'bureaucrat' );
1829 $user->addGroup( 'interface-admin' );
1830 if ( $this->getVar( '_AdminEmail' ) ) {
1831 $user->setEmail( $this->getVar( '_AdminEmail' ) );
1832 }
1833 $user->saveSettings();
1834
1835 // Update user count
1836 $ssUpdate = SiteStatsUpdate::factory( [ 'users' => 1 ] );
1837 $ssUpdate->doUpdate();
1838 }
1839
1840 if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1841 return $this->subscribeToMediaWikiAnnounce();
1842 }
1843 return Status::newGood();
1844 }
1845
1849 private function subscribeToMediaWikiAnnounce() {
1850 $status = Status::newGood();
1851 $http = MediaWikiServices::getInstance()->getHttpRequestFactory();
1852 if ( !$http->canMakeRequests() ) {
1853 $status->warning( 'config-install-subscribe-fail',
1854 wfMessage( 'config-install-subscribe-notpossible' ) );
1855 return $status;
1856 }
1857
1858 // Create subscription request
1859 $params = [ 'email' => $this->getVar( '_AdminEmail' ) ];
1860 $req = $http->create( self::MEDIAWIKI_ANNOUNCE_URL . 'anonymous_subscribe',
1861 [ 'method' => 'POST', 'postData' => $params ], __METHOD__ );
1862
1863 // Add headers needed to pass Django's CSRF checks
1864 $token = str_repeat( 'a', 64 );
1865 $req->setHeader( 'Referer', self::MEDIAWIKI_ANNOUNCE_URL );
1866 $req->setHeader( 'Cookie', "csrftoken=$token" );
1867 $req->setHeader( 'X-CSRFToken', $token );
1868
1869 // Send subscription request
1870 $reqStatus = $req->execute();
1871 if ( !$reqStatus->isOK() ) {
1872 $status->warning( 'config-install-subscribe-fail',
1873 Status::wrap( $reqStatus )->getMessage() );
1874 return $status;
1875 }
1876
1877 // Was the request submitted successfully?
1878 // The status message is displayed after a redirect, using Django's messages
1879 // framework, so load the list summary page and look for the expected text.
1880 // (Though parsing the cookie set by the framework may be possible, it isn't
1881 // simple, since the format of the cookie has changed between versions.)
1882 $checkReq = $http->create( self::MEDIAWIKI_ANNOUNCE_URL, [], __METHOD__ );
1883 $checkReq->setCookieJar( $req->getCookieJar() );
1884 if ( !$checkReq->execute()->isOK() ) {
1885 $status->warning( 'config-install-subscribe-possiblefail' );
1886 return $status;
1887 }
1888 $html = $checkReq->getContent();
1889 if ( strpos( $html, 'Please check your inbox for further instructions' ) !== false ) {
1890 // Success
1891 } elseif ( strpos( $html, 'Member already subscribed' ) !== false ) {
1892 $status->warning( 'config-install-subscribe-alreadysubscribed' );
1893 } elseif ( strpos( $html, 'Subscription request already pending' ) !== false ) {
1894 $status->warning( 'config-install-subscribe-alreadypending' );
1895 } else {
1896 $status->warning( 'config-install-subscribe-possiblefail' );
1897 }
1898 return $status;
1899 }
1900
1907 protected function createMainpage( DatabaseInstaller $installer ) {
1908 $status = Status::newGood();
1909 $title = Title::newMainPage();
1910 if ( $title->exists() ) {
1911 $status->warning( 'config-install-mainpage-exists' );
1912 return $status;
1913 }
1914 try {
1915 $page = WikiPage::factory( $title );
1917 wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
1918 wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
1919 );
1920
1921 $status = $page->doEditContent(
1922 $content,
1923 '',
1924 EDIT_NEW,
1925 false,
1926 User::newSystemUser( 'MediaWiki default' )
1927 );
1928 } catch ( Exception $e ) {
1929 // using raw, because $wgShowExceptionDetails can not be set yet
1930 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1931 }
1932
1933 return $status;
1934 }
1935
1939 public static function overrideConfig() {
1940 // Use PHP's built-in session handling, since MediaWiki's
1941 // SessionHandler can't work before we have an object cache set up.
1942 if ( !defined( 'MW_NO_SESSION_HANDLER' ) ) {
1943 define( 'MW_NO_SESSION_HANDLER', 1 );
1944 }
1945
1946 // Don't access the database
1947 $GLOBALS['wgUseDatabaseMessages'] = false;
1948 // Don't cache langconv tables
1949 $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE;
1950 // Don't try to cache ResourceLoader dependencies in the database
1951 $GLOBALS['wgResourceLoaderUseObjectCacheForDeps'] = true;
1952 // Debug-friendly
1953 $GLOBALS['wgShowExceptionDetails'] = true;
1954 $GLOBALS['wgShowHostnames'] = true;
1955 // Don't break forms
1956 $GLOBALS['wgExternalLinkTarget'] = '_blank';
1957
1958 // Allow multiple ob_flush() calls
1959 $GLOBALS['wgDisableOutputCompression'] = true;
1960
1961 // Use a sensible cookie prefix (not my_wiki)
1962 $GLOBALS['wgCookiePrefix'] = 'mw_installer';
1963
1964 // Some of the environment checks make shell requests, remove limits
1965 $GLOBALS['wgMaxShellMemory'] = 0;
1966
1967 // Override the default CookieSessionProvider with a dummy
1968 // implementation that won't stomp on PHP's cookies.
1969 $GLOBALS['wgSessionProviders'] = [
1970 [
1971 'class' => InstallerSessionProvider::class,
1972 'args' => [ [
1973 'priority' => 1,
1974 ] ]
1975 ]
1976 ];
1977
1978 // Don't use the DB as the main stash
1979 $GLOBALS['wgMainStash'] = CACHE_NONE;
1980
1981 // Don't try to use any object cache for SessionManager either.
1982 $GLOBALS['wgSessionCacheType'] = CACHE_NONE;
1983
1984 // Set a dummy $wgServer to bypass the check in Setup.php, the
1985 // web installer will automatically detect it and not use this value.
1986 $GLOBALS['wgServer'] = 'https://🌻.invalid';
1987 }
1988
1996 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1997 $this->extraInstallSteps[$findStep][] = $callback;
1998 }
1999
2004 protected function disableTimeLimit() {
2005 Wikimedia\suppressWarnings();
2006 set_time_limit( 0 );
2007 Wikimedia\restoreWarnings();
2008 }
2009}
$GLOBALS['IP']
$wgObjectCaches
Advanced object cache configuration.
$wgStyleDirectory
Filesystem stylesheets directory.
$wgAutoloadClasses
Array mapping class names to filenames, for autoloading.
$wgHooks
Global list of hooks.
$wgExtensionDirectory
Filesystem extensions directory.
$wgExternalLinkTarget
Set a default target for external links, e.g.
wfShorthandToInteger(?string $string='', int $default=-1)
Converts shorthand byte notation to integer form.
wfIsWindows()
Check if the operating system is Windows.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
$wgMemc
Definition Setup.php:691
$wgLang
Definition Setup.php:781
$IP
Definition WebStart.php:49
Base class for DBMS-specific installation helper classes.
getConnection()
Connect to the database using the administrative user/password currently defined in the session.
Copyright (C) 2018 Kunal Mehta legoktm@member.fsf.org
ExtensionRegistry class.
Accesses configuration settings from $GLOBALS.
A Config instance which stores all settings as a member variable.
Base installer class.
Definition Installer.php:54
envPrepServer()
Environment prep for the server hostname.
parse( $text, $lineStart=false)
Convert wikitext $text to HTML.
array $compiledDBs
List of detected DBs, access using getCompiledDBs().
Definition Installer.php:80
getExtensionInfo( $type, $parentRelPath, $name)
Title $parserTitle
Cached Title, used by parse().
includeExtensions()
Installs the auto-detected extensions.
createMainpage(DatabaseInstaller $installer)
Insert Main Page with default content.
const MINIMUM_PCRE_VERSION
The oldest version of PCRE we can support.
Definition Installer.php:62
getDefaultSkin(array $skinNames)
Returns a default value to be used for $wgDefaultSkin: normally the one set in DefaultSettings,...
envCheckLibicu()
Check the libicu version.
getDBInstaller( $type=false)
Get an instance of DatabaseInstaller for the specified DB type.
envCheckDB()
Environment check for DB types.
envCheckSuhosinMaxValueLength()
Checks if suhosin.get.max_value_length is set, and if so generate a warning because it is incompatibl...
setVar( $name, $value)
Set a MW configuration variable, or internal installer configuration variable.
array $internalDefaults
Variables that are stored alongside globals, and are used for any configuration of the installation p...
envCheckModSecurity()
Scare user to death if they have mod_security or mod_security2.
getFakePassword( $realPassword)
Get a fake password for sending back to the user in HTML.
envCheckServer()
Environment check to inform user which server we've assumed.
disableTimeLimit()
Disable the time limit for execution.
static apacheModulePresent( $moduleName)
Checks for presence of an Apache module.
array $rightsProfiles
User rights profiles.
addInstallStep( $callback, $findStep='BEGINNING')
Add an installation step following the given step.
getParserOptions()
getCompiledDBs()
Get a list of DBs supported by current PHP setup.
ParserOptions $parserOptions
Cached ParserOptions, used by parse().
dirIsExecutable( $dir, $url)
Checks if scripts located in the given directory can be executed via the given URL.
doEnvironmentPreps()
envCheckGit()
Search for git.
getDocUrl( $page)
Overridden by WebInstaller to provide lastPage parameters.
array[] $installSteps
The actual list of installation steps.
array $defaultVarNames
MediaWiki configuration globals that will eventually be passed through to LocalSettings....
const MEDIAWIKI_ANNOUNCE_URL
URL to mediawiki-announce list summary page.
Definition Installer.php:67
resetMediaWikiServices(Config $installerConfig=null, $serviceOverrides=[])
Reset the global service container and associated global state to accommodate different stages of the...
static getExistingLocalSettings()
Determine if LocalSettings.php exists.
static getInstallerConfig(Config $baseConfig)
Constructs a Config object that contains configuration settings that should be overwritten for the in...
array $settings
Definition Installer.php:73
restoreServices()
Restore services that have been redefined in the early stage of installation.
includeExtensionFiles( $files)
Include the specified extension PHP files.
getAutoExtensionData()
Auto-detect extensions with an extension.json file.
envCheckMemory()
Environment check for available memory.
array $objectCaches
Known object cache types and the functions used to test for their existence.
array $licenses
License types.
static maybeGetWebserverPrimaryGroup()
On POSIX systems return the primary group of the webserver we're running under.
doEnvironmentChecks()
Do initial checks of the PHP environment.
disableLinkPopups()
performInstallation( $startCB, $endCB)
Actually perform the installation.
getAutoExtensionLegacyHooks()
Auto-detect extensions with an old style .php registration file, load the extensions,...
envCheck64Bit()
Checks if we're running on 64 bit or not.
generateKeys()
Generate $wgSecretKey.
populateSiteStats(DatabaseInstaller $installer)
Install step which adds a row to the site_stats table with appropriate initial values.
envCheckCache()
Environment check for compiled object cache types.
__construct()
Constructor, always call this from child classes.
int $minMemorySize
Minimum memory size in MB.
Definition Installer.php:94
findExtensionsByType( $type='extension', $directory='extensions')
Find extensions or skins, and return an array containing the value for 'Name' for each found extensio...
doGenerateKeys( $keys)
Generate a secret value for variables using a secure generator.
showStatusMessage(Status $status)
Show a message to the installing user by using a Status object.
readExtension( $fullJsonFile, $extDeps=[], $skinDeps=[])
envCheckPath()
Environment check to inform user which paths we've assumed.
array $envPreps
A list of environment preparation methods called by doEnvironmentPreps().
setPassword( $name, $value)
Set a variable which stores a password, except if the new value is a fake password in which case leav...
envPrepPath()
Environment prep for setting $IP and $wgScriptPath.
static getDBTypes()
Get a list of known DB types.
createSysop()
Create the first user account, grant it sysop, bureaucrat and interface-admin rights.
envCheckPCRE()
Environment check for the PCRE module.
getVar( $name, $default=null)
Get an MW configuration variable, or internal installer configuration variable.
static getDBInstallerClass( $type)
Get the DatabaseInstaller class name for this type.
static overrideConfig()
Override the necessary bits of the config to run an installation.
restoreLinkPopups()
array $extraInstallSteps
Extra steps for installation, for things like DatabaseInstallers to modify.
HookContainer null $autoExtensionHookContainer
static array $dbTypes
Known database types.
getAutoExtensionHookContainer()
Get the hook container previously populated by includeExtensions().
envCheckDiff3()
Search for GNU diff3.
subscribeToMediaWikiAnnounce()
envCheckShellLocale()
Environment check for preferred locale in shell.
envGetDefaultServer()
Helper function to be called from envPrepServer()
getInstallSteps(DatabaseInstaller $installer)
Get an array of install steps.
array $dbInstallers
Cached DB installer instances, access using getDBInstaller().
Definition Installer.php:87
array $envChecks
A list of environment check methods called by doEnvironmentChecks().
envCheckGraphics()
Environment check for ImageMagick and GD.
showMessage( $msg,... $params)
UI interface for displaying a short message The parameters are like parameters to wfMessage().
showError( $msg,... $params)
Same as showMessage(), but for displaying errors.
envCheckUploadsDirectory()
Environment check for the permissions of the uploads directory.
setParserLanguage( $lang)
ParserOptions are constructed before we determined the language, so fix it.
findExtensions( $directory='extensions')
Find extensions or skins in a subdirectory of $IP.
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
This is a simple immutable HookRegistry which can be used to set up a local HookContainer in tests an...
An interwiki lookup that has no data, intended for use in the installer.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Executes shell commands.
Definition Shell.php:44
Provides a fallback sequence for Config objects.
Set options of the Parser.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
Represents a title within MediaWiki.
Definition Title.php:42
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:541
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:565
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition User.php:758
Class for the core installer web interface.
Content object for wiki text pages.
const CACHE_NONE
Definition Defines.php:92
const CACHE_ANYTHING
Definition Defines.php:91
const CACHE_MEMCACHED
Definition Defines.php:94
const CACHE_DB
Definition Defines.php:93
const EDIT_NEW
Definition Defines.php:142
Interface for configuration instances.
Definition Config.php:30
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
$line
Definition mcc.php:119
$source
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...
$content
Definition router.php:76
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42
if(!is_readable( $file)) $ext
Definition router.php:48
if(!isset( $args[0])) $lang
if(!file_exists( $CREDITS)) $lines