summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--controllers/task.php109
-rw-r--r--locales/fr_FR/translations.php2
-rw-r--r--locales/pl_PL/translations.php2
-rw-r--r--models/acl.php2
-rw-r--r--models/board.php5
-rw-r--r--models/task.php214
-rw-r--r--templates/task_new.php7
-rw-r--r--templates/task_show.php2
-rw-r--r--tests/TaskTest.php26
9 files changed, 344 insertions, 25 deletions
diff --git a/controllers/task.php b/controllers/task.php
index 75063b7b..0dae014e 100644
--- a/controllers/task.php
+++ b/controllers/task.php
@@ -4,9 +4,19 @@ namespace Controller;
require_once __DIR__.'/base.php';
+/**
+ * Task controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
class Task extends Base
{
- // Webhook to create a task (useful for external software)
+ /**
+ * Webhook to create a task (useful for external software)
+ *
+ * @access public
+ */
public function add()
{
$token = $this->request->getStringParam('token');
@@ -41,7 +51,11 @@ class Task extends Base
$this->response->text('FAILED');
}
- // Display the template show task, common between different task view
+ /**
+ * Display the template show task, common between different task view
+ *
+ * @access public
+ */
private function showTask(array $task, array $comment_form = array(), array $description_form = array())
{
if (empty($comment_form)) {
@@ -77,7 +91,11 @@ class Task extends Base
)));
}
- // Show a task
+ /**
+ * Show a task
+ *
+ * @access public
+ */
public function show()
{
$task = $this->task->getById($this->request->getIntegerParam('task_id'), true);
@@ -88,7 +106,11 @@ class Task extends Base
$this->showTask($task);
}
- // Add a comment
+ /**
+ * Add a comment
+ *
+ * @access public
+ */
public function comment()
{
$task = $this->task->getById($this->request->getIntegerParam('task_id'), true);
@@ -117,7 +139,11 @@ class Task extends Base
);
}
- // Add a description from the show task page
+ /**
+ * Add a description from the show task page
+ *
+ * @access public
+ */
public function description()
{
$task = $this->task->getById($this->request->getIntegerParam('task_id'), true);
@@ -147,7 +173,11 @@ class Task extends Base
);
}
- // Display a form to create a new task
+ /**
+ * Display a form to create a new task
+ *
+ * @access public
+ */
public function create()
{
$project_id = $this->request->getIntegerParam('project_id');
@@ -171,7 +201,11 @@ class Task extends Base
)));
}
- // Validate and save a new task
+ /**
+ * Validate and save a new task
+ *
+ * @access public
+ */
public function save()
{
$values = $this->request->getValues();
@@ -210,7 +244,11 @@ class Task extends Base
)));
}
- // Display a form to edit a task
+ /**
+ * Display a form to edit a task
+ *
+ * @access public
+ */
public function edit()
{
$task = $this->task->getById($this->request->getIntegerParam('task_id'));
@@ -233,7 +271,11 @@ class Task extends Base
)));
}
- // Validate and update a task
+ /**
+ * Validate and update a task
+ *
+ * @access public
+ */
public function update()
{
$values = $this->request->getValues();
@@ -263,7 +305,11 @@ class Task extends Base
)));
}
- // Hide a task
+ /**
+ * Hide a task
+ *
+ * @access public
+ */
public function close()
{
$task = $this->task->getById($this->request->getIntegerParam('task_id'));
@@ -280,7 +326,11 @@ class Task extends Base
$this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
}
- // Confirmation dialog before to close a task
+ /**
+ * Confirmation dialog before to close a task
+ *
+ * @access public
+ */
public function confirmClose()
{
$task = $this->task->getById($this->request->getIntegerParam('task_id'));
@@ -295,7 +345,11 @@ class Task extends Base
)));
}
- // Open a task
+ /**
+ * Open a task
+ *
+ * @access public
+ */
public function open()
{
$task = $this->task->getById($this->request->getIntegerParam('task_id'));
@@ -312,7 +366,11 @@ class Task extends Base
$this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
}
- // Confirmation dialog before to open a task
+ /**
+ * Confirmation dialog before to open a task
+ *
+ * @access public
+ */
public function confirmOpen()
{
$task = $this->task->getById($this->request->getIntegerParam('task_id'));
@@ -326,4 +384,29 @@ class Task extends Base
'title' => t('Open a task')
)));
}
+
+ /**
+ * Duplicate a task (fill the form for a new task)
+ *
+ * @access public
+ */
+ public function duplicate()
+ {
+ $task = $this->task->getById($this->request->getIntegerParam('task_id'));
+
+ if (! $task) $this->notfound();
+ $this->checkProjectPermissions($task['project_id']);
+
+ $this->response->html($this->template->layout('task_new', array(
+ 'errors' => array(),
+ 'values' => $task,
+ 'projects_list' => $this->project->getListByStatus(\Model\Project::ACTIVE),
+ 'columns_list' => $this->board->getColumnsList($task['project_id']),
+ 'users_list' => $this->project->getUsersList($task['project_id']),
+ 'colors_list' => $this->task->getColors(),
+ 'duplicate' => true,
+ 'menu' => 'tasks',
+ 'title' => t('New task')
+ )));
+ }
}
diff --git a/locales/fr_FR/translations.php b/locales/fr_FR/translations.php
index 3b84029e..4c604e28 100644
--- a/locales/fr_FR/translations.php
+++ b/locales/fr_FR/translations.php
@@ -252,4 +252,6 @@ return array(
'Position' => 'Position',
'Move Up' => 'Déplacer vers le haut',
'Move Down' => 'Déplacer vers le bas',
+ 'Duplicate to another project' => 'Dupliquer dans un autre projet',
+ 'Duplicate' => 'Dupliquer',
);
diff --git a/locales/pl_PL/translations.php b/locales/pl_PL/translations.php
index 458ad25d..c030bebf 100644
--- a/locales/pl_PL/translations.php
+++ b/locales/pl_PL/translations.php
@@ -255,4 +255,6 @@ return array(
// 'Position' => '',
// 'Move Up' => '',
// 'Move Down' => '',
+ // 'Duplicate to another project' => '',
+ // 'Duplicate' => '',
);
diff --git a/models/acl.php b/models/acl.php
index 25386254..fe23bbb4 100644
--- a/models/acl.php
+++ b/models/acl.php
@@ -18,7 +18,7 @@ class Acl extends Base
'app' => array('index'),
'board' => array('index', 'show', 'assign', 'assigntask', 'save'),
'project' => array('tasks', 'index', 'forbidden'),
- 'task' => array('show', 'create', 'save', 'edit', 'update', 'close', 'confirmclose', 'open', 'confirmopen', 'comment', 'description'),
+ 'task' => array('show', 'create', 'save', 'edit', 'update', 'close', 'confirmclose', 'open', 'confirmopen', 'comment', 'description', 'duplicate'),
'user' => array('index', 'edit', 'update', 'forbidden', 'logout', 'index'),
'config' => array('index'),
);
diff --git a/models/board.php b/models/board.php
index ae306508..723ffd85 100644
--- a/models/board.php
+++ b/models/board.php
@@ -16,6 +16,11 @@ use \SimpleValidator\Validators;
*/
class Board extends Base
{
+ /**
+ * SQL table name
+ *
+ * @var string
+ */
const TABLE = 'columns';
/**
diff --git a/models/task.php b/models/task.php
index fe0f6350..cbe37feb 100644
--- a/models/task.php
+++ b/models/task.php
@@ -8,9 +8,34 @@ require_once __DIR__.'/comment.php';
use \SimpleValidator\Validator;
use \SimpleValidator\Validators;
+/**
+ * Task model
+ *
+ * @package model
+ * @author Frederic Guillot
+ */
class Task extends Base
{
+ /**
+ * SQL table name
+ *
+ * @var string
+ */
const TABLE = 'tasks';
+
+ /**
+ * Task status
+ *
+ * @var integer
+ */
+ const STATUS_OPEN = 1;
+ const STATUS_CLOSED = 0;
+
+ /**
+ * Events
+ *
+ * @var string
+ */
const EVENT_MOVE_COLUMN = 'task.move.column';
const EVENT_MOVE_POSITION = 'task.move.position';
const EVENT_UPDATE = 'task.update';
@@ -18,6 +43,12 @@ class Task extends Base
const EVENT_CLOSE = 'task.close';
const EVENT_OPEN = 'task.open';
+ /**
+ * Get available colors
+ *
+ * @access public
+ * @return array
+ */
public function getColors()
{
return array(
@@ -31,6 +62,14 @@ class Task extends Base
);
}
+ /**
+ * Fetch one task
+ *
+ * @access public
+ * @param integer $task_id Task id
+ * @param boolean $more If true, fetch all related information
+ * @return array
+ */
public function getById($task_id, $more = false)
{
if ($more) {
@@ -67,7 +106,15 @@ class Task extends Base
}
}
- public function getAllByProjectId($project_id, array $status = array(1, 0))
+ /**
+ * Get all tasks for a given project
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param array $status List of status id
+ * @return array
+ */
+ public function getAllByProjectId($project_id, array $status = array(self::STATUS_OPEN, self::STATUS_CLOSED))
{
return $this->db->table(self::TABLE)
->columns(
@@ -95,7 +142,15 @@ class Task extends Base
->findAll();
}
- public function countByProjectId($project_id, $status = array(1, 0))
+ /**
+ * Count all tasks for a given project and status
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param array $status List of status id
+ * @return integer
+ */
+ public function countByProjectId($project_id, array $status = array(self::STATUS_OPEN, self::STATUS_CLOSED))
{
return $this->db
->table(self::TABLE)
@@ -104,7 +159,16 @@ class Task extends Base
->count();
}
- public function getAllByColumnId($project_id, $column_id, $status = array(1))
+ /**
+ * Get all tasks for a given column
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $column_id Column id
+ * @param array $status List of status id
+ * @return array
+ */
+ public function getAllByColumnId($project_id, $column_id, array $status = array(self::STATUS_OPEN))
{
$tasks = $this->db
->table(self::TABLE)
@@ -125,7 +189,16 @@ class Task extends Base
return $tasks;
}
- public function countByColumnId($project_id, $column_id, $status = array(1))
+ /**
+ * Count the number of tasks for a given column and status
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $column_id Column id
+ * @param array $status List of status id
+ * @return integer
+ */
+ public function countByColumnId($project_id, $column_id, array $status = array(self::STATUS_OPEN))
{
return $this->db
->table(self::TABLE)
@@ -135,6 +208,55 @@ class Task extends Base
->count();
}
+ /**
+ * Duplicate a task
+ *
+ * @access public
+ * @param integer $task_id Task id
+ * @return boolean
+ */
+ public function duplicate($task_id)
+ {
+ $this->db->startTransaction();
+
+ $boardModel = new Board($this->db, $this->event);
+
+ // Get the original task
+ $task = $this->getById($task_id);
+
+ // Cleanup data
+ unset($task['id']);
+ unset($task['date_completed']);
+
+ // Assign new values
+ $task['date_creation'] = time();
+ $task['is_active'] = 1;
+ $task['position'] = $this->countByColumnId($task['project_id'], $task['column_id']);
+
+ // Save task
+ if (! $this->db->table(self::TABLE)->save($task)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+
+ $task_id = $this->db->getConnection()->getLastId();
+
+ $this->db->closeTransaction();
+
+ // Trigger events
+ $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $task);
+
+ return $task_id;
+ }
+
+ /**
+ * Duplicate a task to another project (always copy to the first column)
+ *
+ * @access public
+ * @param integer $task_id Task id
+ * @param integer $project_id Destination project id
+ * @return boolean
+ */
public function duplicateToAnotherProject($task_id, $project_id)
{
$this->db->startTransaction();
@@ -172,6 +294,13 @@ class Task extends Base
return $task_id;
}
+ /**
+ * Create a task
+ *
+ * @access public
+ * @param array $values Form values
+ * @return boolean
+ */
public function create(array $values)
{
$this->db->startTransaction();
@@ -204,6 +333,13 @@ class Task extends Base
return $task_id;
}
+ /**
+ * Update a task
+ *
+ * @access public
+ * @param array $values Form values
+ * @return boolean
+ */
public function update(array $values)
{
// Prepare data
@@ -241,7 +377,13 @@ class Task extends Base
return $result;
}
- // Mark a task closed
+ /**
+ * Mark a task closed
+ *
+ * @access public
+ * @param integer $task_id Task id
+ * @return boolean
+ */
public function close($task_id)
{
$result = $this->db
@@ -259,7 +401,13 @@ class Task extends Base
return $result;
}
- // Mark a task open
+ /**
+ * Mark a task open
+ *
+ * @access public
+ * @param integer $task_id Task id
+ * @return boolean
+ */
public function open($task_id)
{
$result = $this->db
@@ -277,13 +425,27 @@ class Task extends Base
return $result;
}
- // Remove a task
+ /**
+ * Remove a task
+ *
+ * @access public
+ * @param integer $task_id Task id
+ * @return boolean
+ */
public function remove($task_id)
{
return $this->db->table(self::TABLE)->eq('id', $task_id)->remove();
}
- // Move a task to another column or to another position
+ /**
+ * Move a task to another column or to another position
+ *
+ * @access public
+ * @param integer $task_id Task id
+ * @param integer $column_id Column id
+ * @param integer $position Position (must be greater than 1)
+ * @return boolean
+ */
public function move($task_id, $column_id, $position)
{
return $this->update(array(
@@ -293,6 +455,13 @@ class Task extends Base
));
}
+ /**
+ * Validate task creation
+ *
+ * @access public
+ * @param array $values Form values
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
+ */
public function validateCreation(array $values)
{
$v = new Validator($values, array(
@@ -314,6 +483,13 @@ class Task extends Base
);
}
+ /**
+ * Validate description creation
+ *
+ * @access public
+ * @param array $values Form values
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
+ */
public function validateDescriptionCreation(array $values)
{
$v = new Validator($values, array(
@@ -328,6 +504,13 @@ class Task extends Base
);
}
+ /**
+ * Validate task 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)
{
$v = new Validator($values, array(
@@ -351,6 +534,13 @@ class Task extends Base
);
}
+ /**
+ * Validate assignee change
+ *
+ * @access public
+ * @param array $values Form values
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
+ */
public function validateAssigneeModification(array $values)
{
$v = new Validator($values, array(
@@ -368,6 +558,14 @@ class Task extends Base
);
}
+ /**
+ * Parse a date (different format for each locale) to a timestamp
+ *
+ * @access public
+ * @param string $value Date to parse
+ * @param string $format Date format
+ * @return integer
+ */
public function getTimestampFromDate($value, $format)
{
$date = \DateTime::createFromFormat($format, $value);
diff --git a/templates/task_new.php b/templates/task_new.php
index 88ffd53f..4280bf80 100644
--- a/templates/task_new.php
+++ b/templates/task_new.php
@@ -8,8 +8,7 @@
<?= Helper\form_label(t('Title'), 'title') ?>
<?= Helper\form_text('title', $values, $errors, array('autofocus required')) ?><br/>
- <?= Helper\form_label(t('Project'), 'project_id') ?>
- <?= Helper\form_select('project_id', $projects_list, $values, $errors) ?><br/>
+ <?= Helper\form_hidden('project_id', $values) ?>
<?= Helper\form_label(t('Column'), 'column_id') ?>
<?= Helper\form_select('column_id', $columns_list, $values, $errors) ?><br/>
@@ -30,7 +29,9 @@
<?= Helper\form_textarea('description', $values, $errors) ?><br/>
<div class="form-help"><a href="http://kanboard.net/documentation/syntax-guide" target="_blank" rel="noreferrer"><?= t('Write your text in Markdown') ?></a></div>
- <?= Helper\form_checkbox('another_task', t('Create another task'), 1, isset($values['another_task']) && $values['another_task'] == 1) ?>
+ <?php if (! isset($duplicate)): ?>
+ <?= Helper\form_checkbox('another_task', t('Create another task'), 1, isset($values['another_task']) && $values['another_task'] == 1) ?>
+ <?php endif ?>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
diff --git a/templates/task_show.php b/templates/task_show.php
index cc1d9e25..986b240d 100644
--- a/templates/task_show.php
+++ b/templates/task_show.php
@@ -3,6 +3,8 @@
<h2>#<?= $task['id'] ?> - <?= Helper\escape($task['title']) ?></h2>
<ul>
<li><a href="?controller=board&amp;action=show&amp;project_id=<?= $task['project_id'] ?>"><?= t('Back to the board') ?></a></li>
+ <li><a href="?controller=task&amp;action=duplicate&amp;project_id=<?= $task['project_id'] ?>&amp;task_id=<?= $task['id'] ?>"><?= t('Duplicate') ?></a></li>
+ <!-- <li><a href="#"><?= t('Duplicate to another project') ?></a></li> -->
</ul>
</div>
<section>
diff --git a/tests/TaskTest.php b/tests/TaskTest.php
index a3417e91..5ddb1860 100644
--- a/tests/TaskTest.php
+++ b/tests/TaskTest.php
@@ -20,6 +20,32 @@ class TaskTest extends Base
$this->assertEquals(0, $t->getTimestampFromDate('5-3-2014', 'd/m/Y'));
}
+ public function testDuplicateTask()
+ {
+ $t = new Task($this->db, $this->event);
+ $p = new Project($this->db, $this->event);
+
+ // We create a task and a project
+ $this->assertEquals(1, $p->create(array('name' => 'test1')));
+ $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1)));
+
+ $task = $t->getById(1);
+ $this->assertNotEmpty($task);
+ $this->assertEquals(0, $task['position']);
+
+ // We duplicate our task
+ $this->assertEquals(2, $t->duplicate(1));
+ $this->assertEquals(Task::EVENT_CREATE, $this->event->getLastTriggeredEvent());
+
+ // Check the values of the duplicated task
+ $task = $t->getById(2);
+ $this->assertNotEmpty($task);
+ $this->assertEquals(Task::STATUS_OPEN, $task['is_active']);
+ $this->assertEquals(1, $task['project_id']);
+ $this->assertEquals(1, $task['owner_id']);
+ $this->assertEquals(1, $task['position']);
+ }
+
public function testDuplicateToAnotherProject()
{
$t = new Task($this->db, $this->event);