summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrédéric Guillot <fguillot@users.noreply.github.com>2014-03-09 23:21:23 -0400
committerFrédéric Guillot <fguillot@users.noreply.github.com>2014-03-09 23:21:23 -0400
commit7749b8ed569f6d27b0bb2ed4c2040e8b61ed4422 (patch)
treeee101992e87d740bdf0362e35ea040c866986f5a
parent7bd4697dfca41a21f5857f83d6b29108fafb9a1e (diff)
Automatic actions
-rw-r--r--actions/.htaccess (renamed from lib/.htaccess)0
-rw-r--r--actions/Base.php55
-rw-r--r--actions/task_assign_current_user.php45
-rw-r--r--actions/task_assign_specific_user.php45
-rw-r--r--actions/task_close.php39
-rw-r--r--actions/task_duplicate_another_project.php43
-rw-r--r--assets/js/board.js17
-rw-r--r--common.php90
-rw-r--r--controllers/action.php140
-rw-r--r--controllers/app.php2
-rw-r--r--controllers/base.php51
-rw-r--r--controllers/board.php2
-rw-r--r--controllers/config.php2
-rw-r--r--controllers/project.php2
-rw-r--r--controllers/task.php2
-rw-r--r--controllers/user.php2
-rw-r--r--core/.htaccess1
-rw-r--r--core/event.php124
-rw-r--r--core/helper.php (renamed from lib/helper.php)5
-rw-r--r--core/registry.php79
-rw-r--r--core/request.php (renamed from lib/request.php)2
-rw-r--r--core/response.php (renamed from lib/response.php)2
-rw-r--r--core/router.php (renamed from lib/router.php)21
-rw-r--r--core/session.php (renamed from lib/session.php)2
-rw-r--r--core/template.php (renamed from lib/template.php)2
-rw-r--r--core/translator.php (renamed from lib/translator.php)0
-rw-r--r--index.php17
-rw-r--r--locales/fr_FR/translations.php31
-rw-r--r--locales/pl_PL/translations.php29
-rw-r--r--models/acl.php2
-rw-r--r--models/action.php247
-rw-r--r--models/base.php48
-rw-r--r--models/board.php10
-rw-r--r--models/comment.php6
-rw-r--r--models/config.php2
-rw-r--r--models/project.php37
-rw-r--r--models/schema.php23
-rw-r--r--models/task.php175
-rw-r--r--models/user.php2
-rw-r--r--templates/action_index.php73
-rw-r--r--templates/action_params.php38
-rw-r--r--templates/action_remove.php16
-rw-r--r--templates/config_index.php2
-rw-r--r--templates/project_index.php3
-rw-r--r--tests/AclTest.php18
-rw-r--r--tests/ActionTest.php164
-rw-r--r--tests/Base.php37
-rw-r--r--tests/ProjectTest.php46
-rw-r--r--tests/TaskTest.php67
49 files changed, 1678 insertions, 190 deletions
diff --git a/lib/.htaccess b/actions/.htaccess
index 14249c50..14249c50 100644
--- a/lib/.htaccess
+++ b/actions/.htaccess
diff --git a/actions/Base.php b/actions/Base.php
new file mode 100644
index 00000000..bb9b8bc1
--- /dev/null
+++ b/actions/Base.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Action;
+
+abstract class Base implements \Core\Listener
+{
+ private $project_id = 0;
+ private $params = array();
+
+ abstract public function doAction(array $data);
+ abstract public function getActionRequiredParameters();
+ abstract public function getEventRequiredParameters();
+
+ public function __construct($project_id)
+ {
+ $this->project_id = $project_id;
+ }
+
+ public function setParam($name, $value)
+ {
+ $this->params[$name] = $value;
+ }
+
+ public function getParam($name, $default_value = null)
+ {
+ return isset($this->params[$name]) ? $this->params[$name] : $default_value;
+ }
+
+ public function isExecutable(array $data)
+ {
+ if (isset($data['project_id']) && $data['project_id'] == $this->project_id && $this->hasRequiredParameters($data)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public function hasRequiredParameters(array $data)
+ {
+ foreach ($this->getEventRequiredParameters() as $parameter) {
+ if (! isset($data[$parameter])) return false;
+ }
+
+ return true;
+ }
+
+ public function execute(array $data)
+ {
+ if ($this->isExecutable($data)) {
+ return $this->doAction($data);
+ }
+
+ return false;
+ }
+}
diff --git a/actions/task_assign_current_user.php b/actions/task_assign_current_user.php
new file mode 100644
index 00000000..5a8edd01
--- /dev/null
+++ b/actions/task_assign_current_user.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Action;
+
+require_once __DIR__.'/base.php';
+
+class TaskAssignCurrentUser extends Base
+{
+ public function __construct($project_id, \Model\Task $task, \Model\Acl $acl)
+ {
+ parent::__construct($project_id);
+ $this->task = $task;
+ $this->acl = $acl;
+ }
+
+ public function getActionRequiredParameters()
+ {
+ return array(
+ 'column_id' => t('Column'),
+ );
+ }
+
+ public function getEventRequiredParameters()
+ {
+ return array(
+ 'task_id',
+ 'column_id',
+ );
+ }
+
+ public function doAction(array $data)
+ {
+ if ($data['column_id'] == $this->getParam('column_id')) {
+
+ $this->task->update(array(
+ 'id' => $data['task_id'],
+ 'owner_id' => $this->acl->getUserId(),
+ ));
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/actions/task_assign_specific_user.php b/actions/task_assign_specific_user.php
new file mode 100644
index 00000000..8cafde6d
--- /dev/null
+++ b/actions/task_assign_specific_user.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Action;
+
+require_once __DIR__.'/base.php';
+
+class TaskAssignSpecificUser extends Base
+{
+ public function __construct($project_id, \Model\Task $task)
+ {
+ parent::__construct($project_id);
+ $this->task = $task;
+ }
+
+ public function getActionRequiredParameters()
+ {
+ return array(
+ 'column_id' => t('Column'),
+ 'user_id' => t('Assignee'),
+ );
+ }
+
+ public function getEventRequiredParameters()
+ {
+ return array(
+ 'task_id',
+ 'column_id',
+ );
+ }
+
+ public function doAction(array $data)
+ {
+ if ($data['column_id'] == $this->getParam('column_id')) {
+
+ $this->task->update(array(
+ 'id' => $data['task_id'],
+ 'owner_id' => $this->getParam('user_id'),
+ ));
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/actions/task_close.php b/actions/task_close.php
new file mode 100644
index 00000000..4ac579c4
--- /dev/null
+++ b/actions/task_close.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Action;
+
+require_once __DIR__.'/base.php';
+
+class TaskClose extends Base
+{
+ public function __construct($project_id, \Model\Task $task)
+ {
+ parent::__construct($project_id);
+ $this->task = $task;
+ }
+
+ public function getActionRequiredParameters()
+ {
+ return array(
+ 'column_id' => t('Column'),
+ );
+ }
+
+ public function getEventRequiredParameters()
+ {
+ return array(
+ 'task_id',
+ 'column_id',
+ );
+ }
+
+ public function doAction(array $data)
+ {
+ if ($data['column_id'] == $this->getParam('column_id')) {
+ $this->task->close($data['task_id']);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/actions/task_duplicate_another_project.php b/actions/task_duplicate_another_project.php
new file mode 100644
index 00000000..31089c67
--- /dev/null
+++ b/actions/task_duplicate_another_project.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Action;
+
+require_once __DIR__.'/base.php';
+
+class TaskDuplicateAnotherProject extends Base
+{
+ public function __construct($project_id, \Model\Task $task)
+ {
+ parent::__construct($project_id);
+ $this->task = $task;
+ }
+
+ public function getActionRequiredParameters()
+ {
+ return array(
+ 'column_id' => t('Column'),
+ 'project_id' => t('Project'),
+ );
+ }
+
+ public function getEventRequiredParameters()
+ {
+ return array(
+ 'task_id',
+ 'column_id',
+ 'project_id',
+ );
+ }
+
+ public function doAction(array $data)
+ {
+ if ($data['column_id'] == $this->getParam('column_id') && $data['project_id'] != $this->getParam('project_id')) {
+
+ $this->task->duplicateToAnotherProject($data['task_id'], $this->getParam('project_id'));
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/assets/js/board.js b/assets/js/board.js
index 33e47053..501c39c3 100644
--- a/assets/js/board.js
+++ b/assets/js/board.js
@@ -140,6 +140,23 @@
var xhr = new XMLHttpRequest();
xhr.open("POST", "?controller=board&action=save&project_id=" + projectId, true);
+
+ xhr.onreadystatechange = function(response) {
+
+ if (this.readyState == this.DONE) {
+ try {
+ var response = JSON.parse(this.responseText);
+
+ if (response.result == true) {
+
+ // TODO: don't refresh the whole page!
+ window.location = "?controller=board&action=show&project_id=" + projectId;
+ }
+ }
+ catch (e) {}
+ }
+ };
+
xhr.send(JSON.stringify(data));
}
diff --git a/common.php b/common.php
new file mode 100644
index 00000000..0c163b5d
--- /dev/null
+++ b/common.php
@@ -0,0 +1,90 @@
+<?php
+
+require __DIR__.'/core/registry.php';
+require __DIR__.'/core/helper.php';
+require __DIR__.'/core/translator.php';
+
+$registry = new Core\Registry;
+
+$registry->db_version = 10;
+
+$registry->db = function() use ($registry) {
+ require __DIR__.'/vendor/PicoDb/Database.php';
+ require __DIR__.'/models/schema.php';
+
+ $db = new \PicoDb\Database(array(
+ 'driver' => 'sqlite',
+ 'filename' => DB_FILENAME
+ ));
+
+ if ($db->schema()->check($registry->db_version)) {
+ return $db;
+ }
+ else {
+ die('Unable to migrate database schema!');
+ }
+};
+
+$registry->event = function() use ($registry) {
+ require __DIR__.'/core/event.php';
+ return new \Core\Event;
+};
+
+$registry->action = function() use ($registry) {
+ require_once __DIR__.'/models/action.php';
+ return new \Model\Action($registry->shared('db'), $registry->shared('event'));
+};
+
+$registry->config = function() use ($registry) {
+ require_once __DIR__.'/models/config.php';
+ return new \Model\Config($registry->shared('db'), $registry->shared('event'));
+};
+
+$registry->acl = function() use ($registry) {
+ require_once __DIR__.'/models/acl.php';
+ return new \Model\Acl($registry->shared('db'), $registry->shared('event'));
+};
+
+$registry->user = function() use ($registry) {
+ require_once __DIR__.'/models/user.php';
+ return new \Model\User($registry->shared('db'), $registry->shared('event'));
+};
+
+$registry->comment = function() use ($registry) {
+ require_once __DIR__.'/models/comment.php';
+ return new \Model\Comment($registry->shared('db'), $registry->shared('event'));
+};
+
+$registry->task = function() use ($registry) {
+ require_once __DIR__.'/models/task.php';
+ return new \Model\Task($registry->shared('db'), $registry->shared('event'));
+};
+
+$registry->board = function() use ($registry) {
+ require_once __DIR__.'/models/board.php';
+ return new \Model\Board($registry->shared('db'), $registry->shared('event'));
+};
+
+$registry->project = function() use ($registry) {
+ require_once __DIR__.'/models/project.php';
+ return new \Model\Project($registry->shared('db'), $registry->shared('event'));
+};
+
+$registry->action = function() use ($registry) {
+ require_once __DIR__.'/models/action.php';
+ return new \Model\Action($registry->shared('db'), $registry->shared('event'));
+};
+
+if (file_exists('config.php')) require 'config.php';
+
+// Auto-refresh frequency in seconds for the public board view
+defined('AUTO_REFRESH_DURATION') or define('AUTO_REFRESH_DURATION', 60);
+
+// Custom session save path
+defined('SESSION_SAVE_PATH') or define('SESSION_SAVE_PATH', '');
+
+// Database filename
+defined('DB_FILENAME') or define('DB_FILENAME', 'data/db.sqlite');
+
+// Application version
+defined('APP_VERSION') or define('APP_VERSION', 'master');
diff --git a/controllers/action.php b/controllers/action.php
new file mode 100644
index 00000000..3ee44364
--- /dev/null
+++ b/controllers/action.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Controller;
+
+require_once __DIR__.'/Base.php';
+
+/**
+ * Automatic actions management
+ *
+ * @package controllers
+ * @author Frederic Guillot
+ */
+class Action extends Base
+{
+ /**
+ * List of automatic actions for a given project
+ *
+ * @access public
+ */
+ public function index()
+ {
+ $project_id = $this->request->getIntegerParam('project_id');
+ $project = $this->project->getById($project_id);
+
+ if (! $project) {
+ $this->session->flashError(t('Project not found.'));
+ $this->response->redirect('?controller=project');
+ }
+
+ $this->response->html($this->template->layout('action_index', array(
+ 'values' => array('project_id' => $project['id']),
+ 'project' => $project,
+ 'actions' => $this->action->getAllByProject($project['id']),
+ 'available_actions' => $this->action->getAvailableActions(),
+ 'available_events' => $this->action->getAvailableEvents(),
+ 'available_params' => $this->action->getAllActionParameters(),
+ 'columns_list' => $this->board->getColumnsList($project['id']),
+ 'users_list' => $this->project->getUsersList($project['id'], false),
+ 'projects_list' => $this->project->getList(false),
+ 'menu' => 'projects',
+ 'title' => t('Automatic actions')
+ )));
+ }
+
+ /**
+ * Define action parameters (step 2)
+ *
+ * @access public
+ */
+ public function params()
+ {
+ $project_id = $this->request->getIntegerParam('project_id');
+ $project = $this->project->getById($project_id);
+
+ if (! $project) {
+ $this->session->flashError(t('Project not found.'));
+ $this->response->redirect('?controller=project');
+ }
+
+ $values = $this->request->getValues();
+ $action = $this->action->load($values['action_name'], $values['project_id']);
+
+ $this->response->html($this->template->layout('action_params', array(
+ 'values' => $values,
+ 'action_params' => $action->getActionRequiredParameters(),
+ 'columns_list' => $this->board->getColumnsList($project['id']),
+ 'users_list' => $this->project->getUsersList($project['id'], false),
+ 'projects_list' => $this->project->getList(false),
+ 'project' => $project,
+ 'menu' => 'projects',
+ 'title' => t('Automatic actions')
+ )));
+ }
+
+ /**
+ * Create a new action (last step)
+ *
+ * @access public
+ */
+ public function create()
+ {
+ $project_id = $this->request->getIntegerParam('project_id');
+ $project = $this->project->getById($project_id);
+
+ if (! $project) {
+ $this->session->flashError(t('Project not found.'));
+ $this->response->redirect('?controller=project');
+ }
+
+ $values = $this->request->getValues();
+
+ list($valid, $errors) = $this->action->validateCreation($values);
+
+ if ($valid) {
+
+ if ($this->action->create($values)) {
+ $this->session->flash(t('Your automatic action have been created successfully.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to create your automatic action.'));
+ }
+ }
+
+ $this->response->redirect('?controller=action&action=index&project_id='.$project['id']);
+ }
+
+ /**
+ * Confirmation dialog before removing an action
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $this->response->html($this->template->layout('action_remove', array(
+ 'action' => $this->action->getById($this->request->getIntegerParam('action_id')),
+ 'available_events' => $this->action->getAvailableEvents(),
+ 'available_actions' => $this->action->getAvailableActions(),
+ 'menu' => 'projects',
+ 'title' => t('Remove an action')
+ )));
+ }
+
+ /**
+ * Remove an action
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $action = $this->action->getById($this->request->getIntegerParam('action_id'));
+
+ if ($action && $this->action->remove($action['id'])) {
+ $this->session->flash(t('Action removed successfully.'));
+ } else {
+ $this->session->flashError(t('Unable to remove this action.'));
+ }
+
+ $this->response->redirect('?controller=action&action=index&project_id='.$action['project_id']);
+ }
+}
diff --git a/controllers/app.php b/controllers/app.php
index 981abbbe..633433fc 100644
--- a/controllers/app.php
+++ b/controllers/app.php
@@ -2,6 +2,8 @@
namespace Controller;
+require_once __DIR__.'/Base.php';
+
class App extends Base
{
public function index()
diff --git a/controllers/base.php b/controllers/base.php
index 6dc9c0be..dd7c0642 100644
--- a/controllers/base.php
+++ b/controllers/base.php
@@ -2,48 +2,18 @@
namespace Controller;
-require __DIR__.'/../lib/request.php';
-require __DIR__.'/../lib/response.php';
-require __DIR__.'/../lib/session.php';
-require __DIR__.'/../lib/template.php';
-require __DIR__.'/../lib/helper.php';
-require __DIR__.'/../lib/translator.php';
-require __DIR__.'/../models/base.php';
-require __DIR__.'/../models/acl.php';
-require __DIR__.'/../models/config.php';
-require __DIR__.'/../models/user.php';
-require __DIR__.'/../models/project.php';
-require __DIR__.'/../models/task.php';
-require __DIR__.'/../models/board.php';
-require __DIR__.'/../models/comment.php';
-
abstract class Base
{
- protected $request;
- protected $response;
- protected $session;
- protected $template;
- protected $user;
- protected $project;
- protected $task;
- protected $board;
- protected $config;
- protected $acl;
- protected $comment;
-
- public function __construct()
+ public function __construct(\Core\Registry $registry)
{
- $this->request = new \Request;
- $this->response = new \Response;
- $this->session = new \Session;
- $this->template = new \Template;
- $this->config = new \Model\Config;
- $this->user = new \Model\User;
- $this->project = new \Model\Project;
- $this->task = new \Model\Task;
- $this->board = new \Model\Board;
- $this->acl = new \Model\Acl;
- $this->comment = new \Model\Comment;
+ $this->acl = $registry->acl;
+ $this->action = $registry->action;
+ $this->board = $registry->board;
+ $this->config = $registry->config;
+ $this->project = $registry->project;
+ $this->task = $registry->task;
+ $this->user = $registry->user;
+ $this->comment = $registry->comment;
}
public function beforeAction($controller, $action)
@@ -74,6 +44,9 @@ abstract class Base
if (! $this->acl->isPageAccessAllowed($controller, $action)) {
$this->response->redirect('?controller=user&action=forbidden');
}
+
+ // Attach events for automatic actions
+ $this->action->attachEvents();
}
public function checkProjectPermissions($project_id)
diff --git a/controllers/board.php b/controllers/board.php
index 13714b3c..9cdc4386 100644
--- a/controllers/board.php
+++ b/controllers/board.php
@@ -2,6 +2,8 @@
namespace Controller;
+require_once __DIR__.'/Base.php';
+
class Board extends Base
{
// Change a task assignee directly from the board
diff --git a/controllers/config.php b/controllers/config.php
index 064fa06d..c4880b4a 100644
--- a/controllers/config.php
+++ b/controllers/config.php
@@ -2,6 +2,8 @@
namespace Controller;
+require_once __DIR__.'/Base.php';
+
class Config extends Base
{
// Settings page
diff --git a/controllers/project.php b/controllers/project.php
index 8d8584bc..8b232e94 100644
--- a/controllers/project.php
+++ b/controllers/project.php
@@ -2,6 +2,8 @@
namespace Controller;
+require_once __DIR__.'/Base.php';
+
class Project extends Base
{
// Display access forbidden page
diff --git a/controllers/task.php b/controllers/task.php
index fba4d4f5..05dd935e 100644
--- a/controllers/task.php
+++ b/controllers/task.php
@@ -2,6 +2,8 @@
namespace Controller;
+require_once __DIR__.'/Base.php';
+
class Task extends Base
{
// Webhook to create a task (useful for external software)
diff --git a/controllers/user.php b/controllers/user.php
index 10d3ad21..700e5fae 100644
--- a/controllers/user.php
+++ b/controllers/user.php
@@ -2,6 +2,8 @@
namespace Controller;
+require_once __DIR__.'/Base.php';
+
class User extends Base
{
// Display access forbidden page
diff --git a/core/.htaccess b/core/.htaccess
new file mode 100644
index 00000000..14249c50
--- /dev/null
+++ b/core/.htaccess
@@ -0,0 +1 @@
+Deny from all \ No newline at end of file
diff --git a/core/event.php b/core/event.php
new file mode 100644
index 00000000..7addb41d
--- /dev/null
+++ b/core/event.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace Core;
+
+/**
+ * Event listener interface
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+interface Listener {
+ public function execute(array $data);
+}
+
+/**
+ * Event dispatcher class
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+class Event
+{
+ /**
+ * Contains all listeners
+ *
+ * @access private
+ * @var array
+ */
+ private $listeners = array();
+
+ /**
+ * The last triggered event
+ *
+ * @access private
+ * @var string
+ */
+ private $lastEvent = '';
+
+ /**
+ * Triggered events list
+ *
+ * @access private
+ * @var array
+ */
+ private $events = array();
+
+ /**
+ * Attach a listener object to an event
+ *
+ * @access public
+ * @param string $eventName Event name
+ * @param Listener $listener Object that implements the Listener interface
+ */
+ public function attach($eventName, Listener $listener)
+ {
+ if (! isset($this->listeners[$eventName])) {
+ $this->listeners[$eventName] = array();
+ }
+
+ $this->listeners[$eventName][] = $listener;
+ }
+
+ /**
+ * Trigger an event
+ *
+ * @access public
+ * @param string $eventName Event name
+ * @param array $data Event data
+ */
+ public function trigger($eventName, array $data)
+ {
+ $this->lastEvent = $eventName;
+ $this->events[] = $eventName;
+
+ if (isset($this->listeners[$eventName])) {
+ foreach ($this->listeners[$eventName] as $listener) {
+ $listener->execute($data); // TODO: keep an history of executed actions for unit test
+ }
+ }
+ }
+
+ /**
+ * Get the last fired event
+ *
+ * @access public
+ * @return string Event name
+ */
+ public function getLastTriggeredEvent()
+ {
+ return $this->lastEvent;
+ }
+
+ /**
+ * Get a list of triggered events
+ *
+ * @access public
+ * @return array
+ */
+ public function getTriggeredEvents()
+ {
+ return $this->events;
+ }
+
+ /**
+ * Check if a listener bind to an event
+ *
+ * @access public
+ * @param string $eventName Event name
+ * @param mixed $instance Instance name or object itself
+ * @return bool Yes or no
+ */
+ public function hasListener($eventName, $instance)
+ {
+ if (isset($this->listeners[$eventName])) {
+ foreach ($this->listeners[$eventName] as $listener) {
+ if ($listener instanceof $instance) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/lib/helper.php b/core/helper.php
index 50071be1..e4ad26f1 100644
--- a/lib/helper.php
+++ b/core/helper.php
@@ -89,6 +89,11 @@ function summary($value, $min_length = 5, $max_length = 120, $end = '[...]')
return $value;
}
+function contains($haystack, $needle)
+{
+ return strpos($haystack, $needle) !== false;
+}
+
function in_list($id, array $listing)
{
if (isset($listing[$id])) {
diff --git a/core/registry.php b/core/registry.php
new file mode 100644
index 00000000..f11d427c
--- /dev/null
+++ b/core/registry.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Core;
+
+/**
+ * The registry class is a dependency injection container
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+class Registry
+{
+ /**
+ * Contains all dependencies
+ *
+ * @access private
+ * @var array
+ */
+ private $container = array();
+
+ /**
+ * Contains all instances
+ *
+ * @access private
+ * @var array
+ */
+ private $instances = array();
+
+ /**
+ * Set a dependency
+ *
+ * @access public
+ * @param string $name Unique identifier for the service/parameter
+ * @param mixed $value The value of the parameter or a closure to define an object
+ */
+ public function __set($name, $value)
+ {
+ $this->container[$name] = $value;
+ }
+
+ /**
+ * Get a dependency
+ *
+ * @access public
+ * @param string $name Unique identifier for the service/parameter
+ * @return mixed The value of the parameter or an object
+ * @throws RuntimeException If the identifier is not found
+ */
+ public function __get($name)
+ {
+ if (isset($this->container[$name])) {
+
+ if (is_callable($this->container[$name])) {
+ return $this->container[$name]();
+ }
+ else {
+ return $this->container[$name];
+ }
+ }
+
+ throw new \RuntimeException('Identifier not found in the registry: '.$name);
+ }
+
+ /**
+ * Return a shared instance of a dependency
+ *
+ * @access public
+ * @param string $name Unique identifier for the service/parameter
+ * @return mixed Same object instance of the dependency
+ */
+ public function shared($name)
+ {
+ if (! isset($this->instances[$name])) {
+ $this->instances[$name] = $this->$name;
+ }
+
+ return $this->instances[$name];
+ }
+}
diff --git a/lib/request.php b/core/request.php
index 8840e7a4..b2c3e12e 100644
--- a/lib/request.php
+++ b/core/request.php
@@ -1,5 +1,7 @@
<?php
+namespace Core;
+
class Request
{
public function getStringParam($name, $default_value = '')
diff --git a/lib/response.php b/core/response.php
index ceaf32c5..4a00ed79 100644
--- a/lib/response.php
+++ b/core/response.php
@@ -1,5 +1,7 @@
<?php
+namespace Core;
+
class Response
{
public function forceDownload($filename)
diff --git a/lib/router.php b/core/router.php
index 979968d4..5a27276c 100644
--- a/lib/router.php
+++ b/core/router.php
@@ -1,12 +1,21 @@
<?php
+namespace Core;
+
+require __DIR__.'/request.php';
+require __DIR__.'/response.php';
+require __DIR__.'/session.php';
+require __DIR__.'/template.php';
+
class Router
{
private $controller = '';
private $action = '';
+ private $registry;
- public function __construct($controller = '', $action = '')
+ public function __construct(Registry $registry, $controller = '', $action = '')
{
+ $this->registry = $registry;
$this->controller = empty($_GET['controller']) ? $controller : $_GET['controller'];
$this->action = empty($_GET['action']) ? $controller : $_GET['action'];
}
@@ -16,7 +25,7 @@ class Router
return ! ctype_alpha($value) || empty($value) ? $default_value : strtolower($value);
}
- public function loadController($filename, $class, $method)
+ public function load($filename, $class, $method)
{
if (file_exists($filename)) {
@@ -24,7 +33,11 @@ class Router
if (! method_exists($class, $method)) return false;
- $instance = new $class;
+ $instance = new $class($this->registry);
+ $instance->request = new Request;
+ $instance->response = new Response;
+ $instance->session = new Session;
+ $instance->template = new Template;
$instance->beforeAction($this->controller, $this->action);
$instance->$method();
@@ -39,7 +52,7 @@ class Router
$this->controller = $this->sanitize($this->controller, 'app');
$this->action = $this->sanitize($this->action, 'index');
- if (! $this->loadController('controllers/'.$this->controller.'.php', '\Controller\\'.$this->controller, $this->action)) {
+ if (! $this->load('controllers/'.$this->controller.'.php', '\Controller\\'.$this->controller, $this->action)) {
die('Page not found!');
}
}
diff --git a/lib/session.php b/core/session.php
index 688004b3..7fe8e0c1 100644
--- a/lib/session.php
+++ b/core/session.php
@@ -1,5 +1,7 @@
<?php
+namespace Core;
+
class Session
{
const SESSION_LIFETIME = 2678400; // 31 days
diff --git a/lib/template.php b/core/template.php
index 09f9aa29..ad31ffb7 100644
--- a/lib/template.php
+++ b/core/template.php
@@ -1,5 +1,7 @@
<?php
+namespace Core;
+
class Template
{
const PATH = 'templates/';
diff --git a/lib/translator.php b/core/translator.php
index 75d40a23..75d40a23 100644
--- a/lib/translator.php
+++ b/core/translator.php
diff --git a/index.php b/index.php
index 8ab3dcba..bd691c10 100644
--- a/index.php
+++ b/index.php
@@ -1,19 +1,8 @@
<?php
require __DIR__.'/check_setup.php';
-require __DIR__.'/controllers/base.php';
-require __DIR__.'/lib/router.php';
+require __DIR__.'/common.php';
+require __DIR__.'/core/router.php';
-if (file_exists('config.php')) require 'config.php';
-
-// Auto-refresh frequency in seconds for the public board view
-defined('AUTO_REFRESH_DURATION') or define('AUTO_REFRESH_DURATION', 60);
-
-// Custom session save path
-defined('SESSION_SAVE_PATH') or define('SESSION_SAVE_PATH', '');
-
-// Database filename
-defined('DB_FILENAME') or define('DB_FILENAME', 'data/db.sqlite');
-
-$router = new Router;
+$router = new Core\Router($registry);
$router->execute();
diff --git a/locales/fr_FR/translations.php b/locales/fr_FR/translations.php
index 29214886..b85d05d9 100644
--- a/locales/fr_FR/translations.php
+++ b/locales/fr_FR/translations.php
@@ -99,7 +99,7 @@ return array(
'Edit a task' => 'Modifier une tâche',
'Column' => 'Colonne',
'Color' => 'Couleur',
- 'Assignee' => 'Affectation',
+ 'Assignee' => 'Personne assigné',
'Create another task' => 'Créer une autre tâche',
'New task' => 'Nouvelle tâche',
'Open a task' => 'Ouvrir une tâche',
@@ -218,4 +218,33 @@ return array(
'Invalid date' => 'Date invalide',
'Must be done before %B %e, %G' => 'Doit être fait avant le %e %B %G',
'%B %e, %G' => '%e %B %G',
+ 'Automatic actions' => 'Actions automatisées',
+ 'Your automatic action have been created successfully.' => 'Votre action automatisée a été ajouté avec succès.',
+ 'Unable to create your automatic action.' => 'Impossible de créer votre action automatisée.',
+ 'Remove an action' => 'Supprimer une action',
+ 'Unable to remove this action.' => 'Impossible de supprimer cette action',
+ 'Action removed successfully.' => 'Action supprimée avec succès.',
+ 'Automatic actions for the project "%s"' => 'Actions automatisées pour le projet « %s »',
+ 'Defined actions' => 'Actions définies',
+ 'Event name' => 'Nom de l\'événement',
+ 'Action name' => 'Nom de l\'action',
+ 'Action parameters' => 'Paramètres de l\'action',
+ 'Action' => 'Action',
+ 'Event' => 'Événement',
+ 'When the selected event occurs execute the corresponding action.' => 'Lorsque l\'événement sélectionné se déclenche, executer l\'action correspondante.',
+ 'Next step' => 'Étape suivante',
+ 'Define action parameters' => 'Définition des paramètres de l\'action',
+ 'Save this action' => 'Sauvegarder cette action',
+ 'Do you really want to remove this action: "%s"?' => 'Voulez-vous vraiment supprimer cette action « %s » ?',
+ 'Remove an automatic action' => 'Supprimer une action automatisée',
+ 'Close the task' => 'Fermer cette tâche',
+ 'Assign the task to a specific user' => 'Assigner la tâche à un utilisateur spécifique',
+ 'Assign the task to the person who does the action' => 'Assigner la tâche à la personne qui fait l\'action',
+ 'Duplicate the task to another project' => 'Dupliquer la tâche vers un autre projet',
+ 'Move a task to another column' => 'Déplacement d\'une tâche vers un autre colonne',
+ 'Move a task to another position in the same column' => 'Déplacement d\'une tâche à une autre position mais dans la même colonne',
+ 'Task modification' => 'Modification d\'une tâche',
+ 'Task creation' => 'Création d\'une tâche',
+ 'Open a closed task' => 'Ouverture d\'une tâche fermée',
+ 'Closing a task' => 'Fermeture d\'une tâche',
);
diff --git a/locales/pl_PL/translations.php b/locales/pl_PL/translations.php
index ae5ec4de..7da7c330 100644
--- a/locales/pl_PL/translations.php
+++ b/locales/pl_PL/translations.php
@@ -221,4 +221,33 @@ return array(
'Invalid date' => 'Błędna data',
'Must be done before %B %e, %G' => 'Termin do %e %B %G',
'%B %e, %G' => '%e %B %G',
+ // 'Automatic actions' => '',
+ // 'Your automatic action have been created successfully.' => '',
+ // 'Unable to create your automatic action.' => '',
+ // 'Remove an action' => '',
+ // 'Unable to remove this action.' => '',
+ // 'Action removed successfully.' => '',
+ // 'Automatic actions for the project "%s"' => '',
+ // 'Defined actions' => '',
+ // 'Event name' => '',
+ // 'Action name' => '',
+ // 'Action parameters' => '',
+ // 'Action' => '',
+ // 'Event' => '',
+ // 'When the selected event occurs execute the corresponding action.' => '',
+ // 'Next step' => '',
+ // 'Define action parameters' => '',
+ // 'Save this action' => '',
+ // 'Do you really want to remove this action: "%s"?' => '',
+ // 'Remove an automatic action' => '',
+ // 'Close the task' => '',
+ // 'Assign the task to a specific user' => '',
+ // 'Assign the task to the person who does the action' => '',
+ // 'Duplicate the task to another project' => '',
+ // 'Move a task to another column' => '',
+ // 'Move a task to another position in the same column' => '',
+ // 'Task modification' => '',
+ // 'Task creation' => '',
+ // 'Open a closed task' => '',
+ // 'Closing a task' => '',
);
diff --git a/models/acl.php b/models/acl.php
index 86db3c32..25386254 100644
--- a/models/acl.php
+++ b/models/acl.php
@@ -2,6 +2,8 @@
namespace Model;
+require_once __DIR__.'/base.php';
+
class Acl extends Base
{
// Controllers and actions allowed from outside
diff --git a/models/action.php b/models/action.php
new file mode 100644
index 00000000..9b18d461
--- /dev/null
+++ b/models/action.php
@@ -0,0 +1,247 @@
+<?php
+
+namespace Model;
+
+require_once __DIR__.'/base.php';
+require_once __DIR__.'/task.php';
+
+use \SimpleValidator\Validator;
+use \SimpleValidator\Validators;
+
+class Action extends Base
+{
+ const TABLE = 'actions';
+ const TABLE_PARAMS = 'action_has_params';
+
+ /**
+ * Return the name and description of available actions
+ *
+ * @access public
+ * @return array
+ */
+ public function getAvailableActions()
+ {
+ return array(
+ 'TaskClose' => t('Close the task'),
+ 'TaskAssignSpecificUser' => t('Assign the task to a specific user'),
+ 'TaskAssignCurrentUser' => t('Assign the task to the person who does the action'),
+ 'TaskDuplicateAnotherProject' => t('Duplicate the task to another project'),
+ );
+ }
+
+ /**
+ * Return the name and description of available actions
+ *
+ * @access public
+ * @return array
+ */
+ public function getAvailableEvents()
+ {
+ return array(
+ Task::EVENT_MOVE_COLUMN => t('Move a task to another column'),
+ Task::EVENT_MOVE_POSITION => t('Move a task to another position in the same column'),
+ Task::EVENT_UPDATE => t('Task modification'),
+ Task::EVENT_CREATE => t('Task creation'),
+ Task::EVENT_OPEN => t('Open a closed task'),
+ Task::EVENT_CLOSE => t('Closing a task'),
+ );
+ }
+
+ /**
+ * Return actions and parameters for a given project
+ *
+ * @access public
+ * @return array
+ */
+ public function getAllByProject($project_id)
+ {
+ $actions = $this->db->table(self::TABLE)->eq('project_id', $project_id)->findAll();
+
+ foreach ($actions as &$action) {
+ $action['params'] = $this->db->table(self::TABLE_PARAMS)->eq('action_id', $action['id'])->findAll();
+ }
+
+ return $actions;
+ }
+
+ /**
+ * Return all actions and parameters
+ *
+ * @access public
+ * @return array
+ */
+ public function getAll()
+ {
+ $actions = $this->db->table(self::TABLE)->findAll();
+
+ foreach ($actions as &$action) {
+ $action['params'] = $this->db->table(self::TABLE_PARAMS)->eq('action_id', $action['id'])->findAll();
+ }
+
+ return $actions;
+ }
+
+ /**
+ * Get all required action parameters for all registered actions
+ *
+ * @access public
+ * @return array All required parameters for all actions
+ */
+ public function getAllActionParameters()
+ {
+ $params = array();
+
+ foreach ($this->getAll() as $action) {
+
+ $action = $this->load($action['action_name'], $action['project_id']);
+ $params += $action->getActionRequiredParameters();
+ }
+
+ return $params;
+ }
+
+ /**
+ * Fetch an action
+ *
+ * @access public
+ * @param integer $action_id Action id
+ * @return array Action data
+ */
+ public function getById($action_id)
+ {
+ $action = $this->db->table(self::TABLE)->eq('id', $action_id)->findOne();
+ $action['params'] = $this->db->table(self::TABLE_PARAMS)->eq('action_id', $action_id)->findAll();
+
+ return $action;
+ }
+
+ /**
+ * Remove an action
+ *
+ * @access public
+ * @param integer $action_id Action id
+ * @return bool Success or not
+ */
+ public function remove($action_id)
+ {
+ return $this->db->table(self::TABLE)->eq('id', $action_id)->remove();
+ }
+
+ /**
+ * Create an action
+ *
+ * @access public
+ * @param array $values Required parameters to save an action
+ * @return bool Success or not
+ */
+ public function create(array $values)
+ {
+ $this->db->startTransaction();
+
+ $action = array(
+ 'project_id' => $values['project_id'],
+ 'event_name' => $values['event_name'],
+ 'action_name' => $values['action_name'],
+ );
+
+ if (! $this->db->table(self::TABLE)->save($action)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+
+ $action_id = $this->db->getConnection()->getLastId();
+
+ foreach ($values['params'] as $param_name => $param_value) {
+
+ $action_param = array(
+ 'action_id' => $action_id,
+ 'name' => $param_name,
+ 'value' => $param_value,
+ );
+
+ if (! $this->db->table(self::TABLE_PARAMS)->save($action_param)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ }
+
+ $this->db->closeTransaction();
+
+ return true;
+ }
+
+ /**
+ * Load all actions and attach events
+ *
+ * @access public
+ */
+ public function attachEvents()
+ {
+ foreach ($this->getAll() as $action) {
+
+ $listener = $this->load($action['action_name'], $action['project_id']);
+
+ foreach ($action['params'] as $param) {
+ $listener->setParam($param['name'], $param['value']);
+ }
+
+ $this->event->attach($action['event_name'], $listener);
+ }
+ }
+
+ /**
+ * Load an action
+ *
+ * @access public
+ * @param string $name Action class name
+ * @param integer $project_id Project id
+ * @return mixed Action Instance
+ * @throw LogicException
+ */
+ public function load($name, $project_id)
+ {
+ switch ($name) {
+ case 'TaskClose':
+ require_once __DIR__.'/../actions/task_close.php';
+ $className = '\Action\TaskClose';
+ return new $className($project_id, new Task($this->db, $this->event));
+ case 'TaskAssignCurrentUser':
+ require_once __DIR__.'/../actions/task_assign_current_user.php';
+ $className = '\Action\TaskAssignCurrentUser';
+ return new $className($project_id, new Task($this->db, $this->event), new Acl($this->db, $this->event));
+ case 'TaskAssignSpecificUser':
+ require_once __DIR__.'/../actions/task_assign_specific_user.php';
+ $className = '\Action\TaskAssignSpecificUser';
+ return new $className($project_id, new Task($this->db, $this->event));
+ case 'TaskDuplicateAnotherProject':
+ require_once __DIR__.'/../actions/task_duplicate_another_project.php';
+ $className = '\Action\TaskDuplicateAnotherProject';
+ return new $className($project_id, new Task($this->db, $this->event));
+ default:
+ throw new \LogicException('Action not found: '.$name);
+ }
+ }
+
+ /**
+ * Validate action creation
+ *
+ * @access public
+ * @param array $values Required parameters to save an action
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
+ */
+ public function validateCreation(array $values)
+ {
+ $v = new Validator($values, array(
+ new Validators\Required('project_id', t('The project id is required')),
+ new Validators\Integer('project_id', t('This value must be an integer')),
+ new Validators\Required('event_name', t('This value is required')),
+ new Validators\Required('action_name', t('This value is required')),
+ new Validators\Required('params', t('This value is required')),
+ ));
+
+ return array(
+ $v->execute(),
+ $v->getErrors()
+ );
+ }
+}
diff --git a/models/base.php b/models/base.php
index 97022576..6a1dea97 100644
--- a/models/base.php
+++ b/models/base.php
@@ -13,45 +13,22 @@ require __DIR__.'/../vendor/SimpleValidator/Validators/Equals.php';
require __DIR__.'/../vendor/SimpleValidator/Validators/AlphaNumeric.php';
require __DIR__.'/../vendor/SimpleValidator/Validators/GreaterThan.php';
require __DIR__.'/../vendor/SimpleValidator/Validators/Date.php';
-require __DIR__.'/../vendor/PicoDb/Database.php';
-require __DIR__.'/schema.php';
abstract class Base
{
- const APP_VERSION = 'master';
- const DB_VERSION = 9;
-
- private static $dbInstance = null;
protected $db;
+ protected $event;
- public function __construct()
- {
- if (self::$dbInstance === null) {
- self::$dbInstance = $this->getDatabaseInstance();
- }
-
- $this->db = self::$dbInstance;
- }
-
- public function getDatabaseInstance()
+ public function __construct(\PicoDb\Database $db, \Core\Event $event)
{
- $db = new \PicoDb\Database(array(
- 'driver' => 'sqlite',
- 'filename' => DB_FILENAME
- ));
-
- if ($db->schema()->check(self::DB_VERSION)) {
- return $db;
- }
- else {
- die('Unable to migrate database schema!');
- }
+ $this->db = $db;
+ $this->event = $event;
}
// Generate a random token from /dev/urandom or with uniqid()
public static function generateToken()
{
- if (ini_get('open_basedir') === '' and strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
+ if (ini_get('open_basedir') === '' && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
$token = file_get_contents('/dev/urandom', false, null, 0, 30);
}
else {
@@ -60,19 +37,4 @@ abstract class Base
return hash('crc32b', $token);
}
-
- public function getTimestampFromDate($value, $format)
- {
- $date = \DateTime::createFromFormat($format, $value);
-
- if ($date !== false) {
- $errors = \DateTime::getLastErrors();
- if ($errors['error_count'] === 0 && $errors['warning_count'] === 0) {
- $timestamp = $date->getTimestamp();
- return $timestamp > 0 ? $timestamp : 0;
- }
- }
-
- return 0;
- }
}
diff --git a/models/board.php b/models/board.php
index 1a5b8b81..2835f02f 100644
--- a/models/board.php
+++ b/models/board.php
@@ -2,6 +2,9 @@
namespace Model;
+require_once __DIR__.'/base.php';
+require_once __DIR__.'/task.php';
+
use \SimpleValidator\Validator;
use \SimpleValidator\Validators;
@@ -14,8 +17,8 @@ class Board extends Base
{
$this->db->startTransaction();
- $taskModel = new \Model\Task;
$results = array();
+ $taskModel = new Task($this->db, $this->event);
foreach ($values as $value) {
$results[] = $taskModel->move(
@@ -30,7 +33,7 @@ class Board extends Base
return ! in_array(false, $results, true);
}
- // Create board with default columns => must executed inside a transaction
+ // Create board with default columns => must be executed inside a transaction
public function create($project_id, array $columns)
{
$position = 0;
@@ -75,11 +78,10 @@ class Board extends Base
// Get columns and tasks for each column
public function get($project_id)
{
- $taskModel = new \Model\Task;
-
$this->db->startTransaction();
$columns = $this->getColumns($project_id);
+ $taskModel = new Task($this->db, $this->event);
foreach ($columns as &$column) {
$column['tasks'] = $taskModel->getAllByColumnId($project_id, $column['id'], array(1));
diff --git a/models/comment.php b/models/comment.php
index 9e5cf8fd..c476e693 100644
--- a/models/comment.php
+++ b/models/comment.php
@@ -2,6 +2,8 @@
namespace Model;
+require_once __DIR__.'/base.php';
+
use \SimpleValidator\Validator;
use \SimpleValidator\Validators;
@@ -17,9 +19,9 @@ class Comment extends Base
self::TABLE.'.id',
self::TABLE.'.date',
self::TABLE.'.comment',
- \Model\User::TABLE.'.username'
+ User::TABLE.'.username'
)
- ->join(\Model\User::TABLE, 'id', 'user_id')
+ ->join(User::TABLE, 'id', 'user_id')
->orderBy(self::TABLE.'.date', 'ASC')
->eq(self::TABLE.'.task_id', $task_id)
->findAll();
diff --git a/models/config.php b/models/config.php
index 8f818a3b..d2cbe785 100644
--- a/models/config.php
+++ b/models/config.php
@@ -2,6 +2,8 @@
namespace Model;
+require_once __DIR__.'/base.php';
+
use \SimpleValidator\Validator;
use \SimpleValidator\Validators;
diff --git a/models/project.php b/models/project.php
index 238a60b4..b2a54571 100644
--- a/models/project.php
+++ b/models/project.php
@@ -2,6 +2,11 @@
namespace Model;
+require_once __DIR__.'/base.php';
+require_once __DIR__.'/acl.php';
+require_once __DIR__.'/board.php';
+require_once __DIR__.'/task.php';
+
use \SimpleValidator\Validator;
use \SimpleValidator\Validators;
@@ -13,16 +18,20 @@ class Project extends Base
const INACTIVE = 0;
// Get a list of people that can by assigned for tasks
- public function getUsersList($project_id)
+ public function getUsersList($project_id, $prepend = true)
{
$allowed_users = $this->getAllowedUsers($project_id);
+ $userModel = new User($this->db, $this->event);
if (empty($allowed_users)) {
- $userModel = new User;
$allowed_users = $userModel->getList();
}
- return array(t('Unassigned')) + $allowed_users;
+ if ($prepend) {
+ return array(t('Unassigned')) + $allowed_users;
+ }
+
+ return $allowed_users;
}
// Get a list of allowed people for a project
@@ -30,7 +39,7 @@ class Project extends Base
{
return $this->db
->table(self::TABLE_USERS)
- ->join(\Model\User::TABLE, 'id', 'user_id')
+ ->join(User::TABLE, 'id', 'user_id')
->eq('project_id', $project_id)
->asc('username')
->listing('user_id', 'username');
@@ -44,7 +53,7 @@ class Project extends Base
'not_allowed' => array(),
);
- $userModel = new User;
+ $userModel = new User($this->db, $this->event);
$all_users = $userModel->getList();
$users['allowed'] = $this->getAllowedUsers($project_id);
@@ -90,7 +99,7 @@ class Project extends Base
// Check if user has admin rights
$nb_users = $this->db
- ->table(\Model\User::TABLE)
+ ->table(User::TABLE)
->eq('id', $user_id)
->eq('is_admin', 1)
->count();
@@ -133,9 +142,9 @@ class Project extends Base
->asc('name')
->findAll();
- $taskModel = new \Model\Task;
- $boardModel = new \Model\Board;
- $aclModel = new \Model\Acl;
+ $boardModel = new Board($this->db, $this->event);
+ $taskModel = new Task($this->db, $this->event);
+ $aclModel = new Acl($this->db, $this->event);
foreach ($projects as $pkey => &$project) {
@@ -163,9 +172,13 @@ class Project extends Base
return $projects;
}
- public function getList()
+ public function getList($prepend = true)
{
- return array(t('None')) + $this->db->table(self::TABLE)->asc('name')->listing('id', 'name');
+ if ($prepend) {
+ return array(t('None')) + $this->db->table(self::TABLE)->asc('name')->listing('id', 'name');
+ }
+
+ return $this->db->table(self::TABLE)->asc('name')->listing('id', 'name');
}
public function getAllByStatus($status)
@@ -218,7 +231,7 @@ class Project extends Base
$project_id = $this->db->getConnection()->getLastId();
- $boardModel = new \Model\Board;
+ $boardModel = new Board($this->db, $this->event);
$boardModel->create($project_id, array(
t('Backlog'),
t('Ready'),
diff --git a/models/schema.php b/models/schema.php
index 14604e60..621bc981 100644
--- a/models/schema.php
+++ b/models/schema.php
@@ -2,6 +2,29 @@
namespace Schema;
+function version_10($pdo)
+{
+ $pdo->exec(
+ 'CREATE TABLE actions (
+ id INTEGER PRIMARY KEY,
+ project_id INTEGER,
+ event_name TEXT,
+ action_name TEXT,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
+ )'
+ );
+
+ $pdo->exec(
+ 'CREATE TABLE action_has_params (
+ id INTEGER PRIMARY KEY,
+ action_id INTEGER,
+ name TEXT,
+ value TEXT,
+ FOREIGN KEY(action_id) REFERENCES actions(id) ON DELETE CASCADE
+ )'
+ );
+}
+
function version_9($pdo)
{
$pdo->exec("ALTER TABLE tasks ADD COLUMN date_due INTEGER");
diff --git a/models/task.php b/models/task.php
index 017c7806..fe0f6350 100644
--- a/models/task.php
+++ b/models/task.php
@@ -2,12 +2,21 @@
namespace Model;
+require_once __DIR__.'/base.php';
+require_once __DIR__.'/comment.php';
+
use \SimpleValidator\Validator;
use \SimpleValidator\Validators;
class Task extends Base
{
- const TABLE = 'tasks';
+ const TABLE = 'tasks';
+ const EVENT_MOVE_COLUMN = 'task.move.column';
+ const EVENT_MOVE_POSITION = 'task.move.position';
+ const EVENT_UPDATE = 'task.update';
+ const EVENT_CREATE = 'task.create';
+ const EVENT_CLOSE = 'task.close';
+ const EVENT_OPEN = 'task.open';
public function getColors()
{
@@ -42,13 +51,13 @@ class Task extends Base
self::TABLE.'.position',
self::TABLE.'.is_active',
self::TABLE.'.score',
- \Model\Project::TABLE.'.name AS project_name',
- \Model\Board::TABLE.'.title AS column_title',
- \Model\User::TABLE.'.username'
+ Project::TABLE.'.name AS project_name',
+ Board::TABLE.'.title AS column_title',
+ User::TABLE.'.username'
)
- ->join(\Model\Project::TABLE, 'id', 'project_id')
- ->join(\Model\Board::TABLE, 'id', 'column_id')
- ->join(\Model\User::TABLE, 'id', 'owner_id')
+ ->join(Project::TABLE, 'id', 'project_id')
+ ->join(Board::TABLE, 'id', 'column_id')
+ ->join(User::TABLE, 'id', 'owner_id')
->eq(self::TABLE.'.id', $task_id)
->findOne();
}
@@ -75,11 +84,11 @@ class Task extends Base
self::TABLE.'.position',
self::TABLE.'.is_active',
self::TABLE.'.score',
- \Model\Board::TABLE.'.title AS column_title',
- \Model\User::TABLE.'.username'
+ Board::TABLE.'.title AS column_title',
+ User::TABLE.'.username'
)
- ->join(\Model\Board::TABLE, 'id', 'column_id')
- ->join(\Model\User::TABLE, 'id', 'owner_id')
+ ->join(Board::TABLE, 'id', 'column_id')
+ ->join(User::TABLE, 'id', 'owner_id')
->eq(self::TABLE.'.project_id', $project_id)
->in('is_active', $status)
->desc('date_completed')
@@ -107,7 +116,7 @@ class Task extends Base
->asc('position')
->findAll();
- $commentModel = new Comment;
+ $commentModel = new Comment($this->db, $this->event);
foreach ($tasks as &$task) {
$task['nb_comments'] = $commentModel->count($task['id']);
@@ -126,19 +135,60 @@ class Task extends Base
->count();
}
+ public function duplicateToAnotherProject($task_id, $project_id)
+ {
+ $this->db->startTransaction();
+
+ $boardModel = new Board($this->db, $this->event);
+
+ // Get the original task
+ $task = $this->getById($task_id);
+
+ // Cleanup data
+ unset($task['id']);
+ unset($task['date_completed']);
+
+ // Assign new values
+ $task['date_creation'] = time();
+ $task['owner_id'] = 0;
+ $task['is_active'] = 1;
+ $task['column_id'] = $boardModel->getFirstColumn($project_id);
+ $task['project_id'] = $project_id;
+ $task['position'] = $this->countByColumnId($task['project_id'], $task['column_id']);
+
+ // Save task
+ if (! $this->db->table(self::TABLE)->save($task)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+
+ $task_id = $this->db->getConnection()->getLastId();
+
+ $this->db->closeTransaction();
+
+ // Trigger events
+ $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $task);
+
+ return $task_id;
+ }
+
public function create(array $values)
{
$this->db->startTransaction();
- unset($values['another_task']);
+ // Prepare data
+ if (isset($values['another_task'])) {
+ unset($values['another_task']);
+ }
- if (! empty($values['date_due'])) {
+ if (! empty($values['date_due']) && ! is_numeric($values['date_due'])) {
$values['date_due'] = $this->getTimestampFromDate($values['date_due'], t('m/d/Y')) ?: null;
}
$values['date_creation'] = time();
$values['position'] = $this->countByColumnId($values['project_id'], $values['column_id']);
+ // Save task
if (! $this->db->table(self::TABLE)->save($values)) {
$this->db->cancelTransaction();
return false;
@@ -148,38 +198,83 @@ class Task extends Base
$this->db->closeTransaction();
+ // Trigger events
+ $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $values);
+
return $task_id;
}
public function update(array $values)
{
- if (! empty($values['date_due'])) {
+ // Prepare data
+ if (! empty($values['date_due']) && ! is_numeric($values['date_due'])) {
$values['date_due'] = $this->getTimestampFromDate($values['date_due'], t('m/d/Y')) ?: null;
}
- return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values);
+ $original_task = $this->getById($values['id']);
+ $result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values);
+
+ // Trigger events
+ if ($result) {
+
+ $events = array();
+
+ if ($this->event->getLastTriggeredEvent() !== self::EVENT_UPDATE) {
+ $events[] = self::EVENT_UPDATE;
+ }
+
+ if (isset($values['column_id']) && $original_task['column_id'] != $values['column_id']) {
+ $events[] = self::EVENT_MOVE_COLUMN;
+ }
+ else if (isset($values['position']) && $original_task['position'] != $values['position']) {
+ $events[] = self::EVENT_MOVE_POSITION;
+ }
+
+ $event_data = array_merge($original_task, $values);
+ $event_data['task_id'] = $original_task['id'];
+
+ foreach ($events as $event) {
+ $this->event->trigger($event, $event_data);
+ }
+ }
+
+ return $result;
}
// Mark a task closed
public function close($task_id)
{
- return $this->db->table(self::TABLE)
- ->eq('id', $task_id)
- ->update(array(
- 'is_active' => 0,
- 'date_completed' => time()
- ));
+ $result = $this->db
+ ->table(self::TABLE)
+ ->eq('id', $task_id)
+ ->update(array(
+ 'is_active' => 0,
+ 'date_completed' => time()
+ ));
+
+ if ($result) {
+ $this->event->trigger(self::EVENT_CLOSE, array('task_id' => $task_id) + $this->getById($task_id));
+ }
+
+ return $result;
}
// Mark a task open
public function open($task_id)
{
- return $this->db->table(self::TABLE)
- ->eq('id', $task_id)
- ->update(array(
- 'is_active' => 1,
- 'date_completed' => ''
- ));
+ $result = $this->db
+ ->table(self::TABLE)
+ ->eq('id', $task_id)
+ ->update(array(
+ 'is_active' => 1,
+ 'date_completed' => ''
+ ));
+
+ if ($result) {
+ $this->event->trigger(self::EVENT_OPEN, array('task_id' => $task_id) + $this->getById($task_id));
+ }
+
+ return $result;
}
// Remove a task
@@ -191,10 +286,11 @@ class Task extends Base
// Move a task to another column or to another position
public function move($task_id, $column_id, $position)
{
- return (bool) $this->db
- ->table(self::TABLE)
- ->eq('id', $task_id)
- ->update(array('column_id' => $column_id, 'position' => $position));
+ return $this->update(array(
+ 'id' => $task_id,
+ 'column_id' => $column_id,
+ 'position' => $position,
+ ));
}
public function validateCreation(array $values)
@@ -271,4 +367,19 @@ class Task extends Base
$v->getErrors()
);
}
+
+ public function getTimestampFromDate($value, $format)
+ {
+ $date = \DateTime::createFromFormat($format, $value);
+
+ if ($date !== false) {
+ $errors = \DateTime::getLastErrors();
+ if ($errors['error_count'] === 0 && $errors['warning_count'] === 0) {
+ $timestamp = $date->getTimestamp();
+ return $timestamp > 0 ? $timestamp : 0;
+ }
+ }
+
+ return 0;
+ }
}
diff --git a/models/user.php b/models/user.php
index 394cf742..21c65b59 100644
--- a/models/user.php
+++ b/models/user.php
@@ -2,6 +2,8 @@
namespace Model;
+require_once __DIR__.'/base.php';
+
use \SimpleValidator\Validator;
use \SimpleValidator\Validators;
diff --git a/templates/action_index.php b/templates/action_index.php
new file mode 100644
index 00000000..6e62949e
--- /dev/null
+++ b/templates/action_index.php
@@ -0,0 +1,73 @@
+<section id="main">
+ <div class="page-header">
+ <h2><?= t('Automatic actions for the project "%s"', $project['name']) ?></h2>
+ <ul>
+ <li><a href="?controller=project"><?= t('All projects') ?></a></li>
+ </ul>
+ </div>
+ <section>
+
+ <?php if (! empty($actions)): ?>
+
+ <h3><?= t('Defined actions') ?></h3>
+ <table>
+ <tr>
+ <th><?= t('Event name') ?></th>
+ <th><?= t('Action name') ?></th>
+ <th><?= t('Action parameters') ?></th>
+ <th><?= t('Action') ?></th>
+ </tr>
+
+ <?php foreach ($actions as $action): ?>
+ <tr>
+ <td><?= Helper\in_list($action['event_name'], $available_events) ?></td>
+ <td><?= Helper\in_list($action['action_name'], $available_actions) ?></td>
+ <td>
+ <ul>
+ <?php foreach ($action['params'] as $param): ?>
+ <li>
+ <?= Helper\in_list($param['name'], $available_params) ?> =
+ <strong>
+ <?php if (Helper\contains($param['name'], 'column_id')): ?>
+ <?= Helper\in_list($param['value'], $columns_list) ?>
+ <?php elseif (Helper\contains($param['name'], 'user_id')): ?>
+ <?= Helper\in_list($param['value'], $users_list) ?>
+ <?php elseif (Helper\contains($param['name'], 'project_id')): ?>
+ <?= Helper\in_list($param['value'], $projects_list) ?>
+ <?php endif ?>
+ </strong>
+ </li>
+ <?php endforeach ?>
+ </ul>
+ </td>
+ <td>
+ <a href="?controller=action&amp;action=confirm&amp;action_id=<?= $action['id'] ?>"><?= t('Remove') ?></a>
+ </td>
+ </tr>
+ <?php endforeach ?>
+
+ </table>
+
+ <?php endif ?>
+
+ <h3><?= t('Add an action') ?></h3>
+ <form method="post" action="?controller=action&amp;action=params&amp;project_id=<?= $project['id'] ?>" autocomplete="off">
+
+ <?= Helper\form_hidden('project_id', $values) ?>
+
+ <?= Helper\form_label(t('Event'), 'event_name') ?>
+ <?= Helper\form_select('event_name', $available_events, $values) ?><br/>
+
+ <?= Helper\form_label(t('Action'), 'action_name') ?>
+ <?= Helper\form_select('action_name', $available_actions, $values) ?><br/>
+
+ <div class="form-help">
+ <?= t('When the selected event occurs execute the corresponding action.') ?>
+ </div>
+
+ <div class="form-actions">
+ <input type="submit" value="<?= t('Next step') ?>" class="btn btn-blue"/>
+ </div>
+ </form>
+ </section>
+</section> \ No newline at end of file
diff --git a/templates/action_params.php b/templates/action_params.php
new file mode 100644
index 00000000..e7efcda5
--- /dev/null
+++ b/templates/action_params.php
@@ -0,0 +1,38 @@
+<section id="main">
+ <div class="page-header">
+ <h2><?= t('Automatic actions for the project "%s"', $project['name']) ?></h2>
+ <ul>
+ <li><a href="?controller=project"><?= t('All projects') ?></a></li>
+ </ul>
+ </div>
+ <section>
+
+ <h3><?= t('Define action parameters') ?></h3>
+ <form method="post" action="?controller=action&amp;action=create&amp;project_id=<?= $project['id'] ?>" autocomplete="off">
+
+ <?= Helper\form_hidden('project_id', $values) ?>
+ <?= Helper\form_hidden('event_name', $values) ?>
+ <?= Helper\form_hidden('action_name', $values) ?>
+
+ <?php foreach ($action_params as $param_name => $param_desc): ?>
+
+ <?php if (Helper\contains($param_name, 'column_id')): ?>
+ <?= Helper\form_label($param_desc, $param_name) ?>
+ <?= Helper\form_select('params['.$param_name.']', $columns_list, $values) ?><br/>
+ <?php elseif (Helper\contains($param_name, 'user_id')): ?>
+ <?= Helper\form_label($param_desc, $param_name) ?>
+ <?= Helper\form_select('params['.$param_name.']', $users_list, $values) ?><br/>
+ <?php elseif (Helper\contains($param_name, 'project_id')): ?>
+ <?= Helper\form_label($param_desc, $param_name) ?>
+ <?= Helper\form_select('params['.$param_name.']', $projects_list, $values) ?><br/>
+ <?php endif ?>
+
+ <?php endforeach ?>
+
+ <div class="form-actions">
+ <input type="submit" value="<?= t('Save this action') ?>" class="btn btn-blue"/>
+ <?= t('or') ?> <a href="?controller=action&amp;action=index&amp;project_id=<?= $project['id'] ?>"><?= t('cancel') ?></a>
+ </div>
+ </form>
+ </section>
+</section> \ No newline at end of file
diff --git a/templates/action_remove.php b/templates/action_remove.php
new file mode 100644
index 00000000..b90136e8
--- /dev/null
+++ b/templates/action_remove.php
@@ -0,0 +1,16 @@
+<section id="main">
+ <div class="page-header">
+ <h2><?= t('Remove an automatic action') ?></h2>
+ </div>
+
+ <div class="confirm">
+ <p class="alert alert-info">
+ <?= t('Do you really want to remove this action: "%s"?', Helper\in_list($action['event_name'], $available_events).'/'.Helper\in_list($action['action_name'], $available_actions)) ?>
+ </p>
+
+ <div class="form-actions">
+ <a href="?controller=action&amp;action=remove&amp;action_id=<?= $action['id'] ?>" class="btn btn-red"><?= t('Yes') ?></a>
+ <?= t('or') ?> <a href="?controller=action&amp;action=index&amp;project_id=<?= $action['project_id'] ?>"><?= t('cancel') ?></a>
+ </div>
+ </div>
+</section> \ No newline at end of file
diff --git a/templates/config_index.php b/templates/config_index.php
index d94ee8b9..0af2f9c2 100644
--- a/templates/config_index.php
+++ b/templates/config_index.php
@@ -45,7 +45,7 @@
</li>
<li>
<?= t('Application version:') ?>
- <?= Model\Base::APP_VERSION ?>
+ <?= APP_VERSION ?>
</li>
</ul>
</section>
diff --git a/templates/project_index.php b/templates/project_index.php
index 7d3e1844..74d807f6 100644
--- a/templates/project_index.php
+++ b/templates/project_index.php
@@ -69,6 +69,9 @@
<a href="?controller=board&amp;action=edit&amp;project_id=<?= $project['id'] ?>"><?= t('Edit board') ?></a>
</li>
<li>
+ <a href="?controller=action&amp;action=index&amp;project_id=<?= $project['id'] ?>"><?= t('Automatic actions') ?></a>
+ </li>
+ <li>
<?php if ($project['is_active']): ?>
<a href="?controller=project&amp;action=disable&amp;project_id=<?= $project['id'] ?>"><?= t('Disable') ?></a>
<?php else: ?>
diff --git a/tests/AclTest.php b/tests/AclTest.php
index 0996a51f..566d7245 100644
--- a/tests/AclTest.php
+++ b/tests/AclTest.php
@@ -1,24 +1,18 @@
<?php
-require_once __DIR__.'/../models/base.php';
-require_once __DIR__.'/../models/acl.php';
+require_once __DIR__.'/base.php';
use Model\Acl;
-class AclTest extends PHPUnit_Framework_TestCase
+class AclTest extends Base
{
- public function setUp()
- {
- defined('DB_FILENAME') or define('DB_FILENAME', ':memory:');
- }
-
public function testAllowedAction()
{
$acl_rules = array(
'controller1' => array('action1', 'action3'),
);
- $acl = new Acl;
+ $acl = new Acl($this->db, $this->event);
$this->assertTrue($acl->isAllowedAction($acl_rules, 'controller1', 'action1'));
$this->assertTrue($acl->isAllowedAction($acl_rules, 'controller1', 'action3'));
$this->assertFalse($acl->isAllowedAction($acl_rules, 'controller1', 'action2'));
@@ -28,7 +22,7 @@ class AclTest extends PHPUnit_Framework_TestCase
public function testIsAdmin()
{
- $acl = new Acl;
+ $acl = new Acl($this->db, $this->event);
$_SESSION = array();
$this->assertFalse($acl->isAdminUser());
@@ -51,7 +45,7 @@ class AclTest extends PHPUnit_Framework_TestCase
public function testIsUser()
{
- $acl = new Acl;
+ $acl = new Acl($this->db, $this->event);
$_SESSION = array();
$this->assertFalse($acl->isRegularUser());
@@ -74,7 +68,7 @@ class AclTest extends PHPUnit_Framework_TestCase
public function testIsPageAllowed()
{
- $acl = new Acl;
+ $acl = new Acl($this->db, $this->event);
// Public access
$_SESSION = array();
diff --git a/tests/ActionTest.php b/tests/ActionTest.php
new file mode 100644
index 00000000..de7f2c9f
--- /dev/null
+++ b/tests/ActionTest.php
@@ -0,0 +1,164 @@
+<?php
+
+require_once __DIR__.'/base.php';
+
+use Model\Action;
+use Model\Project;
+use Model\Board;
+use Model\Task;
+
+class ActionTest extends Base
+{/*
+ public function testFetchActions()
+ {
+ $action = new Action($this->db, $this->event);
+ $board = new Board($this->db, $this->event);
+ $project = new Project($this->db, $this->event);
+
+ $this->assertEquals(1, $project->create(array('name' => 'unit_test')));
+
+ // We should have nothing
+ $this->assertEmpty($action->getAll());
+ $this->assertEmpty($action->getAllByProject(1));
+
+ // We create a new action
+ $this->assertTrue($action->create(array(
+ 'project_id' => 1,
+ 'event_name' => Task::EVENT_MOVE_COLUMN,
+ 'action_name' => 'TaskClose',
+ 'params' => array(
+ 'column_id' => 4,
+ )
+ )));
+
+ // We should have our action
+ $this->assertNotEmpty($action->getAll());
+ $this->assertEquals($action->getAll(), $action->getAllByProject(1));
+
+ $actions = $action->getAll();
+
+ $this->assertEquals(1, count($actions));
+ $this->assertEquals(1, $actions[0]['project_id']);
+ $this->assertEquals(Task::EVENT_MOVE_COLUMN, $actions[0]['event_name']);
+ $this->assertEquals('TaskClose', $actions[0]['action_name']);
+ $this->assertEquals('column_id', $actions[0]['params'][0]['name']);
+ $this->assertEquals(4, $actions[0]['params'][0]['value']);
+ }
+
+ public function testExecuteAction()
+ {
+ $task = new Task($this->db, $this->event);
+ $board = new Board($this->db, $this->event);
+ $project = new Project($this->db, $this->event);
+ $action = new Action($this->db, $this->event);
+
+ // We create a project
+ $this->assertEquals(1, $project->create(array('name' => 'unit_test')));
+
+ // We create a task
+ $this->assertEquals(1, $task->create(array(
+ 'title' => 'unit_test',
+ 'project_id' => 1,
+ 'owner_id' => 1,
+ 'color_id' => 'red',
+ 'column_id' => 1,
+ )));
+
+ // We create a new action
+ $this->assertTrue($action->create(array(
+ 'project_id' => 1,
+ 'event_name' => Task::EVENT_MOVE_COLUMN,
+ 'action_name' => 'TaskClose',
+ 'params' => array(
+ 'column_id' => 4,
+ )
+ )));
+
+ // We bind events
+ $action->attachEvents();
+
+ // Our task should be open
+ $t1 = $task->getById(1);
+ $this->assertEquals(1, $t1['is_active']);
+ $this->assertEquals(1, $t1['column_id']);
+
+ // We move our task
+ $task->move(1, 4, 1);
+
+ // Our task should be closed
+ $t1 = $task->getById(1);
+ $this->assertEquals(4, $t1['column_id']);
+ $this->assertEquals(0, $t1['is_active']);
+ }*/
+
+ public function testExecuteMultipleActions()
+ {
+ $task = new Task($this->db, $this->event);
+ $board = new Board($this->db, $this->event);
+ $project = new Project($this->db, $this->event);
+ $action = new Action($this->db, $this->event);
+
+ // We create 2 projects
+ $this->assertEquals(1, $project->create(array('name' => 'unit_test1')));
+ $this->assertEquals(2, $project->create(array('name' => 'unit_test2')));
+
+ // We create a task
+ $this->assertEquals(1, $task->create(array(
+ 'title' => 'unit_test',
+ 'project_id' => 1,
+ 'owner_id' => 1,
+ 'color_id' => 'red',
+ 'column_id' => 1,
+ )));
+
+ // We create 2 actions
+ $this->assertTrue($action->create(array(
+ 'project_id' => 1,
+ 'event_name' => Task::EVENT_CLOSE,
+ 'action_name' => 'TaskDuplicateAnotherProject',
+ 'params' => array(
+ 'column_id' => 4,
+ 'project_id' => 2,
+ )
+ )));
+
+ $this->assertTrue($action->create(array(
+ 'project_id' => 1,
+ 'event_name' => Task::EVENT_MOVE_COLUMN,
+ 'action_name' => 'TaskClose',
+ 'params' => array(
+ 'column_id' => 4,
+ )
+ )));
+
+ // We bind events
+ $action->attachEvents();
+
+ // Events should be attached
+ $this->assertTrue($this->event->hasListener(Task::EVENT_CLOSE, 'Action\TaskDuplicateAnotherProject'));
+ $this->assertTrue($this->event->hasListener(Task::EVENT_MOVE_COLUMN, 'Action\TaskClose'));
+
+ // Our task should be open, linked to the first project and in the first column
+ $t1 = $task->getById(1);
+ $this->assertEquals(1, $t1['is_active']);
+ $this->assertEquals(1, $t1['column_id']);
+ $this->assertEquals(1, $t1['project_id']);
+
+ // We move our task
+ $task->move(1, 4, 1);
+ $this->assertEquals(Task::EVENT_CREATE, $this->event->getLastTriggeredEvent());
+
+ // Our task should be closed
+ $t1 = $task->getById(1);
+ $this->assertEquals(4, $t1['column_id']);
+ $this->assertEquals(0, $t1['is_active']);
+
+ // Our task should be duplicated to the 2nd project
+ $t2 = $task->getById(2);
+ $this->assertNotEmpty($t2);
+ $this->assertNotEquals(4, $t2['column_id']);
+ $this->assertEquals(1, $t2['is_active']);
+ $this->assertEquals(2, $t2['project_id']);
+ $this->assertEquals('unit_test', $t2['title']);
+ }
+}
diff --git a/tests/Base.php b/tests/Base.php
new file mode 100644
index 00000000..6efb92e5
--- /dev/null
+++ b/tests/Base.php
@@ -0,0 +1,37 @@
+<?php
+
+require_once __DIR__.'/../vendor/PicoDb/Database.php';
+require_once __DIR__.'/../core/event.php';
+require_once __DIR__.'/../core/translator.php';
+require_once __DIR__.'/../models/schema.php';
+require_once __DIR__.'/../models/task.php';
+require_once __DIR__.'/../models/acl.php';
+require_once __DIR__.'/../models/comment.php';
+require_once __DIR__.'/../models/project.php';
+require_once __DIR__.'/../models/user.php';
+require_once __DIR__.'/../models/board.php';
+require_once __DIR__.'/../models/action.php';
+
+abstract class Base extends PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ $this->db = $this->getDbConnection();
+ $this->event = new \Core\Event;
+ }
+
+ public function getDbConnection()
+ {
+ $db = new \PicoDb\Database(array(
+ 'driver' => 'sqlite',
+ 'filename' => ':memory:'
+ ));
+
+ if ($db->schema()->check(10)) {
+ return $db;
+ }
+ else {
+ die('Unable to migrate database schema!');
+ }
+ }
+}
diff --git a/tests/ProjectTest.php b/tests/ProjectTest.php
index e6725b99..c04c7ff0 100644
--- a/tests/ProjectTest.php
+++ b/tests/ProjectTest.php
@@ -1,24 +1,19 @@
<?php
-require_once __DIR__.'/../lib/translator.php';
-require_once __DIR__.'/../models/base.php';
-require_once __DIR__.'/../models/board.php';
-require_once __DIR__.'/../models/user.php';
-require_once __DIR__.'/../models/project.php';
+require_once __DIR__.'/base.php';
use Model\Project;
use Model\User;
+use Model\Task;
+use Model\Acl;
+use Model\Board;
-class ProjectTest extends PHPUnit_Framework_TestCase
+class ProjectTest extends Base
{
- public function setUp()
- {
- defined('DB_FILENAME') or define('DB_FILENAME', ':memory:');
- }
-
public function testCreation()
{
- $p = new Project;
+ $p = new Project($this->db, $this->event);
+
$this->assertEquals(1, $p->create(array('name' => 'UnitTest')));
$this->assertNotEmpty($p->getById(1));
}
@@ -26,10 +21,10 @@ class ProjectTest extends PHPUnit_Framework_TestCase
public function testAllowEverybody()
{
// We create a regular user
- $user = new User;
+ $user = new User($this->db, $this->event);
$user->create(array('username' => 'unittest', 'password' => 'unittest'));
- $p = new Project;
+ $p = new Project($this->db, $this->event);
$this->assertEmpty($p->getAllowedUsers(1)); // Nobody is specified for the given project
$this->assertTrue($p->isUserAllowed(1, 1)); // Everybody should be allowed
$this->assertTrue($p->isUserAllowed(1, 2)); // Everybody should be allowed
@@ -37,7 +32,12 @@ class ProjectTest extends PHPUnit_Framework_TestCase
public function testAllowUser()
{
- $p = new Project;
+ $p = new Project($this->db, $this->event);
+ $user = new User($this->db, $this->event);
+ $user->create(array('username' => 'unittest', 'password' => 'unittest'));
+
+ // We create a project
+ $this->assertEquals(1, $p->create(array('name' => 'UnitTest')));
// We allow the admin user
$this->assertTrue($p->allowUser(1, 1));
@@ -58,7 +58,13 @@ class ProjectTest extends PHPUnit_Framework_TestCase
public function testRevokeUser()
{
- $p = new Project;
+ $p = new Project($this->db, $this->event);
+
+ $user = new User($this->db, $this->event);
+ $user->create(array('username' => 'unittest', 'password' => 'unittest'));
+
+ // We create a project
+ $this->assertEquals(1, $p->create(array('name' => 'UnitTest')));
// We revoke our admin user
$this->assertTrue($p->revokeUser(1, 1));
@@ -107,7 +113,13 @@ class ProjectTest extends PHPUnit_Framework_TestCase
public function testUsersList()
{
- $p = new Project;
+ $p = new Project($this->db, $this->event);
+
+ $user = new User($this->db, $this->event);
+ $user->create(array('username' => 'unittest', 'password' => 'unittest'));
+
+ // We create project
+ $this->assertEquals(1, $p->create(array('name' => 'UnitTest')));
// No restriction, we should have everybody
$this->assertEquals(
diff --git a/tests/TaskTest.php b/tests/TaskTest.php
index 415faede..a3417e91 100644
--- a/tests/TaskTest.php
+++ b/tests/TaskTest.php
@@ -1,20 +1,15 @@
<?php
-require_once __DIR__.'/../models/base.php';
-require_once __DIR__.'/../models/task.php';
+require_once __DIR__.'/base.php';
use Model\Task;
+use Model\Project;
-class TaskTest extends PHPUnit_Framework_TestCase
+class TaskTest extends Base
{
- public function setUp()
- {
- defined('DB_FILENAME') or define('DB_FILENAME', ':memory:');
- }
-
public function testDateFormat()
{
- $t = new Task;
+ $t = new Task($this->db, $this->event);
$this->assertEquals('2014-03-05', date('Y-m-d', $t->getTimestampFromDate('05/03/2014', 'd/m/Y')));
$this->assertEquals('2014-03-05', date('Y-m-d', $t->getTimestampFromDate('03/05/2014', 'm/d/Y')));
@@ -24,4 +19,58 @@ class TaskTest extends PHPUnit_Framework_TestCase
$this->assertEquals(0, $t->getTimestampFromDate('5/3/14', 'd/m/Y'));
$this->assertEquals(0, $t->getTimestampFromDate('5-3-2014', 'd/m/Y'));
}
+
+ public function testDuplicateToAnotherProject()
+ {
+ $t = new Task($this->db, $this->event);
+ $p = new Project($this->db, $this->event);
+
+ // We create 2 projects
+ $this->assertEquals(1, $p->create(array('name' => 'test1')));
+ $this->assertEquals(2, $p->create(array('name' => 'test2')));
+
+ // We create a task
+ $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1)));
+
+ // We duplicate our task to the 2nd project
+ $this->assertEquals(2, $t->duplicateToAnotherProject(1, 2));
+ $this->assertEquals(Task::EVENT_CREATE, $this->event->getLastTriggeredEvent());
+ }
+
+ public function testEvents()
+ {
+ $t = new Task($this->db, $this->event);
+ $p = new Project($this->db, $this->event);
+
+ // We create a project
+ $this->assertEquals(1, $p->create(array('name' => 'test')));
+
+ // We create task
+ $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1)));
+ $this->assertEquals(Task::EVENT_CREATE, $this->event->getLastTriggeredEvent());
+
+ // We update a task
+ $this->assertTrue($t->update(array('title' => 'test2', 'id' => 1)));
+ $this->assertEquals(Task::EVENT_UPDATE, $this->event->getLastTriggeredEvent());
+
+ // We close our task
+ $this->assertTrue($t->close(1));
+ $this->assertEquals(Task::EVENT_CLOSE, $this->event->getLastTriggeredEvent());
+
+ // We open our task
+ $this->assertTrue($t->open(1));
+ $this->assertEquals(Task::EVENT_OPEN, $this->event->getLastTriggeredEvent());
+
+ // We change the column of our task
+ $this->assertTrue($t->move(1, 2, 1));
+ $this->assertEquals(Task::EVENT_MOVE_COLUMN, $this->event->getLastTriggeredEvent());
+
+ // We change the position of our task
+ $this->assertTrue($t->move(1, 2, 2));
+ $this->assertEquals(Task::EVENT_MOVE_POSITION, $this->event->getLastTriggeredEvent());
+
+ // We change the column and the position of our task
+ $this->assertTrue($t->move(1, 1, 3));
+ $this->assertEquals(Task::EVENT_MOVE_COLUMN, $this->event->getLastTriggeredEvent());
+ }
}