Project activity refactoring and listeners improvements

This commit is contained in:
Frédéric Guillot
2014-10-12 21:38:56 -04:00
parent 4061927d21
commit 074056352d
56 changed files with 695 additions and 1208 deletions

View File

@@ -1,70 +0,0 @@
<?php
namespace Model;
use PDO;
use Core\Template;
/**
* Task history model
*
* @package model
* @author Frederic Guillot
*/
abstract class BaseHistory extends Base
{
/**
* SQL table name
*
* @access protected
* @var string
*/
protected $table = '';
/**
* Remove old event entries to avoid a large table
*
* @access public
* @param integer $max Maximum number of items to keep in the table
*/
public function cleanup($max)
{
if ($this->db->table($this->table)->count() > $max) {
$this->db->execute('
DELETE FROM '.$this->table.'
WHERE id <= (
SELECT id FROM (
SELECT id FROM '.$this->table.' ORDER BY id DESC LIMIT 1 OFFSET '.$max.'
) foo
)');
}
}
/**
* Get all events for a given project
*
* @access public
* @return array
*/
public function getAllByProjectId($project_id)
{
return $this->db->table($this->table)
->eq('project_id', $project_id)
->desc('id')
->findAll();
}
/**
* Get the event html content
*
* @access public
* @param array $params Event properties
* @return string
*/
public function getContent(array $params)
{
$tpl = new Template;
return $tpl->load('event_'.str_replace('.', '_', $params['event_name']), $params);
}
}

View File

@@ -1,152 +0,0 @@
<?php
namespace Model;
use PDO;
use Core\Registry;
use Event\CommentHistoryListener;
/**
* Comment history model
*
* @package model
* @author Frederic Guillot
*/
class CommentHistory extends BaseHistory
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'comment_has_events';
/**
* Maximum number of events
*
* @var integer
*/
const MAX_EVENTS = 5000;
/**
* Constructor
*
* @access public
* @param \Core\Registry $registry Registry instance
*/
public function __construct(Registry $registry)
{
parent::__construct($registry);
$this->table = self::TABLE;
}
/**
* Create a new event
*
* @access public
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param integer $comment_id Comment id
* @param integer $creator_id Author of the event (user id)
* @param string $event_name Task event name
* @param string $data Current comment
* @return boolean
*/
public function create($project_id, $task_id, $comment_id, $creator_id, $event_name, $data)
{
$values = array(
'project_id' => $project_id,
'task_id' => $task_id,
'comment_id' => $comment_id,
'creator_id' => $creator_id,
'event_name' => $event_name,
'date_creation' => time(),
'data' => $data,
);
$this->db->startTransaction();
$this->cleanup(self::MAX_EVENTS - 1);
$result = $this->db->table(self::TABLE)->insert($values);
$this->db->closeTransaction();
return $result;
}
/**
* Get all necessary content to display activity feed
*
* $author_name
* $author_username
* $task['id', 'title', 'position', 'column_name']
*/
public function getAllContentByProjectId($project_id, $limit = 50)
{
$sql = '
SELECT
comment_has_events.id,
comment_has_events.date_creation,
comment_has_events.event_name,
comment_has_events.data as comment,
comment_has_events.task_id,
tasks.title as task_title,
users.username as author_username,
users.name as author_name
FROM comment_has_events
LEFT JOIN users ON users.id=comment_has_events.creator_id
LEFT JOIN tasks ON tasks.id=comment_has_events.task_id
WHERE comment_has_events.project_id = ?
ORDER BY comment_has_events.id DESC
LIMIT '.$limit.' OFFSET 0
';
$rq = $this->db->execute($sql, array($project_id));
$events = $rq->fetchAll(PDO::FETCH_ASSOC);
foreach ($events as &$event) {
$event['author'] = $event['author_name'] ?: $event['author_username'];
$event['event_title'] = $this->getTitle($event);
$event['event_content'] = $this->getContent($event);
$event['event_type'] = 'comment';
}
return $events;
}
/**
* Get the event title (translated)
*
* @access public
* @param array $event Event properties
* @return string
*/
public function getTitle(array $event)
{
$titles = array(
Comment::EVENT_UPDATE => t('%s updated a comment on the task #%d', $event['author'], $event['task_id']),
Comment::EVENT_CREATE => t('%s commented on the task #%d', $event['author'], $event['task_id']),
);
return isset($titles[$event['event_name']]) ? $titles[$event['event_name']] : '';
}
/**
* Attach events to be able to record the history
*
* @access public
*/
public function attachEvents()
{
$events = array(
Comment::EVENT_UPDATE,
Comment::EVENT_CREATE,
);
$listener = new CommentHistoryListener($this);
foreach ($events as $event_name) {
$this->event->attach($event_name, $listener);
}
}
}

View File

@@ -5,12 +5,10 @@ namespace Model;
use Core\Session;
use Core\Translator;
use Core\Template;
use Event\TaskNotificationListener;
use Event\CommentNotificationListener;
use Event\FileNotificationListener;
use Event\SubTaskNotificationListener;
use Event\NotificationListener;
use Swift_Message;
use Swift_Mailer;
use Swift_TransportException;
/**
* Notification model
@@ -91,21 +89,28 @@ class Notification extends Base
*/
public function attachEvents()
{
$this->event->attach(File::EVENT_CREATE, new FileNotificationListener($this, 'notification_file_creation'));
$events = array(
Task::EVENT_CREATE => 'notification_task_creation',
Task::EVENT_UPDATE => 'notification_task_update',
Task::EVENT_CLOSE => 'notification_task_close',
Task::EVENT_OPEN => 'notification_task_open',
Task::EVENT_MOVE_COLUMN => 'notification_task_move_column',
Task::EVENT_MOVE_POSITION => 'notification_task_move_position',
Task::EVENT_ASSIGNEE_CHANGE => 'notification_task_assignee_change',
SubTask::EVENT_CREATE => 'notification_subtask_creation',
SubTask::EVENT_UPDATE => 'notification_subtask_update',
Comment::EVENT_CREATE => 'notification_comment_creation',
Comment::EVENT_UPDATE => 'notification_comment_update',
File::EVENT_CREATE => 'notification_file_creation',
);
$this->event->attach(Comment::EVENT_CREATE, new CommentNotificationListener($this, 'notification_comment_creation'));
$this->event->attach(Comment::EVENT_UPDATE, new CommentNotificationListener($this, 'notification_comment_update'));
foreach ($events as $event_name => $template_name) {
$this->event->attach(SubTask::EVENT_CREATE, new SubTaskNotificationListener($this, 'notification_subtask_creation'));
$this->event->attach(SubTask::EVENT_UPDATE, new SubTaskNotificationListener($this, 'notification_subtask_update'));
$listener = new NotificationListener($this->registry);
$listener->setTemplate($template_name);
$this->event->attach(Task::EVENT_CREATE, new TaskNotificationListener($this, 'notification_task_creation'));
$this->event->attach(Task::EVENT_UPDATE, new TaskNotificationListener($this, 'notification_task_update'));
$this->event->attach(Task::EVENT_CLOSE, new TaskNotificationListener($this, 'notification_task_close'));
$this->event->attach(Task::EVENT_OPEN, new TaskNotificationListener($this, 'notification_task_open'));
$this->event->attach(Task::EVENT_MOVE_COLUMN, new TaskNotificationListener($this, 'notification_task_move_column'));
$this->event->attach(Task::EVENT_MOVE_POSITION, new TaskNotificationListener($this, 'notification_task_move_position'));
$this->event->attach(Task::EVENT_ASSIGNEE_CHANGE, new TaskNotificationListener($this, 'notification_task_assignee_change'));
$this->event->attach($event_name, $listener);
}
}
/**
@@ -118,17 +123,22 @@ class Notification extends Base
*/
public function sendEmails($template, array $users, array $data)
{
$transport = $this->registry->shared('mailer');
$mailer = Swift_Mailer::newInstance($transport);
try {
$transport = $this->registry->shared('mailer');
$mailer = Swift_Mailer::newInstance($transport);
$message = Swift_Message::newInstance()
->setSubject($this->getMailSubject($template, $data))
->setFrom(array(MAIL_FROM => 'Kanboard'))
->setBody($this->getMailContent($template, $data), 'text/html');
$message = Swift_Message::newInstance()
->setSubject($this->getMailSubject($template, $data))
->setFrom(array(MAIL_FROM => 'Kanboard'))
->setBody($this->getMailContent($template, $data), 'text/html');
foreach ($users as $user) {
$message->setTo(array($user['email'] => $user['name'] ?: $user['username']));
$mailer->send($message);
foreach ($users as $user) {
$message->setTo(array($user['email'] => $user['name'] ?: $user['username']));
$mailer->send($message);
}
}
catch (Swift_TransportException $e) {
debug($e->getMessage());
}
}

View File

@@ -4,7 +4,7 @@ namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Event\ProjectModificationDate;
use Event\ProjectModificationDateListener;
use Core\Security;
/**
@@ -512,41 +512,10 @@ class Project extends Base
GithubWebhook::EVENT_COMMIT,
);
$listener = new ProjectModificationDate($this);
$listener = new ProjectModificationDateListener($this->registry);
foreach ($events as $event_name) {
$this->event->attach($event_name, $listener);
}
}
/**
* Get project activity
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getActivity($project_id)
{
$activity = array();
$tasks = $this->taskHistory->getAllContentByProjectId($project_id, 25);
$comments = $this->commentHistory->getAllContentByProjectId($project_id, 25);
$subtasks = $this->subtaskHistory->getAllContentByProjectId($project_id, 25);
foreach ($tasks as &$task) {
$activity[$task['date_creation'].'-'.$task['id']] = $task;
}
foreach ($subtasks as &$subtask) {
$activity[$subtask['date_creation'].'-'.$subtask['id']] = $subtask;
}
foreach ($comments as &$comment) {
$activity[$comment['date_creation'].'-'.$comment['id']] = $comment;
}
krsort($activity);
return $activity;
}
}

View File

@@ -0,0 +1,189 @@
<?php
namespace Model;
use Core\Template;
use Event\ProjectActivityListener;
/**
* Project activity model
*
* @package model
* @author Frederic Guillot
*/
class ProjectActivity extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'project_activities';
/**
* Maximum number of events
*
* @var integer
*/
const MAX_EVENTS = 5000;
/**
* Add a new event for the project
*
* @access public
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param integer $creator_id User id
* @param string $event_name Event name
* @param array $data Event data (will be serialized)
* @return boolean
*/
public function createEvent($project_id, $task_id, $creator_id, $event_name, array $data)
{
$values = array(
'project_id' => $project_id,
'task_id' => $task_id,
'creator_id' => $creator_id,
'event_name' => $event_name,
'date_creation' => time(),
'data' => serialize($data),
);
$this->cleanup(self::MAX_EVENTS - 1);
return $this->db->table(self::TABLE)->insert($values);
}
/**
* Get all events for the given project
*
* @access public
* @param integer $project_id Project id
* @param integer $limit Maximum events number
* @return array
*/
public function getAll($project_id, $limit = 50)
{
$events = $this->db->table(self::TABLE)
->columns(
self::TABLE.'.*',
User::TABLE.'.username AS author_username',
User::TABLE.'.name AS author_name'
)
->eq('project_id', $project_id)
->join(User::TABLE, 'id', 'creator_id')
->desc('id')
->limit($limit)
->findAll();
foreach ($events as &$event) {
$event += unserialize($event['data']);
unset($event['data']);
$event['author'] = $event['author_name'] ?: $event['author_username'];
$event['event_title'] = $this->getTitle($event);
$event['event_content'] = $this->getContent($event);
}
return $events;
}
/**
* Remove old event entries to avoid large table
*
* @access public
* @param integer $max Maximum number of items to keep in the table
*/
public function cleanup($max)
{
if ($this->db->table(self::TABLE)->count() > $max) {
$this->db->execute('
DELETE FROM '.self::TABLE.'
WHERE id <= (
SELECT id FROM (
SELECT id FROM '.self::TABLE.' ORDER BY id DESC LIMIT 1 OFFSET '.$max.'
) foo
)'
);
}
}
/**
* Attach events to be able to record the history
*
* @access public
*/
public function attachEvents()
{
$events = array(
Task::EVENT_ASSIGNEE_CHANGE,
Task::EVENT_UPDATE,
Task::EVENT_CREATE,
Task::EVENT_CLOSE,
Task::EVENT_OPEN,
Task::EVENT_MOVE_COLUMN,
Task::EVENT_MOVE_POSITION,
Comment::EVENT_UPDATE,
Comment::EVENT_CREATE,
SubTask::EVENT_UPDATE,
SubTask::EVENT_CREATE,
);
$listener = new ProjectActivityListener($this->registry);
foreach ($events as $event_name) {
$this->event->attach($event_name, $listener);
}
}
/**
* Get the event html content
*
* @access public
* @param array $params Event properties
* @return string
*/
public function getContent(array $params)
{
$tpl = new Template;
return $tpl->load('event_'.str_replace('.', '_', $params['event_name']), $params);
}
/**
* Get the event title (translated)
*
* @access public
* @param array $event Event properties
* @return string
*/
public function getTitle(array $event)
{
switch ($event['event_name']) {
case Task::EVENT_ASSIGNEE_CHANGE:
return t('%s change the assignee of the task #%d to %s', $event['author'], $event['task']['id'], $event['task']['assignee_name'] ?: $event['task']['assignee_username']);
case Task::EVENT_UPDATE:
return t('%s updated the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_CREATE:
return t('%s created the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_CLOSE:
return t('%s closed the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_OPEN:
return t('%s open the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_MOVE_COLUMN:
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 SubTask::EVENT_UPDATE:
return t('%s updated a subtask for the task #%d', $event['author'], $event['task']['id']);
case SubTask::EVENT_CREATE:
return t('%s created a subtask for the task #%d', $event['author'], $event['task']['id']);
case Comment::EVENT_UPDATE:
return t('%s updated a comment on the task #%d', $event['author'], $event['task']['id']);
case Comment::EVENT_CREATE:
return t('%s commented on the task #%d', $event['author'], $event['task']['id']);
default:
return '';
}
}
}

View File

@@ -1,161 +0,0 @@
<?php
namespace Model;
use PDO;
use Core\Registry;
use Event\SubtaskHistoryListener;
/**
* Comment history model
*
* @package model
* @author Frederic Guillot
*/
class SubtaskHistory extends BaseHistory
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'subtask_has_events';
/**
* Maximum number of events
*
* @var integer
*/
const MAX_EVENTS = 5000;
/**
* Constructor
*
* @access public
* @param \Core\Registry $registry Registry instance
*/
public function __construct(Registry $registry)
{
parent::__construct($registry);
$this->table = self::TABLE;
}
/**
* Create a new event
*
* @access public
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param integer $subtask_id Subtask id
* @param integer $creator_id Author of the event (user id)
* @param string $event_name Task event name
* @param string $data Current comment
* @return boolean
*/
public function create($project_id, $task_id, $subtask_id, $creator_id, $event_name, $data)
{
$values = array(
'project_id' => $project_id,
'task_id' => $task_id,
'subtask_id' => $subtask_id,
'creator_id' => $creator_id,
'event_name' => $event_name,
'date_creation' => time(),
'data' => $data,
);
$this->db->startTransaction();
$this->cleanup(self::MAX_EVENTS - 1);
$result = $this->db->table(self::TABLE)->insert($values);
$this->db->closeTransaction();
return $result;
}
/**
* Get all necessary content to display activity feed
*
* $author_name
* $author_username
* $task['id', 'title', 'position', 'column_name']
*/
public function getAllContentByProjectId($project_id, $limit = 50)
{
$sql = '
SELECT
subtask_has_events.id,
subtask_has_events.date_creation,
subtask_has_events.event_name,
subtask_has_events.task_id,
tasks.title as task_title,
users.username as author_username,
users.name as author_name,
assignees.name as subtask_assignee_name,
assignees.username as subtask_assignee_username,
task_has_subtasks.title as subtask_title,
task_has_subtasks.status as subtask_status,
task_has_subtasks.time_spent as subtask_time_spent,
task_has_subtasks.time_estimated as subtask_time_estimated
FROM subtask_has_events
LEFT JOIN users ON users.id=subtask_has_events.creator_id
LEFT JOIN tasks ON tasks.id=subtask_has_events.task_id
LEFT JOIN task_has_subtasks ON task_has_subtasks.id=subtask_has_events.subtask_id
LEFT JOIN users AS assignees ON assignees.id=task_has_subtasks.user_id
WHERE subtask_has_events.project_id = ?
ORDER BY subtask_has_events.id DESC
LIMIT '.$limit.' OFFSET 0
';
$rq = $this->db->execute($sql, array($project_id));
$events = $rq->fetchAll(PDO::FETCH_ASSOC);
foreach ($events as &$event) {
$event['author'] = $event['author_name'] ?: $event['author_username'];
$event['subtask_assignee'] = $event['subtask_assignee_name'] ?: $event['subtask_assignee_username'];
$event['subtask_status_list'] = $this->subTask->getStatusList();
$event['event_title'] = $this->getTitle($event);
$event['event_content'] = $this->getContent($event);
$event['event_type'] = 'subtask';
}
return $events;
}
/**
* Get the event title (translated)
*
* @access public
* @param array $event Event properties
* @return string
*/
public function getTitle(array $event)
{
$titles = array(
SubTask::EVENT_UPDATE => t('%s updated a subtask for the task #%d', $event['author'], $event['task_id']),
SubTask::EVENT_CREATE => t('%s created a subtask for the task #%d', $event['author'], $event['task_id']),
);
return isset($titles[$event['event_name']]) ? $titles[$event['event_name']] : '';
}
/**
* Attach events to be able to record the history
*
* @access public
*/
public function attachEvents()
{
$events = array(
SubTask::EVENT_UPDATE,
SubTask::EVENT_CREATE,
);
$listener = new SubtaskHistoryListener($this);
foreach ($events as $event_name) {
$this->event->attach($event_name, $listener);
}
}
}

View File

@@ -1,160 +0,0 @@
<?php
namespace Model;
use PDO;
use Core\Registry;
use Event\TaskHistoryListener;
/**
* Task history model
*
* @package model
* @author Frederic Guillot
*/
class TaskHistory extends BaseHistory
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'task_has_events';
/**
* Maximum number of events
*
* @var integer
*/
const MAX_EVENTS = 5000;
/**
* Constructor
*
* @access public
* @param \Core\Registry $registry Registry instance
*/
public function __construct(Registry $registry)
{
parent::__construct($registry);
$this->table = self::TABLE;
}
/**
* Create a new event
*
* @access public
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param integer $creator_id Author of the event (user id)
* @param string $event_name Task event name
* @return boolean
*/
public function create($project_id, $task_id, $creator_id, $event_name)
{
$values = array(
'project_id' => $project_id,
'task_id' => $task_id,
'creator_id' => $creator_id,
'event_name' => $event_name,
'date_creation' => time(),
);
$this->db->startTransaction();
$this->cleanup(self::MAX_EVENTS - 1);
$result = $this->db->table(self::TABLE)->insert($values);
$this->db->closeTransaction();
return $result;
}
/**
* Get all necessary content to display activity feed
*
* $author_name
* $author_username
* $task['id', 'title', 'position', 'column_name']
*/
public function getAllContentByProjectId($project_id, $limit = 50)
{
$sql = '
SELECT
task_has_events.id,
task_has_events.date_creation,
task_has_events.event_name,
task_has_events.task_id,
tasks.title as task_title,
tasks.position as task_position,
columns.title as task_column_name,
users.username as author_username,
users.name as author_name
FROM task_has_events
LEFT JOIN users ON users.id=task_has_events.creator_id
LEFT JOIN tasks ON tasks.id=task_has_events.task_id
LEFT JOIN columns ON columns.id=tasks.column_id
WHERE task_has_events.project_id = ?
ORDER BY task_has_events.id DESC
LIMIT '.$limit.' OFFSET 0
';
$rq = $this->db->execute($sql, array($project_id));
$events = $rq->fetchAll(PDO::FETCH_ASSOC);
foreach ($events as &$event) {
$event['author'] = $event['author_name'] ?: $event['author_username'];
$event['event_title'] = $this->getTitle($event);
$event['event_content'] = $this->getContent($event);
$event['event_type'] = 'task';
}
return $events;
}
/**
* Get the event title (translated)
*
* @access public
* @param array $event Event properties
* @return string
*/
public function getTitle(array $event)
{
$titles = array(
Task::EVENT_ASSIGNEE_CHANGE => t('%s change the assignee of the task #%d', $event['author'], $event['task_id']),
Task::EVENT_UPDATE => t('%s updated the task #%d', $event['author'], $event['task_id']),
Task::EVENT_CREATE => t('%s created the task #%d', $event['author'], $event['task_id']),
Task::EVENT_CLOSE => t('%s closed the task #%d', $event['author'], $event['task_id']),
Task::EVENT_OPEN => t('%s open the task #%d', $event['author'], $event['task_id']),
Task::EVENT_MOVE_COLUMN => t('%s moved the task #%d to the column "%s"', $event['author'], $event['task_id'], $event['task_column_name']),
Task::EVENT_MOVE_POSITION => 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_name']),
);
return isset($titles[$event['event_name']]) ? $titles[$event['event_name']] : '';
}
/**
* Attach events to be able to record the history
*
* @access public
*/
public function attachEvents()
{
$events = array(
Task::EVENT_ASSIGNEE_CHANGE,
Task::EVENT_UPDATE,
Task::EVENT_CREATE,
Task::EVENT_CLOSE,
Task::EVENT_OPEN,
Task::EVENT_MOVE_COLUMN,
Task::EVENT_MOVE_POSITION,
);
$listener = new TaskHistoryListener($this);
foreach ($events as $event_name) {
$this->event->attach($event_name, $listener);
}
}
}

View File

@@ -88,9 +88,13 @@ class Webhook extends Base
Task::EVENT_UPDATE,
Task::EVENT_CLOSE,
Task::EVENT_OPEN,
Task::EVENT_MOVE_COLUMN,
Task::EVENT_MOVE_POSITION,
Task::EVENT_ASSIGNEE_CHANGE,
);
$listener = new WebhookListener($this->url_task_modification, $this);
$listener = new WebhookListener($this->registry);
$listener->setUrl($this->url_task_modification);
foreach ($events as $event_name) {
$this->event->attach($event_name, $listener);
@@ -104,7 +108,10 @@ class Webhook extends Base
*/
public function attachCreateEvents()
{
$this->event->attach(Task::EVENT_CREATE, new WebhookListener($this->url_task_creation, $this));
$listener = new WebhookListener($this->registry);
$listener->setUrl($this->url_task_creation);
$this->event->attach(Task::EVENT_CREATE, $listener);
}
/**