Prompt user when moving or duplicate a task to another project

This commit is contained in:
Frederic Guillot 2015-07-19 17:03:06 -04:00
parent 0dd17c6137
commit fcdd71af2c
33 changed files with 517 additions and 176 deletions

View File

@ -366,34 +366,6 @@ class Task extends Base
)));
}
/**
* Duplicate a task
*
* @access public
*/
public function duplicate()
{
$task = $this->getTask();
if ($this->request->getStringParam('confirmation') === 'yes') {
$this->checkCSRFParam();
$task_id = $this->taskDuplication->duplicate($task['id']);
if ($task_id) {
$this->session->flash(t('Task created successfully.'));
$this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
} else {
$this->session->flashError(t('Unable to create this task.'));
$this->response->redirect($this->helper->url->to('task', 'duplicate', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
}
$this->response->html($this->taskLayout('task/duplicate', array(
'task' => $task,
)));
}
/**
* Edit description form
*
@ -492,84 +464,6 @@ class Task extends Base
$this->response->html($this->taskLayout('task/edit_recurrence', $params));
}
/**
* Move a task to another project
*
* @access public
*/
public function move()
{
$task = $this->getTask();
$values = $task;
$errors = array();
$projects_list = $this->projectPermission->getActiveMemberProjects($this->userSession->getId());
unset($projects_list[$task['project_id']]);
if ($this->request->isPost()) {
$values = $this->request->getValues();
list($valid, $errors) = $this->taskValidator->validateProjectModification($values);
if ($valid) {
if ($this->taskDuplication->moveToProject($task['id'], $values['project_id'])) {
$this->session->flash(t('Task updated successfully.'));
$this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
else {
$this->session->flashError(t('Unable to update your task.'));
}
}
}
$this->response->html($this->taskLayout('task/move_project', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
'projects_list' => $projects_list,
)));
}
/**
* Duplicate a task to another project
*
* @access public
*/
public function copy()
{
$task = $this->getTask();
$values = $task;
$errors = array();
$projects_list = $this->projectPermission->getActiveMemberProjects($this->userSession->getId());
unset($projects_list[$task['project_id']]);
if ($this->request->isPost()) {
$values = $this->request->getValues();
list($valid, $errors) = $this->taskValidator->validateProjectModification($values);
if ($valid) {
$task_id = $this->taskDuplication->duplicateToProject($task['id'], $values['project_id']);
if ($task_id) {
$this->session->flash(t('Task created successfully.'));
$this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
else {
$this->session->flashError(t('Unable to create your task.'));
}
}
}
$this->response->html($this->taskLayout('task/duplicate_project', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
'projects_list' => $projects_list,
)));
}
/**
* Display the time tracking details
*

View File

@ -0,0 +1,143 @@
<?php
namespace Controller;
/**
* Task Duplication controller
*
* @package controller
* @author Frederic Guillot
*/
class Taskduplication extends Base
{
/**
* Duplicate a task
*
* @access public
*/
public function duplicate()
{
$task = $this->getTask();
if ($this->request->getStringParam('confirmation') === 'yes') {
$this->checkCSRFParam();
$task_id = $this->taskDuplication->duplicate($task['id']);
if ($task_id > 0) {
$this->session->flash(t('Task created successfully.'));
$this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
} else {
$this->session->flashError(t('Unable to create this task.'));
$this->response->redirect($this->helper->url->to('taskduplication', 'duplicate', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
}
$this->response->html($this->taskLayout('task_duplication/duplicate', array(
'task' => $task,
)));
}
/**
* Move a task to another project
*
* @access public
*/
public function move()
{
$task = $this->getTask();
if ($this->request->isPost()) {
$values = $this->request->getValues();
list($valid, $errors) = $this->taskValidator->validateProjectModification($values);
if ($valid && $this->taskDuplication->moveToProject($task['id'],
$values['project_id'],
$values['swimlane_id'],
$values['column_id'],
$values['category_id'],
$values['owner_id'])) {
$this->session->flash(t('Task updated successfully.'));
$this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $values['project_id'], 'task_id' => $task['id'])));
}
$this->session->flashError(t('Unable to update your task.'));
}
$this->chooseDestination($task, 'task_duplication/move');
}
/**
* Duplicate a task to another project
*
* @access public
*/
public function copy()
{
$task = $this->getTask();
if ($this->request->isPost()) {
$values = $this->request->getValues();
list($valid, $errors) = $this->taskValidator->validateProjectModification($values);
if ($valid && $this->taskDuplication->duplicateToProject($task['id'],
$values['project_id'],
$values['swimlane_id'],
$values['column_id'],
$values['category_id'],
$values['owner_id'])) {
$this->session->flash(t('Task created successfully.'));
$this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
$this->session->flashError(t('Unable to create your task.'));
}
$this->chooseDestination($task, 'task_duplication/copy');
}
/**
* Choose destination when move/copy task to another project
*
* @access private
*/
private function chooseDestination(array $task, $template)
{
$values = array();
$projects_list = $this->projectPermission->getActiveMemberProjects($this->userSession->getId());
unset($projects_list[$task['project_id']]);
if (! empty($projects_list)) {
$dst_project_id = $this->request->getIntegerParam('dst_project_id', key($projects_list));
$swimlanes_list = $this->swimlane->getList($dst_project_id, false, true);
$columns_list = $this->board->getColumnsList($dst_project_id);
$categories_list = $this->category->getList($dst_project_id);
$users_list = $this->projectPermission->getMemberList($dst_project_id);
$values = $this->taskDuplication->checkDestinationProjectValues($task);
$values['project_id'] = $dst_project_id;
}
else {
$swimlanes_list = array();
$columns_list = array();
$categories_list = array();
$users_list = array();
}
$this->response->html($this->taskLayout($template, array(
'values' => $values,
'task' => $task,
'projects_list' => $projects_list,
'swimlanes_list' => $swimlanes_list,
'columns_list' => $columns_list,
'categories_list' => $categories_list,
'users_list' => $users_list,
)));
}
}

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -1001,4 +1001,8 @@ return array(
'New remote user' => 'Créer un utilisateur distant',
'New local user' => 'Créer un utilisateur local',
'Default task color' => 'Couleur par défaut des tâches',
'Hide sidebar' => 'Cacher la barre latérale',
'Expand sidebar' => 'Déplier la barre latérale',
'This feature does not work with all browsers.' => 'Cette fonctionnalité n\'est pas compatible avec tous les navigateurs',
'There is no destination project available.' => 'Il n\'y a pas de projet de destination disponible.',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -999,4 +999,8 @@ return array(
// 'New remote user' => '',
// 'New local user' => '',
// 'Default task color' => '',
// 'Hide sidebar' => '',
// 'Expand sidebar' => '',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
);

View File

@ -41,6 +41,7 @@ class Acl extends Base
'activity' => '*',
'subtask' => '*',
'task' => '*',
'taskduplication' => '*',
'tasklink' => '*',
'timer' => '*',
'calendar' => array('show', 'project'),

View File

@ -93,15 +93,22 @@ class TaskDuplication extends Base
* Duplicate a task to another project
*
* @access public
* @param integer $task_id Task id
* @param integer $project_id Project id
* @return boolean|integer Duplicated task id
* @param integer $task_id
* @param integer $project_id
* @param integer $swimlane_id
* @param integer $column_id
* @param integer $category_id
* @param integer $owner_id
* @return boolean|integer
*/
public function duplicateToProject($task_id, $project_id)
public function duplicateToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null)
{
$values = $this->copyFields($task_id);
$values['project_id'] = $project_id;
$values['column_id'] = $this->board->getFirstColumn($project_id);
$values['column_id'] = $column_id !== null ? $column_id : $this->board->getFirstColumn($project_id);
$values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $values['swimlane_id'];
$values['category_id'] = $category_id !== null ? $category_id : $values['category_id'];
$values['owner_id'] = $owner_id !== null ? $owner_id : $values['owner_id'];
$this->checkDestinationProjectValues($values);
@ -112,22 +119,26 @@ class TaskDuplication extends Base
* Move a task to another project
*
* @access public
* @param integer $task_id Task id
* @param integer $project_id Project id
* @param integer $task_id
* @param integer $project_id
* @param integer $swimlane_id
* @param integer $column_id
* @param integer $category_id
* @param integer $owner_id
* @return boolean
*/
public function moveToProject($task_id, $project_id)
public function moveToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null)
{
$task = $this->taskFinder->getById($task_id);
$values = array();
$values['is_active'] = 1;
$values['project_id'] = $project_id;
$values['column_id'] = $this->board->getFirstColumn($project_id);
$values['column_id'] = $column_id !== null ? $column_id : $this->board->getFirstColumn($project_id);
$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'];
$values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $task['swimlane_id'];
$values['category_id'] = $category_id !== null ? $category_id : $task['category_id'];
$values['owner_id'] = $owner_id !== null ? $owner_id : $task['owner_id'];
$this->checkDestinationProjectValues($values);
@ -144,10 +155,10 @@ class TaskDuplication extends Base
/**
* Check if the assignee and the category are available in the destination project
*
* @access private
* @access public
* @param array $values
*/
private function checkDestinationProjectValues(&$values)
public function checkDestinationProjectValues(array &$values)
{
// Check if the assigned user is allowed for the destination project
if ($values['owner_id'] > 0 && ! $this->projectPermission->isUserAllowed($values['project_id'], $values['owner_id'])) {
@ -169,6 +180,8 @@ class TaskDuplication extends Base
$this->swimlane->getNameById($values['swimlane_id'])
);
}
return $values;
}
/**

View File

@ -1,24 +0,0 @@
<div class="page-header">
<h2><?= t('Duplicate the task to another project') ?></h2>
</div>
<?php if (empty($projects_list)): ?>
<p class="alert"><?= t('No project') ?></p>
<?php else: ?>
<form method="post" action="<?= $this->url->href('task', 'copy', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
<?= $this->form->hidden('id', $values) ?>
<?= $this->form->label(t('Project'), 'project_id') ?>
<?= $this->form->select('project_id', $projects_list, $values, $errors) ?><br/>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
<?= t('or') ?>
<?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</div>
</form>
<?php endif ?>

View File

@ -1,24 +0,0 @@
<div class="page-header">
<h2><?= t('Move the task to another project') ?></h2>
</div>
<?php if (empty($projects_list)): ?>
<p class="alert"><?= t('No project') ?></p>
<?php else: ?>
<form method="post" action="<?= $this->url->href('task', 'move', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
<?= $this->form->hidden('id', $values) ?>
<?= $this->form->label(t('Project'), 'project_id') ?>
<?= $this->form->select('project_id', $projects_list, $values, $errors) ?><br/>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
<?= t('or') ?>
<?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</div>
</form>
<?php endif ?>

View File

@ -46,13 +46,13 @@
<?= $this->url->link(t('Add a screenshot'), 'file', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<li>
<?= $this->url->link(t('Duplicate'), 'task', 'duplicate', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
<?= $this->url->link(t('Duplicate'), 'taskduplication', 'duplicate', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<li>
<?= $this->url->link(t('Duplicate to another project'), 'task', 'copy', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
<?= $this->url->link(t('Duplicate to another project'), 'taskduplication', 'copy', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<li>
<?= $this->url->link(t('Move to another project'), 'task', 'move', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
<?= $this->url->link(t('Move to another project'), 'taskduplication', 'move', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<li>
<?php if ($task['is_active'] == 1): ?>

View File

@ -0,0 +1,43 @@
<div class="page-header">
<h2><?= t('Duplicate the task to another project') ?></h2>
</div>
<?php if (empty($projects_list)): ?>
<p class="alert"><?= t('There is no destination project available.') ?></p>
<?php else: ?>
<form method="post" action="<?= $this->url->href('taskduplication', 'copy', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
<?= $this->form->hidden('id', $values) ?>
<?= $this->form->label(t('Project'), 'project_id') ?>
<?= $this->form->select(
'project_id',
$projects_list,
$values,
array(),
array('data-redirect="'.$this->url->href('taskduplication', 'copy', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'dst_project_id' => 'PROJECT_ID')).'"'),
'task-reload-project-destination'
) ?>
<?= $this->form->label(t('Swimlane'), 'swimlane_id') ?>
<?= $this->form->select('swimlane_id', $swimlanes_list, $values) ?>
<?= $this->form->label(t('Column'), 'column_id') ?>
<?= $this->form->select('column_id', $columns_list, $values) ?>
<?= $this->form->label(t('Category'), 'category_id') ?>
<?= $this->form->select('category_id', $categories_list, $values) ?>
<?= $this->form->label(t('Assignee'), 'owner_id') ?>
<?= $this->form->select('owner_id', $users_list, $values) ?>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
<?= t('or') ?>
<?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</div>
</form>
<?php endif ?>

View File

@ -8,7 +8,7 @@
</p>
<div class="form-actions">
<?= $this->url->link(t('Yes'), 'task', 'duplicate', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'confirmation' => 'yes'), true, 'btn btn-red') ?>
<?= $this->url->link(t('Yes'), 'taskduplication', 'duplicate', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'confirmation' => 'yes'), true, 'btn btn-red') ?>
<?= t('or') ?>
<?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</div>

View File

@ -0,0 +1,43 @@
<div class="page-header">
<h2><?= t('Move the task to another project') ?></h2>
</div>
<?php if (empty($projects_list)): ?>
<p class="alert"><?= t('There is no destination project available.') ?></p>
<?php else: ?>
<form method="post" action="<?= $this->url->href('taskduplication', 'move', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
<?= $this->form->hidden('id', $values) ?>
<?= $this->form->label(t('Project'), 'project_id') ?>
<?= $this->form->select(
'project_id',
$projects_list,
$values,
array(),
array('data-redirect="'.$this->url->href('taskduplication', 'move', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'dst_project_id' => 'PROJECT_ID')).'"'),
'task-reload-project-destination'
) ?>
<?= $this->form->label(t('Swimlane'), 'swimlane_id') ?>
<?= $this->form->select('swimlane_id', $swimlanes_list, $values) ?>
<?= $this->form->label(t('Column'), 'column_id') ?>
<?= $this->form->select('column_id', $columns_list, $values) ?>
<?= $this->form->label(t('Category'), 'category_id') ?>
<?= $this->form->select('category_id', $categories_list, $values) ?>
<?= $this->form->label(t('Assignee'), 'owner_id') ?>
<?= $this->form->select('owner_id', $users_list, $values) ?>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
<?= t('or') ?>
<?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</div>
</form>
<?php endif ?>

View File

@ -89,6 +89,12 @@ if (ENABLE_URL_REWRITE) {
$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/column/:column_id', 'task', 'create', array('project_id', 'swimlane_id', 'column_id'));
$container['router']->addRoute('public/task/:task_id/:token', 'task', 'readonly', array('task_id', 'token'));
$container['router']->addRoute('project/:project_id/task/:task_id/duplicate', 'taskduplication', 'duplicate', array('task_id', 'project_id'));
$container['router']->addRoute('project/:project_id/task/:task_id/copy', 'taskduplication', 'copy', array('task_id', 'project_id'));
$container['router']->addRoute('project/:project_id/task/:task_id/copy/:dst_project_id', 'taskduplication', 'copy', array('task_id', 'project_id', 'dst_project_id'));
$container['router']->addRoute('project/:project_id/task/:task_id/move', 'taskduplication', 'move', array('task_id', 'project_id'));
$container['router']->addRoute('project/:project_id/task/:task_id/move/:dst_project_id', 'taskduplication', 'move', array('task_id', 'project_id', 'dst_project_id'));
// Board routes
$container['router']->addRoute('board/:project_id', 'board', 'show', array('project_id'));
$container['router']->addRoute('b/:project_id', 'board', 'show', array('project_id'));

View File

@ -147,10 +147,11 @@ type:"POST",processData:!1,dataType:"html",data:JSON.stringify({text:h.val()})})
CheckSession:function(){$(".form-login").length||$.ajax({cache:!1,url:$("body").data("status-url"),statusCode:{401:function(){window.location=$("body").data("login-url")}}})},Init:function(){$(".chosen-select").chosen({width:"200px",no_results_text:$(".chosen-select").data("notfound"),disable_search_threshold:10});$("#board-selector").chosen({width:180,no_results_text:$("#board-selector").data("notfound")});$("#board-selector").change(function(){window.location=$(this).attr("data-board-url").replace(/PROJECT_ID/g,
$(this).val())});window.setInterval(Kanboard.CheckSession,6E4);Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(b){b.preventDefault();$("#board-selector").trigger("chosen:open")});Mousetrap.bind("f",function(b){b.preventDefault();(b=document.getElementById("form-search"))&&b.focus()});Mousetrap.bind("v b",function(b){b=$(".view-board");b.length&&(window.location=b.attr("href"))});Mousetrap.bind("v c",function(b){b=$(".view-calendar");b.length&&(window.location=
b.attr("href"))});Mousetrap.bind("v l",function(b){b=$(".view-listing");b.length&&(window.location=b.attr("href"))});$(document).on("focus","#form-search",function(){$("#form-search")[0].setSelectionRange&&$("#form-search")[0].setSelectionRange($("#form-search").val().length,$("#form-search").val().length)});$(document).on("click",".filter-helper",function(b){b.preventDefault();$("#form-search").val($(this).data("filter"));$("form.search").submit()});$(document).on("click",".sidebar-collapse",function(b){b.preventDefault();
$(".sidebar-container").addClass("sidebar-collapsed");$(".sidebar-expand").show();$(".sidebar h2").hide();$(".sidebar ul").hide();$(".sidebar-collapse").hide()});$(document).on("click",".sidebar-expand",function(b){b.preventDefault();$(".sidebar-container").removeClass("sidebar-collapsed");$(".sidebar-collapse").show();$(".sidebar h2").show();$(".sidebar ul").show();$(".sidebar-expand").hide()});$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);$(".alert-fade-out").delay(4E3).fadeOut(800,
function(){$(this).remove()});Kanboard.InitAfterAjax()},InitAfterAjax:function(){$(document).on("click",".popover",Kanboard.Popover);$("[autofocus]").each(function(b,a){$(this).focus()});$(".form-date").datepicker({showOtherMonths:!0,selectOtherMonths:!0,dateFormat:"yy-mm-dd",constrainInput:!1});$(".form-datetime").datetimepicker({controlType:"select",oneLine:!0,dateFormat:"yy-mm-dd",constrainInput:!1});$("#markdown-preview").click(Kanboard.MarkdownPreview);$("#markdown-write").click(Kanboard.MarkdownWriter);
$(".auto-select").focus(function(){$(this).select()});$(".dropit-submenu").hide();$(".dropdown").not(".dropit").dropit({triggerParentEl:"span"});$(".task-autocomplete").length&&(""==$(".opposite_task_id").val()&&$(".task-autocomplete").parent().find("input[type=submit]").attr("disabled","disabled"),$(".task-autocomplete").autocomplete({source:$(".task-autocomplete").data("search-url"),minLength:1,select:function(b,a){var c=$(".task-autocomplete").data("dst-field");$("input[name="+c+"]").val(a.item.id);
$(".task-autocomplete").parent().find("input[type=submit]").removeAttr("disabled")}}));$(".tooltip").tooltip({content:function(){return'<div class="markdown">'+$(this).attr("title")+"</div>"},position:{my:"left-20 top",at:"center bottom+9",using:function(b,a){$(this).css(b);var c=a.target.left+a.target.width/2-a.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(a.vertical).addClass(1>c?"align-left":"align-right").appendTo(this)}}});Kanboard.Exists("screenshot-zone")&&Kanboard.Screenshot.Init()}}}();
$(".sidebar-container").addClass("sidebar-collapsed");$(".sidebar-expand").show();$(".sidebar h2").hide();$(".sidebar ul").hide();$(".sidebar-collapse").hide()});$(document).on("click",".sidebar-expand",function(b){b.preventDefault();$(".sidebar-container").removeClass("sidebar-collapsed");$(".sidebar-collapse").show();$(".sidebar h2").show();$(".sidebar ul").show();$(".sidebar-expand").hide()});$("select.task-reload-project-destination").change(function(){window.location=$(this).data("redirect").replace(/PROJECT_ID/g,
$(this).val())});$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);$(".alert-fade-out").delay(4E3).fadeOut(800,function(){$(this).remove()});Kanboard.InitAfterAjax()},InitAfterAjax:function(){$(document).on("click",".popover",Kanboard.Popover);$("[autofocus]").each(function(b,a){$(this).focus()});$(".form-date").datepicker({showOtherMonths:!0,selectOtherMonths:!0,dateFormat:"yy-mm-dd",constrainInput:!1});$(".form-datetime").datetimepicker({controlType:"select",oneLine:!0,
dateFormat:"yy-mm-dd",constrainInput:!1});$("#markdown-preview").click(Kanboard.MarkdownPreview);$("#markdown-write").click(Kanboard.MarkdownWriter);$(".auto-select").focus(function(){$(this).select()});$(".dropit-submenu").hide();$(".dropdown").not(".dropit").dropit({triggerParentEl:"span"});$(".task-autocomplete").length&&(""==$(".opposite_task_id").val()&&$(".task-autocomplete").parent().find("input[type=submit]").attr("disabled","disabled"),$(".task-autocomplete").autocomplete({source:$(".task-autocomplete").data("search-url"),
minLength:1,select:function(b,a){var c=$(".task-autocomplete").data("dst-field");$("input[name="+c+"]").val(a.item.id);$(".task-autocomplete").parent().find("input[type=submit]").removeAttr("disabled")}}));$(".tooltip").tooltip({content:function(){return'<div class="markdown">'+$(this).attr("title")+"</div>"},position:{my:"left-20 top",at:"center bottom+9",using:function(b,a){$(this).css(b);var c=a.target.left+a.target.width/2-a.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(a.vertical).addClass(1>
c?"align-left":"align-right").appendTo(this)}}});Kanboard.Exists("screenshot-zone")&&Kanboard.Screenshot.Init()}}}();
(function(){function b(a){a.preventDefault();a.stopPropagation();Kanboard.Popover(a,Kanboard.InitAfterAjax)}function a(){Mousetrap.bind("n",function(){Kanboard.OpenPopover($("#board").data("task-creation-url"),Kanboard.InitAfterAjax)});Mousetrap.bind("s",function(){$.ajax({cache:!1,url:$('.filter-display-mode:not([style="display: none;"]) a').attr("href"),success:function(a){$("#board-container").remove();$("#main").append(a);Kanboard.InitAfterAjax();clearInterval(k);c();f();$(".filter-display-mode").toggle()}})});
Mousetrap.bind("c",function(){d()})}function c(){$(".column").sortable({delay:300,distance:5,connectWith:".column",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(a,c){e(c.item.attr("data-task-id"),c.item.parent().attr("data-column-id"),c.item.index()+1,c.item.parent().attr("data-swimlane-id"))}});$("#board").on("click",".task-board-popover",b);$("#board").on("click",".task-board",function(){window.location=$(this).data("task-url")});$(".task-board-tooltip").tooltip({track:!1,
position:{my:"left-20 top",at:"center bottom+9",using:function(a,c){$(this).css(a);var b=c.target.left+c.target.width/2-c.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(c.vertical).addClass(1>b?"align-left":"align-right").appendTo(this)}},content:function(a){if(a=$(this).attr("data-href")){var c=this;$.get(a,function l(a){$(".ui-tooltip-content:visible").html(a);a=$(".ui-tooltip:visible");a.css({top:"",left:""});a.children(".tooltip-arrow").remove();var b=$(c).tooltip("option","position");

View File

@ -273,6 +273,10 @@ var Kanboard = (function() {
$(".sidebar-expand").hide();
});
$("select.task-reload-project-destination").change(function() {
window.location = $(this).data("redirect").replace(/PROJECT_ID/g, $(this).val());
});
// Datepicker translation
$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);

View File

@ -0,0 +1,58 @@
Duplicate and move tasks
========================
Duplicate a task into the same project
--------------------------------------
Go to the task view and choose **Duplicate** on the left.
![Task Duplication](http://kanboard.net/screenshots/documentation/task-duplication.png)
A new task will be created with the same properties as the original.
Duplicate a task to another project
-----------------------------------
Go to the task view and choose **Duplicate to another project**.
![Task Duplication Another Project](http://kanboard.net/screenshots/documentation/task-duplication-another-project.png)
Only projects where you are member will be shown in the dropdown.
Before to copy the tasks, Kanboard will ask you the destination properties that are not common between the source and destination project.
Basically, you need to define:
- The destination swimlane
- The column
- The category
- The assignee
Move a task to another project
------------------------------
Go to the task view and choose **Move to another project**.
Moving a task to another project work in the same way as the duplication, you have to choose the new properties of the task.
List of fields duplicated
-------------------------
Here are the list of properties duplicated:
- title
- description
- date_due
- color_id
- project_id
- column_id
- owner_id
- score
- category_id
- time_estimated
- swimlane_id
- recurrence_status
- recurrence_trigger
- recurrence_factor
- recurrence_timeframe
- recurrence_basedate

View File

@ -26,6 +26,7 @@ Using Kanboard
- [Creating tasks](creating-tasks.markdown)
- [Closing tasks](closing-tasks.markdown)
- [Duplicate and move tasks](duplicate-move-tasks.markdown)
- [Adding screenshots](screenshots.markdown)
- [Task links](task-links.markdown)
- [Transitions](transitions.markdown)

View File

@ -174,6 +174,45 @@ class TaskDuplicationTest extends Base
$this->assertEquals('test', $task['title']);
}
public function testDuplicateAnotherProjectWithPredefinedCategory()
{
$td = new TaskDuplication($this->container);
$tc = new TaskCreation($this->container);
$tf = new TaskFinder($this->container);
$p = new Project($this->container);
$c = new Category($this->container);
// We create 2 projects
$this->assertEquals(1, $p->create(array('name' => 'test1')));
$this->assertEquals(2, $p->create(array('name' => 'test2')));
$this->assertNotFalse($c->create(array('name' => 'Category #1', 'project_id' => 1)));
$this->assertNotFalse($c->create(array('name' => 'Category #1', 'project_id' => 2)));
$this->assertNotFalse($c->create(array('name' => 'Category #2', 'project_id' => 2)));
$this->assertTrue($c->exists(1, 1));
$this->assertTrue($c->exists(2, 2));
$this->assertTrue($c->exists(3, 2));
// We create a task
$this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'category_id' => 1)));
// We duplicate our task to the 2nd project with no category
$this->assertEquals(2, $td->duplicateToProject(1, 2, null, null, 0));
// Check the values of the duplicated task
$task = $tf->getById(2);
$this->assertNotEmpty($task);
$this->assertEquals(0, $task['category_id']);
// We duplicate our task to the 2nd project with a different category
$this->assertEquals(3, $td->duplicateToProject(1, 2, null, null, 3));
// Check the values of the duplicated task
$task = $tf->getById(3);
$this->assertNotEmpty($task);
$this->assertEquals(3, $task['category_id']);
}
public function testDuplicateAnotherProjectWithSwimlane()
{
$td = new TaskDuplication($this->container);
@ -240,6 +279,57 @@ class TaskDuplicationTest extends Base
$this->assertEquals('test', $task['title']);
}
public function testDuplicateAnotherProjectWithPredefinedSwimlane()
{
$td = new TaskDuplication($this->container);
$tc = new TaskCreation($this->container);
$tf = new TaskFinder($this->container);
$p = new Project($this->container);
$s = new Swimlane($this->container);
// We create 2 projects
$this->assertEquals(1, $p->create(array('name' => 'test1')));
$this->assertEquals(2, $p->create(array('name' => 'test2')));
$this->assertNotFalse($s->create(1, 'Swimlane #1'));
$this->assertNotFalse($s->create(2, 'Swimlane #1'));
$this->assertNotFalse($s->create(2, 'Swimlane #2'));
// We create a task
$this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'swimlane_id' => 1)));
// We duplicate our task to the 2nd project
$this->assertEquals(2, $td->duplicateToProject(1, 2, 3));
// Check the values of the duplicated task
$task = $tf->getById(2);
$this->assertNotEmpty($task);
$this->assertEquals(3, $task['swimlane_id']);
}
public function testDuplicateAnotherProjectWithPredefinedColumn()
{
$td = new TaskDuplication($this->container);
$tc = new TaskCreation($this->container);
$tf = new TaskFinder($this->container);
$p = new Project($this->container);
// We create 2 projects
$this->assertEquals(1, $p->create(array('name' => 'test1')));
$this->assertEquals(2, $p->create(array('name' => 'test2')));
// We create a task
$this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2)));
// We duplicate our task to the 2nd project with a different column
$this->assertEquals(2, $td->duplicateToProject(1, 2, null, 7));
// Check the values of the duplicated task
$task = $tf->getById(2);
$this->assertNotEmpty($task);
$this->assertEquals(7, $task['column_id']);
}
public function testDuplicateAnotherProjectWithUser()
{
$td = new TaskDuplication($this->container);
@ -297,6 +387,30 @@ class TaskDuplicationTest extends Base
$this->assertEquals(5, $task['column_id']);
}
public function testDuplicateAnotherProjectWithPredefinedUser()
{
$td = new TaskDuplication($this->container);
$tc = new TaskCreation($this->container);
$tf = new TaskFinder($this->container);
$p = new Project($this->container);
$pp = new ProjectPermission($this->container);
// We create 2 projects
$this->assertEquals(1, $p->create(array('name' => 'test1')));
$this->assertEquals(2, $p->create(array('name' => 'test2')));
// We create a task
$this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'owner_id' => 2)));
// We duplicate our task to the 2nd project
$this->assertEquals(2, $td->duplicateToProject(1, 2, null, null, null, 1));
// Check the values of the duplicated task
$task = $tf->getById(2);
$this->assertNotEmpty($task);
$this->assertEquals(1, $task['owner_id']);
}
public function onMoveProject($event)
{
$this->assertInstanceOf('Event\TaskEvent', $event);