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 +168 -53 1.22trunk 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,32 +75,57 @@
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 130 return $this->sendSuccess([
105 131 'message' => __('Comment has been added', 'fluent-boards'),
@@ -105,19 +131,16 @@
105 131 'message' => __('Comment has been added', 'fluent-boards'),
106 132 'comment' => $comment
107 133 ], 201);
108 134 } catch (\Exception $e) {
109 - return $this->sendError($e->getMessage(), 404);
135 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 400);
110 136 }
111 137 }
112 138
113 139 public function update(Request $request, $board_id, $comment_id)
114 140 {
115 -// $commentData = $this->commentSanitizeAndValidate($request->all(), [
116 -// 'description' => 'required|string',
117 -// ]);
118 141 $requestData = [
119 - 'description' => $request->comment
142 + 'description' => $request->getSafe('comment', 'sanitize_textarea_field')
120 143 ];
121 144
122 145 $validationRules = [
123 146 'description' => 'required|string'
@@ -122,9 +145,11 @@
122 145 $validationRules = [
123 146 'description' => 'required|string'
124 147 ];
125 148
126 - if ($request->images) {
149 + $hasImagesParam = $this->requestHasImagesArray($request);
150 + $imageIds = $this->getImageIdsFromRequest($request);
151 + if ($hasImagesParam) {
127 152 $validationRules['description'] = 'nullable|string';
128 153 }
129 154
130 155 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
@@ -129,39 +154,51 @@
129 154
130 155 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
131 156
132 157 try {
133 - $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 + }
134 166
135 - if($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);
174 + }
175 +
176 + if(!empty($mentionData))
136 177 {
137 - $this->notificationService->mentionInComment($comment, $request->mentionData);
178 + $this->notificationService->mentionInComment($comment, $mentionData);
138 179 }
139 180
140 - if($request->images)
141 - {
142 - $this->commentService->attachCommentImages($comment, $request->images);
181 + if ($hasImagesParam) {
182 + $this->commentService->attachCommentImages($comment, $imageIds);
143 183 $comment->load(['images']);
144 184 }
145 185
146 - if ( !$comment ) {
147 - $errorMessage = __('Unauthorized Action', 'fluent-boards');
148 - return $this->sendError($errorMessage, 401);
149 - }
186 + $comment->load('user');
150 187
151 188 return $this->sendSuccess([
152 - 'description' => $comment->description,
189 + 'comment' => $comment,
153 190 'message' => __('Comment has been updated', 'fluent-boards'),
154 191 ], 200);
155 192 } catch (\Exception $e) {
156 - return $this->sendError($e->getMessage(), 404);
193 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
157 194 }
158 195 }
159 196
160 - public function delete($board_id, $comment_id)
197 + public function deleteComment($board_id, $comment_id)
161 198 {
162 199 try {
163 - $this->commentService->delete($comment_id);
200 + $this->commentService->delete($comment_id, $board_id);
164 201
165 202 return $this->sendSuccess([
166 203 'message' => __('Comment has been deleted', 'fluent-boards'),
167 204 ], 200);
@@ -172,9 +209,9 @@
172 209
173 210 public function updateReply(Request $request, $board_id, $reply_id)
174 211 {
175 212 $requestData = [
176 - 'description' => $request->comment
213 + 'description' => $request->getSafe('comment', 'sanitize_textarea_field')
177 214 ];
178 215
179 216 $validationRules = [
180 217 'description' => 'required|string'
@@ -182,9 +219,11 @@
182 219
183 220 $replyData = $this->commentSanitizeAndValidate($requestData, $validationRules);
184 221
185 222 try {
186 - $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);
187 226
188 227 if (!$reply) {
189 228 $errorMessage = __('Unauthorized Action', 'fluent-boards');
190 229 return $this->sendError($errorMessage, 401);
@@ -194,9 +233,9 @@
194 233 'description' => $reply->description,
195 234 'message' => __('Reply has been updated', 'fluent-boards'),
196 235 ], 200);
197 236 } catch (\Exception $e) {
198 - return $this->sendError($e->getMessage(), 404);
237 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
199 238 }
200 239 }
201 240
202 241 public function deleteReply($board_id, $reply_id)
@@ -201,9 +240,9 @@
201 240
202 241 public function deleteReply($board_id, $reply_id)
203 242 {
204 243 try {
205 - $this->commentService->deleteReply($reply_id);
244 + $this->commentService->deleteReply($reply_id, $board_id);
206 245
207 246 return $this->sendSuccess([
208 247 'message' => __('Reply has been deleted', 'fluent-boards'),
209 248 ], 200);
@@ -211,17 +250,47 @@
211 250 return $this->sendError($e->getMessage(), 404);
212 251 }
213 252 }
214 253
215 - public function sendMailAfterComment($commentId, $usersToSendEmail)
254 + public function sendMailAfterComment($commentId, $recipientUserIds)
216 255 {
217 256 $current_user_id = get_current_user_id();
218 257
219 258 /* this will run in background as soon as possible */
220 259 /* sending Model or Model Instance won't work here */
221 - 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');
222 261 }
223 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 +
224 293 private function commentSanitizeAndValidate($data, array $rules = [])
225 294 {
226 295 $data = Helper::sanitizeComment($data);
227 296
@@ -247,13 +316,22 @@
247 316 ], [
248 317 'file.mimetypes' => __('The file must be a image type.', 'fluent-boards')
249 318 ]);
250 319
320 + (new \FluentBoards\App\Services\TaskService())->findTaskOnBoard($task_id, $board_id);
321 +
251 322 $uploadInfo = UploadService::handleFileUpload( $files, $board_id);
252 323
253 324 $imageData = $uploadInfo[0];
254 -// $attachmentService = new AttachmentService();
255 - $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);
256 334
257 335 return $this->sendSuccess([
258 336 'message' => __('attachment has been added', 'fluent-boards'),
259 337 'imageAttachment' => $attachment
@@ -258,6 +336,43 @@
258 336 'message' => __('attachment has been added', 'fluent-boards'),
259 337 'imageAttachment' => $attachment
260 338 ], 200);
261 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'));
262 377 }
263 378 }