Add Mysql/MariaDB support
This commit is contained in:
parent
34711f5846
commit
d9dfd9d619
|
|
@ -42,8 +42,8 @@ Thumbs.db
|
|||
|
||||
# Vagrant #
|
||||
###########
|
||||
|
||||
.vagrant
|
||||
|
||||
# App specific #
|
||||
################
|
||||
config.php
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
45
common.php
45
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');
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Deny from all
|
||||
|
|
@ -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()."')
|
||||
");
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
");
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.'`';
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ class Schema
|
|||
$current_version = $this->db->getConnection()->getSchemaVersion();
|
||||
|
||||
if ($current_version < $last_version) {
|
||||
|
||||
return $this->migrateTo($current_version, $last_version);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue