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

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

370 lines 13.2 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 $current_user_id = get_current_user_id();
248
249 /* this will run in background as soon as possible */
250 /* sending Model or Model Instance won't work here */
251 as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_comment', [$commentId, $recipientUserIds, $current_user_id], 'fluent-boards');
252 }
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 private function commentSanitizeAndValidate($data, array $rules = [])
285 {
286 $data = Helper::sanitizeComment($data);
287
288 return $this->validate($data, $rules);
289 }
290
291 public function handleImageUpload(Request $request, $board_id, $task_id)
292 {
293 $allowedTypes = implode(',', [
294 "image/jpeg",
295 "image/gif",
296 "image/png",
297 "image/bmp",
298 "image/tiff",
299 "image/webp",
300 "image/avif",
301 "image/x-icon",
302 "image/heic",
303 ]);
304
305 $files = $this->validate($request->files(), [
306 'file' => 'mimetypes:' . $allowedTypes,
307 ], [
308 'file.mimetypes' => __('The file must be a image type.', 'fluent-boards')
309 ]);
310
311 (new \FluentBoards\App\Services\TaskService())->findTaskOnBoard($task_id, $board_id);
312
313 $uploadInfo = UploadService::handleFileUpload( $files, $board_id);
314
315 $imageData = $uploadInfo[0];
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);
325
326 return $this->sendSuccess([
327 'message' => __('attachment has been added', 'fluent-boards'),
328 'imageAttachment' => $attachment
329 ], 200);
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'));
368 }
369 }
370