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.1 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 All 44 releases
← All changes | app/Http/Controllers/TaskController.php +50 -14 2.0.0 → 2.2.0 View file →
@@ -1,8 +1,9 @@
1 1 <?php
2 2
3 3 namespace FluentBoards\App\Http\Controllers;
4 4
5 +use FluentBoards\Framework\Database\Orm\ModelNotFoundException;
5 6 use FluentBoards\App\Models\Meta;
6 7 use FluentBoards\App\Models\Stage;
7 8 use FluentBoards\App\Models\Task;
8 9 use FluentBoards\App\Models\Board;
@@ -52,9 +53,9 @@
52 53 ? 0
53 54 : (int) Task::whereIn('id', $task_ids)
54 55 ->whereNull('archived_at')
55 56 ->whereNull('parent_id')
56 - ->excludeTemplateBoards()
57 + ->onActiveAvailableBoards()
57 58 ->count();
58 59
59 60 return [
60 61 'data' => $tasksArray,
@@ -61,10 +62,12 @@
61 62 'counts' => $taskCounts,
62 63 ];
63 64 }
64 65
66 + /** Return all board tasks with a cursor captured before reading their state. */
65 67 public function getTasksByBoard(Request $request, $board_id)
66 68 {
69 + $syncStartedAt = current_time('mysql');
67 70 $board_id = absint($board_id);
68 71 $board = Board::findOrFail($board_id);
69 72 $includeArchived = $request->getSafe('include_archived', 'boolval', false);
70 73
@@ -92,13 +95,16 @@
92 95 }
93 96
94 97 return [
95 98 'tasks' => $tasks,
99 + 'synced_at' => $syncStartedAt,
96 100 ];
97 101 }
98 102
103 + /** Fetch each stage's first page, then batch metadata for the combined card list. */
99 104 public function getTasksByBoardStage(Request $request, $board_id)
100 105 {
106 + $syncStartedAt = current_time('mysql');
101 107 $board_id = absint($board_id);
102 108 $board = Board::findOrFail($board_id);
103 109 $includeArchived = $request->getSafe('include_archived', 'boolval', false);
104 110
@@ -116,15 +122,9 @@
116 122 ->orderBy('position', 'ASC')
117 123 ->limit(20)
118 124 ->get();
119 125
120 - // Process each stage's tasks
121 - $this->processTasks($stageTasks, $board, [
122 - 'includeContact' => false,
123 - 'includeObserverState' => false,
124 - 'includeRoadmapPopularity' => false,
125 - ]);
126 - $tasks = array_merge($tasks, $stageTasks->toArray()); // Merge with the main task list
126 + $tasks = array_merge($tasks, $stageTasks->all());
127 127
128 128 $startCursor = $stageTasks->count() ? (float) $stageTasks->first()->position : null;
129 129 $endCursor = $stageTasks->count() ? (float) $stageTasks->last()->position : null;
130 130 $loadedCount = $stageTasks->count();
@@ -143,14 +143,22 @@
143 143 'end_cursor' => $endCursor,
144 144 ];
145 145 }
146 146
147 + $this->processTasks($tasks, $board, [
148 + 'includeContact' => false,
149 + 'includeObserverState' => false,
150 + 'includeRoadmapPopularity' => false,
151 + ]);
152 +
147 153 return [
148 - 'tasks' => $tasks,
154 + 'tasks' => (new Task())->newCollection($tasks)->toArray(),
149 155 'pagination_by_stage' => $paginationByStage,
156 + 'synced_at' => $syncStartedAt,
150 157 ];
151 158 }
152 159
160 + /** Return paginated tasks matching the board filters, including recurrence and templates. */
153 161 public function getTableTasks(Request $request, $board_id)
154 162 {
155 163 $board_id = absint($board_id);
156 164 $board = Board::findOrFail($board_id);
@@ -162,8 +170,10 @@
162 170 'search' => $request->getSafe('search', 'sanitize_text_field', ''),
163 171 'include_archived' => $request->getSafe('include_archived', 'boolval', false),
164 172 'stage' => $request->get('stage', []),
165 173 'task_status' => $request->get('task_status', []),
174 + 'recurring' => $request->get('recurring', []),
175 + 'template' => $request->get('template', []),
166 176 'priority' => $request->get('priority', []),
167 177 'assignee' => $request->get('assignee', []),
168 178 'labels' => $request->get('labels', []),
169 179 'watchers' => $request->get('watchers', []),
@@ -190,8 +200,9 @@
190 200 ],
191 201 ], 200);
192 202 }
193 203
204 + /** Return board tasks matching the requested filters, including recurrence and templates. */
194 205 public function getFilteredBoardTasks(Request $request, $board_id)
195 206 {
196 207 $board_id = absint($board_id);
197 208 $board = Board::findOrFail($board_id);
@@ -199,8 +210,10 @@
199 210 'search' => $request->getSafe('search', 'sanitize_text_field', ''),
200 211 'include_archived' => $request->getSafe('include_archived', 'boolval', false),
201 212 'stage' => $request->get('stage', []),
202 213 'task_status' => $request->get('task_status', []),
214 + 'recurring' => $request->get('recurring', []),
215 + 'template' => $request->get('template', []),
203 216 'priority' => $request->get('priority', []),
204 217 'assignee' => $request->get('assignee', []),
205 218 'labels' => $request->get('labels', []),
206 219 'watchers' => $request->get('watchers', []),
@@ -408,8 +421,10 @@
408 421 * @param array $options
409 422 */
410 423 private function processTasks($tasks, $board, $options = [])
411 424 {
425 + $this->taskService->loadTaskListMetadata($tasks);
426 +
412 427 $includeContact = Arr::get($options, 'includeContact', true);
413 428 $includeObserverState = Arr::get($options, 'includeObserverState', true);
414 429 $includeRoadmapPopularity = Arr::get($options, 'includeRoadmapPopularity', true);
415 430 $taskIds = [];
@@ -439,13 +454,32 @@
439 454 }
440 455 }
441 456 }
442 457
443 -
458 + /**
459 + * Create a task with field-specific sanitization for its request data.
460 + *
461 + * @param Request $request
462 + * @param int $board_id
463 + * @return mixed
464 + */
444 465 public function create(Request $request, $board_id)
445 466 {
446 467 $board_id = absint($board_id);
447 - $taskData = $this->taskSanitizeAndValidate($request->getSafe('task'), [
468 + $safeTaskData = $request->getSafe('task');
469 + $rawTaskData = $request->get('task', []);
470 +
471 + // Milkdown serializes pasted URLs as <https://...>, which generic text
472 + // sanitization removes as a tag.
473 + if (
474 + is_array($safeTaskData) &&
475 + is_array($rawTaskData) &&
476 + array_key_exists('description', $rawTaskData)
477 + ) {
478 + $safeTaskData['description'] = fluent_boards_sanitize_description($rawTaskData['description']);
479 + }
480 +
481 + $taskData = $this->taskSanitizeAndValidate($safeTaskData, [
448 482 'title' => 'required|string',
449 483 'board_id' => 'required|numeric',
450 484 'stage_id' => 'required|numeric',
451 485 'priority' => 'nullable|string',
@@ -522,8 +556,10 @@
522 556 return [
523 557 'task' => $task
524 558 ];
525 559
560 + } catch (ModelNotFoundException $e) {
561 + throw $e;
526 562 } catch (\Exception $e ) {
527 563 return $this->sendError($e->getMessage(), 400);
528 564 }
529 565
@@ -735,9 +771,9 @@
735 771 $task_id = absint($task_id);
736 772 //Properties in col: settings, assignees,crm_contact_id, archived_at(AUTO_SET_TIMESTAMP) , status, title, description, priority, is_watching, is_template
737 773 $col = $request->getSafe('property', 'sanitize_text_field');
738 774 if ($col === 'description') {
739 - $value = $request->getSafe('value', 'wp_kses_post');
775 + $value = $request->getSafe('value', 'fluent_boards_sanitize_description');
740 776 } elseif ($col === 'settings' || $col === 'assignees') {
741 777 $value = $request->get('value');
742 778 if (is_array($value) && isset($value['cover']) && is_array($value['cover'])) {
743 779 if (isset($value['cover']['backgroundColor'])) {
@@ -1475,9 +1511,9 @@
1475 1511 {
1476 1512 $default_config = [
1477 1513 [
1478 1514 'name' => 'due_today',
1479 - 'label' => __('Today', 'fluent-boards'),
1515 + 'label' => __('Due Today', 'fluent-boards'),
1480 1516 'visible' => 'true',
1481 1517 'order' => 1
1482 1518 ],
1483 1519 [
@@ -1587,9 +1623,9 @@
1587 1623 }
1588 1624
1589 1625 // Always apply fresh translations based on tab name
1590 1626 $labelMap = [
1591 - 'due_today' => __('Today', 'fluent-boards'),
1627 + 'due_today' => __('Due Today', 'fluent-boards'),
1592 1628 'assigned' => __('Assigned', 'fluent-boards'),
1593 1629 'upcoming' => __('Upcoming', 'fluent-boards'),
1594 1630 'overdue' => __('Overdue', 'fluent-boards'),
1595 1631 'mentioned' => __('Mentioned', 'fluent-boards'),