From cf821e117ce8b937cff7f386a107aaa81ba6bf9b Mon Sep 17 00:00:00 2001 From: Frédéric Guillot Date: Fri, 26 Dec 2014 17:43:13 -0500 Subject: Add swimlanes --- app/Controller/Board.php | 100 ++++---- app/Controller/Category.php | 4 +- app/Controller/Swimlane.php | 256 ++++++++++++++++++++ app/Controller/Task.php | 1 + app/Locale/da_DK/translations.php | 21 ++ app/Locale/de_DE/translations.php | 21 ++ app/Locale/es_ES/translations.php | 21 ++ app/Locale/fi_FI/translations.php | 21 ++ app/Locale/fr_FR/translations.php | 21 ++ app/Locale/hu_HU/translations.php | 21 ++ app/Locale/it_IT/translations.php | 21 ++ app/Locale/ja_JP/translations.php | 21 ++ app/Locale/pl_PL/translations.php | 21 ++ app/Locale/pt_BR/translations.php | 21 ++ app/Locale/ru_RU/translations.php | 21 ++ app/Locale/sv_SE/translations.php | 21 ++ app/Locale/th_TH/translations.php | 21 ++ app/Locale/zh_CN/translations.php | 21 ++ app/Model/Board.php | 23 +- app/Model/Swimlane.php | 474 ++++++++++++++++++++++++++++++++++++++ app/Model/Task.php | 1 + app/Model/TaskCreation.php | 2 +- app/Model/TaskDuplication.php | 16 +- app/Model/TaskFinder.php | 10 +- app/Model/TaskPosition.php | 67 ++++-- app/Model/TaskValidator.php | 1 + app/Schema/Mysql.php | 22 +- app/Schema/Postgres.php | 21 +- app/Schema/Sqlite.php | 23 +- app/Template/board/edit.php | 2 +- app/Template/board/index.php | 18 +- app/Template/board/public.php | 43 +--- app/Template/board/show.php | 73 ++---- app/Template/board/swimlane.php | 59 +++++ app/Template/board/task.php | 16 +- app/Template/category/index.php | 7 +- app/Template/project/sidebar.php | 3 + app/Template/project/users.php | 4 +- app/Template/swimlane/edit.php | 18 ++ app/Template/swimlane/index.php | 47 ++++ app/Template/swimlane/remove.php | 17 ++ app/Template/swimlane/table.php | 44 ++++ app/Template/task/new.php | 1 + 43 files changed, 1480 insertions(+), 187 deletions(-) create mode 100644 app/Controller/Swimlane.php create mode 100644 app/Model/Swimlane.php create mode 100644 app/Template/board/swimlane.php create mode 100644 app/Template/swimlane/edit.php create mode 100644 app/Template/swimlane/index.php create mode 100644 app/Template/swimlane/remove.php create mode 100644 app/Template/swimlane/table.php (limited to 'app') 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 @@ +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 @@ +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 @@ - + 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, )) ?> - -

- - $project, - 'board' => $board, - 'categories' => $categories, - 'board_private_refresh_interval' => $board_private_refresh_interval, - 'board_highlight_period' => $board_highlight_period, - )) ?> - + $project, + 'swimlanes' => $swimlanes, + 'categories' => $categories, + 'board_private_refresh_interval' => $board_private_refresh_interval, + 'board_highlight_period' => $board_highlight_period, + )) ?> 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 @@
- -

- - - - - - - - - - - - - -
- - - () - -
- -
- - $task, - 'categories' => $categories, - 'not_editable' => true, - 'project' => $project - )) ?> - -
- -
- + $project, + 'swimlanes' => $swimlanes, + 'categories' => $categories, + 'board_private_refresh_interval' => $board_private_refresh_interval, + 'board_highlight_period' => $board_highlight_period, + 'not_editable' => true, + )) ?>
\ 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 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + + \ 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 @@ - + + + + +
$task['id'], 'token' => $project['token'])) ?> @@ -28,6 +32,14 @@ +
+ $task['id']), false, 'task-edit-popover', t('Edit this task')) ?> @@ -114,3 +126,5 @@
+ + \ 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 @@ + - -
-
- $column['project_id'], 'column_id' => $column['id']), false, 'task-creation-popover', t('Add a new task')) ?> -
- - - - (/) - - - - () - - -
- -
- $task, 'categories' => $categories)) ?> + + + +
+ - - - - - + + +

+ + + $project, + 'swimlane' => $swimlane, + 'board_highlight_period' => $board_highlight_period, + 'categories' => $categories, + 'hide_swimlane' => count($swimlanes) === 1, + 'not_editable' => isset($not_editable), + )) ?> + +
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 @@ +
+ +
+ $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']), false, 'task-creation-popover', t('Add a new task')) ?> +
+ + + + + + + (/) + + + + () + + +
+ + + + + + + + $project, + 'task' => $task, + 'categories' => $categories, + 'board_highlight_period' => $board_highlight_period, + 'not_editable' => $not_editable, + )) ?> + +
@@ -26,7 +25,9 @@
-

+
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 @@
  • $project['id'])) ?>
  • +
  • + $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] - ( $project['id'], 'user_id' => $user_id, 'is_owner' => 0), true) ?> > + ( $project['id'], 'user_id' => $user_id, 'is_owner' => 0), true) ?> > - ( $project['id'], 'user_id' => $user_id, 'is_owner' => 1), true) ?> > + ( $project['id'], 'user_id' => $user_id, 'is_owner' => 1), true) ?> > or $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 @@ + + + + + + + + + + + + +
    + +
    +
  • \ 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 @@ + + + $active_swimlanes, 'project' => $project)) ?> + + + +
    + + + + + + + +
    + +
    +
    + + +
    + + + + + +
    + + + +
    + +
    +
    + + + + $inactive_swimlanes, 'project' => $project, 'hide_position' => true)) ?> + \ 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 @@ +
    + + +
    +

    + +

    + +
    + $project['id'], 'swimlane_id' => $swimlane['id']), true, 'btn btn-red') ?> + + $project['id'])) ?> +
    +
    +
    \ 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 @@ + + + + + + + + + + + + + + + + + +
    # +
      + +
    • + $project['id'], 'swimlane_id' => $swimlane['id']), true) ?> +
    • + + +
    • + $project['id'], 'swimlane_id' => $swimlane['id']), true) ?> +
    • + +
    • + $project['id'], 'swimlane_id' => $swimlane['id'])) ?> +
    • +
    • + + $project['id'], 'swimlane_id' => $swimlane['id']), true) ?> + + $project['id'], 'swimlane_id' => $swimlane['id']), true) ?> + +
    • +
    • + $project['id'], 'swimlane_id' => $swimlane['id'])) ?> +
    • +
    +
    \ 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 @@
    +
    -- cgit v1.2.3