Task duplication
This commit is contained in:
parent
41dd9b2eff
commit
ab63ffafc5
|
|
@ -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')
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -255,4 +255,6 @@ return array(
|
|||
// 'Position' => '',
|
||||
// 'Move Up' => '',
|
||||
// 'Move Down' => '',
|
||||
// 'Duplicate to another project' => '',
|
||||
// 'Duplicate' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ use \SimpleValidator\Validators;
|
|||
*/
|
||||
class Board extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'columns';
|
||||
|
||||
/**
|
||||
|
|
|
|||
214
models/task.php
214
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);
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
<h2>#<?= $task['id'] ?> - <?= Helper\escape($task['title']) ?></h2>
|
||||
<ul>
|
||||
<li><a href="?controller=board&action=show&project_id=<?= $task['project_id'] ?>"><?= t('Back to the board') ?></a></li>
|
||||
<li><a href="?controller=task&action=duplicate&project_id=<?= $task['project_id'] ?>&task_id=<?= $task['id'] ?>"><?= t('Duplicate') ?></a></li>
|
||||
<!-- <li><a href="#"><?= t('Duplicate to another project') ?></a></li> -->
|
||||
</ul>
|
||||
</div>
|
||||
<section>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue