PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / trunk
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration vtrunk
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 1.41 1.45 All 41 releases
← All changes | app/Modules/MCP/Tools/TaskTools.php +60 -4 2.0.10trunk View file →
@@ -1,12 +1,14 @@
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\PermissionManager;
9 11 use FluentBoards\App\Services\TaskService;
10 12
11 13 /**
12 14 * Task read/write MCP tools.
@@ -152,8 +154,28 @@
152 154 if (!MCPHelper::canWriteBoard($task->board_id)) {
153 155 return MCPHelper::error('forbidden', __('You do not have permission to update this task', 'fluent-boards'));
154 156 }
155 157
158 + $assigneeIds = null;
159 + if (array_key_exists('assignees', $params)) {
160 + $assigneeIds = array_values(array_unique(MCPHelper::sanitizeIdArray($params['assignees'])));
161 + $task->load('assignees');
162 + $currentAssigneeIds = [];
163 + foreach ($task->assignees as $assignee) {
164 + $currentAssigneeIds[] = (int) $assignee->ID;
165 + }
166 +
167 + $nonMemberIds = self::findIneligibleAssigneeUserIds(
168 + array_diff($assigneeIds, $currentAssigneeIds),
169 + $task->board_id
170 + );
171 + if ($nonMemberIds) {
172 + return MCPHelper::error('forbidden', __('Some users are not members of this board', 'fluent-boards'), [
173 + 'non_board_member_user_ids' => $nonMemberIds,
174 + ]);
175 + }
176 + }
177 +
156 178 $service = new TaskService();
157 179 $updatable = [
158 180 'title' => 'text',
159 181 'description' => 'markdown',
@@ -178,10 +200,10 @@
178 200 $value = self::sanitizeUpdateValue($params[$field], $type);
179 201 $task = $service->updateTaskProperty($field, $value, $task);
180 202 }
181 203
182 - if (array_key_exists('assignees', $params)) {
183 - $task = self::syncAssignees($task, MCPHelper::sanitizeIdArray($params['assignees']));
204 + if ($assigneeIds !== null) {
205 + $task = self::syncAssignees($task, $assigneeIds);
184 206 }
185 207
186 208 MCPHelper::loadTaskDetails($task);
187 209
@@ -279,9 +301,9 @@
279 301 ]);
280 302 }
281 303
282 304 $hasUserIds = array_key_exists('user_ids', $params);
283 - $userIds = MCPHelper::sanitizeIdArray($params['user_ids'] ?? []);
305 + $userIds = array_values(array_unique(MCPHelper::sanitizeIdArray($params['user_ids'] ?? [])));
284 306 if (!$hasUserIds && !empty($params['user_id'])) {
285 307 $userIds = [absint($params['user_id'])];
286 308 }
287 309
@@ -321,8 +343,15 @@
321 343 $toAdd = array_diff($userIds, $currentIds);
322 344 $toRemove = array_diff($currentIds, $userIds);
323 345 }
324 346
347 + $nonMemberIds = self::findIneligibleAssigneeUserIds($toAdd, $task->board_id);
348 + if ($nonMemberIds) {
349 + return MCPHelper::error('forbidden', __('Some users are not members of this board', 'fluent-boards'), [
350 + 'non_board_member_user_ids' => $nonMemberIds,
351 + ]);
352 + }
353 +
325 354 $service = new TaskService();
326 355
327 356 foreach (array_merge($toRemove, $toAdd) as $userId) {
328 357 $service->updateAssignee($userId, $task);
@@ -336,9 +365,9 @@
336 365 'board_id' => (int) $task->board_id,
337 366 'mode' => $mode,
338 367 'added' => array_values($toAdd),
339 368 'removed' => array_values($toRemove),
340 - 'assignees' => MCPHelper::formatUserList($task->assignees),
369 + 'assignees' => MCPHelper::formatUserList($task->assignees, $task->board_id),
341 370 'message' => __('Task assignees have been updated', 'fluent-boards'),
342 371 ];
343 372 }
344 373
@@ -378,8 +407,35 @@
378 407
379 408 $found = array_map('intval', (array) $found);
380 409
381 410 return array_values(array_diff($userIds, $found));
411 + }
412 +
413 + /**
414 + * @return array User ids that are neither board members nor global administrators.
415 + */
416 + private static function findIneligibleAssigneeUserIds($userIds, $boardId)
417 + {
418 + if (!$userIds) {
419 + return [];
420 + }
421 +
422 + $memberIds = Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
423 + ->where('object_id', (int) $boardId)
424 + ->whereIn('foreign_id', $userIds)
425 + ->pluck('foreign_id')
426 + ->toArray();
427 +
428 + $memberIds = array_map('intval', $memberIds);
429 +
430 + $ineligibleIds = [];
431 + foreach (array_diff($userIds, $memberIds) as $userId) {
432 + if (!PermissionManager::isAdmin($userId)) {
433 + $ineligibleIds[] = $userId;
434 + }
435 + }
436 +
437 + return $ineligibleIds;
382 438 }
383 439
384 440 private static function sanitizeUpdateValue($value, $type)
385 441 {