Improve Automatic Actions plugin api

This commit is contained in:
Frederic Guillot
2016-01-03 16:43:13 -05:00
parent d578b612ea
commit a296ba5b18
82 changed files with 3011 additions and 1665 deletions

View File

@@ -2,14 +2,11 @@
namespace Kanboard\Model;
use Kanboard\Integration\GitlabWebhook;
use Kanboard\Integration\GithubWebhook;
use Kanboard\Integration\BitbucketWebhook;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
/**
* Action model
* Action Model
*
* @package model
* @author Frederic Guillot
@@ -24,143 +21,33 @@ class Action extends Base
const TABLE = 'actions';
/**
* SQL table name for action parameters
*
* @var string
*/
const TABLE_PARAMS = 'action_has_params';
/**
* Extended actions
*
* @access private
* @var array
*/
private $actions = array();
/**
* Extend the list of default actions
*
* @access public
* @param string $className
* @param string $description
* @return Action
*/
public function extendActions($className, $description)
{
$this->actions[$className] = $description;
return $this;
}
/**
* Return the name and description of available actions
* Return actions and parameters for a given user
*
* @access public
* @param integer $user_id
* @return array
*/
public function getAvailableActions()
public function getAllByUser($user_id)
{
$values = array(
'TaskClose' => t('Close a task'),
'TaskOpen' => t('Open a 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'),
'TaskMoveAnotherProject' => t('Move the task to another project'),
'TaskMoveColumnAssigned' => t('Move the task to another column when assigned to a user'),
'TaskMoveColumnUnAssigned' => t('Move the task to another column when assignee is cleared'),
'TaskAssignColorColumn' => t('Assign a color when the task is moved to a specific column'),
'TaskAssignColorUser' => t('Assign a color to a specific user'),
'TaskAssignColorCategory' => t('Assign automatically a color based on a category'),
'TaskAssignCategoryColor' => t('Assign automatically a category based on a color'),
'TaskAssignCategoryLink' => t('Assign automatically a category based on a link'),
'CommentCreation' => t('Create a comment from an external provider'),
'TaskCreation' => t('Create a task from an external provider'),
'TaskLogMoveAnotherColumn' => t('Add a comment log when moving the task between columns'),
'TaskAssignUser' => t('Change the assignee based on an external username'),
'TaskAssignCategoryLabel' => t('Change the category based on an external label'),
'TaskUpdateStartDate' => t('Automatically update the start date'),
'TaskMoveColumnCategoryChange' => t('Move the task to another column when the category is changed'),
'TaskEmail' => t('Send a task by email to someone'),
'TaskAssignColorLink' => t('Change task color when using a specific task link'),
);
$project_ids = $this->projectPermission->getActiveProjectIds($user_id);
$actions = array();
$values = array_merge($values, $this->actions);
if (! empty($project_ids)) {
$actions = $this->db->table(self::TABLE)->in('project_id', $project_ids)->findAll();
asort($values);
return $values;
}
/**
* Return the name and description of available actions
*
* @access public
* @return array
*/
public function getAvailableEvents()
{
$values = array(
TaskLink::EVENT_CREATE_UPDATE => t('Task link creation or modification'),
Task::EVENT_MOVE_COLUMN => t('Move a task to another column'),
Task::EVENT_UPDATE => t('Task modification'),
Task::EVENT_CREATE => t('Task creation'),
Task::EVENT_OPEN => t('Reopen a task'),
Task::EVENT_CLOSE => t('Closing a task'),
Task::EVENT_CREATE_UPDATE => t('Task creation or modification'),
Task::EVENT_ASSIGNEE_CHANGE => t('Task assignee change'),
GithubWebhook::EVENT_COMMIT => t('Github commit received'),
GithubWebhook::EVENT_ISSUE_OPENED => t('Github issue opened'),
GithubWebhook::EVENT_ISSUE_CLOSED => t('Github issue closed'),
GithubWebhook::EVENT_ISSUE_REOPENED => t('Github issue reopened'),
GithubWebhook::EVENT_ISSUE_ASSIGNEE_CHANGE => t('Github issue assignee change'),
GithubWebhook::EVENT_ISSUE_LABEL_CHANGE => t('Github issue label change'),
GithubWebhook::EVENT_ISSUE_COMMENT => t('Github issue comment created'),
GitlabWebhook::EVENT_COMMIT => t('Gitlab commit received'),
GitlabWebhook::EVENT_ISSUE_OPENED => t('Gitlab issue opened'),
GitlabWebhook::EVENT_ISSUE_REOPENED => t('Gitlab issue reopened'),
GitlabWebhook::EVENT_ISSUE_CLOSED => t('Gitlab issue closed'),
GitlabWebhook::EVENT_ISSUE_COMMENT => t('Gitlab issue comment created'),
BitbucketWebhook::EVENT_COMMIT => t('Bitbucket commit received'),
BitbucketWebhook::EVENT_ISSUE_OPENED => t('Bitbucket issue opened'),
BitbucketWebhook::EVENT_ISSUE_CLOSED => t('Bitbucket issue closed'),
BitbucketWebhook::EVENT_ISSUE_REOPENED => t('Bitbucket issue reopened'),
BitbucketWebhook::EVENT_ISSUE_ASSIGNEE_CHANGE => t('Bitbucket issue assignee change'),
BitbucketWebhook::EVENT_ISSUE_COMMENT => t('Bitbucket issue comment created'),
);
asort($values);
return $values;
}
/**
* Return the name and description of compatible actions
*
* @access public
* @param string $action_name Action name
* @return array
*/
public function getCompatibleEvents($action_name)
{
$action = $this->load($action_name, 0, '');
$compatible_events = $action->getCompatibleEvents();
$events = array();
foreach ($this->getAvailableEvents() as $event_name => $event_description) {
if (in_array($event_name, $compatible_events)) {
$events[$event_name] = $event_description;
foreach ($actions as &$action) {
$action['params'] = $this->actionParameter->getAll($action['id']);
}
}
return $events;
return $actions;
}
/**
* Return actions and parameters for a given project
*
* @access public
* @param $project_id
* @param integer $project_id
* @return array
*/
public function getAllByProject($project_id)
@@ -168,7 +55,7 @@ class Action extends Base
$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();
$action['params'] = $this->actionParameter->getAll($action['id']);
}
return $actions;
@@ -183,52 +70,27 @@ class Action extends Base
public function getAll()
{
$actions = $this->db->table(self::TABLE)->findAll();
$params = $this->db->table(self::TABLE_PARAMS)->findAll();
foreach ($actions as &$action) {
$action['params'] = array();
foreach ($params as $param) {
if ($param['action_id'] === $action['id']) {
$action['params'][] = $param;
}
}
$action['params'] = $this->actionParameter->getAll($action['id']);
}
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'], $action['event_name']);
$params += $action->getActionRequiredParameters();
}
return $params;
}
/**
* Fetch an action
*
* @access public
* @param integer $action_id Action id
* @return array Action data
* @param integer $action_id
* @return array
*/
public function getById($action_id)
{
$action = $this->db->table(self::TABLE)->eq('id', $action_id)->findOne();
if (! empty($action)) {
$action['params'] = $this->db->table(self::TABLE_PARAMS)->eq('action_id', $action_id)->findAll();
$action['params'] = $this->actionParameter->getAll($action_id);
}
return $action;
@@ -238,8 +100,8 @@ class Action extends Base
* Remove an action
*
* @access public
* @param integer $action_id Action id
* @return bool Success or not
* @param integer $action_id
* @return bool
*/
public function remove($action_id)
{
@@ -263,24 +125,16 @@ class Action extends Base
'action_name' => $values['action_name'],
);
if (! $this->db->table(self::TABLE)->save($action)) {
if (! $this->db->table(self::TABLE)->insert($action)) {
$this->db->cancelTransaction();
return false;
}
$action_id = $this->db->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;
}
if (! $this->actionParameter->create($action_id, $values)) {
$this->db->cancelTransaction();
return false;
}
$this->db->closeTransaction();
@@ -288,42 +142,6 @@ class Action extends Base
return $action_id;
}
/**
* Load all actions and attach events
*
* @access public
*/
public function attachEvents()
{
$actions = $this->getAll();
foreach ($actions as $action) {
$listener = $this->load($action['action_name'], $action['project_id'], $action['event_name']);
foreach ($action['params'] as $param) {
$listener->setParam($param['name'], $param['value']);
}
$this->container['dispatcher']->addListener($action['event_name'], array($listener, 'execute'));
}
}
/**
* Load an action
*
* @access public
* @param string $name Action class name
* @param integer $project_id Project id
* @param string $event Event name
* @return \Action\Base
*/
public function load($name, $project_id, $event)
{
$className = $name{0}
!== '\\' ? '\Kanboard\Action\\'.$name : $name;
return new $className($this->container, $project_id, $event);
}
/**
* Copy actions from a project to another one (skip actions that cannot resolve parameters)
*
@@ -346,15 +164,14 @@ class Action extends Base
);
if (! $this->db->table(self::TABLE)->insert($values)) {
$this->container['logger']->debug('Action::duplicate => unable to create '.$action['action_name']);
$this->db->cancelTransaction();
continue;
}
$action_id = $this->db->getLastId();
if (! $this->duplicateParameters($dst_project_id, $action_id, $action['params'])) {
$this->container['logger']->debug('Action::duplicate => unable to copy parameters for '.$action['action_name']);
if (! $this->actionParameter->duplicateParameters($dst_project_id, $action_id, $action['params'])) {
$this->logger->error('Action::duplicate => skip action '.$action['action_name'].' '.$action['id']);
$this->db->cancelTransaction();
continue;
}
@@ -365,74 +182,6 @@ class Action extends Base
return true;
}
/**
* Duplicate action parameters
*
* @access public
* @param integer $project_id
* @param integer $action_id
* @param array $params
* @return boolean
*/
public function duplicateParameters($project_id, $action_id, array $params)
{
foreach ($params as $param) {
$value = $this->resolveParameters($param, $project_id);
if ($value === false) {
$this->container['logger']->debug('Action::duplicateParameters => unable to resolve '.$param['name'].'='.$param['value']);
return false;
}
$values = array(
'action_id' => $action_id,
'name' => $param['name'],
'value' => $value,
);
if (! $this->db->table(self::TABLE_PARAMS)->insert($values)) {
return false;
}
}
return true;
}
/**
* Resolve action parameter values according to another project
*
* @author Antonio Rabelo
* @access public
* @param array $param Action parameter
* @param integer $project_id Project to find the corresponding values
* @return mixed
*/
public function resolveParameters(array $param, $project_id)
{
switch ($param['name']) {
case 'project_id':
return $project_id;
case 'category_id':
return $this->category->getIdByName($project_id, $this->category->getNameById($param['value'])) ?: false;
case 'src_column_id':
case 'dest_column_id':
case 'dst_column_id':
case 'column_id':
$column = $this->board->getColumn($param['value']);
if (empty($column)) {
return false;
}
return $this->board->getColumnIdByTitle($project_id, $column['title']) ?: false;
case 'user_id':
case 'owner_id':
return $this->projectPermission->isAssignable($project_id, $param['value']) ? $param['value'] : false;
default:
return $param['value'];
}
}
/**
* Validate action creation
*

View File

@@ -0,0 +1,122 @@
<?php
namespace Kanboard\Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
/**
* Action Parameter Model
*
* @package model
* @author Frederic Guillot
*/
class ActionParameter extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'action_has_params';
/**
* Get all action params
*
* @access public
* @param integer $action_id
* @return array
*/
public function getAll($action_id)
{
return $this->db->hashtable(self::TABLE)->eq('action_id', $action_id)->getAll('name', 'value');
}
/**
* Insert new parameters for an action
*
* @access public
* @param integer $action_id
* @param array $values
* @return boolean
*/
public function create($action_id, array $values)
{
foreach ($values['params'] as $name => $value) {
$param = array(
'action_id' => $action_id,
'name' => $name,
'value' => $value,
);
if (! $this->db->table(self::TABLE)->save($param)) {
return false;
}
}
return true;
}
/**
* Duplicate action parameters
*
* @access public
* @param integer $project_id
* @param integer $action_id
* @param array $params
* @return boolean
*/
public function duplicateParameters($project_id, $action_id, array $params)
{
foreach ($params as $name => $value) {
$value = $this->resolveParameter($project_id, $name, $value);
if ($value === false) {
$this->logger->error('ActionParameter::duplicateParameters => unable to resolve '.$name.'='.$value);
return false;
}
$values = array(
'action_id' => $action_id,
'name' => $name,
'value' => $value,
);
if (! $this->db->table(self::TABLE)->insert($values)) {
return false;
}
}
return true;
}
/**
* Resolve action parameter values according to another project
*
* @access private
* @param integer $project_id
* @param string $name
* @param string $value
* @return mixed
*/
private function resolveParameter($project_id, $name, $value)
{
switch ($name) {
case 'project_id':
return $value != $project_id ? $value : false;
case 'category_id':
return $this->category->getIdByName($project_id, $this->category->getNameById($value)) ?: false;
case 'src_column_id':
case 'dest_column_id':
case 'dst_column_id':
case 'column_id':
$column = $this->board->getColumn($value);
return empty($column) ? false : $this->board->getColumnIdByTitle($project_id, $column['title']) ?: false;
case 'user_id':
case 'owner_id':
return $this->projectPermission->isAssignable($project_id, $value) ? $value : false;
default:
return $value;
}
}
}

View File

@@ -75,6 +75,7 @@ class Comment extends Base
self::TABLE.'.user_id',
self::TABLE.'.date_creation',
self::TABLE.'.comment',
self::TABLE.'.reference',
User::TABLE.'.username',
User::TABLE.'.name'
)

View File

@@ -52,11 +52,11 @@ class ProjectUserRole extends Base
->getAll(Project::TABLE.'.id', Project::TABLE.'.name');
$groupProjects = $this->projectGroupRole->getProjectsByUser($user_id, $status);
$groups = $userProjects + $groupProjects;
$projects = $userProjects + $groupProjects;
asort($groups);
asort($projects);
return $groups;
return $projects;
}
/**