summaryrefslogtreecommitdiff
path: root/app/Model
diff options
context:
space:
mode:
Diffstat (limited to 'app/Model')
-rw-r--r--app/Model/Action.php1
-rw-r--r--app/Model/Base.php19
-rw-r--r--app/Model/File.php134
-rw-r--r--app/Model/Notification.php377
-rw-r--r--app/Model/ProjectActivity.php5
-rw-r--r--app/Model/ProjectPermission.php4
-rw-r--r--app/Model/SubtaskTimeTracking.php3
-rw-r--r--app/Model/Task.php1
-rw-r--r--app/Model/TaskFinder.php4
-rw-r--r--app/Model/TaskModification.php25
-rw-r--r--app/Model/Webhook.php2
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);
}
}
}