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