diff options
author | Frédéric Guillot <fguillot@users.noreply.github.com> | 2014-03-09 23:21:23 -0400 |
---|---|---|
committer | Frédéric Guillot <fguillot@users.noreply.github.com> | 2014-03-09 23:21:23 -0400 |
commit | 7749b8ed569f6d27b0bb2ed4c2040e8b61ed4422 (patch) | |
tree | ee101992e87d740bdf0362e35ea040c866986f5a /models | |
parent | 7bd4697dfca41a21f5857f83d6b29108fafb9a1e (diff) |
Automatic actions
Diffstat (limited to 'models')
-rw-r--r-- | models/acl.php | 2 | ||||
-rw-r--r-- | models/action.php | 247 | ||||
-rw-r--r-- | models/base.php | 48 | ||||
-rw-r--r-- | models/board.php | 10 | ||||
-rw-r--r-- | models/comment.php | 6 | ||||
-rw-r--r-- | models/config.php | 2 | ||||
-rw-r--r-- | models/project.php | 37 | ||||
-rw-r--r-- | models/schema.php | 23 | ||||
-rw-r--r-- | models/task.php | 175 | ||||
-rw-r--r-- | models/user.php | 2 |
10 files changed, 459 insertions, 93 deletions
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; |