PHPSessionHandler: Implement SessionHandlerInterface
[mediawiki.git] / includes / installer / Installer.php
blob40e51f0b4128d6b6e95b2aa2e9c394d07d3ca2bc
1 <?php
2 /**
3 * Base code for MediaWiki installer.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
20 * @file
21 * @ingroup Deployment
24 /**
25 * This documentation group collects source code files with deployment functionality.
27 * @defgroup Deployment Deployment
30 /**
31 * Base installer class.
33 * This class provides the base for installation and update functionality
34 * for both MediaWiki core and extensions.
36 * @ingroup Deployment
37 * @since 1.17
39 abstract class Installer {
41 /**
42 * The oldest version of PCRE we can support.
44 * Defining this is necessary because PHP may be linked with a system version
45 * of PCRE, which may be older than that bundled with the minimum PHP version.
47 const MINIMUM_PCRE_VERSION = '7.2';
49 /**
50 * @var array
52 protected $settings;
54 /**
55 * List of detected DBs, access using getCompiledDBs().
57 * @var array
59 protected $compiledDBs;
61 /**
62 * Cached DB installer instances, access using getDBInstaller().
64 * @var array
66 protected $dbInstallers = array();
68 /**
69 * Minimum memory size in MB.
71 * @var int
73 protected $minMemorySize = 50;
75 /**
76 * Cached Title, used by parse().
78 * @var Title
80 protected $parserTitle;
82 /**
83 * Cached ParserOptions, used by parse().
85 * @var ParserOptions
87 protected $parserOptions;
89 /**
90 * Known database types. These correspond to the class names <type>Installer,
91 * and are also MediaWiki database types valid for $wgDBtype.
93 * To add a new type, create a <type>Installer class and a Database<type>
94 * class, and add a config-type-<type> message to MessagesEn.php.
96 * @var array
98 protected static $dbTypes = array(
99 'mysql',
100 'postgres',
101 'oracle',
102 'mssql',
103 'sqlite',
107 * A list of environment check methods called by doEnvironmentChecks().
108 * These may output warnings using showMessage(), and/or abort the
109 * installation process by returning false.
111 * For the WebInstaller these are only called on the Welcome page,
112 * if these methods have side-effects that should affect later page loads
113 * (as well as the generated stylesheet), use envPreps instead.
115 * @var array
117 protected $envChecks = array(
118 'envCheckDB',
119 'envCheckRegisterGlobals',
120 'envCheckBrokenXML',
121 'envCheckMagicQuotes',
122 'envCheckMbstring',
123 'envCheckSafeMode',
124 'envCheckXML',
125 'envCheckPCRE',
126 'envCheckMemory',
127 'envCheckCache',
128 'envCheckModSecurity',
129 'envCheckDiff3',
130 'envCheckGraphics',
131 'envCheckGit',
132 'envCheckServer',
133 'envCheckPath',
134 'envCheckShellLocale',
135 'envCheckUploadsDirectory',
136 'envCheckLibicu',
137 'envCheckSuhosinMaxValueLength',
138 'envCheckCtype',
139 'envCheckIconv',
140 'envCheckJSON',
144 * A list of environment preparation methods called by doEnvironmentPreps().
146 * @var array
148 protected $envPreps = array(
149 'envPrepServer',
150 'envPrepPath',
154 * MediaWiki configuration globals that will eventually be passed through
155 * to LocalSettings.php. The names only are given here, the defaults
156 * typically come from DefaultSettings.php.
158 * @var array
160 protected $defaultVarNames = array(
161 'wgSitename',
162 'wgPasswordSender',
163 'wgLanguageCode',
164 'wgRightsIcon',
165 'wgRightsText',
166 'wgRightsUrl',
167 'wgEnableEmail',
168 'wgEnableUserEmail',
169 'wgEnotifUserTalk',
170 'wgEnotifWatchlist',
171 'wgEmailAuthentication',
172 'wgDBname',
173 'wgDBtype',
174 'wgDiff3',
175 'wgImageMagickConvertCommand',
176 'wgGitBin',
177 'IP',
178 'wgScriptPath',
179 'wgMetaNamespace',
180 'wgDeletedDirectory',
181 'wgEnableUploads',
182 'wgShellLocale',
183 'wgSecretKey',
184 'wgUseInstantCommons',
185 'wgUpgradeKey',
186 'wgDefaultSkin',
190 * Variables that are stored alongside globals, and are used for any
191 * configuration of the installation process aside from the MediaWiki
192 * configuration. Map of names to defaults.
194 * @var array
196 protected $internalDefaults = array(
197 '_UserLang' => 'en',
198 '_Environment' => false,
199 '_SafeMode' => false,
200 '_RaiseMemory' => false,
201 '_UpgradeDone' => false,
202 '_InstallDone' => false,
203 '_Caches' => array(),
204 '_InstallPassword' => '',
205 '_SameAccount' => true,
206 '_CreateDBAccount' => false,
207 '_NamespaceType' => 'site-name',
208 '_AdminName' => '', // will be set later, when the user selects language
209 '_AdminPassword' => '',
210 '_AdminPasswordConfirm' => '',
211 '_AdminEmail' => '',
212 '_Subscribe' => false,
213 '_SkipOptional' => 'continue',
214 '_RightsProfile' => 'wiki',
215 '_LicenseCode' => 'none',
216 '_CCDone' => false,
217 '_Extensions' => array(),
218 '_Skins' => array(),
219 '_MemCachedServers' => '',
220 '_UpgradeKeySupplied' => false,
221 '_ExistingDBSettings' => false,
223 // $wgLogo is probably wrong (bug 48084); set something that will work.
224 // Single quotes work fine here, as LocalSettingsGenerator outputs this unescaped.
225 'wgLogo' => '$wgResourceBasePath/resources/assets/wiki.png',
226 'wgAuthenticationTokenVersion' => 1,
230 * The actual list of installation steps. This will be initialized by getInstallSteps()
232 * @var array
234 private $installSteps = array();
237 * Extra steps for installation, for things like DatabaseInstallers to modify
239 * @var array
241 protected $extraInstallSteps = array();
244 * Known object cache types and the functions used to test for their existence.
246 * @var array
248 protected $objectCaches = array(
249 'xcache' => 'xcache_get',
250 'apc' => 'apc_fetch',
251 'wincache' => 'wincache_ucache_get'
255 * User rights profiles.
257 * @var array
259 public $rightsProfiles = array(
260 'wiki' => array(),
261 'no-anon' => array(
262 '*' => array( 'edit' => false )
264 'fishbowl' => array(
265 '*' => array(
266 'createaccount' => false,
267 'edit' => false,
270 'private' => array(
271 '*' => array(
272 'createaccount' => false,
273 'edit' => false,
274 'read' => false,
280 * License types.
282 * @var array
284 public $licenses = array(
285 'cc-by' => array(
286 'url' => 'https://creativecommons.org/licenses/by/4.0/',
287 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by.png',
289 'cc-by-sa' => array(
290 'url' => 'https://creativecommons.org/licenses/by-sa/4.0/',
291 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-sa.png',
293 'cc-by-nc-sa' => array(
294 'url' => 'https://creativecommons.org/licenses/by-nc-sa/4.0/',
295 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-nc-sa.png',
297 'cc-0' => array(
298 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
299 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-0.png',
301 'pd' => array(
302 'url' => '',
303 'icon' => '$wgResourceBasePath/resources/assets/licenses/public-domain.png',
305 'gfdl' => array(
306 'url' => 'https://www.gnu.org/copyleft/fdl.html',
307 'icon' => '$wgResourceBasePath/resources/assets/licenses/gnu-fdl.png',
309 'none' => array(
310 'url' => '',
311 'icon' => '',
312 'text' => ''
314 'cc-choose' => array(
315 // Details will be filled in by the selector.
316 'url' => '',
317 'icon' => '',
318 'text' => '',
323 * URL to mediawiki-announce subscription
325 protected $mediaWikiAnnounceUrl =
326 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce';
329 * Supported language codes for Mailman
331 protected $mediaWikiAnnounceLanguages = array(
332 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu',
333 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru',
334 'sl', 'sr', 'sv', 'tr', 'uk'
338 * UI interface for displaying a short message
339 * The parameters are like parameters to wfMessage().
340 * The messages will be in wikitext format, which will be converted to an
341 * output format such as HTML or text before being sent to the user.
342 * @param string $msg
344 abstract public function showMessage( $msg /*, ... */ );
347 * Same as showMessage(), but for displaying errors
348 * @param string $msg
350 abstract public function showError( $msg /*, ... */ );
353 * Show a message to the installing user by using a Status object
354 * @param Status $status
356 abstract public function showStatusMessage( Status $status );
359 * Constructor, always call this from child classes.
361 public function __construct() {
362 global $wgMessagesDirs, $wgUser;
364 // Don't attempt to load user language options (T126177)
365 // This will be overridden in the web installer with the user-specified language
366 RequestContext::getMain()->setLanguage( 'en' );
368 // Disable the i18n cache
369 Language::getLocalisationCache()->disableBackend();
370 // Disable LoadBalancer and wfGetDB etc.
371 LBFactory::disableBackend();
373 // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
374 // SqlBagOStuff will then throw since we just disabled wfGetDB)
375 $GLOBALS['wgMemc'] = new EmptyBagOStuff;
376 ObjectCache::clear();
377 $emptyCache = array( 'class' => 'EmptyBagOStuff' );
378 // disable (problematic) object cache types explicitly, preserving all other (working) ones
379 // bug T113843
380 $GLOBALS['wgObjectCaches'] = array(
381 CACHE_NONE => $emptyCache,
382 CACHE_DB => $emptyCache,
383 CACHE_ANYTHING => $emptyCache,
384 CACHE_MEMCACHED => $emptyCache,
385 ) + $GLOBALS['wgObjectCaches'];
387 // Load the installer's i18n.
388 $wgMessagesDirs['MediawikiInstaller'] = __DIR__ . '/i18n';
390 // Having a user with id = 0 safeguards us from DB access via User::loadOptions().
391 $wgUser = User::newFromId( 0 );
392 RequestContext::getMain()->setUser( $wgUser );
394 $this->settings = $this->internalDefaults;
396 foreach ( $this->defaultVarNames as $var ) {
397 $this->settings[$var] = $GLOBALS[$var];
400 $this->doEnvironmentPreps();
402 $this->compiledDBs = array();
403 foreach ( self::getDBTypes() as $type ) {
404 $installer = $this->getDBInstaller( $type );
406 if ( !$installer->isCompiled() ) {
407 continue;
409 $this->compiledDBs[] = $type;
412 $this->parserTitle = Title::newFromText( 'Installer' );
413 $this->parserOptions = new ParserOptions( $wgUser ); // language will be wrong :(
414 $this->parserOptions->setEditSection( false );
418 * Get a list of known DB types.
420 * @return array
422 public static function getDBTypes() {
423 return self::$dbTypes;
427 * Do initial checks of the PHP environment. Set variables according to
428 * the observed environment.
430 * It's possible that this may be called under the CLI SAPI, not the SAPI
431 * that the wiki will primarily run under. In that case, the subclass should
432 * initialise variables such as wgScriptPath, before calling this function.
434 * Under the web subclass, it can already be assumed that PHP 5+ is in use
435 * and that sessions are working.
437 * @return Status
439 public function doEnvironmentChecks() {
440 // Php version has already been checked by entry scripts
441 // Show message here for information purposes
442 if ( wfIsHHVM() ) {
443 $this->showMessage( 'config-env-hhvm', HHVM_VERSION );
444 } else {
445 $this->showMessage( 'config-env-php', PHP_VERSION );
448 $good = true;
449 // Must go here because an old version of PCRE can prevent other checks from completing
450 list( $pcreVersion ) = explode( ' ', PCRE_VERSION, 2 );
451 if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) {
452 $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion );
453 $good = false;
454 } else {
455 foreach ( $this->envChecks as $check ) {
456 $status = $this->$check();
457 if ( $status === false ) {
458 $good = false;
463 $this->setVar( '_Environment', $good );
465 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
468 public function doEnvironmentPreps() {
469 foreach ( $this->envPreps as $prep ) {
470 $this->$prep();
475 * Set a MW configuration variable, or internal installer configuration variable.
477 * @param string $name
478 * @param mixed $value
480 public function setVar( $name, $value ) {
481 $this->settings[$name] = $value;
485 * Get an MW configuration variable, or internal installer configuration variable.
486 * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
487 * Installer variables are typically prefixed by an underscore.
489 * @param string $name
490 * @param mixed $default
492 * @return mixed
494 public function getVar( $name, $default = null ) {
495 if ( !isset( $this->settings[$name] ) ) {
496 return $default;
497 } else {
498 return $this->settings[$name];
503 * Get a list of DBs supported by current PHP setup
505 * @return array
507 public function getCompiledDBs() {
508 return $this->compiledDBs;
512 * Get an instance of DatabaseInstaller for the specified DB type.
514 * @param mixed $type DB installer for which is needed, false to use default.
516 * @return DatabaseInstaller
518 public function getDBInstaller( $type = false ) {
519 if ( !$type ) {
520 $type = $this->getVar( 'wgDBtype' );
523 $type = strtolower( $type );
525 if ( !isset( $this->dbInstallers[$type] ) ) {
526 $class = ucfirst( $type ) . 'Installer';
527 $this->dbInstallers[$type] = new $class( $this );
530 return $this->dbInstallers[$type];
534 * Determine if LocalSettings.php exists. If it does, return its variables.
536 * @return array
538 public static function getExistingLocalSettings() {
539 global $IP;
541 // You might be wondering why this is here. Well if you don't do this
542 // then some poorly-formed extensions try to call their own classes
543 // after immediately registering them. We really need to get extension
544 // registration out of the global scope and into a real format.
545 // @see https://phabricator.wikimedia.org/T69440
546 global $wgAutoloadClasses;
547 $wgAutoloadClasses = array();
549 // @codingStandardsIgnoreStart
550 // LocalSettings.php should not call functions, except wfLoadSkin/wfLoadExtensions
551 // Define the required globals here, to ensure, the functions can do it work correctly.
552 global $wgExtensionDirectory, $wgStyleDirectory;
553 // @codingStandardsIgnoreEnd
555 MediaWiki\suppressWarnings();
556 $_lsExists = file_exists( "$IP/LocalSettings.php" );
557 MediaWiki\restoreWarnings();
559 if ( !$_lsExists ) {
560 return false;
562 unset( $_lsExists );
564 require "$IP/includes/DefaultSettings.php";
565 require "$IP/LocalSettings.php";
567 return get_defined_vars();
571 * Get a fake password for sending back to the user in HTML.
572 * This is a security mechanism to avoid compromise of the password in the
573 * event of session ID compromise.
575 * @param string $realPassword
577 * @return string
579 public function getFakePassword( $realPassword ) {
580 return str_repeat( '*', strlen( $realPassword ) );
584 * Set a variable which stores a password, except if the new value is a
585 * fake password in which case leave it as it is.
587 * @param string $name
588 * @param mixed $value
590 public function setPassword( $name, $value ) {
591 if ( !preg_match( '/^\*+$/', $value ) ) {
592 $this->setVar( $name, $value );
597 * On POSIX systems return the primary group of the webserver we're running under.
598 * On other systems just returns null.
600 * This is used to advice the user that he should chgrp his mw-config/data/images directory as the
601 * webserver user before he can install.
603 * Public because SqliteInstaller needs it, and doesn't subclass Installer.
605 * @return mixed
607 public static function maybeGetWebserverPrimaryGroup() {
608 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
609 # I don't know this, this isn't UNIX.
610 return null;
613 # posix_getegid() *not* getmygid() because we want the group of the webserver,
614 # not whoever owns the current script.
615 $gid = posix_getegid();
616 $getpwuid = posix_getpwuid( $gid );
617 $group = $getpwuid['name'];
619 return $group;
623 * Convert wikitext $text to HTML.
625 * This is potentially error prone since many parser features require a complete
626 * installed MW database. The solution is to just not use those features when you
627 * write your messages. This appears to work well enough. Basic formatting and
628 * external links work just fine.
630 * But in case a translator decides to throw in a "#ifexist" or internal link or
631 * whatever, this function is guarded to catch the attempted DB access and to present
632 * some fallback text.
634 * @param string $text
635 * @param bool $lineStart
636 * @return string
638 public function parse( $text, $lineStart = false ) {
639 global $wgParser;
641 try {
642 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
643 $html = $out->getText();
644 } catch ( DBAccessError $e ) {
645 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
647 if ( !empty( $this->debug ) ) {
648 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
652 return $html;
656 * @return ParserOptions
658 public function getParserOptions() {
659 return $this->parserOptions;
662 public function disableLinkPopups() {
663 $this->parserOptions->setExternalLinkTarget( false );
666 public function restoreLinkPopups() {
667 global $wgExternalLinkTarget;
668 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
672 * Install step which adds a row to the site_stats table with appropriate
673 * initial values.
675 * @param DatabaseInstaller $installer
677 * @return Status
679 public function populateSiteStats( DatabaseInstaller $installer ) {
680 $status = $installer->getConnection();
681 if ( !$status->isOK() ) {
682 return $status;
684 $status->value->insert(
685 'site_stats',
686 array(
687 'ss_row_id' => 1,
688 'ss_total_edits' => 0,
689 'ss_good_articles' => 0,
690 'ss_total_pages' => 0,
691 'ss_users' => 0,
692 'ss_images' => 0
694 __METHOD__, 'IGNORE'
697 return Status::newGood();
701 * Environment check for DB types.
702 * @return bool
704 protected function envCheckDB() {
705 global $wgLang;
707 $allNames = array();
709 // Messages: config-type-mysql, config-type-postgres, config-type-oracle,
710 // config-type-sqlite
711 foreach ( self::getDBTypes() as $name ) {
712 $allNames[] = wfMessage( "config-type-$name" )->text();
715 $databases = $this->getCompiledDBs();
717 $databases = array_flip( $databases );
718 foreach ( array_keys( $databases ) as $db ) {
719 $installer = $this->getDBInstaller( $db );
720 $status = $installer->checkPrerequisites();
721 if ( !$status->isGood() ) {
722 $this->showStatusMessage( $status );
724 if ( !$status->isOK() ) {
725 unset( $databases[$db] );
728 $databases = array_flip( $databases );
729 if ( !$databases ) {
730 $this->showError( 'config-no-db', $wgLang->commaList( $allNames ), count( $allNames ) );
732 // @todo FIXME: This only works for the web installer!
733 return false;
736 return true;
740 * Environment check for register_globals.
741 * Prevent installation if enabled
742 * @return bool
744 protected function envCheckRegisterGlobals() {
745 if ( wfIniGetBool( 'register_globals' ) ) {
746 $this->showMessage( 'config-register-globals-error' );
747 return false;
750 return true;
754 * Some versions of libxml+PHP break < and > encoding horribly
755 * @return bool
757 protected function envCheckBrokenXML() {
758 $test = new PhpXmlBugTester();
759 if ( !$test->ok ) {
760 $this->showError( 'config-brokenlibxml' );
762 return false;
765 return true;
769 * Environment check for magic_quotes_(gpc|runtime|sybase).
770 * @return bool
772 protected function envCheckMagicQuotes() {
773 $status = true;
774 foreach ( array( 'gpc', 'runtime', 'sybase' ) as $magicJunk ) {
775 if ( wfIniGetBool( "magic_quotes_$magicJunk" ) ) {
776 $this->showError( "config-magic-quotes-$magicJunk" );
777 $status = false;
781 return $status;
785 * Environment check for mbstring.func_overload.
786 * @return bool
788 protected function envCheckMbstring() {
789 if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
790 $this->showError( 'config-mbstring' );
792 return false;
795 return true;
799 * Environment check for safe_mode.
800 * @return bool
802 protected function envCheckSafeMode() {
803 if ( wfIniGetBool( 'safe_mode' ) ) {
804 $this->setVar( '_SafeMode', true );
805 $this->showMessage( 'config-safe-mode' );
808 return true;
812 * Environment check for the XML module.
813 * @return bool
815 protected function envCheckXML() {
816 if ( !function_exists( "utf8_encode" ) ) {
817 $this->showError( 'config-xml-bad' );
819 return false;
822 return true;
826 * Environment check for the PCRE module.
828 * @note If this check were to fail, the parser would
829 * probably throw an exception before the result
830 * of this check is shown to the user.
831 * @return bool
833 protected function envCheckPCRE() {
834 MediaWiki\suppressWarnings();
835 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
836 // Need to check for \p support too, as PCRE can be compiled
837 // with utf8 support, but not unicode property support.
838 // check that \p{Zs} (space separators) matches
839 // U+3000 (Ideographic space)
840 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" );
841 MediaWiki\restoreWarnings();
842 if ( $regexd != '--' || $regexprop != '--' ) {
843 $this->showError( 'config-pcre-no-utf8' );
845 return false;
848 return true;
852 * Environment check for available memory.
853 * @return bool
855 protected function envCheckMemory() {
856 $limit = ini_get( 'memory_limit' );
858 if ( !$limit || $limit == -1 ) {
859 return true;
862 $n = wfShorthandToInteger( $limit );
864 if ( $n < $this->minMemorySize * 1024 * 1024 ) {
865 $newLimit = "{$this->minMemorySize}M";
867 if ( ini_set( "memory_limit", $newLimit ) === false ) {
868 $this->showMessage( 'config-memory-bad', $limit );
869 } else {
870 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
871 $this->setVar( '_RaiseMemory', true );
875 return true;
879 * Environment check for compiled object cache types.
881 protected function envCheckCache() {
882 $caches = array();
883 foreach ( $this->objectCaches as $name => $function ) {
884 if ( function_exists( $function ) ) {
885 if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) {
886 continue;
888 $caches[$name] = true;
892 if ( !$caches ) {
893 $key = 'config-no-cache';
894 // PHP >=5.5 is called APCu, earlier versions use APC (T61998).
895 if ( !wfIsHHVM() && version_compare( PHP_VERSION, '5.5', '>=' ) ) {
896 // config-no-cache-apcu
897 $key .= '-apcu';
899 $this->showMessage( $key );
902 $this->setVar( '_Caches', $caches );
906 * Scare user to death if they have mod_security or mod_security2
907 * @return bool
909 protected function envCheckModSecurity() {
910 if ( self::apacheModulePresent( 'mod_security' )
911 || self::apacheModulePresent( 'mod_security2' ) ) {
912 $this->showMessage( 'config-mod-security' );
915 return true;
919 * Search for GNU diff3.
920 * @return bool
922 protected function envCheckDiff3() {
923 $names = array( "gdiff3", "diff3", "diff3.exe" );
924 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' );
926 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo );
928 if ( $diff3 ) {
929 $this->setVar( 'wgDiff3', $diff3 );
930 } else {
931 $this->setVar( 'wgDiff3', false );
932 $this->showMessage( 'config-diff3-bad' );
935 return true;
939 * Environment check for ImageMagick and GD.
940 * @return bool
942 protected function envCheckGraphics() {
943 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
944 $versionInfo = array( '$1 -version', 'ImageMagick' );
945 $convert = self::locateExecutableInDefaultPaths( $names, $versionInfo );
947 $this->setVar( 'wgImageMagickConvertCommand', '' );
948 if ( $convert ) {
949 $this->setVar( 'wgImageMagickConvertCommand', $convert );
950 $this->showMessage( 'config-imagemagick', $convert );
952 return true;
953 } elseif ( function_exists( 'imagejpeg' ) ) {
954 $this->showMessage( 'config-gd' );
955 } else {
956 $this->showMessage( 'config-no-scaling' );
959 return true;
963 * Search for git.
965 * @since 1.22
966 * @return bool
968 protected function envCheckGit() {
969 $names = array( wfIsWindows() ? 'git.exe' : 'git' );
970 $versionInfo = array( '$1 --version', 'git version' );
972 $git = self::locateExecutableInDefaultPaths( $names, $versionInfo );
974 if ( $git ) {
975 $this->setVar( 'wgGitBin', $git );
976 $this->showMessage( 'config-git', $git );
977 } else {
978 $this->setVar( 'wgGitBin', false );
979 $this->showMessage( 'config-git-bad' );
982 return true;
986 * Environment check to inform user which server we've assumed.
988 * @return bool
990 protected function envCheckServer() {
991 $server = $this->envGetDefaultServer();
992 if ( $server !== null ) {
993 $this->showMessage( 'config-using-server', $server );
995 return true;
999 * Environment check to inform user which paths we've assumed.
1001 * @return bool
1003 protected function envCheckPath() {
1004 $this->showMessage(
1005 'config-using-uri',
1006 $this->getVar( 'wgServer' ),
1007 $this->getVar( 'wgScriptPath' )
1009 return true;
1013 * Environment check for preferred locale in shell
1014 * @return bool
1016 protected function envCheckShellLocale() {
1017 $os = php_uname( 's' );
1018 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
1020 if ( !in_array( $os, $supported ) ) {
1021 return true;
1024 # Get a list of available locales.
1025 $ret = false;
1026 $lines = wfShellExec( '/usr/bin/locale -a', $ret );
1028 if ( $ret ) {
1029 return true;
1032 $lines = array_map( 'trim', explode( "\n", $lines ) );
1033 $candidatesByLocale = array();
1034 $candidatesByLang = array();
1036 foreach ( $lines as $line ) {
1037 if ( $line === '' ) {
1038 continue;
1041 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
1042 continue;
1045 list( , $lang, , , ) = $m;
1047 $candidatesByLocale[$m[0]] = $m;
1048 $candidatesByLang[$lang][] = $m;
1051 # Try the current value of LANG.
1052 if ( isset( $candidatesByLocale[getenv( 'LANG' )] ) ) {
1053 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
1055 return true;
1058 # Try the most common ones.
1059 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
1060 foreach ( $commonLocales as $commonLocale ) {
1061 if ( isset( $candidatesByLocale[$commonLocale] ) ) {
1062 $this->setVar( 'wgShellLocale', $commonLocale );
1064 return true;
1068 # Is there an available locale in the Wiki's language?
1069 $wikiLang = $this->getVar( 'wgLanguageCode' );
1071 if ( isset( $candidatesByLang[$wikiLang] ) ) {
1072 $m = reset( $candidatesByLang[$wikiLang] );
1073 $this->setVar( 'wgShellLocale', $m[0] );
1075 return true;
1078 # Are there any at all?
1079 if ( count( $candidatesByLocale ) ) {
1080 $m = reset( $candidatesByLocale );
1081 $this->setVar( 'wgShellLocale', $m[0] );
1083 return true;
1086 # Give up.
1087 return true;
1091 * Environment check for the permissions of the uploads directory
1092 * @return bool
1094 protected function envCheckUploadsDirectory() {
1095 global $IP;
1097 $dir = $IP . '/images/';
1098 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
1099 $safe = !$this->dirIsExecutable( $dir, $url );
1101 if ( !$safe ) {
1102 $this->showMessage( 'config-uploads-not-safe', $dir );
1105 return true;
1109 * Checks if suhosin.get.max_value_length is set, and if so generate
1110 * a warning because it decreases ResourceLoader performance.
1111 * @return bool
1113 protected function envCheckSuhosinMaxValueLength() {
1114 $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
1115 if ( $maxValueLength > 0 && $maxValueLength < 1024 ) {
1116 // Only warn if the value is below the sane 1024
1117 $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
1120 return true;
1124 * Convert a hex string representing a Unicode code point to that code point.
1125 * @param string $c
1126 * @return string
1128 protected function unicodeChar( $c ) {
1129 $c = hexdec( $c );
1130 if ( $c <= 0x7F ) {
1131 return chr( $c );
1132 } elseif ( $c <= 0x7FF ) {
1133 return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F );
1134 } elseif ( $c <= 0xFFFF ) {
1135 return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F ) .
1136 chr( 0x80 | $c & 0x3F );
1137 } elseif ( $c <= 0x10FFFF ) {
1138 return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F ) .
1139 chr( 0x80 | $c >> 6 & 0x3F ) .
1140 chr( 0x80 | $c & 0x3F );
1141 } else {
1142 return false;
1147 * Check the libicu version
1149 protected function envCheckLibicu() {
1151 * This needs to be updated something that the latest libicu
1152 * will properly normalize. This normalization was found at
1153 * http://www.unicode.org/versions/Unicode5.2.0/#Character_Additions
1154 * Note that we use the hex representation to create the code
1155 * points in order to avoid any Unicode-destroying during transit.
1157 $not_normal_c = $this->unicodeChar( "FA6C" );
1158 $normal_c = $this->unicodeChar( "242EE" );
1160 $useNormalizer = 'php';
1161 $needsUpdate = false;
1163 if ( function_exists( 'normalizer_normalize' ) ) {
1164 $useNormalizer = 'intl';
1165 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
1166 if ( $intl !== $normal_c ) {
1167 $needsUpdate = true;
1171 // Uses messages 'config-unicode-using-php' and 'config-unicode-using-intl'
1172 if ( $useNormalizer === 'php' ) {
1173 $this->showMessage( 'config-unicode-pure-php-warning' );
1174 } else {
1175 $this->showMessage( 'config-unicode-using-' . $useNormalizer );
1176 if ( $needsUpdate ) {
1177 $this->showMessage( 'config-unicode-update-warning' );
1183 * @return bool
1185 protected function envCheckCtype() {
1186 if ( !function_exists( 'ctype_digit' ) ) {
1187 $this->showError( 'config-ctype' );
1189 return false;
1192 return true;
1196 * @return bool
1198 protected function envCheckIconv() {
1199 if ( !function_exists( 'iconv' ) ) {
1200 $this->showError( 'config-iconv' );
1202 return false;
1205 return true;
1209 * @return bool
1211 protected function envCheckJSON() {
1212 if ( !function_exists( 'json_decode' ) ) {
1213 $this->showError( 'config-json' );
1215 return false;
1218 return true;
1222 * Environment prep for the server hostname.
1224 protected function envPrepServer() {
1225 $server = $this->envGetDefaultServer();
1226 if ( $server !== null ) {
1227 $this->setVar( 'wgServer', $server );
1232 * Helper function to be called from envPrepServer()
1233 * @return string
1235 abstract protected function envGetDefaultServer();
1238 * Environment prep for setting $IP and $wgScriptPath.
1240 protected function envPrepPath() {
1241 global $IP;
1242 $IP = dirname( dirname( __DIR__ ) );
1243 $this->setVar( 'IP', $IP );
1247 * Get an array of likely places we can find executables. Check a bunch
1248 * of known Unix-like defaults, as well as the PATH environment variable
1249 * (which should maybe make it work for Windows?)
1251 * @return array
1253 protected static function getPossibleBinPaths() {
1254 return array_merge(
1255 array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
1256 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
1257 explode( PATH_SEPARATOR, getenv( 'PATH' ) )
1262 * Search a path for any of the given executable names. Returns the
1263 * executable name if found. Also checks the version string returned
1264 * by each executable.
1266 * Used only by environment checks.
1268 * @param string $path Path to search
1269 * @param array $names Array of executable names
1270 * @param array|bool $versionInfo False or array with two members:
1271 * 0 => Command to run for version check, with $1 for the full executable name
1272 * 1 => String to compare the output with
1274 * If $versionInfo is not false, only executables with a version
1275 * matching $versionInfo[1] will be returned.
1276 * @return bool|string
1278 public static function locateExecutable( $path, $names, $versionInfo = false ) {
1279 if ( !is_array( $names ) ) {
1280 $names = array( $names );
1283 foreach ( $names as $name ) {
1284 $command = $path . DIRECTORY_SEPARATOR . $name;
1286 MediaWiki\suppressWarnings();
1287 $file_exists = file_exists( $command );
1288 MediaWiki\restoreWarnings();
1290 if ( $file_exists ) {
1291 if ( !$versionInfo ) {
1292 return $command;
1295 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] );
1296 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) {
1297 return $command;
1302 return false;
1306 * Same as locateExecutable(), but checks in getPossibleBinPaths() by default
1307 * @see locateExecutable()
1308 * @param array $names Array of possible names.
1309 * @param array|bool $versionInfo Default: false or array with two members:
1310 * 0 => Command to run for version check, with $1 for the full executable name
1311 * 1 => String to compare the output with
1313 * If $versionInfo is not false, only executables with a version
1314 * matching $versionInfo[1] will be returned.
1315 * @return bool|string
1317 public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
1318 foreach ( self::getPossibleBinPaths() as $path ) {
1319 $exe = self::locateExecutable( $path, $names, $versionInfo );
1320 if ( $exe !== false ) {
1321 return $exe;
1325 return false;
1329 * Checks if scripts located in the given directory can be executed via the given URL.
1331 * Used only by environment checks.
1332 * @param string $dir
1333 * @param string $url
1334 * @return bool|int|string
1336 public function dirIsExecutable( $dir, $url ) {
1337 $scriptTypes = array(
1338 'php' => array(
1339 "<?php echo 'ex' . 'ec';",
1340 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
1344 // it would be good to check other popular languages here, but it'll be slow.
1346 MediaWiki\suppressWarnings();
1348 foreach ( $scriptTypes as $ext => $contents ) {
1349 foreach ( $contents as $source ) {
1350 $file = 'exectest.' . $ext;
1352 if ( !file_put_contents( $dir . $file, $source ) ) {
1353 break;
1356 try {
1357 $text = Http::get( $url . $file, array( 'timeout' => 3 ), __METHOD__ );
1358 } catch ( Exception $e ) {
1359 // Http::get throws with allow_url_fopen = false and no curl extension.
1360 $text = null;
1362 unlink( $dir . $file );
1364 if ( $text == 'exec' ) {
1365 MediaWiki\restoreWarnings();
1367 return $ext;
1372 MediaWiki\restoreWarnings();
1374 return false;
1378 * Checks for presence of an Apache module. Works only if PHP is running as an Apache module, too.
1380 * @param string $moduleName Name of module to check.
1381 * @return bool
1383 public static function apacheModulePresent( $moduleName ) {
1384 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1385 return true;
1387 // try it the hard way
1388 ob_start();
1389 phpinfo( INFO_MODULES );
1390 $info = ob_get_clean();
1392 return strpos( $info, $moduleName ) !== false;
1396 * ParserOptions are constructed before we determined the language, so fix it
1398 * @param Language $lang
1400 public function setParserLanguage( $lang ) {
1401 $this->parserOptions->setTargetLanguage( $lang );
1402 $this->parserOptions->setUserLang( $lang );
1406 * Overridden by WebInstaller to provide lastPage parameters.
1407 * @param string $page
1408 * @return string
1410 protected function getDocUrl( $page ) {
1411 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1415 * Finds extensions that follow the format /$directory/Name/Name.php,
1416 * and returns an array containing the value for 'Name' for each found extension.
1418 * Reasonable values for $directory include 'extensions' (the default) and 'skins'.
1420 * @param string $directory Directory to search in
1421 * @return array
1423 public function findExtensions( $directory = 'extensions' ) {
1424 if ( $this->getVar( 'IP' ) === null ) {
1425 return array();
1428 $extDir = $this->getVar( 'IP' ) . '/' . $directory;
1429 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
1430 return array();
1433 // extensions -> extension.json, skins -> skin.json
1434 $jsonFile = substr( $directory, 0, strlen( $directory ) -1 ) . '.json';
1436 $dh = opendir( $extDir );
1437 $exts = array();
1438 while ( ( $file = readdir( $dh ) ) !== false ) {
1439 if ( !is_dir( "$extDir/$file" ) ) {
1440 continue;
1442 if ( file_exists( "$extDir/$file/$jsonFile" ) || file_exists( "$extDir/$file/$file.php" ) ) {
1443 $exts[] = $file;
1446 closedir( $dh );
1447 natcasesort( $exts );
1449 return $exts;
1453 * Returns a default value to be used for $wgDefaultSkin: normally the one set in DefaultSettings,
1454 * but will fall back to another if the default skin is missing and some other one is present
1455 * instead.
1457 * @param string[] $skinNames Names of installed skins.
1458 * @return string
1460 public function getDefaultSkin( array $skinNames ) {
1461 $defaultSkin = $GLOBALS['wgDefaultSkin'];
1462 if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) {
1463 return $defaultSkin;
1464 } else {
1465 return $skinNames[0];
1470 * Installs the auto-detected extensions.
1472 * @return Status
1474 protected function includeExtensions() {
1475 global $IP;
1476 $exts = $this->getVar( '_Extensions' );
1477 $IP = $this->getVar( 'IP' );
1480 * We need to include DefaultSettings before including extensions to avoid
1481 * warnings about unset variables. However, the only thing we really
1482 * want here is $wgHooks['LoadExtensionSchemaUpdates']. This won't work
1483 * if the extension has hidden hook registration in $wgExtensionFunctions,
1484 * but we're not opening that can of worms
1485 * @see https://phabricator.wikimedia.org/T28857
1487 global $wgAutoloadClasses;
1488 $wgAutoloadClasses = array();
1489 $queue = array();
1491 require "$IP/includes/DefaultSettings.php";
1493 foreach ( $exts as $e ) {
1494 if ( file_exists( "$IP/extensions/$e/extension.json" ) ) {
1495 $queue["$IP/extensions/$e/extension.json"] = 1;
1496 } else {
1497 require_once "$IP/extensions/$e/$e.php";
1501 $registry = new ExtensionRegistry();
1502 $data = $registry->readFromQueue( $queue );
1503 $wgAutoloadClasses += $data['autoload'];
1505 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
1506 $wgHooks['LoadExtensionSchemaUpdates'] : array();
1508 if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
1509 $hooksWeWant = array_merge_recursive(
1510 $hooksWeWant,
1511 $data['globals']['wgHooks']['LoadExtensionSchemaUpdates']
1514 // Unset everyone else's hooks. Lord knows what someone might be doing
1515 // in ParserFirstCallInit (see bug 27171)
1516 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
1518 return Status::newGood();
1522 * Get an array of install steps. Should always be in the format of
1523 * array(
1524 * 'name' => 'someuniquename',
1525 * 'callback' => array( $obj, 'method' ),
1527 * There must be a config-install-$name message defined per step, which will
1528 * be shown on install.
1530 * @param DatabaseInstaller $installer DatabaseInstaller so we can make callbacks
1531 * @return array
1533 protected function getInstallSteps( DatabaseInstaller $installer ) {
1534 $coreInstallSteps = array(
1535 array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ),
1536 array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ),
1537 array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ),
1538 array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ),
1539 array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ),
1540 array( 'name' => 'updates', 'callback' => array( $installer, 'insertUpdateKeys' ) ),
1541 array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ),
1542 array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ),
1545 // Build the array of install steps starting from the core install list,
1546 // then adding any callbacks that wanted to attach after a given step
1547 foreach ( $coreInstallSteps as $step ) {
1548 $this->installSteps[] = $step;
1549 if ( isset( $this->extraInstallSteps[$step['name']] ) ) {
1550 $this->installSteps = array_merge(
1551 $this->installSteps,
1552 $this->extraInstallSteps[$step['name']]
1557 // Prepend any steps that want to be at the beginning
1558 if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1559 $this->installSteps = array_merge(
1560 $this->extraInstallSteps['BEGINNING'],
1561 $this->installSteps
1565 // Extensions should always go first, chance to tie into hooks and such
1566 if ( count( $this->getVar( '_Extensions' ) ) ) {
1567 array_unshift( $this->installSteps,
1568 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
1570 $this->installSteps[] = array(
1571 'name' => 'extension-tables',
1572 'callback' => array( $installer, 'createExtensionTables' )
1576 return $this->installSteps;
1580 * Actually perform the installation.
1582 * @param callable $startCB A callback array for the beginning of each step
1583 * @param callable $endCB A callback array for the end of each step
1585 * @return array Array of Status objects
1587 public function performInstallation( $startCB, $endCB ) {
1588 $installResults = array();
1589 $installer = $this->getDBInstaller();
1590 $installer->preInstall();
1591 $steps = $this->getInstallSteps( $installer );
1592 foreach ( $steps as $stepObj ) {
1593 $name = $stepObj['name'];
1594 call_user_func_array( $startCB, array( $name ) );
1596 // Perform the callback step
1597 $status = call_user_func( $stepObj['callback'], $installer );
1599 // Output and save the results
1600 call_user_func( $endCB, $name, $status );
1601 $installResults[$name] = $status;
1603 // If we've hit some sort of fatal, we need to bail.
1604 // Callback already had a chance to do output above.
1605 if ( !$status->isOk() ) {
1606 break;
1609 if ( $status->isOk() ) {
1610 $this->setVar( '_InstallDone', true );
1613 return $installResults;
1617 * Generate $wgSecretKey. Will warn if we had to use an insecure random source.
1619 * @return Status
1621 public function generateKeys() {
1622 $keys = array( 'wgSecretKey' => 64 );
1623 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1624 $keys['wgUpgradeKey'] = 16;
1627 return $this->doGenerateKeys( $keys );
1631 * Generate a secret value for variables using our CryptRand generator.
1632 * Produce a warning if the random source was insecure.
1634 * @param array $keys
1635 * @return Status
1637 protected function doGenerateKeys( $keys ) {
1638 $status = Status::newGood();
1640 $strong = true;
1641 foreach ( $keys as $name => $length ) {
1642 $secretKey = MWCryptRand::generateHex( $length, true );
1643 if ( !MWCryptRand::wasStrong() ) {
1644 $strong = false;
1647 $this->setVar( $name, $secretKey );
1650 if ( !$strong ) {
1651 $names = array_keys( $keys );
1652 $names = preg_replace( '/^(.*)$/', '\$$1', $names );
1653 global $wgLang;
1654 $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) );
1657 return $status;
1661 * Create the first user account, grant it sysop and bureaucrat rights
1663 * @return Status
1665 protected function createSysop() {
1666 $name = $this->getVar( '_AdminName' );
1667 $user = User::newFromName( $name );
1669 if ( !$user ) {
1670 // We should've validated this earlier anyway!
1671 return Status::newFatal( 'config-admin-error-user', $name );
1674 if ( $user->idForName() == 0 ) {
1675 $user->addToDatabase();
1677 try {
1678 $user->setPassword( $this->getVar( '_AdminPassword' ) );
1679 } catch ( PasswordError $pwe ) {
1680 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
1683 $user->addGroup( 'sysop' );
1684 $user->addGroup( 'bureaucrat' );
1685 if ( $this->getVar( '_AdminEmail' ) ) {
1686 $user->setEmail( $this->getVar( '_AdminEmail' ) );
1688 $user->saveSettings();
1690 // Update user count
1691 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
1692 $ssUpdate->doUpdate();
1694 $status = Status::newGood();
1696 if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1697 $this->subscribeToMediaWikiAnnounce( $status );
1700 return $status;
1704 * @param Status $s
1706 private function subscribeToMediaWikiAnnounce( Status $s ) {
1707 $params = array(
1708 'email' => $this->getVar( '_AdminEmail' ),
1709 'language' => 'en',
1710 'digest' => 0
1713 // Mailman doesn't support as many languages as we do, so check to make
1714 // sure their selected language is available
1715 $myLang = $this->getVar( '_UserLang' );
1716 if ( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
1717 $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
1718 $params['language'] = $myLang;
1721 if ( MWHttpRequest::canMakeRequests() ) {
1722 $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
1723 array( 'method' => 'POST', 'postData' => $params ), __METHOD__ )->execute();
1724 if ( !$res->isOK() ) {
1725 $s->warning( 'config-install-subscribe-fail', $res->getMessage() );
1727 } else {
1728 $s->warning( 'config-install-subscribe-notpossible' );
1733 * Insert Main Page with default content.
1735 * @param DatabaseInstaller $installer
1736 * @return Status
1738 protected function createMainpage( DatabaseInstaller $installer ) {
1739 $status = Status::newGood();
1740 try {
1741 $page = WikiPage::factory( Title::newMainPage() );
1742 $content = new WikitextContent(
1743 wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
1744 wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
1747 $page->doEditContent( $content,
1749 EDIT_NEW,
1750 false,
1751 User::newFromName( 'MediaWiki default' )
1753 } catch ( Exception $e ) {
1754 // using raw, because $wgShowExceptionDetails can not be set yet
1755 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1758 return $status;
1762 * Override the necessary bits of the config to run an installation.
1764 public static function overrideConfig() {
1765 define( 'MW_NO_SESSION', 1 );
1767 // Don't access the database
1768 $GLOBALS['wgUseDatabaseMessages'] = false;
1769 // Don't cache langconv tables
1770 $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE;
1771 // Debug-friendly
1772 $GLOBALS['wgShowExceptionDetails'] = true;
1773 // Don't break forms
1774 $GLOBALS['wgExternalLinkTarget'] = '_blank';
1776 // Extended debugging
1777 $GLOBALS['wgShowSQLErrors'] = true;
1778 $GLOBALS['wgShowDBErrorBacktrace'] = true;
1780 // Allow multiple ob_flush() calls
1781 $GLOBALS['wgDisableOutputCompression'] = true;
1783 // Use a sensible cookie prefix (not my_wiki)
1784 $GLOBALS['wgCookiePrefix'] = 'mw_installer';
1786 // Some of the environment checks make shell requests, remove limits
1787 $GLOBALS['wgMaxShellMemory'] = 0;
1791 * Add an installation step following the given step.
1793 * @param callable $callback A valid installation callback array, in this form:
1794 * array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
1795 * @param string $findStep The step to find. Omit to put the step at the beginning
1797 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1798 $this->extraInstallSteps[$findStep][] = $callback;
1802 * Disable the time limit for execution.
1803 * Some long-running pages (Install, Upgrade) will want to do this
1805 protected function disableTimeLimit() {
1806 MediaWiki\suppressWarnings();
1807 set_time_limit( 0 );
1808 MediaWiki\restoreWarnings();