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 +89 -73 1.95.22.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;
@@ -55,10 +52,10 @@
55 52 $requestData = [
56 53 'parent_id' => $request->getSafe('parent_id', function ($value) {
57 54 return (empty($value)) ? null : intval( $value);
58 55 }, null),
59 - 'description' => $request->getSafe('comment', 'sanitize_textarea_field'),
60 - 'created_by' => $request->getSafe('comment_by', 'intval', get_current_user_id()),
56 + 'description' => $this->commentService->sanitizeContent($request->get('comment', '')),
57 + 'created_by' => get_current_user_id(),
61 58 'task_id' => (int) $task_id,
62 59 'type' => $request->getSafe('comment_type', 'sanitize_text_field', 'comment'),
63 60 'board_id' => (int) $board_id,
64 61 ];
@@ -69,10 +66,10 @@
69 66 'task_id' => 'required|integer',
70 67 'type' => 'required|string'
71 68 ];
72 69
73 - $images = $request->getSafe('images');
74 - if ($images) {
70 + $imageIds = $this->getImageIdsFromRequest($request);
71 + if ($imageIds) {
75 72 $validationRules['description'] = 'nullable|string';
76 73 }
77 74
78 75 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
@@ -78,47 +75,38 @@
78 75 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
79 76
80 77
81 78 try {
79 + if (!empty($imageIds)) {
80 + $this->commentService->assertCommentImagesAttachable($imageIds, $board_id, $task_id);
81 + }
82 82
83 83 $rawDescription = $commentData['description'];
84 - // Sanitize mentionData array to integers
85 - $mentionData = [];
86 - $rawMentionData = $request->getSafe('mentionData');
87 - if ($rawMentionData && is_array($rawMentionData)) {
88 - $mentionData = array_filter(array_map('intval', $rawMentionData));
89 - }
84 + $mentionData = $this->getMentionData($request, $board_id);
90 85 $commentData['settings'] = [ 'raw_description' => $rawDescription, 'mentioned_id' => $mentionData ];
91 86
92 - // Ensure UTF-8 encoding for comment description
93 - $description = mb_convert_encoding($commentData['description'], 'UTF-8', 'auto');
87 + $commentData['description'] = $this->commentService->renderContent($rawDescription, $mentionData);
94 88
95 - if(!empty($mentionData)) {
96 - // Process mentions and links with UTF-8 support
97 - $commentData['description'] = $this->commentService->processMentionAndLink($description, $mentionData);
98 - } else {
99 - // Process links with UTF-8 support
100 - $commentData['description'] = $this->commentService->checkIfCommentHaveLinks($description);
89 + $comment = $this->commentService->create($commentData, $task_id, $board_id);
90 + if (!empty($imageIds)) {
91 + $this->commentService->attachCommentImages($comment, $imageIds);
92 + $comment->load(['images']);
101 93 }
102 -
103 - $comment = $this->commentService->create($commentData, $task_id, $board_id);
104 94 $comment['user'] = $comment->user;
105 95
106 - $usersToSendEmail = [];
96 + $recipientUserIds = [];
107 97 if ($comment->type == 'reply') {
108 98 $parentComment = Comment::findOrFail($comment->parent_id);
109 99 $commenterId = $parentComment->created_by;
110 100 if ($commenterId != get_current_user_id())
111 101 {
112 - $commenter = User::select('user_email')->findOrFail($commenterId);
113 - $commenterEmail = $commenter->user_email;
114 - $usersToSendEmail[] = $commenterEmail;
102 + $recipientUserIds[] = absint($commenterId);
115 103 }
116 - $this->sendMailAfterComment($comment->id, $usersToSendEmail);
104 + $this->sendMailAfterComment($comment->id, $recipientUserIds);
117 105 } else {
118 - //sending emails to assignees who enabled their email
119 - $usersToSendEmail = $this->notificationService->filterAssigneeToSendEmail($task_id, Constant::BOARD_EMAIL_COMMENT);
120 - $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);
121 109 }
122 110
123 111 if(!empty($mentionData))
124 112 {
@@ -124,21 +112,8 @@
124 112 {
125 113 $this->notificationService->mentionInComment($comment, $mentionData);
126 114 }
127 115
128 - $images = $request->getSafe('images');
129 - if ($images) {
130 - // Sanitize images array to integers
131 - $imageIds = [];
132 - if (is_array($images)) {
133 - $imageIds = array_filter(array_map('intval', $images));
134 - }
135 - if (!empty($imageIds)) {
136 - $this->commentService->attachCommentImages($comment, $imageIds);
137 - $comment->load(['images']);
138 - }
139 - }
140 -
141 116 if ($comment->type == 'comment')
142 117 {
143 118 $comment->load('replies');
144 119 }
@@ -147,9 +122,9 @@
147 122 'message' => __('Comment has been added', 'fluent-boards'),
148 123 'comment' => $comment
149 124 ], 201);
150 125 } catch (\Exception $e) {
151 - return $this->sendError($e->getMessage(), 400);
126 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 400);
152 127 }
153 128 }
154 129
155 130 public function update(Request $request, $board_id, $comment_id)
@@ -154,9 +129,9 @@
154 129
155 130 public function update(Request $request, $board_id, $comment_id)
156 131 {
157 132 $requestData = [
158 - 'description' => $request->getSafe('comment', 'sanitize_textarea_field')
133 + 'description' => $this->commentService->sanitizeContent($request->get('comment', ''))
159 134 ];
160 135
161 136 $validationRules = [
162 137 'description' => 'required|string'
@@ -161,10 +136,11 @@
161 136 $validationRules = [
162 137 'description' => 'required|string'
163 138 ];
164 139
165 - $images = $request->getSafe('images');
166 - if ($images) {
140 + $hasImagesParam = $this->requestHasImagesArray($request);
141 + $imageIds = $this->getImageIdsFromRequest($request);
142 + if ($hasImagesParam) {
167 143 $validationRules['description'] = 'nullable|string';
168 144 }
169 145
170 146 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
@@ -169,14 +145,18 @@
169 145
170 146 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
171 147
172 148 try {
173 - // Sanitize mentionData array to integers
174 - $mentionData = [];
175 - $rawMentionData = $request->getSafe('mentionData');
176 - if ($rawMentionData && is_array($rawMentionData)) {
177 - $mentionData = array_filter(array_map('intval', $rawMentionData));
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);
178 156 }
157 +
158 + $mentionData = $this->getMentionData($request);
179 159
180 160 $comment = $this->commentService->update($commentData, $comment_id, $mentionData, $board_id);
181 161
182 162 if (!$comment) {
@@ -188,16 +168,11 @@
188 168 {
189 169 $this->notificationService->mentionInComment($comment, $mentionData);
190 170 }
191 171
192 - // Sanitize images array to integers
193 - $rawImages = $request->getSafe('images');
194 - if ($rawImages && is_array($rawImages)) {
195 - $imageIds = array_filter(array_map('intval', $rawImages));
196 - if (!empty($imageIds)) {
197 - $this->commentService->attachCommentImages($comment, $imageIds);
198 - $comment->load(['images']);
199 - }
172 + if ($hasImagesParam) {
173 + $this->commentService->attachCommentImages($comment, $imageIds);
174 + $comment->load(['images']);
200 175 }
201 176
202 177 $comment->load('user');
203 178
@@ -205,9 +180,9 @@
205 180 'comment' => $comment,
206 181 'message' => __('Comment has been updated', 'fluent-boards'),
207 182 ], 200);
208 183 } catch (\Exception $e) {
209 - return $this->sendError($e->getMessage(), 404);
184 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
210 185 }
211 186 }
212 187
213 188 public function deleteComment($board_id, $comment_id)
@@ -225,9 +200,9 @@
225 200
226 201 public function updateReply(Request $request, $board_id, $reply_id)
227 202 {
228 203 $requestData = [
229 - 'description' => $request->getSafe('comment', 'sanitize_textarea_field')
204 + 'description' => $this->commentService->sanitizeContent($request->get('comment', ''))
230 205 ];
231 206
232 207 $validationRules = [
233 208 'description' => 'required|string'
@@ -235,14 +210,9 @@
235 210
236 211 $replyData = $this->commentSanitizeAndValidate($requestData, $validationRules);
237 212
238 213 try {
239 - // Sanitize mentionData array to integers
240 - $mentionData = [];
241 - $rawMentionData = $request->getSafe('mentionData');
242 - if ($rawMentionData && is_array($rawMentionData)) {
243 - $mentionData = array_filter(array_map('intval', $rawMentionData));
244 - }
214 + $mentionData = $this->getMentionData($request);
245 215
246 216 $reply = $this->commentService->update($replyData, $reply_id, $mentionData, $board_id);
247 217
248 218 if (!$reply) {
@@ -254,9 +224,9 @@
254 224 'description' => $reply->description,
255 225 'message' => __('Reply has been updated', 'fluent-boards'),
256 226 ], 200);
257 227 } catch (\Exception $e) {
258 - return $this->sendError($e->getMessage(), 404);
228 + return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
259 229 }
260 230 }
261 231
262 232 public function deleteReply($board_id, $reply_id)
@@ -271,17 +241,47 @@
271 241 return $this->sendError($e->getMessage(), 404);
272 242 }
273 243 }
274 244
275 - public function sendMailAfterComment($commentId, $usersToSendEmail)
245 + public function sendMailAfterComment($commentId, $recipientUserIds)
276 246 {
277 247 $current_user_id = get_current_user_id();
278 248
279 249 /* this will run in background as soon as possible */
280 250 /* sending Model or Model Instance won't work here */
281 - 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');
282 252 }
283 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 +
284 284 private function commentSanitizeAndValidate($data, array $rules = [])
285 285 {
286 286 $data = Helper::sanitizeComment($data);
287 287
@@ -312,9 +312,9 @@
312 312
313 313 $uploadInfo = UploadService::handleFileUpload( $files, $board_id);
314 314
315 315 $imageData = $uploadInfo[0];
316 - $attachment = $this->commentService->createCommentImage($imageData, $board_id);
316 + $attachment = $this->commentService->createCommentImage($imageData, $board_id, $task_id);
317 317 if(!!defined('FLUENT_BOARDS_PRO_VERSION')) {
318 318 $mediaData = (new AttachmentService())->processMediaData($imageData, $files['file']);
319 319 $attachment['driver'] = $mediaData['driver'];
320 320 $attachment['file_path'] = $mediaData['file_path'];
@@ -348,6 +348,22 @@
348 348 $privacy = $comment->privacy == 'public' ? __('public', 'fluent-boards') : __('private', 'fluent-boards'),
349 349 // translators: %s is the privacy setting (public or private)
350 350 'message' => sprintf(__('This comment is now %s', 'fluent-boards'), $privacy),
351 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'));
352 368 }
353 369 }