diff --git a/Core/Frameworks/Baikal/Model/Calendar.php b/Core/Frameworks/Baikal/Model/Calendar.php index 1a9467148..6736c82b5 100644 --- a/Core/Frameworks/Baikal/Model/Calendar.php +++ b/Core/Frameworks/Baikal/Model/Calendar.php @@ -242,7 +242,7 @@ function isDefault() { function hasInstances() { $rSql = $GLOBALS["DB"]->exec_SELECTquery( - "count(*)", + "count(*) as count", "calendarinstances", "calendarid" . "='" . $this->aData["calendarid"] . "'" ); @@ -252,7 +252,7 @@ function hasInstances() { } else { reset($aRs); - return $aRs["count(*)"] > 1; + return $aRs["count"] > 1; } } diff --git a/Core/Frameworks/Baikal/Model/Calendar/Calendar.php b/Core/Frameworks/Baikal/Model/Calendar/Calendar.php index 4dfe46428..3031d7604 100644 --- a/Core/Frameworks/Baikal/Model/Calendar/Calendar.php +++ b/Core/Frameworks/Baikal/Model/Calendar/Calendar.php @@ -38,7 +38,7 @@ class Calendar extends \Flake\Core\Model\Db { function hasInstances() { $rSql = $GLOBALS["DB"]->exec_SELECTquery( - "count(*)", + "count(*) as count", "calendarinstances", "calendarid" . "='" . $this->aData["id"] . "'" ); @@ -48,7 +48,7 @@ function hasInstances() { } else { reset($aRs); - return $aRs["count(*)"] > 1; + return $aRs["count"] > 1; } } diff --git a/Core/Frameworks/Baikal/Model/Config/Database.php b/Core/Frameworks/Baikal/Model/Config/Database.php index e0035d59b..f26301068 100644 --- a/Core/Frameworks/Baikal/Model/Config/Database.php +++ b/Core/Frameworks/Baikal/Model/Config/Database.php @@ -37,6 +37,11 @@ class Database extends \Baikal\Model\Config { "mysql_username" => "", "mysql_password" => "", "encryption_key" => "", + "pgsql" => false, + "pgsql_host" => "", + "pgsql_dbname" => "", + "pgsql_username" => "", + "pgsql_password" => "", ]; function __construct() { @@ -82,6 +87,34 @@ function formMorphologyForThisModelInstance() { "label" => "MySQL password", ])); + $oMorpho->add(new \Formal\Element\Checkbox(array( + "prop" => "pgsql", + "label" => "Use PostgreSQL", + "help" => "If checked, Baïkal will use PostgreSQL", + "refreshonchange" => true, + ))); + + $oMorpho->add(new \Formal\Element\Text(array( + "prop" => "pgsql_host", + "label" => "PostgreSQL host", + "help" => "Host ip or name, including ':portnumber' if port is not the default one (?)" + ))); + + $oMorpho->add(new \Formal\Element\Text(array( + "prop" => "pgsql_dbname", + "label" => "PostgreSQL database name", + ))); + + $oMorpho->add(new \Formal\Element\Text(array( + "prop" => "pgsql_username", + "label" => "PostgreSQL username", + ))); + + $oMorpho->add(new \Formal\Element\Password(array( + "prop" => "pgsql_password", + "label" => "PostgreSQL password", + ))); + return $oMorpho; } diff --git a/Core/Frameworks/BaikalAdmin/Controller/Install/Database.php b/Core/Frameworks/BaikalAdmin/Controller/Install/Database.php index 4a57584f5..1da56b788 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Install/Database.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Install/Database.php @@ -48,8 +48,8 @@ function execute() { $this->oForm = $this->oModel->formForThisModelInstance([ "close" => false, - "hook.validation" => [$this, "validateConnection"], - "hook.morphology" => [$this, "hideMySQLFieldWhenNeeded"], + "hook.validation" => [$this, "validateSQLConnection"], + "hook.morphology" => [$this, "hideSQLFieldWhenNeeded"], ]); if ($this->oForm->submitted()) { @@ -99,7 +99,7 @@ function render() { return $oView->render(); } - function validateConnection($oForm, $oMorpho) { + function validateMySQLConnection($oForm, $oMorpho) { if ($oForm->refreshed()) { return true; } @@ -226,4 +226,102 @@ function hideMySQLFieldWhenNeeded(\Formal\Form $oForm, \Formal\Form\Morphology $ $oMorpho->remove("mysql_password"); } } + + function validatePgSQLConnection($oForm, $oMorpho) { + $bPgSqlEnabled = $oMorpho->element("pgsql")->value(); + + if ($bPgSqlEnabled) { + $sHost = $oMorpho->element("pgsql_host")->value(); + $sDbname = $oMorpho->element("pgsql_dbname")->value(); + $sUsername = $oMorpho->element("pgsql_username")->value(); + $sPassword = $oMorpho->element("pgsql_password")->value(); + + try { + $oDb = new \Flake\Core\Database\Pgsql( + $sHost, + $sDbname, + $sUsername, + $sPassword + ); + + if(($aMissingTables = \Baikal\Core\Tools::isDBStructurallyComplete($oDb)) !== true) { + + # Checking if all tables are missing + $aRequiredTables = \Baikal\Core\Tools::getRequiredTablesList(); + if(count($aRequiredTables) !== count($aMissingTables)) { + $sMessage = "

Database is not structurally complete.

"; + $sMessage .= "

Missing tables are: " . implode(", ", $aMissingTables) . "

"; + $sMessage .= "

You will find the SQL definition of Baïkal tables in this file: Core/Resources/Db/PgSQL/db.sql

"; + $sMessage .= "

Nothing has been saved. Please, add these tables to the database before pursuing Baïkal initialization.

"; + + $oForm->declareError( + $oMorpho->element("pgsql"), + $sMessage + ); + } else { + # All tables are missing + # We add these tables ourselves to the database, to initialize Baïkal + $sSqlDefinition = file_get_contents(PROJECT_PATH_CORERESOURCES . "Db/PgSQL/db.sql"); + $oDb->getPDO()->exec($sSqlDefinition); + } + } + return TRUE; + } catch (\Exception $e) { + $oForm->declareError( + $oMorpho->element("pgsql"), + "Baïkal was not able to establish a connexion to the PostgreSQL database as configured.
PostgreSQL says: " . $e->getMessage() + ); + + $oForm->declareError( + $oMorpho->element("pgsql_host") + ); + + $oForm->declareError( + $oMorpho->element("pgsql_dbname") + ); + + $oForm->declareError( + $oMorpho->element("pgsql_username") + ); + + $oForm->declareError( + $oMorpho->element("pgsql_password") + ); + } + } + } + + public function validateSQLConnection($oForm, $oMorpho) { + if ($oMorpho->element("mysql")->value()) { + $this->validateMySQLConnection($oForm, $oMorpho); + } else if ($oMorpho->element("pgsql")->value()) { + $this->validatePgSQLConnection($oForm, $oMorpho); + } + } + + public function hideSqlFieldWhenNeeded(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { + if ($oMorpho->element("mysql")->value()) { + $this->hideMySQLFieldWhenNeeded($oForm, $oMorpho); + } else if ($oMorpho->element("pgsql")->value()) { + $this->hidePgSQLFieldWhenNeeded($oForm, $oMorpho); + } + } + + public function hidePgSQLFieldWhenNeeded(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { + if($oForm->submitted()) { + $bPgSQL = (intval($oForm->postValue("pgsql")) === 1); + } else { + $bPgSQL = pgsql; + } + + if($bPgSQL === true) { + $oMorpho->remove("sqlite_file"); + $this->hideMySQLFieldWhenNeeded($oForm, $oMorpho); + } else { + $oMorpho->remove("pgsql_host"); + $oMorpho->remove("pgsql_dbname"); + $oMorpho->remove("pgsql_username"); + $oMorpho->remove("pgsql_password"); + } + } } diff --git a/Core/Frameworks/Flake/Core/Database/Pgsql.php b/Core/Frameworks/Flake/Core/Database/Pgsql.php new file mode 100644 index 000000000..d84565d95 --- /dev/null +++ b/Core/Frameworks/Flake/Core/Database/Pgsql.php @@ -0,0 +1,67 @@ + +# All rights reserved +# +# http://flake.codr.fr +# +# This script is part of the Flake project. The Flake +# project is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# This copyright notice MUST APPEAR in all copies of the script! +################################################################# + +namespace Flake\Core\Database; + +class Pgsql extends \Flake\Core\Database { + + protected $oDb = FALSE; // current DB link + protected $debugOutput = FALSE; + protected $store_lastBuiltQuery = TRUE; + protected $debug_lastBuiltQuery = ""; + protected $sHost = ""; + protected $sDbName = ""; + protected $sUsername = ""; + protected $sPassword = ""; + + public function __construct($sHost, $sDbName, $sUsername, $sPassword) { + $this->sHost = $sHost; + $this->sDbName = $sDbName; + $this->sUsername = $sUsername; + $this->sPassword = $sPassword; + + $this->oDb = new \PDO( + 'pgsql:host=' . $this->sHost . ';dbname=' . $this->sDbName, + $this->sUsername, + $this->sPassword + ); + } + + public function tables() { + $aTables = array(); + + $sSql = "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'public'"; + $oStmt = $this->query($sSql); + + while(($aRs = $oStmt->fetch()) !== FALSE) { + $aTables[] = array_shift($aRs); + } + + asort($aTables); + reset($aTables); + return $aTables; + } +} diff --git a/Core/Frameworks/Flake/Framework.php b/Core/Frameworks/Flake/Framework.php index 73f6b9df4..b7780455a 100644 --- a/Core/Frameworks/Flake/Framework.php +++ b/Core/Frameworks/Flake/Framework.php @@ -261,6 +261,8 @@ protected static function initDb() { } if ($config['database']['mysql'] === true) { self::initDbMysql($config); + } elseif ($config['database']['pgsql'] === true) { + self::initDbPgsql($config); } else { self::initDbSqlite($config); } @@ -325,6 +327,37 @@ protected static function initDbMysql(array $config) { return true; } + protected static function initDbPgsql(array $config) { + if (!$config['database']['pgsql_host']) { + die("

The constant PROJECT_DB_PGSQL_HOST, containing the PostgreSQL host name, is not set.
You should set it in config/baikal.yaml

"); + } + + if (!$config['database']['pgsql_dbname']) { + die("

The constant PROJECT_DB_PGSQL_DBNAME, containing the PostgreSQL database name, is not set.
You should set it in config/baikal.yaml

"); + } + + if (!$config['database']['pgsql_username']) { + die("

The constant PROJECT_DB_PGSQL_USERNAME, containing the PostgreSQL database username, is not set.
You should set it in config/baikal.yaml

"); + } + + if ($config['database']['pgsql_password'] === null) { + die("

The constant PROJECT_DB_PGSQL_PASSWORD, containing the PostgreSQL database password, is not set.
You should set it in config/baikal.yaml

"); + } + + try { + $GLOBALS["DB"] = new \Flake\Core\Database\Pgsql( + $config['database']['pgsql_host'], + $config['database']['pgsql_dbname'], + $config['database']['pgsql_username'], + $config['database']['pgsql_password'] + ); + + $GLOBALS["DB"]->query("SET NAMES 'UTF8'"); + } catch(\Exception $e) { + die("

Baïkal was not able to establish a connection to the configured PostgreSQL database (as configured in config/baikal.yaml).

"); + } + } + static function isDBInitialized() { return isset($GLOBALS["DB"]) && \Flake\Util\Tools::is_a($GLOBALS["DB"], "\Flake\Core\Database"); } diff --git a/Core/Resources/Db/PgSQL/db.sql b/Core/Resources/Db/PgSQL/db.sql new file mode 100644 index 000000000..7335dbb2a --- /dev/null +++ b/Core/Resources/Db/PgSQL/db.sql @@ -0,0 +1,142 @@ + +CREATE TABLE addressbooks ( + id SERIAL PRIMARY KEY, + principaluri TEXT, + displayname VARCHAR(255), + uri TEXT, + description TEXT, + synctoken INT CHECK (synctoken > 0) NOT NULL DEFAULT '1' +); + +CREATE TABLE cards ( + id SERIAL PRIMARY KEY, + addressbookid INT CHECK (addressbookid > 0) NOT NULL, + carddata TEXT, + uri TEXT, + lastmodified INT CHECK (lastmodified > 0), + etag TEXT, + size INT CHECK (size > 0) NOT NULL +); + +CREATE TABLE addressbookchanges ( + id SERIAL PRIMARY KEY, + uri TEXT NOT NULL, + synctoken INT CHECK (synctoken > 0) NOT NULL, + addressbookid INT CHECK (addressbookid > 0) NOT NULL, + operation SMALLINT NOT NULL +); + +CREATE INDEX addressbookid_synctoken ON addressbookchanges (addressbookid, synctoken); + +CREATE TABLE calendarobjects ( + id SERIAL PRIMARY KEY, + calendardata TEXT, + uri TEXT, + calendarid INTEGER CHECK (calendarid > 0) NOT NULL, + lastmodified INT CHECK (lastmodified > 0), + etag TEXT, + size INT CHECK (size > 0) NOT NULL, + componenttype TEXT, + firstoccurence INT CHECK (firstoccurence > 0), + lastoccurence INT CHECK (lastoccurence > 0), + uid TEXT +); + +CREATE TABLE calendars ( + id SERIAL PRIMARY KEY, + synctoken INTEGER CHECK (synctoken > 0) NOT NULL DEFAULT '1', + components TEXT +); + +CREATE TABLE calendarinstances ( + id SERIAL PRIMARY KEY, + calendarid INTEGER CHECK (calendarid > 0) NOT NULL, + principaluri TEXT, + access SMALLINT NOT NULL DEFAULT '1', + displayname VARCHAR(100), + uri TEXT, + description TEXT, + calendarorder INT CHECK (calendarorder >= 0) NOT NULL DEFAULT '0', + calendarcolor TEXT, + timezone TEXT, + transparent SMALLINT NOT NULL DEFAULT '0', + share_href TEXT, + share_displayname VARCHAR(100), + share_invitestatus SMALLINT NOT NULL DEFAULT '2' +); + +CREATE TABLE calendarchanges ( + id SERIAL PRIMARY KEY, + uri TEXT NOT NULL, + synctoken INT CHECK (synctoken > 0) NOT NULL, + calendarid INT CHECK (calendarid > 0) NOT NULL, + operation SMALLINT NOT NULL +); + +CREATE INDEX calendarid_synctoken ON calendarchanges (calendarid, synctoken); + +CREATE TABLE calendarsubscriptions ( + id SERIAL PRIMARY KEY, + uri TEXT NOT NULL, + principaluri TEXT NOT NULL, + source TEXT, + displayname VARCHAR(100), + refreshrate VARCHAR(10), + calendarorder INT CHECK (calendarorder >= 0) NOT NULL DEFAULT '0', + calendarcolor TEXT, + striptodos SMALLINT NULL, + stripalarms SMALLINT NULL, + stripattachments SMALLINT NULL, + lastmodified INT CHECK (lastmodified > 0) +); + +CREATE TABLE schedulingobjects ( + id SERIAL PRIMARY KEY, + principaluri TEXT, + calendardata TEXT, + uri TEXT, + lastmodified INT CHECK (lastmodified > 0), + etag TEXT, + size INT CHECK (size > 0) NOT NULL +); +CREATE TABLE locks ( + id SERIAL PRIMARY KEY, + owner VARCHAR(100), + timeout INTEGER CHECK (timeout > 0), + created INTEGER, + token TEXT, + scope SMALLINT, + depth SMALLINT, + uri TEXT +); + +CREATE INDEX ON locks (token); +CREATE INDEX ON locks (uri); + +CREATE TABLE principals ( + id SERIAL PRIMARY KEY, + uri TEXT NOT NULL, + email TEXT, + displayname VARCHAR(80) +); + +CREATE TABLE groupmembers ( + id SERIAL PRIMARY KEY, + principal_id INTEGER CHECK (principal_id > 0) NOT NULL, + member_id INTEGER CHECK (member_id > 0) NOT NULL +); + +CREATE TABLE propertystorage ( + id SERIAL PRIMARY KEY, + path TEXT NOT NULL, + name TEXT NOT NULL, + valuetype INT CHECK (valuetype > 0), + value TEXT +); + +CREATE UNIQUE INDEX path_property ON propertystorage (path, name); +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username TEXT, + digesta1 TEXT +);