diff options
-rw-r--r-- | ChangeLog | 1 | ||||
-rw-r--r-- | app/Api/Procedure/TaskExternalLinkProcedure.php | 106 | ||||
-rw-r--r-- | app/Core/ExternalLink/ExternalLinkManager.php | 24 | ||||
-rw-r--r-- | app/ServiceProvider/ApiProvider.php | 2 | ||||
-rw-r--r-- | app/ServiceProvider/AuthenticationProvider.php | 1 | ||||
-rw-r--r-- | doc/api-external-task-link-procedures.markdown | 221 | ||||
-rw-r--r-- | doc/api-internal-task-link-procedures.markdown | 187 | ||||
-rw-r--r-- | doc/api-json-rpc.markdown | 2 | ||||
-rw-r--r-- | doc/api-link-procedures.markdown | 185 | ||||
-rw-r--r-- | tests/integration/TaskExternalLinkProcedureTest.php | 98 |
10 files changed, 642 insertions, 185 deletions
@@ -5,6 +5,7 @@ New features: * Added application and project roles validation for API procedure calls * Added new API call: "getProjectByIdentifier" +* Added new API calls for external task links Improvements: diff --git a/app/Api/Procedure/TaskExternalLinkProcedure.php b/app/Api/Procedure/TaskExternalLinkProcedure.php new file mode 100644 index 00000000..05ec6906 --- /dev/null +++ b/app/Api/Procedure/TaskExternalLinkProcedure.php @@ -0,0 +1,106 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\TaskAuthorization; +use Kanboard\Core\ExternalLink\ExternalLinkManager; +use Kanboard\Core\ExternalLink\ExternalLinkProviderNotFound; + +/** + * Task External Link API controller + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + */ +class TaskExternalLinkProcedure extends BaseProcedure +{ + public function getExternalTaskLinkTypes() + { + return $this->externalLinkManager->getTypes(); + } + + public function getExternalTaskLinkProviderDependencies($providerName) + { + try { + return $this->externalLinkManager->getProvider($providerName)->getDependencies(); + } catch (ExternalLinkProviderNotFound $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + return false; + } + } + + public function getExternalTaskLinkById($task_id, $link_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getExternalTaskLink', $task_id); + return $this->taskExternalLinkModel->getById($link_id); + } + + public function getAllExternalTaskLinks($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getExternalTaskLinks', $task_id); + return $this->taskExternalLinkModel->getAll($task_id); + } + + public function createExternalTaskLink($task_id, $url, $dependency, $type = ExternalLinkManager::TYPE_AUTO, $title = '') + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createExternalTaskLink', $task_id); + + try { + $provider = $this->externalLinkManager + ->setUserInputText($url) + ->setUserInputType($type) + ->find(); + + $link = $provider->getLink(); + + $values = array( + 'task_id' => $task_id, + 'title' => $title ?: $link->getTitle(), + 'url' => $link->getUrl(), + 'link_type' => $provider->getType(), + 'dependency' => $dependency, + ); + + list($valid, $errors) = $this->externalLinkValidator->validateCreation($values); + + if (! $valid) { + $this->logger->error(__METHOD__.': '.var_export($errors)); + return false; + } + + return $this->taskExternalLinkModel->create($values); + } catch (ExternalLinkProviderNotFound $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + } + + return false; + } + + public function updateExternalTaskLink($task_id, $link_id, $title = null, $url = null, $dependency = null) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateExternalTaskLink', $task_id); + + $link = $this->taskExternalLinkModel->getById($link_id); + $values = $this->filterValues(array( + 'title' => $title, + 'url' => $url, + 'dependency' => $dependency, + )); + + $values = array_merge($link, $values); + list($valid, $errors) = $this->externalLinkValidator->validateModification($values); + + if (! $valid) { + $this->logger->error(__METHOD__.': '.var_export($errors)); + return false; + } + + return $this->taskExternalLinkModel->update($values); + } + + public function removeExternalTaskLink($task_id, $link_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeExternalTaskLink', $task_id); + return $this->taskExternalLinkModel->remove($link_id); + } +} diff --git a/app/Core/ExternalLink/ExternalLinkManager.php b/app/Core/ExternalLink/ExternalLinkManager.php index 804e6b34..5a037999 100644 --- a/app/Core/ExternalLink/ExternalLinkManager.php +++ b/app/Core/ExternalLink/ExternalLinkManager.php @@ -153,6 +153,30 @@ class ExternalLinkManager extends Base } /** + * Set provider type + * + * @access public + * @param string $userInputType + * @return ExternalLinkManager + */ + public function setUserInputType($userInputType) + { + $this->userInputType = $userInputType; + return $this; + } + + /** + * Set external link + * @param string $userInputText + * @return ExternalLinkManager + */ + public function setUserInputText($userInputText) + { + $this->userInputText = $userInputText; + return $this; + } + + /** * Find a provider that user input * * @access private diff --git a/app/ServiceProvider/ApiProvider.php b/app/ServiceProvider/ApiProvider.php index f88d9b4f..194bee5b 100644 --- a/app/ServiceProvider/ApiProvider.php +++ b/app/ServiceProvider/ApiProvider.php @@ -9,6 +9,7 @@ use Kanboard\Api\Procedure\BoardProcedure; use Kanboard\Api\Procedure\CategoryProcedure; use Kanboard\Api\Procedure\ColumnProcedure; use Kanboard\Api\Procedure\CommentProcedure; +use Kanboard\Api\Procedure\TaskExternalLinkProcedure; use Kanboard\Api\Procedure\TaskFileProcedure; use Kanboard\Api\Procedure\GroupProcedure; use Kanboard\Api\Procedure\GroupMemberProcedure; @@ -65,6 +66,7 @@ class ApiProvider implements ServiceProviderInterface ->withObject(new SwimlaneProcedure($container)) ->withObject(new TaskProcedure($container)) ->withObject(new TaskLinkProcedure($container)) + ->withObject(new TaskExternalLinkProcedure($container)) ->withObject(new UserProcedure($container)) ->withObject(new GroupProcedure($container)) ->withObject(new GroupMemberProcedure($container)) diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index 751fe514..34b81b9d 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -204,6 +204,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('SwimlaneProcedure', '*', Role::PROJECT_MANAGER); $acl->add('TaskFileProcedure', '*', Role::PROJECT_MEMBER); $acl->add('TaskLinkProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('TaskExternalLinkProcedure', array('createExternalTaskLink', 'updateExternalTaskLink', 'removeExternalTaskLink'), Role::PROJECT_MEMBER); $acl->add('TaskProcedure', '*', Role::PROJECT_MEMBER); return $acl; diff --git a/doc/api-external-task-link-procedures.markdown b/doc/api-external-task-link-procedures.markdown new file mode 100644 index 00000000..2858be86 --- /dev/null +++ b/doc/api-external-task-link-procedures.markdown @@ -0,0 +1,221 @@ +Internal Task Links API Procedures +================================== + +## getExternalTaskLinkTypes + +- Purpose: **Get all registered external link providers** +- Parameters: **none** +- Result on success: **dict** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"getExternalTaskLinkTypes","id":477370568} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": { + "auto": "Auto", + "attachment": "Attachment", + "file": "Local File", + "weblink": "Web Link" + }, + "id": 477370568 +} +``` + +## getExternalTaskLinkProviderDependencies + +- Purpose: **Get available dependencies for a given provider** +- Parameters: + - **providerName** (string, required) +- Result on success: **dict** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"getExternalTaskLinkProviderDependencies","id":124790226,"params":["weblink"]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": { + "related": "Related" + }, + "id": 124790226 +} +``` + +## createExternalTaskLink + +- Purpose: **Create a new external link** +- Parameters: + - **task_id** (integer, required) + - **url** (string, required) + - **dependency** (string, required) + - **type** (string, optional) + - **title** (string, optional) +- Result on success: **link_id** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"createExternalTaskLink","id":924217495,"params":[9,"http:\/\/localhost\/document.pdf","related","attachment"]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": 1, + "id": 924217495 +} +``` + +## updateExternalTaskLink + +- Purpose: **Update external task link** +- Parameters: + - **task_id** (integer, required) + - **link_id** (integer, required) + - **title** (string, required) + - **url** (string, required) + - **dependency** (string, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc":"2.0", + "method":"updateExternalTaskLink", + "id":1123562620, + "params": { + "task_id":9, + "link_id":1, + "title":"New title" + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": true, + "id": 1123562620 +} +``` + +## getExternalTaskLinkById + +- Purpose: **Get an external task link** +- Parameters: + - **task_id** (integer, required) + - **link_id** (integer, required) +- Result on success: **dict** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"getExternalTaskLinkById","id":2107066744,"params":[9,1]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": { + "id": "1", + "link_type": "attachment", + "dependency": "related", + "title": "document.pdf", + "url": "http:\/\/localhost\/document.pdf", + "date_creation": "1466965256", + "date_modification": "1466965256", + "task_id": "9", + "creator_id": "0" + }, + "id": 2107066744 +} +``` + +## getAllExternalTaskLinks + +- Purpose: **Get all external links attached to a task** +- Parameters: + - **task_id** (integer, required) +- Result on success: **list of external links** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"getAllExternalTaskLinks","id":2069307223,"params":[9]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": [ + { + "id": "1", + "link_type": "attachment", + "dependency": "related", + "title": "New title", + "url": "http:\/\/localhost\/document.pdf", + "date_creation": "1466965256", + "date_modification": "1466965256", + "task_id": "9", + "creator_id": "0", + "creator_name": null, + "creator_username": null, + "dependency_label": "Related", + "type": "Attachment" + } + ], + "id": 2069307223 +} +``` + +## removeExternalTaskLink + +- Purpose: **Remove an external link** +- Parameters: + - **task_id** (integer, required) + - **link_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"removeExternalTaskLink","id":552055660,"params":[9,1]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": true, + "id": 552055660 +} +``` diff --git a/doc/api-internal-task-link-procedures.markdown b/doc/api-internal-task-link-procedures.markdown new file mode 100644 index 00000000..859228de --- /dev/null +++ b/doc/api-internal-task-link-procedures.markdown @@ -0,0 +1,187 @@ +Internal Task Links API Procedures +================================== + +## createTaskLink + +- Purpose: **Create a link between two tasks** +- Parameters: + - **task_id** (integer, required) + - **opposite_task_id** (integer, required) + - **link_id** (integer, required) +- Result on success: **task_link_id** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "createTaskLink", + "id": 509742912, + "params": [ + 2, + 3, + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 509742912, + "result": 1 +} +``` + +## updateTaskLink + +- Purpose: **Update task link** +- Parameters: + - **task_link_id** (integer, required) + - **task_id** (integer, required) + - **opposite_task_id** (integer, required) + - **link_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "updateTaskLink", + "id": 669037109, + "params": [ + 1, + 2, + 4, + 2 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 669037109, + "result": true +} +``` + +## getTaskLinkById + +- Purpose: **Get a task link** +- Parameters: + - **task_link_id** (integer, required) +- Result on success: **task link properties** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getTaskLinkById", + "id": 809885202, + "params": [ + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 809885202, + "result": { + "id": "1", + "link_id": "1", + "task_id": "2", + "opposite_task_id": "3" + } +} +``` + +## getAllTaskLinks + +- Purpose: **Get all links related to a task** +- Parameters: + - **task_id** (integer, required) +- Result on success: **list of task link** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getAllTaskLinks", + "id": 810848359, + "params": [ + 2 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 810848359, + "result": [ + { + "id": "1", + "task_id": "3", + "label": "relates to", + "title": "B", + "is_active": "1", + "project_id": "1", + "task_time_spent": "0", + "task_time_estimated": "0", + "task_assignee_id": "0", + "task_assignee_username": null, + "task_assignee_name": null, + "column_title": "Backlog" + } + ] +} +``` + +## removeTaskLink + +- Purpose: **Remove a link between two tasks** +- Parameters: + - **task_link_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeTaskLink", + "id": 473028226, + "params": [ + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 473028226, + "result": true +} +``` diff --git a/doc/api-json-rpc.markdown b/doc/api-json-rpc.markdown index 0f922a7c..8e783e71 100644 --- a/doc/api-json-rpc.markdown +++ b/doc/api-json-rpc.markdown @@ -60,6 +60,8 @@ Usage - [Subtasks](api-subtask-procedures.markdown) - [Files](api-file-procedures.markdown) - [Links](api-link-procedures.markdown) +- [Internal Task Links](api-internal-task-link-procedures.markdown) +- [External Task Links](api-external-task-link-procedures.markdown) - [Comments](api-comment-procedures.markdown) - [Users](api-user-procedures.markdown) - [Groups](api-group-procedures.markdown) diff --git a/doc/api-link-procedures.markdown b/doc/api-link-procedures.markdown index 6113316f..44e78a2a 100644 --- a/doc/api-link-procedures.markdown +++ b/doc/api-link-procedures.markdown @@ -283,188 +283,3 @@ Response example: "result": true } ``` - -## createTaskLink - -- Purpose: **Create a link between two tasks** -- Parameters: - - **task_id** (integer, required) - - **opposite_task_id** (integer, required) - - **link_id** (integer, required) -- Result on success: **task_link_id** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "createTaskLink", - "id": 509742912, - "params": [ - 2, - 3, - 1 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 509742912, - "result": 1 -} -``` - -## updateTaskLink - -- Purpose: **Update task link** -- Parameters: - - **task_link_id** (integer, required) - - **task_id** (integer, required) - - **opposite_task_id** (integer, required) - - **link_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "updateTaskLink", - "id": 669037109, - "params": [ - 1, - 2, - 4, - 2 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 669037109, - "result": true -} -``` - -## getTaskLinkById - -- Purpose: **Get a task link** -- Parameters: - - **task_link_id** (integer, required) -- Result on success: **task link properties** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getTaskLinkById", - "id": 809885202, - "params": [ - 1 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 809885202, - "result": { - "id": "1", - "link_id": "1", - "task_id": "2", - "opposite_task_id": "3" - } -} -``` - -## getAllTaskLinks - -- Purpose: **Get all links related to a task** -- Parameters: - - **task_id** (integer, required) -- Result on success: **list of task link** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getAllTaskLinks", - "id": 810848359, - "params": [ - 2 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 810848359, - "result": [ - { - "id": "1", - "task_id": "3", - "label": "relates to", - "title": "B", - "is_active": "1", - "project_id": "1", - "task_time_spent": "0", - "task_time_estimated": "0", - "task_assignee_id": "0", - "task_assignee_username": null, - "task_assignee_name": null, - "column_title": "Backlog" - } - ] -} -``` - -## removeTaskLink - -- Purpose: **Remove a link between two tasks** -- Parameters: - - **task_link_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "removeTaskLink", - "id": 473028226, - "params": [ - 1 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 473028226, - "result": true -} -``` diff --git a/tests/integration/TaskExternalLinkProcedureTest.php b/tests/integration/TaskExternalLinkProcedureTest.php new file mode 100644 index 00000000..47ff53ad --- /dev/null +++ b/tests/integration/TaskExternalLinkProcedureTest.php @@ -0,0 +1,98 @@ +<?php + +require_once __DIR__.'/BaseProcedureTest.php'; + +class TaskExternalLinkProcedureTest extends BaseProcedureTest +{ + protected $projectName = 'My project to test external links'; + private $linkId = 0; + + public function testAll() + { + $this->assertCreateTeamProject(); + $this->assertCreateTask(); + $this->assertGetExternalTaskLinkTypes(); + $this->assertGetExternalTaskLinkProviderDependencies(); + $this->assertGetExternalTaskLinkProviderDependenciesWithProviderNotFound(); + $this->assertCreateExternalTaskLink(); + $this->assertUpdateExternalTaskLink(); + $this->assertGetAllExternalTaskLinks(); + $this->assertRemoveExternalTaskLink(); + } + + public function assertGetExternalTaskLinkTypes() + { + $expected = array( + 'auto' => 'Auto', + 'attachment' => 'Attachment', + 'file' => 'Local File', + 'weblink' => 'Web Link', + ); + + $types = $this->app->getExternalTaskLinkTypes(); + $this->assertEquals($expected, $types); + } + + public function assertGetExternalTaskLinkProviderDependencies() + { + $expected = array( + 'related' => 'Related', + ); + + $dependencies = $this->app->getExternalTaskLinkProviderDependencies('weblink'); + + $this->assertEquals($expected, $dependencies); + } + + public function assertGetExternalTaskLinkProviderDependenciesWithProviderNotFound() + { + $this->assertFalse($this->app->getExternalTaskLinkProviderDependencies('foobar')); + } + + public function assertCreateExternalTaskLink() + { + $url = 'http://localhost/document.pdf'; + $this->linkId = $this->app->createExternalTaskLink($this->taskId, $url, 'related', 'attachment'); + $this->assertNotFalse($this->linkId); + + $link = $this->app->getExternalTaskLinkById($this->taskId, $this->linkId); + $this->assertEquals($this->linkId, $link['id']); + $this->assertEquals($this->taskId, $link['task_id']); + $this->assertEquals('document.pdf', $link['title']); + $this->assertEquals($url, $link['url']); + $this->assertEquals('related', $link['dependency']); + $this->assertEquals(0, $link['creator_id']); + } + + public function assertUpdateExternalTaskLink() + { + $this->assertTrue($this->app->updateExternalTaskLink(array( + 'task_id' => $this->taskId, + 'link_id' => $this->linkId, + 'title' => 'New title', + ))); + + $link = $this->app->getExternalTaskLinkById($this->taskId, $this->linkId); + $this->assertEquals($this->linkId, $link['id']); + $this->assertEquals($this->taskId, $link['task_id']); + $this->assertEquals('New title', $link['title']); + $this->assertEquals('related', $link['dependency']); + $this->assertEquals(0, $link['creator_id']); + } + + public function assertGetAllExternalTaskLinks() + { + $links = $this->app->getAllExternalTaskLinks($this->taskId); + $this->assertCount(1, $links); + $this->assertEquals($this->linkId, $links[0]['id']); + $this->assertEquals($this->taskId, $links[0]['task_id']); + $this->assertEquals('New title', $links[0]['title']); + $this->assertEquals('related', $links[0]['dependency']); + $this->assertEquals(0, $links[0]['creator_id']); + } + + public function assertRemoveExternalTaskLink() + { + $this->assertTrue($this->app->removeExternalTaskLink($this->taskId, $this->linkId)); + } +} |