From efedefdae5b54986d34624cee460a61821213515 Mon Sep 17 00:00:00 2001 From: Max Pozdeev Date: Mon, 31 Jan 2022 21:49:26 +0300 Subject: [PATCH] use new config file for database connection, setup is updated to support it --- src/config-sample.php | 22 + src/db/config.php.default | 18 - src/includes/class.config.php | 141 ++++-- src/includes/class.db.mysql.php | 16 +- src/includes/class.db.mysqli.php | 19 +- src/includes/class.db.sqlite3.php | 15 +- src/includes/class.dbconnection.php | 18 +- src/init.php | 50 +- src/setup.php | 682 ++++++++++++++++------------ 9 files changed, 591 insertions(+), 390 deletions(-) create mode 100644 src/config-sample.php delete mode 100644 src/db/config.php.default diff --git a/src/config-sample.php b/src/config-sample.php new file mode 100644 index 0000000..812d753 --- /dev/null +++ b/src/config-sample.php @@ -0,0 +1,22 @@ + diff --git a/src/includes/class.config.php b/src/includes/class.config.php index 32fbef6..9fd6b14 100644 --- a/src/includes/class.config.php +++ b/src/includes/class.config.php @@ -14,19 +14,29 @@ class Config /** @var array[] */ private static $dbparams = array( # Database type: sqlite or mysql - 'db' => array('default'=>'sqlite', 'type'=>'s'), + 'db.type' => array('default'=>'sqlite', 'type'=>'s'), - # Specify these settings if you selected above to use Mysql - 'mysql.host' => array('default'=>'localhost', 'type'=>'s'), - 'mysql.db' => array('default'=>'mytinytodo', 'type'=>'s'), - 'mysql.user' => array('default'=>'user', 'type'=>'s'), - 'mysql.password' => array('default'=>'', 'type'=>'s'), + # Specific database api + 'db.driver' => array('default'=>'', 'type'=>'s'), - # Tables prefix - 'prefix' => array('default'=>'', 'type'=>'s'), + # Mysql connection settings + 'db.host' => array('default'=>'localhost', 'type'=>'s'), + 'db.user' => array('default'=>'mtt', 'type'=>'s'), + 'db.password' => array('default'=>'mtt', 'type'=>'s'), + 'db.name' => array('default'=>'mytinytodo', 'type'=>'s'), - # Use mysqli driver for mysql db. Will use PDO if set to 0. - 'mysqli' => array('default'=>1, 'type'=>'i') + # Prefix for table names + 'db.prefix' => array('default'=>'', 'type'=>'s') + ); + + /** @var array[] */ + private static $convert = array( + 'mysql.host' => 'db.host', + 'mysql.user' => 'db.user', + 'mysql.password' => 'db.password', + 'mysql.db' => 'db.name', + 'db' => 'db.type', + 'prefix' => 'db.prefix' ); /** @var array[] */ @@ -77,7 +87,7 @@ class Config ); /** @var mixed[] */ - private static $config; + private static $config = array(); /** @@ -85,9 +95,21 @@ class Config * @param mixed[] $config * @return void */ - public static function loadDbConfig(array $config) + public static function loadConfigV14(array $config) { - self::$config = $config; + foreach ($config as $key => $val) { + if (isset(self::$convert[$key])) { + $key = self::$convert[$key]; + } + elseif ($key == 'mysqli' && (int)$val != 0) { + $key = 'db.driver'; + $val = 'mysqli'; + } + // if (!isset(self::$dbparams[$key])) { + // throw new Exception("Unknown key: $key"); + // } + self::$config[$key] = $val; + } } /** @@ -145,45 +167,12 @@ class Config */ public static function set($key, $value) { - if ($key == "prefix" && $value !== "" && !preg_match("/^[a-zA-Z0-9_]+$/", $value)) { + if ($key == "db.prefix" && $value != "" && !preg_match("/^[a-zA-Z0-9_]+$/", $value)) { throw new Exception("Incorrect table prefix. Can contain only latin letters, digits and underscore character."); } self::$config[$key] = $value; } - /** - * - * @return void - * @throws Exception - */ - public static function saveDbConfig() - { - $s = ''; - foreach (self::$dbparams as $param => $v) - { - if ( !isset(self::$config[$param]) ) $val = $v['default']; - elseif ( isset($v['options']) && !in_array(self::$config[$param], $v['options']) ) $val = $v['default']; - else $val = self::$config[$param]; - if ($v['type']=='i') { - $s .= "\$config['$param'] = ".(int)$val.";\n"; - } - else { - $s .= "\$config['$param'] = '".str_replace(array("\\","'"),array("\\\\","\\'"),$val)."';\n"; - } - } - $f = fopen(MTTPATH. 'db/config.php', 'w'); - if($f === false) throw new Exception("Error while saving config file"); - fwrite($f, ""); - fclose($f); - - //Reset Zend OPcache - //opcache_get_status() sometimes crashes - //TODO: save config in database! - if (function_exists("opcache_invalidate") && 0 != (int)opcache_get_configuration()["directives"]["opcache.enable"]) { - opcache_invalidate(MTTPATH. 'db/config.php', true); - } - - } /** * @@ -241,6 +230,64 @@ class Config $db->ex("INSERT INTO {$db->prefix}settings (param_key,param_value) VALUES (?,?)", array($key,$json) ); } } + + public static function defineDbConstants() + { + define("MTT_DB_TYPE", self::get('db.type')); + define("MTT_DB_HOST", self::get('db.host')); + define("MTT_DB_USER", self::get('db.user')); + define("MTT_DB_PASSWORD", self::get('db.password')); + define("MTT_DB_NAME", self::get('db.name')); + define("MTT_DB_PREFIX", self::get('db.prefix')); + if ( self::get('db.driver') != '' ) { + define("MTT_DB_DRIVER", self::get('db.driver')); + } + } + + public static function dbConfigAsFileContents(): string + { + $a = array(); + $a[] = " \ No newline at end of file diff --git a/src/includes/class.db.mysql.php b/src/includes/class.db.mysql.php index b49b653..555a47e 100644 --- a/src/includes/class.db.mysql.php +++ b/src/includes/class.db.mysql.php @@ -2,14 +2,16 @@ /* This file is a part of myTinyTodo. - (C) Copyright 2020-2021 Max Pozdeev + (C) Copyright 2020-2022 Max Pozdeev Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details. */ // ---------------------------------------------------------------------------- // class DatabaseResult_Mysql extends DatabaseResult_Abstract { + /** @var PDOStatement */ private $q; + private $affected; function __construct($dbh, $query, $resultless = 0) @@ -46,7 +48,9 @@ class DatabaseResult_Mysql extends DatabaseResult_Abstract // ---------------------------------------------------------------------------- // class Database_Mysql extends Database_Abstract { + /** @var PDO */ private $dbh; + private $affected = null; var $lastQuery; private $dbname; @@ -173,6 +177,16 @@ class Database_Mysql extends Database_Abstract if ($r === false || $r === null) return false; return true; } + + function tableFieldExists($table, $field): bool + { + $table = str_replace('`', '\\`', addslashes($table)); + $q = $this->dq("DESCRIBE `$table`"); + while ($r = $q->fetchRow()) { + if ($r[0] == $field) return true; + } + return false; + } } ?> \ No newline at end of file diff --git a/src/includes/class.db.mysqli.php b/src/includes/class.db.mysqli.php index 5ab7e61..8a5d456 100644 --- a/src/includes/class.db.mysqli.php +++ b/src/includes/class.db.mysqli.php @@ -2,14 +2,15 @@ /* This file is a part of myTinyTodo. - (C) Copyright 2019-2021 Max Pozdeev + (C) Copyright 2019-2022 Max Pozdeev Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details. */ // ---------------------------------------------------------------------------- // class DatabaseResult_Mysql extends DatabaseResult_Abstract { - private $q; //mysqli_result + /** @var mysqli_result */ + private $q; function __construct(mysqli $dbh, $query, $resultless = 0) { @@ -30,7 +31,9 @@ class DatabaseResult_Mysql extends DatabaseResult_Abstract // ---------------------------------------------------------------------------- // class Database_Mysql extends Database_Abstract { - private $dbh; //mysqli + /** @var mysqli */ + private $dbh; + private $dbname; var $prefix = ''; @@ -139,6 +142,16 @@ class Database_Mysql extends Database_Abstract if ($r === false || $r === null) return false; return true; } + + function tableFieldExists($table, $field): bool + { + $table = str_replace('`', '\\`', addslashes($table)); + $q = $this->dq("DESCRIBE `$table`"); + while ($r = $q->fetchRow()) { + if ($r[0] == $field) return true; + } + return false; + } } ?> \ No newline at end of file diff --git a/src/includes/class.db.sqlite3.php b/src/includes/class.db.sqlite3.php index 5814d69..fefbc13 100644 --- a/src/includes/class.db.sqlite3.php +++ b/src/includes/class.db.sqlite3.php @@ -2,13 +2,15 @@ /* This file is a part of myTinyTodo. - (C) Copyright 2009,2019-2021 Max Pozdeev + (C) Copyright 2009,2019-2022 Max Pozdeev Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details. */ class DatabaseResult_Sqlite3 extends DatabaseResult_Abstract { + /** @var PDOStatement */ private $q; + private $affected; function __construct($dbh, $query, $resultless = 0) @@ -45,7 +47,9 @@ class DatabaseResult_Sqlite3 extends DatabaseResult_Abstract class Database_Sqlite3 extends Database_Abstract { + /** @var PDO */ private $dbh; + private $affected = null; var $lastQuery; var $prefix = ''; @@ -167,6 +171,15 @@ class Database_Sqlite3 extends Database_Abstract } return false; } + + function tableFieldExists($table, $field): bool + { + $q = $this->dq("PRAGMA table_info(". $this->quote($table). ")"); + while ($r = $q->fetchRow()) { + if ($r[1] == $field) return true; + } + return false; + } } ?> \ No newline at end of file diff --git a/src/includes/class.dbconnection.php b/src/includes/class.dbconnection.php index 1004636..3011cc7 100644 --- a/src/includes/class.dbconnection.php +++ b/src/includes/class.dbconnection.php @@ -2,7 +2,7 @@ /* This file is a part of myTinyTodo. - (C) Copyright 2021 Max Pozdeev + (C) Copyright 2021,2022 Max Pozdeev Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details. */ @@ -27,14 +27,14 @@ class DBConnection public static function setPrefix($prefix) { $db = self::instance(); - $db->prefix = $prefix; + $db->setPrefix($prefix); } } abstract class Database_Abstract { var $lastQuery = null; - var $prefix = ''; + var $prefix = ''; //TODO: make private abstract function connect($params); abstract function sq($query, $p = NULL); abstract function sqa($query, $p = NULL); @@ -45,6 +45,18 @@ abstract class Database_Abstract abstract function quoteForLike($format, $s); abstract function lastInsertId($name = null); abstract function tableExists($table); + abstract function tableFieldExists($table, $field): bool; + + function prefix(): string { + return $this->prefix; + } + + function setPrefix(string $prefix) { + if ($prefix != '' && !preg_match("/^[a-zA-Z0-9_]+$/", $prefix)) { + throw new Exception("Incorrect table prefix"); + } + $this->prefix = $prefix; + } } abstract class DatabaseResult_Abstract diff --git a/src/init.php b/src/init.php index ecc3250..a92fb8d 100644 --- a/src/init.php +++ b/src/init.php @@ -30,28 +30,25 @@ else { define('MTT_DEBUG', false); } +requireConfig(); require_once(MTTINC. 'common.php'); require_once(MTTINC. 'class.dbconnection.php'); require_once(MTTINC. 'class.dbcore.php'); require_once(MTTINC. 'class.config.php'); -require_once(MTTPATH. 'db/config.php'); -if(!isset($config)) global $config; -Config::loadDbConfig($config); -unset($config); # MySQL Database Connection -if (Config::get('db') == 'mysql') +if (MTT_DB_TYPE == 'mysql') { - if (Config::get('mysqli')) require_once(MTTINC. 'class.db.mysqli.php'); + if (defined('MTT_DB_DRIVER') && MTT_DB_DRIVER == 'mysqli') require_once(MTTINC. 'class.db.mysqli.php'); else require_once(MTTINC. 'class.db.mysql.php'); $db = DBConnection::init(new Database_Mysql); try { $db->connect( array( - 'host' => Config::get('mysql.host'), - 'user' => Config::get('mysql.user'), - 'password' => Config::get('mysql.password'), - 'db' => Config::get('mysql.db') + 'host' => MTT_DB_HOST, + 'user' => MTT_DB_USER, + 'password' => MTT_DB_PASSWORD, + 'db' => MTT_DB_NAME, )); } catch(Exception $e) { @@ -61,17 +58,17 @@ if (Config::get('db') == 'mysql') } # SQLite3 Database -elseif(Config::get('db') == 'sqlite') +elseif (MTT_DB_TYPE == 'sqlite') { require_once(MTTINC. 'class.db.sqlite3.php'); $db = DBConnection::init(new Database_Sqlite3); $db->connect( array( 'filename' => MTTPATH. 'db/todolist.db' ) ); } else { - # It seems not installed - die("Not installed. Run setup.php first."); + die("Incorrect database connection config"); } -DBConnection::setPrefix(Config::get('prefix')); + +DBConnection::setPrefix(MTT_DB_PREFIX); DBCore::setDefaultInstance(new DBCore($db)); Config::load(); @@ -96,6 +93,21 @@ if (need_auth() && !isset($dontStartSession)) { setup_and_start_session(); } + +function requireConfig() +{ + $exists = file_exists(MTTPATH. 'config.php'); + $defined = false; + if ($exists) { + require_once(MTTPATH. 'config.php'); + $defined = defined('MTT_DB_TYPE'); + } + # It seems not installed + if (!$defined) { + die("Not installed. Run setup.php first."); + } +} + function need_auth() { return (Config::get('password') != '') ? 1 : 0; @@ -196,6 +208,11 @@ function mttinfo($v) echo get_mttinfo($v); } +function get_mttinfo($v) +{ + return htmlspecialchars( get_unsafe_mttinfo($v) ); +} + /* * Returned values from get_unsafe_mttinfo() can be unsafe for html. * But '\r' and '\n' in URLs taken from config are removed. @@ -243,11 +260,6 @@ function get_unsafe_mttinfo($v) } } -function get_mttinfo($v) -{ - return htmlspecialchars( get_unsafe_mttinfo($v) ); -} - function reset_mttinfo($key) { global $_mttinfo; diff --git a/src/setup.php b/src/setup.php index 6136f0d..617f972 100644 --- a/src/setup.php +++ b/src/setup.php @@ -6,279 +6,149 @@ Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details. */ -set_exception_handler('myExceptionHandler'); - -# Check old config file -require_once('./db/config.php'); -if (!isset($config['db'])) -{ - if(isset($config['mysql'])) { - $config['db'] = 'mysql'; - $config['mysql.host'] = $config['mysql'][0]; - $config['mysql.db'] = $config['mysql'][3]; - $config['mysql.user'] = $config['mysql'][1]; - $config['mysql.password'] = $config['mysql'][2]; - } else { - $config['db'] = 'sqlite'; - } - if(isset($config['allow']) && $config['allow'] == 'read') $config['allowread'] = 1; -} - -if ($config['db'] != '') -{ - require_once('./includes/class.config.php'); - Config::$noDatabase = true; //will not load settings from database in init.php - - require_once('./init.php'); - if ( !is_logged() ) - { - die("Access denied!
Disable password protection or Log in."); - } - $db = DBConnection::instance(); - $dbtype = (strtolower(get_class($db)) == 'database_mysql') ? 'mysql' : 'sqlite'; -} -else -{ - if (!defined('MTTPATH')) define('MTTPATH', dirname(__FILE__) .'/'); - if (!defined('MTTINC')) define('MTTINC', MTTPATH. 'includes/'); - require_once(MTTINC. 'common.php'); - require_once(MTTINC. 'class.dbconnection.php'); - require_once(MTTINC. 'class.config.php'); - Config::$noDatabase = true; - Config::loadDbConfig($config); - unset($config); - - $db = null; - $dbtype = ''; -} - +// Can be used to upgrade database from myTinyTodo v1.4 or later $lastVer = '1.7'; + +if (getenv('MTT_ENABLE_DEBUG') == 'YES') { + set_exception_handler('debugExceptionHandler'); +} +else { + set_exception_handler('myExceptionHandler'); +} + +if (!defined('MTTPATH')) define('MTTPATH', dirname(__FILE__) .'/'); +if (!defined('MTTINC')) define('MTTINC', MTTPATH. 'includes/'); +require_once(MTTINC. 'common.php'); +require_once(MTTINC. 'class.dbconnection.php'); +require_once(MTTINC. 'class.config.php'); + +$db = null; +$ver = ''; +$error = ''; + +$configExists = file_exists(MTTPATH. 'config.php'); +$oldConfigExists = file_exists(MTTPATH. 'db/config.php'); + + echo 'myTinyTodo @VERSION Setup'; echo "myTinyTodo @VERSION Setup

"; -# determine current installed version -$ver = $db ? get_ver($db, $dbtype) : ''; - -if (!$ver) +if (!$configExists && $oldConfigExists) { - if (!isset($_POST['installdb']) && !isset($_POST['install']) && $db !== null) + // First we need to migrate database config + require_once(MTTPATH. 'db/config.php'); + if (isset($config['password']) && $config['password'] != '') { + if (!isset($_POST['configpassword']) || $_POST['configpassword'] != $config['password']) { + exitMessage("Enter current password to continue.
"); + } + } + Config::loadConfigV14($config); + tryToSaveDBConfig(); + $configExists = true; +} + +if ($configExists) +{ + // No need to migrate database config + require_once(MTTPATH. 'config.php'); + $db = testConnect($error); + if (!$db) { + exitMessage( "Database connection config file seems to be incorrect. You can remove config.php or edit it manually and then reload setup.

". + "Error: ". htmlspecialchars($error) ); + } + // Config file v1.7 already exists and set up correctly + $dbtype = MTT_DB_TYPE; + + // Determine current installed db version + $ver = databaseVersion($db); + + if ($ver == '1.4') { + // Need to upgrade. Do not ask for old password + require_once(MTTPATH. 'db/config.php'); + Config::loadConfigV14($config); + unset($config); + DBConnection::init($db); + } + else { + if ($ver != '1.7') { + Config::$noDatabase = true; //will not load settings from database in init.php + } + require_once('./init.php'); + if ( !is_logged() ) { + die("Access denied!
Disable password protection or Log in."); + } + } +} + +if ($ver == '') +{ + $install = trim(_post('install')); + + if ($install == '' && $db !== null) { # We already have settings file and need to create tables. exitMessage("
Click next to create tables in '". htmlspecialchars($dbtype). "' database.

-
"); + "); } - else if (!isset($_POST['installdb']) && !isset($_POST['install'])) + elseif ($install == '') { # Specify database type and connection settings to save. - exitMessage("
Select database type to use:

-

-
-
"); + exitMessage(" +
Select database type to use:

+ +

+
+
+ "); } - elseif(isset($_POST['installdb'])) + elseif ($install == 'config') { # Save configuration - $dbtype = ($_POST['installdb'] == 'mysql') ? 'mysql' : 'sqlite'; - Config::set('db', $dbtype); - if($dbtype == 'mysql') { - Config::set('mysql.host', _post('mysql_host')); - Config::set('mysql.db', _post('mysql_db')); - Config::set('mysql.user', _post('mysql_user')); - Config::set('mysql.password', _post('mysql_password')); - Config::set('prefix', trim(_post('prefix'))); + $dbtype = ($_POST['db_type'] == 'mysql') ? 'mysql' : 'sqlite'; + Config::set('db.type', $dbtype); + if ($dbtype == 'mysql') { + Config::set('db.host', _post('db_host')); + Config::set('db.name', _post('db_name')); + Config::set('db.user', _post('db_user')); + Config::set('db.password', _post('db_password')); + Config::set('db.prefix', trim(_post('db_prefix'))); } - if(!testConnect($error)) { + Config::defineDbConstants(); + $db = testConnect($error); + if (!$db) { exitMessage("Database connection error: ". htmlspecialchars($error)); } - if(!is_writable('./db/config.php')) { - exitMessage("Config file ('db/config.php') is not writable."); + if (defined('MTT_DB_DRIVER')) { + Config::set('db.driver', MTT_DB_DRIVER); } - Config::saveDbConfig(); - exitMessage("This will create myTinyTodo database
"); + tryToSaveDBConfig(); + exitMessage("This will create myTinyTodo database
"); } - - # install database - if($dbtype == 'mysql') + elseif ($install == 'create') { - try - { - - $db->ex( -"CREATE TABLE {$db->prefix}lists ( - `id` INT UNSIGNED NOT NULL auto_increment, - `uuid` CHAR(36) NOT NULL default '', - `ow` INT NOT NULL default 0, - `name` VARCHAR(50) NOT NULL default '', - `d_created` INT UNSIGNED NOT NULL default 0, - `d_edited` INT UNSIGNED NOT NULL default 0, - `sorting` TINYINT UNSIGNED NOT NULL default 0, - `published` TINYINT UNSIGNED NOT NULL default 0, - `taskview` INT UNSIGNED NOT NULL default 0, - PRIMARY KEY(`id`), - UNIQUE KEY(`uuid`) -) CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci "); - - - $db->ex( -"CREATE TABLE {$db->prefix}todolist ( - `id` INT UNSIGNED NOT NULL auto_increment, - `uuid` CHAR(36) NOT NULL default '', - `list_id` INT UNSIGNED NOT NULL default 0, - `d_created` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ - `d_completed` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ - `d_edited` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ - `compl` TINYINT UNSIGNED NOT NULL default 0, - `title` VARCHAR(250) NOT NULL, - `note` TEXT, - `prio` TINYINT NOT NULL default 0, /* priority -,0,+ */ - `ow` INT NOT NULL default 0, /* order weight */ - `tags` VARCHAR(600) NOT NULL default '', /* for fast access to task tags */ - `tags_ids` VARCHAR(250) NOT NULL default '', /* no more than 22 tags (x11 chars) */ - `duedate` DATE default NULL, - PRIMARY KEY(`id`), - KEY(`list_id`), - UNIQUE KEY(`uuid`) -) CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci "); - - - $db->ex( -"CREATE TABLE {$db->prefix}tags ( - `id` INT UNSIGNED NOT NULL auto_increment, - `name` VARCHAR(50) NOT NULL, - PRIMARY KEY(`id`), - UNIQUE KEY `name` (`name`) -) CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci "); - - - $db->ex( -"CREATE TABLE {$db->prefix}tag2task ( - `tag_id` INT UNSIGNED NOT NULL, - `task_id` INT UNSIGNED NOT NULL, - `list_id` INT UNSIGNED NOT NULL, - KEY(`tag_id`), - KEY(`task_id`), - KEY(`list_id`) /* for tagcloud */ -) CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci "); - - - $db->ex( -"CREATE TABLE {$db->prefix}settings ( - `param_key` VARCHAR(100) NOT NULL default '', - `param_value` TEXT, -UNIQUE KEY `param_key` (`param_key`) -) CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci "); - - - $db->ex( -"CREATE TABLE {$db->prefix}sessions ( - `id` VARCHAR(64) NOT NULL default '', /* upto 64 bytes for sha256 */ - `data` TEXT, - `last_access` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ - `expires` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ -UNIQUE KEY `id` (`id`) -) CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci "); - - + # install database + try { + createAllTables($db, $dbtype); } catch (Exception $e) { exitMessage("Error: ". htmlarray($e->getMessage())); } + + # create default list + $db->ex( "INSERT INTO {$db->prefix}lists (uuid,name,d_created,taskview) VALUES (?,?,?,?)", array(generateUUID(), 'Todo', time(), 1) ); + + Config::save(); } - else #sqlite - { - try - { - - $db->ex( -"CREATE TABLE {$db->prefix}lists ( - id INTEGER PRIMARY KEY, - uuid CHAR(36) NOT NULL, - ow INTEGER NOT NULL default 0, - name VARCHAR(50) NOT NULL, - d_created INTEGER UNSIGNED NOT NULL default 0, - d_edited INTEGER UNSIGNED NOT NULL default 0, - sorting TINYINT UNSIGNED NOT NULL default 0, - published TINYINT UNSIGNED NOT NULL default 0, - taskview INTEGER UNSIGNED NOT NULL default 0 -) "); - - $db->ex("CREATE UNIQUE INDEX lists_uuid ON {$db->prefix}lists (uuid)"); - - $db->ex( -"CREATE TABLE {$db->prefix}todolist ( - id INTEGER PRIMARY KEY, - uuid CHAR(36) NOT NULL, - list_id INTEGER UNSIGNED NOT NULL default 0, - d_created INTEGER UNSIGNED NOT NULL default 0, - d_completed INTEGER UNSIGNED NOT NULL default 0, - d_edited INTEGER UNSIGNED NOT NULL default 0, - compl TINYINT UNSIGNED NOT NULL default 0, - title VARCHAR(250) NOT NULL, - note TEXT, - prio TINYINT NOT NULL default 0, - ow INTEGER NOT NULL default 0, - tags VARCHAR(600) NOT NULL default '', - tags_ids VARCHAR(250) NOT NULL default '', - duedate DATE default NULL -) "); - $db->ex("CREATE INDEX todo_list_id ON {$db->prefix}todolist (list_id)"); - $db->ex("CREATE UNIQUE INDEX todo_uuid ON {$db->prefix}todolist (uuid)"); - - - $db->ex( -"CREATE TABLE {$db->prefix}tags ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR(50) NOT NULL COLLATE NOCASE -) "); - $db->ex("CREATE UNIQUE INDEX tags_name ON {$db->prefix}tags (name COLLATE NOCASE)"); - - - $db->ex( -"CREATE TABLE {$db->prefix}tag2task ( - tag_id INTEGER NOT NULL, - task_id INTEGER NOT NULL, - list_id INTEGER NOT NULL -) "); - $db->ex("CREATE INDEX tag2task_tag_id ON {$db->prefix}tag2task (tag_id)"); - $db->ex("CREATE INDEX tag2task_task_id ON {$db->prefix}tag2task (task_id)"); - $db->ex("CREATE INDEX tag2task_list_id ON {$db->prefix}tag2task (list_id)"); /* for tagcloud */ - - - $db->ex( -"CREATE TABLE {$db->prefix}settings ( - param_key VARCHAR(100) NOT NULL default '', - param_value TEXT -) "); - - $db->ex("CREATE UNIQUE INDEX settings_key ON {$db->prefix}settings (param_key COLLATE NOCASE)"); - - - $db->ex( -"CREATE TABLE {$db->prefix}sessions ( - id VARCHAR(64) NOT NULL default '', - data TEXT, - last_access INTEGER UNSIGNED NOT NULL default 0, - expires INTEGER UNSIGNED NOT NULL default 0 -) "); - - $db->ex("CREATE UNIQUE INDEX sessions_id ON {$db->prefix}sessions (id COLLATE NOCASE)"); - - } catch (Exception $e) { - exitMessage("Error: ". htmlarray($e->getMessage())); - } + else { + exitMessage("Unknown action"); } - - # create default list - $db->ex( "INSERT INTO {$db->prefix}lists (uuid,name,d_created,taskview) VALUES (?,?,?,?)", array(generateUUID(), 'Todo', time(), 1) ); - - Config::save(); - Config::saveDbConfig(); } -elseif($ver == $lastVer) +elseif ($ver == $lastVer) { exitMessage("Installed version does not require database update."); } @@ -287,9 +157,12 @@ else if (!in_array($ver, array('1.4'))) { exitMessage(htmlspecialchars("Can not update. Unsupported database version ($ver).")); } + if (!isset($_POST['update'])) { exitMessage(htmlspecialchars("Update database v$ver to v$lastVer"). "

-
"); +
+ +
"); } # update process @@ -298,24 +171,193 @@ else update_14_17($db, $dbtype); } } -echo "Done

Attention! Delete this file for security reasons."; + +echo "Done

Attention! Delete this file for security reasons.

Go to homepage."; printFooter(); -function get_ver(Database_Abstract $db, $dbtype) +function createAllTables($db, $dbtype) { - if ( !$db || $dbtype == '' ) return ''; + if ($dbtype == 'mysql') { + createMysqlTables($db); + } + else { + createSqliteTables($db); + } +} + + +function createMysqlTables($db) +{ + $db->ex( +"CREATE TABLE {$db->prefix}lists ( + `id` INT UNSIGNED NOT NULL auto_increment, + `uuid` CHAR(36) NOT NULL default '', + `ow` INT NOT NULL default 0, + `name` VARCHAR(50) NOT NULL default '', + `d_created` INT UNSIGNED NOT NULL default 0, + `d_edited` INT UNSIGNED NOT NULL default 0, + `sorting` TINYINT UNSIGNED NOT NULL default 0, + `published` TINYINT UNSIGNED NOT NULL default 0, + `taskview` INT UNSIGNED NOT NULL default 0, + PRIMARY KEY(`id`), + UNIQUE KEY(`uuid`) +) CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci "); + + + $db->ex( +"CREATE TABLE {$db->prefix}todolist ( + `id` INT UNSIGNED NOT NULL auto_increment, + `uuid` CHAR(36) NOT NULL default '', + `list_id` INT UNSIGNED NOT NULL default 0, + `d_created` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ + `d_completed` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ + `d_edited` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ + `compl` TINYINT UNSIGNED NOT NULL default 0, + `title` VARCHAR(250) NOT NULL, + `note` TEXT, + `prio` TINYINT NOT NULL default 0, /* priority -,0,+ */ + `ow` INT NOT NULL default 0, /* order weight */ + `tags` VARCHAR(600) NOT NULL default '', /* for fast access to task tags */ + `tags_ids` VARCHAR(250) NOT NULL default '', /* no more than 22 tags (x11 chars) */ + `duedate` DATE default NULL, + PRIMARY KEY(`id`), + KEY(`list_id`), + UNIQUE KEY(`uuid`) +) CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci "); + + + $db->ex( +"CREATE TABLE {$db->prefix}tags ( + `id` INT UNSIGNED NOT NULL auto_increment, + `name` VARCHAR(50) NOT NULL, + PRIMARY KEY(`id`), + UNIQUE KEY `name` (`name`) +) CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci "); + + + $db->ex( +"CREATE TABLE {$db->prefix}tag2task ( + `tag_id` INT UNSIGNED NOT NULL, + `task_id` INT UNSIGNED NOT NULL, + `list_id` INT UNSIGNED NOT NULL, + KEY(`tag_id`), + KEY(`task_id`), + KEY(`list_id`) /* for tagcloud */ +) CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci "); + + + $db->ex( +"CREATE TABLE {$db->prefix}settings ( + `param_key` VARCHAR(100) NOT NULL default '', + `param_value` TEXT, +UNIQUE KEY `param_key` (`param_key`) +) CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci "); + + + $db->ex( +"CREATE TABLE {$db->prefix}sessions ( + `id` VARCHAR(64) NOT NULL default '', /* upto 64 bytes for sha256 */ + `data` TEXT, + `last_access` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ + `expires` INT UNSIGNED NOT NULL default 0, /* time() timestamp */ +UNIQUE KEY `id` (`id`) +) CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci "); +} + + +function createSqliteTables($db) +{ + $db->ex( +"CREATE TABLE {$db->prefix}lists ( + id INTEGER PRIMARY KEY, + uuid CHAR(36) NOT NULL, + ow INTEGER NOT NULL default 0, + name VARCHAR(50) NOT NULL, + d_created INTEGER UNSIGNED NOT NULL default 0, + d_edited INTEGER UNSIGNED NOT NULL default 0, + sorting TINYINT UNSIGNED NOT NULL default 0, + published TINYINT UNSIGNED NOT NULL default 0, + taskview INTEGER UNSIGNED NOT NULL default 0 +) "); + + $db->ex("CREATE UNIQUE INDEX lists_uuid ON {$db->prefix}lists (uuid)"); + + $db->ex( +"CREATE TABLE {$db->prefix}todolist ( + id INTEGER PRIMARY KEY, + uuid CHAR(36) NOT NULL, + list_id INTEGER UNSIGNED NOT NULL default 0, + d_created INTEGER UNSIGNED NOT NULL default 0, + d_completed INTEGER UNSIGNED NOT NULL default 0, + d_edited INTEGER UNSIGNED NOT NULL default 0, + compl TINYINT UNSIGNED NOT NULL default 0, + title VARCHAR(250) NOT NULL, + note TEXT, + prio TINYINT NOT NULL default 0, + ow INTEGER NOT NULL default 0, + tags VARCHAR(600) NOT NULL default '', + tags_ids VARCHAR(250) NOT NULL default '', + duedate DATE default NULL +) "); + $db->ex("CREATE INDEX todo_list_id ON {$db->prefix}todolist (list_id)"); + $db->ex("CREATE UNIQUE INDEX todo_uuid ON {$db->prefix}todolist (uuid)"); + + + $db->ex( +"CREATE TABLE {$db->prefix}tags ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(50) NOT NULL COLLATE NOCASE +) "); + $db->ex("CREATE UNIQUE INDEX tags_name ON {$db->prefix}tags (name COLLATE NOCASE)"); + + + $db->ex( +"CREATE TABLE {$db->prefix}tag2task ( + tag_id INTEGER NOT NULL, + task_id INTEGER NOT NULL, + list_id INTEGER NOT NULL +) "); + $db->ex("CREATE INDEX tag2task_tag_id ON {$db->prefix}tag2task (tag_id)"); + $db->ex("CREATE INDEX tag2task_task_id ON {$db->prefix}tag2task (task_id)"); + $db->ex("CREATE INDEX tag2task_list_id ON {$db->prefix}tag2task (list_id)"); /* for tagcloud */ + + + $db->ex( +"CREATE TABLE {$db->prefix}settings ( + param_key VARCHAR(100) NOT NULL default '', + param_value TEXT +) "); + + $db->ex("CREATE UNIQUE INDEX settings_key ON {$db->prefix}settings (param_key COLLATE NOCASE)"); + + + $db->ex( +"CREATE TABLE {$db->prefix}sessions ( + id VARCHAR(64) NOT NULL default '', + data TEXT, + last_access INTEGER UNSIGNED NOT NULL default 0, + expires INTEGER UNSIGNED NOT NULL default 0 +) "); + + $db->ex("CREATE UNIQUE INDEX sessions_id ON {$db->prefix}sessions (id COLLATE NOCASE)"); +} + + +function databaseVersion(Database_Abstract $db): string +{ + if ( !$db ) return ''; if ( !$db->tableExists($db->prefix.'todolist') ) return ''; $v = '1.0'; if ( !$db->tableExists($db->prefix.'tags') ) return $v; $v = '1.1'; - if ( !db_has_field($dbtype, $db, $db->prefix.'todolist', 'duedate') ) return $v; + if ( !$db->tableFieldExists($db->prefix.'todolist', 'duedate') ) return $v; $v = '1.2'; if ( !$db->tableExists($db->prefix.'lists') ) return $v; $v = '1.3.0'; - if ( !db_has_field($dbtype, $db, $db->prefix.'todolist', 'd_completed') ) return $v; + if ( !$db->tableFieldExists($db->prefix.'todolist', 'd_completed') ) return $v; $v = '1.3.1'; - if ( !db_has_field($dbtype, $db, $db->prefix.'todolist', 'd_edited') ) return $v; + if ( !$db->tableFieldExists($db->prefix.'todolist', 'd_edited') ) return $v; $v = '1.4'; if ( !$db->tableExists($db->prefix.'settings') ) return $v; $v = '1.7'; @@ -334,79 +376,123 @@ function printFooter() echo ""; } -function db_has_field($dbtype, Database_Abstract $db, $table, $field) +function tryToSaveDbConfig() { - if ($dbtype == 'mysql') return has_field_mysql($db, $table, $field); - elseif ($dbtype == 'sqlite') return has_field_sqlite($db, $table, $field); - else throw new Exception("Unexpected database type"); -} - -function has_field_sqlite(Database_Abstract $db, $table, $field) -{ - $q = $db->dq("PRAGMA table_info(". $db->quote($table). ")"); - while($r = $q->fetchRow()) { - if($r[1] == $field) return true; + if (!file_exists(MTTPATH.'config.php')) { + @touch(MTTPATH.'config.php'); } - return false; -} - -function has_field_mysql(Database_Abstract $db, $table, $field) -{ - $q = $db->dq("DESCRIBE `$table`"); - while($r = $q->fetchRow()) { - if($r[0] == $field) return true; + if (!is_writable(MTTPATH.'config.php')) { + exitMessage("Database connection config file ('config.php') is not writable. You need to edit it manually, set contents to this and run setup once more.

\n". + "\n". + "" + ); } - return false; + Config::saveDbConfig(); } function testConnect(&$error) { + $db = null; try { - if(Config::get('db') == 'mysql') + if (!defined('MTT_DB_TYPE')) throw new Exception("MTT_DB_TYPE is not defined"); + + if (MTT_DB_TYPE == 'mysql') { + $hasPDO = false; + $hasMysqli = false; if (defined('PDO::MYSQL_ATTR_FOUND_ROWS')) { - require_once(MTTINC. 'class.db.mysql.php'); - Config::set('mysqli', 0); + $hasPDO = true; } - else if (function_exists("mysqli_connect")) { - require_once(MTTINC. 'class.db.mysqli.php'); - Config::set('mysqli', 1); + if (function_exists("mysqli_connect")) { + $hasMysqli = true; + } + + $driver = ''; + if (defined('MTT_DB_DRIVER')) { + // forced to use specific mysql interface + if ( in_array(MTT_DB_DRIVER, ['mysqli', 'pdo', '']) ) { + $driver = MTT_DB_DRIVER; + if ($driver == '') $driver = 'pdo'; // default + } + else { + throw new Exception("Unknown database driver"); + } + } + + if ($driver == '') { + // auto-detect driver + if ($hasPDO) $driver = 'pdo'; + else if ($hasMysqli) $driver = 'mysqli'; + } + + if ($driver == 'mysqli') { + if ($hasMysqli) { + require_once(MTTINC. 'class.db.mysqli.php'); + if (!defined('MTT_DB_DRIVER')) define('MTT_DB_DRIVER', 'mysqli'); + } + else { + throw new Exception("Required PHP extension 'MySQLi' is not installed."); + } } else { - $text = "Required PHP extension 'PDO mysql' is not installed."; - throw new Exception($text); + if ($hasPDO) { + require_once(MTTINC. 'class.db.mysql.php'); + if (!defined('MTT_DB_DRIVER')) define('MTT_DB_DRIVER', ''); // set pdo? + } + else { + throw new Exception("Required PHP extension 'PDO_MySQL' is not installed."); + } + } + + foreach (['MTT_DB_HOST', 'MTT_DB_USER', 'MTT_DB_PASSWORD', 'MTT_DB_NAME', 'MTT_DB_PREFIX'] as $c) { + if (!defined($c)) throw new Exception("$c is not defined"); } $db = new Database_Mysql; - $db->connect(array( - 'host' => Config::get('mysql.host'), - 'user' => Config::get('mysql.user'), - 'password' => Config::get('mysql.password'), - 'db' => Config::get('mysql.db') + $db->connect( array( + 'host' => MTT_DB_HOST, + 'user' => MTT_DB_USER, + 'password' => MTT_DB_PASSWORD, + 'db' => MTT_DB_NAME )); } - else + else if (MTT_DB_TYPE == 'sqlite') { - if(false === $f = @fopen(MTTPATH. 'db/todolist.db', 'a+')) throw new Exception("database file is not readable/writable"); - else fclose($f); - - if(!is_writable(MTTPATH. 'db/')) throw new Exception("database directory ('db') is not writable"); - + if (false === $f = @fopen(MTTPATH. 'db/todolist.db', 'a+')) { + throw new Exception("database file is not readable/writable"); + } + else { + fclose($f); + } + if (!is_writable(MTTPATH. 'db/')) { + throw new Exception("database directory ('db') is not writable"); + } require_once(MTTINC. 'class.db.sqlite3.php'); $db = new Database_Sqlite3; $db->connect( array( 'filename' => MTTPATH. 'db/todolist.db' ) ); } - } catch(Exception $e) { - $error = $e->getMessage(); - return 0; + else { + new Exception("Unsupported database type"); + } + + if (!defined('MTT_DB_PREFIX')) define('MTT_DB_PREFIX', ''); + $db->setPrefix(MTT_DB_PREFIX); } - return 1; + catch(Exception $e) { + //if (MTT_DEBUG) throw $e; + $error = $e->getMessage(); + return null; + } + $error = ''; + return $db; } function debugExceptionHandler($e) { - echo '
Error: \''. htmlspecialchars($e->getMessage()) .'\' in '. htmlspecialchars($e->getFile() .':'. $e->getLine()). ''. + echo '
Error ('. htmlspecialchars(get_class($e)) .'): \''. htmlspecialchars($e->getMessage()) .'\' in '. htmlspecialchars($e->getFile() .':'. $e->getLine()). ''. "\n
". htmlspecialchars($e->getTraceAsString()) . "
\n"; exit; }