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