summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrédéric Guillot <fred@kanboard.net>2014-06-20 15:41:05 -0300
committerFrédéric Guillot <fred@kanboard.net>2014-06-20 15:41:05 -0300
commit7c5b900bd83b6b9bdb5656eb169381ff46f8106a (patch)
tree39481ff2ee73d7479369655ba86d343f302e1499
parentefdc959c555872677e599d2ff12e1263d719f3f2 (diff)
First API implementation
-rw-r--r--app/Model/Board.php15
-rw-r--r--app/Model/Category.php15
-rw-r--r--app/Model/Project.php15
-rw-r--r--app/Model/Task.php17
-rw-r--r--app/Model/User.php2
-rw-r--r--app/Schema/Mysql.php7
-rw-r--r--app/Schema/Sqlite.php7
-rw-r--r--app/Templates/config_index.php4
-rw-r--r--jsonrpc.php238
-rw-r--r--phpunit.xml2
-rwxr-xr-xscripts/api-client.php20
-rw-r--r--tests/Base.php57
-rw-r--r--tests/functionals/ApiTest.php417
-rw-r--r--tests/units/AclTest.php (renamed from tests/AclTest.php)0
-rw-r--r--tests/units/ActionTaskAssignColorCategoryTest.php (renamed from tests/ActionTaskAssignColorCategoryTest.php)0
-rw-r--r--tests/units/ActionTaskAssignColorUserTest.php (renamed from tests/ActionTaskAssignColorUserTest.php)0
-rw-r--r--tests/units/ActionTaskAssignCurrentUserTest.php (renamed from tests/ActionTaskAssignCurrentUserTest.php)0
-rw-r--r--tests/units/ActionTaskAssignSpecificUserTest.php (renamed from tests/ActionTaskAssignSpecificUserTest.php)0
-rw-r--r--tests/units/ActionTaskCloseTest.php (renamed from tests/ActionTaskCloseTest.php)0
-rw-r--r--tests/units/ActionTaskDuplicateAnotherProjectTest.php (renamed from tests/ActionTaskDuplicateAnotherProjectTest.php)0
-rw-r--r--tests/units/ActionTest.php (renamed from tests/ActionTest.php)0
-rw-r--r--tests/units/Base.php57
-rw-r--r--tests/units/BoardTest.php (renamed from tests/BoardTest.php)0
-rw-r--r--tests/units/CommentTest.php (renamed from tests/CommentTest.php)0
-rw-r--r--tests/units/ProjectTest.php (renamed from tests/ProjectTest.php)0
-rw-r--r--tests/units/TaskTest.php (renamed from tests/TaskTest.php)0
-rw-r--r--vendor/JsonRPC/Client.php174
-rw-r--r--vendor/JsonRPC/Server.php301
28 files changed, 1285 insertions, 63 deletions
diff --git a/app/Model/Board.php b/app/Model/Board.php
index 59a98923..09fc5b50 100644
--- a/app/Model/Board.php
+++ b/app/Model/Board.php
@@ -99,7 +99,7 @@ class Board extends Base
foreach (array('title', 'task_limit') as $field) {
foreach ($values[$field] as $column_id => $field_value) {
- $this->db->table(self::TABLE)->eq('id', $column_id)->update(array($field => $field_value));
+ $this->updateColumn($column_id, array($field => $field_value));
}
}
@@ -109,6 +109,19 @@ class Board extends Base
}
/**
+ * Update a column
+ *
+ * @access public
+ * @param integer $column_id Column id
+ * @param array $values Form values
+ * @return boolean
+ */
+ public function updateColumn($column_id, array $values)
+ {
+ return $this->db->table(self::TABLE)->eq('id', $column_id)->update($values);
+ }
+
+ /**
* Move a column down, increment the column position value
*
* @access public
diff --git a/app/Model/Category.php b/app/Model/Category.php
index 58eba403..f86abe58 100644
--- a/app/Model/Category.php
+++ b/app/Model/Category.php
@@ -62,6 +62,21 @@ class Category extends Base
}
/**
+ * Return all categories for a given project
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @return array
+ */
+ public function getAll($project_id)
+ {
+ return $this->db->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->asc('name')
+ ->findAll();
+ }
+
+ /**
* Create a category
*
* @access public
diff --git a/app/Model/Project.php b/app/Model/Project.php
index e1465012..b5716a81 100644
--- a/app/Model/Project.php
+++ b/app/Model/Project.php
@@ -198,6 +198,18 @@ class Project extends Base
}
/**
+ * Get a project by the name
+ *
+ * @access public
+ * @param string $project_name Project name
+ * @return array
+ */
+ public function getByName($project_name)
+ {
+ return $this->db->table(self::TABLE)->eq('name', $project_name)->findOne();
+ }
+
+ /**
* Fetch project data by using the token
*
* @access public
@@ -505,7 +517,8 @@ class Project extends Base
new Validators\Integer('id', t('This value must be an integer')),
new Validators\Required('name', t('The project name is required')),
new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50),
- new Validators\Unique('name', t('This project must be unique'), $this->db->getConnection(), self::TABLE)
+ new Validators\Unique('name', t('This project must be unique'), $this->db->getConnection(), self::TABLE),
+ new Validators\Integer('is_active', t('This value must be an integer'))
));
return array(
diff --git a/app/Model/Task.php b/app/Model/Task.php
index 70f1404c..09e2f4e4 100644
--- a/app/Model/Task.php
+++ b/app/Model/Task.php
@@ -114,6 +114,23 @@ class Task extends Base
* @access public
* @param integer $project_id Project id
* @param array $status List of status id
+ * @return array
+ */
+ public function getAll($project_id, array $status = array(self::STATUS_OPEN, self::STATUS_CLOSED))
+ {
+ return $this->db
+ ->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->in('is_active', $status)
+ ->findAll();
+ }
+
+ /**
+ * Count all tasks for a given project and status
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param array $status List of status id
* @return integer
*/
public function countByProjectId($project_id, array $status = array(self::STATUS_OPEN, self::STATUS_CLOSED))
diff --git a/app/Model/User.php b/app/Model/User.php
index 6804d765..3240d547 100644
--- a/app/Model/User.php
+++ b/app/Model/User.php
@@ -135,7 +135,7 @@ class User extends Base
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values);
- if ($_SESSION['user']['id'] == $values['id']) {
+ if (session_id() !== '' && $_SESSION['user']['id'] == $values['id']) {
$this->updateSession();
}
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index 5c7e256f..ddb2acee 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -2,7 +2,12 @@
namespace Schema;
-const VERSION = 18;
+const VERSION = 19;
+
+function version_19($pdo)
+{
+ $pdo->exec("ALTER TABLE config ADD COLUMN api_token VARCHAR(255) DEFAULT '".\Core\Security::generateToken()."'");
+}
function version_18($pdo)
{
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index bfe81c13..438769f0 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -2,7 +2,12 @@
namespace Schema;
-const VERSION = 18;
+const VERSION = 19;
+
+function version_19($pdo)
+{
+ $pdo->exec("ALTER TABLE config ADD COLUMN api_token TEXT DEFAULT '".\Core\Security::generateToken()."'");
+}
function version_18($pdo)
{
diff --git a/app/Templates/config_index.php b/app/Templates/config_index.php
index 602e2070..b242d16f 100644
--- a/app/Templates/config_index.php
+++ b/app/Templates/config_index.php
@@ -46,6 +46,10 @@
<?= t('Webhooks token:') ?>
<strong><?= Helper\escape($values['webhooks_token']) ?></strong>
</li>
+ <li>
+ <?= t('API token:') ?>
+ <strong><?= Helper\escape($values['api_token']) ?></strong>
+ </li>
<?php if (DB_DRIVER === 'sqlite'): ?>
<li>
<?= t('Database size:') ?>
diff --git a/jsonrpc.php b/jsonrpc.php
new file mode 100644
index 00000000..981fefa2
--- /dev/null
+++ b/jsonrpc.php
@@ -0,0 +1,238 @@
+<?php
+
+require __DIR__.'/app/common.php';
+require __DIR__.'/vendor/JsonRPC/Server.php';
+
+use JsonRPC\Server;
+use Model\Project;
+use Model\Task;
+use Model\User;
+use Model\Config;
+use Model\Category;
+use Model\Comment;
+use Model\Subtask;
+use Model\Board;
+use Model\Action;
+
+$config = new Config($registry->shared('db'), $registry->shared('event'));
+$project = new Project($registry->shared('db'), $registry->shared('event'));
+$task = new Task($registry->shared('db'), $registry->shared('event'));
+$user = new User($registry->shared('db'), $registry->shared('event'));
+$category = new Category($registry->shared('db'), $registry->shared('event'));
+$comment = new Comment($registry->shared('db'), $registry->shared('event'));
+$subtask = new Subtask($registry->shared('db'), $registry->shared('event'));
+$board = new Board($registry->shared('db'), $registry->shared('event'));
+$action = new Action($registry->shared('db'), $registry->shared('event'));
+
+$action->attachEvents();
+$project->attachEvents();
+
+$server = new Server;
+$server->authentication(array('jsonrpc' => $config->get('api_token')));
+
+/**
+ * Project procedures
+ */
+$server->register('createProject', function($name) use ($project) {
+ $values = array('name' => $name);
+ list($valid,) = $project->validateCreation($values);
+ return $valid && $project->create($values);
+});
+
+$server->register('getProjectById', function($project_id) use ($project) {
+ return $project->getById($project_id);
+});
+
+$server->register('getProjectByName', function($name) use ($project) {
+ return $project->getByName($name);
+});
+
+$server->register('getAllProjects', function() use ($project) {
+ return $project->getAll();
+});
+
+$server->register('updateProject', function(array $values) use ($project) {
+ list($valid,) = $project->validateModification($values);
+ return $valid && $project->update($values);
+});
+
+$server->register('removeProject', function($project_id) use ($project) {
+ return $project->remove($project_id);
+});
+
+$server->register('getBoard', function($project_id) use ($board) {
+ return $board->get($project_id);
+});
+
+$server->register('getColumns', function($project_id) use ($board) {
+ return $board->getColumns($project_id);
+});
+
+$server->register('moveColumnUp', function($project_id, $column_id) use ($board) {
+ return $board->moveUp($project_id, $column_id);
+});
+
+$server->register('moveColumnDown', function($project_id, $column_id) use ($board) {
+ return $board->moveDown($project_id, $column_id);
+});
+
+$server->register('updateColumn', function($column_id, array $values) use ($board) {
+ return $board->updateColumn($column_id, $values);
+});
+
+$server->register('addColumn', function($project_id, array $values) use ($board) {
+ $values += array('project_id' => $project_id);
+ return $board->add($values);
+});
+
+$server->register('removeColumn', function($column_id) use ($board) {
+ return $board->removeColumn($column_id);
+});
+
+$server->register('getAllowedUsers', function($project_id) use ($project) {
+ return $project->getUsersList($project_id, false, false);
+});
+
+$server->register('revokeUser', function($project_id, $user_id) use ($project) {
+ return $project->revokeUser($project_id, $user_id);
+});
+
+$server->register('allowUser', function($project_id, $user_id) use ($project) {
+ return $project->allowUser($project_id, $user_id);
+});
+
+
+/**
+ * Task procedures
+ */
+$server->register('createTask', function(array $values) use ($task) {
+ list($valid,) = $task->validateCreation($values);
+ return $valid && $task->create($values) !== false;
+});
+
+$server->register('getTask', function($task_id) use ($task) {
+ return $task->getById($task_id);
+});
+
+$server->register('getAllTasks', function($project_id, array $status) use ($task) {
+ return $task->getAll($project_id, $status);
+});
+
+$server->register('updateTask', function($values) use ($task) {
+ list($valid,) = $task->validateModification($values);
+ return $valid && $task->update($values);
+});
+
+$server->register('removeTask', function($task_id) use ($task) {
+ return $task->remove($task_id);
+});
+
+
+/**
+ * User procedures
+ */
+$server->register('createUser', function(array $values) use ($user) {
+ list($valid,) = $user->validateCreation($values);
+ return $valid && $user->create($values);
+});
+
+$server->register('getUser', function($user_id) use ($user) {
+ return $user->getById($user_id);
+});
+
+$server->register('getAllUsers', function() use ($user) {
+ return $user->getAll();
+});
+
+$server->register('updateUser', function($values) use ($user) {
+ list($valid,) = $user->validateModification($values);
+ return $valid && $user->update($values);
+});
+
+$server->register('removeUser', function($user_id) use ($user) {
+ return $user->remove($user_id);
+});
+
+
+/**
+ * Category procedures
+ */
+$server->register('createCategory', function(array $values) use ($category) {
+ list($valid,) = $category->validateCreation($values);
+ return $valid && $category->create($values);
+});
+
+$server->register('getCategory', function($category_id) use ($category) {
+ return $category->getById($category_id);
+});
+
+$server->register('getAllCategories', function($project_id) use ($category) {
+ return $category->getAll($project_id);
+});
+
+$server->register('updateCategory', function($values) use ($category) {
+ list($valid,) = $category->validateModification($values);
+ return $valid && $category->update($values);
+});
+
+$server->register('removeCategory', function($category_id) use ($category) {
+ return $category->remove($category_id);
+});
+
+
+/**
+ * Comments procedures
+ */
+$server->register('createComment', function(array $values) use ($comment) {
+ list($valid,) = $comment->validateCreation($values);
+ return $valid && $comment->create($values);
+});
+
+$server->register('getComment', function($comment_id) use ($comment) {
+ return $comment->getById($comment_id);
+});
+
+$server->register('getAllComments', function($task_id) use ($comment) {
+ return $comment->getAll($task_id);
+});
+
+$server->register('updateComment', function($values) use ($comment) {
+ list($valid,) = $comment->validateModification($values);
+ return $valid && $comment->update($values);
+});
+
+$server->register('removeComment', function($comment_id) use ($comment) {
+ return $comment->remove($comment_id);
+});
+
+
+/**
+ * Subtask procedures
+ */
+$server->register('createSubtask', function(array $values) use ($subtask) {
+ list($valid,) = $subtask->validate($values);
+ return $valid && $subtask->create($values);
+});
+
+$server->register('getSubtask', function($subtask_id) use ($subtask) {
+ return $subtask->getById($subtask_id);
+});
+
+$server->register('getAllSubtasks', function($task_id) use ($subtask) {
+ return $subtask->getAll($task_id);
+});
+
+$server->register('updateSubtask', function($values) use ($subtask) {
+ list($valid,) = $subtask->validate($values);
+ return $valid && $subtask->update($values);
+});
+
+$server->register('removeSubtask', function($subtask_id) use ($subtask) {
+ return $subtask->remove($subtask_id);
+});
+
+
+/**
+ * Parse incoming requests
+ */
+echo $server->execute();
diff --git a/phpunit.xml b/phpunit.xml
index 0a1e5c45..5c4ce58c 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,7 +1,7 @@
<phpunit>
<testsuites>
<testsuite name="Kanboard">
- <directory>tests</directory>
+ <directory>tests/units</directory>
</testsuite>
</testsuites>
</phpunit> \ No newline at end of file
diff --git a/scripts/api-client.php b/scripts/api-client.php
new file mode 100755
index 00000000..0fb45117
--- /dev/null
+++ b/scripts/api-client.php
@@ -0,0 +1,20 @@
+#!/usr/bin/env php
+<?php
+
+require 'vendor/JsonRPC/Client.php';
+
+if ($argc !== 3) {
+ die('Usage: '.$argv[0].' <url> <token>'.PHP_EOL);
+}
+
+$client = new JsonRPC\Client($argv[1], 5, true);
+$client->authentication('jsonrpc', $argv[2]);
+
+
+$client->createProject('Test API');
+
+
+$r = $client->getAllProjects();
+
+var_dump($r);
+
diff --git a/tests/Base.php b/tests/Base.php
deleted file mode 100644
index d4065982..00000000
--- a/tests/Base.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-if (version_compare(PHP_VERSION, '5.5.0', '<')) {
- require __DIR__.'/../vendor/password.php';
-}
-
-require_once __DIR__.'/../app/Core/Security.php';
-
-require_once __DIR__.'/../vendor/PicoDb/Database.php';
-require_once __DIR__.'/../app/Schema/Sqlite.php';
-
-require_once __DIR__.'/../app/Core/Listener.php';
-require_once __DIR__.'/../app/Core/Event.php';
-require_once __DIR__.'/../app/Core/Translator.php';
-require_once __DIR__.'/../app/translator.php';
-
-require_once __DIR__.'/../app/Model/Base.php';
-require_once __DIR__.'/../app/Model/Task.php';
-require_once __DIR__.'/../app/Model/Acl.php';
-require_once __DIR__.'/../app/Model/Comment.php';
-require_once __DIR__.'/../app/Model/Project.php';
-require_once __DIR__.'/../app/Model/User.php';
-require_once __DIR__.'/../app/Model/Board.php';
-require_once __DIR__.'/../app/Model/Action.php';
-require_once __DIR__.'/../app/Model/Category.php';
-
-require_once __DIR__.'/../app/Action/Base.php';
-require_once __DIR__.'/../app/Action/TaskClose.php';
-require_once __DIR__.'/../app/Action/TaskAssignSpecificUser.php';
-require_once __DIR__.'/../app/Action/TaskAssignColorUser.php';
-require_once __DIR__.'/../app/Action/TaskAssignColorCategory.php';
-require_once __DIR__.'/../app/Action/TaskAssignCurrentUser.php';
-require_once __DIR__.'/../app/Action/TaskDuplicateAnotherProject.php';
-
-abstract class Base extends PHPUnit_Framework_TestCase
-{
- public function setUp()
- {
- $this->db = $this->getDbConnection();
- $this->event = new \Core\Event;
- }
-
- public function getDbConnection()
- {
- $db = new \PicoDb\Database(array(
- 'driver' => 'sqlite',
- 'filename' => ':memory:'
- ));
-
- if ($db->schema()->check(\Schema\VERSION)) {
- return $db;
- }
- else {
- die('Unable to migrate database schema!');
- }
- }
-}
diff --git a/tests/functionals/ApiTest.php b/tests/functionals/ApiTest.php
new file mode 100644
index 00000000..89d525a2
--- /dev/null
+++ b/tests/functionals/ApiTest.php
@@ -0,0 +1,417 @@
+<?php
+
+require_once __DIR__.'/../../vendor/JsonRPC/Client.php';
+
+class Api extends PHPUnit_Framework_TestCase
+{
+ const URL = 'http://localhost:8000/jsonrpc.php';
+ const KEY = '19ffd9709d03ce50675c3a43d1c49c1ac207f4bc45f06c5b2701fbdf8929';
+
+ private $client;
+
+ public function setUp()
+ {
+ $this->client = new JsonRPC\Client(self::URL, 5, true);
+ $this->client->authentication('jsonrpc', self::KEY);
+
+ $pdo = new PDO('sqlite:data/db.sqlite');
+ $pdo->exec('UPDATE config SET api_token="'.self::KEY.'"');
+ }
+
+ public function testRemoveAll()
+ {
+ $projects = $this->client->getAllProjects();
+
+ if ($projects) {
+ foreach ($projects as $project) {
+ $this->client->removeProject($project['id']);
+ }
+ }
+ }
+
+ public function testCreateProject()
+ {
+ $this->assertTrue($this->client->createProject('API test'));
+ }
+
+ public function testGetProjectById()
+ {
+ $project = $this->client->getProjectById(1);
+ $this->assertNotEmpty($project);
+ $this->assertEquals(1, $project['id']);
+ }
+
+ public function testUpdateProject()
+ {
+ $project = $this->client->getProjectById(1);
+ $this->assertNotEmpty($project);
+ $this->assertTrue($this->client->updateProject(array('id' => 1, 'name' => 'API test 2', 'is_active' => 0)));
+
+ $project = $this->client->getProjectById(1);
+ $this->assertEquals('API test 2', $project['name']);
+ $this->assertEquals(0, $project['is_active']);
+
+ $this->assertTrue($this->client->updateProject(array('id' => 1, 'name' => 'API test', 'is_active' => 1)));
+
+ $project = $this->client->getProjectById(1);
+ $this->assertEquals('API test', $project['name']);
+ $this->assertEquals(1, $project['is_active']);
+ }
+
+ public function testGetBoard()
+ {
+ $board = $this->client->getBoard(1);
+ $this->assertTrue(is_array($board));
+ $this->assertEquals(4, count($board));
+ }
+
+ public function testGetColumns()
+ {
+ $columns = $this->client->getColumns(1);
+ $this->assertTrue(is_array($columns));
+ $this->assertEquals(4, count($columns));
+ $this->assertEquals('Done', $columns[3]['title']);
+ }
+
+ public function testMoveColumnUp()
+ {
+ $this->assertTrue($this->client->moveColumnUp(1, 4));
+
+ $columns = $this->client->getColumns(1);
+ $this->assertTrue(is_array($columns));
+ $this->assertEquals('Done', $columns[2]['title']);
+ $this->assertEquals('Work in progress', $columns[3]['title']);
+ }
+
+ public function testMoveColumnDown()
+ {
+ $this->assertTrue($this->client->moveColumnDown(1, 4));
+
+ $columns = $this->client->getColumns(1);
+ $this->assertTrue(is_array($columns));
+ $this->assertEquals('Work in progress', $columns[2]['title']);
+ $this->assertEquals('Done', $columns[3]['title']);
+ }
+
+ public function testUpdateColumn()
+ {
+ $this->assertTrue($this->client->updateColumn(4, array('title' => 'Boo', 'task_limit' => 2)));
+
+ $columns = $this->client->getColumns(1);
+ $this->assertTrue(is_array($columns));
+ $this->assertEquals('Boo', $columns[3]['title']);
+ $this->assertEquals(2, $columns[3]['task_limit']);
+ }
+
+ public function testAddColumn()
+ {
+ $this->assertTrue($this->client->addColumn(1, array('title' => 'New column')));
+
+ $columns = $this->client->getColumns(1);
+ $this->assertTrue(is_array($columns));
+ $this->assertEquals(5, count($columns));
+ $this->assertEquals('New column', $columns[4]['title']);
+ }
+
+ public function testRemoveColumn()
+ {
+ $this->assertTrue($this->client->removeColumn(5));
+
+ $columns = $this->client->getColumns(1);
+ $this->assertTrue(is_array($columns));
+ $this->assertEquals(4, count($columns));
+ }
+
+ public function testCreateTask()
+ {
+ $task = array(
+ 'title' => 'Task #1',
+ 'color_id' => 'blue',
+ 'owner_id' => 1,
+ 'project_id' => 1,
+ 'column_id' => 2,
+ );
+
+ $this->assertTrue($this->client->createTask($task));
+
+ $task = array(
+ 'title' => 'Task #1',
+ 'color_id' => 'blue',
+ 'owner_id' => 1,
+ );
+
+ $this->assertFalse($this->client->createTask($task));
+ }
+
+ public function testGetTask()
+ {
+ $task = $this->client->getTask(1);
+
+ $this->assertNotFalse($task);
+ $this->assertTrue(is_array($task));
+ $this->assertEquals('Task #1', $task['title']);
+ }
+
+ public function testGetAllTasks()
+ {
+ $tasks = $this->client->getAllTasks(1, array(1));
+
+ $this->assertNotFalse($tasks);
+ $this->assertTrue(is_array($tasks));
+ $this->assertEquals('Task #1', $tasks[0]['title']);
+
+ $tasks = $this->client->getAllTasks(2, array(1, 2));
+
+ $this->assertNotFalse($tasks);
+ $this->assertTrue(is_array($tasks));
+ $this->assertEmpty($tasks);
+ }
+
+ public function testUpdateTask()
+ {
+ $task = $this->client->getTask(1);
+ $task['color_id'] = 'green';
+ $task['column_id'] = 1;
+ $task['description'] = 'test';
+
+ $this->assertTrue($this->client->updateTask($task));
+ }
+
+ public function testRemoveTask()
+ {
+ $this->assertTrue($this->client->removeTask(1));
+ }
+
+ public function testRemoveUsers()
+ {
+ $users = $this->client->getAllUsers();
+ $this->assertNotFalse($users);
+ $this->assertNotEmpty($users);
+
+ foreach ($users as $user) {
+ if ($user['id'] > 1) {
+ $this->assertTrue($this->client->removeUser($user['id']));
+ }
+ }
+ }
+
+ public function testCreateUser()
+ {
+ $user = array(
+ 'username' => 'toto',
+ 'name' => 'Toto',
+ 'password' => '123456',
+ 'confirmation' => '123456',
+ );
+
+ $this->assertTrue($this->client->createUser($user));
+
+ $user = array(
+ 'username' => 'titi',
+ 'name' => 'Titi',
+ 'password' => '123456',
+ 'confirmation' => '789',
+ );
+
+ $this->assertFalse($this->client->createUser($user));
+ }
+
+ public function testGetUser()
+ {
+ $user = $this->client->getUser(2);
+
+ $this->assertNotFalse($user);
+ $this->assertTrue(is_array($user));
+ $this->assertEquals('toto', $user['username']);
+ }
+
+ public function testUpdateUser()
+ {
+ $user = $this->client->getUser(2);
+ $user['username'] = 'titi';
+ $user['name'] = 'Titi';
+ unset($user['password']);
+
+ $this->assertTrue($this->client->updateUser($user));
+
+ $user = $this->client->getUser(2);
+
+ $this->assertNotFalse($user);
+ $this->assertTrue(is_array($user));
+ $this->assertEquals('titi', $user['username']);
+ $this->assertEquals('Titi', $user['name']);
+ }
+
+ public function testGetAllowedUsers()
+ {
+ $users = $this->client->getAllowedUsers(1);
+ $this->assertNotFalse($users);
+ $this->assertEquals(array(1 => 'admin', 2 => 'titi'), $users);
+ }
+
+ public function testAllowedUser()
+ {
+ $this->assertTrue($this->client->allowUser(1, 2));
+
+ $users = $this->client->getAllowedUsers(1);
+ $this->assertNotFalse($users);
+ $this->assertEquals(array(2 => 'titi'), $users);
+ }
+
+ public function testRevokeUser()
+ {
+ $this->assertTrue($this->client->revokeUser(1, 2));
+
+ $users = $this->client->getAllowedUsers(1);
+ $this->assertNotFalse($users);
+ $this->assertEquals(array(1 => 'admin', 2 => 'titi'), $users);
+ }
+
+ public function testCreateComment()
+ {
+ $task = array(
+ 'title' => 'Task with comment',
+ 'color_id' => 'red',
+ 'owner_id' => 1,
+ 'project_id' => 1,
+ 'column_id' => 1,
+ );
+
+ $this->assertTrue($this->client->createTask($task));
+
+ $comment = array(
+ 'task_id' => 1,
+ 'user_id' => 2,
+ 'comment' => 'boo',
+ );
+
+ $this->assertTrue($this->client->createComment($comment));
+ }
+
+ public function testGetComment()
+ {
+ $comment = $this->client->getComment(1);
+ $this->assertNotFalse($comment);
+ $this->assertNotEmpty($comment);
+ $this->assertEquals(1, $comment['task_id']);
+ $this->assertEquals(2, $comment['user_id']);
+ $this->assertEquals('boo', $comment['comment']);
+ }
+
+ public function testUpdateComment()
+ {
+ $comment = $this->client->getComment(1);
+ $comment['comment'] = 'test';
+
+ $this->assertTrue($this->client->updateComment($comment));
+
+ $comment = $this->client->getComment(1);
+ $this->assertEquals('test', $comment['comment']);
+ }
+
+ public function testGetAllComments()
+ {
+ $comment = array(
+ 'task_id' => 1,
+ 'user_id' => 1,
+ 'comment' => 'blabla',
+ );
+
+ $this->assertTrue($this->client->createComment($comment));
+
+ $comments = $this->client->getAllComments(1);
+ $this->assertNotFalse($comments);
+ $this->assertNotEmpty($comments);
+ $this->assertTrue(is_array($comments));
+ $this->assertEquals(2, count($comments));
+ }
+
+ public function testRemoveComment()
+ {
+ $this->assertTrue($this->client->removeComment(1));
+
+ $comments = $this->client->getAllComments(1);
+ $this->assertNotFalse($comments);
+ $this->assertNotEmpty($comments);
+ $this->assertTrue(is_array($comments));
+ $this->assertEquals(1, count($comments));
+ }
+
+ public function testCreateSubtask()
+ {
+ $subtask = array(
+ 'task_id' => 1,
+ 'title' => 'subtask #1',
+ );
+
+ $this->assertTrue($this->client->createSubtask($subtask));
+ }
+
+ public function testGetSubtask()
+ {
+ $subtask = $this->client->getSubtask(1);
+ $this->assertNotFalse($subtask);
+ $this->assertNotEmpty($subtask);
+ $this->assertEquals(1, $subtask['task_id']);
+ $this->assertEquals(0, $subtask['user_id']);
+ $this->assertEquals('subtask #1', $subtask['title']);
+ }
+
+ public function testUpdateSubtask()
+ {
+ $subtask = $this->client->getSubtask(1);
+ $subtask['title'] = 'test';
+
+ $this->assertTrue($this->client->updateSubtask($subtask));
+
+ $subtask = $this->client->getSubtask(1);
+ $this->assertEquals('test', $subtask['title']);
+ }
+
+ public function testGetAllSubtasks()
+ {
+ $subtask = array(
+ 'task_id' => 1,
+ 'user_id' => 2,
+ 'title' => 'Subtask #2',
+ );
+
+ $this->assertTrue($this->client->createSubtask($subtask));
+
+ $subtasks = $this->client->getAllSubtasks(1);
+ $this->assertNotFalse($subtasks);
+ $this->assertNotEmpty($subtasks);
+ $this->assertTrue(is_array($subtasks));
+ $this->assertEquals(2, count($subtasks));
+ }
+
+ public function testRemoveSubtask()
+ {
+ $this->assertTrue($this->client->removeSubtask(1));
+
+ $subtasks = $this->client->getAllSubtasks(1);
+ $this->assertNotFalse($subtasks);
+ $this->assertNotEmpty($subtasks);
+ $this->assertTrue(is_array($subtasks));
+ $this->assertEquals(1, count($subtasks));
+ }
+/*
+ public function testAutomaticActions()
+ {
+ $task = array(
+ 'title' => 'Task #1',
+ 'color_id' => 'blue',
+ 'owner_id' => 0,
+ 'project_id' => 1,
+ 'column_id' => 1,
+ );
+
+ $this->assertTrue($this->client->createTask($task));
+
+ $tasks = $this->client->getAllTasks(1, array(1));
+ $task = $tasks[count($tasks) - 1];
+ $task['column_id'] = 3;
+
+ $this->assertTrue($this->client->updateTask($task));
+ }*/
+}
diff --git a/tests/AclTest.php b/tests/units/AclTest.php
index a2c1c111..a2c1c111 100644
--- a/tests/AclTest.php
+++ b/tests/units/AclTest.php
diff --git a/tests/ActionTaskAssignColorCategoryTest.php b/tests/units/ActionTaskAssignColorCategoryTest.php
index 18b4311e..18b4311e 100644
--- a/tests/ActionTaskAssignColorCategoryTest.php
+++ b/tests/units/ActionTaskAssignColorCategoryTest.php
diff --git a/tests/ActionTaskAssignColorUserTest.php b/tests/units/ActionTaskAssignColorUserTest.php
index 04e3172f..04e3172f 100644
--- a/tests/ActionTaskAssignColorUserTest.php
+++ b/tests/units/ActionTaskAssignColorUserTest.php
diff --git a/tests/ActionTaskAssignCurrentUserTest.php b/tests/units/ActionTaskAssignCurrentUserTest.php
index 0780b603..0780b603 100644
--- a/tests/ActionTaskAssignCurrentUserTest.php
+++ b/tests/units/ActionTaskAssignCurrentUserTest.php
diff --git a/tests/ActionTaskAssignSpecificUserTest.php b/tests/units/ActionTaskAssignSpecificUserTest.php
index 3a919978..3a919978 100644
--- a/tests/ActionTaskAssignSpecificUserTest.php
+++ b/tests/units/ActionTaskAssignSpecificUserTest.php
diff --git a/tests/ActionTaskCloseTest.php b/tests/units/ActionTaskCloseTest.php
index 0c864f42..0c864f42 100644
--- a/tests/ActionTaskCloseTest.php
+++ b/tests/units/ActionTaskCloseTest.php
diff --git a/tests/ActionTaskDuplicateAnotherProjectTest.php b/tests/units/ActionTaskDuplicateAnotherProjectTest.php
index 0b2e4bd5..0b2e4bd5 100644
--- a/tests/ActionTaskDuplicateAnotherProjectTest.php
+++ b/tests/units/ActionTaskDuplicateAnotherProjectTest.php
diff --git a/tests/ActionTest.php b/tests/units/ActionTest.php
index 2eb12784..2eb12784 100644
--- a/tests/ActionTest.php
+++ b/tests/units/ActionTest.php
diff --git a/tests/units/Base.php b/tests/units/Base.php
new file mode 100644
index 00000000..1f8109ed
--- /dev/null
+++ b/tests/units/Base.php
@@ -0,0 +1,57 @@
+<?php
+
+if (version_compare(PHP_VERSION, '5.5.0', '<')) {
+ require __DIR__.'/../../vendor/password.php';
+}
+
+require_once __DIR__.'/../../app/Core/Security.php';
+
+require_once __DIR__.'/../../vendor/PicoDb/Database.php';
+require_once __DIR__.'/../../app/Schema/Sqlite.php';
+
+require_once __DIR__.'/../../app/Core/Listener.php';
+require_once __DIR__.'/../../app/Core/Event.php';
+require_once __DIR__.'/../../app/Core/Translator.php';
+require_once __DIR__.'/../../app/translator.php';
+
+require_once __DIR__.'/../../app/Model/Base.php';
+require_once __DIR__.'/../../app/Model/Task.php';
+require_once __DIR__.'/../../app/Model/Acl.php';
+require_once __DIR__.'/../../app/Model/Comment.php';
+require_once __DIR__.'/../../app/Model/Project.php';
+require_once __DIR__.'/../../app/Model/User.php';
+require_once __DIR__.'/../../app/Model/Board.php';
+require_once __DIR__.'/../../app/Model/Action.php';
+require_once __DIR__.'/../../app/Model/Category.php';
+
+require_once __DIR__.'/../../app/Action/Base.php';
+require_once __DIR__.'/../../app/Action/TaskClose.php';
+require_once __DIR__.'/../../app/Action/TaskAssignSpecificUser.php';
+require_once __DIR__.'/../../app/Action/TaskAssignColorUser.php';
+require_once __DIR__.'/../../app/Action/TaskAssignColorCategory.php';
+require_once __DIR__.'/../../app/Action/TaskAssignCurrentUser.php';
+require_once __DIR__.'/../../app/Action/TaskDuplicateAnotherProject.php';
+
+abstract class Base extends PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ $this->db = $this->getDbConnection();
+ $this->event = new \Core\Event;
+ }
+
+ public function getDbConnection()
+ {
+ $db = new \PicoDb\Database(array(
+ 'driver' => 'sqlite',
+ 'filename' => ':memory:'
+ ));
+
+ if ($db->schema()->check(\Schema\VERSION)) {
+ return $db;
+ }
+ else {
+ die('Unable to migrate database schema!');
+ }
+ }
+}
diff --git a/tests/BoardTest.php b/tests/units/BoardTest.php
index d5686b3f..d5686b3f 100644
--- a/tests/BoardTest.php
+++ b/tests/units/BoardTest.php
diff --git a/tests/CommentTest.php b/tests/units/CommentTest.php
index 46f05abc..46f05abc 100644
--- a/tests/CommentTest.php
+++ b/tests/units/CommentTest.php
diff --git a/tests/ProjectTest.php b/tests/units/ProjectTest.php
index 5ca8177c..5ca8177c 100644
--- a/tests/ProjectTest.php
+++ b/tests/units/ProjectTest.php
diff --git a/tests/TaskTest.php b/tests/units/TaskTest.php
index da7e6a70..da7e6a70 100644
--- a/tests/TaskTest.php
+++ b/tests/units/TaskTest.php
diff --git a/vendor/JsonRPC/Client.php b/vendor/JsonRPC/Client.php
new file mode 100644
index 00000000..bbdb7200
--- /dev/null
+++ b/vendor/JsonRPC/Client.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace JsonRPC;
+
+/**
+ * JsonRPC client class
+ *
+ * @package JsonRPC
+ * @author Frderic Guillot
+ * @license Unlicense http://unlicense.org/
+ */
+class Client
+{
+ /**
+ * URL of the server
+ *
+ * @access private
+ * @var string
+ */
+ private $url;
+
+ /**
+ * HTTP client timeout
+ *
+ * @access private
+ * @var integer
+ */
+ private $timeout;
+
+ /**
+ * Debug flag
+ *
+ * @access private
+ * @var bool
+ */
+ private $debug;
+
+ /**
+ * Username for authentication
+ *
+ * @access private
+ * @var string
+ */
+ private $username;
+
+ /**
+ * Password for authentication
+ *
+ * @access private
+ * @var string
+ */
+ private $password;
+
+ /**
+ * Default HTTP headers to send to the server
+ *
+ * @access private
+ * @var array
+ */
+ private $headers = array(
+ 'Connection: close',
+ 'Content-Type: application/json',
+ 'Accept: application/json'
+ );
+
+ /**
+ * Constructor
+ *
+ * @access public
+ * @param string $url Server URL
+ * @param integer $timeout Server URL
+ * @param bool $debug Debug flag
+ * @param array $headers Custom HTTP headers
+ */
+ public function __construct($url, $timeout = 5, $debug = false, $headers = array())
+ {
+ $this->url = $url;
+ $this->timeout = $timeout;
+ $this->debug = $debug;
+ $this->headers = array_merge($this->headers, $headers);
+ }
+
+ /**
+ * Automatic mapping of procedures
+ *
+ * @access public
+ * @param string $method Procedure name
+ * @param array $params Procedure arguments
+ * @return mixed
+ */
+ public function __call($method, $params)
+ {
+ return $this->execute($method, $params);
+ }
+
+ /**
+ * Set authentication parameters
+ *
+ * @access public
+ * @param string $username Username
+ * @param string $password Password
+ */
+ public function authentication($username, $password)
+ {
+ $this->username = $username;
+ $this->password = $password;
+ }
+
+ /**
+ * Execute
+ *
+ * @access public
+ * @param string $procedure Procedure name
+ * @param array $params Procedure arguments
+ * @return mixed
+ */
+ public function execute($procedure, array $params = array())
+ {
+ $id = mt_rand();
+
+ $payload = array(
+ 'jsonrpc' => '2.0',
+ 'method' => $procedure,
+ 'id' => $id
+ );
+
+ if (! empty($params)) {
+ $payload['params'] = $params;
+ }
+
+ $result = $this->doRequest($payload);
+
+ if (isset($result['id']) && $result['id'] == $id && array_key_exists('result', $result)) {
+ return $result['result'];
+ }
+ else if ($this->debug && isset($result['error'])) {
+ print_r($result['error']);
+ }
+
+ return null;
+ }
+
+ /**
+ * Do the HTTP request
+ *
+ * @access public
+ * @param string $payload Data to send
+ */
+ public function doRequest($payload)
+ {
+ $ch = curl_init();
+
+ curl_setopt($ch, CURLOPT_URL, $this->url);
+ curl_setopt($ch, CURLOPT_HEADER, false);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout);
+ curl_setopt($ch, CURLOPT_USERAGENT, 'JSON-RPC PHP Client');
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
+
+ if ($this->username && $this->password) {
+ curl_setopt($ch, CURLOPT_USERPWD, $this->username.':'.$this->password);
+ }
+
+ $result = curl_exec($ch);
+ $response = json_decode($result, true);
+
+ curl_close($ch);
+
+ return is_array($response) ? $response : array();
+ }
+}
diff --git a/vendor/JsonRPC/Server.php b/vendor/JsonRPC/Server.php
new file mode 100644
index 00000000..93d46cdb
--- /dev/null
+++ b/vendor/JsonRPC/Server.php
@@ -0,0 +1,301 @@
+<?php
+
+namespace JsonRPC;
+
+use ReflectionFunction;
+use Closure;
+
+/**
+ * JsonRPC server class
+ *
+ * @package JsonRPC
+ * @author Frderic Guillot
+ * @license Unlicense http://unlicense.org/
+ */
+class Server
+{
+ /**
+ * Data received from the client
+ *
+ * @access private
+ * @var string
+ */
+ private $payload;
+
+ /**
+ * List of procedures
+ *
+ * @static
+ * @access private
+ * @var array
+ */
+ static private $procedures = array();
+
+ /**
+ * Constructor
+ *
+ * @access public
+ * @param string $payload Client data
+ */
+ public function __construct($payload = '')
+ {
+ $this->payload = $payload;
+ }
+
+ /**
+ * IP based client restrictions
+ *
+ * Return an HTTP error 403 if the client is not allowed
+ *
+ * @access public
+ * @param array $hosts List of hosts
+ */
+ public function allowHosts(array $hosts) {
+
+ if (! in_array($_SERVER['REMOTE_ADDR'], $hosts)) {
+
+ header('Content-Type: application/json');
+ header('HTTP/1.0 403 Forbidden');
+ echo '["Access Forbidden"]';
+ exit;
+ }
+ }
+
+ /**
+ * HTTP Basic authentication
+ *
+ * Return an HTTP error 401 if the client is not allowed
+ *
+ * @access public
+ * @param array $users Map of username/password
+ */
+ public function authentication(array $users)
+ {
+ // OVH workaround
+ if (isset($_SERVER['REMOTE_USER'])) {
+ list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':', base64_decode(substr($_SERVER['REMOTE_USER'], 6)));
+ }
+
+ if (! isset($_SERVER['PHP_AUTH_USER']) ||
+ ! isset($users[$_SERVER['PHP_AUTH_USER']]) ||
+ $users[$_SERVER['PHP_AUTH_USER']] !== $_SERVER['PHP_AUTH_PW']) {
+
+ header('WWW-Authenticate: Basic realm="JsonRPC"');
+ header('Content-Type: application/json');
+ header('HTTP/1.0 401 Unauthorized');
+ echo '["Authentication failed"]';
+ exit;
+ }
+ }
+
+ /**
+ * Register a new procedure
+ *
+ * @access public
+ * @param string $name Procedure name
+ * @param closure $callback Callback
+ */
+ public function register($name, Closure $callback)
+ {
+ self::$procedures[$name] = $callback;
+ }
+
+ /**
+ * Unregister a procedure
+ *
+ * @access public
+ * @param string $name Procedure name
+ */
+ public function unregister($name)
+ {
+ if (isset(self::$procedures[$name])) {
+ unset(self::$procedures[$name]);
+ }
+ }
+
+ /**
+ * Unregister all procedures
+ *
+ * @access public
+ */
+ public function unregisterAll()
+ {
+ self::$procedures = array();
+ }
+
+ /**
+ * Return the response to the client
+ *
+ * @access public
+ * @param array $data Data to send to the client
+ * @param array $payload Incoming data
+ * @return string
+ */
+ public function getResponse(array $data, array $payload = array())
+ {
+ if (! array_key_exists('id', $payload)) {
+ return '';
+ }
+
+ $response = array(
+ 'jsonrpc' => '2.0',
+ 'id' => $payload['id']
+ );
+
+ $response = array_merge($response, $data);
+
+ @header('Content-Type: application/json');
+ return json_encode($response);
+ }
+
+ /**
+ * Map arguments to the procedure
+ *
+ * @access public
+ * @param array $request_params Incoming arguments
+ * @param array $method_params Procedure arguments
+ * @param array $params Arguments to pass to the callback
+ * @return bool
+ */
+ public function mapParameters(array $request_params, array $method_params, array &$params)
+ {
+ // Positional parameters
+ if (array_keys($request_params) === range(0, count($request_params) - 1)) {
+
+ if (count($request_params) !== count($method_params)) return false;
+ $params = $request_params;
+
+ return true;
+ }
+
+ // Named parameters
+ foreach ($method_params as $p) {
+
+ $name = $p->getName();
+
+ if (isset($request_params[$name])) {
+ $params[$name] = $request_params[$name];
+ }
+ else if ($p->isDefaultValueAvailable()) {
+ continue;
+ }
+ else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Parse incoming requests
+ *
+ * @access public
+ * @return string
+ */
+ public function execute()
+ {
+ // Parse payload
+ if (empty($this->payload)) {
+ $this->payload = file_get_contents('php://input');
+ }
+
+ if (is_string($this->payload)) {
+ $this->payload = json_decode($this->payload, true);
+ }
+
+ // Check JSON format
+ if (! is_array($this->payload)) {
+
+ return $this->getResponse(array(
+ 'error' => array(
+ 'code' => -32700,
+ 'message' => 'Parse error'
+ )),
+ array('id' => null)
+ );
+ }
+
+ // Handle batch request
+ if (array_keys($this->payload) === range(0, count($this->payload) - 1)) {
+
+ $responses = array();
+
+ foreach ($this->payload as $payload) {
+
+ if (! is_array($payload)) {
+
+ $responses[] = $this->getResponse(array(
+ 'error' => array(
+ 'code' => -32600,
+ 'message' => 'Invalid Request'
+ )),
+ array('id' => null)
+ );
+ }
+ else {
+
+ $server = new Server($payload);
+ $response = $server->execute();
+
+ if ($response) $responses[] = $response;
+ }
+ }
+
+ return empty($responses) ? '' : '['.implode(',', $responses).']';
+ }
+
+ // Check JSON-RPC format
+ if (! isset($this->payload['jsonrpc']) ||
+ ! isset($this->payload['method']) ||
+ ! is_string($this->payload['method']) ||
+ $this->payload['jsonrpc'] !== '2.0' ||
+ (isset($this->payload['params']) && ! is_array($this->payload['params']))) {
+
+ return $this->getResponse(array(
+ 'error' => array(
+ 'code' => -32600,
+ 'message' => 'Invalid Request'
+ )),
+ array('id' => null)
+ );
+ }
+
+ // Procedure not found
+ if (! isset(self::$procedures[$this->payload['method']])) {
+
+ return $this->getResponse(array(
+ 'error' => array(
+ 'code' => -32601,
+ 'message' => 'Method not found'
+ )),
+ $this->payload
+ );
+ }
+
+ $callback = self::$procedures[$this->payload['method']];
+ $params = array();
+
+ $reflection = new ReflectionFunction($callback);
+
+ if (isset($this->payload['params'])) {
+
+ $parameters = $reflection->getParameters();
+
+ if (! $this->mapParameters($this->payload['params'], $parameters, $params)) {
+
+ return $this->getResponse(array(
+ 'error' => array(
+ 'code' => -32602,
+ 'message' => 'Invalid params'
+ )),
+ $this->payload
+ );
+ }
+ }
+
+ $result = $reflection->invokeArgs($params);
+
+ return $this->getResponse(array('result' => $result), $this->payload);
+ }
+}