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 +99 -92 1.91.62.1.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,47 @@
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 {
286 247 $current_user_id = get_current_user_id();
287 248
288 249 /* this will run in background as soon as possible */
289 250 /* 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');
251 + as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_comment', [$commentId, $recipientUserIds, $current_user_id], 'fluent-boards');
291 252 }
292 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 +
293 284 private function commentSanitizeAndValidate($data, array $rules = [])
294 285 {
295 286 $data = Helper::sanitizeComment($data);
296 287
@@ -316,12 +307,14 @@
316 307 ], [
317 308 'file.mimetypes' => __('The file must be a image type.', 'fluent-boards')
318 309 ]);
319 310
311 + (new \FluentBoards\App\Services\TaskService())->findTaskOnBoard($task_id, $board_id);
312 +
320 313 $uploadInfo = UploadService::handleFileUpload( $files, $board_id);
321 314
322 315 $imageData = $uploadInfo[0];
323 - $attachment = $this->commentService->createCommentImage($imageData, $board_id);
316 + $attachment = $this->commentService->createCommentImage($imageData, $board_id, $task_id);
324 317 if(!!defined('FLUENT_BOARDS_PRO_VERSION')) {
325 318 $mediaData = (new AttachmentService())->processMediaData($imageData, $files['file']);
326 319 $attachment['driver'] = $mediaData['driver'];
327 320 $attachment['file_path'] = $mediaData['file_path'];
@@ -338,11 +331,9 @@
338 331 }
339 332
340 333 public function updateCommentPrivacy($board_id, $comment_id)
341 334 {
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();
335 + $comment = $this->commentService->findCommentOnBoard($comment_id, $board_id);
345 336
346 337 // Check if user has permission to update the comment
347 338 if ($comment->created_by != get_current_user_id()) {
348 339 return $this->sendError(__('Unauthorized Action', 'fluent-boards'), 401);
@@ -357,6 +348,22 @@
357 348 $privacy = $comment->privacy == 'public' ? __('public', 'fluent-boards') : __('private', 'fluent-boards'),
358 349 // translators: %s is the privacy setting (public or private)
359 350 'message' => sprintf(__('This comment is now %s', 'fluent-boards'), $privacy),
360 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'));
361 368 }
362 369 }