diff --git a/app/Controller/Board.php b/app/Controller/Board.php index d92436337..f539a77c6 100644 --- a/app/Controller/Board.php +++ b/app/Controller/Board.php @@ -197,7 +197,7 @@ class Board extends Base { $task = $this->getTask(); $this->response->html($this->template->render('board/tasklinks', array( - 'links' => $this->taskLink->getLinks($task['id']), + 'links' => $this->taskLink->getAll($task['id']), 'task' => $task, ))); } diff --git a/app/Controller/Task.php b/app/Controller/Task.php index 5bca45106..866ef7747 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -36,7 +36,7 @@ class Task extends Base 'project' => $project, 'comments' => $this->comment->getAll($task['id']), 'subtasks' => $this->subtask->getAll($task['id']), - 'links' => $this->taskLink->getLinks($task['id']), + 'links' => $this->taskLink->getAllGroupedByLabel($task['id']), 'task' => $task, 'columns_list' => $this->board->getColumnsList($task['project_id']), 'colors_list' => $this->color->getList(), @@ -72,7 +72,7 @@ class Task extends Base 'images' => $this->file->getAllImages($task['id']), 'comments' => $this->comment->getAll($task['id']), 'subtasks' => $subtasks, - 'links' => $this->taskLink->getLinks($task['id']), + 'links' => $this->taskLink->getAllGroupedByLabel($task['id']), 'task' => $task, 'values' => $values, 'link_label_list' => $this->link->getList(0, false), diff --git a/app/Controller/Tasklink.php b/app/Controller/Tasklink.php index 8376b75b1..eccf149f5 100644 --- a/app/Controller/Tasklink.php +++ b/app/Controller/Tasklink.php @@ -38,13 +38,7 @@ class Tasklink extends Base $task = $this->getTask(); $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); - if (empty($values)) { - $values = array( - 'task_id' => $task['id'], - ); - } - - if ($ajax) { + if ($ajax && empty($errors)) { $this->response->html($this->template->render('tasklink/create', array( 'values' => $values, 'errors' => $errors, @@ -81,19 +75,72 @@ class Tasklink extends Base if ($this->taskLink->create($values['task_id'], $values['opposite_task_id'], $values['link_id'])) { $this->session->flash(t('Link added successfully.')); + if ($ajax) { $this->response->redirect($this->helper->url('board', 'show', array('project_id' => $task['project_id']))); } + $this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); } - else { - $this->session->flashError(t('Unable to create your link.')); - } + + $errors = array('title' => array(t('The exact same link already exists'))); + $this->session->flashError(t('Unable to create your link.')); } $this->create($values, $errors); } + /** + * Edit form + * + * @access public + */ + public function edit(array $values = array(), array $errors = array()) + { + $task = $this->getTask(); + $task_link = $this->getTaskLink(); + + if (empty($values)) { + $opposite_task = $this->taskFinder->getById($task_link['opposite_task_id']); + $values = $task_link; + $values['title'] = '#'.$opposite_task['id'].' - '.$opposite_task['title']; + } + + $this->response->html($this->taskLayout('tasklink/edit', array( + 'values' => $values, + 'errors' => $errors, + 'task_link' => $task_link, + 'task' => $task, + 'labels' => $this->link->getList(0, false), + 'title' => t('Edit link') + ))); + } + + /** + * Validation and update + * + * @access public + */ + public function update() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + + list($valid, $errors) = $this->taskLink->validateModification($values); + + if ($valid) { + + if ($this->taskLink->update($values['id'], $values['task_id'], $values['opposite_task_id'], $values['link_id'])) { + $this->session->flash(t('Link updated successfully.')); + $this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); + } + + $this->session->flashError(t('Unable to update your link.')); + } + + $this->edit($values, $errors); + } + /** * Confirmation dialog before removing a link * @@ -127,6 +174,6 @@ class Tasklink extends Base $this->session->flashError(t('Unable to remove this link.')); } - $this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); + $this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); } } diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index f06f87c14..663c2603e 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', // 'Disable two factor authentication' => '', // 'Do you really want to disable the two factor authentication for this user: "%s"?' => '', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index 78c35467c..6efe61676 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -868,4 +868,8 @@ return array( 'Help on Sendgrid integration' => 'Hilfe bei Sendgrid-Integration', 'Disable two factor authentication' => 'Deaktiviere Zwei-Faktor-Authentifizierung', 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Willst du wirklich für folgenden Nutzer die Zwei-Faktor-Authentifizierung deaktivieren: "%s"?', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index 3edb17d53..449fcd942 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -614,7 +614,7 @@ return array( 'Remove a swimlane' => 'Remover un carril', 'Rename' => 'Renombrar', 'Show default swimlane' => 'Mostrar carril por defecto', - 'Swimlane modification for the project "%s"' => '', + // 'Swimlane modification for the project "%s"' => '', 'Swimlane not found.' => 'Carril no encontrado', 'Swimlane removed successfully.' => 'Carril removido correctamente', 'Swimlanes' => 'Carriles', @@ -653,7 +653,7 @@ return array( 'Filter by status' => 'Filtrar por estado', 'Calendar' => 'Calendario', 'Next' => 'Siguiente', - '#%d' => '', + // '#%d' => '', 'Filter by color' => 'Filtrar por color', 'Filter by swimlane' => 'Filtrar por carril', 'All swimlanes' => 'Todos los carriles', @@ -846,8 +846,8 @@ return array( 'Secret key: ' => 'Clave secreta: ', 'Test your device' => 'Probar tu dispositivo', 'Assign a color when the task is moved to a specific column' => 'Asignar un color al mover la tarea a una columna específica', - '%s via Kanboard' => '%s vía Kanboard', - 'uploaded by: %s' => 'cargado por: %s', + '%s via Kanboard' => '%s vía Kanboard', + 'uploaded by: %s' => 'cargado por: %s', 'uploaded on: %s' => 'cargado en: %s', 'size: %s' => 'tamaño: %s', 'Burndown chart for "%s"' => 'Trabajo pendiente para "%s"', @@ -868,5 +868,8 @@ return array( 'Help on Sendgrid integration' => 'Ayuda sobre la integración con Sendgrid', 'Disable two factor authentication' => 'Desactivar la autenticación de dos factores', 'Do you really want to disable the two factor authentication for this user: "%s"?' => '¿Realmentes quieres desactuvar la autenticación de dos factores para este usuario: "%s?"', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); - diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index 9ca386d6b..15c919ed4 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', // 'Disable two factor authentication' => '', // 'Do you really want to disable the two factor authentication for this user: "%s"?' => '', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index c05dd689d..84a28207f 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -719,7 +719,7 @@ return array( 'is a child of' => 'est un enfant de', 'is a parent of' => 'est un parent de', 'targets milestone' => 'vise l\'étape importante', - 'is a milestone of' => 'est une étape importante de', + 'is a milestone of' => 'est une étape importante incluant', 'fixes' => 'corrige', 'is fixed by' => 'est corrigée par', 'This task' => 'Cette tâche', @@ -870,4 +870,8 @@ return array( 'Help on Sendgrid integration' => 'Aide sur l\'intégration avec Sendgrid', 'Disable two factor authentication' => 'Désactiver l\'authentification à deux facteurs', 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Voulez-vous vraiment désactiver l\'authentification à deux facteurs pour cet utilisateur : « %s » ?', + 'Edit a link' => 'Modifier un lien', + 'Start to type task title...' => 'Tappez le titre de la tâche...', + 'A task cannot be linked to itself' => 'Une tâche ne peut être liée à elle-même', + 'The exact same link already exists' => 'Un lien identique existe déjà', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index f07b691c2..26b1e132d 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', // 'Disable two factor authentication' => '', // 'Do you really want to disable the two factor authentication for this user: "%s"?' => '', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 0ccefe552..8736572e9 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', // 'Disable two factor authentication' => '', // 'Do you really want to disable the two factor authentication for this user: "%s"?' => '', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index 644dcfa76..fa047f698 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', // 'Disable two factor authentication' => '', // 'Do you really want to disable the two factor authentication for this user: "%s"?' => '', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php index e0285fdd5..eecdaa9fc 100644 --- a/app/Locale/nl_NL/translations.php +++ b/app/Locale/nl_NL/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', // 'Disable two factor authentication' => '', // 'Do you really want to disable the two factor authentication for this user: "%s"?' => '', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 39c92379e..a38717234 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', // 'Disable two factor authentication' => '', // 'Do you really want to disable the two factor authentication for this user: "%s"?' => '', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index 04d3138ff..3e1ddd139 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -868,4 +868,8 @@ return array( 'Help on Sendgrid integration' => 'Ajuda na integração do Sendgrid', 'Disable two factor authentication' => 'Desativar autenticação à dois fatores', 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Você deseja realmente desativar a autenticação à dois fatores para esse usuário: "%s"?', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index c119b5649..4787d2c3c 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', 'Disable two factor authentication' => 'Выключить двухфакторную авторизацию', 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Вы действительно хотите выключить двухфакторную авторизацию для пользователя "%s"?', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php index 7dcb6f990..b74be22ff 100644 --- a/app/Locale/sr_Latn_RS/translations.php +++ b/app/Locale/sr_Latn_RS/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', // 'Disable two factor authentication' => '', // 'Do you really want to disable the two factor authentication for this user: "%s"?' => '', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index f3d586965..f56369251 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', // 'Disable two factor authentication' => '', // 'Do you really want to disable the two factor authentication for this user: "%s"?' => '', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index 552e2f430..1e1d81d92 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', // 'Disable two factor authentication' => '', // 'Do you really want to disable the two factor authentication for this user: "%s"?' => '', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php index e4efe2583..5bd66e4a3 100644 --- a/app/Locale/tr_TR/translations.php +++ b/app/Locale/tr_TR/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', // 'Disable two factor authentication' => '', // 'Do you really want to disable the two factor authentication for this user: "%s"?' => '', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index a51a54d37..8a7c5e42a 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -868,4 +868,8 @@ return array( // 'Help on Sendgrid integration' => '', // 'Disable two factor authentication' => '', // 'Do you really want to disable the two factor authentication for this user: "%s"?' => '', + // 'Edit a link' => '', + // 'Start to type task title...' => '', + // 'A task cannot be linked to itself' => '', + // 'The exact same link already exists' => '', ); diff --git a/app/Model/TaskLink.php b/app/Model/TaskLink.php index 623913710..7b696afcd 100644 --- a/app/Model/TaskLink.php +++ b/app/Model/TaskLink.php @@ -34,6 +34,24 @@ class TaskLink extends Base return $this->db->table(self::TABLE)->eq('id', $task_link_id)->findOne(); } + /** + * Get the opposite task link (use the unique index task_has_links_unique) + * + * @access public + * @param array $task_link + * @return array + */ + public function getOppositeTaskLink(array $task_link) + { + $opposite_link_id = $this->link->getOppositeLinkId($task_link['link_id']); + + return $this->db->table(self::TABLE) + ->eq('opposite_task_id', $task_link['task_id']) + ->eq('task_id', $task_link['opposite_task_id']) + ->eq('link_id', $opposite_link_id) + ->findOne(); + } + /** * Get all links attached to a task * @@ -41,7 +59,7 @@ class TaskLink extends Base * @param integer $task_id Task id * @return array */ - public function getLinks($task_id) + public function getAll($task_id) { return $this->db ->table(self::TABLE) @@ -52,16 +70,46 @@ class TaskLink extends Base Task::TABLE.'.title', Task::TABLE.'.is_active', Task::TABLE.'.project_id', + Task::TABLE.'.time_spent AS task_time_spent', + Task::TABLE.'.time_estimated AS task_time_estimated', + Task::TABLE.'.owner_id AS task_assignee_id', + User::TABLE.'.username AS task_assignee_username', + User::TABLE.'.name AS task_assignee_name', Board::TABLE.'.title AS column_title' ) ->eq(self::TABLE.'.task_id', $task_id) ->join(Link::TABLE, 'id', 'link_id') ->join(Task::TABLE, 'id', 'opposite_task_id') ->join(Board::TABLE, 'id', 'column_id', Task::TABLE) + ->join(User::TABLE, 'id', 'owner_id', Task::TABLE) ->orderBy(Link::TABLE.'.id ASC, '.Board::TABLE.'.position ASC, '.Task::TABLE.'.is_active DESC, '.Task::TABLE.'.id', Table::SORT_ASC) ->findAll(); } + /** + * Get all links attached to a task grouped by label + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getAllGroupedByLabel($task_id) + { + $links = $this->getAll($task_id); + $result = array(); + + foreach ($links as $link) { + + if (! isset($result[$link['label']])) { + $result[$link['label']] = array(); + } + + $result[$link['label']][] = $link; + } + + return $result; + } + /** * Create a new link * @@ -69,31 +117,76 @@ class TaskLink extends Base * @param integer $task_id Task id * @param integer $opposite_task_id Opposite task id * @param integer $link_id Link id - * @return boolean + * @return integer Task link id */ public function create($task_id, $opposite_task_id, $link_id) { $this->db->startTransaction(); - // Create the original link + // Get opposite link + $opposite_link_id = $this->link->getOppositeLinkId($link_id); + + // Create the original task link $this->db->table(self::TABLE)->insert(array( 'task_id' => $task_id, 'opposite_task_id' => $opposite_task_id, 'link_id' => $link_id, )); - $link_id = $this->link->getOppositeLinkId($link_id); + $task_link_id = $this->db->getConnection()->getLastId(); - // Create the opposite link + // Create the opposite task link $this->db->table(self::TABLE)->insert(array( 'task_id' => $opposite_task_id, 'opposite_task_id' => $task_id, - 'link_id' => $link_id, + 'link_id' => $opposite_link_id, )); $this->db->closeTransaction(); - return true; + return $task_link_id; + } + + /** + * Update a task link + * + * @access public + * @param integer $task_link_id Task link id + * @param integer $task_id Task id + * @param integer $opposite_task_id Opposite task id + * @param integer $link_id Link id + * @return boolean + */ + public function update($task_link_id, $task_id, $opposite_task_id, $link_id) + { + $this->db->startTransaction(); + + // Get original task link + $task_link = $this->getById($task_link_id); + + // Find opposite task link + $opposite_task_link = $this->getOppositeTaskLink($task_link); + + // Get opposite link + $opposite_link_id = $this->link->getOppositeLinkId($link_id); + + // Update the original task link + $rs1 = $this->db->table(self::TABLE)->eq('id', $task_link_id)->update(array( + 'task_id' => $task_id, + 'opposite_task_id' => $opposite_task_id, + 'link_id' => $link_id, + )); + + // Update the opposite link + $rs2 = $this->db->table(self::TABLE)->eq('id', $opposite_task_link['id'])->update(array( + 'task_id' => $opposite_task_id, + 'opposite_task_id' => $task_id, + 'link_id' => $opposite_link_id, + )); + + $this->db->closeTransaction(); + + return $rs1 && $rs2; } /** @@ -123,6 +216,23 @@ class TaskLink extends Base return true; } + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Required('task_id', t('Field required')), + new Validators\Required('opposite_task_id', t('Field required')), + new Validators\Required('link_id', t('Field required')), + new Validators\NotEquals('opposite_task_id', 'task_id', t('A task cannot be linked to itself')), + new Validators\Exists('opposite_task_id', t('This linked task id doesn\'t exists'), $this->db->getConnection(), Task::TABLE, 'id') + ); + } + /** * Validate creation * @@ -132,11 +242,28 @@ class TaskLink extends Base */ public function validateCreation(array $values) { - $v = new Validator($values, array( - new Validators\Required('task_id', t('Field required')), - new Validators\Required('link_id', t('Field required')), - new Validators\Required('title', t('Field required')), - )); + $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('Field required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); return array( $v->execute(), diff --git a/app/Template/board/tasklinks.php b/app/Template/board/tasklinks.php index 9c4f52ca5..f934cff97 100644 --- a/app/Template/board/tasklinks.php +++ b/app/Template/board/tasklinks.php @@ -9,6 +9,9 @@ false, $link['is_active'] ? '' : 'task-link-closed' ) ?> + + [= $this->e($link['task_assignee_name'] ?: $link['task_assignee_username']) ?>] + diff --git a/app/Template/tasklink/create.php b/app/Template/tasklink/create.php index acf9d6d1c..3394271a3 100644 --- a/app/Template/tasklink/create.php +++ b/app/Template/tasklink/create.php @@ -5,7 +5,7 @@