diff --git a/.gitignore b/.gitignore index e14d545cd..4dd624328 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 f05ca5d7f..a91b9a06a 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 3751e59d9..1484d459d 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 0c163b5da..8c64419bf 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 7aefb98cb..16075ec20 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 000000000..a46dcbf85 --- /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 +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 cbe37febf..e327e8c22 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 000000000..14249c50b --- /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 000000000..3b9cc014b --- /dev/null +++ b/schemas/mysql.php @@ -0,0 +1,165 @@ +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 similarity index 98% rename from models/schema.php rename to schemas/sqlite.php index 621bc9819..8e5698f52 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 0af2f9c2c..fef8909d1 100644 --- a/templates/config_index.php +++ b/templates/config_index.php @@ -27,18 +27,20 @@ = t('Webhooks token:') ?> = Helper\escape($values['webhooks_token']) ?> -