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.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 All 43 releases
fluent-boards / app / Http / Controllers / CommentController.php

CommentController.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 2.2.0, at app/Http/Controllers/CommentController.php

374 lines 13.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App\Http\Controllers;
4
5 use FluentBoards\App\Models\Comment;
6 use FluentBoards\App\Services\NotificationService;
7 use FluentBoards\App\Services\Helper;
8 use FluentBoards\App\Services\UploadService;
9 use FluentBoards\Framework\Http\Request\Request;
10 use FluentBoards\App\Services\CommentService;
11 use FluentBoardsPro\App\Services\AttachmentService;
12
13 class CommentController extends Controller
14 {
15 private $commentService;
16 private $notificationService;
17
18 public function __construct(CommentService $commentService, NotificationService $notificationService)
19 {
20 parent::__construct();
21 $this->commentService = $commentService;
22 $this->notificationService = $notificationService;
23 }
24
25 public function getComments(Request $request, $board_id, $task_id)
26 {
27 try {
28 $filter = $request->getSafe('filter', 'sanitize_text_field');
29 $per_page = 10;
30
31 $comments = $this->commentService->getComments($task_id, $per_page, $filter, $board_id);
32 $totalComments = $this->commentService->getTotal($task_id, $board_id);
33
34 return $this->sendSuccess([
35 'comments' => $comments,
36 'total' => $totalComments
37 ], 200);
38 } catch (\Exception $e) {
39 return $this->sendError($e->getMessage(), 404);
40 }
41 }
42
43 /*
44 * handles comment or reply creation
45 * @param $board_id int
46 * @param $task_id int
47 * @return json
48 */
49 public function create(Request $request, $board_id, $task_id)
50 {
51 // TODO: Refactor the whole request and sanitize process here.. minimize the code in this functions.
52 $requestData = [
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 'board_id' => (int) $board_id,
61 ];
62 $validationRules = [
63 'description' => 'required|string',
64 'created_by' => 'required|integer',
65 'board_id' => 'required|integer',
66 'task_id' => 'required|integer',
67 'type' => 'required|string'
68 ];
69
70 $imageIds = $this->getImageIdsFromRequest($request);
71 if ($imageIds) {
72 $validationRules['description'] = 'nullable|string';
73 }
74
75 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
76
77
78 try {
79 if (!empty($imageIds)) {
80 $this->commentService->assertCommentImagesAttachable($imageIds, $board_id, $task_id);
81 }
82
83 $rawDescription = $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']);
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);
105 } else {
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);
109 }
110
111 if(!empty($mentionData))
112 {
113 $this->notificationService->mentionInComment($comment, $mentionData);
114 }
115
116 if ($comment->type == 'comment')
117 {
118 $comment->load('replies');
119 }
120
121 return $this->sendSuccess([
122 'message' => __('Comment has been added', 'fluent-boards'),
123 'comment' => $comment
124 ], 201);
125 } catch (\Exception $e) {
126 return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 400);
127 }
128 }
129
130 public function update(Request $request, $board_id, $comment_id)
131 {
132 $requestData = [
133 'description' => $this->commentService->sanitizeContent($request->get('comment', ''))
134 ];
135
136 $validationRules = [
137 'description' => 'required|string'
138 ];
139
140 $hasImagesParam = $this->requestHasImagesArray($request);
141 $imageIds = $this->getImageIdsFromRequest($request);
142 if ($hasImagesParam) {
143 $validationRules['description'] = 'nullable|string';
144 }
145
146 $commentData = $this->commentSanitizeAndValidate($requestData, $validationRules);
147
148 try {
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 }
157
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);
165 }
166
167 if(!empty($mentionData))
168 {
169 $this->notificationService->mentionInComment($comment, $mentionData);
170 }
171
172 if ($hasImagesParam) {
173 $this->commentService->attachCommentImages($comment, $imageIds);
174 $comment->load(['images']);
175 }
176
177 $comment->load('user');
178
179 return $this->sendSuccess([
180 'comment' => $comment,
181 'message' => __('Comment has been updated', 'fluent-boards'),
182 ], 200);
183 } catch (\Exception $e) {
184 return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
185 }
186 }
187
188 public function deleteComment($board_id, $comment_id)
189 {
190 try {
191 $this->commentService->delete($comment_id, $board_id);
192
193 return $this->sendSuccess([
194 'message' => __('Comment has been deleted', 'fluent-boards'),
195 ], 200);
196 } catch (\Exception $e) {
197 return $this->sendError($e->getMessage(), 404);
198 }
199 }
200
201 public function updateReply(Request $request, $board_id, $reply_id)
202 {
203 $requestData = [
204 'description' => $this->commentService->sanitizeContent($request->get('comment', ''))
205 ];
206
207 $validationRules = [
208 'description' => 'required|string'
209 ];
210
211 $replyData = $this->commentSanitizeAndValidate($requestData, $validationRules);
212
213 try {
214 $mentionData = $this->getMentionData($request);
215
216 $reply = $this->commentService->update($replyData, $reply_id, $mentionData, $board_id);
217
218 if (!$reply) {
219 $errorMessage = __('Unauthorized Action', 'fluent-boards');
220 return $this->sendError($errorMessage, 401);
221 }
222
223 return $this->sendSuccess([
224 'description' => $reply->description,
225 'message' => __('Reply has been updated', 'fluent-boards'),
226 ], 200);
227 } catch (\Exception $e) {
228 return $this->sendError($e->getMessage(), $e->getCode() === 403 ? 403 : 404);
229 }
230 }
231
232 public function deleteReply($board_id, $reply_id)
233 {
234 try {
235 $this->commentService->deleteReply($reply_id, $board_id);
236
237 return $this->sendSuccess([
238 'message' => __('Reply has been deleted', 'fluent-boards'),
239 ], 200);
240 } catch (\Exception $e) {
241 return $this->sendError($e->getMessage(), 404);
242 }
243 }
244
245 public function sendMailAfterComment($commentId, $recipientUserIds)
246 {
247 if (empty($recipientUserIds)) {
248 return;
249 }
250
251 $current_user_id = get_current_user_id();
252
253 /* this will run in background as soon as possible */
254 /* sending Model or Model Instance won't work here */
255 as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_comment', [$commentId, $recipientUserIds, $current_user_id], 'fluent-boards');
256 }
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
288 private function commentSanitizeAndValidate($data, array $rules = [])
289 {
290 $data = Helper::sanitizeComment($data);
291
292 return $this->validate($data, $rules);
293 }
294
295 public function handleImageUpload(Request $request, $board_id, $task_id)
296 {
297 $allowedTypes = implode(',', [
298 "image/jpeg",
299 "image/gif",
300 "image/png",
301 "image/bmp",
302 "image/tiff",
303 "image/webp",
304 "image/avif",
305 "image/x-icon",
306 "image/heic",
307 ]);
308
309 $files = $this->validate($request->files(), [
310 'file' => 'mimetypes:' . $allowedTypes,
311 ], [
312 'file.mimetypes' => __('The file must be a image type.', 'fluent-boards')
313 ]);
314
315 (new \FluentBoards\App\Services\TaskService())->findTaskOnBoard($task_id, $board_id);
316
317 $uploadInfo = UploadService::handleFileUpload( $files, $board_id);
318
319 $imageData = $uploadInfo[0];
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);
329
330 return $this->sendSuccess([
331 'message' => __('attachment has been added', 'fluent-boards'),
332 'imageAttachment' => $attachment
333 ], 200);
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'));
372 }
373 }
374