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