MediaWiki
REL1_22
|
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( 'wgDBserver', 'wgDBname', 'wgDBprefix' ) ); 00104 00105 // Validate them. 00106 $status = Status::newGood(); 00107 if ( !strlen( $newValues['wgDBserver'] ) ) { 00108 $status->fatal( 'config-missing-db-host' ); 00109 } 00110 if ( !strlen( $newValues['wgDBname'] ) ) { 00111 $status->fatal( 'config-missing-db-name' ); 00112 } elseif ( !preg_match( '/^[a-z0-9+_-]+$/i', $newValues['wgDBname'] ) ) { 00113 $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] ); 00114 } 00115 if ( !preg_match( '/^[a-z0-9_-]*$/i', $newValues['wgDBprefix'] ) ) { 00116 $status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] ); 00117 } 00118 if ( !$status->isOK() ) { 00119 return $status; 00120 } 00121 00122 // Submit user box 00123 $status = $this->submitInstallUserBox(); 00124 if ( !$status->isOK() ) { 00125 return $status; 00126 } 00127 00128 // Try to connect 00129 $status = $this->getConnection(); 00130 if ( !$status->isOK() ) { 00131 return $status; 00132 } 00136 $conn = $status->value; 00137 00138 // Check version 00139 $version = $conn->getServerVersion(); 00140 if ( version_compare( $version, $this->minimumVersion ) < 0 ) { 00141 return Status::newFatal( 'config-mysql-old', $this->minimumVersion, $version ); 00142 } 00143 00144 return $status; 00145 } 00146 00150 public function openConnection() { 00151 $status = Status::newGood(); 00152 try { 00153 $db = DatabaseBase::factory( 'mysql', array( 00154 'host' => $this->getVar( 'wgDBserver' ), 00155 'user' => $this->getVar( '_InstallUser' ), 00156 'password' => $this->getVar( '_InstallPassword' ), 00157 'dbname' => false, 00158 'flags' => 0, 00159 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) ); 00160 $status->value = $db; 00161 } catch ( DBConnectionError $e ) { 00162 $status->fatal( 'config-connection-error', $e->getMessage() ); 00163 } 00164 00165 return $status; 00166 } 00167 00168 public function preUpgrade() { 00169 global $wgDBuser, $wgDBpassword; 00170 00171 $status = $this->getConnection(); 00172 if ( !$status->isOK() ) { 00173 $this->parent->showStatusError( $status ); 00174 00175 return; 00176 } 00180 $conn = $status->value; 00181 $conn->selectDB( $this->getVar( 'wgDBname' ) ); 00182 00183 # Determine existing default character set 00184 if ( $conn->tableExists( "revision", __METHOD__ ) ) { 00185 $revision = $conn->buildLike( $this->getVar( 'wgDBprefix' ) . 'revision' ); 00186 $res = $conn->query( "SHOW TABLE STATUS $revision", __METHOD__ ); 00187 $row = $conn->fetchObject( $res ); 00188 if ( !$row ) { 00189 $this->parent->showMessage( 'config-show-table-status' ); 00190 $existingSchema = false; 00191 $existingEngine = false; 00192 } else { 00193 if ( preg_match( '/^latin1/', $row->Collation ) ) { 00194 $existingSchema = 'latin1'; 00195 } elseif ( preg_match( '/^utf8/', $row->Collation ) ) { 00196 $existingSchema = 'utf8'; 00197 } elseif ( preg_match( '/^binary/', $row->Collation ) ) { 00198 $existingSchema = 'binary'; 00199 } else { 00200 $existingSchema = false; 00201 $this->parent->showMessage( 'config-unknown-collation' ); 00202 } 00203 if ( isset( $row->Engine ) ) { 00204 $existingEngine = $row->Engine; 00205 } else { 00206 $existingEngine = $row->Type; 00207 } 00208 } 00209 } else { 00210 $existingSchema = false; 00211 $existingEngine = false; 00212 } 00213 00214 if ( $existingSchema && $existingSchema != $this->getVar( '_MysqlCharset' ) ) { 00215 $this->setVar( '_MysqlCharset', $existingSchema ); 00216 } 00217 if ( $existingEngine && $existingEngine != $this->getVar( '_MysqlEngine' ) ) { 00218 $this->setVar( '_MysqlEngine', $existingEngine ); 00219 } 00220 00221 # Normal user and password are selected after this step, so for now 00222 # just copy these two 00223 $wgDBuser = $this->getVar( '_InstallUser' ); 00224 $wgDBpassword = $this->getVar( '_InstallPassword' ); 00225 } 00226 00232 public function getEngines() { 00233 $status = $this->getConnection(); 00234 00238 $conn = $status->value; 00239 00240 $engines = array(); 00241 $res = $conn->query( 'SHOW ENGINES', __METHOD__ ); 00242 foreach ( $res as $row ) { 00243 if ( $row->Support == 'YES' || $row->Support == 'DEFAULT' ) { 00244 $engines[] = $row->Engine; 00245 } 00246 } 00247 $engines = array_intersect( $this->supportedEngines, $engines ); 00248 00249 return $engines; 00250 } 00251 00257 public function getCharsets() { 00258 return array( 'binary', 'utf8' ); 00259 } 00260 00266 public function canCreateAccounts() { 00267 $status = $this->getConnection(); 00268 if ( !$status->isOK() ) { 00269 return false; 00270 } 00274 $conn = $status->value; 00275 00276 // Get current account name 00277 $currentName = $conn->selectField( '', 'CURRENT_USER()', '', __METHOD__ ); 00278 $parts = explode( '@', $currentName ); 00279 if ( count( $parts ) != 2 ) { 00280 return false; 00281 } 00282 $quotedUser = $conn->addQuotes( $parts[0] ) . 00283 '@' . $conn->addQuotes( $parts[1] ); 00284 00285 // The user needs to have INSERT on mysql.* to be able to CREATE USER 00286 // The grantee will be double-quoted in this query, as required 00287 $res = $conn->select( 'INFORMATION_SCHEMA.USER_PRIVILEGES', '*', 00288 array( 'GRANTEE' => $quotedUser ), __METHOD__ ); 00289 $insertMysql = false; 00290 $grantOptions = array_flip( $this->webUserPrivs ); 00291 foreach ( $res as $row ) { 00292 if ( $row->PRIVILEGE_TYPE == 'INSERT' ) { 00293 $insertMysql = true; 00294 } 00295 if ( $row->IS_GRANTABLE ) { 00296 unset( $grantOptions[$row->PRIVILEGE_TYPE] ); 00297 } 00298 } 00299 00300 // Check for DB-specific privs for mysql.* 00301 if ( !$insertMysql ) { 00302 $row = $conn->selectRow( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*', 00303 array( 00304 'GRANTEE' => $quotedUser, 00305 'TABLE_SCHEMA' => 'mysql', 00306 'PRIVILEGE_TYPE' => 'INSERT', 00307 ), __METHOD__ ); 00308 if ( $row ) { 00309 $insertMysql = true; 00310 } 00311 } 00312 00313 if ( !$insertMysql ) { 00314 return false; 00315 } 00316 00317 // Check for DB-level grant options 00318 $res = $conn->select( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*', 00319 array( 00320 'GRANTEE' => $quotedUser, 00321 'IS_GRANTABLE' => 1, 00322 ), __METHOD__ ); 00323 foreach ( $res as $row ) { 00324 $regex = $conn->likeToRegex( $row->TABLE_SCHEMA ); 00325 if ( preg_match( $regex, $this->getVar( 'wgDBname' ) ) ) { 00326 unset( $grantOptions[$row->PRIVILEGE_TYPE] ); 00327 } 00328 } 00329 if ( count( $grantOptions ) ) { 00330 // Can't grant everything 00331 return false; 00332 } 00333 00334 return true; 00335 } 00336 00340 public function getSettingsForm() { 00341 if ( $this->canCreateAccounts() ) { 00342 $noCreateMsg = false; 00343 } else { 00344 $noCreateMsg = 'config-db-web-no-create-privs'; 00345 } 00346 $s = $this->getWebUserBox( $noCreateMsg ); 00347 00348 // Do engine selector 00349 $engines = $this->getEngines(); 00350 // If the current default engine is not supported, use an engine that is 00351 if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) { 00352 $this->setVar( '_MysqlEngine', reset( $engines ) ); 00353 } 00354 00355 $s .= Xml::openElement( 'div', array( 00356 'id' => 'dbMyisamWarning' 00357 ) ); 00358 $myisamWarning = 'config-mysql-myisam-dep'; 00359 if ( count( $engines ) === 1 ) { 00360 $myisamWarning = 'config-mysql-only-myisam-dep'; 00361 } 00362 $s .= $this->parent->getWarningBox( wfMessage( $myisamWarning )->text() ); 00363 $s .= Xml::closeElement( 'div' ); 00364 00365 if ( $this->getVar( '_MysqlEngine' ) != 'MyISAM' ) { 00366 $s .= Xml::openElement( 'script', array( 'type' => 'text/javascript' ) ); 00367 $s .= '$(\'#dbMyisamWarning\').hide();'; 00368 $s .= Xml::closeElement( 'script' ); 00369 } 00370 00371 if ( count( $engines ) >= 2 ) { 00372 // getRadioSet() builds a set of labeled radio buttons. 00373 // For grep: The following messages are used as the item labels: 00374 // config-mysql-innodb, config-mysql-myisam 00375 $s .= $this->getRadioSet( array( 00376 'var' => '_MysqlEngine', 00377 'label' => 'config-mysql-engine', 00378 'itemLabelPrefix' => 'config-mysql-', 00379 'values' => $engines, 00380 'itemAttribs' => array( 00381 'MyISAM' => array( 00382 'class' => 'showHideRadio', 00383 'rel' => 'dbMyisamWarning' 00384 ), 00385 'InnoDB' => array( 00386 'class' => 'hideShowRadio', 00387 'rel' => 'dbMyisamWarning' 00388 ) 00389 ) 00390 ) ); 00391 $s .= $this->parent->getHelpBox( 'config-mysql-engine-help' ); 00392 } 00393 00394 // If the current default charset is not supported, use a charset that is 00395 $charsets = $this->getCharsets(); 00396 if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) { 00397 $this->setVar( '_MysqlCharset', reset( $charsets ) ); 00398 } 00399 00400 // Do charset selector 00401 if ( count( $charsets ) >= 2 ) { 00402 // getRadioSet() builds a set of labeled radio buttons. 00403 // For grep: The following messages are used as the item labels: 00404 // config-mysql-binary, config-mysql-utf8 00405 $s .= $this->getRadioSet( array( 00406 'var' => '_MysqlCharset', 00407 'label' => 'config-mysql-charset', 00408 'itemLabelPrefix' => 'config-mysql-', 00409 'values' => $charsets 00410 ) ); 00411 $s .= $this->parent->getHelpBox( 'config-mysql-charset-help' ); 00412 } 00413 00414 return $s; 00415 } 00416 00420 public function submitSettingsForm() { 00421 $this->setVarsFromRequest( array( '_MysqlEngine', '_MysqlCharset' ) ); 00422 $status = $this->submitWebUserBox(); 00423 if ( !$status->isOK() ) { 00424 return $status; 00425 } 00426 00427 // Validate the create checkbox 00428 $canCreate = $this->canCreateAccounts(); 00429 if ( !$canCreate ) { 00430 $this->setVar( '_CreateDBAccount', false ); 00431 $create = false; 00432 } else { 00433 $create = $this->getVar( '_CreateDBAccount' ); 00434 } 00435 00436 if ( !$create ) { 00437 // Test the web account 00438 try { 00439 $db = DatabaseBase::factory( 'mysql', array( 00440 'host' => $this->getVar( 'wgDBserver' ), 00441 'user' => $this->getVar( 'wgDBuser' ), 00442 'password' => $this->getVar( 'wgDBpassword' ), 00443 'dbname' => false, 00444 'flags' => 0, 00445 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) ); 00446 } catch ( DBConnectionError $e ) { 00447 return Status::newFatal( 'config-connection-error', $e->getMessage() ); 00448 } 00449 } 00450 00451 // Validate engines and charsets 00452 // This is done pre-submit already so it's just for security 00453 $engines = $this->getEngines(); 00454 if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) { 00455 $this->setVar( '_MysqlEngine', reset( $engines ) ); 00456 } 00457 $charsets = $this->getCharsets(); 00458 if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) { 00459 $this->setVar( '_MysqlCharset', reset( $charsets ) ); 00460 } 00461 00462 return Status::newGood(); 00463 } 00464 00465 public function preInstall() { 00466 # Add our user callback to installSteps, right before the tables are created. 00467 $callback = array( 00468 'name' => 'user', 00469 'callback' => array( $this, 'setupUser' ), 00470 ); 00471 $this->parent->addInstallStep( $callback, 'tables' ); 00472 } 00473 00477 public function setupDatabase() { 00478 $status = $this->getConnection(); 00479 if ( !$status->isOK() ) { 00480 return $status; 00481 } 00482 $conn = $status->value; 00483 $dbName = $this->getVar( 'wgDBname' ); 00484 if ( !$conn->selectDB( $dbName ) ) { 00485 $conn->query( 00486 "CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ) . "CHARACTER SET utf8", 00487 __METHOD__ 00488 ); 00489 $conn->selectDB( $dbName ); 00490 } 00491 $this->setupSchemaVars(); 00492 00493 return $status; 00494 } 00495 00499 public function setupUser() { 00500 $dbUser = $this->getVar( 'wgDBuser' ); 00501 if ( $dbUser == $this->getVar( '_InstallUser' ) ) { 00502 return Status::newGood(); 00503 } 00504 $status = $this->getConnection(); 00505 if ( !$status->isOK() ) { 00506 return $status; 00507 } 00508 00509 $this->setupSchemaVars(); 00510 $dbName = $this->getVar( 'wgDBname' ); 00511 $this->db->selectDB( $dbName ); 00512 $server = $this->getVar( 'wgDBserver' ); 00513 $password = $this->getVar( 'wgDBpassword' ); 00514 $grantableNames = array(); 00515 00516 if ( $this->getVar( '_CreateDBAccount' ) ) { 00517 // Before we blindly try to create a user that already has access, 00518 try { // first attempt to connect to the database 00519 $db = DatabaseBase::factory( 'mysql', array( 00520 'host' => $server, 00521 'user' => $dbUser, 00522 'password' => $password, 00523 'dbname' => false, 00524 'flags' => 0, 00525 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) ); 00526 $grantableNames[] = $this->buildFullUserName( $dbUser, $server ); 00527 $tryToCreate = false; 00528 } catch ( DBConnectionError $e ) { 00529 $tryToCreate = true; 00530 } 00531 } else { 00532 $grantableNames[] = $this->buildFullUserName( $dbUser, $server ); 00533 $tryToCreate = false; 00534 } 00535 00536 if ( $tryToCreate ) { 00537 $createHostList = array( 00538 $server, 00539 'localhost', 00540 'localhost.localdomain', 00541 '%' 00542 ); 00543 00544 $createHostList = array_unique( $createHostList ); 00545 $escPass = $this->db->addQuotes( $password ); 00546 00547 foreach ( $createHostList as $host ) { 00548 $fullName = $this->buildFullUserName( $dbUser, $host ); 00549 if ( !$this->userDefinitelyExists( $dbUser, $host ) ) { 00550 try { 00551 $this->db->begin( __METHOD__ ); 00552 $this->db->query( "CREATE USER $fullName IDENTIFIED BY $escPass", __METHOD__ ); 00553 $this->db->commit( __METHOD__ ); 00554 $grantableNames[] = $fullName; 00555 } catch ( DBQueryError $dqe ) { 00556 if ( $this->db->lastErrno() == 1396 /* ER_CANNOT_USER */ ) { 00557 // User (probably) already exists 00558 $this->db->rollback( __METHOD__ ); 00559 $status->warning( 'config-install-user-alreadyexists', $dbUser ); 00560 $grantableNames[] = $fullName; 00561 break; 00562 } else { 00563 // If we couldn't create for some bizzare reason and the 00564 // user probably doesn't exist, skip the grant 00565 $this->db->rollback( __METHOD__ ); 00566 $status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getText() ); 00567 } 00568 } 00569 } else { 00570 $status->warning( 'config-install-user-alreadyexists', $dbUser ); 00571 $grantableNames[] = $fullName; 00572 break; 00573 } 00574 } 00575 } 00576 00577 // Try to grant to all the users we know exist or we were able to create 00578 $dbAllTables = $this->db->addIdentifierQuotes( $dbName ) . '.*'; 00579 foreach ( $grantableNames as $name ) { 00580 try { 00581 $this->db->begin( __METHOD__ ); 00582 $this->db->query( "GRANT ALL PRIVILEGES ON $dbAllTables TO $name", __METHOD__ ); 00583 $this->db->commit( __METHOD__ ); 00584 } catch ( DBQueryError $dqe ) { 00585 $this->db->rollback( __METHOD__ ); 00586 $status->fatal( 'config-install-user-grant-failed', $dbUser, $dqe->getText() ); 00587 } 00588 } 00589 00590 return $status; 00591 } 00592 00599 private function buildFullUserName( $name, $host ) { 00600 return $this->db->addQuotes( $name ) . '@' . $this->db->addQuotes( $host ); 00601 } 00602 00610 private function userDefinitelyExists( $host, $user ) { 00611 try { 00612 $res = $this->db->selectRow( 'mysql.user', array( 'Host', 'User' ), 00613 array( 'Host' => $host, 'User' => $user ), __METHOD__ ); 00614 00615 return (bool)$res; 00616 } catch ( DBQueryError $dqe ) { 00617 return false; 00618 } 00619 } 00620 00627 protected function getTableOptions() { 00628 $options = array(); 00629 if ( $this->getVar( '_MysqlEngine' ) !== null ) { 00630 $options[] = "ENGINE=" . $this->getVar( '_MysqlEngine' ); 00631 } 00632 if ( $this->getVar( '_MysqlCharset' ) !== null ) { 00633 $options[] = 'DEFAULT CHARSET=' . $this->getVar( '_MysqlCharset' ); 00634 } 00635 00636 return implode( ', ', $options ); 00637 } 00638 00644 public function getSchemaVars() { 00645 return array( 00646 'wgDBTableOptions' => $this->getTableOptions(), 00647 'wgDBname' => $this->getVar( 'wgDBname' ), 00648 'wgDBuser' => $this->getVar( 'wgDBuser' ), 00649 'wgDBpassword' => $this->getVar( 'wgDBpassword' ), 00650 ); 00651 } 00652 00653 public function getLocalSettings() { 00654 $dbmysql5 = wfBoolToStr( $this->getVar( 'wgDBmysql5', true ) ); 00655 $prefix = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBprefix' ) ); 00656 $tblOpts = LocalSettingsGenerator::escapePhpString( $this->getTableOptions() ); 00657 00658 return "# MySQL specific settings 00659 \$wgDBprefix = \"{$prefix}\"; 00660 00661 # MySQL table options to use during installation or update 00662 \$wgDBTableOptions = \"{$tblOpts}\"; 00663 00664 # Experimental charset support for MySQL 5.0. 00665 \$wgDBmysql5 = {$dbmysql5};"; 00666 } 00667 }