PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.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 1.41 All 42 releases
fluent-boards / app / Http / Controllers / PublicBoardController.php

PublicBoardController.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 2.1.0, at app/Http/Controllers/PublicBoardController.php

275 lines 8.8 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\Http\Controllers;
4
5 use FluentBoards\App\Models\Board;
6 use FluentBoards\App\Models\Stage;
7 use FluentBoards\App\Models\Task;
8 use FluentBoards\App\Services\DescriptionMarkdownConverter;
9 use FluentBoards\App\Services\PublicAccessService;
10 use FluentBoards\Framework\Http\Request\Request;
11
12 class PublicBoardController extends Controller
13 {
14 public function find($board_id)
15 {
16 $board_id = absint($board_id);
17 $board = Board::findOrFail($board_id);
18
19 $board->background = maybe_unserialize($board->background);
20 $board->description = DescriptionMarkdownConverter::normalize($board->description);
21 $board->createdOn = $board->created_at ? $board->created_at->format('Y-m-d') : null;
22 $board->load(['stages', 'labels', 'users']);
23
24 // Members must never be serialized from the User relation; replace it with the reduced list.
25 PublicAccessService::replaceUserRelation($board, 'users');
26 $board->isUserOnlyViewer = true;
27 $board->is_pinned = false;
28
29 $board->makeHidden([
30 'settings', 'currency', 'crm_contact_id',
31 'updated_at', 'created_by',
32 ]);
33
34 PublicAccessService::stripUserRelations($board);
35
36 return [
37 'board' => $board
38 ];
39 }
40
41 public function getTasksByBoard($board_id)
42 {
43 $board_id = absint($board_id);
44 Board::findOrFail($board_id);
45
46 $stageIds = $this->getStageIdsByBoard($board_id);
47
48 $tasks = Task::with(['assignees', 'labels'])
49 ->where('board_id', $board_id)
50 ->whereNull('archived_at')
51 ->whereNull('parent_id')
52 ->whereIn('stage_id', $stageIds)
53 ->orderBy('due_at', 'ASC')
54 ->get();
55
56 $this->sanitizeTasks($tasks);
57
58 return [
59 'tasks' => $tasks,
60 ];
61 }
62
63 public function getTasksByBoardStage($board_id)
64 {
65 $board_id = absint($board_id);
66 Board::findOrFail($board_id);
67
68 $tasks = [];
69 $stageIds = $this->getStageIdsByBoard($board_id);
70 $stageTaskCounts = $this->getStageTaskCounts($board_id, $stageIds);
71 $paginationByStage = [];
72
73 foreach ($stageIds as $stageId) {
74 $stageTasks = Task::with(['assignees', 'labels'])
75 ->where('board_id', $board_id)
76 ->where('stage_id', $stageId)
77 ->whereNull('archived_at')
78 ->whereNull('parent_id')
79 ->orderBy('position', 'ASC')
80 ->limit(20)
81 ->get();
82
83 $this->sanitizeTasks($stageTasks);
84 $tasks = array_merge($tasks, $stageTasks->toArray());
85
86 $startCursor = $stageTasks->count() ? (float) $stageTasks->first()->position : null;
87 $endCursor = $stageTasks->count() ? (float) $stageTasks->last()->position : null;
88 $loadedCount = $stageTasks->count();
89 $hasMoreAfter = (int) ($stageTaskCounts[$stageId] ?? 0) > $loadedCount;
90
91 $paginationByStage[$stageId] = [
92 'stage_id' => (int) $stageId,
93 'total_count' => (int) ($stageTaskCounts[$stageId] ?? 0),
94 'limit' => 20,
95 'direction' => 'next',
96 'cursor' => null,
97 'has_more' => $hasMoreAfter,
98 'has_more_before' => false,
99 'has_more_after' => $hasMoreAfter,
100 'start_cursor' => $startCursor,
101 'end_cursor' => $endCursor,
102 ];
103 }
104
105 return [
106 'tasks' => $tasks,
107 'pagination_by_stage' => $paginationByStage
108 ];
109 }
110
111 public function getStageTasksPage(Request $request, $board_id)
112 {
113 $board_id = absint($board_id);
114 Board::findOrFail($board_id);
115
116 $stageId = $request->getSafe('stage_id', 'intval');
117 $limit = $request->getSafe('limit', 'intval', 20);
118 $direction = $request->getSafe('direction', 'sanitize_text_field', 'next');
119 $cursor = $request->getSafe('cursor', 'floatval');
120
121 if (!$stageId) {
122 return $this->sendError(esc_html__('Invalid Stage', 'fluent-boards'), 400);
123 }
124
125 if (!in_array($direction, ['next', 'prev'], true)) {
126 return $this->sendError(esc_html__('Invalid direction', 'fluent-boards'), 400);
127 }
128
129 $limit = max(1, min(100, $limit));
130
131 $stage = Stage::where('board_id', $board_id)
132 ->where('id', $stageId)
133 ->whereNull('archived_at')
134 ->first();
135
136 if (!$stage) {
137 return $this->sendError(esc_html__('Stage not found', 'fluent-boards'), 404);
138 }
139
140 $stageTasksQuery = $this->makeStageTasksQuery($board_id, $stageId);
141
142 if ($cursor !== null) {
143 if ($direction === 'prev') {
144 $stageTasksQuery->where('position', '<', $cursor);
145 } else {
146 $stageTasksQuery->where('position', '>', $cursor);
147 }
148 }
149
150 $stageTasks = $stageTasksQuery
151 ->orderBy('position', $direction === 'prev' ? 'DESC' : 'ASC')
152 ->limit($limit + 1)
153 ->get();
154
155 $hasMoreInDirection = $stageTasks->count() > $limit;
156 if ($hasMoreInDirection) {
157 $stageTasks = $stageTasks->slice(0, $limit)->values();
158 }
159
160 if ($direction === 'prev') {
161 $stageTasks = $stageTasks->sortBy('position')->values();
162 }
163
164 $this->sanitizeTasks($stageTasks);
165
166 $startCursor = $stageTasks->count() ? (float) $stageTasks->first()->position : null;
167 $endCursor = $stageTasks->count() ? (float) $stageTasks->last()->position : null;
168
169 $hasMoreBefore = false;
170 $hasMoreAfter = false;
171
172 if ($startCursor !== null) {
173 $hasMoreBefore = $this->makeStageTasksQuery($board_id, $stageId)
174 ->where('position', '<', $startCursor)
175 ->exists();
176 }
177
178 if ($endCursor !== null) {
179 $hasMoreAfter = $this->makeStageTasksQuery($board_id, $stageId)
180 ->where('position', '>', $endCursor)
181 ->exists();
182 }
183
184 return [
185 'tasks' => $stageTasks,
186 'pagination' => [
187 'stage_id' => (int) $stageId,
188 'limit' => (int) $limit,
189 'direction' => $direction,
190 'cursor' => $cursor !== null ? (float) $cursor : null,
191 'has_more' => $hasMoreInDirection,
192 'has_more_before' => $hasMoreBefore,
193 'has_more_after' => $hasMoreAfter,
194 'start_cursor' => $startCursor,
195 'end_cursor' => $endCursor,
196 ],
197 ];
198 }
199
200 private function getStageIdsByBoard($board_id)
201 {
202 return Stage::where('board_id', $board_id)
203 ->whereNull('archived_at')
204 ->pluck('id')
205 ->toArray();
206 }
207
208 private function getStageTaskCounts($board_id, array $stageIds)
209 {
210 if (!$stageIds) {
211 return [];
212 }
213
214 return Task::query()
215 ->selectRaw('stage_id, COUNT(*) as total_count')
216 ->where('board_id', $board_id)
217 ->whereNull('archived_at')
218 ->whereNull('parent_id')
219 ->whereIn('stage_id', $stageIds)
220 ->groupBy('stage_id')
221 ->pluck('total_count', 'stage_id')
222 ->map(function ($count) {
223 return (int) $count;
224 })
225 ->toArray();
226 }
227
228 private function makeStageTasksQuery($board_id, $stageId)
229 {
230 return Task::query()
231 ->with(['assignees', 'labels'])
232 ->where('board_id', $board_id)
233 ->where('stage_id', $stageId)
234 ->whereNull('archived_at')
235 ->whereNull('parent_id');
236 }
237
238 private function sanitizeTasks($tasks)
239 {
240 $sensitiveFields = [
241 'description',
242 'crm_contact_id',
243 'lead_value',
244 'created_by',
245 'source',
246 'source_id',
247 'settings',
248 'reminder_type',
249 'remind_at',
250 'slug',
251 'events',
252 'started_at',
253 'last_completed_at',
254 'comments_count',
255 'archived_at',
256 'parent_id',
257 'type',
258 ];
259
260 foreach ($tasks as $task) {
261 $task->setAppends([]);
262 $task->makeHidden($sensitiveFields);
263 $task->isOverdue = $task->isOverdue();
264 $task->isUpcoming = $task->upcoming();
265 $task->is_watching = false;
266 $task->contact = null;
267 $task->notifications = [];
268 $task->watchers = [];
269 // Assignees must never be serialized from the User relation; replace it with the reduced list.
270 PublicAccessService::replaceUserRelation($task, 'assignees');
271 PublicAccessService::stripUserRelations($task);
272 }
273 }
274 }
275