MediaWiki REL1_37
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 'wgLocaltimezone',
176 'wgRightsIcon',
177 'wgRightsText',
178 'wgRightsUrl',
179 'wgEnableEmail',
180 'wgEnableUserEmail',
181 'wgEnotifUserTalk',
182 'wgEnotifWatchlist',
183 'wgEmailAuthentication',
184 'wgDBname',
185 'wgDBtype',
186 'wgDiff3',
187 'wgImageMagickConvertCommand',
188 'wgGitBin',
189 'IP',
190 'wgScriptPath',
191 'wgMetaNamespace',
192 'wgDeletedDirectory',
193 'wgEnableUploads',
194 'wgShellLocale',
195 'wgSecretKey',
196 'wgUseInstantCommons',
197 'wgUpgradeKey',
198 'wgDefaultSkin',
199 'wgPingback',
200 ];
201
209 protected $internalDefaults = [
210 '_UserLang' => 'en',
211 '_Environment' => false,
212 '_RaiseMemory' => false,
213 '_UpgradeDone' => false,
214 '_InstallDone' => false,
215 '_Caches' => [],
216 '_InstallPassword' => '',
217 '_SameAccount' => true,
218 '_CreateDBAccount' => false,
219 '_NamespaceType' => 'site-name',
220 '_AdminName' => '', // will be set later, when the user selects language
221 '_AdminPassword' => '',
222 '_AdminPasswordConfirm' => '',
223 '_AdminEmail' => '',
224 '_Subscribe' => false,
225 '_SkipOptional' => 'continue',
226 '_RightsProfile' => 'wiki',
227 '_LicenseCode' => 'none',
228 '_CCDone' => false,
229 '_Extensions' => [],
230 '_Skins' => [],
231 '_MemCachedServers' => '',
232 '_UpgradeKeySupplied' => false,
233 '_ExistingDBSettings' => false,
234 // Single quotes are intentional, LocalSettingsGenerator must output this unescaped.
235 '_Logo' => '$wgResourceBasePath/resources/assets/wiki.png',
236
237 'wgAuthenticationTokenVersion' => 1,
238 ];
239
246 private $installSteps = [];
247
253 protected $extraInstallSteps = [];
254
260 protected $objectCaches = [
261 'apcu' => 'apcu_fetch',
262 'wincache' => 'wincache_ucache_get'
263 ];
264
271 'wiki' => [],
272 'no-anon' => [
273 '*' => [ 'edit' => false ]
274 ],
275 'fishbowl' => [
276 '*' => [
277 'createaccount' => false,
278 'edit' => false,
279 ],
280 ],
281 'private' => [
282 '*' => [
283 'createaccount' => false,
284 'edit' => false,
285 'read' => false,
286 ],
287 ],
288 ];
289
295 public $licenses = [
296 'cc-by' => [
297 'url' => 'https://creativecommons.org/licenses/by/4.0/',
298 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by.png',
299 ],
300 'cc-by-sa' => [
301 'url' => 'https://creativecommons.org/licenses/by-sa/4.0/',
302 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-sa.png',
303 ],
304 'cc-by-nc-sa' => [
305 'url' => 'https://creativecommons.org/licenses/by-nc-sa/4.0/',
306 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-nc-sa.png',
307 ],
308 'cc-0' => [
309 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
310 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-0.png',
311 ],
312 'gfdl' => [
313 'url' => 'https://www.gnu.org/copyleft/fdl.html',
314 'icon' => '$wgResourceBasePath/resources/assets/licenses/gnu-fdl.png',
315 ],
316 'none' => [
317 'url' => '',
318 'icon' => '',
319 'text' => ''
320 ],
321 'cc-choose' => [
322 // Details will be filled in by the selector.
323 'url' => '',
324 'icon' => '',
325 'text' => '',
326 ],
327 ];
328
333
342 abstract public function showMessage( $msg, ...$params );
343
349 abstract public function showError( $msg, ...$params );
350
355 abstract public function showStatusMessage( Status $status );
356
367 public static function getInstallerConfig( Config $baseConfig ) {
368 $configOverrides = new HashConfig();
369
370 // disable (problematic) object cache types explicitly, preserving all other (working) ones
371 // bug T113843
372 $emptyCache = [ 'class' => EmptyBagOStuff::class ];
373
374 $objectCaches = [
375 CACHE_NONE => $emptyCache,
376 CACHE_DB => $emptyCache,
377 CACHE_ANYTHING => $emptyCache,
378 CACHE_MEMCACHED => $emptyCache,
379 ] + $baseConfig->get( 'ObjectCaches' );
380
381 $configOverrides->set( 'ObjectCaches', $objectCaches );
382
383 // Load the installer's i18n.
384 $messageDirs = $baseConfig->get( 'MessagesDirs' );
385 $messageDirs['MediawikiInstaller'] = __DIR__ . '/i18n';
386
387 $configOverrides->set( 'MessagesDirs', $messageDirs );
388
389 $installerConfig = new MultiConfig( [ $configOverrides, $baseConfig ] );
390
391 // make sure we use the installer config as the main config
392 $configRegistry = $baseConfig->get( 'ConfigRegistry' );
393 $configRegistry['main'] = static function () use ( $installerConfig ) {
394 return $installerConfig;
395 };
396
397 $configOverrides->set( 'ConfigRegistry', $configRegistry );
398
399 return $installerConfig;
400 }
401
405 public function __construct() {
406 $defaultConfig = new GlobalVarConfig(); // all the stuff from DefaultSettings.php
407 $installerConfig = self::getInstallerConfig( $defaultConfig );
408
409 $this->resetMediaWikiServices( $installerConfig );
410
411 // Disable all storage services, since we don't have any configuration yet!
412 MediaWikiServices::disableStorageBackend();
413
414 $this->settings = $this->internalDefaults;
415
416 foreach ( $this->defaultVarNames as $var ) {
417 $this->settings[$var] = $GLOBALS[$var];
418 }
419
420 $this->doEnvironmentPreps();
421
422 $this->compiledDBs = [];
423 foreach ( self::getDBTypes() as $type ) {
424 $installer = $this->getDBInstaller( $type );
425
426 if ( !$installer->isCompiled() ) {
427 continue;
428 }
429 $this->compiledDBs[] = $type;
430 }
431
432 $this->parserTitle = Title::newFromText( 'Installer' );
433 }
434
449 public function resetMediaWikiServices( Config $installerConfig = null, $serviceOverrides = [] ) {
450 global $wgObjectCaches, $wgLang;
451
452 $serviceOverrides += [
453 // Disable interwiki lookup, to avoid database access during parses
454 'InterwikiLookup' => static function () {
455 return new NullInterwikiLookup();
456 },
457
458 // Disable user options database fetching, only rely on default options.
459 'UserOptionsLookup' => static function ( MediaWikiServices $services ) {
460 return $services->get( '_DefaultOptionsLookup' );
461 }
462 ];
463
464 $lang = $this->getVar( '_UserLang', 'en' );
465
466 // Reset all services and inject config overrides
467 MediaWikiServices::resetGlobalInstance( $installerConfig );
468
469 $mwServices = MediaWikiServices::getInstance();
470
471 foreach ( $serviceOverrides as $name => $callback ) {
472 // Skip if the caller set $callback to null
473 // to suppress default overrides.
474 if ( $callback ) {
475 $mwServices->redefineService( $name, $callback );
476 }
477 }
478
479 // Disable i18n cache
480 $mwServices->getLocalisationCache()->disableBackend();
481
482 // Set a fake user.
483 // Note that this will reset the context's language,
484 // so set the user before setting the language.
485 $user = User::newFromId( 0 );
487
488 RequestContext::getMain()->setUser( $user );
489
490 // Don't attempt to load user language options (T126177)
491 // This will be overridden in the web installer with the user-specified language
492 // Ensure $wgLang does not have a reference to a stale LocalisationCache instance
493 // (T241638, T261081)
494 RequestContext::getMain()->setLanguage( $lang );
495 $wgLang = RequestContext::getMain()->getLanguage();
496
497 // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
498 // SqlBagOStuff will then throw since we just disabled wfGetDB)
499 $wgObjectCaches = $mwServices->getMainConfig()->get( 'ObjectCaches' );
500
501 $this->parserOptions = new ParserOptions( $user ); // language will be wrong :(
502 // Don't try to access DB before user language is initialised
503 $this->setParserLanguage( $mwServices->getLanguageFactory()->getLanguage( 'en' ) );
504
505 return $mwServices;
506 }
507
513 public static function getDBTypes() {
514 return self::$dbTypes;
515 }
516
530 public function doEnvironmentChecks() {
531 // PHP version has already been checked by entry scripts
532 // Show message here for information purposes
533 $this->showMessage( 'config-env-php', PHP_VERSION );
534
535 $good = true;
536 // Must go here because an old version of PCRE can prevent other checks from completing
537 $pcreVersion = explode( ' ', PCRE_VERSION, 2 )[0];
538 if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) {
539 $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion );
540 $good = false;
541 } else {
542 foreach ( $this->envChecks as $check ) {
543 $status = $this->$check();
544 if ( $status === false ) {
545 $good = false;
546 }
547 }
548 }
549
550 $this->setVar( '_Environment', $good );
551
552 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
553 }
554
555 public function doEnvironmentPreps() {
556 foreach ( $this->envPreps as $prep ) {
557 $this->$prep();
558 }
559 }
560
567 public function setVar( $name, $value ) {
568 $this->settings[$name] = $value;
569 }
570
581 public function getVar( $name, $default = null ) {
582 return $this->settings[$name] ?? $default;
583 }
584
590 public function getCompiledDBs() {
591 return $this->compiledDBs;
592 }
593
601 public static function getDBInstallerClass( $type ) {
602 return ucfirst( $type ) . 'Installer';
603 }
604
612 public function getDBInstaller( $type = false ) {
613 if ( !$type ) {
614 $type = $this->getVar( 'wgDBtype' );
615 }
616
617 $type = strtolower( $type );
618
619 if ( !isset( $this->dbInstallers[$type] ) ) {
621 $this->dbInstallers[$type] = new $class( $this );
622 }
623
624 return $this->dbInstallers[$type];
625 }
626
632 public static function getExistingLocalSettings() {
633 global $IP;
634
635 // You might be wondering why this is here. Well if you don't do this
636 // then some poorly-formed extensions try to call their own classes
637 // after immediately registering them. We really need to get extension
638 // registration out of the global scope and into a real format.
639 // @see https://phabricator.wikimedia.org/T69440
640 global $wgAutoloadClasses;
642
643 // LocalSettings.php should not call functions, except wfLoadSkin/wfLoadExtensions
644 // Define the required globals here, to ensure, the functions can do it work correctly.
645 // phpcs:ignore MediaWiki.VariableAnalysis.UnusedGlobalVariables
647
648 Wikimedia\suppressWarnings();
649 $_lsExists = file_exists( "$IP/LocalSettings.php" );
650 Wikimedia\restoreWarnings();
651
652 if ( !$_lsExists ) {
653 return false;
654 }
655 unset( $_lsExists );
656
657 require "$IP/includes/DefaultSettings.php";
658 require "$IP/LocalSettings.php";
659
660 return get_defined_vars();
661 }
662
672 public function getFakePassword( $realPassword ) {
673 return str_repeat( '*', strlen( $realPassword ) );
674 }
675
683 public function setPassword( $name, $value ) {
684 if ( !preg_match( '/^\*+$/', $value ) ) {
685 $this->setVar( $name, $value );
686 }
687 }
688
700 public static function maybeGetWebserverPrimaryGroup() {
701 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
702 # I don't know this, this isn't UNIX.
703 return null;
704 }
705
706 # posix_getegid() *not* getmygid() because we want the group of the webserver,
707 # not whoever owns the current script.
708 $gid = posix_getegid();
709 return posix_getpwuid( $gid )['name'] ?? null;
710 }
711
728 public function parse( $text, $lineStart = false ) {
729 $parser = MediaWikiServices::getInstance()->getParser();
730
731 try {
732 $out = $parser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
733 $html = $out->getText( [
734 'enableSectionEditLinks' => false,
735 'unwrap' => true,
736 ] );
737 $html = Parser::stripOuterParagraph( $html );
738 } catch ( Wikimedia\Services\ServiceDisabledException $e ) {
739 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
740 }
741
742 return $html;
743 }
744
748 public function getParserOptions() {
750 }
751
752 public function disableLinkPopups() {
753 $this->parserOptions->setExternalLinkTarget( false );
754 }
755
756 public function restoreLinkPopups() {
758 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
759 }
760
769 public function populateSiteStats( DatabaseInstaller $installer ) {
770 $status = $installer->getConnection();
771 if ( !$status->isOK() ) {
772 return $status;
773 }
774 // @phan-suppress-next-line PhanUndeclaredMethod
775 $status->value->insert(
776 'site_stats',
777 [
778 'ss_row_id' => 1,
779 'ss_total_edits' => 0,
780 'ss_good_articles' => 0,
781 'ss_total_pages' => 0,
782 'ss_users' => 0,
783 'ss_active_users' => 0,
784 'ss_images' => 0
785 ],
786 __METHOD__,
787 'IGNORE'
788 );
789
790 return Status::newGood();
791 }
792
797 protected function envCheckDB() {
798 global $wgLang;
800 $dbType = $this->getVar( 'wgDBtype' );
801
802 $allNames = [];
803
804 // Messages: config-type-mysql, config-type-postgres, config-type-sqlite
805 foreach ( self::getDBTypes() as $name ) {
806 $allNames[] = wfMessage( "config-type-$name" )->text();
807 }
808
809 $databases = $this->getCompiledDBs();
810
811 $databases = array_flip( $databases );
812 $ok = true;
813 foreach ( array_keys( $databases ) as $db ) {
814 $installer = $this->getDBInstaller( $db );
815 $status = $installer->checkPrerequisites();
816 if ( !$status->isGood() ) {
817 if ( !$this instanceof WebInstaller && $db === $dbType ) {
818 // Strictly check the key database type instead of just outputting message
819 // Note: No perform this check run from the web installer, since this method always called by
820 // the welcome page under web installation, so $dbType will always be 'mysql'
821 $ok = false;
822 }
823 $this->showStatusMessage( $status );
824 unset( $databases[$db] );
825 }
826 }
827 $databases = array_flip( $databases );
828 if ( !$databases ) {
829 $this->showError( 'config-no-db', $wgLang->commaList( $allNames ), count( $allNames ) );
830 return false;
831 }
832 return $ok;
833 }
834
843 protected function envCheckPCRE() {
844 Wikimedia\suppressWarnings();
845 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
846 // Need to check for \p support too, as PCRE can be compiled
847 // with utf8 support, but not unicode property support.
848 // check that \p{Zs} (space separators) matches
849 // U+3000 (Ideographic space)
850 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\u{3000}-" );
851 Wikimedia\restoreWarnings();
852 if ( $regexd != '--' || $regexprop != '--' ) {
853 $this->showError( 'config-pcre-no-utf8' );
854
855 return false;
856 }
857
858 // PCRE must be compiled using PCRE_CONFIG_NEWLINE other than -1 (any)
859 // otherwise it will misidentify some unicode characters containing 0x85
860 // code with break lines
861 if ( preg_match( '/^b.*c$/', 'bąc' ) === 0 ) {
862 $this->showError( 'config-pcre-invalid-newline' );
863
864 return false;
865 }
866
867 return true;
868 }
869
874 protected function envCheckMemory() {
875 $limit = ini_get( 'memory_limit' );
876
877 if ( !$limit || $limit == -1 ) {
878 return true;
879 }
880
881 $n = wfShorthandToInteger( $limit );
882
883 if ( $n < $this->minMemorySize * 1024 * 1024 ) {
884 $newLimit = "{$this->minMemorySize}M";
885
886 if ( ini_set( "memory_limit", $newLimit ) === false ) {
887 $this->showMessage( 'config-memory-bad', $limit );
888 } else {
889 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
890 $this->setVar( '_RaiseMemory', true );
891 }
892 }
893
894 return true;
895 }
896
900 protected function envCheckCache() {
901 $caches = [];
902 foreach ( $this->objectCaches as $name => $function ) {
903 if ( function_exists( $function ) ) {
904 $caches[$name] = true;
905 }
906 }
907
908 if ( !$caches ) {
909 $this->showMessage( 'config-no-cache-apcu' );
910 }
911
912 $this->setVar( '_Caches', $caches );
913 }
914
919 protected function envCheckModSecurity() {
920 if ( self::apacheModulePresent( 'mod_security' )
921 || self::apacheModulePresent( 'mod_security2' ) ) {
922 $this->showMessage( 'config-mod-security' );
923 }
924
925 return true;
926 }
927
932 protected function envCheckDiff3() {
933 $names = [ "gdiff3", "diff3" ];
934 if ( wfIsWindows() ) {
935 $names[] = 'diff3.exe';
936 }
937 $versionInfo = [ '--version', 'GNU diffutils' ];
938
939 $diff3 = ExecutableFinder::findInDefaultPaths( $names, $versionInfo );
940
941 if ( $diff3 ) {
942 $this->setVar( 'wgDiff3', $diff3 );
943 } else {
944 $this->setVar( 'wgDiff3', false );
945 $this->showMessage( 'config-diff3-bad' );
946 }
947
948 return true;
949 }
950
955 protected function envCheckGraphics() {
956 $names = wfIsWindows() ? 'convert.exe' : 'convert';
957 $versionInfo = [ '-version', 'ImageMagick' ];
958 $convert = ExecutableFinder::findInDefaultPaths( $names, $versionInfo );
959
960 $this->setVar( 'wgImageMagickConvertCommand', '' );
961 if ( $convert ) {
962 $this->setVar( 'wgImageMagickConvertCommand', $convert );
963 $this->showMessage( 'config-imagemagick', $convert );
964 } elseif ( function_exists( 'imagejpeg' ) ) {
965 $this->showMessage( 'config-gd' );
966 } else {
967 $this->showMessage( 'config-no-scaling' );
968 }
969
970 return true;
971 }
972
979 protected function envCheckGit() {
980 $names = wfIsWindows() ? 'git.exe' : 'git';
981 $versionInfo = [ '--version', 'git version' ];
982
983 $git = ExecutableFinder::findInDefaultPaths( $names, $versionInfo );
984
985 if ( $git ) {
986 $this->setVar( 'wgGitBin', $git );
987 $this->showMessage( 'config-git', $git );
988 } else {
989 $this->setVar( 'wgGitBin', false );
990 $this->showMessage( 'config-git-bad' );
991 }
992
993 return true;
994 }
995
1001 protected function envCheckServer() {
1002 $server = $this->envGetDefaultServer();
1003 if ( $server !== null ) {
1004 $this->showMessage( 'config-using-server', $server );
1005 }
1006 return true;
1007 }
1008
1014 protected function envCheckPath() {
1015 $this->showMessage(
1016 'config-using-uri',
1017 $this->getVar( 'wgServer' ),
1018 $this->getVar( 'wgScriptPath' )
1019 );
1020 return true;
1021 }
1022
1027 protected function envCheckShellLocale() {
1028 $os = php_uname( 's' );
1029 $supported = [ 'Linux', 'SunOS', 'HP-UX', 'Darwin' ]; # Tested these
1030
1031 if ( !in_array( $os, $supported ) ) {
1032 return true;
1033 }
1034
1035 if ( Shell::isDisabled() ) {
1036 return true;
1037 }
1038
1039 # Get a list of available locales.
1040 $result = Shell::command( '/usr/bin/locale', '-a' )->execute();
1041
1042 if ( $result->getExitCode() != 0 ) {
1043 return true;
1044 }
1045
1046 $lines = $result->getStdout();
1047 $lines = array_map( 'trim', explode( "\n", $lines ) );
1048 $candidatesByLocale = [];
1049 $candidatesByLang = [];
1050 foreach ( $lines as $line ) {
1051 if ( $line === '' ) {
1052 continue;
1053 }
1054
1055 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
1056 continue;
1057 }
1058
1059 list( , $lang, , , ) = $m;
1060
1061 $candidatesByLocale[$m[0]] = $m;
1062 $candidatesByLang[$lang][] = $m;
1063 }
1064
1065 # Try the current value of LANG.
1066 if ( isset( $candidatesByLocale[getenv( 'LANG' )] ) ) {
1067 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
1068
1069 return true;
1070 }
1071
1072 # Try the most common ones.
1073 $commonLocales = [ 'C.UTF-8', 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' ];
1074 foreach ( $commonLocales as $commonLocale ) {
1075 if ( isset( $candidatesByLocale[$commonLocale] ) ) {
1076 $this->setVar( 'wgShellLocale', $commonLocale );
1077
1078 return true;
1079 }
1080 }
1081
1082 # Is there an available locale in the Wiki's language?
1083 $wikiLang = $this->getVar( 'wgLanguageCode' );
1084
1085 if ( isset( $candidatesByLang[$wikiLang] ) ) {
1086 $m = reset( $candidatesByLang[$wikiLang] );
1087 $this->setVar( 'wgShellLocale', $m[0] );
1088
1089 return true;
1090 }
1091
1092 # Are there any at all?
1093 if ( count( $candidatesByLocale ) ) {
1094 $m = reset( $candidatesByLocale );
1095 $this->setVar( 'wgShellLocale', $m[0] );
1096
1097 return true;
1098 }
1099
1100 # Give up.
1101 return true;
1102 }
1103
1108 protected function envCheckUploadsDirectory() {
1109 global $IP;
1110
1111 $dir = $IP . '/images/';
1112 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
1113 $safe = !$this->dirIsExecutable( $dir, $url );
1114
1115 if ( !$safe ) {
1116 $this->showMessage( 'config-uploads-not-safe', $dir );
1117 }
1118
1119 return true;
1120 }
1121
1127 protected function envCheckSuhosinMaxValueLength() {
1128 $currentValue = ini_get( 'suhosin.get.max_value_length' );
1129 $minRequired = 2000;
1130 $recommended = 5000;
1131 if ( $currentValue > 0 && $currentValue < $minRequired ) {
1132 $this->showError( 'config-suhosin-max-value-length', $currentValue, $minRequired, $recommended );
1133 return false;
1134 }
1135
1136 return true;
1137 }
1138
1145 protected function envCheck64Bit() {
1146 if ( PHP_INT_SIZE == 4 ) {
1147 $this->showMessage( 'config-using-32bit' );
1148 }
1149
1150 return true;
1151 }
1152
1156 protected function envCheckLibicu() {
1164 $not_normal_c = "\u{FA6C}";
1165 $normal_c = "\u{242EE}";
1166
1167 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
1168
1169 $this->showMessage( 'config-unicode-using-intl' );
1170 if ( $intl !== $normal_c ) {
1171 $this->showMessage( 'config-unicode-update-warning' );
1172 }
1173 }
1174
1178 protected function envPrepServer() {
1179 $server = $this->envGetDefaultServer();
1180 if ( $server !== null ) {
1181 $this->setVar( 'wgServer', $server );
1182 }
1183 }
1184
1189 abstract protected function envGetDefaultServer();
1190
1194 protected function envPrepPath() {
1195 global $IP;
1196 $IP = dirname( dirname( __DIR__ ) );
1197 $this->setVar( 'IP', $IP );
1198 }
1199
1208 public function dirIsExecutable( $dir, $url ) {
1209 $scriptTypes = [
1210 'php' => [
1211 "<?php echo 'exec';",
1212 "#!/var/env php\n<?php echo 'exec';",
1213 ],
1214 ];
1215
1216 // it would be good to check other popular languages here, but it'll be slow.
1217 // TODO no need to have a loop if there is going to only be one script type
1218
1219 $httpRequestFactory = MediaWikiServices::getInstance()->getHttpRequestFactory();
1220
1221 Wikimedia\suppressWarnings();
1222
1223 foreach ( $scriptTypes as $ext => $contents ) {
1224 foreach ( $contents as $source ) {
1225 $file = 'exectest.' . $ext;
1226
1227 if ( !file_put_contents( $dir . $file, $source ) ) {
1228 break;
1229 }
1230
1231 try {
1232 $text = $httpRequestFactory->get(
1233 $url . $file,
1234 [ 'timeout' => 3 ],
1235 __METHOD__
1236 );
1237 } catch ( Exception $e ) {
1238 // HttpRequestFactory::get can throw with allow_url_fopen = false and no curl
1239 // extension.
1240 $text = null;
1241 }
1242 unlink( $dir . $file );
1243
1244 if ( $text == 'exec' ) {
1245 Wikimedia\restoreWarnings();
1246
1247 return $ext;
1248 }
1249 }
1250 }
1251
1252 Wikimedia\restoreWarnings();
1253
1254 return false;
1255 }
1256
1263 public static function apacheModulePresent( $moduleName ) {
1264 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1265 return true;
1266 }
1267 // try it the hard way
1268 ob_start();
1269 phpinfo( INFO_MODULES );
1270 $info = ob_get_clean();
1271
1272 return strpos( $info, $moduleName ) !== false;
1273 }
1274
1280 public function setParserLanguage( $lang ) {
1281 $this->parserOptions->setTargetLanguage( $lang );
1282 $this->parserOptions->setUserLang( $lang );
1283 }
1284
1290 protected function getDocUrl( $page ) {
1291 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1292 }
1293
1303 public function findExtensions( $directory = 'extensions' ) {
1304 switch ( $directory ) {
1305 case 'extensions':
1306 return $this->findExtensionsByType( 'extension', 'extensions' );
1307 case 'skins':
1308 return $this->findExtensionsByType( 'skin', 'skins' );
1309 default:
1310 throw new InvalidArgumentException( "Invalid extension type" );
1311 }
1312 }
1313
1323 protected function findExtensionsByType( $type = 'extension', $directory = 'extensions' ) {
1324 if ( $this->getVar( 'IP' ) === null ) {
1325 return Status::newGood( [] );
1326 }
1327
1328 $extDir = $this->getVar( 'IP' ) . '/' . $directory;
1329 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
1330 return Status::newGood( [] );
1331 }
1332
1333 // @phan-suppress-next-line SecurityCheck-PathTraversal False positive
1334 $dh = opendir( $extDir );
1335 $exts = [];
1336 $status = new Status;
1337 while ( ( $file = readdir( $dh ) ) !== false ) {
1338 // skip non-dirs and hidden directories
1339 if ( !is_dir( "$extDir/$file" ) || $file[0] === '.' ) {
1340 continue;
1341 }
1342 $extStatus = $this->getExtensionInfo( $type, $directory, $file );
1343 if ( $extStatus->isOK() ) {
1344 $exts[$file] = $extStatus->value;
1345 } elseif ( $extStatus->hasMessage( 'config-extension-not-found' ) ) {
1346 // (T225512) The directory is not actually an extension. Downgrade to warning.
1347 $status->warning( 'config-extension-not-found', $file );
1348 } else {
1349 $status->merge( $extStatus );
1350 }
1351 }
1352 closedir( $dh );
1353 uksort( $exts, 'strnatcasecmp' );
1354
1355 $status->value = $exts;
1356
1357 return $status;
1358 }
1359
1367 protected function getExtensionInfo( $type, $parentRelPath, $name ) {
1368 if ( $this->getVar( 'IP' ) === null ) {
1369 throw new Exception( 'Cannot find extensions since the IP variable is not yet set' );
1370 }
1371 if ( $type !== 'extension' && $type !== 'skin' ) {
1372 throw new InvalidArgumentException( "Invalid extension type" );
1373 }
1374 $absDir = $this->getVar( 'IP' ) . "/$parentRelPath/$name";
1375 $relDir = "../$parentRelPath/$name";
1376 if ( !is_dir( $absDir ) ) {
1377 return Status::newFatal( 'config-extension-not-found', $name );
1378 }
1379 $jsonFile = $type . '.json';
1380 $fullJsonFile = "$absDir/$jsonFile";
1381 $isJson = file_exists( $fullJsonFile );
1382 $isPhp = false;
1383 if ( !$isJson ) {
1384 // Only fallback to PHP file if JSON doesn't exist
1385 $fullPhpFile = "$absDir/$name.php";
1386 $isPhp = file_exists( $fullPhpFile );
1387 }
1388 if ( !$isJson && !$isPhp ) {
1389 return Status::newFatal( 'config-extension-not-found', $name );
1390 }
1391
1392 // Extension exists. Now see if there are screenshots
1393 $info = [];
1394 if ( is_dir( "$absDir/screenshots" ) ) {
1395 $paths = glob( "$absDir/screenshots/*.png" );
1396 foreach ( $paths as $path ) {
1397 $info['screenshots'][] = str_replace( $absDir, $relDir, $path );
1398 }
1399 }
1400
1401 if ( $isJson ) {
1402 $jsonStatus = $this->readExtension( $fullJsonFile );
1403 if ( !$jsonStatus->isOK() ) {
1404 return $jsonStatus;
1405 }
1406 $info += $jsonStatus->value;
1407 }
1408
1409 // @phan-suppress-next-line SecurityCheckMulti
1410 return Status::newGood( $info );
1411 }
1412
1421 private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
1422 $load = [
1423 $fullJsonFile => 1
1424 ];
1425 if ( $extDeps ) {
1426 $extDir = $this->getVar( 'IP' ) . '/extensions';
1427 foreach ( $extDeps as $dep ) {
1428 $fname = "$extDir/$dep/extension.json";
1429 if ( !file_exists( $fname ) ) {
1430 return Status::newFatal( 'config-extension-not-found', $dep );
1431 }
1432 $load[$fname] = 1;
1433 }
1434 }
1435 if ( $skinDeps ) {
1436 $skinDir = $this->getVar( 'IP' ) . '/skins';
1437 foreach ( $skinDeps as $dep ) {
1438 $fname = "$skinDir/$dep/skin.json";
1439 if ( !file_exists( $fname ) ) {
1440 return Status::newFatal( 'config-extension-not-found', $dep );
1441 }
1442 $load[$fname] = 1;
1443 }
1444 }
1445 $registry = new ExtensionRegistry();
1446 try {
1447 $info = $registry->readFromQueue( $load );
1448 } catch ( ExtensionDependencyError $e ) {
1449 if ( $e->incompatibleCore || $e->incompatibleSkins
1450 || $e->incompatibleExtensions
1451 ) {
1452 // If something is incompatible with a dependency, we have no real
1453 // option besides skipping it
1454 return Status::newFatal( 'config-extension-dependency',
1455 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1456 } elseif ( $e->missingExtensions || $e->missingSkins ) {
1457 // There's an extension missing in the dependency tree,
1458 // so add those to the dependency list and try again
1459 $status = $this->readExtension(
1460 $fullJsonFile,
1461 array_merge( $extDeps, $e->missingExtensions ),
1462 array_merge( $skinDeps, $e->missingSkins )
1463 );
1464 if ( !$status->isOK() && !$status->hasMessage( 'config-extension-dependency' ) ) {
1465 $status = Status::newFatal( 'config-extension-dependency',
1466 basename( dirname( $fullJsonFile ) ), $status->getMessage() );
1467 }
1468 return $status;
1469 }
1470 // Some other kind of dependency error?
1471 return Status::newFatal( 'config-extension-dependency',
1472 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1473 }
1474 $ret = [];
1475 // The order of credits will be the order of $load,
1476 // so the first extension is the one we want to load,
1477 // everything else is a dependency
1478 $i = 0;
1479 foreach ( $info['credits'] as $name => $credit ) {
1480 $i++;
1481 if ( $i == 1 ) {
1482 // Extension we want to load
1483 continue;
1484 }
1485 $type = basename( $credit['path'] ) === 'skin.json' ? 'skins' : 'extensions';
1486 $ret['requires'][$type][] = $credit['name'];
1487 }
1488 $credits = array_values( $info['credits'] )[0];
1489 if ( isset( $credits['url'] ) ) {
1490 $ret['url'] = $credits['url'];
1491 }
1492 $ret['type'] = $credits['type'];
1493
1494 return Status::newGood( $ret );
1495 }
1496
1505 public function getDefaultSkin( array $skinNames ) {
1506 $defaultSkin = $GLOBALS['wgDefaultSkin'];
1507 if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) {
1508 return $defaultSkin;
1509 } else {
1510 return $skinNames[0];
1511 }
1512 }
1513
1519 protected function includeExtensions() {
1520 // Marker for DatabaseUpdater::loadExtensions so we don't
1521 // double load extensions
1522 define( 'MW_EXTENSIONS_LOADED', true );
1523
1524 $legacySchemaHooks = $this->getAutoExtensionLegacyHooks();
1525 $data = $this->getAutoExtensionData();
1526 if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
1527 $legacySchemaHooks = array_merge( $legacySchemaHooks,
1528 $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] );
1529 }
1530 $extDeprecatedHooks = $data['attributes']['DeprecatedHooks'] ?? [];
1531
1532 $this->autoExtensionHookContainer = new HookContainer(
1534 [ 'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
1535 $data['attributes']['Hooks'] ?? [],
1536 $extDeprecatedHooks
1537 ),
1538 MediaWikiServices::getInstance()->getObjectFactory()
1539 );
1540
1541 return Status::newGood();
1542 }
1543
1551 protected function getAutoExtensionLegacyHooks() {
1552 $exts = $this->getVar( '_Extensions' );
1553 $installPath = $this->getVar( 'IP' );
1554 $files = [];
1555 foreach ( $exts as $e ) {
1556 if ( file_exists( "$installPath/extensions/$e/$e.php" ) ) {
1557 $files[] = "$installPath/extensions/$e/$e.php";
1558 }
1559 }
1560
1561 if ( $files ) {
1562 return $this->includeExtensionFiles( $files );
1563 } else {
1564 return [];
1565 }
1566 }
1567
1575 protected function includeExtensionFiles( $files ) {
1576 global $IP;
1577 $IP = $this->getVar( 'IP' );
1578
1587 // @phan-suppress-next-line SecurityCheck-PathTraversal
1588 require "$IP/includes/DefaultSettings.php";
1589
1590 // phpcs:ignore MediaWiki.VariableAnalysis.UnusedGlobalVariables
1591 global $wgAutoloadClasses;
1592 foreach ( $files as $file ) {
1593 require_once $file;
1594 }
1595
1596 // @phpcs:disable MediaWiki.VariableAnalysis.MisleadingGlobalNames.Misleading$wgHooks
1597 // @phan-suppress-next-line PhanUndeclaredVariable,PhanCoalescingAlwaysNull $wgHooks is set by DefaultSettings
1598 $hooksWeWant = $wgHooks['LoadExtensionSchemaUpdates'] ?? [];
1599 // @phpcs:enable MediaWiki.VariableAnalysis.MisleadingGlobalNames.Misleading$wgHooks
1600
1601 // Ignore everyone else's hooks. Lord knows what someone might be doing
1602 // in ParserFirstCallInit (see T29171)
1603 return [ 'LoadExtensionSchemaUpdates' => $hooksWeWant ];
1604 }
1605
1612 protected function getAutoExtensionData() {
1613 $exts = $this->getVar( '_Extensions' );
1614 $installPath = $this->getVar( 'IP' );
1615 $queue = [];
1616 foreach ( $exts as $e ) {
1617 if ( file_exists( "$installPath/extensions/$e/extension.json" ) ) {
1618 $queue["$installPath/extensions/$e/extension.json"] = 1;
1619 }
1620 }
1621
1622 $registry = new ExtensionRegistry();
1623 $data = $registry->readFromQueue( $queue );
1624 global $wgAutoloadClasses;
1625 $wgAutoloadClasses += $data['globals']['wgAutoloadClasses'];
1626 return $data;
1627 }
1628
1637 if ( !$this->autoExtensionHookContainer ) {
1638 throw new \Exception( __METHOD__ .
1639 ': includeExtensions() has not been called' );
1640 }
1642 }
1643
1657 protected function getInstallSteps( DatabaseInstaller $installer ) {
1658 $coreInstallSteps = [
1659 [ 'name' => 'database', 'callback' => [ $installer, 'setupDatabase' ] ],
1660 [ 'name' => 'tables', 'callback' => [ $installer, 'createTables' ] ],
1661 [ 'name' => 'tables-manual', 'callback' => [ $installer, 'createManualTables' ] ],
1662 [ 'name' => 'interwiki', 'callback' => [ $installer, 'populateInterwikiTable' ] ],
1663 [ 'name' => 'stats', 'callback' => [ $this, 'populateSiteStats' ] ],
1664 [ 'name' => 'keys', 'callback' => [ $this, 'generateKeys' ] ],
1665 [ 'name' => 'updates', 'callback' => [ $installer, 'insertUpdateKeys' ] ],
1666 [ 'name' => 'restore-services', 'callback' => [ $this, 'restoreServices' ] ],
1667 [ 'name' => 'sysop', 'callback' => [ $this, 'createSysop' ] ],
1668 [ 'name' => 'mainpage', 'callback' => [ $this, 'createMainpage' ] ],
1669 ];
1670
1671 // Build the array of install steps starting from the core install list,
1672 // then adding any callbacks that wanted to attach after a given step
1673 foreach ( $coreInstallSteps as $step ) {
1674 $this->installSteps[] = $step;
1675 if ( isset( $this->extraInstallSteps[$step['name']] ) ) {
1676 $this->installSteps = array_merge(
1677 $this->installSteps,
1678 $this->extraInstallSteps[$step['name']]
1679 );
1680 }
1681 }
1682
1683 // Prepend any steps that want to be at the beginning
1684 if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1685 $this->installSteps = array_merge(
1686 $this->extraInstallSteps['BEGINNING'],
1687 $this->installSteps
1688 );
1689 }
1690
1691 // Extensions should always go first, chance to tie into hooks and such
1692 if ( count( $this->getVar( '_Extensions' ) ) ) {
1693 array_unshift( $this->installSteps,
1694 [ 'name' => 'extensions', 'callback' => [ $this, 'includeExtensions' ] ]
1695 );
1696 $this->installSteps[] = [
1697 'name' => 'extension-tables',
1698 'callback' => [ $installer, 'createExtensionTables' ]
1699 ];
1700 }
1701
1702 return $this->installSteps;
1703 }
1704
1713 public function performInstallation( $startCB, $endCB ) {
1714 $installResults = [];
1715 $installer = $this->getDBInstaller();
1716 $installer->preInstall();
1717 $steps = $this->getInstallSteps( $installer );
1718 foreach ( $steps as $stepObj ) {
1719 $name = $stepObj['name'];
1720 call_user_func_array( $startCB, [ $name ] );
1721
1722 // Perform the callback step
1723 $status = call_user_func( $stepObj['callback'], $installer );
1724
1725 // Output and save the results
1726 call_user_func( $endCB, $name, $status );
1727 $installResults[$name] = $status;
1728
1729 // If we've hit some sort of fatal, we need to bail.
1730 // Callback already had a chance to do output above.
1731 if ( !$status->isOK() ) {
1732 break;
1733 }
1734 }
1735 if ( $status->isOK() ) {
1736 $this->showMessage(
1737 'config-install-db-success'
1738 );
1739 $this->setVar( '_InstallDone', true );
1740 }
1741
1742 return $installResults;
1743 }
1744
1750 public function generateKeys() {
1751 $keys = [ 'wgSecretKey' => 64 ];
1752 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1753 $keys['wgUpgradeKey'] = 16;
1754 }
1755
1756 return $this->doGenerateKeys( $keys );
1757 }
1758
1763 public function restoreServices() {
1764 $this->resetMediaWikiServices( null, [
1765 'UserOptionsLookup' => static function ( MediaWikiServices $services ) {
1766 return $services->get( 'UserOptionsManager' );
1767 }
1768 ] );
1769 return Status::newGood();
1770 }
1771
1778 protected function doGenerateKeys( $keys ) {
1779 foreach ( $keys as $name => $length ) {
1780 $secretKey = MWCryptRand::generateHex( $length );
1781 $this->setVar( $name, $secretKey );
1782 }
1783 return Status::newGood();
1784 }
1785
1791 protected function createSysop() {
1792 $name = $this->getVar( '_AdminName' );
1793 $user = User::newFromName( $name );
1794
1795 if ( !$user ) {
1796 // We should've validated this earlier anyway!
1797 return Status::newFatal( 'config-admin-error-user', $name );
1798 }
1799
1800 if ( $user->idForName() == 0 ) {
1801 $user->addToDatabase();
1802
1803 $password = $this->getVar( '_AdminPassword' );
1804 $status = $user->changeAuthenticationData( [
1805 'username' => $user->getName(),
1806 'password' => $password,
1807 'retype' => $password,
1808 ] );
1809 if ( !$status->isGood() ) {
1810 return Status::newFatal( 'config-admin-error-password',
1811 $name, $status->getWikiText( null, null, $this->getVar( '_UserLang' ) ) );
1812 }
1813
1814 $userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager();
1815 $userGroupManager->addUserToGroup( $user, 'sysop' );
1816 $userGroupManager->addUserToGroup( $user, 'bureaucrat' );
1817 $userGroupManager->addUserToGroup( $user, 'interface-admin' );
1818 if ( $this->getVar( '_AdminEmail' ) ) {
1819 $user->setEmail( $this->getVar( '_AdminEmail' ) );
1820 }
1821 $user->saveSettings();
1822
1823 // Update user count
1824 $ssUpdate = SiteStatsUpdate::factory( [ 'users' => 1 ] );
1825 $ssUpdate->doUpdate();
1826 }
1827
1828 if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1829 return $this->subscribeToMediaWikiAnnounce();
1830 }
1831 return Status::newGood();
1832 }
1833
1837 private function subscribeToMediaWikiAnnounce() {
1838 $status = Status::newGood();
1839 $http = MediaWikiServices::getInstance()->getHttpRequestFactory();
1840 if ( !$http->canMakeRequests() ) {
1841 $status->warning( 'config-install-subscribe-fail',
1842 wfMessage( 'config-install-subscribe-notpossible' ) );
1843 return $status;
1844 }
1845
1846 // Create subscription request
1847 $params = [ 'email' => $this->getVar( '_AdminEmail' ) ];
1848 $req = $http->create( self::MEDIAWIKI_ANNOUNCE_URL . 'anonymous_subscribe',
1849 [ 'method' => 'POST', 'postData' => $params ], __METHOD__ );
1850
1851 // Add headers needed to pass Django's CSRF checks
1852 $token = str_repeat( 'a', 64 );
1853 $req->setHeader( 'Referer', self::MEDIAWIKI_ANNOUNCE_URL );
1854 $req->setHeader( 'Cookie', "csrftoken=$token" );
1855 $req->setHeader( 'X-CSRFToken', $token );
1856
1857 // Send subscription request
1858 $reqStatus = $req->execute();
1859 if ( !$reqStatus->isOK() ) {
1860 $status->warning( 'config-install-subscribe-fail',
1861 Status::wrap( $reqStatus )->getMessage() );
1862 return $status;
1863 }
1864
1865 // Was the request submitted successfully?
1866 // The status message is displayed after a redirect, using Django's messages
1867 // framework, so load the list summary page and look for the expected text.
1868 // (Though parsing the cookie set by the framework may be possible, it isn't
1869 // simple, since the format of the cookie has changed between versions.)
1870 $checkReq = $http->create( self::MEDIAWIKI_ANNOUNCE_URL, [], __METHOD__ );
1871 $checkReq->setCookieJar( $req->getCookieJar() );
1872 if ( !$checkReq->execute()->isOK() ) {
1873 $status->warning( 'config-install-subscribe-possiblefail' );
1874 return $status;
1875 }
1876 $html = $checkReq->getContent();
1877 if ( strpos( $html, 'Please check your inbox for further instructions' ) !== false ) {
1878 // Success
1879 } elseif ( strpos( $html, 'Member already subscribed' ) !== false ) {
1880 $status->warning( 'config-install-subscribe-alreadysubscribed' );
1881 } elseif ( strpos( $html, 'Subscription request already pending' ) !== false ) {
1882 $status->warning( 'config-install-subscribe-alreadypending' );
1883 } else {
1884 $status->warning( 'config-install-subscribe-possiblefail' );
1885 }
1886 return $status;
1887 }
1888
1895 protected function createMainpage( DatabaseInstaller $installer ) {
1896 $status = Status::newGood();
1897 $title = Title::newMainPage();
1898 if ( $title->exists() ) {
1899 $status->warning( 'config-install-mainpage-exists' );
1900 return $status;
1901 }
1902 try {
1903 $page = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
1905 wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
1906 wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
1907 );
1908
1909 $status = $page->doUserEditContent(
1910 $content,
1911 User::newSystemUser( 'MediaWiki default' ),
1912 '',
1913 EDIT_NEW
1914 );
1915 } catch ( Exception $e ) {
1916 // using raw, because $wgShowExceptionDetails can not be set yet
1917 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1918 }
1919
1920 return $status;
1921 }
1922
1926 public static function overrideConfig() {
1927 // Use PHP's built-in session handling, since MediaWiki's
1928 // SessionHandler can't work before we have an object cache set up.
1929 if ( !defined( 'MW_NO_SESSION_HANDLER' ) ) {
1930 define( 'MW_NO_SESSION_HANDLER', 1 );
1931 }
1932
1933 // Don't access the database
1934 $GLOBALS['wgUseDatabaseMessages'] = false;
1935 // Don't cache langconv tables
1936 $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE;
1937 // Don't try to cache ResourceLoader dependencies in the database
1938 $GLOBALS['wgResourceLoaderUseObjectCacheForDeps'] = true;
1939 // Debug-friendly
1940 $GLOBALS['wgShowExceptionDetails'] = true;
1941 $GLOBALS['wgShowHostnames'] = true;
1942 // Don't break forms
1943 $GLOBALS['wgExternalLinkTarget'] = '_blank';
1944
1945 // Allow multiple ob_flush() calls
1946 $GLOBALS['wgDisableOutputCompression'] = true;
1947
1948 // Use a sensible cookie prefix (not my_wiki)
1949 $GLOBALS['wgCookiePrefix'] = 'mw_installer';
1950
1951 // Some of the environment checks make shell requests, remove limits
1952 $GLOBALS['wgMaxShellMemory'] = 0;
1953
1954 // Override the default CookieSessionProvider with a dummy
1955 // implementation that won't stomp on PHP's cookies.
1956 $GLOBALS['wgSessionProviders'] = [
1957 [
1958 'class' => InstallerSessionProvider::class,
1959 'args' => [ [
1960 'priority' => 1,
1961 ] ]
1962 ]
1963 ];
1964
1965 // Don't use the DB as the main stash
1966 $GLOBALS['wgMainStash'] = CACHE_NONE;
1967
1968 // Don't try to use any object cache for SessionManager either.
1969 $GLOBALS['wgSessionCacheType'] = CACHE_NONE;
1970
1971 // Set a dummy $wgServer to bypass the check in Setup.php, the
1972 // web installer will automatically detect it and not use this value.
1973 $GLOBALS['wgServer'] = 'https://🌻.invalid';
1974 }
1975
1983 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1984 $this->extraInstallSteps[$findStep][] = $callback;
1985 }
1986
1991 protected function disableTimeLimit() {
1992 Wikimedia\suppressWarnings();
1993 set_time_limit( 0 );
1994 Wikimedia\restoreWarnings();
1995 }
1996}
$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.
const CACHE_NONE
Definition Defines.php:86
const CACHE_ANYTHING
Definition Defines.php:85
const CACHE_MEMCACHED
Definition Defines.php:88
const CACHE_DB
Definition Defines.php:87
const EDIT_NEW
Definition Defines.php:125
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.
$wgLang
Definition Setup.php:831
$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@debian.org
The Registry loads JSON files, and uses a Processor to extract information from them.
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 MiB.
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.
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:45
Provides a fallback sequence for Config objects.
Set options of the Parser.
static stripOuterParagraph( $html)
Strip outer.
Definition Parser.php:6336
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
static setUser( $user)
Reset the stub global user to a different "real" user object, while ensuring that any method calls on...
Represents a title within MediaWiki.
Definition Title.php:48
static newFromName( $name, $validate='valid')
Definition User.php:607
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:648
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition User.php:810
Class for the core installer web interface.
Content object for wiki text pages.
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