/**
* 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;
*
* @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
*
* @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
*
* @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;
$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;
* @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 ) {
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;
* 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
*/
}
}
+ /**
+ * 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
*/
$type = strtolower( $type );
if ( !isset( $this->dbInstallers[$type] ) ) {
- $class = ucfirst( $type ). 'Installer';
+ $class = self::getDBInstallerClass( $type );
$this->dbInstallers[$type] = new $class( $this );
}
}
/**
- * 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();
}
* 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
*/
* 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 ) ) {
# 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;
}
* 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;
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 ) ) {
return $html;
}
+ /**
+ * @return ParserOptions
+ */
public function getParserOptions() {
return $this->parserOptions;
}
/**
* 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' );
$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' ) ) {
}
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 );
$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;
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 === '' ) {
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;
}
}
if ( isset( $candidatesByLang[$wikiLang] ) ) {
$m = reset( $candidatesByLang[$wikiLang] );
$this->setVar( 'wgShellLocale', $m[0] );
+
return true;
}
if ( count( $candidatesByLocale ) ) {
$m = reset( $candidatesByLocale );
$this->setVar( 'wgShellLocale', $m[0] );
+
return true;
}
}
/**
- * 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
* 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' ) )
);
}
*
* 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 ) {
}
}
}
+
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;
}
* 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 ) {
}
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.
*
* 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
}
// 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 );
// 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;
}
* @return Status
*/
public function generateKeys() {
- $keys = array( 'wgSecretKey' => 64 );
+ $keys = [ 'wgSecretKey' => 64 ];
if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
$keys['wgUpgradeKey'] = 16;
}
+
return $this->doGenerateKeys( $keys );
}
* 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 ) {
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();
}
$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 {
/**
* 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() );
}
* 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
// 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();
+ }
}