summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/Controller/Project.php8
-rw-r--r--app/Model/Base.php1
-rw-r--r--app/Model/ProjectDuplication.php27
-rw-r--r--app/Model/Swimlane.php33
-rw-r--r--app/Template/project/duplicate.php19
-rw-r--r--tests/units/Base.php2
-rw-r--r--tests/units/ProjectDuplicationTest.php118
-rw-r--r--tests/units/SwimlaneTest.php31
8 files changed, 223 insertions, 16 deletions
diff --git a/app/Controller/Project.php b/app/Controller/Project.php
index 8178ab5f..842ed6e8 100644
--- a/app/Controller/Project.php
+++ b/app/Controller/Project.php
@@ -297,7 +297,7 @@ class Project extends Base
/**
* Duplicate a project
*
- * @author Antonio Rabelo
+ * @author Antonio Rabelo & Michael Lüpkes
* @access public
*/
public function duplicate()
@@ -305,10 +305,8 @@ class Project extends Base
$project = $this->getProject();
if ($this->request->getStringParam('duplicate') === 'yes') {
-
- $this->checkCSRFParam();
-
- if ($this->projectDuplication->duplicate($project['id'])) {
+ $values = array_keys($this->request->getValues());
+ if ($this->projectDuplication->duplicate($project['id'], $values)) {
$this->session->flash(t('Project cloned successfully.'));
} else {
$this->session->flashError(t('Unable to clone this project.'));
diff --git a/app/Model/Base.php b/app/Model/Base.php
index 785785a7..e29588a2 100644
--- a/app/Model/Base.php
+++ b/app/Model/Base.php
@@ -34,6 +34,7 @@ use Pimple\Container;
* @property \Model\Swimlane $swimlane
* @property \Model\Task $task
* @property \Model\TaskCreation $taskCreation
+ * @property \Model\TaskDuplication $taskDuplication
* @property \Model\TaskExport $taskExport
* @property \Model\TaskFinder $taskFinder
* @property \Model\TaskHistory $taskHistory
diff --git a/app/Model/ProjectDuplication.php b/app/Model/ProjectDuplication.php
index ef4558ba..b93cbee5 100644
--- a/app/Model/ProjectDuplication.php
+++ b/app/Model/ProjectDuplication.php
@@ -59,10 +59,11 @@ class ProjectDuplication extends Base
/**
* Clone a project with all settings
*
- * @param integer $project_id Project Id
+ * @param integer $project_id Project Id
+ * @param array $part_selection Selection of optional project parts to duplicate. Possible options: 'swimlane', 'action', 'category', 'task'
* @return integer Cloned Project Id
*/
- public function duplicate($project_id)
+ public function duplicate($project_id, $part_selection = array('category', 'action'))
{
$this->db->startTransaction();
@@ -74,7 +75,14 @@ class ProjectDuplication extends Base
return false;
}
- foreach (array('board', 'category', 'projectPermission', 'action') as $model) {
+ // Clone Columns, Categories, Permissions and Actions
+ $optional_parts = array('swimlane', 'action', 'category');
+ foreach (array('board', 'category', 'projectPermission', 'action', 'swimlane') as $model) {
+
+ // Skip if optional part has not been selected
+ if (in_array($model, $optional_parts) && ! in_array($model, $part_selection)) {
+ continue;
+ }
if (! $this->$model->duplicate($project_id, $clone_project_id)) {
$this->db->cancelTransaction();
@@ -82,8 +90,21 @@ class ProjectDuplication extends Base
}
}
+
$this->db->closeTransaction();
+ //* Clone Tasks if in $part_selection
+
+ if(in_array('task', $part_selection)) {
+ $tasks = $this->taskFinder->getAll($project_id);
+
+ foreach ($tasks as $task) {
+ if (!$this->taskDuplication->duplicateToProject($task['id'], $clone_project_id)) {
+ return false;
+ }
+ }
+ }
+
return (int) $clone_project_id;
}
}
diff --git a/app/Model/Swimlane.php b/app/Model/Swimlane.php
index 71b95ae9..c9bc43e1 100644
--- a/app/Model/Swimlane.php
+++ b/app/Model/Swimlane.php
@@ -183,7 +183,7 @@ class Swimlane extends Base
* @access public
* @param integer $project_id
* @param string $name
- * @return bool
+ * @return integer|boolean
*/
public function create($project_id, $name)
{
@@ -413,6 +413,37 @@ class Swimlane extends Base
}
/**
+ * Duplicate Swimlane to project
+ *
+ * @access public
+ * @param integer $project_from Project Template
+ * @param integer $project_to Project that receives the copy
+ * @return integer|boolean
+ */
+
+ public function duplicate($project_from, $project_to)
+ {
+ $swimlanes = $this->getAll($project_from);
+
+ foreach ($swimlanes as $swimlane) {
+
+ unset($swimlane['id']);
+ $swimlane['project_id'] = $project_to;
+
+ if (! $this->db->table(self::TABLE)->save($swimlane)) {
+ return false;
+ }
+ }
+
+ $default_swimlane = $this->getDefault($project_from);
+ $default_swimlane['id'] = $project_to;
+
+ $this->updateDefault($default_swimlane);
+
+ return true;
+ }
+
+ /**
* Validate creation
*
* @access public
diff --git a/app/Template/project/duplicate.php b/app/Template/project/duplicate.php
index fc704b1e..b6184220 100644
--- a/app/Template/project/duplicate.php
+++ b/app/Template/project/duplicate.php
@@ -4,11 +4,20 @@
<div class="confirm">
<p class="alert alert-info">
- <?= t('Do you really want to duplicate this project: "%s"?', $project['name']) ?>
+ <?= t('Which parts of the project do you want to duplicate?') ?>
</p>
+ <form method="post" action="<?= $this->u('project', 'duplicate', array('project_id' => $project['id'], 'duplicate' => 'yes')) ?>" autocomplete="off">
- <div class="form-actions">
- <?= $this->a(t('Yes'), 'project', 'duplicate', array('project_id' => $project['id'], 'duplicate' => 'yes'), true, 'btn btn-red') ?>
- <?= t('or') ?> <?= $this->a(t('cancel'), 'project', 'show', array('project_id' => $project['id'])) ?>
- </div>
+ <?= $this->formCsrf() ?>
+
+ <?= $this->formCheckbox('category', t('Categories'), 1, true) ?>
+ <?= $this->formCheckbox('action', t('Actions'), 1, true) ?>
+ <?= $this->formCheckbox('swimlane', t('Swimlanes'), 1, true) ?>
+ <?= $this->formCheckbox('task', t('Tasks'), 1, true) ?>
+
+ <div class="form-actions">
+ <input type="submit" value="<?= t('Duplicate') ?>" class="btn btn-red"/>
+ <?= t('or') ?> <?= $this->a(t('cancel'), 'project', 'show', array('project_id' => $project['id'])) ?>
+ </div>
+ </form>
</div> \ No newline at end of file
diff --git a/tests/units/Base.php b/tests/units/Base.php
index b216aff4..311d3cdf 100644
--- a/tests/units/Base.php
+++ b/tests/units/Base.php
@@ -11,6 +11,8 @@ date_default_timezone_set('UTC');
abstract class Base extends PHPUnit_Framework_TestCase
{
+ protected $container;
+
public function setUp()
{
if (DB_DRIVER === 'mysql') {
diff --git a/tests/units/ProjectDuplicationTest.php b/tests/units/ProjectDuplicationTest.php
index ab50b9f1..bb8e0408 100644
--- a/tests/units/ProjectDuplicationTest.php
+++ b/tests/units/ProjectDuplicationTest.php
@@ -8,10 +8,10 @@ use Model\Category;
use Model\ProjectPermission;
use Model\ProjectDuplication;
use Model\User;
+use Model\Swimlane;
use Model\Task;
use Model\TaskCreation;
-use Model\Acl;
-use Model\Board;
+use Model\TaskFinder;
class ProjectDuplicationTest extends Base
{
@@ -203,4 +203,118 @@ class ProjectDuplicationTest extends Base
$this->assertEquals('blue', $actions[0]['params'][0]['value']);
$this->assertEquals(5, $actions[0]['params'][1]['value']);
}
+
+ public function testCloneProjectWithSwimlanesAndTasks()
+ {
+ $p = new Project($this->container);
+ $pd = new ProjectDuplication($this->container);
+ $s = new Swimlane($this->container);
+ $tc = new TaskCreation($this->container);
+ $tf = new TaskFinder($this->container);
+
+ $this->assertEquals(1, $p->create(array('name' => 'P1')));
+
+ // create initial swimlanes
+ $this->assertEquals(1, $s->create(1, 'S1'));
+ $this->assertEquals(2, $s->create(1, 'S2'));
+ $this->assertEquals(3, $s->create(1, 'S3'));
+
+ $default_swimlane1 = $s->getDefault(1);
+ $default_swimlane1['default_swimlane'] = 'New Default';
+
+ $this->assertTrue($s->updateDefault($default_swimlane1));
+
+ //create initial tasks
+ $this->assertEquals(1, $tc->create(array('title' => 'T1', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1)));
+ $this->assertEquals(2, $tc->create(array('title' => 'T2', 'project_id' => 1, 'column_id' => 2, 'owner_id' => 1)));
+ $this->assertEquals(3, $tc->create(array('title' => 'T3', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1)));
+
+ $this->container['dispatcher']->addListener(Task::EVENT_CREATE_UPDATE, function() {});
+ $this->container['dispatcher']->addListener(Task::EVENT_CREATE, function() {});
+
+ $this->assertEquals(2, $pd->duplicate(1, array('category', 'action', 'swimlane', 'task')));
+
+ // Check if Swimlanes have been duplicated
+ $swimlanes = $s->getAll(2);
+
+ $this->assertCount(3, $swimlanes);
+ $this->assertEquals(4, $swimlanes[0]['id']);
+ $this->assertEquals('S1', $swimlanes[0]['name']);
+ $this->assertEquals(5, $swimlanes[1]['id']);
+ $this->assertEquals('S2', $swimlanes[1]['name']);
+ $this->assertEquals(6, $swimlanes[2]['id']);
+ $this->assertEquals('S3', $swimlanes[2]['name']);
+ $new_default = $s->getDefault(2);
+ $this->assertEquals('New Default', $new_default['default_swimlane']);
+
+ // Check if Tasks have been duplicated
+
+ $tasks = $tf->getAll(2);
+
+ $this->assertCount(3, $tasks);
+ $this->assertEquals(4, $tasks[0]['id']);
+ $this->assertEquals('T1', $tasks[0]['title']);
+ $this->assertEquals(5, $tasks[1]['id']);
+ $this->assertEquals('T2', $tasks[1]['title']);
+ $this->assertEquals(6, $tasks[2]['id']);
+ $this->assertEquals('T3', $tasks[2]['title']);
+
+ // Drop project
+ unset($tasks);
+ unset($swimlanes);
+ unset($new_default);
+
+ $p->remove(2);
+
+ $this->assertFalse($p->exists(2));
+ $this->assertCount(0, $s->getAll(2));
+ $this->assertCount(0, $tf->getAll(2));
+
+ // Check duplication with Swimlanes only
+ $this->assertEquals(2, $pd->duplicate(1, array('category', 'action', 'swimlane')));
+
+ // Check if Swimlanes have been duplicated
+ $swimlanes = $s->getAll(2);
+
+ $this->assertCount(3, $swimlanes);
+ $this->assertEquals(4, $swimlanes[0]['id']);
+ $this->assertEquals('S1', $swimlanes[0]['name']);
+ $this->assertEquals(5, $swimlanes[1]['id']);
+ $this->assertEquals('S2', $swimlanes[1]['name']);
+ $this->assertEquals(6, $swimlanes[2]['id']);
+ $this->assertEquals('S3', $swimlanes[2]['name']);
+ $new_default = $s->getDefault(2);
+ $this->assertEquals('New Default', $new_default['default_swimlane']);
+
+ // Check if Tasks have NOT been duplicated
+ $this->assertCount(0, $tf->getAll(2));
+
+ // Drop project
+ unset($tasks);
+ unset($swimlanes);
+ unset($new_default);
+
+ $p->remove(2);
+
+ $this->assertFalse($p->exists(2));
+ $this->assertCount(0, $s->getAll(2));
+ $this->assertCount(0, $tf->getAll(2));
+
+ // Check duplication with Tasks only
+ $this->assertEquals(2, $pd->duplicate(1, array('category', 'action', 'task')));
+
+ // Check if Swimlanes have NOT been duplicated
+ $this->assertCount(0, $s->getAll(2));
+
+ // Check if Tasks have been duplicated
+ $tasks = $tf->getAll(2);
+
+ $this->assertCount(3, $tasks);
+ $this->assertEquals(4, $tasks[0]['id']);
+ $this->assertEquals('T1', $tasks[0]['title']);
+ $this->assertEquals(5, $tasks[1]['id']);
+ $this->assertEquals('T2', $tasks[1]['title']);
+ $this->assertEquals(6, $tasks[2]['id']);
+ $this->assertEquals('T3', $tasks[2]['title']);
+ }
}
diff --git a/tests/units/SwimlaneTest.php b/tests/units/SwimlaneTest.php
index bda0f590..37226613 100644
--- a/tests/units/SwimlaneTest.php
+++ b/tests/units/SwimlaneTest.php
@@ -375,4 +375,35 @@ class SwimlaneTest extends Base
$this->assertEquals(0, $swimlane['is_active']);
$this->assertEquals(0, $swimlane['position']);
}
+
+ public function testDuplicateSwimlane()
+ {
+ $p = new Project($this->container);
+ $s = new Swimlane($this->container);
+
+ $this->assertEquals(1, $p->create(array('name' => 'P1')));
+ $this->assertEquals(2, $p->create(array('name' => 'P2')));
+ $this->assertEquals(1, $s->create(1, 'S1'));
+ $this->assertEquals(2, $s->create(1, 'S2'));
+ $this->assertEquals(3, $s->create(1, 'S3'));
+
+ $default_swimlane1 = $s->getDefault(1);
+ $default_swimlane1['default_swimlane'] = 'New Default';
+
+ $this->assertTrue($s->updateDefault($default_swimlane1));
+
+ $this->assertTrue($s->duplicate(1, 2));
+
+ $swimlanes = $s->getAll(2);
+
+ $this->assertCount(3, $swimlanes);
+ $this->assertEquals(4, $swimlanes[0]['id']);
+ $this->assertEquals('S1', $swimlanes[0]['name']);
+ $this->assertEquals(5, $swimlanes[1]['id']);
+ $this->assertEquals('S2', $swimlanes[1]['name']);
+ $this->assertEquals(6, $swimlanes[2]['id']);
+ $this->assertEquals('S3', $swimlanes[2]['name']);
+ $new_default = $s->getDefault(2);
+ $this->assertEquals('New Default', $new_default['default_swimlane']);
+ }
}