diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | README.markdown | 1 | ||||
-rw-r--r-- | check_setup.php | 6 | ||||
-rw-r--r-- | common.php | 45 | ||||
-rw-r--r-- | controllers/task.php | 14 | ||||
-rw-r--r-- | docs/mysql-configuration.markdown | 55 | ||||
-rw-r--r-- | models/board.php | 16 | ||||
-rw-r--r-- | models/task.php | 10 | ||||
-rw-r--r-- | schemas/.htaccess | 1 | ||||
-rw-r--r-- | schemas/mysql.php | 165 | ||||
-rw-r--r-- | schemas/sqlite.php (renamed from models/schema.php) | 2 | ||||
-rw-r--r-- | templates/config_index.php | 26 | ||||
-rw-r--r-- | tests/Base.php | 2 | ||||
-rw-r--r-- | tests/CommentTest.php | 2 | ||||
-rw-r--r-- | vendor/PicoDb/Database.php | 7 | ||||
-rw-r--r-- | vendor/PicoDb/Drivers/Mysql.php | 76 | ||||
-rw-r--r-- | vendor/PicoDb/Drivers/Sqlite.php | 17 | ||||
-rw-r--r-- | vendor/PicoDb/Schema.php | 1 | ||||
-rw-r--r-- | vendor/PicoDb/Table.php | 3 |
19 files changed, 405 insertions, 46 deletions
@@ -42,8 +42,8 @@ Thumbs.db # Vagrant # ########### - .vagrant # App specific # ################ +config.php diff --git a/README.markdown b/README.markdown index f05ca5d7..a91b9a06 100644 --- a/README.markdown +++ b/README.markdown @@ -66,6 +66,7 @@ Documentation - [Installation on Debian](docs/debian-installation.markdown) - [Upgrade Kanboard to a new version](docs/update.markdown) - [Sqlite database management](docs/sqlite-database.markdown) +- [How to use Mysql instead of Sqlite](docs/mysql-configuration.markdown) - [How to use Kanboard with Vagrant](docs/vagrant.markdown) - [Webhooks](docs/webhooks.markdown) diff --git a/check_setup.php b/check_setup.php index 3751e59d..1484d459 100644 --- a/check_setup.php +++ b/check_setup.php @@ -19,9 +19,9 @@ if (version_compare(PHP_VERSION, '5.4.0', '<')) { } } -// Check extension: PDO Sqlite -if (! extension_loaded('pdo_sqlite')) { - die('PHP extension required: pdo_sqlite'); +// Check extension: PDO +if (! extension_loaded('pdo_sqlite') && ! extension_loaded('pdo_mysql')) { + die('PHP extension required: pdo_sqlite or pdo_mysql'); } // Check extension: mbstring @@ -10,12 +10,32 @@ $registry->db_version = 10; $registry->db = function() use ($registry) { require __DIR__.'/vendor/PicoDb/Database.php'; - require __DIR__.'/models/schema.php'; - $db = new \PicoDb\Database(array( - 'driver' => 'sqlite', - 'filename' => DB_FILENAME - )); + if (DB_DRIVER === 'sqlite') { + + require __DIR__.'/schemas/sqlite.php'; + + $db = new \PicoDb\Database(array( + 'driver' => 'sqlite', + 'filename' => DB_FILENAME + )); + } + elseif (DB_DRIVER === 'mysql') { + + require __DIR__.'/schemas/mysql.php'; + + $db = new \PicoDb\Database(array( + 'driver' => 'mysql', + 'hostname' => DB_HOSTNAME, + 'username' => DB_USERNAME, + 'password' => DB_PASSWORD, + 'database' => DB_NAME, + 'charset' => 'utf8', + )); + } + else { + die('Database driver not supported'); + } if ($db->schema()->check($registry->db_version)) { return $db; @@ -83,8 +103,17 @@ defined('AUTO_REFRESH_DURATION') or define('AUTO_REFRESH_DURATION', 60); // Custom session save path defined('SESSION_SAVE_PATH') or define('SESSION_SAVE_PATH', ''); -// Database filename -defined('DB_FILENAME') or define('DB_FILENAME', 'data/db.sqlite'); - // Application version defined('APP_VERSION') or define('APP_VERSION', 'master'); + +// Database driver: sqlite or mysql +defined('DB_DRIVER') or define('DB_DRIVER', 'sqlite'); + +// Sqlite configuration +defined('DB_FILENAME') or define('DB_FILENAME', 'data/db.sqlite'); + +// Mysql configuration +defined('DB_USERNAME') or define('DB_USERNAME', 'root'); +defined('DB_PASSWORD') or define('DB_PASSWORD', ''); +defined('DB_HOSTNAME') or define('DB_HOSTNAME', 'localhost'); +defined('DB_NAME') or define('DB_NAME', 'kanboard'); diff --git a/controllers/task.php b/controllers/task.php index 7aefb98c..16075ec2 100644 --- a/controllers/task.php +++ b/controllers/task.php @@ -186,6 +186,11 @@ class Task extends Base if (! empty($task['date_due'])) { $task['date_due'] = date(t('m/d/Y'), $task['date_due']); } + else { + $task['date_due'] = ''; + } + + $task['score'] = $task['score'] ?: ''; $this->response->html($this->template->layout('task_edit', array( 'errors' => array(), @@ -324,6 +329,15 @@ class Task extends Base if (! $task) $this->notfound(); $this->checkProjectPermissions($task['project_id']); + if (! empty($task['date_due'])) { + $task['date_due'] = date(t('m/d/Y'), $task['date_due']); + } + else { + $task['date_due'] = ''; + } + + $task['score'] = $task['score'] ?: ''; + $this->response->html($this->template->layout('task_new', array( 'errors' => array(), 'values' => $task, diff --git a/docs/mysql-configuration.markdown b/docs/mysql-configuration.markdown new file mode 100644 index 00000000..a46dcbf8 --- /dev/null +++ b/docs/mysql-configuration.markdown @@ -0,0 +1,55 @@ +How to use Mysql or MariaDB instead of Sqlite +============================================= + +By default Kanboard use Sqlite to stores its data. +However it's possible to use Mysql or MariaDB instead of Sqlite. + +By example, it can be useful if you don't want to store any data on the web server itself. + +Mysql configuration +------------------- + +### Create a database + +The first step is to create a database on your Mysql server. +By example, you can do that with the command line mysql client: + +```sql +CREATE DATABASE kanboard; +``` + +### Create a config file + +All application constants can be overrided by using a config file at the root of the project. +The second step is to create a config file named `config.php`: + +``` +. +├── LICENSE +├── common.php +├── .... +├── config.php <== Our config file +├── .... +├── index.php +├── .... +└── vendor +``` + +### Define Mysql parameters + +Inside our config file write those lines: + +```php +<?php + +// We choose to use Mysql instead of Sqlite +define('DB_DRIVER', 'mysql'); + +// Mysql parameters +define('DB_USERNAME', 'REPLACE_ME'); +define('DB_PASSWORD', 'REPLACE_ME'); +define('DB_HOSTNAME', 'REPLACE_ME'); +define('DB_NAME', 'kanboard'); +``` + +Now, you are ready to use Mysql! diff --git a/models/board.php b/models/board.php index 723ffd85..3bff8d56 100644 --- a/models/board.php +++ b/models/board.php @@ -32,22 +32,20 @@ class Board extends Base */ public function saveTasksPosition(array $values) { - $this->db->startTransaction(); - - $results = array(); $taskModel = new Task($this->db, $this->event); + $this->db->startTransaction(); + foreach ($values as $value) { - $results[] = $taskModel->move( - $value['task_id'], - $value['column_id'], - $value['position'] - ); + if (! $taskModel->move($value['task_id'], $value['column_id'], $value['position'])) { + $this->db->cancelTransaction(); + return false; + } } $this->db->closeTransaction(); - return ! in_array(false, $results, true); + return true; } /** diff --git a/models/task.php b/models/task.php index cbe37feb..e327e8c2 100644 --- a/models/task.php +++ b/models/task.php @@ -348,7 +348,15 @@ class Task extends Base } $original_task = $this->getById($values['id']); - $result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values); + + if ($original_task === false) { + return false; + } + + $updated_task = $values; + unset($updated_task['id']); + + $result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($updated_task); // Trigger events if ($result) { diff --git a/schemas/.htaccess b/schemas/.htaccess new file mode 100644 index 00000000..14249c50 --- /dev/null +++ b/schemas/.htaccess @@ -0,0 +1 @@ +Deny from all
\ No newline at end of file diff --git a/schemas/mysql.php b/schemas/mysql.php new file mode 100644 index 00000000..3b9cc014 --- /dev/null +++ b/schemas/mysql.php @@ -0,0 +1,165 @@ +<?php + +namespace Schema; + +function version_10($pdo) +{ +} + +function version_9($pdo) +{ +} + +function version_8($pdo) +{ +} + +function version_7($pdo) +{ +} + +function version_6($pdo) +{ +} + +function version_5($pdo) +{ +} + +function version_4($pdo) +{ +} + +function version_3($pdo) +{ +} + +function version_2($pdo) +{ +} + +function version_1($pdo) +{ + $pdo->exec(" + CREATE TABLE config ( + language CHAR(5) DEFAULT 'en_US', + webhooks_token VARCHAR(255), + timezone VARCHAR(50) DEFAULT 'UTC' + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE users ( + id INT NOT NULL AUTO_INCREMENT, + username VARCHAR(50), + password VARCHAR(255), + is_admin TINYINT DEFAULT 0, + default_project_id INT DEFAULT 0, + PRIMARY KEY (id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE projects ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(50) UNIQUE, + is_active TINYINT DEFAULT 1, + token VARCHAR(255), + PRIMARY KEY (id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE project_has_users ( + id INT NOT NULL AUTO_INCREMENT, + project_id INT, + user_id INT, + PRIMARY KEY (id), + UNIQUE KEY `idx_project_user` (project_id, user_id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE columns ( + id INT NOT NULL AUTO_INCREMENT, + title VARCHAR(255), + position INT NOT NULL, + project_id INT NOT NULL, + task_limit INT DEFAULT '0', + UNIQUE KEY `idx_title_project` (title, project_id), + PRIMARY KEY (id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE tasks ( + id INT NOT NULL AUTO_INCREMENT, + title VARCHAR(255), + description TEXT, + date_creation INT, + date_completed INT, + date_due INT, + color_id VARCHAR(50), + project_id INT, + column_id INT, + owner_id INT DEFAULT '0', + position INT, + score INT, + is_active TINYINT DEFAULT 1, + PRIMARY KEY (id), + INDEX `idx_task_active` (is_active), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE comments ( + id INT NOT NULL AUTO_INCREMENT, + task_id INT, + user_id INT, + date INT, + comment TEXT, + PRIMARY KEY (id), + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES tasks(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE actions ( + id INT NOT NULL AUTO_INCREMENT, + project_id INT, + event_name VARCHAR(50), + action_name VARCHAR(50), + PRIMARY KEY (id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE action_has_params ( + id INT NOT NULL AUTO_INCREMENT, + action_id INT, + name VARCHAR(50), + value VARCHAR(50), + PRIMARY KEY (id), + FOREIGN KEY(action_id) REFERENCES actions(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + INSERT INTO users + (username, password, is_admin) + VALUES ('admin', '".\password_hash('admin', PASSWORD_BCRYPT)."', '1') + "); + + $pdo->exec(" + INSERT INTO config + (webhooks_token) + VALUES ('".\Model\Base::generateToken()."') + "); +} diff --git a/models/schema.php b/schemas/sqlite.php index 621bc981..8e5698f5 100644 --- a/models/schema.php +++ b/schemas/sqlite.php @@ -113,7 +113,7 @@ function version_1($pdo) username TEXT, password TEXT, is_admin INTEGER DEFAULT 0, - default_project_id DEFAULT 0 + default_project_id INTEGER DEFAULT 0 ) "); diff --git a/templates/config_index.php b/templates/config_index.php index 0af2f9c2..fef8909d 100644 --- a/templates/config_index.php +++ b/templates/config_index.php @@ -27,18 +27,20 @@ <?= t('Webhooks token:') ?> <strong><?= Helper\escape($values['webhooks_token']) ?></strong> </li> - <li> - <?= t('Database size:') ?> - <strong><?= Helper\format_bytes($db_size) ?></strong> - </li> - <li> - <a href="?controller=config&action=downloadDb"><?= t('Download the database') ?></a> - <?= t('(Gzip compressed Sqlite file)') ?> - </li> - <li> - <a href="?controller=config&action=optimizeDb"><?= t('Optimize the database') ?></a> - <?= t('(VACUUM command)') ?> - </li> + <?php if (DB_DRIVER === 'sqlite'): ?> + <li> + <?= t('Database size:') ?> + <strong><?= Helper\format_bytes($db_size) ?></strong> + </li> + <li> + <a href="?controller=config&action=downloadDb"><?= t('Download the database') ?></a> + <?= t('(Gzip compressed Sqlite file)') ?> + </li> + <li> + <a href="?controller=config&action=optimizeDb"><?= t('Optimize the database') ?></a> + <?= t('(VACUUM command)') ?> + </li> + <?php endif ?> <li> <?= t('Official website:') ?> <a href="http://kanboard.net/" target="_blank" rel="noreferer">http://kanboard.net/</a> diff --git a/tests/Base.php b/tests/Base.php index ba664c3a..236d0000 100644 --- a/tests/Base.php +++ b/tests/Base.php @@ -3,7 +3,7 @@ require_once __DIR__.'/../vendor/PicoDb/Database.php'; require_once __DIR__.'/../core/event.php'; require_once __DIR__.'/../core/translator.php'; -require_once __DIR__.'/../models/schema.php'; +require_once __DIR__.'/../schemas/sqlite.php'; require_once __DIR__.'/../models/task.php'; require_once __DIR__.'/../models/acl.php'; require_once __DIR__.'/../models/comment.php'; diff --git a/tests/CommentTest.php b/tests/CommentTest.php index c23c4e51..52652711 100644 --- a/tests/CommentTest.php +++ b/tests/CommentTest.php @@ -63,8 +63,6 @@ class CommentTest extends Base $comment = $c->getById(1); $this->assertNotEmpty($comment); $this->assertEquals('bla', $comment['comment']); - - $this->assertFalse($c->update(array('id' => 4, 'comment' => 'bla'))); } public function testValidateCreation() diff --git a/vendor/PicoDb/Database.php b/vendor/PicoDb/Database.php index 86f5fe3c..9201e9d2 100644 --- a/vendor/PicoDb/Database.php +++ b/vendor/PicoDb/Database.php @@ -19,7 +19,12 @@ class Database case 'sqlite': require_once __DIR__.'/Drivers/Sqlite.php'; - $this->pdo = new Sqlite($settings['filename']); + $this->pdo = new Sqlite($settings); + break; + + case 'mysql': + require_once __DIR__.'/Drivers/Mysql.php'; + $this->pdo = new Mysql($settings); break; default: diff --git a/vendor/PicoDb/Drivers/Mysql.php b/vendor/PicoDb/Drivers/Mysql.php new file mode 100644 index 00000000..22277a01 --- /dev/null +++ b/vendor/PicoDb/Drivers/Mysql.php @@ -0,0 +1,76 @@ +<?php + +namespace PicoDb; + +class Mysql extends \PDO { + + private $schema_table = 'schema_version'; + + + public function __construct(array $settings) + { + $required_atttributes = array( + 'hostname', + 'username', + 'password', + 'database', + 'charset', + ); + + foreach ($required_atttributes as $attribute) { + if (! isset($settings[$attribute])) { + throw new \LogicException('This configuration parameter is missing: "'.$attribute.'"'); + } + } + + $dsn = 'mysql:host='.$settings['hostname'].';dbname='.$settings['database']; + $options = array( + \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES '.$settings['charset'] + ); + + parent::__construct($dsn, $settings['username'], $settings['password'], $options); + + if (isset($settings['schema_table'])) { + $this->schema_table = $settings['schema_table']; + } + } + + + public function getSchemaVersion() + { + $this->exec("CREATE TABLE IF NOT EXISTS `".$this->schema_table."` (`version` INT DEFAULT '0')"); + + $rq = $this->prepare('SELECT `version` FROM `'.$this->schema_table.'`'); + $rq->execute(); + $result = $rq->fetch(\PDO::FETCH_ASSOC); + + if (isset($result['version'])) { + return (int) $result['version']; + } + else { + $this->exec('INSERT INTO `'.$this->schema_table.'` VALUES(0)'); + } + + return 0; + } + + + public function setSchemaVersion($version) + { + $rq = $this->prepare('UPDATE `'.$this->schema_table.'` SET `version`=?'); + $rq->execute(array($version)); + } + + + public function getLastId() + { + return $this->lastInsertId(); + } + + + public function escapeIdentifier($value) + { + if (strpos($value, '.') !== false) return $value; + return '`'.$value.'`'; + } +}
\ No newline at end of file diff --git a/vendor/PicoDb/Drivers/Sqlite.php b/vendor/PicoDb/Drivers/Sqlite.php index 6555e73d..83b61c40 100644 --- a/vendor/PicoDb/Drivers/Sqlite.php +++ b/vendor/PicoDb/Drivers/Sqlite.php @@ -5,9 +5,19 @@ namespace PicoDb; class Sqlite extends \PDO { - public function __construct($filename) + public function __construct(array $settings) { - parent::__construct('sqlite:'.$filename); + $required_atttributes = array( + 'filename', + ); + + foreach ($required_atttributes as $attribute) { + if (! isset($settings[$attribute])) { + throw new \LogicException('This configuration parameter is missing: "'.$attribute.'"'); + } + } + + parent::__construct('sqlite:'.$settings['filename']); $this->exec('PRAGMA foreign_keys = ON'); } @@ -20,8 +30,7 @@ class Sqlite extends \PDO { $result = $rq->fetch(\PDO::FETCH_ASSOC); if (isset($result['user_version'])) { - - return $result['user_version']; + return (int) $result['user_version']; } return 0; diff --git a/vendor/PicoDb/Schema.php b/vendor/PicoDb/Schema.php index 2f52b846..b75366ea 100644 --- a/vendor/PicoDb/Schema.php +++ b/vendor/PicoDb/Schema.php @@ -18,7 +18,6 @@ class Schema $current_version = $this->db->getConnection()->getSchemaVersion(); if ($current_version < $last_version) { - return $this->migrateTo($current_version, $last_version); } diff --git a/vendor/PicoDb/Table.php b/vendor/PicoDb/Table.php index 4cdf35f7..494e350a 100644 --- a/vendor/PicoDb/Table.php +++ b/vendor/PicoDb/Table.php @@ -67,8 +67,7 @@ class Table $result = $this->db->execute($sql, $values); - if ($result !== false && $result->rowCount() > 0) { - + if ($result !== false/* && $result->rowCount() > 0*/) { return true; } |