├── .travis.yml
├── Action
├── AssignGroup.php
├── EmailGroup.php
├── EmailGroupDue.php
├── EmailOtherAssignees.php
└── EmailOtherAssigneesDue.php
├── Api
└── Procedure
│ └── GroupAssignTaskProcedures.php
├── Assets
├── css
│ └── group_assign.css
└── js
│ └── group_assign.js
├── Controller
├── GroupAssignTaskCreationController.php
└── GroupAssignTaskModificationController.php
├── Filter
└── TaskAllAssigneeFilter.php
├── Helper
├── NewTaskHelper.php
└── SizeAvatarHelperExtend.php
├── LICENSE
├── Locale
├── bg_BG
│ └── translations.php
├── de_DE
│ └── translations.php
├── de_DE_du
│ └── translations.php
├── fr_FR
│ └── translations.php
├── pt_BR
│ └── translations.php
└── uk_UA
│ └── translations.php
├── Makefile
├── Model
├── GroupAssignCalendarModel.php
├── GroupAssignTaskDuplicationModel.php
├── GroupColorExtension.php
├── MultiselectMemberModel.php
├── MultiselectModel.php
├── NewMetaMagikSubquery.php
├── NewTaskFinderModel.php
├── NewUserNotificationFilterModel.php
├── OldMetaMagikSubquery.php
├── OldTaskFinderModel.php
├── TaskProjectMoveModel.php
└── TaskRecurrenceModel.php
├── Plugin.php
├── README.md
├── Schema
├── Mysql.php
├── Postgres.php
└── Sqlite.php
├── Template
├── action_creation
│ └── params.php
├── board
│ ├── filter.php
│ ├── group.php
│ └── multi.php
├── config
│ └── toggle.php
├── header
│ └── user_dropdown.php
├── task
│ ├── changes.php
│ ├── details.php
│ └── multi.php
├── task_creation
│ ├── show.php
│ └── show_TT.php
└── task_modification
│ ├── show.php
│ └── show_TT.php
├── Test
├── Helper
│ └── NewTaskHelperTest.php
├── Model
│ └── NewTaskFinderModelTest.php
└── PluginTest.php
└── _config.yml
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | sudo: false
3 |
4 | services:
5 | - postgresql
6 | - mysql
7 |
8 | php:
9 | - 7.3
10 | - 7.2
11 |
12 | env:
13 | global:
14 | - PLUGIN=Group_assign
15 | - KANBOARD_REPO=https://github.com/kanboard/kanboard.git
16 | matrix:
17 | - DB=sqlite
18 | - DB=mysql
19 | - DB=postgres
20 |
21 | matrix:
22 | fast_finish: true
23 |
24 | install:
25 | - git clone --depth 1 $KANBOARD_REPO
26 | - ln -s $TRAVIS_BUILD_DIR kanboard/plugins/$PLUGIN
27 |
28 | before_script:
29 | - cd kanboard
30 | - phpenv config-add tests/php.ini
31 | - composer install
32 | - ls -la plugins/
33 |
34 | script:
35 | - ./vendor/bin/phpunit -c tests/units.$DB.xml plugins/$PLUGIN/Test/
36 |
--------------------------------------------------------------------------------
/Action/AssignGroup.php:
--------------------------------------------------------------------------------
1 | t('Column'),
46 | 'group_id' => t('Group'),
47 | );
48 | }
49 |
50 | /**
51 | * Get the required parameter for the event
52 | *
53 | * @access public
54 | * @return string[]
55 | */
56 | public function getEventRequiredParameters()
57 | {
58 | return array(
59 | 'task_id',
60 | 'task' => array(
61 | 'project_id',
62 | 'column_id',
63 | ),
64 | );
65 | }
66 |
67 | /**
68 | * Execute the action (assign the given user)
69 | *
70 | * @access public
71 | * @param array $data Event data dictionary
72 | * @return bool True if the action was executed or false when not executed
73 | */
74 | public function doAction(array $data)
75 | {
76 | $values = array(
77 | 'id' => $data['task_id'],
78 | 'owner_gp' => $this->getParam('group_id'),
79 | );
80 |
81 | return $this->taskModificationModel->update($values);
82 | }
83 |
84 | /**
85 | * Check if the event data meet the action condition
86 | *
87 | * @access public
88 | * @param array $data Event data dictionary
89 | * @return bool
90 | */
91 | public function hasRequiredCondition(array $data)
92 | {
93 | return $data['task']['column_id'] == $this->getParam('column_id');
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Action/EmailGroup.php:
--------------------------------------------------------------------------------
1 | t('Column'),
31 | 'subject' => t('Email subject'),
32 | 'check_box_include_title' => t('Include Task Title and ID in subject line?'),
33 | );
34 | }
35 |
36 |
37 | public function getEventRequiredParameters()
38 | {
39 | return array(
40 | 'task_id',
41 | 'task' => array(
42 | 'project_id',
43 | 'column_id',
44 | 'owner_id',
45 | 'owner_gp',
46 | ),
47 | );
48 | }
49 |
50 |
51 | public function doAction(array $data)
52 | {
53 | $groupmembers = $this->groupMemberModel->getMembers($data['task']['owner_gp']);
54 |
55 | if ($this->getParam('check_box_include_title') == true) {
56 | $subject = $this->getParam('subject') . ": " . $data['task']['title'] . "(#" . $data['task_id'] . ")";
57 | } else {
58 | $subject = $this->getParam('subject');
59 | }
60 |
61 | if (! empty($groupmembers)) {
62 | foreach ($groupmembers as $members) {
63 | $user = $this->userModel->getById($members['id']);
64 | if (! empty($user['email'])) {
65 | $this->emailClient->send(
66 | $user['email'],
67 | $user['name'] ?: $user['username'],
68 | $subject,
69 | $this->template->render('notification/task_create', array(
70 | 'task' => $data['task'],
71 | ))
72 | );
73 | }
74 | }
75 | return true;
76 | }
77 |
78 | return false;
79 | }
80 |
81 |
82 |
83 | public function hasRequiredCondition(array $data)
84 | {
85 | return $data['task']['column_id'] == $this->getParam('column_id');
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Action/EmailGroupDue.php:
--------------------------------------------------------------------------------
1 | t('Email subject'),
45 | 'duration' => t('Duration in days'),
46 | );
47 | }
48 | /**
49 | * Get the required parameter for the event
50 | *
51 | * @access public
52 | * @return string[]
53 | */
54 | public function getEventRequiredParameters()
55 | {
56 | return array('tasks');
57 | }
58 | /**
59 | * Check if the event data meet the action condition
60 | *
61 | * @access public
62 | * @param array $data Event data dictionary
63 | * @return bool
64 | */
65 | public function hasRequiredCondition(array $data)
66 | {
67 | return count($data['tasks']) > 0;
68 | }
69 |
70 | public function doAction(array $data)
71 | {
72 | $results = array();
73 | $max = $this->getParam('duration') * 86400;
74 |
75 | foreach ($data['tasks'] as $task) {
76 | $groupmembers = $this->groupMemberModel->getMembers($task['owner_gp']);
77 |
78 | if (! empty($groupmembers)) {
79 | foreach ($groupmembers as $members) {
80 | $user = $this->userModel->getById($members['id']);
81 |
82 | $duration = $task['date_due'] - time();
83 | if ($task['date_due'] > 0) {
84 | if ($duration < $max) {
85 | if (! empty($user['email'])) {
86 | $results[] = $this->sendEmail($task['id'], $user);
87 | }
88 | }
89 | }
90 | }
91 | }
92 | }
93 |
94 | return in_array(true, $results, true);
95 | }
96 | /**
97 | * Send email
98 | *
99 | * @access private
100 | * @param integer $task_id
101 | * @param array $user
102 | * @return boolean
103 | */
104 | private function sendEmail($task_id, array $user)
105 | {
106 | $task = $this->taskFinderModel->getDetails($task_id);
107 | $this->emailClient->send(
108 | $user['email'],
109 | $user['name'] ?: $user['username'],
110 | $this->getParam('subject'),
111 | $this->template->render('notification/task_create', array('task' => $task))
112 | );
113 | return true;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Action/EmailOtherAssignees.php:
--------------------------------------------------------------------------------
1 | t('Column'),
30 | 'subject' => t('Email subject'),
31 | 'check_box_include_title' => t('Include Task Title and ID in subject line?'),
32 | );
33 | }
34 |
35 |
36 | public function getEventRequiredParameters()
37 | {
38 | return array(
39 | 'task_id',
40 | 'task' => array(
41 | 'project_id',
42 | 'column_id',
43 | 'owner_id',
44 | 'owner_ms',
45 | ),
46 | );
47 | }
48 |
49 |
50 | public function doAction(array $data)
51 | {
52 | $multimembers = $this->multiselectMemberModel->getMembers($data['task']['owner_ms']);
53 |
54 | if ($this->getParam('check_box_include_title') == true) {
55 | $subject = $this->getParam('subject') . ": " . $data['task']['title'] . "(#" . $data['task_id'] . ")";
56 | } else {
57 | $subject = $this->getParam('subject');
58 | }
59 |
60 | if (! empty($multimembers)) {
61 | foreach ($multimembers as $members) {
62 | $user = $this->userModel->getById($members['id']);
63 | if (! empty($user['email'])) {
64 | $this->emailClient->send(
65 | $user['email'],
66 | $user['name'] ?: $user['username'],
67 | $subject,
68 | $this->template->render('notification/task_create', array(
69 | 'task' => $data['task'],
70 | ))
71 | );
72 | }
73 | }
74 | return true;
75 | }
76 |
77 | return false;
78 | }
79 |
80 |
81 |
82 | public function hasRequiredCondition(array $data)
83 | {
84 | return $data['task']['column_id'] == $this->getParam('column_id');
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Action/EmailOtherAssigneesDue.php:
--------------------------------------------------------------------------------
1 | t('Email subject'),
45 | 'duration' => t('Duration in days'),
46 | );
47 | }
48 | /**
49 | * Get the required parameter for the event
50 | *
51 | * @access public
52 | * @return string[]
53 | */
54 | public function getEventRequiredParameters()
55 | {
56 | return array('tasks');
57 | }
58 | /**
59 | * Check if the event data meet the action condition
60 | *
61 | * @access public
62 | * @param array $data Event data dictionary
63 | * @return bool
64 | */
65 | public function hasRequiredCondition(array $data)
66 | {
67 | return count($data['tasks']) > 0;
68 | }
69 |
70 | public function doAction(array $data)
71 | {
72 | $results = array();
73 | $max = $this->getParam('duration') * 86400;
74 |
75 | foreach ($data['tasks'] as $task) {
76 | $groupmembers = $this->multiselectMemberModel->getMembers($task['owner_ms']);
77 |
78 | if (! empty($groupmembers)) {
79 | foreach ($groupmembers as $members) {
80 | $user = $this->userModel->getById($members['id']);
81 |
82 | $duration = $task['date_due'] - time();
83 | if ($task['date_due'] > 0) {
84 | if ($duration < $max) {
85 | if (! empty($user['email'])) {
86 | $results[] = $this->sendEmail($task['id'], $user);
87 | }
88 | }
89 | }
90 | }
91 | }
92 | }
93 |
94 | return in_array(true, $results, true);
95 | }
96 | /**
97 | * Send email
98 | *
99 | * @access private
100 | * @param integer $task_id
101 | * @param array $user
102 | * @return boolean
103 | */
104 | private function sendEmail($task_id, array $user)
105 | {
106 | $task = $this->taskFinderModel->getDetails($task_id);
107 | $this->emailClient->send(
108 | $user['email'],
109 | $user['name'] ?: $user['username'],
110 | $this->getParam('subject'),
111 | $this->template->render('notification/task_create', array('task' => $task))
112 | );
113 | return true;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Api/Procedure/GroupAssignTaskProcedures.php:
--------------------------------------------------------------------------------
1 | container)->check($this->getClassName(), 'createTaskGroupAssign', $project_id);
52 |
53 | if ($owner_id !== 0 && ! $this->projectPermissionModel->isAssignable($project_id, $owner_id)) {
54 | return false;
55 | }
56 |
57 | if ($this->userSession->isLogged()) {
58 | $creator_id = $this->userSession->getId();
59 | }
60 |
61 | if (!empty($other_assignees)) {
62 | $ms_id = $this->multiselectModel->create();
63 | foreach ($other_assignees as $user) {
64 | $this->multiselectMemberModel->addUser($ms_id, $user);
65 | }
66 | }
67 |
68 |
69 | $values = array(
70 | 'title' => $title,
71 | 'project_id' => $project_id,
72 | 'color_id' => $color_id,
73 | 'column_id' => $column_id,
74 | 'owner_id' => $owner_id,
75 | 'creator_id' => $creator_id,
76 | 'date_due' => $date_due,
77 | 'description' => $description,
78 | 'category_id' => $category_id,
79 | 'score' => $score,
80 | 'swimlane_id' => $swimlane_id,
81 | 'recurrence_status' => $recurrence_status,
82 | 'recurrence_trigger' => $recurrence_trigger,
83 | 'recurrence_factor' => $recurrence_factor,
84 | 'recurrence_timeframe' => $recurrence_timeframe,
85 | 'recurrence_basedate' => $recurrence_basedate,
86 | 'reference' => $reference,
87 | 'priority' => $priority,
88 | 'tags' => $tags,
89 | 'date_started' => $date_started,
90 | 'time_spent' => $time_spent,
91 | 'time_estimated' => $time_estimated,
92 | 'owner_gp' => $group_id,
93 | 'owner_ms' => (!empty($other_assignees)) ? $ms_id : 0,
94 | );
95 |
96 | list($valid, ) = $this->taskValidator->validateCreation($values);
97 |
98 | return $valid ? $this->taskCreationModel->create($values) : false;
99 | }
100 |
101 | public function updateTaskGroupAssign(
102 | $id,
103 | $title = null,
104 | $color_id = null,
105 | $owner_id = null,
106 | $date_due = null,
107 | $description = null,
108 | $category_id = null,
109 | $score = null,
110 | $priority = null,
111 | $recurrence_status = null,
112 | $recurrence_trigger = null,
113 | $recurrence_factor = null,
114 | $recurrence_timeframe = null,
115 | $recurrence_basedate = null,
116 | $reference = null,
117 | $tags = null,
118 | $date_started = null,
119 | $time_spent = null,
120 | $time_estimated = null,
121 | $group_id = 0,
122 | array $other_assignees = array()
123 | )
124 | {
125 | TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTask', $id);
126 | $project_id = $this->taskFinderModel->getProjectId($id);
127 |
128 | if ($project_id === 0) {
129 | return false;
130 | }
131 |
132 | if ($owner_id !== null && $owner_id != 0 && ! $this->projectPermissionModel->isAssignable($project_id, $owner_id)) {
133 | return false;
134 | }
135 |
136 | if (!empty($other_assignees)) {
137 | $ms_id = $this->multiselectModel->create();
138 | foreach ($other_assignees as $user) {
139 | $this->multiselectMemberModel->addUser($ms_id, $user);
140 | }
141 | }
142 |
143 | $values = $this->filterValues(array(
144 | 'id' => $id,
145 | 'title' => $title,
146 | 'color_id' => $color_id,
147 | 'owner_id' => $owner_id,
148 | 'date_due' => $date_due,
149 | 'description' => $description,
150 | 'category_id' => $category_id,
151 | 'score' => $score,
152 | 'recurrence_status' => $recurrence_status,
153 | 'recurrence_trigger' => $recurrence_trigger,
154 | 'recurrence_factor' => $recurrence_factor,
155 | 'recurrence_timeframe' => $recurrence_timeframe,
156 | 'recurrence_basedate' => $recurrence_basedate,
157 | 'reference' => $reference,
158 | 'priority' => $priority,
159 | 'tags' => $tags,
160 | 'date_started' => $date_started,
161 | 'time_spent' => $time_spent,
162 | 'time_estimated' => $time_estimated,
163 | 'owner_gp' => $group_id,
164 | 'owner_ms' => (!empty($other_assignees)) ? $ms_id : 0,
165 | ));
166 |
167 | list($valid) = $this->taskValidator->validateApiModification($values);
168 | return $valid && $this->taskModificationModel->update($values);
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/Assets/css/group_assign.css:
--------------------------------------------------------------------------------
1 | .avatar-13 .avatar-letter {
2 | border-radius: 6.5px;
3 | line-height: 13px;
4 | width: 13px;
5 | font-size: 7px;
6 | }
7 | .avatar-13 img {
8 | border-radius: 6.5px;
9 | }
10 |
11 | .assigned-group {
12 | display: inline-block;
13 | margin: 3px 3px 0 0;
14 | padding: 1px 3px 1px 3px;
15 | color: #333;
16 | border-radius: 4px;
17 | }
18 |
--------------------------------------------------------------------------------
/Assets/js/group_assign.js:
--------------------------------------------------------------------------------
1 |
2 | KB.on('modal.afterRender', function () {
3 | $(".group-assign-select").select2({
4 | });
5 | });
--------------------------------------------------------------------------------
/Controller/GroupAssignTaskCreationController.php:
--------------------------------------------------------------------------------
1 | getProject();
31 | $swimlanesList = $this->swimlaneModel->getList($project['id'], false, true);
32 | $values += $this->prepareValues($project['is_private'], $swimlanesList);
33 |
34 | $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values));
35 | $values = $this->hook->merge('controller:task-creation:form:default', $values, array('default_values' => $values));
36 |
37 | $this->response->html($this->template->render('task_creation/show', array(
38 | 'project' => $project,
39 | 'errors' => $errors,
40 | 'values' => $values + array('project_id' => $project['id']),
41 | 'columns_list' => $this->columnModel->getList($project['id']),
42 | 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id'], true, false, $project['is_private'] == 1),
43 | 'categories_list' => $this->categoryModel->getList($project['id']),
44 | 'swimlanes_list' => $swimlanesList,
45 | )));
46 | }
47 |
48 | /**
49 | * Validate and save a new task
50 | *
51 | * @access public
52 | */
53 | public function save()
54 | {
55 | $project = $this->getProject();
56 | $values = $this->request->getValues();
57 | $values['project_id'] = $project['id'];
58 | if (isset($values['owner_ms']) && !empty($values['owner_ms'])) {
59 | $ms_id = $this->multiselectModel->create();
60 | foreach ($values['owner_ms'] as $user) {
61 | $this->multiselectMemberModel->addUser($ms_id, $user);
62 | }
63 | unset($values['owner_ms']);
64 | $values['owner_ms'] = $ms_id;
65 | }
66 |
67 | list($valid, $errors) = $this->taskValidator->validateCreation($values);
68 |
69 | if (! $valid) {
70 | $this->flash->failure(t('Unable to create your task.'));
71 | $this->show($values, $errors);
72 | } elseif (! $this->helper->projectRole->canCreateTaskInColumn($project['id'], $values['column_id'])) {
73 | $this->flash->failure(t('You cannot create tasks in this column.'));
74 | $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true);
75 | } else {
76 | $task_id = $this->taskCreationModel->create($values);
77 |
78 | if ($task_id > 0) {
79 | $this->flash->success(t('Task created successfully.'));
80 | $this->afterSave($project, $values, $task_id);
81 | } else {
82 | $this->flash->failure(t('Unable to create this task.'));
83 | $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true);
84 | }
85 | }
86 | }
87 |
88 | /**
89 | * Duplicate created tasks to multiple projects
90 | *
91 | * @throws PageNotFoundException
92 | */
93 | public function duplicateProjects()
94 | {
95 | $project = $this->getProject();
96 | $values = $this->request->getValues();
97 |
98 | if (isset($values['project_ids'])) {
99 | foreach ($values['project_ids'] as $project_id) {
100 | $this->taskProjectDuplicationModel->duplicateToProject($values['task_id'], $project_id);
101 | }
102 | }
103 |
104 | $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true);
105 | }
106 |
107 | /**
108 | * Executed after the task is saved
109 | *
110 | * @param array $project
111 | * @param array $values
112 | * @param integer $task_id
113 | */
114 | protected function afterSave(array $project, array &$values, $task_id)
115 | {
116 | if (isset($values['duplicate_multiple_projects']) && $values['duplicate_multiple_projects'] == 1) {
117 | $this->chooseProjects($project, $task_id);
118 | } elseif (isset($values['another_task']) && $values['another_task'] == 1) {
119 | $this->show(array(
120 | 'owner_id' => $values['owner_id'],
121 | 'color_id' => $values['color_id'],
122 | 'category_id' => isset($values['category_id']) ? $values['category_id'] : 0,
123 | 'column_id' => $values['column_id'],
124 | 'swimlane_id' => isset($values['swimlane_id']) ? $values['swimlane_id'] : 0,
125 | 'another_task' => 1,
126 | ));
127 | } else {
128 | $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true);
129 | }
130 | }
131 |
132 | /**
133 | * Prepare form values
134 | *
135 | * @access protected
136 | * @param bool $isPrivateProject
137 | * @param array $swimlanesList
138 | * @return array
139 | */
140 | protected function prepareValues($isPrivateProject, array $swimlanesList)
141 | {
142 | $values = array(
143 | 'swimlane_id' => $this->request->getIntegerParam('swimlane_id', key($swimlanesList)),
144 | 'column_id' => $this->request->getIntegerParam('column_id'),
145 | 'color_id' => $this->colorModel->getDefaultColor(),
146 | );
147 |
148 | if ($isPrivateProject) {
149 | $values['owner_id'] = $this->userSession->getId();
150 | }
151 |
152 | return $values;
153 | }
154 |
155 | /**
156 | * Choose projects
157 | *
158 | * @param array $project
159 | * @param integer $task_id
160 | */
161 | protected function chooseProjects(array $project, $task_id)
162 | {
163 | $task = $this->taskFinderModel->getById($task_id);
164 | $projects = $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId());
165 | unset($projects[$project['id']]);
166 |
167 | $this->response->html($this->template->render('task_creation/duplicate_projects', array(
168 | 'project' => $project,
169 | 'task' => $task,
170 | 'projects_list' => $projects,
171 | 'values' => array('task_id' => $task['id'])
172 | )));
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/Controller/GroupAssignTaskModificationController.php:
--------------------------------------------------------------------------------
1 | getTask();
29 | $values = ['id' => $task['id'], 'owner_id' => $this->userSession->getId()];
30 |
31 | if (! $this->helper->projectRole->canUpdateTask($task)) {
32 | throw new AccessForbiddenException(t('You are not allowed to update tasks assigned to someone else.'));
33 | }
34 |
35 | $this->taskModificationModel->update($values);
36 | $this->redirectAfterQuickAction($task);
37 | }
38 |
39 | /**
40 | * Set the start date automatically
41 | *
42 | * @access public
43 | */
44 | public function start()
45 | {
46 | $task = $this->getTask();
47 | $values = ['id' => $task['id'], 'date_started' => time()];
48 |
49 | if (! $this->helper->projectRole->canUpdateTask($task)) {
50 | throw new AccessForbiddenException(t('You are not allowed to update tasks assigned to someone else.'));
51 | }
52 |
53 | $this->taskModificationModel->update($values);
54 | $this->redirectAfterQuickAction($task);
55 | }
56 |
57 | protected function redirectAfterQuickAction(array $task)
58 | {
59 | switch ($this->request->getStringParam('redirect')) {
60 | case 'board':
61 | $this->response->redirect($this->helper->url->to('BoardViewController', 'show', ['project_id' => $task['project_id']]));
62 | break;
63 | case 'list':
64 | $this->response->redirect($this->helper->url->to('TaskListController', 'show', ['project_id' => $task['project_id']]));
65 | break;
66 | case 'dashboard':
67 | $this->response->redirect($this->helper->url->to('DashboardController', 'show', [], 'project-tasks-'.$task['project_id']));
68 | break;
69 | case 'dashboard-tasks':
70 | $this->response->redirect($this->helper->url->to('DashboardController', 'tasks', ['user_id' => $this->userSession->getId()]));
71 | break;
72 | default:
73 | $this->response->redirect($this->helper->url->to('TaskViewController', 'show', ['project_id' => $task['project_id'], 'task_id' => $task['id']]));
74 | }
75 | }
76 |
77 | /**
78 | * Display a form to edit a task
79 | *
80 | * @access public
81 | * @param array $values
82 | * @param array $errors
83 | * @throws \Kanboard\Core\Controller\AccessForbiddenException
84 | * @throws \Kanboard\Core\Controller\PageNotFoundException
85 | */
86 | public function edit(array $values = array(), array $errors = array())
87 | {
88 | $task = $this->getTask();
89 |
90 | if (! $this->helper->projectRole->canUpdateTask($task)) {
91 | throw new AccessForbiddenException(t('You are not allowed to update tasks assigned to someone else.'));
92 | }
93 |
94 | $project = $this->projectModel->getById($task['project_id']);
95 |
96 | if (empty($values)) {
97 | $values = $task;
98 | }
99 |
100 | $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values));
101 | $values = $this->hook->merge('controller:task-modification:form:default', $values, array('default_values' => $values));
102 |
103 | $params = array(
104 | 'project' => $project,
105 | 'values' => $values,
106 | 'errors' => $errors,
107 | 'task' => $task,
108 | 'tags' => $this->taskTagModel->getList($task['id']),
109 | 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($task['project_id']),
110 | 'categories_list' => $this->categoryModel->getList($task['project_id']),
111 | );
112 |
113 | $this->renderTemplate($task, $params);
114 | }
115 |
116 | protected function renderTemplate(array &$task, array &$params)
117 | {
118 | if (empty($task['external_uri'])) {
119 | $this->response->html($this->template->render('task_modification/show', $params));
120 | } else {
121 | try {
122 | $taskProvider = $this->externalTaskManager->getProvider($task['external_provider']);
123 | $params['template'] = $taskProvider->getModificationFormTemplate();
124 | $params['external_task'] = $taskProvider->fetch($task['external_uri']);
125 | } catch (ExternalTaskAccessForbiddenException $e) {
126 | throw new AccessForbiddenException($e->getMessage());
127 | } catch (ExternalTaskException $e) {
128 | $params['error_message'] = $e->getMessage();
129 | }
130 |
131 | $this->response->html($this->template->render('external_task_modification/show', $params));
132 | }
133 | }
134 |
135 | /**
136 | * Validate and update a task
137 | *
138 | * @access public
139 | */
140 | public function update()
141 | {
142 | $previousMembers = array();
143 | $task = $this->getTask();
144 | $values = $this->request->getValues();
145 | $values['id'] = $task['id'];
146 | $values['project_id'] = $task['project_id'];
147 | if (isset($values['owner_ms']) && !empty($values['owner_ms'])) {
148 | if (!empty($task['owner_ms'])) {
149 | $ms_id = $task['owner_ms'];
150 | $previousMembers = $this->multiselectMemberModel->getMembers($ms_id);
151 | $this->multiselectMemberModel->removeAllUsers($ms_id);
152 | } else {
153 | $ms_id = $this->multiselectModel->create();
154 | }
155 | foreach ($values['owner_ms'] as $user) {
156 | if ($user !== 0) {
157 | $this->multiselectMemberModel->addUser($ms_id, $user);
158 | }
159 | }
160 | unset($values['owner_ms']);
161 | $values['owner_ms'] = $ms_id;
162 |
163 | $newMembersSet = $this->multiselectMemberModel->getMembers($values['owner_ms']);
164 | if (sort($previousMembers) !== sort($newMembersSet)) {
165 | $this->multiselectMemberModel->assigneeChanged($task, $values);
166 | }
167 |
168 | if ($values['owner_gp'] !== $task['owner_gp']) {
169 | $this->multiselectMemberModel->assigneeChanged($task, $values);
170 | }
171 | } else {
172 | $this->multiselectMemberModel->removeAllUsers($task['owner_ms']);
173 | }
174 |
175 | list($valid, $errors) = $this->taskValidator->validateModification($values);
176 |
177 | if ($valid && $this->updateTask($task, $values, $errors)) {
178 | $this->flash->success(t('Task updated successfully.'));
179 | $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), true);
180 | } else {
181 | $this->flash->failure(t('Unable to update your task.'));
182 | $this->edit($values, $errors);
183 | }
184 | }
185 |
186 | protected function updateTask(array &$task, array &$values, array &$errors)
187 | {
188 | if (isset($values['owner_id']) && $values['owner_id'] != $task['owner_id'] && !$this->helper->projectRole->canChangeAssignee($task)) {
189 | throw new AccessForbiddenException(t('You are not allowed to change the assignee.'));
190 | }
191 |
192 | if (! $this->helper->projectRole->canUpdateTask($task)) {
193 | throw new AccessForbiddenException(t('You are not allowed to update tasks assigned to someone else.'));
194 | }
195 |
196 | $result = $this->taskModificationModel->update($values);
197 |
198 | if ($result && ! empty($task['external_uri'])) {
199 | try {
200 | $taskProvider = $this->externalTaskManager->getProvider($task['external_provider']);
201 | $result = $taskProvider->save($task['external_uri'], $values, $errors);
202 | } catch (ExternalTaskAccessForbiddenException $e) {
203 | throw new AccessForbiddenException($e->getMessage());
204 | } catch (ExternalTaskException $e) {
205 | $this->logger->error($e->getMessage());
206 | $result = false;
207 | }
208 | }
209 |
210 | return $result;
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/Filter/TaskAllAssigneeFilter.php:
--------------------------------------------------------------------------------
1 | db = $db;
33 | return $this;
34 | }
35 |
36 | /**
37 | * Current user id
38 | *
39 | * @access private
40 | * @var int
41 | */
42 | private $currentUserId = 0;
43 |
44 | /**
45 | * Set current user id
46 | *
47 | * @access public
48 | * @param integer $userId
49 | * @return TaskAssigneeFilter
50 | */
51 | public function setCurrentUserId($userId)
52 | {
53 | $this->currentUserId = $userId;
54 | return $this;
55 | }
56 |
57 | /**
58 | * Get search attribute
59 | *
60 | * @access public
61 | * @return string[]
62 | */
63 | public function getAttributes()
64 | {
65 | return array('allassignees');
66 | }
67 |
68 | /**
69 | * Apply filter
70 | *
71 | * @access public
72 | * @return string
73 | */
74 | public function apply()
75 | {
76 | if (is_int($this->value) || ctype_digit($this->value)) {
77 | $this->query->beginOr();
78 | $this->query->eq(TaskModel::TABLE.'.owner_id', $this->value);
79 | $this->query->addCondition(TaskModel::TABLE.".owner_gp IN (SELECT group_id FROM ".GroupMemberModel::TABLE." WHERE ".GroupMemberModel::TABLE.".user_id='$this->value')");
80 | $this->query->addCondition(TaskModel::TABLE.".owner_ms IN (SELECT group_id FROM ".MultiselectMemberModel::TABLE." WHERE ".MultiselectMemberModel::TABLE.".user_id='$this->value')");
81 | $this->query->closeOr();
82 | } else {
83 | switch ($this->value) {
84 | case 'me':
85 | $this->query->beginOr();
86 | $this->query->eq(TaskModel::TABLE.'.owner_id', $this->currentUserId);
87 | $this->query->addCondition(TaskModel::TABLE.".owner_gp IN (SELECT group_id FROM ".GroupMemberModel::TABLE." WHERE ".GroupMemberModel::TABLE.".user_id='$this->currentUserId')");
88 | $this->query->addCondition(TaskModel::TABLE.".owner_ms IN (SELECT group_id FROM ".MultiselectMemberModel::TABLE." WHERE ".MultiselectMemberModel::TABLE.".user_id='$this->currentUserId')");
89 | $this->query->closeOr();
90 | break;
91 | case 'nobody':
92 | $this->query->eq(TaskModel::TABLE.'.owner_id', 0);
93 | $this->query->eq(TaskModel::TABLE.'.owner_gp', 0);
94 | $this->query->eq(TaskModel::TABLE.'.owner_ms', 0);
95 | break;
96 | default:
97 | $useridsarray = $this->getSubQuery()->findAllByColumn('id');
98 | $useridstring = implode("','", $useridsarray);
99 | (!empty($useridstring)) ? $useridstring = $useridstring : $useridstring = 0;
100 | if ($useridstring == '') {
101 | $useridstring = 0;
102 | }
103 | $this->query->beginOr();
104 | $this->query->ilike(UserModel::TABLE.'.username', '%'.$this->value.'%');
105 | $this->query->ilike(UserModel::TABLE.'.name', '%'.$this->value.'%');
106 | $this->query->addCondition(TaskModel::TABLE.".owner_gp IN (SELECT id FROM `".GroupModel::TABLE."` WHERE `".GroupModel::TABLE."`.name='$this->value')");
107 | $this->query->addCondition(TaskModel::TABLE.".owner_gp IN (SELECT group_id FROM ".GroupMemberModel::TABLE." WHERE ".GroupMemberModel::TABLE.".user_id IN ('$useridstring'))");
108 | $this->query->addCondition(TaskModel::TABLE.".owner_ms IN (SELECT group_id FROM ".MultiselectMemberModel::TABLE." WHERE ".MultiselectMemberModel::TABLE.".user_id IN ('$useridstring'))");
109 | $this->query->closeOr();
110 | }
111 | }
112 | }
113 | public function getSubQuery()
114 | {
115 | return $this->db->table(UserModel::TABLE)
116 | ->columns(
117 | UserModel::TABLE.'.id',
118 | UserModel::TABLE.'.username',
119 | UserModel::TABLE.'.name'
120 | )
121 | ->beginOr()
122 | ->ilike(UserModel::TABLE.'.username', '%'.$this->value.'%')
123 | ->ilike(UserModel::TABLE.'.name', '%'.$this->value.'%')
124 | ->closeOr();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Helper/NewTaskHelper.php:
--------------------------------------------------------------------------------
1 | colorModel->getList();
22 | }
23 |
24 | public function recurrenceTriggers()
25 | {
26 | return $this->taskRecurrenceModel->getRecurrenceTriggerList();
27 | }
28 |
29 | public function recurrenceTimeframes()
30 | {
31 | return $this->taskRecurrenceModel->getRecurrenceTimeframeList();
32 | }
33 |
34 | public function recurrenceBasedates()
35 | {
36 | return $this->taskRecurrenceModel->getRecurrenceBasedateList();
37 | }
38 |
39 | public function renderTitleField(array $values, array $errors)
40 | {
41 | return $this->helper->form->text(
42 | 'title',
43 | $values,
44 | $errors,
45 | array(
46 | 'autofocus',
47 | 'required',
48 | 'maxlength="200"',
49 | 'tabindex="1"',
50 | 'placeholder="'.t('Title').'"'
51 | )
52 | );
53 | }
54 |
55 | public function renderDescriptionField(array $values, array $errors)
56 | {
57 | return $this->helper->form->textEditor('description', $values, $errors, array('tabindex' => 2));
58 | }
59 |
60 | public function renderDescriptionTemplateDropdown($projectId)
61 | {
62 | $templates = $this->predefinedTaskDescriptionModel->getAll($projectId);
63 |
64 | if (! empty($templates)) {
65 | $html = '
';
66 | $html .= '';
67 | $html .= '
';
78 | return $html;
79 | }
80 |
81 | return '';
82 | }
83 |
84 | public function renderTagField(array $project, array $tags = array())
85 | {
86 | $options = $this->tagModel->getAssignableList($project['id']);
87 |
88 | $html = $this->helper->form->label(t('Tags'), 'tags[]');
89 | $html .= '';
90 | $html .= '';
102 |
103 | return $html;
104 | }
105 |
106 | public function renderColorField(array $values)
107 | {
108 | $colors = $this->colorModel->getList();
109 | $html = $this->helper->form->label(t('Color'), 'color_id');
110 | $html .= $this->helper->form->select('color_id', $colors, $values, array(), array(), 'color-picker');
111 | return $html;
112 | }
113 |
114 | public function renderAssigneeField(array $users, array $values, array $errors = array(), array $attributes = array())
115 | {
116 | if (isset($values['project_id']) && ! $this->helper->projectRole->canChangeAssignee($values)) {
117 | return '';
118 | }
119 |
120 | $attributes = array_merge(array('tabindex="3"'), $attributes);
121 |
122 | $html = $this->helper->form->label(t('Assignee'), 'owner_id');
123 | $html .= $this->helper->form->select('owner_id', $users, $values, $errors, $attributes);
124 | $html .= ' ';
125 | $html .= '';
126 | $html .= ''.t('Me').'';
127 | $html .= '';
128 |
129 | return $html;
130 | }
131 |
132 | public function renderMultiAssigneeField(array $users, array $values, array $errors = array(), array $attributes = array())
133 | {
134 | if (isset($values['project_id']) && ! $this->helper->projectRole->canChangeAssignee($values)) {
135 | return '';
136 | }
137 |
138 | $attributes = array_merge(array('tabindex="4"'), $attributes);
139 | $name = 'owner_ms';
140 |
141 | $html = $this->helper->form->label(t('Other Assignees'), $name.'[]');
142 |
143 | $html .= '';
171 |
172 | return $html;
173 | }
174 |
175 | public function renderGroupField(array $values, array $errors = array(), array $attributes = array())
176 | {
177 | if (isset($values['project_id']) && ! $this->helper->projectRole->canChangeAssignee($values)) {
178 | return '';
179 | }
180 | $groups = $this->projectGroupRoleModel->getGroups($values['project_id']);
181 | $groupnames = array();
182 | $groupids = array();
183 |
184 | $groupids[] = 0;
185 | $groupnames[] = t('Unassigned');
186 |
187 |
188 | foreach ($groups as $group) {
189 | // array_splice($groupnames, 1, 0, $group['name']);
190 | $groupnames[] = $group['name'];
191 | $groupids[] = $group['id'];
192 | }
193 |
194 | $groupvalues = array_combine($groupids, $groupnames);
195 |
196 |
197 | $attributes = array_merge(array('tabindex="4"'), $attributes);
198 |
199 | $html = $this->helper->form->label(t('Assigned Group'), 'owner_gp');
200 | $html .= $this->helper->form->select('owner_gp', $groupvalues, $values, $errors, $attributes);
201 | $html .= ' ';
202 |
203 | return $html;
204 | }
205 |
206 | public function renderCategoryField(array $categories, array $values, array $errors = array(), array $attributes = array(), $allow_one_item = false)
207 | {
208 | $attributes = array_merge(array('tabindex="5"'), $attributes);
209 | $html = '';
210 |
211 | if (! (! $allow_one_item && count($categories) === 1 && key($categories) == 0)) {
212 | $html .= $this->helper->form->label(t('Category'), 'category_id');
213 | $html .= $this->helper->form->select('category_id', $categories, $values, $errors, $attributes);
214 | }
215 |
216 | return $html;
217 | }
218 |
219 | public function renderSwimlaneField(array $swimlanes, array $values, array $errors = array(), array $attributes = array())
220 | {
221 | $attributes = array_merge(array('tabindex="6"'), $attributes);
222 | $html = '';
223 |
224 | if (count($swimlanes) > 1) {
225 | $html .= $this->helper->form->label(t('Swimlane'), 'swimlane_id');
226 | $html .= $this->helper->form->select('swimlane_id', $swimlanes, $values, $errors, $attributes);
227 | }
228 |
229 | return $html;
230 | }
231 |
232 | public function renderColumnField(array $columns, array $values, array $errors = array(), array $attributes = array())
233 | {
234 | $attributes = array_merge(array('tabindex="7"'), $attributes);
235 |
236 | $html = $this->helper->form->label(t('Column'), 'column_id');
237 | $html .= $this->helper->form->select('column_id', $columns, $values, $errors, $attributes);
238 |
239 | return $html;
240 | }
241 |
242 | public function renderPriorityField(array $project, array $values)
243 | {
244 | $range = range($project['priority_start'], $project['priority_end']);
245 | $options = array_combine($range, $range);
246 | $values += array('priority' => $project['priority_default']);
247 |
248 | $html = $this->helper->form->label(t('Priority'), 'priority');
249 | $html .= $this->helper->form->select('priority', $options, $values, array(), array('tabindex="8"'));
250 |
251 | return $html;
252 | }
253 |
254 | public function renderScoreField(array $values, array $errors = array(), array $attributes = array())
255 | {
256 | $attributes = array_merge(array('tabindex="13"'), $attributes);
257 |
258 | $html = $this->helper->form->label(t('Complexity'), 'score');
259 | $html .= $this->helper->form->number('score', $values, $errors, $attributes);
260 |
261 | return $html;
262 | }
263 |
264 | public function renderReferenceField(array $values, array $errors = array(), array $attributes = array())
265 | {
266 | $attributes = array_merge(array('tabindex="14"'), $attributes);
267 |
268 | $html = $this->helper->form->label(t('Reference'), 'reference');
269 | $html .= $this->helper->form->text('reference', $values, $errors, $attributes, 'form-input-small');
270 |
271 | return $html;
272 | }
273 |
274 | public function renderTimeEstimatedField(array $values, array $errors = array(), array $attributes = array())
275 | {
276 | $attributes = array_merge(array('tabindex="11"'), $attributes);
277 |
278 | $html = $this->helper->form->label(t('Original estimate'), 'time_estimated');
279 | $html .= $this->helper->form->numeric('time_estimated', $values, $errors, $attributes);
280 | $html .= ' '.t('hours');
281 |
282 | return $html;
283 | }
284 |
285 | public function renderTimeSpentField(array $values, array $errors = array(), array $attributes = array())
286 | {
287 | $attributes = array_merge(array('tabindex="12"'), $attributes);
288 |
289 | $html = $this->helper->form->label(t('Time spent'), 'time_spent');
290 | $html .= $this->helper->form->numeric('time_spent', $values, $errors, $attributes);
291 | $html .= ' '.t('hours');
292 |
293 | return $html;
294 | }
295 |
296 | public function renderStartDateField(array $values, array $errors = array(), array $attributes = array())
297 | {
298 | $attributes = array_merge(array('tabindex="10"'), $attributes);
299 | return $this->helper->form->datetime(t('Start Date'), 'date_started', $values, $errors, $attributes);
300 | }
301 |
302 | public function renderDueDateField(array $values, array $errors = array(), array $attributes = array())
303 | {
304 | $attributes = array_merge(array('tabindex="9"'), $attributes);
305 | return $this->helper->form->datetime(t('Due Date'), 'date_due', $values, $errors, $attributes);
306 | }
307 |
308 | public function renderPriority($priority)
309 | {
310 | $html = '';
311 | $html .= $this->helper->text->e($priority >= 0 ? 'P'.$priority : '-P'.abs($priority));
312 | $html .= '';
313 |
314 | return $html;
315 | }
316 |
317 | public function renderReference(array $task)
318 | {
319 | if (! empty($task['reference'])) {
320 | $reference = $this->helper->text->e($task['reference']);
321 |
322 | if (filter_var($task['reference'], FILTER_VALIDATE_URL) !== false) {
323 | return sprintf('%s', $reference, $reference);
324 | }
325 |
326 | return $reference;
327 | }
328 |
329 | return '';
330 | }
331 |
332 | public function getProgress($task)
333 | {
334 | if (! isset($this->columns[$task['project_id']])) {
335 | $this->columns[$task['project_id']] = $this->columnModel->getList($task['project_id']);
336 | }
337 |
338 | return $this->taskModel->getProgress($task, $this->columns[$task['project_id']]);
339 | }
340 |
341 | public function getNewBoardTaskButton(array $swimlane, array $column)
342 | {
343 | $html = '';
344 | $providers = $this->externalTaskManager->getProviders();
345 |
346 | if (empty($providers)) {
347 | $html .= $this->helper->modal->largeIcon(
348 | 'plus',
349 | t('Add a new task'),
350 | 'TaskCreationController',
351 | 'show',
352 | array(
353 | 'project_id' => $column['project_id'],
354 | 'column_id' => $column['id'],
355 | 'swimlane_id' => $swimlane['id'],
356 | )
357 | );
358 | } else {
359 | $html .= '
';
360 | $html .= '
';
361 |
362 | $link = $this->helper->modal->large(
363 | 'plus',
364 | t('Add a new Kanboard task'),
365 | 'TaskCreationController',
366 | 'show',
367 | array(
368 | 'project_id' => $column['project_id'],
369 | 'column_id' => $column['id'],
370 | 'swimlane_id' => $swimlane['id'],
371 | )
372 | );
373 |
374 | $html .= '- '.$link.'
';
375 |
376 | foreach ($providers as $provider) {
377 | $link = $this->helper->url->link(
378 | $provider->getMenuAddLabel(),
379 | 'ExternalTaskCreationController',
380 | 'step1',
381 | array('project_id' => $column['project_id'], 'swimlane_id' => $swimlane['id'], 'column_id' => $column['id'], 'provider_name' => $provider->getName()),
382 | false,
383 | 'js-modal-large'
384 | );
385 |
386 | $html .= '- '.$provider->getIcon().' '.$link.'
';
387 | }
388 |
389 | $html .= '
';
390 | }
391 |
392 | $html .= '
';
393 |
394 | return $html;
395 | }
396 | }
397 |
--------------------------------------------------------------------------------
/Helper/SizeAvatarHelperExtend.php:
--------------------------------------------------------------------------------
1 | multiselectMemberModel->getMembers($owner_ms);
19 | $html = "";
20 | foreach ($assignees as $assignee) {
21 | $user = $this->userModel->getById($assignee['user_id']);
22 | $html .= $this->render($assignee['user_id'], $user['username'], $user['name'], $user['email'], $user['avatar_path'], $css, $size);
23 | }
24 | return $html;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 creecros
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Locale/bg_BG/translations.php:
--------------------------------------------------------------------------------
1 | 'Изпълнителна група',
5 | 'Assigned Group:' => 'Изпълнителна група:',
6 | 'Other Assignees' => 'Други изпълнители',
7 | 'Other Assignees:' => 'Други изпълнители:',
8 | 'Send a task by email to assigned group members' => 'Изпращане на задача по имейл до определената изпълнителна група',
9 | 'Send a task by email to the other assignees for a task.' => 'Изпращане на задача по имейл до другите изпълнители на задачата',
10 | 'Send email notification of impending due date to Group Members Assigned' => 'Изпращане на имейл известие за изтичащ краен срок до избраните членове на група',
11 | 'Send email notification of impending due date to the other assignees of a task' => 'Изпращане на имейл известие за изтичащ краен срок до другите изпълнители на задача',
12 | 'Assign the task to a specific group' => 'Присвояване на задача към специфична група',
13 | 'Action' => 'Действие',
14 | 'Action date' => 'Дата на действие',
15 | 'Add a new Kanboard task' => 'Добавяне на нова Kanboard задача',
16 | 'Add a new task' => 'Добавяне на нова задача',
17 | 'All tasks' => 'Всички задачи',
18 | 'Assignee' => 'Изпълнител',
19 | 'Assign to me' => 'Възложи на мен',
20 | 'Category' => 'Категория',
21 | 'Create another task' => 'Създай друга задача',
22 | 'Column' => 'Колона',
23 | 'Color' => 'Цвят',
24 | 'Complexity' => 'Трудност',
25 | 'Day(s)' => 'Ден(и)',
26 | 'Documentation' => 'Документация',
27 | 'Due Date' => 'Краен срок',
28 | 'Duplicate to multiple projects' => 'Дублиране към множество проекти',
29 | 'Duration in days' => 'Продължителност в дни',
30 | 'Email subject' => 'Тема на имейла',
31 | 'Enter one line per task, or leave blank to copy Task Title and create only one subtask.' => 'Въведете по един ред на задача, или оставете празно, за да копирате заглавието на задачата и да създадете само една подзадача.',
32 | 'Event' => 'Събитие',
33 | 'Existing due date' => 'Съществуващ краен срок',
34 | 'Group' => 'Група',
35 | 'Groups management' => 'Управление на групи',
36 | 'hours' => 'часове',
37 | 'Logout' => 'Изход',
38 | 'Me' => 'Аз',
39 | 'Month(s)' => 'Месец(и)',
40 | 'My dashboard' => 'Моето табло',
41 | 'My profile' => 'Моят профил',
42 | 'New assignee: %s' => 'Нов изпълнител: %s',
43 | 'New category: %s' => 'Нова категория: %s',
44 | 'New color: %s' => 'Нов цвят: %s',
45 | 'New complexity: %d' => 'Нова трудност: %d',
46 | 'New due date: ' => 'Нов краен срок: ',
47 | 'New group assigned: %s' => 'Зададена нова група: %s',
48 | 'New task' => 'Нова задача',
49 | 'New title: %s' => 'Ново заглавие: %s',
50 | 'No' => 'Не',
51 | 'not assigned' => 'не е зададено',
52 | 'Only for tasks assigned to me' => 'Само за задачи, възложени на мен',
53 | 'Only for tasks created by me' => 'Само за задачи, създадени от мен',
54 | 'Only for tasks created by me and tasks assigned to me' => 'Само за задачи, създадени от мен и възложени на мен',
55 | 'Options' => 'Опции',
56 | 'Original estimate' => 'Първоначална оценка',
57 | 'Priority' => 'Приоритет',
58 | 'Projects management' => 'Управление на проекти',
59 | 'Recurrence settings have been modified' => 'Настройките за повторение са променени',
60 | 'Reference' => 'Референция',
61 | 'Settings' => 'Настройки',
62 | 'Start Date' => 'Начална дата',
63 | 'Start date changed: ' => 'Промяна на начална дата: ',
64 | 'Swimlane' => 'Коридор',
65 | 'Tags' => 'Тагове',
66 | 'Task created successfully.' => 'Задачата е създадена успешно.',
67 | 'Task priority' => 'Приоритет на задачата',
68 | 'Task updated successfully.' => 'Задачата е актуализирана успешно.',
69 | 'Template for the task description' => 'Шаблон за описанието на задачата',
70 | 'The description has been modified:' => 'Описанието е променено:',
71 | 'The due date have been removed' => 'Крайния срок е премахнат',
72 | 'The field "%s" have been updated' => 'Полето "%s" беше актуализирано',
73 | 'The task is not assigned anymore' => 'Задачата вече не е възложена на никого',
74 | 'The task is not assigned to a group anymore' => 'Задачата вече не е възложена на група',
75 | 'The task is not assigned to multiple users anymore' => 'Задачата вече не е възложена на множество потребители',
76 | 'The task has been assigned other users' => 'Задачата е възложена на други потребители',
77 | 'There is no category now' => 'Вече няма категория',
78 | 'There is no description anymore' => 'Вече няма описание',
79 | 'Time estimated changed: %sh' => 'Променено прогнозно време: %sч',
80 | 'Time spent' => 'Отделено време',
81 | 'Time spent changed: %sh' => 'Променено отделено време: %sч',
82 | 'Title' => 'Заглавие',
83 | 'Unable to create this task.' => 'Не можете да създадете тази задача.',
84 | 'Unable to create your task.' => 'Не можете да създадете вашата задача.',
85 | 'Unable to update your task.' => 'Не можете да актуализирате вашата задача.',
86 | 'Unassigned' => 'Неприсвоен',
87 | 'Users management' => 'Управление на потребителите',
88 | 'When task is moved from first column' => 'Когато задача е преместена от първата колона',
89 | 'When task is moved to last column' => 'Когато задача е преместена в последната колона',
90 | 'When task is closed' => 'Когато задача е затворена',
91 | 'Year(s)' => 'Година(и)',
92 | 'Yes' => 'Да',
93 | 'You are not allowed to change the assignee.' => 'Не Ви е позволено да променяте изпълнителя.',
94 | 'You are not allowed to update tasks assigned to someone else.' => 'Не можете да променяте задачи с друг изпълнител.',
95 | 'You cannot create tasks in this column.' => 'Не можете да създавате задачи в тази колона.',
96 | '[DUPLICATE]' => '[ДУБЛИКАТ]',
97 | 'Enable Group Managment for Application Managers' => 'Включване на Управление на групи за Приложни управители.',
98 | 'Disable Group Managment for Application Managers' => 'Изключване на Управление на групи за Приложни управители.'
99 | );
100 |
--------------------------------------------------------------------------------
/Locale/de_DE/translations.php:
--------------------------------------------------------------------------------
1 | 'Aktion',
5 | 'Action date' => 'Datum der Aktion',
6 | 'Add a new Kanboard task' => 'Eine neue Kanboard-Aufgabe hinzufügen',
7 | 'Add a new task' => 'Eine neue Aufgabe hinzufügen',
8 | 'All tasks' => 'Alle Aufgaben',
9 | 'Assignee' => 'Zugeordneter',
10 | 'Assigned Group' => 'Zugeordnete Gruppe',
11 | 'Assigned Group:' => 'Zugeordnete Gruppe:',
12 | 'Assign to me' => 'Mir zuweisen',
13 | 'Assign the task to a specific group' => 'Aufgabe einer bestimmten Gruppe zuordnen',
14 | 'Category' => 'Kategorie',
15 | 'Create another task' => 'Andere Aufgabe erstellen',
16 | 'Column' => 'Spalte',
17 | 'Color' => 'Farbe',
18 | 'Complexity' => 'Komplexität',
19 | 'Day(s)' => 'Tag(e)',
20 | 'Documentation' => 'Dokumentation',
21 | 'Due Date' => 'Fälligkeitsdatum',
22 | 'Duplicate to multiple projects' => 'In mehrere Projekte duplizieren',
23 | 'Duration in days' => 'Dauer in Tagen',
24 | 'Email subject' => 'E-Mail-Betreff',
25 | 'Enter one line per task, or leave blank to copy Task Title and create only one subtask.' => 'Geben Sie eine Zeile pro Aufgabe ein, oder lassen Sie die Zeile leer, um den Aufgabentitel zu kopieren und nur eine Unteraufgabe zu erstellen.',
26 | 'Event' => 'Ereignis',
27 | 'Existing due date' => 'Vorhandenes Fälligkeitsdatum',
28 | 'Group' => 'Gruppe',
29 | 'Groups management' => 'Gruppen-Verwaltung',
30 | 'hours' => 'Stunden',
31 | 'Logout' => 'Abmeldung',
32 | 'Me' => 'Ich',
33 | 'Month(s)' => 'Monat(e)',
34 | 'My dashboard' => 'Mein Dashboard',
35 | 'My profile' => 'Mein Profil',
36 | 'New assignee: %s' => 'Neuer Zugewiesener: %s',
37 | 'New category: %s' => 'Neue Kategorie: %s',
38 | 'New color: %s' => 'Neue Farbe: %s',
39 | 'New complexity: %d' => 'Neue Komplexität: %d',
40 | 'New due date: ' => 'Neues Fälligkeitsdatum: ',
41 | 'New group assigned: %s' => 'Neue zugewiesene Gruppe: %s',
42 | 'New task' => 'Neue Aufgabe',
43 | 'New title: %s' => 'Neuer Titel: %s',
44 | 'No' => 'Keine',
45 | 'not assigned' => 'nicht zugewiesen',
46 | 'Only for tasks assigned to me' => 'Nur für Aufgaben, die mir zugewiesen wurden',
47 | 'Only for tasks created by me' => 'Nur für von mir erstellte Aufgaben',
48 | 'Only for tasks created by me and tasks assigned to me' => 'Nur für von mir erstellte Aufgaben und mir zugewiesene Aufgaben',
49 | 'Options' => 'Optionen',
50 | 'Original estimate' => 'Ursprüngliche Schätzung',
51 | 'Other Assignees' => 'Weitere Zuordnungen',
52 | 'Other Assignees:' => 'Weitere Zuordnungen:',
53 | 'Priority' => 'Priorität',
54 | 'Projects management' => 'Verwaltung der Projekte',
55 | 'Recurrence settings have been modified' => 'Die Wiederholungseinstellungen wurden geändert',
56 | 'Reference' => 'Referenz',
57 | 'Send a task by email to assigned group members' => 'Senden einer Aufgabe per E-Mail an zugewiesene Gruppenmitglieder',
58 | 'Send a task by email to the other assignees for a task.' => 'Aufgabe per E-Mail an die weiteren zugeordneten der Aufgabe senden',
59 | 'Send email notification of impending due date to Group Members Assigned' => 'E-Mail-Benachrichtigung über bevorstehendes Fälligkeitsdatum an die zugewiesenen Gruppenmitglieder senden',
60 | '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',
61 | 'Settings' => 'Einstellungen',
62 | 'Start Date' => 'Anfangsdatum',
63 | 'Start date changed: ' => 'Das Anfangsdatum hat sich geändert: ',
64 | 'Swimlane' => 'Swimlane',
65 | 'Tags' => 'Tags',
66 | 'Task created successfully.' => 'Aufgabe erfolgreich erstellt.',
67 | 'Task priority' => 'Priorität der Aufgabe',
68 | 'Task updated successfully.' => 'Aufgabe erfolgreich aktualisiert.',
69 | 'Template for the task description' => 'Vorlage für Aufgabenbeschreibung',
70 | 'The description has been modified:' => 'Die Beschreibung wurde geändert:',
71 | 'The due date have been removed' => 'Das Fälligkeitsdatum wurde entfernt',
72 | 'The field "%s" have been updated' => 'Das Feld "%s" wurde aktualisiert',
73 | 'The task is not assigned anymore' => 'Die Aufgabe ist nicht mehr zugewiesen',
74 | 'The task is not assigned to a group anymore' => 'Die Aufgabe ist nicht mehr einer Gruppe zugewiesen',
75 | 'The task is not assigned to multiple users anymore' => 'Die Aufgabe ist nicht mehr mehreren Benutzern zugewiesen',
76 | 'The task has been assigned other users' => 'Die Aufgabe wurde anderen Benutzern zugewiesen',
77 | 'There is no category now' => 'Es gibt derzeit keine Kategorie',
78 | 'There is no description anymore' => 'Es gibt keine Beschreibung mehr',
79 | 'Time estimated changed: %sh' => 'Geschätzte Bearbeitungsdauer geändert: %sh',
80 | 'Time spent' => 'Aufgewendete Zeit',
81 | 'Time spent changed: %sh' => 'Aufgewendete Zeit geändert: %sh',
82 | 'Title' => 'Titel',
83 | 'Unable to create this task.' => 'Diese Aufgabe kann nicht erstellt werden',
84 | 'Unable to create your task.' => 'Ihre Aufgabe kann nicht erstellt werden.',
85 | 'Unable to update your task.' => 'Diese Aufgabe kann nicht aktualisiert werden',
86 | 'Unassigned' => 'Nicht zugewiesen',
87 | 'Users management' => 'Benutzerverwaltung ',
88 | 'When task is moved from first column' => 'Wenn die Aufgabe aus der ersten Spalte heraus verschoben wird',
89 | 'When task is moved to last column' => 'Wenn die Aufgabe in die letzte Spalte verschoben wird',
90 | 'When task is closed' => 'Wenn die Aufgabe geschlossen wird',
91 | 'Year(s)' => 'Jahre',
92 | 'Yes' => 'Ja',
93 | 'You are not allowed to change the assignee.' => 'Sie dürfen die Zuweisung nicht ändern.',
94 | 'You are not allowed to update tasks assigned to someone else.' => 'Sie dürfen keine Aufgaben aktualisieren, die jemand anderem zugewiesen sind.',
95 | 'You cannot create tasks in this column.' => 'Sie können in dieser Spalte keine Aufgaben erstellen.',
96 | '[DUPLICATE]' => '[DUPLIZIERT]',
97 | );
98 |
--------------------------------------------------------------------------------
/Locale/de_DE_du/translations.php:
--------------------------------------------------------------------------------
1 | 'Zugeordnete Gruppe',
5 | 'Assigned Group:' => 'Zugeordnete Gruppe:',
6 | 'Other Assignees' => 'Weitere Zuordnungen',
7 | 'Other Assignees:' => 'Weitere Zuordnungen:',
8 | 'Send a task by email to assigned group members' => 'Senden einer Aufgabe per E-Mail an zugewiesene Gruppenmitglieder',
9 | 'Send a task by email to the other assignees for a task.' => 'Aufgabe per E-Mail an die weiteren zugeordneten der Aufgabe senden',
10 | 'Send email notification of impending due date to Group Members Assigned' => 'E-Mail-Benachrichtigung über bevorstehendes Fälligkeitsdatum an die zugewiesenen Gruppenmitglieder senden',
11 | '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',
12 | 'Assign the task to a specific group' => 'Aufgabe einer bestimmten Gruppe zuordnen'
13 | );
14 |
--------------------------------------------------------------------------------
/Locale/fr_FR/translations.php:
--------------------------------------------------------------------------------
1 | 'Groupe assigné',
5 | 'Assigned Group:' => 'Groupe assigné :',
6 | 'Other Assignees' => 'Autres assignés',
7 | 'Other Assignees:' => 'Autres assignés :',
8 | 'Send a task by email to assigned group members' => 'Envoyer une tâche par e-mail aux membres du groupe assigné',
9 | 'Send a task by email to the other assignees for a task.' => 'Envoyer une tâche par e-mail aux autres personnes assignées à la tâche',
10 | 'Send email notification of impending due date to Group Members Assigned' => 'Envoyer un e-mail de notification à l\'approche d\'une date d\'échéance aux membres du groupe assigné',
11 | 'Send email notification of impending due date to the other assignees of a task' => 'Envoyer un e-mail de notification à l\'approche d\'une date d\'échéance aux autres personnes assignées à la tâche',
12 | 'Assign the task to a specific group' => 'Assigner la tâche à un groupe spécifique'
13 | );
14 |
--------------------------------------------------------------------------------
/Locale/pt_BR/translations.php:
--------------------------------------------------------------------------------
1 | 'Ação',
5 | 'Action date' => 'Data de ação',
6 | 'Add a new Kanboard task' => 'Adicionar uma nova tarefa do Kanboard',
7 | 'Add a new task' => 'Adicionar uma nova tarefa',
8 | 'All tasks' => 'Todas as tarefas',
9 | 'Assignee' => 'Responsável',
10 | 'Assigned Group' => 'Grupo Designado',
11 | 'Assigned Group:' => 'Grupo Designado:',
12 | 'Assign to me' => 'Atribuir para mim',
13 | 'Assign the task to a specific group' => 'Atribuir uma tarefa para um grupo específico',
14 | 'Category' => 'Categoria',
15 | 'Create another task' => 'Criar outra tarefa',
16 | 'Column' => 'Coluna',
17 | 'Color' => 'Cor',
18 | 'Complexity' => 'Complexidade',
19 | 'Day(s)' => 'Dia(s)',
20 | 'Documentation' => 'Documentação',
21 | 'Due Date' => 'Data de Vencimento',
22 | 'Duplicate to multiple projects' => 'Duplicar para múltiplos projetos',
23 | 'Duration in days' => 'Duração em dias',
24 | 'Email subject' => 'Assunto do email',
25 | '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.',
26 | 'Event' => 'Evento',
27 | 'Existing due date' => 'Data de vencimeto existente',
28 | 'Group' => 'Grupo',
29 | 'Groups management' => 'Gestão de grupos',
30 | 'hours' => 'horas',
31 | 'Logout' => 'Sair',
32 | 'Me' => 'Eu',
33 | 'Month(s)' => 'Mês(es)',
34 | 'My dashboard' => 'Meu painel',
35 | 'My profile' => 'Meu perfil',
36 | 'New assignee: %s' => 'Novo responsável: %s',
37 | 'New category: %s' => 'Nova categoria: %s',
38 | 'New color: %s' => 'Nova cor: %s',
39 | 'New complexity: %d' => 'Nova complexidade: %d',
40 | 'New due date: ' => 'Nova data de vencimento: ',
41 | 'New group assigned: %s' => 'Novo grupo atribuido: %s',
42 | 'New task' => 'Nova tarefa',
43 | 'New title: %s' => 'Novo título: %s',
44 | 'No' => 'Não',
45 | 'not assigned' => 'não atribuído',
46 | 'Only for tasks assigned to me' => 'Apenas tarefas atribuídas a mim',
47 | 'Only for tasks created by me' => 'Apenas tarefas criadas por mim',
48 | 'Only for tasks created by me and tasks assigned to me' => 'Apenas tarefas criadas por mim ou tarefas atribuídas a mim',
49 | 'Options' => 'Opções',
50 | 'Original estimate' => 'Estimativa original',
51 | 'Other Assignees' => 'Outros Responsáveis',
52 | 'Other Assignees:' => 'Outros Responsáveis:',
53 | 'Priority' => 'Prioridade',
54 | 'Projects management' => 'Gestão de projetos',
55 | 'Recurrence settings have been modified' => 'Configurações de recorrência foram modificadas',
56 | 'Reference' => 'Referência',
57 | 'Send a task by email to assigned group members' => 'Enviar uma tarefa por email para membros do grupo designado',
58 | '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',
59 | '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',
60 | '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',
61 | 'Settings' => 'Definições',
62 | 'Start Date' => 'Data de Início',
63 | 'Start date changed: ' => 'Data de início alterada: ',
64 | 'Swimlane' => 'Raia',
65 | 'Tags' => 'Etiquetas',
66 | 'Task created successfully.' => 'Tarefa criada com sucesso.',
67 | 'Task priority' => 'Prioridade da tarefa',
68 | 'Task updated successfully.' => 'Tarefa atualizada com sucesso',
69 | 'Template for the task description' => 'Modelo para a descrição da tarefa',
70 | 'The description has been modified:' => 'A descrição foi modificada:',
71 | 'The due date have been removed' => 'A data de vencimento foi removida',
72 | 'The field "%s" have been updated' => 'O campo "%s" foi atualizado',
73 | 'The task is not assigned anymore' => 'A tarefa não está mais atribuída',
74 | 'The task is not assigned to a group anymore' => 'A tarefa não está mais atribuída para um grupo',
75 | 'The task is not assigned to multiple users anymore' => 'A tarefa não está mais atribuída para múltiplos usuários',
76 | 'The task has been assigned other users' => 'A tarefa foi atribuída para outros usuários',
77 | 'There is no category now' => 'Não há categoria agora',
78 | 'There is no description anymore' => 'Não há descrição agora',
79 | 'Time estimated changed: %sh' => 'Tempo estimado alterado: %sh',
80 | 'Time spent' => 'Tempo gasto',
81 | 'Time spent changed: %sh' => 'Tempo gasto alterado: %sh',
82 | 'Title' => 'Título',
83 | 'Unable to create this task.' => 'Incapaz de criar esta tarefa',
84 | 'Unable to create your task.' => 'Incapaz de criar a sua tarefa.',
85 | 'Unable to update your task.' => 'Incapaz de atualizar sua tarefa',
86 | 'Unassigned' => 'Não atribuido',
87 | 'Users management' => 'Gestão de usuários',
88 | 'When task is moved from first column' => 'Quando a tarefa é movida da primeira coluna',
89 | 'When task is moved to last column' => 'Quando a tarefa é movida para a última coluna',
90 | 'When task is closed' => 'Quando a tarefa é fechada',
91 | 'Year(s)' => 'Ano(s)',
92 | 'Yes' => 'Sim',
93 | 'You are not allowed to change the assignee.' => 'Você não tem permissão para mudar o responsável.',
94 | 'You are not allowed to update tasks assigned to someone else.' => 'Você não tem permissão para atualizar tarefas designadas para outras pessoas.',
95 | 'You cannot create tasks in this column.' => 'Você não pode criar tarefas nesta coluna.',
96 | '[DUPLICATE]' => '[DUPLICADO]',
97 | 'Enable Group Managment for Application Managers' => 'Ativar gerenciamento de grupo para gestores da aplicação.',
98 | 'Disable Group Managment for Application Managers' => 'Desativar gerenciamento de grupo para gestores da aplicação.',
99 | );
100 |
--------------------------------------------------------------------------------
/Locale/uk_UA/translations.php:
--------------------------------------------------------------------------------
1 | 'Призначена група',
5 | 'Assigned Group:' => 'Призначена група:',
6 | 'Other Assignees' => 'Інші відповідальні',
7 | 'Other Assignees:' => 'Інші відповідальні:',
8 | 'Send a task by email to assigned group members' => 'Надіслати завдання електронною поштою призначеним учасникам групи',
9 | 'Send a task by email to the other assignees for a task.' => 'Надіслати завдання електронною поштою іншим виконавцям завдання.',
10 | 'Send email notification of impending due date to Group Members Assigned' => 'Надіслати сповіщення електронною поштою про наближення терміну виконання призначеним членам групи',
11 | 'Send email notification of impending due date to the other assignees of a task' => 'Надіслати сповіщення електронною поштою про наближення терміну виконання іншим виконавцям завдання',
12 | 'Assign the task to a specific group' => 'Доручити завдання певній групі',
13 | 'Action' => 'Дія',
14 | 'Action date' => 'Дата дії',
15 | 'Add a new Kanboard task' => 'Додати нове завдання Kanboard',
16 | 'Add a new task' => 'Додати нове завдання',
17 | 'All tasks' => 'Всі завдання',
18 | 'Assignee' => 'Відповідальний',
19 | 'Assign to me' => 'Призначити мені',
20 | 'Category' => 'Категорія',
21 | 'Create another task' => 'Створити інше завдання',
22 | 'Column' => 'Колонка',
23 | 'Color' => 'Колір',
24 | 'Complexity' => 'Складність',
25 | 'Day(s)' => 'День(дні)',
26 | 'Documentation' => 'Документація',
27 | 'Due Date' => 'Термін виконання',
28 | 'Duplicate to multiple projects' => 'Дублювати для кількох проектів',
29 | 'Duration in days' => 'Тривалість в днях',
30 | 'Email subject' => 'Тема електронного листа',
31 | 'Enter one line per task, or leave blank to copy Task Title and create only one subtask.' => 'Введіть один рядок для кожного завдання або залиште поле порожнім, щоб скопіювати назву завдання та створити лише одне підзавдання.',
32 | 'Event' => 'Подія',
33 | 'Existing due date' => 'Наявний термін виконання',
34 | 'Group' => 'Група',
35 | 'Groups management' => 'Управління групами',
36 | 'hours' => 'години',
37 | 'Logout' => 'Вийти',
38 | 'Me' => 'Я',
39 | 'Month(s)' => 'Місяць(и)',
40 | 'My dashboard' => 'Мій профіль',
41 | 'My profile' => 'Мій профіль',
42 | 'New assignee: %s' => 'Новий відповідальний: %s',
43 | 'New category: %s' => 'Нова категорія: %s',
44 | 'New color: %s' => 'Новий колір: %s',
45 | 'New complexity: %d' => 'Нова складність: %d',
46 | 'New due date: ' => 'Новий термін виконання: ',
47 | 'New group assigned: %s' => 'Нова призначена група: %s',
48 | 'New task' => 'Нове завдання',
49 | 'New title: %s' => 'Нова назва: %s',
50 | 'No' => 'Ні',
51 | 'not assigned' => 'не призначено',
52 | 'Only for tasks assigned to me' => 'Тільки для завдань, призначених мені',
53 | 'Only for tasks created by me' => 'Тільки для завдань, створених мною',
54 | 'Only for tasks created by me and tasks assigned to me' => 'Тільки для завдань, створених мною, і завдань, призначених мені',
55 | 'Options' => 'Опції',
56 | 'Original estimate' => 'Початкова оцінка',
57 | 'Priority' => 'Пріоритет',
58 | 'Projects management' => 'Управління проєктами',
59 | 'Recurrence settings have been modified' => 'Налаштування повторення змінено',
60 | 'Reference' => 'Зовнішній ID',
61 | 'Settings' => 'Налаштування',
62 | 'Start Date' => 'Дата початку',
63 | 'Start date changed: ' => 'Дата початку змінена: ',
64 | 'Swimlane' => 'Доріжка',
65 | 'Tags' => 'Мітки',
66 | 'Task created successfully.' => 'Завдання створено успішно.',
67 | 'Task priority' => 'Пріоритет завдання',
68 | 'Task updated successfully.' => 'Завдання оновлено успішно.',
69 | 'Template for the task description' => 'Шаблон для опису завдання',
70 | 'The description has been modified:' => 'Опис змінено:',
71 | 'The due date have been removed' => 'Термін виконання видалено',
72 | 'The field "%s" have been updated' => 'Поле "%s" оновлено',
73 | 'The task is not assigned anymore' => 'Завдання більше не ставиться',
74 | 'The task is not assigned to a group anymore' => 'Завдання більше не призначається групі',
75 | 'The task is not assigned to multiple users anymore' => 'Завдання більше не призначається кільком користувачам',
76 | 'The task has been assigned other users' => 'Завдання призначено іншим користувачам',
77 | 'There is no category now' => 'Категорії зараз немає',
78 | 'There is no description anymore' => 'Опису більше немає',
79 | 'Time estimated changed: %sh' => 'Приблизний час змінено: %sг',
80 | 'Time spent' => 'Витрачений час',
81 | 'Time spent changed: %sh' => 'Витрачений час змінено: %sг',
82 | 'Title' => 'Назва',
83 | 'Unable to create this task.' => 'Не вдалося створити це завдання.',
84 | 'Unable to create your task.' => 'Не вдалося створити ваше завдання.',
85 | 'Unable to update your task.' => 'Не вдалося оновити ваше завдання.',
86 | 'Unassigned' => 'Непризначено',
87 | 'Users management' => 'Керування користувачами',
88 | 'When task is moved from first column' => 'Коли завдання переміщується з першого стовпця',
89 | 'When task is moved to last column' => 'Коли завдання переміщується в останній стовпець',
90 | 'When task is closed' => 'Коли завдання закрите',
91 | 'Year(s)' => 'рік(роки)',
92 | 'Yes' => 'Так ',
93 | 'You are not allowed to change the assignee.' => 'Ви не маєте права змінювати відповідального.',
94 | 'You are not allowed to update tasks assigned to someone else.' => 'Ви не маєте права оновлювати завдання, призначені комусь іншому.',
95 | 'You cannot create tasks in this column.' => 'Ви не можете створювати завдання в цьому стовпці.',
96 | '[DUPLICATE]' => '[ДУБЛІКАТ]',
97 | 'Enable Group Managment for Application Managers' => 'Увімкнути групове керування для менеджерів програм',
98 | 'Disable Group Managment for Application Managers' => 'Вимкнути групове керування для менеджерів програм'
99 | );
100 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | extract = $(shell grep -A2 $(1) Plugin.php | tail -n1 | tr -d " ;'" | sed "s/return//")
5 |
6 | plugin = $(call extract, getPluginName)
7 | version = $(call extract, getPluginVersion)
8 |
9 | all:
10 | @echo "Build archive for plugin ${plugin} version=${version}"
11 | @git archive HEAD --prefix=${plugin}/ --format=zip -o ${plugin}-${version}.zip
--------------------------------------------------------------------------------
/Model/GroupAssignCalendarModel.php:
--------------------------------------------------------------------------------
1 | db->table(MultiselectMemberModel::TABLE)
37 | ->eq('user_id', $user_id)
38 | ->findAllByColumn('group_id');
39 |
40 | $getGr_Ids = $this->db->table(GroupMemberModel::TABLE)
41 | ->eq('user_id', $user_id)
42 | ->findAllByColumn('group_id');
43 |
44 | $tasks = $this->db->table(self::TABLE)
45 | ->beginOr()
46 | ->in('owner_gp', $getGr_Ids)
47 | ->in('owner_ms', $getMS_Ids)
48 | ->closeOr()
49 | ->gte('date_due', strtotime($start))
50 | ->lte('date_due', strtotime($end))
51 | ->neq('is_active', 0)
52 | ->findAll();
53 |
54 | $events = array();
55 |
56 | foreach ($tasks as $task) {
57 | $startDate = new DateTime();
58 | $startDate->setTimestamp($task['date_started']);
59 |
60 | $endDate = new DateTime();
61 | $endDate->setTimestamp($task['date_due']);
62 |
63 | if ($startDate == 0) {
64 | $startDate = $endDate;
65 | }
66 |
67 | $allDay = $startDate == $endDate && $endDate->format('Hi') == '0000';
68 | $format = $allDay ? 'Y-m-d' : 'Y-m-d\TH:i:s';
69 |
70 | $events[] = array(
71 | 'timezoneParam' => $this->timezoneModel->getCurrentTimezone(),
72 | 'id' => $task['id'],
73 | 'title' => t('#%d', $task['id']).' '.$task['title'],
74 | 'backgroundColor' => $this->colorModel->getBackgroundColor('dark_grey'),
75 | 'borderColor' => $this->colorModel->getBorderColor($task['color_id']),
76 | 'textColor' => 'white',
77 | 'url' => $this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])),
78 | 'start' => $startDate->format($format),
79 | 'end' => $endDate->format($format),
80 | 'editable' => $allDay,
81 | 'allday' => $allDay,
82 | );
83 | }
84 |
85 | return $events;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Model/GroupAssignTaskDuplicationModel.php:
--------------------------------------------------------------------------------
1 | copyFields($task_id);
68 | $values['title'] = t('[DUPLICATE]').' '.$values['title'];
69 |
70 | $new_task_id = $this->save($task_id, $values);
71 |
72 | if ($new_task_id !== false) {
73 | $this->tagDuplicationModel->duplicateTaskTags($task_id, $new_task_id);
74 | $this->taskLinkModel->create($new_task_id, $task_id, 4);
75 | }
76 |
77 | return $new_task_id;
78 | }
79 |
80 | /**
81 | * Check if the assignee and the category are available in the destination project
82 | *
83 | * @access public
84 | * @param array $values
85 | * @return array
86 | */
87 | public function checkDestinationProjectValues(array &$values)
88 | {
89 | // Check if the assigned user is allowed for the destination project
90 | if ($values['owner_id'] > 0 && ! $this->projectPermissionModel->isUserAllowed($values['project_id'], $values['owner_id'])) {
91 | $values['owner_id'] = 0;
92 | }
93 |
94 | // Check if the category exists for the destination project
95 | if ($values['category_id'] > 0) {
96 | $values['category_id'] = $this->categoryModel->getIdByName(
97 | $values['project_id'],
98 | $this->categoryModel->getNameById($values['category_id'])
99 | );
100 | }
101 |
102 | // Check if the swimlane exists for the destination project
103 | $values['swimlane_id'] = $this->swimlaneModel->getIdByName(
104 | $values['project_id'],
105 | $this->swimlaneModel->getNameById($values['swimlane_id'])
106 | );
107 |
108 | if ($values['swimlane_id'] == 0) {
109 | $values['swimlane_id'] = $this->swimlaneModel->getFirstActiveSwimlaneId($values['project_id']);
110 | }
111 |
112 | // Check if the column exists for the destination project
113 | if ($values['column_id'] > 0) {
114 | $values['column_id'] = $this->columnModel->getColumnIdByTitle(
115 | $values['project_id'],
116 | $this->columnModel->getColumnTitleById($values['column_id'])
117 | );
118 |
119 | $values['column_id'] = $values['column_id'] ?: $this->columnModel->getFirstColumnId($values['project_id']);
120 | }
121 |
122 | // Check if priority exists for destination project
123 | $values['priority'] = $this->projectTaskPriorityModel->getPriorityForProject(
124 | $values['project_id'],
125 | empty($values['priority']) ? 0 : $values['priority']
126 | );
127 |
128 | return $values;
129 | }
130 |
131 | /**
132 | * Duplicate fields for the new task
133 | *
134 | * @access protected
135 | * @param integer $task_id Task id
136 | * @return array
137 | */
138 | protected function copyFields($task_id)
139 | {
140 | $task = $this->taskFinderModel->getById($task_id);
141 | $values = array();
142 |
143 | foreach ($this->fieldsToDuplicate as $field) {
144 | $values[$field] = $task[$field];
145 | }
146 |
147 | $ms_id = $this->multiselectModel->create();
148 | $users_in_ms = $this->multiselectMemberModel->getMembers($values['owner_ms']);
149 | $values['owner_ms'] = $ms_id;
150 | foreach ($users_in_ms as $user) {
151 | $this->multiselectMemberModel->addUser($ms_id, $user['id']);
152 | }
153 |
154 | return $values;
155 | }
156 |
157 | /**
158 | * Create the new task and duplicate subtasks
159 | *
160 | * @access protected
161 | * @param integer $task_id Task id
162 | * @param array $values Form values
163 | * @return boolean|integer
164 | */
165 | protected function save($task_id, array $values)
166 | {
167 | $new_task_id = $this->taskCreationModel->create($values);
168 |
169 | if ($new_task_id !== false) {
170 | $this->subtaskModel->duplicate($task_id, $new_task_id);
171 | }
172 |
173 | return $new_task_id;
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/Model/GroupColorExtension.php:
--------------------------------------------------------------------------------
1 | 130) {
28 | return 'black';
29 | } else {
30 | return 'white';
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Model/MultiselectMemberModel.php:
--------------------------------------------------------------------------------
1 | db->table(self::TABLE)
36 | ->join(UserModel::TABLE, 'id', 'user_id')
37 | ->eq('group_id', $group_id);
38 | }
39 |
40 | /**
41 | * Get all users
42 | *
43 | * @access public
44 | * @param integer $group_id
45 | * @return array
46 | */
47 | public function getMembers($group_id)
48 | {
49 | return $this->getQuery($group_id)->findAll();
50 | }
51 |
52 | /**
53 | * Get all not members
54 | *
55 | * @access public
56 | * @param integer $group_id
57 | * @return array
58 | */
59 | public function getNotMembers($group_id)
60 | {
61 | $subquery = $this->db->table(self::TABLE)
62 | ->columns('user_id')
63 | ->eq('group_id', $group_id);
64 |
65 | return $this->db->table(UserModel::TABLE)
66 | ->notInSubquery('id', $subquery)
67 | ->eq('is_active', 1)
68 | ->findAll();
69 | }
70 |
71 | /**
72 | * Add user to a group
73 | *
74 | * @access public
75 | * @param integer $group_id
76 | * @param integer $user_id
77 | * @return boolean
78 | */
79 | public function addUser($group_id, $user_id)
80 | {
81 | return $this->db->table(self::TABLE)->insert(array(
82 | 'group_id' => $group_id,
83 | 'user_id' => $user_id,
84 | ));
85 | }
86 |
87 | /**
88 | * Remove user from a group
89 | *
90 | * @access public
91 | * @param integer $group_id
92 | * @param integer $user_id
93 | * @return boolean
94 | */
95 | public function removeUser($group_id, $user_id)
96 | {
97 | return $this->db->table(self::TABLE)
98 | ->eq('group_id', $group_id)
99 | ->eq('user_id', $user_id)
100 | ->remove();
101 | }
102 |
103 | /**
104 | * Remove all users from a group
105 | *
106 | * @access public
107 | * @param integer $group_id
108 | * @param integer $user_id
109 | * @return boolean
110 | */
111 | public function removeAllUsers($group_id)
112 | {
113 | return $this->db->table(self::TABLE)
114 | ->eq('group_id', $group_id)
115 | ->remove();
116 | }
117 |
118 | /**
119 | * Check if a user is member
120 | *
121 | * @access public
122 | * @param integer $group_id
123 | * @param integer $user_id
124 | * @return boolean
125 | */
126 | public function isMember($group_id, $user_id)
127 | {
128 | return $this->db->table(self::TABLE)
129 | ->eq('group_id', $group_id)
130 | ->eq('user_id', $user_id)
131 | ->exists();
132 | }
133 |
134 | /**
135 | * Get all groups for a given user
136 | *
137 | * @access public
138 | * @param integer $user_id
139 | * @return array
140 | */
141 | public function getGroups($user_id)
142 | {
143 | return $this->db->table(self::TABLE)
144 | ->columns(MultiselectModel::TABLE.'.id', MultiselectModel::TABLE.'.external_id')
145 | ->join(MultiselectModel::TABLE, 'id', 'group_id')
146 | ->eq(self::TABLE.'.user_id', $user_id)
147 | ->asc(MultiselectModel::TABLE.'.id')
148 | ->findAll();
149 | }
150 |
151 | /**
152 | * Fire Assignee Change
153 | *
154 | * @access protected
155 | * @param array $task
156 | * @param array $changes
157 | */
158 | public function assigneeChanged(array $task, array $changes)
159 | {
160 | $events = array();
161 | $events[] = TaskModel::EVENT_ASSIGNEE_CHANGE;
162 |
163 | if (! empty($events)) {
164 | $this->queueManager->push(
165 | $this->taskEventJob
166 | ->withParams($task['id'], $events, $changes, array(), $task)
167 | );
168 | }
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/Model/MultiselectModel.php:
--------------------------------------------------------------------------------
1 | db->table(self::TABLE)
32 | ->columns('id', 'external_id')
33 | ->subquery('SELECT COUNT(*) FROM '.MultiselectMemberModel::TABLE.' WHERE group_id='.self::TABLE.'.id', 'nb_users');
34 | }
35 |
36 | /**
37 | * Get a specific group by id
38 | *
39 | * @access public
40 | * @param integer $group_id
41 | * @return array
42 | */
43 | public function getById($group_id)
44 | {
45 | return $this->db->table(self::TABLE)->eq('id', $group_id)->findOne();
46 | }
47 |
48 | /**
49 | * Get a specific group by externalID
50 | *
51 | * @access public
52 | * @param string $external_id
53 | * @return array
54 | */
55 | public function getByExternalId($external_id)
56 | {
57 | return $this->db->table(self::TABLE)->eq('external_id', $external_id)->findOne();
58 | }
59 |
60 | /**
61 | * Get specific groups by externalIDs
62 | *
63 | * @access public
64 | * @param string[] $external_ids
65 | * @return array
66 | */
67 | public function getByExternalIds(array $external_ids)
68 | {
69 | if (empty($external_ids)) {
70 | return [];
71 | }
72 |
73 | return $this->db->table(self::TABLE)->in('external_id', $external_ids)->findAll();
74 | }
75 |
76 | /**
77 | * Get all groups
78 | *
79 | * @access public
80 | * @return array
81 | */
82 | public function getAll()
83 | {
84 | return $this->getQuery()->asc('id')->findAll();
85 | }
86 |
87 | /**
88 | * Remove a group
89 | *
90 | * @access public
91 | * @param integer $group_id
92 | * @return boolean
93 | */
94 | public function remove($group_id)
95 | {
96 | return $this->db->table(self::TABLE)->eq('id', $group_id)->remove();
97 | }
98 |
99 | /**
100 | * Create a new group
101 | *
102 | * @access public
103 | * @param string $external_id
104 | * @return integer|boolean
105 | */
106 | public function create($external_id = '')
107 | {
108 | return $this->db->table(self::TABLE)->persist(array(
109 | 'external_id' => $external_id,
110 | ));
111 | }
112 |
113 | /**
114 | * Update existing group
115 | *
116 | * @access public
117 | * @param array $values
118 | * @return boolean
119 | */
120 | public function update(array $values)
121 | {
122 | return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values);
123 | }
124 |
125 | /**
126 | * Get groupId from externalGroupId and create the group if not found
127 | *
128 | * @access public
129 | * @param string $name
130 | * @param string $external_id
131 | * @return bool|integer
132 | */
133 | public function getOrCreateExternalGroupId($name, $external_id)
134 | {
135 | $group_id = $this->db->table(self::TABLE)->eq('external_id', $external_id)->findOneColumn('id');
136 |
137 | if (empty($group_id)) {
138 | $group_id = $this->create($external_id);
139 | }
140 |
141 | return $group_id;
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/Model/NewMetaMagikSubquery.php:
--------------------------------------------------------------------------------
1 | subquery('(SELECT COUNT(*) FROM '.self::METADATA_TABLE.' WHERE task_id=tasks.id)', 'nb_metadata');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Model/NewUserNotificationFilterModel.php:
--------------------------------------------------------------------------------
1 | t('All tasks'),
45 | self::FILTER_ASSIGNEE => t('Only for tasks assigned to me'),
46 | self::FILTER_CREATOR => t('Only for tasks created by me'),
47 | self::FILTER_BOTH => t('Only for tasks created by me and tasks assigned to me'),
48 | );
49 | }
50 |
51 | /**
52 | * Get user selected filter
53 | *
54 | * @access public
55 | * @param integer $user_id
56 | * @return integer
57 | */
58 | public function getSelectedFilter($user_id)
59 | {
60 | return $this->db->table(UserModel::TABLE)->eq('id', $user_id)->findOneColumn('notifications_filter');
61 | }
62 |
63 | /**
64 | * Save selected filter for a user
65 | *
66 | * @access public
67 | * @param integer $user_id
68 | * @param string $filter
69 | * @return boolean
70 | */
71 | public function saveFilter($user_id, $filter)
72 | {
73 | return $this->db->table(UserModel::TABLE)->eq('id', $user_id)->update(array(
74 | 'notifications_filter' => $filter,
75 | ));
76 | }
77 |
78 | /**
79 | * Get user selected projects
80 | *
81 | * @access public
82 | * @param integer $user_id
83 | * @return array
84 | */
85 | public function getSelectedProjects($user_id)
86 | {
87 | return $this->db->table(self::PROJECT_TABLE)->eq('user_id', $user_id)->findAllByColumn('project_id');
88 | }
89 |
90 | /**
91 | * Save selected projects for a user
92 | *
93 | * @access public
94 | * @param integer $user_id
95 | * @param integer[] $project_ids
96 | * @return boolean
97 | */
98 | public function saveSelectedProjects($user_id, array $project_ids)
99 | {
100 | $results = array();
101 | $this->db->table(self::PROJECT_TABLE)->eq('user_id', $user_id)->remove();
102 |
103 | foreach ($project_ids as $project_id) {
104 | $results[] = $this->db->table(self::PROJECT_TABLE)->insert(array(
105 | 'user_id' => $user_id,
106 | 'project_id' => $project_id,
107 | ));
108 | }
109 |
110 | return !in_array(false, $results, true);
111 | }
112 |
113 | /**
114 | * Return true if the user should receive notification
115 | *
116 | * @access public
117 | * @param array $user
118 | * @param array $event_data
119 | * @return boolean
120 | */
121 | public function shouldReceiveNotification(array $user, array $event_data)
122 | {
123 | $filters = array(
124 | 'filterNone',
125 | 'filterAssignee',
126 | 'filterCreator',
127 | 'filterBoth',
128 | );
129 |
130 | foreach ($filters as $filter) {
131 | if ($this->$filter($user, $event_data)) {
132 | return $this->filterProject($user, $event_data);
133 | }
134 | }
135 |
136 | return false;
137 | }
138 |
139 | /**
140 | * Return true if the user will receive all notifications
141 | *
142 | * @access public
143 | * @param array $user
144 | * @return boolean
145 | */
146 | public function filterNone(array $user)
147 | {
148 | return $user['notifications_filter'] == self::FILTER_NONE;
149 | }
150 |
151 | /**
152 | * Return true if the user is the assignee and selected the filter "assignee"
153 | *
154 | * @access public
155 | * @param array $user
156 | * @param array $event_data
157 | * @return boolean
158 | */
159 | public function filterAssignee(array $user, array $event_data)
160 | {
161 | if (!isset($event_data['task']['owner_ms'])) {
162 | $event_data['task']['owner_ms'] = 0;
163 | }
164 | if (!isset($event_data['task']['owner_gp'])) {
165 | $event_data['task']['owner_gp'] = 0;
166 | }
167 | return $user['notifications_filter'] == self::FILTER_ASSIGNEE &&
168 | ($event_data['task']['owner_id'] == $user['id'] ||
169 | $this->multiselectMemberModel->isMember($event_data['task']['owner_ms'], $user['id']) ||
170 | $this->groupMemberModel->isMember($event_data['task']['owner_gp'], $user['id']));
171 | }
172 |
173 | /**
174 | * Return true if the user is the creator and enabled the filter "creator"
175 | *
176 | * @access public
177 | * @param array $user
178 | * @param array $event_data
179 | * @return boolean
180 | */
181 | public function filterCreator(array $user, array $event_data)
182 | {
183 | return $user['notifications_filter'] == self::FILTER_CREATOR && $event_data['task']['creator_id'] == $user['id'];
184 | }
185 |
186 | /**
187 | * Return true if the user is the assignee or the creator and selected the filter "both"
188 | *
189 | * @access public
190 | * @param array $user
191 | * @param array $event_data
192 | * @return boolean
193 | */
194 | public function filterBoth(array $user, array $event_data)
195 | {
196 | if (!isset($event_data['task']['owner_ms'])) {
197 | $event_data['task']['owner_ms'] = 0;
198 | }
199 | if (!isset($event_data['task']['owner_gp'])) {
200 | $event_data['task']['owner_gp'] = 0;
201 | }
202 | return $user['notifications_filter'] == self::FILTER_BOTH &&
203 | ($event_data['task']['creator_id'] == $user['id'] || $event_data['task']['owner_id'] == $user['id'] ||
204 | $this->multiselectMemberModel->isMember($event_data['task']['owner_ms'], $user['id']) ||
205 | $this->groupMemberModel->isMember($event_data['task']['owner_gp'], $user['id']));
206 | }
207 |
208 | /**
209 | * Return true if the user want to receive notification for the selected project
210 | *
211 | * @access public
212 | * @param array $user
213 | * @param array $event_data
214 | * @return boolean
215 | */
216 | public function filterProject(array $user, array $event_data)
217 | {
218 | $projects = $this->getSelectedProjects($user['id']);
219 |
220 | if (! empty($projects)) {
221 | return in_array($event_data['task']['project_id'], $projects);
222 | }
223 |
224 | return true;
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/Model/OldMetaMagikSubquery.php:
--------------------------------------------------------------------------------
1 | subquery('(SELECT COUNT(*) FROM '.self::METADATA_TABLE.' WHERE task_id=tasks.id)', 'nb_metadata');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Model/OldTaskFinderModel.php:
--------------------------------------------------------------------------------
1 | db
96 | ->table(TaskModel::TABLE)
97 | ->columns(
98 | TaskModel::TABLE.'.id',
99 | TaskModel::TABLE.'.title',
100 | TaskModel::TABLE.'.date_due',
101 | TaskModel::TABLE.'.date_started',
102 | TaskModel::TABLE.'.project_id',
103 | TaskModel::TABLE.'.color_id',
104 | TaskModel::TABLE.'.priority',
105 | TaskModel::TABLE.'.time_spent',
106 | TaskModel::TABLE.'.time_estimated',
107 | ProjectModel::TABLE.'.name AS project_name',
108 | ColumnModel::TABLE.'.title AS column_name',
109 | UserModel::TABLE.'.username AS assignee_username',
110 | UserModel::TABLE.'.name AS assignee_name'
111 | )
112 | ->eq(TaskModel::TABLE.'.is_active', $is_active)
113 | ->in(ProjectModel::TABLE.'.id', $project_ids)
114 | ->join(ProjectModel::TABLE, 'id', 'project_id')
115 | ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE)
116 | ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE);
117 | }
118 |
119 | /**
120 | * Get query for assigned user tasks
121 | *
122 | * @access public
123 | * @param integer $user_id User id
124 | * @return \PicoDb\Table
125 | */
126 | public function getUserQuery($user_id)
127 | {
128 | return $this->getExtendedQuery()
129 | ->beginOr()
130 | ->eq(TaskModel::TABLE.'.owner_id', $user_id)
131 | ->addCondition(TaskModel::TABLE.".id IN (SELECT task_id FROM ".SubtaskModel::TABLE." WHERE ".SubtaskModel::TABLE.".user_id='$user_id')")
132 | ->addCondition(TaskModel::TABLE.".owner_gp IN (SELECT group_id FROM ".GroupMemberModel::TABLE." WHERE ".GroupMemberModel::TABLE.".user_id='$user_id')")
133 | ->addCondition(TaskModel::TABLE.".owner_ms IN (SELECT group_id FROM ".MultiselectMemberModel::TABLE." WHERE ".MultiselectMemberModel::TABLE.".user_id='$user_id')")
134 | ->closeOr()
135 | ->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN)
136 | ->eq(ProjectModel::TABLE.'.is_active', ProjectModel::ACTIVE)
137 | ->eq(ColumnModel::TABLE.'.hide_in_dashboard', 0);
138 | }
139 |
140 | /**
141 | * Extended query
142 | *
143 | * @access public
144 | * @return \PicoDb\Table
145 | */
146 | public function getExtendedQuery()
147 | {
148 | return $this->db
149 | ->table(TaskModel::TABLE)
150 | ->columns(
151 | '(SELECT COUNT(*) FROM '.CommentModel::TABLE.' WHERE task_id=tasks.id) AS nb_comments',
152 | '(SELECT COUNT(*) FROM '.TaskFileModel::TABLE.' WHERE task_id=tasks.id) AS nb_files',
153 | '(SELECT COUNT(*) FROM '.SubtaskModel::TABLE.' WHERE '.SubtaskModel::TABLE.'.task_id=tasks.id) AS nb_subtasks',
154 | '(SELECT COUNT(*) FROM '.SubtaskModel::TABLE.' WHERE '.SubtaskModel::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks',
155 | '(SELECT COUNT(*) FROM '.TaskLinkModel::TABLE.' WHERE '.TaskLinkModel::TABLE.'.task_id = tasks.id) AS nb_links',
156 | '(SELECT COUNT(*) FROM '.TaskExternalLinkModel::TABLE.' WHERE '.TaskExternalLinkModel::TABLE.'.task_id = tasks.id) AS nb_external_links',
157 | '(SELECT DISTINCT 1 FROM '.TaskLinkModel::TABLE.' WHERE '.TaskLinkModel::TABLE.'.task_id = tasks.id AND '.TaskLinkModel::TABLE.'.link_id = 9) AS is_milestone',
158 | TaskModel::TABLE.'.id',
159 | TaskModel::TABLE.'.reference',
160 | TaskModel::TABLE.'.title',
161 | TaskModel::TABLE.'.description',
162 | TaskModel::TABLE.'.date_creation',
163 | TaskModel::TABLE.'.date_modification',
164 | TaskModel::TABLE.'.date_completed',
165 | TaskModel::TABLE.'.date_started',
166 | TaskModel::TABLE.'.date_due',
167 | TaskModel::TABLE.'.color_id',
168 | TaskModel::TABLE.'.project_id',
169 | TaskModel::TABLE.'.column_id',
170 | TaskModel::TABLE.'.swimlane_id',
171 | TaskModel::TABLE.'.owner_id',
172 | TaskModel::TABLE.'.creator_id',
173 | TaskModel::TABLE.'.position',
174 | TaskModel::TABLE.'.is_active',
175 | TaskModel::TABLE.'.score',
176 | TaskModel::TABLE.'.category_id',
177 | TaskModel::TABLE.'.priority',
178 | TaskModel::TABLE.'.date_moved',
179 | TaskModel::TABLE.'.recurrence_status',
180 | TaskModel::TABLE.'.recurrence_trigger',
181 | TaskModel::TABLE.'.recurrence_factor',
182 | TaskModel::TABLE.'.recurrence_timeframe',
183 | TaskModel::TABLE.'.recurrence_basedate',
184 | TaskModel::TABLE.'.recurrence_parent',
185 | TaskModel::TABLE.'.recurrence_child',
186 | TaskModel::TABLE.'.time_estimated',
187 | TaskModel::TABLE.'.time_spent',
188 | UserModel::TABLE.'.username AS assignee_username',
189 | UserModel::TABLE.'.name AS assignee_name',
190 | UserModel::TABLE.'.email AS assignee_email',
191 | UserModel::TABLE.'.avatar_path AS assignee_avatar_path',
192 | CategoryModel::TABLE.'.name AS category_name',
193 | CategoryModel::TABLE.'.description AS category_description',
194 | ColumnModel::TABLE.'.title AS column_name',
195 | ColumnModel::TABLE.'.position AS column_position',
196 | SwimlaneModel::TABLE.'.name AS swimlane_name',
197 | ProjectModel::TABLE.'.name AS project_name',
198 | TaskModel::TABLE.'.owner_ms',
199 | GroupModel::TABLE.'.name AS assigned_groupname'
200 | )
201 | ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE)
202 | ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id')
203 | ->join(CategoryModel::TABLE, 'id', 'category_id', TaskModel::TABLE)
204 | ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE)
205 | ->join(SwimlaneModel::TABLE, 'id', 'swimlane_id', TaskModel::TABLE)
206 | ->join(GroupModel::TABLE, 'id', 'owner_gp', TaskModel::TABLE)
207 | ->join(MultiselectModel::TABLE, 'id', 'owner_ms', TaskModel::TABLE)
208 | ->join(ProjectModel::TABLE, 'id', 'project_id', TaskModel::TABLE);
209 | }
210 |
211 | /**
212 | * Get all tasks for a given project and status
213 | *
214 | * @access public
215 | * @param integer $project_id Project id
216 | * @param integer $status_id Status id
217 | * @return array
218 | */
219 | public function getAll($project_id, $status_id = TaskModel::STATUS_OPEN)
220 | {
221 | return $this->db
222 | ->table(TaskModel::TABLE)
223 | ->eq(TaskModel::TABLE.'.project_id', $project_id)
224 | ->eq(TaskModel::TABLE.'.is_active', $status_id)
225 | ->asc(TaskModel::TABLE.'.id')
226 | ->findAll();
227 | }
228 |
229 | /**
230 | * Get all tasks for a given project and status
231 | *
232 | * @access public
233 | * @param integer $project_id
234 | * @param array $status
235 | * @return array
236 | */
237 | public function getAllIds($project_id, array $status = array(TaskModel::STATUS_OPEN))
238 | {
239 | return $this->db
240 | ->table(TaskModel::TABLE)
241 | ->eq(TaskModel::TABLE.'.project_id', $project_id)
242 | ->in(TaskModel::TABLE.'.is_active', $status)
243 | ->asc(TaskModel::TABLE.'.id')
244 | ->findAllByColumn(TaskModel::TABLE.'.id');
245 | }
246 |
247 | /**
248 | * Get overdue tasks query
249 | *
250 | * @access public
251 | * @return \PicoDb\Table
252 | */
253 | public function getOverdueTasksQuery()
254 | {
255 | return $this->db->table(TaskModel::TABLE)
256 | ->columns(
257 | TaskModel::TABLE.'.id',
258 | TaskModel::TABLE.'.title',
259 | TaskModel::TABLE.'.date_due',
260 | TaskModel::TABLE.'.project_id',
261 | TaskModel::TABLE.'.creator_id',
262 | TaskModel::TABLE.'.owner_id',
263 | ProjectModel::TABLE.'.name AS project_name',
264 | UserModel::TABLE.'.username AS assignee_username',
265 | UserModel::TABLE.'.name AS assignee_name'
266 | )
267 | ->join(ProjectModel::TABLE, 'id', 'project_id')
268 | ->join(UserModel::TABLE, 'id', 'owner_id')
269 | ->eq(ProjectModel::TABLE.'.is_active', 1)
270 | ->eq(TaskModel::TABLE.'.is_active', 1)
271 | ->neq(TaskModel::TABLE.'.date_due', 0)
272 | ->lte(TaskModel::TABLE.'.date_due', time());
273 | }
274 |
275 | /**
276 | * Get a list of overdue tasks for all projects
277 | *
278 | * @access public
279 | * @return array
280 | */
281 | public function getOverdueTasks()
282 | {
283 | return $this->getOverdueTasksQuery()->findAll();
284 | }
285 |
286 | /**
287 | * Get a list of overdue tasks by project
288 | *
289 | * @access public
290 | * @param integer $project_id
291 | * @return array
292 | */
293 | public function getOverdueTasksByProject($project_id)
294 | {
295 | return $this->getOverdueTasksQuery()->eq(TaskModel::TABLE.'.project_id', $project_id)->findAll();
296 | }
297 |
298 | /**
299 | * Get a list of overdue tasks by user
300 | *
301 | * @access public
302 | * @param integer $user_id
303 | * @return array
304 | */
305 | public function getOverdueTasksByUser($user_id)
306 | {
307 | return $this->getOverdueTasksQuery()->eq(TaskModel::TABLE.'.owner_id', $user_id)->findAll();
308 | }
309 |
310 | /**
311 | * Get project id for a given task
312 | *
313 | * @access public
314 | * @param integer $task_id Task id
315 | * @return integer
316 | */
317 | public function getProjectId($task_id)
318 | {
319 | return (int) $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOneColumn('project_id') ?: 0;
320 | }
321 |
322 | /**
323 | * Fetch a task by the id
324 | *
325 | * @access public
326 | * @param integer $task_id Task id
327 | * @return array
328 | */
329 | public function getById($task_id)
330 | {
331 | return $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOne();
332 | }
333 |
334 | /**
335 | * Fetch a task by the reference (external id)
336 | *
337 | * @access public
338 | * @param integer $project_id Project id
339 | * @param string $reference Task reference
340 | * @return array
341 | */
342 | public function getByReference($project_id, $reference)
343 | {
344 | return $this->db->table(TaskModel::TABLE)->eq('project_id', $project_id)->eq('reference', $reference)->findOne();
345 | }
346 |
347 | /**
348 | * Get task details (fetch more information from other tables)
349 | *
350 | * @access public
351 | * @param integer $task_id Task id
352 | * @return array
353 | */
354 | public function getDetails($task_id)
355 | {
356 | return $this->db->table(TaskModel::TABLE)
357 | ->columns(
358 | TaskModel::TABLE.'.*',
359 | CategoryModel::TABLE.'.name AS category_name',
360 | SwimlaneModel::TABLE.'.name AS swimlane_name',
361 | ProjectModel::TABLE.'.name AS project_name',
362 | ColumnModel::TABLE.'.title AS column_title',
363 | UserModel::TABLE.'.username AS assignee_username',
364 | UserModel::TABLE.'.name AS assignee_name',
365 | 'uc.username AS creator_username',
366 | 'uc.name AS creator_name',
367 | CategoryModel::TABLE.'.description AS category_description',
368 | ColumnModel::TABLE.'.position AS column_position',
369 | GroupModel::TABLE.'.name AS assigned_groupname'
370 | )
371 | ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE)
372 | ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id')
373 | ->join(CategoryModel::TABLE, 'id', 'category_id', TaskModel::TABLE)
374 | ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE)
375 | ->join(SwimlaneModel::TABLE, 'id', 'swimlane_id', TaskModel::TABLE)
376 | ->join(ProjectModel::TABLE, 'id', 'project_id', TaskModel::TABLE)
377 | ->join(GroupModel::TABLE, 'id', 'owner_gp', TaskModel::TABLE)
378 | ->join(MultiselectModel::TABLE, 'id', 'owner_ms', TaskModel::TABLE)
379 | ->eq(TaskModel::TABLE.'.id', $task_id)
380 | ->findOne();
381 | }
382 |
383 | /**
384 | * Get iCal query
385 | *
386 | * @access public
387 | * @return \PicoDb\Table
388 | */
389 | public function getICalQuery()
390 | {
391 | return $this->db->table(TaskModel::TABLE)
392 | ->left(UserModel::TABLE, 'ua', 'id', TaskModel::TABLE, 'owner_id')
393 | ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id')
394 | ->columns(
395 | TaskModel::TABLE.'.*',
396 | 'ua.email AS assignee_email',
397 | 'ua.name AS assignee_name',
398 | 'ua.username AS assignee_username',
399 | 'uc.email AS creator_email',
400 | 'uc.name AS creator_name',
401 | 'uc.username AS creator_username'
402 | );
403 | }
404 |
405 | /**
406 | * Count all tasks for a given project and status
407 | *
408 | * @access public
409 | * @param integer $project_id Project id
410 | * @param array $status List of status id
411 | * @return integer
412 | */
413 | public function countByProjectId($project_id, array $status = array(TaskModel::STATUS_OPEN, TaskModel::STATUS_CLOSED))
414 | {
415 | return $this->db
416 | ->table(TaskModel::TABLE)
417 | ->eq('project_id', $project_id)
418 | ->in('is_active', $status)
419 | ->count();
420 | }
421 |
422 | /**
423 | * Count the number of tasks for a given column and status
424 | *
425 | * @access public
426 | * @param integer $project_id Project id
427 | * @param integer $column_id Column id
428 | * @param array $status
429 | * @return int
430 | */
431 | public function countByColumnId($project_id, $column_id, array $status = array(TaskModel::STATUS_OPEN))
432 | {
433 | return $this->db
434 | ->table(TaskModel::TABLE)
435 | ->eq('project_id', $project_id)
436 | ->eq('column_id', $column_id)
437 | ->in('is_active', $status)
438 | ->count();
439 | }
440 |
441 | /**
442 | * Count the number of tasks for a given column and swimlane
443 | *
444 | * @access public
445 | * @param integer $project_id Project id
446 | * @param integer $column_id Column id
447 | * @param integer $swimlane_id Swimlane id
448 | * @return integer
449 | */
450 | public function countByColumnAndSwimlaneId($project_id, $column_id, $swimlane_id)
451 | {
452 | return $this->db
453 | ->table(TaskModel::TABLE)
454 | ->eq('project_id', $project_id)
455 | ->eq('column_id', $column_id)
456 | ->eq('swimlane_id', $swimlane_id)
457 | ->eq('is_active', 1)
458 | ->count();
459 | }
460 |
461 | /**
462 | * Return true if the task exists
463 | *
464 | * @access public
465 | * @param integer $task_id Task id
466 | * @return boolean
467 | */
468 | public function exists($task_id)
469 | {
470 | return $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->exists();
471 | }
472 |
473 | /**
474 | * Get project token
475 | *
476 | * @access public
477 | * @param integer $task_id
478 | * @return string
479 | */
480 | public function getProjectToken($task_id)
481 | {
482 | return $this->db
483 | ->table(TaskModel::TABLE)
484 | ->eq(TaskModel::TABLE.'.id', $task_id)
485 | ->join(ProjectModel::TABLE, 'id', 'project_id')
486 | ->findOneColumn(ProjectModel::TABLE.'.token');
487 | }
488 | }
489 |
--------------------------------------------------------------------------------
/Model/TaskProjectMoveModel.php:
--------------------------------------------------------------------------------
1 | taskFinderModel->getById($task_id);
36 | $values = $this->prepare($project_id, $swimlane_id, $column_id, $category_id, $owner_id, $task);
37 |
38 | $this->checkDestinationProjectValues($values);
39 | $this->tagDuplicationModel->syncTaskTagsToAnotherProject($task_id, $project_id);
40 |
41 | // Check if the group is allowed for the destination project and unassign if not
42 | $group_id = $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOneColumn('owner_gp');
43 | if ($group_id > 0) {
44 | $group_in_project = $this->db
45 | ->table(ProjectGroupRoleModel::TABLE)
46 | ->eq('project_id', $project_id)
47 | ->eq('group_id', $group_id)
48 | ->exists();
49 | if (!$group_in_project) {
50 | $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->update(['owner_gp' => 0]);
51 | }
52 | }
53 |
54 | // Check if the other assignees are allowed for the destination project and remove from ms group if not
55 | $ms_id = $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOneColumn('owner_ms');
56 | if ($ms_id > 0) {
57 | $users_in_ms = $this->multiselectMemberModel->getMembers($ms_id);
58 | foreach ($users_in_ms as $user) {
59 | if (! $this->projectPermissionModel->isAssignable($project_id, $user['id'])) {
60 | $this->multiselectMemberModel->removeUser($ms_id, $user['id']);
61 | }
62 | }
63 | }
64 |
65 |
66 | if ($this->db->table(TaskModel::TABLE)->eq('id', $task_id)->update($values)) {
67 | $this->queueManager->push($this->taskEventJob->withParams($task_id, array(TaskModel::EVENT_MOVE_PROJECT), $values));
68 | }
69 |
70 | return true;
71 | }
72 |
73 | /**
74 | * Prepare new task values
75 | *
76 | * @access protected
77 | * @param integer $project_id
78 | * @param integer $swimlane_id
79 | * @param integer $column_id
80 | * @param integer $category_id
81 | * @param integer $owner_id
82 | * @param array $task
83 | * @return array
84 | */
85 | protected function prepare($project_id, $swimlane_id, $column_id, $category_id, $owner_id, array $task)
86 | {
87 | $values = array();
88 | $values['is_active'] = 1;
89 | $values['project_id'] = $project_id;
90 | $values['column_id'] = $column_id !== null ? $column_id : $task['column_id'];
91 | $values['position'] = $this->taskFinderModel->countByColumnId($project_id, $values['column_id']) + 1;
92 | $values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $task['swimlane_id'];
93 | $values['category_id'] = $category_id !== null ? $category_id : $task['category_id'];
94 | $values['owner_id'] = $owner_id !== null ? $owner_id : $task['owner_id'];
95 | $values['priority'] = $task['priority'];
96 | return $values;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Model/TaskRecurrenceModel.php:
--------------------------------------------------------------------------------
1 | t('No'),
33 | TaskModel::RECURRING_STATUS_PENDING => t('Yes'),
34 | );
35 | }
36 |
37 | /**
38 | * Return the list recurrence triggers
39 | *
40 | * @access public
41 | * @return array
42 | */
43 | public function getRecurrenceTriggerList()
44 | {
45 | return array(
46 | TaskModel::RECURRING_TRIGGER_FIRST_COLUMN => t('When task is moved from first column'),
47 | TaskModel::RECURRING_TRIGGER_LAST_COLUMN => t('When task is moved to last column'),
48 | TaskModel::RECURRING_TRIGGER_CLOSE => t('When task is closed'),
49 | );
50 | }
51 |
52 | /**
53 | * Return the list options to calculate recurrence due date
54 | *
55 | * @access public
56 | * @return array
57 | */
58 | public function getRecurrenceBasedateList()
59 | {
60 | return array(
61 | TaskModel::RECURRING_BASEDATE_DUEDATE => t('Existing due date'),
62 | TaskModel::RECURRING_BASEDATE_TRIGGERDATE => t('Action date'),
63 | );
64 | }
65 |
66 | /**
67 | * Return the list recurrence timeframes
68 | *
69 | * @access public
70 | * @return array
71 | */
72 | public function getRecurrenceTimeframeList()
73 | {
74 | return array(
75 | TaskModel::RECURRING_TIMEFRAME_DAYS => t('Day(s)'),
76 | TaskModel::RECURRING_TIMEFRAME_MONTHS => t('Month(s)'),
77 | TaskModel::RECURRING_TIMEFRAME_YEARS => t('Year(s)'),
78 | );
79 | }
80 |
81 | /**
82 | * Duplicate recurring task
83 | *
84 | * @access public
85 | * @param integer $task_id Task id
86 | * @return boolean|integer Recurrence task id
87 | */
88 | public function duplicateRecurringTask($task_id)
89 | {
90 | $values = $this->copyFields($task_id);
91 |
92 | if ($values['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING) {
93 | $values['recurrence_parent'] = $task_id;
94 | $values['column_id'] = $this->columnModel->getFirstColumnId($values['project_id']);
95 | $this->calculateRecurringTaskDueDate($values);
96 |
97 | $recurring_task_id = $this->save($task_id, $values);
98 |
99 | if ($recurring_task_id !== false) {
100 | $this->tagDuplicationModel->duplicateTaskTags($task_id, $recurring_task_id);
101 |
102 | $parent_update = $this->db
103 | ->table(TaskModel::TABLE)
104 | ->eq('id', $task_id)
105 | ->update(array(
106 | 'recurrence_status' => TaskModel::RECURRING_STATUS_PROCESSED,
107 | 'recurrence_child' => $recurring_task_id,
108 | ));
109 |
110 | if ($parent_update) {
111 | return $recurring_task_id;
112 | }
113 | }
114 | }
115 |
116 | return false;
117 | }
118 |
119 | /**
120 | * Calculate new due date for new recurrence task
121 | *
122 | * @access public
123 | * @param array $values Task fields
124 | */
125 | public function calculateRecurringTaskDueDate(array &$values)
126 | {
127 | if (! empty($values['date_due']) && $values['recurrence_factor'] != 0) {
128 | if ($values['recurrence_basedate'] == TaskModel::RECURRING_BASEDATE_TRIGGERDATE) {
129 | $values['date_due'] = time();
130 | }
131 |
132 | $factor = abs($values['recurrence_factor']);
133 | $subtract = $values['recurrence_factor'] < 0;
134 |
135 | switch ($values['recurrence_timeframe']) {
136 | case TaskModel::RECURRING_TIMEFRAME_MONTHS:
137 | $interval = 'P' . $factor . 'M';
138 | break;
139 | case TaskModel::RECURRING_TIMEFRAME_YEARS:
140 | $interval = 'P' . $factor . 'Y';
141 | break;
142 | default:
143 | $interval = 'P' . $factor . 'D';
144 | }
145 |
146 | $date_due = new DateTime();
147 | $date_due->setTimestamp($values['date_due']);
148 |
149 | $subtract ? $date_due->sub(new DateInterval($interval)) : $date_due->add(new DateInterval($interval));
150 |
151 | $values['date_due'] = $date_due->getTimestamp();
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/Plugin.php:
--------------------------------------------------------------------------------
1 | template->setTemplateOverride('task/changes', 'group_assign:task/changes');
39 |
40 | //Notifications
41 | $this->container['userNotificationFilterModel'] = $this->container->factory(function ($c) {
42 | return new NewUserNotificationFilterModel($c);
43 | });
44 |
45 | //Helpers
46 | $this->helper->register('newTaskHelper', '\Kanboard\Plugin\Group_assign\Helper\NewTaskHelper');
47 | $this->helper->register('sizeAvatarHelperExtend', '\Kanboard\Plugin\Group_assign\Helper\SizeAvatarHelperExtend');
48 |
49 | //Models and backward compatibility
50 | $load_new_classes = true;
51 | $applications_version = str_replace('v', '', APP_VERSION);
52 |
53 | if (strpos(APP_VERSION, 'master') !== false || strpos(APP_VERSION, 'main') !== false) {
54 | if (file_exists('ChangeLog')) {
55 | $applications_version = trim(file_get_contents('ChangeLog', false, null, 8, 6), ' ');
56 | $clean_appversion = preg_replace('/\s+/', '', $applications_version);
57 | $load_new_classes = version_compare($clean_appversion, '1.2.6', '<');
58 | }
59 | } else {
60 | $load_new_classes = version_compare($applications_version, '1.2.5', '>');
61 | }
62 |
63 | if ($load_new_classes) {
64 | if (file_exists('plugins/MetaMagik')) {
65 | $this->container['taskFinderModel'] = $this->container->factory(function ($c) {
66 | return new NewMetaMagikSubquery($c);
67 | });
68 | } else {
69 | $this->container['taskFinderModel'] = $this->container->factory(function ($c) {
70 | return new NewTaskFinderModel($c);
71 | });
72 | }
73 | $this->container['taskDuplicationModel'] = $this->container->factory(function ($c) {
74 | return new GroupAssignTaskDuplicationModel($c);
75 | });
76 | $this->container['taskProjectMoveModel '] = $this->container->factory(function ($c) {
77 | return new TaskProjectMoveModel($c);
78 | });
79 | $this->container['taskRecurrenceModel '] = $this->container->factory(function ($c) {
80 | return new TaskRecurrenceModel($c);
81 | });
82 | } else {
83 | if (file_exists('plugins/MetaMagik')) {
84 | $this->container['taskFinderModel'] = $this->container->factory(function ($c) {
85 | return new OldMetaMagikSubquery($c);
86 | });
87 | } else {
88 | $this->container['taskFinderModel'] = $this->container->factory(function ($c) {
89 | return new OldTaskFinderModel($c);
90 | });
91 | }
92 | $this->container['taskDuplicationModel'] = $this->container->factory(function ($c) {
93 | return new GroupAssignTaskDuplicationModel($c);
94 | });
95 | $this->container['taskProjectMoveModel '] = $this->container->factory(function ($c) {
96 | return new TaskProjectMoveModel($c);
97 | });
98 | $this->container['taskRecurrenceModel '] = $this->container->factory(function ($c) {
99 | return new TaskRecurrenceModel($c);
100 | });
101 | }
102 |
103 | $this->hook->on('model:task:project_duplication:aftersave', function($hook_values) {
104 | $destination = $hook_values['destination_task_id'];
105 | $source = $hook_values['source_task_id'];
106 | $project_Id = $this->taskFinderModel->getProjectId($destination);
107 |
108 | if ($destination !== false) {
109 | // Check if the group is allowed for the destination project
110 | $group_id = $this->db->table(TaskModel::TABLE)->eq('id', $source)->findOneColumn('owner_gp');
111 | if ($group_id > 0) {
112 | $group_in_project = $this->db
113 | ->table(ProjectGroupRoleModel::TABLE)
114 | ->eq('project_id', $project_Id)
115 | ->eq('group_id', $group_id)
116 | ->exists();
117 | if ($group_in_project) {
118 | $this->db->table(TaskModel::TABLE)->eq('id', $hook_values['destination_task_id'])->update(['owner_gp' => $group_id]);
119 | }
120 | }
121 |
122 | // Check if the other assignees are allowed for the destination project
123 | $ms_id = $this->db->table(TaskModel::TABLE)->eq('id', $source)->findOneColumn('owner_ms');
124 | if ($ms_id > 0) {
125 | $users_in_ms = $this->multiselectMemberModel->getMembers($ms_id);
126 | $new_ms_id = $this->multiselectModel->create();
127 | $this->db->table(TaskModel::TABLE)->eq('id', $hook_values['destination_task_id'])->update(['owner_ms' => $new_ms_id]);
128 | foreach ($users_in_ms as $user) {
129 | if ($this->projectPermissionModel->isAssignable($project_Id, $user['id'])) {
130 | $this->multiselectMemberModel->addUser($new_ms_id, $user['id']);
131 | }
132 | }
133 | }
134 | }
135 | });
136 |
137 | //Task - Template - details.php
138 | $this->template->hook->attach('template:task:details:third-column', 'group_assign:task/details');
139 | $this->template->hook->attach('template:task:details:third-column', 'group_assign:task/multi');
140 |
141 | //Forms - task_creation.php and task_modification.php
142 | if (file_exists('plugins/TemplateTitle')) {
143 | $this->template->setTemplateOverride('task_creation/show', 'group_assign:task_creation/show_TT');
144 | $this->template->setTemplateOverride('task_modification/show', 'group_assign:task_modification/show_TT');
145 | } else {
146 | $this->template->setTemplateOverride('task_creation/show', 'group_assign:task_creation/show');
147 | $this->template->setTemplateOverride('task_modification/show', 'group_assign:task_modification/show');
148 | }
149 | //Board
150 | $this->template->hook->attach('template:board:private:task:before-title', 'group_assign:board/group');
151 |
152 | if ($this->configModel->get('boardcustomizer_compactlayout', '') == 'enable') {
153 | $this->template->hook->attach('template:board:private:task:before-avatar', 'group_assign:board/multi');
154 | } else {
155 | $this->template->hook->attach('template:board:private:task:before-title', 'group_assign:board/multi');
156 | }
157 | $groupmodel = $this->projectGroupRoleModel;
158 | $this->template->hook->attachCallable('template:app:filters-helper:after', 'group_assign:board/filter', function ($array = array()) use ($groupmodel) {
159 | if (!empty($array) && $array['id'] >= 1) {
160 | return ['grouplist' => array_column($groupmodel->getGroups($array['id']), 'name')];
161 | } else {
162 | return ['grouplist' => array()];
163 | }
164 | });
165 |
166 | //Filter
167 | $this->container->extend('taskLexer', function ($taskLexer, $c) {
168 | $taskLexer->withFilter(TaskAllAssigneeFilter::getInstance()->setDatabase($c['db'])
169 | ->setCurrentUserId($c['userSession']->getId()));
170 | return $taskLexer;
171 | });
172 |
173 | //Actions
174 | $this->actionManager->register(new EmailGroup($this->container));
175 | $this->actionManager->register(new EmailGroupDue($this->container));
176 | $this->actionManager->register(new EmailOtherAssignees($this->container));
177 | $this->actionManager->register(new EmailOtherAssigneesDue($this->container));
178 | $this->actionManager->register(new AssignGroup($this->container));
179 |
180 | //Params
181 | $this->template->setTemplateOverride('action_creation/params', 'group_assign:action_creation/params');
182 |
183 | //CSS
184 | $this->hook->on('template:layout:css', array('template' => 'plugins/Group_assign/Assets/css/group_assign.css'));
185 |
186 | //JS
187 | $this->hook->on('template:layout:js', array('template' => 'plugins/Group_assign/Assets/js/group_assign.js'));
188 |
189 | //Calendar Events
190 | $container = $this->container;
191 |
192 | $this->hook->on('controller:calendar:user:events', function ($user_id, $start, $end) use ($container) {
193 | $model = new GroupAssignCalendarModel($container);
194 | return $model->getUserCalendarEvents($user_id, $start, $end); // Return new events
195 | });
196 |
197 | //Roles
198 | $this->template->hook->attach('template:config:application', 'group_assign:config/toggle');
199 |
200 | if ($this->configModel->get('enable_am_group_management', '2') == 1) {
201 | $this->applicationAccessMap->add('GroupListController', '*', Role::APP_MANAGER);
202 | $this->applicationAccessMap->add('GroupCreationController', '*', Role::APP_MANAGER);
203 | $this->template->setTemplateOverride('header/user_dropdown', 'group_assign:header/user_dropdown');
204 | }
205 |
206 | //API
207 | $this->api->getProcedureHandler()->withClassAndMethod('createTaskGroupAssign', new GroupAssignTaskProcedures($this->container), 'createTaskGroupAssign');
208 | $this->api->getProcedureHandler()->withClassAndMethod('updateTaskGroupAssign', new GroupAssignTaskProcedures($this->container), 'updateTaskGroupAssign');
209 | }
210 |
211 | public function onStartup()
212 | {
213 | Translator::load($this->languageModel->getCurrentLanguage(), __DIR__.'/Locale');
214 | }
215 |
216 | public function getClasses()
217 | {
218 | return [
219 | 'Plugin\Group_assign\Model' => [
220 | 'MultiselectMemberModel', 'MultiselectModel', 'GroupColorExtension', 'TaskProjectMoveModel', 'TaskRecurrenceModel',
221 | ],
222 | ];
223 | }
224 |
225 | public function getPluginName()
226 | {
227 | return 'Group_assign';
228 | }
229 | public function getPluginDescription()
230 | {
231 | return t('Add group assignment to tasks');
232 | }
233 | public function getPluginAuthor()
234 | {
235 | return 'Craig Crosby';
236 | }
237 | public function getPluginVersion()
238 | {
239 | return '1.8.2';
240 | }
241 | public function getPluginHomepage()
242 | {
243 | return 'https://github.com/creecros/group_assign';
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Checkout our latest project
2 | [](https://github.com/docpht/docpht)
3 |
4 | - With [DocPHT](https://github.com/docpht/docpht) you can take notes and quickly document anything and without the use of any database.
5 | -----------
6 | [](https://github.com/creecros/Group_assign/releases)
7 | [](https://github.com/creecros/Group_assign/blob/master/LICENSE)
8 | [](https://github.com/creecros/Group_assign/graphs/contributors)
9 | []()
10 | [](https://github.com/creecros/Group_assign/releases)
11 |
12 | Donate to help keep this project maintained.
13 |
14 |
15 |
16 | :star: If you use it, you should star it on Github!
17 | It's the least you can do for all the work put into it!
18 |
19 |
20 | # Group_assign
21 | Assign Tasks to Groups or from Multi-Select of Users with permissions from the project
22 |
23 | # Requirements
24 | Kanboard v1.1.0 or Higher
25 |
26 | Group_assign Versions 1.7.7 and below will support PHP version below 7
27 |
28 | # Features and usage
29 | * A task can have an assigned group or selection of users
30 | * Can only assign groups or other assigness to a task that have permissions in the Project.
31 | * If a user is in a group that a task is assigned to, it will show up on their dashboard.
32 | * If a user is in other assignees multiselect that a task is assigned to, it will show up on their dashboard.
33 | * If a user is in a group that a task is assigned to, it will show up in their calendar.
34 | * If a user is in other assignees multiselect that a task is assigned to, it will show up in their calendar.
35 | * 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.
36 | * Includes 5 Automatic Actions to utilize the Assigned Group
37 | * Email Assigned Group on Task Modification, Creation, Close, or Movement
38 | * Email Assigned Group of impending Task Due Date
39 | * Email Other Assignees on Task Modification, Creation, Close, or Movement
40 | * Email Other Assignees of impending Task Due Date
41 | * Assign task to a group on creation or movement
42 | * 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.
43 | * 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.
44 | * 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.
45 | * 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.
46 | * User assigneed via a group or multiselect will now recieve notifications
47 | * Changing assigned group or any multiselect users will now trigger `EVENT_ASSIGNEE_CHANGE`
48 | * Duplicating Tasks will include assigned groups and other users.
49 | * Duplicating to another project or moving to another project will check permissions of assignees, and remove those without permission.
50 | * Task Reccurences will include group assigned and other assignees in the recurrence.
51 | * Setting included to enable group managment for Application Managers
52 | * Found in `Settings > Application settings`
53 |
54 | # Future enhancments
55 | Find bugs or missing functionality, please report it.
56 |
57 | - [x] Add a few basic automatic actions that utilize Groups assigned
58 | - [x] Add relationship for ``allassignees:Username`` or ``allassignees:Name`` in the table lookup
59 | - [x] Add an event for assigned group change.
60 | - [x] Incorporate into notifications
61 | - [x] Address Task Duplication
62 | - [x] Task Recurrence
63 |
64 | # Manual Installation
65 |
66 | - Find the release you wish to install: https://github.com/creecros/Group_assign/releases
67 | - Download the provided zip, not the source zip, i.e. `Group_assign-x.x.x.zip`
68 | - Unzip contents to the plugins folder
69 |
70 | 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.
71 |
72 | # Screenshots
73 |
74 | ## Task Details:
75 | 
76 |
77 | ## Task Creation/Modification:
78 | 
79 | 
80 |
81 | ## Board View:
82 | 
83 |
84 | ## Users Calendar View
85 |
86 | - 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.
87 |
88 | 
89 |
90 |
91 | ## Automatic Actions:
92 | 
93 |
94 | 
95 |
96 | 
97 |
98 |
--------------------------------------------------------------------------------
/Schema/Mysql.php:
--------------------------------------------------------------------------------
1 | exec('ALTER TABLE multiselect CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
12 | $pdo->exec('ALTER TABLE multiselect_has_users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
13 | }
14 |
15 | function version_2(PDO $pdo)
16 | {
17 | $pdo->exec("ALTER TABLE `tasks` ADD COLUMN `owner_ms` INT DEFAULT '0'");
18 |
19 | $pdo->exec("
20 | CREATE TABLE `multiselect` (
21 | id INT NOT NULL AUTO_INCREMENT,
22 | external_id VARCHAR(255) DEFAULT '',
23 | PRIMARY KEY(id)
24 | ) ENGINE=InnoDB CHARSET=utf8
25 | ");
26 | $pdo->exec("
27 | CREATE TABLE multiselect_has_users (
28 | group_id INT NOT NULL,
29 | user_id INT NOT NULL,
30 | FOREIGN KEY(group_id) REFERENCES `multiselect`(id) ON DELETE CASCADE,
31 | FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
32 | UNIQUE(group_id, user_id)
33 | ) ENGINE=InnoDB CHARSET=utf8
34 | ");
35 | }
36 |
37 | function version_1(PDO $pdo)
38 | {
39 | $pdo->exec("ALTER TABLE `tasks` ADD COLUMN `owner_gp` INT DEFAULT '0'");
40 | }
41 |
--------------------------------------------------------------------------------
/Schema/Postgres.php:
--------------------------------------------------------------------------------
1 | exec("ALTER TABLE tasks ADD COLUMN owner_ms INT DEFAULT '0'");
12 |
13 | $pdo->exec("
14 | CREATE TABLE multiselect (
15 | id SERIAL PRIMARY KEY,
16 | external_id VARCHAR(255) DEFAULT ''
17 | )
18 | ");
19 |
20 | $pdo->exec("
21 | CREATE TABLE multiselect_has_users (
22 | group_id INTEGER NOT NULL,
23 | user_id INTEGER NOT NULL,
24 | FOREIGN KEY(group_id) REFERENCES multiselect(id) ON DELETE CASCADE,
25 | FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
26 | UNIQUE(group_id, user_id)
27 | )
28 | ");
29 | }
30 |
31 | function version_1(PDO $pdo)
32 | {
33 | $pdo->exec("ALTER TABLE tasks ADD COLUMN owner_gp INT DEFAULT '0'");
34 | }
35 |
--------------------------------------------------------------------------------
/Schema/Sqlite.php:
--------------------------------------------------------------------------------
1 | exec("ALTER TABLE tasks ADD COLUMN owner_ms INTEGER DEFAULT '0'");
12 |
13 | $pdo->exec("
14 | CREATE TABLE multiselect (
15 | id INTEGER PRIMARY KEY,
16 | external_id TEXT DEFAULT ''
17 | )
18 | ");
19 |
20 | $pdo->exec("
21 | CREATE TABLE multiselect_has_users (
22 | group_id INTEGER NOT NULL,
23 | user_id INTEGER NOT NULL,
24 | FOREIGN KEY(group_id) REFERENCES multiselect(id) ON DELETE CASCADE,
25 | FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
26 | UNIQUE(group_id, user_id)
27 | )
28 | ");
29 | }
30 |
31 | function version_1(PDO $pdo)
32 | {
33 | $pdo->exec("ALTER TABLE tasks ADD COLUMN owner_gp INTEGER DEFAULT '0'");
34 | }
35 |
--------------------------------------------------------------------------------
/Template/action_creation/params.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
74 |
--------------------------------------------------------------------------------
/Template/board/filter.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
--------------------------------------------------------------------------------
/Template/board/group.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | = t('Assigned Group:') ?>
4 | = $this->text->e($task['assigned_groupname'] ?: $task['owner_gp']) ?>
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Template/board/multi.php:
--------------------------------------------------------------------------------
1 | 0 && count($this->task->multiselectMemberModel->getMembers($task['owner_ms'])) > 0) : ?>
2 |
3 | user->userMetadataModel->exists($this->user->getid(), "boardcustomizer_compactlayout")) {
4 | /* compact card layout */
5 | ?>
6 |
7 |
= t('Other Assignees:') ?>
8 | = $this->helper->sizeAvatarHelperExtend->sizeMultiple($task['owner_ms'], 'avatar-inline avatar-bdyn', $this->task->configModel->get('b_av_size', '20')) ?>
9 |
10 |
13 |
14 |
= t('Other Assignees:') ?>
15 | = $this->helper->sizeAvatarHelperExtend->sizeMultiple($task['owner_ms'], 'avatar-inline avatar-bdyn', 13) ?>
16 |
17 |
18 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Template/config/toggle.php:
--------------------------------------------------------------------------------
1 |
2 | = $this->form->radio('enable_am_group_management', t('Enable Group Managment for Application Managers'), 1, isset($values['enable_am_group_management'])&& $values['enable_am_group_management']==1) ?>
3 | = $this->form->radio('enable_am_group_management', t('Disable Group Managment for Application Managers'), 2, isset($values['enable_am_group_management'])&& $values['enable_am_group_management']==2) ?>
4 |
5 |
--------------------------------------------------------------------------------
/Template/header/user_dropdown.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - = $this->text->e($this->user->getFullname()) ?>
5 | -
6 | = $this->url->icon('tachometer', t('My dashboard'), 'DashboardController', 'show', array('user_id' => $this->user->getId())) ?>
7 |
8 | -
9 | = $this->url->icon('home', t('My profile'), 'UserViewController', 'show', array('user_id' => $this->user->getId())) ?>
10 |
11 | -
12 | = $this->url->icon('folder', t('Projects management'), 'ProjectListController', 'show') ?>
13 |
14 | user->hasAccess('GroupListController', 'index') && $_SESSION['user']['role'] == 'app-manager'): ?>
15 | -
16 | = $this->url->icon('group', t('Groups management'), 'GroupListController', 'index') ?>
17 |
18 |
19 | user->hasAccess('UserListController', 'show')): ?>
20 | -
21 | = $this->url->icon('user', t('Users management'), 'UserListController', 'show') ?>
22 |
23 | -
24 | = $this->url->icon('group', t('Groups management'), 'GroupListController', 'index') ?>
25 |
26 | -
27 | = $this->url->icon('cubes', t('Plugins'), 'PluginController', 'show') ?>
28 |
29 | -
30 | = $this->url->icon('cog', t('Settings'), 'ConfigController', 'index') ?>
31 |
32 |
33 |
34 | = $this->hook->render('template:header:dropdown') ?>
35 |
36 | -
37 |
38 | = $this->url->doc(t('Documentation'), 'index') ?>
39 |
40 |
41 | -
42 | = $this->url->icon('sign-out', t('Logout'), 'AuthController', 'logout') ?>
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Template/task/changes.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | $value) {
6 | switch ($field) {
7 | case 'title':
8 | echo '- '.t('New title: %s', $task['title']).'
';
9 | break;
10 | case 'owner_id':
11 | if (empty($task['owner_id'])) {
12 | echo '- '.t('The task is not assigned anymore').'
';
13 | } else {
14 | echo '- '.t('New assignee: %s', $task['assignee_name'] ?: $task['assignee_username']).'
';
15 | }
16 | break;
17 | case 'category_id':
18 | if (empty($task['category_id'])) {
19 | echo '- '.t('There is no category now').'
';
20 | } else {
21 | echo '- '.t('New category: %s', $task['category_name']).'
';
22 | }
23 | break;
24 | case 'color_id':
25 | echo '- '.t('New color: %s', $this->text->in($task['color_id'], $this->task->getColors())).'
';
26 | break;
27 | case 'score':
28 | echo '- '.t('New complexity: %d', $task['score']).'
';
29 | break;
30 | case 'date_due':
31 | if (empty($task['date_due'])) {
32 | echo '- '.t('The due date have been removed').'
';
33 | } else {
34 | echo '- '.t('New due date: ').$this->dt->datetime($task['date_due']).'
';
35 | }
36 | break;
37 | case 'description':
38 | if (empty($task['description'])) {
39 | echo '- '.t('There is no description anymore').'
';
40 | }
41 | break;
42 | case 'recurrence_status':
43 | case 'recurrence_trigger':
44 | case 'recurrence_factor':
45 | case 'recurrence_timeframe':
46 | case 'recurrence_basedate':
47 | case 'recurrence_parent':
48 | case 'recurrence_child':
49 | echo '- '.t('Recurrence settings have been modified').'
';
50 | break;
51 | case 'time_spent':
52 | echo '- '.t('Time spent changed: %sh', $task['time_spent']).'
';
53 | break;
54 | case 'time_estimated':
55 | echo '- '.t('Time estimated changed: %sh', $task['time_estimated']).'
';
56 | break;
57 | case 'date_started':
58 | if ($value != 0) {
59 | echo '- '.t('Start date changed: ').$this->dt->datetime($task['date_started']).'
';
60 | }
61 | break;
62 | case 'owner_gp':
63 | if (empty($task['owner_gp'])) {
64 | echo '- '.t('The task is not assigned to a group anymore').'
';
65 | } else {
66 | echo '- '.t('New group assigned: %s', $task['assigned_groupname']).'
';
67 | }
68 | break;
69 | case 'owner_ms':
70 | if (empty($task['owner_ms'])) {
71 | echo '- '.t('The task is not assigned to multiple users anymore').'
';
72 | } else {
73 | echo '- '.t('The task has been assigned other users').'
';
74 | }
75 | break;
76 | default:
77 | echo '- '.t('The field "%s" have been updated', $field).'
';
78 | }
79 | }
80 |
81 | ?>
82 |
83 |
84 |
85 |
= t('The description has been modified:') ?>
86 |
87 |
= $this->text->markdown($task['description'], true) ?>
88 |
89 |
= $this->text->markdown($task['description']) ?>
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/Template/task/details.php:
--------------------------------------------------------------------------------
1 |
2 | = t('Assigned Group:') ?>
3 |
4 |
5 | = $this->text->e($task['assigned_groupname'] ?: $task['owner_gp']) ?>
6 |
7 | = t('not assigned') ?>
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Template/task/multi.php:
--------------------------------------------------------------------------------
1 | 0 && count($this->task->multiselectMemberModel->getMembers($task['owner_ms'])) > 0) : ?>
2 |
3 | = t('Other Assignees:') ?>
4 |
5 | = $this->helper->sizeAvatarHelperExtend->sizeMultiple($task['owner_ms'], 'avatar-inline', 20) ?>
6 |
7 |
--------------------------------------------------------------------------------
/Template/task_creation/show.php:
--------------------------------------------------------------------------------
1 |
4 |
54 |
--------------------------------------------------------------------------------
/Template/task_creation/show_TT.php:
--------------------------------------------------------------------------------
1 |
4 |
54 |
--------------------------------------------------------------------------------
/Template/task_modification/show.php:
--------------------------------------------------------------------------------
1 |
4 |
47 |
--------------------------------------------------------------------------------
/Template/task_modification/show_TT.php:
--------------------------------------------------------------------------------
1 |
4 |
47 |
--------------------------------------------------------------------------------
/Test/Helper/NewTaskHelperTest.php:
--------------------------------------------------------------------------------
1 | container);
14 | $plugin->scan();
15 | }
16 | public function testSelectPriority()
17 | {
18 | $helper = new NewTaskHelper($this->container);
19 | $this->assertNotEmpty($helper->renderPriorityField(array('priority_end' => '1', 'priority_start' => '5', 'priority_default' => '2'), array()));
20 | $this->assertNotEmpty($helper->renderPriorityField(array('priority_end' => '3', 'priority_start' => '1', 'priority_default' => '2'), array()));
21 | }
22 | public function testFormatPriority()
23 | {
24 | $helper = new NewTaskHelper($this->container);
25 | $this->assertEquals(
26 | '
P2',
27 | $helper->renderPriority(2)
28 | );
29 | $this->assertEquals(
30 | '
-P6',
31 | $helper->renderPriority(-6)
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Test/Model/NewTaskFinderModelTest.php:
--------------------------------------------------------------------------------
1 | container);
19 | $plugin->scan();
20 | }
21 |
22 | public function testGetDetails()
23 | {
24 | $taskCreationModel = new TaskCreationModel($this->container);
25 | $taskFinderModel = new NewTaskFinderModel($this->container);
26 | $projectModel = new ProjectModel($this->container);
27 | $categoryModel = new \Kanboard\Model\CategoryModel($this->container);
28 | $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1')));
29 | $this->assertEquals(1, $categoryModel->create(array('project_id' => 1, 'name' => 'C1')));
30 | $this->assertEquals(1, $taskCreationModel->create(array(
31 | 'project_id' => 1,
32 | 'title' => 'Task #1',
33 | 'reference' => 'test',
34 | 'description' => 'desc',
35 | 'owner_id' => 1,
36 | 'category_id' => 1,
37 | )));
38 | $task = $taskFinderModel->getDetails(1);
39 | $this->assertEquals(1, $task['id']);
40 | $this->assertEquals('test', $task['reference']);
41 | $this->assertEquals('Task #1', $task['title']);
42 | $this->assertEquals('desc', $task['description']);
43 | $this->assertEquals(time(), $task['date_creation'], 'Delta', 1);
44 | $this->assertEquals(time(), $task['date_modification'], 'Delta', 1);
45 | $this->assertEquals(time(), $task['date_moved'], 'Delta', 1);
46 | $this->assertEquals(0, $task['date_completed']);
47 | $this->assertEquals(0, $task['date_due']);
48 | $this->assertEquals(0, $task['date_started']);
49 | $this->assertEquals(0, $task['time_estimated']);
50 | $this->assertEquals(0, $task['time_spent']);
51 | $this->assertEquals('yellow', $task['color_id']);
52 | $this->assertEquals(1, $task['project_id']);
53 | $this->assertEquals(1, $task['column_id']);
54 | $this->assertEquals(1, $task['owner_id']);
55 | $this->assertEquals(0, $task['creator_id']);
56 | $this->assertEquals(1, $task['position']);
57 | $this->assertEquals(TaskModel::STATUS_OPEN, $task['is_active']);
58 | $this->assertEquals(0, $task['score']);
59 | $this->assertEquals(1, $task['category_id']);
60 | $this->assertEquals(0, $task['priority']);
61 | $this->assertEquals(1, $task['swimlane_id']);
62 | $this->assertEquals(TaskModel::RECURRING_STATUS_NONE, $task['recurrence_status']);
63 | $this->assertEquals(TaskModel::RECURRING_TRIGGER_FIRST_COLUMN, $task['recurrence_trigger']);
64 | $this->assertEquals(0, $task['recurrence_factor']);
65 | $this->assertEquals(TaskModel::RECURRING_TIMEFRAME_DAYS, $task['recurrence_timeframe']);
66 | $this->assertEquals(TaskModel::RECURRING_BASEDATE_DUEDATE, $task['recurrence_basedate']);
67 | $this->assertEquals(0, $task['recurrence_parent']);
68 | $this->assertEquals(0, $task['recurrence_child']);
69 | $this->assertEquals('C1', $task['category_name']);
70 | $this->assertEquals('Default swimlane', $task['swimlane_name']);
71 | $this->assertEquals('Project #1', $task['project_name']);
72 | $this->assertEquals('Backlog', $task['column_title']);
73 | $this->assertEquals('admin', $task['assignee_username']);
74 | $this->assertEquals('', $task['assignee_name']);
75 | $this->assertEquals('', $task['creator_username']);
76 | $this->assertEquals('', $task['creator_name']);
77 | }
78 | public function testGetTasksForDashboardWithHiddenColumn()
79 | {
80 | $taskCreationModel = new TaskCreationModel($this->container);
81 | $taskFinderModel = new NewTaskFinderModel($this->container);
82 | $projectModel = new ProjectModel($this->container);
83 | $columnModel = new ColumnModel($this->container);
84 | $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1')));
85 | $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1)));
86 | $this->assertEquals(2, $taskCreationModel->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 2, 'owner_id' => 1)));
87 | $tasks = $taskFinderModel->getUserQuery(1)->findAll();
88 | $this->assertCount(2, $tasks);
89 | $this->assertTrue($columnModel->update(2, 'Test', 0, '', 1));
90 | $tasks = $taskFinderModel->getUserQuery(1)->findAll();
91 | $this->assertCount(1, $tasks);
92 | $this->assertEquals('Task #1', $tasks[0]['title']);
93 | $this->assertEquals(1, $tasks[0]['column_id']);
94 | $this->assertTrue($columnModel->update(2, 'Test', 0, '', 0));
95 | $tasks = $taskFinderModel->getUserQuery(1)->findAll();
96 | $this->assertCount(2, $tasks);
97 | }
98 | public function testGetOverdueTasks()
99 | {
100 | $taskCreationModel = new TaskCreationModel($this->container);
101 | $taskFinderModel = new NewTaskFinderModel($this->container);
102 | $projectModel = new ProjectModel($this->container);
103 | $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1')));
104 | $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1, 'date_due' => strtotime('-1 day'))));
105 | $this->assertEquals(2, $taskCreationModel->create(array('title' => 'Task #2', 'project_id' => 1, 'date_due' => strtotime('+1 day'))));
106 | $this->assertEquals(3, $taskCreationModel->create(array('title' => 'Task #3', 'project_id' => 1, 'date_due' => 0)));
107 | $this->assertEquals(4, $taskCreationModel->create(array('title' => 'Task #3', 'project_id' => 1)));
108 | $tasks = $taskFinderModel->getOverdueTasks();
109 | $this->assertNotEmpty($tasks);
110 | $this->assertTrue(is_array($tasks));
111 | $this->assertCount(1, $tasks);
112 | $this->assertEquals('Task #1', $tasks[0]['title']);
113 | }
114 | public function testGetOverdueTasksByProject()
115 | {
116 | $taskCreationModel = new TaskCreationModel($this->container);
117 | $taskFinderModel = new NewTaskFinderModel($this->container);
118 | $projectModel = new ProjectModel($this->container);
119 | $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1')));
120 | $this->assertEquals(2, $projectModel->create(array('name' => 'Project #2')));
121 | $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1, 'date_due' => strtotime('-1 day'))));
122 | $this->assertEquals(2, $taskCreationModel->create(array('title' => 'Task #2', 'project_id' => 2, 'date_due' => strtotime('-1 day'))));
123 | $this->assertEquals(3, $taskCreationModel->create(array('title' => 'Task #3', 'project_id' => 1, 'date_due' => strtotime('+1 day'))));
124 | $this->assertEquals(4, $taskCreationModel->create(array('title' => 'Task #4', 'project_id' => 1, 'date_due' => 0)));
125 | $this->assertEquals(5, $taskCreationModel->create(array('title' => 'Task #5', 'project_id' => 1)));
126 | $tasks = $taskFinderModel->getOverdueTasksByProject(1);
127 | $this->assertNotEmpty($tasks);
128 | $this->assertTrue(is_array($tasks));
129 | $this->assertCount(1, $tasks);
130 | $this->assertEquals('Task #1', $tasks[0]['title']);
131 | }
132 | public function testGetOverdueTasksByUser()
133 | {
134 | $taskCreationModel = new TaskCreationModel($this->container);
135 | $taskFinderModel = new NewTaskFinderModel($this->container);
136 | $projectModel = new ProjectModel($this->container);
137 | $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1')));
138 | $this->assertEquals(2, $projectModel->create(array('name' => 'Project #2')));
139 | $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1, 'owner_id' => 1, 'date_due' => strtotime('-1 day'))));
140 | $this->assertEquals(2, $taskCreationModel->create(array('title' => 'Task #2', 'project_id' => 2, 'owner_id' => 1, 'date_due' => strtotime('-1 day'))));
141 | $this->assertEquals(3, $taskCreationModel->create(array('title' => 'Task #3', 'project_id' => 1, 'date_due' => strtotime('+1 day'))));
142 | $this->assertEquals(4, $taskCreationModel->create(array('title' => 'Task #4', 'project_id' => 1, 'date_due' => 0)));
143 | $this->assertEquals(5, $taskCreationModel->create(array('title' => 'Task #5', 'project_id' => 1)));
144 | $tasks = $taskFinderModel->getOverdueTasksByUser(1);
145 | $this->assertNotEmpty($tasks);
146 | $this->assertTrue(is_array($tasks));
147 | $this->assertCount(2, $tasks);
148 | $this->assertEquals(1, $tasks[0]['id']);
149 | $this->assertEquals('Task #1', $tasks[0]['title']);
150 | $this->assertEquals(1, $tasks[0]['owner_id']);
151 | $this->assertEquals(1, $tasks[0]['project_id']);
152 | $this->assertEquals('Project #1', $tasks[0]['project_name']);
153 | $this->assertEquals('admin', $tasks[0]['assignee_username']);
154 | $this->assertEquals('', $tasks[0]['assignee_name']);
155 | $this->assertEquals('Task #2', $tasks[1]['title']);
156 | }
157 | public function testCountByProject()
158 | {
159 | $taskCreationModel = new TaskCreationModel($this->container);
160 | $taskFinderModel = new NewTaskFinderModel($this->container);
161 | $projectModel = new ProjectModel($this->container);
162 | $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1')));
163 | $this->assertEquals(2, $projectModel->create(array('name' => 'Project #2')));
164 | $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1)));
165 | $this->assertEquals(2, $taskCreationModel->create(array('title' => 'Task #2', 'project_id' => 2)));
166 | $this->assertEquals(3, $taskCreationModel->create(array('title' => 'Task #3', 'project_id' => 2)));
167 | $this->assertEquals(1, $taskFinderModel->countByProjectId(1));
168 | $this->assertEquals(2, $taskFinderModel->countByProjectId(2));
169 | }
170 | public function testGetProjectToken()
171 | {
172 | $taskCreationModel = new TaskCreationModel($this->container);
173 | $taskFinderModel = new NewTaskFinderModel($this->container);
174 | $projectModel = new ProjectModel($this->container);
175 | $this->assertEquals(1, $projectModel->create(array('name' => 'Project #1')));
176 | $this->assertEquals(2, $projectModel->create(array('name' => 'Project #2')));
177 | $this->assertTrue($projectModel->enablePublicAccess(1));
178 | $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Task #1', 'project_id' => 1)));
179 | $this->assertEquals(2, $taskCreationModel->create(array('title' => 'Task #2', 'project_id' => 2)));
180 | $project = $projectModel->getById(1);
181 | $this->assertEquals($project['token'], $taskFinderModel->getProjectToken(1));
182 | $this->assertEmpty($taskFinderModel->getProjectToken(2));
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/Test/PluginTest.php:
--------------------------------------------------------------------------------
1 | container);
12 | $this->assertSame(null, $plugin->initialize());
13 | $this->assertNotEmpty($plugin->getPluginName());
14 | $this->assertNotEmpty($plugin->getPluginDescription());
15 | $this->assertNotEmpty($plugin->getPluginAuthor());
16 | $this->assertNotEmpty($plugin->getPluginVersion());
17 | $this->assertNotEmpty($plugin->getPluginHomepage());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
2 | plugins:
3 | - jemoji
4 |
--------------------------------------------------------------------------------