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
← All changes | app/Http/Controllers/CommentController.php +165 -54 1.30trunk View file →
@@ -2,11 +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 6 use FluentBoards\App\Services\NotificationService;
8 -use FluentBoards\App\Services\Constant;
9 7 use FluentBoards\App\Services\Helper;
10 8 use FluentBoards\App\Services\UploadService;
11 9 use FluentBoards\Framework\Http\Request\Request;
12 10 use FluentBoards\App\Services\CommentService;
@@ -26,13 +24,13 @@
26 24
27 25 public function getComments(Request $request, $board_id, $task_id)
28 26 {
29 27 try {
30 - $filter = $request->getSafe('filter');
28 + $filter = $request->getSafe('filter', 'sanitize_text_field');
31 29 $per_page = 10;
32 30
33 - $comments = $this->commentService->getComments($task_id, $per_page, $filter);
34 - $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);
35 33
36 34 return $this->sendSuccess([
37 35 'comments' => $comments,
38 36 'total' => $totalComments
@@ -51,13 +49,15 @@
51 49 public function create(Request $request, $board_id, $task_id)
52 50 {
53 51 // TODO: Refactor the whole request and sanitize process here.. minimize the code in this functions.
54 52 $requestData = [
55 - 'parent_id' => $request->parent_id,
56 - 'description' => $request->comment,
57 - 'created_by' => $request->comment_by,
58 - 'task_id' => $task_id,
59 - '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' => $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 60 'board_id' => (int) $board_id,
61 61 ];
62 62 $validationRules = [
63 63 'description' => 'required|string',
@@ -66,9 +66,10 @@
66 66 'task_id' => 'required|integer',
67 67 'type' => 'required|string'
68 68 ];
69 69
70 - if ($request->images) {
70 + $imageIds = $this->getImageIdsFromRequest($request);
71 + if ($imageIds) {
71 72 $validationRules['description'] = 'nullable|string';
72 73 }
73 74
74 75 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
@@ -74,52 +75,72 @@
74 75 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
75 76
76 77
77 78 try {
79 + if (!empty($imageIds)) {
80 + $this->commentService->assertCommentImagesAttachable($imageIds, $board_id, $task_id);
81 + }
82 +
78 83 $rawDescription = $commentData['description'];
79 - $commentData['settings'] = [ 'raw_description' => $rawDescription, 'mentioned_id' => $request->mentionData ];
80 - if($request->mentionData) {
81 - $commentData['description'] = $this->commentService->processMentionAndLink($commentData['description'], $request->mentionData);
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);
82 93 } else {
83 - $commentData['description'] = $this->commentService->checkIfCommentHaveLinks($commentData['description']);
94 + // Process links with UTF-8 support
95 + $commentData['description'] = $this->commentService->checkIfCommentHaveLinks($description);
84 96 }
85 97
86 - $comment = $this->commentService->create($commentData, $task_id);
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 + }
87 103 $comment['user'] = $comment->user;
88 - //sending emails to assignees who enabled their email
89 - $usersToSendEmail = $this->notificationService->filterAssigneeToSendEmail($task_id, Constant::BOARD_EMAIL_COMMENT);
90 104
91 - $this->sendMailAfterComment($comment->id, $usersToSendEmail);
92 -
93 - if($request->mentionData)
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))
94 121 {
95 - $this->notificationService->mentionInComment($comment, $request->mentionData);
122 + $this->notificationService->mentionInComment($comment, $mentionData);
96 123 }
97 124
98 - if($request->images)
125 + if ($comment->type == 'comment')
99 126 {
100 - $this->commentService->attachCommentImages($comment, $request->images);
101 - $comment->load(['images']);
127 + $comment->load('replies');
102 128 }
103 129
104 - $comment->load('replies');
105 -
106 130 return $this->sendSuccess([
107 131 'message' => __('Comment has been added', 'fluent-boards'),
108 132 'comment' => $comment
109 133 ], 201);
110 134 } catch (\Exception $e) {
111 - return $this->sendError($e->getMessage(), 404);
135 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 400);
112 136 }
113 137 }
114 138
115 139 public function update(Request $request, $board_id, $comment_id)
116 140 {
117 -// $commentData = $this->commentSanitizeAndValidate($request->all(), [
118 -// 'description' => 'required|string',
119 -// ]);
120 141 $requestData = [
121 - 'description' => $request->comment
142 + 'description' => $request->getSafe('comment', 'sanitize_textarea_field')
122 143 ];
123 144
124 145 $validationRules = [
125 146 'description' => 'required|string'
@@ -124,9 +145,11 @@
124 145 $validationRules = [
125 146 'description' => 'required|string'
126 147 ];
127 148
128 - if ($request->images) {
149 + $hasImagesParam = $this->requestHasImagesArray($request);
150 + $imageIds = $this->getImageIdsFromRequest($request);
151 + if ($hasImagesParam) {
129 152 $validationRules['description'] = 'nullable|string';
130 153 }
131 154
132 155 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
@@ -131,24 +154,34 @@
131 154
132 155 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
133 156
134 157 try {
135 - $comment = $this->commentService->update($commentData, $comment_id, $request->mentionData);
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 + }
136 166
137 - if($request->mentionData)
138 - {
139 - $this->notificationService->mentionInComment($comment, $request->mentionData);
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);
140 174 }
141 175
142 - if($request->images)
176 + if(!empty($mentionData))
143 177 {
144 - $this->commentService->attachCommentImages($comment, $request->images);
145 - $comment->load(['images']);
178 + $this->notificationService->mentionInComment($comment, $mentionData);
146 179 }
147 180
148 - if ( !$comment ) {
149 - $errorMessage = __('Unauthorized Action', 'fluent-boards');
150 - return $this->sendError($errorMessage, 401);
181 + if ($hasImagesParam) {
182 + $this->commentService->attachCommentImages($comment, $imageIds);
183 + $comment->load(['images']);
151 184 }
152 185
153 186 $comment->load('user');
154 187
@@ -156,16 +189,16 @@
156 189 'comment' => $comment,
157 190 'message' => __('Comment has been updated', 'fluent-boards'),
158 191 ], 200);
159 192 } catch (\Exception $e) {
160 - return $this->sendError($e->getMessage(), 404);
193 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
161 194 }
162 195 }
163 196
164 - public function delete($board_id, $comment_id)
197 + public function deleteComment($board_id, $comment_id)
165 198 {
166 199 try {
167 - $this->commentService->delete($comment_id);
200 + $this->commentService->delete($comment_id, $board_id);
168 201
169 202 return $this->sendSuccess([
170 203 'message' => __('Comment has been deleted', 'fluent-boards'),
171 204 ], 200);
@@ -176,9 +209,9 @@
176 209
177 210 public function updateReply(Request $request, $board_id, $reply_id)
178 211 {
179 212 $requestData = [
180 - 'description' => $request->comment
213 + 'description' => $request->getSafe('comment', 'sanitize_textarea_field')
181 214 ];
182 215
183 216 $validationRules = [
184 217 'description' => 'required|string'
@@ -186,9 +219,11 @@
186 219
187 220 $replyData = $this->commentSanitizeAndValidate($requestData, $validationRules);
188 221
189 222 try {
190 - $reply = $this->commentService->update($replyData, $reply_id, $request->mentionData);
223 + $mentionData = $this->getMentionData($request);
224 +
225 + $reply = $this->commentService->update($replyData, $reply_id, $mentionData, $board_id);
191 226
192 227 if (!$reply) {
193 228 $errorMessage = __('Unauthorized Action', 'fluent-boards');
194 229 return $this->sendError($errorMessage, 401);
@@ -198,9 +233,9 @@
198 233 'description' => $reply->description,
199 234 'message' => __('Reply has been updated', 'fluent-boards'),
200 235 ], 200);
201 236 } catch (\Exception $e) {
202 - return $this->sendError($e->getMessage(), 404);
237 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
203 238 }
204 239 }
205 240
206 241 public function deleteReply($board_id, $reply_id)
@@ -205,9 +240,9 @@
205 240
206 241 public function deleteReply($board_id, $reply_id)
207 242 {
208 243 try {
209 - $this->commentService->deleteReply($reply_id);
244 + $this->commentService->deleteReply($reply_id, $board_id);
210 245
211 246 return $this->sendSuccess([
212 247 'message' => __('Reply has been deleted', 'fluent-boards'),
213 248 ], 200);
@@ -215,17 +250,47 @@
215 250 return $this->sendError($e->getMessage(), 404);
216 251 }
217 252 }
218 253
219 - public function sendMailAfterComment($commentId, $usersToSendEmail)
254 + public function sendMailAfterComment($commentId, $recipientUserIds)
220 255 {
221 256 $current_user_id = get_current_user_id();
222 257
223 258 /* this will run in background as soon as possible */
224 259 /* sending Model or Model Instance won't work here */
225 - as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_comment', [$commentId, $usersToSendEmail, $current_user_id], 'fluent-boards');
260 + as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_comment', [$commentId, $recipientUserIds, $current_user_id], 'fluent-boards');
226 261 }
227 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 +
228 293 private function commentSanitizeAndValidate($data, array $rules = [])
229 294 {
230 295 $data = Helper::sanitizeComment($data);
231 296
@@ -251,13 +316,22 @@
251 316 ], [
252 317 'file.mimetypes' => __('The file must be a image type.', 'fluent-boards')
253 318 ]);
254 319
320 + (new \FluentBoards\App\Services\TaskService())->findTaskOnBoard($task_id, $board_id);
321 +
255 322 $uploadInfo = UploadService::handleFileUpload( $files, $board_id);
256 323
257 324 $imageData = $uploadInfo[0];
258 -// $attachmentService = new AttachmentService();
259 - $attachment = $this->commentService->createCommentImage($imageData, $board_id);
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);
260 334
261 335 return $this->sendSuccess([
262 336 'message' => __('attachment has been added', 'fluent-boards'),
263 337 'imageAttachment' => $attachment
@@ -262,6 +336,43 @@
262 336 'message' => __('attachment has been added', 'fluent-boards'),
263 337 'imageAttachment' => $attachment
264 338 ], 200);
265 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'));
266 377 }
267 378 }