summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--README.markdown1
-rw-r--r--check_setup.php6
-rw-r--r--common.php45
-rw-r--r--controllers/task.php14
-rw-r--r--docs/mysql-configuration.markdown55
-rw-r--r--models/board.php16
-rw-r--r--models/task.php10
-rw-r--r--schemas/.htaccess1
-rw-r--r--schemas/mysql.php165
-rw-r--r--schemas/sqlite.php (renamed from models/schema.php)2
-rw-r--r--templates/config_index.php26
-rw-r--r--tests/Base.php2
-rw-r--r--tests/CommentTest.php2
-rw-r--r--vendor/PicoDb/Database.php7
-rw-r--r--vendor/PicoDb/Drivers/Mysql.php76
-rw-r--r--vendor/PicoDb/Drivers/Sqlite.php17
-rw-r--r--vendor/PicoDb/Schema.php1
-rw-r--r--vendor/PicoDb/Table.php3
19 files changed, 405 insertions, 46 deletions
diff --git a/.gitignore b/.gitignore
index e14d545c..4dd62432 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/common.php b/common.php
index 0c163b5d..8c64419b 100644
--- a/common.php
+++ b/common.php
@@ -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&amp;action=downloadDb"><?= t('Download the database') ?></a>
- <?= t('(Gzip compressed Sqlite file)') ?>
- </li>
- <li>
- <a href="?controller=config&amp;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&amp;action=downloadDb"><?= t('Download the database') ?></a>
+ <?= t('(Gzip compressed Sqlite file)') ?>
+ </li>
+ <li>
+ <a href="?controller=config&amp;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;
}