diff options
54 files changed, 2175 insertions, 251 deletions
diff --git a/app/Controller/Board.php b/app/Controller/Board.php index 7d498f81..c5823328 100644 --- a/app/Controller/Board.php +++ b/app/Controller/Board.php @@ -130,12 +130,14 @@ class Board extends Base // Display the board with a specific layout $this->response->html($this->template->layout('board/public', array( 'project' => $project, - 'columns' => $this->board->get($project['id']), + 'swimlanes' => $this->board->getBoard($project['id']), 'categories' => $this->category->getList($project['id'], false), 'title' => $project['name'], 'no_layout' => true, 'not_editable' => true, 'board_public_refresh_interval' => $this->config->get('board_public_refresh_interval'), + 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'), + 'board_highlight_period' => $this->config->get('board_highlight_period'), ))); } @@ -188,7 +190,7 @@ class Board extends Base 'users' => $this->projectPermission->getMemberList($project['id'], true, true), 'projects' => $projects, 'project' => $project, - 'board' => $this->board->get($project['id']), + 'swimlanes' => $this->board->getBoard($project['id']), 'categories' => $this->category->getList($project['id'], true, true), 'title' => $project['name'], 'board_selector' => $board_selector, @@ -339,35 +341,38 @@ class Board extends Base { $project_id = $this->request->getIntegerParam('project_id'); - if ($project_id > 0 && $this->request->isAjax()) { + if (! $project_id || ! $this->request->isAjax()) { + return $this->response->status(403); + } - if (! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) { - $this->response->text('Forbidden', 403); - } + if (! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) { + $this->response->text('Forbidden', 403); + } - $values = $this->request->getJson(); + $values = $this->request->getJson(); - if ($this->taskPosition->movePosition($project_id, $values['task_id'], $values['column_id'], $values['position'])) { + $result =$this->taskPosition->movePosition( + $project_id, + $values['task_id'], + $values['column_id'], + $values['position'], + $values['swimlane_id'] + ); - $this->response->html( - $this->template->load('board/show', array( - 'project' => $this->project->getById($project_id), - 'board' => $this->board->get($project_id), - 'categories' => $this->category->getList($project_id, false), - 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'), - 'board_highlight_period' => $this->config->get('board_highlight_period'), - )), - 201 - ); - } - else { - - $this->response->status(400); - } - } - else { - $this->response->status(403); + if (! $result) { + return $this->response->status(400); } + + $this->response->html( + $this->template->load('board/show', array( + 'project' => $this->project->getById($project_id), + 'swimlanes' => $this->board->getBoard($project_id), + 'categories' => $this->category->getList($project_id, false), + 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'), + 'board_highlight_period' => $this->config->get('board_highlight_period'), + )), + 201 + ); } /** @@ -377,33 +382,30 @@ class Board extends Base */ public function check() { - if ($this->request->isAjax()) { - - $project_id = $this->request->getIntegerParam('project_id'); - $timestamp = $this->request->getIntegerParam('timestamp'); + if (! $this->request->isAjax()) { + return $this->response->status(403); + } - if ($project_id > 0 && ! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) { - $this->response->text('Forbidden', 403); - } + $project_id = $this->request->getIntegerParam('project_id'); + $timestamp = $this->request->getIntegerParam('timestamp'); - if ($this->project->isModifiedSince($project_id, $timestamp)) { - $this->response->html( - $this->template->load('board/show', array( - 'project' => $this->project->getById($project_id), - 'board' => $this->board->get($project_id), - 'categories' => $this->category->getList($project_id, false), - 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'), - 'board_highlight_period' => $this->config->get('board_highlight_period'), - )) - ); - } - else { - $this->response->status(304); - } + if (! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) { + $this->response->text('Forbidden', 403); } - else { - $this->response->status(403); + + if (! $this->project->isModifiedSince($project_id, $timestamp)) { + return $this->response->status(304); } + + $this->response->html( + $this->template->load('board/show', array( + 'project' => $this->project->getById($project_id), + 'swimlanes' => $this->board->getBoard($project_id), + 'categories' => $this->category->getList($project_id, false), + 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'), + 'board_highlight_period' => $this->config->get('board_highlight_period'), + )) + ); } /** diff --git a/app/Controller/Category.php b/app/Controller/Category.php index 27c0d9fc..b30608b7 100644 --- a/app/Controller/Category.php +++ b/app/Controller/Category.php @@ -14,7 +14,7 @@ class Category extends Base * Get the category (common method between actions) * * @access private - * @param $project_id + * @param integer $project_id * @return array */ private function getCategory($project_id) @@ -48,7 +48,7 @@ class Category extends Base } /** - * Validate and save a new project + * Validate and save a new category * * @access public */ diff --git a/app/Controller/Swimlane.php b/app/Controller/Swimlane.php new file mode 100644 index 00000000..f0920f60 --- /dev/null +++ b/app/Controller/Swimlane.php @@ -0,0 +1,256 @@ +<?php + +namespace Controller; + +use Model\Swimlane as SwimlaneModel; + +/** + * Swimlanes + * + * @package controller + * @author Frederic Guillot + */ +class Swimlane extends Base +{ + /** + * Get the swimlane (common method between actions) + * + * @access private + * @param integer $project_id + * @return array + */ + private function getSwimlane($project_id) + { + $swimlane = $this->swimlane->getById($this->request->getIntegerParam('swimlane_id')); + + if (! $swimlane) { + $this->session->flashError(t('Swimlane not found.')); + $this->response->redirect('?controller=swimlane&action=index&project_id='.$project_id); + } + + return $swimlane; + } + + /** + * List of swimlanes for a given project + * + * @access public + */ + public function index(array $values = array(), array $errors = array()) + { + $project = $this->getProjectManagement(); + + $this->response->html($this->projectLayout('swimlane/index', array( + 'default_swimlane' => $this->swimlane->getDefault($project['id']), + 'active_swimlanes' => $this->swimlane->getAllByStatus($project['id'], SwimlaneModel::ACTIVE), + 'inactive_swimlanes' => $this->swimlane->getAllByStatus($project['id'], SwimlaneModel::INACTIVE), + 'values' => $values + array('project_id' => $project['id']), + 'errors' => $errors, + 'project' => $project, + 'title' => t('Swimlanes') + ))); + } + + /** + * Validate and save a new swimlane + * + * @access public + */ + public function save() + { + $project = $this->getProjectManagement(); + + $values = $this->request->getValues(); + list($valid, $errors) = $this->swimlane->validateCreation($values); + + if ($valid) { + + if ($this->swimlane->create($project['id'], $values['name'])) { + $this->session->flash(t('Your swimlane have been created successfully.')); + $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + } + else { + $this->session->flashError(t('Unable to create your swimlane.')); + } + } + + $this->index($values, $errors); + } + + /** + * Change the default swimlane + * + * @access public + */ + public function change() + { + $project = $this->getProjectManagement(); + + $values = $this->request->getValues(); + list($valid, $errors) = $this->swimlane->validateDefaultModification($values); + + if ($valid) { + + if ($this->swimlane->updateDefault($values)) { + $this->session->flash(t('The default swimlane have been updated successfully.')); + $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + } + else { + $this->session->flashError(t('Unable to update this swimlane.')); + } + } + + $this->index(); + } + + /** + * Edit a swimlane (display the form) + * + * @access public + */ + public function edit(array $values = array(), array $errors = array()) + { + $project = $this->getProjectManagement(); + $swimlane = $this->getSwimlane($project['id']); + + $this->response->html($this->projectLayout('swimlane/edit', array( + 'values' => empty($values) ? $swimlane : $values, + 'errors' => $errors, + 'project' => $project, + 'title' => t('Swimlanes') + ))); + } + + /** + * Edit a swimlane (validate the form and update the database) + * + * @access public + */ + public function update() + { + $project = $this->getProjectManagement(); + + $values = $this->request->getValues(); + list($valid, $errors) = $this->swimlane->validateModification($values); + + if ($valid) { + + if ($this->swimlane->rename($values['id'], $values['name'])) { + $this->session->flash(t('Swimlane updated successfully.')); + $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + } + else { + $this->session->flashError(t('Unable to update this swimlane.')); + } + } + + $this->edit($values, $errors); + } + + /** + * Confirmation dialog before removing a swimlane + * + * @access public + */ + public function confirm() + { + $project = $this->getProjectManagement(); + $swimlane = $this->getSwimlane($project['id']); + + $this->response->html($this->projectLayout('swimlane/remove', array( + 'project' => $project, + 'swimlane' => $swimlane, + 'title' => t('Remove a swimlane') + ))); + } + + /** + * Remove a swimlane + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProjectManagement(); + $swimlane_id = $this->request->getIntegerParam('swimlane_id'); + + if ($this->swimlane->remove($project['id'], $swimlane_id)) { + $this->session->flash(t('Swimlane removed successfully.')); + } else { + $this->session->flashError(t('Unable to remove this swimlane.')); + } + + $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + } + + /** + * Disable a swimlane + * + * @access public + */ + public function disable() + { + $this->checkCSRFParam(); + $project = $this->getProjectManagement(); + $swimlane_id = $this->request->getIntegerParam('swimlane_id'); + + if ($this->swimlane->disable($project['id'], $swimlane_id)) { + $this->session->flash(t('Swimlane updated successfully.')); + } else { + $this->session->flashError(t('Unable to update this swimlane.')); + } + + $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + } + + /** + * Enable a swimlane + * + * @access public + */ + public function enable() + { + $this->checkCSRFParam(); + $project = $this->getProjectManagement(); + $swimlane_id = $this->request->getIntegerParam('swimlane_id'); + + if ($this->swimlane->enable($project['id'], $swimlane_id)) { + $this->session->flash(t('Swimlane updated successfully.')); + } else { + $this->session->flashError(t('Unable to update this swimlane.')); + } + + $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + } + + /** + * Move up a swimlane + * + * @access public + */ + public function moveup() + { + $this->checkCSRFParam(); + $project = $this->getProjectManagement(); + $swimlane_id = $this->request->getIntegerParam('swimlane_id'); + + $this->swimlane->moveUp($project['id'], $swimlane_id); + $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + } + + /** + * Move down a swimlane + * + * @access public + */ + public function movedown() + { + $this->checkCSRFParam(); + $project = $this->getProjectManagement(); + $swimlane_id = $this->request->getIntegerParam('swimlane_id'); + + $this->swimlane->moveDown($project['id'], $swimlane_id); + $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']); + } +} diff --git a/app/Controller/Task.php b/app/Controller/Task.php index 8d38317c..33b4b039 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -94,6 +94,7 @@ class Task extends Base if (empty($values)) { $values = array( + 'swimlane_id' => $this->request->getIntegerParam('swimlane_id'), 'column_id' => $this->request->getIntegerParam('column_id'), 'color_id' => $this->request->getStringParam('color_id'), 'owner_id' => $this->request->getIntegerParam('owner_id'), diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index bcc1962c..1636ccb2 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -603,4 +603,25 @@ return array( // 'Nothing to preview...' => '', // 'Preview' => '', // 'Write' => '', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index 2be6497e..6e6301ec 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -603,4 +603,25 @@ return array( 'Nothing to preview...' => 'Nichts in der Vorschau anzuzeigen ...', 'Preview' => 'Vorschau', 'Write' => 'Ändern', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index 1fbc886a..6e37eead 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -603,4 +603,25 @@ return array( // 'Nothing to preview...' => '', // 'Preview' => '', // 'Write' => '', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index 20232584..524bbee4 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -603,4 +603,25 @@ return array( // 'Nothing to preview...' => '', // 'Preview' => '', // 'Write' => '', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index 28c999a5..9ae853bf 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -603,4 +603,25 @@ return array( 'Nothing to preview...' => 'Rien à prévisualiser...', 'Preview' => 'Prévisualiser', 'Write' => 'Écrire', + 'Active swimlanes' => 'Swimlanes actives', + 'Add a new swimlane' => 'Ajouter une nouvelle swimlane', + 'Change default swimlane' => 'Modifier la swimlane par défaut', + 'Default swimlane' => 'Swimlane par défaut', + 'Do you really want to remove this swimlane: "%s"?' => 'Voulez-vous vraiment supprimer cette swimlane : « %s » ?', + 'Inactive swimlanes' => 'Swimlanes inactives', + 'set manager' => 'mettre gérant', + 'set user' => 'mettre utilisateur', + 'Remove a swimlane' => 'Supprimer une swimlane', + 'Rename' => 'Renommer', + 'Show default swimlane' => 'Afficher la swimlane par défaut', + 'Swimlane modification for the project "%s"' => 'Modification d\'une swimlane pour le projet « %s »', + 'Swimlane not found.' => 'Cette swimlane est introuvable.', + 'Swimlane removed successfully.' => 'Swimlane supprimée avec succès.', + 'Swimlanes' => 'Swimlanes', + 'Swimlane updated successfully.' => 'Swimlane mise à jour avec succès.', + 'The default swimlane have been updated successfully.' => 'La swimlane par défaut a été mise à jour avec succès.', + 'Unable to create your swimlane.' => 'Impossible de créer votre swimlane.', + 'Unable to remove this swimlane.' => 'Impossible de supprimer cette swimlane.', + 'Unable to update this swimlane.' => 'Impossible de mettre à jour cette swimlane.', + 'Your swimlane have been created successfully.' => 'Votre swimlane a été créée avec succès.', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index 0dbc962d..352cd1ba 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -603,4 +603,25 @@ return array( 'Nothing to preview...' => 'Nincs semmi az előnézetben ...', 'Preview' => 'Előnézet', 'Write' => 'Írd', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 0895fa53..c07dd042 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -603,4 +603,25 @@ return array( // 'Nothing to preview...' => '', // 'Preview' => '', // 'Write' => '', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index b4440a55..3811243a 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -603,4 +603,25 @@ return array( // 'Nothing to preview...' => '', // 'Preview' => '', // 'Write' => '', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index a5045f8f..2d1572a1 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -603,4 +603,25 @@ return array( // 'Nothing to preview...' => '', // 'Preview' => '', // 'Write' => '', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index 0c8e1adc..5b9316b3 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -603,4 +603,25 @@ return array( 'Nothing to preview...' => 'Nada para pré-visualizar...', 'Preview' => 'Pré-visualizar', // 'Write' => '', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 336d4af8..457c803d 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -603,4 +603,25 @@ return array( // 'Nothing to preview...' => '', // 'Preview' => '', // 'Write' => '', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index bc64dbd0..33e45341 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -603,4 +603,25 @@ return array( // 'Nothing to preview...' => '', // 'Preview' => '', // 'Write' => '', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index 11e67ca2..5f65f572 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -603,4 +603,25 @@ return array( // 'Nothing to preview...' => '', // 'Preview' => '', // 'Write' => '', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index cf41049c..ce8767d4 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -603,4 +603,25 @@ return array( 'Nothing to preview...' => '没有需要预览的内容', 'Preview' => '预览', 'Write' => '书写', + // 'Active swimlanes' => '', + // 'Add a new swimlane' => '', + // 'Change default swimlane' => '', + // 'Default swimlane' => '', + // 'Do you really want to remove this swimlane: "%s"?' => '', + // 'Inactive swimlanes' => '', + // 'set manager' => '', + // 'set user' => '', + // 'Remove a swimlane' => '', + // 'Rename' => '', + // 'Show default swimlane' => '', + // 'Swimlane modification for the project "%s"' => '', + // 'Swimlane not found.' => '', + // 'Swimlane removed successfully.' => '', + // 'Swimlanes' => '', + // 'Swimlane updated successfully.' => '', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + // 'Your swimlane have been created successfully.' => '', ); diff --git a/app/Model/Board.php b/app/Model/Board.php index 9ba2e066..8208b99d 100644 --- a/app/Model/Board.php +++ b/app/Model/Board.php @@ -227,29 +227,30 @@ class Board extends Base } /** - * Get all columns and tasks for a given project + * Get all tasks sorted by columns and swimlanes * * @access public * @param integer $project_id Project id * @return array */ - public function get($project_id) + public function getBoard($project_id) { + $swimlanes = $this->swimlane->getSwimlanes($project_id); $columns = $this->getColumns($project_id); - $tasks = $this->taskFinder->getTasksOnBoard($project_id); + $nb_columns = count($columns); - foreach ($columns as &$column) { + foreach ($swimlanes as &$swimlane) { - $column['tasks'] = array(); - - foreach ($tasks as &$task) { - if ($task['column_id'] == $column['id']) { - $column['tasks'][] = $task; - } + foreach ($columns as &$column) { + $column['tasks'] = $this->taskFinder->getTasksByColumnAndSwimlane($project_id, $column['id'], $swimlane['id']); + $column['nb_tasks'] = count($column['tasks']); } + + $swimlane['columns'] = $columns; + $swimlane['nb_columns'] = $nb_columns; } - return $columns; + return $swimlanes; } /** diff --git a/app/Model/Swimlane.php b/app/Model/Swimlane.php new file mode 100644 index 00000000..ffcfb474 --- /dev/null +++ b/app/Model/Swimlane.php @@ -0,0 +1,474 @@ +<?php + +namespace Model; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Swimlanes + * + * @package model + * @author Frederic Guillot + */ +class Swimlane extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'swimlanes'; + + /** + * Value for active swimlanes + * + * @var integer + */ + const ACTIVE = 1; + + /** + * Value for inactive swimlanes + * + * @var integer + */ + const INACTIVE = 0; + + /** + * Get a swimlane by the id + * + * @access public + * @param integer $swimlane_id Swimlane id + * @return array + */ + public function getById($swimlane_id) + { + return $this->db->table(self::TABLE)->eq('id', $swimlane_id)->findOne(); + } + + /** + * Get the swimlane name by the id + * + * @access public + * @param integer $swimlane_id Swimlane id + * @return string + */ + public function getNameById($swimlane_id) + { + return $this->db->table(self::TABLE)->eq('id', $swimlane_id)->findOneColumn('name') ?: ''; + } + + /** + * Get a swimlane id by the project and the name + * + * @access public + * @param integer $project_id Project id + * @param string $name Name + * @return integer + */ + public function getIdByName($project_id, $name) + { + return (int) $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('name', $name) + ->findOneColumn('id'); + } + + /** + * Get default swimlane properties + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getDefault($project_id) + { + return $this->db->table(Project::TABLE) + ->eq('id', $project_id) + ->columns('id', 'default_swimlane', 'show_default_swimlane') + ->findOne(); + } + + /** + * Get all swimlanes for a given project + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getAll($project_id) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->orderBy('position', 'asc') + ->findAll(); + } + + /** + * Get the list of swimlanes by status + * + * @access public + * @param integer $project_id Project id + * @param integer $status Status + * @return array + */ + public function getAllByStatus($project_id, $status = self::ACTIVE) + { + $query = $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('is_active', $status); + + if ($status == self::ACTIVE) { + $query->asc('position'); + } + else { + $query->asc('name'); + } + + return $query->findAll(); + } + + /** + * Get active swimlanes + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getSwimlanes($project_id) + { + $swimlanes = $this->db->table(self::TABLE) + ->columns('id', 'name') + ->eq('project_id', $project_id) + ->eq('is_active', self::ACTIVE) + ->orderBy('position', 'asc') + ->findAll(); + + $default_swimlane = $this->db->table(Project::TABLE) + ->eq('id', $project_id) + ->eq('show_default_swimlane', 1) + ->findOneColumn('default_swimlane'); + + if ($default_swimlane) { + array_unshift($swimlanes, array('id' => 0, 'name' => $default_swimlane)); + } + + return $swimlanes; + } + + /** + * Add a new swimlane + * + * @access public + * @param integer $project_id + * @param string $name + * @return bool + */ + public function create($project_id, $name) + { + return $this->persist(self::TABLE, array( + 'project_id' => $project_id, + 'name' => $name, + 'position' => $this->getLastPosition($project_id), + )); + } + + /** + * Rename a swimlane + * + * @access public + * @param integer $swimlane_id Swimlane id + * @param string $name Swimlane name + * @return bool + */ + public function rename($swimlane_id, $name) + { + return $this->db->table(self::TABLE) + ->eq('id', $swimlane_id) + ->update(array('name' => $name)); + } + + /** + * Update the default swimlane + * + * @access public + * @param array $values Form values + * @return bool + */ + public function updateDefault(array $values) + { + return $this->db + ->table(Project::TABLE) + ->eq('id', $values['id']) + ->update(array( + 'default_swimlane' => $values['default_swimlane'], + 'show_default_swimlane' => $values['show_default_swimlane'], + )); + } + + /** + * Get the last position of a swimlane + * + * @access public + * @param integer $project_id + * @return bool + */ + public function getLastPosition($project_id) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('is_active', 1) + ->count() + 1; + } + + /** + * Disable a swimlane + * + * @access public + * @param integer $project_id Project id + * @param integer $swimlane_id Swimlane id + * @return bool + */ + public function disable($project_id, $swimlane_id) + { + $result = $this->db + ->table(self::TABLE) + ->eq('id', $swimlane_id) + ->update(array( + 'is_active' => self::INACTIVE, + 'position' => 0, + )); + + if ($result) { + // Re-order positions + $this->updatePositions($project_id); + } + + return $result; + } + + /** + * Enable a swimlane + * + * @access public + * @param integer $project_id Project id + * @param integer $swimlane_id Swimlane id + * @return bool + */ + public function enable($project_id, $swimlane_id) + { + return $this->db + ->table(self::TABLE) + ->eq('id', $swimlane_id) + ->update(array( + 'is_active' => self::ACTIVE, + 'position' => $this->getLastPosition($project_id), + )); + } + + /** + * Remove a swimlane + * + * @access public + * @param integer $project_id Project id + * @param integer $swimlane_id Swimlane id + * @return bool + */ + public function remove($project_id, $swimlane_id) + { + $this->db->startTransaction(); + + // Tasks should not be assigned anymore to this swimlane + $this->db->table(Task::TABLE)->eq('swimlane_id', $swimlane_id)->update(array('swimlane_id' => 0)); + + if (! $this->db->table(self::TABLE)->eq('id', $swimlane_id)->remove()) { + $this->db->cancelTransaction(); + return false; + } + + // Re-order positions + $this->updatePositions($project_id); + + $this->db->closeTransaction(); + + return true; + } + + /** + * Update swimlane positions after disabling or removing a swimlane + * + * @access public + * @param integer $project_id Project id + * @return boolean + */ + public function updatePositions($project_id) + { + $position = 0; + $swimlanes = $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('is_active', 1) + ->asc('position') + ->findAllByColumn('id'); + + if (! $swimlanes) { + return false; + } + + foreach ($swimlanes as $swimlane_id) { + $this->db->table(self::TABLE) + ->eq('id', $swimlane_id) + ->update(array('position' => ++$position)); + } + + return true; + } + + /** + * Move a swimlane down, increment the position value + * + * @access public + * @param integer $project_id Project id + * @param integer $swimlane_id Swimlane id + * @return boolean + */ + public function moveDown($project_id, $swimlane_id) + { + $swimlanes = $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('is_active', self::ACTIVE) + ->asc('position') + ->listing('id', 'position'); + + $positions = array_flip($swimlanes); + + if (isset($swimlanes[$swimlane_id]) && $swimlanes[$swimlane_id] < count($swimlanes)) { + + $position = ++$swimlanes[$swimlane_id]; + $swimlanes[$positions[$position]]--; + + $this->db->startTransaction(); + $this->db->table(self::TABLE)->eq('id', $swimlane_id)->update(array('position' => $position)); + $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $swimlanes[$positions[$position]])); + $this->db->closeTransaction(); + + return true; + } + + return false; + } + + /** + * Move a swimlane up, decrement the position value + * + * @access public + * @param integer $project_id Project id + * @param integer $swimlane_id Swimlane id + * @return boolean + */ + public function moveUp($project_id, $swimlane_id) + { + $swimlanes = $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('is_active', self::ACTIVE) + ->asc('position') + ->listing('id', 'position'); + + $positions = array_flip($swimlanes); + + if (isset($swimlanes[$swimlane_id]) && $swimlanes[$swimlane_id] > 1) { + + $position = --$swimlanes[$swimlane_id]; + $swimlanes[$positions[$position]]++; + + $this->db->startTransaction(); + $this->db->table(self::TABLE)->eq('id', $swimlane_id)->update(array('position' => $position)); + $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $swimlanes[$positions[$position]])); + $this->db->closeTransaction(); + + return true; + } + + return false; + } + + /** + * 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) + { + $rules = array( + new Validators\Required('project_id', t('The project id is required')), + new Validators\Required('name', t('The name is required')), + ); + + $v = new Validator($values, array_merge($rules, $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')), + new Validators\Required('name', t('The name is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate default swimlane modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateDefaultModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + new Validators\Required('default_swimlane', t('The name 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\Integer('id', t('The id must be an integer')), + new Validators\Integer('project_id', t('The project id must be an integer')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50) + ); + } +} diff --git a/app/Model/Task.php b/app/Model/Task.php index a745f30f..3cd03741 100644 --- a/app/Model/Task.php +++ b/app/Model/Task.php @@ -32,6 +32,7 @@ class Task extends Base */ const EVENT_MOVE_COLUMN = 'task.move.column'; const EVENT_MOVE_POSITION = 'task.move.position'; + const EVENT_MOVE_SWIMLANE = 'task.move.swimlane'; const EVENT_UPDATE = 'task.update'; const EVENT_CREATE = 'task.create'; const EVENT_CLOSE = 'task.close'; diff --git a/app/Model/TaskCreation.php b/app/Model/TaskCreation.php index 320bcb93..de9f7ce1 100644 --- a/app/Model/TaskCreation.php +++ b/app/Model/TaskCreation.php @@ -39,7 +39,7 @@ class TaskCreation extends Base { $this->dateParser->convert($values, array('date_due', 'date_started')); $this->removeFields($values, array('another_task')); - $this->resetFields($values, array('owner_id', 'owner_id', 'date_due', 'score', 'category_id', 'time_estimated')); + $this->resetFields($values, array('owner_id', 'swimlane_id', 'date_due', 'score', 'category_id', 'time_estimated')); if (empty($values['column_id'])) { $values['column_id'] = $this->board->getFirstColumn($values['project_id']); diff --git a/app/Model/TaskDuplication.php b/app/Model/TaskDuplication.php index ab7a57f1..2410213b 100644 --- a/app/Model/TaskDuplication.php +++ b/app/Model/TaskDuplication.php @@ -27,6 +27,7 @@ class TaskDuplication extends Base 'score', 'category_id', 'time_estimated', + 'swimlane_id', ); /** @@ -79,6 +80,7 @@ class TaskDuplication extends Base $values['position'] = $this->taskFinder->countByColumnId($project_id, $values['column_id']) + 1; $values['owner_id'] = $task['owner_id']; $values['category_id'] = $task['category_id']; + $values['swimlane_id'] = $task['swimlane_id']; $this->checkDestinationProjectValues($values); @@ -100,8 +102,18 @@ class TaskDuplication extends Base // Check if the category exists for the destination project if ($values['category_id'] > 0) { - $category_name = $this->category->getNameById($values['category_id']); - $values['category_id'] = $this->category->getIdByName($values['project_id'], $category_name); + $values['category_id'] = $this->category->getIdByName( + $values['project_id'], + $this->category->getNameById($values['category_id']) + ); + } + + // Check if the swimlane exists for the destination project + if ($values['swimlane_id'] > 0) { + $values['swimlane_id'] = $this->swimlane->getIdByName( + $values['project_id'], + $this->swimlane->getNameById($values['swimlane_id']) + ); } } diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php index 0e581025..117edae8 100644 --- a/app/Model/TaskFinder.php +++ b/app/Model/TaskFinder.php @@ -38,6 +38,7 @@ class TaskFinder extends Base 'tasks.color_id', 'tasks.project_id', 'tasks.column_id', + 'tasks.swimlane_id', 'tasks.owner_id', 'tasks.creator_id', 'tasks.position', @@ -54,13 +55,17 @@ class TaskFinder extends Base * Get all tasks shown on the board (sorted by position) * * @access public - * @param integer $project_id Project id + * @param integer $project_id Project id + * @param integer $column_id Column id + * @param integer $swimlane_id Swimlane id * @return array */ - public function getTasksOnBoard($project_id) + public function getTasksByColumnAndSwimlane($project_id, $column_id, $swimlane_id = 0) { return $this->getQuery() ->eq('project_id', $project_id) + ->eq('column_id', $column_id) + ->eq('swimlane_id', $swimlane_id) ->eq('is_active', Task::STATUS_OPEN) ->asc('tasks.position') ->findAll(); @@ -167,6 +172,7 @@ class TaskFinder extends Base tasks.is_active, tasks.score, tasks.category_id, + tasks.swimlane_id, project_has_categories.name AS category_name, projects.name AS project_name, columns.title AS column_title, diff --git a/app/Model/TaskPosition.php b/app/Model/TaskPosition.php index c23bc3b5..9a9642e5 100644 --- a/app/Model/TaskPosition.php +++ b/app/Model/TaskPosition.php @@ -18,20 +18,25 @@ class TaskPosition extends Base * @param integer $task_id Task id * @param integer $column_id Column id * @param integer $position Position (must be >= 1) + * @param integer $swimlane_id Swimlane id * @return boolean */ - public function movePosition($project_id, $task_id, $column_id, $position) + public function movePosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0) { $original_task = $this->taskFinder->getById($task_id); - $positions = $this->calculatePositions($project_id, $task_id, $column_id, $position); - if ($positions === false || ! $this->savePositions($positions)) { - return false; - } + $result = $this->calculateAndSave($project_id, $task_id, $column_id, $position, $swimlane_id); - $this->fireEvents($original_task, $column_id, $position); + if ($result) { - return true; + if ($original_task['swimlane_id'] != $swimlane_id) { + $this->calculateAndSave($project_id, 0, $column_id, 1, $original_task['swimlane_id']); + } + + $this->fireEvents($original_task, $column_id, $position, $swimlane_id); + } + + return $result; } /** @@ -42,9 +47,10 @@ class TaskPosition extends Base * @param integer $task_id Task id * @param integer $column_id Column id * @param integer $position Position (must be >= 1) + * @param integer $swimlane_id Swimlane id * @return array|boolean */ - public function calculatePositions($project_id, $task_id, $column_id, $position) + public function calculatePositions($project_id, $task_id, $column_id, $position, $swimlane_id = 0) { // The position can't be lower than 1 if ($position < 1) { @@ -59,6 +65,7 @@ class TaskPosition extends Base $columns[$board_column_id] = $this->db->table(Task::TABLE) ->eq('is_active', 1) + ->eq('swimlane_id', $swimlane_id) ->eq('project_id', $project_id) ->eq('column_id', $board_column_id) ->neq('id', $task_id) @@ -72,7 +79,9 @@ class TaskPosition extends Base } // We put our task to the new position - array_splice($columns[$column_id], $position - 1, 0, $task_id); + if ($task_id) { + array_splice($columns[$column_id], $position - 1, 0, $task_id); + } return $columns; } @@ -84,9 +93,9 @@ class TaskPosition extends Base * @param array $columns Sorted tasks * @return boolean */ - private function savePositions(array $columns) + private function savePositions(array $columns, $swimlane_id) { - return $this->db->transaction(function ($db) use ($columns) { + return $this->db->transaction(function ($db) use ($columns, $swimlane_id) { foreach ($columns as $column_id => $column) { @@ -96,7 +105,8 @@ class TaskPosition extends Base $result = $db->table(Task::TABLE)->eq('id', $task_id)->update(array( 'position' => $position, - 'column_id' => $column_id + 'column_id' => $column_id, + 'swimlane_id' => $swimlane_id, )); if (! $result) { @@ -112,25 +122,52 @@ class TaskPosition extends Base /** * Fire events * - * @access public + * @access private * @param array $task * @param integer $new_column_id * @param integer $new_position + * @param integer $new_swimlane_id */ - public function fireEvents(array $task, $new_column_id, $new_position) + private function fireEvents(array $task, $new_column_id, $new_position, $new_swimlane_id) { $event_data = array( 'task_id' => $task['id'], 'project_id' => $task['project_id'], 'position' => $new_position, 'column_id' => $new_column_id, + 'swimlane_id' => $new_swimlane_id, ); - if ($task['column_id'] != $new_column_id) { + if ($task['swimlane_id'] != $new_swimlane_id) { + $this->event->trigger(Task::EVENT_MOVE_SWIMLANE, $event_data); + } + else if ($task['column_id'] != $new_column_id) { $this->event->trigger(Task::EVENT_MOVE_COLUMN, $event_data); } else if ($task['position'] != $new_position) { $this->event->trigger(Task::EVENT_MOVE_POSITION, $event_data); } } + + /** + * Calculate the new position of all tasks + * + * @access private + * @param integer $project_id Project id + * @param integer $task_id Task id + * @param integer $column_id Column id + * @param integer $position Position (must be >= 1) + * @param integer $swimlane_id Swimlane id + * @return boolean + */ + private function calculateAndSave($project_id, $task_id, $column_id, $position, $swimlane_id) + { + $positions = $this->calculatePositions($project_id, $task_id, $column_id, $position, $swimlane_id); + + if ($positions === false || ! $this->savePositions($positions, $swimlane_id)) { + return false; + } + + return true; + } } diff --git a/app/Model/TaskValidator.php b/app/Model/TaskValidator.php index ecaf0902..ae21ca28 100644 --- a/app/Model/TaskValidator.php +++ b/app/Model/TaskValidator.php @@ -29,6 +29,7 @@ class TaskValidator extends Base new Validators\Integer('creator_id', t('This value must be an integer')), new Validators\Integer('score', t('This value must be an integer')), new Validators\Integer('category_id', t('This value must be an integer')), + new Validators\Integer('swimlane_id', t('This value must be an integer')), new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200), new Validators\Date('date_due', t('Invalid date'), $this->dateParser->getDateFormats()), new Validators\Date('date_started', t('Invalid date'), $this->dateParser->getDateFormats()), diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 32953656..7fc8da6a 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -5,7 +5,27 @@ namespace Schema; use PDO; use Core\Security; -const VERSION = 37; +const VERSION = 38; + +function version_38($pdo) +{ + $pdo->exec(" + CREATE TABLE swimlanes ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(200) NOT NULL, + position INT DEFAULT 1, + is_active INT DEFAULT 1, + project_id INT, + PRIMARY KEY(id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE (name, project_id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec('ALTER TABLE tasks ADD COLUMN swimlane_id INT DEFAULT 0'); + $pdo->exec("ALTER TABLE projects ADD COLUMN default_swimlane VARCHAR(200) DEFAULT '".t('Default swimlane')."'"); + $pdo->exec("ALTER TABLE projects ADD COLUMN show_default_swimlane INT DEFAULT 1"); +} function version_37($pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index 8f114616..fd68d9c3 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -5,7 +5,26 @@ namespace Schema; use PDO; use Core\Security; -const VERSION = 18; +const VERSION = 19; + +function version_19($pdo) +{ + $pdo->exec(" + CREATE TABLE swimlanes ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + position INTEGER DEFAULT 1, + is_active BOOLEAN DEFAULT '1', + project_id INTEGER, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE (name, project_id) + ) + "); + + $pdo->exec('ALTER TABLE tasks ADD COLUMN swimlane_id INTEGER DEFAULT 0'); + $pdo->exec("ALTER TABLE projects ADD COLUMN default_swimlane VARCHAR(200) DEFAULT '".t('Default swimlane')."'"); + $pdo->exec("ALTER TABLE projects ADD COLUMN show_default_swimlane BOOLEAN DEFAULT '1'"); +} function version_18($pdo) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index 8efe8d30..cd319241 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -5,7 +5,26 @@ namespace Schema; use Core\Security; use PDO; -const VERSION = 36; +const VERSION = 37; + +function version_37($pdo) +{ + $pdo->exec(" + CREATE TABLE swimlanes ( + id INTEGER PRIMARY KEY, + name TEXT, + position INTEGER DEFAULT 1, + is_active INTEGER DEFAULT 1, + project_id INTEGER, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE (name, project_id) + ) + "); + + $pdo->exec('ALTER TABLE tasks ADD COLUMN swimlane_id INTEGER DEFAULT 0'); + $pdo->exec("ALTER TABLE projects ADD COLUMN default_swimlane TEXT DEFAULT '".t('Default swimlane')."'"); + $pdo->exec("ALTER TABLE projects ADD COLUMN show_default_swimlane INTEGER DEFAULT 1"); +} function version_36($pdo) { @@ -462,7 +481,7 @@ function version_1($pdo) $pdo->exec(" CREATE TABLE tasks ( id INTEGER PRIMARY KEY, - title TEXT NOT NULL, + title TEXT NOCASE NOT NULL, description TEXT, date_creation INTEGER, color_id TEXT, diff --git a/app/Template/board/edit.php b/app/Template/board/edit.php index d7086504..a44abcc8 100644 --- a/app/Template/board/edit.php +++ b/app/Template/board/edit.php @@ -16,7 +16,7 @@ </tr> <?php foreach ($columns as $column): ?> <tr> - <td><?= Helper\form_label(++$i, 'title['.$column['id'].']', array('title="column_id='.$column['id'].'"')) ?></td> + <td><?= Helper\form_label('#'.++$i, 'title['.$column['id'].']', array('title="column_id='.$column['id'].'"')) ?></td> <td><?= Helper\form_text('title['.$column['id'].']', $values, $errors, array('required')) ?></td> <td><?= Helper\form_number('task_limit['.$column['id'].']', $values, $errors, array('placeholder="'.t('limit').'"')) ?></td> <td> diff --git a/app/Template/board/index.php b/app/Template/board/index.php index 21040373..53c662a0 100644 --- a/app/Template/board/index.php +++ b/app/Template/board/index.php @@ -6,16 +6,12 @@ 'project' => $project, )) ?> - <?php if (empty($board)): ?> - <p class="alert alert-error"><?= t('There is no column in your project!') ?></p> - <?php else: ?> - <?= Helper\template('board/show', array( - 'project' => $project, - 'board' => $board, - 'categories' => $categories, - 'board_private_refresh_interval' => $board_private_refresh_interval, - 'board_highlight_period' => $board_highlight_period, - )) ?> - <?php endif ?> + <?= Helper\template('board/show', array( + 'project' => $project, + 'swimlanes' => $swimlanes, + 'categories' => $categories, + 'board_private_refresh_interval' => $board_private_refresh_interval, + 'board_highlight_period' => $board_highlight_period, + )) ?> </section> diff --git a/app/Template/board/public.php b/app/Template/board/public.php index 090d617b..0544f392 100644 --- a/app/Template/board/public.php +++ b/app/Template/board/public.php @@ -1,39 +1,12 @@ <section id="main" class="public-board"> - <?php if (empty($columns)): ?> - <p class="alert alert-error"><?= t('There is no column in your project!') ?></p> - <?php else: ?> - <table id="board"> - <tr> - <?php $column_with = round(100 / count($columns), 2); ?> - <?php foreach ($columns as $column): ?> - <th width="<?= $column_with ?>%"> - <?= Helper\escape($column['title']) ?> - <?php if ($column['task_limit']): ?> - <span title="<?= t('Task limit') ?>" class="task-limit">(<?= Helper\escape(count($column['tasks']).'/'.$column['task_limit']) ?>)</span> - <?php endif ?> - </th> - <?php endforeach ?> - </tr> - <tr> - <?php foreach ($columns as $column): ?> - <td class="column <?= $column['task_limit'] && count($column['tasks']) > $column['task_limit'] ? 'task-limit-warning' : '' ?>"> - <?php foreach ($column['tasks'] as $task): ?> - <div class="task-board task-<?= $task['color_id'] ?>"> - - <?= Helper\template('board/task', array( - 'task' => $task, - 'categories' => $categories, - 'not_editable' => true, - 'project' => $project - )) ?> - - </div> - <?php endforeach ?> - </td> - <?php endforeach ?> - </tr> - </table> - <?php endif ?> + <?= Helper\template('board/show', array( + 'project' => $project, + 'swimlanes' => $swimlanes, + 'categories' => $categories, + 'board_private_refresh_interval' => $board_private_refresh_interval, + 'board_highlight_period' => $board_highlight_period, + 'not_editable' => true, + )) ?> </section>
\ No newline at end of file diff --git a/app/Template/board/show.php b/app/Template/board/show.php index 142969c9..7df19bc1 100644 --- a/app/Template/board/show.php +++ b/app/Template/board/show.php @@ -1,51 +1,28 @@ -<table id="board" - data-project-id="<?= $project['id'] ?>" - data-check-interval="<?= $board_private_refresh_interval ?>" - data-save-url="<?= Helper\u('board', 'save', array('project_id' => $project['id'])) ?>" - data-check-url="<?= Helper\u('board', 'check', array('project_id' => $project['id'], 'timestamp' => time())) ?>" -> -<tr> - <?php $column_with = round(100 / count($board), 2); ?> - <?php foreach ($board as $column): ?> - <th width="<?= $column_with ?>%"> - <div class="board-add-icon"> - <?= Helper\a('+', 'task', 'create', array('project_id' => $column['project_id'], 'column_id' => $column['id']), false, 'task-creation-popover', t('Add a new task')) ?> - </div> - <?= Helper\escape($column['title']) ?> - <?php if ($column['task_limit']): ?> - <span title="<?= t('Task limit') ?>" class="task-limit"> - (<span id="task-number-column-<?= $column['id'] ?>"><?= count($column['tasks']) ?></span>/<?= Helper\escape($column['task_limit']) ?>) - </span> - <?php else: ?> - <span title="<?= t('Task count') ?>" class="task-count"> - (<span id="task-number-column-<?= $column['id'] ?>"><?= count($column['tasks']) ?></span>) - </span> - <?php endif ?> - </th> - <?php endforeach ?> -</tr> -<tr> - <?php foreach ($board as $column): ?> - <td - id="column-<?= $column['id'] ?>" - class="column <?= $column['task_limit'] && count($column['tasks']) > $column['task_limit'] ? 'task-limit-warning' : '' ?>" - data-column-id="<?= $column['id'] ?>" - data-task-limit="<?= $column['task_limit'] ?>" - > - <?php foreach ($column['tasks'] as $task): ?> - <div class="task-board draggable-item task-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - $board_highlight_period ? 'task-board-recent' : '' ?>" - data-task-id="<?= $task['id'] ?>" - data-owner-id="<?= $task['owner_id'] ?>" - data-category-id="<?= $task['category_id'] ?>" - data-due-date="<?= $task['date_due'] ?>" - data-task-url="<?= Helper\u('task', 'show', array('task_id' => $task['id'])) ?>" - title="<?= t('View this task') ?>"> - <?= Helper\template('board/task', array('task' => $task, 'categories' => $categories)) ?> +<?php if (isset($not_editable)): ?> + <table id="board"> +<?php else: ?> + <table id="board" + data-project-id="<?= $project['id'] ?>" + data-check-interval="<?= $board_private_refresh_interval ?>" + data-save-url="<?= Helper\u('board', 'save', array('project_id' => $project['id'])) ?>" + data-check-url="<?= Helper\u('board', 'check', array('project_id' => $project['id'], 'timestamp' => time())) ?>" + > +<?php endif ?> - </div> - <?php endforeach ?> - </td> - <?php endforeach ?> -</tr> +<?php foreach ($swimlanes as $swimlane): ?> + <?php if (empty($swimlane['columns'])): ?> + <p class="alert alert-error"><?= t('There is no column in your project!') ?></p> + <?php break ?> + <?php else: ?> + <?= Helper\template('board/swimlane', array( + 'project' => $project, + 'swimlane' => $swimlane, + 'board_highlight_period' => $board_highlight_period, + 'categories' => $categories, + 'hide_swimlane' => count($swimlanes) === 1, + 'not_editable' => isset($not_editable), + )) ?> + <?php endif ?> +<?php endforeach ?> </table> diff --git a/app/Template/board/swimlane.php b/app/Template/board/swimlane.php new file mode 100644 index 00000000..e48eb6f1 --- /dev/null +++ b/app/Template/board/swimlane.php @@ -0,0 +1,59 @@ +<tr> + <?php if (! $hide_swimlane): ?> + <td width="10%"></td> + <?php endif ?> + + <?php foreach ($swimlane['columns'] as $column): ?> + <th> + <?php if (! $not_editable): ?> + <div class="board-add-icon"> + <?= Helper\a('+', 'task', 'create', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']), false, 'task-creation-popover', t('Add a new task')) ?> + </div> + <?php endif ?> + + <?= Helper\escape($column['title']) ?> + + <?php if ($column['task_limit']): ?> + <span title="<?= t('Task limit') ?>" class="task-limit"> + (<span id="task-number-column-<?= $column['id'] ?>"><?= $column['nb_tasks'] ?></span>/<?= Helper\escape($column['task_limit']) ?>) + </span> + <?php else: ?> + <span title="<?= t('Task count') ?>" class="task-count"> + (<span id="task-number-column-<?= $column['id'] ?>"><?= $column['nb_tasks'] ?></span>) + </span> + <?php endif ?> + </th> + <?php endforeach ?> +</tr> +<tr> + <?php if (! $hide_swimlane): ?> + <th class="board-swimlane-title"> + <?= Helper\escape($swimlane['name']) ?> + </th> + <?php endif ?> + + <?php foreach ($swimlane['columns'] as $column): ?> + + <?php if ($not_editable): ?> + <td> + <?php else: ?> + <td + id="column-<?= $column['id'] ?>" + class="column <?= $column['task_limit'] && count($column['tasks']) > $column['task_limit'] ? 'task-limit-warning' : '' ?>" + data-column-id="<?= $column['id'] ?>" + data-swimlane-id="<?= $swimlane['id'] ?>" + data-task-limit="<?= $column['task_limit'] ?>"> + <?php endif ?> + + <?php foreach ($column['tasks'] as $task): ?> + <?= Helper\template('board/task', array( + 'project' => $project, + 'task' => $task, + 'categories' => $categories, + 'board_highlight_period' => $board_highlight_period, + 'not_editable' => $not_editable, + )) ?> + <?php endforeach ?> + </td> + <?php endforeach ?> +</tr>
\ No newline at end of file diff --git a/app/Template/board/task.php b/app/Template/board/task.php index 64448acc..d077c773 100644 --- a/app/Template/board/task.php +++ b/app/Template/board/task.php @@ -1,4 +1,8 @@ -<?php if (isset($not_editable)): ?> + + +<?php if ($not_editable): ?> + +<div class="task-board task-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - $board_highlight_period ? 'task-board-recent' : '' ?>"> <?= Helper\a('#'.$task['id'], 'task', 'readonly', array('task_id' => $task['id'], 'token' => $project['token'])) ?> @@ -28,6 +32,14 @@ <?php else: ?> +<div class="task-board draggable-item task-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - $board_highlight_period ? 'task-board-recent' : '' ?>" + data-task-id="<?= $task['id'] ?>" + data-owner-id="<?= $task['owner_id'] ?>" + data-category-id="<?= $task['category_id'] ?>" + data-due-date="<?= $task['date_due'] ?>" + data-task-url="<?= Helper\u('task', 'show', array('task_id' => $task['id'])) ?>" + title="<?= t('View this task') ?>"> + <?= Helper\a('#'.$task['id'], 'task', 'edit', array('task_id' => $task['id']), false, 'task-edit-popover', t('Edit this task')) ?> <?php if ($task['reference']): ?> @@ -114,3 +126,5 @@ </div> </div> <?php endif ?> + +</div>
\ No newline at end of file diff --git a/app/Template/category/index.php b/app/Template/category/index.php index 8a3eb4f1..bd275a67 100644 --- a/app/Template/category/index.php +++ b/app/Template/category/index.php @@ -1,8 +1,7 @@ +<?php if (! empty($categories)): ?> <div class="page-header"> <h2><?= t('Categories') ?></h2> </div> - -<?php if (! empty($categories)): ?> <table> <tr> <th><?= t('Category Name') ?></th> @@ -26,7 +25,9 @@ </table> <?php endif ?> -<h3><?= t('Add a new category') ?></h3> +<div class="page-header"> + <h2><?= t('Add a new category') ?></h2> +</div> <form method="post" action="<?= Helper\u('category', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off"> <?= Helper\form_csrf() ?> diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php index dc9a811b..5107451a 100644 --- a/app/Template/project/sidebar.php +++ b/app/Template/project/sidebar.php @@ -20,6 +20,9 @@ <li> <?= Helper\a(t('Category management'), 'category', 'index', array('project_id' => $project['id'])) ?> </li> + <li> + <?= Helper\a(t('Swimlanes'), 'swimlane', 'index', array('project_id' => $project['id'])) ?> + </li> <?php if ($project['is_private'] == 0): ?> <li> <?= Helper\a(t('User management'), 'project', 'users', array('project_id' => $project['id'])) ?> diff --git a/app/Template/project/users.php b/app/Template/project/users.php index 3a59df7a..691fe9fd 100644 --- a/app/Template/project/users.php +++ b/app/Template/project/users.php @@ -19,9 +19,9 @@ if ($is_owner): ?> [owner] <?php endif ?> <?php if ($project['is_private'] == 0): ?> <?php if ($is_owner): ?> - (<a href=<?= Helper\u('project', 'setOwner', array('project_id' => $project['id'], 'user_id' => $user_id, 'is_owner' => 0), true) ?> ><?= t('make user') ?></a> + (<a href=<?= Helper\u('project', 'setOwner', array('project_id' => $project['id'], 'user_id' => $user_id, 'is_owner' => 0), true) ?> ><?= t('set user') ?></a> <?php else: ?> - (<a href=<?= Helper\u('project', 'setOwner', array('project_id' => $project['id'], 'user_id' => $user_id, 'is_owner' => 1), true) ?> ><?= t('make owner') ?></a> + (<a href=<?= Helper\u('project', 'setOwner', array('project_id' => $project['id'], 'user_id' => $user_id, 'is_owner' => 1), true) ?> ><?= t('set manager') ?></a> <?php endif ?> or <?= Helper\a(t('revoke'), 'project', 'revoke', array('project_id' => $project['id'], 'user_id' => $user_id), true) ?>) diff --git a/app/Template/swimlane/edit.php b/app/Template/swimlane/edit.php new file mode 100644 index 00000000..fca555f7 --- /dev/null +++ b/app/Template/swimlane/edit.php @@ -0,0 +1,18 @@ +<div class="page-header"> + <h2><?= t('Swimlane modification for the project "%s"', $project['name']) ?></h2> +</div> + +<form method="post" action="<?= Helper\u('swimlane', 'update', array('project_id' => $project['id'], 'swimlane_id' => $values['id'])) ?>" autocomplete="off"> + + <?= Helper\form_csrf() ?> + + <?= Helper\form_hidden('id', $values) ?> + <?= Helper\form_hidden('project_id', $values) ?> + + <?= Helper\form_label(t('Name'), 'name') ?> + <?= Helper\form_text('name', $values, $errors, array('autofocus required')) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/swimlane/index.php b/app/Template/swimlane/index.php new file mode 100644 index 00000000..ec822c15 --- /dev/null +++ b/app/Template/swimlane/index.php @@ -0,0 +1,47 @@ +<?php if (! empty($active_swimlanes)): ?> +<div class="page-header"> + <h2><?= t('Active swimlanes') ?></h2> +</div> +<?= Helper\template('swimlane/table', array('swimlanes' => $active_swimlanes, 'project' => $project)) ?> +<?php endif ?> + +<div class="page-header"> + <h2><?= t('Add a new swimlane') ?></h2> +</div> +<form method="post" action="<?= Helper\u('swimlane', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off"> + + <?= Helper\form_csrf() ?> + <?= Helper\form_hidden('project_id', $values) ?> + + <?= Helper\form_label(t('Name'), 'name') ?> + <?= Helper\form_text('name', $values, $errors, array('autofocus required')) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form> + +<div class="page-header"> + <h2><?= t('Change default swimlane') ?></h2> +</div> +<form method="post" action="<?= Helper\u('swimlane', 'change', array('project_id' => $project['id'])) ?>" autocomplete="off"> + + <?= Helper\form_csrf() ?> + <?= Helper\form_hidden('id', $default_swimlane) ?> + + <?= Helper\form_label(t('Rename'), 'default_swimlane') ?> + <?= Helper\form_text('default_swimlane', $default_swimlane, array(), array('autofocus required')) ?><br/> + + <?= Helper\form_checkbox('show_default_swimlane', t('Show default swimlane'), 1, isset($default_swimlane['show_default_swimlane']) && $default_swimlane['show_default_swimlane'] == 1) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form> + +<?php if (! empty($inactive_swimlanes)): ?> +<div class="page-header"> + <h2><?= t('Inactive swimlanes') ?></h2> +</div> +<?= Helper\template('swimlane/table', array('swimlanes' => $inactive_swimlanes, 'project' => $project, 'hide_position' => true)) ?> +<?php endif ?>
\ No newline at end of file diff --git a/app/Template/swimlane/remove.php b/app/Template/swimlane/remove.php new file mode 100644 index 00000000..edf8803c --- /dev/null +++ b/app/Template/swimlane/remove.php @@ -0,0 +1,17 @@ +<section id="main"> + <div class="page-header"> + <h2><?= t('Remove a swimlane') ?></h2> + </div> + + <div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this swimlane: "%s"?', $swimlane['name']) ?> + </p> + + <div class="form-actions"> + <?= Helper\a(t('Yes'), 'swimlane', 'remove', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id']), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= Helper\a(t('cancel'), 'swimlane', 'index', array('project_id' => $project['id'])) ?> + </div> + </div> +</section>
\ No newline at end of file diff --git a/app/Template/swimlane/table.php b/app/Template/swimlane/table.php new file mode 100644 index 00000000..ac7ed835 --- /dev/null +++ b/app/Template/swimlane/table.php @@ -0,0 +1,44 @@ +<table> + <tr> + <?php if (! isset($hide_position)): ?> + <th><?= t('Position') ?></th> + <?php endif ?> + <th class="column-70"><?= t('Name') ?></th> + <th><?= t('Actions') ?></th> + </tr> + <?php foreach ($swimlanes as $swimlane): ?> + <tr> + <?php if (! isset($hide_position)): ?> + <td>#<?= $swimlane['position'] ?></td> + <?php endif ?> + <td><?= Helper\escape($swimlane['name']) ?></td> + <td> + <ul> + <?php if ($swimlane['position'] != 0 && $swimlane['position'] != 1): ?> + <li> + <?= Helper\a(t('Move Up'), 'swimlane', 'moveup', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id']), true) ?> + </li> + <?php endif ?> + <?php if ($swimlane['position'] != 0 && $swimlane['position'] != count($swimlanes)): ?> + <li> + <?= Helper\a(t('Move Down'), 'swimlane', 'movedown', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id']), true) ?> + </li> + <?php endif ?> + <li> + <?= Helper\a(t('Rename'), 'swimlane', 'edit', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id'])) ?> + </li> + <li> + <?php if ($swimlane['is_active']): ?> + <?= Helper\a(t('Disable'), 'swimlane', 'disable', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id']), true) ?> + <?php else: ?> + <?= Helper\a(t('Enable'), 'swimlane', 'enable', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id']), true) ?> + <?php endif ?> + </li> + <li> + <?= Helper\a(t('Remove'), 'swimlane', 'confirm', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id'])) ?> + </li> + </ul> + </td> + </tr> + <?php endforeach ?> +</table>
\ No newline at end of file diff --git a/app/Template/task/new.php b/app/Template/task/new.php index 72e7d25b..3e2576c5 100644 --- a/app/Template/task/new.php +++ b/app/Template/task/new.php @@ -47,6 +47,7 @@ <div class="form-column"> <?= Helper\form_hidden('project_id', $values) ?> + <?= Helper\form_hidden('swimlane_id', $values) ?> <?= Helper\form_label(t('Assignee'), 'owner_id') ?> <?= Helper\form_select('owner_id', $users_list, $values, $errors) ?><br/> diff --git a/assets/css/app.css b/assets/css/app.css index 14d5ace7..44bc9c2b 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -733,7 +733,12 @@ a.filter-on { height: 70px; margin-bottom: 10px; } -/* project view */ + +/* swimlanes */ +th.board-swimlane-title { + vertical-align: top; + text-align: right; +}/* project view */ .project-listing { max-width: 500px; margin-left: 30px; diff --git a/assets/css/board.css b/assets/css/board.css index c939b345..37dba974 100644 --- a/assets/css/board.css +++ b/assets/css/board.css @@ -60,3 +60,9 @@ a.filter-on { height: 70px; margin-bottom: 10px; } + +/* swimlanes */ +th.board-swimlane-title { + vertical-align: top; + text-align: right; +}
\ No newline at end of file diff --git a/assets/js/app.js b/assets/js/app.js index 0be4fca4..088c929f 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -202,7 +202,8 @@ Kanboard.Board = (function() { board_save( ui.item.attr('data-task-id'), ui.item.parent().attr("data-column-id"), - ui.item.index() + 1 + ui.item.index() + 1, + ui.item.parent().attr('data-swimlane-id') ); } }); @@ -320,7 +321,7 @@ Kanboard.Board = (function() { } // Save and refresh the board - function board_save(taskId, columnId, position) + function board_save(taskId, columnId, position, swimlaneId) { board_unload_events(); @@ -333,6 +334,7 @@ Kanboard.Board = (function() { data: JSON.stringify({ "task_id": taskId, "column_id": columnId, + "swimlane_id": swimlaneId, "position": position, }), success: function(data) { diff --git a/assets/js/board.js b/assets/js/board.js index 6e56dfa1..9a0f070d 100644 --- a/assets/js/board.js +++ b/assets/js/board.js @@ -21,7 +21,8 @@ Kanboard.Board = (function() { board_save( ui.item.attr('data-task-id'), ui.item.parent().attr("data-column-id"), - ui.item.index() + 1 + ui.item.index() + 1, + ui.item.parent().attr('data-swimlane-id') ); } }); @@ -139,7 +140,7 @@ Kanboard.Board = (function() { } // Save and refresh the board - function board_save(taskId, columnId, position) + function board_save(taskId, columnId, position, swimlaneId) { board_unload_events(); @@ -152,6 +153,7 @@ Kanboard.Board = (function() { data: JSON.stringify({ "task_id": taskId, "column_id": columnId, + "swimlane_id": swimlaneId, "position": position, }), success: function(data) { diff --git a/docs/api-json-rpc.markdown b/docs/api-json-rpc.markdown index 5280600e..ef013880 100644 --- a/docs/api-json-rpc.markdown +++ b/docs/api-json-rpc.markdown @@ -630,7 +630,7 @@ Request example: { "jsonrpc": "2.0", "method": "getBoard", - "id": 1627282648, + "id": 827046470, "params": [ 1 ] @@ -642,58 +642,51 @@ Response example: ```json { "jsonrpc": "2.0", - "id": 1627282648, + "id": 827046470, "result": [ { - "id": "1", - "title": "Backlog", - "position": "1", - "project_id": "1", - "task_limit": "0", - "tasks": [] - }, - { - "id": "2", - "title": "Ready", - "position": "2", - "project_id": "1", - "task_limit": "0", - "tasks": [] - }, - { - "id": "3", - "title": "Work in progress", - "position": "3", - "project_id": "1", - "task_limit": "0", - "tasks": [ + "id": 0, + "name": "Default swimlane", + "columns": [ { - "nb_comments": "0", - "nb_files": "0", - "nb_subtasks": "1", - "nb_completed_subtasks": "0", "id": "1", - "title": "Task with comment", - "description": "", - "date_creation": "1410952872", - "date_modification": "1410952872", - "date_completed": null, - "date_due": "0", - "color_id": "red", - "project_id": "1", - "column_id": "3", - "owner_id": "1", - "creator_id": "0", + "title": "Backlog", "position": "1", - "is_active": "1", - "score": "0", - "category_id": "0", - "assignee_username": "admin", - "assignee_name": null + "project_id": "1", + "task_limit": "0", + "tasks": [], + "nb_tasks": 0 + }, + { + "id": "2", + "title": "Ready", + "position": "2", + "project_id": "1", + "task_limit": "0", + "tasks": [], + "nb_tasks": 0 + }, + { + "id": "3", + "title": "Work in progress", + "position": "3", + "project_id": "1", + "task_limit": "0", + "tasks": [], + "nb_tasks": 0 + }, + { + "id": "4", + "title": "Done", + "position": "4", + "project_id": "1", + "task_limit": "0", + "tasks": [], + "nb_tasks": 0 } - ] - }, - ... + ], + "nb_columns": 4 + } ] } ``` @@ -969,6 +962,7 @@ Response example: - **score** (integer, optional) - **date_due**: ISO8601 format (string, optional) - **category_id** (integer, optional) + - **swimelane_id** (integer, optional) - Result on success: **task_id** - Result on failure: **false** @@ -1047,7 +1041,8 @@ Response example: "date_due": "0", "category_id": "0", "creator_id": "0", - "date_modification": "1409963206" + "date_modification": "1409963206", + "swimlane_id": 0 } } ``` @@ -1098,7 +1093,8 @@ Response example: "date_due": "0", "category_id": "0", "creator_id": "0", - "date_modification": "1409961789" + "date_modification": "1409961789", + "swimlane_id": 0 }, { "id": "2", @@ -1116,7 +1112,8 @@ Response example: "date_due": "0", "category_id": "0", "creator_id": "0", - "date_modification": "1409962115" + "date_modification": "1409962115", + "swimlane_id": 0 }, ... ] @@ -1138,6 +1135,7 @@ Response example: - **score** (integer, optional) - **date_due**: ISO8601 format (string, optional) - **category_id** (integer, optional) + - **swimlane_id** (integer, optional) - Result on success: **true** - Result on failure: **false** diff --git a/jsonrpc.php b/jsonrpc.php index 28c640ea..c2d216cf 100644 --- a/jsonrpc.php +++ b/jsonrpc.php @@ -87,7 +87,7 @@ $server->register('updateProject', function($id, $name, $is_active = null, $is_p /** * Board procedures */ -$server->bind('getBoard', $boardModel, 'get'); +$server->bind('getBoard', $boardModel, 'getBoard'); $server->bind('getColumns', $boardModel, 'getColumns'); $server->bind('getColumn', $boardModel, 'getColumn'); $server->bind('moveColumnUp', $boardModel, 'moveUp'); @@ -113,7 +113,7 @@ $server->bind('closeTask', $taskStatusModel, 'close'); $server->bind('removeTask', $taskModel, 'remove'); $server->bind('moveTaskPosition', $taskPositionModel, 'movePosition'); -$server->register('createTask', function($title, $project_id, $color_id = '', $column_id = 0, $owner_id = 0, $creator_id = 0, $date_due = '', $description = '', $category_id = 0, $score = 0) use ($taskCreationModel, $taskValidatorModel) { +$server->register('createTask', function($title, $project_id, $color_id = '', $column_id = 0, $owner_id = 0, $creator_id = 0, $date_due = '', $description = '', $category_id = 0, $score = 0, $swimlane_id = 0) use ($taskCreationModel, $taskValidatorModel) { $values = array( 'title' => $title, @@ -126,6 +126,7 @@ $server->register('createTask', function($title, $project_id, $color_id = '', $c 'description' => $description, 'category_id' => $category_id, 'score' => $score, + 'swimlane_id' => $swimlane_id, ); list($valid,) = $taskValidatorModel->validateCreation($values); @@ -137,7 +138,7 @@ $server->register('createTask', function($title, $project_id, $color_id = '', $c return $taskCreationModel->create($values); }); -$server->register('updateTask', function($id, $title = null, $project_id = null, $color_id = null, $column_id = null, $owner_id = null, $creator_id = null, $date_due = null, $description = null, $category_id = null, $score = null) use ($taskModificationModel, $taskValidatorModel) { +$server->register('updateTask', function($id, $title = null, $project_id = null, $color_id = null, $column_id = null, $owner_id = null, $creator_id = null, $date_due = null, $description = null, $category_id = null, $score = null, $swimlane_id = null) use ($taskModificationModel, $taskValidatorModel) { $values = array( 'id' => $id, @@ -151,6 +152,7 @@ $server->register('updateTask', function($id, $title = null, $project_id = null, 'description' => $description, 'category_id' => $category_id, 'score' => $score, + 'swimlane_id' => $swimlane_id, ); foreach ($values as $key => $value) { diff --git a/tests/functionals/ApiTest.php b/tests/functionals/ApiTest.php index a9196121..3ce1fb19 100644 --- a/tests/functionals/ApiTest.php +++ b/tests/functionals/ApiTest.php @@ -116,7 +116,9 @@ class Api extends PHPUnit_Framework_TestCase { $board = $this->client->getBoard(1); $this->assertTrue(is_array($board)); - $this->assertEquals(4, count($board)); + $this->assertEquals(1, count($board)); + $this->assertEquals('Default swimlane', $board[0]['name']); + $this->assertEquals(4, count($board[0]['columns'])); } public function testGetColumns() diff --git a/tests/units/BoardTest.php b/tests/units/BoardTest.php index a7de34bd..866d3bab 100644 --- a/tests/units/BoardTest.php +++ b/tests/units/BoardTest.php @@ -48,11 +48,14 @@ class BoardTest extends Base $this->assertEquals(1, $p->create(array('name' => 'UnitTest1'))); - $board = $b->get(1); + $board = $b->getBoard(1); $this->assertNotEmpty($board); - $this->assertEquals(4, count($board)); - $this->assertTrue(array_key_exists('tasks', $board[2])); - $this->assertTrue(array_key_exists('title', $board[2])); + $this->assertEquals(1, count($board)); + $this->assertEquals(4, count($board[0])); + $this->assertTrue(array_key_exists('name', $board[0])); + $this->assertTrue(array_key_exists('columns', $board[0])); + $this->assertTrue(array_key_exists('tasks', $board[0]['columns'][2])); + $this->assertTrue(array_key_exists('title', $board[0]['columns'][2])); } public function testGetColumn() diff --git a/tests/units/SwimlaneTest.php b/tests/units/SwimlaneTest.php new file mode 100644 index 00000000..0c0cdfc5 --- /dev/null +++ b/tests/units/SwimlaneTest.php @@ -0,0 +1,363 @@ +<?php + +require_once __DIR__.'/Base.php'; + +use Model\Project; +use Model\Task; +use Model\TaskCreation; +use Model\TaskFinder; +use Model\Swimlane; + +class SwimlaneTest extends Base +{ + public function testCreation() + { + $p = new Project($this->container); + $s = new Swimlane($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertEquals(1, $s->create(1, 'Swimlane #1')); + + $swimlanes = $s->getSwimlanes(1); + $this->assertNotEmpty($swimlanes); + $this->assertEquals(2, count($swimlanes)); + $this->assertEquals('Default swimlane', $swimlanes[0]['name']); + $this->assertEquals('Swimlane #1', $swimlanes[1]['name']); + + $this->assertEquals(1, $s->getIdByName(1, 'Swimlane #1')); + $this->assertEquals(0, $s->getIdByName(2, 'Swimlane #2')); + + $this->assertEquals('Swimlane #1', $s->getNameById(1)); + $this->assertEquals('', $s->getNameById(23)); + } + + public function testRename() + { + $p = new Project($this->container); + $s = new Swimlane($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertEquals(1, $s->create(1, 'Swimlane #1')); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals('Swimlane #1', $swimlane['name']); + + $this->assertTrue($s->rename(1, 'foobar')); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals('foobar', $swimlane['name']); + } + + public function testRenameDefaultSwimlane() + { + $p = new Project($this->container); + $s = new Swimlane($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertTrue($s->updateDefault(array('id' => 1, 'default_swimlane' => 'foo', 'show_default_swimlane' => 1))); + + $default = $s->getDefault(1); + $this->assertNotEmpty($default); + $this->assertEquals('foo', $default['default_swimlane']); + $this->assertEquals(1, $default['show_default_swimlane']); + + $this->assertTrue($s->updateDefault(array('id' => 1, 'default_swimlane' => 'foo', 'show_default_swimlane' => 0))); + + $default = $s->getDefault(1); + $this->assertNotEmpty($default); + $this->assertEquals('foo', $default['default_swimlane']); + $this->assertEquals(0, $default['show_default_swimlane']); + } + + public function testDisable() + { + $p = new Project($this->container); + $s = new Swimlane($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertEquals(1, $s->create(1, 'Swimlane #1')); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + + $this->assertEquals(2, $s->getLastPosition(1)); + $this->assertTrue($s->disable(1, 1)); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(0, $swimlane['is_active']); + $this->assertEquals(0, $swimlane['position']); + + $this->assertEquals(1, $s->getLastPosition(1)); + + // Create a new swimlane + $this->assertEquals(2, $s->create(1, 'Swimlane #2')); + + $swimlane = $s->getById(2); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + + // Enable our disabled swimlane + $this->assertTrue($s->enable(1, 1)); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(2, $swimlane['position']); + } + + public function testRemove() + { + $p = new Project($this->container); + $s = new Swimlane($this->container); + $tc = new TaskCreation($this->container); + $tf = new TaskFinder($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertEquals(1, $s->create(1, 'Swimlane #1')); + $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'swimlane_id' => 1))); + + $task = $tf->getbyId(1); + $this->assertNotEmpty($task); + $this->assertEquals(1, $task['swimlane_id']); + + $this->assertTrue($s->remove(1, 1)); + + $task = $tf->getbyId(1); + $this->assertNotEmpty($task); + $this->assertEquals(0, $task['swimlane_id']); + + $this->assertEmpty($s->getById(1)); + } + + public function testUpdatePositions() + { + $p = new Project($this->container); + $s = new Swimlane($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertEquals(1, $s->create(1, 'Swimlane #1')); + $this->assertEquals(2, $s->create(1, 'Swimlane #2')); + $this->assertEquals(3, $s->create(1, 'Swimlane #3')); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + + $swimlane = $s->getById(2); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(2, $swimlane['position']); + + $swimlane = $s->getById(3); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(3, $swimlane['position']); + + // Disable the 2nd swimlane + $this->assertTrue($s->disable(1, 2)); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + + $swimlane = $s->getById(2); + $this->assertNotEmpty($swimlane); + $this->assertEquals(0, $swimlane['is_active']); + $this->assertEquals(0, $swimlane['position']); + + $swimlane = $s->getById(3); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(2, $swimlane['position']); + + // Remove the first swimlane + $this->assertTrue($s->remove(1, 1)); + + $swimlane = $s->getById(1); + $this->assertEmpty($swimlane); + + $swimlane = $s->getById(2); + $this->assertNotEmpty($swimlane); + $this->assertEquals(0, $swimlane['is_active']); + $this->assertEquals(0, $swimlane['position']); + + $swimlane = $s->getById(3); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + } + + public function testMoveUp() + { + $p = new Project($this->container); + $s = new Swimlane($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertEquals(1, $s->create(1, 'Swimlane #1')); + $this->assertEquals(2, $s->create(1, 'Swimlane #2')); + $this->assertEquals(3, $s->create(1, 'Swimlane #3')); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + + $swimlane = $s->getById(2); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(2, $swimlane['position']); + + $swimlane = $s->getById(3); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(3, $swimlane['position']); + + // Move the swimlane 3 up + $this->assertTrue($s->moveUp(1, 3)); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + + $swimlane = $s->getById(2); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(3, $swimlane['position']); + + $swimlane = $s->getById(3); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(2, $swimlane['position']); + + // First swimlane can be moved up + $this->assertFalse($s->moveUp(1, 1)); + + // Move with a disabled swimlane + $this->assertTrue($s->disable(1, 1)); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(0, $swimlane['is_active']); + $this->assertEquals(0, $swimlane['position']); + + $swimlane = $s->getById(2); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(2, $swimlane['position']); + + $swimlane = $s->getById(3); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + + // Move the 2nd swimlane up + $this->assertTrue($s->moveUp(1, 2)); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(0, $swimlane['is_active']); + $this->assertEquals(0, $swimlane['position']); + + $swimlane = $s->getById(2); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + + $swimlane = $s->getById(3); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(2, $swimlane['position']); + } + + public function testMoveDown() + { + $p = new Project($this->container); + $s = new Swimlane($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertEquals(1, $s->create(1, 'Swimlane #1')); + $this->assertEquals(2, $s->create(1, 'Swimlane #2')); + $this->assertEquals(3, $s->create(1, 'Swimlane #3')); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + + $swimlane = $s->getById(2); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(2, $swimlane['position']); + + $swimlane = $s->getById(3); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(3, $swimlane['position']); + + // Move the swimlane 1 down + $this->assertTrue($s->moveDown(1, 1)); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(2, $swimlane['position']); + + $swimlane = $s->getById(2); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + + $swimlane = $s->getById(3); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(3, $swimlane['position']); + + // Last swimlane can be moved down + $this->assertFalse($s->moveDown(1, 3)); + + // Move with a disabled swimlane + $this->assertTrue($s->disable(1, 3)); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(2, $swimlane['position']); + + $swimlane = $s->getById(2); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + + $swimlane = $s->getById(3); + $this->assertNotEmpty($swimlane); + $this->assertEquals(0, $swimlane['is_active']); + $this->assertEquals(0, $swimlane['position']); + + // Move the 2st swimlane down + $this->assertTrue($s->moveDown(1, 2)); + + $swimlane = $s->getById(1); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(1, $swimlane['position']); + + $swimlane = $s->getById(2); + $this->assertNotEmpty($swimlane); + $this->assertEquals(1, $swimlane['is_active']); + $this->assertEquals(2, $swimlane['position']); + + $swimlane = $s->getById(3); + $this->assertNotEmpty($swimlane); + $this->assertEquals(0, $swimlane['is_active']); + $this->assertEquals(0, $swimlane['position']); + } +} diff --git a/tests/units/TaskDuplicationTest.php b/tests/units/TaskDuplicationTest.php index 80f037de..7b2983f8 100644 --- a/tests/units/TaskDuplicationTest.php +++ b/tests/units/TaskDuplicationTest.php @@ -11,6 +11,7 @@ use Model\Project; use Model\ProjectPermission; use Model\Category; use Model\User; +use Model\Swimlane; class TaskDuplicationTest extends Base { @@ -56,6 +57,7 @@ class TaskDuplicationTest extends Base $this->assertEquals(1, $task['project_id']); $this->assertEquals(1, $task['owner_id']); $this->assertEquals(2, $task['category_id']); + $this->assertEquals(0, $task['swimlane_id']); $this->assertEquals(3, $task['column_id']); $this->assertEquals(2, $task['position']); $this->assertEquals('test', $task['title']); @@ -90,6 +92,7 @@ class TaskDuplicationTest extends Base $this->assertNotEmpty($task); $this->assertEquals(1, $task['owner_id']); $this->assertEquals(0, $task['category_id']); + $this->assertEquals(0, $task['swimlane_id']); $this->assertEquals(5, $task['column_id']); $this->assertEquals(1, $task['position']); $this->assertEquals(2, $task['project_id']); @@ -126,6 +129,77 @@ class TaskDuplicationTest extends Base $this->assertNotEmpty($task); $this->assertEquals(0, $task['owner_id']); $this->assertEquals(2, $task['category_id']); + $this->assertEquals(0, $task['swimlane_id']); + $this->assertEquals(5, $task['column_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals(2, $task['project_id']); + $this->assertEquals('test', $task['title']); + } + + public function testDuplicateAnotherProjectWithSwimlane() + { + $td = new TaskDuplication($this->container); + $tc = new TaskCreation($this->container); + $tf = new TaskFinder($this->container); + $p = new Project($this->container); + $s = new Swimlane($this->container); + + // We create 2 projects + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(2, $p->create(array('name' => 'test2'))); + + $this->assertNotFalse($s->create(1, 'Swimlane #1')); + $this->assertNotFalse($s->create(2, 'Swimlane #1')); + + // We create a task + $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'swimlane_id' => 1))); + + // We duplicate our task to the 2nd project + $this->assertEquals(2, $td->duplicateToProject(1, 2)); + $this->assertTrue($this->container['event']->isEventTriggered(Task::EVENT_CREATE)); + $this->assertTrue($this->container['event']->isEventTriggered(Task::EVENT_CREATE_UPDATE)); + + // Check the values of the duplicated task + $task = $tf->getById(2); + $this->assertNotEmpty($task); + $this->assertEquals(0, $task['owner_id']); + $this->assertEquals(0, $task['category_id']); + $this->assertEquals(2, $task['swimlane_id']); + $this->assertEquals(5, $task['column_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals(2, $task['project_id']); + $this->assertEquals('test', $task['title']); + } + + public function testDuplicateAnotherProjectWithoutSwimlane() + { + $td = new TaskDuplication($this->container); + $tc = new TaskCreation($this->container); + $tf = new TaskFinder($this->container); + $p = new Project($this->container); + $s = new Swimlane($this->container); + + // We create 2 projects + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(2, $p->create(array('name' => 'test2'))); + + $this->assertNotFalse($s->create(1, 'Swimlane #1')); + $this->assertNotFalse($s->create(2, 'Swimlane #2')); + + // We create a task + $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'swimlane_id' => 1))); + + // We duplicate our task to the 2nd project + $this->assertEquals(2, $td->duplicateToProject(1, 2)); + $this->assertTrue($this->container['event']->isEventTriggered(Task::EVENT_CREATE)); + $this->assertTrue($this->container['event']->isEventTriggered(Task::EVENT_CREATE_UPDATE)); + + // Check the values of the duplicated task + $task = $tf->getById(2); + $this->assertNotEmpty($task); + $this->assertEquals(0, $task['owner_id']); + $this->assertEquals(0, $task['category_id']); + $this->assertEquals(0, $task['swimlane_id']); $this->assertEquals(5, $task['column_id']); $this->assertEquals(1, $task['position']); $this->assertEquals(2, $task['project_id']); @@ -215,6 +289,7 @@ class TaskDuplicationTest extends Base $this->assertNotEmpty($task); $this->assertEquals(1, $task['owner_id']); $this->assertEquals(0, $task['category_id']); + $this->assertEquals(0, $task['swimlane_id']); $this->assertEquals(2, $task['project_id']); $this->assertEquals(5, $task['column_id']); $this->assertEquals(1, $task['position']); @@ -249,6 +324,7 @@ class TaskDuplicationTest extends Base $this->assertNotEmpty($task); $this->assertEquals(0, $task['owner_id']); $this->assertEquals(2, $task['category_id']); + $this->assertEquals(0, $task['swimlane_id']); $this->assertEquals(5, $task['column_id']); $this->assertEquals(1, $task['position']); $this->assertEquals(2, $task['project_id']); @@ -324,4 +400,70 @@ class TaskDuplicationTest extends Base $this->assertEquals(2, $task['project_id']); $this->assertEquals(5, $task['column_id']); } + + public function testMoveAnotherProjectWithSwimlane() + { + $td = new TaskDuplication($this->container); + $tc = new TaskCreation($this->container); + $tf = new TaskFinder($this->container); + $p = new Project($this->container); + $s = new Swimlane($this->container); + + // We create 2 projects + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(2, $p->create(array('name' => 'test2'))); + + $this->assertNotFalse($s->create(1, 'Swimlane #1')); + $this->assertNotFalse($s->create(2, 'Swimlane #1')); + + // We create a task + $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'swimlane_id' => 1))); + + // We move our task to the 2nd project + $this->assertTrue($td->moveToProject(1, 2)); + + // Check the values of the moved task + $task = $tf->getById(1); + $this->assertNotEmpty($task); + $this->assertEquals(0, $task['owner_id']); + $this->assertEquals(0, $task['category_id']); + $this->assertEquals(2, $task['swimlane_id']); + $this->assertEquals(5, $task['column_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals(2, $task['project_id']); + $this->assertEquals('test', $task['title']); + } + + public function testMoveAnotherProjectWithoutSwimlane() + { + $td = new TaskDuplication($this->container); + $tc = new TaskCreation($this->container); + $tf = new TaskFinder($this->container); + $p = new Project($this->container); + $s = new Swimlane($this->container); + + // We create 2 projects + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(2, $p->create(array('name' => 'test2'))); + + $this->assertNotFalse($s->create(1, 'Swimlane #1')); + $this->assertNotFalse($s->create(2, 'Swimlane #2')); + + // We create a task + $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'swimlane_id' => 1))); + + // We move our task to the 2nd project + $this->assertTrue($td->moveToProject(1, 2)); + + // Check the values of the moved task + $task = $tf->getById(1); + $this->assertNotEmpty($task); + $this->assertEquals(0, $task['owner_id']); + $this->assertEquals(0, $task['category_id']); + $this->assertEquals(0, $task['swimlane_id']); + $this->assertEquals(5, $task['column_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals(2, $task['project_id']); + $this->assertEquals('test', $task['title']); + } } diff --git a/tests/units/TaskPositionTest.php b/tests/units/TaskPositionTest.php index 3285bfae..dbc6c74b 100644 --- a/tests/units/TaskPositionTest.php +++ b/tests/units/TaskPositionTest.php @@ -7,6 +7,7 @@ use Model\TaskPosition; use Model\TaskCreation; use Model\TaskFinder; use Model\Project; +use Model\Swimlane; class TaskPositionTest extends Base { @@ -202,7 +203,7 @@ class TaskPositionTest extends Base $this->assertEquals(3, $tc->create(array('title' => 'Task #3', 'project_id' => 1, 'column_id' => 1))); $this->assertEquals(4, $tc->create(array('title' => 'Task #4', 'project_id' => 1, 'column_id' => 1))); - // Move the last task to hte top + // Move the last task to the bottom $this->assertTrue($tp->movePosition(1, 1, 1, 4)); // Check tasks position @@ -383,14 +384,88 @@ class TaskPositionTest extends Base $this->assertEquals($task_per_column + 1, $tf->countByColumnId(1, 4)); } + public function testMoveTaskSwimlane() + { + $tp = new TaskPosition($this->container); + $tc = new TaskCreation($this->container); + $tf = new TaskFinder($this->container); + $p = new Project($this->container); + $s = new Swimlane($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $s->create(1, 'test 1')); + $this->assertEquals(1, $tc->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1))); + $this->assertEquals(2, $tc->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 1))); + $this->assertEquals(3, $tc->create(array('title' => 'Task #3', 'project_id' => 1, 'column_id' => 1))); + $this->assertEquals(4, $tc->create(array('title' => 'Task #4', 'project_id' => 1, 'column_id' => 1))); + + // Move the task to the swimlane + $this->assertTrue($tp->movePosition(1, 1, 2, 1, 1)); + + // Check tasks position + $task = $tf->getById(1); + $this->assertEquals(1, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals(1, $task['swimlane_id']); + + $task = $tf->getById(2); + $this->assertEquals(2, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals(0, $task['swimlane_id']); + + $task = $tf->getById(3); + $this->assertEquals(3, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(2, $task['position']); + $this->assertEquals(0, $task['swimlane_id']); + + $task = $tf->getById(4); + $this->assertEquals(4, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(3, $task['position']); + $this->assertEquals(0, $task['swimlane_id']); + + // Move the task to the swimlane + $this->assertTrue($tp->movePosition(1, 2, 2, 1, 1)); + + // Check tasks position + $task = $tf->getById(1); + $this->assertEquals(1, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(2, $task['position']); + $this->assertEquals(1, $task['swimlane_id']); + + $task = $tf->getById(2); + $this->assertEquals(2, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals(1, $task['swimlane_id']); + + $task = $tf->getById(3); + $this->assertEquals(3, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals(0, $task['swimlane_id']); + + $task = $tf->getById(4); + $this->assertEquals(4, $task['id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(2, $task['position']); + $this->assertEquals(0, $task['swimlane_id']); + } + public function testEvents() { $tp = new TaskPosition($this->container); $tc = new TaskCreation($this->container); $tf = new TaskFinder($this->container); $p = new Project($this->container); + $s = new Swimlane($this->container); $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $s->create(1, 'test 1')); $this->assertEquals(1, $tc->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1))); $this->assertEquals(2, $tc->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 2))); @@ -408,6 +483,7 @@ class TaskPositionTest extends Base $this->assertEquals(2, $task['column_id']); $this->assertEquals(2, $task['position']); + $this->assertFalse($this->container['event']->isEventTriggered(Task::EVENT_MOVE_SWIMLANE)); $this->assertTrue($this->container['event']->isEventTriggered(Task::EVENT_MOVE_COLUMN)); $this->assertFalse($this->container['event']->isEventTriggered(Task::EVENT_MOVE_POSITION)); @@ -433,6 +509,7 @@ class TaskPositionTest extends Base $this->assertEquals(2, $task['column_id']); $this->assertEquals(1, $task['position']); + $this->assertFalse($this->container['event']->isEventTriggered(Task::EVENT_MOVE_SWIMLANE)); $this->assertFalse($this->container['event']->isEventTriggered(Task::EVENT_MOVE_COLUMN)); $this->assertTrue($this->container['event']->isEventTriggered(Task::EVENT_MOVE_POSITION)); @@ -442,5 +519,34 @@ class TaskPositionTest extends Base $this->assertEquals(2, $event_data['position']); $this->assertEquals(2, $event_data['column_id']); $this->assertEquals(1, $event_data['project_id']); + + $this->container['event']->clearTriggeredEvents(); + + // Move to another swimlane + $this->assertTrue($tp->movePosition(1, 1, 3, 1, 1)); + + $task = $tf->getById(1); + $this->assertEquals(1, $task['id']); + $this->assertEquals(3, $task['column_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals(1, $task['swimlane_id']); + + $task = $tf->getById(2); + $this->assertEquals(2, $task['id']); + $this->assertEquals(2, $task['column_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals(0, $task['swimlane_id']); + + $this->assertTrue($this->container['event']->isEventTriggered(Task::EVENT_MOVE_SWIMLANE)); + $this->assertFalse($this->container['event']->isEventTriggered(Task::EVENT_MOVE_POSITION)); + $this->assertFalse($this->container['event']->isEventTriggered(Task::EVENT_MOVE_POSITION)); + + $event_data = $this->container['event']->getEventData(Task::EVENT_MOVE_SWIMLANE); + $this->assertNotEmpty($event_data); + $this->assertEquals(1, $event_data['task_id']); + $this->assertEquals(1, $event_data['position']); + $this->assertEquals(3, $event_data['column_id']); + $this->assertEquals(1, $event_data['project_id']); + $this->assertEquals(1, $event_data['swimlane_id']); } } |