PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.2.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.2.0
2.2.0 2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 All 43 releases
← All changes | app/Modules/MCP/Tools/TaskTools.php +66 -4 2.0.12 → 2.2.0 View file →
@@ -1,12 +1,15 @@
1 1 <?php
2 2
3 3 namespace FluentBoards\App\Modules\MCP\Tools;
4 4
5 +use FluentBoards\App\Models\Relation;
5 6 use FluentBoards\App\Models\Stage;
6 7 use FluentBoards\App\Models\Task;
7 8 use FluentBoards\App\Modules\MCP\Helpers\MCPHelper;
8 9 use FluentBoards\App\Services\Constant;
10 +use FluentBoards\App\Services\NotificationService;
11 +use FluentBoards\App\Services\PermissionManager;
9 12 use FluentBoards\App\Services\TaskService;
10 13
11 14 /**
12 15 * Task read/write MCP tools.
@@ -54,8 +57,10 @@
54 57 'due_date' => isset($params['due_date']) ? (array) $params['due_date'] : [],
55 58 ];
56 59
57 60 $paginated = (new TaskService())->getTableTasks($board->id, $args);
61 + // Every summary includes its stage; load once for the page instead of per task.
62 + $paginated->getCollection()->load('stage');
58 63
59 64 return [
60 65 'items' => MCPHelper::formatTaskList($paginated->items()),
61 66 'pagination' => [
@@ -152,8 +157,28 @@
152 157 if (!MCPHelper::canWriteBoard($task->board_id)) {
153 158 return MCPHelper::error('forbidden', __('You do not have permission to update this task', 'fluent-boards'));
154 159 }
155 160
161 + $assigneeIds = null;
162 + if (array_key_exists('assignees', $params)) {
163 + $assigneeIds = array_values(array_unique(MCPHelper::sanitizeIdArray($params['assignees'])));
164 + $task->load('assignees');
165 + $currentAssigneeIds = [];
166 + foreach ($task->assignees as $assignee) {
167 + $currentAssigneeIds[] = (int) $assignee->ID;
168 + }
169 +
170 + $nonMemberIds = self::findIneligibleAssigneeUserIds(
171 + array_diff($assigneeIds, $currentAssigneeIds),
172 + $task->board_id
173 + );
174 + if ($nonMemberIds) {
175 + return MCPHelper::error('forbidden', __('Some users are not members of this board', 'fluent-boards'), [
176 + 'non_board_member_user_ids' => $nonMemberIds,
177 + ]);
178 + }
179 + }
180 +
156 181 $service = new TaskService();
157 182 $updatable = [
158 183 'title' => 'text',
159 184 'description' => 'markdown',
@@ -178,10 +203,10 @@
178 203 $value = self::sanitizeUpdateValue($params[$field], $type);
179 204 $task = $service->updateTaskProperty($field, $value, $task);
180 205 }
181 206
182 - if (array_key_exists('assignees', $params)) {
183 - $task = self::syncAssignees($task, MCPHelper::sanitizeIdArray($params['assignees']));
207 + if ($assigneeIds !== null) {
208 + $task = self::syncAssignees($task, $assigneeIds);
184 209 }
185 210
186 211 MCPHelper::loadTaskDetails($task);
187 212
@@ -245,8 +270,11 @@
245 270 $task = $task->reopen();
246 271 }
247 272
248 273 do_action('fluent_boards/task_stage_updated', $task, $oldStageId);
274 +
275 + $recipients = (new NotificationService())->filterAssigneeToSendEmail($task->id, Constant::BOARD_EMAIL_STAGE_CHANGE);
276 + $service->sendMailAfterTaskModify('stage_change', $recipients, $task->id);
249 277 }
250 278
251 279 do_action('fluent_boards/task_updated', $task, 'position');
252 280 MCPHelper::loadTaskDetails($task);
@@ -279,9 +307,9 @@
279 307 ]);
280 308 }
281 309
282 310 $hasUserIds = array_key_exists('user_ids', $params);
283 - $userIds = MCPHelper::sanitizeIdArray($params['user_ids'] ?? []);
311 + $userIds = array_values(array_unique(MCPHelper::sanitizeIdArray($params['user_ids'] ?? [])));
284 312 if (!$hasUserIds && !empty($params['user_id'])) {
285 313 $userIds = [absint($params['user_id'])];
286 314 }
287 315
@@ -321,8 +349,15 @@
321 349 $toAdd = array_diff($userIds, $currentIds);
322 350 $toRemove = array_diff($currentIds, $userIds);
323 351 }
324 352
353 + $nonMemberIds = self::findIneligibleAssigneeUserIds($toAdd, $task->board_id);
354 + if ($nonMemberIds) {
355 + return MCPHelper::error('forbidden', __('Some users are not members of this board', 'fluent-boards'), [
356 + 'non_board_member_user_ids' => $nonMemberIds,
357 + ]);
358 + }
359 +
325 360 $service = new TaskService();
326 361
327 362 foreach (array_merge($toRemove, $toAdd) as $userId) {
328 363 $service->updateAssignee($userId, $task);
@@ -336,9 +371,9 @@
336 371 'board_id' => (int) $task->board_id,
337 372 'mode' => $mode,
338 373 'added' => array_values($toAdd),
339 374 'removed' => array_values($toRemove),
340 - 'assignees' => MCPHelper::formatUserList($task->assignees),
375 + 'assignees' => MCPHelper::formatUserList($task->assignees, $task->board_id),
341 376 'message' => __('Task assignees have been updated', 'fluent-boards'),
342 377 ];
343 378 }
344 379
@@ -378,8 +413,35 @@
378 413
379 414 $found = array_map('intval', (array) $found);
380 415
381 416 return array_values(array_diff($userIds, $found));
417 + }
418 +
419 + /**
420 + * @return array User ids that are neither board members nor global administrators.
421 + */
422 + private static function findIneligibleAssigneeUserIds($userIds, $boardId)
423 + {
424 + if (!$userIds) {
425 + return [];
426 + }
427 +
428 + $memberIds = Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
429 + ->where('object_id', (int) $boardId)
430 + ->whereIn('foreign_id', $userIds)
431 + ->pluck('foreign_id')
432 + ->toArray();
433 +
434 + $memberIds = array_map('intval', $memberIds);
435 +
436 + $ineligibleIds = [];
437 + foreach (array_diff($userIds, $memberIds) as $userId) {
438 + if (!PermissionManager::isAdmin($userId)) {
439 + $ineligibleIds[] = $userId;
440 + }
441 + }
442 +
443 + return $ineligibleIds;
382 444 }
383 445
384 446 private static function sanitizeUpdateValue($value, $type)
385 447 {