diff options
Diffstat (limited to 'app/Model')
-rw-r--r-- | app/Model/Action.php | 1 | ||||
-rw-r--r-- | app/Model/Base.php | 19 | ||||
-rw-r--r-- | app/Model/File.php | 134 | ||||
-rw-r--r-- | app/Model/Notification.php | 377 | ||||
-rw-r--r-- | app/Model/ProjectActivity.php | 5 | ||||
-rw-r--r-- | app/Model/ProjectPermission.php | 4 | ||||
-rw-r--r-- | app/Model/SubtaskTimeTracking.php | 3 | ||||
-rw-r--r-- | app/Model/Task.php | 1 | ||||
-rw-r--r-- | app/Model/TaskFinder.php | 4 | ||||
-rw-r--r-- | app/Model/TaskModification.php | 25 | ||||
-rw-r--r-- | app/Model/Webhook.php | 2 |
11 files changed, 351 insertions, 224 deletions
diff --git a/app/Model/Action.php b/app/Model/Action.php index 3e8aa091..c3bfe017 100644 --- a/app/Model/Action.php +++ b/app/Model/Action.php @@ -57,6 +57,7 @@ class Action extends Base 'TaskAssignUser' => t('Change the assignee based on an external username'), 'TaskAssignCategoryLabel' => t('Change the category based on an external label'), 'TaskUpdateStartDate' => t('Automatically update the start date'), + 'TaskMoveColumnCategoryChange' => t('Move the task to another column when the category is changed'), ); asort($values); diff --git a/app/Model/Base.php b/app/Model/Base.php index 784545fe..51ae782d 100644 --- a/app/Model/Base.php +++ b/app/Model/Base.php @@ -143,4 +143,23 @@ abstract class Base extends \Core\Base 'url' => $this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), ); } + + /** + * Group a collection of records by a column + * + * @access public + * @param array $collection + * @param string $column + * @return array + */ + public function groupByColumn(array $collection, $column) + { + $result = array(); + + foreach ($collection as $item) { + $result[$item[$column]][] = $item; + } + + return $result; + } } diff --git a/app/Model/File.php b/app/Model/File.php index 1f62a55e..38b34cd3 100644 --- a/app/Model/File.php +++ b/app/Model/File.php @@ -49,7 +49,8 @@ class File extends Base { $file = $this->getbyId($file_id); - if (! empty($file) && @unlink(FILES_DIR.$file['path'])) { + if (! empty($file)) { + @unlink(FILES_DIR.$file['path']); return $this->db->table(self::TABLE)->eq('id', $file_id)->remove(); } @@ -66,10 +67,13 @@ class File extends Base public function removeAll($task_id) { $files = $this->getAll($task_id); + $results = array(); foreach ($files as $file) { - $this->remove($file['id']); + $results[] = $this->remove($file['id']); } + + return ! in_array(false, $results, true); } /** @@ -79,36 +83,41 @@ class File extends Base * @param integer $task_id Task id * @param string $name Filename * @param string $path Path on the disk - * @param bool $is_image Image or not * @param integer $size File size - * @return bool + * @return bool|integer */ - public function create($task_id, $name, $path, $is_image, $size) + public function create($task_id, $name, $path, $size) { - $this->container['dispatcher']->dispatch( - self::EVENT_CREATE, - new FileEvent(array('task_id' => $task_id, 'name' => $name)) - ); - - return $this->db->table(self::TABLE)->save(array( + $result = $this->db->table(self::TABLE)->save(array( 'task_id' => $task_id, 'name' => substr($name, 0, 255), 'path' => $path, - 'is_image' => $is_image ? '1' : '0', + 'is_image' => $this->isImage($name) ? 1 : 0, 'size' => $size, 'user_id' => $this->userSession->getId() ?: 0, 'date' => time(), )); + + if ($result) { + + $this->container['dispatcher']->dispatch( + self::EVENT_CREATE, + new FileEvent(array('task_id' => $task_id, 'name' => $name)) + ); + + return (int) $this->db->getConnection()->getLastId(); + } + + return false; } /** - * Get all files for a given task + * Get PicoDb query to get all files * * @access public - * @param integer $task_id Task id - * @return array + * @return \PicoDb\Table */ - public function getAll($task_id) + public function getQuery() { return $this->db ->table(self::TABLE) @@ -125,9 +134,19 @@ class File extends Base User::TABLE.'.name as user_name' ) ->join(User::TABLE, 'id', 'user_id') - ->eq('task_id', $task_id) - ->asc(self::TABLE.'.name') - ->findAll(); + ->asc(self::TABLE.'.name'); + } + + /** + * Get all files for a given task + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getAll($task_id) + { + return $this->getQuery()->eq('task_id', $task_id)->findAll(); } /** @@ -139,25 +158,7 @@ class File extends Base */ public function getAllImages($task_id) { - return $this->db - ->table(self::TABLE) - ->columns( - self::TABLE.'.id', - self::TABLE.'.name', - self::TABLE.'.path', - self::TABLE.'.is_image', - self::TABLE.'.task_id', - self::TABLE.'.date', - self::TABLE.'.user_id', - self::TABLE.'.size', - User::TABLE.'.username', - User::TABLE.'.name as user_name' - ) - ->join(User::TABLE, 'id', 'user_id') - ->eq('task_id', $task_id) - ->eq('is_image', 1) - ->asc(self::TABLE.'.name') - ->findAll(); + return $this->getQuery()->eq('task_id', $task_id)->eq('is_image', 1)->findAll(); } /** @@ -169,29 +170,11 @@ class File extends Base */ public function getAllDocuments($task_id) { - return $this->db - ->table(self::TABLE) - ->columns( - self::TABLE.'.id', - self::TABLE.'.name', - self::TABLE.'.path', - self::TABLE.'.is_image', - self::TABLE.'.task_id', - self::TABLE.'.date', - self::TABLE.'.user_id', - self::TABLE.'.size', - User::TABLE.'.username', - User::TABLE.'.name as user_name' - ) - ->join(User::TABLE, 'id', 'user_id') - ->eq('task_id', $task_id) - ->eq('is_image', 0) - ->asc(self::TABLE.'.name') - ->findAll(); + return $this->getQuery()->eq('task_id', $task_id)->eq('is_image', 0)->findAll(); } /** - * Check if a filename is an image + * Check if a filename is an image (file types that can be shown as thumbnail) * * @access public * @param string $filename Filename @@ -227,24 +210,6 @@ class File extends Base } /** - * Check if the base directory is created correctly - * - * @access public - */ - public function setup() - { - if (! is_dir(FILES_DIR)) { - if (! mkdir(FILES_DIR, 0755, true)) { - die('Unable to create the upload directory: "'.FILES_DIR.'"'); - } - } - - if (! is_writable(FILES_DIR)) { - die('The directory "'.FILES_DIR.'" must be writeable by your webserver user'); - } - } - - /** * Handle file upload * * @access public @@ -255,8 +220,7 @@ class File extends Base */ public function upload($project_id, $task_id, $form_name) { - $this->setup(); - $result = array(); + $results = array(); if (! empty($_FILES[$form_name])) { @@ -272,11 +236,10 @@ class File extends Base if (@move_uploaded_file($uploaded_filename, FILES_DIR.$destination_filename)) { - $result[] = $this->create( + $results[] = $this->create( $task_id, $original_filename, $destination_filename, - $this->isImage($original_filename), $_FILES[$form_name]['size'][$key] ); } @@ -284,7 +247,7 @@ class File extends Base } } - return count(array_unique($result)) === 1; + return ! in_array(false, $results, true); } /** @@ -294,7 +257,7 @@ class File extends Base * @param integer $project_id Project id * @param integer $task_id Task id * @param string $blob Base64 encoded image - * @return bool + * @return bool|integer */ public function uploadScreenshot($project_id, $task_id, $blob) { @@ -314,7 +277,6 @@ class File extends Base $task_id, $original_filename, $destination_filename, - true, strlen($data) ); } @@ -326,11 +288,10 @@ class File extends Base * @param integer $project_id Project id * @param integer $task_id Task id * @param string $filename Filename - * @param bool $is_image Is image file? * @param string $blob Base64 encoded image - * @return bool + * @return bool|integer */ - public function uploadContent($project_id, $task_id, $filename, $is_image, &$blob) + public function uploadContent($project_id, $task_id, $filename, $blob) { $data = base64_decode($blob); @@ -347,7 +308,6 @@ class File extends Base $task_id, $filename, $destination_filename, - $is_image, strlen($data) ); } diff --git a/app/Model/Notification.php b/app/Model/Notification.php index 048b6a39..ec349681 100644 --- a/app/Model/Notification.php +++ b/app/Model/Notification.php @@ -2,11 +2,7 @@ namespace Model; -use Core\Session; use Core\Translator; -use Swift_Message; -use Swift_Mailer; -use Swift_TransportException; /** * Notification model @@ -24,207 +20,329 @@ class Notification extends Base const TABLE = 'user_has_notifications'; /** - * Get a list of people with notifications enabled + * User filters + * + * @var integer + */ + const FILTER_NONE = 1; + const FILTER_ASSIGNEE = 2; + const FILTER_CREATOR = 3; + const FILTER_BOTH = 4; + + /** + * Send overdue tasks * * @access public - * @param integer $project_id Project id - * @param array $exclude_users List of user_id to exclude - * @return array */ - public function getUsersWithNotification($project_id, array $exclude_users = array()) + public function sendOverdueTaskNotifications() { - if ($this->projectPermission->isEverybodyAllowed($project_id)) { + $tasks = $this->taskFinder->getOverdueTasks(); + $projects = array(); - return $this->db - ->table(User::TABLE) - ->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', User::TABLE.'.email', User::TABLE.'.language') - ->eq('notifications_enabled', '1') - ->neq('email', '') - ->notin(User::TABLE.'.id', $exclude_users) - ->findAll(); + foreach ($this->groupByColumn($tasks, 'project_id') as $project_id => $project_tasks) { + + // Get the list of users that should receive notifications for each projects + $users = $this->notification->getUsersWithNotificationEnabled($project_id); + + foreach ($users as $user) { + $this->sendUserOverdueTaskNotifications($user, $project_tasks); + } } - return $this->db - ->table(ProjectPermission::TABLE) - ->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', User::TABLE.'.email', User::TABLE.'.language') - ->join(User::TABLE, 'id', 'user_id') - ->eq('project_id', $project_id) - ->eq('notifications_enabled', '1') - ->neq('email', '') - ->notin(User::TABLE.'.id', $exclude_users) - ->findAll(); + return $tasks; } /** - * Get the list of users to send the notification for a given project + * Send overdue tasks for a given user * * @access public - * @param integer $project_id Project id - * @param array $exclude_users List of user_id to exclude - * @return array + * @param array $user + * @param array $tasks */ - public function getUsersList($project_id, array $exclude_users = array()) + public function sendUserOverdueTaskNotifications(array $user, array $tasks) { - // Exclude the connected user - if (Session::isOpen() && $this->userSession->isLogged()) { - $exclude_users[] = $this->userSession->getId(); - } - - $users = $this->getUsersWithNotification($project_id, $exclude_users); + $user_tasks = array(); - foreach ($users as $index => $user) { + foreach ($tasks as $task) { + if ($this->notification->shouldReceiveNotification($user, array('task' => $task))) { + $user_tasks[] = $task; + } + } - $projects = $this->db->table(self::TABLE) - ->eq('user_id', $user['id']) - ->findAllByColumn('project_id'); + if (! empty($user_tasks)) { + $this->sendEmailNotification( + $user, + Task::EVENT_OVERDUE, + array('tasks' => $user_tasks, 'project_name' => $tasks[0]['project_name']) + ); + } + } - // The user have selected only some projects - if (! empty($projects)) { + /** + * Send notifications to people + * + * @access public + * @param string $event_name + * @param array $event_data + */ + public function sendNotifications($event_name, array $event_data) + { + $logged_user_id = $this->userSession->isLogged() ? $this->userSession->getId() : 0; + $users = $this->notification->getUsersWithNotificationEnabled($event_data['task']['project_id'], $logged_user_id); - // If the user didn't select this project we remove that guy from the list - if (! in_array($project_id, $projects)) { - unset($users[$index]); - } + foreach ($users as $user) { + if ($this->shouldReceiveNotification($user, $event_data)) { + $this->sendEmailNotification($user, $event_name, $event_data); } } - return $users; + // Restore locales + $this->config->setupTranslations(); } /** - * Send the email notifications + * Send email notification to someone * * @access public - * @param string $template Template name - * @param array $users List of users - * @param array $data Template data + * @param array $user User + * @param string $event_name + * @param array $event_data */ - public function sendEmails($template, array $users, array $data) + public function sendEmailNotification(array $user, $event_name, array $event_data) { - try { + // Use the user language otherwise use the application language (do not use the session language) + if (! empty($user['language'])) { + Translator::load($user['language']); + } + else { + Translator::load($this->config->get('application_language', 'en_US')); + } - $author = ''; + $this->emailClient->send( + $user['email'], + $user['name'] ?: $user['username'], + $this->getMailSubject($event_name, $event_data), + $this->getMailContent($event_name, $event_data) + ); + } + + /** + * Return true if the user should receive notification + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function shouldReceiveNotification(array $user, array $event_data) + { + $filters = array( + 'filterNone', + 'filterAssignee', + 'filterCreator', + 'filterBoth', + ); - if (Session::isOpen() && $this->userSession->isLogged()) { - $author = e('%s via Kanboard', $this->user->getFullname($this->session['user'])); + foreach ($filters as $filter) { + if ($this->$filter($user, $event_data)) { + return $this->filterProject($user, $event_data); } + } - $mailer = Swift_Mailer::newInstance($this->container['mailer']); + return false; + } - foreach ($users as $user) { + /** + * Return true if the user will receive all notifications + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterNone(array $user, array $event_data) + { + return $user['notifications_filter'] == self::FILTER_NONE; + } - $this->container['logger']->debug('Send email notification to '.$user['username'].' lang='.$user['language']); - $start_time = microtime(true); + /** + * Return true if the user is the assignee and selected the filter "assignee" + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterAssignee(array $user, array $event_data) + { + return $user['notifications_filter'] == self::FILTER_ASSIGNEE && $event_data['task']['owner_id'] == $user['id']; + } - // Use the user language otherwise use the application language (do not use the session language) - if (! empty($user['language'])) { - Translator::load($user['language']); - } - else { - Translator::load($this->config->get('application_language', 'en_US')); - } + /** + * Return true if the user is the creator and enabled the filter "creator" + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterCreator(array $user, array $event_data) + { + return $user['notifications_filter'] == self::FILTER_CREATOR && $event_data['task']['creator_id'] == $user['id']; + } - // Send the message - $message = Swift_Message::newInstance() - ->setSubject($this->getMailSubject($template, $data)) - ->setFrom(array(MAIL_FROM => $author ?: 'Kanboard')) - ->setBody($this->getMailContent($template, $data), 'text/html') - ->setTo(array($user['email'] => $user['name'] ?: $user['username'])); + /** + * Return true if the user is the assignee or the creator and selected the filter "both" + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterBoth(array $user, array $event_data) + { + return $user['notifications_filter'] == self::FILTER_BOTH && + ($event_data['task']['creator_id'] == $user['id'] || $event_data['task']['owner_id'] == $user['id']); + } - $mailer->send($message); + /** + * Return true if the user want to receive notification for the selected project + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterProject(array $user, array $event_data) + { + $projects = $this->db->table(self::TABLE)->eq('user_id', $user['id'])->findAllByColumn('project_id'); - $this->container['logger']->debug('Email sent in '.round(microtime(true) - $start_time, 6).' seconds'); - } + if (! empty($projects)) { + return in_array($event_data['task']['project_id'], $projects); } - catch (Swift_TransportException $e) { - $this->container['logger']->error($e->getMessage()); + + return true; + } + + /** + * Get a list of people with notifications enabled + * + * @access public + * @param integer $project_id Project id + * @param array $exclude_user_id User id to exclude + * @return array + */ + public function getUsersWithNotificationEnabled($project_id, $exclude_user_id = 0) + { + if ($this->projectPermission->isEverybodyAllowed($project_id)) { + + return $this->db + ->table(User::TABLE) + ->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', User::TABLE.'.email', User::TABLE.'.language', User::TABLE.'.notifications_filter') + ->eq('notifications_enabled', '1') + ->neq('email', '') + ->neq(User::TABLE.'.id', $exclude_user_id) + ->findAll(); } - // Restore locales - $this->config->setupTranslations(); + return $this->db + ->table(ProjectPermission::TABLE) + ->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', User::TABLE.'.email', User::TABLE.'.language', User::TABLE.'.notifications_filter') + ->join(User::TABLE, 'id', 'user_id') + ->eq('project_id', $project_id) + ->eq('notifications_enabled', '1') + ->neq('email', '') + ->neq(User::TABLE.'.id', $exclude_user_id) + ->findAll(); } /** - * Get the mail subject for a given label + * Get the mail content for a given template name * - * @access private - * @param string $label Label - * @param array $data Template data + * @access public + * @param string $event_name Event name + * @param array $event_data Event data + * @return string */ - private function getStandardMailSubject($label, array $data) + public function getMailContent($event_name, array $event_data) { - return sprintf('[%s][%s] %s (#%d)', $data['task']['project_name'], $label, $data['task']['title'], $data['task']['id']); + return $this->template->render( + 'notification/'.str_replace('.', '_', $event_name), + $event_data + array('application_url' => $this->config->get('application_url')) + ); } /** * Get the mail subject for a given template name * * @access public - * @param string $template Template name - * @param array $data Template data + * @param string $event_name Event name + * @param array $event_data Event data + * @return string */ - public function getMailSubject($template, array $data) + public function getMailSubject($event_name, array $event_data) { - switch ($template) { - case 'file_creation': - $subject = $this->getStandardMailSubject(t('New attachment'), $data); + switch ($event_name) { + case File::EVENT_CREATE: + $subject = $this->getStandardMailSubject(e('New attachment'), $event_data); + break; + case Comment::EVENT_CREATE: + $subject = $this->getStandardMailSubject(e('New comment'), $event_data); break; - case 'comment_creation': - $subject = $this->getStandardMailSubject(t('New comment'), $data); + case Comment::EVENT_UPDATE: + $subject = $this->getStandardMailSubject(e('Comment updated'), $event_data); break; - case 'comment_update': - $subject = $this->getStandardMailSubject(t('Comment updated'), $data); + case Subtask::EVENT_CREATE: + $subject = $this->getStandardMailSubject(e('New subtask'), $event_data); break; - case 'subtask_creation': - $subject = $this->getStandardMailSubject(t('New subtask'), $data); + case Subtask::EVENT_UPDATE: + $subject = $this->getStandardMailSubject(e('Subtask updated'), $event_data); break; - case 'subtask_update': - $subject = $this->getStandardMailSubject(t('Subtask updated'), $data); + case Task::EVENT_CREATE: + $subject = $this->getStandardMailSubject(e('New task'), $event_data); break; - case 'task_creation': - $subject = $this->getStandardMailSubject(t('New task'), $data); + case Task::EVENT_UPDATE: + $subject = $this->getStandardMailSubject(e('Task updated'), $event_data); break; - case 'task_update': - $subject = $this->getStandardMailSubject(t('Task updated'), $data); + case Task::EVENT_CLOSE: + $subject = $this->getStandardMailSubject(e('Task closed'), $event_data); break; - case 'task_close': - $subject = $this->getStandardMailSubject(t('Task closed'), $data); + case Task::EVENT_OPEN: + $subject = $this->getStandardMailSubject(e('Task opened'), $event_data); break; - case 'task_open': - $subject = $this->getStandardMailSubject(t('Task opened'), $data); + case Task::EVENT_MOVE_COLUMN: + $subject = $this->getStandardMailSubject(e('Column change'), $event_data); break; - case 'task_move_column': - $subject = $this->getStandardMailSubject(t('Column Change'), $data); + case Task::EVENT_MOVE_POSITION: + $subject = $this->getStandardMailSubject(e('Position change'), $event_data); break; - case 'task_move_position': - $subject = $this->getStandardMailSubject(t('Position Change'), $data); + case Task::EVENT_MOVE_SWIMLANE: + $subject = $this->getStandardMailSubject(e('Swimlane change'), $event_data); break; - case 'task_assignee_change': - $subject = $this->getStandardMailSubject(t('Assignee Change'), $data); + case Task::EVENT_ASSIGNEE_CHANGE: + $subject = $this->getStandardMailSubject(e('Assignee change'), $event_data); break; - case 'task_due': - $subject = e('[%s][Due tasks]', $data['project']); + case Task::EVENT_OVERDUE: + $subject = e('[%s] Overdue tasks', $event_data['project_name']); break; default: - $subject = e('[Kanboard] Notification'); + $subject = e('Notification'); } return $subject; } /** - * Get the mail content for a given template name + * Get the mail subject for a given label * - * @access public - * @param string $template Template name + * @access private + * @param string $label Label * @param array $data Template data + * @return string */ - public function getMailContent($template, array $data) + private function getStandardMailSubject($label, array $data) { - return $this->template->render( - 'notification/'.$template, - $data + array('application_url' => $this->config->get('application_url')) - ); + return sprintf('[%s][%s] %s (#%d)', $data['task']['project_name'], $label, $data['task']['title'], $data['task']['id']); } /** @@ -243,7 +361,8 @@ class Notification extends Base // Activate notifications $this->db->table(User::TABLE)->eq('id', $user_id)->update(array( - 'notifications_enabled' => '1' + 'notifications_enabled' => '1', + 'notifications_filter' => empty($values['notifications_filter']) ? self::FILTER_BOTH : $values['notifications_filter'], )); // Save selected projects @@ -275,9 +394,7 @@ class Notification extends Base */ public function readSettings($user_id) { - $values = array(); - $values['notifications_enabled'] = $this->db->table(User::TABLE)->eq('id', $user_id)->findOneColumn('notifications_enabled'); - + $values = $this->db->table(User::TABLE)->eq('id', $user_id)->columns('notifications_enabled', 'notifications_filter')->findOne(); $projects = $this->db->table(self::TABLE)->eq('user_id', $user_id)->findAllByColumn('project_id'); foreach ($projects as $project_id) { diff --git a/app/Model/ProjectActivity.php b/app/Model/ProjectActivity.php index 27f1cfcd..a9222fcc 100644 --- a/app/Model/ProjectActivity.php +++ b/app/Model/ProjectActivity.php @@ -227,6 +227,11 @@ class ProjectActivity extends Base return t('%s moved the task #%d to the column "%s"', $event['author'], $event['task']['id'], $event['task']['column_title']); case Task::EVENT_MOVE_POSITION: return t('%s moved the task #%d to the position %d in the column "%s"', $event['author'], $event['task']['id'], $event['task']['position'], $event['task']['column_title']); + case Task::EVENT_MOVE_SWIMLANE: + if ($event['task']['swimlane_id'] == 0) { + return t('%s moved the task #%d to the first swimlane', $event['author'], $event['task']['id']); + } + return t('%s moved the task #%d to the swimlane "%s"', $event['author'], $event['task']['id'], $event['task']['swimlane_name']); case Subtask::EVENT_UPDATE: return t('%s updated a subtask for the task #%d', $event['author'], $event['task']['id']); case Subtask::EVENT_CREATE: diff --git a/app/Model/ProjectPermission.php b/app/Model/ProjectPermission.php index d4f44f66..b0a09df4 100644 --- a/app/Model/ProjectPermission.php +++ b/app/Model/ProjectPermission.php @@ -290,7 +290,7 @@ class ProjectPermission extends Base } /** - * Return a list of allowed projects for a given user + * Return a list of allowed active projects for a given user * * @access public * @param integer $user_id User id @@ -302,7 +302,7 @@ class ProjectPermission extends Base return $this->project->getListByStatus(Project::ACTIVE); } - return $this->getMemberProjects($user_id); + return $this->getActiveMemberProjects($user_id); } /** diff --git a/app/Model/SubtaskTimeTracking.php b/app/Model/SubtaskTimeTracking.php index d4edf660..93a698b6 100644 --- a/app/Model/SubtaskTimeTracking.php +++ b/app/Model/SubtaskTimeTracking.php @@ -105,7 +105,8 @@ class SubtaskTimeTracking extends Base ->join(Subtask::TABLE, 'id', 'subtask_id') ->join(Task::TABLE, 'id', 'task_id', Subtask::TABLE) ->join(User::TABLE, 'id', 'user_id', self::TABLE) - ->eq(Task::TABLE.'.project_id', $project_id); + ->eq(Task::TABLE.'.project_id', $project_id) + ->asc(self::TABLE.'.id'); } /** diff --git a/app/Model/Task.php b/app/Model/Task.php index abd787ad..71d973a4 100644 --- a/app/Model/Task.php +++ b/app/Model/Task.php @@ -40,6 +40,7 @@ class Task extends Base const EVENT_OPEN = 'task.open'; const EVENT_CREATE_UPDATE = 'task.create_update'; const EVENT_ASSIGNEE_CHANGE = 'task.assignee_change'; + const EVENT_OVERDUE = 'task.overdue'; /** * Recurrence: status diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php index 6f53249a..327b480f 100644 --- a/app/Model/TaskFinder.php +++ b/app/Model/TaskFinder.php @@ -168,6 +168,8 @@ class TaskFinder extends Base Task::TABLE.'.title', Task::TABLE.'.date_due', Task::TABLE.'.project_id', + Task::TABLE.'.creator_id', + Task::TABLE.'.owner_id', Project::TABLE.'.name AS project_name', User::TABLE.'.username AS assignee_username', User::TABLE.'.name AS assignee_name' @@ -261,6 +263,7 @@ class TaskFinder extends Base tasks.recurrence_parent, tasks.recurrence_child, project_has_categories.name AS category_name, + swimlanes.name AS swimlane_name, projects.name AS project_name, columns.title AS column_title, users.username AS assignee_username, @@ -273,6 +276,7 @@ class TaskFinder extends Base LEFT JOIN project_has_categories ON project_has_categories.id = tasks.category_id LEFT JOIN projects ON projects.id = tasks.project_id LEFT JOIN columns ON columns.id = tasks.column_id + LEFT JOIN swimlanes ON swimlanes.id = tasks.swimlane_id WHERE tasks.id = ? '; diff --git a/app/Model/TaskModification.php b/app/Model/TaskModification.php index 677fcd60..4691ce81 100644 --- a/app/Model/TaskModification.php +++ b/app/Model/TaskModification.php @@ -42,13 +42,19 @@ class TaskModification extends Base */ public function fireEvents(array $task, array $new_values) { + $events = array(); $event_data = array_merge($task, $new_values, array('task_id' => $task['id'])); - if (isset($new_values['owner_id']) && $task['owner_id'] != $new_values['owner_id']) { - $events = array(Task::EVENT_ASSIGNEE_CHANGE); + // Values changed + $event_data['changes'] = array_diff_assoc($new_values, $task); + unset($event_data['changes']['date_modification']); + + if ($this->isFieldModified('owner_id', $event_data['changes'])) { + $events[] = Task::EVENT_ASSIGNEE_CHANGE; } else { - $events = array(Task::EVENT_CREATE_UPDATE, Task::EVENT_UPDATE); + $events[] = Task::EVENT_CREATE_UPDATE; + $events[] = Task::EVENT_UPDATE; } foreach ($events as $event) { @@ -57,6 +63,19 @@ class TaskModification extends Base } /** + * Return true if the field is the only modified value + * + * @access public + * @param string $field + * @param array $changes + * @return boolean + */ + public function isFieldModified($field, array $changes) + { + return isset($changes[$field]) && count($changes) === 1; + } + + /** * Prepare data before task modification * * @access public diff --git a/app/Model/Webhook.php b/app/Model/Webhook.php index 8c270fb6..e3af37f7 100644 --- a/app/Model/Webhook.php +++ b/app/Model/Webhook.php @@ -30,7 +30,7 @@ class Webhook extends Base $url .= '?token='.$token; } - return $this->httpClient->post($url, $values); + return $this->httpClient->postJson($url, $values); } } } |