MediaWiki  master
Installer.php
Go to the documentation of this file.
1 <?php
34 use Wikimedia\AtEase\AtEase;
35 
57 abstract 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  'Sitename',
175  'PasswordSender',
176  'LanguageCode',
177  'Localtimezone',
178  'RightsIcon',
179  'RightsText',
180  'RightsUrl',
181  'EnableEmail',
182  'EnableUserEmail',
183  'EnotifUserTalk',
184  'EnotifWatchlist',
185  'EmailAuthentication',
186  'DBname',
187  'DBtype',
188  'Diff3',
189  'ImageMagickConvertCommand',
190  'GitBin',
191  'ScriptPath',
192  'MetaNamespace',
193  'DeletedDirectory',
194  'EnableUploads',
195  'SecretKey',
196  'UseInstantCommons',
197  'UpgradeKey',
198  'DefaultSkin',
199  '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 
277  public $rightsProfiles = [
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 );
509  StubGlobalUser::setUser( $user );
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;
664  $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() {
806  global $wgExternalLinkTarget;
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  // @phan-suppress-next-line SecurityCheckMulti
1377  return Status::newGood( $info );
1378  }
1379 
1388  private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
1389  $load = [
1390  $fullJsonFile => 1
1391  ];
1392  if ( $extDeps ) {
1393  $extDir = $this->getVar( 'IP' ) . '/extensions';
1394  foreach ( $extDeps as $dep ) {
1395  $fname = "$extDir/$dep/extension.json";
1396  if ( !file_exists( $fname ) ) {
1397  return Status::newFatal( 'config-extension-not-found', $dep );
1398  }
1399  $load[$fname] = 1;
1400  }
1401  }
1402  if ( $skinDeps ) {
1403  $skinDir = $this->getVar( 'IP' ) . '/skins';
1404  foreach ( $skinDeps as $dep ) {
1405  $fname = "$skinDir/$dep/skin.json";
1406  if ( !file_exists( $fname ) ) {
1407  return Status::newFatal( 'config-extension-not-found', $dep );
1408  }
1409  $load[$fname] = 1;
1410  }
1411  }
1412  $registry = new ExtensionRegistry();
1413  try {
1414  $info = $registry->readFromQueue( $load );
1415  } catch ( ExtensionDependencyError $e ) {
1416  if ( $e->incompatibleCore || $e->incompatibleSkins
1417  || $e->incompatibleExtensions
1418  ) {
1419  // If something is incompatible with a dependency, we have no real
1420  // option besides skipping it
1421  return Status::newFatal( 'config-extension-dependency',
1422  basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1423  } elseif ( $e->missingExtensions || $e->missingSkins ) {
1424  // There's an extension missing in the dependency tree,
1425  // so add those to the dependency list and try again
1426  $status = $this->readExtension(
1427  $fullJsonFile,
1428  array_merge( $extDeps, $e->missingExtensions ),
1429  array_merge( $skinDeps, $e->missingSkins )
1430  );
1431  if ( !$status->isOK() && !$status->hasMessage( 'config-extension-dependency' ) ) {
1432  $status = Status::newFatal( 'config-extension-dependency',
1433  basename( dirname( $fullJsonFile ) ), $status->getMessage() );
1434  }
1435  return $status;
1436  }
1437  // Some other kind of dependency error?
1438  return Status::newFatal( 'config-extension-dependency',
1439  basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1440  }
1441  $ret = [];
1442  // The order of credits will be the order of $load,
1443  // so the first extension is the one we want to load,
1444  // everything else is a dependency
1445  $i = 0;
1446  foreach ( $info['credits'] as $name => $credit ) {
1447  $i++;
1448  if ( $i == 1 ) {
1449  // Extension we want to load
1450  continue;
1451  }
1452  $type = basename( $credit['path'] ) === 'skin.json' ? 'skins' : 'extensions';
1453  $ret['requires'][$type][] = $credit['name'];
1454  }
1455  $credits = array_values( $info['credits'] )[0];
1456  if ( isset( $credits['url'] ) ) {
1457  $ret['url'] = $credits['url'];
1458  }
1459  $ret['type'] = $credits['type'];
1460 
1461  return Status::newGood( $ret );
1462  }
1463 
1472  public function getDefaultSkin( array $skinNames ) {
1473  $defaultSkin = $GLOBALS['wgDefaultSkin'];
1474  if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) {
1475  return $defaultSkin;
1476  } else {
1477  return $skinNames[0];
1478  }
1479  }
1480 
1486  protected function includeExtensions() {
1487  // Marker for DatabaseUpdater::loadExtensions so we don't
1488  // double load extensions
1489  define( 'MW_EXTENSIONS_LOADED', true );
1490 
1491  $legacySchemaHooks = $this->getAutoExtensionLegacyHooks();
1492  $data = $this->getAutoExtensionData();
1493  if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
1494  $legacySchemaHooks = array_merge( $legacySchemaHooks,
1495  $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] );
1496  }
1497  $extDeprecatedHooks = $data['attributes']['DeprecatedHooks'] ?? [];
1498 
1499  $this->autoExtensionHookContainer = new HookContainer(
1500  new StaticHookRegistry(
1501  [ 'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
1502  $data['attributes']['Hooks'] ?? [],
1503  $extDeprecatedHooks
1504  ),
1505  MediaWikiServices::getInstance()->getObjectFactory()
1506  );
1507 
1508  return Status::newGood();
1509  }
1510 
1518  protected function getAutoExtensionLegacyHooks() {
1519  $exts = $this->getVar( '_Extensions' );
1520  $installPath = $this->getVar( 'IP' );
1521  $files = [];
1522  foreach ( $exts as $e ) {
1523  if ( file_exists( "$installPath/extensions/$e/$e.php" ) ) {
1524  $files[] = "$installPath/extensions/$e/$e.php";
1525  }
1526  }
1527 
1528  if ( $files ) {
1529  return $this->includeExtensionFiles( $files );
1530  } else {
1531  return [];
1532  }
1533  }
1534 
1542  protected function includeExtensionFiles( $files ) {
1543  global $IP;
1544  $IP = $this->getVar( 'IP' );
1545 
1554  // Extract the defaults into the current scope
1555  foreach ( MainConfigSchema::listDefaultValues( 'wg' ) as $var => $value ) {
1556  $$var = $value;
1557  }
1558 
1559  // phpcs:ignore MediaWiki.VariableAnalysis.UnusedGlobalVariables
1561  $wgExtensionDirectory = "$IP/extensions";
1562  $wgStyleDirectory = "$IP/skins";
1563 
1564  foreach ( $files as $file ) {
1565  require_once $file;
1566  }
1567 
1568  // @phpcs:disable MediaWiki.VariableAnalysis.MisleadingGlobalNames.Misleading$wgHooks
1569  // @phpcs:ignore Generic.Files.LineLength.TooLong
1570  // @phan-suppress-next-line PhanUndeclaredVariable,PhanCoalescingAlwaysNull $wgHooks is defined by MainConfigSchema
1571  $hooksWeWant = $wgHooks['LoadExtensionSchemaUpdates'] ?? [];
1572  // @phpcs:enable MediaWiki.VariableAnalysis.MisleadingGlobalNames.Misleading$wgHooks
1573 
1574  // Ignore everyone else's hooks. Lord knows what someone might be doing
1575  // in ParserFirstCallInit (see T29171)
1576  return [ 'LoadExtensionSchemaUpdates' => $hooksWeWant ];
1577  }
1578 
1585  protected function getAutoExtensionData() {
1586  $exts = $this->getVar( '_Extensions' );
1587  $installPath = $this->getVar( 'IP' );
1588 
1589  $extensionProcessor = new ExtensionProcessor();
1590  foreach ( $exts as $e ) {
1591  $jsonPath = "$installPath/extensions/$e/extension.json";
1592  if ( file_exists( $jsonPath ) ) {
1593  $extensionProcessor->extractInfoFromFile( $jsonPath );
1594  }
1595  }
1596 
1597  $autoload = $extensionProcessor->getExtractedAutoloadInfo();
1598  AutoLoader::loadFiles( $autoload['files'] );
1599  AutoLoader::registerClasses( $autoload['classes'] );
1600  AutoLoader::registerNamespaces( $autoload['namespaces'] );
1601 
1602  return $extensionProcessor->getExtractedInfo();
1603  }
1604 
1612  public function getAutoExtensionHookContainer() {
1613  if ( !$this->autoExtensionHookContainer ) {
1614  throw new \Exception( __METHOD__ .
1615  ': includeExtensions() has not been called' );
1616  }
1617  return $this->autoExtensionHookContainer;
1618  }
1619 
1633  protected function getInstallSteps( DatabaseInstaller $installer ) {
1634  $coreInstallSteps = [
1635  [ 'name' => 'database', 'callback' => [ $installer, 'setupDatabase' ] ],
1636  [ 'name' => 'tables', 'callback' => [ $installer, 'createTables' ] ],
1637  [ 'name' => 'tables-manual', 'callback' => [ $installer, 'createManualTables' ] ],
1638  [ 'name' => 'interwiki', 'callback' => [ $installer, 'populateInterwikiTable' ] ],
1639  [ 'name' => 'stats', 'callback' => [ $this, 'populateSiteStats' ] ],
1640  [ 'name' => 'keys', 'callback' => [ $this, 'generateKeys' ] ],
1641  [ 'name' => 'updates', 'callback' => [ $installer, 'insertUpdateKeys' ] ],
1642  [ 'name' => 'restore-services', 'callback' => [ $this, 'restoreServices' ] ],
1643  [ 'name' => 'sysop', 'callback' => [ $this, 'createSysop' ] ],
1644  [ 'name' => 'mainpage', 'callback' => [ $this, 'createMainpage' ] ],
1645  ];
1646 
1647  // Build the array of install steps starting from the core install list,
1648  // then adding any callbacks that wanted to attach after a given step
1649  foreach ( $coreInstallSteps as $step ) {
1650  $this->installSteps[] = $step;
1651  if ( isset( $this->extraInstallSteps[$step['name']] ) ) {
1652  $this->installSteps = array_merge(
1653  $this->installSteps,
1654  $this->extraInstallSteps[$step['name']]
1655  );
1656  }
1657  }
1658 
1659  // Prepend any steps that want to be at the beginning
1660  if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1661  $this->installSteps = array_merge(
1662  $this->extraInstallSteps['BEGINNING'],
1663  $this->installSteps
1664  );
1665  }
1666 
1667  // Extensions should always go first, chance to tie into hooks and such
1668  if ( count( $this->getVar( '_Extensions' ) ) ) {
1669  array_unshift( $this->installSteps,
1670  [ 'name' => 'extensions', 'callback' => [ $this, 'includeExtensions' ] ]
1671  );
1672  $this->installSteps[] = [
1673  'name' => 'extension-tables',
1674  'callback' => [ $installer, 'createExtensionTables' ]
1675  ];
1676  }
1677 
1678  return $this->installSteps;
1679  }
1680 
1689  public function performInstallation( $startCB, $endCB ) {
1690  $installResults = [];
1691  $installer = $this->getDBInstaller();
1692  $installer->preInstall();
1693  $steps = $this->getInstallSteps( $installer );
1694  foreach ( $steps as $stepObj ) {
1695  $name = $stepObj['name'];
1696  call_user_func_array( $startCB, [ $name ] );
1697 
1698  // Perform the callback step
1699  $status = call_user_func( $stepObj['callback'], $installer );
1700 
1701  // Output and save the results
1702  call_user_func( $endCB, $name, $status );
1703  $installResults[$name] = $status;
1704 
1705  // If we've hit some sort of fatal, we need to bail.
1706  // Callback already had a chance to do output above.
1707  if ( !$status->isOK() ) {
1708  break;
1709  }
1710  }
1711  // @phan-suppress-next-next-line PhanPossiblyUndeclaredVariable
1712  // $steps has at least one element and that defines $status
1713  if ( $status->isOK() ) {
1714  $this->showMessage(
1715  'config-install-db-success'
1716  );
1717  $this->setVar( '_InstallDone', true );
1718  }
1719 
1720  return $installResults;
1721  }
1722 
1728  public function generateKeys() {
1729  $keys = [ 'wgSecretKey' => 64 ];
1730  if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1731  $keys['wgUpgradeKey'] = 16;
1732  }
1733 
1734  return $this->doGenerateKeys( $keys );
1735  }
1736 
1741  public function restoreServices() {
1742  $this->resetMediaWikiServices( null, [
1743  'UserOptionsLookup' => static function ( MediaWikiServices $services ) {
1744  return $services->get( 'UserOptionsManager' );
1745  }
1746  ] );
1747  return Status::newGood();
1748  }
1749 
1756  protected function doGenerateKeys( $keys ) {
1757  foreach ( $keys as $name => $length ) {
1758  $secretKey = MWCryptRand::generateHex( $length );
1759  $this->setVar( $name, $secretKey );
1760  }
1761  return Status::newGood();
1762  }
1763 
1769  protected function createSysop() {
1770  $name = $this->getVar( '_AdminName' );
1771  $user = User::newFromName( $name );
1772 
1773  if ( !$user ) {
1774  // We should've validated this earlier anyway!
1775  return Status::newFatal( 'config-admin-error-user', $name );
1776  }
1777 
1778  if ( $user->idForName() == 0 ) {
1779  $user->addToDatabase();
1780 
1781  $password = $this->getVar( '_AdminPassword' );
1782  $status = $user->changeAuthenticationData( [
1783  'username' => $user->getName(),
1784  'password' => $password,
1785  'retype' => $password,
1786  ] );
1787  if ( !$status->isGood() ) {
1788  return Status::newFatal( 'config-admin-error-password',
1789  $name, $status->getWikiText( false, false, $this->getVar( '_UserLang' ) ) );
1790  }
1791 
1792  $userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager();
1793  $userGroupManager->addUserToGroup( $user, 'sysop' );
1794  $userGroupManager->addUserToGroup( $user, 'bureaucrat' );
1795  $userGroupManager->addUserToGroup( $user, 'interface-admin' );
1796  if ( $this->getVar( '_AdminEmail' ) ) {
1797  $user->setEmail( $this->getVar( '_AdminEmail' ) );
1798  }
1799  $user->saveSettings();
1800 
1801  // Update user count
1802  $ssUpdate = SiteStatsUpdate::factory( [ 'users' => 1 ] );
1803  $ssUpdate->doUpdate();
1804  }
1805 
1806  if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1807  return $this->subscribeToMediaWikiAnnounce();
1808  }
1809  return Status::newGood();
1810  }
1811 
1815  private function subscribeToMediaWikiAnnounce() {
1816  $status = Status::newGood();
1817  $http = MediaWikiServices::getInstance()->getHttpRequestFactory();
1818  if ( !$http->canMakeRequests() ) {
1819  $status->warning( 'config-install-subscribe-fail',
1820  wfMessage( 'config-install-subscribe-notpossible' ) );
1821  return $status;
1822  }
1823 
1824  // Create subscription request
1825  $params = [ 'email' => $this->getVar( '_AdminEmail' ) ];
1826  $req = $http->create( self::MEDIAWIKI_ANNOUNCE_URL . 'anonymous_subscribe',
1827  [ 'method' => 'POST', 'postData' => $params ], __METHOD__ );
1828 
1829  // Add headers needed to pass Django's CSRF checks
1830  $token = str_repeat( 'a', 64 );
1831  $req->setHeader( 'Referer', self::MEDIAWIKI_ANNOUNCE_URL );
1832  $req->setHeader( 'Cookie', "csrftoken=$token" );
1833  $req->setHeader( 'X-CSRFToken', $token );
1834 
1835  // Send subscription request
1836  $reqStatus = $req->execute();
1837  if ( !$reqStatus->isOK() ) {
1838  $status->warning( 'config-install-subscribe-fail',
1839  Status::wrap( $reqStatus )->getMessage() );
1840  return $status;
1841  }
1842 
1843  // Was the request submitted successfully?
1844  // The status message is displayed after a redirect, using Django's messages
1845  // framework, so load the list summary page and look for the expected text.
1846  // (Though parsing the cookie set by the framework may be possible, it isn't
1847  // simple, since the format of the cookie has changed between versions.)
1848  $checkReq = $http->create( self::MEDIAWIKI_ANNOUNCE_URL, [], __METHOD__ );
1849  $checkReq->setCookieJar( $req->getCookieJar() );
1850  if ( !$checkReq->execute()->isOK() ) {
1851  $status->warning( 'config-install-subscribe-possiblefail' );
1852  return $status;
1853  }
1854  $html = $checkReq->getContent();
1855  if ( strpos( $html, 'Please check your inbox for further instructions' ) !== false ) {
1856  // Success
1857  } elseif ( strpos( $html, 'Member already subscribed' ) !== false ) {
1858  $status->warning( 'config-install-subscribe-alreadysubscribed' );
1859  } elseif ( strpos( $html, 'Subscription request already pending' ) !== false ) {
1860  $status->warning( 'config-install-subscribe-alreadypending' );
1861  } else {
1862  $status->warning( 'config-install-subscribe-possiblefail' );
1863  }
1864  return $status;
1865  }
1866 
1873  protected function createMainpage( DatabaseInstaller $installer ) {
1874  $status = Status::newGood();
1876  if ( $title->exists() ) {
1877  $status->warning( 'config-install-mainpage-exists' );
1878  return $status;
1879  }
1880  try {
1881  $page = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
1882  $content = new WikitextContent(
1883  wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
1884  wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
1885  );
1886 
1887  $status = $page->doUserEditContent(
1888  $content,
1889  User::newSystemUser( 'MediaWiki default' ),
1890  '',
1891  EDIT_NEW
1892  );
1893  } catch ( Exception $e ) {
1894  // using raw, because $wgShowExceptionDetails can not be set yet
1895  $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1896  }
1897 
1898  return $status;
1899  }
1900 
1906  public static function overrideConfig( SettingsBuilder $settings ) {
1907  // Use PHP's built-in session handling, since MediaWiki's
1908  // SessionHandler can't work before we have an object cache set up.
1909  if ( !defined( 'MW_NO_SESSION_HANDLER' ) ) {
1910  define( 'MW_NO_SESSION_HANDLER', 1 );
1911  }
1912 
1913  $settings->overrideConfigValues( [
1914 
1915  // Don't access the database
1916  MainConfigNames::UseDatabaseMessages => false,
1917 
1918  // Don't cache langconv tables
1919  MainConfigNames::LanguageConverterCacheType => CACHE_NONE,
1920 
1921  // Don't try to cache ResourceLoader dependencies in the database
1922  MainConfigNames::ResourceLoaderUseObjectCacheForDeps => true,
1923 
1924  // Debug-friendly
1925  MainConfigNames::ShowExceptionDetails => true,
1926  MainConfigNames::ShowHostnames => true,
1927 
1928  // Don't break forms
1929  MainConfigNames::ExternalLinkTarget => '_blank',
1930 
1931  // Allow multiple ob_flush() calls
1932  MainConfigNames::DisableOutputCompression => true,
1933 
1934  // Use a sensible cookie prefix (not my_wiki)
1935  MainConfigNames::CookiePrefix => 'mw_installer',
1936 
1937  // Some of the environment checks make shell requests, remove limits
1938  MainConfigNames::MaxShellMemory => 0,
1939 
1940  // Override the default CookieSessionProvider with a dummy
1941  // implementation that won't stomp on PHP's cookies.
1942  MainConfigNames::SessionProviders => [
1943  [
1944  'class' => InstallerSessionProvider::class,
1945  'args' => [ [
1946  'priority' => 1,
1947  ] ]
1948  ]
1949  ],
1950 
1951  // Don't use the DB as the main stash
1952  MainConfigNames::MainStash => CACHE_NONE,
1953 
1954  // Don't try to use any object cache for SessionManager either.
1955  MainConfigNames::SessionCacheType => CACHE_NONE,
1956 
1957  // Set a dummy $wgServer to bypass the check in Setup.php, the
1958  // web installer will automatically detect it and not use this value.
1959  MainConfigNames::Server => 'https://🌻.invalid',
1960  ] );
1961  }
1962 
1970  public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1971  $this->extraInstallSteps[$findStep][] = $callback;
1972  }
1973 
1978  protected function disableTimeLimit() {
1979  AtEase::suppressWarnings();
1980  set_time_limit( 0 );
1981  AtEase::restoreWarnings();
1982  }
1983 }
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:125
wfIsWindows()
Check if the operating system is Windows.
wfShorthandToInteger( $string='', $default=-1)
Converts shorthand byte notation to integer form.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(!defined( 'MEDIAWIKI')) if(ini_get( 'mbstring.func_overload')) if(!defined( 'MW_ENTRY_POINT')) global $IP
Environment checks.
Definition: Setup.php:90
$wgAutoloadClasses
Definition: Setup.php:140
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) $wgLang
Definition: Setup.php:486
Base class for DBMS-specific installation helper classes.
getConnection()
Connect to the database using the administrative user/password currently defined in the session.
static findInDefaultPaths( $names, $versionInfo=false)
Same as locateExecutable(), but checks in getPossibleBinPaths() by default.
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.
Definition: HashConfig.php:30
Base installer class.
Definition: Installer.php:57
envPrepServer()
Environment prep for the server hostname.
Definition: Installer.php:1146
parse( $text, $lineStart=false)
Convert wikitext $text to HTML.
Definition: Installer.php:777
array $compiledDBs
List of detected DBs, access using getCompiledDBs().
Definition: Installer.php:83
getExtensionInfo( $type, $parentRelPath, $name)
Definition: Installer.php:1334
Title $parserTitle
Cached Title, used by parse().
Definition: Installer.php:104
includeExtensions()
Installs the auto-detected extensions.
Definition: Installer.php:1486
createMainpage(DatabaseInstaller $installer)
Insert Main Page with default content.
Definition: Installer.php:1873
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....
Definition: Installer.php:1472
envCheckLibicu()
Check the libicu version.
Definition: Installer.php:1124
getDBInstaller( $type=false)
Get an instance of DatabaseInstaller for the specified DB type.
Definition: Installer.php:635
envCheckDB()
Environment check for DB types.
Definition: Installer.php:846
getDefaultSettings()
Definition: Installer.php:441
envCheckSuhosinMaxValueLength()
Checks if suhosin.get.max_value_length is set, and if so generate a warning because it is incompatibl...
Definition: Installer.php:1095
setVar( $name, $value)
Set a MW configuration variable, or internal installer configuration variable.
Definition: Installer.php:590
array $internalDefaults
Variables that are stored alongside globals, and are used for any configuration of the installation p...
Definition: Installer.php:209
envCheckModSecurity()
Scare user to death if they have mod_security or mod_security2.
Definition: Installer.php:968
getFakePassword( $realPassword)
Get a fake password for sending back to the user in HTML.
Definition: Installer.php:721
envCheckServer()
Environment check to inform user which server we've assumed.
Definition: Installer.php:1050
disableTimeLimit()
Disable the time limit for execution.
Definition: Installer.php:1978
static apacheModulePresent( $moduleName)
Checks for presence of an Apache module.
Definition: Installer.php:1231
array $rightsProfiles
User rights profiles.
Definition: Installer.php:277
addInstallStep( $callback, $findStep='BEGINNING')
Add an installation step following the given step.
Definition: Installer.php:1970
getParserOptions()
Definition: Installer.php:797
getCompiledDBs()
Get a list of DBs supported by current PHP setup.
Definition: Installer.php:613
ParserOptions $parserOptions
Cached ParserOptions, used by parse().
Definition: Installer.php:111
dirIsExecutable( $dir, $url)
Checks if scripts located in the given directory can be executed via the given URL.
Definition: Installer.php:1176
doEnvironmentPreps()
Definition: Installer.php:578
envCheckGit()
Search for git.
Definition: Installer.php:1028
getDocUrl( $page)
Overridden by WebInstaller to provide lastPage parameters.
Definition: Installer.php:1258
array[] $installSteps
The actual list of installation steps.
Definition: Installer.php:253
const MEDIAWIKI_ANNOUNCE_URL
URL to mediawiki-announce list summary page.
Definition: Installer.php:70
resetMediaWikiServices(Config $installerConfig=null, $serviceOverrides=[])
Reset the global service container and associated global state to accommodate different stages of the...
Definition: Installer.php:472
static getExistingLocalSettings()
Determine if LocalSettings.php exists.
Definition: Installer.php:655
static getInstallerConfig(Config $baseConfig)
Constructs a Config object that contains configuration settings that should be overwritten for the in...
Definition: Installer.php:374
array $settings
Definition: Installer.php:76
restoreServices()
Restore services that have been redefined in the early stage of installation.
Definition: Installer.php:1741
includeExtensionFiles( $files)
Include the specified extension PHP files.
Definition: Installer.php:1542
getAutoExtensionData()
Auto-detect extensions with an extension.json file.
Definition: Installer.php:1585
envCheckMemory()
Environment check for available memory.
Definition: Installer.php:923
array $objectCaches
Known object cache types and the functions used to test for their existence.
Definition: Installer.php:267
array $licenses
License types.
Definition: Installer.php:302
static maybeGetWebserverPrimaryGroup()
On POSIX systems return the primary group of the webserver we're running under.
Definition: Installer.php:749
doEnvironmentChecks()
Do initial checks of the PHP environment.
Definition: Installer.php:553
disableLinkPopups()
Definition: Installer.php:801
performInstallation( $startCB, $endCB)
Actually perform the installation.
Definition: Installer.php:1689
getAutoExtensionLegacyHooks()
Auto-detect extensions with an old style .php registration file, load the extensions,...
Definition: Installer.php:1518
envCheck64Bit()
Checks if we're running on 64 bit or not.
Definition: Installer.php:1113
generateKeys()
Generate $wgSecretKey.
Definition: Installer.php:1728
populateSiteStats(DatabaseInstaller $installer)
Install step which adds a row to the site_stats table with appropriate initial values.
Definition: Installer.php:818
envCheckCache()
Environment check for compiled object cache types.
Definition: Installer.php:949
__construct()
Constructor, always call this from child classes.
Definition: Installer.php:412
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...
Definition: Installer.php:1291
doGenerateKeys( $keys)
Generate a secret value for variables using a secure generator.
Definition: Installer.php:1756
showStatusMessage(Status $status)
Show a message to the installing user by using a Status object.
readExtension( $fullJsonFile, $extDeps=[], $skinDeps=[])
Definition: Installer.php:1388
envCheckPath()
Environment check to inform user which paths we've assumed.
Definition: Installer.php:1063
array $envPreps
A list of environment preparation methods called by doEnvironmentPreps().
Definition: Installer.php:161
static overrideConfig(SettingsBuilder $settings)
Override the necessary bits of the config to run an installation.
Definition: Installer.php:1906
setPassword( $name, $value)
Set a variable which stores a password, except if the new value is a fake password in which case leav...
Definition: Installer.php:732
envPrepPath()
Environment prep for setting $IP and $wgScriptPath.
Definition: Installer.php:1162
static getDBTypes()
Get a list of known DB types.
Definition: Installer.php:536
createSysop()
Create the first user account, grant it sysop, bureaucrat and interface-admin rights.
Definition: Installer.php:1769
envCheckPCRE()
Environment check for the PCRE module.
Definition: Installer.php:892
getVar( $name, $default=null)
Get an MW configuration variable, or internal installer configuration variable.
Definition: Installer.php:604
static getDBInstallerClass( $type)
Get the DatabaseInstaller class name for this type.
Definition: Installer.php:624
restoreLinkPopups()
Definition: Installer.php:805
array $extraInstallSteps
Extra steps for installation, for things like DatabaseInstallers to modify.
Definition: Installer.php:260
HookContainer null $autoExtensionHookContainer
Definition: Installer.php:339
static array $dbTypes
Known database types.
Definition: Installer.php:122
getAutoExtensionHookContainer()
Get the hook container previously populated by includeExtensions().
Definition: Installer.php:1612
envCheckDiff3()
Search for GNU diff3.
Definition: Installer.php:981
subscribeToMediaWikiAnnounce()
Definition: Installer.php:1815
envGetDefaultServer()
Helper function to be called from envPrepServer()
getInstallSteps(DatabaseInstaller $installer)
Get an array of install steps.
Definition: Installer.php:1633
array $dbInstallers
Cached DB installer instances, access using getDBInstaller().
Definition: Installer.php:90
array $envChecks
A list of environment check methods called by doEnvironmentChecks().
Definition: Installer.php:139
envCheckGraphics()
Environment check for ImageMagick and GD.
Definition: Installer.php:1004
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.
Definition: Installer.php:1076
setParserLanguage( $lang)
ParserOptions are constructed before we determined the language, so fix it.
Definition: Installer.php:1248
findExtensions( $directory='extensions')
Find extensions or skins in a subdirectory of $IP.
Definition: Installer.php:1271
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:36
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.
Definition: MultiConfig.php:28
Set options of the Parser.
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6474
static getMain()
Get the RequestContext object associated with the main request.
static factory(array $deltas)
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:62
static setUser( $user)
Reset the stub global user to a different "real" user object, while ensuring that any method calls on...
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:700
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:370
static newFromName( $name, $validate='valid')
Definition: User.php:597
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:638
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:805
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
$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