MediaWiki
REL1_19
|
00001 <?php 00015 class MysqlInstaller extends DatabaseInstaller { 00016 00017 protected $globalNames = array( 00018 'wgDBserver', 00019 'wgDBname', 00020 'wgDBuser', 00021 'wgDBpassword', 00022 'wgDBprefix', 00023 'wgDBTableOptions', 00024 'wgDBmysql5', 00025 ); 00026 00027 protected $internalDefaults = array( 00028 '_MysqlEngine' => 'InnoDB', 00029 '_MysqlCharset' => 'binary', 00030 '_InstallUser' => 'root', 00031 ); 00032 00033 public $supportedEngines = array( 'InnoDB', 'MyISAM' ); 00034 00035 public $minimumVersion = '5.0.2'; 00036 00037 public $webUserPrivs = array( 00038 'DELETE', 00039 'INSERT', 00040 'SELECT', 00041 'UPDATE', 00042 'CREATE TEMPORARY TABLES', 00043 ); 00044 00048 public function getName() { 00049 return 'mysql'; 00050 } 00051 00052 public function __construct( $parent ) { 00053 parent::__construct( $parent ); 00054 } 00055 00059 public function isCompiled() { 00060 return self::checkExtension( 'mysql' ); 00061 } 00062 00066 public function getGlobalDefaults() { 00067 return array(); 00068 } 00069 00073 public function getConnectForm() { 00074 return 00075 $this->getTextBox( 'wgDBserver', 'config-db-host', array(), $this->parent->getHelpBox( 'config-db-host-help' ) ) . 00076 Html::openElement( 'fieldset' ) . 00077 Html::element( 'legend', array(), wfMsg( 'config-db-wiki-settings' ) ) . 00078 $this->getTextBox( 'wgDBname', 'config-db-name', array( 'dir' => 'ltr' ), $this->parent->getHelpBox( 'config-db-name-help' ) ) . 00079 $this->getTextBox( 'wgDBprefix', 'config-db-prefix', array( 'dir' => 'ltr' ), $this->parent->getHelpBox( 'config-db-prefix-help' ) ) . 00080 Html::closeElement( 'fieldset' ) . 00081 $this->getInstallUserBox(); 00082 } 00083 00084 public function submitConnectForm() { 00085 // Get variables from the request. 00086 $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBname', 'wgDBprefix' ) ); 00087 00088 // Validate them. 00089 $status = Status::newGood(); 00090 if ( !strlen( $newValues['wgDBserver'] ) ) { 00091 $status->fatal( 'config-missing-db-host' ); 00092 } 00093 if ( !strlen( $newValues['wgDBname'] ) ) { 00094 $status->fatal( 'config-missing-db-name' ); 00095 } elseif ( !preg_match( '/^[a-z0-9_-]+$/i', $newValues['wgDBname'] ) ) { 00096 $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] ); 00097 } 00098 if ( !preg_match( '/^[a-z0-9_-]*$/i', $newValues['wgDBprefix'] ) ) { 00099 $status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] ); 00100 } 00101 if ( !$status->isOK() ) { 00102 return $status; 00103 } 00104 00105 // Submit user box 00106 $status = $this->submitInstallUserBox(); 00107 if ( !$status->isOK() ) { 00108 return $status; 00109 } 00110 00111 // Try to connect 00112 $status = $this->getConnection(); 00113 if ( !$status->isOK() ) { 00114 return $status; 00115 } 00119 $conn = $status->value; 00120 00121 // Check version 00122 $version = $conn->getServerVersion(); 00123 if ( version_compare( $version, $this->minimumVersion ) < 0 ) { 00124 return Status::newFatal( 'config-mysql-old', $this->minimumVersion, $version ); 00125 } 00126 00127 return $status; 00128 } 00129 00133 public function openConnection() { 00134 $status = Status::newGood(); 00135 try { 00136 $db = new DatabaseMysql( 00137 $this->getVar( 'wgDBserver' ), 00138 $this->getVar( '_InstallUser' ), 00139 $this->getVar( '_InstallPassword' ), 00140 false, 00141 false, 00142 0, 00143 $this->getVar( 'wgDBprefix' ) 00144 ); 00145 $status->value = $db; 00146 } catch ( DBConnectionError $e ) { 00147 $status->fatal( 'config-connection-error', $e->getMessage() ); 00148 } 00149 return $status; 00150 } 00151 00152 public function preUpgrade() { 00153 global $wgDBuser, $wgDBpassword; 00154 00155 $status = $this->getConnection(); 00156 if ( !$status->isOK() ) { 00157 $this->parent->showStatusError( $status ); 00158 return; 00159 } 00163 $conn = $status->value; 00164 $conn->selectDB( $this->getVar( 'wgDBname' ) ); 00165 00166 # Determine existing default character set 00167 if ( $conn->tableExists( "revision", __METHOD__ ) ) { 00168 $revision = $conn->buildLike( $this->getVar( 'wgDBprefix' ) . 'revision' ); 00169 $res = $conn->query( "SHOW TABLE STATUS $revision", __METHOD__ ); 00170 $row = $conn->fetchObject( $res ); 00171 if ( !$row ) { 00172 $this->parent->showMessage( 'config-show-table-status' ); 00173 $existingSchema = false; 00174 $existingEngine = false; 00175 } else { 00176 if ( preg_match( '/^latin1/', $row->Collation ) ) { 00177 $existingSchema = 'latin1'; 00178 } elseif ( preg_match( '/^utf8/', $row->Collation ) ) { 00179 $existingSchema = 'utf8'; 00180 } elseif ( preg_match( '/^binary/', $row->Collation ) ) { 00181 $existingSchema = 'binary'; 00182 } else { 00183 $existingSchema = false; 00184 $this->parent->showMessage( 'config-unknown-collation' ); 00185 } 00186 if ( isset( $row->Engine ) ) { 00187 $existingEngine = $row->Engine; 00188 } else { 00189 $existingEngine = $row->Type; 00190 } 00191 } 00192 } else { 00193 $existingSchema = false; 00194 $existingEngine = false; 00195 } 00196 00197 if ( $existingSchema && $existingSchema != $this->getVar( '_MysqlCharset' ) ) { 00198 $this->setVar( '_MysqlCharset', $existingSchema ); 00199 } 00200 if ( $existingEngine && $existingEngine != $this->getVar( '_MysqlEngine' ) ) { 00201 $this->setVar( '_MysqlEngine', $existingEngine ); 00202 } 00203 00204 # Normal user and password are selected after this step, so for now 00205 # just copy these two 00206 $wgDBuser = $this->getVar( '_InstallUser' ); 00207 $wgDBpassword = $this->getVar( '_InstallPassword' ); 00208 } 00209 00215 public function getEngines() { 00216 $status = $this->getConnection(); 00217 00221 $conn = $status->value; 00222 00223 $engines = array(); 00224 $res = $conn->query( 'SHOW ENGINES', __METHOD__ ); 00225 foreach ( $res as $row ) { 00226 if ( $row->Support == 'YES' || $row->Support == 'DEFAULT' ) { 00227 $engines[] = $row->Engine; 00228 } 00229 } 00230 $engines = array_intersect( $this->supportedEngines, $engines ); 00231 return $engines; 00232 } 00233 00239 public function getCharsets() { 00240 return array( 'binary', 'utf8' ); 00241 } 00242 00248 public function canCreateAccounts() { 00249 $status = $this->getConnection(); 00250 if ( !$status->isOK() ) { 00251 return false; 00252 } 00256 $conn = $status->value; 00257 00258 // Get current account name 00259 $currentName = $conn->selectField( '', 'CURRENT_USER()', '', __METHOD__ ); 00260 $parts = explode( '@', $currentName ); 00261 if ( count( $parts ) != 2 ) { 00262 return false; 00263 } 00264 $quotedUser = $conn->addQuotes( $parts[0] ) . 00265 '@' . $conn->addQuotes( $parts[1] ); 00266 00267 // The user needs to have INSERT on mysql.* to be able to CREATE USER 00268 // The grantee will be double-quoted in this query, as required 00269 $res = $conn->select( 'INFORMATION_SCHEMA.USER_PRIVILEGES', '*', 00270 array( 'GRANTEE' => $quotedUser ), __METHOD__ ); 00271 $insertMysql = false; 00272 $grantOptions = array_flip( $this->webUserPrivs ); 00273 foreach ( $res as $row ) { 00274 if ( $row->PRIVILEGE_TYPE == 'INSERT' ) { 00275 $insertMysql = true; 00276 } 00277 if ( $row->IS_GRANTABLE ) { 00278 unset( $grantOptions[$row->PRIVILEGE_TYPE] ); 00279 } 00280 } 00281 00282 // Check for DB-specific privs for mysql.* 00283 if ( !$insertMysql ) { 00284 $row = $conn->selectRow( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*', 00285 array( 00286 'GRANTEE' => $quotedUser, 00287 'TABLE_SCHEMA' => 'mysql', 00288 'PRIVILEGE_TYPE' => 'INSERT', 00289 ), __METHOD__ ); 00290 if ( $row ) { 00291 $insertMysql = true; 00292 } 00293 } 00294 00295 if ( !$insertMysql ) { 00296 return false; 00297 } 00298 00299 // Check for DB-level grant options 00300 $res = $conn->select( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*', 00301 array( 00302 'GRANTEE' => $quotedUser, 00303 'IS_GRANTABLE' => 1, 00304 ), __METHOD__ ); 00305 foreach ( $res as $row ) { 00306 $regex = $conn->likeToRegex( $row->TABLE_SCHEMA ); 00307 if ( preg_match( $regex, $this->getVar( 'wgDBname' ) ) ) { 00308 unset( $grantOptions[$row->PRIVILEGE_TYPE] ); 00309 } 00310 } 00311 if ( count( $grantOptions ) ) { 00312 // Can't grant everything 00313 return false; 00314 } 00315 return true; 00316 } 00317 00321 public function getSettingsForm() { 00322 if ( $this->canCreateAccounts() ) { 00323 $noCreateMsg = false; 00324 } else { 00325 $noCreateMsg = 'config-db-web-no-create-privs'; 00326 } 00327 $s = $this->getWebUserBox( $noCreateMsg ); 00328 00329 // Do engine selector 00330 $engines = $this->getEngines(); 00331 // If the current default engine is not supported, use an engine that is 00332 if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) { 00333 $this->setVar( '_MysqlEngine', reset( $engines ) ); 00334 } 00335 00336 $s .= Xml::openElement( 'div', array( 00337 'id' => 'dbMyisamWarning' 00338 )); 00339 $s .= $this->parent->getWarningBox( wfMsg( 'config-mysql-myisam-dep' ) ); 00340 $s .= Xml::closeElement( 'div' ); 00341 00342 if( $this->getVar( '_MysqlEngine' ) != 'MyISAM' ) { 00343 $s .= Xml::openElement( 'script', array( 'type' => 'text/javascript' ) ); 00344 $s .= '$(\'#dbMyisamWarning\').hide();'; 00345 $s .= Xml::closeElement( 'script' ); 00346 } 00347 00348 if ( count( $engines ) >= 2 ) { 00349 $s .= $this->getRadioSet( array( 00350 'var' => '_MysqlEngine', 00351 'label' => 'config-mysql-engine', 00352 'itemLabelPrefix' => 'config-mysql-', 00353 'values' => $engines, 00354 'itemAttribs' => array( 00355 'MyISAM' => array( 00356 'class' => 'showHideRadio', 00357 'rel' => 'dbMyisamWarning'), 00358 'InnoDB' => array( 00359 'class' => 'hideShowRadio', 00360 'rel' => 'dbMyisamWarning') 00361 ))); 00362 $s .= $this->parent->getHelpBox( 'config-mysql-engine-help' ); 00363 } 00364 00365 // If the current default charset is not supported, use a charset that is 00366 $charsets = $this->getCharsets(); 00367 if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) { 00368 $this->setVar( '_MysqlCharset', reset( $charsets ) ); 00369 } 00370 00371 // Do charset selector 00372 if ( count( $charsets ) >= 2 ) { 00373 $s .= $this->getRadioSet( array( 00374 'var' => '_MysqlCharset', 00375 'label' => 'config-mysql-charset', 00376 'itemLabelPrefix' => 'config-mysql-', 00377 'values' => $charsets 00378 )); 00379 $s .= $this->parent->getHelpBox( 'config-mysql-charset-help' ); 00380 } 00381 00382 return $s; 00383 } 00384 00388 public function submitSettingsForm() { 00389 $this->setVarsFromRequest( array( '_MysqlEngine', '_MysqlCharset' ) ); 00390 $status = $this->submitWebUserBox(); 00391 if ( !$status->isOK() ) { 00392 return $status; 00393 } 00394 00395 // Validate the create checkbox 00396 $canCreate = $this->canCreateAccounts(); 00397 if ( !$canCreate ) { 00398 $this->setVar( '_CreateDBAccount', false ); 00399 $create = false; 00400 } else { 00401 $create = $this->getVar( '_CreateDBAccount' ); 00402 } 00403 00404 if ( !$create ) { 00405 // Test the web account 00406 try { 00407 new DatabaseMysql( 00408 $this->getVar( 'wgDBserver' ), 00409 $this->getVar( 'wgDBuser' ), 00410 $this->getVar( 'wgDBpassword' ), 00411 false, 00412 false, 00413 0, 00414 $this->getVar( 'wgDBprefix' ) 00415 ); 00416 } catch ( DBConnectionError $e ) { 00417 return Status::newFatal( 'config-connection-error', $e->getMessage() ); 00418 } 00419 } 00420 00421 // Validate engines and charsets 00422 // This is done pre-submit already so it's just for security 00423 $engines = $this->getEngines(); 00424 if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) { 00425 $this->setVar( '_MysqlEngine', reset( $engines ) ); 00426 } 00427 $charsets = $this->getCharsets(); 00428 if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) { 00429 $this->setVar( '_MysqlCharset', reset( $charsets ) ); 00430 } 00431 return Status::newGood(); 00432 } 00433 00434 public function preInstall() { 00435 # Add our user callback to installSteps, right before the tables are created. 00436 $callback = array( 00437 'name' => 'user', 00438 'callback' => array( $this, 'setupUser' ), 00439 ); 00440 $this->parent->addInstallStep( $callback, 'tables' ); 00441 } 00442 00446 public function setupDatabase() { 00447 $status = $this->getConnection(); 00448 if ( !$status->isOK() ) { 00449 return $status; 00450 } 00451 $conn = $status->value; 00452 $dbName = $this->getVar( 'wgDBname' ); 00453 if( !$conn->selectDB( $dbName ) ) { 00454 $conn->query( "CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ), __METHOD__ ); 00455 $conn->selectDB( $dbName ); 00456 } 00457 $this->setupSchemaVars(); 00458 return $status; 00459 } 00460 00464 public function setupUser() { 00465 $dbUser = $this->getVar( 'wgDBuser' ); 00466 if( $dbUser == $this->getVar( '_InstallUser' ) ) { 00467 return Status::newGood(); 00468 } 00469 $status = $this->getConnection(); 00470 if ( !$status->isOK() ) { 00471 return $status; 00472 } 00473 00474 $this->setupSchemaVars(); 00475 $dbName = $this->getVar( 'wgDBname' ); 00476 $this->db->selectDB( $dbName ); 00477 $server = $this->getVar( 'wgDBserver' ); 00478 $password = $this->getVar( 'wgDBpassword' ); 00479 $grantableNames = array(); 00480 00481 if ( $this->getVar( '_CreateDBAccount' ) ) { 00482 // Before we blindly try to create a user that already has access, 00483 try { // first attempt to connect to the database 00484 new DatabaseMysql( 00485 $server, 00486 $dbUser, 00487 $password, 00488 false, 00489 false, 00490 0, 00491 $this->getVar( 'wgDBprefix' ) 00492 ); 00493 $grantableNames[] = $this->buildFullUserName( $dbUser, $server ); 00494 $tryToCreate = false; 00495 } catch ( DBConnectionError $e ) { 00496 $tryToCreate = true; 00497 } 00498 } else { 00499 $grantableNames[] = $this->buildFullUserName( $dbUser, $server ); 00500 $tryToCreate = false; 00501 } 00502 00503 if( $tryToCreate ) { 00504 $createHostList = array($server, 00505 'localhost', 00506 'localhost.localdomain', 00507 '%' 00508 ); 00509 00510 $createHostList = array_unique( $createHostList ); 00511 $escPass = $this->db->addQuotes( $password ); 00512 00513 foreach( $createHostList as $host ) { 00514 $fullName = $this->buildFullUserName( $dbUser, $host ); 00515 if( !$this->userDefinitelyExists( $dbUser, $host ) ) { 00516 try{ 00517 $this->db->begin(); 00518 $this->db->query( "CREATE USER $fullName IDENTIFIED BY $escPass", __METHOD__ ); 00519 $this->db->commit(); 00520 $grantableNames[] = $fullName; 00521 } catch( DBQueryError $dqe ) { 00522 if( $this->db->lastErrno() == 1396 /* ER_CANNOT_USER */ ) { 00523 // User (probably) already exists 00524 $this->db->rollback(); 00525 $status->warning( 'config-install-user-alreadyexists', $dbUser ); 00526 $grantableNames[] = $fullName; 00527 break; 00528 } else { 00529 // If we couldn't create for some bizzare reason and the 00530 // user probably doesn't exist, skip the grant 00531 $this->db->rollback(); 00532 $status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getText() ); 00533 } 00534 } 00535 } else { 00536 $status->warning( 'config-install-user-alreadyexists', $dbUser ); 00537 $grantableNames[] = $fullName; 00538 break; 00539 } 00540 } 00541 } 00542 00543 // Try to grant to all the users we know exist or we were able to create 00544 $dbAllTables = $this->db->addIdentifierQuotes( $dbName ) . '.*'; 00545 foreach( $grantableNames as $name ) { 00546 try { 00547 $this->db->begin(); 00548 $this->db->query( "GRANT ALL PRIVILEGES ON $dbAllTables TO $name", __METHOD__ ); 00549 $this->db->commit(); 00550 } catch( DBQueryError $dqe ) { 00551 $this->db->rollback(); 00552 $status->fatal( 'config-install-user-grant-failed', $dbUser, $dqe->getText() ); 00553 } 00554 } 00555 00556 return $status; 00557 } 00558 00565 private function buildFullUserName( $name, $host ) { 00566 return $this->db->addQuotes( $name ) . '@' . $this->db->addQuotes( $host ); 00567 } 00568 00576 private function userDefinitelyExists( $host, $user ) { 00577 try { 00578 $res = $this->db->selectRow( 'mysql.user', array( 'Host', 'User' ), 00579 array( 'Host' => $host, 'User' => $user ), __METHOD__ ); 00580 return (bool)$res; 00581 } catch( DBQueryError $dqe ) { 00582 return false; 00583 } 00584 00585 } 00586 00593 protected function getTableOptions() { 00594 $options = array(); 00595 if ( $this->getVar( '_MysqlEngine' ) !== null ) { 00596 $options[] = "ENGINE=" . $this->getVar( '_MysqlEngine' ); 00597 } 00598 if ( $this->getVar( '_MysqlCharset' ) !== null ) { 00599 $options[] = 'DEFAULT CHARSET=' . $this->getVar( '_MysqlCharset' ); 00600 } 00601 return implode( ', ', $options ); 00602 } 00603 00609 public function getSchemaVars() { 00610 return array( 00611 'wgDBTableOptions' => $this->getTableOptions(), 00612 'wgDBname' => $this->getVar( 'wgDBname' ), 00613 'wgDBuser' => $this->getVar( 'wgDBuser' ), 00614 'wgDBpassword' => $this->getVar( 'wgDBpassword' ), 00615 ); 00616 } 00617 00618 public function getLocalSettings() { 00619 $dbmysql5 = wfBoolToStr( $this->getVar( 'wgDBmysql5', true ) ); 00620 $prefix = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBprefix' ) ); 00621 $tblOpts = LocalSettingsGenerator::escapePhpString( $this->getTableOptions() ); 00622 return 00623 "# MySQL specific settings 00624 \$wgDBprefix = \"{$prefix}\"; 00625 00626 # MySQL table options to use during installation or update 00627 \$wgDBTableOptions = \"{$tblOpts}\"; 00628 00629 # Experimental charset support for MySQL 5.0. 00630 \$wgDBmysql5 = {$dbmysql5};"; 00631 } 00632 }