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 +138 -60 1.352.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;
@@ -27,13 +24,13 @@
27 24
28 25 public function getComments(Request $request, $board_id, $task_id)
29 26 {
30 27 try {
31 - $filter = $request->getSafe('filter');
28 + $filter = $request->getSafe('filter', 'sanitize_text_field');
32 29 $per_page = 10;
33 30
34 - $comments = $this->commentService->getComments($task_id, $per_page, $filter);
35 - $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);
36 33
37 34 return $this->sendSuccess([
38 35 'comments' => $comments,
39 36 'total' => $totalComments
@@ -52,13 +49,15 @@
52 49 public function create(Request $request, $board_id, $task_id)
53 50 {
54 51 // TODO: Refactor the whole request and sanitize process here.. minimize the code in this functions.
55 52 $requestData = [
56 - 'parent_id' => $request->parent_id,
57 - 'description' => $request->comment,
58 - 'created_by' => $request->comment_by,
59 - 'task_id' => $task_id,
60 - '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'),
61 60 'board_id' => (int) $board_id,
62 61 ];
63 62 $validationRules = [
64 63 'description' => 'required|string',
@@ -67,9 +66,10 @@
67 66 'task_id' => 'required|integer',
68 67 'type' => 'required|string'
69 68 ];
70 69
71 - if ($request->images) {
70 + $imageIds = $this->getImageIdsFromRequest($request);
71 + if ($imageIds) {
72 72 $validationRules['description'] = 'nullable|string';
73 73 }
74 74
75 75 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
@@ -75,47 +75,45 @@
75 75 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
76 76
77 77
78 78 try {
79 + if (!empty($imageIds)) {
80 + $this->commentService->assertCommentImagesAttachable($imageIds, $board_id, $task_id);
81 + }
82 +
79 83 $rawDescription = $commentData['description'];
80 - $commentData['settings'] = [ 'raw_description' => $rawDescription, 'mentioned_id' => $request->mentionData ];
81 - if($request->mentionData) {
82 - $commentData['description'] = $this->commentService->processMentionAndLink($commentData['description'], $request->mentionData);
83 - } else {
84 - $commentData['description'] = $this->commentService->checkIfCommentHaveLinks($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']);
85 93 }
86 -
87 - $comment = $this->commentService->create($commentData, $task_id);
88 94 $comment['user'] = $comment->user;
89 95
90 - $usersToSendEmail = [];
96 + $recipientUserIds = [];
91 97 if ($comment->type == 'reply') {
92 98 $parentComment = Comment::findOrFail($comment->parent_id);
93 99 $commenterId = $parentComment->created_by;
94 100 if ($commenterId != get_current_user_id())
95 101 {
96 - $commenter = User::select('user_email')->findOrFail($commenterId);
97 - $commenterEmail = $commenter->user_email;
98 - $usersToSendEmail[] = $commenterEmail;
102 + $recipientUserIds[] = absint($commenterId);
99 103 }
100 - $this->sendMailAfterComment($comment->id, $usersToSendEmail);
104 + $this->sendMailAfterComment($comment->id, $recipientUserIds);
101 105 } else {
102 - //sending emails to assignees who enabled their email
103 - $usersToSendEmail = $this->notificationService->filterAssigneeToSendEmail($task_id, Constant::BOARD_EMAIL_COMMENT);
104 - $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);
105 109 }
106 110
107 - if($request->mentionData)
111 + if(!empty($mentionData))
108 112 {
109 - $this->notificationService->mentionInComment($comment, $request->mentionData);
113 + $this->notificationService->mentionInComment($comment, $mentionData);
110 114 }
111 115
112 - if($request->images)
113 - {
114 - $this->commentService->attachCommentImages($comment, $request->images);
115 - $comment->load(['images']);
116 - }
117 -
118 116 if ($comment->type == 'comment')
119 117 {
120 118 $comment->load('replies');
121 119 }
@@ -124,19 +122,16 @@
124 122 'message' => __('Comment has been added', 'fluent-boards'),
125 123 'comment' => $comment
126 124 ], 201);
127 125 } catch (\Exception $e) {
128 - return $this->sendError($e->getMessage(), 404);
126 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 400);
129 127 }
130 128 }
131 129
132 130 public function update(Request $request, $board_id, $comment_id)
133 131 {
134 -// $commentData = $this->commentSanitizeAndValidate($request->all(), [
135 -// 'description' => 'required|string',
136 -// ]);
137 132 $requestData = [
138 - 'description' => $request->comment
133 + 'description' => $this->commentService->sanitizeContent($request->get('comment', ''))
139 134 ];
140 135
141 136 $validationRules = [
142 137 'description' => 'required|string'
@@ -141,9 +136,11 @@
141 136 $validationRules = [
142 137 'description' => 'required|string'
143 138 ];
144 139
145 - if ($request->images) {
140 + $hasImagesParam = $this->requestHasImagesArray($request);
141 + $imageIds = $this->getImageIdsFromRequest($request);
142 + if ($hasImagesParam) {
146 143 $validationRules['description'] = 'nullable|string';
147 144 }
148 145
149 146 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
@@ -148,24 +145,34 @@
148 145
149 146 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
150 147
151 148 try {
152 - $comment = $this->commentService->update($commentData, $comment_id, $request->mentionData);
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 + }
153 157
154 - if($request->mentionData)
155 - {
156 - $this->notificationService->mentionInComment($comment, $request->mentionData);
158 + $mentionData = $this->getMentionData($request);
159 +
160 + $comment = $this->commentService->update($commentData, $comment_id, $mentionData, $board_id);
161 +
162 + if (!$comment) {
163 + $errorMessage = __('Unauthorized Action', 'fluent-boards');
164 + return $this->sendError($errorMessage, 401);
157 165 }
158 166
159 - if($request->images)
167 + if(!empty($mentionData))
160 168 {
161 - $this->commentService->attachCommentImages($comment, $request->images);
162 - $comment->load(['images']);
169 + $this->notificationService->mentionInComment($comment, $mentionData);
163 170 }
164 171
165 - if ( !$comment ) {
166 - $errorMessage = __('Unauthorized Action', 'fluent-boards');
167 - return $this->sendError($errorMessage, 401);
172 + if ($hasImagesParam) {
173 + $this->commentService->attachCommentImages($comment, $imageIds);
174 + $comment->load(['images']);
168 175 }
169 176
170 177 $comment->load('user');
171 178
@@ -173,9 +180,9 @@
173 180 'comment' => $comment,
174 181 'message' => __('Comment has been updated', 'fluent-boards'),
175 182 ], 200);
176 183 } catch (\Exception $e) {
177 - return $this->sendError($e->getMessage(), 404);
184 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
178 185 }
179 186 }
180 187
181 188 public function deleteComment($board_id, $comment_id)
@@ -180,9 +187,9 @@
180 187
181 188 public function deleteComment($board_id, $comment_id)
182 189 {
183 190 try {
184 - $this->commentService->delete($comment_id);
191 + $this->commentService->delete($comment_id, $board_id);
185 192
186 193 return $this->sendSuccess([
187 194 'message' => __('Comment has been deleted', 'fluent-boards'),
188 195 ], 200);
@@ -193,9 +200,9 @@
193 200
194 201 public function updateReply(Request $request, $board_id, $reply_id)
195 202 {
196 203 $requestData = [
197 - 'description' => $request->comment
204 + 'description' => $this->commentService->sanitizeContent($request->get('comment', ''))
198 205 ];
199 206
200 207 $validationRules = [
201 208 'description' => 'required|string'
@@ -203,9 +210,11 @@
203 210
204 211 $replyData = $this->commentSanitizeAndValidate($requestData, $validationRules);
205 212
206 213 try {
207 - $reply = $this->commentService->update($replyData, $reply_id, $request->mentionData);
214 + $mentionData = $this->getMentionData($request);
215 +
216 + $reply = $this->commentService->update($replyData, $reply_id, $mentionData, $board_id);
208 217
209 218 if (!$reply) {
210 219 $errorMessage = __('Unauthorized Action', 'fluent-boards');
211 220 return $this->sendError($errorMessage, 401);
@@ -215,9 +224,9 @@
215 224 'description' => $reply->description,
216 225 'message' => __('Reply has been updated', 'fluent-boards'),
217 226 ], 200);
218 227 } catch (\Exception $e) {
219 - return $this->sendError($e->getMessage(), 404);
228 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
220 229 }
221 230 }
222 231
223 232 public function deleteReply($board_id, $reply_id)
@@ -222,9 +231,9 @@
222 231
223 232 public function deleteReply($board_id, $reply_id)
224 233 {
225 234 try {
226 - $this->commentService->deleteReply($reply_id);
235 + $this->commentService->deleteReply($reply_id, $board_id);
227 236
228 237 return $this->sendSuccess([
229 238 'message' => __('Reply has been deleted', 'fluent-boards'),
230 239 ], 200);
@@ -232,17 +241,47 @@
232 241 return $this->sendError($e->getMessage(), 404);
233 242 }
234 243 }
235 244
236 - public function sendMailAfterComment($commentId, $usersToSendEmail)
245 + public function sendMailAfterComment($commentId, $recipientUserIds)
237 246 {
238 247 $current_user_id = get_current_user_id();
239 248
240 249 /* this will run in background as soon as possible */
241 250 /* sending Model or Model Instance won't work here */
242 - 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');
243 252 }
244 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 +
245 284 private function commentSanitizeAndValidate($data, array $rules = [])
246 285 {
247 286 $data = Helper::sanitizeComment($data);
248 287
@@ -268,12 +307,14 @@
268 307 ], [
269 308 'file.mimetypes' => __('The file must be a image type.', 'fluent-boards')
270 309 ]);
271 310
311 + (new \FluentBoards\App\Services\TaskService())->findTaskOnBoard($task_id, $board_id);
312 +
272 313 $uploadInfo = UploadService::handleFileUpload( $files, $board_id);
273 314
274 315 $imageData = $uploadInfo[0];
275 - $attachment = $this->commentService->createCommentImage($imageData, $board_id);
316 + $attachment = $this->commentService->createCommentImage($imageData, $board_id, $task_id);
276 317 if(!!defined('FLUENT_BOARDS_PRO_VERSION')) {
277 318 $mediaData = (new AttachmentService())->processMediaData($imageData, $files['file']);
278 319 $attachment['driver'] = $mediaData['driver'];
279 320 $attachment['file_path'] = $mediaData['file_path'];
@@ -286,6 +327,43 @@
286 327 'message' => __('attachment has been added', 'fluent-boards'),
287 328 'imageAttachment' => $attachment
288 329 ], 200);
289 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'));
290 368 }
291 369 }