From a620daf697ad15c9755393242f2849f1d267adbb Mon Sep 17 00:00:00 2001 From: Jeroen De Dauw Date: Thu, 22 Jul 2010 17:58:26 +0000 Subject: [PATCH] Split Installer into Installer and CoreInstaller + made misc style and doc improvements --- includes/AutoLoader.php | 1 + includes/installer/CliInstaller.php | 7 +- includes/installer/CoreInstaller.php | 447 +++++++++++ includes/installer/DatabaseInstaller.php | 2 +- includes/installer/Installer.php | 937 ++++++++--------------- includes/installer/WebInstaller.php | 24 +- includes/installer/WebInstallerPage.php | 5 +- 7 files changed, 788 insertions(+), 635 deletions(-) create mode 100644 includes/installer/CoreInstaller.php diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index e893189ac3..77e174c58a 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -415,6 +415,7 @@ $wgAutoloadLocalClasses = array( # includes/installer 'CliInstaller' => 'includes/installer/CliInstaller.php', 'Installer' => 'includes/installer/Installer.php', + 'CoreInstaller' => 'includes/installer/CoreInstaller.php', 'DatabaseInstaller' => 'includes/installer/DatabaseInstaller.php', 'DatabaseUpdater' => 'includes/installer/DatabaseUpdater.php', 'LBFactory_InstallerFake' => 'includes/installer/Installer.php', diff --git a/includes/installer/CliInstaller.php b/includes/installer/CliInstaller.php index 5319b0f67c..bbb43fbfe0 100644 --- a/includes/installer/CliInstaller.php +++ b/includes/installer/CliInstaller.php @@ -1,6 +1,11 @@ 'en', + '_Environment' => false, + '_CompiledDBs' => array(), + '_SafeMode' => false, + '_RaiseMemory' => false, + '_UpgradeDone' => false, + '_InstallDone' => false, + '_Caches' => array(), + '_InstallUser' => 'root', + '_InstallPassword' => '', + '_SameAccount' => true, + '_CreateDBAccount' => false, + '_NamespaceType' => 'site-name', + '_AdminName' => '', // will be set later, when the user selects language + '_AdminPassword' => '', + '_AdminPassword2' => '', + '_AdminEmail' => '', + '_Subscribe' => false, + '_SkipOptional' => 'continue', + '_RightsProfile' => 'wiki', + '_LicenseCode' => 'none', + '_CCDone' => false, + '_Extensions' => array(), + '_MemCachedServers' => '', + '_ExternalHTTP' => false, + ); + + /** + * Steps for installation. + * + * @var array + */ + protected $installSteps = array( + 'database', + 'tables', + 'interwiki', + 'secretkey', + 'sysop', + ); + + /** + * Known object cache types and the functions used to test for their existence. + * + * @var array + */ + protected $objectCaches = array( + 'xcache' => 'xcache_get', + 'apc' => 'apc_fetch', + 'eaccel' => 'eaccelerator_get', + 'wincache' => 'wincache_ucache_get' + ); + + /** + * User rights profiles. + * + * @var array + */ + public $rightsProfiles = array( + 'wiki' => array(), + 'no-anon' => array( + '*' => array( 'edit' => false ) + ), + 'fishbowl' => array( + '*' => array( + 'createaccount' => false, + 'edit' => false, + ), + ), + 'private' => array( + '*' => array( + 'createaccount' => false, + 'edit' => false, + 'read' => false, + ), + ), + ); + + /** + * License types. + * + * @var array + */ + public $licenses = array( + 'none' => array( + 'url' => '', + 'icon' => '', + 'text' => '' + ), + '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', + ), + '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', + ), + 'cc-choose' => array( + // details will be filled in by the selector + 'url' => '', + 'icon' => '', + 'text' => '', + ), + ); + + /** + * Constructor, always call this from child classes. + */ + public function __construct() { + parent::__construct(); + + global $wgExtensionMessagesFiles, $wgUser, $wgHooks; + + // Load the installer's i18n file. + $wgExtensionMessagesFiles['MediawikiInstaller'] = + './includes/installer/Installer.i18n.php'; + + // Having a user with id = 0 safeguards us from DB access via User::loadOptions(). + $wgUser = User::newFromId( 0 ); + + // Set our custom hook. + $wgHooks['ParserFirstCallInit'][] = array( $this, 'registerDocLink' ); + + $this->settings = $this->internalDefaults; + + foreach ( $this->defaultVarNames as $var ) { + $this->settings[$var] = $GLOBALS[$var]; + } + + foreach ( $this->dbTypes 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->parserTitle = Title::newFromText( 'Installer' ); + $this->parserOptions = new ParserOptions; + $this->parserOptions->setEditSection( false ); + } + + /** + * Register tag hook below. + * + * @param $parser Parser + */ + public function registerDocLink( Parser &$parser ) { + $parser->setHook( 'doclink', array( $this, 'docLink' ) ); + return true; + } + + /** + * Extension tag hook for a documentation link. + */ + public function docLink( $linkText, $attribs, $parser ) { + $url = $this->getDocUrl( $attribs['href'] ); + return '' . + htmlspecialchars( $linkText ) . + ''; + } + + /** + * Overridden by WebInstaller to provide lastPage parameters. + */ + protected function getDocUrl( $page ) { + return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $attribs['href'] ); + } + + /** + * Finds extensions that follow the format /extensions/Name/Name.php, + * and returns an array containing the value for 'Name' for each found extension. + * + * @return array + */ + public function findExtensions() { + if( $this->getVar( 'IP' ) === null ) { + return false; + } + + $exts = array(); + $dir = $this->getVar( 'IP' ) . '/extensions'; + $dh = opendir( $dir ); + + while ( ( $file = readdir( $dh ) ) !== false ) { + if( file_exists( "$dir/$file/$file.php" ) ) { + $exts[] = $file; + } + } + + $this->setVar( '_Extensions', $exts ); + + return $exts; + } + + /** + * Installs the auto-detected extensions. + * + * TODO: this only requires them? + * + * @return Status + */ + public function installExtensions() { + global $wgHooks, $wgAutoloadClasses; + + $exts = $this->getVar( '_Extensions' ); + $path = $this->getVar( 'IP' ) . '/extensions'; + + foreach( $exts as $e ) { + require( "$path/$e/$e.php" ); + } + + return Status::newGood(); + } + + public function getInstallSteps() { + if( $this->getVar( '_UpgradeDone' ) ) { + $this->installSteps = array( 'localsettings' ); + } + + if( count( $this->getVar( '_Extensions' ) ) ) { + array_unshift( $this->installSteps, 'extensions' ); + } + + return $this->installSteps; + } + + /** + * Actually perform the installation. + * + * @param Array $startCB A callback array for the beginning of each step + * @param Array $endCB A callback array for the end of each step + * + * @return Array of Status objects + */ + public function performInstallation( $startCB, $endCB ) { + $installResults = array(); + $installer = $this->getDBInstaller(); + + foreach( $this->getInstallSteps() as $stepObj ) { + $step = is_array( $stepObj ) ? $stepObj['name'] : $stepObj; + call_user_func_array( $startCB, array( $step ) ); + $status = null; + + # Call our working function + if ( is_array( $stepObj ) ) { + # A custom callaback + $callback = $stepObj['callback']; + $status = call_user_func_array( $callback, array( $installer ) ); + } else { + # Boring implicitly named callback + $func = 'install' . ucfirst( $step ); + $status = $this->{$func}( $installer ); + } + + call_user_func_array( $endCB, array( $step, $status ) ); + $installResults[$step] = $status; + + // If we've hit some sort of fatal, we need to bail. + // Callback already had a chance to do output above. + if( !$status->isOk() ) { + break; + } + + } + + if( $status->isOk() ) { + $this->setVar( '_InstallDone', true ); + } + + return $installResults; + } + + /** + * TODO: document + * + * @return Status + */ + public function installSecretKey() { + if ( wfIsWindows() ) { + $file = null; + } else { + wfSuppressWarnings(); + $file = fopen( "/dev/urandom", "r" ); + wfRestoreWarnings(); + } + + $status = Status::newGood(); + + if ( $file ) { + $secretKey = bin2hex( fread( $file, 32 ) ); + fclose( $file ); + } else { + $secretKey = ''; + + for ( $i=0; $i<8; $i++ ) { + $secretKey .= dechex( mt_rand( 0, 0x7fffffff ) ); + } + + $status->warning( 'config-insecure-secretkey' ); + } + + $this->setVar( 'wgSecretKey', $secretKey ); + + return $status; + } + + /** + * TODO: document + * + * @return Status + */ + public function installSysop() { + $name = $this->getVar( '_AdminName' ); + $user = User::newFromName( $name ); + + if ( !$user ) { + // We should've validated this earlier anyway! + return Status::newFatal( 'config-admin-error-user', $name ); + } + + if ( $user->idForName() == 0 ) { + $user->addToDatabase(); + + try { + $user->setPassword( $this->getVar( '_AdminPassword' ) ); + } catch( PasswordError $pwe ) { + return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() ); + } + + $user->addGroup( 'sysop' ); + $user->addGroup( 'bureaucrat' ); + $user->saveSettings(); + } + + return Status::newGood(); + } + + /** + * Override the necessary bits of the config to run an installation. + */ + public static function overrideConfig() { + define( 'MW_NO_SESSION', 1 ); + + // Don't access the database + $GLOBALS['wgUseDatabaseMessages'] = false; + // Debug-friendly + $GLOBALS['wgShowExceptionDetails'] = true; + // Don't break forms + $GLOBALS['wgExternalLinkTarget'] = '_blank'; + + // Extended debugging. Maybe disable before release? + $GLOBALS['wgShowSQLErrors'] = true; + $GLOBALS['wgShowDBErrorBacktrace'] = true; + } + + /** + * Add an installation step following the given step. + * + * @param $findStep String the step to find. Use NULL to put the step at the beginning. + * @param $callback array + */ + public function addInstallStepFollowing( $findStep, $callback ) { + $where = 0; + + if( $findStep !== null ) { + $where = array_search( $findStep, $this->installSteps ); + } + + array_splice( $this->installSteps, $where, 0, $callback ); + } + +} \ No newline at end of file diff --git a/includes/installer/DatabaseInstaller.php b/includes/installer/DatabaseInstaller.php index fa16dc17e4..acf0e6891c 100644 --- a/includes/installer/DatabaseInstaller.php +++ b/includes/installer/DatabaseInstaller.php @@ -101,7 +101,7 @@ abstract class DatabaseInstaller { /** * Create database tables from scratch. * - * @return \type Status + * @return Status */ public abstract function createTables(); diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php index bfca426645..a8a641b6da 100644 --- a/includes/installer/Installer.php +++ b/includes/installer/Installer.php @@ -2,128 +2,65 @@ /** * Base installer class. - * Handles everything that is independent of user interface. + * + * This class provides the base for installation and update functionality + * for both MediaWiki core and extensions. + * + * @since 1.17 */ abstract class Installer { - public $settings; - - /** - * - * @var unknown_type - */ - public $output; - /** - * MediaWiki configuration globals that will eventually be passed through - * to LocalSettings.php. The names only are given here, the defaults - * typically come from DefaultSettings.php. + * TODO: make protected? * * @var array */ - protected $defaultVarNames = array( - 'wgSitename', - 'wgPasswordSender', - 'wgLanguageCode', - 'wgRightsIcon', - 'wgRightsText', - 'wgRightsUrl', - 'wgMainCacheType', - 'wgEnableEmail', - 'wgEnableUserEmail', - 'wgEnotifUserTalk', - 'wgEnotifWatchlist', - 'wgEmailAuthentication', - 'wgDBtype', - 'wgDiff3', - 'wgImageMagickConvertCommand', - 'IP', - 'wgScriptPath', - 'wgScriptExtension', - 'wgMetaNamespace', - 'wgDeletedDirectory', - 'wgEnableUploads', - 'wgLogo', - 'wgShellLocale', - 'wgSecretKey', - 'wgUseInstantCommons', - ); - + public $settings; + /** - * Variables that are stored alongside globals, and are used for any - * configuration of the installation process aside from the MediaWiki - * configuration. Map of names to defaults. + * Cached DB installer instances, access using getDBInstaller(). * * @var array */ - protected $internalDefaults = array( - '_UserLang' => 'en', - '_Environment' => false, - '_CompiledDBs' => array(), - '_SafeMode' => false, - '_RaiseMemory' => false, - '_UpgradeDone' => false, - '_InstallDone' => false, - '_Caches' => array(), - '_InstallUser' => 'root', - '_InstallPassword' => '', - '_SameAccount' => true, - '_CreateDBAccount' => false, - '_NamespaceType' => 'site-name', - '_AdminName' => '', // will be set later, when the user selects language - '_AdminPassword' => '', - '_AdminPassword2' => '', - '_AdminEmail' => '', - '_Subscribe' => false, - '_SkipOptional' => 'continue', - '_RightsProfile' => 'wiki', - '_LicenseCode' => 'none', - '_CCDone' => false, - '_Extensions' => array(), - '_MemCachedServers' => '', - '_ExternalHTTP' => false, - ); - - /** - * Known database types. These correspond to the class names Installer, - * and are also MediaWiki database types valid for $wgDBtype. - * - * To add a new type, create a Installer class and a Database - * class, and add a config-type- message to MessagesEn.php. - * - * @var array - */ - private $dbTypes = array( - 'mysql', - 'postgres', - 'sqlite', - 'oracle' - ); + protected $dbInstallers = array(); /** * Minimum memory size in MB. * * @var integer */ - private $minMemorySize = 50; - + protected $minMemorySize = 50; + /** * Cached Title, used by parse(). + * + * @var Title */ - private $parserTitle; + protected $parserTitle; /** * Cached ParserOptions, used by parse(). + * + * @var ParserOptions */ - private $parserOptions; + protected $parserOptions; /** - * Cached DB installer instances, access using getDBInstaller(). + * Known database types. These correspond to the class names Installer, + * and are also MediaWiki database types valid for $wgDBtype. + * + * To add a new type, create a Installer class and a Database + * class, and add a config-type- message to MessagesEn.php. * * @var array */ - private $dbInstallers = array(); - + protected $dbTypes = array( + 'mysql', + 'postgres', + 'sqlite', + 'oracle' + ); + /** * A list of environment check methods called by doEnvironmentChecks(). * These may output warnings using showMessage(), and/or abort the @@ -151,177 +88,39 @@ abstract class Installer { 'envCheckExtension', 'envCheckShellLocale', 'envCheckUploadsDirectory', - ); - - /** - * Steps for installation. - * - * @var array - */ - protected $installSteps = array( - 'database', - 'tables', - 'interwiki', - 'secretkey', - 'sysop', - ); - - /** - * Known object cache types and the functions used to test for their existence. - * - * @var array - */ - protected $objectCaches = array( - 'xcache' => 'xcache_get', - 'apc' => 'apc_fetch', - 'eaccel' => 'eaccelerator_get', - 'wincache' => 'wincache_ucache_get' - ); - + ); + /** - * User rights profiles. - * - * @var array + * UI interface for displaying a short message + * The parameters are like parameters to wfMsg(). + * 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. */ - public $rightsProfiles = array( - 'wiki' => array(), - 'no-anon' => array( - '*' => array( 'edit' => false ) - ), - 'fishbowl' => array( - '*' => array( - 'createaccount' => false, - 'edit' => false, - ), - ), - 'private' => array( - '*' => array( - 'createaccount' => false, - 'edit' => false, - 'read' => false, - ), - ), - ); + public abstract function showMessage( $msg /*, ... */ ); /** - * License types. + * TODO: doucment * - * @var array + * @param $status */ - public $licenses = array( - 'none' => array( - 'url' => '', - 'icon' => '', - 'text' => '' - ), - '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', - ), - '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', - ), - 'cc-choose' => array( - // details will be filled in by the selector - 'url' => '', - 'icon' => '', - 'text' => '', - ), - ); - - + public abstract function showStatusMessage( $status ); + /** - * Constructor, always call this from child classes + * Constructor, always call this from child classes. */ - public function __construct() { + public function __construct() { // Disable the i18n cache and LoadBalancer Language::getLocalisationCache()->disableBackend(); LBFactory::disableBackend(); - - // Load the installer's i18n file - global $wgExtensionMessagesFiles; - $wgExtensionMessagesFiles['MediawikiInstaller'] = - './includes/installer/Installer.i18n.php'; - - global $wgUser; - $wgUser = User::newFromId( 0 ); - // Having a user with id = 0 safeguards us from DB access via User::loadOptions() - - // Set our custom hook - global $wgHooks; - $wgHooks['ParserFirstCallInit'][] = array( $this, 'registerDocLink' ); - - $this->settings = $this->internalDefaults; - foreach ( $this->defaultVarNames as $var ) { - $this->settings[$var] = $GLOBALS[$var]; - } - foreach ( $this->dbTypes 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->parserTitle = Title::newFromText( 'Installer' ); - $this->parserOptions = new ParserOptions; - $this->parserOptions->setEditSection( false ); } - - /** - * UI interface for displaying a short message - * The parameters are like parameters to wfMsg(). - * 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. - */ - public abstract function showMessage( $msg /*, ... */ ); - - public abstract function showStatusMessage( $status ); - + /** * Get a list of known DB types. */ public function getDBTypes() { return $this->dbTypes; - } - - /** - * Get an instance of DatabaseInstaller for the specified DB type - * @param $type Mixed: DB installer for which is needed, false to use default. - */ - public function getDBInstaller( $type = false ) { - if ( !$type ) { - $type = $this->getVar( 'wgDBtype' ); - } - $type = strtolower($type); - - if ( !isset( $this->dbInstallers[$type] ) ) { - $class = ucfirst( $type ). 'Installer'; - $this->dbInstallers[$type] = new $class( $this ); - } - return $this->dbInstallers[$type]; - } - + } + /** * Do initial checks of the PHP environment. Set variables according to * the observed environment. @@ -332,30 +131,51 @@ abstract class Installer { * * Under the web subclass, it can already be assumed that PHP 5+ is in use * and that sessions are working. + * + * @return boolean */ public function doEnvironmentChecks() { $this->showMessage( 'config-env-php', phpversion() ); $good = true; + foreach ( $this->envChecks as $check ) { $status = $this->$check(); if ( $status === false ) { $good = false; } } + $this->setVar( '_Environment', $good ); + if ( $good ) { $this->showMessage( 'config-env-good' ); } else { $this->showMessage( 'config-env-bad' ); } + return $good; } + /** + * Set a MW configuration variable, or internal installer configuration variable. + * + * @param $name String + * @param $value Mixed + */ + public function setVar( $name, $value ) { + $this->settings[$name] = $value; + } + /** * Get an MW configuration variable, or internal installer configuration variable. * The defaults come from $GLOBALS (ultimately DefaultSettings.php). * Installer variables are typically prefixed by an underscore. + * + * @param $name String + * @param $default Mixed + * + * @return mixed */ public function getVar( $name, $default = null ) { if ( !isset( $this->settings[$name] ) ) { @@ -363,30 +183,65 @@ abstract class Installer { } else { return $this->settings[$name]; } - } - + } + /** - * Set a MW configuration variable, or internal installer configuration variable. + * Get an instance of DatabaseInstaller for the specified DB type. + * + * @param $type Mixed: DB installer for which is needed, false to use default. + * + * @return DatabaseInstaller */ - public function setVar( $name, $value ) { - $this->settings[$name] = $value; - } + public function getDBInstaller( $type = false ) { + if ( !$type ) { + $type = $this->getVar( 'wgDBtype' ); + } + + $type = strtolower( $type ); + if ( !isset( $this->dbInstallers[$type] ) ) { + $class = ucfirst( $type ). 'Installer'; + $this->dbInstallers[$type] = new $class( $this ); + } + + return $this->dbInstallers[$type]; + } + /** - * Exports all wg* variables stored by the installer into global scope + * Determine if LocalSettings exists. If it does, return an appropriate + * status for whether we should can upgrade or not. + * + * @return Status */ - public function exportVars() { - foreach ( $this->settings as $name => $value ) { - if ( substr( $name, 0, 2 ) == 'wg' ) { - $GLOBALS[$name] = $value; + public function getLocalSettingsStatus() { + global $IP; + + $status = Status::newGood(); + + wfSuppressWarnings(); + $ls = file_exists( "$IP/LocalSettings.php" ); + wfRestoreWarnings(); + + if( $ls ) { + if( $this->getDBInstaller()->needsUpgrade() ) { + $status->warning( 'config-localsettings-upgrade' ); + } + else { + $status->fatal( 'config-localsettings-noupgrade' ); } } - } - + + return $status; + } + /** * Get a fake password for sending back to the user in HTML. * This is a security mechanism to avoid compromise of the password in the * event of session ID compromise. + * + * @param $realPassword String + * + * @return string */ public function getFakePassword( $realPassword ) { return str_repeat( '*', strlen( $realPassword ) ); @@ -395,44 +250,175 @@ 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 */ public function setPassword( $name, $value ) { if ( !preg_match( '/^\*+$/', $value ) ) { $this->setVar( $name, $value ); } + } + + /** + * On POSIX systems return the primary group of the webserver we're running under. + * On other systems just returns null. + * + * This is used to advice the user that he should chgrp his config/data/images directory as the + * webserver user before he can install. + * + * Public because SqliteInstaller needs it, and doesn't subclass Installer. + * + * @return mixed + */ + public static function maybeGetWebserverPrimaryGroup() { + if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) { + # I don't know this, this isn't UNIX. + return null; + } + + # 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']; + + return $group; + } + + /** + * Convert wikitext $text to HTML. + * + * This is potentially error prone since many parser features require a complete + * installed MW database. The solution is to just not use those features when you + * 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 + * whatever, this function is guarded to catch attempted DB access and to present + * some fallback text. + * + * @param $text String + * @param $lineStart Boolean + * @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 ) { + $html = ' ' . htmlspecialchars( $text ); + + if ( !empty( $this->debug ) ) { + $html .= ""; + } + } + + return $html; + } + + /** + * TODO: document + * + * @param DatabaseInstaller $installer + * + * @return Status + */ + public function installDatabase( DatabaseInstaller &$installer ) { + if( !$installer ) { + $type = $this->getVar( 'wgDBtype' ); + $status = Status::newFatal( "config-no-db", $type ); + } else { + $status = $installer->setupDatabase(); + } + + return $status; } - /** Check if we're installing the latest version */ + /** + * TODO: document + * + * @param DatabaseInstaller $installer + * + * @return Status + */ + public function installTables( DatabaseInstaller &$installer ) { + $status = $installer->createTables(); + + if( $status->isOK() ) { + LBFactory::enableBackend(); + } + + return $status; + } + + /** + * TODO: document + * + * @param DatabaseInstaller $installer + * + * @return Status + */ + public function installInterwiki( DatabaseInstaller &$installer ) { + return $installer->populateInterwikiTable(); + } + + /** + * 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; + } + } + } + + /** + * Check if we're installing the latest version. + */ public function envLatestVersion() { global $wgVersion; + $latestInfoUrl = 'http://www.mediawiki.org/w/api.php?action=mwreleases&format=json'; $latestInfo = Http::get( $latestInfoUrl ); + if( !$latestInfo ) { $this->showMessage( 'config-env-latest-can-not-check', $latestInfoUrl ); return; } + $this->setVar( '_ExternalHTTP', true ); $latestInfo = FormatJson::decode($latestInfo); + if ($latestInfo === false || !isset( $latestInfo->mwreleases ) ) { # For when the request is successful but there's e.g. some silly man in # the middle firewall blocking us, e.g. one of those annoying airport ones $this->showMessage( 'config-env-latest-data-invalid', $latestInfoUrl ); return; } + foreach( $latestInfo->mwreleases as $rel ) { - if( isset( $rel->current ) ) + if( isset( $rel->current ) ) { $currentVersion = $rel->version; + } } + if( version_compare( $wgVersion, $currentVersion, '<' ) ) { $this->showMessage( 'config-env-latest-old' ); $this->showHelpBox( 'config-env-latest-help', $wgVersion, $currentVersion ); } elseif( version_compare( $wgVersion, $currentVersion, '>' ) ) { $this->showMessage( 'config-env-latest-new' ); } + $this->showMessage( 'config-env-latest-ok' ); } - /** Environment check for DB types */ + /** + * Environment check for DB types. + */ public function envCheckDB() { global $wgLang; @@ -629,53 +615,9 @@ abstract class Installer { } else { $this->setVar( 'wgDiff3', false ); $this->showMessage( 'config-diff3-bad' ); - } - } - - /** - * Search a path for any of the given executable names. Returns the - * executable name if found. Also checks the version string returned - * by each executable. - * - * @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 path - * 1 => String to compare the output with - * - * If $versionInfo is not false, only executables with a version - * matching $versionInfo[1] will be returned. - */ - public function locateExecutable( $path, $names, $versionInfo = false ) { - if ( !is_array( $names ) ) { - $names = array( $names ); - } - - foreach ( $names as $name ) { - $command = "$path/$name"; - - wfSuppressWarnings(); - $file_exists = file_exists( $command ); - wfRestoreWarnings(); - - if ( $file_exists ) { - if ( !$versionInfo ) { - return $command; - } - - $file = str_replace( '$1', $command, $versionInfo[0] ); - - # Should maybe be wfShellExec( $file), but runs into a ulimit, see - # http://www.mediawiki.org/w/index.php?title=New-installer_issues&diff=prev&oldid=335456 - if ( strstr( `$file`, $versionInfo[1]) !== false ) { - return $command; - } - } - } - - return false; - } - + } + } + /** * Environment check for ImageMagick and GD. */ @@ -768,6 +710,9 @@ abstract class Installer { $this->showMessage( 'config-file-extension', $ext ); } + /** + * TODO: document + */ public function envCheckShellLocale() { # Give up now if we're in safe mode or open_basedir. # It's theoretically possible but tricky to work with. @@ -848,6 +793,9 @@ abstract class Installer { return true; } + /** + * TODO: document + */ public function envCheckUploadsDirectory() { global $IP, $wgServer; @@ -860,10 +808,58 @@ abstract class Installer { } else { $this->showMessage( 'config-uploads-not-safe', $dir ); } - } + } + + /** + * Search a path for any of the given executable names. Returns the + * executable name if found. Also checks the version string returned + * by each executable. + * + * 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 path + * 1 => String to compare the output with + * + * If $versionInfo is not false, only executables with a version + * matching $versionInfo[1] will be returned. + */ + protected function locateExecutable( $path, $names, $versionInfo = false ) { + if ( !is_array( $names ) ) { + $names = array( $names ); + } + foreach ( $names as $name ) { + $command = "$path/$name"; + + wfSuppressWarnings(); + $file_exists = file_exists( $command ); + wfRestoreWarnings(); + + if ( $file_exists ) { + if ( !$versionInfo ) { + return $command; + } + + $file = str_replace( '$1', $command, $versionInfo[0] ); + + # Should maybe be wfShellExec( $file), but runs into a ulimit, see + # http://www.mediawiki.org/w/index.php?title=New-installer_issues&diff=prev&oldid=335456 + if ( strstr( `$file`, $versionInfo[1]) !== false ) { + return $command; + } + } + } + + return false; + } + /** * Checks if scripts located in the given directory can be executed via the given URL. + * + * Used only by environment checks. */ public function dirIsExecutable( $dir, $url ) { $scriptTypes = array( @@ -898,321 +894,6 @@ abstract class Installer { wfRestoreWarnings(); return false; - } - - /** - * Convert wikitext $text to HTML. - * - * This is potentially error prone since many parser features require a complete - * installed MW database. The solution is to just not use those features when you - * 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 - * whatever, this function is guarded to catch attempted DB access and to present - * some fallback text. - * - * @param $text String - * @param $lineStart Boolean - * @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 ) { - $html = ' ' . htmlspecialchars( $text ); - - if ( !empty( $this->debug ) ) { - $html .= ""; - } - } - - return $html; - } - - /** - * Register tag hook below. - */ - public function registerDocLink( &$parser ) { - $parser->setHook( 'doclink', array( $this, 'docLink' ) ); - return true; - } - - /** - * Extension tag hook for a documentation link. - */ - public function docLink( $linkText, $attribs, $parser ) { - $url = $this->getDocUrl( $attribs['href'] ); - return '' . - htmlspecialchars( $linkText ) . - ''; - } - - /** - * Overridden by WebInstaller to provide lastPage parameters. - */ - protected function getDocUrl( $page ) { - return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $attribs['href'] ); - } - - public function findExtensions() { - if( $this->getVar( 'IP' ) === null ) { - return false; - } - - $exts = array(); - $dir = $this->getVar( 'IP' ) . '/extensions'; - $dh = opendir( $dir ); - - while ( ( $file = readdir( $dh ) ) !== false ) { - if( file_exists( "$dir/$file/$file.php" ) ) { - $exts[$file] = null; - } - } - - $this->setVar( '_Extensions', $exts ); - - return $exts; - } - - public function getInstallSteps() { - if( $this->getVar( '_UpgradeDone' ) ) { - $this->installSteps = array( 'localsettings' ); - } - - if( count( $this->getVar( '_Extensions' ) ) ) { - array_unshift( $this->installSteps, 'extensions' ); - } - - return $this->installSteps; - } - - /** - * Actually perform the installation. - * - * @param Array $startCB A callback array for the beginning of each step - * @param Array $endCB A callback array for the end of each step - * - * @return Array of Status objects - */ - public function performInstallation( $startCB, $endCB ) { - $installResults = array(); - $installer = $this->getDBInstaller(); - - foreach( $this->getInstallSteps() as $stepObj ) { - $step = is_array( $stepObj ) ? $stepObj['name'] : $stepObj; - call_user_func_array( $startCB, array( $step ) ); - $status = null; - - # Call our working function - if ( is_array( $stepObj ) ) { - # A custom callaback - $callback = $stepObj['callback']; - $status = call_user_func_array( $callback, array( $installer ) ); - } else { - # Boring implicitly named callback - $func = 'install' . ucfirst( $step ); - $status = $this->{$func}( $installer ); - } - - call_user_func_array( $endCB, array( $step, $status ) ); - $installResults[$step] = $status; - - // If we've hit some sort of fatal, we need to bail. - // Callback already had a chance to do output above. - if( !$status->isOk() ) { - break; - } - - } - - if( $status->isOk() ) { - $this->setVar( '_InstallDone', true ); - } - - return $installResults; - } - - public function installExtensions() { - global $wgHooks, $wgAutoloadClasses; - - $exts = $this->getVar( '_Extensions' ); - $path = $this->getVar( 'IP' ) . '/extensions'; - - foreach( $exts as $e ) { - require( "$path/$e/$e.php" ); - } - - return Status::newGood(); - } - - public function installDatabase( &$installer ) { - if( !$installer ) { - $type = $this->getVar( 'wgDBtype' ); - $status = Status::newFatal( "config-no-db", $type ); - } else { - $status = $installer->setupDatabase(); - } - - return $status; - } - - public function installTables( &$installer ) { - $status = $installer->createTables(); - - if( $status->isOK() ) { - LBFactory::enableBackend(); - } - - return $status; - } - - public function installInterwiki( &$installer ) { - return $installer->populateInterwikiTable(); - } - - public function installSecretKey() { - if ( wfIsWindows() ) { - $file = null; - } else { - wfSuppressWarnings(); - $file = fopen( "/dev/urandom", "r" ); - wfRestoreWarnings(); - } - - $status = Status::newGood(); - - if ( $file ) { - $secretKey = bin2hex( fread( $file, 32 ) ); - fclose( $file ); - } else { - $secretKey = ''; - - for ( $i=0; $i<8; $i++ ) { - $secretKey .= dechex(mt_rand(0, 0x7fffffff)); - } - - $status->warning( 'config-insecure-secretkey' ); - } - - $this->setVar( 'wgSecretKey', $secretKey ); - - return $status; - } - - public function installSysop() { - $name = $this->getVar( '_AdminName' ); - $user = User::newFromName( $name ); - - if ( !$user ) { - // we should've validated this earlier anyway! - return Status::newFatal( 'config-admin-error-user', $name ); - } - - if ( $user->idForName() == 0 ) { - $user->addToDatabase(); - - try { - $user->setPassword( $this->getVar( '_AdminPassword' ) ); - } catch( PasswordError $pwe ) { - return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() ); - } - - $user->addGroup( 'sysop' ); - $user->addGroup( 'bureaucrat' ); - $user->saveSettings(); - } - - return Status::newGood(); - } - - /** - * Determine if LocalSettings exists. If it does, return an appropriate - * status for whether we should can upgrade or not. - * @return Status - */ - public function getLocalSettingsStatus() { - global $IP; - - $status = Status::newGood(); - - wfSuppressWarnings(); - $ls = file_exists( "$IP/LocalSettings.php" ); - wfRestoreWarnings(); - - if( $ls ) { - if( $this->getDBInstaller()->needsUpgrade() ) { - $status->warning( 'config-localsettings-upgrade' ); - } - else { - $status->fatal( 'config-localsettings-noupgrade' ); - } - } - - return $status; - } - - /** - * On POSIX systems return the primary group of the webserver we're running under. - * On other systems just returns null. - * - * This is used to advice the user that he should chgrp his config/data/images directory as the - * webserver user before he can install. - * - * Public because SqliteInstaller needs it, and doesn't subclass Installer. - * - * @return String - */ - public static function maybeGetWebserverPrimaryGroup() { - if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) { - # I don't know this, this isn't UNIX. - return null; - } - - # 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']; - - return $group; - } - - /** - * Override the necessary bits of the config to run an installation. - */ - public static function overrideConfig() { - define( 'MW_NO_SESSION', 1 ); - - // Don't access the database - $GLOBALS['wgUseDatabaseMessages'] = false; - // Debug-friendly - $GLOBALS['wgShowExceptionDetails'] = true; - // Don't break forms - $GLOBALS['wgExternalLinkTarget'] = '_blank'; - - // Extended debugging. Maybe disable before release? - $GLOBALS['wgShowSQLErrors'] = true; - $GLOBALS['wgShowDBErrorBacktrace'] = true; - } - - /** - * Add an installation step following the given step. - * - * @param $findStep String the step to find. Use NULL to put the step at the beginning. - * @param $callback array - */ - public function addInstallStepFollowing( $findStep, $callback ) { - $where = 0; - - if( $findStep !== null ) { - $where = array_search( $findStep, $this->installSteps ); - } - - array_splice( $this->installSteps, $where, 0, $callback ); - } - + } + } \ No newline at end of file diff --git a/includes/installer/WebInstaller.php b/includes/installer/WebInstaller.php index a336b2b4bb..b18536c902 100644 --- a/includes/installer/WebInstaller.php +++ b/includes/installer/WebInstaller.php @@ -1,6 +1,16 @@ pageSequence as $id => $pageName ) { $happy = !empty( $this->happyPages[$id] ); - $s .= $this->getPageListItem( $pageName, - $happy || $lastHappy == $id - 1, $currentPageName ); + $s .= $this->getPageListItem( + $pageName, + $happy || $lastHappy == $id - 1, + $currentPageName + ); if ( $happy ) { $lastHappy = $id; @@ -930,9 +944,11 @@ class WebInstaller extends Installer { */ public function getDocUrl( $page ) { $url = "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page ); + if ( in_array( $this->currentPageName, $this->pageSequence ) ) { $url .= '&lastPage=' . urlencode( $this->currentPageName ); } + return $url; } diff --git a/includes/installer/WebInstallerPage.php b/includes/installer/WebInstallerPage.php index 640e6f4311..b6b0266254 100644 --- a/includes/installer/WebInstallerPage.php +++ b/includes/installer/WebInstallerPage.php @@ -595,14 +595,17 @@ class WebInstaller_Options extends WebInstallerPage { ); $extensions = $this->parent->findExtensions(); + if( $extensions ) { $extHtml = $this->parent->getFieldsetStart( 'config-extensions' ); - foreach( array_keys($extensions) as $ext ) { + + foreach( $extensions as $ext ) { $extHtml .= $this->parent->getCheckBox( array( 'var' => "ext-$ext", 'rawtext' => $ext, ) ); } + $extHtml .= $this->parent->getHelpBox( 'config-extensions-help' ) . $this->parent->getFieldsetEnd(); $this->addHTML( $extHtml ); -- 2.20.1