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/Services/CommentService.php +560 -32 1.132.1.0 View file →
@@ -1,20 +1,48 @@
1 1 <?php
2 2
3 3 namespace FluentBoards\App\Services;
4 4
5 +use FluentBoards\App\App;
5 6 use FluentBoards\App\Models\Comment;
7 +use FluentBoards\App\Models\CommentImage;
6 8 use FluentBoards\App\Models\Task;
7 9 use FluentBoards\App\Models\TaskActivity;
10 +use FluentBoardsPro\App\Services\AttachmentService;
11 +use FluentBoardsPro\App\Services\RemoteUrlParser;
12 +use RuntimeException;
8 13
9 14 class CommentService
10 15 {
11 - public function getComments($id, $per_page, $filter)
16 + /**
17 + * Resolve a comment only when its task belongs to the requested board.
18 + *
19 + * @param int $commentId
20 + * @param int $boardId
21 + * @return Comment
22 + * @throws \Exception
23 + */
24 + public function findCommentOnBoard($commentId, $boardId)
12 25 {
13 - $task = Task::findOrFail($id);
26 + $comment = Comment::findOrFail($commentId);
27 + (new TaskService())->findTaskOnBoard($comment->task_id, $boardId);
14 28
29 + if ($comment->board_id && (int) $comment->board_id !== absint($boardId)) {
30 + throw new \Exception(esc_html__('Comment not found', 'fluent-boards'));
31 + }
32 +
33 + return $comment;
34 + }
35 +
36 + /**
37 + * Get paginated parent comments with users, images, and replies preloaded.
38 + */
39 + public function getComments($id, $per_page, $filter, $boardId = null)
40 + {
41 + $task = $boardId ? (new TaskService())->findTaskOnBoard($id, $boardId) : Task::findOrFail($id);
42 +
15 43 $commentsQuery = $task->comments()->whereNull('parent_id')
16 - ->with(['user']);
44 + ->with(['user', 'images', 'replies.user', 'replies.images']);
17 45
18 46 if ($filter == 'oldest') {
19 47 $commentsQuery = $commentsQuery->oldest();
20 48 } else { // latest or newest
@@ -22,9 +50,8 @@
22 50 }
23 51 $comments = $commentsQuery->paginate($per_page);
24 52
25 53 foreach ($comments as $comment) {
26 - $comment->replies = $this->getReplies($comment);
27 54 $comment->replies_count = count($comment->replies);
28 55 }
29 56
30 57 return $comments;
@@ -29,11 +56,11 @@
29 56
30 57 return $comments;
31 58 }
32 59
33 - public function getTotal($id)
60 + public function getTotal($id, $boardId = null)
34 61 {
35 - $task = Task::findOrFail($id);
62 + $task = $boardId ? (new TaskService())->findTaskOnBoard($id, $boardId) : Task::findOrFail($id);
36 63 $totalComment = Comment::where('task_id', $task->id)
37 64 ->type('comment')
38 65 ->count();
39 66 $totalReply = Comment::where('task_id', $task->id)
@@ -48,55 +75,489 @@
48 75 $replies = Comment::where('parent_id', $comment->id)->with(['user'])->get();
49 76 return $replies;
50 77 }
51 78
52 - public function create($commentData, $id)
79 + public function create($commentData, $id, $boardId = null)
53 80 {
81 + if ($boardId) {
82 + (new TaskService())->findTaskOnBoard($id, $boardId);
83 +
84 + if (!empty($commentData['parent_id'])) {
85 + $parentComment = $this->findCommentOnBoard($commentData['parent_id'], $boardId);
86 +
87 + if ((int) $parentComment->task_id !== (int) $id) {
88 + throw new \Exception(esc_html__('Comment not found', 'fluent-boards'));
89 + }
90 + }
91 + }
92 +
54 93 $comment = Comment::create($commentData);
55 - do_action('fluent_boards/task_comment_added', $comment);
94 + do_action('fluent_boards/comment_created', $comment);
56 95 return $comment;
57 96 }
58 97
59 - public function update($commentData, $comment_id)
98 + /** Allow only paragraph content and the comment editor's five inline formats. */
99 + public function sanitizeContent($content)
60 100 {
61 - $comment = Comment::findOrFail($comment_id);
101 + if (!is_string($content) || trim(wp_strip_all_tags($content)) === '') {
102 + return '';
103 + }
62 104
63 - if ($comment->created_by != get_current_user_id()) {
105 + return trim(wp_kses($content, [
106 + 'p' => [], 'br' => [], 'strong' => [], 'b' => [],
107 + 'em' => [], 'i' => [], 'del' => [], 's' => [], 'code' => [],
108 + 'a' => ['href' => true, 'title' => true],
109 + ]));
110 + }
111 +
112 + /** Resolve mentions and bare URLs in text without rewriting link attributes or code. */
113 + public function renderContent($content, $mentionData = [])
114 + {
115 + $parts = wp_html_split($this->sanitizeContent($content));
116 + $skipDepth = 0;
117 + foreach ($parts as &$part) {
118 + if (preg_match('~^</?(a|code)\b~i', $part)) {
119 + $skipDepth += strpos($part, '</') === 0 ? -1 : 1;
120 + $skipDepth = max(0, $skipDepth);
121 + } elseif ($skipDepth === 0 && $part !== '' && $part[0] !== '<') {
122 + $part = $this->processMentionAndLink($part, $mentionData);
123 + }
124 + }
125 + unset($part);
126 +
127 + return wp_kses_post(implode('', $parts));
128 + }
129 +
130 + private function startsWithAt($word) {
131 + return mb_strpos($word, '@') === 0;
132 + }
133 +
134 + private function isValidUrl($url)
135 + {
136 + try {
137 + if (empty($url)) {
138 + return false;
139 + }
140 +
141 + $url = trim($url);
142 +
143 + // Early return for obviously invalid formats
144 + if ($url === 'http://' || $url === 'https://') {
145 + return false;
146 + }
147 +
148 + // Handle www. URLs
149 + if (strpos($url, 'www.') === 0) {
150 + $url = 'http://' . $url;
151 + }
152 + // If it's not already a URL, make it one
153 + elseif (!preg_match('~^(?:f|ht)tps?://~i', $url)) {
154 + $url = 'https://' . $url;
155 + }
156 +
157 + $components = wp_parse_url($url);
158 +
159 + if (empty($components) || !isset($components['host'])) {
160 + return false;
161 + }
162 +
163 + // Additional validation with filter_var
164 + $isValid = filter_var($url, FILTER_VALIDATE_URL) !== false;
165 + return $isValid;
166 +
167 + } catch (\Exception $e) {
64 168 return false;
65 169 }
170 + }
66 171
67 - $oldComment = $comment->description;
68 - $comment->description = $commentData['description'];
69 - $comment->save();
70 - do_action('fluent_boards/task_comment_updated', $comment->task_id, $oldComment, $comment->description);
172 + private function extractUrls($text) {
173 + try {
174 + // More permissive URL pattern that handles international domains and various formats
175 + $urlPattern = '%\b(?:(?:https?|ftp):\/\/|www\.)[^\s<>\[\]{}"\']+'
176 + . '(?:\([^\s<>\[\]{}"\')]*\)|[^\s<>\[\]{}"\'\)])*%iu';
177 +
178 + if (preg_match_all($urlPattern, $text, $matches)) {
179 + $urls = array_filter($matches[0], function($url) {
180 + $trimmed = trim($url);
181 + return !empty($trimmed);
182 + });
183 + return array_values($urls); // Re-index array
184 + }
185 + return [];
186 + } catch (\Exception $e) {
187 + return [];
188 + }
189 + }
71 190
72 - return $comment;
191 + private function extractMentions($text) {
192 + try {
193 + // Pattern that combines zero-width delimiters with international username support
194 + $pattern = '/@\x{200B}([\p{L}\p{N}_. -@]+)\x{200C}/u';
195 +
196 + if (preg_match_all($pattern, $text, $matches)) {
197 + $mentions = array_filter($matches[1], function($mention) {
198 + $trimmed = trim($mention);
199 + return !empty($trimmed);
200 + });
201 + return array_values($mentions); // Re-index array
202 + }
203 + return [];
204 + } catch (\Exception $e) {
205 + return [];
206 + }
73 207 }
74 208
75 - public function delete($comment_id)
209 + public function processMentionAndLink($commentDescription, $mentionData = [])
76 210 {
77 - $comment = Comment::findOrFail($comment_id);
78 - $taskId = $comment->task_id;
211 + if ($commentDescription === '' || $commentDescription === null) {
212 + return '';
213 + }
79 214
215 + try {
216 + // Ensure UTF-8 encoding with error handling
217 + $commentDescription = mb_convert_encoding($commentDescription, 'UTF-8', 'auto');
218 +
219 + // Extract all URLs from the text
220 + $urls = $this->extractUrls($commentDescription);
221 + $urlReplacements = [];
222 +
223 + if (!empty($urls)) {
224 + foreach ($urls as $url) {
225 + if ($this->isValidUrl($url)) {
226 + $urlReplacements[$url] = sprintf(
227 + '<a class="fbs_link" target="_blank" rel="noopener noreferrer" href="%1$s">%1$s</a>',
228 + esc_url($url)
229 + );
230 + }
231 + }
232 + }
233 +
234 + // Process mentions
235 + $mentionedUsernames = [];
236 + if (!empty($mentionData) && is_array($mentionData)) {
237 + foreach ($mentionData as $mentionedId) {
238 + $user = get_userdata($mentionedId);
239 + if ($user) {
240 + $mentionedUsernames[$user->user_login] = [
241 + 'user_id' => $user->ID,
242 + 'display_name' => htmlspecialchars($user->display_name, ENT_QUOTES, 'UTF-8')
243 + ];
244 + }
245 + }
246 + }
247 +
248 + // Extract all mentions from the text
249 + $mentions = $this->extractMentions($commentDescription);
250 + $mentionReplacements = [];
251 +
252 + if (!empty($mentions)) {
253 + foreach ($mentions as $mention) {
254 + if (array_key_exists($mention, $mentionedUsernames)) {
255 + $mentionReplacements['@' . "\u{200B}" . $mention . "\u{200C}"] = sprintf(
256 + '<a class="fbs_mention" href="%smember/%d/tasks">%s</a>',
257 + esc_url(fluent_boards_page_url()),
258 + $mentionedUsernames[$mention]['user_id'],
259 + $mentionedUsernames[$mention]['display_name']
260 + );
261 + }
262 + }
263 + }
264 +
265 + // Apply replacements
266 + $originalText = $commentDescription;
267 +
268 + // First replace URLs (longer strings first to avoid partial replacements)
269 + if (!empty($urls)) {
270 + usort($urls, function($a, $b) {
271 + return strlen($b) - strlen($a);
272 + });
273 + foreach ($urls as $url) {
274 + if (isset($urlReplacements[$url])) {
275 + $commentDescription = str_replace($url, $urlReplacements[$url], $commentDescription);
276 + }
277 + }
278 + }
279 +
280 + // Then replace mentions
281 + if (!empty($mentionReplacements)) {
282 + foreach ($mentionReplacements as $mention => $replacement) {
283 + $commentDescription = str_replace($mention, $replacement, $commentDescription);
284 + }
285 + }
286 +
287 + return $commentDescription;
288 +
289 + } catch (\Exception $e) {
290 + return $commentDescription; // Return original text if processing fails
291 + }
292 + }
293 +
294 + public function checkIfCommentHaveLinks($comment)
295 + {
296 + if (empty($comment)) {
297 + return '';
298 + }
299 +
300 + try {
301 + // Ensure UTF-8 encoding
302 + $comment = mb_convert_encoding($comment, 'UTF-8', 'auto');
303 +
304 + // Extract all URLs from the text
305 + $urls = $this->extractUrls($comment);
306 + $hasLinks = false;
307 +
308 + // Replace URLs with links
309 + if (!empty($urls)) {
310 + foreach ($urls as $url) {
311 + if ($this->isValidUrl($url)) {
312 + $hasLinks = true;
313 + $replacement = sprintf(
314 + '<a class="fbs_link" target="_blank" rel="noopener noreferrer" href="%1$s">%1$s</a>',
315 + esc_url($url)
316 + );
317 + $comment = str_replace($url, $replacement, $comment);
318 + }
319 + }
320 + }
321 +
322 + return $hasLinks ? $comment : $comment;
323 +
324 + } catch (\Exception $e) {
325 + return $comment; // Return original text if processing fails
326 + }
327 + }
328 +
329 + /**
330 + * Retain this comment's images or claim the current user's pending uploads on its board.
331 + * Validate the complete list before changing attachments or removing omitted images.
332 + */
333 + public function attachCommentImages($comment, $imageIds)
334 + {
335 + $imageIds = $this->normalizeCommentImageIds($imageIds);
336 + $commentId = absint($comment->id);
337 + $boardId = absint($comment->board_id);
338 + $taskId = absint($comment->task_id);
339 + $userId = get_current_user_id();
340 +
341 + if (!$commentId || !$boardId || !$userId) {
342 + throw new RuntimeException(__('Invalid comment image selection.', 'fluent-boards'));
343 + }
344 +
345 + App::getInstance('db')->transaction(function () use ($imageIds, $commentId, $boardId, $taskId, $userId) {
346 + // Serialize edits to this comment and prevent concurrent claims of the same upload.
347 + Comment::withoutGlobalScopes()->where('id', $commentId)->where('board_id', $boardId)->lockForUpdate()->firstOrFail();
348 + $images = CommentImage::whereIn('id', $imageIds)
349 + ->where('object_type', Constant::COMMENT_IMAGE)
350 + ->orderBy('id')
351 + ->lockForUpdate()
352 + ->get();
353 +
354 + if ($images->count() !== count($imageIds)) {
355 + throw new RuntimeException(__('Invalid comment image selection.', 'fluent-boards'));
356 + }
357 +
358 + foreach ($images as $image) {
359 + if ((int) $image->object_id === $commentId) {
360 + continue;
361 + }
362 +
363 + if ((int) $image->object_id !== 0
364 + || !$this->commentImageScopeMatches($image, $boardId, $taskId, $userId)) {
365 + throw new RuntimeException(__('Invalid comment image selection.', 'fluent-boards'));
366 + }
367 + }
368 +
369 + foreach ($images as $image) {
370 + if ((int) $image->object_id === 0) {
371 + $image->object_id = $commentId;
372 + if (!$image->save()) {
373 + throw new RuntimeException(__('Could not attach comment image.', 'fluent-boards'));
374 + }
375 + }
376 + }
377 +
378 + $removedImages = CommentImage::where('object_id', $commentId)
379 + ->where('object_type', Constant::COMMENT_IMAGE)
380 + ->whereNotIn('id', $imageIds)
381 + ->get();
382 +
383 + foreach ($removedImages as $image) {
384 + if (!$image->delete()) {
385 + throw new RuntimeException(__('Could not remove comment image.', 'fluent-boards'));
386 + }
387 + }
388 + });
389 + }
390 +
391 + /**
392 + * Allow retained images on this comment and validate all newly supplied uploads.
393 + */
394 + public function assertCommentImagesAttachableForComment($comment, $imageIds)
395 + {
396 + $imageIds = $this->normalizeCommentImageIds($imageIds);
397 +
398 + if (empty($imageIds)) {
399 + return [];
400 + }
401 +
402 + $commentImages = CommentImage::where('object_id', $comment->id)
403 + ->where('object_type', Constant::COMMENT_IMAGE)
404 + ->get();
405 +
406 + foreach ($commentImages as $commentImage) {
407 + $key = array_search((int) $commentImage->id, $imageIds, true);
408 + if ($key !== false) {
409 + unset($imageIds[$key]);
410 + }
411 + }
412 +
413 + return $this->assertCommentImagesAttachable(
414 + array_values($imageIds),
415 + $comment->board_id,
416 + $comment->task_id
417 + );
418 + }
419 +
420 + /**
421 + * Reject the entire image list unless every upload is unbound and owned by this actor, board, and task.
422 + */
423 + public function assertCommentImagesAttachable($imageIds, $boardId, $taskId)
424 + {
425 + $imageIds = $this->normalizeCommentImageIds($imageIds);
426 +
427 + if (empty($imageIds)) {
428 + return [];
429 + }
430 +
431 + $currentUserId = get_current_user_id();
432 + if (!$currentUserId) {
433 + throw new \Exception(esc_html__('Invalid comment image attachment', 'fluent-boards'));
434 + }
435 +
436 + $attachments = CommentImage::whereIn('id', $imageIds)
437 + ->where('object_id', 0)
438 + ->where('object_type', Constant::COMMENT_IMAGE)
439 + ->get();
440 +
441 + if (count($attachments) !== count($imageIds)) {
442 + throw new \Exception(esc_html__('Invalid comment image attachment', 'fluent-boards'));
443 + }
444 +
445 + foreach ($attachments as $attachment) {
446 + if (!$this->commentImageScopeMatches($attachment, $boardId, $taskId, $currentUserId)) {
447 + throw new \Exception(esc_html__('Invalid comment image attachment', 'fluent-boards'));
448 + }
449 + }
450 +
451 + return $attachments;
452 + }
453 +
454 + /**
455 + * Fail closed for legacy uploads without recorded board, task, and uploader ownership.
456 + */
457 + private function commentImageScopeMatches($attachment, $boardId, $taskId, $userId)
458 + {
459 + $settings = is_array($attachment->settings) ? $attachment->settings : [];
460 + $scope = isset($settings['comment_image_scope']) && is_array($settings['comment_image_scope'])
461 + ? $settings['comment_image_scope']
462 + : [];
463 +
464 + return intval($scope['board_id'] ?? 0) === intval($boardId)
465 + && intval($scope['task_id'] ?? 0) === intval($taskId)
466 + && intval($scope['created_by'] ?? 0) === intval($userId);
467 + }
468 +
469 + /**
470 + * Record trusted upload ownership in attachment metadata before saving.
471 + */
472 + public function applyCommentImageScope($attachment, $boardId, $taskId, $createdBy = null)
473 + {
474 + $settings = is_array($attachment->settings) ? $attachment->settings : [];
475 + $settings['comment_image_scope'] = [
476 + 'board_id' => intval($boardId),
477 + 'task_id' => intval($taskId),
478 + 'created_by' => intval($createdBy === null ? get_current_user_id() : $createdBy),
479 + ];
480 + $attachment->settings = $settings;
481 +
482 + return $attachment;
483 + }
484 +
485 + /**
486 + * Normalize submitted image IDs and remove duplicates before validating the full list.
487 + */
488 + private function normalizeCommentImageIds($imageIds)
489 + {
490 + if (!is_array($imageIds)) {
491 + return [];
492 + }
493 +
494 + return array_values(array_filter(array_unique(array_map('intval', $imageIds))));
495 + }
496 +
497 + public function update($commentData, $comment_id, $mentionData, $boardId = null)
498 + {
499 + $comment = $boardId ? $this->findCommentOnBoard($comment_id, $boardId) : Comment::findOrFail($comment_id);
500 +
80 501 if ($comment->created_by != get_current_user_id()) {
81 502 return false;
82 503 }
83 504
84 - $commentDescription = strip_tags($comment->description);
505 + $effectiveBoardId = absint($comment->board_id ?: $boardId);
506 + $notificationService = new NotificationService();
507 + $existingMentionedIds = array_values(array_unique(array_filter(array_map('absint', (array) ($comment->settings['mentioned_id'] ?? [])))));
508 + $newMentionedIds = array_values(array_unique(array_filter(array_map('absint', (array) $mentionData))));
509 + $requestedMentionedIds = array_values(array_unique(array_merge($existingMentionedIds, $newMentionedIds)));
510 + $allMentionedIds = $notificationService->resolveBoardMentionUserIds($effectiveBoardId, $requestedMentionedIds);
85 511
86 - $deleted = $comment->delete();
512 + if (array_diff($newMentionedIds, $allMentionedIds)) {
513 + throw new \Exception(esc_html__('One or more mentioned users are not members of this board', 'fluent-boards'), 403);
514 + }
87 515
88 - if ($deleted) {
89 - $this->relatedReplyDelete($comment_id);
516 + $commentData['description'] = $this->sanitizeContent($commentData['description']);
517 + $processedDescription = $this->renderContent($commentData['description'], $allMentionedIds);
90 518
91 - $task = Task::findOrFail($taskId);
92 - $task->comments_count = $task->comments_count - 1;
93 - $task->save();
519 + $oldComment = $comment->settings['raw_description'] ?? $comment->description;
520 + $comment->description = $processedDescription;
521 +
522 + if($comment->settings != null)
523 + {
524 + $tempSettings = $comment->settings;
525 + $tempSettings['raw_description'] = $commentData['description'];
526 + $tempSettings['mentioned_id'] = $allMentionedIds;
527 + $comment->settings = $tempSettings;
528 + } else {
529 + $comment->settings = [
530 + 'raw_description' => $commentData['description'],
531 + 'mentioned_id' => $allMentionedIds
532 + ];
94 533 }
534 + $comment->save();
95 535
96 - do_action('fluent_boards/task_comment_deleted', $taskId, $commentDescription);
536 + if(!$comment->parent_id) {
537 + do_action('fluent_boards/comment_updated', $comment, $oldComment);
538 + }
539 +
540 + return $comment;
97 541 }
98 542
543 + public function delete($comment_id, $boardId = null)
544 + {
545 + $comment = $boardId ? $this->findCommentOnBoard($comment_id, $boardId) : Comment::findOrFail($comment_id);
546 +
547 + if ($comment->created_by != get_current_user_id()) {
548 + return false;
549 + }
550 +
551 + // Delete related replies first (model event will handle their images)
552 + $this->relatedReplyDelete($comment_id);
553 +
554 + // Delete the comment (model deleting event will handle images and comments_count)
555 + $comment->delete();
556 +
557 + do_action('fluent_boards/comment_deleted', $comment);
558 + }
559 +
99 560 public function relatedReplyDelete($comment_id)
100 561 {
101 562 $replies = Comment::where('parent_id', $comment_id)
102 563 ->type('reply')
@@ -101,15 +562,16 @@
101 562 $replies = Comment::where('parent_id', $comment_id)
102 563 ->type('reply')
103 564 ->get();
104 565 foreach ($replies as $reply) {
566 + // Delete reply (model deleting event will handle images)
105 567 $reply->delete();
106 568 }
107 569 }
108 570
109 - public function updateReply($replyData, $id)
571 + public function updateReply($replyData, $id, $boardId = null)
110 572 {
111 - $reply = Comment::findOrFail($id);
573 + $reply = $boardId ? $this->findCommentOnBoard($id, $boardId) : Comment::findOrFail($id);
112 574
113 575 if ($reply->created_by != get_current_user_id()) {
114 576 return false;
115 577 }
@@ -121,11 +583,11 @@
121 583
122 584 return $reply;
123 585 }
124 586
125 - public function deleteReply($id)
587 + public function deleteReply($id, $boardId = null)
126 588 {
127 - $reply = Comment::findOrFail($id);
589 + $reply = $boardId ? $this->findCommentOnBoard($id, $boardId) : Comment::findOrFail($id);
128 590 // $taskId = $reply->task_id;
129 591
130 592 if ($reply->created_by != get_current_user_id()) {
131 593 return false;
@@ -130,9 +592,75 @@
130 592 if ($reply->created_by != get_current_user_id()) {
131 593 return false;
132 594 }
133 595
596 + // Delete reply (model deleting event will handle images)
134 597 $reply->delete();
135 598
136 -// do_action('fluent_boards/task_comment_deleted', $taskId);
599 +// do_action('fluent_boards/comment_deleted', $taskId);
600 + }
601 +
602 + /**
603 + * Persist an unbound comment upload with trusted board, task, and uploader metadata.
604 + * Legacy uploads without this scope cannot be newly attached to a comment.
605 + *
606 + * @return CommentImage
607 + */
608 + public function createCommentImage($data, $boardId, $taskId = null)
609 + {
610 + /*
611 + * I will refactor this function later- within March 2024 Last Week
612 + */
613 + $initialDataData = [
614 + 'type' => 'url',
615 + 'url' => '',
616 + 'name' => '',
617 + 'size' => 0,
618 + ];
619 +
620 + $attachData = array_merge($initialDataData, $data);
621 + $UrlMeta = [];
622 + if($attachData['type'] == 'url') {
623 + $UrlMeta = RemoteUrlParser::parse($attachData['url']);
624 + }
625 + $attachment = new CommentImage();
626 + $attachment->object_id = 0;
627 + $attachment->object_type = Constant::COMMENT_IMAGE;
628 + $attachment->attachment_type = $attachData['type'];
629 + $attachment->title = $this->setTitle($attachData['type'], $attachData['name'], $UrlMeta);
630 + $attachment->file_path = $attachData['type'] != 'url' ? $attachData['file'] : null;
631 + $attachment->full_url = esc_url($attachData['url']);
632 + $attachment->file_size = $attachData['size'];
633 + $settings = $attachData['type'] == 'url' ? [
634 + 'meta' => $UrlMeta
635 + ] : [];
636 + $settings['board_id'] = absint($boardId);
637 + $attachment->settings = $settings + [
638 + Constant::ATTACHMENT_UPLOAD_BOARD_ID => absint($boardId),
639 + Constant::ATTACHMENT_UPLOAD_USER_ID => get_current_user_id(),
640 + ];
641 + $this->applyCommentImageScope($attachment, $boardId, $taskId);
642 + $attachment->driver = 'local';
643 + $attachment->save();
644 +
645 + return $attachment;
646 + }
647 +
648 + public function createPublicUrl($attachment, $boardId)
649 + {
650 + $boardId = absint($boardId);
651 +
652 + return add_query_arg([
653 + 'fbs' => 1,
654 + 'fbs_type' => 'public_url',
655 + 'fbs_comment_image' => $attachment->file_hash,
656 + ], site_url('/index.php'));
657 + }
658 +
659 + private function setTitle($type, $title, $UrlMeta)
660 + {
661 + if($type != 'url') {
662 + return sanitize_file_name($title);
663 + }
664 + return $title ?? $UrlMeta['title'] ?? '';
137 665 }
138 666 }