From b8fa0246803dab40cf57d40b45984c53046f2d55 Mon Sep 17 00:00:00 2001 From: "Dzial Techniczny WMW Projekt s.c" Date: Tue, 10 Dec 2019 11:34:53 +0100 Subject: Plugins directory and local modifications --- plugins/Group_assign/.travis.yml | 30 ++ plugins/Group_assign/Action/AssignGroup.php | 95 ++++ plugins/Group_assign/Action/EmailGroup.php | 80 ++++ plugins/Group_assign/Action/EmailGroupDue.php | 116 +++++ .../Group_assign/Action/EmailOtherAssignees.php | 80 ++++ .../Group_assign/Action/EmailOtherAssigneesDue.php | 116 +++++ plugins/Group_assign/Assets/css/group_assign.css | 17 + plugins/Group_assign/Assets/js/group_assign.js | 5 + .../GroupAssignTaskCreationController.php | 175 ++++++++ .../GroupAssignTaskModificationController.php | 208 +++++++++ .../Group_assign/Filter/TaskAllAssigneeFilter.php | 122 +++++ plugins/Group_assign/Helper/NewTaskHelper.php | 389 ++++++++++++++++ .../Helper/SmallAvatarHelperExtend.php | 35 ++ plugins/Group_assign/LICENSE | 21 + plugins/Group_assign/Locale/de_DE/translations.php | 13 + plugins/Group_assign/Locale/pt_BR/translations.php | 97 ++++ .../Model/GroupAssignCalendarModel.php | 89 ++++ .../Model/GroupAssignTaskDuplicationModel.php | 175 ++++++++ plugins/Group_assign/Model/GroupColorExtension.php | 28 ++ .../Group_assign/Model/MultiselectMemberModel.php | 169 +++++++ plugins/Group_assign/Model/MultiselectModel.php | 143 ++++++ .../Group_assign/Model/NewMetaMagikSubquery.php | 28 ++ plugins/Group_assign/Model/NewTaskFinderModel.php | 491 +++++++++++++++++++++ .../Model/NewUserNotificationFilterModel.php | 218 +++++++++ .../Group_assign/Model/OldMetaMagikSubquery.php | 28 ++ plugins/Group_assign/Model/OldTaskFinderModel.php | 489 ++++++++++++++++++++ .../Model/TaskProjectDuplicationModel.php | 94 ++++ .../Group_assign/Model/TaskProjectMoveModel.php | 96 ++++ plugins/Group_assign/Model/TaskRecurrenceModel.php | 154 +++++++ plugins/Group_assign/Plugin.php | 200 +++++++++ plugins/Group_assign/README.md | 95 ++++ plugins/Group_assign/Schema/Mysql.php | 34 ++ plugins/Group_assign/Schema/Postgres.php | 34 ++ plugins/Group_assign/Schema/Sqlite.php | 34 ++ .../Template/action_creation/params.php | 73 +++ plugins/Group_assign/Template/board/filter.php | 12 + plugins/Group_assign/Template/board/group.php | 7 + plugins/Group_assign/Template/board/multi.php | 5 + plugins/Group_assign/Template/config/toggle.php | 4 + .../Group_assign/Template/header/user_dropdown.php | 46 ++ plugins/Group_assign/Template/task/changes.php | 92 ++++ plugins/Group_assign/Template/task/details.php | 10 + plugins/Group_assign/Template/task/multi.php | 6 + .../Group_assign/Template/task_creation/show.php | 50 +++ .../Template/task_modification/show.php | 43 ++ .../Group_assign/Test/Helper/NewTaskHelperTest.php | 34 ++ .../Test/Model/NewTaskFinderModelTest.php | 184 ++++++++ plugins/Group_assign/Test/PluginTest.php | 19 + plugins/Group_assign/_config.yml | 3 + 49 files changed, 4786 insertions(+) create mode 100644 plugins/Group_assign/.travis.yml create mode 100644 plugins/Group_assign/Action/AssignGroup.php create mode 100644 plugins/Group_assign/Action/EmailGroup.php create mode 100644 plugins/Group_assign/Action/EmailGroupDue.php create mode 100644 plugins/Group_assign/Action/EmailOtherAssignees.php create mode 100644 plugins/Group_assign/Action/EmailOtherAssigneesDue.php create mode 100644 plugins/Group_assign/Assets/css/group_assign.css create mode 100644 plugins/Group_assign/Assets/js/group_assign.js create mode 100644 plugins/Group_assign/Controller/GroupAssignTaskCreationController.php create mode 100644 plugins/Group_assign/Controller/GroupAssignTaskModificationController.php create mode 100644 plugins/Group_assign/Filter/TaskAllAssigneeFilter.php create mode 100644 plugins/Group_assign/Helper/NewTaskHelper.php create mode 100644 plugins/Group_assign/Helper/SmallAvatarHelperExtend.php create mode 100644 plugins/Group_assign/LICENSE create mode 100644 plugins/Group_assign/Locale/de_DE/translations.php create mode 100644 plugins/Group_assign/Locale/pt_BR/translations.php create mode 100644 plugins/Group_assign/Model/GroupAssignCalendarModel.php create mode 100644 plugins/Group_assign/Model/GroupAssignTaskDuplicationModel.php create mode 100644 plugins/Group_assign/Model/GroupColorExtension.php create mode 100644 plugins/Group_assign/Model/MultiselectMemberModel.php create mode 100644 plugins/Group_assign/Model/MultiselectModel.php create mode 100644 plugins/Group_assign/Model/NewMetaMagikSubquery.php create mode 100644 plugins/Group_assign/Model/NewTaskFinderModel.php create mode 100644 plugins/Group_assign/Model/NewUserNotificationFilterModel.php create mode 100644 plugins/Group_assign/Model/OldMetaMagikSubquery.php create mode 100644 plugins/Group_assign/Model/OldTaskFinderModel.php create mode 100644 plugins/Group_assign/Model/TaskProjectDuplicationModel.php create mode 100644 plugins/Group_assign/Model/TaskProjectMoveModel.php create mode 100644 plugins/Group_assign/Model/TaskRecurrenceModel.php create mode 100644 plugins/Group_assign/Plugin.php create mode 100644 plugins/Group_assign/README.md create mode 100644 plugins/Group_assign/Schema/Mysql.php create mode 100644 plugins/Group_assign/Schema/Postgres.php create mode 100644 plugins/Group_assign/Schema/Sqlite.php create mode 100644 plugins/Group_assign/Template/action_creation/params.php create mode 100644 plugins/Group_assign/Template/board/filter.php create mode 100644 plugins/Group_assign/Template/board/group.php create mode 100644 plugins/Group_assign/Template/board/multi.php create mode 100644 plugins/Group_assign/Template/config/toggle.php create mode 100644 plugins/Group_assign/Template/header/user_dropdown.php create mode 100644 plugins/Group_assign/Template/task/changes.php create mode 100644 plugins/Group_assign/Template/task/details.php create mode 100644 plugins/Group_assign/Template/task/multi.php create mode 100644 plugins/Group_assign/Template/task_creation/show.php create mode 100644 plugins/Group_assign/Template/task_modification/show.php create mode 100644 plugins/Group_assign/Test/Helper/NewTaskHelperTest.php create mode 100644 plugins/Group_assign/Test/Model/NewTaskFinderModelTest.php create mode 100644 plugins/Group_assign/Test/PluginTest.php create mode 100644 plugins/Group_assign/_config.yml (limited to 'plugins/Group_assign') diff --git a/plugins/Group_assign/.travis.yml b/plugins/Group_assign/.travis.yml new file mode 100644 index 00000000..6e29a9fc --- /dev/null +++ b/plugins/Group_assign/.travis.yml @@ -0,0 +1,30 @@ +language: php +sudo: false + +php: + - 7.2 + +env: + global: + - PLUGIN=Group_assign + - KANBOARD_REPO=https://github.com/kanboard/kanboard.git + matrix: + - DB=sqlite + - DB=mysql + - DB=postgres + +matrix: + fast_finish: true + +install: + - git clone --depth 1 $KANBOARD_REPO + - ln -s $TRAVIS_BUILD_DIR kanboard/plugins/$PLUGIN + +before_script: + - cd kanboard + - phpenv config-add tests/php.ini + - composer install + - ls -la plugins/ + +script: + - phpunit -c tests/units.$DB.xml plugins/$PLUGIN/Test/ diff --git a/plugins/Group_assign/Action/AssignGroup.php b/plugins/Group_assign/Action/AssignGroup.php new file mode 100644 index 00000000..4a63696b --- /dev/null +++ b/plugins/Group_assign/Action/AssignGroup.php @@ -0,0 +1,95 @@ + t('Column'), + 'group_id' => t('Group'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + ), + ); + } + + /** + * Execute the action (assign the given user) + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function doAction(array $data) + { + $values = array( + 'id' => $data['task_id'], + 'owner_gp' => $this->getParam('group_id'), + ); + + return $this->taskModificationModel->update($values); + } + + /** + * Check if the event data meet the action condition + * + * @access public + * @param array $data Event data dictionary + * @return bool + */ + public function hasRequiredCondition(array $data) + { + return $data['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/plugins/Group_assign/Action/EmailGroup.php b/plugins/Group_assign/Action/EmailGroup.php new file mode 100644 index 00000000..6d12bc73 --- /dev/null +++ b/plugins/Group_assign/Action/EmailGroup.php @@ -0,0 +1,80 @@ + t('Column'), + 'subject' => t('Email subject'), + ); + } + + + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + 'owner_id', + 'owner_gp', + ), + ); + } + + + public function doAction(array $data) + { + $groupmembers = $this->groupMemberModel->getMembers($data['task']['owner_gp']); + + if (! empty($groupmembers)) { + foreach ($groupmembers as $members) { + $user = $this->userModel->getById($members['id']); + if (! empty($user['email'])) { + $this->emailClient->send( + $user['email'], + $user['name'] ?: $user['username'], + $this->getParam('subject'), + $this->template->render('notification/task_create', array( + 'task' => $data['task'], + )) + ); + } + } + return true; + } + + return false; + } + + + + public function hasRequiredCondition(array $data) + { + return $data['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/plugins/Group_assign/Action/EmailGroupDue.php b/plugins/Group_assign/Action/EmailGroupDue.php new file mode 100644 index 00000000..8f1a66a7 --- /dev/null +++ b/plugins/Group_assign/Action/EmailGroupDue.php @@ -0,0 +1,116 @@ + t('Email subject'), + 'duration' => t('Duration in days'), + ); + } + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array('tasks'); + + } + /** + * Check if the event data meet the action condition + * + * @access public + * @param array $data Event data dictionary + * @return bool + */ + public function hasRequiredCondition(array $data) + { + return count($data['tasks']) > 0; + } + + public function doAction(array $data) + { + $results = array(); + $max = $this->getParam('duration') * 86400; + + foreach ($data['tasks'] as $task) { + $groupmembers = $this->groupMemberModel->getMembers($task['owner_gp']); + + if (! empty($groupmembers)) { + foreach ($groupmembers as $members) { + $user = $this->userModel->getById($members['id']); + + $duration = $task['date_due'] - time(); + if ($task['date_due'] > 0) { + if ($duration < $max) { + if (! empty($user['email'])) { + $results[] = $this->sendEmail($task['id'], $user); + } + } + } + } + } + } + + return in_array(true, $results, true); + } + /** + * Send email + * + * @access private + * @param integer $task_id + * @param array $user + * @return boolean + */ + private function sendEmail($task_id, array $user) + { + $task = $this->taskFinderModel->getDetails($task_id); + $this->emailClient->send( + $user['email'], + $user['name'] ?: $user['username'], + $this->getParam('subject'), + $this->template->render('notification/task_create', array('task' => $task)) + ); + return true; + } +} diff --git a/plugins/Group_assign/Action/EmailOtherAssignees.php b/plugins/Group_assign/Action/EmailOtherAssignees.php new file mode 100644 index 00000000..f9ab091c --- /dev/null +++ b/plugins/Group_assign/Action/EmailOtherAssignees.php @@ -0,0 +1,80 @@ + t('Column'), + 'subject' => t('Email subject'), + ); + } + + + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + 'owner_id', + 'owner_ms', + ), + ); + } + + + public function doAction(array $data) + { + $multimembers = $this->multiselectMemberModel->getMembers($data['task']['owner_ms']); + + if (! empty($multimembers)) { + foreach ($multimembers as $members) { + $user = $this->userModel->getById($members['id']); + if (! empty($user['email'])) { + $this->emailClient->send( + $user['email'], + $user['name'] ?: $user['username'], + $this->getParam('subject'), + $this->template->render('notification/task_create', array( + 'task' => $data['task'], + )) + ); + } + } + return true; + } + + return false; + } + + + + public function hasRequiredCondition(array $data) + { + return $data['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/plugins/Group_assign/Action/EmailOtherAssigneesDue.php b/plugins/Group_assign/Action/EmailOtherAssigneesDue.php new file mode 100644 index 00000000..abd15003 --- /dev/null +++ b/plugins/Group_assign/Action/EmailOtherAssigneesDue.php @@ -0,0 +1,116 @@ + t('Email subject'), + 'duration' => t('Duration in days'), + ); + } + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array('tasks'); + + } + /** + * Check if the event data meet the action condition + * + * @access public + * @param array $data Event data dictionary + * @return bool + */ + public function hasRequiredCondition(array $data) + { + return count($data['tasks']) > 0; + } + + public function doAction(array $data) + { + $results = array(); + $max = $this->getParam('duration') * 86400; + + foreach ($data['tasks'] as $task) { + $groupmembers = $this->multiselectMemberModel->getMembers($task['owner_ms']); + + if (! empty($groupmembers)) { + foreach ($groupmembers as $members) { + $user = $this->userModel->getById($members['id']); + + $duration = $task['date_due'] - time(); + if ($task['date_due'] > 0) { + if ($duration < $max) { + if (! empty($user['email'])) { + $results[] = $this->sendEmail($task['id'], $user); + } + } + } + } + } + } + + return in_array(true, $results, true); + } + /** + * Send email + * + * @access private + * @param integer $task_id + * @param array $user + * @return boolean + */ + private function sendEmail($task_id, array $user) + { + $task = $this->taskFinderModel->getDetails($task_id); + $this->emailClient->send( + $user['email'], + $user['name'] ?: $user['username'], + $this->getParam('subject'), + $this->template->render('notification/task_create', array('task' => $task)) + ); + return true; + } +} diff --git a/plugins/Group_assign/Assets/css/group_assign.css b/plugins/Group_assign/Assets/css/group_assign.css new file mode 100644 index 00000000..e5693f83 --- /dev/null +++ b/plugins/Group_assign/Assets/css/group_assign.css @@ -0,0 +1,17 @@ +.avatar-13 .avatar-letter { + border-radius: 6.5px; + line-height: 13px; + width: 13px; + font-size: 7px; +} +.avatar-13 img { + border-radius: 6.5px; +} + +.assigned-group { + display: inline-block; + margin: 3px 3px 0 0; + padding: 1px 3px 1px 3px; + color: #333; + border-radius: 4px; +} diff --git a/plugins/Group_assign/Assets/js/group_assign.js b/plugins/Group_assign/Assets/js/group_assign.js new file mode 100644 index 00000000..daf47e89 --- /dev/null +++ b/plugins/Group_assign/Assets/js/group_assign.js @@ -0,0 +1,5 @@ + + KB.on('modal.afterRender', function () { + $(".group-assign-select").select2({ + }); + }); \ No newline at end of file diff --git a/plugins/Group_assign/Controller/GroupAssignTaskCreationController.php b/plugins/Group_assign/Controller/GroupAssignTaskCreationController.php new file mode 100644 index 00000000..f7dadbee --- /dev/null +++ b/plugins/Group_assign/Controller/GroupAssignTaskCreationController.php @@ -0,0 +1,175 @@ +getProject(); + $swimlanesList = $this->swimlaneModel->getList($project['id'], false, true); + $values += $this->prepareValues($project['is_private'], $swimlanesList); + + $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values)); + $values = $this->hook->merge('controller:task-creation:form:default', $values, array('default_values' => $values)); + + $this->response->html($this->template->render('task_creation/show', array( + 'project' => $project, + 'errors' => $errors, + 'values' => $values + array('project_id' => $project['id']), + 'columns_list' => $this->columnModel->getList($project['id']), + 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id'], true, false, $project['is_private'] == 1), + 'categories_list' => $this->categoryModel->getList($project['id']), + 'swimlanes_list' => $swimlanesList, + ))); + } + + /** + * Validate and save a new task + * + * @access public + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + $values['project_id'] = $project['id']; + if (isset($values['owner_ms']) && !empty($values['owner_ms'])) { + $ms_id = $this->multiselectModel->create(); + foreach ($values['owner_ms'] as $user) { + $this->multiselectMemberModel->addUser($ms_id, $user); + } + unset($values['owner_ms']); + $values['owner_ms'] = $ms_id; + } + + list($valid, $errors) = $this->taskValidator->validateCreation($values); + + 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); + + if ($task_id > 0) { + $this->flash->success(t('Task created successfully.')); + $this->afterSave($project, $values, $task_id); + } else { + $this->flash->failure(t('Unable to create this task.')); + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true); + } + } + } + + /** + * Duplicate created tasks to multiple projects + * + * @throws PageNotFoundException + */ + public function duplicateProjects() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + if (isset($values['project_ids'])) { + foreach ($values['project_ids'] as $project_id) { + $this->taskProjectDuplicationModel->duplicateToProject($values['task_id'], $project_id); + } + } + + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true); + } + + /** + * Executed after the task is saved + * + * @param array $project + * @param array $values + * @param integer $task_id + */ + protected function afterSave(array $project, array &$values, $task_id) + { + if (isset($values['duplicate_multiple_projects']) && $values['duplicate_multiple_projects'] == 1) { + $this->chooseProjects($project, $task_id); + } elseif (isset($values['another_task']) && $values['another_task'] == 1) { + $this->show(array( + 'owner_id' => $values['owner_id'], + 'color_id' => $values['color_id'], + 'category_id' => isset($values['category_id']) ? $values['category_id'] : 0, + 'column_id' => $values['column_id'], + 'swimlane_id' => isset($values['swimlane_id']) ? $values['swimlane_id'] : 0, + 'another_task' => 1, + )); + } else { + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true); + } + } + + /** + * Prepare form values + * + * @access protected + * @param bool $isPrivateProject + * @param array $swimlanesList + * @return array + */ + protected function prepareValues($isPrivateProject, array $swimlanesList) + { + $values = array( + 'swimlane_id' => $this->request->getIntegerParam('swimlane_id', key($swimlanesList)), + 'column_id' => $this->request->getIntegerParam('column_id'), + 'color_id' => $this->colorModel->getDefaultColor(), + ); + + if ($isPrivateProject) { + $values['owner_id'] = $this->userSession->getId(); + } + + return $values; + } + + /** + * Choose projects + * + * @param array $project + * @param integer $task_id + */ + protected function chooseProjects(array $project, $task_id) + { + $task = $this->taskFinderModel->getById($task_id); + $projects = $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId()); + unset($projects[$project['id']]); + + $this->response->html($this->template->render('task_creation/duplicate_projects', array( + 'project' => $project, + 'task' => $task, + 'projects_list' => $projects, + 'values' => array('task_id' => $task['id']) + ))); + } +} diff --git a/plugins/Group_assign/Controller/GroupAssignTaskModificationController.php b/plugins/Group_assign/Controller/GroupAssignTaskModificationController.php new file mode 100644 index 00000000..b975c3d9 --- /dev/null +++ b/plugins/Group_assign/Controller/GroupAssignTaskModificationController.php @@ -0,0 +1,208 @@ +getTask(); + $values = ['id' => $task['id'], 'owner_id' => $this->userSession->getId()]; + + if (! $this->helper->projectRole->canUpdateTask($task)) { + throw new AccessForbiddenException(t('You are not allowed to update tasks assigned to someone else.')); + } + + $this->taskModificationModel->update($values); + $this->redirectAfterQuickAction($task); + } + + /** + * Set the start date automatically + * + * @access public + */ + public function start() + { + $task = $this->getTask(); + $values = ['id' => $task['id'], 'date_started' => time()]; + + if (! $this->helper->projectRole->canUpdateTask($task)) { + throw new AccessForbiddenException(t('You are not allowed to update tasks assigned to someone else.')); + } + + $this->taskModificationModel->update($values); + $this->redirectAfterQuickAction($task); + } + + protected function redirectAfterQuickAction(array $task) + { + switch ($this->request->getStringParam('redirect')) { + case 'board': + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', ['project_id' => $task['project_id']])); + break; + case 'list': + $this->response->redirect($this->helper->url->to('TaskListController', 'show', ['project_id' => $task['project_id']])); + break; + case 'dashboard': + $this->response->redirect($this->helper->url->to('DashboardController', 'show', [], 'project-tasks-'.$task['project_id'])); + break; + case 'dashboard-tasks': + $this->response->redirect($this->helper->url->to('DashboardController', 'tasks', ['user_id' => $this->userSession->getId()])); + break; + default: + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', ['project_id' => $task['project_id'], 'task_id' => $task['id']])); + } + } + + /** + * Display a form to edit a task + * + * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function edit(array $values = array(), array $errors = array()) + { + $task = $this->getTask(); + + if (! $this->helper->projectRole->canUpdateTask($task)) { + throw new AccessForbiddenException(t('You are not allowed to update tasks assigned to someone else.')); + } + + $project = $this->projectModel->getById($task['project_id']); + + if (empty($values)) { + $values = $task; + } + + $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values)); + $values = $this->hook->merge('controller:task-modification:form:default', $values, array('default_values' => $values)); + + $params = array( + 'project' => $project, + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'tags' => $this->taskTagModel->getList($task['id']), + 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($task['project_id']), + 'categories_list' => $this->categoryModel->getList($task['project_id']), + ); + + $this->renderTemplate($task, $params); + } + + protected function renderTemplate(array &$task, array &$params) + { + if (empty($task['external_uri'])) { + $this->response->html($this->template->render('task_modification/show', $params)); + } else { + + try { + $taskProvider = $this->externalTaskManager->getProvider($task['external_provider']); + $params['template'] = $taskProvider->getModificationFormTemplate(); + $params['external_task'] = $taskProvider->fetch($task['external_uri']); + } catch (ExternalTaskAccessForbiddenException $e) { + throw new AccessForbiddenException($e->getMessage()); + } catch (ExternalTaskException $e) { + $params['error_message'] = $e->getMessage(); + } + + $this->response->html($this->template->render('external_task_modification/show', $params)); + } + } + + /** + * Validate and update a task + * + * @access public + */ + public function update() + { + $previousMembers = array(); + $task = $this->getTask(); + $values = $this->request->getValues(); + $values['id'] = $task['id']; + $values['project_id'] = $task['project_id']; + if (isset($values['owner_ms']) && !empty($values['owner_ms'])) { + if (!empty($task['owner_ms'])) { + $ms_id = $task['owner_ms']; + $previousMembers = $this->multiselectMemberModel->getMembers($ms_id); + $this->multiselectMemberModel->removeAllUsers($ms_id); + } else { + $ms_id = $this->multiselectModel->create(); + } + foreach ($values['owner_ms'] as $user) { + if ($user !== 0) { $this->multiselectMemberModel->addUser($ms_id, $user); } + } + unset($values['owner_ms']); + $values['owner_ms'] = $ms_id; + + $newMembersSet = $this->multiselectMemberModel->getMembers($values['owner_ms']); + if (sort($previousMembers) !== sort($newMembersSet)) { $this->multiselectMemberModel->assigneeChanged($task, $values); } + + if ($values['owner_gp'] !== $task['owner_gp']) { $this->multiselectMemberModel->assigneeChanged($task, $values); } + } else { + $this->multiselectMemberModel->removeAllUsers($task['owner_ms']); + } + + list($valid, $errors) = $this->taskValidator->validateModification($values); + + if ($valid && $this->updateTask($task, $values, $errors)) { + $this->flash->success(t('Task updated successfully.')); + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), true); + } else { + $this->flash->failure(t('Unable to update your task.')); + $this->edit($values, $errors); + } + } + + protected function updateTask(array &$task, array &$values, array &$errors) + { + if (isset($values['owner_id']) && $values['owner_id'] != $task['owner_id'] && !$this->helper->projectRole->canChangeAssignee($task)) { + throw new AccessForbiddenException(t('You are not allowed to change the assignee.')); + } + + if (! $this->helper->projectRole->canUpdateTask($task)) { + throw new AccessForbiddenException(t('You are not allowed to update tasks assigned to someone else.')); + } + + $result = $this->taskModificationModel->update($values); + + if ($result && ! empty($task['external_uri'])) { + try { + $taskProvider = $this->externalTaskManager->getProvider($task['external_provider']); + $result = $taskProvider->save($task['external_uri'], $values, $errors); + } catch (ExternalTaskAccessForbiddenException $e) { + throw new AccessForbiddenException($e->getMessage()); + } catch (ExternalTaskException $e) { + $this->logger->error($e->getMessage()); + $result = false; + } + } + + return $result; + } +} diff --git a/plugins/Group_assign/Filter/TaskAllAssigneeFilter.php b/plugins/Group_assign/Filter/TaskAllAssigneeFilter.php new file mode 100644 index 00000000..e7ffc06f --- /dev/null +++ b/plugins/Group_assign/Filter/TaskAllAssigneeFilter.php @@ -0,0 +1,122 @@ +db = $db; + return $this; + } + + /** + * Current user id + * + * @access private + * @var int + */ + private $currentUserId = 0; + + /** + * Set current user id + * + * @access public + * @param integer $userId + * @return TaskAssigneeFilter + */ + public function setCurrentUserId($userId) + { + $this->currentUserId = $userId; + return $this; + } + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('allassignees'); + } + + /** + * Apply filter + * + * @access public + * @return string + */ + public function apply() + { + if (is_int($this->value) || ctype_digit($this->value)) { + $this->query->beginOr(); + $this->query->eq(TaskModel::TABLE.'.owner_id', $this->value); + $this->query->addCondition(TaskModel::TABLE.".owner_gp IN (SELECT group_id FROM ".GroupMemberModel::TABLE." WHERE ".GroupMemberModel::TABLE.".user_id='$this->value')"); + $this->query->addCondition(TaskModel::TABLE.".owner_ms IN (SELECT group_id FROM ".MultiselectMemberModel::TABLE." WHERE ".MultiselectMemberModel::TABLE.".user_id='$this->value')"); + $this->query->closeOr(); + } else { + switch ($this->value) { + case 'me': + $this->query->beginOr(); + $this->query->eq(TaskModel::TABLE.'.owner_id', $this->currentUserId); + $this->query->addCondition(TaskModel::TABLE.".owner_gp IN (SELECT group_id FROM ".GroupMemberModel::TABLE." WHERE ".GroupMemberModel::TABLE.".user_id='$this->currentUserId')"); + $this->query->addCondition(TaskModel::TABLE.".owner_ms IN (SELECT group_id FROM ".MultiselectMemberModel::TABLE." WHERE ".MultiselectMemberModel::TABLE.".user_id='$this->currentUserId')"); + $this->query->closeOr(); + break; + case 'nobody': + $this->query->eq(TaskModel::TABLE.'.owner_id', 0); + break; + default: + $useridsarray = $this->getSubQuery()->findAllByColumn('id'); + $useridstring = implode("','", $useridsarray); + $this->query->beginOr(); + $this->query->ilike(UserModel::TABLE.'.username', '%'.$this->value.'%'); + $this->query->ilike(UserModel::TABLE.'.name', '%'.$this->value.'%'); + $this->query->addCondition(TaskModel::TABLE.".owner_gp IN (SELECT id FROM ".GroupModel::TABLE." WHERE ".GroupModel::TABLE.".name='$this->value')"); + $this->query->addCondition(TaskModel::TABLE.".owner_gp IN (SELECT group_id FROM ".GroupMemberModel::TABLE." WHERE ".GroupMemberModel::TABLE.".user_id IN ('$useridstring'))"); + $this->query->addCondition(TaskModel::TABLE.".owner_ms IN (SELECT group_id FROM ".MultiselectMemberModel::TABLE." WHERE ".MultiselectMemberModel::TABLE.".user_id IN ('$useridstring'))"); + $this->query->closeOr(); + } + } + } + public function getSubQuery() + { + return $this->db->table(UserModel::TABLE) + ->columns( + UserModel::TABLE.'.id', + UserModel::TABLE.'.username', + UserModel::TABLE.'.name' + ) + ->beginOr() + ->ilike(UserModel::TABLE.'.username', '%'.$this->value.'%') + ->ilike(UserModel::TABLE.'.name', '%'.$this->value.'%') + ->closeOr(); + } + +} diff --git a/plugins/Group_assign/Helper/NewTaskHelper.php b/plugins/Group_assign/Helper/NewTaskHelper.php new file mode 100644 index 00000000..b15262a1 --- /dev/null +++ b/plugins/Group_assign/Helper/NewTaskHelper.php @@ -0,0 +1,389 @@ +colorModel->getList(); + } + + public function recurrenceTriggers() + { + return $this->taskRecurrenceModel->getRecurrenceTriggerList(); + } + + public function recurrenceTimeframes() + { + return $this->taskRecurrenceModel->getRecurrenceTimeframeList(); + } + + public function recurrenceBasedates() + { + return $this->taskRecurrenceModel->getRecurrenceBasedateList(); + } + + public function renderTitleField(array $values, array $errors) + { + return $this->helper->form->text( + 'title', + $values, + $errors, + array( + 'autofocus', + 'required', + 'maxlength="200"', + 'tabindex="1"', + 'placeholder="'.t('Title').'"' + ) + ); + } + + public function renderDescriptionField(array $values, array $errors) + { + return $this->helper->form->textEditor('description', $values, $errors, array('tabindex' => 2)); + } + + public function renderDescriptionTemplateDropdown($projectId) + { + $templates = $this->predefinedTaskDescriptionModel->getAll($projectId); + + if (! empty($templates)) { + $html = ''; + return $html; + } + + return ''; + } + + public function renderTagField(array $project, array $tags = array()) + { + $options = $this->tagModel->getAssignableList($project['id']); + + $html = $this->helper->form->label(t('Tags'), 'tags[]'); + $html .= ''; + $html .= ''; + + return $html; + } + + public function renderColorField(array $values) + { + $colors = $this->colorModel->getList(); + $html = $this->helper->form->label(t('Color'), 'color_id'); + $html .= $this->helper->form->select('color_id', $colors, $values, array(), array(), 'color-picker'); + return $html; + } + + public function renderAssigneeField(array $users, array $values, array $errors = array(), array $attributes = array()) + { + if (isset($values['project_id']) && ! $this->helper->projectRole->canChangeAssignee($values)) { + return ''; + } + + $attributes = array_merge(array('tabindex="3"'), $attributes); + + $html = $this->helper->form->label(t('Assignee'), 'owner_id'); + $html .= $this->helper->form->select('owner_id', $users, $values, $errors, $attributes); + $html .= ' '; + $html .= ''; + $html .= ''.t('Me').''; + $html .= ''; + + return $html; + } + + public function renderMultiAssigneeField(array $users, array $values, array $errors = array(), array $attributes = array()) + { + if (isset($values['project_id']) && ! $this->helper->projectRole->canChangeAssignee($values)) { + return ''; + } + + $attributes = array_merge(array('tabindex="4"'), $attributes); + $name = 'owner_ms'; + + $html = $this->helper->form->label(t('Other Assignees'), $name.'[]'); + + $html .= ''; + + return $html; + } + + public function renderGroupField(array $values, array $errors = array(), array $attributes = array()) + { + if (isset($values['project_id']) && ! $this->helper->projectRole->canChangeAssignee($values)) { + return ''; + } + $groups = $this->projectGroupRoleModel->getGroups($values['project_id']); + $groupnames = array(); + $groupids = array(); + + $groupids[] = 0; + $groupnames[] = t('Unassigned'); + + + foreach ($groups as $group) { + // array_splice($groupnames, 1, 0, $group['name']); + $groupnames[] = $group['name']; + $groupids[] = $group['id']; + } + + $groupvalues = array_combine($groupids, $groupnames); + + + $attributes = array_merge(array('tabindex="4"'), $attributes); + + $html = $this->helper->form->label(t('Assigned Group'), 'owner_gp'); + $html .= $this->helper->form->select('owner_gp', $groupvalues, $values, $errors, $attributes); + $html .= ' '; + + return $html; + } + + public function renderCategoryField(array $categories, array $values, array $errors = array(), array $attributes = array(), $allow_one_item = false) + { + $attributes = array_merge(array('tabindex="5"'), $attributes); + $html = ''; + + if (! (! $allow_one_item && count($categories) === 1 && key($categories) == 0)) { + $html .= $this->helper->form->label(t('Category'), 'category_id'); + $html .= $this->helper->form->select('category_id', $categories, $values, $errors, $attributes); + } + + return $html; + } + + public function renderSwimlaneField(array $swimlanes, array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="6"'), $attributes); + $html = ''; + + if (count($swimlanes) > 1) { + $html .= $this->helper->form->label(t('Swimlane'), 'swimlane_id'); + $html .= $this->helper->form->select('swimlane_id', $swimlanes, $values, $errors, $attributes); + } + + return $html; + } + + public function renderColumnField(array $columns, array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="7"'), $attributes); + + $html = $this->helper->form->label(t('Column'), 'column_id'); + $html .= $this->helper->form->select('column_id', $columns, $values, $errors, $attributes); + + return $html; + } + + public function renderPriorityField(array $project, array $values) + { + $range = range($project['priority_start'], $project['priority_end']); + $options = array_combine($range, $range); + $values += array('priority' => $project['priority_default']); + + $html = $this->helper->form->label(t('Priority'), 'priority'); + $html .= $this->helper->form->select('priority', $options, $values, array(), array('tabindex="8"')); + + return $html; + } + + public function renderScoreField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="13"'), $attributes); + + $html = $this->helper->form->label(t('Complexity'), 'score'); + $html .= $this->helper->form->number('score', $values, $errors, $attributes); + + return $html; + } + + public function renderReferenceField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="14"'), $attributes); + + $html = $this->helper->form->label(t('Reference'), 'reference'); + $html .= $this->helper->form->text('reference', $values, $errors, $attributes, 'form-input-small'); + + return $html; + } + + public function renderTimeEstimatedField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="11"'), $attributes); + + $html = $this->helper->form->label(t('Original estimate'), 'time_estimated'); + $html .= $this->helper->form->numeric('time_estimated', $values, $errors, $attributes); + $html .= ' '.t('hours'); + + return $html; + } + + public function renderTimeSpentField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="12"'), $attributes); + + $html = $this->helper->form->label(t('Time spent'), 'time_spent'); + $html .= $this->helper->form->numeric('time_spent', $values, $errors, $attributes); + $html .= ' '.t('hours'); + + return $html; + } + + public function renderStartDateField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="10"'), $attributes); + return $this->helper->form->datetime(t('Start Date'), 'date_started', $values, $errors, $attributes); + } + + public function renderDueDateField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="9"'), $attributes); + return $this->helper->form->datetime(t('Due Date'), 'date_due', $values, $errors, $attributes); + } + + public function renderPriority($priority) + { + $html = ''; + $html .= $this->helper->text->e($priority >= 0 ? 'P'.$priority : '-P'.abs($priority)); + $html .= ''; + + return $html; + } + + public function renderReference(array $task) + { + if (! empty($task['reference'])) { + $reference = $this->helper->text->e($task['reference']); + + if (filter_var($task['reference'], FILTER_VALIDATE_URL) !== false) { + return sprintf('%s', $reference, $reference); + } + + return $reference; + } + + return ''; + } + + public function getProgress($task) + { + if (! isset($this->columns[$task['project_id']])) { + $this->columns[$task['project_id']] = $this->columnModel->getList($task['project_id']); + } + + return $this->taskModel->getProgress($task, $this->columns[$task['project_id']]); + } + + public function getNewBoardTaskButton(array $swimlane, array $column) + { + $html = '
'; + $providers = $this->externalTaskManager->getProviders(); + + if (empty($providers)) { + $html .= $this->helper->modal->largeIcon( + 'plus', + t('Add a new task'), + 'TaskCreationController', + 'show', array( + 'project_id' => $column['project_id'], + 'column_id' => $column['id'], + 'swimlane_id' => $swimlane['id'], + ) + ); + } else { + $html .= ''; + } + + $html .= '
'; + + return $html; + } +} diff --git a/plugins/Group_assign/Helper/SmallAvatarHelperExtend.php b/plugins/Group_assign/Helper/SmallAvatarHelperExtend.php new file mode 100644 index 00000000..1ee35c98 --- /dev/null +++ b/plugins/Group_assign/Helper/SmallAvatarHelperExtend.php @@ -0,0 +1,35 @@ +multiselectMemberModel->getMembers($owner_ms); + $html = ""; + foreach ($assignees as $assignee) { + $user = $this->userModel->getById($assignee['user_id']); + $html .= $this->render($assignee['user_id'], $user['username'], $user['name'], $user['email'], $user['avatar_path'], $css, 20); + } + return $html; + } + + public function miniMultiple($owner_ms, $css = '') { + $assignees = $this->multiselectMemberModel->getMembers($owner_ms); + $html = ""; + foreach ($assignees as $assignee) { + $user = $this->userModel->getById($assignee['user_id']); + $html .= $this->render($assignee['user_id'], $user['username'], $user['name'], $user['email'], $user['avatar_path'], $css, 13); + } + return $html; + } + } diff --git a/plugins/Group_assign/LICENSE b/plugins/Group_assign/LICENSE new file mode 100644 index 00000000..b2e46df8 --- /dev/null +++ b/plugins/Group_assign/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 creecros + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/Group_assign/Locale/de_DE/translations.php b/plugins/Group_assign/Locale/de_DE/translations.php new file mode 100644 index 00000000..eb5128ba --- /dev/null +++ b/plugins/Group_assign/Locale/de_DE/translations.php @@ -0,0 +1,13 @@ + 'Zugeordnete Gruppe', + 'Assigned Group:' => 'Zugeordnete Gruppe:', + 'Other Assignees' => 'Weitere Zuordnungen', + 'Other Assignees:' => 'Weitere Zuordnungen:', + 'Send a task by email to assigned group members' => 'Senden einer Aufgabe per E-Mail an zugewiesene Gruppenmitglieder', + 'Send a task by email to the other assignees for a task.' => 'Aufgabe per E-Mail an die weiteren zugeordneten der Aufgabe senden', + 'Send email notification of impending due date to Group Members Assigned' => 'E-Mail-Benachrichtigung über bevorstehendes Fälligkeitsdatum an die zugewiesenen Gruppenmitglieder senden', + 'Send email notification of impending due date to the other assignees of a task' => 'E-Mail-Benachrichtigung über das bevorstehende Fälligkeitsdatum an die anderen Empfänger einer Aufgabe senden', + 'Assign the task to a specific group' => 'Aufgabe einer bestimmten Gruppe zuordnen' +); diff --git a/plugins/Group_assign/Locale/pt_BR/translations.php b/plugins/Group_assign/Locale/pt_BR/translations.php new file mode 100644 index 00000000..fbbdf207 --- /dev/null +++ b/plugins/Group_assign/Locale/pt_BR/translations.php @@ -0,0 +1,97 @@ + 'Ação', + 'Action date' => 'Data de ação', + 'Add a new Kanboard task' => 'Adicionar uma nova tarefa do Kanboard', + 'Add a new task' => 'Adicionar uma nova tarefa', + 'All tasks' => 'Todas as tarefas', + 'Assignee' => 'Responsável', + 'Assigned Group' => 'Grupo Designado', + 'Assigned Group:' => 'Grupo Designado:', + 'Assign to me' => 'Atribuir para mim', + 'Assign the task to a specific group' => 'Atribuir uma tarefa para um grupo específico', + 'Category' => 'Categoria', + 'Create another task' => 'Criar outra tarefa', + 'Column' => 'Coluna', + 'Color' => 'Cor', + 'Complexity' => 'Complexidade', + 'Day(s)' => 'Dia(s)', + 'Documentation' => 'Documentação', + 'Due Date' => 'Data de Vencimento', + 'Duplicate to multiple projects' => 'Duplicar para múltiplos projetos', + 'Duration in days' => 'Duração em dias', + 'Email subject' => 'Assunto do email', + 'Enter one line per task, or leave blank to copy Task Title and create only one subtask.' => 'Digite uma linha por tarefa, ou deixe em branco para copiar o Título da Tarefa e criar apenas uma subtarefa.', + 'Event' => 'Evento', + 'Existing due date' => 'Data de vencimeto existente', + 'Group' => 'Grupo', + 'Groups management' => 'Gestão de grupos', + 'hours' => 'horas', + 'Logout' => 'Sair', + 'Me' => 'Eu', + 'Month(s)' => 'Mês(es)', + 'My dashboard' => 'Meu painel', + 'My profile' => 'Meu perfil', + 'New assignee: %s' => 'Novo responsável: %s', + 'New category: %s' => 'Nova categoria: %s', + 'New color: %s' => 'Nova cor: %s', + 'New complexity: %d' => 'Nova complexidade: %d', + 'New due date: ' => 'Nova data de vencimento: ', + 'New group assigned: %s' => 'Novo grupo atribuido: %s', + 'New task' => 'Nova tarefa', + 'New title: %s' => 'Novo título: %s', + 'No' => 'Não', + 'not assigned' => 'não atribuído', + 'Only for tasks assigned to me' => 'Apenas tarefas atribuídas a mim', + 'Only for tasks created by me' => 'Apenas tarefas criadas por mim', + 'Only for tasks created by me and tasks assigned to me' => 'Apenas tarefas criadas por mim ou tarefas atribuídas a mim', + 'Options' => 'Opções', + 'Original estimate' => 'Estimativa original', + 'Other Assignees' => 'Outros Responsáveis', + 'Other Assignees:' => 'Outros Responsáveis:', + 'Priority' => 'Prioridade', + 'Projects management' => 'Gestão de projetos', + 'Recurrence settings have been modified' => 'Configurações de recorrência foram modificadas', + 'Reference' => 'Referência', + 'Send a task by email to assigned group members' => 'Enviar uma tarefa por email para membros do grupo designado', + 'Send a task by email to the other assignees for a task.' => 'Enviar uma tarefa por email para os outros responsáveis por uma tarefa', + 'Send email notification of impending due date to Group Members Assigned' => 'Enviar email de notificação de data de vencimento iminente para os membros de grupos designados', + 'Send email notification of impending due date to the other assignees of a task' => 'Enviar email de notificação de data de vencimento iminente para os outros designados por uma tarefa', + 'Settings' => 'Definições', + 'Start Date' => 'Data de Início', + 'Start date changed: ' => 'Data de início alterada: ', + 'Swimlane' => 'Raia', + 'Tags' => 'Etiquetas', + 'Task created successfully.' => 'Tarefa criada com sucesso.', + 'Task priority' => 'Prioridade da tarefa', + 'Task updated successfully.' => 'Tarefa atualizada com sucesso', + 'Template for the task description' => 'Modelo para a descrição da tarefa', + 'The description has been modified:' => 'A descrição foi modificada:', + 'The due date have been removed' => 'A data de vencimento foi removida', + 'The field "%s" have been updated' => 'O campo "%s" foi atualizado', + 'The task is not assigned anymore' => 'A tarefa não está mais atribuída', + 'The task is not assigned to a group anymore' => 'A tarefa não está mais atribuída para um grupo', + 'The task is not assigned to multiple users anymore' => 'A tarefa não está mais atribuída para múltiplos usuários', + 'The task has been assigned other users' => 'A tarefa foi atribuída para outros usuários', + 'There is no category now' => 'Não há categoria agora', + 'There is no description anymore' => 'Não há descrição agora', + 'Time estimated changed: %sh' => 'Tempo estimado alterado: %sh', + 'Time spent' => 'Tempo gasto', + 'Time spent changed: %sh' => 'Tempo gasto alterado: %sh', + 'Title' => 'Título', + 'Unable to create this task.' => 'Incapaz de criar esta tarefa', + 'Unable to create your task.' => 'Incapaz de criar a sua tarefa.', + 'Unable to update your task.' => 'Incapaz de atualizar sua tarefa', + 'Unassigned' => 'Não atribuido', + 'Users management' => 'Gestão de usuários', + 'When task is moved from first column' => 'Quando a tarefa é movida da primeira coluna', + 'When task is moved to last column' => 'Quando a tarefa é movida para a última coluna', + 'When task is closed' => 'Quando a tarefa é fechada', + 'Year(s)' => 'Ano(s)', + 'Yes' => 'Sim', + 'You are not allowed to change the assignee.' => 'Você não tem permissão para mudar o responsável.', + 'You are not allowed to update tasks assigned to someone else.' => 'Você não tem permissão para atualizar tarefas designadas para outras pessoas.', + 'You cannot create tasks in this column.' => 'Você não pode criar tarefas nesta coluna.', + '[DUPLICATE]' => '[DUPLICADO]', +); diff --git a/plugins/Group_assign/Model/GroupAssignCalendarModel.php b/plugins/Group_assign/Model/GroupAssignCalendarModel.php new file mode 100644 index 00000000..5b45ca09 --- /dev/null +++ b/plugins/Group_assign/Model/GroupAssignCalendarModel.php @@ -0,0 +1,89 @@ +db->table(MultiselectMemberModel::TABLE) + ->eq('user_id', $user_id) + ->findAllByColumn('group_id'); + + $getGr_Ids = $this->db->table(GroupMemberModel::TABLE) + ->eq('user_id', $user_id) + ->findAllByColumn('group_id'); + + $tasks = $this->db->table(self::TABLE) + ->beginOr() + ->eq('owner_id', $user_id) + ->in('owner_gp', $getGr_Ids) + ->in('owner_ms', $getMS_Ids) + ->closeOr() + ->gte('date_due', strtotime($start)) + ->lte('date_due', strtotime($end)) + ->neq('is_active', 0); + + $tasks = $tasks->findAll(); + + $events = array(); + + foreach ($tasks as $task) { + + $startDate = new DateTime(); + $startDate->setTimestamp($task['date_started']); + + $endDate = new DateTime(); + $endDate->setTimestamp($task['date_due']); + + if ($startDate->getTimestamp() == 0) { $startDate = $endDate; } + + $allDay = $startDate == $endDate && $endDate->format('Hi') == '0000'; + $format = $allDay ? 'Y-m-d' : 'Y-m-d\TH:i:s'; + + $events[] = array( + 'timezoneParam' => $this->timezoneModel->getCurrentTimezone(), + 'id' => $task['id'], + 'title' => t('#%d', $task['id']).' '.$task['title'], + 'backgroundColor' => $this->colorModel->getBackgroundColor('dark_grey'), + 'borderColor' => $this->colorModel->getBorderColor($task['color_id']), + 'textColor' => 'white', + 'url' => $this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), + 'start' => $startDate->format($format), + 'end' => $endDate->format($format), + 'editable' => $allDay, + 'allday' => $allDay, + ); + } + + return $events; + } + +} diff --git a/plugins/Group_assign/Model/GroupAssignTaskDuplicationModel.php b/plugins/Group_assign/Model/GroupAssignTaskDuplicationModel.php new file mode 100644 index 00000000..179f6630 --- /dev/null +++ b/plugins/Group_assign/Model/GroupAssignTaskDuplicationModel.php @@ -0,0 +1,175 @@ +copyFields($task_id); + $values['title'] = t('[DUPLICATE]').' '.$values['title']; + + $new_task_id = $this->save($task_id, $values); + + if ($new_task_id !== false) { + $this->tagDuplicationModel->duplicateTaskTags($task_id, $new_task_id); + $this->taskLinkModel->create($new_task_id, $task_id, 4); + } + + return $new_task_id; + } + + /** + * Check if the assignee and the category are available in the destination project + * + * @access public + * @param array $values + * @return array + */ + public function checkDestinationProjectValues(array &$values) + { + // Check if the assigned user is allowed for the destination project + if ($values['owner_id'] > 0 && ! $this->projectPermissionModel->isUserAllowed($values['project_id'], $values['owner_id'])) { + $values['owner_id'] = 0; + } + + // Check if the category exists for the destination project + if ($values['category_id'] > 0) { + $values['category_id'] = $this->categoryModel->getIdByName( + $values['project_id'], + $this->categoryModel->getNameById($values['category_id']) + ); + } + + // Check if the swimlane exists for the destination project + $values['swimlane_id'] = $this->swimlaneModel->getIdByName( + $values['project_id'], + $this->swimlaneModel->getNameById($values['swimlane_id']) + ); + + if ($values['swimlane_id'] == 0) { + $values['swimlane_id'] = $this->swimlaneModel->getFirstActiveSwimlaneId($values['project_id']); + } + + // Check if the column exists for the destination project + if ($values['column_id'] > 0) { + $values['column_id'] = $this->columnModel->getColumnIdByTitle( + $values['project_id'], + $this->columnModel->getColumnTitleById($values['column_id']) + ); + + $values['column_id'] = $values['column_id'] ?: $this->columnModel->getFirstColumnId($values['project_id']); + } + + // Check if priority exists for destination project + $values['priority'] = $this->projectTaskPriorityModel->getPriorityForProject( + $values['project_id'], + empty($values['priority']) ? 0 : $values['priority'] + ); + + return $values; + } + + /** + * Duplicate fields for the new task + * + * @access protected + * @param integer $task_id Task id + * @return array + */ + protected function copyFields($task_id) + { + $task = $this->taskFinderModel->getById($task_id); + $values = array(); + + foreach ($this->fieldsToDuplicate as $field) { + $values[$field] = $task[$field]; + } + + $ms_id = $this->multiselectModel->create(); + $users_in_ms = $this->multiselectMemberModel->getMembers($values['owner_ms']); + $values['owner_ms'] = $ms_id; + foreach ($users_in_ms as $user) { + $this->multiselectMemberModel->addUser($ms_id, $user['id']); + } + + return $values; + } + + /** + * Create the new task and duplicate subtasks + * + * @access protected + * @param integer $task_id Task id + * @param array $values Form values + * @return boolean|integer + */ + protected function save($task_id, array $values) + { + $new_task_id = $this->taskCreationModel->create($values); + + if ($new_task_id !== false) { + $this->subtaskModel->duplicate($task_id, $new_task_id); + } + + return $new_task_id; + } +} diff --git a/plugins/Group_assign/Model/GroupColorExtension.php b/plugins/Group_assign/Model/GroupColorExtension.php new file mode 100644 index 00000000..e8e19850 --- /dev/null +++ b/plugins/Group_assign/Model/GroupColorExtension.php @@ -0,0 +1,28 @@ + 130) { return 'black'; } else { return 'white'; } + } +} diff --git a/plugins/Group_assign/Model/MultiselectMemberModel.php b/plugins/Group_assign/Model/MultiselectMemberModel.php new file mode 100644 index 00000000..ce0f3ace --- /dev/null +++ b/plugins/Group_assign/Model/MultiselectMemberModel.php @@ -0,0 +1,169 @@ +db->table(self::TABLE) + ->join(UserModel::TABLE, 'id', 'user_id') + ->eq('group_id', $group_id); + } + + /** + * Get all users + * + * @access public + * @param integer $group_id + * @return array + */ + public function getMembers($group_id) + { + return $this->getQuery($group_id)->findAll(); + } + + /** + * Get all not members + * + * @access public + * @param integer $group_id + * @return array + */ + public function getNotMembers($group_id) + { + $subquery = $this->db->table(self::TABLE) + ->columns('user_id') + ->eq('group_id', $group_id); + + return $this->db->table(UserModel::TABLE) + ->notInSubquery('id', $subquery) + ->eq('is_active', 1) + ->findAll(); + } + + /** + * Add user to a group + * + * @access public + * @param integer $group_id + * @param integer $user_id + * @return boolean + */ + public function addUser($group_id, $user_id) + { + return $this->db->table(self::TABLE)->insert(array( + 'group_id' => $group_id, + 'user_id' => $user_id, + )); + } + + /** + * Remove user from a group + * + * @access public + * @param integer $group_id + * @param integer $user_id + * @return boolean + */ + public function removeUser($group_id, $user_id) + { + return $this->db->table(self::TABLE) + ->eq('group_id', $group_id) + ->eq('user_id', $user_id) + ->remove(); + } + + /** + * Remove all users from a group + * + * @access public + * @param integer $group_id + * @param integer $user_id + * @return boolean + */ + public function removeAllUsers($group_id) + { + return $this->db->table(self::TABLE) + ->eq('group_id', $group_id) + ->remove(); + } + + /** + * Check if a user is member + * + * @access public + * @param integer $group_id + * @param integer $user_id + * @return boolean + */ + public function isMember($group_id, $user_id) + { + return $this->db->table(self::TABLE) + ->eq('group_id', $group_id) + ->eq('user_id', $user_id) + ->exists(); + } + + /** + * Get all groups for a given user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getGroups($user_id) + { + return $this->db->table(self::TABLE) + ->columns(MultiselectModel::TABLE.'.id', MultiselectModel::TABLE.'.external_id') + ->join(MultiselectModel::TABLE, 'id', 'group_id') + ->eq(self::TABLE.'.user_id', $user_id) + ->asc(MultiselectModel::TABLE.'.id') + ->findAll(); + } + + /** + * Fire Assignee Change + * + * @access protected + * @param array $task + * @param array $changes + */ + public function assigneeChanged(array $task, array $changes) + { + $events = array(); + $events[] = TaskModel::EVENT_ASSIGNEE_CHANGE; + + if (! empty($events)) { + $this->queueManager->push($this->taskEventJob + ->withParams($task['id'], $events, $changes, array(), $task) + ); + } + } +} diff --git a/plugins/Group_assign/Model/MultiselectModel.php b/plugins/Group_assign/Model/MultiselectModel.php new file mode 100644 index 00000000..36f1a9d1 --- /dev/null +++ b/plugins/Group_assign/Model/MultiselectModel.php @@ -0,0 +1,143 @@ +db->table(self::TABLE) + ->columns('id', 'external_id') + ->subquery('SELECT COUNT(*) FROM '.MultiselectMemberModel::TABLE.' WHERE group_id='.self::TABLE.'.id', 'nb_users'); + } + + /** + * Get a specific group by id + * + * @access public + * @param integer $group_id + * @return array + */ + public function getById($group_id) + { + return $this->db->table(self::TABLE)->eq('id', $group_id)->findOne(); + } + + /** + * Get a specific group by externalID + * + * @access public + * @param string $external_id + * @return array + */ + public function getByExternalId($external_id) + { + return $this->db->table(self::TABLE)->eq('external_id', $external_id)->findOne(); + } + + /** + * Get specific groups by externalIDs + * + * @access public + * @param string[] $external_ids + * @return array + */ + public function getByExternalIds(array $external_ids) + { + if (empty($external_ids)) { + return []; + } + + return $this->db->table(self::TABLE)->in('external_id', $external_ids)->findAll(); + } + + /** + * Get all groups + * + * @access public + * @return array + */ + public function getAll() + { + return $this->getQuery()->asc('id')->findAll(); + } + + /** + * Remove a group + * + * @access public + * @param integer $group_id + * @return boolean + */ + public function remove($group_id) + { + return $this->db->table(self::TABLE)->eq('id', $group_id)->remove(); + } + + /** + * Create a new group + * + * @access public + * @param string $external_id + * @return integer|boolean + */ + public function create($external_id = '') + { + return $this->db->table(self::TABLE)->persist(array( + 'external_id' => $external_id, + )); + } + + /** + * Update existing group + * + * @access public + * @param array $values + * @return boolean + */ + public function update(array $values) + { + return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values); + } + + /** + * Get groupId from externalGroupId and create the group if not found + * + * @access public + * @param string $name + * @param string $external_id + * @return bool|integer + */ + public function getOrCreateExternalGroupId($name, $external_id) + { + $group_id = $this->db->table(self::TABLE)->eq('external_id', $external_id)->findOneColumn('id'); + + if (empty($group_id)) { + $group_id = $this->create($external_id); + } + + return $group_id; + } +} diff --git a/plugins/Group_assign/Model/NewMetaMagikSubquery.php b/plugins/Group_assign/Model/NewMetaMagikSubquery.php new file mode 100644 index 00000000..4870120e --- /dev/null +++ b/plugins/Group_assign/Model/NewMetaMagikSubquery.php @@ -0,0 +1,28 @@ +subquery('(SELECT COUNT(*) FROM '.self::METADATA_TABLE.' WHERE task_id=tasks.id)', 'nb_metadata'); + } +} \ No newline at end of file diff --git a/plugins/Group_assign/Model/NewTaskFinderModel.php b/plugins/Group_assign/Model/NewTaskFinderModel.php new file mode 100644 index 00000000..b8b10916 --- /dev/null +++ b/plugins/Group_assign/Model/NewTaskFinderModel.php @@ -0,0 +1,491 @@ +db + ->table(TaskModel::TABLE) + ->columns( + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + TaskModel::TABLE.'.date_due', + TaskModel::TABLE.'.date_started', + TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.color_id', + TaskModel::TABLE.'.priority', + TaskModel::TABLE.'.time_spent', + TaskModel::TABLE.'.time_estimated', + ProjectModel::TABLE.'.name AS project_name', + ColumnModel::TABLE.'.title AS column_name', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name' + ) + ->eq(TaskModel::TABLE.'.is_active', $is_active) + ->in(ProjectModel::TABLE.'.id', $project_ids) + ->join(ProjectModel::TABLE, 'id', 'project_id') + ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE) + ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE); + } + + /** + * Get query for assigned user tasks + * + * @access public + * @param integer $user_id User id + * @return \PicoDb\Table + */ + public function getUserQuery($user_id) + { + return $this->getExtendedQuery() + ->beginOr() + ->eq(TaskModel::TABLE.'.owner_id', $user_id) + ->addCondition(TaskModel::TABLE.".id IN (SELECT task_id FROM ".SubtaskModel::TABLE." WHERE ".SubtaskModel::TABLE.".user_id='$user_id')") + ->addCondition(TaskModel::TABLE.".owner_gp IN (SELECT group_id FROM ".GroupMemberModel::TABLE." WHERE ".GroupMemberModel::TABLE.".user_id='$user_id')") + ->addCondition(TaskModel::TABLE.".owner_ms IN (SELECT group_id FROM ".MultiselectMemberModel::TABLE." WHERE ".MultiselectMemberModel::TABLE.".user_id='$user_id')") + ->closeOr() + ->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN) + ->eq(ProjectModel::TABLE.'.is_active', ProjectModel::ACTIVE) + ->eq(ColumnModel::TABLE.'.hide_in_dashboard', 0); + } + + /** + * Extended query + * + * @access public + * @return \PicoDb\Table + */ + public function getExtendedQuery() + { + return $this->db + ->table(TaskModel::TABLE) + ->columns( + '(SELECT COUNT(*) FROM '.CommentModel::TABLE.' WHERE task_id=tasks.id) AS nb_comments', + '(SELECT COUNT(*) FROM '.TaskFileModel::TABLE.' WHERE task_id=tasks.id) AS nb_files', + '(SELECT COUNT(*) FROM '.SubtaskModel::TABLE.' WHERE '.SubtaskModel::TABLE.'.task_id=tasks.id) AS nb_subtasks', + '(SELECT COUNT(*) FROM '.SubtaskModel::TABLE.' WHERE '.SubtaskModel::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks', + '(SELECT COUNT(*) FROM '.TaskLinkModel::TABLE.' WHERE '.TaskLinkModel::TABLE.'.task_id = tasks.id) AS nb_links', + '(SELECT COUNT(*) FROM '.TaskExternalLinkModel::TABLE.' WHERE '.TaskExternalLinkModel::TABLE.'.task_id = tasks.id) AS nb_external_links', + '(SELECT DISTINCT 1 FROM '.TaskLinkModel::TABLE.' WHERE '.TaskLinkModel::TABLE.'.task_id = tasks.id AND '.TaskLinkModel::TABLE.'.link_id = 9) AS is_milestone', + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.reference', + TaskModel::TABLE.'.title', + TaskModel::TABLE.'.description', + TaskModel::TABLE.'.date_creation', + TaskModel::TABLE.'.date_modification', + TaskModel::TABLE.'.date_completed', + TaskModel::TABLE.'.date_started', + TaskModel::TABLE.'.date_due', + TaskModel::TABLE.'.color_id', + TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.column_id', + TaskModel::TABLE.'.swimlane_id', + TaskModel::TABLE.'.owner_id', + TaskModel::TABLE.'.creator_id', + TaskModel::TABLE.'.position', + TaskModel::TABLE.'.is_active', + TaskModel::TABLE.'.score', + TaskModel::TABLE.'.category_id', + TaskModel::TABLE.'.priority', + TaskModel::TABLE.'.date_moved', + TaskModel::TABLE.'.recurrence_status', + TaskModel::TABLE.'.recurrence_trigger', + TaskModel::TABLE.'.recurrence_factor', + TaskModel::TABLE.'.recurrence_timeframe', + TaskModel::TABLE.'.recurrence_basedate', + TaskModel::TABLE.'.recurrence_parent', + TaskModel::TABLE.'.recurrence_child', + TaskModel::TABLE.'.time_estimated', + TaskModel::TABLE.'.time_spent', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name', + UserModel::TABLE.'.email AS assignee_email', + UserModel::TABLE.'.avatar_path AS assignee_avatar_path', + CategoryModel::TABLE.'.name AS category_name', + CategoryModel::TABLE.'.description AS category_description', + CategoryModel::TABLE.'.color_id AS category_color_id', + ColumnModel::TABLE.'.title AS column_name', + ColumnModel::TABLE.'.position AS column_position', + SwimlaneModel::TABLE.'.name AS swimlane_name', + ProjectModel::TABLE.'.name AS project_name', + TaskModel::TABLE.'.owner_ms', + GroupModel::TABLE.'.name AS assigned_groupname' + ) + ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE) + ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id') + ->join(CategoryModel::TABLE, 'id', 'category_id', TaskModel::TABLE) + ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE) + ->join(SwimlaneModel::TABLE, 'id', 'swimlane_id', TaskModel::TABLE) + ->join(GroupModel::TABLE, 'id', 'owner_gp', TaskModel::TABLE) + ->join(MultiselectModel::TABLE, 'id', 'owner_ms', TaskModel::TABLE) + ->join(ProjectModel::TABLE, 'id', 'project_id', TaskModel::TABLE); + } + + /** + * Get all tasks for a given project and status + * + * @access public + * @param integer $project_id Project id + * @param integer $status_id Status id + * @return array + */ + public function getAll($project_id, $status_id = TaskModel::STATUS_OPEN) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.project_id', $project_id) + ->eq(TaskModel::TABLE.'.is_active', $status_id) + ->asc(TaskModel::TABLE.'.id') + ->findAll(); + } + + /** + * Get all tasks for a given project and status + * + * @access public + * @param integer $project_id + * @param array $status + * @return array + */ + public function getAllIds($project_id, array $status = array(TaskModel::STATUS_OPEN)) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.project_id', $project_id) + ->in(TaskModel::TABLE.'.is_active', $status) + ->asc(TaskModel::TABLE.'.id') + ->findAllByColumn(TaskModel::TABLE.'.id'); + } + + /** + * Get overdue tasks query + * + * @access public + * @return \PicoDb\Table + */ + public function getOverdueTasksQuery() + { + return $this->db->table(TaskModel::TABLE) + ->columns( + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + TaskModel::TABLE.'.date_due', + TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.creator_id', + TaskModel::TABLE.'.owner_id', + ProjectModel::TABLE.'.name AS project_name', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name' + ) + ->join(ProjectModel::TABLE, 'id', 'project_id') + ->join(UserModel::TABLE, 'id', 'owner_id') + ->eq(ProjectModel::TABLE.'.is_active', 1) + ->eq(TaskModel::TABLE.'.is_active', 1) + ->neq(TaskModel::TABLE.'.date_due', 0) + ->lte(TaskModel::TABLE.'.date_due', time()); + } + + /** + * Get a list of overdue tasks for all projects + * + * @access public + * @return array + */ + public function getOverdueTasks() + { + return $this->getOverdueTasksQuery()->findAll(); + } + + /** + * Get a list of overdue tasks by project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getOverdueTasksByProject($project_id) + { + return $this->getOverdueTasksQuery()->eq(TaskModel::TABLE.'.project_id', $project_id)->findAll(); + } + + /** + * Get a list of overdue tasks by user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getOverdueTasksByUser($user_id) + { + return $this->getOverdueTasksQuery()->eq(TaskModel::TABLE.'.owner_id', $user_id)->findAll(); + } + + /** + * Get project id for a given task + * + * @access public + * @param integer $task_id Task id + * @return integer + */ + public function getProjectId($task_id) + { + return (int) $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOneColumn('project_id') ?: 0; + } + + /** + * Fetch a task by the id + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getById($task_id) + { + return $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOne(); + } + + /** + * Fetch a task by the reference (external id) + * + * @access public + * @param integer $project_id Project id + * @param string $reference Task reference + * @return array + */ + public function getByReference($project_id, $reference) + { + return $this->db->table(TaskModel::TABLE)->eq('project_id', $project_id)->eq('reference', $reference)->findOne(); + } + + /** + * Get task details (fetch more information from other tables) + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getDetails($task_id) + { + return $this->db->table(TaskModel::TABLE) + ->columns( + TaskModel::TABLE.'.*', + CategoryModel::TABLE.'.name AS category_name', + SwimlaneModel::TABLE.'.name AS swimlane_name', + ProjectModel::TABLE.'.name AS project_name', + ColumnModel::TABLE.'.title AS column_title', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name', + 'uc.username AS creator_username', + 'uc.name AS creator_name', + CategoryModel::TABLE.'.description AS category_description', + ColumnModel::TABLE.'.position AS column_position', + GroupModel::TABLE.'.name AS assigned_groupname', + ColumnModel::TABLE.'.position AS column_position' + ) + ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE) + ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id') + ->join(CategoryModel::TABLE, 'id', 'category_id', TaskModel::TABLE) + ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE) + ->join(SwimlaneModel::TABLE, 'id', 'swimlane_id', TaskModel::TABLE) + ->join(ProjectModel::TABLE, 'id', 'project_id', TaskModel::TABLE) + ->join(GroupModel::TABLE, 'id', 'owner_gp', TaskModel::TABLE) + ->join(MultiselectModel::TABLE, 'id', 'owner_ms', TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.id', $task_id) + ->findOne(); + } + + /** + * Get iCal query + * + * @access public + * @return \PicoDb\Table + */ + public function getICalQuery() + { + return $this->db->table(TaskModel::TABLE) + ->left(UserModel::TABLE, 'ua', 'id', TaskModel::TABLE, 'owner_id') + ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id') + ->columns( + TaskModel::TABLE.'.*', + 'ua.email AS assignee_email', + 'ua.name AS assignee_name', + 'ua.username AS assignee_username', + 'uc.email AS creator_email', + 'uc.name AS creator_name', + 'uc.username AS creator_username' + ); + } + + /** + * Count all tasks for a given project and status + * + * @access public + * @param integer $project_id Project id + * @param array $status List of status id + * @return integer + */ + public function countByProjectId($project_id, array $status = array(TaskModel::STATUS_OPEN, TaskModel::STATUS_CLOSED)) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq('project_id', $project_id) + ->in('is_active', $status) + ->count(); + } + + /** + * Count the number of tasks for a given column and status + * + * @access public + * @param integer $project_id Project id + * @param integer $column_id Column id + * @param array $status + * @return int + */ + public function countByColumnId($project_id, $column_id, array $status = array(TaskModel::STATUS_OPEN)) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq('project_id', $project_id) + ->eq('column_id', $column_id) + ->in('is_active', $status) + ->count(); + } + + /** + * Count the number of tasks for a given column and swimlane + * + * @access public + * @param integer $project_id Project id + * @param integer $column_id Column id + * @param integer $swimlane_id Swimlane id + * @return integer + */ + public function countByColumnAndSwimlaneId($project_id, $column_id, $swimlane_id) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq('project_id', $project_id) + ->eq('column_id', $column_id) + ->eq('swimlane_id', $swimlane_id) + ->eq('is_active', 1) + ->count(); + } + + /** + * Return true if the task exists + * + * @access public + * @param integer $task_id Task id + * @return boolean + */ + public function exists($task_id) + { + return $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->exists(); + } + + /** + * Get project token + * + * @access public + * @param integer $task_id + * @return string + */ + public function getProjectToken($task_id) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.id', $task_id) + ->join(ProjectModel::TABLE, 'id', 'project_id') + ->findOneColumn(ProjectModel::TABLE.'.token'); + } +} diff --git a/plugins/Group_assign/Model/NewUserNotificationFilterModel.php b/plugins/Group_assign/Model/NewUserNotificationFilterModel.php new file mode 100644 index 00000000..3fa67487 --- /dev/null +++ b/plugins/Group_assign/Model/NewUserNotificationFilterModel.php @@ -0,0 +1,218 @@ + t('All tasks'), + self::FILTER_ASSIGNEE => t('Only for tasks assigned to me'), + self::FILTER_CREATOR => t('Only for tasks created by me'), + self::FILTER_BOTH => t('Only for tasks created by me and tasks assigned to me'), + ); + } + + /** + * Get user selected filter + * + * @access public + * @param integer $user_id + * @return integer + */ + public function getSelectedFilter($user_id) + { + return $this->db->table(UserModel::TABLE)->eq('id', $user_id)->findOneColumn('notifications_filter'); + } + + /** + * Save selected filter for a user + * + * @access public + * @param integer $user_id + * @param string $filter + * @return boolean + */ + public function saveFilter($user_id, $filter) + { + return $this->db->table(UserModel::TABLE)->eq('id', $user_id)->update(array( + 'notifications_filter' => $filter, + )); + } + + /** + * Get user selected projects + * + * @access public + * @param integer $user_id + * @return array + */ + public function getSelectedProjects($user_id) + { + return $this->db->table(self::PROJECT_TABLE)->eq('user_id', $user_id)->findAllByColumn('project_id'); + } + + /** + * Save selected projects for a user + * + * @access public + * @param integer $user_id + * @param integer[] $project_ids + * @return boolean + */ + public function saveSelectedProjects($user_id, array $project_ids) + { + $results = array(); + $this->db->table(self::PROJECT_TABLE)->eq('user_id', $user_id)->remove(); + + foreach ($project_ids as $project_id) { + $results[] = $this->db->table(self::PROJECT_TABLE)->insert(array( + 'user_id' => $user_id, + 'project_id' => $project_id, + )); + } + + return !in_array(false, $results, true); + } + + /** + * Return true if the user should receive notification + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function shouldReceiveNotification(array $user, array $event_data) + { + $filters = array( + 'filterNone', + 'filterAssignee', + 'filterCreator', + 'filterBoth', + ); + + foreach ($filters as $filter) { + if ($this->$filter($user, $event_data)) { + return $this->filterProject($user, $event_data); + } + } + + return false; + } + + /** + * Return true if the user will receive all notifications + * + * @access public + * @param array $user + * @return boolean + */ + public function filterNone(array $user) + { + return $user['notifications_filter'] == self::FILTER_NONE; + } + + /** + * Return true if the user is the assignee and selected the filter "assignee" + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterAssignee(array $user, array $event_data) + { + if (!isset($event_data['task']['owner_ms'])) $event_data['task']['owner_ms'] = 0; + if (!isset($event_data['task']['owner_gp'])) $event_data['task']['owner_gp'] = 0; + return $user['notifications_filter'] == self::FILTER_ASSIGNEE && + ($event_data['task']['owner_id'] == $user['id'] || + $this->multiselectMemberModel->isMember($event_data['task']['owner_ms'], $user['id']) || + $this->groupMemberModel->isMember($event_data['task']['owner_gp'], $user['id'])); + } + + /** + * Return true if the user is the creator and enabled the filter "creator" + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterCreator(array $user, array $event_data) + { + return $user['notifications_filter'] == self::FILTER_CREATOR && $event_data['task']['creator_id'] == $user['id']; + } + + /** + * Return true if the user is the assignee or the creator and selected the filter "both" + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterBoth(array $user, array $event_data) + { + if (!isset($event_data['task']['owner_ms'])) $event_data['task']['owner_ms'] = 0; + if (!isset($event_data['task']['owner_gp'])) $event_data['task']['owner_gp'] = 0; + return $user['notifications_filter'] == self::FILTER_BOTH && + ($event_data['task']['creator_id'] == $user['id'] || $event_data['task']['owner_id'] == $user['id'] || + $this->multiselectMemberModel->isMember($event_data['task']['owner_ms'], $user['id']) || + $this->groupMemberModel->isMember($event_data['task']['owner_gp'], $user['id'])); + } + + /** + * Return true if the user want to receive notification for the selected project + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterProject(array $user, array $event_data) + { + $projects = $this->getSelectedProjects($user['id']); + + if (! empty($projects)) { + return in_array($event_data['task']['project_id'], $projects); + } + + return true; + } +} diff --git a/plugins/Group_assign/Model/OldMetaMagikSubquery.php b/plugins/Group_assign/Model/OldMetaMagikSubquery.php new file mode 100644 index 00000000..787b7e4a --- /dev/null +++ b/plugins/Group_assign/Model/OldMetaMagikSubquery.php @@ -0,0 +1,28 @@ +subquery('(SELECT COUNT(*) FROM '.self::METADATA_TABLE.' WHERE task_id=tasks.id)', 'nb_metadata'); + } +} \ No newline at end of file diff --git a/plugins/Group_assign/Model/OldTaskFinderModel.php b/plugins/Group_assign/Model/OldTaskFinderModel.php new file mode 100644 index 00000000..70d06126 --- /dev/null +++ b/plugins/Group_assign/Model/OldTaskFinderModel.php @@ -0,0 +1,489 @@ +db + ->table(TaskModel::TABLE) + ->columns( + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + TaskModel::TABLE.'.date_due', + TaskModel::TABLE.'.date_started', + TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.color_id', + TaskModel::TABLE.'.priority', + TaskModel::TABLE.'.time_spent', + TaskModel::TABLE.'.time_estimated', + ProjectModel::TABLE.'.name AS project_name', + ColumnModel::TABLE.'.title AS column_name', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name' + ) + ->eq(TaskModel::TABLE.'.is_active', $is_active) + ->in(ProjectModel::TABLE.'.id', $project_ids) + ->join(ProjectModel::TABLE, 'id', 'project_id') + ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE) + ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE); + } + + /** + * Get query for assigned user tasks + * + * @access public + * @param integer $user_id User id + * @return \PicoDb\Table + */ + public function getUserQuery($user_id) + { + return $this->getExtendedQuery() + ->beginOr() + ->eq(TaskModel::TABLE.'.owner_id', $user_id) + ->addCondition(TaskModel::TABLE.".id IN (SELECT task_id FROM ".SubtaskModel::TABLE." WHERE ".SubtaskModel::TABLE.".user_id='$user_id')") + ->addCondition(TaskModel::TABLE.".owner_gp IN (SELECT group_id FROM ".GroupMemberModel::TABLE." WHERE ".GroupMemberModel::TABLE.".user_id='$user_id')") + ->addCondition(TaskModel::TABLE.".owner_ms IN (SELECT group_id FROM ".MultiselectMemberModel::TABLE." WHERE ".MultiselectMemberModel::TABLE.".user_id='$user_id')") + ->closeOr() + ->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN) + ->eq(ProjectModel::TABLE.'.is_active', ProjectModel::ACTIVE) + ->eq(ColumnModel::TABLE.'.hide_in_dashboard', 0); + } + + /** + * Extended query + * + * @access public + * @return \PicoDb\Table + */ + public function getExtendedQuery() + { + return $this->db + ->table(TaskModel::TABLE) + ->columns( + '(SELECT COUNT(*) FROM '.CommentModel::TABLE.' WHERE task_id=tasks.id) AS nb_comments', + '(SELECT COUNT(*) FROM '.TaskFileModel::TABLE.' WHERE task_id=tasks.id) AS nb_files', + '(SELECT COUNT(*) FROM '.SubtaskModel::TABLE.' WHERE '.SubtaskModel::TABLE.'.task_id=tasks.id) AS nb_subtasks', + '(SELECT COUNT(*) FROM '.SubtaskModel::TABLE.' WHERE '.SubtaskModel::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks', + '(SELECT COUNT(*) FROM '.TaskLinkModel::TABLE.' WHERE '.TaskLinkModel::TABLE.'.task_id = tasks.id) AS nb_links', + '(SELECT COUNT(*) FROM '.TaskExternalLinkModel::TABLE.' WHERE '.TaskExternalLinkModel::TABLE.'.task_id = tasks.id) AS nb_external_links', + '(SELECT DISTINCT 1 FROM '.TaskLinkModel::TABLE.' WHERE '.TaskLinkModel::TABLE.'.task_id = tasks.id AND '.TaskLinkModel::TABLE.'.link_id = 9) AS is_milestone', + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.reference', + TaskModel::TABLE.'.title', + TaskModel::TABLE.'.description', + TaskModel::TABLE.'.date_creation', + TaskModel::TABLE.'.date_modification', + TaskModel::TABLE.'.date_completed', + TaskModel::TABLE.'.date_started', + TaskModel::TABLE.'.date_due', + TaskModel::TABLE.'.color_id', + TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.column_id', + TaskModel::TABLE.'.swimlane_id', + TaskModel::TABLE.'.owner_id', + TaskModel::TABLE.'.creator_id', + TaskModel::TABLE.'.position', + TaskModel::TABLE.'.is_active', + TaskModel::TABLE.'.score', + TaskModel::TABLE.'.category_id', + TaskModel::TABLE.'.priority', + TaskModel::TABLE.'.date_moved', + TaskModel::TABLE.'.recurrence_status', + TaskModel::TABLE.'.recurrence_trigger', + TaskModel::TABLE.'.recurrence_factor', + TaskModel::TABLE.'.recurrence_timeframe', + TaskModel::TABLE.'.recurrence_basedate', + TaskModel::TABLE.'.recurrence_parent', + TaskModel::TABLE.'.recurrence_child', + TaskModel::TABLE.'.time_estimated', + TaskModel::TABLE.'.time_spent', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name', + UserModel::TABLE.'.email AS assignee_email', + UserModel::TABLE.'.avatar_path AS assignee_avatar_path', + CategoryModel::TABLE.'.name AS category_name', + CategoryModel::TABLE.'.description AS category_description', + ColumnModel::TABLE.'.title AS column_name', + ColumnModel::TABLE.'.position AS column_position', + SwimlaneModel::TABLE.'.name AS swimlane_name', + ProjectModel::TABLE.'.name AS project_name', + TaskModel::TABLE.'.owner_ms', + GroupModel::TABLE.'.name AS assigned_groupname' + ) + ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE) + ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id') + ->join(CategoryModel::TABLE, 'id', 'category_id', TaskModel::TABLE) + ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE) + ->join(SwimlaneModel::TABLE, 'id', 'swimlane_id', TaskModel::TABLE) + ->join(GroupModel::TABLE, 'id', 'owner_gp', TaskModel::TABLE) + ->join(MultiselectModel::TABLE, 'id', 'owner_ms', TaskModel::TABLE) + ->join(ProjectModel::TABLE, 'id', 'project_id', TaskModel::TABLE); + } + + /** + * Get all tasks for a given project and status + * + * @access public + * @param integer $project_id Project id + * @param integer $status_id Status id + * @return array + */ + public function getAll($project_id, $status_id = TaskModel::STATUS_OPEN) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.project_id', $project_id) + ->eq(TaskModel::TABLE.'.is_active', $status_id) + ->asc(TaskModel::TABLE.'.id') + ->findAll(); + } + + /** + * Get all tasks for a given project and status + * + * @access public + * @param integer $project_id + * @param array $status + * @return array + */ + public function getAllIds($project_id, array $status = array(TaskModel::STATUS_OPEN)) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.project_id', $project_id) + ->in(TaskModel::TABLE.'.is_active', $status) + ->asc(TaskModel::TABLE.'.id') + ->findAllByColumn(TaskModel::TABLE.'.id'); + } + + /** + * Get overdue tasks query + * + * @access public + * @return \PicoDb\Table + */ + public function getOverdueTasksQuery() + { + return $this->db->table(TaskModel::TABLE) + ->columns( + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + TaskModel::TABLE.'.date_due', + TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.creator_id', + TaskModel::TABLE.'.owner_id', + ProjectModel::TABLE.'.name AS project_name', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name' + ) + ->join(ProjectModel::TABLE, 'id', 'project_id') + ->join(UserModel::TABLE, 'id', 'owner_id') + ->eq(ProjectModel::TABLE.'.is_active', 1) + ->eq(TaskModel::TABLE.'.is_active', 1) + ->neq(TaskModel::TABLE.'.date_due', 0) + ->lte(TaskModel::TABLE.'.date_due', time()); + } + + /** + * Get a list of overdue tasks for all projects + * + * @access public + * @return array + */ + public function getOverdueTasks() + { + return $this->getOverdueTasksQuery()->findAll(); + } + + /** + * Get a list of overdue tasks by project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getOverdueTasksByProject($project_id) + { + return $this->getOverdueTasksQuery()->eq(TaskModel::TABLE.'.project_id', $project_id)->findAll(); + } + + /** + * Get a list of overdue tasks by user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getOverdueTasksByUser($user_id) + { + return $this->getOverdueTasksQuery()->eq(TaskModel::TABLE.'.owner_id', $user_id)->findAll(); + } + + /** + * Get project id for a given task + * + * @access public + * @param integer $task_id Task id + * @return integer + */ + public function getProjectId($task_id) + { + return (int) $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOneColumn('project_id') ?: 0; + } + + /** + * Fetch a task by the id + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getById($task_id) + { + return $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOne(); + } + + /** + * Fetch a task by the reference (external id) + * + * @access public + * @param integer $project_id Project id + * @param string $reference Task reference + * @return array + */ + public function getByReference($project_id, $reference) + { + return $this->db->table(TaskModel::TABLE)->eq('project_id', $project_id)->eq('reference', $reference)->findOne(); + } + + /** + * Get task details (fetch more information from other tables) + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getDetails($task_id) + { + return $this->db->table(TaskModel::TABLE) + ->columns( + TaskModel::TABLE.'.*', + CategoryModel::TABLE.'.name AS category_name', + SwimlaneModel::TABLE.'.name AS swimlane_name', + ProjectModel::TABLE.'.name AS project_name', + ColumnModel::TABLE.'.title AS column_title', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name', + 'uc.username AS creator_username', + 'uc.name AS creator_name', + CategoryModel::TABLE.'.description AS category_description', + ColumnModel::TABLE.'.position AS column_position', + GroupModel::TABLE.'.name AS assigned_groupname' + ) + ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE) + ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id') + ->join(CategoryModel::TABLE, 'id', 'category_id', TaskModel::TABLE) + ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE) + ->join(SwimlaneModel::TABLE, 'id', 'swimlane_id', TaskModel::TABLE) + ->join(ProjectModel::TABLE, 'id', 'project_id', TaskModel::TABLE) + ->join(GroupModel::TABLE, 'id', 'owner_gp', TaskModel::TABLE) + ->join(MultiselectModel::TABLE, 'id', 'owner_ms', TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.id', $task_id) + ->findOne(); + } + + /** + * Get iCal query + * + * @access public + * @return \PicoDb\Table + */ + public function getICalQuery() + { + return $this->db->table(TaskModel::TABLE) + ->left(UserModel::TABLE, 'ua', 'id', TaskModel::TABLE, 'owner_id') + ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id') + ->columns( + TaskModel::TABLE.'.*', + 'ua.email AS assignee_email', + 'ua.name AS assignee_name', + 'ua.username AS assignee_username', + 'uc.email AS creator_email', + 'uc.name AS creator_name', + 'uc.username AS creator_username' + ); + } + + /** + * Count all tasks for a given project and status + * + * @access public + * @param integer $project_id Project id + * @param array $status List of status id + * @return integer + */ + public function countByProjectId($project_id, array $status = array(TaskModel::STATUS_OPEN, TaskModel::STATUS_CLOSED)) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq('project_id', $project_id) + ->in('is_active', $status) + ->count(); + } + + /** + * Count the number of tasks for a given column and status + * + * @access public + * @param integer $project_id Project id + * @param integer $column_id Column id + * @param array $status + * @return int + */ + public function countByColumnId($project_id, $column_id, array $status = array(TaskModel::STATUS_OPEN)) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq('project_id', $project_id) + ->eq('column_id', $column_id) + ->in('is_active', $status) + ->count(); + } + + /** + * Count the number of tasks for a given column and swimlane + * + * @access public + * @param integer $project_id Project id + * @param integer $column_id Column id + * @param integer $swimlane_id Swimlane id + * @return integer + */ + public function countByColumnAndSwimlaneId($project_id, $column_id, $swimlane_id) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq('project_id', $project_id) + ->eq('column_id', $column_id) + ->eq('swimlane_id', $swimlane_id) + ->eq('is_active', 1) + ->count(); + } + + /** + * Return true if the task exists + * + * @access public + * @param integer $task_id Task id + * @return boolean + */ + public function exists($task_id) + { + return $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->exists(); + } + + /** + * Get project token + * + * @access public + * @param integer $task_id + * @return string + */ + public function getProjectToken($task_id) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.id', $task_id) + ->join(ProjectModel::TABLE, 'id', 'project_id') + ->findOneColumn(ProjectModel::TABLE.'.token'); + } +} diff --git a/plugins/Group_assign/Model/TaskProjectDuplicationModel.php b/plugins/Group_assign/Model/TaskProjectDuplicationModel.php new file mode 100644 index 00000000..835f2063 --- /dev/null +++ b/plugins/Group_assign/Model/TaskProjectDuplicationModel.php @@ -0,0 +1,94 @@ +prepare($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id); + + $this->checkDestinationProjectValues($values); + $new_task_id = $this->save($task_id, $values); + if ($new_task_id !== false) { + // Check if the group is allowed for the destination project + $group_id = $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOneColumn('owner_gp'); + if ($group_id > 0) { + $group_in_project = $this->db + ->table(ProjectGroupRoleModel::TABLE) + ->eq('project_id', $values['project_id']) + ->eq('group_id', $group_id) + ->exists(); + if ($group_in_project) { $this->db->table(TaskModel::TABLE)->eq('id', $new_task_id)->update(['owner_gp' => $group_id]); } + } + + // Check if the other assignees are allowed for the destination project + $ms_id = $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOneColumn('owner_ms'); + if ($ms_id > 0) { + $users_in_ms = $this->multiselectMemberModel->getMembers($ms_id); + $new_ms_id = $this->multiselectModel->create(); + $this->db->table(TaskModel::TABLE)->eq('id', $new_task_id)->update(['owner_ms' => $new_ms_id]); + foreach ($users_in_ms as $user) { + if ($this->projectPermissionModel->isAssignable($values['project_id'], $user['id'])) { + $this->multiselectMemberModel->addUser($new_ms_id, $user['id']); + } + } + } + + $this->tagDuplicationModel->duplicateTaskTagsToAnotherProject($task_id, $new_task_id, $project_id); + $this->taskLinkModel->create($new_task_id, $task_id, 4); + } + + return $new_task_id; + } + + /** + * Prepare values before duplication + * + * @access protected + * @param integer $task_id + * @param integer $project_id + * @param integer $swimlane_id + * @param integer $column_id + * @param integer $category_id + * @param integer $owner_id + * @return array + */ + protected function prepare($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id) + { + $values = $this->copyFields($task_id); + $values['project_id'] = $project_id; + $values['column_id'] = $column_id !== null ? $column_id : $values['column_id']; + $values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $values['swimlane_id']; + $values['category_id'] = $category_id !== null ? $category_id : $values['category_id']; + $values['owner_id'] = $owner_id !== null ? $owner_id : $values['owner_id']; + + return $values; + } +} diff --git a/plugins/Group_assign/Model/TaskProjectMoveModel.php b/plugins/Group_assign/Model/TaskProjectMoveModel.php new file mode 100644 index 00000000..554d3330 --- /dev/null +++ b/plugins/Group_assign/Model/TaskProjectMoveModel.php @@ -0,0 +1,96 @@ +taskFinderModel->getById($task_id); + $values = $this->prepare($project_id, $swimlane_id, $column_id, $category_id, $owner_id, $task); + + $this->checkDestinationProjectValues($values); + $this->tagDuplicationModel->syncTaskTagsToAnotherProject($task_id, $project_id); + + // Check if the group is allowed for the destination project and unassign if not + $group_id = $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOneColumn('owner_gp'); + if ($group_id > 0) { + $group_in_project = $this->db + ->table(ProjectGroupRoleModel::TABLE) + ->eq('project_id', $project_id) + ->eq('group_id', $group_id) + ->exists(); + if (!$group_in_project) { $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->update(['owner_gp' => 0]); } + } + + // Check if the other assignees are allowed for the destination project and remove from ms group if not + $ms_id = $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOneColumn('owner_ms'); + if ($ms_id > 0) { + $users_in_ms = $this->multiselectMemberModel->getMembers($ms_id); + foreach ($users_in_ms as $user) { + if (! $this->projectPermissionModel->isAssignable($project_id, $user['id'])) { + $this->multiselectMemberModel->removeUser($ms_id, $user['id']); + } + } + } + + + if ($this->db->table(TaskModel::TABLE)->eq('id', $task_id)->update($values)) { + $this->queueManager->push($this->taskEventJob->withParams($task_id, array(TaskModel::EVENT_MOVE_PROJECT), $values)); + } + + return true; + } + + /** + * Prepare new task values + * + * @access protected + * @param integer $project_id + * @param integer $swimlane_id + * @param integer $column_id + * @param integer $category_id + * @param integer $owner_id + * @param array $task + * @return array + */ + protected function prepare($project_id, $swimlane_id, $column_id, $category_id, $owner_id, array $task) + { + $values = array(); + $values['is_active'] = 1; + $values['project_id'] = $project_id; + $values['column_id'] = $column_id !== null ? $column_id : $task['column_id']; + $values['position'] = $this->taskFinderModel->countByColumnId($project_id, $values['column_id']) + 1; + $values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $task['swimlane_id']; + $values['category_id'] = $category_id !== null ? $category_id : $task['category_id']; + $values['owner_id'] = $owner_id !== null ? $owner_id : $task['owner_id']; + $values['priority'] = $task['priority']; + return $values; + } +} diff --git a/plugins/Group_assign/Model/TaskRecurrenceModel.php b/plugins/Group_assign/Model/TaskRecurrenceModel.php new file mode 100644 index 00000000..999751e1 --- /dev/null +++ b/plugins/Group_assign/Model/TaskRecurrenceModel.php @@ -0,0 +1,154 @@ + t('No'), + TaskModel::RECURRING_STATUS_PENDING => t('Yes'), + ); + } + + /** + * Return the list recurrence triggers + * + * @access public + * @return array + */ + public function getRecurrenceTriggerList() + { + return array( + TaskModel::RECURRING_TRIGGER_FIRST_COLUMN => t('When task is moved from first column'), + TaskModel::RECURRING_TRIGGER_LAST_COLUMN => t('When task is moved to last column'), + TaskModel::RECURRING_TRIGGER_CLOSE => t('When task is closed'), + ); + } + + /** + * Return the list options to calculate recurrence due date + * + * @access public + * @return array + */ + public function getRecurrenceBasedateList() + { + return array( + TaskModel::RECURRING_BASEDATE_DUEDATE => t('Existing due date'), + TaskModel::RECURRING_BASEDATE_TRIGGERDATE => t('Action date'), + ); + } + + /** + * Return the list recurrence timeframes + * + * @access public + * @return array + */ + public function getRecurrenceTimeframeList() + { + return array( + TaskModel::RECURRING_TIMEFRAME_DAYS => t('Day(s)'), + TaskModel::RECURRING_TIMEFRAME_MONTHS => t('Month(s)'), + TaskModel::RECURRING_TIMEFRAME_YEARS => t('Year(s)'), + ); + } + + /** + * Duplicate recurring task + * + * @access public + * @param integer $task_id Task id + * @return boolean|integer Recurrence task id + */ + public function duplicateRecurringTask($task_id) + { + $values = $this->copyFields($task_id); + + if ($values['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING) { + $values['recurrence_parent'] = $task_id; + $values['column_id'] = $this->columnModel->getFirstColumnId($values['project_id']); + $this->calculateRecurringTaskDueDate($values); + + $recurring_task_id = $this->save($task_id, $values); + + if ($recurring_task_id !== false) { + $this->tagDuplicationModel->duplicateTaskTags($task_id, $recurring_task_id); + + $parent_update = $this->db + ->table(TaskModel::TABLE) + ->eq('id', $task_id) + ->update(array( + 'recurrence_status' => TaskModel::RECURRING_STATUS_PROCESSED, + 'recurrence_child' => $recurring_task_id, + )); + + if ($parent_update) { + return $recurring_task_id; + } + } + } + + return false; + } + + /** + * Calculate new due date for new recurrence task + * + * @access public + * @param array $values Task fields + */ + public function calculateRecurringTaskDueDate(array &$values) + { + if (! empty($values['date_due']) && $values['recurrence_factor'] != 0) { + if ($values['recurrence_basedate'] == TaskModel::RECURRING_BASEDATE_TRIGGERDATE) { + $values['date_due'] = time(); + } + + $factor = abs($values['recurrence_factor']); + $subtract = $values['recurrence_factor'] < 0; + + switch ($values['recurrence_timeframe']) { + case TaskModel::RECURRING_TIMEFRAME_MONTHS: + $interval = 'P' . $factor . 'M'; + break; + case TaskModel::RECURRING_TIMEFRAME_YEARS: + $interval = 'P' . $factor . 'Y'; + break; + default: + $interval = 'P' . $factor . 'D'; + } + + $date_due = new DateTime(); + $date_due->setTimestamp($values['date_due']); + + $subtract ? $date_due->sub(new DateInterval($interval)) : $date_due->add(new DateInterval($interval)); + + $values['date_due'] = $date_due->getTimestamp(); + } + } +} diff --git a/plugins/Group_assign/Plugin.php b/plugins/Group_assign/Plugin.php new file mode 100644 index 00000000..6f69388c --- /dev/null +++ b/plugins/Group_assign/Plugin.php @@ -0,0 +1,200 @@ +template->setTemplateOverride('task/changes', 'group_assign:task/changes'); + + //Notifications + $this->container['userNotificationFilterModel'] = $this->container->factory(function ($c) { + return new NewUserNotificationFilterModel($c); + }); + + //Helpers + $this->helper->register('newTaskHelper', '\Kanboard\Plugin\Group_assign\Helper\NewTaskHelper'); + $this->helper->register('smallAvatarHelperExtend', '\Kanboard\Plugin\Group_assign\Helper\SmallAvatarHelperExtend'); + + + //Models and backward compatibility + + $applications_version = str_replace('v', '', APP_VERSION); + if (strpos(APP_VERSION, 'master') !== false && file_exists('ChangeLog')) { $applications_version = trim(file_get_contents('ChangeLog', false, null, 8, 6), ' '); } + $clean_appversion = preg_replace('/\s+/', '', $applications_version); + + if (version_compare($clean_appversion, '1.2.5', '>')) { + if (file_exists('plugins/MetaMagik')){ + $this->container['taskFinderModel'] = $this->container->factory(function ($c) { + return new NewMetaMagikSubquery($c); + }); + } else { + $this->container['taskFinderModel'] = $this->container->factory(function ($c) { + return new NewTaskFinderModel($c); + }); + } + $this->container['taskDuplicationModel'] = $this->container->factory(function ($c) { + return new GroupAssignTaskDuplicationModel($c); + }); + $this->container['taskProjectDuplicationModel '] = $this->container->factory(function ($c) { + return new TaskProjectDuplicationModel ($c); + }); + $this->container['taskProjectMoveModel '] = $this->container->factory(function ($c) { + return new TaskProjectMoveModel ($c); + }); + $this->container['taskRecurrenceModel '] = $this->container->factory(function ($c) { + return new TaskRecurrenceModel ($c); + }); + } else { + if (file_exists('plugins/MetaMagik')){ + $this->container['taskFinderModel'] = $this->container->factory(function ($c) { + return new OldMetaMagikSubquery($c); + }); + } else { + $this->container['taskFinderModel'] = $this->container->factory(function ($c) { + return new OldTaskFinderModel($c); + }); + } + $this->container['taskDuplicationModel'] = $this->container->factory(function ($c) { + return new GroupAssignTaskDuplicationModel($c); + }); + $this->container['taskProjectDuplicationModel '] = $this->container->factory(function ($c) { + return new TaskProjectDuplicationModel ($c); + }); + $this->container['taskProjectMoveModel '] = $this->container->factory(function ($c) { + return new TaskProjectMoveModel ($c); + }); + $this->container['taskRecurrenceModel '] = $this->container->factory(function ($c) { + return new TaskRecurrenceModel ($c); + }); + } + + //Task - Template - details.php + $this->template->hook->attach('template:task:details:third-column', 'group_assign:task/details'); + $this->template->hook->attach('template:task:details:third-column', 'group_assign:task/multi'); + + //Forms - task_creation.php and task_modification.php + $this->template->setTemplateOverride('task_creation/show', 'group_assign:task_creation/show'); + $this->template->setTemplateOverride('task_modification/show', 'group_assign:task_modification/show'); + + //Board + $this->template->hook->attach('template:board:private:task:before-title', 'group_assign:board/group'); + $this->template->hook->attach('template:board:private:task:before-title', 'group_assign:board/multi'); + $groupmodel = $this->projectGroupRoleModel; + $this->template->hook->attachCallable('template:app:filters-helper:after', 'group_assign:board/filter', function($array = array()) use ($groupmodel) { + if(!empty($array) && $array['id'] >= 1){ + return ['grouplist' => array_column($groupmodel->getGroups($array['id']), 'name')]; + } else { + return ['grouplist' => array()]; + } + }); + + //Filter + $this->container->extend('taskLexer', function($taskLexer, $c) { + $taskLexer->withFilter(TaskAllAssigneeFilter::getInstance()->setDatabase($c['db']) + ->setCurrentUserId($c['userSession']->getId())); + return $taskLexer; + }); + + //Actions + $this->actionManager->register(new EmailGroup($this->container)); + $this->actionManager->register(new EmailGroupDue($this->container)); + $this->actionManager->register(new EmailOtherAssignees($this->container)); + $this->actionManager->register(new EmailOtherAssigneesDue($this->container)); + $this->actionManager->register(new AssignGroup($this->container)); + + //Params + $this->template->setTemplateOverride('action_creation/params', 'group_assign:action_creation/params'); + + //CSS + $this->hook->on('template:layout:css', array('template' => 'plugins/Group_assign/Assets/css/group_assign.css')); + + //JS + $this->hook->on('template:layout:js', array('template' => 'plugins/Group_assign/Assets/js/group_assign.js')); + + //Calendar Events + $container = $this->container; + + $this->hook->on('controller:calendar:user:events', function($user_id, $start, $end) use ($container) { + $model = new GroupAssignCalendarModel($container); + return $model->getUserCalendarEvents($user_id, $start, $end); // Return new events + }); + + //Roles + + $this->template->hook->attach('template:config:application', 'group_assign:config/toggle'); + + if ($this->configModel->get('enable_am_group_management', '2') == 1) { + $this->applicationAccessMap->add('GroupListController', '*', Role::APP_MANAGER); + $this->applicationAccessMap->add('GroupCreationController', '*', Role::APP_MANAGER); + $this->template->setTemplateOverride('header/user_dropdown', 'group_assign:header/user_dropdown'); + } + + + } + + public function onStartup() + { + Translator::load($this->languageModel->getCurrentLanguage(), __DIR__.'/Locale'); + } + + public function getClasses() + { + return [ + 'Plugin\Group_assign\Model' => [ + 'MultiselectMemberModel', 'MultiselectModel', 'GroupColorExtension', 'TaskProjectDuplicationModel', 'TaskProjectMoveModel', 'TaskRecurrenceModel', + ], + ]; + } + + public function getPluginName() + { + return 'Group_assign'; + } + public function getPluginDescription() + { + return t('Add group assignment to tasks'); + } + public function getPluginAuthor() + { + return 'Craig Crosby'; + } + public function getPluginVersion() + { + return '1.7.8'; + } + public function getPluginHomepage() + { + return 'https://github.com/creecros/group_assign'; + } +} diff --git a/plugins/Group_assign/README.md b/plugins/Group_assign/README.md new file mode 100644 index 00000000..43078d9c --- /dev/null +++ b/plugins/Group_assign/README.md @@ -0,0 +1,95 @@ +## Checkout our latest project +[![](https://raw.githubusercontent.com/docpht/docpht/master/public/assets/img/logo.png)](https://github.com/docpht/docpht) + +- With [DocPHT](https://github.com/docpht/docpht) you can take notes and quickly document anything and without the use of any database. +----------- +[![Latest release](https://img.shields.io/github/release/creecros/Group_assign.svg)](https://github.com/creecros/Group_assign/releases) +[![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/creecros/Group_assign/blob/master/LICENSE) +[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/creecros/Group_assign/graphs/contributors) +[![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)]() +[![Downloads](https://img.shields.io/github/downloads/creecros/Group_assign/total.svg)](https://github.com/creecros/Group_assign/releases) + +Donate to help keep this project maintained. + +Donate with PayPal button + +:star: If you use it, you should star it on Github! +It's the least you can do for all the work put into it! + + +# Group_assign +Assign Tasks to Groups or from Multi-Select of Users with permissions from the project + +# Requirements +Kanboard v1.1.0 or Higher + +# Features and usage +* A task can have an assigned group or selection of users +* Can only assign groups or other assigness to a task that have permissions in the Project. +* If a user is in a group that a task is assigned to, it will show up on their dashboard. +* If a user is in other assignees multiselect that a task is assigned to, it will show up on their dashboard. +* If a user is in a group that a task is assigned to, it will show up in their calendar. +* If a user is in other assignees multiselect that a task is assigned to, it will show up in their calendar. +* If a group is assigned or a user is assigneed in other assignees, it will be appear on the task in detail view, board view, creation, modification. +* Includes 5 Automatic Actions to utilize the Assigned Group + * Email Assigned Group on Task Modification, Creation, Close, or Movement + * Email Assigned Group of impending Task Due Date + * Email Other Assignees on Task Modification, Creation, Close, or Movement + * Email Other Assignees of impending Task Due Date + * Assign task to a group on creation or movement +* using ``allassignees:me`` (``assignee:me`` for pre 1.7.3 versions) in filter will find tasks assigned to groups that the user is in or assignee in other assignees is in. +* using ``allassignees:GroupName`` (``assignee:GroupName`` for pre 1.7.3 versions) in filter will find tasks assigned to a group by NAME of the group. +* using ``allassignees:GroupID`` (``assignee:GroupID`` for pre 1.7.3 versions) in filter will find tasks assigned to a group by ID number of group. +* using ``allassignees:Username`` or ``allassignees:Name`` will find all tasks assigned to that user regardless of how they have been assigneed, whether in the group or in Other Assignees or Assignee. +* User assigneed via a group or multiselect will now recieve notifications +* Changing assigned group or any multiselect users will now trigger `EVENT_ASSIGNEE_CHANGE` +* Duplicating Tasks will include assigned groups and other users. + * Duplicating to another project or moving to another project will check permissions of assignees, and remove those without permission. +* Task Reccurences will include group assigned and other assignees in the recurrence. +* Setting included to enable group managment for Application Managers + * Found in `Settings > Application settings` + +# Future enhancments +Find bugs or missing functionality, please report it. + +- [x] Add a few basic automatic actions that utilize Groups assigned +- [x] Add relationship for ``allassignees:Username`` or ``allassignees:Name`` in the table lookup +- [x] Add an event for assigned group change. +- [x] Incorporate into notifications +- [x] Address Task Duplication +- [x] Task Recurrence + +# Manual Installation + +- Find the release you wish to install: https://github.com/creecros/Group_assign/releases +- Download the provided zip, not the source zip, i.e. `Group_assign-x.x.x.zip` +- Unzip contents to the plugins folder + +In the event that you use the master repo, ensure that the directory of the plugin is named `Group_assign`, or else the plugin will not work. + +# Screenshots + +## Task Details: +![image](https://user-images.githubusercontent.com/26339368/49951197-64546680-fec7-11e8-9473-82820b1a4f7e.png) + +## Task Creation/Modification: +![image](https://user-images.githubusercontent.com/26339368/38753761-692db008-3f2d-11e8-8ce2-59d88ddf39b1.png) +![image](https://user-images.githubusercontent.com/26339368/49557918-3c696f80-f8d7-11e8-91b8-7cef11c6eec0.png) + +## Board View: +![image](https://user-images.githubusercontent.com/26339368/49951135-3a9b3f80-fec7-11e8-9bf6-3a777c09c675.png) + +## Users Calendar View + +- Tasks that a user is assigned too but not main assignee will show up in calendar, with Dark Grey Background and Task color Border, to differentiate that they are not the main assignee. + +![image](https://user-images.githubusercontent.com/26339368/49655821-b7cb3e00-fa09-11e8-9608-952abbf146fa.png) + + +## Automatic Actions: +![image](https://user-images.githubusercontent.com/26339368/38754253-0a0fd2de-3f2f-11e8-9dde-2036de011a6b.png) + +![image](https://user-images.githubusercontent.com/26339368/38754279-2285d0d4-3f2f-11e8-88c2-0ed91e452f90.png) + +![image](https://user-images.githubusercontent.com/26339368/38754288-310df2c6-3f2f-11e8-9993-39e96b55076c.png) + diff --git a/plugins/Group_assign/Schema/Mysql.php b/plugins/Group_assign/Schema/Mysql.php new file mode 100644 index 00000000..17318a29 --- /dev/null +++ b/plugins/Group_assign/Schema/Mysql.php @@ -0,0 +1,34 @@ +exec("ALTER TABLE `tasks` ADD COLUMN `owner_ms` INT DEFAULT '0'"); + + $pdo->exec(" + CREATE TABLE `multiselect` ( + id INT NOT NULL AUTO_INCREMENT, + external_id VARCHAR(255) DEFAULT '', + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + $pdo->exec(" + CREATE TABLE multiselect_has_users ( + group_id INT NOT NULL, + user_id INT NOT NULL, + FOREIGN KEY(group_id) REFERENCES `multiselect`(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(group_id, user_id) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_1(PDO $pdo) +{ + $pdo->exec("ALTER TABLE `tasks` ADD COLUMN `owner_gp` INT DEFAULT '0'"); +} diff --git a/plugins/Group_assign/Schema/Postgres.php b/plugins/Group_assign/Schema/Postgres.php new file mode 100644 index 00000000..54b0a2be --- /dev/null +++ b/plugins/Group_assign/Schema/Postgres.php @@ -0,0 +1,34 @@ +exec("ALTER TABLE tasks ADD COLUMN owner_ms INT DEFAULT '0'"); + + $pdo->exec(" + CREATE TABLE multiselect ( + id SERIAL PRIMARY KEY, + external_id VARCHAR(255) DEFAULT '' + ) + "); + + $pdo->exec(" + CREATE TABLE multiselect_has_users ( + group_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY(group_id) REFERENCES multiselect(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(group_id, user_id) + ) + "); +} + +function version_1(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN owner_gp INT DEFAULT '0'"); +} diff --git a/plugins/Group_assign/Schema/Sqlite.php b/plugins/Group_assign/Schema/Sqlite.php new file mode 100644 index 00000000..0d5d3e9c --- /dev/null +++ b/plugins/Group_assign/Schema/Sqlite.php @@ -0,0 +1,34 @@ +exec("ALTER TABLE tasks ADD COLUMN owner_ms INTEGER DEFAULT '0'"); + + $pdo->exec(" + CREATE TABLE multiselect ( + id INTEGER PRIMARY KEY, + external_id TEXT DEFAULT '' + ) + "); + + $pdo->exec(" + CREATE TABLE multiselect_has_users ( + group_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY(group_id) REFERENCES multiselect(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(group_id, user_id) + ) + "); +} + +function version_1(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN owner_gp INTEGER DEFAULT '0'"); +} diff --git a/plugins/Group_assign/Template/action_creation/params.php b/plugins/Group_assign/Template/action_creation/params.php new file mode 100644 index 00000000..0506007b --- /dev/null +++ b/plugins/Group_assign/Template/action_creation/params.php @@ -0,0 +1,73 @@ + + +
+ form->csrf() ?> + + form->hidden('event_name', $values) ?> + form->hidden('action_name', $values) ?> + + form->label(t('Action'), 'action_name') ?> + form->select('action_name', $available_actions, $values, array(), array('disabled')) ?> + + form->label(t('Event'), 'event_name') ?> + form->select('event_name', $events, $values, array(), array('disabled')) ?> + + $param_desc): ?> + text->contains($param_name, 'column_id')): ?> + form->label($param_desc, $param_name) ?> + form->select('params['.$param_name.']', $columns_list, $values) ?> + text->contains($param_name, 'user_id')): ?> + form->label($param_desc, $param_name) ?> + form->select('params['.$param_name.']', $users_list, $values) ?> + text->contains($param_name, 'group_id')): ?> + model->projectGroupRoleModel->getGroups($values['project_id']); ?> + + + + + + form->label($param_desc, $param_name) ?> + form->select('params['.$param_name.']', $groupvalues, $values) ?> + text->contains($param_name, 'check_box')): ?> + form->label(t('Options'), $param_name) ?> + form->checkbox('params['.$param_name.']', $param_desc, 1) ?> + text->contains($param_name, 'project_id')): ?> + form->label($param_desc, $param_name) ?> + form->select('params['.$param_name.']', $projects_list, $values) ?> + text->contains($param_name, 'color_id')): ?> + form->label($param_desc, $param_name) ?> + form->select('params['.$param_name.']', $colors_list, $values) ?> + text->contains($param_name, 'category_id')): ?> + form->label($param_desc, $param_name) ?> + form->select('params['.$param_name.']', $categories_list, $values) ?> + text->contains($param_name, 'link_id')): ?> + form->label($param_desc, $param_name) ?> + form->select('params['.$param_name.']', $links_list, $values) ?> + + form->label($param_desc, $param_name) ?> + form->select('params['.$param_name.']', $priorities_list, $values) ?> + text->contains($param_name, 'duration')): ?> + form->label($param_desc, $param_name) ?> + form->number('params['.$param_name.']', $values) ?> + text->contains($param_name, 'swimlane_id')): ?> + form->label($param_desc, $param_name) ?> + form->select('params['.$param_name.']', $swimlane_list, $values) ?> + + form->label(ucfirst($param_name), $param_name) ?> + form->select('params['.$param_name.']', $param_desc, $values) ?> + text->contains($param_name, 'multitasktitles')): ?> + form->label($param_desc, $param_name) ?> + form->textarea('params['.$param_name.']', $values) ?> +
+ +
+ + form->label($param_desc, $param_name) ?> + form->text('params['.$param_name.']', $values) ?> + + + + modal->submitButtons() ?> +
diff --git a/plugins/Group_assign/Template/board/filter.php b/plugins/Group_assign/Template/board/filter.php new file mode 100644 index 00000000..322c483f --- /dev/null +++ b/plugins/Group_assign/Template/board/filter.php @@ -0,0 +1,12 @@ + + +
+ + \ No newline at end of file diff --git a/plugins/Group_assign/Template/board/group.php b/plugins/Group_assign/Template/board/group.php new file mode 100644 index 00000000..6ac95c1f --- /dev/null +++ b/plugins/Group_assign/Template/board/group.php @@ -0,0 +1,7 @@ + + + + text->e($task['assigned_groupname'] ?: $task['owner_gp']) ?> +
+ +
diff --git a/plugins/Group_assign/Template/board/multi.php b/plugins/Group_assign/Template/board/multi.php new file mode 100644 index 00000000..46084b5f --- /dev/null +++ b/plugins/Group_assign/Template/board/multi.php @@ -0,0 +1,5 @@ + 0 && count($this->task->multiselectMemberModel->getMembers($task['owner_ms'])) > 0) : ?> + + helper->smallAvatarHelperExtend->miniMultiple($task['owner_ms'], 'avatar-inline') ?> +
+ diff --git a/plugins/Group_assign/Template/config/toggle.php b/plugins/Group_assign/Template/config/toggle.php new file mode 100644 index 00000000..801a6a26 --- /dev/null +++ b/plugins/Group_assign/Template/config/toggle.php @@ -0,0 +1,4 @@ +
+ form->radio('enable_am_group_management', 'Enable Group Managment for Application Managers' , 1, isset($values['enable_am_group_management'])&& $values['enable_am_group_management']==1) ?> + form->radio('enable_am_group_management', 'Disable Group Managment for Application Managers' , 2, isset($values['enable_am_group_management'])&& $values['enable_am_group_management']==2) ?> +
diff --git a/plugins/Group_assign/Template/header/user_dropdown.php b/plugins/Group_assign/Template/header/user_dropdown.php new file mode 100644 index 00000000..e3a38787 --- /dev/null +++ b/plugins/Group_assign/Template/header/user_dropdown.php @@ -0,0 +1,46 @@ + diff --git a/plugins/Group_assign/Template/task/changes.php b/plugins/Group_assign/Template/task/changes.php new file mode 100644 index 00000000..7a0d2720 --- /dev/null +++ b/plugins/Group_assign/Template/task/changes.php @@ -0,0 +1,92 @@ + + + + +

+ +
text->markdown($task['description'], true) ?>
+ +
text->markdown($task['description']) ?>
+ + + diff --git a/plugins/Group_assign/Template/task/details.php b/plugins/Group_assign/Template/task/details.php new file mode 100644 index 00000000..907739bc --- /dev/null +++ b/plugins/Group_assign/Template/task/details.php @@ -0,0 +1,10 @@ +
  • + + + + text->e($task['assigned_groupname'] ?: $task['owner_gp']) ?> + + + + +
  • diff --git a/plugins/Group_assign/Template/task/multi.php b/plugins/Group_assign/Template/task/multi.php new file mode 100644 index 00000000..5c1fcfb0 --- /dev/null +++ b/plugins/Group_assign/Template/task/multi.php @@ -0,0 +1,6 @@ + 0 && count($this->task->multiselectMemberModel->getMembers($task['owner_ms'])) > 0) : ?> +
  • + +
  • + helper->smallAvatarHelperExtend->smallMultiple($task['owner_ms'], 'avatar-inline') ?> + diff --git a/plugins/Group_assign/Template/task_creation/show.php b/plugins/Group_assign/Template/task_creation/show.php new file mode 100644 index 00000000..81f610a8 --- /dev/null +++ b/plugins/Group_assign/Template/task_creation/show.php @@ -0,0 +1,50 @@ + +
    + form->csrf() ?> + +
    +
    + task->renderTitleField($values, $errors) ?> + task->renderDescriptionField($values, $errors) ?> + task->renderDescriptionTemplateDropdown($project['id']) ?> + task->renderTagField($project) ?> + + hook->render('template:task:form:first-column', array('values' => $values, 'errors' => $errors)) ?> +
    + +
    + task->renderColorField($values) ?> + task->renderAssigneeField($users_list, $values, $errors) ?> + helper->newTaskHelper->renderGroupField($values, $errors) ?> + helper->newTaskHelper->renderMultiAssigneeField($users_list, $values) ?> + task->renderCategoryField($categories_list, $values, $errors) ?> + task->renderSwimlaneField($swimlanes_list, $values, $errors) ?> + task->renderColumnField($columns_list, $values, $errors) ?> + task->renderPriorityField($project, $values) ?> + + hook->render('template:task:form:second-column', array('values' => $values, 'errors' => $errors)) ?> +
    + +
    + task->renderDueDateField($values, $errors) ?> + task->renderStartDateField($values, $errors) ?> + task->renderTimeEstimatedField($values, $errors) ?> + task->renderTimeSpentField($values, $errors) ?> + task->renderScoreField($values, $errors) ?> + task->renderReferenceField($values, $errors) ?> + + hook->render('template:task:form:third-column', array('values' => $values, 'errors' => $errors)) ?> +
    + +
    + + form->checkbox('another_task', t('Create another task'), 1, isset($values['another_task']) && $values['another_task'] == 1) ?> + form->checkbox('duplicate_multiple_projects', t('Duplicate to multiple projects'), 1) ?> + + + modal->submitButtons() ?> +
    +
    +
    diff --git a/plugins/Group_assign/Template/task_modification/show.php b/plugins/Group_assign/Template/task_modification/show.php new file mode 100644 index 00000000..31599ef6 --- /dev/null +++ b/plugins/Group_assign/Template/task_modification/show.php @@ -0,0 +1,43 @@ + +
    + form->csrf() ?> + +
    +
    + task->renderTitleField($values, $errors) ?> + task->renderDescriptionField($values, $errors) ?> + task->renderDescriptionTemplateDropdown($project['id']) ?> + task->renderTagField($project, $tags) ?> + + hook->render('template:task:form:first-column', array('values' => $values, 'errors' => $errors)) ?> +
    + +
    + task->renderColorField($values) ?> + task->renderAssigneeField($users_list, $values, $errors) ?> + helper->newTaskHelper->renderGroupField($values, $errors) ?> + helper->newTaskHelper->renderMultiAssigneeField($users_list, $values) ?> + task->renderCategoryField($categories_list, $values, $errors) ?> + task->renderPriorityField($project, $values) ?> + + hook->render('template:task:form:second-column', array('values' => $values, 'errors' => $errors)) ?> +
    + +
    + task->renderDueDateField($values, $errors) ?> + task->renderStartDateField($values, $errors) ?> + task->renderTimeEstimatedField($values, $errors) ?> + task->renderTimeSpentField($values, $errors) ?> + task->renderScoreField($values, $errors) ?> + task->renderReferenceField($values, $errors) ?> + + hook->render('template:task:form:third-column', array('values' => $values, 'errors' => $errors)) ?> +
    + +
    + modal->submitButtons() ?> +
    +
    +
    diff --git a/plugins/Group_assign/Test/Helper/NewTaskHelperTest.php b/plugins/Group_assign/Test/Helper/NewTaskHelperTest.php new file mode 100644 index 00000000..8c378474 --- /dev/null +++ b/plugins/Group_assign/Test/Helper/NewTaskHelperTest.php @@ -0,0 +1,34 @@ +container); + $plugin->scan(); + } + public function testSelectPriority() + { + $helper = new NewTaskHelper($this->container); + $this->assertNotEmpty($helper->renderPriorityField(array('priority_end' => '1', 'priority_start' => '5', 'priority_default' => '2'), array())); + $this->assertNotEmpty($helper->renderPriorityField(array('priority_end' => '3', 'priority_start' => '1', 'priority_default' => '2'), array())); + } + public function testFormatPriority() + { + $helper = new NewTaskHelper($this->container); + $this->assertEquals( + 'P2', + $helper->renderPriority(2) + ); + $this->assertEquals( + '-P6', + $helper->renderPriority(-6) + ); + } +} diff --git a/plugins/Group_assign/Test/Model/NewTaskFinderModelTest.php b/plugins/Group_assign/Test/Model/NewTaskFinderModelTest.php new file mode 100644 index 00000000..58ff4404 --- /dev/null +++ b/plugins/Group_assign/Test/Model/NewTaskFinderModelTest.php @@ -0,0 +1,184 @@ +container); + $plugin->scan(); + } + + public function testGetDetails() + { + $taskCreationModel = new TaskCreationModel($this->container); + $taskFinderModel = new NewTaskFinderModel($this->container); + $projectModel = new ProjectModel($this->container); + $categoryModel = new \Kanboard\Model\CategoryModel($this->container); + $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $categoryModel->create(array('project_id' => 1, 'name' => 'C1'))); + $this->assertEquals(1, $taskCreationModel->create(array( + 'project_id' => 1, + 'title' => 'Task #1', + 'reference' => 'test', + 'description' => 'desc', + 'owner_id' => 1, + 'category_id' => 1, + ))); + $task = $taskFinderModel->getDetails(1); + $this->assertEquals(1, $task['id']); + $this->assertEquals('test', $task['reference']); + $this->assertEquals('Task #1', $task['title']); + $this->assertEquals('desc', $task['description']); + $this->assertEquals(time(), $task['date_creation'], 'Delta', 1); + $this->assertEquals(time(), $task['date_modification'], 'Delta', 1); + $this->assertEquals(time(), $task['date_moved'], 'Delta', 1); + $this->assertEquals(0, $task['date_completed']); + $this->assertEquals(0, $task['date_due']); + $this->assertEquals(0, $task['date_started']); + $this->assertEquals(0, $task['time_estimated']); + $this->assertEquals(0, $task['time_spent']); + $this->assertEquals('yellow', $task['color_id']); + $this->assertEquals(1, $task['project_id']); + $this->assertEquals(1, $task['column_id']); + $this->assertEquals(1, $task['owner_id']); + $this->assertEquals(0, $task['creator_id']); + $this->assertEquals(1, $task['position']); + $this->assertEquals(TaskModel::STATUS_OPEN, $task['is_active']); + $this->assertEquals(0, $task['score']); + $this->assertEquals(1, $task['category_id']); + $this->assertEquals(0, $task['priority']); + $this->assertEquals(1, $task['swimlane_id']); + $this->assertEquals(TaskModel::RECURRING_STATUS_NONE, $task['recurrence_status']); + $this->assertEquals(TaskModel::RECURRING_TRIGGER_FIRST_COLUMN, $task['recurrence_trigger']); + $this->assertEquals(0, $task['recurrence_factor']); + $this->assertEquals(TaskModel::RECURRING_TIMEFRAME_DAYS, $task['recurrence_timeframe']); + $this->assertEquals(TaskModel::RECURRING_BASEDATE_DUEDATE, $task['recurrence_basedate']); + $this->assertEquals(0, $task['recurrence_parent']); + $this->assertEquals(0, $task['recurrence_child']); + $this->assertEquals('C1', $task['category_name']); + $this->assertEquals('Default swimlane', $task['swimlane_name']); + $this->assertEquals('Project #1', $task['project_name']); + $this->assertEquals('Backlog', $task['column_title']); + $this->assertEquals('admin', $task['assignee_username']); + $this->assertEquals('', $task['assignee_name']); + $this->assertEquals('', $task['creator_username']); + $this->assertEquals('', $task['creator_name']); + } + public function testGetTasksForDashboardWithHiddenColumn() + { + $taskCreationModel = new TaskCreationModel($this->container); + $taskFinderModel = new NewTaskFinderModel($this->container); + $projectModel = new ProjectModel($this->container); + $columnModel = new ColumnModel($this->container); + $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1))); + $this->assertEquals(2, $taskCreationModel->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 2, 'owner_id' => 1))); + $tasks = $taskFinderModel->getUserQuery(1)->findAll(); + $this->assertCount(2, $tasks); + $this->assertTrue($columnModel->update(2, 'Test', 0, '', 1)); + $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)); + $tasks = $taskFinderModel->getUserQuery(1)->findAll(); + $this->assertCount(2, $tasks); + } + public function testGetOverdueTasks() + { + $taskCreationModel = new TaskCreationModel($this->container); + $taskFinderModel = new NewTaskFinderModel($this->container); + $projectModel = new ProjectModel($this->container); + $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1, 'date_due' => strtotime('-1 day')))); + $this->assertEquals(2, $taskCreationModel->create(array('title' => 'Task #2', 'project_id' => 1, 'date_due' => strtotime('+1 day')))); + $this->assertEquals(3, $taskCreationModel->create(array('title' => 'Task #3', 'project_id' => 1, 'date_due' => 0))); + $this->assertEquals(4, $taskCreationModel->create(array('title' => 'Task #3', 'project_id' => 1))); + $tasks = $taskFinderModel->getOverdueTasks(); + $this->assertNotEmpty($tasks); + $this->assertTrue(is_array($tasks)); + $this->assertCount(1, $tasks); + $this->assertEquals('Task #1', $tasks[0]['title']); + } + public function testGetOverdueTasksByProject() + { + $taskCreationModel = new TaskCreationModel($this->container); + $taskFinderModel = new NewTaskFinderModel($this->container); + $projectModel = new ProjectModel($this->container); + $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1'))); + $this->assertEquals(2, $projectModel->create(array('name' => 'Project #2'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1, 'date_due' => strtotime('-1 day')))); + $this->assertEquals(2, $taskCreationModel->create(array('title' => 'Task #2', 'project_id' => 2, 'date_due' => strtotime('-1 day')))); + $this->assertEquals(3, $taskCreationModel->create(array('title' => 'Task #3', 'project_id' => 1, 'date_due' => strtotime('+1 day')))); + $this->assertEquals(4, $taskCreationModel->create(array('title' => 'Task #4', 'project_id' => 1, 'date_due' => 0))); + $this->assertEquals(5, $taskCreationModel->create(array('title' => 'Task #5', 'project_id' => 1))); + $tasks = $taskFinderModel->getOverdueTasksByProject(1); + $this->assertNotEmpty($tasks); + $this->assertTrue(is_array($tasks)); + $this->assertCount(1, $tasks); + $this->assertEquals('Task #1', $tasks[0]['title']); + } + public function testGetOverdueTasksByUser() + { + $taskCreationModel = new TaskCreationModel($this->container); + $taskFinderModel = new NewTaskFinderModel($this->container); + $projectModel = new ProjectModel($this->container); + $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1'))); + $this->assertEquals(2, $projectModel->create(array('name' => 'Project #2'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1, 'owner_id' => 1, 'date_due' => strtotime('-1 day')))); + $this->assertEquals(2, $taskCreationModel->create(array('title' => 'Task #2', 'project_id' => 2, 'owner_id' => 1, 'date_due' => strtotime('-1 day')))); + $this->assertEquals(3, $taskCreationModel->create(array('title' => 'Task #3', 'project_id' => 1, 'date_due' => strtotime('+1 day')))); + $this->assertEquals(4, $taskCreationModel->create(array('title' => 'Task #4', 'project_id' => 1, 'date_due' => 0))); + $this->assertEquals(5, $taskCreationModel->create(array('title' => 'Task #5', 'project_id' => 1))); + $tasks = $taskFinderModel->getOverdueTasksByUser(1); + $this->assertNotEmpty($tasks); + $this->assertTrue(is_array($tasks)); + $this->assertCount(2, $tasks); + $this->assertEquals(1, $tasks[0]['id']); + $this->assertEquals('Task #1', $tasks[0]['title']); + $this->assertEquals(1, $tasks[0]['owner_id']); + $this->assertEquals(1, $tasks[0]['project_id']); + $this->assertEquals('Project #1', $tasks[0]['project_name']); + $this->assertEquals('admin', $tasks[0]['assignee_username']); + $this->assertEquals('', $tasks[0]['assignee_name']); + $this->assertEquals('Task #2', $tasks[1]['title']); + } + public function testCountByProject() + { + $taskCreationModel = new TaskCreationModel($this->container); + $taskFinderModel = new NewTaskFinderModel($this->container); + $projectModel = new ProjectModel($this->container); + $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1'))); + $this->assertEquals(2, $projectModel->create(array('name' => 'Project #2'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1))); + $this->assertEquals(2, $taskCreationModel->create(array('title' => 'Task #2', 'project_id' => 2))); + $this->assertEquals(3, $taskCreationModel->create(array('title' => 'Task #3', 'project_id' => 2))); + $this->assertEquals(1, $taskFinderModel->countByProjectId(1)); + $this->assertEquals(2, $taskFinderModel->countByProjectId(2)); + } + public function testGetProjectToken() + { + $taskCreationModel = new TaskCreationModel($this->container); + $taskFinderModel = new NewTaskFinderModel($this->container); + $projectModel = new ProjectModel($this->container); + $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1'))); + $this->assertEquals(2, $projectModel->create(array('name' => 'Project #2'))); + $this->assertTrue($projectModel->enablePublicAccess(1)); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1))); + $this->assertEquals(2, $taskCreationModel->create(array('title' => 'Task #2', 'project_id' => 2))); + $project = $projectModel->getById(1); + $this->assertEquals($project['token'], $taskFinderModel->getProjectToken(1)); + $this->assertEmpty($taskFinderModel->getProjectToken(2)); + } +} diff --git a/plugins/Group_assign/Test/PluginTest.php b/plugins/Group_assign/Test/PluginTest.php new file mode 100644 index 00000000..c647786c --- /dev/null +++ b/plugins/Group_assign/Test/PluginTest.php @@ -0,0 +1,19 @@ +container); + $this->assertSame(null, $plugin->initialize()); + $this->assertNotEmpty($plugin->getPluginName()); + $this->assertNotEmpty($plugin->getPluginDescription()); + $this->assertNotEmpty($plugin->getPluginAuthor()); + $this->assertNotEmpty($plugin->getPluginVersion()); + $this->assertNotEmpty($plugin->getPluginHomepage()); + } +} diff --git a/plugins/Group_assign/_config.yml b/plugins/Group_assign/_config.yml new file mode 100644 index 00000000..be854e84 --- /dev/null +++ b/plugins/Group_assign/_config.yml @@ -0,0 +1,3 @@ +theme: jekyll-theme-cayman +plugins: + - jemoji -- cgit v1.2.3