MediaWiki  master
Installer.php
Go to the documentation of this file.
1 <?php
35 use Wikimedia\AtEase\AtEase;
36 
58 abstract class Installer {
59 
66  public const MINIMUM_PCRE_VERSION = '7.2';
67 
71  private const MEDIAWIKI_ANNOUNCE_URL =
72  'https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/';
73 
77  protected $settings;
78 
84  protected $compiledDBs;
85 
91  protected $dbInstallers = [];
92 
98  protected $minMemorySize = 50;
99 
105  protected $parserTitle;
106 
112  protected $parserOptions;
113 
123  protected static $dbTypes = [
124  'mysql',
125  'postgres',
126  'sqlite',
127  ];
128 
140  protected $envChecks = [
141  'envCheckDB',
142  'envCheckPCRE',
143  'envCheckMemory',
144  'envCheckCache',
145  'envCheckModSecurity',
146  'envCheckDiff3',
147  'envCheckGraphics',
148  'envCheckGit',
149  'envCheckServer',
150  'envCheckPath',
151  'envCheckUploadsDirectory',
152  'envCheckLibicu',
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 
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  // T317647: This ParserOptions method is deprecated; we should be
803  // updating ExternalLinkTarget in the Configuration instead.
804  $this->parserOptions->setExternalLinkTarget( false );
805  }
806 
807  public function restoreLinkPopups() {
808  // T317647: This ParserOptions method is deprecated; we should be
809  // updating ExternalLinkTarget in the Configuration instead.
810  global $wgExternalLinkTarget;
811  $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
812  }
813 
822  public function populateSiteStats( DatabaseInstaller $installer ) {
823  $status = $installer->getConnection();
824  if ( !$status->isOK() ) {
825  return $status;
826  }
827  // @phan-suppress-next-line PhanUndeclaredMethod
828  $status->value->insert(
829  'site_stats',
830  [
831  'ss_row_id' => 1,
832  'ss_total_edits' => 0,
833  'ss_good_articles' => 0,
834  'ss_total_pages' => 0,
835  'ss_users' => 0,
836  'ss_active_users' => 0,
837  'ss_images' => 0
838  ],
839  __METHOD__,
840  'IGNORE'
841  );
842 
843  return Status::newGood();
844  }
845 
850  protected function envCheckDB() {
851  global $wgLang;
853  $dbType = $this->getVar( 'wgDBtype' );
854 
855  $allNames = [];
856 
857  // Messages: config-type-mysql, config-type-postgres, config-type-sqlite
858  foreach ( self::getDBTypes() as $name ) {
859  $allNames[] = wfMessage( "config-type-$name" )->text();
860  }
861 
862  $databases = $this->getCompiledDBs();
863 
864  $databases = array_flip( $databases );
865  $ok = true;
866  foreach ( array_keys( $databases ) as $db ) {
867  $installer = $this->getDBInstaller( $db );
868  $status = $installer->checkPrerequisites();
869  if ( !$status->isGood() ) {
870  if ( !$this instanceof WebInstaller && $db === $dbType ) {
871  // Strictly check the key database type instead of just outputting message
872  // Note: No perform this check run from the web installer, since this method always called by
873  // the welcome page under web installation, so $dbType will always be 'mysql'
874  $ok = false;
875  }
876  $this->showStatusMessage( $status );
877  unset( $databases[$db] );
878  }
879  }
880  $databases = array_flip( $databases );
881  if ( !$databases ) {
882  $this->showError( 'config-no-db', $wgLang->commaList( $allNames ), count( $allNames ) );
883  return false;
884  }
885  return $ok;
886  }
887 
896  protected function envCheckPCRE() {
897  AtEase::suppressWarnings();
898  $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
899  // Need to check for \p support too, as PCRE can be compiled
900  // with utf8 support, but not unicode property support.
901  // check that \p{Zs} (space separators) matches
902  // U+3000 (Ideographic space)
903  $regexprop = preg_replace( '/\p{Zs}/u', '', "-\u{3000}-" );
904  AtEase::restoreWarnings();
905  if ( $regexd != '--' || $regexprop != '--' ) {
906  $this->showError( 'config-pcre-no-utf8' );
907 
908  return false;
909  }
910 
911  // PCRE must be compiled using PCRE_CONFIG_NEWLINE other than -1 (any)
912  // otherwise it will misidentify some unicode characters containing 0x85
913  // code with break lines
914  if ( preg_match( '/^b.*c$/', 'bąc' ) === 0 ) {
915  $this->showError( 'config-pcre-invalid-newline' );
916 
917  return false;
918  }
919 
920  return true;
921  }
922 
927  protected function envCheckMemory() {
928  $limit = ini_get( 'memory_limit' );
929 
930  if ( !$limit || $limit == -1 ) {
931  return true;
932  }
933 
934  $n = wfShorthandToInteger( $limit );
935 
936  if ( $n < $this->minMemorySize * 1024 * 1024 ) {
937  $newLimit = "{$this->minMemorySize}M";
938 
939  if ( ini_set( "memory_limit", $newLimit ) === false ) {
940  $this->showMessage( 'config-memory-bad', $limit );
941  } else {
942  $this->showMessage( 'config-memory-raised', $limit, $newLimit );
943  $this->setVar( '_RaiseMemory', true );
944  }
945  }
946 
947  return true;
948  }
949 
953  protected function envCheckCache() {
954  $caches = [];
955  foreach ( $this->objectCaches as $name => $function ) {
956  if ( function_exists( $function ) ) {
957  $caches[$name] = true;
958  }
959  }
960 
961  if ( !$caches ) {
962  $this->showMessage( 'config-no-cache-apcu' );
963  }
964 
965  $this->setVar( '_Caches', $caches );
966  }
967 
972  protected function envCheckModSecurity() {
973  if ( self::apacheModulePresent( 'mod_security' )
974  || self::apacheModulePresent( 'mod_security2' ) ) {
975  $this->showMessage( 'config-mod-security' );
976  }
977 
978  return true;
979  }
980 
985  protected function envCheckDiff3() {
986  $names = [ "gdiff3", "diff3" ];
987  if ( wfIsWindows() ) {
988  $names[] = 'diff3.exe';
989  }
990  $versionInfo = [ '--version', 'GNU diffutils' ];
991 
992  $diff3 = ExecutableFinder::findInDefaultPaths( $names, $versionInfo );
993 
994  if ( $diff3 ) {
995  $this->setVar( 'wgDiff3', $diff3 );
996  } else {
997  $this->setVar( 'wgDiff3', false );
998  $this->showMessage( 'config-diff3-bad' );
999  }
1000 
1001  return true;
1002  }
1003 
1008  protected function envCheckGraphics() {
1009  $names = wfIsWindows() ? 'convert.exe' : 'convert';
1010  $versionInfo = [ '-version', 'ImageMagick' ];
1011  $convert = ExecutableFinder::findInDefaultPaths( $names, $versionInfo );
1012 
1013  $this->setVar( 'wgImageMagickConvertCommand', '' );
1014  if ( $convert ) {
1015  $this->setVar( 'wgImageMagickConvertCommand', $convert );
1016  $this->showMessage( 'config-imagemagick', $convert );
1017  } elseif ( function_exists( 'imagejpeg' ) ) {
1018  $this->showMessage( 'config-gd' );
1019  } else {
1020  $this->showMessage( 'config-no-scaling' );
1021  }
1022 
1023  return true;
1024  }
1025 
1032  protected function envCheckGit() {
1033  $names = wfIsWindows() ? 'git.exe' : 'git';
1034  $versionInfo = [ '--version', 'git version' ];
1035 
1036  $git = ExecutableFinder::findInDefaultPaths( $names, $versionInfo );
1037 
1038  if ( $git ) {
1039  $this->setVar( 'wgGitBin', $git );
1040  $this->showMessage( 'config-git', $git );
1041  } else {
1042  $this->setVar( 'wgGitBin', false );
1043  $this->showMessage( 'config-git-bad' );
1044  }
1045 
1046  return true;
1047  }
1048 
1054  protected function envCheckServer() {
1055  $server = $this->envGetDefaultServer();
1056  if ( $server !== null ) {
1057  $this->showMessage( 'config-using-server', $server );
1058  }
1059  return true;
1060  }
1061 
1067  protected function envCheckPath() {
1068  $this->showMessage(
1069  'config-using-uri',
1070  $this->getVar( 'wgServer' ),
1071  $this->getVar( 'wgScriptPath' )
1072  );
1073  return true;
1074  }
1075 
1080  protected function envCheckUploadsDirectory() {
1081  global $IP;
1082 
1083  $dir = $IP . '/images/';
1084  $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
1085  $safe = !$this->dirIsExecutable( $dir, $url );
1086 
1087  if ( !$safe ) {
1088  $this->showMessage( 'config-uploads-not-safe', $dir );
1089  }
1090 
1091  return true;
1092  }
1093 
1100  protected function envCheck64Bit() {
1101  if ( PHP_INT_SIZE == 4 ) {
1102  $this->showMessage( 'config-using-32bit' );
1103  }
1104 
1105  return true;
1106  }
1107 
1111  protected function envCheckLibicu() {
1119  $not_normal_c = "\u{FA6C}";
1120  $normal_c = "\u{242EE}";
1121 
1122  $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
1123 
1124  $this->showMessage( 'config-unicode-using-intl' );
1125  if ( $intl !== $normal_c ) {
1126  $this->showMessage( 'config-unicode-update-warning' );
1127  }
1128  }
1129 
1133  protected function envPrepServer() {
1134  $server = $this->envGetDefaultServer();
1135  if ( $server !== null ) {
1136  $this->setVar( 'wgServer', $server );
1137  }
1138  }
1139 
1144  abstract protected function envGetDefaultServer();
1145 
1149  protected function envPrepPath() {
1150  global $IP;
1151  $IP = dirname( dirname( __DIR__ ) );
1152  $this->setVar( 'IP', $IP );
1153  }
1154 
1163  public function dirIsExecutable( $dir, $url ) {
1164  $scriptTypes = [
1165  'php' => [
1166  "<?php echo 'exec';",
1167  "#!/var/env php\n<?php echo 'exec';",
1168  ],
1169  ];
1170 
1171  // it would be good to check other popular languages here, but it'll be slow.
1172  // TODO no need to have a loop if there is going to only be one script type
1173 
1174  $httpRequestFactory = MediaWikiServices::getInstance()->getHttpRequestFactory();
1175 
1176  AtEase::suppressWarnings();
1177 
1178  foreach ( $scriptTypes as $ext => $contents ) {
1179  foreach ( $contents as $source ) {
1180  $file = 'exectest.' . $ext;
1181 
1182  if ( !file_put_contents( $dir . $file, $source ) ) {
1183  break;
1184  }
1185 
1186  try {
1187  $text = $httpRequestFactory->get(
1188  $url . $file,
1189  [ 'timeout' => 3 ],
1190  __METHOD__
1191  );
1192  } catch ( Exception $e ) {
1193  // HttpRequestFactory::get can throw with allow_url_fopen = false and no curl
1194  // extension.
1195  $text = null;
1196  }
1197  unlink( $dir . $file );
1198 
1199  if ( $text == 'exec' ) {
1200  AtEase::restoreWarnings();
1201 
1202  return $ext;
1203  }
1204  }
1205  }
1206 
1207  AtEase::restoreWarnings();
1208 
1209  return false;
1210  }
1211 
1218  public static function apacheModulePresent( $moduleName ) {
1219  if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1220  return true;
1221  }
1222  // try it the hard way
1223  ob_start();
1224  phpinfo( INFO_MODULES );
1225  $info = ob_get_clean();
1226 
1227  return strpos( $info, $moduleName ) !== false;
1228  }
1229 
1235  public function setParserLanguage( $lang ) {
1236  $this->parserOptions->setTargetLanguage( $lang );
1237  $this->parserOptions->setUserLang( $lang );
1238  }
1239 
1245  protected function getDocUrl( $page ) {
1246  return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1247  }
1248 
1258  public function findExtensions( $directory = 'extensions' ) {
1259  switch ( $directory ) {
1260  case 'extensions':
1261  return $this->findExtensionsByType( 'extension', 'extensions' );
1262  case 'skins':
1263  return $this->findExtensionsByType( 'skin', 'skins' );
1264  default:
1265  throw new InvalidArgumentException( "Invalid extension type" );
1266  }
1267  }
1268 
1278  protected function findExtensionsByType( $type = 'extension', $directory = 'extensions' ) {
1279  if ( $this->getVar( 'IP' ) === null ) {
1280  return Status::newGood( [] );
1281  }
1282 
1283  $extDir = $this->getVar( 'IP' ) . '/' . $directory;
1284  if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
1285  return Status::newGood( [] );
1286  }
1287 
1288  $dh = opendir( $extDir );
1289  $exts = [];
1290  $status = new Status;
1291  while ( ( $file = readdir( $dh ) ) !== false ) {
1292  // skip non-dirs and hidden directories
1293  if ( !is_dir( "$extDir/$file" ) || $file[0] === '.' ) {
1294  continue;
1295  }
1296  $extStatus = $this->getExtensionInfo( $type, $directory, $file );
1297  if ( $extStatus->isOK() ) {
1298  $exts[$file] = $extStatus->value;
1299  } elseif ( $extStatus->hasMessage( 'config-extension-not-found' ) ) {
1300  // (T225512) The directory is not actually an extension. Downgrade to warning.
1301  $status->warning( 'config-extension-not-found', $file );
1302  } else {
1303  $status->merge( $extStatus );
1304  }
1305  }
1306  closedir( $dh );
1307  uksort( $exts, 'strnatcasecmp' );
1308 
1309  $status->value = $exts;
1310 
1311  return $status;
1312  }
1313 
1321  protected function getExtensionInfo( $type, $parentRelPath, $name ) {
1322  if ( $this->getVar( 'IP' ) === null ) {
1323  throw new Exception( 'Cannot find extensions since the IP variable is not yet set' );
1324  }
1325  if ( $type !== 'extension' && $type !== 'skin' ) {
1326  throw new InvalidArgumentException( "Invalid extension type" );
1327  }
1328  $absDir = $this->getVar( 'IP' ) . "/$parentRelPath/$name";
1329  $relDir = "../$parentRelPath/$name";
1330  if ( !is_dir( $absDir ) ) {
1331  return Status::newFatal( 'config-extension-not-found', $name );
1332  }
1333  $jsonFile = $type . '.json';
1334  $fullJsonFile = "$absDir/$jsonFile";
1335  $isJson = file_exists( $fullJsonFile );
1336  $isPhp = false;
1337  if ( !$isJson ) {
1338  // Only fallback to PHP file if JSON doesn't exist
1339  $fullPhpFile = "$absDir/$name.php";
1340  $isPhp = file_exists( $fullPhpFile );
1341  }
1342  if ( !$isJson && !$isPhp ) {
1343  return Status::newFatal( 'config-extension-not-found', $name );
1344  }
1345 
1346  // Extension exists. Now see if there are screenshots
1347  $info = [];
1348  if ( is_dir( "$absDir/screenshots" ) ) {
1349  $paths = glob( "$absDir/screenshots/*.png" );
1350  foreach ( $paths as $path ) {
1351  $info['screenshots'][] = str_replace( $absDir, $relDir, $path );
1352  }
1353  }
1354 
1355  if ( $isJson ) {
1356  $jsonStatus = $this->readExtension( $fullJsonFile );
1357  if ( !$jsonStatus->isOK() ) {
1358  return $jsonStatus;
1359  }
1360  $info += $jsonStatus->value;
1361  }
1362 
1363  return Status::newGood( $info );
1364  }
1365 
1374  private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
1375  $load = [
1376  $fullJsonFile => 1
1377  ];
1378  if ( $extDeps ) {
1379  $extDir = $this->getVar( 'IP' ) . '/extensions';
1380  foreach ( $extDeps as $dep ) {
1381  $fname = "$extDir/$dep/extension.json";
1382  if ( !file_exists( $fname ) ) {
1383  return Status::newFatal( 'config-extension-not-found', $dep );
1384  }
1385  $load[$fname] = 1;
1386  }
1387  }
1388  if ( $skinDeps ) {
1389  $skinDir = $this->getVar( 'IP' ) . '/skins';
1390  foreach ( $skinDeps as $dep ) {
1391  $fname = "$skinDir/$dep/skin.json";
1392  if ( !file_exists( $fname ) ) {
1393  return Status::newFatal( 'config-extension-not-found', $dep );
1394  }
1395  $load[$fname] = 1;
1396  }
1397  }
1398  $registry = new ExtensionRegistry();
1399  try {
1400  $info = $registry->readFromQueue( $load );
1401  } catch ( ExtensionDependencyError $e ) {
1402  if ( $e->incompatibleCore || $e->incompatibleSkins
1403  || $e->incompatibleExtensions
1404  ) {
1405  // If something is incompatible with a dependency, we have no real
1406  // option besides skipping it
1407  return Status::newFatal( 'config-extension-dependency',
1408  basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1409  } elseif ( $e->missingExtensions || $e->missingSkins ) {
1410  // There's an extension missing in the dependency tree,
1411  // so add those to the dependency list and try again
1412  $status = $this->readExtension(
1413  $fullJsonFile,
1414  array_merge( $extDeps, $e->missingExtensions ),
1415  array_merge( $skinDeps, $e->missingSkins )
1416  );
1417  if ( !$status->isOK() && !$status->hasMessage( 'config-extension-dependency' ) ) {
1418  $status = Status::newFatal( 'config-extension-dependency',
1419  basename( dirname( $fullJsonFile ) ), $status->getMessage() );
1420  }
1421  return $status;
1422  }
1423  // Some other kind of dependency error?
1424  return Status::newFatal( 'config-extension-dependency',
1425  basename( dirname( $fullJsonFile ) ), $e->getMessage() );
1426  }
1427  $ret = [];
1428  // The order of credits will be the order of $load,
1429  // so the first extension is the one we want to load,
1430  // everything else is a dependency
1431  $i = 0;
1432  foreach ( $info['credits'] as $credit ) {
1433  $i++;
1434  if ( $i == 1 ) {
1435  // Extension we want to load
1436  continue;
1437  }
1438  $type = basename( $credit['path'] ) === 'skin.json' ? 'skins' : 'extensions';
1439  $ret['requires'][$type][] = $credit['name'];
1440  }
1441  $credits = array_values( $info['credits'] )[0];
1442  if ( isset( $credits['url'] ) ) {
1443  $ret['url'] = $credits['url'];
1444  }
1445  $ret['type'] = $credits['type'];
1446 
1447  return Status::newGood( $ret );
1448  }
1449 
1458  public function getDefaultSkin( array $skinNames ) {
1459  $defaultSkin = $GLOBALS['wgDefaultSkin'];
1460  if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) {
1461  return $defaultSkin;
1462  } else {
1463  return $skinNames[0];
1464  }
1465  }
1466 
1472  protected function includeExtensions() {
1473  // Marker for DatabaseUpdater::loadExtensions so we don't
1474  // double load extensions
1475  define( 'MW_EXTENSIONS_LOADED', true );
1476 
1477  $legacySchemaHooks = $this->getAutoExtensionLegacyHooks();
1478  $data = $this->getAutoExtensionData();
1479  if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
1480  $legacySchemaHooks = array_merge( $legacySchemaHooks,
1481  $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] );
1482  }
1483  $extDeprecatedHooks = $data['attributes']['DeprecatedHooks'] ?? [];
1484 
1485  $this->autoExtensionHookContainer = new HookContainer(
1486  new StaticHookRegistry(
1487  [ 'LoadExtensionSchemaUpdates' => $legacySchemaHooks ],
1488  $data['attributes']['Hooks'] ?? [],
1489  $extDeprecatedHooks
1490  ),
1491  MediaWikiServices::getInstance()->getObjectFactory()
1492  );
1493 
1494  return Status::newGood();
1495  }
1496 
1504  protected function getAutoExtensionLegacyHooks() {
1505  $exts = $this->getVar( '_Extensions' );
1506  $installPath = $this->getVar( 'IP' );
1507  $files = [];
1508  foreach ( $exts as $e ) {
1509  if ( file_exists( "$installPath/extensions/$e/$e.php" ) ) {
1510  $files[] = "$installPath/extensions/$e/$e.php";
1511  }
1512  }
1513 
1514  if ( $files ) {
1515  return $this->includeExtensionFiles( $files );
1516  } else {
1517  return [];
1518  }
1519  }
1520 
1528  protected function includeExtensionFiles( $files ) {
1529  global $IP;
1530  $IP = $this->getVar( 'IP' );
1531 
1540  // Extract the defaults into the current scope
1541  foreach ( MainConfigSchema::listDefaultValues( 'wg' ) as $var => $value ) {
1542  $$var = $value;
1543  }
1544 
1545  // phpcs:ignore MediaWiki.VariableAnalysis.UnusedGlobalVariables
1547  $wgExtensionDirectory = "$IP/extensions";
1548  $wgStyleDirectory = "$IP/skins";
1549 
1550  foreach ( $files as $file ) {
1551  require_once $file;
1552  }
1553 
1554  // @phpcs:disable MediaWiki.VariableAnalysis.MisleadingGlobalNames.Misleading$wgHooks
1555  // @phpcs:ignore Generic.Files.LineLength.TooLong
1556  // @phan-suppress-next-line PhanUndeclaredVariable,PhanCoalescingAlwaysNull $wgHooks is defined by MainConfigSchema
1557  $hooksWeWant = $wgHooks['LoadExtensionSchemaUpdates'] ?? [];
1558  // @phpcs:enable MediaWiki.VariableAnalysis.MisleadingGlobalNames.Misleading$wgHooks
1559 
1560  // Ignore everyone else's hooks. Lord knows what someone might be doing
1561  // in ParserFirstCallInit (see T29171)
1562  return [ 'LoadExtensionSchemaUpdates' => $hooksWeWant ];
1563  }
1564 
1571  protected function getAutoExtensionData() {
1572  $exts = $this->getVar( '_Extensions' );
1573  $installPath = $this->getVar( 'IP' );
1574 
1575  $extensionProcessor = new ExtensionProcessor();
1576  foreach ( $exts as $e ) {
1577  $jsonPath = "$installPath/extensions/$e/extension.json";
1578  if ( file_exists( $jsonPath ) ) {
1579  $extensionProcessor->extractInfoFromFile( $jsonPath );
1580  }
1581  }
1582 
1583  $autoload = $extensionProcessor->getExtractedAutoloadInfo();
1584  AutoLoader::loadFiles( $autoload['files'] );
1585  AutoLoader::registerClasses( $autoload['classes'] );
1586  AutoLoader::registerNamespaces( $autoload['namespaces'] );
1587 
1588  return $extensionProcessor->getExtractedInfo();
1589  }
1590 
1598  public function getAutoExtensionHookContainer() {
1599  if ( !$this->autoExtensionHookContainer ) {
1600  throw new \Exception( __METHOD__ .
1601  ': includeExtensions() has not been called' );
1602  }
1603  return $this->autoExtensionHookContainer;
1604  }
1605 
1619  protected function getInstallSteps( DatabaseInstaller $installer ) {
1620  $coreInstallSteps = [
1621  [ 'name' => 'database', 'callback' => [ $installer, 'setupDatabase' ] ],
1622  [ 'name' => 'tables', 'callback' => [ $installer, 'createTables' ] ],
1623  [ 'name' => 'tables-manual', 'callback' => [ $installer, 'createManualTables' ] ],
1624  [ 'name' => 'interwiki', 'callback' => [ $installer, 'populateInterwikiTable' ] ],
1625  [ 'name' => 'stats', 'callback' => [ $this, 'populateSiteStats' ] ],
1626  [ 'name' => 'keys', 'callback' => [ $this, 'generateKeys' ] ],
1627  [ 'name' => 'updates', 'callback' => [ $installer, 'insertUpdateKeys' ] ],
1628  [ 'name' => 'restore-services', 'callback' => [ $this, 'restoreServices' ] ],
1629  [ 'name' => 'sysop', 'callback' => [ $this, 'createSysop' ] ],
1630  [ 'name' => 'mainpage', 'callback' => [ $this, 'createMainpage' ] ],
1631  ];
1632 
1633  // Build the array of install steps starting from the core install list,
1634  // then adding any callbacks that wanted to attach after a given step
1635  foreach ( $coreInstallSteps as $step ) {
1636  $this->installSteps[] = $step;
1637  if ( isset( $this->extraInstallSteps[$step['name']] ) ) {
1638  $this->installSteps = array_merge(
1639  $this->installSteps,
1640  $this->extraInstallSteps[$step['name']]
1641  );
1642  }
1643  }
1644 
1645  // Prepend any steps that want to be at the beginning
1646  if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1647  $this->installSteps = array_merge(
1648  $this->extraInstallSteps['BEGINNING'],
1649  $this->installSteps
1650  );
1651  }
1652 
1653  // Extensions should always go first, chance to tie into hooks and such
1654  if ( count( $this->getVar( '_Extensions' ) ) ) {
1655  array_unshift( $this->installSteps,
1656  [ 'name' => 'extensions', 'callback' => [ $this, 'includeExtensions' ] ]
1657  );
1658  $this->installSteps[] = [
1659  'name' => 'extension-tables',
1660  'callback' => [ $installer, 'createExtensionTables' ]
1661  ];
1662  }
1663 
1664  return $this->installSteps;
1665  }
1666 
1675  public function performInstallation( $startCB, $endCB ) {
1676  $installResults = [];
1677  $installer = $this->getDBInstaller();
1678  $installer->preInstall();
1679  $steps = $this->getInstallSteps( $installer );
1680  foreach ( $steps as $stepObj ) {
1681  $name = $stepObj['name'];
1682  call_user_func_array( $startCB, [ $name ] );
1683 
1684  // Perform the callback step
1685  $status = call_user_func( $stepObj['callback'], $installer );
1686 
1687  // Output and save the results
1688  call_user_func( $endCB, $name, $status );
1689  $installResults[$name] = $status;
1690 
1691  // If we've hit some sort of fatal, we need to bail.
1692  // Callback already had a chance to do output above.
1693  if ( !$status->isOK() ) {
1694  break;
1695  }
1696  }
1697  // @phan-suppress-next-next-line PhanPossiblyUndeclaredVariable
1698  // $steps has at least one element and that defines $status
1699  if ( $status->isOK() ) {
1700  $this->showMessage(
1701  'config-install-db-success'
1702  );
1703  $this->setVar( '_InstallDone', true );
1704  }
1705 
1706  return $installResults;
1707  }
1708 
1714  public function generateKeys() {
1715  $keys = [ 'wgSecretKey' => 64 ];
1716  if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1717  $keys['wgUpgradeKey'] = 16;
1718  }
1719 
1720  return $this->doGenerateKeys( $keys );
1721  }
1722 
1727  public function restoreServices() {
1728  $this->resetMediaWikiServices( null, [
1729  'UserOptionsLookup' => static function ( MediaWikiServices $services ) {
1730  return $services->get( 'UserOptionsManager' );
1731  }
1732  ] );
1733  return Status::newGood();
1734  }
1735 
1742  protected function doGenerateKeys( $keys ) {
1743  foreach ( $keys as $name => $length ) {
1744  $secretKey = MWCryptRand::generateHex( $length );
1745  $this->setVar( $name, $secretKey );
1746  }
1747  return Status::newGood();
1748  }
1749 
1755  protected function createSysop() {
1756  $name = $this->getVar( '_AdminName' );
1757  $user = User::newFromName( $name );
1758 
1759  if ( !$user ) {
1760  // We should've validated this earlier anyway!
1761  return Status::newFatal( 'config-admin-error-user', $name );
1762  }
1763 
1764  if ( $user->idForName() == 0 ) {
1765  $user->addToDatabase();
1766 
1767  $password = $this->getVar( '_AdminPassword' );
1768  $status = $user->changeAuthenticationData( [
1769  'username' => $user->getName(),
1770  'password' => $password,
1771  'retype' => $password,
1772  ] );
1773  if ( !$status->isGood() ) {
1774  return Status::newFatal( 'config-admin-error-password',
1775  $name, $status->getWikiText( false, false, $this->getVar( '_UserLang' ) ) );
1776  }
1777 
1778  $userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager();
1779  $userGroupManager->addUserToGroup( $user, 'sysop' );
1780  $userGroupManager->addUserToGroup( $user, 'bureaucrat' );
1781  $userGroupManager->addUserToGroup( $user, 'interface-admin' );
1782  if ( $this->getVar( '_AdminEmail' ) ) {
1783  $user->setEmail( $this->getVar( '_AdminEmail' ) );
1784  }
1785  $user->saveSettings();
1786 
1787  // Update user count
1788  $ssUpdate = SiteStatsUpdate::factory( [ 'users' => 1 ] );
1789  $ssUpdate->doUpdate();
1790  }
1791 
1792  if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1793  return $this->subscribeToMediaWikiAnnounce();
1794  }
1795  return Status::newGood();
1796  }
1797 
1801  private function subscribeToMediaWikiAnnounce() {
1802  $status = Status::newGood();
1803  $http = MediaWikiServices::getInstance()->getHttpRequestFactory();
1804  if ( !$http->canMakeRequests() ) {
1805  $status->warning( 'config-install-subscribe-fail',
1806  wfMessage( 'config-install-subscribe-notpossible' ) );
1807  return $status;
1808  }
1809 
1810  // Create subscription request
1811  $params = [ 'email' => $this->getVar( '_AdminEmail' ) ];
1812  $req = $http->create( self::MEDIAWIKI_ANNOUNCE_URL . 'anonymous_subscribe',
1813  [ 'method' => 'POST', 'postData' => $params ], __METHOD__ );
1814 
1815  // Add headers needed to pass Django's CSRF checks
1816  $token = str_repeat( 'a', 64 );
1817  $req->setHeader( 'Referer', self::MEDIAWIKI_ANNOUNCE_URL );
1818  $req->setHeader( 'Cookie', "csrftoken=$token" );
1819  $req->setHeader( 'X-CSRFToken', $token );
1820 
1821  // Send subscription request
1822  $reqStatus = $req->execute();
1823  if ( !$reqStatus->isOK() ) {
1824  $status->warning( 'config-install-subscribe-fail',
1825  Status::wrap( $reqStatus )->getMessage() );
1826  return $status;
1827  }
1828 
1829  // Was the request submitted successfully?
1830  // The status message is displayed after a redirect, using Django's messages
1831  // framework, so load the list summary page and look for the expected text.
1832  // (Though parsing the cookie set by the framework may be possible, it isn't
1833  // simple, since the format of the cookie has changed between versions.)
1834  $checkReq = $http->create( self::MEDIAWIKI_ANNOUNCE_URL, [], __METHOD__ );
1835  $checkReq->setCookieJar( $req->getCookieJar() );
1836  if ( !$checkReq->execute()->isOK() ) {
1837  $status->warning( 'config-install-subscribe-possiblefail' );
1838  return $status;
1839  }
1840  $html = $checkReq->getContent();
1841  if ( strpos( $html, 'Please check your inbox for further instructions' ) !== false ) {
1842  // Success
1843  } elseif ( strpos( $html, 'Member already subscribed' ) !== false ) {
1844  $status->warning( 'config-install-subscribe-alreadysubscribed' );
1845  } elseif ( strpos( $html, 'Subscription request already pending' ) !== false ) {
1846  $status->warning( 'config-install-subscribe-alreadypending' );
1847  } else {
1848  $status->warning( 'config-install-subscribe-possiblefail' );
1849  }
1850  return $status;
1851  }
1852 
1859  protected function createMainpage( DatabaseInstaller $installer ) {
1860  $status = Status::newGood();
1862  if ( $title->exists() ) {
1863  $status->warning( 'config-install-mainpage-exists' );
1864  return $status;
1865  }
1866  try {
1867  $page = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
1868  $content = new WikitextContent(
1869  wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
1870  wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
1871  );
1872 
1873  $status = $page->doUserEditContent(
1874  $content,
1875  User::newSystemUser( 'MediaWiki default' ),
1876  '',
1877  EDIT_NEW
1878  );
1879  } catch ( Exception $e ) {
1880  // using raw, because $wgShowExceptionDetails can not be set yet
1881  $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1882  }
1883 
1884  return $status;
1885  }
1886 
1892  public static function overrideConfig( SettingsBuilder $settings ) {
1893  // Use PHP's built-in session handling, since MediaWiki's
1894  // SessionHandler can't work before we have an object cache set up.
1895  if ( !defined( 'MW_NO_SESSION_HANDLER' ) ) {
1896  define( 'MW_NO_SESSION_HANDLER', 1 );
1897  }
1898 
1899  $settings->overrideConfigValues( [
1900 
1901  // Don't access the database
1902  MainConfigNames::UseDatabaseMessages => false,
1903 
1904  // Don't cache langconv tables
1905  MainConfigNames::LanguageConverterCacheType => CACHE_NONE,
1906 
1907  // Don't try to cache ResourceLoader dependencies in the database
1908  MainConfigNames::ResourceLoaderUseObjectCacheForDeps => true,
1909 
1910  // Debug-friendly
1911  MainConfigNames::ShowExceptionDetails => true,
1912  MainConfigNames::ShowHostnames => true,
1913 
1914  // Don't break forms
1915  MainConfigNames::ExternalLinkTarget => '_blank',
1916 
1917  // Allow multiple ob_flush() calls
1918  MainConfigNames::DisableOutputCompression => true,
1919 
1920  // Use a sensible cookie prefix (not my_wiki)
1921  MainConfigNames::CookiePrefix => 'mw_installer',
1922 
1923  // Some of the environment checks make shell requests, remove limits
1924  MainConfigNames::MaxShellMemory => 0,
1925 
1926  // Override the default CookieSessionProvider with a dummy
1927  // implementation that won't stomp on PHP's cookies.
1928  MainConfigNames::SessionProviders => [
1929  [
1930  'class' => InstallerSessionProvider::class,
1931  'args' => [ [
1932  'priority' => 1,
1933  ] ]
1934  ]
1935  ],
1936 
1937  // Don't use the DB as the main stash
1938  MainConfigNames::MainStash => CACHE_NONE,
1939 
1940  // Don't try to use any object cache for SessionManager either.
1941  MainConfigNames::SessionCacheType => CACHE_NONE,
1942 
1943  // Set a dummy $wgServer to bypass the check in Setup.php, the
1944  // web installer will automatically detect it and not use this value.
1945  MainConfigNames::Server => 'https://🌻.invalid',
1946  ] );
1947  }
1948 
1956  public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1957  $this->extraInstallSteps[$findStep][] = $callback;
1958  }
1959 
1964  protected function disableTimeLimit() {
1965  AtEase::suppressWarnings();
1966  set_time_limit( 0 );
1967  AtEase::restoreWarnings();
1968  }
1969 }
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:493
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:58
envPrepServer()
Environment prep for the server hostname.
Definition: Installer.php:1133
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:84
getExtensionInfo( $type, $parentRelPath, $name)
Definition: Installer.php:1321
Title $parserTitle
Cached Title, used by parse().
Definition: Installer.php:105
includeExtensions()
Installs the auto-detected extensions.
Definition: Installer.php:1472
createMainpage(DatabaseInstaller $installer)
Insert Main Page with default content.
Definition: Installer.php:1859
const MINIMUM_PCRE_VERSION
The oldest version of PCRE we can support.
Definition: Installer.php:66
getDefaultSkin(array $skinNames)
Returns a default value to be used for $wgDefaultSkin: normally the DefaultSkin from config-schema....
Definition: Installer.php:1458
envCheckLibicu()
Check the libicu version.
Definition: Installer.php:1111
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:850
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:972
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:1054
disableTimeLimit()
Disable the time limit for execution.
Definition: Installer.php:1964
static apacheModulePresent( $moduleName)
Checks for presence of an Apache module.
Definition: Installer.php:1218
array $rightsProfiles
User rights profiles.
Definition: Installer.php:277
addInstallStep( $callback, $findStep='BEGINNING')
Add an installation step following the given step.
Definition: Installer.php:1956
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:112
dirIsExecutable( $dir, $url)
Checks if scripts located in the given directory can be executed via the given URL.
Definition: Installer.php:1163
doEnvironmentPreps()
Definition: Installer.php:578
envCheckGit()
Search for git.
Definition: Installer.php:1032
getDocUrl( $page)
Overridden by WebInstaller to provide lastPage parameters.
Definition: Installer.php:1245
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:77
restoreServices()
Restore services that have been redefined in the early stage of installation.
Definition: Installer.php:1727
includeExtensionFiles( $files)
Include the specified extension PHP files.
Definition: Installer.php:1528
getAutoExtensionData()
Auto-detect extensions with an extension.json file.
Definition: Installer.php:1571
envCheckMemory()
Environment check for available memory.
Definition: Installer.php:927
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:1675
getAutoExtensionLegacyHooks()
Auto-detect extensions with an old style .php registration file, load the extensions,...
Definition: Installer.php:1504
envCheck64Bit()
Checks if we're running on 64 bit or not.
Definition: Installer.php:1100
generateKeys()
Generate $wgSecretKey.
Definition: Installer.php:1714
populateSiteStats(DatabaseInstaller $installer)
Install step which adds a row to the site_stats table with appropriate initial values.
Definition: Installer.php:822
envCheckCache()
Environment check for compiled object cache types.
Definition: Installer.php:953
__construct()
Constructor, always call this from child classes.
Definition: Installer.php:412
int $minMemorySize
Minimum memory size in MiB.
Definition: Installer.php:98
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:1278
doGenerateKeys( $keys)
Generate a secret value for variables using a secure generator.
Definition: Installer.php:1742
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.
Definition: Installer.php:1067
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:1892
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:1149
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:1755
envCheckPCRE()
Environment check for the PCRE module.
Definition: Installer.php:896
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:807
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:123
getAutoExtensionHookContainer()
Get the hook container previously populated by includeExtensions().
Definition: Installer.php:1598
envCheckDiff3()
Search for GNU diff3.
Definition: Installer.php:985
envGetDefaultServer()
Helper function to be called from envPrepServer()
getInstallSteps(DatabaseInstaller $installer)
Get an array of install steps.
Definition: Installer.php:1619
array $dbInstallers
Cached DB installer instances, access using getDBInstaller().
Definition: Installer.php:91
array $envChecks
A list of environment check methods called by doEnvironmentChecks().
Definition: Installer.php:140
envCheckGraphics()
Environment check for ImageMagick and GD.
Definition: Installer.php:1008
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:1080
setParserLanguage( $lang)
ParserOptions are constructed before we determined the language, so fix it.
Definition: Installer.php:1235
findExtensions( $directory='extensions')
Find extensions or skins in a subdirectory of $IP.
Definition: Installer.php:1258
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.
Stub object for the global user ($wgUser) that makes it possible to change the relevant underlying ob...
Provides a fallback sequence for Config objects.
Definition: MultiConfig.php:28
Set options of the Parser.
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6468
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:73
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:85
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:45
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:63
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:703
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:373
static newFromName( $name, $validate='valid')
Definition: User.php:587
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:628
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:795
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