3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
10 * Copyright © 2013-2023 The Galette Team
12 * This file is part of Galette (http://galette.tuxfamily.org).
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2013-2023 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.8 - 2013-01-09
37 namespace Galette\Core
;
41 use Laminas\Db\Adapter\Adapter
;
44 * Galette installation
49 * @author Johan Cwiklinski <johan@x-tnd.be>
50 * @copyright 2013-2023 The Galette Team
51 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
52 * @link http://galette.tuxfamily.org
53 * @since Available since 0.8 - 2013-01-09
57 public const STEP_CHECK
= 0;
58 public const STEP_TYPE
= 1;
59 public const STEP_DB
= 2;
60 public const STEP_DB_CHECKS
= 3;
61 public const STEP_VERSION
= 4; //only for update
62 public const STEP_DB_UPGRADE
= 5;
63 public const STEP_DB_INSTALL
= 6;
64 public const STEP_ADMIN
= 7;
65 public const STEP_GALETTE_INIT
= 8;
66 public const STEP_END
= 9;
68 public const INSTALL
= 'i';
69 public const UPDATE
= 'u';
71 //db version/galette version mapper
72 private $versions_mapper = array(
83 private $_installed_version;
93 private $_db_connected;
96 private $_admin_login;
104 public function __construct()
106 $this->_step
= self
::STEP_CHECK
;
108 $this->_version
= str_replace('v', '', GALETTE_VERSION
);
109 $this->_db_connected
= false;
110 $this->_db_prefix
= null;
114 * Return current step title
118 public function getStepTitle()
121 switch ($this->_step
) {
122 case self
::STEP_CHECK
:
123 $step_title = _T("Checks");
125 case self
::STEP_TYPE
:
126 $step_title = _T("Installation mode");
129 $step_title = _T("Database");
131 case self
::STEP_DB_CHECKS
:
132 $step_title = _T("Database access and permissions");
134 case self
::STEP_VERSION
:
135 $step_title = _T("Previous version selection");
137 case self
::STEP_DB_UPGRADE
:
138 $step_title = _T("Datapase upgrade");
140 case self
::STEP_DB_INSTALL
:
141 $step_title = _T("Tables Creation");
143 case self
::STEP_ADMIN
:
144 $step_title = _T("Admin parameters");
146 case self
::STEP_GALETTE_INIT
:
147 $step_title = _T("Galette initialization");
150 $step_title = _T("End!");
157 * HTML validation image
159 * @param bool $arg Argument
161 * @return string html string
163 public function getValidationImage($arg)
165 $img_name = ($arg === true) ?
'green check' : 'red times';
166 $alt = ($arg === true) ?
_T("Ok") : _T("Ko");
167 $img = '<i class="ui ' . $img_name . ' icon"></i><span class="displaynone">' . $alt . '</span>';
176 public function getMode()
186 public function isInstall()
188 return $this->_mode
=== self
::INSTALL
;
196 public function isUpgrade()
198 return $this->_mode
=== self
::UPDATE
;
202 * Set installation mode
204 * @param string $mode Requested mode
208 public function setMode($mode)
210 if ($mode === self
::INSTALL ||
$mode === self
::UPDATE
) {
211 $this->_mode
= $mode;
213 throw new \
UnexpectedValueException('Unknown mode "' . $mode . '"');
218 * Go back to previous step
222 public function atPreviousStep()
224 if ($this->_step
> 0) {
226 $this->_step
- 1 !== self
::STEP_DB_INSTALL
227 && $this->_step
!== self
::STEP_END
229 if ($this->_step
=== self
::STEP_DB_INSTALL
) {
230 $this->_step
= self
::STEP_DB_CHECKS
;
232 if ($this->_step
=== self
::STEP_DB_UPGRADE
) {
233 $this->setInstalledVersion(null);
235 $this->_step
= $this->_step
- 1;
239 if ($this->_step
=== self
::STEP_END
) {
240 $msg = 'Ok man, install is finished already!';
242 $msg = 'It is forbidden to rerun database install!';
244 Analog
::log($msg, Analog
::WARNING
);
250 * Are we at check step?
254 public function isCheckStep()
256 return $this->_step
=== self
::STEP_CHECK
;
260 * Set step to type of installation
264 public function atTypeStep()
266 $this->_step
= self
::STEP_TYPE
;
270 * Are we at type step?
274 public function isTypeStep()
276 return $this->_step
=== self
::STEP_TYPE
;
280 * Set step to database information
284 public function atDbStep()
286 $this->_step
= self
::STEP_DB
;
290 * Are we at database step?
294 public function isDbStep()
296 return $this->_step
=== self
::STEP_DB
;
304 public function postCheckDb()
306 return $this->_step
> self
::STEP_DB_CHECKS
;
312 * @param string $type Database type
313 * @param array $errs Errors array
317 public function setDbType($type, &$errs)
322 $this->_db_type
= $type;
325 $errs[] = _T("Database type unknown");
335 public function getDbType()
337 return $this->_db_type
;
341 * Set connection information
343 * @param string $host Database host
344 * @param string $port Database port
345 * @param string $name Database name
346 * @param string $user Database user name
347 * @param string $pass Database user's password
351 public function setDsn($host, $port, $name, $user, $pass)
353 $this->_db_host
= $host;
354 $this->_db_port
= $port;
355 $this->_db_name
= $name;
356 $this->_db_user
= $user;
357 $this->_db_pass
= $pass;
363 * @param string $prefix Prefix
367 public function setTablesPrefix($prefix)
369 $this->_db_prefix
= $prefix;
373 * Retrieve database host
377 public function getDbHost()
379 return $this->_db_host
;
383 * Retrieve database port
387 public function getDbPort()
389 return $this->_db_port
;
393 * Retrieve database name
397 public function getDbName()
399 return $this->_db_name
;
403 * Retrieve database user
407 public function getDbUser()
409 return $this->_db_user
;
413 * Retrieve database password
417 public function getDbPass()
419 return $this->_db_pass
;
423 * Retrieve tables prefix
427 public function getTablesPrefix()
429 return $this->_db_prefix
;
433 * Set step to database checks
437 public function atDbCheckStep()
439 $this->_step
= self
::STEP_DB_CHECKS
;
443 * Are we at database check step?
447 public function isDbCheckStep()
449 return $this->_step
=== self
::STEP_DB_CHECKS
;
453 * Test database connection
459 public function testDbConnexion()
461 return Db
::testConnectivity(
472 * Is database connexion ok?
476 public function isDbConnected()
478 return $this->_db_connected
;
482 * Set step to version selection
486 public function atVersionSelection()
488 $this->_step
= self
::STEP_VERSION
;
492 * Are we at version selection step?
496 public function isVersionSelectionStep()
498 return $this->_step
=== self
::STEP_VERSION
;
502 * Set step to database installation
506 public function atDbInstallStep()
508 $this->_step
= self
::STEP_DB_INSTALL
;
512 * Are we at db installation step?
516 public function isDbinstallStep()
518 return $this->_step
=== self
::STEP_DB_INSTALL
;
522 * Set step to database upgrade
526 public function atDbUpgradeStep()
528 $this->_step
= self
::STEP_DB_UPGRADE
;
532 * Are we at db upgrade step?
536 public function isDbUpgradeStep()
538 return $this->_step
=== self
::STEP_DB_UPGRADE
;
543 * Install/Update SQL scripts
545 * @param string $path Path to scripts (defaults to core scripts)
549 public function getScripts($path = null)
551 if ($path === null) {
552 $path = GALETTE_ROOT
. '/install';
554 $update_scripts = array();
556 if ($this->isUpgrade()) {
557 $update_scripts = self
::getUpdateScripts(
560 $this->_installed_version
563 $update_scripts['current'] = $this->_db_type
. '.sql';
566 return $update_scripts;
570 * List updates scripts from given path
572 * @param string $path Scripts path
573 * @param string $db_type Database type
574 * @param string $version Previous version, defaults to null
576 * @return array If a previous version is provided, update scripts
577 * file path from this one to the latest will be returned.
578 * If no previous version is provided, that will return all
579 * updates versions known.
581 public static function getUpdateScripts(
586 $dh = opendir($path . '/scripts');
587 $php_update_scripts = array();
588 $sql_update_scripts = array();
589 $update_scripts = [];
591 while (($file = readdir($dh)) !== false) {
592 if (preg_match("/upgrade-to-(.*).php/", $file, $ver)) {
593 if ($version === null) {
594 $php_update_scripts[$ver[1]] = $ver[1];
596 if ($version < $ver[1]) {
597 $php_update_scripts[$ver[1]] = $file;
603 "/upgrade-to-(.*)-" . $db_type . ".sql/",
608 if ($version === null) {
609 $sql_update_scripts[$ver[1]] = $ver[1];
611 if ($version < $ver[1]) {
612 $sql_update_scripts[$ver[1]] = $file;
617 $update_scripts = array_merge($sql_update_scripts, $php_update_scripts);
619 ksort($update_scripts);
621 return $update_scripts;
625 * Execute SQL scripts
627 * @param Db $zdb Database instance
628 * @param string $spath Path to scripts
632 public function executeScripts(Db
$zdb, $spath = null)
634 $fatal_error = false;
635 $update_scripts = $this->getScripts($spath);
636 $this->_report
= array();
637 $scripts_path = ($spath ?? GALETTE_ROOT
. '/install') . '/scripts/';
639 foreach ($update_scripts as $key => $val) {
640 if (substr($val, -strlen('.sql')) === '.sql') {
641 //just a SQL script, run it
642 $script = fopen($scripts_path . $val, 'r');
644 if ($script === false) {
645 throw new \
RuntimeException(
646 'Unable to read SQL script from ' . $scripts_path . $val
652 @filesize
($scripts_path . $val)
655 $sql_res = $this->executeSql($zdb, $sql_query);
660 //we got an update class
661 include_once $scripts_path . $val;
662 $className = '\Galette\Updates\UpgradeTo' .
663 str_replace('.', '', $key);
669 $updater = new $className();
670 if ($updater instanceof \Galette\Updater\AbstractUpdater
) {
671 $updater->run($zdb, $this);
672 $ret = $updater->getReport();
673 $this->_report
= array_merge($this->_report
, $ret);
677 'Update class does not extends AbstractUpdater!',
682 $ret['message'] = str_replace(
685 _T("%version script has been successfully executed :)")
688 $this->_report
[] = $ret;
689 } catch (\RuntimeException
$e) {
694 $ret['message'] = str_replace(
697 _T("Unable to run %version update script :(")
700 $this->_report
[] = $ret;
705 str_replace('%s', $key, 'Upgrade to %s complete'),
710 return !$fatal_error;
714 * Executes SQL queries
716 * @param Db $zdb Database instance
717 * @param string $sql_query SQL instructions
721 public function executeSql($zdb, $sql_query)
723 $queries_results = array();
724 $fatal_error = false;
726 // begin : copyright (2002) the phpbb group (support@phpbb.com)
727 // load in the sql parser
728 include_once GALETTE_ROOT
. 'includes/sql_parse.php';
730 $sql_query = preg_replace('/galette_/', $this->_db_prefix
, $sql_query);
731 $sql_query = remove_remarks($sql_query);
733 $sql_query = split_sql_file($sql_query, ';');
735 $zdb->connection
->beginTransaction();
737 $sql_size = sizeof($sql_query);
738 for ($i = 0; $i < $sql_size; $i++
) {
739 $query = trim($sql_query[$i]);
740 if ($query != '' && $query[0] != '-') {
750 Adapter
::QUERY_MODE_EXECUTE
753 } catch (Throwable
$e) {
754 $log_lvl = Analog
::WARNING
;
755 //if error are on drop, DROP, rename or RENAME we can continue
756 $parts = explode(' ', $query, 1);
758 (strcasecmp(trim($parts[0]), 'drop') != 0)
759 && (strcasecmp(trim($parts[0]), 'rename') != 0)
761 $log_lvl = Analog
::ERROR
;
762 $ret['debug'] = $e->getMessage();
763 $ret['query'] = $query;
770 'Error executing query | ' . $e->getMessage(),
775 $queries_results[] = $ret;
781 $zdb->connection
->rollBack();
782 } catch (\PDOException
$e) {
783 //to avoid php8/mysql autocommit issue
784 if ($zdb->isPostgres() ||
(!$zdb->isPostgres() && !str_contains($e->getMessage(), 'no active transaction'))) {
790 $zdb->connection
->commit();
791 } catch (\PDOException
$e) {
792 //to avoid php8/mysql autocommit issue
793 if ($zdb->isPostgres() ||
(!$zdb->isPostgres() && !str_contains($e->getMessage(), 'no active transaction'))) {
799 $this->_report
= array_merge($this->_report
, $queries_results);
800 return !$fatal_error;
804 * Retrieve database installation report
808 public function getDbInstallReport()
810 return $this->_report
;
814 * Reinitialize report array
818 public function reinitReport()
820 $this->_report
= array();
824 * Set step to super admin information
828 public function atAdminStep()
830 $this->_step
= self
::STEP_ADMIN
;
834 * Are we at super admin information step?
838 public function isAdminStep()
840 return $this->_step
=== self
::STEP_ADMIN
;
844 * Set super administrator information
846 * @param string $login Login
847 * @param string $pass Password
851 public function setAdminInfos($login, $pass)
853 $this->_admin_login
= $login;
854 $this->_admin_pass
= password_hash($pass, PASSWORD_BCRYPT
);
858 * Retrieve super admin login
862 public function getAdminLogin()
864 return $this->_admin_login
;
868 * Retrieve super admin password
872 public function getAdminPass()
874 return $this->_admin_pass
;
878 * Set step to Galette initialization
882 public function atGaletteInitStep()
884 $this->_step
= self
::STEP_GALETTE_INIT
;
888 * Are we at Galette initialization step?
892 public function isGaletteInitStep()
894 return $this->_step
=== self
::STEP_GALETTE_INIT
;
898 * Load existing config
900 * @param array $post_data Data posted
901 * @param array $error_detected Errors array
905 public function loadExistingConfig($post_data, &$error_detected)
907 if (file_exists(GALETTE_CONFIG_PATH
. 'config.inc.php')) {
908 $existing = $this->loadExistingConfigFile($post_data);
910 if ($existing['db_type'] !== null) {
911 $this->setDbType($existing['db_type'], $error_detected);
915 $existing['db_host'] !== null
916 ||
$existing['db_user'] !== null
917 ||
$existing['db_name'] !== null
920 $existing['db_host'],
921 $existing['db_port'],
922 $existing['db_name'],
923 $existing['db_user'],
928 if ($existing['prefix'] !== null) {
929 $this->setTablesPrefix(
937 * Load contents from existing config file
939 * @param array $post_data Data posted
940 * @param boolean $pass Retrieve password
944 private function loadExistingConfigFile($post_data = array(), $pass = false)
955 if (file_exists(GALETTE_CONFIG_PATH
. 'config.inc.php')) {
956 $conf = file_get_contents(GALETTE_CONFIG_PATH
. 'config.inc.php');
957 if ($conf !== false) {
958 if (!isset($post_data['install_dbtype'])) {
960 '/TYPE_DB["\'], ["\'](.*)["\']\);/',
964 if (isset($matches[1])) {
965 $existing['db_type'] = $matches[1];
968 if (!isset($post_data['install_dbhost'])) {
970 '/HOST_DB["\'], ["\'](.*)["\']\);/',
974 if (isset($matches[1])) {
975 $existing['db_host'] = $matches[1];
978 if (!isset($post_data['install_dbport'])) {
980 '/PORT_DB["\'], ["\'](.*)["\']\);/',
984 if (isset($matches[1])) {
985 $existing['db_port'] = $matches[1];
988 if (!isset($post_data['install_dbuser'])) {
990 '/USER_DB["\'], ["\'](.*)["\']\);/',
994 if (isset($matches[1])) {
995 $existing['db_user'] = $matches[1];
998 if (!isset($post_data['install_dbname'])) {
1000 '/NAME_DB["\'], ["\'](.*)["\']\);/',
1004 if (isset($matches[1])) {
1005 $existing['db_name'] = $matches[1];
1010 if (!isset($post_data['install_dbprefix'])) {
1012 '/PREFIX_DB["\'], ["\'](.*)["\']\);/',
1016 if (isset($matches[1])) {
1017 $existing['prefix'] = $matches[1];
1021 if ($pass === true) {
1023 '/PWD_DB["\'], ["\'](.*)["\']\);/',
1027 if (isset($matches[1])) {
1028 $existing['pwd_db'] = $matches[1];
1038 * Write configuration file to disk
1042 public function writeConfFile()
1046 'message' => _T("Write configuration file"),
1050 //if config file is already up-to-date, nothing to write
1051 $existing = $this->loadExistingConfigFile(array(), true);
1054 isset($existing['db_type'])
1055 && $existing['db_type'] == $this->_db_type
1056 && isset($existing['db_host'])
1057 && $existing['db_host'] == $this->_db_host
1058 && isset($existing['db_port'])
1059 && $existing['db_port'] == $this->_db_port
1060 && isset($existing['db_user'])
1061 && $existing['db_user'] == $this->_db_user
1062 && isset($existing['pwd_db'])
1063 && $existing['pwd_db'] == $this->_db_pass
1064 && isset($existing['db_name'])
1065 && $existing['db_name'] == $this->_db_name
1066 && isset($existing['prefix'])
1067 && $existing['prefix'] == $this->_db_prefix
1070 'Config file is already up-to-date, nothing to do.',
1074 $this->_report
[] = array(
1075 'message' => _T("Config file already exists and is up to date"),
1081 $conffile = GALETTE_CONFIG_PATH
. 'config.inc.php';
1083 is_writable(GALETTE_CONFIG_PATH
)
1084 && (!file_exists($conffile) ||
file_exists($conffile) && is_writable($conffile))
1085 && $fd = @fopen
($conffile, 'w')
1088 define('TYPE_DB', '" . $this->_db_type
. "');
1089 define('HOST_DB', '" . $this->_db_host
. "');
1090 define('PORT_DB', '" . $this->_db_port
. "');
1091 define('USER_DB', '" . $this->_db_user
. "');
1092 define('PWD_DB', '" . $this->_db_pass
. "');
1093 define('NAME_DB', '" . $this->_db_name
. "');
1094 define('PREFIX_DB', '" . $this->_db_prefix
. "');
1099 Analog
::log('Configuration file written on disk', Analog
::INFO
);
1104 _T("Unable to create configuration file (%path)")
1106 Analog
::log($str, Analog
::WARNING
);
1107 $ret['error'] = $str;
1110 $this->_report
[] = $ret;
1115 * Initialize Galette relevant objects
1117 * @param I18n $i18n I18n
1118 * @param Db $zdb Database instance
1119 * @param Login $login Loged in instance
1123 public function initObjects(I18n
$i18n, Db
$zdb, Login
$login)
1125 if ($this->isInstall()) {
1126 $preferences = new Preferences($zdb, false);
1127 $ct = new \Galette\Entity\
ContributionsTypes($zdb);
1128 $status = new \Galette\Entity\
Status($zdb);
1129 include_once '../includes/fields_defs/members_fields.php';
1130 include_once '../includes/fields_defs/members_fields_cats.php';
1131 $fc = new \Galette\Entity\
FieldsConfig(
1133 \Galette\Entity\Adherent
::TABLE
,
1134 //@phpstan-ignore-next-line
1136 //@phpstan-ignore-next-line
1137 $members_fields_cats,
1142 $login = new \Galette\Core\
Login($zdb, $i18n);
1143 //$fc = new \Galette\Entity\FieldsCategories();
1144 $texts = new \Galette\Entity\
Texts($preferences);
1145 $titles = new \Galette\Repository\
Titles();
1147 $models = new \Galette\Repository\
PdfModels($zdb, $preferences, $login);
1149 $this->_error
= false;
1151 //Install preferences
1152 $res = $preferences->installInit(
1154 $this->getAdminLogin(),
1155 $this->getAdminPass()
1157 $this->proceedReport(_T("Preferences"), $res);
1159 //Install contributions types
1160 $res = $ct->installInit();
1161 $this->proceedReport(_T("Contributions types"), $res);
1164 $res = $status->installInit();
1165 $this->proceedReport(_T("Status"), $res);
1167 //Install fields configuration and categories
1168 $res = $fc->installInit();
1169 $this->proceedReport(_T("Fields config and categories"), $res);
1172 $res = $texts->installInit(false);
1173 $this->proceedReport(_T("Mails texts"), $res);
1176 $res = $titles->installInit($zdb);
1177 $this->proceedReport(_T("Titles"), $res);
1179 //Install PDF models
1180 $res = $models->installInit(false);
1181 $this->proceedReport(_T("PDF Models"), $res);
1183 return !$this->_error
;
1184 } elseif ($this->isUpgrade()) {
1185 $preferences = new Preferences($zdb);
1186 $preferences->store();
1187 $this->proceedReport(_T("Update preferences"), true);
1189 $models = new \Galette\Repository\
PdfModels($zdb, $preferences, new Login($zdb, $i18n));
1190 $models->installInit(true);
1191 $this->proceedReport(_T("Update models"), true);
1193 $texts = new \Galette\Entity\
Texts($preferences);
1194 $texts->installInit(true);
1195 $this->proceedReport(_T("Mails texts"), true);
1203 * Proceed installation report for each Entity/Repository
1205 * @param string $msg Report message title
1206 * @param mixed $res Initialialization result
1210 private function proceedReport($msg, $res)
1217 if ($res instanceof \Exception
) {
1218 $ret['debug'] = $res->getMessage();
1219 $this->_error
= true;
1223 $this->_report
[] = $ret;
1226 * Retrieve galette initialization report
1230 public function getInitializationReport()
1232 return $this->_report
;
1236 * Set step to database installation
1240 public function atEndStep()
1242 $this->_step
= self
::STEP_END
;
1246 * Are we at end step?
1250 public function isEndStep()
1252 return $this->_step
=== self
::STEP_END
;
1256 * Set installed version if we're upgrading
1258 * @param string $version Installed version
1262 public function setInstalledVersion($version)
1264 $this->_installed_version
= $version;
1268 * Current Galette installed version, according to database
1270 * @param Db $zdb Database instance
1272 * @return string|false
1274 public function getCurrentVersion($zdb)
1277 $db_ver = $zdb->getDbVersion(true);
1278 if (isset($this->versions_mapper
[$db_ver])) {
1279 return $this->versions_mapper
[$db_ver];
1281 return (string)$db_ver;
1283 } catch (\LogicException
$e) {
1289 * Check if step is passed
1291 * @param int $step Step
1295 public function isStepPassed($step)
1297 return $this->_step
> $step;