X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Finstaller%2FMysqlInstaller.php;h=7585fe7a5949c34bce465bfce53f76ea1d367b1c;hb=0d43066e1871e5ca2b2cda196daf0785e0f9c320;hp=f0d6035b8d0feda5b15dda36630146679900f38f;hpb=f32be895b8301b943bf242c30d9603039adbc25a;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/installer/MysqlInstaller.php b/includes/installer/MysqlInstaller.php index f0d6035b8d..7585fe7a59 100644 --- a/includes/installer/MysqlInstaller.php +++ b/includes/installer/MysqlInstaller.php @@ -1,6 +1,19 @@ 'InnoDB', '_MysqlCharset' => 'binary', + '_InstallUser' => 'root', ); - var $supportedEngines = array( 'InnoDB', 'MyISAM' ); + public $supportedEngines = array( 'InnoDB', 'MyISAM' ); - var $minimumVersion = '4.0.14'; + public $minimumVersion = '5.0.2'; - var $webUserPrivs = array( + public $webUserPrivs = array( 'DELETE', 'INSERT', 'SELECT', @@ -28,44 +42,60 @@ class MysqlInstaller extends InstallerDBType { 'CREATE TEMPORARY TABLES', ); - function getName() { + /** + * @return string + */ + public function getName() { return 'mysql'; } - - function isCompiled() { - return $this->checkExtension( 'mysql' ); + + public function __construct( $parent ) { + parent::__construct( $parent ); + } + + /** + * @return Bool + */ + public function isCompiled() { + return self::checkExtension( 'mysql' ); } - function getGlobalDefaults() { + /** + * @return array + */ + public function getGlobalDefaults() { return array(); } - function getConnectForm() { + /** + * @return string + */ + public function getConnectForm() { return - $this->getTextBox( 'wgDBserver', 'config-db-host' ) . - $this->parent->getHelpBox( 'config-db-host-help' ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', array(), wfMsg( 'config-db-wiki-settings' ) ) . - $this->getTextBox( 'wgDBname', 'config-db-name' ) . - $this->parent->getHelpBox( 'config-db-name-help' ) . - $this->getTextBox( 'wgDBprefix', 'config-db-prefix' ) . - $this->parent->getHelpBox( 'config-db-prefix-help' ) . - Xml::closeElement( 'fieldset' ) . + $this->getTextBox( 'wgDBserver', 'config-db-host', array(), $this->parent->getHelpBox( 'config-db-host-help' ) ) . + Html::openElement( 'fieldset' ) . + Html::element( 'legend', array(), wfMsg( 'config-db-wiki-settings' ) ) . + $this->getTextBox( 'wgDBname', 'config-db-name', array( 'dir' => 'ltr' ), $this->parent->getHelpBox( 'config-db-name-help' ) ) . + $this->getTextBox( 'wgDBprefix', 'config-db-prefix', array( 'dir' => 'ltr' ), $this->parent->getHelpBox( 'config-db-prefix-help' ) ) . + Html::closeElement( 'fieldset' ) . $this->getInstallUserBox(); } - function submitConnectForm() { - // Get variables from the request + public function submitConnectForm() { + // Get variables from the request. $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBname', 'wgDBprefix' ) ); - // Validate them + // Validate them. $status = Status::newGood(); + if ( !strlen( $newValues['wgDBserver'] ) ) { + $status->fatal( 'config-missing-db-host' ); + } if ( !strlen( $newValues['wgDBname'] ) ) { $status->fatal( 'config-missing-db-name' ); - } elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) { + } elseif ( !preg_match( '/^[a-z0-9_-]+$/i', $newValues['wgDBname'] ) ) { $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] ); } - if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBprefix'] ) ) { + if ( !preg_match( '/^[a-z0-9_-]*$/i', $newValues['wgDBprefix'] ) ) { $status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] ); } if ( !$status->isOK() ) { @@ -83,6 +113,9 @@ class MysqlInstaller extends InstallerDBType { if ( !$status->isOK() ) { return $status; } + /** + * @var $conn DatabaseBase + */ $conn = $status->value; // Check version @@ -94,38 +127,46 @@ class MysqlInstaller extends InstallerDBType { return $status; } - function getConnection() { + /** + * @return Status + */ + public function openConnection() { $status = Status::newGood(); try { - $this->db = new DatabaseMysql( + $db = new DatabaseMysql( $this->getVar( 'wgDBserver' ), $this->getVar( '_InstallUser' ), $this->getVar( '_InstallPassword' ), false, false, - 0, + 0, $this->getVar( 'wgDBprefix' ) ); - $status->value = $this->db; - return $status; + $status->value = $db; } catch ( DBConnectionError $e ) { $status->fatal( 'config-connection-error', $e->getMessage() ); } return $status; } - function doUpgrade() { + public function preUpgrade() { + global $wgDBuser, $wgDBpassword; + $status = $this->getConnection(); if ( !$status->isOK() ) { $this->parent->showStatusError( $status ); return; } + /** + * @var $conn DatabaseBase + */ $conn = $status->value; + $conn->selectDB( $this->getVar( 'wgDBname' ) ); # Determine existing default character set - if ( $conn->tableExists( "revision" ) ) { - $revision = $conn->escapeLike( $this->getVar( 'wgDBprefix' ) . 'revision' ); - $res = $conn->query( "SHOW TABLE STATUS LIKE '$revision'" ); + if ( $conn->tableExists( "revision", __METHOD__ ) ) { + $revision = $conn->buildLike( $this->getVar( 'wgDBprefix' ) . 'revision' ); + $res = $conn->query( "SHOW TABLE STATUS $revision", __METHOD__ ); $row = $conn->fetchObject( $res ); if ( !$row ) { $this->parent->showMessage( 'config-show-table-status' ); @@ -133,11 +174,11 @@ class MysqlInstaller extends InstallerDBType { $existingEngine = false; } else { if ( preg_match( '/^latin1/', $row->Collation ) ) { - $existingSchema = 'mysql4'; + $existingSchema = 'latin1'; } elseif ( preg_match( '/^utf8/', $row->Collation ) ) { - $existingSchema = 'mysql5'; + $existingSchema = 'utf8'; } elseif ( preg_match( '/^binary/', $row->Collation ) ) { - $existingSchema = 'mysql5-binary'; + $existingSchema = 'binary'; } else { $existingSchema = false; $this->parent->showMessage( 'config-unknown-collation' ); @@ -148,30 +189,39 @@ class MysqlInstaller extends InstallerDBType { $existingEngine = $row->Type; } } + } else { + $existingSchema = false; + $existingEngine = false; + } + + if ( $existingSchema && $existingSchema != $this->getVar( '_MysqlCharset' ) ) { + $this->setVar( '_MysqlCharset', $existingSchema ); + } + if ( $existingEngine && $existingEngine != $this->getVar( '_MysqlEngine' ) ) { + $this->setVar( '_MysqlEngine', $existingEngine ); } - - // TODO + + # Normal user and password are selected after this step, so for now + # just copy these two + $wgDBuser = $this->getVar( '_InstallUser' ); + $wgDBpassword = $this->getVar( '_InstallPassword' ); } /** * Get a list of storage engines that are available and supported + * + * @return array */ - function getEngines() { - $engines = array( 'InnoDB', 'MyISAM' ); + public function getEngines() { $status = $this->getConnection(); - if ( !$status->isOK() ) { - return $engines; - } - $conn = $status->value; - $version = $conn->getServerVersion(); - if ( version_compare( $version, "4.1.2", "<" ) ) { - // No SHOW ENGINES in this version - return $engines; - } + /** + * @var $conn DatabaseBase + */ + $conn = $status->value; $engines = array(); - $res = $conn->query( 'SHOW ENGINES' ); + $res = $conn->query( 'SHOW ENGINES', __METHOD__ ); foreach ( $res as $row ) { if ( $row->Support == 'YES' || $row->Support == 'DEFAULT' ) { $engines[] = $row->Engine; @@ -183,47 +233,40 @@ class MysqlInstaller extends InstallerDBType { /** * Get a list of character sets that are available and supported + * + * @return array */ - function getCharsets() { - $status = $this->getConnection(); - $mysql5 = array( 'binary', 'utf8' ); - $mysql4 = array( 'mysql4' ); - if ( !$status->isOK() ) { - return $mysql5; - } - if ( version_compare( $status->value->getServerVersion(), '4.1.0', '>=' ) ) { - return $mysql5; - } - return $mysql4; + public function getCharsets() { + return array( 'binary', 'utf8' ); } /** * Return true if the install user can create accounts + * + * @return bool */ - function canCreateAccounts() { + public function canCreateAccounts() { $status = $this->getConnection(); if ( !$status->isOK() ) { return false; } + /** + * @var $conn DatabaseBase + */ $conn = $status->value; - // Check version, need INFORMATION_SCHEMA and CREATE USER - if ( version_compare( $conn->getServerVersion(), '5.0.2', '<' ) ) { - return false; - } - // Get current account name $currentName = $conn->selectField( '', 'CURRENT_USER()', '', __METHOD__ ); $parts = explode( '@', $currentName ); if ( count( $parts ) != 2 ) { return false; } - $quotedUser = $conn->addQuotes( $parts[0] ) . + $quotedUser = $conn->addQuotes( $parts[0] ) . '@' . $conn->addQuotes( $parts[1] ); // The user needs to have INSERT on mysql.* to be able to CREATE USER // The grantee will be double-quoted in this query, as required - $res = $conn->select( 'INFORMATION_SCHEMA.USER_PRIVILEGES', '*', + $res = $conn->select( 'INFORMATION_SCHEMA.USER_PRIVILEGES', '*', array( 'GRANTEE' => $quotedUser ), __METHOD__ ); $insertMysql = false; $grantOptions = array_flip( $this->webUserPrivs ); @@ -239,7 +282,7 @@ class MysqlInstaller extends InstallerDBType { // Check for DB-specific privs for mysql.* if ( !$insertMysql ) { $row = $conn->selectRow( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*', - array( + array( 'GRANTEE' => $quotedUser, 'TABLE_SCHEMA' => 'mysql', 'PRIVILEGE_TYPE' => 'INSERT', @@ -254,7 +297,7 @@ class MysqlInstaller extends InstallerDBType { } // Check for DB-level grant options - $res = $conn->select( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*', + $res = $conn->select( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*', array( 'GRANTEE' => $quotedUser, 'IS_GRANTABLE' => 1, @@ -272,7 +315,10 @@ class MysqlInstaller extends InstallerDBType { return true; } - function getSettingsForm() { + /** + * @return string + */ + public function getSettingsForm() { if ( $this->canCreateAccounts() ) { $noCreateMsg = false; } else { @@ -286,13 +332,33 @@ class MysqlInstaller extends InstallerDBType { if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) { $this->setVar( '_MysqlEngine', reset( $engines ) ); } + + $s .= Xml::openElement( 'div', array( + 'id' => 'dbMyisamWarning' + )); + $s .= $this->parent->getWarningBox( wfMsg( 'config-mysql-myisam-dep' ) ); + $s .= Xml::closeElement( 'div' ); + + if( $this->getVar( '_MysqlEngine' ) != 'MyISAM' ) { + $s .= Xml::openElement( 'script', array( 'type' => 'text/javascript' ) ); + $s .= '$(\'#dbMyisamWarning\').hide();'; + $s .= Xml::closeElement( 'script' ); + } + if ( count( $engines ) >= 2 ) { $s .= $this->getRadioSet( array( - 'var' => '_MysqlEngine', - 'label' => 'config-mysql-engine', - 'itemLabelPrefix' => 'config-mysql-', - 'values' => $engines - )); + 'var' => '_MysqlEngine', + 'label' => 'config-mysql-engine', + 'itemLabelPrefix' => 'config-mysql-', + 'values' => $engines, + 'itemAttribs' => array( + 'MyISAM' => array( + 'class' => 'showHideRadio', + 'rel' => 'dbMyisamWarning'), + 'InnoDB' => array( + 'class' => 'hideShowRadio', + 'rel' => 'dbMyisamWarning') + ))); $s .= $this->parent->getHelpBox( 'config-mysql-engine-help' ); } @@ -316,8 +382,11 @@ class MysqlInstaller extends InstallerDBType { return $s; } - function submitSettingsForm() { - $newValues = $this->setVarsFromRequest( array( '_MysqlEngine', '_MysqlCharset' ) ); + /** + * @return Status + */ + public function submitSettingsForm() { + $this->setVarsFromRequest( array( '_MysqlEngine', '_MysqlCharset' ) ); $status = $this->submitWebUserBox(); if ( !$status->isOK() ) { return $status; @@ -335,13 +404,13 @@ class MysqlInstaller extends InstallerDBType { if ( !$create ) { // Test the web account try { - $webConn = new Database( + new DatabaseMysql( $this->getVar( 'wgDBserver' ), $this->getVar( 'wgDBuser' ), $this->getVar( 'wgDBpassword' ), false, false, - 0, + 0, $this->getVar( 'wgDBprefix' ) ); } catch ( DBConnectionError $e ) { @@ -362,7 +431,19 @@ class MysqlInstaller extends InstallerDBType { return Status::newGood(); } - function setupDatabase() { + public function preInstall() { + # Add our user callback to installSteps, right before the tables are created. + $callback = array( + 'name' => 'user', + 'callback' => array( $this, 'setupUser' ), + ); + $this->parent->addInstallStep( $callback, 'tables' ); + } + + /** + * @return Status + */ + public function setupDatabase() { $status = $this->getConnection(); if ( !$status->isOK() ) { return $status; @@ -370,35 +451,174 @@ class MysqlInstaller extends InstallerDBType { $conn = $status->value; $dbName = $this->getVar( 'wgDBname' ); if( !$conn->selectDB( $dbName ) ) { - $conn->query( "CREATE DATABASE `$dbName`" ); + $conn->query( "CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ), __METHOD__ ); $conn->selectDB( $dbName ); } + $this->setupSchemaVars(); return $status; } - function createTables() { - global $IP; + /** + * @return Status + */ + public function setupUser() { + $dbUser = $this->getVar( 'wgDBuser' ); + if( $dbUser == $this->getVar( '_InstallUser' ) ) { + return Status::newGood(); + } $status = $this->getConnection(); if ( !$status->isOK() ) { return $status; } - $this->db->selectDB( $this->getVar( 'wgDBname' ) ); - if ( !$this->db->sourceFile( "$IP/maintenance/tables.sql" ) ) { - //@todo + + $this->setupSchemaVars(); + $dbName = $this->getVar( 'wgDBname' ); + $this->db->selectDB( $dbName ); + $server = $this->getVar( 'wgDBserver' ); + $password = $this->getVar( 'wgDBpassword' ); + $grantableNames = array(); + + if ( $this->getVar( '_CreateDBAccount' ) ) { + // Before we blindly try to create a user that already has access, + try { // first attempt to connect to the database + new DatabaseMysql( + $server, + $dbUser, + $password, + false, + false, + 0, + $this->getVar( 'wgDBprefix' ) + ); + $grantableNames[] = $this->buildFullUserName( $dbUser, $server ); + $tryToCreate = false; + } catch ( DBConnectionError $e ) { + $tryToCreate = true; + } + } else { + $grantableNames[] = $this->buildFullUserName( $dbUser, $server ); + $tryToCreate = false; + } + + if( $tryToCreate ) { + $createHostList = array($server, + 'localhost', + 'localhost.localdomain', + '%' + ); + + $createHostList = array_unique( $createHostList ); + $escPass = $this->db->addQuotes( $password ); + + foreach( $createHostList as $host ) { + $fullName = $this->buildFullUserName( $dbUser, $host ); + if( !$this->userDefinitelyExists( $dbUser, $host ) ) { + try{ + $this->db->begin(); + $this->db->query( "CREATE USER $fullName IDENTIFIED BY $escPass", __METHOD__ ); + $this->db->commit(); + $grantableNames[] = $fullName; + } catch( DBQueryError $dqe ) { + if( $this->db->lastErrno() == 1396 /* ER_CANNOT_USER */ ) { + // User (probably) already exists + $this->db->rollback(); + $status->warning( 'config-install-user-alreadyexists', $dbUser ); + $grantableNames[] = $fullName; + break; + } else { + // If we couldn't create for some bizzare reason and the + // user probably doesn't exist, skip the grant + $this->db->rollback(); + $status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getText() ); + } + } + } else { + $status->warning( 'config-install-user-alreadyexists', $dbUser ); + $grantableNames[] = $fullName; + break; + } + } } - return Status::newGood(); + + // Try to grant to all the users we know exist or we were able to create + $dbAllTables = $this->db->addIdentifierQuotes( $dbName ) . '.*'; + foreach( $grantableNames as $name ) { + try { + $this->db->begin(); + $this->db->query( "GRANT ALL PRIVILEGES ON $dbAllTables TO $name", __METHOD__ ); + $this->db->commit(); + } catch( DBQueryError $dqe ) { + $this->db->rollback(); + $status->fatal( 'config-install-user-grant-failed', $dbUser, $dqe->getText() ); + } + } + + return $status; + } + + /** + * Return a formal 'User'@'Host' username for use in queries + * @param $name String Username, quotes will be added + * @param $host String Hostname, quotes will be added + * @return String + */ + private function buildFullUserName( $name, $host ) { + return $this->db->addQuotes( $name ) . '@' . $this->db->addQuotes( $host ); } - function getTableOptions() { - return array( 'engine' => $this->getVar( '_MysqlEngine' ), - 'default charset' => $this->getVar( '_MysqlCharset' ) ); + /** + * Try to see if the user account exists. Our "superuser" may not have + * access to mysql.user, so false means "no" or "maybe" + * @param $host String Hostname to check + * @param $user String Username to check + * @return boolean + */ + private function userDefinitelyExists( $host, $user ) { + try { + $res = $this->db->selectRow( 'mysql.user', array( 'Host', 'User' ), + array( 'Host' => $host, 'User' => $user ), __METHOD__ ); + return (bool)$res; + } catch( DBQueryError $dqe ) { + return false; + } + + } + + /** + * Return any table options to be applied to all tables that don't + * override them. + * + * @return String + */ + protected function getTableOptions() { + $options = array(); + if ( $this->getVar( '_MysqlEngine' ) !== null ) { + $options[] = "ENGINE=" . $this->getVar( '_MysqlEngine' ); + } + if ( $this->getVar( '_MysqlCharset' ) !== null ) { + $options[] = 'DEFAULT CHARSET=' . $this->getVar( '_MysqlCharset' ); + } + return implode( ', ', $options ); + } + + /** + * Get variables to substitute into tables.sql and the SQL patch files. + * + * @return array + */ + public function getSchemaVars() { + return array( + 'wgDBTableOptions' => $this->getTableOptions(), + 'wgDBname' => $this->getVar( 'wgDBname' ), + 'wgDBuser' => $this->getVar( 'wgDBuser' ), + 'wgDBpassword' => $this->getVar( 'wgDBpassword' ), + ); } - function getLocalSettings() { + public function getLocalSettings() { $dbmysql5 = wfBoolToStr( $this->getVar( 'wgDBmysql5', true ) ); - $prefix = $this->getVar( 'wgDBprefix' ); - $opts = $this->getTableOptions(); - $tblOpts = "ENGINE=" . $opts['engine'] . ', DEFAULT CHARSET=' . $opts['default charset']; + $prefix = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBprefix' ) ); + $tblOpts = LocalSettingsGenerator::escapePhpString( $this->getTableOptions() ); return "# MySQL specific settings \$wgDBprefix = \"{$prefix}\"; @@ -406,7 +626,7 @@ class MysqlInstaller extends InstallerDBType { # MySQL table options to use during installation or update \$wgDBTableOptions = \"{$tblOpts}\"; -# Experimental charset support for MySQL 4.1/5.0. +# Experimental charset support for MySQL 5.0. \$wgDBmysql5 = {$dbmysql5};"; } }