diff options
Diffstat (limited to 'app')
71 files changed, 1991 insertions, 67 deletions
diff --git a/app/Controller/Base.php b/app/Controller/Base.php index efeab31e..fb64bcf3 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -189,9 +189,15 @@ abstract class Base extends \Kanboard\Core\Base */ protected function taskLayout($template, array $params) { + $params['ajax'] = $this->request->isAjax() || $this->request->getIntegerParam('ajax') === 1; $content = $this->template->render($template, $params); - $params['task_content_for_layout'] = $content; + + if ($params['ajax']) { + return $content; + } + $params['title'] = $params['task']['project_name'].' > '.$params['task']['title']; + $params['task_content_for_layout'] = $content; $params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); return $this->template->layout('task/layout', $params); @@ -319,7 +325,8 @@ abstract class Base extends \Kanboard\Core\Base * @param array &$project * @return string */ - protected function getProjectDescription(array &$project) { + protected function getProjectDescription(array &$project) + { if ($project['owner_id'] > 0) { $description = t('Project owner: ').'**'.$this->template->e($project['owner_name'] ?: $project['owner_username']).'**'.PHP_EOL.PHP_EOL; diff --git a/app/Controller/BoardTooltip.php b/app/Controller/BoardTooltip.php index bcf7de81..06f4d729 100644 --- a/app/Controller/BoardTooltip.php +++ b/app/Controller/BoardTooltip.php @@ -25,6 +25,20 @@ class BoardTooltip extends Base } /** + * Get links on mouseover + * + * @access public + */ + public function externallinks() + { + $task = $this->getTask(); + $this->response->html($this->template->render('board/tooltip_external_links', array( + 'links' => $this->taskExternalLink->getAll($task['id']), + 'task' => $task, + ))); + } + + /** * Get subtasks on mouseover * * @access public diff --git a/app/Controller/Comment.php b/app/Controller/Comment.php index a608dd1c..c77a4712 100644 --- a/app/Controller/Comment.php +++ b/app/Controller/Comment.php @@ -41,7 +41,6 @@ class Comment extends Base public function create(array $values = array(), array $errors = array()) { $task = $this->getTask(); - $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); if (empty($values)) { $values = array( @@ -50,15 +49,6 @@ class Comment extends Base ); } - if ($ajax) { - $this->response->html($this->template->render('comment/create', array( - 'values' => $values, - 'errors' => $errors, - 'task' => $task, - 'ajax' => $ajax, - ))); - } - $this->response->html($this->taskLayout('comment/create', array( 'values' => $values, 'errors' => $errors, diff --git a/app/Controller/TaskExternalLink.php b/app/Controller/TaskExternalLink.php new file mode 100644 index 00000000..3209751b --- /dev/null +++ b/app/Controller/TaskExternalLink.php @@ -0,0 +1,185 @@ +<?php + +namespace Kanboard\Controller; + +use Kanboard\Core\ExternalLink\ExternalLinkProviderNotFound; + +/** + * Task External Link Controller + * + * @package controller + * @author Frederic Guillot + */ +class TaskExternalLink extends Base +{ + /** + * Creation form + * + * @access public + */ + public function show() + { + $task = $this->getTask(); + + $this->response->html($this->taskLayout('task_external_link/show', array( + 'links' => $this->taskExternalLink->getAll($task['id']), + 'task' => $task, + 'title' => t('List of external links'), + ))); + } + + /** + * First creation form + * + * @access public + */ + public function find(array $values = array(), array $errors = array()) + { + $task = $this->getTask(); + + $this->response->html($this->taskLayout('task_external_link/find', array( + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'types' => $this->externalLinkManager->getTypes(), + ))); + } + + /** + * Second creation form + * + * @access public + */ + public function create() + { + try { + + $task = $this->getTask(); + $values = $this->request->getValues(); + + $provider = $this->externalLinkManager->setUserInput($values)->find(); + $link = $provider->getLink(); + + $this->response->html($this->taskLayout('task_external_link/create', array( + 'values' => array( + 'title' => $link->getTitle(), + 'url' => $link->getUrl(), + 'link_type' => $provider->getType(), + ), + 'dependencies' => $provider->getDependencies(), + 'errors' => array(), + 'task' => $task, + ))); + + } catch (ExternalLinkProviderNotFound $e) { + $errors = array('text' => array(t('Unable to fetch link information.'))); + $this->find($values, $errors); + } + } + + /** + * Save link + * + * @access public + */ + public function save() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + list($valid, $errors) = $this->externalLinkValidator->validateCreation($values); + + if ($valid && $this->taskExternalLink->create($values)) { + $this->flash->success(t('Link added successfully.')); + return $this->response->redirect($this->helper->url->to('TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true); + } + + $this->edit($values, $errors); + } + + /** + * Edit form + * + * @access public + */ + public function edit(array $values = array(), array $errors = array()) + { + $task = $this->getTask(); + $link_id = $this->request->getIntegerParam('link_id'); + + if ($link_id > 0) { + $values = $this->taskExternalLink->getById($link_id); + } + + if (empty($values)) { + return $this->notfound(); + } + + $provider = $this->externalLinkManager->getProvider($values['link_type']); + + $this->response->html($this->taskLayout('task_external_link/edit', array( + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'dependencies' => $provider->getDependencies(), + ))); + } + + /** + * Update link + * + * @access public + */ + public function update() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + list($valid, $errors) = $this->externalLinkValidator->validateModification($values); + + if ($valid && $this->taskExternalLink->update($values)) { + $this->flash->success(t('Link updated successfully.')); + return $this->response->redirect($this->helper->url->to('TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); + } + + $this->edit($values, $errors); + } + + /** + * Confirmation dialog before removing a link + * + * @access public + */ + public function confirm() + { + $task = $this->getTask(); + $link_id = $this->request->getIntegerParam('link_id'); + $link = $this->taskExternalLink->getById($link_id); + + if (empty($link)) { + return $this->notfound(); + } + + $this->response->html($this->taskLayout('task_external_link/remove', array( + 'link' => $link, + 'task' => $task, + ))); + } + + /** + * Remove a link + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $task = $this->getTask(); + + if ($this->taskExternalLink->remove($this->request->getIntegerParam('link_id'))) { + $this->flash->success(t('Link removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this link.')); + } + + $this->response->redirect($this->helper->url->to('TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); + } +} diff --git a/app/Controller/Tasklink.php b/app/Controller/Tasklink.php index a81d3ee5..4338e7bf 100644 --- a/app/Controller/Tasklink.php +++ b/app/Controller/Tasklink.php @@ -29,6 +29,25 @@ class Tasklink extends Base } /** + * Show links + * + * @access public + */ + public function show() + { + $task = $this->getTask(); + $project = $this->project->getById($task['project_id']); + + $this->response->html($this->taskLayout('tasklink/show', array( + 'links' => $this->taskLink->getAllGroupedByLabel($task['id']), + 'task' => $task, + 'project' => $project, + 'editable' => true, + 'is_public' => false, + ))); + } + + /** * Creation form * * @access public @@ -36,18 +55,6 @@ class Tasklink extends Base public function create(array $values = array(), array $errors = array()) { $task = $this->getTask(); - $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); - - if ($ajax && empty($errors)) { - $this->response->html($this->template->render('tasklink/create', array( - 'values' => $values, - 'errors' => $errors, - 'task' => $task, - 'labels' => $this->link->getList(0, false), - 'title' => t('Add a new link'), - 'ajax' => $ajax, - ))); - } $this->response->html($this->taskLayout('tasklink/create', array( 'values' => $values, @@ -76,10 +83,10 @@ class Tasklink extends Base $this->flash->success(t('Link added successfully.')); if ($ajax) { - $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); + return $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); } - $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); + return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); } $errors = array('title' => array(t('The exact same link already exists'))); @@ -130,7 +137,7 @@ class Tasklink extends Base if ($valid) { if ($this->taskLink->update($values['id'], $values['task_id'], $values['opposite_task_id'], $values['link_id'])) { $this->flash->success(t('Link updated successfully.')); - $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); + return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); } $this->flash->failure(t('Unable to update your link.')); diff --git a/app/Controller/Taskmodification.php b/app/Controller/Taskmodification.php index 2c97970b..1fcc416b 100644 --- a/app/Controller/Taskmodification.php +++ b/app/Controller/Taskmodification.php @@ -80,14 +80,9 @@ class Taskmodification extends Base 'values' => $values, 'errors' => $errors, 'task' => $task, - 'ajax' => $ajax, ); - if ($ajax) { - $this->response->html($this->template->render('task_modification/edit_description', $params)); - } else { - $this->response->html($this->taskLayout('task_modification/edit_description', $params)); - } + $this->response->html($this->taskLayout('task_modification/edit_description', $params)); } /** diff --git a/app/Core/Base.php b/app/Core/Base.php index 2821e5ae..ab99fcea 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -16,6 +16,7 @@ use Pimple\Container; * @property \Kanboard\Analytic\AverageLeadCycleTimeAnalytic $averageLeadCycleTimeAnalytic * @property \Kanboard\Analytic\AverageTimeSpentColumnAnalytic $averageTimeSpentColumnAnalytic * @property \Kanboard\Core\Action\ActionManager $actionManager + * @property \Kanboard\Core\ExternalLink\ExternalLinkManager $externalLinkManager * @property \Kanboard\Core\Cache\MemoryCache $memoryCache * @property \Kanboard\Core\Event\EventManager $eventManager * @property \Kanboard\Core\Group\GroupManager $groupManager @@ -97,6 +98,7 @@ use Pimple\Container; * @property \Kanboard\Model\TaskCreation $taskCreation * @property \Kanboard\Model\TaskDuplication $taskDuplication * @property \Kanboard\Model\TaskExport $taskExport + * @property \Kanboard\Model\TaskExternalLink $taskExternalLink * @property \Kanboard\Model\TaskImport $taskImport * @property \Kanboard\Model\TaskFinder $taskFinder * @property \Kanboard\Model\TaskFilter $taskFilter @@ -132,6 +134,7 @@ use Pimple\Container; * @property \Kanboard\Validator\SubtaskValidator $subtaskValidator * @property \Kanboard\Validator\SwimlaneValidator $swimlaneValidator * @property \Kanboard\Validator\TaskLinkValidator $taskLinkValidator + * @property \Kanboard\Validator\TaskExternalLinkValidator $taskExternalLinkValidator * @property \Kanboard\Validator\TaskValidator $taskValidator * @property \Kanboard\Validator\UserValidator $userValidator * @property \Psr\Log\LoggerInterface $logger diff --git a/app/Core/ExternalLink/ExternalLinkInterface.php b/app/Core/ExternalLink/ExternalLinkInterface.php new file mode 100644 index 00000000..2dbc0a19 --- /dev/null +++ b/app/Core/ExternalLink/ExternalLinkInterface.php @@ -0,0 +1,36 @@ +<?php + +namespace Kanboard\Core\ExternalLink; + +/** + * External Link Interface + * + * @package externalLink + * @author Frederic Guillot + */ +interface ExternalLinkInterface +{ + /** + * Get link title + * + * @access public + * @return string + */ + public function getTitle(); + + /** + * Get link URL + * + * @access public + * @return string + */ + public function getUrl(); + + /** + * Set link URL + * + * @access public + * @param string $url + */ + public function setUrl($url); +} diff --git a/app/Core/ExternalLink/ExternalLinkManager.php b/app/Core/ExternalLink/ExternalLinkManager.php new file mode 100644 index 00000000..cd3476ca --- /dev/null +++ b/app/Core/ExternalLink/ExternalLinkManager.php @@ -0,0 +1,171 @@ +<?php + +namespace Kanboard\Core\ExternalLink; + +use Kanboard\Core\Base; + +/** + * External Link Manager + * + * @package externalLink + * @author Frederic Guillot + */ +class ExternalLinkManager extends Base +{ + /** + * Automatic type value + * + * @var string + */ + const TYPE_AUTO = 'auto'; + + /** + * Registered providers + * + * @access private + * @var array + */ + private $providers = array(); + + /** + * Type chosen by the user + * + * @access private + * @var string + */ + private $userInputType = ''; + + /** + * Text entered by the user + * + * @access private + * @var string + */ + private $userInputText = ''; + + /** + * Register a new provider + * + * Providers are registered in a LIFO queue + * + * @access public + * @param ExternalLinkProviderInterface $provider + * @return ExternalLinkManager + */ + public function register(ExternalLinkProviderInterface $provider) + { + array_unshift($this->providers, $provider); + return $this; + } + + /** + * Get provider + * + * @access public + * @param string $type + * @throws ExternalLinkProviderNotFound + * @return ExternalLinkProviderInterface + */ + public function getProvider($type) + { + foreach ($this->providers as $provider) { + if ($provider->getType() === $type) { + return $provider; + } + } + + throw new ExternalLinkProviderNotFound('Unable to find link provider: '.$type); + } + + /** + * Get link types + * + * @access public + * @return array + */ + public function getTypes() + { + $types = array(); + + foreach ($this->providers as $provider) { + $types[$provider->getType()] = $provider->getName(); + } + + asort($types); + + return array(self::TYPE_AUTO => t('Auto')) + $types; + } + + /** + * Get dependency label from a provider + * + * @access public + * @param string $type + * @param string $dependency + * @return string + */ + public function getDependencyLabel($type, $dependency) + { + $provider = $this->getProvider($type); + $dependencies = $provider->getDependencies(); + return isset($dependencies[$dependency]) ? $dependencies[$dependency] : $dependency; + } + + /** + * Find a provider that match + * + * @access public + * @throws ExternalLinkProviderNotFound + * @return ExternalLinkProviderInterface + */ + public function find() + { + $provider = null; + + if ($this->userInputType === self::TYPE_AUTO) { + $provider = $this->findProvider(); + } else { + $provider = $this->getProvider($this->userInputType); + $provider->setUserTextInput($this->userInputText); + } + + if ($provider === null) { + throw new ExternalLinkProviderNotFound('Unable to find link information from provided information'); + } + + return $provider; + } + + /** + * Set form values + * + * @access public + * @param array $values + * @return ExternalLinkManager + */ + public function setUserInput(array $values) + { + $this->userInputType = empty($values['type']) ? self::TYPE_AUTO : $values['type']; + $this->userInputText = empty($values['text']) ? '' : trim($values['text']); + return $this; + } + + /** + * Find a provider that user input + * + * @access private + * @return ExternalLinkProviderInterface + */ + private function findProvider() + { + foreach ($this->providers as $provider) { + $provider->setUserTextInput($this->userInputText); + + if ($provider->match()) { + return $provider; + } + } + + return null; + } +} diff --git a/app/Core/ExternalLink/ExternalLinkProviderInterface.php b/app/Core/ExternalLink/ExternalLinkProviderInterface.php new file mode 100644 index 00000000..c908e1eb --- /dev/null +++ b/app/Core/ExternalLink/ExternalLinkProviderInterface.php @@ -0,0 +1,71 @@ +<?php + +namespace Kanboard\Core\ExternalLink; + +/** + * External Link Provider Interface + * + * @package externalLink + * @author Frederic Guillot + */ +interface ExternalLinkProviderInterface +{ + /** + * Get provider name (label) + * + * @access public + * @return string + */ + public function getName(); + + /** + * Get link type (will be saved in the database) + * + * @access public + * @return string + */ + public function getType(); + + /** + * Get a dictionary of supported dependency types by the provider + * + * Example: + * + * [ + * 'related' => t('Related'), + * 'child' => t('Child'), + * 'parent' => t('Parent'), + * 'self' => t('Self'), + * ] + * + * The dictionary key is saved in the database. + * + * @access public + * @return array + */ + public function getDependencies(); + + /** + * Set text entered by the user + * + * @access public + * @param string $input + */ + public function setUserTextInput($input); + + /** + * Return true if the provider can parse correctly the user input + * + * @access public + * @return boolean + */ + public function match(); + + /** + * Get the link found with the properties + * + * @access public + * @return ExternalLinkInterface + */ + public function getLink(); +} diff --git a/app/Core/ExternalLink/ExternalLinkProviderNotFound.php b/app/Core/ExternalLink/ExternalLinkProviderNotFound.php new file mode 100644 index 00000000..4fd05202 --- /dev/null +++ b/app/Core/ExternalLink/ExternalLinkProviderNotFound.php @@ -0,0 +1,15 @@ +<?php + +namespace Kanboard\Core\ExternalLink; + +use Exception; + +/** + * External Link Provider Not Found Exception + * + * @package externalLink + * @author Frederic Guillot + */ +class ExternalLinkProviderNotFound extends Exception +{ +} diff --git a/app/Core/Http/Client.php b/app/Core/Http/Client.php index c6bf36a6..12b0a1cb 100644 --- a/app/Core/Http/Client.php +++ b/app/Core/Http/Client.php @@ -34,6 +34,19 @@ class Client extends Base const HTTP_USER_AGENT = 'Kanboard'; /** + * Send a GET HTTP request + * + * @access public + * @param string $url + * @param string[] $headers + * @return string + */ + public function get($url, array $headers = array()) + { + return $this->doRequest('GET', $url, '', $headers); + } + + /** * Send a GET HTTP request and parse JSON response * * @access public diff --git a/app/Core/Http/Response.php b/app/Core/Http/Response.php index 7fefddeb..a0d8137b 100644 --- a/app/Core/Http/Response.php +++ b/app/Core/Http/Response.php @@ -68,11 +68,12 @@ class Response extends Base * * @access public * @param string $url Redirection URL + * @param boolean $self If Ajax request and true: refresh the current page */ - public function redirect($url) + public function redirect($url, $self = false) { if ($this->request->getServerVariable('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest') { - header('X-Ajax-Redirect: '.$url); + header('X-Ajax-Redirect: '.($self ? 'self' : $url)); } else { header('Location: '.$url); } diff --git a/app/ExternalLink/AttachmentLink.php b/app/ExternalLink/AttachmentLink.php new file mode 100644 index 00000000..5a0d1344 --- /dev/null +++ b/app/ExternalLink/AttachmentLink.php @@ -0,0 +1,26 @@ +<?php + +namespace Kanboard\ExternalLink; + +use Kanboard\Core\ExternalLink\ExternalLinkInterface; + +/** + * Attachment Link + * + * @package externalLink + * @author Frederic Guillot + */ +class AttachmentLink extends BaseLink implements ExternalLinkInterface +{ + /** + * Get link title + * + * @access public + * @return string + */ + public function getTitle() + { + $path = parse_url($this->url, PHP_URL_PATH); + return basename($path); + } +} diff --git a/app/ExternalLink/AttachmentLinkProvider.php b/app/ExternalLink/AttachmentLinkProvider.php new file mode 100644 index 00000000..df27284f --- /dev/null +++ b/app/ExternalLink/AttachmentLinkProvider.php @@ -0,0 +1,117 @@ +<?php + +namespace Kanboard\ExternalLink; + +use Kanboard\Core\ExternalLink\ExternalLinkProviderInterface; + +/** + * Attachment Link Provider + * + * @package externalLink + * @author Frederic Guillot + */ +class AttachmentLinkProvider extends BaseLinkProvider implements ExternalLinkProviderInterface +{ + /** + * File extensions that are not attachments + * + * @access protected + * @var array + */ + protected $extensions = array( + 'html', + 'htm', + 'xhtml', + 'php', + 'jsp', + 'do', + 'action', + 'asp', + 'aspx', + 'cgi', + ); + + /** + * Get provider name + * + * @access public + * @return string + */ + public function getName() + { + return t('Attachment'); + } + + /** + * Get link type + * + * @access public + * @return string + */ + public function getType() + { + return 'attachment'; + } + + /** + * Get a dictionary of supported dependency types by the provider + * + * @access public + * @return array + */ + public function getDependencies() + { + return array( + 'related' => t('Related'), + ); + } + + /** + * Return true if the provider can parse correctly the user input + * + * @access public + * @return boolean + */ + public function match() + { + if (preg_match('/^https?:\/\/.*\.([^\/]+)$/', $this->userInput, $matches)) { + return $this->isValidExtension($matches[1]); + } + + return false; + } + + /** + * Get the link found with the properties + * + * @access public + * @return ExternalLinkInterface + */ + public function getLink() + { + $link = new AttachmentLink($this->container); + $link->setUrl($this->userInput); + + return $link; + } + + /** + * Check file extension + * + * @access protected + * @param string $extension + * @return boolean + */ + protected function isValidExtension($extension) + { + $extension = strtolower($extension); + + foreach ($this->extensions as $ext) { + if ($extension === $ext) { + return false; + } + } + + return true; + } +} diff --git a/app/ExternalLink/BaseLink.php b/app/ExternalLink/BaseLink.php new file mode 100644 index 00000000..08693ae7 --- /dev/null +++ b/app/ExternalLink/BaseLink.php @@ -0,0 +1,44 @@ +<?php + +namespace Kanboard\ExternalLink; + +use Kanboard\Core\Base; + +/** + * Base Link + * + * @package externalLink + * @author Frederic Guillot + */ +abstract class BaseLink extends Base +{ + /** + * URL + * + * @access protected + * @var string + */ + protected $url = ''; + + /** + * Get link URL + * + * @access public + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * Set link URL + * + * @access public + * @param string $url + */ + public function setUrl($url) + { + $this->url = $url; + } +} diff --git a/app/ExternalLink/BaseLinkProvider.php b/app/ExternalLink/BaseLinkProvider.php new file mode 100644 index 00000000..749cda94 --- /dev/null +++ b/app/ExternalLink/BaseLinkProvider.php @@ -0,0 +1,33 @@ +<?php + +namespace Kanboard\ExternalLink; + +use Kanboard\Core\Base; + +/** + * Base Link Provider + * + * @package externalLink + * @author Frederic Guillot + */ +abstract class BaseLinkProvider extends Base +{ + /** + * User input + * + * @access protected + * @var string + */ + protected $userInput = ''; + + /** + * Set text entered by the user + * + * @access public + * @param string $input + */ + public function setUserTextInput($input) + { + $this->userInput = trim($input); + } +} diff --git a/app/ExternalLink/WebLink.php b/app/ExternalLink/WebLink.php new file mode 100644 index 00000000..9338ca42 --- /dev/null +++ b/app/ExternalLink/WebLink.php @@ -0,0 +1,37 @@ +<?php + +namespace Kanboard\ExternalLink; + +use Kanboard\Core\ExternalLink\ExternalLinkInterface; + +/** + * Web Link + * + * @package externalLink + * @author Frederic Guillot + */ +class WebLink extends BaseLink implements ExternalLinkInterface +{ + /** + * Get link title + * + * @access public + * @return string + */ + public function getTitle() + { + $html = $this->httpClient->get($this->url); + + if (preg_match('/<title>(.*)<\/title>/siU', $html, $matches)) { + return trim($matches[1]); + } + + $components = parse_url($this->url); + + if (! empty($components['host']) && ! empty($components['path'])) { + return $components['host'].$components['path']; + } + + return t('Title not found'); + } +} diff --git a/app/ExternalLink/WebLinkProvider.php b/app/ExternalLink/WebLinkProvider.php new file mode 100644 index 00000000..ea6dc132 --- /dev/null +++ b/app/ExternalLink/WebLinkProvider.php @@ -0,0 +1,77 @@ +<?php + +namespace Kanboard\ExternalLink; + +use Kanboard\Core\ExternalLink\ExternalLinkProviderInterface; + +/** + * Web Link Provider + * + * @package externalLink + * @author Frederic Guillot + */ +class WebLinkProvider extends BaseLinkProvider implements ExternalLinkProviderInterface +{ + /** + * Get provider name + * + * @access public + * @return string + */ + public function getName() + { + return t('Web Link'); + } + + /** + * Get link type + * + * @access public + * @return string + */ + public function getType() + { + return 'weblink'; + } + + /** + * Get a dictionary of supported dependency types by the provider + * + * @access public + * @return array + */ + public function getDependencies() + { + return array( + 'related' => t('Related'), + ); + } + + /** + * Return true if the provider can parse correctly the user input + * + * @access public + * @return boolean + */ + public function match() + { + $startWithHttp = strpos($this->userInput, 'http://') === 0 || strpos($this->userInput, 'https://') === 0; + $validUrl = filter_var($this->userInput, FILTER_VALIDATE_URL); + + return $startWithHttp && $validUrl; + } + + /** + * Get the link found with the properties + * + * @access public + * @return ExternalLinkInterface + */ + public function getLink() + { + $link = new WebLink($this->container); + $link->setUrl($this->userInput); + + return $link; + } +} diff --git a/app/Locale/bs_BA/translations.php b/app/Locale/bs_BA/translations.php index 208f7d77..af090d23 100644 --- a/app/Locale/bs_BA/translations.php +++ b/app/Locale/bs_BA/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php index 70d2815b..6dacadcb 100644 --- a/app/Locale/cs_CZ/translations.php +++ b/app/Locale/cs_CZ/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index db32e047..e30eac46 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index 55d144d6..77835aa2 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -1097,4 +1097,30 @@ return array( 'Highest priority' => 'Höchste Priorität', 'If you put zero to the low and high priority, this feature will be disabled.' => 'Wenn Sie Null bei höchster und niedrigster Priorität eintragen, wird diese Funktion deaktiviert.', 'Priority: %d' => 'Priorität: %d', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/el_GR/translations.php b/app/Locale/el_GR/translations.php index e87d1daa..d75c25d1 100644 --- a/app/Locale/el_GR/translations.php +++ b/app/Locale/el_GR/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index c8bc0d7b..02dd6790 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index 6ae4a373..df20259a 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index f04f95ba..df74e5b4 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -1100,4 +1100,30 @@ return array( 'Highest priority' => 'Priorité haute', 'If you put zero to the low and high priority, this feature will be disabled.' => 'Si vous mettez zéro pour la priorité basse et haute, cette fonctionnalité sera désactivée.', 'Priority: %d' => 'Priorité : %d', + 'Close a task when there is no activity' => 'Fermer une tâche sans activité', + 'Duration in days' => 'Durée en jours', + 'Send email when there is no activity on a task' => 'Envoyer un email lorsqu\'il n\'y a pas d\'activité sur une tâche', + 'List of external links' => 'Liste des liens externes', + 'Unable to fetch link information.' => 'Impossible de récupérer les informations sur le lien.', + 'Daily background job for tasks' => 'Tâche planifée quotidienne pour les tâches', + 'Auto' => 'Auto', + 'Related' => 'Relié', + 'Attachment' => 'Pièce-jointe', + 'Title not found' => 'Titre non trouvé', + 'Web Link' => 'Lien web', + 'External links' => 'Liens externes', + 'Add external link' => 'Ajouter un lien externe', + 'Type' => 'Type', + 'Dependency' => 'Dépendance', + 'View internal links' => 'Voir les liens internes', + 'View external links' => 'Voir les liens externes', + 'Add internal link' => 'Ajouter un lien interne', + 'Add a new external link' => 'Ajouter un nouveau lien externe', + 'Edit external link' => 'Modifier un lien externe', + 'External link' => 'Lien externe', + 'Copy and paste your link here...' => 'Copier-coller vôtre lien ici...', + 'URL' => 'URL', + 'There is no external link for the moment.' => 'Il n\'y a pas de lien externe pour le moment.', + 'Internal links' => 'Liens internes', + 'There is no internal link for the moment.' => 'Il n\'y a pas de lien interne pour le moment.', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index 5f34ac56..b8d6d1b6 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php index 5c9be946..02ee727c 100644 --- a/app/Locale/id_ID/translations.php +++ b/app/Locale/id_ID/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 4ff71c7b..b9c45642 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -1097,4 +1097,30 @@ return array( 'Highest priority' => 'Priorità massima', 'If you put zero to the low and high priority, this feature will be disabled.' => 'Se imposti a zero la priorità massima e minima, questa funzionalità sarà disabilitata.', 'Priority: %d' => 'Priorità: %d', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index 21961f3b..a16c44e8 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/my_MY/translations.php b/app/Locale/my_MY/translations.php index 643f5b4a..93185888 100644 --- a/app/Locale/my_MY/translations.php +++ b/app/Locale/my_MY/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php index 66ccd125..b35dce3d 100644 --- a/app/Locale/nb_NO/translations.php +++ b/app/Locale/nb_NO/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php index d7268df3..0117a978 100644 --- a/app/Locale/nl_NL/translations.php +++ b/app/Locale/nl_NL/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 222f1fe2..4f4f840f 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index 155d4e83..1b5b35a6 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php index 99bb096f..baa8eab0 100644 --- a/app/Locale/pt_PT/translations.php +++ b/app/Locale/pt_PT/translations.php @@ -1097,4 +1097,30 @@ return array( 'Highest priority' => 'Prioridade mais alta', 'If you put zero to the low and high priority, this feature will be disabled.' => 'Se colocar zero na prioridade baixa ou alta, essa funcionalidade será desactivada.', 'Priority: %d' => 'Prioridade: %d', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 93419c5b..3aa717b9 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -1097,4 +1097,30 @@ return array( 'Highest priority' => 'Нивысший приоритет', 'If you put zero to the low and high priority, this feature will be disabled.' => 'Если Вы введете 0 для наименьшего и наивысшего приоритета, этот функционал будет отключен.', 'Priority: %d' => 'Приоритет: %d', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php index 430a299f..4b320fe7 100644 --- a/app/Locale/sr_Latn_RS/translations.php +++ b/app/Locale/sr_Latn_RS/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index a4d61922..c225cc05 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index e6411efc..7fa53c6f 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php index a628d6f8..35613345 100644 --- a/app/Locale/tr_TR/translations.php +++ b/app/Locale/tr_TR/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index 8df2c01b..9de31cdd 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Model/TaskExternalLink.php b/app/Model/TaskExternalLink.php new file mode 100644 index 00000000..f2c756b4 --- /dev/null +++ b/app/Model/TaskExternalLink.php @@ -0,0 +1,99 @@ +<?php + +namespace Kanboard\Model; + +/** + * Task External Link Model + * + * @package model + * @author Frederic Guillot + */ +class TaskExternalLink extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'task_has_external_links'; + + /** + * Get all links + * + * @access public + * @param integer $task_id + * @return array + */ + public function getAll($task_id) + { + $types = $this->externalLinkManager->getTypes(); + + $links = $this->db->table(self::TABLE) + ->columns(self::TABLE.'.*', User::TABLE.'.name AS creator_name', User::TABLE.'.username AS creator_username') + ->eq('task_id', $task_id) + ->asc('title') + ->join(User::TABLE, 'id', 'creator_id') + ->findAll(); + + foreach ($links as &$link) { + $link['dependency_label'] = $this->externalLinkManager->getDependencyLabel($link['link_type'], $link['dependency']); + $link['type'] = isset($types[$link['link_type']]) ? $types[$link['link_type']] : t('Unknown'); + } + + return $links; + } + + /** + * Get link + * + * @access public + * @param integer $link_id + * @return array + */ + public function getById($link_id) + { + return $this->db->table(self::TABLE)->eq('id', $link_id)->findOne(); + } + + /** + * Add a new link in the database + * + * @access public + * @param array $values Form values + * @return boolean|integer + */ + public function create(array $values) + { + unset($values['id']); + $values['creator_id'] = $this->userSession->getId(); + $values['date_creation'] = time(); + $values['date_modification'] = $values['date_creation']; + + return $this->persist(self::TABLE, $values); + } + + /** + * Modify external link + * + * @access public + * @param array $values Form values + * @return boolean + */ + public function update(array $values) + { + $values['date_modification'] = time(); + return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values); + } + + /** + * Remove a link + * + * @access public + * @param integer $link_id + * @return boolean + */ + public function remove($link_id) + { + return $this->db->table(self::TABLE)->eq('id', $link_id)->remove(); + } +} diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php index 1c83136b..ab290bce 100644 --- a/app/Model/TaskFinder.php +++ b/app/Model/TaskFinder.php @@ -88,11 +88,12 @@ class TaskFinder extends Base return $this->db ->table(Task::TABLE) ->columns( - '(SELECT count(*) FROM '.Comment::TABLE.' WHERE task_id=tasks.id) AS nb_comments', - '(SELECT count(*) FROM '.File::TABLE.' WHERE task_id=tasks.id) AS nb_files', - '(SELECT count(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id) AS nb_subtasks', - '(SELECT count(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks', - '(SELECT count(*) FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id) AS nb_links', + '(SELECT COUNT(*) FROM '.Comment::TABLE.' WHERE task_id=tasks.id) AS nb_comments', + '(SELECT COUNT(*) FROM '.File::TABLE.' WHERE task_id=tasks.id) AS nb_files', + '(SELECT COUNT(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id) AS nb_subtasks', + '(SELECT COUNT(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks', + '(SELECT COUNT(*) FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id) AS nb_links', + '(SELECT COUNT(*) FROM '.TaskExternalLink::TABLE.' WHERE '.TaskExternalLink::TABLE.'.task_id = tasks.id) AS nb_external_links', '(SELECT DISTINCT 1 FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id AND '.TaskLink::TABLE.'.link_id = 9) AS is_milestone', 'tasks.id', 'tasks.reference', diff --git a/app/Model/User.php b/app/Model/User.php index 0174a040..dd622207 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -265,7 +265,7 @@ class User extends Base * * @access public * @param array $values Form values - * @return array + * @return boolean */ public function update(array $values) { diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 8f1db510..5433c751 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,25 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 103; +const VERSION = 104; + +function version_104(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_external_links ( + id INT NOT NULL AUTO_INCREMENT, + link_type VARCHAR(100) NOT NULL, + dependency VARCHAR(100) NOT NULL, + title VARCHAR(255) NOT NULL, + url VARCHAR(255) NOT NULL, + date_creation INT NOT NULL, + date_modification INT NOT NULL, + task_id INT NOT NULL, + creator_id INT DEFAULT 0, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); +} function version_103(PDO $pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index a7bf8054..363b633b 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,25 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 83; +const VERSION = 84; + +function version_84(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_external_links ( + id SERIAL, + link_type VARCHAR(100) NOT NULL, + dependency VARCHAR(100) NOT NULL, + title VARCHAR(255) NOT NULL, + url VARCHAR(255) NOT NULL, + date_creation INT NOT NULL, + date_modification INT NOT NULL, + task_id INT NOT NULL, + creator_id INT DEFAULT 0, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ) + "); +} function version_83(PDO $pdo) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index 08689749..bc701341 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,25 @@ use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; use PDO; -const VERSION = 95; +const VERSION = 96; + +function version_96(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_external_links ( + id INTEGER PRIMARY KEY, + link_type TEXT NOT NULL, + dependency TEXT NOT NULL, + title TEXT NOT NULL, + url TEXT NOT NULL, + date_creation INTEGER NOT NULL, + date_modification INTEGER NOT NULL, + task_id INTEGER NOT NULL, + creator_id INTEGER DEFAULT 0, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ) + "); +} function version_95(PDO $pdo) { diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index ed0962b6..144abb0e 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -89,6 +89,9 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('Taskduplication', '*', Role::PROJECT_MEMBER); $acl->add('TaskImport', '*', Role::PROJECT_MANAGER); $acl->add('Tasklink', '*', Role::PROJECT_MEMBER); + $acl->add('Tasklink', array('show'), Role::PROJECT_VIEWER); + $acl->add('TaskExternalLink', '*', Role::PROJECT_MEMBER); + $acl->add('TaskExternalLink', array('show'), Role::PROJECT_VIEWER); $acl->add('Taskmodification', '*', Role::PROJECT_MEMBER); $acl->add('Taskstatus', '*', Role::PROJECT_MEMBER); $acl->add('Timer', '*', Role::PROJECT_MEMBER); diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index df4e183b..61a4c512 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -61,6 +61,7 @@ class ClassProvider implements ServiceProviderInterface 'TaskCreation', 'TaskDuplication', 'TaskExport', + 'TaskExternalLink', 'TaskFinder', 'TaskFilter', 'TaskLink', @@ -97,6 +98,7 @@ class ClassProvider implements ServiceProviderInterface 'CommentValidator', 'CurrencyValidator', 'CustomFilterValidator', + 'ExternalLinkValidator', 'GroupValidator', 'LinkValidator', 'PasswordResetValidator', diff --git a/app/ServiceProvider/ExternalLinkProvider.php b/app/ServiceProvider/ExternalLinkProvider.php new file mode 100644 index 00000000..c4bbc4cf --- /dev/null +++ b/app/ServiceProvider/ExternalLinkProvider.php @@ -0,0 +1,34 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Core\ExternalLink\ExternalLinkManager; +use Kanboard\ExternalLink\WebLinkProvider; +use Kanboard\ExternalLink\AttachmentLinkProvider; + +/** + * External Link Provider + * + * @package serviceProvider + * @author Frederic Guillot + */ +class ExternalLinkProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['externalLinkManager'] = new ExternalLinkManager($container); + $container['externalLinkManager']->register(new WebLinkProvider($container)); + $container['externalLinkManager']->register(new AttachmentLinkProvider($container)); + + return $container; + } +} diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php index dd9ee23b..ebe087ae 100644 --- a/app/ServiceProvider/RouteProvider.php +++ b/app/ServiceProvider/RouteProvider.php @@ -106,11 +106,18 @@ class RouteProvider implements ServiceProviderInterface $container['route']->addRoute('project/:project_id/task/:task_id/screenshot', 'file', 'screenshot'); $container['route']->addRoute('project/:project_id/task/:task_id/upload', 'file', 'create'); $container['route']->addRoute('project/:project_id/task/:task_id/comment', 'comment', 'create'); + $container['route']->addRoute('project/:project_id/task/:task_id/links', 'tasklink', 'show'); $container['route']->addRoute('project/:project_id/task/:task_id/link', 'tasklink', 'create'); $container['route']->addRoute('project/:project_id/task/:task_id/transitions', 'task', 'transitions'); $container['route']->addRoute('project/:project_id/task/:task_id/analytics', 'task', 'analytics'); $container['route']->addRoute('project/:project_id/task/:task_id/remove', 'task', 'remove'); + $container['route']->addRoute('project/:project_id/task/:task_id/links/external', 'TaskExternalLink', 'show'); + $container['route']->addRoute('project/:project_id/task/:task_id/link/external/new', 'TaskExternalLink', 'find'); + $container['route']->addRoute('project/:project_id/task/:task_id/link/external/save', 'TaskExternalLink', 'create'); + $container['route']->addRoute('project/:project_id/task/:task_id/link/external/:link_id', 'TaskExternalLink', 'edit'); + $container['route']->addRoute('project/:project_id/task/:task_id/link/external/:link_id/remove', 'TaskExternalLink', 'confirm'); + $container['route']->addRoute('project/:project_id/task/:task_id/edit', 'taskmodification', 'edit'); $container['route']->addRoute('project/:project_id/task/:task_id/description', 'taskmodification', 'description'); $container['route']->addRoute('project/:project_id/task/:task_id/recurrence', 'taskmodification', 'recurrence'); diff --git a/app/Template/board/task_footer.php b/app/Template/board/task_footer.php index 26f3b1d4..1912dd83 100644 --- a/app/Template/board/task_footer.php +++ b/app/Template/board/task_footer.php @@ -35,7 +35,11 @@ <?php endif ?> <?php if (! empty($task['nb_links'])): ?> - <span title="<?= t('Links') ?>" class="tooltip" data-href="<?= $this->url->href('BoardTooltip', 'tasklinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-code-fork"></i> <?= $task['nb_links'] ?></span> + <span title="<?= t('Links') ?>" class="tooltip" data-href="<?= $this->url->href('BoardTooltip', 'tasklinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-code-fork fa-fw"></i><?= $task['nb_links'] ?></span> + <?php endif ?> + + <?php if (! empty($task['nb_external_links'])): ?> + <span title="<?= t('External links') ?>" class="tooltip" data-href="<?= $this->url->href('BoardTooltip', 'externallinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-external-link fa-fw"></i><?= $task['nb_external_links'] ?></span> <?php endif ?> <?php if (! empty($task['nb_subtasks'])): ?> diff --git a/app/Template/board/task_menu.php b/app/Template/board/task_menu.php index b5ed125d..9e26e15b 100644 --- a/app/Template/board/task_menu.php +++ b/app/Template/board/task_menu.php @@ -7,6 +7,7 @@ <li><i class="fa fa-pencil-square-o fa-fw"></i> <?= $this->url->link(t('Edit this task'), 'taskmodification', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li> <li><i class="fa fa-comment-o fa-fw"></i> <?= $this->url->link(t('Add a comment'), 'comment', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li> <li><i class="fa fa-code-fork fa-fw"></i> <?= $this->url->link(t('Add a link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li> + <li><i class="fa fa-external-link fa-fw"></i> <?= $this->url->link(t('Add external link'), 'TaskExternalLink', 'find', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li> <li><i class="fa fa-camera fa-fw"></i> <?= $this->url->link(t('Add a screenshot'), 'BoardPopover', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li> <?php if ($task['is_active'] == 1): ?> <li><i class="fa fa-close fa-fw"></i> <?= $this->url->link(t('Close this task'), 'taskstatus', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'redirect' => 'board'), false, 'popover') ?></li> diff --git a/app/Template/board/tooltip_external_links.php b/app/Template/board/tooltip_external_links.php new file mode 100644 index 00000000..7681c06c --- /dev/null +++ b/app/Template/board/tooltip_external_links.php @@ -0,0 +1,20 @@ +<table class="table-striped table-small"> + <tr> + <th class="column-20"><?= t('Type') ?></th> + <th class="column-80"><?= t('Title') ?></th> + <th class="column-10"><?= t('Dependency') ?></th> + </tr> + <?php foreach ($links as $link): ?> + <tr> + <td> + <?= $link['type'] ?> + </td> + <td> + <a href="<?= $link['url'] ?>" target="_blank"><?= $this->e($link['title']) ?></a> + </td> + <td> + <?= $this->e($link['dependency_label']) ?> + </td> + </tr> + <?php endforeach ?> +</table>
\ No newline at end of file diff --git a/app/Template/comment/create.php b/app/Template/comment/create.php index e9a6404d..8ce9aac3 100644 --- a/app/Template/comment/create.php +++ b/app/Template/comment/create.php @@ -2,7 +2,7 @@ <h2><?= t('Add a comment') ?></h2> </div> -<form method="post" action="<?= $this->url->href('comment', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => isset($ajax))) ?>" autocomplete="off" class="form-comment"> +<form method="post" action="<?= $this->url->href('comment', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" autocomplete="off" class="form-comment"> <?= $this->form->csrf() ?> <?= $this->form->hidden('task_id', $values) ?> <?= $this->form->hidden('user_id', $values) ?> @@ -41,7 +41,7 @@ <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> <?php if (! isset($skip_cancel)): ?> <?= t('or') ?> - <?php if (isset($ajax)): ?> + <?php if ($ajax): ?> <?= $this->url->link(t('cancel'), 'board', 'show', array('project_id' => $task['project_id'])) ?> <?php else: ?> <?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> diff --git a/app/Template/task/comments.php b/app/Template/task/comments.php index 57fb305f..ef85287b 100644 --- a/app/Template/task/comments.php +++ b/app/Template/task/comments.php @@ -28,7 +28,8 @@ 'task_id' => $task['id'], ), 'errors' => array(), - 'task' => $task + 'task' => $task, + 'ajax' => $ajax, )) ?> <?php endif ?> </div> diff --git a/app/Template/task/show.php b/app/Template/task/show.php index f6d47e53..b848d49e 100644 --- a/app/Template/task/show.php +++ b/app/Template/task/show.php @@ -13,14 +13,6 @@ <?= $this->render('task/description', array('task' => $task)) ?> -<?= $this->render('tasklink/show', array( - 'task' => $task, - 'links' => $links, - 'link_label_list' => $link_label_list, - 'editable' => $this->user->hasProjectAccess('tasklink', 'edit', $project['id']), - 'is_public' => false, -)) ?> - <?= $this->render('subtask/show', array( 'task' => $task, 'subtasks' => $subtasks, @@ -29,6 +21,14 @@ 'editable' => $this->user->hasProjectAccess('subtask', 'edit', $project['id']), )) ?> +<?= $this->render('tasklink/show', array( + 'task' => $task, + 'links' => $links, + 'link_label_list' => $link_label_list, + 'editable' => $this->user->hasProjectAccess('tasklink', 'edit', $project['id']), + 'is_public' => false, +)) ?> + <?= $this->render('task/time_tracking_summary', array('task' => $task)) ?> <?= $this->render('file/show', array( @@ -42,4 +42,5 @@ 'comments' => $comments, 'project' => $project, 'editable' => $this->user->hasProjectAccess('comment', 'edit', $project['id']), + 'ajax' => $ajax, )) ?> diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php index f522c1c4..b5a2c4b4 100644 --- a/app/Template/task/sidebar.php +++ b/app/Template/task/sidebar.php @@ -21,6 +21,25 @@ <?= $this->hook->render('template:task:sidebar:information') ?> </ul> + + <h2><?= t('Links') ?></h2> + <ul> + <li <?= $this->app->checkMenuSelection('tasklink', 'show') ?>> + <?= $this->url->link(t('View internal links'), 'tasklink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('TaskExternalLink', 'show') ?>> + <?= $this->url->link(t('View external links'), 'TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </li> + <?php if ($this->user->hasProjectAccess('tasklink', 'create', $task['project_id'])): ?> + <li <?= $this->app->checkMenuSelection('tasklink', 'create') ?>> + <?= $this->url->link(t('Add internal link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('TaskExternalLink', 'find') ?>> + <?= $this->url->link(t('Add external link'), 'TaskExternalLink', 'find', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </li> + <?php endif ?> + </ul> + <?php if ($this->user->hasProjectAccess('taskmodification', 'edit', $task['project_id'])): ?> <h2><?= t('Actions') ?></h2> <ul> @@ -36,9 +55,6 @@ <li <?= $this->app->checkMenuSelection('subtask', 'create') ?>> <?= $this->url->link(t('Add a sub-task'), 'subtask', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->checkMenuSelection('tasklink', 'create') ?>> - <?= $this->url->link(t('Add a link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> - </li> <li <?= $this->app->checkMenuSelection('comment', 'create') ?>> <?= $this->url->link(t('Add a comment'), 'comment', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> diff --git a/app/Template/task_creation/form.php b/app/Template/task_creation/form.php index eaf9024d..01b000f2 100644 --- a/app/Template/task_creation/form.php +++ b/app/Template/task_creation/form.php @@ -10,7 +10,7 @@ </div> <?php endif ?> -<form id="task-form" method="post" action="<?= $this->url->href('taskcreation', 'save', array('project_id' => $values['project_id'])) ?>" autocomplete="off"> +<form id="task-form" class="popover-form" method="post" action="<?= $this->url->href('taskcreation', 'save', array('project_id' => $values['project_id'])) ?>" autocomplete="off"> <?= $this->form->csrf() ?> diff --git a/app/Template/task_external_link/create.php b/app/Template/task_external_link/create.php new file mode 100644 index 00000000..3179d6af --- /dev/null +++ b/app/Template/task_external_link/create.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Add a new external link') ?></h2> +</div> + +<form class="popover-form" action="<?= $this->url->href('TaskExternalLink', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" method="post" autocomplete="off"> + <?= $this->render('task_external_link/form', array('task' => $task, 'dependencies' => $dependencies, 'values' => $values, 'errors' => $errors)) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/task_external_link/edit.php b/app/Template/task_external_link/edit.php new file mode 100644 index 00000000..cf9ddfed --- /dev/null +++ b/app/Template/task_external_link/edit.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Edit external link') ?></h2> +</div> + +<form class="popover-form" action="<?= $this->url->href('TaskExternalLink', 'update', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" method="post" autocomplete="off"> + <?= $this->render('task_external_link/form', array('task' => $task, 'dependencies' => $dependencies, 'values' => $values, 'errors' => $errors)) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/task_external_link/find.php b/app/Template/task_external_link/find.php new file mode 100644 index 00000000..a2304014 --- /dev/null +++ b/app/Template/task_external_link/find.php @@ -0,0 +1,32 @@ +<div class="page-header"> + <h2><?= t('Add a new external link') ?></h2> +</div> + +<form class="popover-form" action="<?= $this->url->href('TaskExternalLink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" method="post" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('task_id', array('task_id' => $task['id'])) ?> + + <?= $this->form->label(t('External link'), 'text') ?> + <?= $this->form->text( + 'text', + $values, + $errors, + array( + 'required', + 'autofocus', + 'placeholder="'.t('Copy and paste your link here...').'"', + )) ?> + + <?= $this->form->label(t('Link type'), 'type') ?> + <?= $this->form->select('type', $types, $values) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Next') ?>" class="btn btn-blue"/> + <?= t('or') ?> + <?php if ($ajax): ?> + <?= $this->url->link(t('cancel'), 'board', 'show', array('project_id' => $task['project_id']), false, 'close-popover') ?> + <?php else: ?> + <?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + <?php endif ?> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/task_external_link/form.php b/app/Template/task_external_link/form.php new file mode 100644 index 00000000..932ca521 --- /dev/null +++ b/app/Template/task_external_link/form.php @@ -0,0 +1,13 @@ +<?= $this->form->csrf() ?> +<?= $this->form->hidden('task_id', array('task_id' => $task['id'])) ?> +<?= $this->form->hidden('id', $values) ?> +<?= $this->form->hidden('link_type', $values) ?> + +<?= $this->form->label(t('URL'), 'url') ?> +<?= $this->form->text('url', $values, $errors, array('required')) ?> + +<?= $this->form->label(t('Title'), 'title') ?> +<?= $this->form->text('title', $values, $errors, array('required')) ?> + +<?= $this->form->label(t('Dependency'), 'dependency') ?> +<?= $this->form->select('dependency', $dependencies, $values, $errors) ?> diff --git a/app/Template/task_external_link/remove.php b/app/Template/task_external_link/remove.php new file mode 100644 index 00000000..f55e751c --- /dev/null +++ b/app/Template/task_external_link/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove a link') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this link: "%s"?', $link['title']) ?> + </p> + + <div class="form-actions"> + <?= $this->url->link(t('Yes'), 'TaskExternalLink', 'remove', array('link_id' => $link['id'], 'task_id' => $task['id'], 'project_id' => $task['project_id']), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/task_external_link/show.php b/app/Template/task_external_link/show.php new file mode 100644 index 00000000..7e8b1948 --- /dev/null +++ b/app/Template/task_external_link/show.php @@ -0,0 +1,50 @@ +<div class="page-header"> + <h2><?= t('External links') ?></h2> +</div> + +<?php if (empty($links)): ?> + <p class="alert"><?= t('There is no external link for the moment.') ?></p> +<?php else: ?> + <table class="table-stripped table-small"> + <tr> + <th class="column-10"><?= t('Type') ?></th> + <th><?= t('Title') ?></th> + <th class="column-10"><?= t('Dependency') ?></th> + <th class="column-15"><?= t('Creator') ?></th> + <th class="column-15"><?= t('Date') ?></th> + <?php if ($this->user->hasProjectAccess('TaskExternalLink', 'edit', $task['project_id'])): ?> + <th class="column-5"><?= t('Action') ?></th> + <?php endif ?> + </tr> + <?php foreach ($links as $link): ?> + <tr> + <td> + <?= $link['type'] ?> + </td> + <td> + <a href="<?= $link['url'] ?>" target="_blank"><?= $this->e($link['title']) ?></a> + </td> + <td> + <?= $this->e($link['dependency_label']) ?> + </td> + <td> + <?= $this->e($link['creator_name'] ?: $link['creator_username']) ?> + </td> + <td> + <?= dt('%B %e, %Y', $link['date_creation']) ?> + </td> + <?php if ($this->user->hasProjectAccess('TaskExternalLink', 'edit', $task['project_id'])): ?> + <td> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-cog fa-fw"></i><i class="fa fa-caret-down"></i></a> + <ul> + <li><?= $this->url->link(t('Edit'), 'TaskExternalLink', 'edit', array('link_id' => $link['id'], 'task_id' => $task['id'], 'project_id' => $task['project_id'])) ?></li> + <li><?= $this->url->link(t('Remove'), 'TaskExternalLink', 'confirm', array('link_id' => $link['id'], 'task_id' => $task['id'], 'project_id' => $task['project_id'])) ?></li> + </ul> + </div> + </td> + <?php endif ?> + </tr> + <?php endforeach ?> + </table> +<?php endif ?> diff --git a/app/Template/tasklink/create.php b/app/Template/tasklink/create.php index 2832bdc7..f4c3cdae 100644 --- a/app/Template/tasklink/create.php +++ b/app/Template/tasklink/create.php @@ -2,7 +2,7 @@ <h2><?= t('Add a new link') ?></h2> </div> -<form action="<?= $this->url->href('tasklink', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => isset($ajax))) ?>" method="post" autocomplete="off"> +<form action="<?= $this->url->href('tasklink', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" method="post" autocomplete="off"> <?= $this->form->csrf() ?> <?= $this->form->hidden('task_id', array('task_id' => $task['id'])) ?> @@ -28,7 +28,7 @@ <div class="form-actions"> <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> <?= t('or') ?> - <?php if (isset($ajax)): ?> + <?php if ($ajax): ?> <?= $this->url->link(t('cancel'), 'board', 'show', array('project_id' => $task['project_id']), false, 'close-popover') ?> <?php else: ?> <?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> diff --git a/app/Template/tasklink/show.php b/app/Template/tasklink/show.php index 5843da17..11b12b73 100644 --- a/app/Template/tasklink/show.php +++ b/app/Template/tasklink/show.php @@ -1,15 +1,17 @@ -<?php if (! empty($links)): ?> <div class="page-header"> - <h2><?= t('Links') ?></h2> + <h2><?= t('Internal links') ?></h2> </div> -<table id="links"> +<?php if (empty($links)): ?> + <p class="alert"><?= t('There is no internal link for the moment.') ?></p> +<?php else: ?> +<table id="links" class="table-small table-stripped"> <tr> <th class="column-20"><?= t('Label') ?></th> <th class="column-30"><?= t('Task') ?></th> <th class="column-20"><?= t('Project') ?></th> <th><?= t('Column') ?></th> <th><?= t('Assignee') ?></th> - <?php if ($editable): ?> + <?php if ($editable && $this->user->hasProjectAccess('Tasklink', 'edit', $task['project_id'])): ?> <th class="column-5"><?= t('Action') ?></th> <?php endif ?> </tr> @@ -64,7 +66,7 @@ <?php endif ?> <?php endif ?> </td> - <?php if ($editable): ?> + <?php if ($editable && $this->user->hasProjectAccess('Tasklink', 'edit', $task['project_id'])): ?> <td> <div class="dropdown"> <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-cog fa-fw"></i><i class="fa fa-caret-down"></i></a> diff --git a/app/Validator/ExternalLinkValidator.php b/app/Validator/ExternalLinkValidator.php new file mode 100644 index 00000000..fff4133b --- /dev/null +++ b/app/Validator/ExternalLinkValidator.php @@ -0,0 +1,76 @@ +<?php + +namespace Kanboard\Validator; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * External Link Validator + * + * @package validator + * @author Frederic Guillot + */ +class ExternalLinkValidator extends Base +{ + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, $this->commonValidationRules()); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Required('url', t('Field required')), + new Validators\MaxLength('url', t('The maximum length is %d characters', 255), 255), + new Validators\Required('title', t('Field required')), + new Validators\MaxLength('title', t('The maximum length is %d characters', 255), 255), + new Validators\Required('link_type', t('Field required')), + new Validators\MaxLength('link_type', t('The maximum length is %d characters', 100), 100), + new Validators\Required('dependency', t('Field required')), + new Validators\MaxLength('dependency', t('The maximum length is %d characters', 100), 100), + new Validators\Integer('id', t('This value must be an integer')), + new Validators\Required('task_id', t('Field required')), + new Validators\Integer('task_id', t('This value must be an integer')), + ); + } +} diff --git a/app/common.php b/app/common.php index fe287811..399fcb86 100644 --- a/app/common.php +++ b/app/common.php @@ -34,4 +34,5 @@ $container->register(new Kanboard\ServiceProvider\EventDispatcherProvider); $container->register(new Kanboard\ServiceProvider\GroupProvider); $container->register(new Kanboard\ServiceProvider\RouteProvider); $container->register(new Kanboard\ServiceProvider\ActionProvider); +$container->register(new Kanboard\ServiceProvider\ExternalLinkProvider); $container->register(new Kanboard\ServiceProvider\PluginProvider); |