├── _config.yml ├── Assets ├── js │ └── group_assign.js └── css │ └── group_assign.css ├── Makefile ├── Template ├── task │ ├── multi.php │ ├── details.php │ └── changes.php ├── config │ └── toggle.php ├── board │ ├── group.php │ ├── filter.php │ └── multi.php ├── header │ └── user_dropdown.php ├── task_modification │ ├── show.php │ └── show_TT.php ├── task_creation │ ├── show_TT.php │ └── show.php └── action_creation │ └── params.php ├── Test ├── PluginTest.php ├── Helper │ └── NewTaskHelperTest.php └── Model │ └── NewTaskFinderModelTest.php ├── .travis.yml ├── Helper ├── SizeAvatarHelperExtend.php └── NewTaskHelper.php ├── Model ├── NewMetaMagikSubquery.php ├── OldMetaMagikSubquery.php ├── GroupColorExtension.php ├── GroupAssignCalendarModel.php ├── MultiselectModel.php ├── TaskProjectMoveModel.php ├── MultiselectMemberModel.php ├── TaskRecurrenceModel.php ├── GroupAssignTaskDuplicationModel.php ├── NewUserNotificationFilterModel.php └── OldTaskFinderModel.php ├── Schema ├── Postgres.php ├── Sqlite.php └── Mysql.php ├── Locale ├── fr_FR │ └── translations.php ├── es_ES │ └── translations.php ├── de_DE_du │ └── translations.php ├── uk_UA │ └── translations.php ├── bg_BG │ └── translations.php ├── de_DE │ └── translations.php └── pt_BR │ └── translations.php ├── LICENSE ├── Action ├── AssignGroup.php ├── EmailOtherAssignees.php ├── EmailGroup.php ├── EmailGroupDue.php └── EmailOtherAssigneesDue.php ├── Filter └── TaskAllAssigneeFilter.php ├── README.md ├── Api └── Procedure │ └── GroupAssignTaskProcedures.php ├── Controller ├── GroupAssignTaskCreationController.php └── GroupAssignTaskModificationController.php └── Plugin.php /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | plugins: 3 | - jemoji 4 | -------------------------------------------------------------------------------- /Assets/js/group_assign.js: -------------------------------------------------------------------------------- 1 | 2 | KB.on('modal.afterRender', function () { 3 | $(".group-assign-select").select2({ 4 | }); 5 | }); -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Template/task/multi.php: -------------------------------------------------------------------------------- 1 | 0 && count($this->task->multiselectMemberModel->getMembers($task['owner_ms'])) > 0) : ?> 2 |
= t('The description has been modified:') ?>
86 | 87 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Controller/GroupAssignTaskCreationController.php:
--------------------------------------------------------------------------------
1 | getProject();
21 | $swimlanesList = $this->swimlaneModel->getList($project['id'], false, true);
22 | $values += $this->prepareValues($project['is_private'], $swimlanesList);
23 |
24 | $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values));
25 | $values = $this->hook->merge('controller:task-creation:form:default', $values, array('default_values' => $values));
26 |
27 | $this->response->html($this->template->render('task_creation/show', array(
28 | 'project' => $project,
29 | 'errors' => $errors,
30 | 'values' => $values + array('project_id' => $project['id']),
31 | 'columns_list' => $this->columnModel->getList($project['id']),
32 | 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id'], true, false, $project['is_private'] == 1),
33 | 'categories_list' => $this->categoryModel->getList($project['id']),
34 | 'swimlanes_list' => $swimlanesList,
35 | 'screenshot' => $screenshot,
36 | 'files' => $files,
37 | )));
38 | }
39 |
40 | /**
41 | * Validate and save a new task
42 | *
43 | * @access public
44 | */
45 | public function save()
46 | {
47 | $project = $this->getProject();
48 | $values = $this->request->getValues();
49 | $values['project_id'] = $project['id'];
50 | $files = $this->request->getFileInfo('files');
51 |
52 | $screenshot = null;
53 | if (array_key_exists('screenshot', $values)) {
54 | $screenshot = $values['screenshot'];
55 | unset($values['screenshot']);
56 | }
57 |
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, $screenshot, $files, $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 | if ($task_id === 0) {
78 | $this->flash->failure(t('Unable to create this task.'));
79 | $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true);
80 | return;
81 | }
82 |
83 | if ($screenshot) {
84 | $this->taskFileModel->uploadScreenshot($task_id, $screenshot);
85 | }
86 |
87 | if (isset($files['name'][0]) && $files['name'][0] !== '') {
88 | $filesUploaded = $this->taskFileModel->uploadFiles($task_id, $files);
89 | if (! $filesUploaded) {
90 | $this->flash->failure(t('Unable to upload files, check the permissions of your data folder.'));
91 | $this->response->redirect($this->helper->url->to('BoardViewController', 'show', ['project_id' => $project['id']]), true);
92 | return;
93 | }
94 | }
95 |
96 | $this->flash->success(t('Task created successfully.'));
97 | $this->afterSave($project, $values, $task_id);
98 | }
99 | }
100 |
101 | /**
102 | * Duplicate created tasks to multiple projects
103 | *
104 | * @throws PageNotFoundException
105 | */
106 | public function duplicateProjects()
107 | {
108 | $project = $this->getProject();
109 | $values = $this->request->getValues();
110 |
111 | if (isset($values['project_ids'])) {
112 | foreach ($values['project_ids'] as $project_id) {
113 | $this->taskProjectDuplicationModel->duplicateToProject($values['task_id'], $project_id);
114 | }
115 | }
116 |
117 | $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true);
118 | }
119 |
120 | /**
121 | * Executed after the task is saved
122 | *
123 | * @param array $project
124 | * @param array $values
125 | * @param integer $task_id
126 | */
127 | protected function afterSave(array $project, array &$values, $task_id)
128 | {
129 | if (isset($values['duplicate_multiple_projects']) && $values['duplicate_multiple_projects'] == 1) {
130 | $this->chooseProjects($project, $task_id);
131 | } elseif (isset($values['another_task']) && $values['another_task'] == 1) {
132 | $this->show(array(
133 | 'owner_id' => $values['owner_id'],
134 | 'color_id' => $values['color_id'],
135 | 'category_id' => isset($values['category_id']) ? $values['category_id'] : 0,
136 | 'column_id' => $values['column_id'],
137 | 'swimlane_id' => isset($values['swimlane_id']) ? $values['swimlane_id'] : 0,
138 | 'another_task' => 1,
139 | ));
140 | } else {
141 | $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true);
142 | }
143 | }
144 |
145 | /**
146 | * Prepare form values
147 | *
148 | * @access protected
149 | * @param bool $isPrivateProject
150 | * @param array $swimlanesList
151 | * @return array
152 | */
153 | protected function prepareValues($isPrivateProject, array $swimlanesList)
154 | {
155 | $values = array(
156 | 'swimlane_id' => $this->request->getIntegerParam('swimlane_id', key($swimlanesList)),
157 | 'column_id' => $this->request->getIntegerParam('column_id'),
158 | 'color_id' => $this->colorModel->getDefaultColor(),
159 | );
160 |
161 | if ($isPrivateProject) {
162 | $values['owner_id'] = $this->userSession->getId();
163 | }
164 |
165 | return $values;
166 | }
167 |
168 | /**
169 | * Choose projects
170 | *
171 | * @param array $project
172 | * @param integer $task_id
173 | */
174 | protected function chooseProjects(array $project, $task_id)
175 | {
176 | $task = $this->taskFinderModel->getById($task_id);
177 | $projects = $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId());
178 | unset($projects[$project['id']]);
179 |
180 | $this->response->html($this->template->render('task_creation/duplicate_projects', array(
181 | 'project' => $project,
182 | 'task' => $task,
183 | 'projects_list' => $projects,
184 | 'values' => array('task_id' => $task['id'])
185 | )));
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.3';
240 | }
241 | public function getPluginHomepage()
242 | {
243 | return 'https://github.com/creecros/group_assign';
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/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 = '