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 +158 -56 1.312.1.0 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' => $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'),
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,63 @@
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 + $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']);
93 + }
94 + $comment['user'] = $comment->user;
95 +
96 + $recipientUserIds = [];
97 + if ($comment->type == 'reply') {
98 + $parentComment = Comment::findOrFail($comment->parent_id);
99 + $commenterId = $parentComment->created_by;
100 + if ($commenterId != get_current_user_id())
101 + {
102 + $recipientUserIds[] = absint($commenterId);
103 + }
104 + $this->sendMailAfterComment($comment->id, $recipientUserIds);
82 105 } else {
83 - $commentData['description'] = $this->commentService->checkIfCommentHaveLinks($commentData['description']);
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);
84 109 }
85 110
86 - $comment = $this->commentService->create($commentData, $task_id);
87 - $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 -
91 - $this->sendMailAfterComment($comment->id, $usersToSendEmail);
92 -
93 - if($request->mentionData)
111 + if(!empty($mentionData))
94 112 {
95 - $this->notificationService->mentionInComment($comment, $request->mentionData);
113 + $this->notificationService->mentionInComment($comment, $mentionData);
96 114 }
97 115
98 - if($request->images)
116 + if ($comment->type == 'comment')
99 117 {
100 - $this->commentService->attachCommentImages($comment, $request->images);
101 - $comment->load(['images']);
118 + $comment->load('replies');
102 119 }
103 120
104 - $comment->load('replies');
105 -
106 121 return $this->sendSuccess([
107 122 'message' => __('Comment has been added', 'fluent-boards'),
108 123 'comment' => $comment
109 124 ], 201);
110 125 } catch (\Exception $e) {
111 - return $this->sendError($e->getMessage(), 404);
126 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 400);
112 127 }
113 128 }
114 129
115 130 public function update(Request $request, $board_id, $comment_id)
116 131 {
117 -// $commentData = $this->commentSanitizeAndValidate($request->all(), [
118 -// 'description' => 'required|string',
119 -// ]);
120 132 $requestData = [
121 - 'description' => $request->comment
133 + 'description' => $this->commentService->sanitizeContent($request->get('comment', ''))
122 134 ];
123 135
124 136 $validationRules = [
125 137 'description' => 'required|string'
@@ -124,9 +136,11 @@
124 136 $validationRules = [
125 137 'description' => 'required|string'
126 138 ];
127 139
128 - if ($request->images) {
140 + $hasImagesParam = $this->requestHasImagesArray($request);
141 + $imageIds = $this->getImageIdsFromRequest($request);
142 + if ($hasImagesParam) {
129 143 $validationRules['description'] = 'nullable|string';
130 144 }
131 145
132 146 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
@@ -131,24 +145,34 @@
131 145
132 146 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
133 147
134 148 try {
135 - $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 + }
136 157
137 - if($request->mentionData)
138 - {
139 - $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);
140 165 }
141 166
142 - if($request->images)
167 + if(!empty($mentionData))
143 168 {
144 - $this->commentService->attachCommentImages($comment, $request->images);
145 - $comment->load(['images']);
169 + $this->notificationService->mentionInComment($comment, $mentionData);
146 170 }
147 171
148 - if ( !$comment ) {
149 - $errorMessage = __('Unauthorized Action', 'fluent-boards');
150 - return $this->sendError($errorMessage, 401);
172 + if ($hasImagesParam) {
173 + $this->commentService->attachCommentImages($comment, $imageIds);
174 + $comment->load(['images']);
151 175 }
152 176
153 177 $comment->load('user');
154 178
@@ -156,16 +180,16 @@
156 180 'comment' => $comment,
157 181 'message' => __('Comment has been updated', 'fluent-boards'),
158 182 ], 200);
159 183 } catch (\Exception $e) {
160 - return $this->sendError($e->getMessage(), 404);
184 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
161 185 }
162 186 }
163 187
164 - public function delete($board_id, $comment_id)
188 + public function deleteComment($board_id, $comment_id)
165 189 {
166 190 try {
167 - $this->commentService->delete($comment_id);
191 + $this->commentService->delete($comment_id, $board_id);
168 192
169 193 return $this->sendSuccess([
170 194 'message' => __('Comment has been deleted', 'fluent-boards'),
171 195 ], 200);
@@ -176,9 +200,9 @@
176 200
177 201 public function updateReply(Request $request, $board_id, $reply_id)
178 202 {
179 203 $requestData = [
180 - 'description' => $request->comment
204 + 'description' => $this->commentService->sanitizeContent($request->get('comment', ''))
181 205 ];
182 206
183 207 $validationRules = [
184 208 'description' => 'required|string'
@@ -186,9 +210,11 @@
186 210
187 211 $replyData = $this->commentSanitizeAndValidate($requestData, $validationRules);
188 212
189 213 try {
190 - $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);
191 217
192 218 if (!$reply) {
193 219 $errorMessage = __('Unauthorized Action', 'fluent-boards');
194 220 return $this->sendError($errorMessage, 401);
@@ -198,9 +224,9 @@
198 224 'description' => $reply->description,
199 225 'message' => __('Reply has been updated', 'fluent-boards'),
200 226 ], 200);
201 227 } catch (\Exception $e) {
202 - return $this->sendError($e->getMessage(), 404);
228 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
203 229 }
204 230 }
205 231
206 232 public function deleteReply($board_id, $reply_id)
@@ -205,9 +231,9 @@
205 231
206 232 public function deleteReply($board_id, $reply_id)
207 233 {
208 234 try {
209 - $this->commentService->deleteReply($reply_id);
235 + $this->commentService->deleteReply($reply_id, $board_id);
210 236
211 237 return $this->sendSuccess([
212 238 'message' => __('Reply has been deleted', 'fluent-boards'),
213 239 ], 200);
@@ -215,17 +241,47 @@
215 241 return $this->sendError($e->getMessage(), 404);
216 242 }
217 243 }
218 244
219 - public function sendMailAfterComment($commentId, $usersToSendEmail)
245 + public function sendMailAfterComment($commentId, $recipientUserIds)
220 246 {
221 247 $current_user_id = get_current_user_id();
222 248
223 249 /* this will run in background as soon as possible */
224 250 /* 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');
251 + as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_comment', [$commentId, $recipientUserIds, $current_user_id], 'fluent-boards');
226 252 }
227 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 +
228 284 private function commentSanitizeAndValidate($data, array $rules = [])
229 285 {
230 286 $data = Helper::sanitizeComment($data);
231 287
@@ -251,13 +307,22 @@
251 307 ], [
252 308 'file.mimetypes' => __('The file must be a image type.', 'fluent-boards')
253 309 ]);
254 310
311 + (new \FluentBoards\App\Services\TaskService())->findTaskOnBoard($task_id, $board_id);
312 +
255 313 $uploadInfo = UploadService::handleFileUpload( $files, $board_id);
256 314
257 315 $imageData = $uploadInfo[0];
258 -// $attachmentService = new AttachmentService();
259 - $attachment = $this->commentService->createCommentImage($imageData, $board_id);
316 + $attachment = $this->commentService->createCommentImage($imageData, $board_id, $task_id);
317 + if(!!defined('FLUENT_BOARDS_PRO_VERSION')) {
318 + $mediaData = (new AttachmentService())->processMediaData($imageData, $files['file']);
319 + $attachment['driver'] = $mediaData['driver'];
320 + $attachment['file_path'] = $mediaData['file_path'];
321 + $attachment['full_url'] = $mediaData['full_url'];
322 + $attachment->save();
323 + }
324 + $attachment->public_url = $this->commentService->createPublicUrl($attachment, $board_id);
260 325
261 326 return $this->sendSuccess([
262 327 'message' => __('attachment has been added', 'fluent-boards'),
263 328 'imageAttachment' => $attachment
@@ -262,6 +327,43 @@
262 327 'message' => __('attachment has been added', 'fluent-boards'),
263 328 'imageAttachment' => $attachment
264 329 ], 200);
265 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'));
266 368 }
267 369 }