MediaWiki REL1_39
Installer.php
Go to the documentation of this file.
1<?php
34use Wikimedia\AtEase\AtEase;
35
57abstract class Installer {
58
65 public const MINIMUM_PCRE_VERSION = '7.2';
66
70 private const MEDIAWIKI_ANNOUNCE_URL =
71 'https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/';
72
76 protected $settings;
77
83 protected $compiledDBs;
84
90 protected $dbInstallers = [];
91
97 protected $minMemorySize = 50;
98
104 protected $parserTitle;
105
111 protected $parserOptions;
112
122 protected static $dbTypes = [
123 'mysql',
124 'postgres',
125 'sqlite',
126 ];
127
139 protected $envChecks = [
140 'envCheckDB',
141 'envCheckPCRE',
142 'envCheckMemory',
143 'envCheckCache',
144 'envCheckModSecurity',
145 'envCheckDiff3',
146 'envCheckGraphics',
147 'envCheckGit',
148 'envCheckServer',
149 'envCheckPath',
150 'envCheckUploadsDirectory',
151 'envCheckLibicu',
152 'envCheckSuhosinMaxValueLength',
153 'envCheck64Bit',
154 ];
155
161 protected $envPreps = [
162 'envPrepServer',
163 'envPrepPath',
164 ];
165
173 private const DEFAULT_VAR_NAMES = [
174 MainConfigNames::Sitename,
175 MainConfigNames::PasswordSender,
176 MainConfigNames::LanguageCode,
177 MainConfigNames::Localtimezone,
178 MainConfigNames::RightsIcon,
179 MainConfigNames::RightsText,
180 MainConfigNames::RightsUrl,
181 MainConfigNames::EnableEmail,
182 MainConfigNames::EnableUserEmail,
183 MainConfigNames::EnotifUserTalk,
184 MainConfigNames::EnotifWatchlist,
185 MainConfigNames::EmailAuthentication,
186 MainConfigNames::DBname,
187 MainConfigNames::DBtype,
188 MainConfigNames::Diff3,
189 MainConfigNames::ImageMagickConvertCommand,
190 MainConfigNames::GitBin,
191 MainConfigNames::ScriptPath,
192 MainConfigNames::MetaNamespace,
193 MainConfigNames::DeletedDirectory,
194 MainConfigNames::EnableUploads,
195 MainConfigNames::SecretKey,
196 MainConfigNames::UseInstantCommons,
197 MainConfigNames::UpgradeKey,
198 MainConfigNames::DefaultSkin,
199 MainConfigNames::Pingback,
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 '_LogoWordmark' => '',
235 '_LogoWordmarkWidth' => 119,
236 '_LogoWordmarkHeight' => 18,
237 // Single quotes are intentional, LocalSettingsGenerator must output this unescaped.
238 '_Logo1x' => '$wgResourceBasePath/resources/assets/change-your-logo.svg',
239 '_LogoIcon' => '$wgResourceBasePath/resources/assets/change-your-logo-icon.svg',
240 '_LogoTagline' => '',
241 '_LogoTaglineWidth' => 117,
242 '_LogoTaglineHeight' => 13,
243
244 'wgAuthenticationTokenVersion' => 1,
245 ];
246
253 private $installSteps = [];
254
260 protected $extraInstallSteps = [];
261
267 protected $objectCaches = [
268 'apcu' => 'apcu_fetch',
269 'wincache' => 'wincache_ucache_get'
270 ];
271
278 'wiki' => [],
279 'no-anon' => [
280 '*' => [ 'edit' => false ]
281 ],
282 'fishbowl' => [
283 '*' => [
284 'createaccount' => false,
285 'edit' => false,
286 ],
287 ],
288 'private' => [
289 '*' => [
290 'createaccount' => false,
291 'edit' => false,
292 'read' => false,
293 ],
294 ],
295 ];
296
302 public $licenses = [
303 'cc-by' => [
304 'url' => 'https://creativecommons.org/licenses/by/4.0/',
305 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by.png',
306 ],
307 'cc-by-sa' => [
308 'url' => 'https://creativecommons.org/licenses/by-sa/4.0/',
309 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-sa.png',
310 ],
311 'cc-by-nc-sa' => [
312 'url' => 'https://creativecommons.org/licenses/by-nc-sa/4.0/',
313 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-nc-sa.png',
314 ],
315 'cc-0' => [
316 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
317 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-0.png',
318 ],
319 'gfdl' => [
320 'url' => 'https://www.gnu.org/copyleft/fdl.html',
321 'icon' => '$wgResourceBasePath/resources/assets/licenses/gnu-fdl.png',
322 ],
323 'none' => [
324 'url' => '',
325 'icon' => '',
326 'text' => ''
327 ],
328 'cc-choose' => [
329 // Details will be filled in by the selector.
330 'url' => '',
331 'icon' => '',
332 'text' => '',
333 ],
334 ];
335
340
349 abstract public function showMessage( $msg, ...$params );
350
356 abstract public function showError( $msg, ...$params );
357
362 abstract public function showStatusMessage( Status $status );
363
374 public static function getInstallerConfig( Config $baseConfig ) {
375 $configOverrides = new HashConfig();
376
377 // disable (problematic) object cache types explicitly, preserving all other (working) ones
378 // bug T113843
379 $emptyCache = [ 'class' => EmptyBagOStuff::class ];
380
381 $objectCaches = [
382 CACHE_NONE => $emptyCache,
383 CACHE_DB => $emptyCache,
384 CACHE_ANYTHING => $emptyCache,
385 CACHE_MEMCACHED => $emptyCache,
386 ] + $baseConfig->get( MainConfigNames::ObjectCaches );
387
388 $configOverrides->set( MainConfigNames::ObjectCaches, $objectCaches );
389
390 // Load the installer's i18n.
391 $messageDirs = $baseConfig->get( MainConfigNames::MessagesDirs );
392 $messageDirs['MediawikiInstaller'] = __DIR__ . '/i18n';
393
394 $configOverrides->set( MainConfigNames::MessagesDirs, $messageDirs );
395
396 $installerConfig = new MultiConfig( [ $configOverrides, $baseConfig ] );
397
398 // make sure we use the installer config as the main config
399 $configRegistry = $baseConfig->get( MainConfigNames::ConfigRegistry );
400 $configRegistry['main'] = static function () use ( $installerConfig ) {
401 return $installerConfig;
402 };
403
404 $configOverrides->set( MainConfigNames::ConfigRegistry, $configRegistry );
405
406 return $installerConfig;
407 }
408
412 public function __construct() {
413 $defaultConfig = new GlobalVarConfig(); // all the defaults from config-schema.yaml.
414 $installerConfig = self::getInstallerConfig( $defaultConfig );
415
416 $this->resetMediaWikiServices( $installerConfig );
417
418 // Disable all storage services, since we don't have any configuration yet!
419 MediaWikiServices::disableStorageBackend();
420
421 $this->settings = $this->getDefaultSettings();
422
423 $this->doEnvironmentPreps();
424
425 $this->compiledDBs = [];
426 foreach ( self::getDBTypes() as $type ) {
427 $installer = $this->getDBInstaller( $type );
428
429 if ( !$installer->isCompiled() ) {
430 continue;
431 }
432 $this->compiledDBs[] = $type;
433 }
434
435 $this->parserTitle = Title::newFromText( 'Installer' );
436 }
437
441 private function getDefaultSettings(): array {
442 global $wgLocaltimezone;
443
445
446 foreach ( self::DEFAULT_VAR_NAMES as $name ) {
447 $var = "wg{$name}";
448 $ret[$var] = MainConfigSchema::getDefaultValue( $name );
449 }
450
451 // Set $wgLocaltimezone to the value of the global, which SetupDynamicConfig.php will have
452 // set to something that is a valid timezone.
453 $ret['wgLocaltimezone'] = $wgLocaltimezone;
454
455 return $ret;
456 }
457
472 public function resetMediaWikiServices( Config $installerConfig = null, $serviceOverrides = [] ) {
473 global $wgObjectCaches, $wgLang;
474
475 $serviceOverrides += [
476 // Disable interwiki lookup, to avoid database access during parses
477 'InterwikiLookup' => static function () {
478 return new NullInterwikiLookup();
479 },
480
481 // Disable user options database fetching, only rely on default options.
482 'UserOptionsLookup' => static function ( MediaWikiServices $services ) {
483 return $services->get( '_DefaultOptionsLookup' );
484 }
485 ];
486
487 $lang = $this->getVar( '_UserLang', 'en' );
488
489 // Reset all services and inject config overrides
490 MediaWikiServices::resetGlobalInstance( $installerConfig );
491
492 $mwServices = MediaWikiServices::getInstance();
493
494 foreach ( $serviceOverrides as $name => $callback ) {
495 // Skip if the caller set $callback to null
496 // to suppress default overrides.
497 if ( $callback ) {
498 $mwServices->redefineService( $name, $callback );
499 }
500 }
501
502 // Disable i18n cache
503 $mwServices->getLocalisationCache()->disableBackend();
504
505 // Set a fake user.
506 // Note that this will reset the context's language,
507 // so set the user before setting the language.
508 $user = User::newFromId( 0 );
510
511 RequestContext::getMain()->setUser( $user );
512
513 // Don't attempt to load user language options (T126177)
514 // This will be overridden in the web installer with the user-specified language
515 // Ensure $wgLang does not have a reference to a stale LocalisationCache instance
516 // (T241638, T261081)
517 RequestContext::getMain()->setLanguage( $lang );
518 $wgLang = RequestContext::getMain()->getLanguage();
519
520 // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
521 // SqlBagOStuff will then throw since we just disabled wfGetDB)
522 $wgObjectCaches = $mwServices->getMainConfig()->get( MainConfigNames::ObjectCaches );
523
524 $this->parserOptions = new ParserOptions( $user ); // language will be wrong :(
525 // Don't try to access DB before user language is initialised
526 $this->setParserLanguage( $mwServices->getLanguageFactory()->getLanguage( 'en' ) );
527
528 return $mwServices;
529 }
530
536 public static function getDBTypes() {
537 return self::$dbTypes;
538 }
539
553 public function doEnvironmentChecks() {
554 // PHP version has already been checked by entry scripts
555 // Show message here for information purposes
556 $this->showMessage( 'config-env-php', PHP_VERSION );
557
558 $good = true;
559 // Must go here because an old version of PCRE can prevent other checks from completing
560 $pcreVersion = explode( ' ', PCRE_VERSION, 2 )[0];
561 if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) {
562 $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion );
563 $good = false;
564 } else {
565 foreach ( $this->envChecks as $check ) {
566 $status = $this->$check();
567 if ( $status === false ) {
568 $good = false;
569 }
570 }
571 }
572
573 $this->setVar( '_Environment', $good );
574
575 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
576 }
577
578 public function doEnvironmentPreps() {
579 foreach ( $this->envPreps as $prep ) {
580 $this->$prep();
581 }
582 }
583
590 public function setVar( $name, $value ) {
591 $this->settings[$name] = $value;
592 }
593
604 public function getVar( $name, $default = null ) {
605 return $this->settings[$name] ?? $default;
606 }
607
613 public function getCompiledDBs() {
614 return $this->compiledDBs;
615 }
616
624 public static function getDBInstallerClass( $type ) {
625 return ucfirst( $type ) . 'Installer';
626 }
627
635 public function getDBInstaller( $type = false ) {
636 if ( !$type ) {
637 $type = $this->getVar( 'wgDBtype' );
638 }
639
640 $type = strtolower( $type );
641
642 if ( !isset( $this->dbInstallers[$type] ) ) {
643 $class = self::getDBInstallerClass( $type );
644 $this->dbInstallers[$type] = new $class( $this );
645 }
646
647 return $this->dbInstallers[$type];
648 }
649
655 public static function getExistingLocalSettings() {
657
658 // You might be wondering why this is here. Well if you don't do this
659 // then some poorly-formed extensions try to call their own classes
660 // after immediately registering them. We really need to get extension
661 // registration out of the global scope and into a real format.
662 // @see https://phabricator.wikimedia.org/T69440
663 global $wgAutoloadClasses;
665
666 // LocalSettings.php should not call functions, except wfLoadSkin/wfLoadExtensions
667 // Define the required globals here, to ensure, the functions can do it work correctly.
668 // phpcs:ignore MediaWiki.VariableAnalysis.UnusedGlobalVariables
670
671 // This will also define MW_CONFIG_FILE
672 $lsFile = wfDetectLocalSettingsFile( $IP );
673 // phpcs:ignore Generic.PHP.NoSilencedErrors
674 $lsExists = @file_exists( $lsFile );
675
676 if ( !$lsExists ) {
677 return false;
678 }
679
680 if ( !str_ends_with( $lsFile, '.php' ) ) {
681 throw new Exception(
682 'The installer cannot yet handle non-php settings files: ' . $lsFile . '. ' .
683 'Use maintenance/update.php to update an existing installation.'
684 );
685 }
686 unset( $lsExists );
687
688 // Extract the defaults into the current scope
689 foreach ( MainConfigSchema::listDefaultValues( 'wg' ) as $var => $value ) {
690 $$var = $value;
691 }
692
693 $wgExtensionDirectory = "$IP/extensions";
694 $wgStyleDirectory = "$IP/skins";
695
696 // NOTE: To support YAML settings files, this needs to start using SettingsBuilder.
697 // However, as of 1.38, YAML settings files are still experimental and
698 // SettingsBuilder is still unstable. For now, the installer will fail if
699 // the existing settings file is not PHP. The updater should still work though.
700 // NOTE: When adding support for YAML settings file, all references to LocalSettings.php
701 // in localisation messages need to be replaced.
702 // NOTE: This assumes simple variable assignments. More complex setups may involve
703 // settings coming from sub-required and/or functions that assign globals
704 // directly. This is fine here because this isn't used as the "real" include.
705 // It is only used for reading out a small set of variables that the installer
706 // validates and/or displays.
707 require $lsFile;
708
709 return get_defined_vars();
710 }
711
721 public function getFakePassword( $realPassword ) {
722 return str_repeat( '*', strlen( $realPassword ) );
723 }
724
732 public function setPassword( $name, $value ) {
733 if ( !preg_match( '/^\*+$/', $value ) ) {
734 $this->setVar( $name, $value );
735 }
736 }
737
749 public static function maybeGetWebserverPrimaryGroup() {
750 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
751 # I don't know this, this isn't UNIX.
752 return null;
753 }
754
755 # posix_getegid() *not* getmygid() because we want the group of the webserver,
756 # not whoever owns the current script.
757 $gid = posix_getegid();
758 return posix_getpwuid( $gid )['name'] ?? null;
759 }
760
777 public function parse( $text, $lineStart = false ) {
778 $parser = MediaWikiServices::getInstance()->getParser();
779
780 try {
781 $out = $parser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
782 $html = $out->getText( [
783 'enableSectionEditLinks' => false,
784 'unwrap' => true,
785 ] );
786 $html = Parser::stripOuterParagraph( $html );
787 } catch ( Wikimedia\Services\ServiceDisabledException $e ) {
788 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
789 }
790
791 return $html;
792 }
793
797 public function getParserOptions() {
798 return $this->parserOptions;
799 }
800
801 public function disableLinkPopups() {
802 $this->parserOptions->setExternalLinkTarget( false );
803 }
804
805 public function restoreLinkPopups() {
807 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
808 }
809
818 public function populateSiteStats( DatabaseInstaller $installer ) {
819 $status = $installer->getConnection();
820 if ( !$status->isOK() ) {
821 return $status;
822 }
823 // @phan-suppress-next-line PhanUndeclaredMethod
824 $status->value->insert(
825 'site_stats',
826 [
827 'ss_row_id' => 1,
828 'ss_total_edits' => 0,
829 'ss_good_articles' => 0,
830 'ss_total_pages' => 0,
831 'ss_users' => 0,
832 'ss_active_users' => 0,
833 'ss_images' => 0
834 ],
835 __METHOD__,
836 'IGNORE'
837 );
838
839 return Status::newGood();
840 }
841
846 protected function envCheckDB() {
847 global $wgLang;
849 $dbType = $this->getVar( 'wgDBtype' );
850
851 $allNames = [];
852
853 // Messages: config-type-mysql, config-type-postgres, config-type-sqlite
854 foreach ( self::getDBTypes() as $name ) {
855 $allNames[] = wfMessage( "config-type-$name" )->text();
856 }
857
858 $databases = $this->getCompiledDBs();
859
860 $databases = array_flip( $databases );
861 $ok = true;
862 foreach ( array_keys( $databases ) as $db ) {
863 $installer = $this->getDBInstaller( $db );
864 $status = $installer->checkPrerequisites();
865 if ( !$status->isGood() ) {
866 if ( !$this instanceof WebInstaller && $db === $dbType ) {
867 // Strictly check the key database type instead of just outputting message
868 // Note: No perform this check run from the web installer, since this method always called by
869 // the welcome page under web installation, so $dbType will always be 'mysql'
870 $ok = false;
871 }
872 $this->showStatusMessage( $status );
873 unset( $databases[$db] );
874 }
875 }
876 $databases = array_flip( $databases );
877 if ( !$databases ) {
878 $this->showError( 'config-no-db', $wgLang->commaList( $allNames ), count( $allNames ) );
879 return false;
880 }
881 return $ok;
882 }
883
892 protected function envCheckPCRE() {
893 AtEase::suppressWarnings();
894 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
895 // Need to check for \p support too, as PCRE can be compiled
896 // with utf8 support, but not unicode property support.
897 // check that \p{Zs} (space separators) matches
898 // U+3000 (Ideographic space)
899 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\u{3000}-" );
900 AtEase::restoreWarnings();
901 if ( $regexd != '--' || $regexprop != '--' ) {
902 $this->showError( 'config-pcre-no-utf8' );
903
904 return false;
905 }
906
907 // PCRE must be compiled using PCRE_CONFIG_NEWLINE other than -1 (any)
908 // otherwise it will misidentify some unicode characters containing 0x85
909 // code with break lines
910 if ( preg_match( '/^b.*c$/', 'bąc' ) === 0 ) {
911 $this->showError( 'config-pcre-invalid-newline' );
912
913 return false;
914 }
915
916 return true;
917 }
918
923 protected function envCheckMemory() {
924 $limit = ini_get( 'memory_limit' );
925
926 if ( !$limit || $limit == -1 ) {
927 return true;
928 }
929
930 $n = wfShorthandToInteger( $limit );
931
932 if ( $n < $this->minMemorySize * 1024 * 1024 ) {
933 $newLimit = "{$this->minMemorySize}M";
934
935 if ( ini_set( "memory_limit", $newLimit ) === false ) {
936 $this->showMessage( 'config-memory-bad', $limit );
937 } else {
938 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
939 $this->setVar( '_RaiseMemory', true );
940 }
941 }
942
943 return true;
944 }
945
949 protected function envCheckCache() {
950 $caches = [];
951 foreach ( $this->objectCaches as $name => $function ) {
952 if ( function_exists( $function ) ) {
953 $caches[$name] = true;
954 }
955 }
956
957 if ( !$caches ) {
958 $this->showMessage( 'config-no-cache-apcu' );
959 }
960
961 $this->setVar( '_Caches', $caches );
962 }
963
968 protected function envCheckModSecurity() {
969 if ( self::apacheModulePresent( 'mod_security' )
970 || self::apacheModulePresent( 'mod_security2' ) ) {
971 $this->showMessage( 'config-mod-security' );
972 }
973
974 return true;
975 }
976
981 protected function envCheckDiff3() {
982 $names = [ "gdiff3", "diff3" ];
983 if ( wfIsWindows() ) {
984 $names[] = 'diff3.exe';
985 }
986 $versionInfo = [ '--version', 'GNU diffutils' ];
987
988 $diff3 = ExecutableFinder::findInDefaultPaths( $names, $versionInfo );
989
990 if ( $diff3 ) {
991 $this->setVar( 'wgDiff3', $diff3 );
992 } else {
993 $this->setVar( 'wgDiff3', false );
994 $this->showMessage( 'config-diff3-bad' );
995 }
996
997 return true;
998 }
999
1004 protected function envCheckGraphics() {
1005 $names = wfIsWindows() ? 'convert.exe' : 'convert';
1006 $versionInfo = [ '-version', 'ImageMagick' ];
1007 $convert = ExecutableFinder::findInDefaultPaths( $names, $versionInfo );
1008
1009 $this->setVar( 'wgImageMagickConvertCommand', '' );
1010 if ( $convert ) {
1011 $this->setVar( 'wgImageMagickConvertCommand', $convert );
1012 $this->showMessage( 'config-imagemagick', $convert );
1013 } elseif ( function_exists( 'imagejpeg' ) ) {
1014 $this->showMessage( 'config-gd' );
1015 } else {
1016 $this->showMessage( 'config-no-scaling' );
1017 }
1018
1019 return true;
1020 }
1021
1028 protected function envCheckGit() {
1029 $names = wfIsWindows() ? 'git.exe' : 'git';
1030 $versionInfo = [ '--version', 'git version' ];
1031
1032 $git = ExecutableFinder::findInDefaultPaths( $names, $versionInfo );
1033
1034 if ( $git ) {
1035 $this->setVar( 'wgGitBin', $git );
1036 $this->showMessage( 'config-git', $git );
1037 } else {
1038 $this->setVar( 'wgGitBin', false );
1039 $this->showMessage( 'config-git-bad' );
1040 }
1041
1042 return true;
1043 }
1044
1050 protected function envCheckServer() {
1051 $server = $this->envGetDefaultServer();
1052 if ( $server !== null ) {
1053 $this->showMessage( 'config-using-server', $server );
1054 }
1055 return true;
1056 }
1057
1063 protected function envCheckPath() {
1064 $this->showMessage(
1065 'config-using-uri',
1066 $this->getVar( 'wgServer' ),
1067 $this->getVar( 'wgScriptPath' )
1068 );
1069 return true;
1070 }
1071
1076 protected function envCheckUploadsDirectory() {
1077 global $IP;
1078
1079 $dir = $IP . '/images/';
1080 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
1081 $safe = !$this->dirIsExecutable( $dir, $url );
1082
1083 if ( !$safe ) {
1084 $this->showMessage( 'config-uploads-not-safe', $dir );
1085 }
1086
1087 return true;
1088 }
1089
1095 protected function envCheckSuhosinMaxValueLength() {
1096 $currentValue = ini_get( 'suhosin.get.max_value_length' );
1097 $minRequired = 2000;
1098 $recommended = 5000;
1099 if ( $currentValue > 0 && $currentValue < $minRequired ) {
1100 $this->showError( 'config-suhosin-max-value-length', $currentValue, $minRequired, $recommended );
1101 return false;
1102 }
1103
1104 return true;
1105 }
1106
1113 protected function envCheck64Bit() {
1114 if ( PHP_INT_SIZE == 4 ) {
1115 $this->showMessage( 'config-using-32bit' );
1116 }
1117
1118 return true;
1119 }
1120
1124 protected function envCheckLibicu() {
1132 $not_normal_c = "\u{FA6C}";
1133 $normal_c = "\u{242EE}";
1134
1135 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
1136
1137 $this->showMessage( 'config-unicode-using-intl' );
1138 if ( $intl !== $normal_c ) {
1139 $this->showMessage( 'config-unicode-update-warning' );
1140 }
1141 }
1142
1146 protected function envPrepServer() {
1147 $server = $this->envGetDefaultServer();
1148 if ( $server !== null ) {
1149 $this->setVar( 'wgServer', $server );
1150 }
1151 }
1152
1157 abstract protected function envGetDefaultServer();
1158
1162 protected function envPrepPath() {
1163 global $IP;
1164 $IP = dirname( dirname( __DIR__ ) );
1165 $this->setVar( 'IP', $IP );
1166 }
1167
1176 public function dirIsExecutable( $dir, $url ) {
1177 $scriptTypes = [
1178 'php' => [
1179 "<?php echo 'exec';",
1180 "#!/var/env php\n<?php echo 'exec';",
1181 ],
1182 ];
1183
1184 // it would be good to check other popular languages here, but it'll be slow.
1185 // TODO no need to have a loop if there is going to only be one script type
1186
1187 $httpRequestFactory = MediaWikiServices::getInstance()->getHttpRequestFactory();
1188
1189 AtEase::suppressWarnings();
1190
1191 foreach ( $scriptTypes as $ext => $contents ) {
1192 foreach ( $contents as $source ) {
1193 $file = 'exectest.' . $ext;
1194
1195 if ( !file_put_contents( $dir . $file, $source ) ) {
1196 break;
1197 }
1198
1199 try {
1200 $text = $httpRequestFactory->get(
1201 $url . $file,
1202 [ 'timeout' => 3 ],
1203 __METHOD__
1204 );
1205 } catch ( Exception $e ) {
1206 // HttpRequestFactory::get can throw with allow_url_fopen = false and no curl
1207 // extension.
1208 $text = null;
1209 }
1210 unlink( $dir . $file );
1211
1212 if ( $text == 'exec' ) {
1213 AtEase::restoreWarnings();
1214
1215 return $ext;
1216 }
1217 }
1218 }
1219
1220 AtEase::restoreWarnings();
1221
1222 return false;
1223 }
1224
1231 public static function apacheModulePresent( $moduleName ) {
1232 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1233 return true;
1234 }
1235 // try it the hard way
1236 ob_start();
1237 phpinfo( INFO_MODULES );
1238 $info = ob_get_clean();
1239
1240 return strpos( $info, $moduleName ) !== false;
1241 }
1242
1248 public function setParserLanguage( $lang ) {
1249 $this->parserOptions->setTargetLanguage( $lang );
1250 $this->parserOptions->setUserLang( $lang );
1251 }
1252
1258 protected function getDocUrl( $page ) {
1259 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1260 }
1261
1271 public function findExtensions( $directory = 'extensions' ) {
1272 switch ( $directory ) {
1273 case 'extensions':
1274 return $this->findExtensionsByType( 'extension', 'extensions' );
1275 case 'skins':
1276 return $this->findExtensionsByType( 'skin', 'skins' );
1277 default:
1278 throw new InvalidArgumentException( "Invalid extension type" );
1279 }
1280 }
1281
1291 protected function findExtensionsByType( $type = 'extension', $directory = 'extensions' ) {
1292 if ( $this->getVar( 'IP' ) === null ) {
1293 return Status::newGood( [] );
1294 }
1295
1296 $extDir = $this->getVar( 'IP' ) . '/' . $directory;
1297 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
1298 return Status::newGood( [] );
1299 }
1300
1301 $dh = opendir( $extDir );
1302 $exts = [];
1303 $status = new Status;
1304 while ( ( $file = readdir( $dh ) ) !== false ) {
1305 // skip non-dirs and hidden directories
1306 if ( !is_dir( "$extDir/$file" ) || $file[0] === '.' ) {
1307 continue;
1308 }
1309 $extStatus = $this->getExtensionInfo( $type, $directory, $file );
1310 if ( $extStatus->isOK() ) {
1311 $exts[$file] = $extStatus->value;
1312 } elseif ( $extStatus->hasMessage( 'config-extension-not-found' ) ) {
1313 // (T225512) The directory is not actually an extension. Downgrade to warning.
1314 $status->warning( 'config-extension-not-found', $file );
1315 } else {
1316 $status->merge( $extStatus );
1317 }
1318 }
1319 closedir( $dh );
1320 uksort( $exts, 'strnatcasecmp' );
1321
1322 $status->value = $exts;
1323
1324 return $status;
1325 }
1326
1334 protected function getExtensionInfo( $type, $parentRelPath, $name ) {
1335 if ( $this->getVar( 'IP' ) === null ) {
1336 throw new Exception( 'Cannot find extensions since the IP variable is not yet set' );
1337 }
1338 if ( $type !== 'extension' && $type !== 'skin' ) {
1339 throw new InvalidArgumentException( "Invalid extension type" );
1340 }
1341 $absDir = $this->getVar( 'IP' ) . "/$parentRelPath/$name";
1342 $relDir = "../$parentRelPath/$name";
1343 if ( !is_dir( $absDir ) ) {
1344 return Status::newFatal( 'config-extension-not-found', $name );
1345 }
1346 $jsonFile = $type . '.json';
1347 $fullJsonFile = "$absDir/$jsonFile";
1348 $isJson = file_exists( $fullJsonFile );
1349 $isPhp = false;
1350 if ( !$isJson ) {
1351 // Only fallback to PHP file if JSON doesn't exist
1352 $fullPhpFile = "$absDir/$name.php";
1353 $isPhp = file_exists( $fullPhpFile );
1354 }
1355 if ( !$isJson && !$isPhp ) {
1356 return Status::newFatal( 'config-extension-not-found', $name );
1357 }
1358
1359 // Extension exists. Now see if there are screenshots
1360 $info = [];
1361 if ( is_dir( "$absDir/screenshots" ) ) {
1362 $paths = glob( "$absDir/screenshots/*.png" );
1363 foreach ( $paths as $path ) {
1364 $info['screenshots'][] = str_replace( $absDir, $relDir, $path );
1365 }
1366 }
1367
1368 if ( $isJson ) {
1369 $jsonStatus = $this->readExtension( $fullJsonFile );
1370 if ( !$jsonStatus->isOK() ) {
1371 return $jsonStatus;
1372 }
1373 $info += $jsonStatus->value;
1374 }
1375
1376 return Status::newGood( $info );
1377 }
1378
1387 private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
1388 $load = [
1389 $fullJsonFile => 1
1390 ];
1391 if ( $extDeps ) {
1392 $extDir = $this->getVar( 'IP' ) . '/extensions';
1393 foreach ( $extDeps as $dep ) {
1394 $fname = "$extDir/$dep/extension.json";
1395 if ( !file_exists( $fname ) ) {
1396 return Status::newFatal( 'config-extension-not-found', $dep );
1397 }
1398 $load[$fname] = 1;
1399 }
1400 }
1401 if ( $skinDeps ) {
1402 $skinDir = $this->getVar( 'IP' ) . '/skins';
1403 foreach ( $skinDeps as $dep ) {
1404 $fname = "$skinDir/$dep/skin.json";
1405 if ( !file_exists( $fname ) ) {
1406 return Status::newFatal( 'config-extension-not-found', $dep );
1407 }
1408 $load[$fname] = 1;
1409 }
1410 }
1411 $registry = new ExtensionRegistry();
1412 try {
1413 $info = $registry->readFromQueue( $load );
1414 } catch ( ExtensionDependencyError $e ) {
1415 if ( $e->incompatibleCore || $e->incompatibleSkins
1416 || $e->incompatibleExtensions
1417 ) {
1418 // If something is incompatible with a dependency, we have no real
1419 // option besides skipping it
1420 return Status::newFatal( 'config-extension-dependency',
1421 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1422 } elseif ( $e->missingExtensions || $e->missingSkins ) {
1423 // There's an extension missing in the dependency tree,
1424 // so add those to the dependency list and try again
1425 $status = $this->readExtension(
1426 $fullJsonFile,
1427 array_merge( $extDeps, $e->missingExtensions ),
1428 array_merge( $skinDeps, $e->missingSkins )
1429 );
1430 if ( !$status->isOK() && !$status->hasMessage( 'config-extension-dependency' ) ) {
1431 $status = Status::newFatal( 'config-extension-dependency',
1432 basename( dirname( $fullJsonFile ) ), $status->getMessage() );
1433 }
1434 return $status;
1435 }
1436 // Some other kind of dependency error?
1437 return Status::newFatal( 'config-extension-dependency',
1438 basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1439 }
1440 $ret = [];
1441 // The order of credits will be the order of $load,
1442 // so the first extension is the one we want to load,
1443 // everything else is a dependency
1444 $i = 0;
1445 foreach ( $info['credits'] as $name => $credit ) {
1446 $i++;
1447 if ( $i == 1 ) {
1448 // Extension we want to load
1449 continue;
1450 }
1451 $type = basename( $credit['path'] ) === 'skin.json' ? 'skins' : 'extensions';
1452 $ret['requires'][$type][] = $credit['name'];
1453 }
1454 $credits = array_values( $info['credits'] )[0];
1455 if ( isset( $credits['url'] ) ) {
1456 $ret['url'] = $credits['url'];
1457 }
1458 $ret['type'] = $credits['type'];
1459
1460 return Status::newGood( $ret );
1461 }
1462
1471 public function getDefaultSkin( array $skinNames ) {
1472 $defaultSkin = $GLOBALS['wgDefaultSkin'];
1473 if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) {
1474 return $defaultSkin;
1475 } else {
1476 return $skinNames[0];
1477 }
1478 }
1479
1485 protected function includeExtensions() {
1486 // Marker for DatabaseUpdater::loadExtensions so we don't
1487 // double load extensions
1488 define( 'MW_EXTENSIONS_LOADED', true );
1489
1490 $legacySchemaHooks = $this->getAutoExtensionLegacyHooks();
1491 $data = $this->getAutoExtensionData();
1492 if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
1493 $legacySchemaHooks = array_merge( $legacySchemaHooks,
1494 $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] );
1495 }
1496 $extDeprecatedHooks = $data['attributes']['DeprecatedHooks'] ?? [];
1497
1498 $this->autoExtensionHookContainer = new HookContainer(
1500 [ 'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
1501 $data['attributes']['Hooks'] ?? [],
1502 $extDeprecatedHooks
1503 ),
1504 MediaWikiServices::getInstance()->getObjectFactory()
1505 );
1506
1507 return Status::newGood();
1508 }
1509
1517 protected function getAutoExtensionLegacyHooks() {
1518 $exts = $this->getVar( '_Extensions' );
1519 $installPath = $this->getVar( 'IP' );
1520 $files = [];
1521 foreach ( $exts as $e ) {
1522 if ( file_exists( "$installPath/extensions/$e/$e.php" ) ) {
1523 $files[] = "$installPath/extensions/$e/$e.php";
1524 }
1525 }
1526
1527 if ( $files ) {
1528 return $this->includeExtensionFiles( $files );
1529 } else {
1530 return [];
1531 }
1532 }
1533
1541 protected function includeExtensionFiles( $files ) {
1542 global $IP;
1543 $IP = $this->getVar( 'IP' );
1544
1553 // Extract the defaults into the current scope
1554 foreach ( MainConfigSchema::listDefaultValues( 'wg' ) as $var => $value ) {
1555 $$var = $value;
1556 }
1557
1558 // phpcs:ignore MediaWiki.VariableAnalysis.UnusedGlobalVariables
1560 $wgExtensionDirectory = "$IP/extensions";
1561 $wgStyleDirectory = "$IP/skins";
1562
1563 foreach ( $files as $file ) {
1564 require_once $file;
1565 }
1566
1567 // @phpcs:disable MediaWiki.VariableAnalysis.MisleadingGlobalNames.Misleading$wgHooks
1568 // @phpcs:ignore Generic.Files.LineLength.TooLong
1569 // @phan-suppress-next-line PhanUndeclaredVariable,PhanCoalescingAlwaysNull $wgHooks is defined by MainConfigSchema
1570 $hooksWeWant = $wgHooks['LoadExtensionSchemaUpdates'] ?? [];
1571 // @phpcs:enable MediaWiki.VariableAnalysis.MisleadingGlobalNames.Misleading$wgHooks
1572
1573 // Ignore everyone else's hooks. Lord knows what someone might be doing
1574 // in ParserFirstCallInit (see T29171)
1575 return [ 'LoadExtensionSchemaUpdates' => $hooksWeWant ];
1576 }
1577
1584 protected function getAutoExtensionData() {
1585 $exts = $this->getVar( '_Extensions' );
1586 $installPath = $this->getVar( 'IP' );
1587
1588 $extensionProcessor = new ExtensionProcessor();
1589 foreach ( $exts as $e ) {
1590 $jsonPath = "$installPath/extensions/$e/extension.json";
1591 if ( file_exists( $jsonPath ) ) {
1592 $extensionProcessor->extractInfoFromFile( $jsonPath );
1593 }
1594 }
1595
1596 $autoload = $extensionProcessor->getExtractedAutoloadInfo();
1597 AutoLoader::loadFiles( $autoload['files'] );
1598 AutoLoader::registerClasses( $autoload['classes'] );
1599 AutoLoader::registerNamespaces( $autoload['namespaces'] );
1600
1601 return $extensionProcessor->getExtractedInfo();
1602 }
1603
1612 if ( !$this->autoExtensionHookContainer ) {
1613 throw new \Exception( __METHOD__ .
1614 ': includeExtensions() has not been called' );
1615 }
1616 return $this->autoExtensionHookContainer;
1617 }
1618
1632 protected function getInstallSteps( DatabaseInstaller $installer ) {
1633 $coreInstallSteps = [
1634 [ 'name' => 'database', 'callback' => [ $installer, 'setupDatabase' ] ],
1635 [ 'name' => 'tables', 'callback' => [ $installer, 'createTables' ] ],
1636 [ 'name' => 'tables-manual', 'callback' => [ $installer, 'createManualTables' ] ],
1637 [ 'name' => 'interwiki', 'callback' => [ $installer, 'populateInterwikiTable' ] ],
1638 [ 'name' => 'stats', 'callback' => [ $this, 'populateSiteStats' ] ],
1639 [ 'name' => 'keys', 'callback' => [ $this, 'generateKeys' ] ],
1640 [ 'name' => 'updates', 'callback' => [ $installer, 'insertUpdateKeys' ] ],
1641 [ 'name' => 'restore-services', 'callback' => [ $this, 'restoreServices' ] ],
1642 [ 'name' => 'sysop', 'callback' => [ $this, 'createSysop' ] ],
1643 [ 'name' => 'mainpage', 'callback' => [ $this, 'createMainpage' ] ],
1644 ];
1645
1646 // Build the array of install steps starting from the core install list,
1647 // then adding any callbacks that wanted to attach after a given step
1648 foreach ( $coreInstallSteps as $step ) {
1649 $this->installSteps[] = $step;
1650 if ( isset( $this->extraInstallSteps[$step['name']] ) ) {
1651 $this->installSteps = array_merge(
1652 $this->installSteps,
1653 $this->extraInstallSteps[$step['name']]
1654 );
1655 }
1656 }
1657
1658 // Prepend any steps that want to be at the beginning
1659 if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1660 $this->installSteps = array_merge(
1661 $this->extraInstallSteps['BEGINNING'],
1662 $this->installSteps
1663 );
1664 }
1665
1666 // Extensions should always go first, chance to tie into hooks and such
1667 if ( count( $this->getVar( '_Extensions' ) ) ) {
1668 array_unshift( $this->installSteps,
1669 [ 'name' => 'extensions', 'callback' => [ $this, 'includeExtensions' ] ]
1670 );
1671 $this->installSteps[] = [
1672 'name' => 'extension-tables',
1673 'callback' => [ $installer, 'createExtensionTables' ]
1674 ];
1675 }
1676
1677 return $this->installSteps;
1678 }
1679
1688 public function performInstallation( $startCB, $endCB ) {
1689 $installResults = [];
1690 $installer = $this->getDBInstaller();
1691 $installer->preInstall();
1692 $steps = $this->getInstallSteps( $installer );
1693 foreach ( $steps as $stepObj ) {
1694 $name = $stepObj['name'];
1695 call_user_func_array( $startCB, [ $name ] );
1696
1697 // Perform the callback step
1698 $status = call_user_func( $stepObj['callback'], $installer );
1699
1700 // Output and save the results
1701 call_user_func( $endCB, $name, $status );
1702 $installResults[$name] = $status;
1703
1704 // If we've hit some sort of fatal, we need to bail.
1705 // Callback already had a chance to do output above.
1706 if ( !$status->isOK() ) {
1707 break;
1708 }
1709 }
1710 // @phan-suppress-next-next-line PhanPossiblyUndeclaredVariable
1711 // $steps has at least one element and that defines $status
1712 if ( $status->isOK() ) {
1713 $this->showMessage(
1714 'config-install-db-success'
1715 );
1716 $this->setVar( '_InstallDone', true );
1717 }
1718
1719 return $installResults;
1720 }
1721
1727 public function generateKeys() {
1728 $keys = [ 'wgSecretKey' => 64 ];
1729 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1730 $keys['wgUpgradeKey'] = 16;
1731 }
1732
1733 return $this->doGenerateKeys( $keys );
1734 }
1735
1740 public function restoreServices() {
1741 $this->resetMediaWikiServices( null, [
1742 'UserOptionsLookup' => static function ( MediaWikiServices $services ) {
1743 return $services->get( 'UserOptionsManager' );
1744 }
1745 ] );
1746 return Status::newGood();
1747 }
1748
1755 protected function doGenerateKeys( $keys ) {
1756 foreach ( $keys as $name => $length ) {
1757 $secretKey = MWCryptRand::generateHex( $length );
1758 $this->setVar( $name, $secretKey );
1759 }
1760 return Status::newGood();
1761 }
1762
1768 protected function createSysop() {
1769 $name = $this->getVar( '_AdminName' );
1770 $user = User::newFromName( $name );
1771
1772 if ( !$user ) {
1773 // We should've validated this earlier anyway!
1774 return Status::newFatal( 'config-admin-error-user', $name );
1775 }
1776
1777 if ( $user->idForName() == 0 ) {
1778 $user->addToDatabase();
1779
1780 $password = $this->getVar( '_AdminPassword' );
1781 $status = $user->changeAuthenticationData( [
1782 'username' => $user->getName(),
1783 'password' => $password,
1784 'retype' => $password,
1785 ] );
1786 if ( !$status->isGood() ) {
1787 return Status::newFatal( 'config-admin-error-password',
1788 $name, $status->getWikiText( false, false, $this->getVar( '_UserLang' ) ) );
1789 }
1790
1791 $userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager();
1792 $userGroupManager->addUserToGroup( $user, 'sysop' );
1793 $userGroupManager->addUserToGroup( $user, 'bureaucrat' );
1794 $userGroupManager->addUserToGroup( $user, 'interface-admin' );
1795 if ( $this->getVar( '_AdminEmail' ) ) {
1796 $user->setEmail( $this->getVar( '_AdminEmail' ) );
1797 }
1798 $user->saveSettings();
1799
1800 // Update user count
1801 $ssUpdate = SiteStatsUpdate::factory( [ 'users' => 1 ] );
1802 $ssUpdate->doUpdate();
1803 }
1804
1805 if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1806 return $this->subscribeToMediaWikiAnnounce();
1807 }
1808 return Status::newGood();
1809 }
1810
1814 private function subscribeToMediaWikiAnnounce() {
1815 $status = Status::newGood();
1816 $http = MediaWikiServices::getInstance()->getHttpRequestFactory();
1817 if ( !$http->canMakeRequests() ) {
1818 $status->warning( 'config-install-subscribe-fail',
1819 wfMessage( 'config-install-subscribe-notpossible' ) );
1820 return $status;
1821 }
1822
1823 // Create subscription request
1824 $params = [ 'email' => $this->getVar( '_AdminEmail' ) ];
1825 $req = $http->create( self::MEDIAWIKI_ANNOUNCE_URL . 'anonymous_subscribe',
1826 [ 'method' => 'POST', 'postData' => $params ], __METHOD__ );
1827
1828 // Add headers needed to pass Django's CSRF checks
1829 $token = str_repeat( 'a', 64 );
1830 $req->setHeader( 'Referer', self::MEDIAWIKI_ANNOUNCE_URL );
1831 $req->setHeader( 'Cookie', "csrftoken=$token" );
1832 $req->setHeader( 'X-CSRFToken', $token );
1833
1834 // Send subscription request
1835 $reqStatus = $req->execute();
1836 if ( !$reqStatus->isOK() ) {
1837 $status->warning( 'config-install-subscribe-fail',
1838 Status::wrap( $reqStatus )->getMessage() );
1839 return $status;
1840 }
1841
1842 // Was the request submitted successfully?
1843 // The status message is displayed after a redirect, using Django's messages
1844 // framework, so load the list summary page and look for the expected text.
1845 // (Though parsing the cookie set by the framework may be possible, it isn't
1846 // simple, since the format of the cookie has changed between versions.)
1847 $checkReq = $http->create( self::MEDIAWIKI_ANNOUNCE_URL, [], __METHOD__ );
1848 $checkReq->setCookieJar( $req->getCookieJar() );
1849 if ( !$checkReq->execute()->isOK() ) {
1850 $status->warning( 'config-install-subscribe-possiblefail' );
1851 return $status;
1852 }
1853 $html = $checkReq->getContent();
1854 if ( strpos( $html, 'Please check your inbox for further instructions' ) !== false ) {
1855 // Success
1856 } elseif ( strpos( $html, 'Member already subscribed' ) !== false ) {
1857 $status->warning( 'config-install-subscribe-alreadysubscribed' );
1858 } elseif ( strpos( $html, 'Subscription request already pending' ) !== false ) {
1859 $status->warning( 'config-install-subscribe-alreadypending' );
1860 } else {
1861 $status->warning( 'config-install-subscribe-possiblefail' );
1862 }
1863 return $status;
1864 }
1865
1872 protected function createMainpage( DatabaseInstaller $installer ) {
1873 $status = Status::newGood();
1874 $title = Title::newMainPage();
1875 if ( $title->exists() ) {
1876 $status->warning( 'config-install-mainpage-exists' );
1877 return $status;
1878 }
1879 try {
1880 $page = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
1882 wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
1883 wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
1884 );
1885
1886 $status = $page->doUserEditContent(
1887 $content,
1888 User::newSystemUser( 'MediaWiki default' ),
1889 '',
1890 EDIT_NEW
1891 );
1892 } catch ( Exception $e ) {
1893 // using raw, because $wgShowExceptionDetails can not be set yet
1894 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1895 }
1896
1897 return $status;
1898 }
1899
1905 public static function overrideConfig( SettingsBuilder $settings ) {
1906 // Use PHP's built-in session handling, since MediaWiki's
1907 // SessionHandler can't work before we have an object cache set up.
1908 if ( !defined( 'MW_NO_SESSION_HANDLER' ) ) {
1909 define( 'MW_NO_SESSION_HANDLER', 1 );
1910 }
1911
1912 $settings->overrideConfigValues( [
1913
1914 // Don't access the database
1915 MainConfigNames::UseDatabaseMessages => false,
1916
1917 // Don't cache langconv tables
1918 MainConfigNames::LanguageConverterCacheType => CACHE_NONE,
1919
1920 // Don't try to cache ResourceLoader dependencies in the database
1921 MainConfigNames::ResourceLoaderUseObjectCacheForDeps => true,
1922
1923 // Debug-friendly
1924 MainConfigNames::ShowExceptionDetails => true,
1925 MainConfigNames::ShowHostnames => true,
1926
1927 // Don't break forms
1928 MainConfigNames::ExternalLinkTarget => '_blank',
1929
1930 // Allow multiple ob_flush() calls
1931 MainConfigNames::DisableOutputCompression => true,
1932
1933 // Use a sensible cookie prefix (not my_wiki)
1934 MainConfigNames::CookiePrefix => 'mw_installer',
1935
1936 // Some of the environment checks make shell requests, remove limits
1937 MainConfigNames::MaxShellMemory => 0,
1938
1939 // Override the default CookieSessionProvider with a dummy
1940 // implementation that won't stomp on PHP's cookies.
1941 MainConfigNames::SessionProviders => [
1942 [
1943 'class' => InstallerSessionProvider::class,
1944 'args' => [ [
1945 'priority' => 1,
1946 ] ]
1947 ]
1948 ],
1949
1950 // Don't use the DB as the main stash
1951 MainConfigNames::MainStash => CACHE_NONE,
1952
1953 // Don't try to use any object cache for SessionManager either.
1954 MainConfigNames::SessionCacheType => CACHE_NONE,
1955
1956 // Set a dummy $wgServer to bypass the check in Setup.php, the
1957 // web installer will automatically detect it and not use this value.
1958 MainConfigNames::Server => 'https://🌻.invalid',
1959 ] );
1960 }
1961
1969 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1970 $this->extraInstallSteps[$findStep][] = $callback;
1971 }
1972
1977 protected function disableTimeLimit() {
1978 AtEase::suppressWarnings();
1979 set_time_limit( 0 );
1980 AtEase::restoreWarnings();
1981 }
1982}
wfDetectLocalSettingsFile(?string $installationPath=null)
Decide and remember where to load LocalSettings from.
wfDetectInstallPath()
Decide and remember where mediawiki is installed.
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:126
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.
if(!defined( 'MEDIAWIKI')) if(ini_get('mbstring.func_overload')) if(!defined( 'MW_ENTRY_POINT')) global $IP
Environment checks.
Definition Setup.php:91
$wgAutoloadClasses
Definition Setup.php:141
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode $wgLang
Definition Setup.php:497
static registerClasses(array $files)
Register a file to load the given class from.
static loadFiles(array $files)
Batch version of loadFile()
static registerNamespaces(array $dirs)
Register a directory to load the classes of a given namespace from, per PSR4.
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
Utility class for loading extension manifests and aggregating their contents.
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:57
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:83
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:65
getDefaultSkin(array $skinNames)
Returns a default value to be used for $wgDefaultSkin: normally the DefaultSkin from config-schema....
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.
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:76
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:97
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.
envCheckPath()
Environment check to inform user which paths we've assumed.
array $envPreps
A list of environment preparation methods called by doEnvironmentPreps().
static overrideConfig(SettingsBuilder $settings)
Override the necessary bits of the config to run an installation.
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.
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.
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:90
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.
A class containing constants representing the names of configuration variables.
This class contains schema declarations for all configuration variables known to MediaWiki core.
Service locator for MediaWiki core services.
Utility for loading settings files.
overrideConfigValues(array $values)
Override the value of multiple config variables.
Provides a fallback sequence for Config objects.
Set options of the Parser.
static stripOuterParagraph( $html)
Strip outer.
Definition Parser.php:6499
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.
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:49
static newFromName( $name, $validate='valid')
Definition User.php:598
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:639
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition User.php:806
Class for the core installer web interface.
Content object for wiki text pages.
$wgObjectCaches
Config variable stub for the ObjectCaches setting, for use by phpdoc and IDEs.
$wgStyleDirectory
Config variable stub for the StyleDirectory setting, for use by phpdoc and IDEs.
$wgHooks
Config variable stub for the Hooks setting, for use by phpdoc and IDEs.
$wgLocaltimezone
Config variable stub for the Localtimezone setting, for use by phpdoc and IDEs.
$wgExtensionDirectory
Config variable stub for the ExtensionDirectory setting, for use by phpdoc and IDEs.
$wgExternalLinkTarget
Config variable stub for the ExternalLinkTarget setting, for use by phpdoc and IDEs.
Interface for configuration instances.
Definition Config.php:30
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
$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