From 44dd1794a1cf89f2c12b11024605120bf67c090a Mon Sep 17 00:00:00 2001 From: Chad Horohoe Date: Fri, 21 Jan 2011 15:27:16 +0000 Subject: [PATCH] Partially revert r69738 (splitting Installer/CoreInstaller). As discussed on CR, this probably wasn't the best route to go with this code. We can figure that out sometime in 1.18 --- config/index.php | 2 +- includes/AutoLoader.php | 1 - includes/installer/CliInstaller.php | 2 +- includes/installer/CoreInstaller.php | 591 ----------------------- includes/installer/DatabaseInstaller.php | 2 +- includes/installer/Installer.php | 565 ++++++++++++++++++++++ includes/installer/WebInstaller.php | 2 +- maintenance/install.php | 2 +- 8 files changed, 570 insertions(+), 597 deletions(-) delete mode 100644 includes/installer/CoreInstaller.php diff --git a/config/index.php b/config/index.php index 0256cfac96..c00a0d0135 100644 --- a/config/index.php +++ b/config/index.php @@ -5,7 +5,7 @@ * @file */ -define( 'MW_CONFIG_CALLBACK', 'CoreInstaller::overrideConfig' ); +define( 'MW_CONFIG_CALLBACK', 'Installer::overrideConfig' ); define( 'MEDIAWIKI_INSTALL', true ); chdir( dirname( dirname( __FILE__ ) ) ); diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 0764d84a40..49aec601e1 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -453,7 +453,6 @@ $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 af50f80515..455a45fabf 100644 --- a/includes/installer/CliInstaller.php +++ b/includes/installer/CliInstaller.php @@ -12,7 +12,7 @@ * @ingroup Deployment * @since 1.17 */ -class CliInstaller extends CoreInstaller { +class CliInstaller extends Installer { private $optionMap = array( 'dbtype' => 'wgDBtype', diff --git a/includes/installer/CoreInstaller.php b/includes/installer/CoreInstaller.php deleted file mode 100644 index 1a77f4c72f..0000000000 --- a/includes/installer/CoreInstaller.php +++ /dev/null @@ -1,591 +0,0 @@ - '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' => '', - '_UpgradeKeySupplied' => false, - '_ExistingDBSettings' => false, - ); - - /** - * The actual list of installation steps. This will be initialized by getInstallSteps() - * - * @var array - */ - private $installSteps = array(); - - /** - * Extra steps for installation, for things like DatabaseInstallers to modify - * - * @var array - */ - protected $extraInstallSteps = array(); - - /** - * 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( - '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', - ), - 'none' => array( - 'url' => '', - 'icon' => '', - 'text' => '' - ), - 'cc-choose' => array( - // 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'; - - /** - * Supported language codes for Mailman - */ - protected $mediaWikiAnnounceLanguages = array( - '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' - ); - - /** - * TODO: document - * - * @param $status Status - */ - public abstract function showStatusMessage( Status $status ); - - /** - * Constructor, always call this from child classes. - */ - public function __construct() { - parent::__construct(); - - global $wgExtensionMessagesFiles, $wgUser, $wgHooks; - - // Load the installer's i18n file. - $wgExtensionMessagesFiles['MediawikiInstaller'] = - dirname( __FILE__ ) . '/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 ( 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->parserTitle = Title::newFromText( 'Installer' ); - $this->parserOptions = new ParserOptions; // language will be wrong :( - $this->parserOptions->setEditSection( false ); - } - - /** - * Register tag hook below. - * - * @todo Move this to WebInstaller with the two things below? - * - * @param $parser Parser - */ - public function registerDocLink( Parser &$parser ) { - $parser->setHook( 'doclink', array( $this, 'docLink' ) ); - return true; - } - - /** - * ParserOptions are constructed before we determined the language, so fix it - */ - public function setParserLanguage( $lang ) { - $this->parserOptions->setTargetLanguage( $lang ); - $this->parserOptions->setUserLang( $lang->getCode() ); - } - - /** - * 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( $page ); - } - - /** - * 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; - } - } - - return $exts; - } - - /** - * Installs the auto-detected extensions. - * - * @return Status - */ - protected function includeExtensions() { - $exts = $this->getVar( '_Extensions' ); - $path = $this->getVar( 'IP' ) . '/extensions'; - - foreach( $exts as $e ) { - require( "$path/$e/$e.php" ); - } - - return Status::newGood(); - } - - /** - * Get an array of install steps. Should always be in the format of - * array( - * 'name' => 'someuniquename', - * 'callback' => array( $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 - * @return array - */ - protected function getInstallSteps( DatabaseInstaller &$installer ) { - $coreInstallSteps = array( - array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ), - array( 'name' => 'tables', 'callback' => array( $this, 'installTables' ) ), - array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ), - array( 'name' => 'secretkey', 'callback' => array( $this, 'generateSecretKey' ) ), - array( 'name' => 'upgradekey', 'callback' => array( $this, 'generateUpgradeKey' ) ), - array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ), - array( 'name' => 'mainpage', 'callback' => array( $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 ) { - $this->installSteps[] = $step; - if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) { - $this->installSteps = array_merge( - $this->installSteps, - $this->extraInstallSteps[ $step['name'] ] - ); - } - } - - // Prepend any steps that want to be at the 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' ) ) ) { - array_unshift( $this->installSteps, - array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) ) - ); - } - return $this->installSteps; - } - - /** - * Actually perform the installation. - * - * @param $startCB A callback array for the beginning of each step - * @param $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(); - $installer->preInstall(); - $steps = $this->getInstallSteps( $installer ); - foreach( $steps as $stepObj ) { - $name = $stepObj['name']; - call_user_func_array( $startCB, array( $name ) ); - - // Perform the callback step - $status = call_user_func_array( $stepObj['callback'], array( &$installer ) ); - - // Output and save the results - call_user_func_array( $endCB, array( $name, $status ) ); - $installResults[$name] = $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; - } - - /** - * Generate $wgSecretKey. Will warn if we had to use mt_rand() instead of - * /dev/urandom - * - * @return Status - */ - protected function generateSecretKey() { - return $this->generateSecret( 'wgSecretKey' ); - } - - /** - * Generate a secret value for a variable using either - * /dev/urandom or mt_rand() Produce a warning in the later case. - * - * @return Status - */ - protected function generateSecret( $secretName, $length = 64 ) { - if ( wfIsWindows() ) { - $file = null; - } else { - wfSuppressWarnings(); - $file = fopen( "/dev/urandom", "r" ); - wfRestoreWarnings(); - } - - $status = Status::newGood(); - - if ( $file ) { - $secretKey = bin2hex( fread( $file, $length / 2 ) ); - fclose( $file ); - } else { - $secretKey = ''; - - for ( $i = 0; $i < $length / 8; $i++ ) { - $secretKey .= dechex( mt_rand( 0, 0x7fffffff ) ); - } - - $status->warning( 'config-insecure-secret', '$' . $secretName ); - } - - $this->setVar( $secretName, $secretKey ); - - return $status; - } - - /** - * Generate a default $wgUpgradeKey. Will warn if we had to use - * mt_rand() instead of /dev/urandom - * - * @return Status - */ - public function generateUpgradeKey() { - if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) { - return $this->generateSecret( 'wgUpgradeKey', 16 ); - } - } - - /** - * Create the first user account, grant it sysop and bureaucrat rights - * - * @return Status - */ - protected function createSysop() { - $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' ); - if( $this->getVar( '_AdminEmail' ) ) { - $user->setEmail( $this->getVar( '_AdminEmail' ) ); - } - $user->saveSettings(); - } - $status = Status::newGood(); - - if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) { - $this->subscribeToMediaWikiAnnounce( $status ); - } - - return $status; - } - - private function subscribeToMediaWikiAnnounce( Status $s ) { - $params = array( - 'email' => $this->getVar( '_AdminEmail' ), - 'language' => 'en', - '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 ) ) { - $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR - $params['language'] = $myLang; - } - - $res = Http::post( $this->mediaWikiAnnounceUrl, array( 'postData' => $params ) ); - if( !$res ) { - $s->warning( 'config-install-subscribe-fail' ); - } - } - - /** - * Insert Main Page with default content. - * - * @return Status - */ - protected function createMainpage( DatabaseInstaller &$installer ) { - $status = Status::newGood(); - 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 - $status->fatal( 'config-install-mainpage-failed', $e->getMessage() ); - } - - return $status; - } - - /** - * 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 - $GLOBALS['wgShowSQLErrors'] = true; - $GLOBALS['wgShowDBErrorBacktrace'] = true; - - // Allow multiple ob_flush() calls - $GLOBALS['wgDisableOutputCompression'] = true; - - // Use a sensible cookie prefix (not my_wiki) - $GLOBALS['wgCookiePrefix'] = 'mw_installer'; - - // Some of the environment checks make shell requests, remove limits - $GLOBALS['wgMaxShellMemory'] = 0; - } - - /** - * 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 - */ - public function addInstallStep( $callback, $findStep = 'BEGINNING' ) { - $this->extraInstallSteps[$findStep][] = $callback; - } -} diff --git a/includes/installer/DatabaseInstaller.php b/includes/installer/DatabaseInstaller.php index 51986cac16..dbdbccff0e 100644 --- a/includes/installer/DatabaseInstaller.php +++ b/includes/installer/DatabaseInstaller.php @@ -19,7 +19,7 @@ abstract class DatabaseInstaller { * * TODO: naming this parent is confusing, 'installer' would be clearer. * - * @var CoreInstaller + * @var Installer */ public $parent; diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php index 497bc85f7b..25e9fb8925 100644 --- a/includes/installer/Installer.php +++ b/includes/installer/Installer.php @@ -104,6 +104,183 @@ abstract class Installer { 'envCheckLibicu' ); + /** + * 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. + * + * @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', + 'wgUpgradeKey', + 'wgDefaultSkin', + ); + + /** + * 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. + * + * @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' => '', + '_UpgradeKeySupplied' => false, + '_ExistingDBSettings' => false, + ); + + /** + * The actual list of installation steps. This will be initialized by getInstallSteps() + * + * @var array + */ + private $installSteps = array(); + + /** + * Extra steps for installation, for things like DatabaseInstallers to modify + * + * @var array + */ + protected $extraInstallSteps = array(); + + /** + * 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( + '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', + ), + 'none' => array( + 'url' => '', + 'icon' => '', + 'text' => '' + ), + 'cc-choose' => array( + // 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'; + + /** + * Supported language codes for Mailman + */ + protected $mediaWikiAnnounceLanguages = array( + '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(). @@ -112,13 +289,59 @@ abstract class Installer { */ public abstract function showMessage( $msg /*, ... */ ); + /** + * Show a message to the installing user by using a Status object + * @param $status Status + */ + public abstract function showStatusMessage( Status $status ); + /** * Constructor, always call this from child classes. */ public function __construct() { + global $wgExtensionMessagesFiles, $wgUser, $wgHooks; + // Disable the i18n cache and LoadBalancer Language::getLocalisationCache()->disableBackend(); LBFactory::disableBackend(); + + // Load the installer's i18n file. + $wgExtensionMessagesFiles['MediawikiInstaller'] = + dirname( __FILE__ ) . '/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 ( 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->parserTitle = Title::newFromText( 'Installer' ); + $this->parserOptions = new ParserOptions; // language will be wrong :( + $this->parserOptions->setEditSection( false ); } /** @@ -907,4 +1130,346 @@ abstract class Installer { return false; } + /** + * Register tag hook below. + * + * @todo Move this to WebInstaller with the two things below? + * + * @param $parser Parser + */ + public function registerDocLink( Parser &$parser ) { + $parser->setHook( 'doclink', array( $this, 'docLink' ) ); + return true; + } + + /** + * ParserOptions are constructed before we determined the language, so fix it + */ + public function setParserLanguage( $lang ) { + $this->parserOptions->setTargetLanguage( $lang ); + $this->parserOptions->setUserLang( $lang->getCode() ); + } + + /** + * 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( $page ); + } + + /** + * 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; + } + } + + return $exts; + } + + /** + * Installs the auto-detected extensions. + * + * @return Status + */ + protected function includeExtensions() { + $exts = $this->getVar( '_Extensions' ); + $path = $this->getVar( 'IP' ) . '/extensions'; + + foreach( $exts as $e ) { + require( "$path/$e/$e.php" ); + } + + return Status::newGood(); + } + + /** + * Get an array of install steps. Should always be in the format of + * array( + * 'name' => 'someuniquename', + * 'callback' => array( $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 + * @return array + */ + protected function getInstallSteps( DatabaseInstaller &$installer ) { + $coreInstallSteps = array( + array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ), + array( 'name' => 'tables', 'callback' => array( $this, 'installTables' ) ), + array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ), + array( 'name' => 'secretkey', 'callback' => array( $this, 'generateSecretKey' ) ), + array( 'name' => 'upgradekey', 'callback' => array( $this, 'generateUpgradeKey' ) ), + array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ), + array( 'name' => 'mainpage', 'callback' => array( $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 ) { + $this->installSteps[] = $step; + if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) { + $this->installSteps = array_merge( + $this->installSteps, + $this->extraInstallSteps[ $step['name'] ] + ); + } + } + + // Prepend any steps that want to be at the 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' ) ) ) { + array_unshift( $this->installSteps, + array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) ) + ); + } + return $this->installSteps; + } + + /** + * Actually perform the installation. + * + * @param $startCB A callback array for the beginning of each step + * @param $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(); + $installer->preInstall(); + $steps = $this->getInstallSteps( $installer ); + foreach( $steps as $stepObj ) { + $name = $stepObj['name']; + call_user_func_array( $startCB, array( $name ) ); + + // Perform the callback step + $status = call_user_func_array( $stepObj['callback'], array( &$installer ) ); + + // Output and save the results + call_user_func_array( $endCB, array( $name, $status ) ); + $installResults[$name] = $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; + } + + /** + * Generate $wgSecretKey. Will warn if we had to use mt_rand() instead of + * /dev/urandom + * + * @return Status + */ + protected function generateSecretKey() { + return $this->generateSecret( 'wgSecretKey' ); + } + + /** + * Generate a secret value for a variable using either + * /dev/urandom or mt_rand() Produce a warning in the later case. + * + * @return Status + */ + protected function generateSecret( $secretName, $length = 64 ) { + if ( wfIsWindows() ) { + $file = null; + } else { + wfSuppressWarnings(); + $file = fopen( "/dev/urandom", "r" ); + wfRestoreWarnings(); + } + + $status = Status::newGood(); + + if ( $file ) { + $secretKey = bin2hex( fread( $file, $length / 2 ) ); + fclose( $file ); + } else { + $secretKey = ''; + + for ( $i = 0; $i < $length / 8; $i++ ) { + $secretKey .= dechex( mt_rand( 0, 0x7fffffff ) ); + } + + $status->warning( 'config-insecure-secret', '$' . $secretName ); + } + + $this->setVar( $secretName, $secretKey ); + + return $status; + } + + /** + * Generate a default $wgUpgradeKey. Will warn if we had to use + * mt_rand() instead of /dev/urandom + * + * @return Status + */ + public function generateUpgradeKey() { + if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) { + return $this->generateSecret( 'wgUpgradeKey', 16 ); + } + } + + /** + * Create the first user account, grant it sysop and bureaucrat rights + * + * @return Status + */ + protected function createSysop() { + $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' ); + if( $this->getVar( '_AdminEmail' ) ) { + $user->setEmail( $this->getVar( '_AdminEmail' ) ); + } + $user->saveSettings(); + } + $status = Status::newGood(); + + if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) { + $this->subscribeToMediaWikiAnnounce( $status ); + } + + return $status; + } + + private function subscribeToMediaWikiAnnounce( Status $s ) { + $params = array( + 'email' => $this->getVar( '_AdminEmail' ), + 'language' => 'en', + '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 ) ) { + $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR + $params['language'] = $myLang; + } + + $res = Http::post( $this->mediaWikiAnnounceUrl, array( 'postData' => $params ) ); + if( !$res ) { + $s->warning( 'config-install-subscribe-fail' ); + } + } + + /** + * Insert Main Page with default content. + * + * @return Status + */ + protected function createMainpage( DatabaseInstaller &$installer ) { + $status = Status::newGood(); + 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 + $status->fatal( 'config-install-mainpage-failed', $e->getMessage() ); + } + + return $status; + } + + /** + * 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 + $GLOBALS['wgShowSQLErrors'] = true; + $GLOBALS['wgShowDBErrorBacktrace'] = true; + + // Allow multiple ob_flush() calls + $GLOBALS['wgDisableOutputCompression'] = true; + + // Use a sensible cookie prefix (not my_wiki) + $GLOBALS['wgCookiePrefix'] = 'mw_installer'; + + // Some of the environment checks make shell requests, remove limits + $GLOBALS['wgMaxShellMemory'] = 0; + } + + /** + * 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 + */ + public function addInstallStep( $callback, $findStep = 'BEGINNING' ) { + $this->extraInstallSteps[$findStep][] = $callback; + } } diff --git a/includes/installer/WebInstaller.php b/includes/installer/WebInstaller.php index 87a879aeaf..9f43f04832 100644 --- a/includes/installer/WebInstaller.php +++ b/includes/installer/WebInstaller.php @@ -12,7 +12,7 @@ * @ingroup Deployment * @since 1.17 */ -class WebInstaller extends CoreInstaller { +class WebInstaller extends Installer { /** * @var WebInstallerOutput diff --git a/maintenance/install.php b/maintenance/install.php index 09a7638764..5504ed3b3e 100644 --- a/maintenance/install.php +++ b/maintenance/install.php @@ -20,7 +20,7 @@ * @see wfWaitForSlaves() */ -define( 'MW_CONFIG_CALLBACK', 'CoreInstaller::overrideConfig' ); +define( 'MW_CONFIG_CALLBACK', 'Installer::overrideConfig' ); require_once( dirname( dirname( __FILE__ ) )."/maintenance/Maintenance.php" ); -- 2.20.1