]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blobdiff - includes/installer/Installer.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / installer / Installer.php
index deb949f33a1ec9eb52b966b9c6dbc93dc05dec78..012b4775782aa7b1524cf61a6761136b87d8f708 100644 (file)
@@ -2,9 +2,28 @@
 /**
  * Base code for MediaWiki installer.
  *
+ * DO NOT PATCH THIS FILE IF YOU NEED TO CHANGE INSTALLER BEHAVIOR IN YOUR PACKAGE!
+ * See mw-config/overrides/README for details.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
  * @file
  * @ingroup Deployment
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * This documentation group collects source code files with deployment functionality.
  */
 abstract class Installer {
 
-       // This is the absolute minimum PHP version we can support
-       const MINIMUM_PHP_VERSION = '5.2.3';
+       /**
+        * The oldest version of PCRE we can support.
+        *
+        * Defining this is necessary because PHP may be linked with a system version
+        * of PCRE, which may be older than that bundled with the minimum PHP version.
+        */
+       const MINIMUM_PCRE_VERSION = '7.2';
 
        /**
         * @var array
         */
        protected $settings;
 
+       /**
+        * List of detected DBs, access using getCompiledDBs().
+        *
+        * @var array
+        */
+       protected $compiledDBs;
+
        /**
         * Cached DB installer instances, access using getDBInstaller().
         *
         * @var array
         */
-       protected $dbInstallers = array();
+       protected $dbInstallers = [];
 
        /**
         * Minimum memory size in MB.
         *
-        * @var integer
+        * @var int
         */
        protected $minMemorySize = 50;
 
@@ -68,43 +99,53 @@ abstract class Installer {
         *
         * @var array
         */
-       protected static $dbTypes = array(
+       protected static $dbTypes = [
                'mysql',
                'postgres',
                'oracle',
+               'mssql',
                'sqlite',
-       );
+       ];
 
        /**
         * A list of environment check methods called by doEnvironmentChecks().
         * These may output warnings using showMessage(), and/or abort the
         * installation process by returning false.
         *
+        * For the WebInstaller these are only called on the Welcome page,
+        * if these methods have side-effects that should affect later page loads
+        * (as well as the generated stylesheet), use envPreps instead.
+        *
         * @var array
         */
-       protected $envChecks = array(
+       protected $envChecks = [
                'envCheckDB',
-               'envCheckRegisterGlobals',
                'envCheckBrokenXML',
-               'envCheckPHP531',
-               'envCheckMagicQuotes',
-               'envCheckMagicSybase',
-               'envCheckMbstring',
-               'envCheckZE1',
-               'envCheckSafeMode',
-               'envCheckXML',
                'envCheckPCRE',
                'envCheckMemory',
                'envCheckCache',
+               'envCheckModSecurity',
                'envCheckDiff3',
                'envCheckGraphics',
+               'envCheckGit',
+               'envCheckServer',
                'envCheckPath',
-               'envCheckExtension',
                'envCheckShellLocale',
                'envCheckUploadsDirectory',
                'envCheckLibicu',
                'envCheckSuhosinMaxValueLength',
-       );
+               'envCheck64Bit',
+       ];
+
+       /**
+        * A list of environment preparation methods called by doEnvironmentPreps().
+        *
+        * @var array
+        */
+       protected $envPreps = [
+               'envPrepServer',
+               'envPrepPath',
+       ];
 
        /**
         * MediaWiki configuration globals that will eventually be passed through
@@ -113,36 +154,35 @@ abstract class Installer {
         *
         * @var array
         */
-       protected $defaultVarNames = array(
+       protected $defaultVarNames = [
                'wgSitename',
                'wgPasswordSender',
                'wgLanguageCode',
                'wgRightsIcon',
                'wgRightsText',
                'wgRightsUrl',
-               'wgMainCacheType',
                'wgEnableEmail',
                'wgEnableUserEmail',
                'wgEnotifUserTalk',
                'wgEnotifWatchlist',
                'wgEmailAuthentication',
+               'wgDBname',
                'wgDBtype',
                'wgDiff3',
                'wgImageMagickConvertCommand',
+               'wgGitBin',
                'IP',
                'wgScriptPath',
-               'wgScriptExtension',
                'wgMetaNamespace',
                'wgDeletedDirectory',
                'wgEnableUploads',
-               'wgLogo',
                'wgShellLocale',
                'wgSecretKey',
                'wgUseInstantCommons',
                'wgUpgradeKey',
                'wgDefaultSkin',
-               'wgResourceLoaderMaxQueryLength',
-       );
+               'wgPingback',
+       ];
 
        /**
         * Variables that are stored alongside globals, and are used for any
@@ -151,177 +191,240 @@ abstract class Installer {
         *
         * @var array
         */
-       protected $internalDefaults = array(
+       protected $internalDefaults = [
                '_UserLang' => 'en',
                '_Environment' => false,
-               '_CompiledDBs' => array(),
-               '_SafeMode' => false,
                '_RaiseMemory' => false,
                '_UpgradeDone' => false,
                '_InstallDone' => false,
-               '_Caches' => array(),
+               '_Caches' => [],
                '_InstallPassword' => '',
                '_SameAccount' => true,
                '_CreateDBAccount' => false,
                '_NamespaceType' => 'site-name',
                '_AdminName' => '', // will be set later, when the user selects language
                '_AdminPassword' => '',
-               '_AdminPassword2' => '',
+               '_AdminPasswordConfirm' => '',
                '_AdminEmail' => '',
                '_Subscribe' => false,
                '_SkipOptional' => 'continue',
                '_RightsProfile' => 'wiki',
                '_LicenseCode' => 'none',
                '_CCDone' => false,
-               '_Extensions' => array(),
+               '_Extensions' => [],
+               '_Skins' => [],
                '_MemCachedServers' => '',
                '_UpgradeKeySupplied' => false,
                '_ExistingDBSettings' => false,
-       );
+
+               // $wgLogo is probably wrong (T50084); set something that will work.
+               // Single quotes work fine here, as LocalSettingsGenerator outputs this unescaped.
+               'wgLogo' => '$wgResourceBasePath/resources/assets/wiki.png',
+               'wgAuthenticationTokenVersion' => 1,
+       ];
 
        /**
         * The actual list of installation steps. This will be initialized by getInstallSteps()
         *
         * @var array
         */
-       private $installSteps = array();
+       private $installSteps = [];
 
        /**
         * Extra steps for installation, for things like DatabaseInstallers to modify
         *
         * @var array
         */
-       protected $extraInstallSteps = array();
+       protected $extraInstallSteps = [];
 
        /**
         * Known object cache types and the functions used to test for their existence.
         *
         * @var array
         */
-       protected $objectCaches = array(
+       protected $objectCaches = [
                'xcache' => 'xcache_get',
                'apc' => 'apc_fetch',
-               'eaccel' => 'eaccelerator_get',
+               'apcu' => 'apcu_fetch',
                'wincache' => 'wincache_ucache_get'
-       );
+       ];
 
        /**
         * User rights profiles.
         *
         * @var array
         */
-       public $rightsProfiles = array(
-               'wiki' => array(),
-               'no-anon' => array(
-                       '*' => array( 'edit' => false )
-               ),
-               'fishbowl' => array(
-                       '*' => array(
+       public $rightsProfiles = [
+               'wiki' => [],
+               'no-anon' => [
+                       '*' => [ 'edit' => false ]
+               ],
+               'fishbowl' => [
+                       '*' => [
                                'createaccount' => false,
                                'edit' => false,
-                       ),
-               ),
-               'private' => array(
-                       '*' => array(
+                       ],
+               ],
+               'private' => [
+                       '*' => [
                                'createaccount' => false,
                                'edit' => false,
                                'read' => false,
-                       ),
-               ),
-       );
+                       ],
+               ],
+       ];
 
        /**
         * License types.
         *
         * @var array
         */
-       public $licenses = array(
-               'cc-by-sa' => array(
-                       'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
-                       'icon' => '{$wgStylePath}/common/images/cc-by-sa.png',
-               ),
-               'cc-by-nc-sa' => array(
-                       'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
-                       'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png',
-               ),
-               'cc-0' => array(
+       public $licenses = [
+               'cc-by' => [
+                       'url' => 'https://creativecommons.org/licenses/by/4.0/',
+                       'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by.png',
+               ],
+               'cc-by-sa' => [
+                       'url' => 'https://creativecommons.org/licenses/by-sa/4.0/',
+                       'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-sa.png',
+               ],
+               'cc-by-nc-sa' => [
+                       'url' => 'https://creativecommons.org/licenses/by-nc-sa/4.0/',
+                       'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-nc-sa.png',
+               ],
+               'cc-0' => [
                        'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
-                       'icon' => '{$wgStylePath}/common/images/cc-0.png',
-               ),
-               'pd' => array(
-                       'url' => 'http://creativecommons.org/licenses/publicdomain/',
-                       'icon' => '{$wgStylePath}/common/images/public-domain.png',
-               ),
-               'gfdl-old' => array(
-                       'url' => 'http://www.gnu.org/licenses/old-licenses/fdl-1.2.html',
-                       'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
-               ),
-               'gfdl-current' => array(
-                       'url' => 'http://www.gnu.org/copyleft/fdl.html',
-                       'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
-               ),
-               'none' => array(
+                       'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-0.png',
+               ],
+               'gfdl' => [
+                       'url' => 'https://www.gnu.org/copyleft/fdl.html',
+                       'icon' => '$wgResourceBasePath/resources/assets/licenses/gnu-fdl.png',
+               ],
+               'none' => [
                        'url' => '',
                        'icon' => '',
                        'text' => ''
-               ),
-               'cc-choose' => array(
+               ],
+               'cc-choose' => [
                        // Details will be filled in by the selector.
                        'url' => '',
                        'icon' => '',
                        'text' => '',
-               ),
-       );
+               ],
+       ];
 
        /**
         * URL to mediawiki-announce subscription
         */
-       protected $mediaWikiAnnounceUrl = 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce';
+       protected $mediaWikiAnnounceUrl =
+               'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce';
 
        /**
         * Supported language codes for Mailman
         */
-       protected $mediaWikiAnnounceLanguages = array(
+       protected $mediaWikiAnnounceLanguages = [
                'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu',
                'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru',
                'sl', 'sr', 'sv', 'tr', 'uk'
-       );
+       ];
 
        /**
         * UI interface for displaying a short message
-        * The parameters are like parameters to wfMsg().
+        * The parameters are like parameters to wfMessage().
         * The messages will be in wikitext format, which will be converted to an
         * output format such as HTML or text before being sent to the user.
+        * @param string $msg
         */
-       public abstract function showMessage( $msg /*, ... */ );
+       abstract public function showMessage( $msg /*, ... */ );
 
        /**
         * Same as showMessage(), but for displaying errors
+        * @param string $msg
         */
-       public abstract function showError( $msg /*, ... */ );
+       abstract public function showError( $msg /*, ... */ );
 
        /**
         * Show a message to the installing user by using a Status object
-        * @param $status Status
+        * @param Status $status
         */
-       public abstract function showStatusMessage( Status $status );
+       abstract public function showStatusMessage( Status $status );
+
+       /**
+        * Constructs a Config object that contains configuration settings that should be
+        * overwritten for the installation process.
+        *
+        * @since 1.27
+        *
+        * @param Config $baseConfig
+        *
+        * @return Config The config to use during installation.
+        */
+       public static function getInstallerConfig( Config $baseConfig ) {
+               $configOverrides = new HashConfig();
+
+               // disable (problematic) object cache types explicitly, preserving all other (working) ones
+               // bug T113843
+               $emptyCache = [ 'class' => 'EmptyBagOStuff' ];
+
+               $objectCaches = [
+                               CACHE_NONE => $emptyCache,
+                               CACHE_DB => $emptyCache,
+                               CACHE_ANYTHING => $emptyCache,
+                               CACHE_MEMCACHED => $emptyCache,
+                       ] + $baseConfig->get( 'ObjectCaches' );
+
+               $configOverrides->set( 'ObjectCaches', $objectCaches );
+
+               // Load the installer's i18n.
+               $messageDirs = $baseConfig->get( 'MessagesDirs' );
+               $messageDirs['MediawikiInstaller'] = __DIR__ . '/i18n';
+
+               $configOverrides->set( 'MessagesDirs', $messageDirs );
+
+               $installerConfig = new MultiConfig( [ $configOverrides, $baseConfig ] );
+
+               // make sure we use the installer config as the main config
+               $configRegistry = $baseConfig->get( 'ConfigRegistry' );
+               $configRegistry['main'] = function () use ( $installerConfig ) {
+                       return $installerConfig;
+               };
+
+               $configOverrides->set( 'ConfigRegistry', $configRegistry );
+
+               return $installerConfig;
+       }
 
        /**
         * Constructor, always call this from child classes.
         */
        public function __construct() {
-               global $wgExtensionMessagesFiles, $wgUser, $wgHooks;
+               global $wgMemc, $wgUser, $wgObjectCaches;
 
-               // Disable the i18n cache and LoadBalancer
+               $defaultConfig = new GlobalVarConfig(); // all the stuff from DefaultSettings.php
+               $installerConfig = self::getInstallerConfig( $defaultConfig );
+
+               // Reset all services and inject config overrides
+               MediaWiki\MediaWikiServices::resetGlobalInstance( $installerConfig );
+
+               // Don't attempt to load user language options (T126177)
+               // This will be overridden in the web installer with the user-specified language
+               RequestContext::getMain()->setLanguage( 'en' );
+
+               // Disable the i18n cache
+               // TODO: manage LocalisationCache singleton in MediaWikiServices
                Language::getLocalisationCache()->disableBackend();
-               LBFactory::disableBackend();
 
-               // Load the installer's i18n file.
-               $wgExtensionMessagesFiles['MediawikiInstaller'] =
-                       dirname( __FILE__ ) . '/Installer.i18n.php';
+               // Disable all global services, since we don't have any configuration yet!
+               MediaWiki\MediaWikiServices::disableStorageBackend();
+
+               // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
+               // SqlBagOStuff will then throw since we just disabled wfGetDB)
+               $wgObjectCaches = MediaWikiServices::getInstance()->getMainConfig()->get( 'ObjectCaches' );
+               $wgMemc = ObjectCache::getInstance( CACHE_NONE );
 
                // Having a user with id = 0 safeguards us from DB access via User::loadOptions().
                $wgUser = User::newFromId( 0 );
+               RequestContext::getMain()->setUser( $wgUser );
 
                $this->settings = $this->internalDefaults;
 
@@ -329,31 +432,30 @@ abstract class Installer {
                        $this->settings[$var] = $GLOBALS[$var];
                }
 
+               $this->doEnvironmentPreps();
+
+               $this->compiledDBs = [];
                foreach ( self::getDBTypes() as $type ) {
                        $installer = $this->getDBInstaller( $type );
 
                        if ( !$installer->isCompiled() ) {
                                continue;
                        }
-
-                       $defaults = $installer->getGlobalDefaults();
-
-                       foreach ( $installer->getGlobalNames() as $var ) {
-                               if ( isset( $defaults[$var] ) ) {
-                                       $this->settings[$var] = $defaults[$var];
-                               } else {
-                                       $this->settings[$var] = $GLOBALS[$var];
-                               }
-                       }
+                       $this->compiledDBs[] = $type;
                }
 
                $this->parserTitle = Title::newFromText( 'Installer' );
-               $this->parserOptions = new ParserOptions; // language will  be wrong :(
+               $this->parserOptions = new ParserOptions( $wgUser ); // language will be wrong :(
                $this->parserOptions->setEditSection( false );
+               $this->parserOptions->setWrapOutputClass( false );
+               // Don't try to access DB before user language is initialised
+               $this->setParserLanguage( Language::factory( 'en' ) );
        }
 
        /**
         * Get a list of known DB types.
+        *
+        * @return array
         */
        public static function getDBTypes() {
                return self::$dbTypes;
@@ -373,16 +475,21 @@ abstract class Installer {
         * @return Status
         */
        public function doEnvironmentChecks() {
-               $phpVersion = phpversion();
-               if( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) {
-                       $this->showMessage( 'config-env-php', $phpVersion );
-                       $good = true;
+               // Php version has already been checked by entry scripts
+               // Show message here for information purposes
+               if ( wfIsHHVM() ) {
+                       $this->showMessage( 'config-env-hhvm', HHVM_VERSION );
                } else {
-                       $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION );
-                       $good = false;
+                       $this->showMessage( 'config-env-php', PHP_VERSION );
                }
 
-               if( $good ) {
+               $good = true;
+               // Must go here because an old version of PCRE can prevent other checks from completing
+               list( $pcreVersion ) = explode( ' ', PCRE_VERSION, 2 );
+               if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) {
+                       $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion );
+                       $good = false;
+               } else {
                        foreach ( $this->envChecks as $check ) {
                                $status = $this->$check();
                                if ( $status === false ) {
@@ -396,11 +503,17 @@ abstract class Installer {
                return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
        }
 
+       public function doEnvironmentPreps() {
+               foreach ( $this->envPreps as $prep ) {
+                       $this->$prep();
+               }
+       }
+
        /**
         * Set a MW configuration variable, or internal installer configuration variable.
         *
-        * @param $name String
-        * @param $value Mixed
+        * @param string $name
+        * @param mixed $value
         */
        public function setVar( $name, $value ) {
                $this->settings[$name] = $value;
@@ -411,8 +524,8 @@ abstract class Installer {
         * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
         * Installer variables are typically prefixed by an underscore.
         *
-        * @param $name String
-        * @param $default Mixed
+        * @param string $name
+        * @param mixed $default
         *
         * @return mixed
         */
@@ -424,10 +537,30 @@ abstract class Installer {
                }
        }
 
+       /**
+        * Get a list of DBs supported by current PHP setup
+        *
+        * @return array
+        */
+       public function getCompiledDBs() {
+               return $this->compiledDBs;
+       }
+
+       /**
+        * Get the DatabaseInstaller class name for this type
+        *
+        * @param string $type database type ($wgDBtype)
+        * @return string Class name
+        * @since 1.30
+        */
+       public static function getDBInstallerClass( $type ) {
+               return ucfirst( $type ) . 'Installer';
+       }
+
        /**
         * Get an instance of DatabaseInstaller for the specified DB type.
         *
-        * @param $type Mixed: DB installer for which is needed, false to use default.
+        * @param mixed $type DB installer for which is needed, false to use default.
         *
         * @return DatabaseInstaller
         */
@@ -439,7 +572,7 @@ abstract class Installer {
                $type = strtolower( $type );
 
                if ( !isset( $this->dbInstallers[$type] ) ) {
-                       $class = ucfirst( $type ). 'Installer';
+                       $class = self::getDBInstallerClass( $type );
                        $this->dbInstallers[$type] = new $class( $this );
                }
 
@@ -447,28 +580,39 @@ abstract class Installer {
        }
 
        /**
-        * Determine if LocalSettings.php exists. If it does, return its variables,
-        * merged with those from AdminSettings.php, as an array.
+        * Determine if LocalSettings.php exists. If it does, return its variables.
         *
-        * @return Array
+        * @return array|false
         */
        public static function getExistingLocalSettings() {
                global $IP;
 
-               wfSuppressWarnings();
+               // You might be wondering why this is here. Well if you don't do this
+               // then some poorly-formed extensions try to call their own classes
+               // after immediately registering them. We really need to get extension
+               // registration out of the global scope and into a real format.
+               // @see https://phabricator.wikimedia.org/T69440
+               global $wgAutoloadClasses;
+               $wgAutoloadClasses = [];
+
+               // @codingStandardsIgnoreStart
+               // LocalSettings.php should not call functions, except wfLoadSkin/wfLoadExtensions
+               // Define the required globals here, to ensure, the functions can do it work correctly.
+               global $wgExtensionDirectory, $wgStyleDirectory;
+               // @codingStandardsIgnoreEnd
+
+               MediaWiki\suppressWarnings();
                $_lsExists = file_exists( "$IP/LocalSettings.php" );
-               wfRestoreWarnings();
+               MediaWiki\restoreWarnings();
 
-               if( !$_lsExists ) {
+               if ( !$_lsExists ) {
                        return false;
                }
-               unset($_lsExists);
+               unset( $_lsExists );
+
+               require "$IP/includes/DefaultSettings.php";
+               require "$IP/LocalSettings.php";
 
-               require( "$IP/includes/DefaultSettings.php" );
-               require( "$IP/LocalSettings.php" );
-               if ( file_exists( "$IP/AdminSettings.php" ) ) {
-                       require( "$IP/AdminSettings.php" );
-               }
                return get_defined_vars();
        }
 
@@ -477,7 +621,7 @@ abstract class Installer {
         * This is a security mechanism to avoid compromise of the password in the
         * event of session ID compromise.
         *
-        * @param $realPassword String
+        * @param string $realPassword
         *
         * @return string
         */
@@ -489,8 +633,8 @@ abstract class Installer {
         * Set a variable which stores a password, except if the new value is a
         * fake password in which case leave it as it is.
         *
-        * @param $name String
-        * @param $value Mixed
+        * @param string $name
+        * @param mixed $value
         */
        public function setPassword( $name, $value ) {
                if ( !preg_match( '/^\*+$/', $value ) ) {
@@ -518,8 +662,7 @@ abstract class Installer {
                # posix_getegid() *not* getmygid() because we want the group of the webserver,
                # not whoever owns the current script.
                $gid = posix_getegid();
-               $getpwuid = posix_getpwuid( $gid );
-               $group = $getpwuid['name'];
+               $group = posix_getpwuid( $gid )['name'];
 
                return $group;
        }
@@ -532,13 +675,13 @@ abstract class Installer {
         * write your messages. This appears to work well enough. Basic formatting and
         * external links work just fine.
         *
-        * But in case a translator decides to throw in a #ifexist or internal link or
+        * But in case a translator decides to throw in a "#ifexist" or internal link or
         * whatever, this function is guarded to catch the attempted DB access and to present
         * some fallback text.
         *
-        * @param $text String
-        * @param $lineStart Boolean
-        * @return String
+        * @param string $text
+        * @param bool $lineStart
+        * @return string
         */
        public function parse( $text, $lineStart = false ) {
                global $wgParser;
@@ -546,7 +689,7 @@ abstract class Installer {
                try {
                        $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
                        $html = $out->getText();
-               } catch ( DBAccessError $e ) {
+               } catch ( MediaWiki\Services\ServiceDisabledException $e ) {
                        $html = '<!--DB access attempted during parse-->  ' . htmlspecialchars( $text );
 
                        if ( !empty( $this->debug ) ) {
@@ -557,6 +700,9 @@ abstract class Installer {
                return $html;
        }
 
+       /**
+        * @return ParserOptions
+        */
        public function getParserOptions() {
                return $this->parserOptions;
        }
@@ -573,184 +719,116 @@ abstract class Installer {
        /**
         * Install step which adds a row to the site_stats table with appropriate
         * initial values.
+        *
+        * @param DatabaseInstaller $installer
+        *
+        * @return Status
         */
        public function populateSiteStats( DatabaseInstaller $installer ) {
                $status = $installer->getConnection();
                if ( !$status->isOK() ) {
                        return $status;
                }
-               $status->value->insert( 'site_stats', array(
-                       'ss_row_id' => 1,
-                       'ss_total_views' => 0,
-                       'ss_total_edits' => 0,
-                       'ss_good_articles' => 0,
-                       'ss_total_pages' => 0,
-                       'ss_users' => 0,
-                       'ss_admins' => 0,
-                       'ss_images' => 0 ),
-                       __METHOD__, 'IGNORE' );
-               return Status::newGood();
-       }
+               $status->value->insert(
+                       'site_stats',
+                       [
+                               'ss_row_id' => 1,
+                               'ss_total_edits' => 0,
+                               'ss_good_articles' => 0,
+                               'ss_total_pages' => 0,
+                               'ss_users' => 0,
+                               'ss_active_users' => 0,
+                               'ss_images' => 0
+                       ],
+                       __METHOD__, 'IGNORE'
+               );
 
-       /**
-        * Exports all wg* variables stored by the installer into global scope.
-        */
-       public function exportVars() {
-               foreach ( $this->settings as $name => $value ) {
-                       if ( substr( $name, 0, 2 ) == 'wg' ) {
-                               $GLOBALS[$name] = $value;
-                       }
-               }
+               return Status::newGood();
        }
 
        /**
         * Environment check for DB types.
+        * @return bool
         */
        protected function envCheckDB() {
                global $wgLang;
 
-               $compiledDBs = array();
-               $allNames = array();
+               $allNames = [];
 
+               // Messages: config-type-mysql, config-type-postgres, config-type-oracle,
+               // config-type-sqlite
                foreach ( self::getDBTypes() as $name ) {
-                       $db = $this->getDBInstaller( $name );
-                       $readableName = wfMsg( 'config-type-' . $name );
-
-                       if ( $db->isCompiled() ) {
-                               $compiledDBs[] = $name;
-                       }
-                       $allNames[] = $readableName;
+                       $allNames[] = wfMessage( "config-type-$name" )->text();
                }
 
-               $this->setVar( '_CompiledDBs', $compiledDBs );
+               $databases = $this->getCompiledDBs();
 
-               if ( !$compiledDBs ) {
-                       $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
-                       // FIXME: this only works for the web installer!
-                       return false;
-               }
-
-               // Check for FTS3 full-text search module
-               $sqlite = $this->getDBInstaller( 'sqlite' );
-               if ( $sqlite->isCompiled() ) {
-                       if( DatabaseSqlite::getFulltextSearchModule() != 'FTS3' ) {
-                               $this->showMessage( 'config-no-fts3' );
+               $databases = array_flip( $databases );
+               foreach ( array_keys( $databases ) as $db ) {
+                       $installer = $this->getDBInstaller( $db );
+                       $status = $installer->checkPrerequisites();
+                       if ( !$status->isGood() ) {
+                               $this->showStatusMessage( $status );
+                       }
+                       if ( !$status->isOK() ) {
+                               unset( $databases[$db] );
                        }
                }
-       }
+               $databases = array_flip( $databases );
+               if ( !$databases ) {
+                       $this->showError( 'config-no-db', $wgLang->commaList( $allNames ), count( $allNames ) );
 
-       /**
-        * Environment check for register_globals.
-        */
-       protected function envCheckRegisterGlobals() {
-               if( wfIniGetBool( 'register_globals' ) ) {
-                       $this->showMessage( 'config-register-globals' );
+                       // @todo FIXME: This only works for the web installer!
+                       return false;
                }
+
+               return true;
        }
 
        /**
         * Some versions of libxml+PHP break < and > encoding horribly
+        * @return bool
         */
        protected function envCheckBrokenXML() {
                $test = new PhpXmlBugTester();
                if ( !$test->ok ) {
                        $this->showError( 'config-brokenlibxml' );
-                       return false;
-               }
-       }
 
-       /**
-        * Test PHP (probably 5.3.1, but it could regress again) to make sure that
-        * reference parameters to __call() are not converted to null
-        */
-       protected function envCheckPHP531() {
-               $test = new PhpRefCallBugTester;
-               $test->execute();
-               if ( !$test->ok ) {
-                       $this->showError( 'config-using531', phpversion() );
                        return false;
                }
-       }
 
-       /**
-        * Environment check for magic_quotes_runtime.
-        */
-       protected function envCheckMagicQuotes() {
-               if( wfIniGetBool( "magic_quotes_runtime" ) ) {
-                       $this->showError( 'config-magic-quotes-runtime' );
-                       return false;
-               }
-       }
-
-       /**
-        * Environment check for magic_quotes_sybase.
-        */
-       protected function envCheckMagicSybase() {
-               if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
-                       $this->showError( 'config-magic-quotes-sybase' );
-                       return false;
-               }
-       }
-
-       /**
-        * Environment check for mbstring.func_overload.
-        */
-       protected function envCheckMbstring() {
-               if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
-                       $this->showError( 'config-mbstring' );
-                       return false;
-               }
-       }
-
-       /**
-        * Environment check for zend.ze1_compatibility_mode.
-        */
-       protected function envCheckZE1() {
-               if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
-                       $this->showError( 'config-ze1' );
-                       return false;
-               }
-       }
-
-       /**
-        * Environment check for safe_mode.
-        */
-       protected function envCheckSafeMode() {
-               if ( wfIniGetBool( 'safe_mode' ) ) {
-                       $this->setVar( '_SafeMode', true );
-                       $this->showMessage( 'config-safe-mode' );
-               }
-       }
-
-       /**
-        * Environment check for the XML module.
-        */
-       protected function envCheckXML() {
-               if ( !function_exists( "utf8_encode" ) ) {
-                       $this->showError( 'config-xml-bad' );
-                       return false;
-               }
+               return true;
        }
 
        /**
         * Environment check for the PCRE module.
+        *
+        * @note If this check were to fail, the parser would
+        *   probably throw an exception before the result
+        *   of this check is shown to the user.
+        * @return bool
         */
        protected function envCheckPCRE() {
-               if ( !function_exists( 'preg_match' ) ) {
-                       $this->showError( 'config-pcre' );
-                       return false;
-               }
-               wfSuppressWarnings();
+               MediaWiki\suppressWarnings();
                $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
-               wfRestoreWarnings();
-               if ( $regexd != '--' ) {
+               // Need to check for \p support too, as PCRE can be compiled
+               // with utf8 support, but not unicode property support.
+               // check that \p{Zs} (space separators) matches
+               // U+3000 (Ideographic space)
+               $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" );
+               MediaWiki\restoreWarnings();
+               if ( $regexd != '--' || $regexprop != '--' ) {
                        $this->showError( 'config-pcre-no-utf8' );
+
                        return false;
                }
+
+               return true;
        }
 
        /**
         * Environment check for available memory.
+        * @return bool
         */
        protected function envCheckMemory() {
                $limit = ini_get( 'memory_limit' );
@@ -761,25 +839,25 @@ abstract class Installer {
 
                $n = wfShorthandToInteger( $limit );
 
-               if( $n < $this->minMemorySize * 1024 * 1024 ) {
+               if ( $n < $this->minMemorySize * 1024 * 1024 ) {
                        $newLimit = "{$this->minMemorySize}M";
 
-                       if( ini_set( "memory_limit", $newLimit ) === false ) {
+                       if ( ini_set( "memory_limit", $newLimit ) === false ) {
                                $this->showMessage( 'config-memory-bad', $limit );
                        } else {
                                $this->showMessage( 'config-memory-raised', $limit, $newLimit );
                                $this->setVar( '_RaiseMemory', true );
                        }
-               } else {
-                       return true;
                }
+
+               return true;
        }
 
        /**
         * Environment check for compiled object cache types.
         */
        protected function envCheckCache() {
-               $caches = array();
+               $caches = [];
                foreach ( $this->objectCaches as $name => $function ) {
                        if ( function_exists( $function ) ) {
                                if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) {
@@ -790,18 +868,33 @@ abstract class Installer {
                }
 
                if ( !$caches ) {
-                       $this->showMessage( 'config-no-cache' );
+                       $key = 'config-no-cache-apcu';
+                       $this->showMessage( $key );
                }
 
                $this->setVar( '_Caches', $caches );
        }
 
+       /**
+        * Scare user to death if they have mod_security or mod_security2
+        * @return bool
+        */
+       protected function envCheckModSecurity() {
+               if ( self::apacheModulePresent( 'mod_security' )
+                       || self::apacheModulePresent( 'mod_security2' ) ) {
+                       $this->showMessage( 'config-mod-security' );
+               }
+
+               return true;
+       }
+
        /**
         * Search for GNU diff3.
+        * @return bool
         */
        protected function envCheckDiff3() {
-               $names = array( "gdiff3", "diff3", "diff3.exe" );
-               $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' );
+               $names = [ "gdiff3", "diff3", "diff3.exe" ];
+               $versionInfo = [ '$1 --version 2>&1', 'GNU diffutils' ];
 
                $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo );
 
@@ -811,75 +904,91 @@ abstract class Installer {
                        $this->setVar( 'wgDiff3', false );
                        $this->showMessage( 'config-diff3-bad' );
                }
+
+               return true;
        }
 
        /**
         * Environment check for ImageMagick and GD.
+        * @return bool
         */
        protected function envCheckGraphics() {
-               $names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
-               $convert = self::locateExecutableInDefaultPaths( $names, array( '$1 -version', 'ImageMagick' ) );
+               $names = [ wfIsWindows() ? 'convert.exe' : 'convert' ];
+               $versionInfo = [ '$1 -version', 'ImageMagick' ];
+               $convert = self::locateExecutableInDefaultPaths( $names, $versionInfo );
 
                $this->setVar( 'wgImageMagickConvertCommand', '' );
                if ( $convert ) {
                        $this->setVar( 'wgImageMagickConvertCommand', $convert );
                        $this->showMessage( 'config-imagemagick', $convert );
+
                        return true;
                } elseif ( function_exists( 'imagejpeg' ) ) {
                        $this->showMessage( 'config-gd' );
-                       return true;
                } else {
                        $this->showMessage( 'config-no-scaling' );
                }
+
+               return true;
        }
 
        /**
-        * Environment check for setting $IP and $wgScriptPath.
+        * Search for git.
+        *
+        * @since 1.22
+        * @return bool
         */
-       protected function envCheckPath() {
-               global $IP;
-               $IP = dirname( dirname( dirname( __FILE__ ) ) );
+       protected function envCheckGit() {
+               $names = [ wfIsWindows() ? 'git.exe' : 'git' ];
+               $versionInfo = [ '$1 --version', 'git version' ];
 
-               $this->setVar( 'IP', $IP );
+               $git = self::locateExecutableInDefaultPaths( $names, $versionInfo );
 
-               // PHP_SELF isn't available sometimes, such as when PHP is CGI but
-               // cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
-               // to get the path to the current script... hopefully it's reliable. SIGH
-               if ( !empty( $_SERVER['PHP_SELF'] ) ) {
-                       $path = $_SERVER['PHP_SELF'];
-               } elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
-                       $path = $_SERVER['SCRIPT_NAME'];
-               } elseif ( $this->getVar( 'wgScriptPath' ) ) {
-                       // Some kind soul has set it for us already (e.g. debconf)
-                       return true;
+               if ( $git ) {
+                       $this->setVar( 'wgGitBin', $git );
+                       $this->showMessage( 'config-git', $git );
                } else {
-                       $this->showError( 'config-no-uri' );
-                       return false;
+                       $this->setVar( 'wgGitBin', false );
+                       $this->showMessage( 'config-git-bad' );
                }
 
-               $uri = preg_replace( '{^(.*)/(mw-)?config.*$}', '$1', $path );
-               $this->setVar( 'wgScriptPath', $uri );
+               return true;
        }
 
        /**
-        * Environment check for setting the preferred PHP file extension.
+        * Environment check to inform user which server we've assumed.
+        *
+        * @return bool
         */
-       protected function envCheckExtension() {
-               // FIXME: detect this properly
-               if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
-                       $ext = 'php5';
-               } else {
-                       $ext = 'php';
+       protected function envCheckServer() {
+               $server = $this->envGetDefaultServer();
+               if ( $server !== null ) {
+                       $this->showMessage( 'config-using-server', $server );
                }
-               $this->setVar( 'wgScriptExtension', ".$ext" );
+               return true;
+       }
+
+       /**
+        * Environment check to inform user which paths we've assumed.
+        *
+        * @return bool
+        */
+       protected function envCheckPath() {
+               $this->showMessage(
+                       'config-using-uri',
+                       $this->getVar( 'wgServer' ),
+                       $this->getVar( 'wgScriptPath' )
+               );
+               return true;
        }
 
        /**
-        * TODO: document
+        * Environment check for preferred locale in shell
+        * @return bool
         */
        protected function envCheckShellLocale() {
                $os = php_uname( 's' );
-               $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
+               $supported = [ 'Linux', 'SunOS', 'HP-UX', 'Darwin' ]; # Tested these
 
                if ( !in_array( $os, $supported ) ) {
                        return true;
@@ -893,9 +1002,9 @@ abstract class Installer {
                        return true;
                }
 
-               $lines = wfArrayMap( 'trim', explode( "\n", $lines ) );
-               $candidatesByLocale = array();
-               $candidatesByLang = array();
+               $lines = array_map( 'trim', explode( "\n", $lines ) );
+               $candidatesByLocale = [];
+               $candidatesByLang = [];
 
                foreach ( $lines as $line ) {
                        if ( $line === '' ) {
@@ -906,23 +1015,25 @@ abstract class Installer {
                                continue;
                        }
 
-                       list( $all, $lang, $territory, $charset, $modifier ) = $m;
+                       list( , $lang, , , ) = $m;
 
                        $candidatesByLocale[$m[0]] = $m;
                        $candidatesByLang[$lang][] = $m;
                }
 
                # Try the current value of LANG.
-               if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
+               if ( isset( $candidatesByLocale[getenv( 'LANG' )] ) ) {
                        $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
+
                        return true;
                }
 
                # Try the most common ones.
-               $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
+               $commonLocales = [ 'C.UTF-8', 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' ];
                foreach ( $commonLocales as $commonLocale ) {
                        if ( isset( $candidatesByLocale[$commonLocale] ) ) {
                                $this->setVar( 'wgShellLocale', $commonLocale );
+
                                return true;
                        }
                }
@@ -933,6 +1044,7 @@ abstract class Installer {
                if ( isset( $candidatesByLang[$wikiLang] ) ) {
                        $m = reset( $candidatesByLang[$wikiLang] );
                        $this->setVar( 'wgShellLocale', $m[0] );
+
                        return true;
                }
 
@@ -940,6 +1052,7 @@ abstract class Installer {
                if ( count( $candidatesByLocale ) ) {
                        $m = reset( $candidatesByLocale );
                        $this->setVar( 'wgShellLocale', $m[0] );
+
                        return true;
                }
 
@@ -948,68 +1061,79 @@ abstract class Installer {
        }
 
        /**
-        * TODO: document
+        * Environment check for the permissions of the uploads directory
+        * @return bool
         */
        protected function envCheckUploadsDirectory() {
-               global $IP, $wgServer;
+               global $IP;
 
                $dir = $IP . '/images/';
-               $url = $wgServer . $this->getVar( 'wgScriptPath' ) . '/images/';
+               $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
                $safe = !$this->dirIsExecutable( $dir, $url );
 
-               if ( $safe ) {
-                       return true;
-               } else {
+               if ( !$safe ) {
                        $this->showMessage( 'config-uploads-not-safe', $dir );
                }
+
+               return true;
        }
 
        /**
-        * Checks if suhosin.get.max_value_length is set, and if so, sets
-        * $wgResourceLoaderMaxQueryLength to that value in the generated
-        * LocalSettings file
+        * Checks if suhosin.get.max_value_length is set, and if so generate
+        * a warning because it decreases ResourceLoader performance.
+        * @return bool
         */
        protected function envCheckSuhosinMaxValueLength() {
                $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
-               if ( $maxValueLength > 0 ) {
+               if ( $maxValueLength > 0 && $maxValueLength < 1024 ) {
+                       // Only warn if the value is below the sane 1024
                        $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
-               } else {
-                       $maxValueLength = -1;
                }
-               $this->setVar( 'wgResourceLoaderMaxQueryLength', $maxValueLength );
+
+               return true;
+       }
+
+       /**
+        * Checks if we're running on 64 bit or not. 32 bit is becoming increasingly
+        * hard to support, so let's at least warn people.
+        *
+        * @return bool
+        */
+       protected function envCheck64Bit() {
+               if ( PHP_INT_SIZE == 4 ) {
+                       $this->showMessage( 'config-using-32bit' );
+               }
+
+               return true;
        }
 
        /**
         * Convert a hex string representing a Unicode code point to that code point.
-        * @param $c String
-        * @return string
+        * @param string $c
+        * @return string|false
         */
        protected function unicodeChar( $c ) {
-               $c = hexdec($c);
-               if ($c <= 0x7F) {
-                       return chr($c);
-               } else if ($c <= 0x7FF) {
-                       return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
-               } else if ($c <= 0xFFFF) {
-                       return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
-                               . chr(0x80 | $c & 0x3F);
-               } else if ($c <= 0x10FFFF) {
-                       return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
-                               . chr(0x80 | $c >> 6 & 0x3F)
-                               . chr(0x80 | $c & 0x3F);
+               $c = hexdec( $c );
+               if ( $c <= 0x7F ) {
+                       return chr( $c );
+               } elseif ( $c <= 0x7FF ) {
+                       return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F );
+               } elseif ( $c <= 0xFFFF ) {
+                       return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F ) .
+                               chr( 0x80 | $c & 0x3F );
+               } elseif ( $c <= 0x10FFFF ) {
+                       return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F ) .
+                               chr( 0x80 | $c >> 6 & 0x3F ) .
+                               chr( 0x80 | $c & 0x3F );
                } else {
                        return false;
                }
        }
 
-
        /**
         * Check the libicu version
         */
        protected function envCheckLibicu() {
-               $utf8 = function_exists( 'utf8_normalize' );
-               $intl = function_exists( 'normalizer_normalize' );
-
                /**
                 * This needs to be updated something that the latest libicu
                 * will properly normalize.  This normalization was found at
@@ -1017,49 +1141,67 @@ abstract class Installer {
                 * Note that we use the hex representation to create the code
                 * points in order to avoid any Unicode-destroying during transit.
                 */
-               $not_normal_c = $this->unicodeChar("FA6C");
-               $normal_c = $this->unicodeChar("242EE");
+               $not_normal_c = $this->unicodeChar( "FA6C" );
+               $normal_c = $this->unicodeChar( "242EE" );
 
                $useNormalizer = 'php';
                $needsUpdate = false;
 
-               /**
-                * We're going to prefer the pecl extension here unless
-                * utf8_normalize is more up to date.
-                */
-               if( $utf8 ) {
-                       $useNormalizer = 'utf8';
-                       $utf8 = utf8_normalize( $not_normal_c, UNORM_NFC );
-                       if ( $utf8 !== $normal_c ) $needsUpdate = true;
-               }
-               if( $intl ) {
+               if ( function_exists( 'normalizer_normalize' ) ) {
                        $useNormalizer = 'intl';
                        $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
-                       if ( $intl !== $normal_c ) $needsUpdate = true;
+                       if ( $intl !== $normal_c ) {
+                               $needsUpdate = true;
+                       }
                }
 
-               // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl'
-               if( $useNormalizer === 'php' ) {
+               // Uses messages 'config-unicode-using-php' and 'config-unicode-using-intl'
+               if ( $useNormalizer === 'php' ) {
                        $this->showMessage( 'config-unicode-pure-php-warning' );
                } else {
                        $this->showMessage( 'config-unicode-using-' . $useNormalizer );
-                       if( $needsUpdate ) {
+                       if ( $needsUpdate ) {
                                $this->showMessage( 'config-unicode-update-warning' );
                        }
                }
        }
 
+       /**
+        * Environment prep for the server hostname.
+        */
+       protected function envPrepServer() {
+               $server = $this->envGetDefaultServer();
+               if ( $server !== null ) {
+                       $this->setVar( 'wgServer', $server );
+               }
+       }
+
+       /**
+        * Helper function to be called from envPrepServer()
+        * @return string
+        */
+       abstract protected function envGetDefaultServer();
+
+       /**
+        * Environment prep for setting $IP and $wgScriptPath.
+        */
+       protected function envPrepPath() {
+               global $IP;
+               $IP = dirname( dirname( __DIR__ ) );
+               $this->setVar( 'IP', $IP );
+       }
+
        /**
         * Get an array of likely places we can find executables. Check a bunch
         * of known Unix-like defaults, as well as the PATH environment variable
         * (which should maybe make it work for Windows?)
         *
-        * @return Array
+        * @return array
         */
        protected static function getPossibleBinPaths() {
                return array_merge(
-                       array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
-                               '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
+                       [ '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
+                               '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ],
                        explode( PATH_SEPARATOR, getenv( 'PATH' ) )
                );
        }
@@ -1071,26 +1213,27 @@ abstract class Installer {
         *
         * Used only by environment checks.
         *
-        * @param $path String: path to search
-        * @param $names Array of executable names
-        * @param $versionInfo Boolean false or array with two members:
-        *               0 => Command to run for version check, with $1 for the full executable name
-        *               1 => String to compare the output with
+        * @param string $path Path to search
+        * @param array $names Array of executable names
+        * @param array|bool $versionInfo False or array with two members:
+        *   0 => Command to run for version check, with $1 for the full executable name
+        *   1 => String to compare the output with
         *
         * If $versionInfo is not false, only executables with a version
         * matching $versionInfo[1] will be returned.
+        * @return bool|string
         */
        public static function locateExecutable( $path, $names, $versionInfo = false ) {
                if ( !is_array( $names ) ) {
-                       $names = array( $names );
+                       $names = [ $names ];
                }
 
                foreach ( $names as $name ) {
                        $command = $path . DIRECTORY_SEPARATOR . $name;
 
-                       wfSuppressWarnings();
-                       $file_exists = file_exists( $command );
-                       wfRestoreWarnings();
+                       MediaWiki\suppressWarnings();
+                       $file_exists = is_executable( $command );
+                       MediaWiki\restoreWarnings();
 
                        if ( $file_exists ) {
                                if ( !$versionInfo ) {
@@ -1103,20 +1246,30 @@ abstract class Installer {
                                }
                        }
                }
+
                return false;
        }
 
        /**
         * Same as locateExecutable(), but checks in getPossibleBinPaths() by default
         * @see locateExecutable()
+        * @param array $names Array of possible names.
+        * @param array|bool $versionInfo Default: false or array with two members:
+        *   0 => Command to run for version check, with $1 for the full executable name
+        *   1 => String to compare the output with
+        *
+        * If $versionInfo is not false, only executables with a version
+        * matching $versionInfo[1] will be returned.
+        * @return bool|string
         */
        public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
-               foreach( self::getPossibleBinPaths() as $path ) {
+               foreach ( self::getPossibleBinPaths() as $path ) {
                        $exe = self::locateExecutable( $path, $names, $versionInfo );
-                       if( $exe !== false ) {
+                       if ( $exe !== false ) {
                                return $exe;
                        }
                }
+
                return false;
        }
 
@@ -1124,18 +1277,21 @@ abstract class Installer {
         * Checks if scripts located in the given directory can be executed via the given URL.
         *
         * Used only by environment checks.
+        * @param string $dir
+        * @param string $url
+        * @return bool|int|string
         */
        public function dirIsExecutable( $dir, $url ) {
-               $scriptTypes = array(
-                       'php' => array(
+               $scriptTypes = [
+                       'php' => [
                                "<?php echo 'ex' . 'ec';",
                                "#!/var/env php5\n<?php echo 'ex' . 'ec';",
-                       ),
-               );
+                       ],
+               ];
 
                // it would be good to check other popular languages here, but it'll be slow.
 
-               wfSuppressWarnings();
+               MediaWiki\suppressWarnings();
 
                foreach ( $scriptTypes as $ext => $contents ) {
                        foreach ( $contents as $source ) {
@@ -1146,65 +1302,126 @@ abstract class Installer {
                                }
 
                                try {
-                                       $text = Http::get( $url . $file, array( 'timeout' => 3 ) );
-                               }
-                               catch( MWException $e ) {
+                                       $text = Http::get( $url . $file, [ 'timeout' => 3 ], __METHOD__ );
+                               } catch ( Exception $e ) {
                                        // Http::get throws with allow_url_fopen = false and no curl extension.
                                        $text = null;
                                }
                                unlink( $dir . $file );
 
                                if ( $text == 'exec' ) {
-                                       wfRestoreWarnings();
+                                       MediaWiki\restoreWarnings();
+
                                        return $ext;
                                }
                        }
                }
 
-               wfRestoreWarnings();
+               MediaWiki\restoreWarnings();
 
                return false;
        }
 
+       /**
+        * Checks for presence of an Apache module. Works only if PHP is running as an Apache module, too.
+        *
+        * @param string $moduleName Name of module to check.
+        * @return bool
+        */
+       public static function apacheModulePresent( $moduleName ) {
+               if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
+                       return true;
+               }
+               // try it the hard way
+               ob_start();
+               phpinfo( INFO_MODULES );
+               $info = ob_get_clean();
+
+               return strpos( $info, $moduleName ) !== false;
+       }
+
        /**
         * ParserOptions are constructed before we determined the language, so fix it
+        *
+        * @param Language $lang
         */
        public function setParserLanguage( $lang ) {
                $this->parserOptions->setTargetLanguage( $lang );
-               $this->parserOptions->setUserLang( $lang->getCode() );
+               $this->parserOptions->setUserLang( $lang );
        }
 
        /**
         * Overridden by WebInstaller to provide lastPage parameters.
+        * @param string $page
+        * @return string
         */
        protected function getDocUrl( $page ) {
                return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
        }
 
        /**
-        * Finds extensions that follow the format /extensions/Name/Name.php,
+        * Finds extensions that follow the format /$directory/Name/Name.php,
         * and returns an array containing the value for 'Name' for each found extension.
         *
-        * @return array
+        * Reasonable values for $directory include 'extensions' (the default) and 'skins'.
+        *
+        * @param string $directory Directory to search in
+        * @return array [ $extName => [ 'screenshots' => [ '...' ] ]
         */
-       public function findExtensions() {
-               if( $this->getVar( 'IP' ) === null ) {
-                       return false;
+       public function findExtensions( $directory = 'extensions' ) {
+               if ( $this->getVar( 'IP' ) === null ) {
+                       return [];
                }
 
-               $exts = array();
-               $dir = $this->getVar( 'IP' ) . '/extensions';
-               $dh = opendir( $dir );
+               $extDir = $this->getVar( 'IP' ) . '/' . $directory;
+               if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
+                       return [];
+               }
+
+               // extensions -> extension.json, skins -> skin.json
+               $jsonFile = substr( $directory, 0, strlen( $directory ) - 1 ) . '.json';
 
+               $dh = opendir( $extDir );
+               $exts = [];
                while ( ( $file = readdir( $dh ) ) !== false ) {
-                       if( file_exists( "$dir/$file/$file.php" ) ) {
-                               $exts[] = $file;
+                       if ( !is_dir( "$extDir/$file" ) ) {
+                               continue;
+                       }
+                       if ( file_exists( "$extDir/$file/$jsonFile" ) || file_exists( "$extDir/$file/$file.php" ) ) {
+                               // Extension exists. Now see if there are screenshots
+                               $exts[$file] = [];
+                               if ( is_dir( "$extDir/$file/screenshots" ) ) {
+                                       $paths = glob( "$extDir/$file/screenshots/*.png" );
+                                       foreach ( $paths as $path ) {
+                                               $exts[$file]['screenshots'][] = str_replace( $extDir, "../$directory", $path );
+                                       }
+
+                               }
                        }
                }
+               closedir( $dh );
+               uksort( $exts, 'strnatcasecmp' );
 
                return $exts;
        }
 
+       /**
+        * Returns a default value to be used for $wgDefaultSkin: normally the one set in DefaultSettings,
+        * but will fall back to another if the default skin is missing and some other one is present
+        * instead.
+        *
+        * @param string[] $skinNames Names of installed skins.
+        * @return string
+        */
+       public function getDefaultSkin( array $skinNames ) {
+               $defaultSkin = $GLOBALS['wgDefaultSkin'];
+               if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) {
+                       return $defaultSkin;
+               } else {
+                       return $skinNames[0];
+               }
+       }
+
        /**
         * Installs the auto-detected extensions.
         *
@@ -1221,62 +1438,81 @@ abstract class Installer {
                 * want here is $wgHooks['LoadExtensionSchemaUpdates']. This won't work
                 * if the extension has hidden hook registration in $wgExtensionFunctions,
                 * but we're not opening that can of worms
-                * @see https://bugzilla.wikimedia.org/show_bug.cgi?id=26857
+                * @see https://phabricator.wikimedia.org/T28857
                 */
                global $wgAutoloadClasses;
-               require( "$IP/includes/DefaultSettings.php" );
+               $wgAutoloadClasses = [];
+               $queue = [];
+
+               require "$IP/includes/DefaultSettings.php";
 
-               foreach( $exts as $e ) {
-                       require_once( $IP . '/extensions' . "/$e/$e.php" );
+               foreach ( $exts as $e ) {
+                       if ( file_exists( "$IP/extensions/$e/extension.json" ) ) {
+                               $queue["$IP/extensions/$e/extension.json"] = 1;
+                       } else {
+                               require_once "$IP/extensions/$e/$e.php";
+                       }
                }
 
+               $registry = new ExtensionRegistry();
+               $data = $registry->readFromQueue( $queue );
+               $wgAutoloadClasses += $data['autoload'];
+
                $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
-                       $wgHooks['LoadExtensionSchemaUpdates'] : array();
+                       /** @suppress PhanUndeclaredVariable $wgHooks is set by DefaultSettings */
+                       $wgHooks['LoadExtensionSchemaUpdates'] : [];
 
+               if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
+                       $hooksWeWant = array_merge_recursive(
+                               $hooksWeWant,
+                               $data['globals']['wgHooks']['LoadExtensionSchemaUpdates']
+                       );
+               }
                // Unset everyone else's hooks. Lord knows what someone might be doing
-               // in ParserFirstCallInit (see bug 27171)
-               $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
+               // in ParserFirstCallInit (see T29171)
+               $GLOBALS['wgHooks'] = [ 'LoadExtensionSchemaUpdates' => $hooksWeWant ];
 
                return Status::newGood();
        }
 
        /**
         * Get an array of install steps. Should always be in the format of
-        * array(
+        * [
         *   'name'     => 'someuniquename',
-        *   'callback' => array( $obj, 'method' ),
-        * )
+        *   'callback' => [ $obj, 'method' ],
+        * ]
         * There must be a config-install-$name message defined per step, which will
         * be shown on install.
         *
-        * @param $installer DatabaseInstaller so we can make callbacks
+        * @param DatabaseInstaller $installer DatabaseInstaller so we can make callbacks
         * @return array
         */
        protected function getInstallSteps( DatabaseInstaller $installer ) {
-               $coreInstallSteps = array(
-                       array( 'name' => 'database',   'callback' => array( $installer, 'setupDatabase' ) ),
-                       array( 'name' => 'tables',     'callback' => array( $installer, 'createTables' ) ),
-                       array( 'name' => 'interwiki',  'callback' => array( $installer, 'populateInterwikiTable' ) ),
-                       array( 'name' => 'stats',      'callback' => array( $this, 'populateSiteStats' ) ),
-                       array( 'name' => 'keys',       'callback' => array( $this, 'generateKeys' ) ),
-                       array( 'name' => 'sysop',      'callback' => array( $this, 'createSysop' ) ),
-                       array( 'name' => 'mainpage',   'callback' => array( $this, 'createMainpage' ) ),
-               );
+               $coreInstallSteps = [
+                       [ 'name' => 'database', 'callback' => [ $installer, 'setupDatabase' ] ],
+                       [ 'name' => 'tables', 'callback' => [ $installer, 'createTables' ] ],
+                       [ 'name' => 'interwiki', 'callback' => [ $installer, 'populateInterwikiTable' ] ],
+                       [ 'name' => 'stats', 'callback' => [ $this, 'populateSiteStats' ] ],
+                       [ 'name' => 'keys', 'callback' => [ $this, 'generateKeys' ] ],
+                       [ 'name' => 'updates', 'callback' => [ $installer, 'insertUpdateKeys' ] ],
+                       [ 'name' => 'sysop', 'callback' => [ $this, 'createSysop' ] ],
+                       [ 'name' => 'mainpage', 'callback' => [ $this, 'createMainpage' ] ],
+               ];
 
                // Build the array of install steps starting from the core install list,
                // then adding any callbacks that wanted to attach after a given step
-               foreach( $coreInstallSteps as $step ) {
+               foreach ( $coreInstallSteps as $step ) {
                        $this->installSteps[] = $step;
-                       if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) {
+                       if ( isset( $this->extraInstallSteps[$step['name']] ) ) {
                                $this->installSteps = array_merge(
                                        $this->installSteps,
-                                       $this->extraInstallSteps[ $step['name'] ]
+                                       $this->extraInstallSteps[$step['name']]
                                );
                        }
                }
 
                // Prepend any steps that want to be at the beginning
-               if( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
+               if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
                        $this->installSteps = array_merge(
                                $this->extraInstallSteps['BEGINNING'],
                                $this->installSteps
@@ -1284,34 +1520,35 @@ abstract class Installer {
                }
 
                // Extensions should always go first, chance to tie into hooks and such
-               if( count( $this->getVar( '_Extensions' ) ) ) {
+               if ( count( $this->getVar( '_Extensions' ) ) ) {
                        array_unshift( $this->installSteps,
-                               array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
+                               [ 'name' => 'extensions', 'callback' => [ $this, 'includeExtensions' ] ]
                        );
-                       $this->installSteps[] = array(
+                       $this->installSteps[] = [
                                'name' => 'extension-tables',
-                               'callback' => array( $installer, 'createExtensionTables' )
-                       );
+                               'callback' => [ $installer, 'createExtensionTables' ]
+                       ];
                }
+
                return $this->installSteps;
        }
 
        /**
         * Actually perform the installation.
         *
-        * @param $startCB Array A callback array for the beginning of each step
-        * @param $endCB Array A callback array for the end of each step
+        * @param callable $startCB A callback array for the beginning of each step
+        * @param callable $endCB A callback array for the end of each step
         *
-        * @return Array of Status objects
+        * @return array Array of Status objects
         */
        public function performInstallation( $startCB, $endCB ) {
-               $installResults = array();
+               $installResults = [];
                $installer = $this->getDBInstaller();
                $installer->preInstall();
                $steps = $this->getInstallSteps( $installer );
-               foreach( $steps as $stepObj ) {
+               foreach ( $steps as $stepObj ) {
                        $name = $stepObj['name'];
-                       call_user_func_array( $startCB, array( $name ) );
+                       call_user_func_array( $startCB, [ $name ] );
 
                        // Perform the callback step
                        $status = call_user_func( $stepObj['callback'], $installer );
@@ -1322,13 +1559,14 @@ abstract class Installer {
 
                        // If we've hit some sort of fatal, we need to bail.
                        // Callback already had a chance to do output above.
-                       if( !$status->isOk() ) {
+                       if ( !$status->isOk() ) {
                                break;
                        }
                }
-               if( $status->isOk() ) {
+               if ( $status->isOk() ) {
                        $this->setVar( '_InstallDone', true );
                }
+
                return $installResults;
        }
 
@@ -1338,10 +1576,11 @@ abstract class Installer {
         * @return Status
         */
        public function generateKeys() {
-               $keys = array( 'wgSecretKey' => 64 );
+               $keys = [ 'wgSecretKey' => 64 ];
                if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
                        $keys['wgUpgradeKey'] = 16;
                }
+
                return $this->doGenerateKeys( $keys );
        }
 
@@ -1349,7 +1588,7 @@ abstract class Installer {
         * Generate a secret value for variables using our CryptRand generator.
         * Produce a warning if the random source was insecure.
         *
-        * @param $keys Array
+        * @param array $keys
         * @return Status
         */
        protected function doGenerateKeys( $keys ) {
@@ -1394,13 +1633,13 @@ abstract class Installer {
 
                        try {
                                $user->setPassword( $this->getVar( '_AdminPassword' ) );
-                       } catch( PasswordError $pwe ) {
+                       } catch ( PasswordError $pwe ) {
                                return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
                        }
 
                        $user->addGroup( 'sysop' );
                        $user->addGroup( 'bureaucrat' );
-                       if( $this->getVar( '_AdminEmail' ) ) {
+                       if ( $this->getVar( '_AdminEmail' ) ) {
                                $user->setEmail( $this->getVar( '_AdminEmail' ) );
                        }
                        $user->saveSettings();
@@ -1411,32 +1650,35 @@ abstract class Installer {
                }
                $status = Status::newGood();
 
-               if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
+               if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
                        $this->subscribeToMediaWikiAnnounce( $status );
                }
 
                return $status;
        }
 
+       /**
+        * @param Status $s
+        */
        private function subscribeToMediaWikiAnnounce( Status $s ) {
-               $params = array(
-                       'email'    => $this->getVar( '_AdminEmail' ),
+               $params = [
+                       'email' => $this->getVar( '_AdminEmail' ),
                        'language' => 'en',
-                       'digest'   => 0
-               );
+                       'digest' => 0
+               ];
 
                // Mailman doesn't support as many languages as we do, so check to make
                // sure their selected language is available
                $myLang = $this->getVar( '_UserLang' );
-               if( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
+               if ( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
                        $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
                        $params['language'] = $myLang;
                }
 
-               if( MWHttpRequest::canMakeRequests() ) {
+               if ( MWHttpRequest::canMakeRequests() ) {
                        $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
-                               array( 'method' => 'POST', 'postData' => $params ) )->execute();
-                       if( !$res->isOK() ) {
+                               [ 'method' => 'POST', 'postData' => $params ], __METHOD__ )->execute();
+                       if ( !$res->isOK() ) {
                                $s->warning( 'config-install-subscribe-fail', $res->getMessage() );
                        }
                } else {
@@ -1447,20 +1689,31 @@ abstract class Installer {
        /**
         * Insert Main Page with default content.
         *
+        * @param DatabaseInstaller $installer
         * @return Status
         */
        protected function createMainpage( DatabaseInstaller $installer ) {
                $status = Status::newGood();
+               $title = Title::newMainPage();
+               if ( $title->exists() ) {
+                       $status->warning( 'config-install-mainpage-exists' );
+                       return $status;
+               }
                try {
-                       $article = new Article( Title::newMainPage() );
-                       $article->doEdit( wfMsgForContent( 'mainpagetext' ) . "\n\n" .
-                                                               wfMsgForContent( 'mainpagedocfooter' ),
-                                                               '',
-                                                               EDIT_NEW,
-                                                               false,
-                                                               User::newFromName( 'MediaWiki default' ) );
-               } catch (MWException $e) {
-                       //using raw, because $wgShowExceptionDetails can not be set yet
+                       $page = WikiPage::factory( $title );
+                       $content = new WikitextContent(
+                               wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
+                               wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
+                       );
+
+                       $status = $page->doEditContent( $content,
+                               '',
+                               EDIT_NEW,
+                               false,
+                               User::newFromName( 'MediaWiki default' )
+                       );
+               } catch ( Exception $e ) {
+                       // using raw, because $wgShowExceptionDetails can not be set yet
                        $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
                }
 
@@ -1471,10 +1724,14 @@ abstract class Installer {
         * Override the necessary bits of the config to run an installation.
         */
        public static function overrideConfig() {
-               define( 'MW_NO_SESSION', 1 );
+               // Use PHP's built-in session handling, since MediaWiki's
+               // SessionHandler can't work before we have an object cache set up.
+               define( 'MW_NO_SESSION_HANDLER', 1 );
 
                // Don't access the database
                $GLOBALS['wgUseDatabaseMessages'] = false;
+               // Don't cache langconv tables
+               $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE;
                // Debug-friendly
                $GLOBALS['wgShowExceptionDetails'] = true;
                // Don't break forms
@@ -1492,16 +1749,40 @@ abstract class Installer {
 
                // Some of the environment checks make shell requests, remove limits
                $GLOBALS['wgMaxShellMemory'] = 0;
+
+               // Override the default CookieSessionProvider with a dummy
+               // implementation that won't stomp on PHP's cookies.
+               $GLOBALS['wgSessionProviders'] = [
+                       [
+                               'class' => 'InstallerSessionProvider',
+                               'args' => [ [
+                                       'priority' => 1,
+                               ] ]
+                       ]
+               ];
+
+               // Don't try to use any object cache for SessionManager either.
+               $GLOBALS['wgSessionCacheType'] = CACHE_NONE;
        }
 
        /**
         * Add an installation step following the given step.
         *
-        * @param $callback Array A valid installation callback array, in this form:
-        *    array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
-        * @param $findStep String the step to find. Omit to put the step at the beginning
+        * @param callable $callback A valid installation callback array, in this form:
+        *    [ 'name' => 'some-unique-name', 'callback' => [ $obj, 'function' ] ];
+        * @param string $findStep The step to find. Omit to put the step at the beginning
         */
        public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
                $this->extraInstallSteps[$findStep][] = $callback;
        }
+
+       /**
+        * Disable the time limit for execution.
+        * Some long-running pages (Install, Upgrade) will want to do this
+        */
+       protected function disableTimeLimit() {
+               MediaWiki\suppressWarnings();
+               set_time_limit( 0 );
+               MediaWiki\restoreWarnings();
+       }
 }