diff --git a/airtime_mvc/build/airtime-setup/forms/database-settings.php b/airtime_mvc/build/airtime-setup/forms/database-settings.php index fa03926a2..4bc93ff8b 100644 --- a/airtime_mvc/build/airtime-setup/forms/database-settings.php +++ b/airtime_mvc/build/airtime-setup/forms/database-settings.php @@ -17,6 +17,7 @@
+
diff --git a/airtime_mvc/public/setup/database-setup.php b/airtime_mvc/public/setup/database-setup.php index 115cf840c..769431778 100644 --- a/airtime_mvc/public/setup/database-setup.php +++ b/airtime_mvc/public/setup/database-setup.php @@ -18,197 +18,173 @@ class DatabaseSetup extends Setup { DB_HOST = "dbHost"; // Form field values - static $user, $pass, $name, $host; + private $user, $pass, $name, $host; // Array of key->value pairs for airtime.conf static $properties; - // Message and error fields to return to the front-end - static $message = null; - static $errors = array(); + static $dbh = null; - function __construct($settings) { - self::$user = $settings[self::DB_USER]; - self::$pass = $settings[self::DB_PASS]; - self::$name = $settings[self::DB_NAME]; - self::$host = $settings[self::DB_HOST]; + public function __construct($settings) { + $this->user = $settings[self::DB_USER]; + $this->pass = $settings[self::DB_PASS]; + $this->name = $settings[self::DB_NAME]; + $this->host = $settings[self::DB_HOST]; self::$properties = array( - "host" => self::$host, - "dbname" => self::$name, - "dbuser" => self::$user, - "dbpass" => self::$pass, + "host" => $this->host, + "dbname" => $this->name, + "dbuser" => $this->user, + "dbpass" => $this->pass, ); } + private function setNewDatabaseConnection($dbName) { + self::$dbh = new PDO("pgsql:host=" . $this->host . ";dbname=" . $dbName . ";port=5432" + . ";user=" . $this->user . ";password=" . $this->pass); + $err = self::$dbh->errorInfo(); + if ($err[1] != null) { + throw new AirtimeDatabaseException("Couldn't establish a connection to the database!", + array( + self::DB_NAME, + self::DB_USER, + self::DB_PASS, + )); + } + } + /** * Runs various database checks against the given settings. If a database with the given name already exists, * we attempt to install the Airtime schema. If not, we first check if the user can create databases, then try * to create the database. If we encounter errors, the offending fields are returned in an array to the browser. * @return array associative array containing a display message and fields with errors + * @throws AirtimeDatabaseException */ - function runSetup() { - // Check the connection and user credentials - if ($this->checkDatabaseConnection()) { - // We know that the user credentials check out, so check if the database exists - if ($this->checkDatabaseExists()) { - // The database already exists, check if the schema exists as well - if ($this->checkSchemaExists()) { - self::$message = "Airtime is already installed in this database!"; - } else { - if ($this->createDatabaseTables()) { - self::$message = "Successfully installed Airtime database to '" . self::$name . "'"; - } else { - self::$message = "Something went wrong setting up the Airtime schema!"; - self::$errors[] = self::DB_NAME; - } - } - } else { - // The database doesn't exist, so check if the user can create databases - if ($this->checkUserCanCreateDb()) { - // The user can create a database, do it - if ($this->createDatabase()) { - // Ensure that the database was installed in UTF8 (we only care about the Airtime database) - if ($this->checkDatabaseSchema()) { - if ($this->createDatabaseTables()) { - self::$message = "Successfully installed Airtime database to '" . self::$name . "'"; - } else { - self::$message = "Something went wrong setting up the Airtime schema!"; - self::$errors[] = self::DB_NAME; - } - } else { - self::$message = "The database was installed with an incorrect encoding type!"; - self::$errors[] = self::DB_NAME; - } - } else { - self::$message = "There was an error installing the database!"; - self::$errors[] = self::DB_NAME; - } - } // The user can't create databases, so we're done - else { - self::$message = "No database " . self::$name . " exists; user " . self::$user - . " does not have permission to create databases on " . self::$host; - self::$errors[] = self::DB_NAME; - } - } + public function runSetup() { + $this->setNewDatabaseConnection("postgres"); + if ($this->checkDatabaseExists()) { + $this->installDatabaseTables(); + } else { + $this->checkUserCanCreateDb(); + $this->createDatabase(); + $this->installDatabaseTables(); } - if (count(self::$errors) <= 0) { - $this->writeToTemp(); - } + $this->writeToTemp(); + self::$dbh = null; return array( - "message" => self::$message, - "errors" => self::$errors, + "message" => "Airtime database was created successfully!", + "errors" => array(), ); } - function writeToTemp() { + protected function writeToTemp() { parent::writeToTemp(self::SECTION, self::$properties); } - - /** - * Check if the user's database connection is valid - * @return boolean true if the connection are valid - */ - function checkDatabaseConnection() { - // This is pretty redundant, but we need to test both - // the existence and the validity of the given credentials - exec("export PGPASSWORD=" . self::$pass . " && psql -h " - . self::$host . " -U " . self::$user . " 2>&1", $out, $status); - foreach ($out as $o) { - if (strpos($o, "host name")) { - self::$message = "Invalid connection parameters!"; - self::$errors[] = self::DB_HOST; - return false; - } else if (strpos($o, "authentication")) { - self::$message = "User credentials are invalid!"; - self::$errors[] = self::DB_USER; - self::$errors[] = self::DB_PASS; - return false; - } - } - return $status == 0; + private function installDatabaseTables() { + $this->checkDatabaseEncoding(); + $this->setNewDatabaseConnection($this->name); + $this->checkSchemaExists(); + $this->createDatabaseTables(); } /** * Check if the database settings and credentials given are valid * @return boolean true if the database given exists and the user is valid and can access it */ - function checkDatabaseExists() { - exec("export PGPASSWORD=" . self::$pass . " && psql -lqt -h " . self::$host . " -U " . self::$user - . "| cut -d \\| -f 1 | grep -w " . self::$name, $out, $status); - return $status == 0; + private function checkDatabaseExists() { + $statement = self::$dbh->prepare("SELECT datname FROM pg_database WHERE datname = :dbname"); + $statement->execute(array(":dbname" => $this->name)); + $result = $statement->fetch(); + return isset($result[0]); } /** * Check if the database schema has already been set up - * @return boolean true if the database schema exists + * @throws AirtimeDatabaseException */ - function checkSchemaExists() { - // Check for cc_pref to see if the schema is already installed in this database - exec("export PGPASSWORD=" . self::$pass . " && psql -U " . self::$user . " -h " - . self::$host . " -d " . self::$name . " -tAc \"SELECT * FROM cc_pref\"", $out, $status); - return $status == 0; + private function checkSchemaExists() { + $statement = self::$dbh->prepare("SELECT EXISTS (SELECT relname FROM pg_class WHERE relname='cc_files')"); + $statement->execute(); + $result = $statement->fetch(); + if (isset($result[0]) && $result[0] == "t") { + throw new AirtimeDatabaseException("Airtime is already installed in this database!", array()); + } } /** * Check if the given user has access on the given host to create a new database - * @return boolean true if the given user has permission to create a database on the given host + * @throws AirtimeDatabaseException */ - function checkUserCanCreateDb() { - exec("export PGPASSWORD=" . self::$pass . " && psql -h " . self::$host . " -U " . self::$user . " -tAc" - . "\"SELECT 1 FROM pg_roles WHERE rolname='" . self::$user . "' AND rolcreatedb='t'\"", $out, $status); - return $status == 0; + private function checkUserCanCreateDb() { + $statement = self::$dbh->prepare("SELECT 1 FROM pg_roles WHERE rolname=:dbuser AND rolcreatedb='t'"); + $statement->execute(array(":dbuser" => $this->user)); + $result = $statement->fetch(); + if (!isset($result[0])) { + throw new AirtimeDatabaseException("No database " . $this->name . " exists; user '" . $this->user + . "' does not have permission to create databases on " . $this->host, + array( + self::DB_NAME, + self::DB_USER, + self::DB_PASS, + )); + } } /** * Creates the Airtime database using the given credentials - * @return boolean true if the database was created + * @throws AirtimeDatabaseException */ - function createDatabase() { - exec("export PGPASSWORD=" . self::$pass . " && psql -h " . self::$host . " -U " . self::$user . " -tAc" - . "\"CREATE DATABASE " . self::$name . " WITH ENCODING 'UTF8' TEMPLATE template0 OWNER " - . self::$user . "\"", $out, $status); - return $status == 0; + private function createDatabase() { + $statement = self::$dbh->prepare("CREATE DATABASE " . pg_escape_string($this->name) + . " WITH ENCODING 'UTF8' TEMPLATE template0" + . " OWNER " . pg_escape_string($this->user)); + if (!$statement->execute()) { + throw new AirtimeDatabaseException("There was an error creating the database!", + array(self::DB_NAME,)); + } } /** * Creates the Airtime database schema using the given credentials - * @return boolean true if the database tables were created without error + * @throws AirtimeDatabaseException */ - function createDatabaseTables() { + private function createDatabaseTables() { $sqlDir = dirname(dirname(__DIR__)) . "/build/sql/"; $files = array("schema.sql", "sequences.sql", "views.sql", "triggers.sql", "defaultdata.sql"); - foreach ($files as $f) { try { - exec("export PGPASSWORD=" . self::$pass . " && psql -U " . self::$user . " --dbname " - . self::$name . " -h " . self::$host . " -f $sqlDir$f 2>/dev/null", $out, $status); + /* + * Unfortunately, we need to use exec here due to PDO's lack of support for importing + * multi-line .sql files. PDO->exec() almost works, but any SQL errors stop the import, + * so the necessary DROPs on non-existent tables make it unusable. Prepared statements + * have multiple issues; they similarly die on any SQL errors, fail to read in multi-line + * commands, and fail on any unescaped ? or $ characters. + */ + exec("export PGPASSWORD=" . $this->pass . " && psql -U " . $this->user . " --dbname " + . $this->name . " -h " . $this->host . " -f $sqlDir$f 2>/dev/null", $out, $status); } catch (Exception $e) { - return false; + throw new AirtimeDatabaseException("There was an error setting up the Airtime schema!", + array(self::DB_NAME,)); } } - return true; } /** * Checks whether the newly-created database's encoding was properly set to UTF8 - * @return boolean true if the database encoding is UTF8 + * @throws AirtimeDatabaseException */ - function checkDatabaseEncoding() { - exec("export PGPASSWORD=" . self::$pass . " && psql -U " . self::$user . " -h " - . self::$host . " -d " . self::$name . " -tAc \"SHOW SERVER_ENCODING\"", $out, $status); - return $out && $out[0] == "UTF8"; + private function checkDatabaseEncoding() { + $statement = self::$dbh->prepare("SELECT pg_encoding_to_char(encoding) " + . "FROM pg_database WHERE datname = :dbname"); + $statement->execute(array(":dbname" => $this->name)); + $encoding = $statement->fetch(); + if (!($encoding && $encoding[0] == "UTF8")) { + throw new AirtimeDatabaseException("The database was installed with an incorrect encoding type!", + array(self::DB_NAME,)); + } } - // TODO Since we already check the encoding, is there a purpose to verifying the schema? - function checkDatabaseSchema() { - $outFile = "/tmp/tempSchema.xml"; - exec("export PGPASSWORD=" . self::$pass . " && psql -U " . self::$user . " -h " - . self::$host . " -o ${outFile} -tAc \"SELECT database_to_xml(FALSE, FALSE, '" - . self::$name . "')\"", $out, $status); - } - -} +} \ No newline at end of file