From 080c608ea9446a2844c7dcb7bf3a9eafae2d730f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 06:25:40 +0000 Subject: [PATCH 1/8] Update friendsofphp/php-cs-fixer requirement from 3.51.0 to 3.52.1 Updates the requirements on [friendsofphp/php-cs-fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer) to permit the latest version. - [Release notes](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases) - [Changelog](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/CHANGELOG.md) - [Commits](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/compare/v3.51.0...v3.52.1) --- updated-dependencies: - dependency-name: friendsofphp/php-cs-fixer dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f34d884..eebf3e2 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "ext-zlib" : "*" }, "require-dev" : { - "friendsofphp/php-cs-fixer": "3.51.0", + "friendsofphp/php-cs-fixer": "3.52.1", "phpstan/phpstan": "^1.10" }, "replace" : { From 6859fb78e94a533d0c7d1961ed8dcf08a87de7d6 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Thu, 18 Apr 2024 17:16:12 +0545 Subject: [PATCH 2/8] chore: fix indent suggested by cs-fixer --- Core/Frameworks/Baikal/Model/Calendar.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/Frameworks/Baikal/Model/Calendar.php b/Core/Frameworks/Baikal/Model/Calendar.php index c27ba9c..9b429b1 100644 --- a/Core/Frameworks/Baikal/Model/Calendar.php +++ b/Core/Frameworks/Baikal/Model/Calendar.php @@ -202,10 +202,10 @@ class Calendar extends \Flake\Core\Model\Db { "label" => "Calendar color", "validation" => "color", "popover" => [ - "title" => "Calendar color", - "content" => "This is the color that will be displayed in your CalDAV client.
" . - "Must be supplied in format '#RRGGBBAA' (alpha channel optional) with hexadecimal values.
" . - "This value is optional.", + "title" => "Calendar color", + "content" => "This is the color that will be displayed in your CalDAV client.
" . + "Must be supplied in format '#RRGGBBAA' (alpha channel optional) with hexadecimal values.
" . + "This value is optional.", ], ])); From 9037d6f2e2f0143e8f9e5fa349a78f013976584a Mon Sep 17 00:00:00 2001 From: Leeloo <164774099+criticalsool@users.noreply.github.com> Date: Wed, 29 May 2024 17:04:10 +0200 Subject: [PATCH 3/8] Update french guide link (#1260) Update french guide link not accurate in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ec0e76..d4abfc0 100644 --- a/README.md +++ b/README.md @@ -27,4 +27,4 @@ Many thanks to Daniel Aleksandersen (@zcode) for greatly improving the quality o [4]: https://fruux.com/ [5]: https://sabre.io/baikal/upgrade/ [6]: https://github.com/JsBergbau/BaikalAnleitung -[7]: https://github.com/AlexandreMonroche/BaikalGuide +[7]: https://github.com/criticalsool/Baikal-Guide-FR From d9dc5070126af9ebd7418d485807f05f73a7d4cb Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Wed, 29 May 2024 17:05:02 +0200 Subject: [PATCH 4/8] Create dist builds with lowest supported php version (#1267) --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index e7a4da1..0ae39c0 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,7 @@ dist: vendor/autoload.php $(BUILD_FILES) \ --exclude="*.swp" \ $(BUILD_DIR) + composer config platform.php 7.2 -d $(BUILD_DIR) composer install --no-interaction --no-dev -d $(BUILD_DIR) rm $(BUILD_DIR)/composer.* cd build; zip -r baikal-$(VERSION).zip baikal/ From 2ee4be608d9d353a1b82444c746855866fc7f883 Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Sat, 13 Jul 2024 13:26:08 +0200 Subject: [PATCH 5/8] Use base uri on login screen (#1273) --- Core/Frameworks/BaikalAdmin/Controller/Login.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/BaikalAdmin/Controller/Login.php b/Core/Frameworks/BaikalAdmin/Controller/Login.php index 9b5689c..70aa682 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Login.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Login.php @@ -34,7 +34,7 @@ class Login extends \Flake\Core\Controller { } function render() { - $sActionUrl = \Flake\Util\Tools::getCurrentUrl(); + $sActionUrl = PROJECT_BASEURI; $sSubmittedFlagName = "auth"; $sMessage = ""; From ae0a34d324d44d660f68aa4b1a775f918419af12 Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Sun, 21 Jul 2024 09:40:48 +0200 Subject: [PATCH 6/8] Fix login page url (#1278) Use Flake framework to get login url. Otherwise slashes in the end of the urls are not handled correctly. Basically reverts 2ee4be608d9d353a1b82444c746855866fc7f883 --- Core/Frameworks/BaikalAdmin/Controller/Login.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Frameworks/BaikalAdmin/Controller/Login.php b/Core/Frameworks/BaikalAdmin/Controller/Login.php index 70aa682..80c2b5d 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Login.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Login.php @@ -34,7 +34,7 @@ class Login extends \Flake\Core\Controller { } function render() { - $sActionUrl = PROJECT_BASEURI; + $sActionUrl = $GLOBALS["ROUTER"]::buildRoute("default", []); $sSubmittedFlagName = "auth"; $sMessage = ""; From cc3eca1dc53bd0328cecefa0a9308bee47fd77f4 Mon Sep 17 00:00:00 2001 From: wrvsrx <42770726+wrvsrx@users.noreply.github.com> Date: Sun, 21 Jul 2024 16:24:53 +0800 Subject: [PATCH 7/8] Support config `PROJECT_PATH_CONFIG` and `PROJECT_PATH_SPECIFIC` via environment variables (#1270) --- Core/Frameworks/Flake/Framework.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Core/Frameworks/Flake/Framework.php b/Core/Frameworks/Flake/Framework.php index 579e25a..347ad67 100644 --- a/Core/Frameworks/Flake/Framework.php +++ b/Core/Frameworks/Flake/Framework.php @@ -163,11 +163,25 @@ class Framework extends \Flake\Core\Framework { define("PROJECT_PATH_CORE", PROJECT_PATH_ROOT . "Core/"); define("PROJECT_PATH_CORERESOURCES", PROJECT_PATH_CORE . "Resources/"); - define("PROJECT_PATH_SPECIFIC", PROJECT_PATH_ROOT . "Specific/"); - define("PROJECT_PATH_CONFIG", PROJECT_PATH_ROOT . "config/"); define("PROJECT_PATH_FRAMEWORKS", PROJECT_PATH_CORE . "Frameworks/"); define("PROJECT_PATH_WWWROOT", PROJECT_PATH_CORE . "WWWRoot/"); + // set PROJECT_PATH_CONFIG from BAIKAL_PATH_CONFIG + $baikalPathConfig = getenv('BAIKAL_PATH_CONFIG'); + if ($baikalPathConfig !== false) { + define("PROJECT_PATH_CONFIG", $baikalPathConfig); + } else { + define("PROJECT_PATH_CONFIG", PROJECT_PATH_ROOT . "config/"); + } + + // set PROJECT_PATH_SPECIFIC from BAIKAL_PATH_CONFIG + $baikalPathConfig = getenv('BAIKAL_PATH_SPECIFIC'); + if ($baikalPathConfig !== false) { + define("PROJECT_PATH_SPECIFIC", $baikalPathConfig); + } else { + define("PROJECT_PATH_SPECIFIC", PROJECT_PATH_ROOT . "Specific/"); + } + require_once PROJECT_PATH_CORE . "Distrib.php"; define("PROJECT_PATH_DOCUMENTROOT", PROJECT_PATH_ROOT . "html/"); From 35a3d018f135ab2a686206be20475cc792cf4596 Mon Sep 17 00:00:00 2001 From: leso-kn Date: Sun, 21 Jul 2024 18:30:25 +0200 Subject: [PATCH 8/8] Add PostgreSQL backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kim Lidström --- Core/Frameworks/Baikal/Core/Tools.php | 4 +- Core/Frameworks/Baikal/Model/Calendar.php | 4 +- .../Baikal/Model/Calendar/Calendar.php | 4 +- .../Baikal/Model/Config/Database.php | 42 +++++- .../Controller/Install/Database.php | 121 +++++++++++++-- .../Controller/Install/Initialize.php | 8 +- .../Controller/Install/VersionUpgrade.php | 8 + .../Controller/Settings/Database.php | 58 ++++--- Core/Frameworks/Flake/Core/Database/Pgsql.php | 68 +++++++++ Core/Frameworks/Flake/Framework.php | 38 ++++- Core/Resources/Db/PgSQL/db.sql | 142 ++++++++++++++++++ config/baikal.yaml.dist | 6 +- 12 files changed, 457 insertions(+), 46 deletions(-) create mode 100644 Core/Frameworks/Flake/Core/Database/Pgsql.php create mode 100644 Core/Resources/Db/PgSQL/db.sql diff --git a/Core/Frameworks/Baikal/Core/Tools.php b/Core/Frameworks/Baikal/Core/Tools.php index 25f7808..73bb28e 100644 --- a/Core/Frameworks/Baikal/Core/Tools.php +++ b/Core/Frameworks/Baikal/Core/Tools.php @@ -45,8 +45,8 @@ class Tools { # Asserting PDO::SQLite or PDO::MySQL $aPDODrivers = \PDO::getAvailableDrivers(); - if (!in_array('sqlite', $aPDODrivers, true) && !in_array('mysql', $aPDODrivers, true)) { - exit('Baikal Fatal Error: Both PDO::sqlite and PDO::mysql are unavailable. One of them at least is required by Baikal.'); + if (!in_array('sqlite', $aPDODrivers, true) && !in_array('mysql', $aPDODrivers, true) && !in_array('pgsql', $aPDODrivers, true)) { + exit('Baikal Fatal Error: None of PDO::sqlite, PDO::mysql or PDO::pgsql are available. One of them at least is required by Baikal.'); } # Assert that the temp folder is writable diff --git a/Core/Frameworks/Baikal/Model/Calendar.php b/Core/Frameworks/Baikal/Model/Calendar.php index 9b429b1..8b55b3b 100644 --- a/Core/Frameworks/Baikal/Model/Calendar.php +++ b/Core/Frameworks/Baikal/Model/Calendar.php @@ -244,7 +244,7 @@ class Calendar extends \Flake\Core\Model\Db { function hasInstances() { $rSql = $GLOBALS["DB"]->exec_SELECTquery( - "count(*)", + "count(*) as count", "calendarinstances", "calendarid='" . $this->aData["calendarid"] . "'" ); @@ -254,7 +254,7 @@ class Calendar extends \Flake\Core\Model\Db { } 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 9c75ade..980a318 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 @@ class Calendar extends \Flake\Core\Model\Db { } 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 e0035d5..4ce20c0 100644 --- a/Core/Frameworks/Baikal/Model/Config/Database.php +++ b/Core/Frameworks/Baikal/Model/Config/Database.php @@ -31,12 +31,16 @@ class Database extends \Baikal\Model\Config { # Default values protected $aData = [ "sqlite_file" => PROJECT_PATH_SPECIFIC . "db/db.sqlite", - "mysql" => false, + "backend" => "", "mysql_host" => "", "mysql_dbname" => "", "mysql_username" => "", "mysql_password" => "", "encryption_key" => "", + "pgsql_host" => "", + "pgsql_dbname" => "", + "pgsql_username" => "", + "pgsql_password" => "", ]; function __construct() { @@ -46,6 +50,14 @@ class Database extends \Baikal\Model\Config { function formMorphologyForThisModelInstance() { $oMorpho = new \Formal\Form\Morphology(); + $oMorpho->add(new \Formal\Element\Listbox([ + "prop" => "backend", + "label" => "Database Backend", + "validation" => "required", + "options" => ['sqlite', 'mysql', 'pgsql'], + "refreshonchange" => true, + ])); + $oMorpho->add(new \Formal\Element\Text([ "prop" => "sqlite_file", "label" => "SQLite file path", @@ -54,13 +66,6 @@ class Database extends \Baikal\Model\Config { "help" => "The absolute server path to the SQLite file", ])); - $oMorpho->add(new \Formal\Element\Checkbox([ - "prop" => "mysql", - "label" => "Use MySQL", - "help" => "If checked, Baïkal will use MySQL instead of SQLite.", - "refreshonchange" => true, - ])); - $oMorpho->add(new \Formal\Element\Text([ "prop" => "mysql_host", "label" => "MySQL host", @@ -82,6 +87,27 @@ class Database extends \Baikal\Model\Config { "label" => "MySQL password", ])); + $oMorpho->add(new \Formal\Element\Text([ + "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([ + "prop" => "pgsql_dbname", + "label" => "PostgreSQL database name", + ])); + + $oMorpho->add(new \Formal\Element\Text([ + "prop" => "pgsql_username", + "label" => "PostgreSQL username", + ])); + + $oMorpho->add(new \Formal\Element\Password([ + "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 4a57584..fc17997 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Install/Database.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Install/Database.php @@ -38,18 +38,22 @@ class Database extends \Flake\Core\Controller { if (file_exists(PROJECT_PATH_SPECIFIC . "config.system.php")) { require_once PROJECT_PATH_SPECIFIC . "config.system.php"; $this->oModel->set('sqlite_file', PROJECT_SQLITE_FILE); - $this->oModel->set('mysql', PROJECT_DB_MYSQL); + $this->oModel->set('backend', PROJECT_DB_BACKEND); $this->oModel->set('mysql_host', PROJECT_DB_MYSQL_HOST); $this->oModel->set('mysql_dbname', PROJECT_DB_MYSQL_DBNAME); $this->oModel->set('mysql_username', PROJECT_DB_MYSQL_USERNAME); $this->oModel->set('mysql_password', PROJECT_DB_MYSQL_PASSWORD); + $this->oModel->set('pgsql_host', PROJECT_DB_PGSQL_HOST); + $this->oModel->set('pgsql_dbname', PROJECT_DB_PGSQL_DBNAME); + $this->oModel->set('pgsql_username', PROJECT_DB_PGSQL_USERNAME); + $this->oModel->set('pgsql_password', PROJECT_DB_PGSQL_PASSWORD); $this->oModel->set('encryption_key', BAIKAL_ENCRYPTION_KEY); } $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,11 +103,11 @@ class Database extends \Flake\Core\Controller { return $oView->render(); } - function validateConnection($oForm, $oMorpho) { + function validateMySQLConnection($oForm, $oMorpho) { if ($oForm->refreshed()) { return true; } - $bMySQLEnabled = $oMorpho->element("mysql")->value(); + $bMySQLEnabled = $oMorpho->element("backend")->value() == 'mysql'; if ($bMySQLEnabled) { $sHost = $oMorpho->element("mysql_host")->value(); @@ -129,7 +133,7 @@ class Database extends \Flake\Core\Controller { $sMessage .= "

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

"; $oForm->declareError( - $oMorpho->element("mysql"), + $oMorpho->element("backend"), $sMessage ); } else { @@ -142,7 +146,7 @@ class Database extends \Flake\Core\Controller { return true; } catch (\Exception $e) { - $oForm->declareError($oMorpho->element("mysql"), + $oForm->declareError($oMorpho->element("backend"), "Baïkal was not able to establish a connexion to the MySQL database as configured.
MySQL says: " . $e->getMessage()); $oForm->declareError($oMorpho->element("mysql_host")); $oForm->declareError($oMorpho->element("mysql_dbname")); @@ -211,10 +215,10 @@ class Database extends \Flake\Core\Controller { function hideMySQLFieldWhenNeeded(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { if ($oForm->submitted()) { - $bMySQL = (intval($oForm->postValue("mysql")) === 1); + $bMySQL = ($oForm->postValue("backend") == 'mysql'); } else { // oMorpho won't have the values from the model set on it yet - $bMySQL = $this->oModel->get("mysql"); + $bMySQL = $this->oModel->get("backend") == 'mysql'; } if ($bMySQL === true) { @@ -226,4 +230,103 @@ class Database extends \Flake\Core\Controller { $oMorpho->remove("mysql_password"); } } + + function validatePgSQLConnection($oForm, $oMorpho) { + $bPgSqlEnabled = $oMorpho->element("backend")->value() == 'pgsql'; + + 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("backend"), + $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("backend"), + "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("backend")->value() == 'mysql') { + $this->validateMySQLConnection($oForm, $oMorpho); + } elseif ($oMorpho->element("backend")->value() == 'pgsql') { + $this->validatePgSQLConnection($oForm, $oMorpho); + } + } + + public function hideSqlFieldWhenNeeded(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { + if ($oMorpho->element("backend")->value() == 'mysql') { + $this->hideMySQLFieldWhenNeeded($oForm, $oMorpho); + } elseif ($oMorpho->element("backend")->value() == 'pgsql') { + $this->hidePgSQLFieldWhenNeeded($oForm, $oMorpho); + } + } + + public function hidePgSQLFieldWhenNeeded(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { + if ($oForm->submitted()) { + $bPgSQL = ($oForm->postValue("backend")) == 'pgsql'; + } else { + // oMorpho won't have the values from the model set on it yet + $bPgSQL = $this->oModel->get("backend") == '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/BaikalAdmin/Controller/Install/Initialize.php b/Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php index f1cc643..6246bc1 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Install/Initialize.php @@ -82,8 +82,12 @@ class Initialize extends \Flake\Core\Controller { # Default: PDO::SQLite or PDO::MySQL ? $aPDODrivers = \PDO::getAvailableDrivers(); - if (!in_array('sqlite', $aPDODrivers)) { # PDO::MySQL is already asserted in \Baikal\Core\Tools::assertEnvironmentIsOk() - $oDatabaseConfig->set("mysql", true); + if (in_array('sqlite', $aPDODrivers)) { # PDO::MySQL is already asserted in \Baikal\Core\Tools::assertEnvironmentIsOk() + $oDatabaseConfig->set("backend", 'sqlite'); + } elseif (in_array('mysql', $aPDODrivers)) { + $oDatabaseConfig->set("backend", 'mysql'); + } else { + $oDatabaseConfig->set("backend", 'pgsql'); } $oDatabaseConfig->persist(); diff --git a/Core/Frameworks/BaikalAdmin/Controller/Install/VersionUpgrade.php b/Core/Frameworks/BaikalAdmin/Controller/Install/VersionUpgrade.php index d4cd91e..67ded66 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Install/VersionUpgrade.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Install/VersionUpgrade.php @@ -505,6 +505,14 @@ SQL $this->aSuccess[] = 'Updated default values in calendarinstances table'; } + if (version_compare($sVersionFrom, '0.10.0', '<')) { + $config = Yaml::parseFile(PROJECT_PATH_CONFIG . "baikal.yaml"); + + $oConfig = new \Baikal\Model\Config\Database(); + $oConfig->set("backend", intval($config['database']['mysql']) === 1 ? 'mysql' : 'sqlite'); + $oConfig->persist(); + } + $this->updateConfiguredVersion($sVersionTo); return true; diff --git a/Core/Frameworks/BaikalAdmin/Controller/Settings/Database.php b/Core/Frameworks/BaikalAdmin/Controller/Settings/Database.php index 84d10d5..df7da64 100644 --- a/Core/Frameworks/BaikalAdmin/Controller/Settings/Database.php +++ b/Core/Frameworks/BaikalAdmin/Controller/Settings/Database.php @@ -74,61 +74,81 @@ class Database extends \Flake\Core\Controller { function morphologyHook(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { if ($oForm->submitted()) { - $bMySQL = (intval($oForm->postValue("mysql")) === 1); + $bMySQL = ($oForm->postValue("backend") == 'mysql'); + $bPgSQL = ($oForm->postValue("backend") == 'pgsql'); } else { try { $config = Yaml::parseFile(PROJECT_PATH_CONFIG . "baikal.yaml"); } catch (\Exception $e) { error_log('Error reading baikal.yaml file : ' . $e->getMessage()); } - $bMySQL = $config['database']['mysql'] ?? true; + $bMySQL = $config['database']['backend'] == 'mysql'; + $bPgSQL = $config['database']['backend'] == 'pgsql'; } - if ($bMySQL === true) { + if ($bMySQL === true || $bPgSQL === true) { $oMorpho->remove("sqlite_file"); - } else { + } + + if (!$bMySQL) { $oMorpho->remove("mysql_host"); $oMorpho->remove("mysql_dbname"); $oMorpho->remove("mysql_username"); $oMorpho->remove("mysql_password"); } + + if (!$bPgSQL) { + $oMorpho->remove("pgsql_host"); + $oMorpho->remove("pgsql_dbname"); + $oMorpho->remove("pgsql_username"); + $oMorpho->remove("pgsql_password"); + } } function validationHook(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) { if ($oForm->refreshed()) { return true; } - if (intval($oForm->modelInstance()->get("mysql")) === 1) { - # We have to check the MySQL connection - $sHost = $oForm->modelInstance()->get("mysql_host"); - $sDbName = $oForm->modelInstance()->get("mysql_dbname"); - $sUsername = $oForm->modelInstance()->get("mysql_username"); - $sPassword = $oForm->modelInstance()->get("mysql_password"); + if ($oForm->modelInstance()->get("backend") == 'mysql' || $oForm->modelInstance()->get("backend") == 'pgsql') { + $dbBackendName = $oForm->modelInstance()->get("backend") == 'pgsql' ? 'PostgreSQL' : 'MySQL'; + $dbBackendPrefix = $oForm->modelInstance()->get("backend"); + + # We have to check the MySQL or PostgreSQL connection + $sHost = $oForm->modelInstance()->get("{$dbBackendPrefix}_host"); + $sDbName = $oForm->modelInstance()->get("{$dbBackendPrefix}_dbname"); + $sUsername = $oForm->modelInstance()->get("{$dbBackendPrefix}_username"); + $sPassword = $oForm->modelInstance()->get("{$dbBackendPrefix}_password"); try { - $oDB = new \Flake\Core\Database\Mysql( + $oDB = (($oForm->modelInstance()->get("backend")) == 'pgsql' + ) ? new \Flake\Core\Database\Pgsql( + $sHost, + $sDbName, + $sUsername, + $sPassword + ) : new \Flake\Core\Database\Mysql( $sHost, $sDbName, $sUsername, $sPassword ); } catch (\Exception $e) { - $sMessage = "MySQL error: " . $e->getMessage(); + $sMessage = "{$dbBackendName} error: " . $e->getMessage(); $sMessage .= "
Nothing has been saved"; - $oForm->declareError($oMorpho->element("mysql_host"), $sMessage); - $oForm->declareError($oMorpho->element("mysql_dbname")); - $oForm->declareError($oMorpho->element("mysql_username")); - $oForm->declareError($oMorpho->element("mysql_password")); + $oForm->declareError($oMorpho->element("{$dbBackendPrefix}_host"), $sMessage); + $oForm->declareError($oMorpho->element("{$dbBackendPrefix}_dbname")); + $oForm->declareError($oMorpho->element("{$dbBackendPrefix}_username")); + $oForm->declareError($oMorpho->element("{$dbBackendPrefix}_password")); return; } if (($aMissingTables = \Baikal\Core\Tools::isDBStructurallyComplete($oDB)) !== true) { - $sMessage = "MySQL error: These tables, required by Baïkal, are missing: " . implode(", ", $aMissingTables) . "
"; - $sMessage .= "You may want create these tables using the file Core/Resources/Db/MySQL/db.sql"; + $sMessage = "{$dbBackendName} error: These tables, required by Baïkal, are missing: " . implode(", ", $aMissingTables) . "
"; + $sMessage .= "You may want create these tables using the file Core/Resources/Db/{$dbBackendName}/db.sql"; $sMessage .= "

Nothing has been saved"; - $oForm->declareError($oMorpho->element("mysql"), $sMessage); + $oForm->declareError($oMorpho->element("backend"), $sMessage); return; } diff --git a/Core/Frameworks/Flake/Core/Database/Pgsql.php b/Core/Frameworks/Flake/Core/Database/Pgsql.php new file mode 100644 index 0000000..8d65054 --- /dev/null +++ b/Core/Frameworks/Flake/Core/Database/Pgsql.php @@ -0,0 +1,68 @@ + +# 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 = []; + + $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 347ad67..3b3cf5a 100644 --- a/Core/Frameworks/Flake/Framework.php +++ b/Core/Frameworks/Flake/Framework.php @@ -273,8 +273,12 @@ class Framework extends \Flake\Core\Framework { if (defined("BAIKAL_CONTEXT_INSTALL") && (!isset($config['system']['configured_version']) || $config['system']['configured_version'] === BAIKAL_VERSION)) { return true; } - if ($config['database']['mysql'] === true) { + # Config key 'mysql' kept for backwards compatibility + $legacyMysql = key_exists('mysql', $config['database']) && $config['database']['mysql'] === true; + if ($legacyMysql || $config['database']['backend'] === 'mysql') { self::initDbMysql($config); + } elseif ($config['database']['backend'] === 'pgsql') { + self::initDbPgsql($config); } else { self::initDbSqlite($config); } @@ -339,6 +343,38 @@ class Framework extends \Flake\Core\Framework { return true; } + protected static function initDbPgsql(array $config) { + if (!$config['database']['pgsql_host']) { + exit("

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']) { + exit("

The constant PROJECT_DB_PGSQL_DBNAME, containing the PostgreSQL database name, 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) { + $message = "Baïkal was not able to establish a connection to the configured PostgreSQL database (as configured in config/baikal.yaml)."; + if (!$config['database']['pgsql_username']) { + exit("

$message Note: The constant PROJECT_DB_PGSQL_USERNAME, containing the PostgreSQL database username, is not set. If your database requires a username you should set it in config/baikal.yaml.

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

$message Note: The constant PROJECT_DB_PGSQL_PASSWORD, containing the PostgreSQL database password, is not set. If your database requires a password you should set it in config/baikal.yaml.

"); + } + + exit("

$message

"); + } + } + 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 0000000..7335dbb --- /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 +); diff --git a/config/baikal.yaml.dist b/config/baikal.yaml.dist index 3fd772b..e592f47 100644 --- a/config/baikal.yaml.dist +++ b/config/baikal.yaml.dist @@ -11,9 +11,13 @@ system: base_uri: '' database: encryption_key: 5d3f0fa0192e3058ea70f1bb20924add + backend: 'mysql' sqlite_file: "absolute/path/to/Specific/db/db.sqlite" - mysql: true mysql_host: 'localhost' mysql_dbname: 'baikal' mysql_username: 'baikal' mysql_password: 'baikal' + pgsql_host: 'localhost' + pgsql_dbname: 'baikal' + pgsql_username: 'baikal' + pgsql_password: 'baikal'