diff --git a/app/Controller/Board.php b/app/Controller/Board.php
index 7d498f81a..c5823328c 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 (! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
- $this->response->text('Forbidden', 403);
- }
-
- $values = $this->request->getJson();
-
- if ($this->taskPosition->movePosition($project_id, $values['task_id'], $values['column_id'], $values['position'])) {
-
- $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);
- }
+ if (! $project_id || ! $this->request->isAjax()) {
+ return $this->response->status(403);
}
- else {
- $this->response->status(403);
+
+ if (! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
+ $this->response->text('Forbidden', 403);
}
+
+ $values = $this->request->getJson();
+
+ $result =$this->taskPosition->movePosition(
+ $project_id,
+ $values['task_id'],
+ $values['column_id'],
+ $values['position'],
+ $values['swimlane_id']
+ );
+
+ 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 ($project_id > 0 && ! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
- $this->response->text('Forbidden', 403);
- }
-
- 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->request->isAjax()) {
+ return $this->response->status(403);
}
- else {
- $this->response->status(403);
+
+ $project_id = $this->request->getIntegerParam('project_id');
+ $timestamp = $this->request->getIntegerParam('timestamp');
+
+ if (! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
+ $this->response->text('Forbidden', 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 27c0d9fce..b30608b7b 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 000000000..f0920f608
--- /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 8d38317c3..33b4b039b 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 bcc1962cb..1636ccb2c 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 2be6497e3..6e6301ec4 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 1fbc886a1..6e37eeadd 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 202325844..524bbee4b 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 28c999a58..9ae853bfc 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 0dbc962dc..352cd1ba3 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 0895fa53c..c07dd042c 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 b4440a551..3811243a7 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 a5045f8ff..2d1572a16 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 0c8e1adc6..5b9316b35 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 336d4af8a..457c803d7 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 bc64dbd03..33e45341e 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 11e67ca29..5f65f5722 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 cf41049cf..ce8767d43 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 9ba2e066f..8208b99db 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 000000000..ffcfb474e
--- /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 a745f30fa..3cd037413 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 320bcb933..de9f7ce15 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 ab7a57f1d..2410213b8 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 0e5810259..117edae8d 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 c23bc3b52..9a9642e54 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);
+
+ if ($result) {
+
+ 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);
}
- $this->fireEvents($original_task, $column_id, $position);
-
- return true;
+ 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 ecaf09024..ae21ca28b 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 329536569..7fc8da6a6 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 8f114616b..fd68d9c3b 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 8efe8d303..cd3192418 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 d7086504c..a44abcc8c 100644
--- a/app/Template/board/edit.php
+++ b/app/Template/board/edit.php
@@ -16,7 +16,7 @@
- | = Helper\form_label(++$i, 'title['.$column['id'].']', array('title="column_id='.$column['id'].'"')) ?> |
+ = Helper\form_label('#'.++$i, 'title['.$column['id'].']', array('title="column_id='.$column['id'].'"')) ?> |
= Helper\form_text('title['.$column['id'].']', $values, $errors, array('required')) ?> |
= Helper\form_number('task_limit['.$column['id'].']', $values, $errors, array('placeholder="'.t('limit').'"')) ?> |
diff --git a/app/Template/board/index.php b/app/Template/board/index.php
index 210403735..53c662a06 100644
--- a/app/Template/board/index.php
+++ b/app/Template/board/index.php
@@ -6,16 +6,12 @@
'project' => $project,
)) ?>
-
- = t('There is no column in your project!') ?>
-
- = 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,
- )) ?>
-
+ = 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,
+ )) ?>
diff --git a/app/Template/board/public.php b/app/Template/board/public.php
index 090d617b4..0544f3924 100644
--- a/app/Template/board/public.php
+++ b/app/Template/board/public.php
@@ -1,39 +1,12 @@
-
- = t('There is no column in your project!') ?>
-
-
-
-
-
- |
- = Helper\escape($column['title']) ?>
-
- (= Helper\escape(count($column['tasks']).'/'.$column['task_limit']) ?>)
-
- |
-
-
-
-
- |
-
-
-
- = Helper\template('board/task', array(
- 'task' => $task,
- 'categories' => $categories,
- 'not_editable' => true,
- 'project' => $project
- )) ?>
-
-
-
- |
-
-
-
-
+ = 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,
+ )) ?>
\ No newline at end of file
diff --git a/app/Template/board/show.php b/app/Template/board/show.php
index 142969c92..7df19bc12 100644
--- a/app/Template/board/show.php
+++ b/app/Template/board/show.php
@@ -1,51 +1,28 @@
-
-
-
-
- |
-
- = Helper\a('+', 'task', 'create', array('project_id' => $column['project_id'], 'column_id' => $column['id']), false, 'task-creation-popover', t('Add a new task')) ?>
-
- = Helper\escape($column['title']) ?>
-
-
- (= count($column['tasks']) ?>/= Helper\escape($column['task_limit']) ?>)
-
-
-
- (= count($column['tasks']) ?>)
-
-
- |
-
-
-
-
-
-
-
- = Helper\template('board/task', array('task' => $task, 'categories' => $categories)) ?>
+
+
+
+
+
-
-
-
-
-
+
+
+ = t('There is no column in your project!') ?>
+
+
+ = 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),
+ )) ?>
+
+
diff --git a/app/Template/board/swimlane.php b/app/Template/board/swimlane.php
new file mode 100644
index 000000000..e48eb6f15
--- /dev/null
+++ b/app/Template/board/swimlane.php
@@ -0,0 +1,59 @@
+
+
+ |
+
+
+
+
+
+
+ = 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')) ?>
+
+
+
+ = Helper\escape($column['title']) ?>
+
+
+
+ (= $column['nb_tasks'] ?>/= Helper\escape($column['task_limit']) ?>)
+
+
+
+ (= $column['nb_tasks'] ?>)
+
+
+ |
+
+
+
+
+ |
+ = Helper\escape($swimlane['name']) ?>
+ |
+
+
+
+
+
+
+
+ |
+
+
+
+ = Helper\template('board/task', array(
+ 'project' => $project,
+ 'task' => $task,
+ 'categories' => $categories,
+ 'board_highlight_period' => $board_highlight_period,
+ 'not_editable' => $not_editable,
+ )) ?>
+
+ |
+
+
\ No newline at end of file
diff --git a/app/Template/board/task.php b/app/Template/board/task.php
index 64448acc4..d077c7735 100644
--- a/app/Template/board/task.php
+++ b/app/Template/board/task.php
@@ -1,4 +1,8 @@
-
+
+
+
+
+
= Helper\a('#'.$task['id'], 'task', 'readonly', array('task_id' => $task['id'], 'token' => $project['token'])) ?>
@@ -28,6 +32,14 @@
+
+
= Helper\a('#'.$task['id'], 'task', 'edit', array('task_id' => $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 8a3eb4f16..bd275a670 100644
--- a/app/Template/category/index.php
+++ b/app/Template/category/index.php
@@ -1,8 +1,7 @@
+
-
-
| = t('Category Name') ?> |
@@ -26,7 +25,9 @@
-= t('Add a new category') ?>
+
| |