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