summaryrefslogtreecommitdiff
path: root/app/Model
diff options
context:
space:
mode:
Diffstat (limited to 'app/Model')
-rw-r--r--app/Model/ColumnModel.php18
-rw-r--r--app/Model/NotificationModel.php37
-rw-r--r--app/Model/ProjectDuplicationModel.php24
-rw-r--r--app/Model/ProjectModel.php13
-rw-r--r--app/Model/ProjectTaskDuplicationModel.php35
-rw-r--r--app/Model/ProjectTaskPriorityModel.php74
-rw-r--r--app/Model/SwimlaneModel.php24
-rw-r--r--app/Model/TagDuplicationModel.php87
-rw-r--r--app/Model/TaskCreationModel.php2
-rw-r--r--app/Model/TaskDuplicationModel.php165
-rw-r--r--app/Model/TaskFinderModel.php8
-rw-r--r--app/Model/TaskModel.php143
-rw-r--r--app/Model/TaskModificationModel.php2
-rw-r--r--app/Model/TaskProjectDuplicationModel.php60
-rw-r--r--app/Model/TaskProjectMoveModel.php68
-rw-r--r--app/Model/TaskRecurrenceModel.php147
-rw-r--r--app/Model/TaskTagModel.php18
17 files changed, 631 insertions, 294 deletions
diff --git a/app/Model/ColumnModel.php b/app/Model/ColumnModel.php
index 795fe692..5498ef54 100644
--- a/app/Model/ColumnModel.php
+++ b/app/Model/ColumnModel.php
@@ -138,19 +138,21 @@ class ColumnModel extends Base
* Add a new column to the board
*
* @access public
- * @param integer $project_id Project id
- * @param string $title Column title
- * @param integer $task_limit Task limit
- * @param string $description Column description
- * @return boolean|integer
+ * @param integer $project_id Project id
+ * @param string $title Column title
+ * @param integer $task_limit Task limit
+ * @param string $description Column description
+ * @param integer $hide_in_dashboard
+ * @return bool|int
*/
- public function create($project_id, $title, $task_limit = 0, $description = '')
+ public function create($project_id, $title, $task_limit = 0, $description = '', $hide_in_dashboard = 0)
{
$values = array(
'project_id' => $project_id,
'title' => $title,
'task_limit' => intval($task_limit),
'position' => $this->getLastColumnPosition($project_id) + 1,
+ 'hide_in_dashboard' => $hide_in_dashboard,
'description' => $description,
);
@@ -165,13 +167,15 @@ class ColumnModel extends Base
* @param string $title Column title
* @param integer $task_limit Task limit
* @param string $description Optional description
+ * @param integer $hide_in_dashboard
* @return boolean
*/
- public function update($column_id, $title, $task_limit = 0, $description = '')
+ public function update($column_id, $title, $task_limit = 0, $description = '', $hide_in_dashboard = 0)
{
return $this->db->table(self::TABLE)->eq('id', $column_id)->update(array(
'title' => $title,
'task_limit' => intval($task_limit),
+ 'hide_in_dashboard' => $hide_in_dashboard,
'description' => $description,
));
}
diff --git a/app/Model/NotificationModel.php b/app/Model/NotificationModel.php
index 8937b77e..4d697b5e 100644
--- a/app/Model/NotificationModel.php
+++ b/app/Model/NotificationModel.php
@@ -133,4 +133,41 @@ class NotificationModel extends Base
return e('Notification');
}
}
+
+ /**
+ * Get task id from event
+ *
+ * @access public
+ * @param string $event_name
+ * @param array $event_data
+ * @return integer
+ */
+ public function getTaskIdFromEvent($event_name, array $event_data)
+ {
+ switch ($event_name) {
+ case TaskFileModel::EVENT_CREATE:
+ return $event_data['file']['task_id'];
+ case CommentModel::EVENT_CREATE:
+ case CommentModel::EVENT_UPDATE:
+ return $event_data['comment']['task_id'];
+ case SubtaskModel::EVENT_CREATE:
+ case SubtaskModel::EVENT_UPDATE:
+ return $event_data['subtask']['task_id'];
+ case TaskModel::EVENT_CREATE:
+ case TaskModel::EVENT_UPDATE:
+ case TaskModel::EVENT_CLOSE:
+ case TaskModel::EVENT_OPEN:
+ case TaskModel::EVENT_MOVE_COLUMN:
+ case TaskModel::EVENT_MOVE_POSITION:
+ case TaskModel::EVENT_MOVE_SWIMLANE:
+ case TaskModel::EVENT_ASSIGNEE_CHANGE:
+ case CommentModel::EVENT_USER_MENTION:
+ case TaskModel::EVENT_USER_MENTION:
+ return $event_data['task']['id'];
+ case TaskModel::EVENT_OVERDUE:
+ return $event_data['tasks'][0]['id'];
+ default:
+ return 0;
+ }
+ }
}
diff --git a/app/Model/ProjectDuplicationModel.php b/app/Model/ProjectDuplicationModel.php
index b67f8302..94b83c80 100644
--- a/app/Model/ProjectDuplicationModel.php
+++ b/app/Model/ProjectDuplicationModel.php
@@ -22,7 +22,15 @@ class ProjectDuplicationModel extends Base
*/
public function getOptionalSelection()
{
- return array('categoryModel', 'projectPermissionModel', 'actionModel', 'swimlaneModel', 'taskModel', 'projectMetadataModel');
+ return array(
+ 'categoryModel',
+ 'projectPermissionModel',
+ 'actionModel',
+ 'swimlaneModel',
+ 'tagDuplicationModel',
+ 'projectMetadataModel',
+ 'projectTaskDuplicationModel',
+ );
}
/**
@@ -33,7 +41,16 @@ class ProjectDuplicationModel extends Base
*/
public function getPossibleSelection()
{
- return array('boardModel', 'categoryModel', 'projectPermissionModel', 'actionModel', 'swimlaneModel', 'taskModel', 'projectMetadataModel');
+ return array(
+ 'boardModel',
+ 'categoryModel',
+ 'projectPermissionModel',
+ 'actionModel',
+ 'swimlaneModel',
+ 'tagDuplicationModel',
+ 'projectMetadataModel',
+ 'projectTaskDuplicationModel',
+ );
}
/**
@@ -129,6 +146,9 @@ class ProjectDuplicationModel extends Base
'is_public' => 0,
'is_private' => $private ? 1 : $is_private,
'owner_id' => $owner_id,
+ 'priority_default' => $project['priority_default'],
+ 'priority_start' => $project['priority_start'],
+ 'priority_end' => $project['priority_end'],
);
if (! $this->db->table(ProjectModel::TABLE)->save($values)) {
diff --git a/app/Model/ProjectModel.php b/app/Model/ProjectModel.php
index 7382537e..850531c9 100644
--- a/app/Model/ProjectModel.php
+++ b/app/Model/ProjectModel.php
@@ -246,19 +246,6 @@ class ProjectModel extends Base
}
/**
- * Get Priority range from a project
- *
- * @access public
- * @param array $project
- * @return array
- */
- public function getPriorities(array $project)
- {
- $range = range($project['priority_start'], $project['priority_end']);
- return array_combine($range, $range);
- }
-
- /**
* Gather some task metrics for a given project
*
* @access public
diff --git a/app/Model/ProjectTaskDuplicationModel.php b/app/Model/ProjectTaskDuplicationModel.php
new file mode 100644
index 00000000..5d2e1322
--- /dev/null
+++ b/app/Model/ProjectTaskDuplicationModel.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Kanboard\Model;
+
+use Kanboard\Core\Base;
+
+/**
+ * Project Task Duplication Model
+ *
+ * @package Kanboard\Model
+ * @author Frederic Guillot
+ */
+class ProjectTaskDuplicationModel extends Base
+{
+ /**
+ * Duplicate all tasks to another project
+ *
+ * @access public
+ * @param integer $src_project_id
+ * @param integer $dst_project_id
+ * @return boolean
+ */
+ public function duplicate($src_project_id, $dst_project_id)
+ {
+ $task_ids = $this->taskFinderModel->getAllIds($src_project_id, array(TaskModel::STATUS_OPEN, TaskModel::STATUS_CLOSED));
+
+ foreach ($task_ids as $task_id) {
+ if (! $this->taskProjectDuplicationModel->duplicateToProject($task_id, $dst_project_id)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/app/Model/ProjectTaskPriorityModel.php b/app/Model/ProjectTaskPriorityModel.php
new file mode 100644
index 00000000..c1a0257a
--- /dev/null
+++ b/app/Model/ProjectTaskPriorityModel.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Kanboard\Model;
+
+use Kanboard\Core\Base;
+
+/**
+ * Project Task Priority Model
+ *
+ * @package Kanboard\Model
+ * @author Frederic Guillot
+ */
+class ProjectTaskPriorityModel extends Base
+{
+ /**
+ * Get Priority range from a project
+ *
+ * @access public
+ * @param array $project
+ * @return array
+ */
+ public function getPriorities(array $project)
+ {
+ $range = range($project['priority_start'], $project['priority_end']);
+ return array_combine($range, $range);
+ }
+
+ /**
+ * Get task priority settings
+ *
+ * @access public
+ * @param int $project_id
+ * @return array|null
+ */
+ public function getPrioritySettings($project_id)
+ {
+ return $this->db
+ ->table(ProjectModel::TABLE)
+ ->columns('priority_default', 'priority_start', 'priority_end')
+ ->eq('id', $project_id)
+ ->findOne();
+ }
+
+ /**
+ * Get default task priority
+ *
+ * @access public
+ * @param int $project_id
+ * @return int
+ */
+ public function getDefaultPriority($project_id)
+ {
+ return $this->db->table(ProjectModel::TABLE)->eq('id', $project_id)->findOneColumn('priority_default') ?: 0;
+ }
+
+ /**
+ * Get priority for a destination project
+ *
+ * @access public
+ * @param integer $dst_project_id
+ * @param integer $priority
+ * @return integer
+ */
+ public function getPriorityForProject($dst_project_id, $priority)
+ {
+ $settings = $this->getPrioritySettings($dst_project_id);
+
+ if ($priority >= $settings['priority_start'] && $priority <= $settings['priority_end']) {
+ return $priority;
+ }
+
+ return $settings['priority_default'];
+ }
+}
diff --git a/app/Model/SwimlaneModel.php b/app/Model/SwimlaneModel.php
index 35e39879..f20bfa2f 100644
--- a/app/Model/SwimlaneModel.php
+++ b/app/Model/SwimlaneModel.php
@@ -94,15 +94,17 @@ class SwimlaneModel extends Base
*
* @access public
* @param integer $project_id
- * @return array
+ * @return array|null
*/
public function getFirstActiveSwimlane($project_id)
{
- return $this->db->table(self::TABLE)
- ->eq('is_active', self::ACTIVE)
- ->eq('project_id', $project_id)
- ->orderBy('position', 'asc')
- ->findOne();
+ $swimlanes = $this->getSwimlanes($project_id);
+
+ if (empty($swimlanes)) {
+ return null;
+ }
+
+ return $swimlanes[0];
}
/**
@@ -184,18 +186,18 @@ class SwimlaneModel extends Base
->orderBy('position', 'asc')
->findAll();
- $default_swimlane = $this->db
+ $defaultSwimlane = $this->db
->table(ProjectModel::TABLE)
->eq('id', $project_id)
->eq('show_default_swimlane', 1)
->findOneColumn('default_swimlane');
- if ($default_swimlane) {
- if ($default_swimlane === 'Default swimlane') {
- $default_swimlane = t($default_swimlane);
+ if ($defaultSwimlane) {
+ if ($defaultSwimlane === 'Default swimlane') {
+ $defaultSwimlane = t($defaultSwimlane);
}
- array_unshift($swimlanes, array('id' => 0, 'name' => $default_swimlane));
+ array_unshift($swimlanes, array('id' => 0, 'name' => $defaultSwimlane));
}
return $swimlanes;
diff --git a/app/Model/TagDuplicationModel.php b/app/Model/TagDuplicationModel.php
new file mode 100644
index 00000000..fb0d8170
--- /dev/null
+++ b/app/Model/TagDuplicationModel.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Kanboard\Model;
+
+use Kanboard\Core\Base;
+
+/**
+ * Tag Duplication
+ *
+ * @package Kanboard\Model
+ * @author Frederic Guillot
+ */
+class TagDuplicationModel extends Base
+{
+ /**
+ * Duplicate project tags to another project
+ *
+ * @access public
+ * @param integer $src_project_id
+ * @param integer $dst_project_id
+ * @return bool
+ */
+ public function duplicate($src_project_id, $dst_project_id)
+ {
+ $tags = $this->tagModel->getAllByProject($src_project_id);
+ $results = array();
+
+ foreach ($tags as $tag) {
+ $results[] = $this->tagModel->create($dst_project_id, $tag['name']);
+ }
+
+ return ! in_array(false, $results, true);
+ }
+
+ /**
+ * Link tags to the new tasks
+ *
+ * @access public
+ * @param integer $src_task_id
+ * @param integer $dst_task_id
+ * @param integer $dst_project_id
+ */
+ public function duplicateTaskTagsToAnotherProject($src_task_id, $dst_task_id, $dst_project_id)
+ {
+ $tags = $this->taskTagModel->getTagsByTask($src_task_id);
+
+ foreach ($tags as $tag) {
+ $tag_id = $this->tagModel->getIdByName($dst_project_id, $tag['name']);
+
+ if ($tag_id) {
+ $this->taskTagModel->associateTag($dst_task_id, $tag_id);
+ }
+ }
+ }
+
+ /**
+ * Duplicate tags to the new task
+ *
+ * @access public
+ * @param integer $src_task_id
+ * @param integer $dst_task_id
+ */
+ public function duplicateTaskTags($src_task_id, $dst_task_id)
+ {
+ $tags = $this->taskTagModel->getTagsByTask($src_task_id);
+
+ foreach ($tags as $tag) {
+ $this->taskTagModel->associateTag($dst_task_id, $tag['id']);
+ }
+ }
+
+ /**
+ * Remove tags that are not available in destination project
+ *
+ * @access public
+ * @param integer $task_id
+ * @param integer $dst_project_id
+ */
+ public function syncTaskTagsToAnotherProject($task_id, $dst_project_id)
+ {
+ $tag_ids = $this->taskTagModel->getTagIdsByTaskNotAvailableInProject($task_id, $dst_project_id);
+
+ foreach ($tag_ids as $tag_id) {
+ $this->taskTagModel->dissociateTag($task_id, $tag_id);
+ }
+ }
+}
diff --git a/app/Model/TaskCreationModel.php b/app/Model/TaskCreationModel.php
index fa2d32c6..cd70a028 100644
--- a/app/Model/TaskCreationModel.php
+++ b/app/Model/TaskCreationModel.php
@@ -60,7 +60,7 @@ class TaskCreationModel extends Base
$values = $this->dateParser->convert($values, array('date_started'), true);
$this->helper->model->removeFields($values, array('another_task'));
- $this->helper->model->resetFields($values, array('date_started', 'creator_id', 'owner_id', 'swimlane_id', 'date_due', 'score', 'category_id', 'time_estimated'));
+ $this->helper->model->resetFields($values, array('creator_id', 'owner_id', 'swimlane_id', 'date_due', 'date_started', 'score', 'category_id', 'time_estimated', 'time_spent'));
if (empty($values['column_id'])) {
$values['column_id'] = $this->columnModel->getFirstColumnId($values['project_id']);
diff --git a/app/Model/TaskDuplicationModel.php b/app/Model/TaskDuplicationModel.php
index 9a4613e2..c9079653 100644
--- a/app/Model/TaskDuplicationModel.php
+++ b/app/Model/TaskDuplicationModel.php
@@ -2,10 +2,7 @@
namespace Kanboard\Model;
-use DateTime;
-use DateInterval;
use Kanboard\Core\Base;
-use Kanboard\Event\TaskEvent;
/**
* Task Duplication
@@ -18,10 +15,10 @@ class TaskDuplicationModel extends Base
/**
* Fields to copy when duplicating a task
*
- * @access private
- * @var array
+ * @access protected
+ * @var string[]
*/
- private $fields_to_duplicate = array(
+ protected $fieldsToDuplicate = array(
'title',
'description',
'date_due',
@@ -30,6 +27,7 @@ class TaskDuplicationModel extends Base
'column_id',
'owner_id',
'score',
+ 'priority',
'category_id',
'time_estimated',
'swimlane_id',
@@ -49,106 +47,13 @@ class TaskDuplicationModel extends Base
*/
public function duplicate($task_id)
{
- return $this->save($task_id, $this->copyFields($task_id));
- }
+ $new_task_id = $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;
- }
- }
+ if ($new_task_id !== false) {
+ $this->tagDuplicationModel->duplicateTaskTags($task_id, $new_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;
+ return $new_task_id;
}
/**
@@ -191,58 +96,28 @@ class TaskDuplicationModel extends Base
$values['column_id'] = $values['column_id'] ?: $this->columnModel->getFirstColumnId($values['project_id']);
}
- return $values;
- }
+ // Check if priority exists for destination project
+ $values['priority'] = $this->projectTaskPriorityModel->getPriorityForProject(
+ $values['project_id'],
+ empty($values['priority']) ? 0 : $values['priority']
+ );
- /**
- * 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();
- }
+ return $values;
}
/**
* Duplicate fields for the new task
*
- * @access private
+ * @access protected
* @param integer $task_id Task id
* @return array
*/
- private function copyFields($task_id)
+ protected function copyFields($task_id)
{
$task = $this->taskFinderModel->getById($task_id);
$values = array();
- foreach ($this->fields_to_duplicate as $field) {
+ foreach ($this->fieldsToDuplicate as $field) {
$values[$field] = $task[$field];
}
@@ -252,16 +127,16 @@ class TaskDuplicationModel extends Base
/**
* Create the new task and duplicate subtasks
*
- * @access private
+ * @access protected
* @param integer $task_id Task id
* @param array $values Form values
* @return boolean|integer
*/
- private function save($task_id, array $values)
+ protected function save($task_id, array $values)
{
$new_task_id = $this->taskCreationModel->create($values);
- if ($new_task_id) {
+ if ($new_task_id !== false) {
$this->subtaskModel->duplicate($task_id, $new_task_id);
}
diff --git a/app/Model/TaskFinderModel.php b/app/Model/TaskFinderModel.php
index 0e99c407..7268052c 100644
--- a/app/Model/TaskFinderModel.php
+++ b/app/Model/TaskFinderModel.php
@@ -81,7 +81,8 @@ class TaskFinderModel extends Base
->join(ColumnModel::TABLE, 'id', 'column_id')
->eq(TaskModel::TABLE.'.owner_id', $user_id)
->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN)
- ->eq(ProjectModel::TABLE.'.is_active', ProjectModel::ACTIVE);
+ ->eq(ProjectModel::TABLE.'.is_active', ProjectModel::ACTIVE)
+ ->eq(ColumnModel::TABLE.'.hide_in_dashboard', 0);
}
/**
@@ -166,6 +167,7 @@ class TaskFinderModel extends Base
->table(TaskModel::TABLE)
->eq(TaskModel::TABLE.'.project_id', $project_id)
->eq(TaskModel::TABLE.'.is_active', $status_id)
+ ->asc(TaskModel::TABLE.'.id')
->findAll();
}
@@ -183,7 +185,8 @@ class TaskFinderModel extends Base
->table(TaskModel::TABLE)
->eq(TaskModel::TABLE.'.project_id', $project_id)
->in(TaskModel::TABLE.'.is_active', $status)
- ->findAllByColumn('id');
+ ->asc(TaskModel::TABLE.'.id')
+ ->findAllByColumn(TaskModel::TABLE.'.id');
}
/**
@@ -367,6 +370,7 @@ class TaskFinderModel extends Base
'ua.name AS assignee_name',
'ua.username AS assignee_username',
'uc.email AS creator_email',
+ 'uc.name AS creator_name',
'uc.username AS creator_username'
);
}
diff --git a/app/Model/TaskModel.php b/app/Model/TaskModel.php
index b0e7772a..5cddb509 100644
--- a/app/Model/TaskModel.php
+++ b/app/Model/TaskModel.php
@@ -5,7 +5,7 @@ namespace Kanboard\Model;
use Kanboard\Core\Base;
/**
- * Task model
+ * Task Model
*
* @package Kanboard\Model
* @author Frederic Guillot
@@ -17,80 +17,80 @@ class TaskModel extends Base
*
* @var string
*/
- const TABLE = 'tasks';
+ const TABLE = 'tasks';
/**
* Task status
*
* @var integer
*/
- const STATUS_OPEN = 1;
- const STATUS_CLOSED = 0;
+ const STATUS_OPEN = 1;
+ const STATUS_CLOSED = 0;
/**
* Events
*
* @var string
*/
- const EVENT_MOVE_PROJECT = 'task.move.project';
- const EVENT_MOVE_COLUMN = 'task.move.column';
- const EVENT_MOVE_POSITION = 'task.move.position';
- const EVENT_MOVE_SWIMLANE = 'task.move.swimlane';
- const EVENT_UPDATE = 'task.update';
- const EVENT_CREATE = 'task.create';
- const EVENT_CLOSE = 'task.close';
- const EVENT_OPEN = 'task.open';
- const EVENT_CREATE_UPDATE = 'task.create_update';
+ const EVENT_MOVE_PROJECT = 'task.move.project';
+ const EVENT_MOVE_COLUMN = 'task.move.column';
+ const EVENT_MOVE_POSITION = 'task.move.position';
+ const EVENT_MOVE_SWIMLANE = 'task.move.swimlane';
+ const EVENT_UPDATE = 'task.update';
+ const EVENT_CREATE = 'task.create';
+ const EVENT_CLOSE = 'task.close';
+ const EVENT_OPEN = 'task.open';
+ const EVENT_CREATE_UPDATE = 'task.create_update';
const EVENT_ASSIGNEE_CHANGE = 'task.assignee_change';
- const EVENT_OVERDUE = 'task.overdue';
- const EVENT_USER_MENTION = 'task.user.mention';
- const EVENT_DAILY_CRONJOB = 'task.cronjob.daily';
+ const EVENT_OVERDUE = 'task.overdue';
+ const EVENT_USER_MENTION = 'task.user.mention';
+ const EVENT_DAILY_CRONJOB = 'task.cronjob.daily';
/**
* Recurrence: status
*
* @var integer
*/
- const RECURRING_STATUS_NONE = 0;
- const RECURRING_STATUS_PENDING = 1;
- const RECURRING_STATUS_PROCESSED = 2;
+ const RECURRING_STATUS_NONE = 0;
+ const RECURRING_STATUS_PENDING = 1;
+ const RECURRING_STATUS_PROCESSED = 2;
/**
* Recurrence: trigger
*
* @var integer
*/
- const RECURRING_TRIGGER_FIRST_COLUMN = 0;
- const RECURRING_TRIGGER_LAST_COLUMN = 1;
- const RECURRING_TRIGGER_CLOSE = 2;
+ const RECURRING_TRIGGER_FIRST_COLUMN = 0;
+ const RECURRING_TRIGGER_LAST_COLUMN = 1;
+ const RECURRING_TRIGGER_CLOSE = 2;
/**
* Recurrence: timeframe
*
* @var integer
*/
- const RECURRING_TIMEFRAME_DAYS = 0;
- const RECURRING_TIMEFRAME_MONTHS = 1;
- const RECURRING_TIMEFRAME_YEARS = 2;
+ const RECURRING_TIMEFRAME_DAYS = 0;
+ const RECURRING_TIMEFRAME_MONTHS = 1;
+ const RECURRING_TIMEFRAME_YEARS = 2;
/**
* Recurrence: base date used to calculate new due date
*
* @var integer
*/
- const RECURRING_BASEDATE_DUEDATE = 0;
- const RECURRING_BASEDATE_TRIGGERDATE = 1;
+ const RECURRING_BASEDATE_DUEDATE = 0;
+ const RECURRING_BASEDATE_TRIGGERDATE = 1;
/**
* Remove a task
*
* @access public
- * @param integer $task_id Task id
+ * @param integer $task_id Task id
* @return boolean
*/
public function remove($task_id)
{
- if (! $this->taskFinderModel->exists($task_id)) {
+ if (!$this->taskFinderModel->exists($task_id)) {
return false;
}
@@ -105,7 +105,7 @@ class TaskModel extends Base
* Example: "Fix bug #1234" will return 1234
*
* @access public
- * @param string $message Text
+ * @param string $message Text
* @return integer
*/
public function getTaskIdFromText($message)
@@ -118,69 +118,11 @@ class TaskModel extends Base
}
/**
- * Return the list user selectable recurrence status
- *
- * @access public
- * @return array
- */
- public function getRecurrenceStatusList()
- {
- return array(
- TaskModel::RECURRING_STATUS_NONE => t('No'),
- TaskModel::RECURRING_STATUS_PENDING => t('Yes'),
- );
- }
-
- /**
- * Return the list recurrence triggers
- *
- * @access public
- * @return array
- */
- public function getRecurrenceTriggerList()
- {
- return array(
- TaskModel::RECURRING_TRIGGER_FIRST_COLUMN => t('When task is moved from first column'),
- TaskModel::RECURRING_TRIGGER_LAST_COLUMN => t('When task is moved to last column'),
- TaskModel::RECURRING_TRIGGER_CLOSE => t('When task is closed'),
- );
- }
-
- /**
- * Return the list options to calculate recurrence due date
- *
- * @access public
- * @return array
- */
- public function getRecurrenceBasedateList()
- {
- return array(
- TaskModel::RECURRING_BASEDATE_DUEDATE => t('Existing due date'),
- TaskModel::RECURRING_BASEDATE_TRIGGERDATE => t('Action date'),
- );
- }
-
- /**
- * Return the list recurrence timeframes
- *
- * @access public
- * @return array
- */
- public function getRecurrenceTimeframeList()
- {
- return array(
- TaskModel::RECURRING_TIMEFRAME_DAYS => t('Day(s)'),
- TaskModel::RECURRING_TIMEFRAME_MONTHS => t('Month(s)'),
- TaskModel::RECURRING_TIMEFRAME_YEARS => t('Year(s)'),
- );
- }
-
- /**
* Get task progress based on the column position
*
* @access public
- * @param array $task
- * @param array $columns
+ * @param array $task
+ * @param array $columns
* @return integer
*/
public function getProgress(array $task, array $columns)
@@ -201,25 +143,4 @@ class TaskModel extends Base
return round(($position * 100) / count($columns), 1);
}
-
- /**
- * Helper method to duplicate all tasks to another project
- *
- * @access public
- * @param integer $src_project_id
- * @param integer $dst_project_id
- * @return boolean
- */
- public function duplicate($src_project_id, $dst_project_id)
- {
- $task_ids = $this->taskFinderModel->getAllIds($src_project_id, array(TaskModel::STATUS_OPEN, TaskModel::STATUS_CLOSED));
-
- foreach ($task_ids as $task_id) {
- if (! $this->taskDuplicationModel->duplicateToProject($task_id, $dst_project_id)) {
- return false;
- }
- }
-
- return true;
- }
}
diff --git a/app/Model/TaskModificationModel.php b/app/Model/TaskModificationModel.php
index 1b176a41..be5f53c8 100644
--- a/app/Model/TaskModificationModel.php
+++ b/app/Model/TaskModificationModel.php
@@ -108,8 +108,6 @@ class TaskModificationModel extends Base
if (isset($values['tags'])) {
$this->taskTagModel->save($original_task['project_id'], $values['id'], $values['tags']);
unset($values['tags']);
- } else {
- $this->taskTagModel->save($original_task['project_id'], $values['id'], array());
}
}
}
diff --git a/app/Model/TaskProjectDuplicationModel.php b/app/Model/TaskProjectDuplicationModel.php
new file mode 100644
index 00000000..8ebed255
--- /dev/null
+++ b/app/Model/TaskProjectDuplicationModel.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Kanboard\Model;
+
+/**
+ * Task Project Duplication
+ *
+ * @package Kanboard\Model
+ * @author Frederic Guillot
+ */
+class TaskProjectDuplicationModel extends TaskDuplicationModel
+{
+ /**
+ * 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->prepare($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id);
+ $this->checkDestinationProjectValues($values);
+ $new_task_id = $this->save($task_id, $values);
+
+ if ($new_task_id !== false) {
+ $this->tagDuplicationModel->duplicateTaskTagsToAnotherProject($task_id, $new_task_id, $project_id);
+ }
+
+ return $new_task_id;
+ }
+
+ /**
+ * Prepare values before duplication
+ *
+ * @access protected
+ * @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 array
+ */
+ protected function prepare($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id)
+ {
+ $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'];
+ return $values;
+ }
+}
diff --git a/app/Model/TaskProjectMoveModel.php b/app/Model/TaskProjectMoveModel.php
new file mode 100644
index 00000000..eda23c0b
--- /dev/null
+++ b/app/Model/TaskProjectMoveModel.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Kanboard\Model;
+
+use Kanboard\Event\TaskEvent;
+
+/**
+ * Task Project Move
+ *
+ * @package Kanboard\Model
+ * @author Frederic Guillot
+ */
+class TaskProjectMoveModel extends TaskDuplicationModel
+{
+ /**
+ * 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 = $this->prepare($project_id, $swimlane_id, $column_id, $category_id, $owner_id, $task);
+
+ $this->checkDestinationProjectValues($values);
+ $this->tagDuplicationModel->syncTaskTagsToAnotherProject($task_id, $project_id);
+
+ if ($this->db->table(TaskModel::TABLE)->eq('id', $task['id'])->update($values)) {
+ $event = new TaskEvent(array_merge($task, $values, array('task_id' => $task['id'])));
+ $this->dispatcher->dispatch(TaskModel::EVENT_MOVE_PROJECT, $event);
+ }
+
+ return true;
+ }
+
+ /**
+ * Prepare new task values
+ *
+ * @access protected
+ * @param integer $project_id
+ * @param integer $swimlane_id
+ * @param integer $column_id
+ * @param integer $category_id
+ * @param integer $owner_id
+ * @param array $task
+ * @return array
+ */
+ protected function prepare($project_id, $swimlane_id, $column_id, $category_id, $owner_id, array $task)
+ {
+ $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'];
+ $values['priority'] = $task['priority'];
+ return $values;
+ }
+}
diff --git a/app/Model/TaskRecurrenceModel.php b/app/Model/TaskRecurrenceModel.php
new file mode 100644
index 00000000..ffe43f8c
--- /dev/null
+++ b/app/Model/TaskRecurrenceModel.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace Kanboard\Model;
+
+use DateInterval;
+use DateTime;
+
+/**
+ * Task Recurrence
+ *
+ * @package Kanboard\Model
+ * @author Frederic Guillot
+ */
+class TaskRecurrenceModel extends TaskDuplicationModel
+{
+ /**
+ * Return the list user selectable recurrence status
+ *
+ * @access public
+ * @return array
+ */
+ public function getRecurrenceStatusList()
+ {
+ return array(
+ TaskModel::RECURRING_STATUS_NONE => t('No'),
+ TaskModel::RECURRING_STATUS_PENDING => t('Yes'),
+ );
+ }
+
+ /**
+ * Return the list recurrence triggers
+ *
+ * @access public
+ * @return array
+ */
+ public function getRecurrenceTriggerList()
+ {
+ return array(
+ TaskModel::RECURRING_TRIGGER_FIRST_COLUMN => t('When task is moved from first column'),
+ TaskModel::RECURRING_TRIGGER_LAST_COLUMN => t('When task is moved to last column'),
+ TaskModel::RECURRING_TRIGGER_CLOSE => t('When task is closed'),
+ );
+ }
+
+ /**
+ * Return the list options to calculate recurrence due date
+ *
+ * @access public
+ * @return array
+ */
+ public function getRecurrenceBasedateList()
+ {
+ return array(
+ TaskModel::RECURRING_BASEDATE_DUEDATE => t('Existing due date'),
+ TaskModel::RECURRING_BASEDATE_TRIGGERDATE => t('Action date'),
+ );
+ }
+
+ /**
+ * Return the list recurrence timeframes
+ *
+ * @access public
+ * @return array
+ */
+ public function getRecurrenceTimeframeList()
+ {
+ return array(
+ TaskModel::RECURRING_TIMEFRAME_DAYS => t('Day(s)'),
+ TaskModel::RECURRING_TIMEFRAME_MONTHS => t('Month(s)'),
+ TaskModel::RECURRING_TIMEFRAME_YEARS => t('Year(s)'),
+ );
+ }
+
+ /**
+ * 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 !== false) {
+ $this->tagDuplicationModel->duplicateTaskTags($task_id, $recurring_task_id);
+
+ $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;
+ }
+
+ /**
+ * 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();
+ }
+ }
+}
diff --git a/app/Model/TaskTagModel.php b/app/Model/TaskTagModel.php
index 91dfd224..0553cc6c 100644
--- a/app/Model/TaskTagModel.php
+++ b/app/Model/TaskTagModel.php
@@ -20,6 +20,23 @@ class TaskTagModel extends Base
const TABLE = 'task_has_tags';
/**
+ * Get all tags not available in a project
+ *
+ * @access public
+ * @param integer $task_id
+ * @param integer $project_id
+ * @return array
+ */
+ public function getTagIdsByTaskNotAvailableInProject($task_id, $project_id)
+ {
+ return $this->db->table(TagModel::TABLE)
+ ->eq(self::TABLE.'.task_id', $task_id)
+ ->notIn(TagModel::TABLE.'.project_id', array(0, $project_id))
+ ->join(self::TABLE, 'tag_id', 'id')
+ ->findAllByColumn(TagModel::TABLE.'.id');
+ }
+
+ /**
* Get all tags associated to a task
*
* @access public
@@ -82,6 +99,7 @@ class TaskTagModel extends Base
public function save($project_id, $task_id, array $tags)
{
$task_tags = $this->getList($task_id);
+ $tags = array_filter($tags);
return $this->associateTags($project_id, $task_id, $task_tags, $tags) &&
$this->dissociateTags($task_id, $task_tags, $tags);