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 +150 -61 1.32 → 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;
@@ -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,51 @@
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 {
247 + if (empty($recipientUserIds)) {
248 + return;
249 + }
250 +
238 251 $current_user_id = get_current_user_id();
239 252
240 253 /* this will run in background as soon as possible */
241 254 /* 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');
255 + as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_comment', [$commentId, $recipientUserIds, $current_user_id], 'fluent-boards');
243 256 }
244 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 +
245 288 private function commentSanitizeAndValidate($data, array $rules = [])
246 289 {
247 290 $data = Helper::sanitizeComment($data);
248 291
@@ -268,13 +311,22 @@
268 311 ], [
269 312 'file.mimetypes' => __('The file must be a image type.', 'fluent-boards')
270 313 ]);
271 314
315 + (new \FluentBoards\App\Services\TaskService())->findTaskOnBoard($task_id, $board_id);
316 +
272 317 $uploadInfo = UploadService::handleFileUpload( $files, $board_id);
273 318
274 319 $imageData = $uploadInfo[0];
275 -// $attachmentService = new AttachmentService();
276 - $attachment = $this->commentService->createCommentImage($imageData, $board_id);
320 + $attachment = $this->commentService->createCommentImage($imageData, $board_id, $task_id);
321 + if(!!defined('FLUENT_BOARDS_PRO_VERSION')) {
322 + $mediaData = (new AttachmentService())->processMediaData($imageData, $files['file']);
323 + $attachment['driver'] = $mediaData['driver'];
324 + $attachment['file_path'] = $mediaData['file_path'];
325 + $attachment['full_url'] = $mediaData['full_url'];
326 + $attachment->save();
327 + }
328 + $attachment->public_url = $this->commentService->createPublicUrl($attachment, $board_id);
277 329
278 330 return $this->sendSuccess([
279 331 'message' => __('attachment has been added', 'fluent-boards'),
280 332 'imageAttachment' => $attachment
@@ -279,6 +331,43 @@
279 331 'message' => __('attachment has been added', 'fluent-boards'),
280 332 'imageAttachment' => $attachment
281 333 ], 200);
282 334
335 + }
336 +
337 + public function updateCommentPrivacy($board_id, $comment_id)
338 + {
339 + $comment = $this->commentService->findCommentOnBoard($comment_id, $board_id);
340 +
341 + // Check if user has permission to update the comment
342 + if ($comment->created_by != get_current_user_id()) {
343 + return $this->sendError(__('Unauthorized Action', 'fluent-boards'), 401);
344 + }
345 +
346 + // Toggle privacy
347 + $comment->privacy = ($comment->privacy === 'public') ? 'private' : 'public';
348 + $comment->save();
349 +
350 + return $this->sendSuccess([
351 + 'comment' => $comment,
352 + $privacy = $comment->privacy == 'public' ? __('public', 'fluent-boards') : __('private', 'fluent-boards'),
353 + // translators: %s is the privacy setting (public or private)
354 + 'message' => sprintf(__('This comment is now %s', 'fluent-boards'), $privacy),
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'));
283 372 }
284 373 }