diff --git a/app/Controller/ColumnRestrictionController.php b/app/Controller/ColumnRestrictionController.php new file mode 100644 index 000000000..ce2a1ca81 --- /dev/null +++ b/app/Controller/ColumnRestrictionController.php @@ -0,0 +1,103 @@ +getProject(); + $role_id = $this->request->getIntegerParam('role_id'); + $role = $this->projectRoleModel->getById($project['id'], $role_id); + + $this->response->html($this->template->render('column_restriction/create', array( + 'project' => $project, + 'role' => $role, + 'rules' => $this->columnRestrictionModel->getRules(), + 'columns' => $this->columnModel->getList($project['id']), + 'values' => $values + array('project_id' => $project['id'], 'role_id' => $role['role_id']), + 'errors' => $errors, + ))); + } + + /** + * Save new column restriction + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + list($valid, $errors) = $this->columnRestrictionValidator->validateCreation($values); + + if ($valid) { + $restriction_id = $this->columnRestrictionModel->create( + $project['id'], + $values['role_id'], + $values['column_id'], + $values['rule'] + ); + + if ($restriction_id !== false) { + $this->flash->success(t('The column restriction has been created successfully.')); + } else { + $this->flash->failure(t('Unable to create this column restriction.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } else { + $this->create($values, $errors); + } + } + + /** + * Confirm suppression + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $restriction_id = $this->request->getIntegerParam('restriction_id'); + + $this->response->html($this->helper->layout->project('column_restriction/remove', array( + 'project' => $project, + 'restriction' => $this->columnRestrictionModel->getById($project['id'], $restriction_id), + ))); + } + + /** + * Remove a restriction + * + * @access public + */ + public function remove() + { + $project = $this->getProject(); + $this->checkCSRFParam(); + $restriction_id = $this->request->getIntegerParam('restriction_id'); + + if ($this->columnRestrictionModel->remove($restriction_id)) { + $this->flash->success(t('Column restriction removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this restriction.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/TaskCreationController.php b/app/Controller/TaskCreationController.php index c68964f6b..c754b0295 100644 --- a/app/Controller/TaskCreationController.php +++ b/app/Controller/TaskCreationController.php @@ -52,12 +52,16 @@ class TaskCreationController extends BaseController list($valid, $errors) = $this->taskValidator->validateCreation($values); - if ($valid && ($task_id = $this->taskCreationModel->create($values))) { - $this->flash->success(t('Task created successfully.')); - $this->afterSave($project, $values, $task_id); - } else { + if (! $valid) { $this->flash->failure(t('Unable to create your task.')); $this->show($values, $errors); + } else if (! $this->helper->projectRole->canCreateTaskInColumn($project['id'], $values['column_id'])) { + $this->flash->failure(t('You cannot create tasks in this column.')); + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true); + } else { + $task_id = $this->taskCreationModel->create($values); + $this->flash->success(t('Task created successfully.')); + $this->afterSave($project, $values, $task_id); } } diff --git a/app/Controller/TaskMovePositionController.php b/app/Controller/TaskMovePositionController.php index c6e8be0cf..1a8eee450 100644 --- a/app/Controller/TaskMovePositionController.php +++ b/app/Controller/TaskMovePositionController.php @@ -2,6 +2,7 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; use Kanboard\Formatter\BoardFormatter; /** diff --git a/app/Core/Base.php b/app/Core/Base.php index d6da13f20..44dfaa39b 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -57,7 +57,9 @@ use Pimple\Container; * @property \Kanboard\Core\Paginator $paginator * @property \Kanboard\Core\Template $template * @property \Kanboard\Decorator\MetadataCacheDecorator $userMetadataCacheDecorator + * @property \Kanboard\Decorator\columnRestrictionCacheDecorator $columnRestrictionCacheDecorator * @property \Kanboard\Decorator\ColumnMoveRestrictionCacheDecorator $columnMoveRestrictionCacheDecorator + * @property \Kanboard\Decorator\ProjectRoleRestrictionCacheDecorator $projectRoleRestrictionCacheDecorator * @property \Kanboard\Model\ActionModel $actionModel * @property \Kanboard\Model\ActionParameterModel $actionParameterModel * @property \Kanboard\Model\AvatarFileModel $avatarFileModel @@ -65,6 +67,7 @@ use Pimple\Container; * @property \Kanboard\Model\CategoryModel $categoryModel * @property \Kanboard\Model\ColorModel $colorModel * @property \Kanboard\Model\ColumnModel $columnModel + * @property \Kanboard\Model\ColumnRestrictionModel $columnRestrictionModel * @property \Kanboard\Model\ColumnMoveRestrictionModel $columnMoveRestrictionModel * @property \Kanboard\Model\CommentModel $commentModel * @property \Kanboard\Model\ConfigModel $configModel @@ -136,6 +139,7 @@ use Pimple\Container; * @property \Kanboard\Validator\AuthValidator $authValidator * @property \Kanboard\Validator\ColumnValidator $columnValidator * @property \Kanboard\Validator\CategoryValidator $categoryValidator + * @property \Kanboard\Validator\ColumnRestrictionValidator $columnRestrictionValidator * @property \Kanboard\Validator\ColumnMoveRestrictionValidator $columnMoveRestrictionValidator * @property \Kanboard\Validator\CommentValidator $commentValidator * @property \Kanboard\Validator\CurrencyValidator $currencyValidator diff --git a/app/Decorator/ColumnMoveRestrictionCacheDecorator.php b/app/Decorator/ColumnMoveRestrictionCacheDecorator.php index 2a3e9c2a8..82140d16a 100644 --- a/app/Decorator/ColumnMoveRestrictionCacheDecorator.php +++ b/app/Decorator/ColumnMoveRestrictionCacheDecorator.php @@ -40,7 +40,8 @@ class ColumnMoveRestrictionCacheDecorator /** * Proxy method to get sortable columns * - * @param int $project_id + * @param int $project_id + * @param string $role * @return array|mixed */ public function getSortableColumns($project_id, $role) diff --git a/app/Decorator/ColumnRestrictionCacheDecorator.php b/app/Decorator/ColumnRestrictionCacheDecorator.php new file mode 100644 index 000000000..a615030d3 --- /dev/null +++ b/app/Decorator/ColumnRestrictionCacheDecorator.php @@ -0,0 +1,59 @@ +cache = $cache; + $this->columnRestrictionModel = $columnMoveRestrictionModel; + } + + /** + * Proxy method to get sortable columns + * + * @param int $project_id + * @param string $role + * @return array|mixed + */ + public function getAllByRole($project_id, $role) + { + $key = $this->cachePrefix.$project_id.$role; + $columnRestrictions = $this->cache->get($key); + + if ($columnRestrictions === null) { + $columnRestrictions = $this->columnRestrictionModel->getAllByRole($project_id, $role); + $this->cache->set($key, $columnRestrictions); + } + + return $columnRestrictions; + } +} diff --git a/app/Decorator/ProjectRoleRestrictionCacheDecorator.php b/app/Decorator/ProjectRoleRestrictionCacheDecorator.php new file mode 100644 index 000000000..a6e24048c --- /dev/null +++ b/app/Decorator/ProjectRoleRestrictionCacheDecorator.php @@ -0,0 +1,59 @@ +cache = $cache; + $this->projectRoleRestrictionModel = $projectRoleRestrictionModel; + } + + /** + * Proxy method to get sortable columns + * + * @param int $project_id + * @param string $role + * @return array|mixed + */ + public function getAllByRole($project_id, $role) + { + $key = $this->cachePrefix.$project_id.$role; + $projectRestrictions = $this->cache->get($key); + + if ($projectRestrictions === null) { + $projectRestrictions = $this->projectRoleRestrictionModel->getAllByRole($project_id, $role); + $this->cache->set($key, $projectRestrictions); + } + + return $projectRestrictions; + } +} diff --git a/app/Helper/ProjectRoleHelper.php b/app/Helper/ProjectRoleHelper.php index 99fa82bcf..e1808be5a 100644 --- a/app/Helper/ProjectRoleHelper.php +++ b/app/Helper/ProjectRoleHelper.php @@ -4,6 +4,8 @@ namespace Kanboard\Helper; use Kanboard\Core\Base; use Kanboard\Core\Security\Role; +use Kanboard\Model\ColumnRestrictionModel; +use Kanboard\Model\ProjectRoleRestrictionModel; /** * Class ProjectRoleHelper @@ -98,6 +100,46 @@ class ProjectRoleHelper extends Base return true; } + /** + * Return true if the user can create a task for the given column + * + * @param int $project_id + * @param int $column_id + * @return bool + */ + public function canCreateTaskInColumn($project_id, $column_id) + { + $role = $this->getProjectUserRole($project_id); + + if ($this->role->isCustomProjectRole($role)) { + if (! $this->isAllowedToCreateTask($project_id, $column_id, $role)) { + return false; + } + } + + return $this->helper->user->hasProjectAccess('TaskCreationController', 'show', $project_id); + } + + /** + * Return true if the user can create a task for the given column + * + * @param int $project_id + * @param int $column_id + * @return bool + */ + public function canChangeTaskStatusInColumn($project_id, $column_id) + { + $role = $this->getProjectUserRole($project_id); + + if ($this->role->isCustomProjectRole($role)) { + if (! $this->isAllowedToChangeTaskStatus($project_id, $column_id, $role)) { + return false; + } + } + + return $this->helper->user->hasProjectAccess('TaskStatusController', 'close', $project_id); + } + /** * Return true if the user can remove a task * @@ -145,13 +187,77 @@ class ProjectRoleHelper extends Base $role = $this->getProjectUserRole($project_id); if ($this->role->isCustomProjectRole($role)) { - $restrictions = $this->projectRoleRestrictionModel->getAllByRole($project_id, $role); - $result = $this->projectRoleRestrictionModel->isAllowed($restrictions, $controller, $action); - $result = $result && $this->projectAuthorization->isAllowed($controller, $action, Role::PROJECT_MEMBER); + $result = $this->projectAuthorization->isAllowed($controller, $action, Role::PROJECT_MEMBER); } else { $result = $this->projectAuthorization->isAllowed($controller, $action, $role); } return $result; } + + /** + * Check authorization for a custom project role to change the task status + * + * @param int $project_id + * @param int $column_id + * @param string $role + * @return bool + */ + protected function isAllowedToChangeTaskStatus($project_id, $column_id, $role) + { + $columnRestrictions = $this->columnRestrictionCacheDecorator->getAllByRole($project_id, $role); + + foreach ($columnRestrictions as $restriction) { + if ($restriction['column_id'] == $column_id) { + if ($restriction['rule'] == ColumnRestrictionModel::RULE_ALLOW_TASK_OPEN_CLOSE) { + return true; + } else if ($restriction['rule'] == ColumnRestrictionModel::RULE_BLOCK_TASK_OPEN_CLOSE) { + return false; + } + } + } + + $projectRestrictions = $this->projectRoleRestrictionCacheDecorator->getAllByRole($project_id, $role); + + foreach ($projectRestrictions as $restriction) { + if ($restriction['rule'] == ProjectRoleRestrictionModel::RULE_TASK_OPEN_CLOSE) { + return false; + } + } + + return true; + } + + /** + * Check authorization for a custom project role to create a task + * + * @param int $project_id + * @param int $column_id + * @param string $role + * @return bool + */ + protected function isAllowedToCreateTask($project_id, $column_id, $role) + { + $columnRestrictions = $this->columnRestrictionCacheDecorator->getAllByRole($project_id, $role); + + foreach ($columnRestrictions as $restriction) { + if ($restriction['column_id'] == $column_id) { + if ($restriction['rule'] == ColumnRestrictionModel::RULE_ALLOW_TASK_CREATION) { + return true; + } else if ($restriction['rule'] == ColumnRestrictionModel::RULE_BLOCK_TASK_CREATION) { + return false; + } + } + } + + $projectRestrictions = $this->projectRoleRestrictionCacheDecorator->getAllByRole($project_id, $role); + + foreach ($projectRestrictions as $restriction) { + if ($restriction['rule'] == ProjectRoleRestrictionModel::RULE_TASK_CREATION) { + return false; + } + } + + return true; + } } diff --git a/app/Model/ColumnRestrictionModel.php b/app/Model/ColumnRestrictionModel.php new file mode 100644 index 000000000..92b2ac60d --- /dev/null +++ b/app/Model/ColumnRestrictionModel.php @@ -0,0 +1,152 @@ + t('Task creation is permitted for this column'), + self::RULE_ALLOW_TASK_OPEN_CLOSE => t('Closing or opening a task is permitted for this column'), + self::RULE_BLOCK_TASK_CREATION => t('Task creation is blocked for this column'), + self::RULE_BLOCK_TASK_OPEN_CLOSE => t('Closing or opening a task is blocked for this column'), + ); + } + + /** + * Fetch one restriction + * + * @param int $project_id + * @param int $restriction_id + * @return array|null + */ + public function getById($project_id, $restriction_id) + { + return $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.restriction_id', + self::TABLE.'.project_id', + self::TABLE.'.role_id', + self::TABLE.'.column_id', + self::TABLE.'.rule', + 'pr.role', + 'c.title as column_title' + ) + ->left(ColumnModel::TABLE, 'c', 'id', self::TABLE, 'column_id') + ->left(ProjectRoleModel::TABLE, 'pr', 'role_id', self::TABLE, 'role_id') + ->eq(self::TABLE.'.project_id', $project_id) + ->eq(self::TABLE.'.restriction_id', $restriction_id) + ->findOne(); + } + + /** + * Get all project column restrictions + * + * @param int $project_id + * @return array + */ + public function getAll($project_id) + { + $rules = $this->getRules(); + $restrictions = $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.restriction_id', + self::TABLE.'.project_id', + self::TABLE.'.role_id', + self::TABLE.'.column_id', + self::TABLE.'.rule', + 'pr.role', + 'c.title as column_title' + ) + ->left(ColumnModel::TABLE, 'c', 'id', self::TABLE, 'column_id') + ->left(ProjectRoleModel::TABLE, 'pr', 'role_id', self::TABLE, 'role_id') + ->eq(self::TABLE.'.project_id', $project_id) + ->findAll(); + + foreach ($restrictions as &$restriction) { + $restriction['title'] = $rules[$restriction['rule']]; + } + + return $restrictions; + } + + /** + * Get restrictions + * + * @param int $project_id + * @param string $role + * @return array + */ + public function getAllByRole($project_id, $role) + { + return $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.restriction_id', + self::TABLE.'.project_id', + self::TABLE.'.role_id', + self::TABLE.'.column_id', + self::TABLE.'.rule', + 'pr.role' + ) + ->eq(self::TABLE.'.project_id', $project_id) + ->eq('pr.role', $role) + ->left(ProjectRoleModel::TABLE, 'pr', 'role_id', self::TABLE, 'role_id') + ->findAll(); + } + + /** + * Create a new column restriction + * + * @param int $project_id + * @param int $role_id + * @param int $column_id + * @param int $rule + * @return bool|int + */ + public function create($project_id, $role_id, $column_id, $rule) + { + return $this->db + ->table(self::TABLE) + ->persist(array( + 'project_id' => $project_id, + 'role_id' => $role_id, + 'column_id' => $column_id, + 'rule' => $rule, + )); + } + + /** + * Remove a permission + * + * @param int $restriction_id + * @return bool + */ + public function remove($restriction_id) + { + return $this->db->table(self::TABLE)->eq('restriction_id', $restriction_id)->remove(); + } +} diff --git a/app/Model/ProjectPermissionModel.php b/app/Model/ProjectPermissionModel.php index 4882343d8..25b6a3828 100644 --- a/app/Model/ProjectPermissionModel.php +++ b/app/Model/ProjectPermissionModel.php @@ -122,8 +122,13 @@ class ProjectPermissionModel extends Base */ public function isAssignable($project_id, $user_id) { - return $this->userModel->isActive($user_id) && - in_array($this->projectUserRoleModel->getUserRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER)); + if ($this->userModel->isActive($user_id)) { + $role = $this->projectUserRoleModel->getUserRole($project_id, $user_id); + + return ! empty($role) && $role !== Role::PROJECT_VIEWER; + } + + return false; } /** diff --git a/app/Model/ProjectRoleModel.php b/app/Model/ProjectRoleModel.php index ed86d6ed1..962ff44fd 100644 --- a/app/Model/ProjectRoleModel.php +++ b/app/Model/ProjectRoleModel.php @@ -71,10 +71,14 @@ class ProjectRoleModel extends Base { $roles = $this->getAll($project_id); - $column_restrictions = $this->columnMoveRestrictionModel->getAll($project_id); + $column_restrictions = $this->columnRestrictionModel->getAll($project_id); $column_restrictions = array_column_index($column_restrictions, 'role_id'); array_merge_relation($roles, $column_restrictions, 'column_restrictions', 'role_id'); + $column_move_restrictions = $this->columnMoveRestrictionModel->getAll($project_id); + $column_move_restrictions = array_column_index($column_move_restrictions, 'role_id'); + array_merge_relation($roles, $column_move_restrictions, 'column_move_restrictions', 'role_id'); + $project_restrictions = $this->projectRoleRestrictionModel->getAll($project_id); $project_restrictions = array_column_index($project_restrictions, 'role_id'); array_merge_relation($roles, $project_restrictions, 'project_restrictions', 'role_id'); @@ -109,13 +113,41 @@ class ProjectRoleModel extends Base */ public function update($role_id, $project_id, $role) { - return $this->db + $this->db->startTransaction(); + + $previousRole = $this->getById($project_id, $role_id); + + $r1 = $this->db + ->table(ProjectUserRoleModel::TABLE) + ->eq('project_id', $project_id) + ->eq('role', $previousRole['role']) + ->update(array( + 'role' => $role + )); + + $r2 = $this->db + ->table(ProjectGroupRoleModel::TABLE) + ->eq('project_id', $project_id) + ->eq('role', $previousRole['role']) + ->update(array( + 'role' => $role + )); + + $r3 = $this->db ->table(self::TABLE) ->eq('role_id', $role_id) ->eq('project_id', $project_id) ->update(array( 'role' => $role, )); + + if ($r1 && $r2 && $r3) { + $this->db->closeTransaction(); + return true; + } + + $this->db->cancelTransaction(); + return false; } /** diff --git a/app/Model/ProjectRoleRestrictionModel.php b/app/Model/ProjectRoleRestrictionModel.php index 7679f6509..dc8abf793 100644 --- a/app/Model/ProjectRoleRestrictionModel.php +++ b/app/Model/ProjectRoleRestrictionModel.php @@ -17,15 +17,6 @@ class ProjectRoleRestrictionModel extends Base const RULE_TASK_CREATION = 'task_creation'; const RULE_TASK_OPEN_CLOSE = 'task_open_close'; - protected $ruleMapping = array( - self::RULE_TASK_CREATION => array( - array('controller' => 'TaskCreationController', 'method' => '*'), - ), - self::RULE_TASK_OPEN_CLOSE => array( - array('controller' => 'TaskStatusController', 'method' => '*'), - ) - ); - /** * Get rules * @@ -91,7 +82,7 @@ class ProjectRoleRestrictionModel extends Base */ public function getAllByRole($project_id, $role) { - $rules = $this->db + return $this->db ->table(self::TABLE) ->columns( self::TABLE.'.restriction_id', @@ -104,12 +95,6 @@ class ProjectRoleRestrictionModel extends Base ->eq('role', $role) ->left(ProjectRoleModel::TABLE, 'pr', 'role_id', self::TABLE, 'role_id') ->findAll(); - - foreach ($rules as &$rule) { - $rule['acl'] = $this->ruleMapping[$rule['rule']]; - } - - return $rules; } /** @@ -140,31 +125,4 @@ class ProjectRoleRestrictionModel extends Base { return $this->db->table(self::TABLE)->eq('restriction_id', $restriction_id)->remove(); } - - /** - * Check if the controller/method is allowed - * - * @param array $restrictions - * @param string $controller - * @param string $method - * @return bool - */ - public function isAllowed(array $restrictions, $controller, $method) - { - $controller = strtolower($controller); - $method = strtolower($method); - - foreach ($restrictions as $restriction) { - foreach ($restriction['acl'] as $acl) { - $acl['controller'] = strtolower($acl['controller']); - $acl['method'] = strtolower($acl['method']); - - if ($acl['controller'] === $controller && ($acl['method'] === '*' || $acl['method'] === $method)) { - return false; - } - } - } - - return true; - } } diff --git a/app/Model/ProjectUserRoleModel.php b/app/Model/ProjectUserRoleModel.php index a0df0cfae..76094431c 100644 --- a/app/Model/ProjectUserRoleModel.php +++ b/app/Model/ProjectUserRoleModel.php @@ -166,7 +166,7 @@ class ProjectUserRoleModel extends Base ->join(UserModel::TABLE, 'id', 'user_id') ->eq(UserModel::TABLE.'.is_active', 1) ->eq(self::TABLE.'.project_id', $project_id) - ->in(self::TABLE.'.role', array(Role::PROJECT_MANAGER, Role::PROJECT_MEMBER)) + ->neq(self::TABLE.'.role', Role::PROJECT_VIEWER) ->findAll(); $groupMembers = $this->projectGroupRoleModel->getAssignableUsers($project_id); diff --git a/app/Model/TaskFinderModel.php b/app/Model/TaskFinderModel.php index 3c32e1408..3185afb7e 100644 --- a/app/Model/TaskFinderModel.php +++ b/app/Model/TaskFinderModel.php @@ -67,6 +67,7 @@ class TaskFinderModel extends Base TaskModel::TABLE.'.date_due', TaskModel::TABLE.'.date_creation', TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.column_id', TaskModel::TABLE.'.color_id', TaskModel::TABLE.'.priority', TaskModel::TABLE.'.time_spent', diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 398b963ec..274ce8c81 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,25 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 114; +const VERSION = 115; + +function version_115(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE column_has_restrictions ( + restriction_id INT NOT NULL AUTO_INCREMENT, + project_id INT NOT NULL, + role_id INT NOT NULL, + column_id INT NOT NULL, + rule VARCHAR(255) NOT NULL, + UNIQUE(role_id, column_id, rule), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE, + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE, + PRIMARY KEY(restriction_id) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} function version_114(PDO $pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index 56cd9de9c..213d98697 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,24 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 93; +const VERSION = 94; + +function version_94(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE column_has_restrictions ( + restriction_id SERIAL PRIMARY KEY, + project_id INTEGER NOT NULL, + role_id INTEGER NOT NULL, + column_id INTEGER NOT NULL, + rule VARCHAR(255) NOT NULL, + UNIQUE(role_id, column_id, rule), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE, + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE + ) + "); +} function version_93(PDO $pdo) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index c952d58f5..f86a6af0a 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,24 @@ use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; use PDO; -const VERSION = 105; +const VERSION = 106; + +function version_106(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE column_has_restrictions ( + restriction_id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL, + role_id INTEGER NOT NULL, + column_id INTEGER NOT NULL, + rule VARCHAR(255) NOT NULL, + UNIQUE(role_id, column_id, rule), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE, + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE + ) + "); +} function version_105(PDO $pdo) { diff --git a/app/ServiceProvider/CacheProvider.php b/app/ServiceProvider/CacheProvider.php index 90d63f819..e93dd5022 100644 --- a/app/ServiceProvider/CacheProvider.php +++ b/app/ServiceProvider/CacheProvider.php @@ -5,7 +5,9 @@ namespace Kanboard\ServiceProvider; use Kanboard\Core\Cache\FileCache; use Kanboard\Core\Cache\MemoryCache; use Kanboard\Decorator\ColumnMoveRestrictionCacheDecorator; +use Kanboard\Decorator\ColumnRestrictionCacheDecorator; use Kanboard\Decorator\MetadataCacheDecorator; +use Kanboard\Decorator\ProjectRoleRestrictionCacheDecorator; use Pimple\Container; use Pimple\ServiceProviderInterface; @@ -54,6 +56,20 @@ class CacheProvider implements ServiceProviderInterface ); }; + $container['columnRestrictionCacheDecorator'] = function($c) { + return new ColumnRestrictionCacheDecorator( + $c['memoryCache'], + $c['columnRestrictionModel'] + ); + }; + + $container['projectRoleRestrictionCacheDecorator'] = function($c) { + return new ProjectRoleRestrictionCacheDecorator( + $c['memoryCache'], + $c['projectRoleRestrictionModel'] + ); + }; + return $container; } } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 4841d1f0d..c5bf0678b 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -34,6 +34,7 @@ class ClassProvider implements ServiceProviderInterface 'CategoryModel', 'ColorModel', 'ColumnModel', + 'ColumnRestrictionModel', 'ColumnMoveRestrictionModel', 'CommentModel', 'ConfigModel', @@ -101,6 +102,7 @@ class ClassProvider implements ServiceProviderInterface 'AuthValidator', 'CategoryValidator', 'ColumnMoveRestrictionValidator', + 'ColumnRestrictionValidator', 'ColumnValidator', 'CommentValidator', 'CurrencyValidator', diff --git a/app/Template/board/table_column.php b/app/Template/board/table_column.php index c0b71eabf..3daa8aed4 100644 --- a/app/Template/board/table_column.php +++ b/app/Template/board/table_column.php @@ -12,7 +12,7 @@
- user->hasProjectAccess('TaskCreationController', 'show', $column['project_id'])): ?> + projectRole->canCreateTaskInColumn($column['project_id'], $column['id'])): ?>
url->link('+', 'TaskCreationController', 'show', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']), false, 'popover', t('Add a new task')) ?>
diff --git a/app/Template/column_move_restriction/create.php b/app/Template/column_move_restriction/create.php index 8d161c3ed..1eb6d5395 100644 --- a/app/Template/column_move_restriction/create.php +++ b/app/Template/column_move_restriction/create.php @@ -1,6 +1,6 @@
form->csrf() ?> diff --git a/app/Template/column_restriction/create.php b/app/Template/column_restriction/create.php new file mode 100644 index 000000000..982733b4e --- /dev/null +++ b/app/Template/column_restriction/create.php @@ -0,0 +1,22 @@ +
+ + + form->csrf() ?> + form->hidden('project_id', $values) ?> + form->hidden('role_id', $values) ?> + + form->label(t('Rule'), 'rule') ?> + form->select('rule', $rules, $values, $errors) ?> + + form->label(t('Column'), 'column_id') ?> + form->select('column_id', $columns, $values, $errors) ?> + +
+ + + url->link(t('cancel'), 'ProjectRoleController', 'show', array(), false, 'close-popover') ?> +
+ +
diff --git a/app/Template/column_restriction/remove.php b/app/Template/column_restriction/remove.php new file mode 100644 index 000000000..97650e2de --- /dev/null +++ b/app/Template/column_restriction/remove.php @@ -0,0 +1,14 @@ + + +
+

+ +

+ +
+ url->link(t('Yes'), 'ColumnRestrictionController', 'remove', array('project_id' => $project['id'], 'restriction_id' => $restriction['restriction_id']), true, 'btn btn-red') ?> + url->link(t('cancel'), 'ProjectRoleController', 'show', array('project_id' => $project['id']), false, 'close-popover') ?> +
+
diff --git a/app/Template/project_role/show.php b/app/Template/project_role/show.php index 81281a3ea..59200fc94 100644 --- a/app/Template/project_role/show.php +++ b/app/Template/project_role/show.php @@ -24,7 +24,11 @@
  • - url->link(t('Add a new column restriction'), 'ColumnMoveRestrictionController', 'create', array('project_id' => $project['id'], 'role_id' => $role['role_id']), false, 'popover') ?> + url->link(t('Add a new drag and drop restriction'), 'ColumnMoveRestrictionController', 'create', array('project_id' => $project['id'], 'role_id' => $role['role_id']), false, 'popover') ?> +
  • +
  • + + url->link(t('Add a new column restriction'), 'ColumnRestrictionController', 'create', array('project_id' => $project['id'], 'role_id' => $role['role_id']), false, 'popover') ?>
  • @@ -41,7 +45,7 @@ - + @@ -49,6 +53,9 @@ + + + text->e($restriction['title']) ?> @@ -60,7 +67,28 @@ - + + + + + + text->e($restriction['column_title']) ?> + + text->e($restriction['title']) ?> + + + + url->link(t('Remove'), 'ColumnRestrictionController', 'confirm', array('project_id' => $project['id'], 'restriction_id' => $restriction['restriction_id']), false, 'popover') ?> + + + + + + + + text->e($restriction['src_column_title']) ?> / text->e($restriction['dst_column_title']) ?> + + diff --git a/app/Template/task/dropdown.php b/app/Template/task/dropdown.php index 127fc89ce..f2423dd82 100644 --- a/app/Template/task/dropdown.php +++ b/app/Template/task/dropdown.php @@ -49,7 +49,7 @@ url->link(t('Remove'), 'TaskSuppressionController', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?>
  • - user->hasProjectAccess('TaskStatusController', 'close', $task['project_id'])): ?> + projectRole->canChangeTaskStatusInColumn($task['project_id'], $task['column_id'])): ?>
  • diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php index 87fe8ceec..640423f4b 100644 --- a/app/Template/task/sidebar.php +++ b/app/Template/task/sidebar.php @@ -78,7 +78,7 @@ url->link(t('Move to another project'), 'TaskDuplicationController', 'move', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?>
  • - user->hasProjectAccess('TaskStatusController', 'close', $task['project_id'])): ?> + projectRole->canChangeTaskStatusInColumn($task['project_id'], $task['column_id'])): ?>
  • diff --git a/app/Validator/ColumnRestrictionValidator.php b/app/Validator/ColumnRestrictionValidator.php new file mode 100644 index 000000000..b1b2e5a05 --- /dev/null +++ b/app/Validator/ColumnRestrictionValidator.php @@ -0,0 +1,40 @@ +execute(), + $v->getErrors() + ); + } +} diff --git a/tests/units/Helper/ProjectRoleHelperTest.php b/tests/units/Helper/ProjectRoleHelperTest.php index cae22d713..4583d58e3 100644 --- a/tests/units/Helper/ProjectRoleHelperTest.php +++ b/tests/units/Helper/ProjectRoleHelperTest.php @@ -4,8 +4,10 @@ use Kanboard\Core\Security\Role; use Kanboard\Core\User\UserSession; use Kanboard\Helper\ProjectRoleHelper; use Kanboard\Model\ColumnMoveRestrictionModel; +use Kanboard\Model\ColumnRestrictionModel; use Kanboard\Model\ProjectModel; use Kanboard\Model\ProjectRoleModel; +use Kanboard\Model\ProjectRoleRestrictionModel; use Kanboard\Model\ProjectUserRoleModel; use Kanboard\Model\TaskCreationModel; use Kanboard\Model\TaskFinderModel; @@ -16,6 +18,182 @@ require_once __DIR__.'/../Base.php'; class ProjectRoleHelperTest extends Base { + public function testCanCreateTaskInColumnWithProjectViewer() + { + $projectRoleHelper = new ProjectRoleHelper($this->container); + $projectModel = new ProjectModel($this->container); + $projectUserRole = new ProjectUserRoleModel($this->container); + $userModel = new UserModel($this->container); + + $this->container['sessionStorage']->user = array( + 'id' => 2, + 'role' => Role::APP_USER, + ); + + $this->assertEquals(2, $userModel->create(array('username' => 'user'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertTrue($projectUserRole->addUser(1, 2, Role::PROJECT_VIEWER)); + + $this->assertFalse($projectRoleHelper->canCreateTaskInColumn(1, 1)); + } + + public function testCanCreateTaskInColumnWithProjectMember() + { + $projectRoleHelper = new ProjectRoleHelper($this->container); + $projectModel = new ProjectModel($this->container); + $projectUserRole = new ProjectUserRoleModel($this->container); + $userModel = new UserModel($this->container); + + $this->container['sessionStorage']->user = array( + 'id' => 2, + 'role' => Role::APP_USER, + ); + + $this->assertEquals(2, $userModel->create(array('username' => 'user'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertTrue($projectUserRole->addUser(1, 2, Role::PROJECT_MEMBER)); + + $this->assertTrue($projectRoleHelper->canCreateTaskInColumn(1, 1)); + } + + public function testCanCreateTaskInColumnWithCustomProjectRole() + { + $projectRoleHelper = new ProjectRoleHelper($this->container); + $projectModel = new ProjectModel($this->container); + $projectUserRole = new ProjectUserRoleModel($this->container); + $userModel = new UserModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + + $this->container['sessionStorage']->user = array( + 'id' => 2, + 'role' => Role::APP_USER, + ); + + $this->assertEquals(2, $userModel->create(array('username' => 'user'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + + $this->assertEquals(1, $projectRoleModel->create(1, 'Custom Role')); + $this->assertTrue($projectUserRole->addUser(1, 2, 'Custom Role')); + + $this->assertTrue($projectRoleHelper->canCreateTaskInColumn(1, 1)); + } + + public function testCanCreateTaskInColumnWithCustomProjectRoleAndRestrictions() + { + $projectRoleHelper = new ProjectRoleHelper($this->container); + $projectModel = new ProjectModel($this->container); + $projectUserRole = new ProjectUserRoleModel($this->container); + $userModel = new UserModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + $projectRoleRestrictionModel = new ProjectRoleRestrictionModel($this->container); + $columnRestrictionModel = new ColumnRestrictionModel($this->container); + + $this->container['sessionStorage']->user = array( + 'id' => 2, + 'role' => Role::APP_USER, + ); + + $this->assertEquals(2, $userModel->create(array('username' => 'user'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + + $this->assertEquals(1, $projectRoleModel->create(1, 'Custom Role')); + $this->assertTrue($projectUserRole->addUser(1, 2, 'Custom Role')); + + $this->assertEquals(1, $projectRoleRestrictionModel->create(1, 1, ProjectRoleRestrictionModel::RULE_TASK_CREATION)); + $this->assertEquals(1, $columnRestrictionModel->create(1, 1, 1, ColumnRestrictionModel::RULE_ALLOW_TASK_CREATION)); + + $this->assertTrue($projectRoleHelper->canCreateTaskInColumn(1, 1)); + $this->assertFalse($projectRoleHelper->canCreateTaskInColumn(1, 2)); + } + + public function testCanChangeTaskStatusInColumnWithProjectViewer() + { + $projectRoleHelper = new ProjectRoleHelper($this->container); + $projectModel = new ProjectModel($this->container); + $projectUserRole = new ProjectUserRoleModel($this->container); + $userModel = new UserModel($this->container); + + $this->container['sessionStorage']->user = array( + 'id' => 2, + 'role' => Role::APP_USER, + ); + + $this->assertEquals(2, $userModel->create(array('username' => 'user'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertTrue($projectUserRole->addUser(1, 2, Role::PROJECT_VIEWER)); + + $this->assertFalse($projectRoleHelper->canChangeTaskStatusInColumn(1, 1)); + } + + public function testCanChangeTaskStatusInColumnWithProjectMember() + { + $projectRoleHelper = new ProjectRoleHelper($this->container); + $projectModel = new ProjectModel($this->container); + $projectUserRole = new ProjectUserRoleModel($this->container); + $userModel = new UserModel($this->container); + + $this->container['sessionStorage']->user = array( + 'id' => 2, + 'role' => Role::APP_USER, + ); + + $this->assertEquals(2, $userModel->create(array('username' => 'user'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertTrue($projectUserRole->addUser(1, 2, Role::PROJECT_MEMBER)); + + $this->assertTrue($projectRoleHelper->canChangeTaskStatusInColumn(1, 1)); + } + + public function testCanChangeTaskStatusInColumnWithCustomProjectRole() + { + $projectRoleHelper = new ProjectRoleHelper($this->container); + $projectModel = new ProjectModel($this->container); + $projectUserRole = new ProjectUserRoleModel($this->container); + $userModel = new UserModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + + $this->container['sessionStorage']->user = array( + 'id' => 2, + 'role' => Role::APP_USER, + ); + + $this->assertEquals(2, $userModel->create(array('username' => 'user'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + + $this->assertEquals(1, $projectRoleModel->create(1, 'Custom Role')); + $this->assertTrue($projectUserRole->addUser(1, 2, 'Custom Role')); + + $this->assertTrue($projectRoleHelper->canChangeTaskStatusInColumn(1, 1)); + } + + public function testCanChangeTaskStatusInColumnWithCustomProjectRoleAndRestrictions() + { + $projectRoleHelper = new ProjectRoleHelper($this->container); + $projectModel = new ProjectModel($this->container); + $projectUserRole = new ProjectUserRoleModel($this->container); + $userModel = new UserModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + $projectRoleRestrictionModel = new ProjectRoleRestrictionModel($this->container); + $columnRestrictionModel = new ColumnRestrictionModel($this->container); + + $this->container['sessionStorage']->user = array( + 'id' => 2, + 'role' => Role::APP_USER, + ); + + $this->assertEquals(2, $userModel->create(array('username' => 'user'))); + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + + $this->assertEquals(1, $projectRoleModel->create(1, 'Custom Role')); + $this->assertTrue($projectUserRole->addUser(1, 2, 'Custom Role')); + + $this->assertEquals(1, $projectRoleRestrictionModel->create(1, 1, ProjectRoleRestrictionModel::RULE_TASK_OPEN_CLOSE)); + $this->assertEquals(1, $columnRestrictionModel->create(1, 1, 1, ColumnRestrictionModel::RULE_ALLOW_TASK_OPEN_CLOSE)); + + $this->assertTrue($projectRoleHelper->canChangeTaskStatusInColumn(1, 1)); + $this->assertFalse($projectRoleHelper->canChangeTaskStatusInColumn(1, 2)); + } + public function testIsDraggableWithProjectMember() { $projectRoleHelper = new ProjectRoleHelper($this->container); diff --git a/tests/units/Model/ColumnRestrictionModelTest.php b/tests/units/Model/ColumnRestrictionModelTest.php new file mode 100644 index 000000000..6f66c258d --- /dev/null +++ b/tests/units/Model/ColumnRestrictionModelTest.php @@ -0,0 +1,100 @@ +container); + $projectRoleModel = new ProjectRoleModel($this->container); + $columnRestrictionModel = new ColumnRestrictionModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $projectRoleModel->create(1, 'my-custom-role')); + $this->assertEquals(1, $columnRestrictionModel->create(1, 1, 2, ColumnRestrictionModel::RULE_BLOCK_TASK_CREATION)); + } + + public function testRemove() + { + $projectModel = new ProjectModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + $projectRoleRestrictionModel = new ColumnRestrictionModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $projectRoleModel->create(1, 'my-custom-role')); + $this->assertEquals(1, $projectRoleRestrictionModel->create(1, 1, 2, ColumnRestrictionModel::RULE_ALLOW_TASK_OPEN_CLOSE)); + $this->assertTrue($projectRoleRestrictionModel->remove(1)); + $this->assertFalse($projectRoleRestrictionModel->remove(1)); + } + + public function testGetById() + { + $projectModel = new ProjectModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + $projectRoleRestrictionModel = new ColumnRestrictionModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $projectRoleModel->create(1, 'my-custom-role')); + $this->assertEquals(1, $projectRoleRestrictionModel->create(1, 1, 2, ColumnRestrictionModel::RULE_ALLOW_TASK_CREATION)); + + $restriction = $projectRoleRestrictionModel->getById(1, 1); + $this->assertEquals(ColumnRestrictionModel::RULE_ALLOW_TASK_CREATION, $restriction['rule']); + $this->assertEquals(1, $restriction['project_id']); + $this->assertEquals(1, $restriction['restriction_id']); + $this->assertEquals('Ready', $restriction['column_title']); + } + + public function testGetAll() + { + $projectModel = new ProjectModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + $projectRoleRestrictionModel = new ColumnRestrictionModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $projectRoleModel->create(1, 'my-custom-role')); + $this->assertEquals(1, $projectRoleRestrictionModel->create(1, 1, 2, ColumnRestrictionModel::RULE_ALLOW_TASK_CREATION)); + + $restrictions = $projectRoleRestrictionModel->getAll(1); + $this->assertCount(1, $restrictions); + $this->assertEquals(ColumnRestrictionModel::RULE_ALLOW_TASK_CREATION, $restrictions[0]['rule']); + $this->assertEquals(1, $restrictions[0]['project_id']); + $this->assertEquals(1, $restrictions[0]['restriction_id']); + $this->assertEquals(1, $restrictions[0]['role_id']); + $this->assertEquals(2, $restrictions[0]['column_id']); + $this->assertEquals('Ready', $restrictions[0]['column_title']); + } + + public function testGetByRole() + { + $projectModel = new ProjectModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + $columnRestrictionModel = new ColumnRestrictionModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $projectRoleModel->create(1, 'my-custom-role')); + $this->assertEquals(1, $columnRestrictionModel->create(1, 1, 2, ColumnRestrictionModel::RULE_BLOCK_TASK_CREATION)); + + $restrictions = $columnRestrictionModel->getAllByRole(1, 'my-custom-role'); + $this->assertCount(1, $restrictions); + $this->assertEquals(ColumnRestrictionModel::RULE_BLOCK_TASK_CREATION, $restrictions[0]['rule']); + $this->assertEquals(1, $restrictions[0]['project_id']); + $this->assertEquals(1, $restrictions[0]['restriction_id']); + $this->assertEquals(1, $restrictions[0]['role_id']); + $this->assertEquals(2, $restrictions[0]['column_id']); + $this->assertEquals('my-custom-role', $restrictions[0]['role']); + } + + public function testGetRules() + { + $columnRestrictionModel = new ColumnRestrictionModel($this->container); + $rules = $columnRestrictionModel->getRules(); + + $this->assertCount(4, $rules); + $this->assertArrayHasKey(ColumnRestrictionModel::RULE_ALLOW_TASK_CREATION, $rules); + } +} diff --git a/tests/units/Model/ProjectPermissionTest.php b/tests/units/Model/ProjectPermissionModelTest.php similarity index 99% rename from tests/units/Model/ProjectPermissionTest.php rename to tests/units/Model/ProjectPermissionModelTest.php index a9f8ab049..3313cf2dc 100644 --- a/tests/units/Model/ProjectPermissionTest.php +++ b/tests/units/Model/ProjectPermissionModelTest.php @@ -11,7 +11,7 @@ use Kanboard\Model\ProjectGroupRoleModel; use Kanboard\Model\ProjectUserRoleModel; use Kanboard\Core\Security\Role; -class ProjectPermissionTest extends Base +class ProjectPermissionModelTest extends Base { public function testFindByUsernames() { diff --git a/tests/units/Model/ProjectRoleModelTest.php b/tests/units/Model/ProjectRoleModelTest.php index 5dd78e20a..65208e6a0 100644 --- a/tests/units/Model/ProjectRoleModelTest.php +++ b/tests/units/Model/ProjectRoleModelTest.php @@ -41,14 +41,44 @@ class ProjectRoleModelTest extends Base $this->assertEquals('Role B', $roles[1]['role']); } - public function testModification() + public function testModificationWithUserRole() { $projectModel = new ProjectModel($this->container); $projectRoleModel = new ProjectRoleModel($this->container); + $projectUserRoleModel = new ProjectUserRoleModel($this->container); + $groupModel = new GroupModel($this->container); + $groupMemberModel = new GroupMemberModel($this->container); + + $this->assertEquals(1, $groupModel->create('Group A')); + $this->assertTrue($groupMemberModel->addUser(1, 1)); $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); $this->assertEquals(1, $projectRoleModel->create(1, 'Role A')); + $this->assertTrue($projectUserRoleModel->addUser(1, 1, 'Role A')); + $this->assertEquals('Role A', $projectUserRoleModel->getUserRole(1, 1)); + $this->assertTrue($projectRoleModel->update(1, 1, 'Role B')); + $this->assertEquals('Role B', $projectUserRoleModel->getUserRole(1, 1)); + } + + public function testModificationWithGroupRole() + { + $projectModel = new ProjectModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + $projectGroupRoleModel = new ProjectGroupRoleModel($this->container); + $groupModel = new GroupModel($this->container); + $groupMemberModel = new GroupMemberModel($this->container); + + $this->assertEquals(1, $groupModel->create('Group A')); + $this->assertTrue($groupMemberModel->addUser(1, 1)); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $projectRoleModel->create(1, 'Role A')); + $this->assertTrue($projectGroupRoleModel->addGroup(1, 1, 'Role A')); + $this->assertEquals('Role A', $projectGroupRoleModel->getUserRole(1, 1)); + + $this->assertTrue($projectRoleModel->update(1, 1, 'Role B')); + $this->assertEquals('Role B', $projectGroupRoleModel->getUserRole(1, 1)); } public function testRemoveWithUserRole() diff --git a/tests/units/Model/ProjectRoleRestrictionModelTest.php b/tests/units/Model/ProjectRoleRestrictionModelTest.php new file mode 100644 index 000000000..07473061c --- /dev/null +++ b/tests/units/Model/ProjectRoleRestrictionModelTest.php @@ -0,0 +1,96 @@ +container); + $projectRoleModel = new ProjectRoleModel($this->container); + $projectRoleRestrictionModel = new ProjectRoleRestrictionModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $projectRoleModel->create(1, 'my-custom-role')); + $this->assertEquals(1, $projectRoleRestrictionModel->create(1, 1, ProjectRoleRestrictionModel::RULE_TASK_CREATION)); + } + + public function testRemove() + { + $projectModel = new ProjectModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + $projectRoleRestrictionModel = new ProjectRoleRestrictionModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $projectRoleModel->create(1, 'my-custom-role')); + $this->assertEquals(1, $projectRoleRestrictionModel->create(1, 1, ProjectRoleRestrictionModel::RULE_TASK_CREATION)); + $this->assertTrue($projectRoleRestrictionModel->remove(1)); + $this->assertFalse($projectRoleRestrictionModel->remove(1)); + } + + public function testGetById() + { + $projectModel = new ProjectModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + $projectRoleRestrictionModel = new ProjectRoleRestrictionModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $projectRoleModel->create(1, 'my-custom-role')); + $this->assertEquals(1, $projectRoleRestrictionModel->create(1, 1, ProjectRoleRestrictionModel::RULE_TASK_CREATION)); + + $restriction = $projectRoleRestrictionModel->getById(1, 1); + $this->assertEquals(ProjectRoleRestrictionModel::RULE_TASK_CREATION, $restriction['rule']); + $this->assertEquals(1, $restriction['project_id']); + $this->assertEquals(1, $restriction['restriction_id']); + } + + public function testGetAll() + { + $projectModel = new ProjectModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + $projectRoleRestrictionModel = new ProjectRoleRestrictionModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $projectRoleModel->create(1, 'my-custom-role')); + $this->assertEquals(1, $projectRoleRestrictionModel->create(1, 1, ProjectRoleRestrictionModel::RULE_TASK_CREATION)); + + $restrictions = $projectRoleRestrictionModel->getAll(1); + $this->assertCount(1, $restrictions); + $this->assertEquals(ProjectRoleRestrictionModel::RULE_TASK_CREATION, $restrictions[0]['rule']); + $this->assertEquals(1, $restrictions[0]['project_id']); + $this->assertEquals(1, $restrictions[0]['restriction_id']); + $this->assertEquals(1, $restrictions[0]['role_id']); + } + + public function testGetByRole() + { + $projectModel = new ProjectModel($this->container); + $projectRoleModel = new ProjectRoleModel($this->container); + $projectRoleRestrictionModel = new ProjectRoleRestrictionModel($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $projectRoleModel->create(1, 'my-custom-role')); + $this->assertEquals(1, $projectRoleRestrictionModel->create(1, 1, ProjectRoleRestrictionModel::RULE_TASK_CREATION)); + + $restrictions = $projectRoleRestrictionModel->getAllByRole(1, 'my-custom-role'); + $this->assertCount(1, $restrictions); + $this->assertEquals(ProjectRoleRestrictionModel::RULE_TASK_CREATION, $restrictions[0]['rule']); + $this->assertEquals(1, $restrictions[0]['project_id']); + $this->assertEquals(1, $restrictions[0]['restriction_id']); + $this->assertEquals(1, $restrictions[0]['role_id']); + $this->assertEquals('my-custom-role', $restrictions[0]['role']); + } + + public function testGetRules() + { + $projectRoleRestrictionModel = new ProjectRoleRestrictionModel($this->container); + $rules = $projectRoleRestrictionModel->getRules(); + + $this->assertCount(2, $rules); + $this->assertArrayHasKey(ProjectRoleRestrictionModel::RULE_TASK_OPEN_CLOSE, $rules); + } +} diff --git a/tests/units/Model/TaskFinderModelTest.php b/tests/units/Model/TaskFinderModelTest.php index a9f018ff5..9e0369ce9 100644 --- a/tests/units/Model/TaskFinderModelTest.php +++ b/tests/units/Model/TaskFinderModelTest.php @@ -89,6 +89,7 @@ class TaskFinderModelTest extends Base $tasks = $taskFinderModel->getUserQuery(1)->findAll(); $this->assertCount(1, $tasks); $this->assertEquals('Task #1', $tasks[0]['title']); + $this->assertEquals(1, $tasks[0]['column_id']); $this->assertTrue($columnModel->update(2, 'Test', 0, '', 0));