summaryrefslogtreecommitdiff
path: root/app/Model/TaskDuplicationModel.php
diff options
context:
space:
mode:
Diffstat (limited to 'app/Model/TaskDuplicationModel.php')
-rw-r--r--app/Model/TaskDuplicationModel.php270
1 files changed, 270 insertions, 0 deletions
diff --git a/app/Model/TaskDuplicationModel.php b/app/Model/TaskDuplicationModel.php
new file mode 100644
index 00000000..9a4613e2
--- /dev/null
+++ b/app/Model/TaskDuplicationModel.php
@@ -0,0 +1,270 @@
+<?php
+
+namespace Kanboard\Model;
+
+use DateTime;
+use DateInterval;
+use Kanboard\Core\Base;
+use Kanboard\Event\TaskEvent;
+
+/**
+ * Task Duplication
+ *
+ * @package Kanboard\Model
+ * @author Frederic Guillot
+ */
+class TaskDuplicationModel extends Base
+{
+ /**
+ * Fields to copy when duplicating a task
+ *
+ * @access private
+ * @var array
+ */
+ private $fields_to_duplicate = array(
+ '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',
+ );
+
+ /**
+ * Duplicate a task to the same project
+ *
+ * @access public
+ * @param integer $task_id Task id
+ * @return boolean|integer Duplicated task id
+ */
+ public function duplicate($task_id)
+ {
+ return $this->save($task_id, $this->copyFields($task_id));
+ }
+
+ /**
+ * Duplicate recurring task
+ *
+ * @access public
+ * @param integer $task_id Task id
+ * @return boolean|integer Recurrence task id
+ */
+ public function duplicateRecurringTask($task_id)
+ {
+ $values = $this->copyFields($task_id);
+
+ if ($values['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING) {
+ $values['recurrence_parent'] = $task_id;
+ $values['column_id'] = $this->columnModel->getFirstColumnId($values['project_id']);
+ $this->calculateRecurringTaskDueDate($values);
+
+ $recurring_task_id = $this->save($task_id, $values);
+
+ if ($recurring_task_id > 0) {
+ $parent_update = $this->db
+ ->table(TaskModel::TABLE)
+ ->eq('id', $task_id)
+ ->update(array(
+ 'recurrence_status' => TaskModel::RECURRING_STATUS_PROCESSED,
+ 'recurrence_child' => $recurring_task_id,
+ ));
+
+ if ($parent_update) {
+ return $recurring_task_id;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Duplicate a task to another project
+ *
+ * @access public
+ * @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, $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'] = $column_id !== null ? $column_id : $values['column_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);
+
+ return $this->save($task_id, $values);
+ }
+
+ /**
+ * Move a task to another project
+ *
+ * @access public
+ * @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, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null)
+ {
+ $task = $this->taskFinderModel->getById($task_id);
+
+ $values = array();
+ $values['is_active'] = 1;
+ $values['project_id'] = $project_id;
+ $values['column_id'] = $column_id !== null ? $column_id : $task['column_id'];
+ $values['position'] = $this->taskFinderModel->countByColumnId($project_id, $values['column_id']) + 1;
+ $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);
+
+ if ($this->db->table(TaskModel::TABLE)->eq('id', $task['id'])->update($values)) {
+ $this->container['dispatcher']->dispatch(
+ TaskModel::EVENT_MOVE_PROJECT,
+ new TaskEvent(array_merge($task, $values, array('task_id' => $task['id'])))
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the assignee and the category are available in the destination project
+ *
+ * @access public
+ * @param array $values
+ * @return array
+ */
+ public function checkDestinationProjectValues(array &$values)
+ {
+ // Check if the assigned user is allowed for the destination project
+ if ($values['owner_id'] > 0 && ! $this->projectPermissionModel->isUserAllowed($values['project_id'], $values['owner_id'])) {
+ $values['owner_id'] = 0;
+ }
+
+ // Check if the category exists for the destination project
+ if ($values['category_id'] > 0) {
+ $values['category_id'] = $this->categoryModel->getIdByName(
+ $values['project_id'],
+ $this->categoryModel->getNameById($values['category_id'])
+ );
+ }
+
+ // Check if the swimlane exists for the destination project
+ if ($values['swimlane_id'] > 0) {
+ $values['swimlane_id'] = $this->swimlaneModel->getIdByName(
+ $values['project_id'],
+ $this->swimlaneModel->getNameById($values['swimlane_id'])
+ );
+ }
+
+ // Check if the column exists for the destination project
+ if ($values['column_id'] > 0) {
+ $values['column_id'] = $this->columnModel->getColumnIdByTitle(
+ $values['project_id'],
+ $this->columnModel->getColumnTitleById($values['column_id'])
+ );
+
+ $values['column_id'] = $values['column_id'] ?: $this->columnModel->getFirstColumnId($values['project_id']);
+ }
+
+ return $values;
+ }
+
+ /**
+ * Calculate new due date for new recurrence task
+ *
+ * @access public
+ * @param array $values Task fields
+ */
+ public function calculateRecurringTaskDueDate(array &$values)
+ {
+ if (! empty($values['date_due']) && $values['recurrence_factor'] != 0) {
+ if ($values['recurrence_basedate'] == TaskModel::RECURRING_BASEDATE_TRIGGERDATE) {
+ $values['date_due'] = time();
+ }
+
+ $factor = abs($values['recurrence_factor']);
+ $subtract = $values['recurrence_factor'] < 0;
+
+ switch ($values['recurrence_timeframe']) {
+ case TaskModel::RECURRING_TIMEFRAME_MONTHS:
+ $interval = 'P' . $factor . 'M';
+ break;
+ case TaskModel::RECURRING_TIMEFRAME_YEARS:
+ $interval = 'P' . $factor . 'Y';
+ break;
+ default:
+ $interval = 'P' . $factor . 'D';
+ }
+
+ $date_due = new DateTime();
+ $date_due->setTimestamp($values['date_due']);
+
+ $subtract ? $date_due->sub(new DateInterval($interval)) : $date_due->add(new DateInterval($interval));
+
+ $values['date_due'] = $date_due->getTimestamp();
+ }
+ }
+
+ /**
+ * Duplicate fields for the new task
+ *
+ * @access private
+ * @param integer $task_id Task id
+ * @return array
+ */
+ private function copyFields($task_id)
+ {
+ $task = $this->taskFinderModel->getById($task_id);
+ $values = array();
+
+ foreach ($this->fields_to_duplicate as $field) {
+ $values[$field] = $task[$field];
+ }
+
+ return $values;
+ }
+
+ /**
+ * Create the new task and duplicate subtasks
+ *
+ * @access private
+ * @param integer $task_id Task id
+ * @param array $values Form values
+ * @return boolean|integer
+ */
+ private function save($task_id, array $values)
+ {
+ $new_task_id = $this->taskCreationModel->create($values);
+
+ if ($new_task_id) {
+ $this->subtaskModel->duplicate($task_id, $new_task_id);
+ }
+
+ return $new_task_id;
+ }
+}