MediaWiki
REL1_24
|
00001 <?php 00030 class PostgresInstaller extends DatabaseInstaller { 00031 00032 protected $globalNames = array( 00033 'wgDBserver', 00034 'wgDBport', 00035 'wgDBname', 00036 'wgDBuser', 00037 'wgDBpassword', 00038 'wgDBmwschema', 00039 ); 00040 00041 protected $internalDefaults = array( 00042 '_InstallUser' => 'postgres', 00043 ); 00044 00045 public $minimumVersion = '8.3'; 00046 public $maxRoleSearchDepth = 5; 00047 00048 protected $pgConns = array(); 00049 00050 function getName() { 00051 return 'postgres'; 00052 } 00053 00054 public function isCompiled() { 00055 return self::checkExtension( 'pgsql' ); 00056 } 00057 00058 function getConnectForm() { 00059 return $this->getTextBox( 00060 'wgDBserver', 00061 'config-db-host', 00062 array(), 00063 $this->parent->getHelpBox( 'config-db-host-help' ) 00064 ) . 00065 $this->getTextBox( 'wgDBport', 'config-db-port' ) . 00066 Html::openElement( 'fieldset' ) . 00067 Html::element( 'legend', array(), wfMessage( 'config-db-wiki-settings' )->text() ) . 00068 $this->getTextBox( 00069 'wgDBname', 00070 'config-db-name', 00071 array(), 00072 $this->parent->getHelpBox( 'config-db-name-help' ) 00073 ) . 00074 $this->getTextBox( 00075 'wgDBmwschema', 00076 'config-db-schema', 00077 array(), 00078 $this->parent->getHelpBox( 'config-db-schema-help' ) 00079 ) . 00080 Html::closeElement( 'fieldset' ) . 00081 $this->getInstallUserBox(); 00082 } 00083 00084 function submitConnectForm() { 00085 // Get variables from the request 00086 $newValues = $this->setVarsFromRequest( array( 00087 'wgDBserver', 'wgDBport', 'wgDBname', 'wgDBmwschema', 00088 '_InstallUser', '_InstallPassword' 00089 ) ); 00090 00091 // Validate them 00092 $status = Status::newGood(); 00093 if ( !strlen( $newValues['wgDBname'] ) ) { 00094 $status->fatal( 'config-missing-db-name' ); 00095 } elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) { 00096 $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] ); 00097 } 00098 if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) { 00099 $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] ); 00100 } 00101 if ( !strlen( $newValues['_InstallUser'] ) ) { 00102 $status->fatal( 'config-db-username-empty' ); 00103 } 00104 if ( !strlen( $newValues['_InstallPassword'] ) ) { 00105 $status->fatal( 'config-db-password-empty', $newValues['_InstallUser'] ); 00106 } 00107 00108 // Submit user box 00109 if ( $status->isOK() ) { 00110 $status->merge( $this->submitInstallUserBox() ); 00111 } 00112 if ( !$status->isOK() ) { 00113 return $status; 00114 } 00115 00116 $status = $this->getPgConnection( 'create-db' ); 00117 if ( !$status->isOK() ) { 00118 return $status; 00119 } 00123 $conn = $status->value; 00124 00125 // Check version 00126 $version = $conn->getServerVersion(); 00127 if ( version_compare( $version, $this->minimumVersion ) < 0 ) { 00128 return Status::newFatal( 'config-postgres-old', $this->minimumVersion, $version ); 00129 } 00130 00131 $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) ); 00132 $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) ); 00133 00134 return Status::newGood(); 00135 } 00136 00137 public function getConnection() { 00138 $status = $this->getPgConnection( 'create-tables' ); 00139 if ( $status->isOK() ) { 00140 $this->db = $status->value; 00141 } 00142 00143 return $status; 00144 } 00145 00146 public function openConnection() { 00147 return $this->openPgConnection( 'create-tables' ); 00148 } 00149 00158 protected function openConnectionWithParams( $user, $password, $dbName, $schema ) { 00159 $status = Status::newGood(); 00160 try { 00161 $db = DatabaseBase::factory( 'postgres', array( 00162 'host' => $this->getVar( 'wgDBserver' ), 00163 'user' => $user, 00164 'password' => $password, 00165 'dbname' => $dbName, 00166 'schema' => $schema ) ); 00167 $status->value = $db; 00168 } catch ( DBConnectionError $e ) { 00169 $status->fatal( 'config-connection-error', $e->getMessage() ); 00170 } 00171 00172 return $status; 00173 } 00174 00180 protected function getPgConnection( $type ) { 00181 if ( isset( $this->pgConns[$type] ) ) { 00182 return Status::newGood( $this->pgConns[$type] ); 00183 } 00184 $status = $this->openPgConnection( $type ); 00185 00186 if ( $status->isOK() ) { 00190 $conn = $status->value; 00191 $conn->clearFlag( DBO_TRX ); 00192 $conn->commit( __METHOD__ ); 00193 $this->pgConns[$type] = $conn; 00194 } 00195 00196 return $status; 00197 } 00198 00224 protected function openPgConnection( $type ) { 00225 switch ( $type ) { 00226 case 'create-db': 00227 return $this->openConnectionToAnyDB( 00228 $this->getVar( '_InstallUser' ), 00229 $this->getVar( '_InstallPassword' ) ); 00230 case 'create-schema': 00231 return $this->openConnectionWithParams( 00232 $this->getVar( '_InstallUser' ), 00233 $this->getVar( '_InstallPassword' ), 00234 $this->getVar( 'wgDBname' ), 00235 $this->getVar( 'wgDBmwschema' ) ); 00236 case 'create-tables': 00237 $status = $this->openPgConnection( 'create-schema' ); 00238 if ( $status->isOK() ) { 00242 $conn = $status->value; 00243 $safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) ); 00244 $conn->query( "SET ROLE $safeRole" ); 00245 } 00246 00247 return $status; 00248 default: 00249 throw new MWException( "Invalid special connection type: \"$type\"" ); 00250 } 00251 } 00252 00253 public function openConnectionToAnyDB( $user, $password ) { 00254 $dbs = array( 00255 'template1', 00256 'postgres', 00257 ); 00258 if ( !in_array( $this->getVar( 'wgDBname' ), $dbs ) ) { 00259 array_unshift( $dbs, $this->getVar( 'wgDBname' ) ); 00260 } 00261 $conn = false; 00262 $status = Status::newGood(); 00263 foreach ( $dbs as $db ) { 00264 try { 00265 $conn = $this->openConnectionWithParams( 00266 $user, 00267 $password, 00268 $db, 00269 $this->getVar( 'wgDBmwschema' ) ); 00270 } catch ( DBConnectionError $error ) { 00271 $conn = false; 00272 $status->fatal( 'config-pg-test-error', $db, 00273 $error->getMessage() ); 00274 } 00275 if ( $conn !== false ) { 00276 break; 00277 } 00278 } 00279 if ( $conn !== false ) { 00280 return Status::newGood( $conn ); 00281 } else { 00282 return $status; 00283 } 00284 } 00285 00286 protected function getInstallUserPermissions() { 00287 $status = $this->getPgConnection( 'create-db' ); 00288 if ( !$status->isOK() ) { 00289 return false; 00290 } 00294 $conn = $status->value; 00295 $superuser = $this->getVar( '_InstallUser' ); 00296 00297 $row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*', 00298 array( 'rolname' => $superuser ), __METHOD__ ); 00299 00300 return $row; 00301 } 00302 00303 protected function canCreateAccounts() { 00304 $perms = $this->getInstallUserPermissions(); 00305 if ( !$perms ) { 00306 return false; 00307 } 00308 00309 return $perms->rolsuper === 't' || $perms->rolcreaterole === 't'; 00310 } 00311 00312 protected function isSuperUser() { 00313 $perms = $this->getInstallUserPermissions(); 00314 if ( !$perms ) { 00315 return false; 00316 } 00317 00318 return $perms->rolsuper === 't'; 00319 } 00320 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 return $s; 00330 } 00331 00332 public function submitSettingsForm() { 00333 $status = $this->submitWebUserBox(); 00334 if ( !$status->isOK() ) { 00335 return $status; 00336 } 00337 00338 $same = $this->getVar( 'wgDBuser' ) === $this->getVar( '_InstallUser' ); 00339 00340 if ( $same ) { 00341 $exists = true; 00342 } else { 00343 // Check if the web user exists 00344 // Connect to the database with the install user 00345 $status = $this->getPgConnection( 'create-db' ); 00346 if ( !$status->isOK() ) { 00347 return $status; 00348 } 00349 $exists = $status->value->roleExists( $this->getVar( 'wgDBuser' ) ); 00350 } 00351 00352 // Validate the create checkbox 00353 if ( $this->canCreateAccounts() && !$same && !$exists ) { 00354 $create = $this->getVar( '_CreateDBAccount' ); 00355 } else { 00356 $this->setVar( '_CreateDBAccount', false ); 00357 $create = false; 00358 } 00359 00360 if ( !$create && !$exists ) { 00361 if ( $this->canCreateAccounts() ) { 00362 $msg = 'config-install-user-missing-create'; 00363 } else { 00364 $msg = 'config-install-user-missing'; 00365 } 00366 00367 return Status::newFatal( $msg, $this->getVar( 'wgDBuser' ) ); 00368 } 00369 00370 if ( !$exists ) { 00371 // No more checks to do 00372 return Status::newGood(); 00373 } 00374 00375 // Existing web account. Test the connection. 00376 $status = $this->openConnectionToAnyDB( 00377 $this->getVar( 'wgDBuser' ), 00378 $this->getVar( 'wgDBpassword' ) ); 00379 if ( !$status->isOK() ) { 00380 return $status; 00381 } 00382 00383 // The web user is conventionally the table owner in PostgreSQL 00384 // installations. Make sure the install user is able to create 00385 // objects on behalf of the web user. 00386 if ( $same || $this->canCreateObjectsForWebUser() ) { 00387 return Status::newGood(); 00388 } else { 00389 return Status::newFatal( 'config-pg-not-in-role' ); 00390 } 00391 } 00392 00398 protected function canCreateObjectsForWebUser() { 00399 if ( $this->isSuperUser() ) { 00400 return true; 00401 } 00402 00403 $status = $this->getPgConnection( 'create-db' ); 00404 if ( !$status->isOK() ) { 00405 return false; 00406 } 00407 $conn = $status->value; 00408 $installerId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid', 00409 array( 'rolname' => $this->getVar( '_InstallUser' ) ), __METHOD__ ); 00410 $webId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid', 00411 array( 'rolname' => $this->getVar( 'wgDBuser' ) ), __METHOD__ ); 00412 00413 return $this->isRoleMember( $conn, $installerId, $webId, $this->maxRoleSearchDepth ); 00414 } 00415 00424 protected function isRoleMember( $conn, $targetMember, $group, $maxDepth ) { 00425 if ( $targetMember === $group ) { 00426 // A role is always a member of itself 00427 return true; 00428 } 00429 // Get all members of the given group 00430 $res = $conn->select( '"pg_catalog"."pg_auth_members"', array( 'member' ), 00431 array( 'roleid' => $group ), __METHOD__ ); 00432 foreach ( $res as $row ) { 00433 if ( $row->member == $targetMember ) { 00434 // Found target member 00435 return true; 00436 } 00437 // Recursively search each member of the group to see if the target 00438 // is a member of it, up to the given maximum depth. 00439 if ( $maxDepth > 0 ) { 00440 if ( $this->isRoleMember( $conn, $targetMember, $row->member, $maxDepth - 1 ) ) { 00441 // Found member of member 00442 return true; 00443 } 00444 } 00445 } 00446 00447 return false; 00448 } 00449 00450 public function preInstall() { 00451 $createDbAccount = array( 00452 'name' => 'user', 00453 'callback' => array( $this, 'setupUser' ), 00454 ); 00455 $commitCB = array( 00456 'name' => 'pg-commit', 00457 'callback' => array( $this, 'commitChanges' ), 00458 ); 00459 $plpgCB = array( 00460 'name' => 'pg-plpgsql', 00461 'callback' => array( $this, 'setupPLpgSQL' ), 00462 ); 00463 $schemaCB = array( 00464 'name' => 'schema', 00465 'callback' => array( $this, 'setupSchema' ) 00466 ); 00467 00468 if ( $this->getVar( '_CreateDBAccount' ) ) { 00469 $this->parent->addInstallStep( $createDbAccount, 'database' ); 00470 } 00471 $this->parent->addInstallStep( $commitCB, 'interwiki' ); 00472 $this->parent->addInstallStep( $plpgCB, 'database' ); 00473 $this->parent->addInstallStep( $schemaCB, 'database' ); 00474 } 00475 00476 function setupDatabase() { 00477 $status = $this->getPgConnection( 'create-db' ); 00478 if ( !$status->isOK() ) { 00479 return $status; 00480 } 00481 $conn = $status->value; 00482 00483 $dbName = $this->getVar( 'wgDBname' ); 00484 00485 $exists = $conn->selectField( '"pg_catalog"."pg_database"', '1', 00486 array( 'datname' => $dbName ), __METHOD__ ); 00487 if ( !$exists ) { 00488 $safedb = $conn->addIdentifierQuotes( $dbName ); 00489 $conn->query( "CREATE DATABASE $safedb", __METHOD__ ); 00490 } 00491 00492 return Status::newGood(); 00493 } 00494 00495 function setupSchema() { 00496 // Get a connection to the target database 00497 $status = $this->getPgConnection( 'create-schema' ); 00498 if ( !$status->isOK() ) { 00499 return $status; 00500 } 00501 $conn = $status->value; 00502 00503 // Create the schema if necessary 00504 $schema = $this->getVar( 'wgDBmwschema' ); 00505 $safeschema = $conn->addIdentifierQuotes( $schema ); 00506 $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) ); 00507 if ( !$conn->schemaExists( $schema ) ) { 00508 try { 00509 $conn->query( "CREATE SCHEMA $safeschema AUTHORIZATION $safeuser" ); 00510 } catch ( DBQueryError $e ) { 00511 return Status::newFatal( 'config-install-pg-schema-failed', 00512 $this->getVar( '_InstallUser' ), $schema ); 00513 } 00514 } 00515 00516 // Select the new schema in the current connection 00517 $conn->determineCoreSchema( $schema ); 00518 00519 return Status::newGood(); 00520 } 00521 00522 function commitChanges() { 00523 $this->db->commit( __METHOD__ ); 00524 00525 return Status::newGood(); 00526 } 00527 00528 function setupUser() { 00529 if ( !$this->getVar( '_CreateDBAccount' ) ) { 00530 return Status::newGood(); 00531 } 00532 00533 $status = $this->getPgConnection( 'create-db' ); 00534 if ( !$status->isOK() ) { 00535 return $status; 00536 } 00537 $conn = $status->value; 00538 00539 $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) ); 00540 $safepass = $conn->addQuotes( $this->getVar( 'wgDBpassword' ) ); 00541 00542 // Check if the user already exists 00543 $userExists = $conn->roleExists( $this->getVar( 'wgDBuser' ) ); 00544 if ( !$userExists ) { 00545 // Create the user 00546 try { 00547 $sql = "CREATE ROLE $safeuser NOCREATEDB LOGIN PASSWORD $safepass"; 00548 00549 // If the install user is not a superuser, we need to make the install 00550 // user a member of the new user's group, so that the install user will 00551 // be able to create a schema and other objects on behalf of the new user. 00552 if ( !$this->isSuperUser() ) { 00553 $sql .= ' ROLE' . $conn->addIdentifierQuotes( $this->getVar( '_InstallUser' ) ); 00554 } 00555 00556 $conn->query( $sql, __METHOD__ ); 00557 } catch ( DBQueryError $e ) { 00558 return Status::newFatal( 'config-install-user-create-failed', 00559 $this->getVar( 'wgDBuser' ), $e->getMessage() ); 00560 } 00561 } 00562 00563 return Status::newGood(); 00564 } 00565 00566 function getLocalSettings() { 00567 $port = $this->getVar( 'wgDBport' ); 00568 $schema = $this->getVar( 'wgDBmwschema' ); 00569 00570 return "# Postgres specific settings 00571 \$wgDBport = \"{$port}\"; 00572 \$wgDBmwschema = \"{$schema}\";"; 00573 } 00574 00575 public function preUpgrade() { 00576 global $wgDBuser, $wgDBpassword; 00577 00578 # Normal user and password are selected after this step, so for now 00579 # just copy these two 00580 $wgDBuser = $this->getVar( '_InstallUser' ); 00581 $wgDBpassword = $this->getVar( '_InstallPassword' ); 00582 } 00583 00584 public function createTables() { 00585 $schema = $this->getVar( 'wgDBmwschema' ); 00586 00587 $status = $this->getConnection(); 00588 if ( !$status->isOK() ) { 00589 return $status; 00590 } 00591 00595 $conn = $status->value; 00596 00597 if ( $conn->tableExists( 'archive' ) ) { 00598 $status->warning( 'config-install-tables-exist' ); 00599 $this->enableLB(); 00600 00601 return $status; 00602 } 00603 00604 $conn->begin( __METHOD__ ); 00605 00606 if ( !$conn->schemaExists( $schema ) ) { 00607 $status->fatal( 'config-install-pg-schema-not-exist' ); 00608 00609 return $status; 00610 } 00611 $error = $conn->sourceFile( $conn->getSchemaPath() ); 00612 if ( $error !== true ) { 00613 $conn->reportQueryError( $error, 0, '', __METHOD__ ); 00614 $conn->rollback( __METHOD__ ); 00615 $status->fatal( 'config-install-tables-failed', $error ); 00616 } else { 00617 $conn->commit( __METHOD__ ); 00618 } 00619 // Resume normal operations 00620 if ( $status->isOk() ) { 00621 $this->enableLB(); 00622 } 00623 00624 return $status; 00625 } 00626 00627 public function getGlobalDefaults() { 00628 // The default $wgDBmwschema is null, which breaks Postgres and other DBMSes that require 00629 // the use of a schema, so we need to set it here 00630 return array( 00631 'wgDBmwschema' => 'mediawiki', 00632 ); 00633 } 00634 00635 public function setupPLpgSQL() { 00636 // Connect as the install user, since it owns the database and so is 00637 // the user that needs to run "CREATE LANGAUGE" 00638 $status = $this->getPgConnection( 'create-schema' ); 00639 if ( !$status->isOK() ) { 00640 return $status; 00641 } 00645 $conn = $status->value; 00646 00647 $exists = $conn->selectField( '"pg_catalog"."pg_language"', 1, 00648 array( 'lanname' => 'plpgsql' ), __METHOD__ ); 00649 if ( $exists ) { 00650 // Already exists, nothing to do 00651 return Status::newGood(); 00652 } 00653 00654 // plpgsql is not installed, but if we have a pg_pltemplate table, we 00655 // should be able to create it 00656 $exists = $conn->selectField( 00657 array( '"pg_catalog"."pg_class"', '"pg_catalog"."pg_namespace"' ), 00658 1, 00659 array( 00660 'pg_namespace.oid=relnamespace', 00661 'nspname' => 'pg_catalog', 00662 'relname' => 'pg_pltemplate', 00663 ), 00664 __METHOD__ ); 00665 if ( $exists ) { 00666 try { 00667 $conn->query( 'CREATE LANGUAGE plpgsql' ); 00668 } catch ( DBQueryError $e ) { 00669 return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) ); 00670 } 00671 } else { 00672 return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) ); 00673 } 00674 00675 return Status::newGood(); 00676 } 00677 }