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

@ -14,6 +14,7 @@ use Core\Tool;
*
* @property \Model\Acl $acl
* @property \Model\Task $task
* @property \Model\TaskFinder $taskFinder
*/
abstract class Base implements Listener
{

View File

@ -153,13 +153,11 @@ abstract class Base
private function attachEvents()
{
$models = array(
'projectActivity', // Order is important
'action',
'project',
'webhook',
'notification',
'taskHistory',
'commentHistory',
'subtaskHistory',
);
foreach ($models as $model) {

View File

@ -364,7 +364,7 @@ class Project extends Base
}
$this->response->xml($this->template->load('project_feed', array(
'events' => $this->project->getActivity($project['id']),
'events' => $this->projectActivity->getAll($project['id']),
'project' => $project,
)));
}
@ -379,7 +379,7 @@ class Project extends Base
$project = $this->getProject();
$this->response->html($this->template->layout('project_activity', array(
'events' => $this->project->getActivity($project['id']),
'events' => $this->projectActivity->getAll($project['id']),
'menu' => 'projects',
'project' => $project,
'title' => t('%s\'s activity', $project['name'])

View File

@ -69,11 +69,14 @@ class Event
{
if (! $this->isEventTriggered($eventName)) {
$this->lastEvent = $eventName;
$this->events[] = $eventName;
if (isset($this->listeners[$eventName])) {
foreach ($this->listeners[$eventName] as $listener) {
$this->lastEvent = $eventName;
if ($listener->execute($data)) {
$this->lastListener = get_class($listener);
}

79
app/Event/Base.php Normal file
View File

@ -0,0 +1,79 @@
<?php
namespace Event;
use Core\Listener;
use Core\Registry;
use Core\Tool;
/**
* Base Listener
*
* @package event
* @author Frederic Guillot
*
* @property \Model\Comment $comment
* @property \Model\Project $project
* @property \Model\ProjectActivity $projectActivity
* @property \Model\SubTask $subTask
* @property \Model\Task $task
* @property \Model\TaskFinder $taskFinder
*/
abstract class Base implements Listener
{
/**
* Registry instance
*
* @access protected
* @var \Core\Registry
*/
protected $registry;
/**
* Constructor
*
* @access public
* @param \Core\Registry $registry Regsitry instance
*/
public function __construct(Registry $registry)
{
$this->registry = $registry;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Load automatically models
*
* @access public
* @param string $name Model name
* @return mixed
*/
public function __get($name)
{
return Tool::loadModel($this->registry, $name);
}
/**
* Get event namespace
*
* Event = task.close | Namespace = task
*
* @access public
* @return string
*/
public function getEventNamespace()
{
$event_name = $this->registry->event->getLastTriggeredEvent();
return substr($event_name, 0, strpos($event_name, '.'));
}
}

View File

@ -1,87 +0,0 @@
<?php
namespace Event;
use Core\Listener;
use Model\Notification;
/**
* Base notification listener
*
* @package event
* @author Frederic Guillot
*/
abstract class BaseNotificationListener implements Listener
{
/**
* Notification model
*
* @accesss protected
* @var Model\Notification
*/
protected $notification;
/**
* Template name
*
* @accesss private
* @var string
*/
private $template = '';
/**
* Fetch data for the mail template
*
* @access public
* @param array $data Event data
* @return array
*/
abstract public function getTemplateData(array $data);
/**
* Constructor
*
* @access public
* @param \Model\Notification $notification Notification model instance
* @param string $template Template name
*/
public function __construct(Notification $notification, $template)
{
$this->template = $template;
$this->notification = $notification;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
$values = $this->getTemplateData($data);
// Get the list of users to be notified
$users = $this->notification->getUsersList($values['task']['project_id']);
// Send notifications
if ($users) {
$this->notification->sendEmails($this->template, $users, $values);
return true;
}
return false;
}
}

View File

@ -1,73 +0,0 @@
<?php
namespace Event;
use Core\Listener;
use Model\CommentHistory;
/**
* Comment history listener
*
* @package event
* @author Frederic Guillot
*/
class CommentHistoryListener implements Listener
{
/**
* Comment History model
*
* @accesss private
* @var \Model\CommentHistory
*/
private $model;
/**
* Constructor
*
* @access public
* @param \Model\CommentHistory $model Comment History model instance
*/
public function __construct(CommentHistory $model)
{
$this->model = $model;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
$creator_id = $this->model->acl->getUserId();
if ($creator_id && isset($data['task_id']) && isset($data['id'])) {
$task = $this->model->taskFinder->getById($data['task_id']);
$this->model->create(
$task['project_id'],
$data['task_id'],
$data['id'],
$creator_id,
$this->model->event->getLastTriggeredEvent(),
$data['comment']
);
}
return false;
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace Event;
use Event\BaseNotificationListener;
/**
* Comment notification listener
*
* @package event
* @author Frederic Guillot
*/
class CommentNotificationListener extends BaseNotificationListener
{
/**
* Fetch data for the mail template
*
* @access public
* @param array $data Event data
* @return array
*/
public function getTemplateData(array $data)
{
$values = array();
$values['comment'] = $this->notification->comment->getById($data['id']);
$values['task'] = $this->notification->taskFinder->getDetails($values['comment']['task_id']);
return $values;
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace Event;
use Event\BaseNotificationListener;
/**
* File notification listener
*
* @package event
* @author Frederic Guillot
*/
class FileNotificationListener extends BaseNotificationListener
{
/**
* Fetch data for the mail template
*
* @access public
* @param array $data Event data
* @return array
*/
public function getTemplateData(array $data)
{
$values = array();
$values['file'] = $data;
$values['task'] = $this->notification->taskFinder->getDetails($data['task_id']);
return $values;
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace Event;
/**
* Notification listener
*
* @package event
* @author Frederic Guillot
*/
class NotificationListener extends Base
{
/**
* Template name
*
* @accesss private
* @var string
*/
private $template = '';
/**
* Set template name
*
* @access public
* @param string $template Template name
*/
public function setTemplate($template)
{
$this->template = $template;
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
$values = $this->getTemplateData($data);
$users = $this->notification->getUsersList($values['task']['project_id']);
if ($users) {
$this->notification->sendEmails($this->template, $users, $values);
return true;
}
return false;
}
/**
* Fetch data for the mail template
*
* @access public
* @param array $data Event data
* @return array
*/
public function getTemplateData(array $data)
{
$values = array();
switch ($this->getEventNamespace()) {
case 'task':
$values['task'] = $this->taskFinder->getDetails($data['task_id']);
break;
case 'subtask':
$values['subtask'] = $this->subtask->getById($data['id'], true);
$values['task'] = $this->taskFinder->getDetails($data['task_id']);
break;
case 'file':
$values['file'] = $data;
$values['task'] = $this->taskFinder->getDetails($data['task_id']);
break;
case 'comment':
$values['comment'] = $this->comment->getById($data['id']);
$values['task'] = $this->taskFinder->getDetails($values['comment']['task_id']);
break;
}
return $values;
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace Event;
/**
* Project activity listener
*
* @package event
* @author Frederic Guillot
*/
class ProjectActivityListener extends Base
{
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
if (isset($data['task_id'])) {
$values = $this->getValues($data);
return $this->projectActivity->createEvent(
$values['task']['project_id'],
$values['task']['id'],
$this->acl->getUserId(),
$this->registry->event->getLastTriggeredEvent(),
$values
);
}
return false;
}
/**
* Get event activity data
*
* @access private
* @param array $data Event data dictionary
* @return array
*/
private function getValues(array $data)
{
$values = array();
$values['task'] = $this->taskFinder->getDetails($data['task_id']);
switch ($this->getEventNamespace()) {
case 'subtask':
$values['subtask'] = $this->subTask->getById($data['id'], true);
break;
case 'comment':
$values['comment'] = $this->comment->getById($data['id']);
break;
}
return $values;
}
}

View File

@ -1,63 +0,0 @@
<?php
namespace Event;
use Core\Listener;
use Model\Project;
/**
* Project modification date listener
*
* Update the last modified field for a project
*
* @package event
* @author Frederic Guillot
*/
class ProjectModificationDate implements Listener
{
/**
* Project model
*
* @accesss private
* @var \Model\Project
*/
private $project;
/**
* Constructor
*
* @access public
* @param \Model\Project $project Project model instance
*/
public function __construct(Project $project)
{
$this->project = $project;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
if (isset($data['project_id'])) {
return $this->project->updateModificationDate($data['project_id']);
}
return false;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Event;
/**
* Project modification date listener
*
* Update the "last_modified" field for a project
*
* @package event
* @author Frederic Guillot
*/
class ProjectModificationDateListener extends Base
{
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
if (isset($data['project_id'])) {
return $this->project->updateModificationDate($data['project_id']);
}
return false;
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace Event;
use Event\BaseNotificationListener;
/**
* SubTask notification listener
*
* @package event
* @author Frederic Guillot
*/
class SubTaskNotificationListener extends BaseNotificationListener
{
/**
* Fetch data for the mail template
*
* @access public
* @param array $data Event data
* @return array
*/
public function getTemplateData(array $data)
{
$values = array();
$values['subtask'] = $this->notification->subtask->getById($data['id'], true);
$values['task'] = $this->notification->taskFinder->getDetails($data['task_id']);
return $values;
}
}

View File

@ -1,73 +0,0 @@
<?php
namespace Event;
use Core\Listener;
use Model\SubtaskHistory;
/**
* Subtask history listener
*
* @package event
* @author Frederic Guillot
*/
class SubtaskHistoryListener implements Listener
{
/**
* Comment History model
*
* @accesss private
* @var \Model\SubtaskHistory
*/
private $model;
/**
* Constructor
*
* @access public
* @param \Model\SubtaskHistory $model Subtask History model instance
*/
public function __construct(SubtaskHistory $model)
{
$this->model = $model;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
$creator_id = $this->model->acl->getUserId();
if ($creator_id && isset($data['task_id']) && isset($data['id'])) {
$task = $this->model->taskFinder->getById($data['task_id']);
$this->model->create(
$task['project_id'],
$data['task_id'],
$data['id'],
$creator_id,
$this->model->event->getLastTriggeredEvent(),
''
);
}
return false;
}
}

View File

@ -1,63 +0,0 @@
<?php
namespace Event;
use Core\Listener;
use Model\TaskHistory;
/**
* Task history listener
*
* @package event
* @author Frederic Guillot
*/
class TaskHistoryListener implements Listener
{
/**
* Task History model
*
* @accesss private
* @var \Model\TaskHistory
*/
private $model;
/**
* Constructor
*
* @access public
* @param \Model\TaskHistory $model Task History model instance
*/
public function __construct(TaskHistory $model)
{
$this->model = $model;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
$creator_id = $this->model->acl->getUserId();
if ($creator_id && isset($data['task_id']) && isset($data['project_id'])) {
$this->model->create($data['project_id'], $data['task_id'], $creator_id, $this->model->event->getLastTriggeredEvent());
}
return false;
}
}

View File

@ -1,29 +0,0 @@
<?php
namespace Event;
use Event\BaseNotificationListener;
/**
* Task notification listener
*
* @package event
* @author Frederic Guillot
*/
class TaskNotificationListener extends BaseNotificationListener
{
/**
* Fetch data for the mail template
*
* @access public
* @param array $data Event data
* @return array
*/
public function getTemplateData(array $data)
{
$values = array();
$values['task'] = $this->notification->taskFinder->getDetails($data['task_id']);
return $values;
}
}

View File

@ -2,25 +2,14 @@
namespace Event;
use Core\Listener;
use Model\Webhook;
/**
* Webhook task events
*
* @package event
* @author Frederic Guillot
*/
class WebhookListener implements Listener
class WebhookListener extends Base
{
/**
* Webhook model
*
* @accesss private
* @var \Model\Webhook
*/
private $webhook;
/**
* Url to call
*
@ -30,27 +19,14 @@ class WebhookListener implements Listener
private $url = '';
/**
* Constructor
* Set webhook url
*
* @access public
* @param string $url URL to call
* @param \Model\Webhook $webhook Webhook model instance
* @param string $url URL to call
*/
public function __construct($url, Webhook $webhook)
public function setUrl($url)
{
$this->url = $url;
$this->webhook = $webhook;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**

View File

@ -495,8 +495,8 @@ return array(
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',

View File

@ -495,8 +495,8 @@ return array(
'Default values are "%s"' => 'Los valores por defecto son "%s"',
'Default columns for new projects (Comma-separated)' => 'Columnas por defecto de los nuevos proyectos (Separadas mediante comas)',
'Task assignee change' => 'Cambiar persona asignada a la tarea',
'%s change the assignee of the task #%d' => '%s cambia la persona asignada a la tarea #%d',
'%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s cambia la persona asignada a la tarea <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
'[%s][Column Change] %s (#%d)' => '[%s][Cambia Columna] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Cambia Posición] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Cambia Persona Asignada] %s (#%d)',

View File

@ -495,8 +495,8 @@ return array(
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',

View File

@ -495,8 +495,8 @@ return array(
'Default values are "%s"' => 'Les valeurs par défaut sont « %s »',
'Default columns for new projects (Comma-separated)' => 'Colonnes par défaut pour les nouveaux projets (séparé par des virgules)',
'Task assignee change' => 'Modification de la personne assignée sur une tâche',
'%s change the assignee of the task #%d' => '%s a changé la personne assignée sur la tâche #%d',
'%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s a changé la personne assignée sur la tâche <a href="?controller=task&amp;action=show&amp;task_id=%d">n°%d</a>',
'%s change the assignee of the task #%d to %s' => '%s a changé la personne assignée sur la tâche #%d pour %s',
'%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '%s a changé la personne assignée sur la tâche <a href="?controller=task&amp;action=show&amp;task_id=%d">n°%d</a> pour %s',
'[%s][Column Change] %s (#%d)' => '[%s][Changement de colonne] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Changement de position] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Changement d\'assigné] %s (#%d)',

View File

@ -495,8 +495,8 @@ return array(
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',

View File

@ -495,8 +495,8 @@ return array(
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',

View File

@ -495,8 +495,8 @@ return array(
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',

View File

@ -495,8 +495,8 @@ return array(
'Default values are "%s"' => 'Колонки по умолчанию: "%s"',
'Default columns for new projects (Comma-separated)' => 'Колонки по умолчанию для новых проектов (разделять запятой)',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',

View File

@ -495,8 +495,8 @@ return array(
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',

View File

@ -495,8 +495,8 @@ return array(
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',

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);
}
/**

View File

@ -5,7 +5,30 @@ namespace Schema;
use PDO;
use Core\Security;
const VERSION = 32;
const VERSION = 33;
function version_33($pdo)
{
$pdo->exec("
CREATE TABLE project_activities (
id INT NOT NULL AUTO_INCREMENT,
date_creation INT NOT NULL,
event_name VARCHAR(50) NOT NULL,
creator_id INT,
project_id INT,
task_id INT,
data TEXT,
PRIMARY KEY(id),
FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
) ENGINE=InnoDB CHARSET=utf8
");
$pdo->exec('DROP TABLE task_has_events');
$pdo->exec('DROP TABLE comment_has_events');
$pdo->exec('DROP TABLE subtask_has_events');
}
function version_32($pdo)
{
@ -355,7 +378,7 @@ function version_1($pdo)
id INT NOT NULL AUTO_INCREMENT,
task_id INT,
user_id INT,
date INT,
`date` INT,
comment TEXT,
PRIMARY KEY (id),
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE,

View File

@ -5,7 +5,29 @@ namespace Schema;
use PDO;
use Core\Security;
const VERSION = 13;
const VERSION = 14;
function version_14($pdo)
{
$pdo->exec("
CREATE TABLE project_activities (
id SERIAL PRIMARY KEY,
date_creation INTEGER NOT NULL,
event_name VARCHAR(50) NOT NULL,
creator_id INTEGER,
project_id INTEGER,
task_id INTEGER,
data TEXT,
FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
)
");
$pdo->exec('DROP TABLE task_has_events');
$pdo->exec('DROP TABLE comment_has_events');
$pdo->exec('DROP TABLE subtask_has_events');
}
function version_13($pdo)
{

View File

@ -5,7 +5,29 @@ namespace Schema;
use Core\Security;
use PDO;
const VERSION = 32;
const VERSION = 33;
function version_33($pdo)
{
$pdo->exec("
CREATE TABLE project_activities (
id INTEGER PRIMARY KEY,
date_creation INTEGER NOT NULL,
event_name TEXT NOT NULL,
creator_id INTEGER,
project_id INTEGER,
task_id INTEGER,
data TEXT,
FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
)
");
$pdo->exec('DROP TABLE task_has_events');
$pdo->exec('DROP TABLE comment_has_events');
$pdo->exec('DROP TABLE subtask_has_events');
}
function version_32($pdo)
{

View File

@ -1,7 +1,7 @@
<p class="activity-title">
<?= e('%s commented the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?>
</p>
<p class="activity-description">
<em><?= Helper\escape($task_title) ?></em><br/>
<div class="markdown"><?= Helper\markdown($comment) ?></div>
</p>
<div class="activity-description">
<em><?= Helper\escape($task['title']) ?></em><br/>
<div class="markdown"><?= Helper\markdown($comment['comment']) ?></div>
</div>

View File

@ -1,7 +1,7 @@
<p class="activity-title">
<?= e('%s updated a comment on the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?>
</p>
<p class="activity-description">
<em><?= Helper\escape($task_title) ?></em><br/>
<div class="markdown"><?= Helper\markdown($comment) ?></div>
</p>
<div class="activity-description">
<em><?= Helper\escape($task['title']) ?></em><br/>
<div class="markdown"><?= Helper\markdown($comment['comment']) ?></div>
</div>

View File

@ -1,12 +1,19 @@
<p class="activity-title">
<?= e('%s created a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?>
</p>
<p class="activity-description">
<em><?= Helper\escape($task_title) ?></em><br/>
<p><?= Helper\escape($subtask_title) ?> <strong>(<?= Helper\in_list($subtask_status, $subtask_status_list) ?>)</strong></p>
<?php if ($subtask_assignee): ?>
<p><?= t('Assigned to %s with an estimate of %s/%sh', $subtask_assignee, $subtask_time_spent, $subtask_time_estimated) ?></p>
<?php else: ?>
<p><?= t('Not assigned, estimate of %sh', $subtask_time_estimated) ?></p>
<?php endif ?>
</p>
<div class="activity-description">
<p><em><?= Helper\escape($task['title']) ?></em></p>
<ul>
<li>
<?= Helper\escape($subtask['title']) ?> (<strong><?= Helper\escape($subtask['status_name']) ?></strong>)
</li>
<li>
<?php if ($subtask['username']): ?>
<?= t('Assigned to %s with an estimate of %s/%sh', $subtask['name'] ?: $subtask['username'], $subtask['time_spent'], $subtask['time_estimated']) ?>
<?php else: ?>
<?= t('Not assigned, estimate of %sh', $subtask['time_estimated']) ?>
<?php endif ?>
</li>
</ul>
</div>

View File

@ -1,12 +1,19 @@
<p class="activity-title">
<?= e('%s updated a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?>
</p>
<p class="activity-description">
<em><?= Helper\escape($task_title) ?></em><br/>
<p><?= Helper\escape($subtask_title) ?> <strong>(<?= Helper\in_list($subtask_status, $subtask_status_list) ?>)</strong></p>
<?php if ($subtask_assignee): ?>
<p><?= t('Assigned to %s with an estimate of %s/%sh', $subtask_assignee, $subtask_time_spent, $subtask_time_estimated) ?></p>
<?php else: ?>
<p><?= t('Not assigned, estimate of %sh', $subtask_time_estimated) ?></p>
<?php endif ?>
</p>
<div class="activity-description">
<p><em><?= Helper\escape($task['title']) ?></em></p>
<ul>
<li>
<?= Helper\escape($subtask['title']) ?> (<strong><?= Helper\escape($subtask['status_name']) ?></strong>)
</li>
<li>
<?php if ($subtask['username']): ?>
<?= t('Assigned to %s with an estimate of %s/%sh', $subtask['name'] ?: $subtask['username'], $subtask['time_spent'], $subtask['time_estimated']) ?>
<?php else: ?>
<?= t('Not assigned, estimate of %sh', $subtask['time_estimated']) ?>
<?php endif ?>
</li>
</ul>
</div>

View File

@ -1,6 +1,12 @@
<p class="activity-title">
<?= e('%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?>
<?= e(
'%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s',
Helper\escape($author),
$task_id,
$task_id,
Helper\escape($task['assignee_name'] ?: $task['assignee_username'])
) ?>
</p>
<p class="activity-description">
<em><?= Helper\escape($task_title) ?></em>
<em><?= Helper\escape($task['title']) ?></em>
</p>

View File

@ -2,5 +2,5 @@
<?= e('%s closed the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?>
</p>
<p class="activity-description">
<em><?= Helper\escape($task_title) ?></em>
<em><?= Helper\escape($task['title']) ?></em>
</p>

View File

@ -2,5 +2,5 @@
<?= e('%s created the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?>
</p>
<p class="activity-description">
<em><?= Helper\escape($task_title) ?></em>
<em><?= Helper\escape($task['title']) ?></em>
</p>

View File

@ -1,6 +1,6 @@
<p class="activity-title">
<?= e('%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the column "%s"', Helper\escape($author), $task_id, $task_id, Helper\escape($task_column_name)) ?>
<?= e('%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the column "%s"', Helper\escape($author), $task_id, $task_id, Helper\escape($task['column_title'])) ?>
</p>
<p class="activity-description">
<em><?= Helper\escape($task_title) ?></em>
<em><?= Helper\escape($task['title']) ?></em>
</p>

View File

@ -1,6 +1,6 @@
<p class="activity-title">
<?= e('%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the position #%d in the column "%s"', Helper\escape($author), $task_id, $task_id, $task_position, Helper\escape($task_column_name)) ?>
<?= e('%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the position #%d in the column "%s"', Helper\escape($author), $task_id, $task_id, $task['position'], Helper\escape($task['column_title'])) ?>
</p>
<p class="activity-description">
<em><?= Helper\escape($task_title) ?></em>
<em><?= Helper\escape($task['title']) ?></em>
</p>

View File

@ -2,5 +2,5 @@
<?= e('%s open the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?>
</p>
<p class="activity-description">
<em><?= Helper\escape($task_title) ?></em>
<em><?= Helper\escape($task['title']) ?></em>
</p>

View File

@ -2,5 +2,5 @@
<?= e('%s updated the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>', Helper\escape($author), $task_id, $task_id) ?>
</p>
<p class="activity-description">
<em><?= Helper\escape($task_title) ?></em>
<em><?= Helper\escape($task['title']) ?></em>
</p>

View File

@ -2,10 +2,10 @@
<div class="page-header">
<h2><?= t('%s\'s activity', $project['name']) ?></h2>
<ul>
<li><a href="?controller=board&amp;action=show&amp;project_id=<?= $project['id'] ?>"><?= t('Back to the board') ?></a></li>
<li><a href="?controller=project&amp;action=search&amp;project_id=<?= $project['id'] ?>"><?= t('Search') ?></a></li>
<li><a href="?controller=project&amp;action=tasks&amp;project_id=<?= $project['id'] ?>"><?= t('Completed tasks') ?></a></li>
<li><a href="?controller=project&amp;action=index"><?= t('List of projects') ?></a></li>
<li><?= Helper\a(t('Back to the board'), 'board', 'show', array('project_id' => $project['id'])) ?></li>
<li><?= Helper\a(t('Search'), 'project', 'search', array('project_id' => $project['id'])) ?></li>
<li><?= Helper\a(t('Completed tasks'), 'project', 'tasks', array('project_id' => $project['id'])) ?></li>
<li><?= Helper\a(t('List of projects'), 'project', 'index') ?></li>
</ul>
</div>
<section>
@ -14,17 +14,17 @@
<?php else: ?>
<?php if ($project['is_public']): ?>
<p class="pull-right"><i class="fa fa-rss-square"></i> <a href="?controller=project&amp;action=feed&amp;token=<?= $project['token'] ?>" target="_blank"><?= t('RSS feed') ?></a></p>
<p class="pull-right"><i class="fa fa-rss-square"></i> <?= Helper\a(t('RSS feed'), 'project', 'feed', array('token' => $project['token'])) ?></p>
<?php endif ?>
<?php foreach ($events as $event): ?>
<div class="activity-event">
<p class="activity-datetime">
<?php if ($event['event_type'] === 'task'): ?>
<?php if (Helper\contains($event['event_name'], 'task')): ?>
<i class="fa fa-newspaper-o"></i>
<?php elseif ($event['event_type'] === 'subtask'): ?>
<?php elseif (Helper\contains($event['event_name'], 'subtask')): ?>
<i class="fa fa-tasks"></i>
<?php elseif ($event['event_type'] === 'comment'): ?>
<?php elseif (Helper\contains($event['event_name'], 'comment')): ?>
<i class="fa fa-comments-o"></i>
<?php endif ?>
&nbsp;<?= dt('%B %e, %Y at %k:%M %p', $event['date_creation']) ?>

View File

@ -2,7 +2,7 @@
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
<title><?= t('%s\'s activity', $project['name']) ?></title>
<link rel="alternate" type="text/html" href="<?= Helper\get_current_base_url() ?>"/>
<link rel="self" type="application/atom+xml" href="<?= Helper\get_current_base_url() ?>?controller=project&amp;action=feed&amp;token=<?= $project['token'] ?>"/>
<link rel="self" type="application/atom+xml" href="<?= Helper\get_current_base_url().Helper\u('project', 'feed', array('token' => $project['token'])) ?>"/>
<updated><?= date(DATE_ATOM) ?></updated>
<id><?= Helper\get_current_base_url() ?></id>
<icon><?= Helper\get_current_base_url() ?>assets/img/favicon.png</icon>
@ -10,7 +10,7 @@
<?php foreach ($events as $e): ?>
<entry>
<title type="text"><?= $e['event_title'] ?></title>
<link rel="alternate" href="<?= Helper\get_current_base_url().'?controller=task&amp;action=show&amp;task_id='.$e['task_id'] ?>"/>
<link rel="alternate" href="<?= Helper\get_current_base_url().Helper\u('task', 'show', array('task_id' => $e['task_id'])) ?>"/>
<id><?= $e['id'].'-'.$e['event_name'].'-'.$e['task_id'].'-'.$e['date_creation'] ?></id>
<published><?= date(DATE_ATOM, $e['date_creation']) ?></published>
<updated><?= date(DATE_ATOM, $e['date_creation']) ?></updated>

View File

@ -2,7 +2,7 @@
<?= Helper\template('task_details', array('task' => $task, 'project' => $project)) ?>
<p class="align-right"><?= Helper\a(t('Back to the board'), 'board', 'readonly', array('token' => $project['token'])) ?></p>
<p class="pull-right"><?= Helper\a(t('Back to the board'), 'board', 'readonly', array('token' => $project['token'])) ?></p>
<?= Helper\template('task_show_description', array('task' => $task)) ?>

View File

@ -26,10 +26,6 @@ body {
text-rendering: optimizeLegibility;
}
.align-right {
text-align: right;
}
/* links */
a {
color: #3366CC;
@ -1108,6 +1104,21 @@ tr td.task-orange,
padding-top: 5px;
}
.activity-description ul {
margin-top: 10px;
}
.activity-description li {
margin-left: 40px;
list-style-type: circle;
color: #555;
}
.activity-description .markdown {
margin-top: 10px;
color: #555;
}
/* confirmation box */
.confirm {
max-width: 700px;

View File

@ -3,26 +3,28 @@
require_once __DIR__.'/Base.php';
use Model\Task;
use Model\TaskHistory;
use Model\TaskFinder;
use Model\ProjectActivity;
use Model\Project;
class TaskHistoryTest extends Base
class ProjectActivityTest extends Base
{
public function testCreation()
{
$e = new TaskHistory($this->registry);
$e = new ProjectActivity($this->registry);
$t = new Task($this->registry);
$tf = new TaskFinder($this->registry);
$p = new Project($this->registry);
$this->assertEquals(1, $p->create(array('name' => 'Project #1')));
$this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1)));
$this->assertEquals(2, $t->create(array('title' => 'Task #2', 'project_id' => 1)));
$this->assertTrue($e->create(1, 1, 1, Task::EVENT_CLOSE));
$this->assertTrue($e->create(1, 2, 1, Task::EVENT_UPDATE));
$this->assertFalse($e->create(1, 1, 0, Task::EVENT_OPEN));
$this->assertTrue($e->createEvent(1, 1, 1, Task::EVENT_CLOSE, array('task' => $tf->getbyId(1))));
$this->assertTrue($e->createEvent(1, 2, 1, Task::EVENT_UPDATE, array('task' => $tf->getById(2))));
$this->assertFalse($e->createEvent(1, 1, 0, Task::EVENT_OPEN, array('task' => $tf->getbyId(1))));
$events = $e->getAllByProjectId(1);
$events = $e->getAll(1);
$this->assertNotEmpty($events);
$this->assertTrue(is_array($events));
@ -34,8 +36,9 @@ class TaskHistoryTest extends Base
public function testFetchAllContent()
{
$e = new TaskHistory($this->registry);
$e = new ProjectActivity($this->registry);
$t = new Task($this->registry);
$tf = new TaskFinder($this->registry);
$p = new Project($this->registry);
$this->assertEquals(1, $p->create(array('name' => 'Project #1')));
@ -44,10 +47,10 @@ class TaskHistoryTest extends Base
$nb_events = 80;
for ($i = 0; $i < $nb_events; $i++) {
$this->assertTrue($e->create(1, 1, 1, Task::EVENT_UPDATE));
$this->assertTrue($e->createEvent(1, 1, 1, Task::EVENT_UPDATE, array('task' => $tf->getbyId(1))));
}
$events = $e->getAllContentByProjectId(1);
$events = $e->getAll(1);
$this->assertNotEmpty($events);
$this->assertTrue(is_array($events));
@ -59,8 +62,9 @@ class TaskHistoryTest extends Base
public function testCleanup()
{
$e = new TaskHistory($this->registry);
$e = new ProjectActivity($this->registry);
$t = new Task($this->registry);
$tf = new TaskFinder($this->registry);
$p = new Project($this->registry);
$this->assertEquals(1, $p->create(array('name' => 'Project #1')));
@ -70,13 +74,13 @@ class TaskHistoryTest extends Base
$nb_events = 100;
for ($i = 0; $i < $nb_events; $i++) {
$this->assertTrue($e->create(1, 1, 1, Task::EVENT_CLOSE));
$this->assertTrue($e->createEvent(1, 1, 1, Task::EVENT_CLOSE, array('task' => $tf->getbyId(1))));
}
$this->assertEquals($nb_events, $this->registry->shared('db')->table('task_has_events')->count());
$this->assertEquals($nb_events, $this->registry->shared('db')->table('project_activities')->count());
$e->cleanup($max);
$events = $e->getAllByProjectId(1);
$events = $e->getAll(1);
$this->assertNotEmpty($events);
$this->assertTrue(is_array($events));
@ -87,12 +91,12 @@ class TaskHistoryTest extends Base
// Cleanup during task creation
$nb_events = TaskHistory::MAX_EVENTS + 10;
$nb_events = ProjectActivity::MAX_EVENTS + 10;
for ($i = 0; $i < $nb_events; $i++) {
$this->assertTrue($e->create(1, 1, 1, Task::EVENT_CLOSE));
$this->assertTrue($e->createEvent(1, 1, 1, Task::EVENT_CLOSE, array('task' => $tf->getbyId(1))));
}
$this->assertEquals(TaskHistory::MAX_EVENTS, $this->registry->shared('db')->table('task_has_events')->count());
$this->assertEquals(ProjectActivity::MAX_EVENTS, $this->registry->shared('db')->table('project_activities')->count());
}
}

View File

@ -63,7 +63,7 @@ class ProjectTest extends Base
$this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1)));
$this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_CREATE));
$this->assertEquals('Event\ProjectModificationDate', $this->registry->shared('event')->getLastListenerExecuted());
$this->assertEquals('Event\ProjectModificationDateListener', $this->registry->shared('event')->getLastListenerExecuted());
$project = $p->getById(1);
$this->assertNotEmpty($project);