diff --git a/app/Model/Board.php b/app/Model/Board.php index 59a98923a..09fc5b502 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)); } } @@ -108,6 +108,19 @@ class Board extends Base return true; } + /** + * 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 * diff --git a/app/Model/Category.php b/app/Model/Category.php index 58eba403c..f86abe58a 100644 --- a/app/Model/Category.php +++ b/app/Model/Category.php @@ -61,6 +61,21 @@ class Category extends Base return $prepend + $listing; } + /** + * 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 * diff --git a/app/Model/Project.php b/app/Model/Project.php index e14650121..b5716a81d 100644 --- a/app/Model/Project.php +++ b/app/Model/Project.php @@ -197,6 +197,18 @@ class Project extends Base return $this->db->table(self::TABLE)->eq('id', $project_id)->findOne(); } + /** + * 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 * @@ -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 70f1404c0..09e2f4e49 100644 --- a/app/Model/Task.php +++ b/app/Model/Task.php @@ -108,6 +108,23 @@ class Task extends Base } } + /** + * 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 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 * diff --git a/app/Model/User.php b/app/Model/User.php index 6804d7653..3240d5475 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 5c7e256fd..ddb2acee8 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 bfe81c136..438769f08 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 602e2070e..b242d16ff 100644 --- a/app/Templates/config_index.php +++ b/app/Templates/config_index.php @@ -46,6 +46,10 @@ +
  • + + +
  • diff --git a/jsonrpc.php b/jsonrpc.php new file mode 100644 index 000000000..981fefa27 --- /dev/null +++ b/jsonrpc.php @@ -0,0 +1,238 @@ +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 0a1e5c45c..5c4ce58cf 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,7 +1,7 @@ - tests + tests/units \ No newline at end of file diff --git a/scripts/api-client.php b/scripts/api-client.php new file mode 100755 index 000000000..0fb451173 --- /dev/null +++ b/scripts/api-client.php @@ -0,0 +1,20 @@ +#!/usr/bin/env php + '.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 d40659826..000000000 --- a/tests/Base.php +++ /dev/null @@ -1,57 +0,0 @@ -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 000000000..89d525a2f --- /dev/null +++ b/tests/functionals/ApiTest.php @@ -0,0 +1,417 @@ +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 similarity index 100% rename from tests/AclTest.php rename to tests/units/AclTest.php diff --git a/tests/ActionTaskAssignColorCategoryTest.php b/tests/units/ActionTaskAssignColorCategoryTest.php similarity index 100% rename from tests/ActionTaskAssignColorCategoryTest.php rename to tests/units/ActionTaskAssignColorCategoryTest.php diff --git a/tests/ActionTaskAssignColorUserTest.php b/tests/units/ActionTaskAssignColorUserTest.php similarity index 100% rename from tests/ActionTaskAssignColorUserTest.php rename to tests/units/ActionTaskAssignColorUserTest.php diff --git a/tests/ActionTaskAssignCurrentUserTest.php b/tests/units/ActionTaskAssignCurrentUserTest.php similarity index 100% rename from tests/ActionTaskAssignCurrentUserTest.php rename to tests/units/ActionTaskAssignCurrentUserTest.php diff --git a/tests/ActionTaskAssignSpecificUserTest.php b/tests/units/ActionTaskAssignSpecificUserTest.php similarity index 100% rename from tests/ActionTaskAssignSpecificUserTest.php rename to tests/units/ActionTaskAssignSpecificUserTest.php diff --git a/tests/ActionTaskCloseTest.php b/tests/units/ActionTaskCloseTest.php similarity index 100% rename from tests/ActionTaskCloseTest.php rename to tests/units/ActionTaskCloseTest.php diff --git a/tests/ActionTaskDuplicateAnotherProjectTest.php b/tests/units/ActionTaskDuplicateAnotherProjectTest.php similarity index 100% rename from tests/ActionTaskDuplicateAnotherProjectTest.php rename to tests/units/ActionTaskDuplicateAnotherProjectTest.php diff --git a/tests/ActionTest.php b/tests/units/ActionTest.php similarity index 100% rename from tests/ActionTest.php rename to tests/units/ActionTest.php diff --git a/tests/units/Base.php b/tests/units/Base.php new file mode 100644 index 000000000..1f8109ed1 --- /dev/null +++ b/tests/units/Base.php @@ -0,0 +1,57 @@ +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 similarity index 100% rename from tests/BoardTest.php rename to tests/units/BoardTest.php diff --git a/tests/CommentTest.php b/tests/units/CommentTest.php similarity index 100% rename from tests/CommentTest.php rename to tests/units/CommentTest.php diff --git a/tests/ProjectTest.php b/tests/units/ProjectTest.php similarity index 100% rename from tests/ProjectTest.php rename to tests/units/ProjectTest.php diff --git a/tests/TaskTest.php b/tests/units/TaskTest.php similarity index 100% rename from tests/TaskTest.php rename to tests/units/TaskTest.php diff --git a/vendor/JsonRPC/Client.php b/vendor/JsonRPC/Client.php new file mode 100644 index 000000000..bbdb72001 --- /dev/null +++ b/vendor/JsonRPC/Client.php @@ -0,0 +1,174 @@ +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 000000000..93d46cdb7 --- /dev/null +++ b/vendor/JsonRPC/Server.php @@ -0,0 +1,301 @@ +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); + } +}