PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.95.2
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.95.2
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 1.41 All 42 releases
fluent-boards / app / Modules / MCP / Helpers / MCPHelper.php

MCPHelper.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.95.2, at app/Modules/MCP/Helpers/MCPHelper.php

360 lines 12.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App\Modules\MCP\Helpers;
4
5 use FluentBoards\App\Models\Board;
6 use FluentBoards\App\Models\Stage;
7 use FluentBoards\App\Models\Task;
8 use FluentBoards\App\Services\Constant;
9 use FluentBoards\App\Services\Helper;
10 use FluentBoards\App\Services\PermissionManager;
11
12 /**
13 * Shared utilities for Fluent Boards MCP tools.
14 */
15 class MCPHelper
16 {
17 const TASK_HISTORY_LIMIT = 20;
18
19 public static function error($code, $message, $data = [])
20 {
21 return new \WP_Error($code, $message, $data);
22 }
23
24 public static function normalizePagination($params, $defaultPerPage = 20, $maxPerPage = 100)
25 {
26 $page = isset($params['page']) ? absint($params['page']) : 1;
27 $perPage = isset($params['per_page']) ? absint($params['per_page']) : $defaultPerPage;
28
29 return [
30 'page' => max(1, $page),
31 'per_page' => max(1, min($maxPerPage, $perPage)),
32 ];
33 }
34
35 public static function resolveBoard($params)
36 {
37 $boardId = isset($params['board_id']) ? absint($params['board_id']) : 0;
38 if (!$boardId) {
39 return self::error('invalid_param', __('Provide board_id', 'fluent-boards'));
40 }
41
42 $board = Board::with(['stages', 'labels', 'users'])->find($boardId);
43 if (!$board) {
44 return self::error('not_found', __('Board not found', 'fluent-boards'), ['board_id' => $boardId]);
45 }
46
47 return $board;
48 }
49
50 public static function resolveTask($params)
51 {
52 $taskId = isset($params['task_id']) ? absint($params['task_id']) : 0;
53 $boardId = isset($params['board_id']) ? absint($params['board_id']) : 0;
54
55 if (!$taskId || !$boardId) {
56 return self::error('invalid_param', __('Provide board_id and task_id', 'fluent-boards'));
57 }
58
59 $task = Task::with(self::taskDetailRelations())->find($taskId);
60 if (!$task || (int) $task->board_id !== $boardId) {
61 return self::error('not_found', __('Task not found on this board', 'fluent-boards'), [
62 'board_id' => $boardId,
63 'task_id' => $taskId,
64 ]);
65 }
66
67 return $task;
68 }
69
70 public static function loadTaskDetails($task)
71 {
72 return $task->load(self::taskDetailRelations());
73 }
74
75 public static function taskDetailRelations()
76 {
77 return [
78 'board',
79 'stage',
80 'labels',
81 'assignees',
82 'watchers',
83 'comments' => function ($query) {
84 $query->without(['images', 'replies'])
85 ->orderBy('id', 'DESC')
86 ->limit(self::TASK_HISTORY_LIMIT);
87 },
88 'activities' => function ($query) {
89 $query->limit(self::TASK_HISTORY_LIMIT);
90 },
91 ];
92 }
93
94 public static function canReadBoard($boardId)
95 {
96 return PermissionManager::userHasBoardPermission(absint($boardId), 'GET');
97 }
98
99 public static function canWriteBoard($boardId)
100 {
101 return PermissionManager::userHasBoardPermission(absint($boardId), 'POST');
102 }
103
104 public static function assertStageBelongsToBoard($stageId, $boardId)
105 {
106 $stage = Stage::where('id', absint($stageId))
107 ->where('board_id', absint($boardId))
108 ->whereNull('archived_at')
109 ->first();
110
111 if (!$stage) {
112 return self::error('not_found', __('Stage not found on this board', 'fluent-boards'), [
113 'board_id' => absint($boardId),
114 'stage_id' => absint($stageId),
115 ]);
116 }
117
118 return $stage;
119 }
120
121 public static function formatBoardSummary($board)
122 {
123 return [
124 'id' => (int) $board->id,
125 'title' => $board->title,
126 'description' => $board->description,
127 'type' => $board->type,
128 'currency' => $board->currency,
129 'created_by' => (int) $board->created_by,
130 'archived_at' => self::toIso8601($board->archived_at),
131 'created_at' => self::toIso8601($board->created_at),
132 'updated_at' => self::toIso8601($board->updated_at),
133 'stages_count' => isset($board->stages) ? count($board->stages) : 0,
134 'labels_count' => isset($board->labels) ? count($board->labels) : 0,
135 'members_count' => isset($board->users) ? count($board->users) : 0,
136 ];
137 }
138
139 public static function formatBoard($board, $includeTasks = false)
140 {
141 $data = self::formatBoardSummary($board);
142 $data['stages'] = self::formatStageList($board->stages ?? []);
143 $data['labels'] = self::formatLabelList($board->labels ?? []);
144 $data['members'] = self::formatUserList($board->users ?? []);
145
146 if ($includeTasks) {
147 $tasks = Task::with(['stage', 'labels', 'assignees'])
148 ->where('board_id', $board->id)
149 ->whereNull('parent_id')
150 ->whereNull('archived_at')
151 ->orderBy('stage_id', 'asc')
152 ->orderBy('position', 'asc')
153 ->limit(100)
154 ->get();
155 $data['tasks'] = self::formatTaskList($tasks);
156 $data['tasks_limited_to'] = 100;
157 }
158
159 return $data;
160 }
161
162 public static function formatTaskSummary($task)
163 {
164 return [
165 'id' => (int) $task->id,
166 'title' => $task->title,
167 'slug' => $task->slug,
168 'board_id' => (int) $task->board_id,
169 'stage_id' => (int) $task->stage_id,
170 'parent_id' => $task->parent_id ? (int) $task->parent_id : null,
171 'status' => $task->status,
172 'priority' => $task->priority,
173 'position' => isset($task->position) ? (float) $task->position : null,
174 'crm_contact_id' => $task->crm_contact_id ? (int) $task->crm_contact_id : null,
175 'comments_count' => isset($task->comments_count) ? (int) $task->comments_count : 0,
176 'due_at' => self::toIso8601($task->due_at),
177 'started_at' => self::toIso8601($task->started_at),
178 'last_completed_at' => self::toIso8601($task->last_completed_at),
179 'archived_at' => self::toIso8601($task->archived_at),
180 'created_at' => self::toIso8601($task->created_at),
181 'updated_at' => self::toIso8601($task->updated_at),
182 'stage' => $task->stage ? self::formatStage($task->stage) : null,
183 'labels' => self::formatLabelList($task->labels ?? []),
184 'assignees' => self::formatUserList($task->assignees ?? []),
185 ];
186 }
187
188 public static function formatTask($task)
189 {
190 $data = self::formatTaskSummary($task);
191 $data['description'] = $task->description;
192 $data['settings'] = $task->settings;
193 $data['board'] = $task->board ? self::formatBoardSummary($task->board) : null;
194 $data['watchers'] = self::formatUserList($task->watchers ?? []);
195 $data['comments'] = self::formatCommentList(self::limitItems($task->comments ?? [], self::TASK_HISTORY_LIMIT));
196 $data['comments_limited_to'] = self::TASK_HISTORY_LIMIT;
197 $data['activities'] = self::formatActivityList(self::limitItems($task->activities ?? [], self::TASK_HISTORY_LIMIT));
198 $data['activities_limited_to'] = self::TASK_HISTORY_LIMIT;
199
200 return $data;
201 }
202
203 public static function formatTaskList($tasks)
204 {
205 $items = [];
206 foreach ($tasks as $task) {
207 $items[] = self::formatTaskSummary($task);
208 }
209 return $items;
210 }
211
212 public static function formatStage($stage)
213 {
214 return [
215 'id' => (int) $stage->id,
216 'board_id' => (int) $stage->board_id,
217 'title' => $stage->title,
218 'slug' => $stage->slug,
219 'position' => isset($stage->position) ? (float) $stage->position : null,
220 'default_task_status' => method_exists($stage, 'defaultTaskStatus') ? $stage->defaultTaskStatus() : 'open',
221 'archived_at' => self::toIso8601($stage->archived_at),
222 ];
223 }
224
225 public static function formatStageList($stages)
226 {
227 $items = [];
228 foreach ($stages as $stage) {
229 $items[] = self::formatStage($stage);
230 }
231 return $items;
232 }
233
234 public static function formatLabelList($labels)
235 {
236 $items = [];
237 foreach ($labels as $label) {
238 $items[] = [
239 'id' => (int) $label->id,
240 'board_id' => (int) $label->board_id,
241 'title' => $label->title,
242 'slug' => $label->slug,
243 'color' => $label->color,
244 'bg_color' => $label->bg_color,
245 'position' => isset($label->position) ? (float) $label->position : null,
246 'archived_at' => self::toIso8601($label->archived_at),
247 ];
248 }
249 return $items;
250 }
251
252 public static function formatUserList($users)
253 {
254 $items = [];
255 foreach ($users as $user) {
256 $name = trim((string) ($user->display_name ?? ''));
257 if (!$name) {
258 $name = trim((string) (($user->first_name ?? '') . ' ' . ($user->last_name ?? '')));
259 }
260
261 $items[] = [
262 'id' => isset($user->ID) ? (int) $user->ID : (int) ($user->id ?? 0),
263 'display_name' => $name,
264 'email' => $user->user_email ?? '',
265 'avatar' => !empty($user->user_email) ? fluent_boards_user_avatar($user->user_email, $name) : '',
266 ];
267 }
268 return $items;
269 }
270
271 public static function formatCommentList($comments)
272 {
273 $items = [];
274 foreach ($comments as $comment) {
275 $items[] = [
276 'id' => (int) $comment->id,
277 'task_id' => (int) $comment->task_id,
278 'parent_id' => $comment->parent_id ? (int) $comment->parent_id : null,
279 'type' => $comment->type,
280 'privacy' => $comment->privacy,
281 'status' => $comment->status,
282 'author_name' => $comment->author_name,
283 'description' => $comment->description,
284 'created_by' => $comment->created_by ? (int) $comment->created_by : null,
285 'created_at' => self::toIso8601($comment->created_at),
286 ];
287 }
288 return $items;
289 }
290
291 public static function formatActivityList($activities)
292 {
293 $items = [];
294 foreach ($activities as $activity) {
295 $items[] = [
296 'id' => (int) $activity->id,
297 'object_id' => isset($activity->object_id) ? (int) $activity->object_id : null,
298 'object_type' => $activity->object_type ?? '',
299 'action' => $activity->action ?? '',
300 'description' => $activity->description ?? '',
301 'created_by' => isset($activity->created_by) ? (int) $activity->created_by : null,
302 'created_at' => self::toIso8601($activity->created_at ?? null),
303 ];
304 }
305 return $items;
306 }
307
308 public static function sanitizeIdArray($values)
309 {
310 return array_values(array_filter(array_map('absint', (array) $values)));
311 }
312
313 private static function limitItems($items, $limit)
314 {
315 if (is_array($items)) {
316 return array_slice($items, 0, $limit);
317 }
318
319 $limited = [];
320 foreach ($items as $item) {
321 if (count($limited) >= $limit) {
322 break;
323 }
324
325 $limited[] = $item;
326 }
327
328 return $limited;
329 }
330
331 public static function currentUser()
332 {
333 $userId = get_current_user_id();
334 $user = $userId ? get_user_by('ID', $userId) : null;
335
336 return [
337 'wp_user_id' => (int) $userId,
338 'name' => $user ? $user->display_name : null,
339 'email' => $user ? $user->user_email : null,
340 'is_wp_admin' => $user ? user_can($user, 'manage_options') : false,
341 'is_fluent_boards_admin' => PermissionManager::isAdmin($userId),
342 'can_create_boards' => PermissionManager::userHasBoardCreationPermission($userId),
343 ];
344 }
345
346 public static function toIso8601($value)
347 {
348 if (!$value) {
349 return null;
350 }
351
352 $timestamp = strtotime((string) $value);
353 if (!$timestamp) {
354 return (string) $value;
355 }
356
357 return date('c', $timestamp);
358 }
359 }
360