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
fluent-boards / app / Http / Controllers / CommentController.php

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

379 lines 13.6 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\Comment;
6 use FluentBoards\App\Services\NotificationService;
7 use FluentBoards\App\Services\Helper;
8 use FluentBoards\App\Services\UploadService;
9 use FluentBoards\Framework\Http\Request\Request;
10 use FluentBoards\App\Services\CommentService;
11 use FluentBoardsPro\App\Services\AttachmentService;
12
13 class CommentController extends Controller
14 {
15 private $commentService;
16 private $notificationService;
17
18 public function __construct(CommentService $commentService, NotificationService $notificationService)
19 {
20 parent::__construct();
21 $this->commentService = $commentService;
22 $this->notificationService = $notificationService;
23 }
24
25 public function getComments(Request $request, $board_id, $task_id)
26 {
27 try {
28 $filter = $request->getSafe('filter', 'sanitize_text_field');
29 $per_page = 10;
30
31 $comments = $this->commentService->getComments($task_id, $per_page, $filter, $board_id);
32 $totalComments = $this->commentService->getTotal($task_id, $board_id);
33
34 return $this->sendSuccess([
35 'comments' => $comments,
36 'total' => $totalComments
37 ], 200);
38 } catch (\Exception $e) {
39 return $this->sendError($e->getMessage(), 404);
40 }
41 }
42
43 /*
44 * handles comment or reply creation
45 * @param $board_id int
46 * @param $task_id int
47 * @return json
48 */
49 public function create(Request $request, $board_id, $task_id)
50 {
51 // TODO: Refactor the whole request and sanitize process here.. minimize the code in this functions.
52 $requestData = [
53 'parent_id' => $request->getSafe('parent_id', function ($value) {
54 return (empty($value)) ? null : intval( $value);
55 }, null),
56 'description' => $request->getSafe('comment', 'sanitize_textarea_field'),
57 'created_by' => get_current_user_id(),
58 'task_id' => (int) $task_id,
59 'type' => $request->getSafe('comment_type', 'sanitize_text_field', 'comment'),
60 'board_id' => (int) $board_id,
61 ];
62 $validationRules = [
63 'description' => 'required|string',
64 'created_by' => 'required|integer',
65 'board_id' => 'required|integer',
66 'task_id' => 'required|integer',
67 'type' => 'required|string'
68 ];
69
70 $imageIds = $this->getImageIdsFromRequest($request);
71 if ($imageIds) {
72 $validationRules['description'] = 'nullable|string';
73 }
74
75 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
76
77
78 try {
79 if (!empty($imageIds)) {
80 $this->commentService->assertCommentImagesAttachable($imageIds, $board_id, $task_id);
81 }
82
83 $rawDescription = $commentData['description'];
84 $mentionData = $this->getMentionData($request, $board_id);
85 $commentData['settings'] = [ 'raw_description' => $rawDescription, 'mentioned_id' => $mentionData ];
86
87 // Ensure UTF-8 encoding for comment description
88 $description = mb_convert_encoding($commentData['description'], 'UTF-8', 'auto');
89
90 if(!empty($mentionData)) {
91 // Process mentions and links with UTF-8 support
92 $commentData['description'] = $this->commentService->processMentionAndLink($description, $mentionData);
93 } else {
94 // Process links with UTF-8 support
95 $commentData['description'] = $this->commentService->checkIfCommentHaveLinks($description);
96 }
97
98 $comment = $this->commentService->create($commentData, $task_id, $board_id);
99 if (!empty($imageIds)) {
100 $this->commentService->attachCommentImages($comment, $imageIds);
101 $comment->load(['images']);
102 }
103 $comment['user'] = $comment->user;
104
105 $recipientUserIds = [];
106 if ($comment->type == 'reply') {
107 $parentComment = Comment::findOrFail($comment->parent_id);
108 $commenterId = $parentComment->created_by;
109 if ($commenterId != get_current_user_id())
110 {
111 $recipientUserIds[] = absint($commenterId);
112 }
113 $this->sendMailAfterComment($comment->id, $recipientUserIds);
114 } else {
115 // Queue revocable IDs; the worker rechecks membership and preferences before sending.
116 $recipientUserIds = $this->notificationService->getCommentRecipientUserIds($task_id);
117 $this->sendMailAfterComment($comment->id, $recipientUserIds);
118 }
119
120 if(!empty($mentionData))
121 {
122 $this->notificationService->mentionInComment($comment, $mentionData);
123 }
124
125 if ($comment->type == 'comment')
126 {
127 $comment->load('replies');
128 }
129
130 return $this->sendSuccess([
131 'message' => __('Comment has been added', 'fluent-boards'),
132 'comment' => $comment
133 ], 201);
134 } catch (\Exception $e) {
135 return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 400);
136 }
137 }
138
139 public function update(Request $request, $board_id, $comment_id)
140 {
141 $requestData = [
142 'description' => $request->getSafe('comment', 'sanitize_textarea_field')
143 ];
144
145 $validationRules = [
146 'description' => 'required|string'
147 ];
148
149 $hasImagesParam = $this->requestHasImagesArray($request);
150 $imageIds = $this->getImageIdsFromRequest($request);
151 if ($hasImagesParam) {
152 $validationRules['description'] = 'nullable|string';
153 }
154
155 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
156
157 try {
158 if ($hasImagesParam) {
159 $commentForImages = $this->commentService->findCommentOnBoard($comment_id, $board_id);
160 if ($commentForImages->created_by != get_current_user_id()) {
161 $errorMessage = __('Unauthorized Action', 'fluent-boards');
162 return $this->sendError($errorMessage, 401);
163 }
164 $this->commentService->assertCommentImagesAttachableForComment($commentForImages, $imageIds);
165 }
166
167 $mentionData = $this->getMentionData($request);
168
169 $comment = $this->commentService->update($commentData, $comment_id, $mentionData, $board_id);
170
171 if (!$comment) {
172 $errorMessage = __('Unauthorized Action', 'fluent-boards');
173 return $this->sendError($errorMessage, 401);
174 }
175
176 if(!empty($mentionData))
177 {
178 $this->notificationService->mentionInComment($comment, $mentionData);
179 }
180
181 if ($hasImagesParam) {
182 $this->commentService->attachCommentImages($comment, $imageIds);
183 $comment->load(['images']);
184 }
185
186 $comment->load('user');
187
188 return $this->sendSuccess([
189 'comment' => $comment,
190 'message' => __('Comment has been updated', 'fluent-boards'),
191 ], 200);
192 } catch (\Exception $e) {
193 return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
194 }
195 }
196
197 public function deleteComment($board_id, $comment_id)
198 {
199 try {
200 $this->commentService->delete($comment_id, $board_id);
201
202 return $this->sendSuccess([
203 'message' => __('Comment has been deleted', 'fluent-boards'),
204 ], 200);
205 } catch (\Exception $e) {
206 return $this->sendError($e->getMessage(), 404);
207 }
208 }
209
210 public function updateReply(Request $request, $board_id, $reply_id)
211 {
212 $requestData = [
213 'description' => $request->getSafe('comment', 'sanitize_textarea_field')
214 ];
215
216 $validationRules = [
217 'description' => 'required|string'
218 ];
219
220 $replyData = $this->commentSanitizeAndValidate($requestData, $validationRules);
221
222 try {
223 $mentionData = $this->getMentionData($request);
224
225 $reply = $this->commentService->update($replyData, $reply_id, $mentionData, $board_id);
226
227 if (!$reply) {
228 $errorMessage = __('Unauthorized Action', 'fluent-boards');
229 return $this->sendError($errorMessage, 401);
230 }
231
232 return $this->sendSuccess([
233 'description' => $reply->description,
234 'message' => __('Reply has been updated', 'fluent-boards'),
235 ], 200);
236 } catch (\Exception $e) {
237 return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
238 }
239 }
240
241 public function deleteReply($board_id, $reply_id)
242 {
243 try {
244 $this->commentService->deleteReply($reply_id, $board_id);
245
246 return $this->sendSuccess([
247 'message' => __('Reply has been deleted', 'fluent-boards'),
248 ], 200);
249 } catch (\Exception $e) {
250 return $this->sendError($e->getMessage(), 404);
251 }
252 }
253
254 public function sendMailAfterComment($commentId, $recipientUserIds)
255 {
256 $current_user_id = get_current_user_id();
257
258 /* this will run in background as soon as possible */
259 /* sending Model or Model Instance won't work here */
260 as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_comment', [$commentId, $recipientUserIds, $current_user_id], 'fluent-boards');
261 }
262
263 /**
264 * Sanitize mention IDs and optionally verify board membership before a create.
265 *
266 * @param Request $request
267 * @param int $boardId
268 * @return array
269 * @throws \Exception
270 */
271 private function getMentionData(Request $request, $boardId = null)
272 {
273 $rawMentionData = $request->getSafe('mentionData');
274 if (!is_array($rawMentionData)) {
275 return [];
276 }
277
278 $mentionData = array_values(array_unique(array_filter(array_map('absint', $rawMentionData))));
279
280 if (!$boardId) {
281 return $mentionData;
282 }
283
284 $boardMemberIds = $this->notificationService->resolveBoardMentionUserIds($boardId, $mentionData);
285
286 if (array_diff($mentionData, $boardMemberIds)) {
287 throw new \Exception(esc_html__('One or more mentioned users are not members of this board', 'fluent-boards'), 403);
288 }
289
290 return $boardMemberIds;
291 }
292
293 private function commentSanitizeAndValidate($data, array $rules = [])
294 {
295 $data = Helper::sanitizeComment($data);
296
297 return $this->validate($data, $rules);
298 }
299
300 public function handleImageUpload(Request $request, $board_id, $task_id)
301 {
302 $allowedTypes = implode(',', [
303 "image/jpeg",
304 "image/gif",
305 "image/png",
306 "image/bmp",
307 "image/tiff",
308 "image/webp",
309 "image/avif",
310 "image/x-icon",
311 "image/heic",
312 ]);
313
314 $files = $this->validate($request->files(), [
315 'file' => 'mimetypes:' . $allowedTypes,
316 ], [
317 'file.mimetypes' => __('The file must be a image type.', 'fluent-boards')
318 ]);
319
320 (new \FluentBoards\App\Services\TaskService())->findTaskOnBoard($task_id, $board_id);
321
322 $uploadInfo = UploadService::handleFileUpload( $files, $board_id);
323
324 $imageData = $uploadInfo[0];
325 $attachment = $this->commentService->createCommentImage($imageData, $board_id, $task_id);
326 if(!!defined('FLUENT_BOARDS_PRO_VERSION')) {
327 $mediaData = (new AttachmentService())->processMediaData($imageData, $files['file']);
328 $attachment['driver'] = $mediaData['driver'];
329 $attachment['file_path'] = $mediaData['file_path'];
330 $attachment['full_url'] = $mediaData['full_url'];
331 $attachment->save();
332 }
333 $attachment->public_url = $this->commentService->createPublicUrl($attachment, $board_id);
334
335 return $this->sendSuccess([
336 'message' => __('attachment has been added', 'fluent-boards'),
337 'imageAttachment' => $attachment
338 ], 200);
339
340 }
341
342 public function updateCommentPrivacy($board_id, $comment_id)
343 {
344 $comment = $this->commentService->findCommentOnBoard($comment_id, $board_id);
345
346 // Check if user has permission to update the comment
347 if ($comment->created_by != get_current_user_id()) {
348 return $this->sendError(__('Unauthorized Action', 'fluent-boards'), 401);
349 }
350
351 // Toggle privacy
352 $comment->privacy = ($comment->privacy === 'public') ? 'private' : 'public';
353 $comment->save();
354
355 return $this->sendSuccess([
356 'comment' => $comment,
357 $privacy = $comment->privacy == 'public' ? __('public', 'fluent-boards') : __('private', 'fluent-boards'),
358 // translators: %s is the privacy setting (public or private)
359 'message' => sprintf(__('This comment is now %s', 'fluent-boards'), $privacy),
360 ], 200);
361 }
362
363 private function getImageIdsFromRequest(Request $request)
364 {
365 $images = $request->getSafe('images');
366
367 if (!$images || !is_array($images)) {
368 return [];
369 }
370
371 return array_values(array_filter(array_unique(array_map('intval', $images))));
372 }
373
374 private function requestHasImagesArray(Request $request)
375 {
376 return $request->exists('images') && is_array($request->getSafe('images'));
377 }
378 }
379