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