PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / trunk
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration vtrunk
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 1.45 All 41 releases
← All changes | app/Services/CommentService.php +383 -96 1.45trunk View file →
@@ -10,14 +10,37 @@
10 10 use FluentBoardsPro\App\Services\RemoteUrlParser;
11 11
12 12 class CommentService
13 13 {
14 - public function getComments($id, $per_page, $filter)
14 + /**
15 + * Resolve a comment only when its task belongs to the requested board.
16 + *
17 + * @param int $commentId
18 + * @param int $boardId
19 + * @return Comment
20 + * @throws \Exception
21 + */
22 + public function findCommentOnBoard($commentId, $boardId)
15 23 {
16 - $task = Task::findOrFail($id);
24 + $comment = Comment::findOrFail($commentId);
25 + (new TaskService())->findTaskOnBoard($comment->task_id, $boardId);
17 26
27 + if ($comment->board_id && (int) $comment->board_id !== absint($boardId)) {
28 + throw new \Exception(esc_html__('Comment not found', 'fluent-boards'));
29 + }
30 +
31 + return $comment;
32 + }
33 +
34 + /**
35 + * Get paginated parent comments with users, images, and replies preloaded.
36 + */
37 + public function getComments($id, $per_page, $filter, $boardId = null)
38 + {
39 + $task = $boardId ? (new TaskService())->findTaskOnBoard($id, $boardId) : Task::findOrFail($id);
40 +
18 41 $commentsQuery = $task->comments()->whereNull('parent_id')
19 - ->with(['user']);
42 + ->with(['user', 'images', 'replies.user', 'replies.images']);
20 43
21 44 if ($filter == 'oldest') {
22 45 $commentsQuery = $commentsQuery->oldest();
23 46 } else { // latest or newest
@@ -25,19 +48,17 @@
25 48 }
26 49 $comments = $commentsQuery->paginate($per_page);
27 50
28 51 foreach ($comments as $comment) {
29 - $comment->replies = $this->getReplies($comment);
30 52 $comment->replies_count = count($comment->replies);
31 - $comment->load('images');
32 53 }
33 54
34 55 return $comments;
35 56 }
36 57
37 - public function getTotal($id)
58 + public function getTotal($id, $boardId = null)
38 59 {
39 - $task = Task::findOrFail($id);
60 + $task = $boardId ? (new TaskService())->findTaskOnBoard($id, $boardId) : Task::findOrFail($id);
40 61 $totalComment = Comment::where('task_id', $task->id)
41 62 ->type('comment')
42 63 ->count();
43 64 $totalReply = Comment::where('task_id', $task->id)
@@ -52,10 +73,22 @@
52 73 $replies = Comment::where('parent_id', $comment->id)->with(['user'])->get();
53 74 return $replies;
54 75 }
55 76
56 - public function create($commentData, $id)
77 + public function create($commentData, $id, $boardId = null)
57 78 {
79 + if ($boardId) {
80 + (new TaskService())->findTaskOnBoard($id, $boardId);
81 +
82 + if (!empty($commentData['parent_id'])) {
83 + $parentComment = $this->findCommentOnBoard($commentData['parent_id'], $boardId);
84 +
85 + if ((int) $parentComment->task_id !== (int) $id) {
86 + throw new \Exception(esc_html__('Comment not found', 'fluent-boards'));
87 + }
88 + }
89 + }
90 +
58 91 $comment = Comment::create($commentData);
59 92 do_action('fluent_boards/comment_created', $comment);
60 93 return $comment;
61 94 }
@@ -60,117 +93,376 @@
60 93 return $comment;
61 94 }
62 95
63 96 private function startsWithAt($word) {
64 - return strpos($word, '@') === 0;
97 + return mb_strpos($word, '@') === 0;
65 98 }
66 99
67 - public function processMentionAndLink($commentDescription, $mentionData)
100 + private function isValidUrl($url)
68 101 {
69 - // Splitting a string by either a space (" ") or a new line ("\n")
70 - $lines = preg_split('/\R/', $commentDescription); // \R matches any kind of line break
102 + try {
103 + if (empty($url)) {
104 + return false;
105 + }
71 106
72 - $mentionedUsernames = [];
73 - foreach ($mentionData as $mentionedId) {
74 - $user = get_userdata($mentionedId);
75 - $mentionedUsernames[$user->user_login] = ['user_id' => $user->ID, 'display_name' => $user->display_name];
107 + $url = trim($url);
108 +
109 + // Early return for obviously invalid formats
110 + if ($url === 'http://' || $url === 'https://') {
111 + return false;
112 + }
113 +
114 + // Handle www. URLs
115 + if (strpos($url, 'www.') === 0) {
116 + $url = 'http://' . $url;
117 + }
118 + // If it's not already a URL, make it one
119 + elseif (!preg_match('~^(?:f|ht)tps?://~i', $url)) {
120 + $url = 'https://' . $url;
121 + }
122 +
123 + $components = wp_parse_url($url);
124 +
125 + if (empty($components) || !isset($components['host'])) {
126 + return false;
127 + }
128 +
129 + // Additional validation with filter_var
130 + $isValid = filter_var($url, FILTER_VALIDATE_URL) !== false;
131 + return $isValid;
132 +
133 + } catch (\Exception $e) {
134 + return false;
76 135 }
136 + }
77 137
78 - foreach ($lines as &$line) {
79 - $words = preg_split('/[ ]+/', $line);
80 - foreach ($words as $index => $word) {
81 - if ($this->startsWithAt($word)) {
82 - $username = substr($word, 1);
83 - if (array_key_exists($username, $mentionedUsernames)) {
84 - $words[$index] = '<a class="fbs_mention" href="' . fluent_boards_page_url() . 'member/' . $mentionedUsernames[$username]['user_id'] . '/tasks">' . $mentionedUsernames[$username]['display_name'] . '</a>';
138 + private function extractUrls($text) {
139 + try {
140 + // More permissive URL pattern that handles international domains and various formats
141 + $urlPattern = '%\b(?:(?:https?|ftp):\/\/|www\.)[^\s<>\[\]{}"\']+'
142 + . '(?:\([^\s<>\[\]{}"\')]*\)|[^\s<>\[\]{}"\'\)])*%iu';
143 +
144 + if (preg_match_all($urlPattern, $text, $matches)) {
145 + $urls = array_filter($matches[0], function($url) {
146 + $trimmed = trim($url);
147 + return !empty($trimmed);
148 + });
149 + return array_values($urls); // Re-index array
150 + }
151 + return [];
152 + } catch (\Exception $e) {
153 + return [];
154 + }
155 + }
156 +
157 + private function extractMentions($text) {
158 + try {
159 + // Pattern that combines zero-width delimiters with international username support
160 + $pattern = '/@\x{200B}([\p{L}\p{N}_. -@]+)\x{200C}/u';
161 +
162 + if (preg_match_all($pattern, $text, $matches)) {
163 + $mentions = array_filter($matches[1], function($mention) {
164 + $trimmed = trim($mention);
165 + return !empty($trimmed);
166 + });
167 + return array_values($mentions); // Re-index array
168 + }
169 + return [];
170 + } catch (\Exception $e) {
171 + return [];
172 + }
173 + }
174 +
175 + public function processMentionAndLink($commentDescription, $mentionData = [])
176 + {
177 + if (empty($commentDescription)) {
178 + return '';
179 + }
180 +
181 + try {
182 + // Ensure UTF-8 encoding with error handling
183 + $commentDescription = mb_convert_encoding($commentDescription, 'UTF-8', 'auto');
184 +
185 + // Extract all URLs from the text
186 + $urls = $this->extractUrls($commentDescription);
187 + $urlReplacements = [];
188 +
189 + if (!empty($urls)) {
190 + foreach ($urls as $url) {
191 + if ($this->isValidUrl($url)) {
192 + $urlReplacements[$url] = sprintf(
193 + '<a class="fbs_link" target="_blank" rel="noopener noreferrer" href="%1$s">%1$s</a>',
194 + esc_url($url)
195 + );
85 196 }
86 - } elseif ($this->isValidUrl(wp_kses_post($word))) {
87 - $words[$index] = '<a class="fbs_link" target="_blank" href="'. esc_url($word). '">'. esc_url($word). '</a>';
88 197 }
89 198 }
90 - // Rejoin the words in this line
91 - $line = implode(' ', $words);
92 - }
93 199
94 -// Rejoin the lines, adding back the new line character
95 - return implode("\n", $lines);
200 + // Process mentions
201 + $mentionedUsernames = [];
202 + if (!empty($mentionData) && is_array($mentionData)) {
203 + foreach ($mentionData as $mentionedId) {
204 + $user = get_userdata($mentionedId);
205 + if ($user) {
206 + $mentionedUsernames[$user->user_login] = [
207 + 'user_id' => $user->ID,
208 + 'display_name' => htmlspecialchars($user->display_name, ENT_QUOTES, 'UTF-8')
209 + ];
210 + }
211 + }
212 + }
96 213
97 - }
214 + // Extract all mentions from the text
215 + $mentions = $this->extractMentions($commentDescription);
216 + $mentionReplacements = [];
217 +
218 + if (!empty($mentions)) {
219 + foreach ($mentions as $mention) {
220 + if (array_key_exists($mention, $mentionedUsernames)) {
221 + $mentionReplacements['@' . "\u{200B}" . $mention . "\u{200C}"] = sprintf(
222 + '<a class="fbs_mention" href="%smember/%d/tasks">%s</a>',
223 + esc_url(fluent_boards_page_url()),
224 + $mentionedUsernames[$mention]['user_id'],
225 + $mentionedUsernames[$mention]['display_name']
226 + );
227 + }
228 + }
229 + }
98 230
99 - private function isValidUrl($url) {
100 - if (filter_var($url, FILTER_VALIDATE_URL) === false) {
101 - return false;
102 - }
231 + // Apply replacements
232 + $originalText = $commentDescription;
103 233
104 - // Additional validation with a regular expression
105 - $regex = "/\b(?:https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]/i";
234 + // First replace URLs (longer strings first to avoid partial replacements)
235 + if (!empty($urls)) {
236 + usort($urls, function($a, $b) {
237 + return strlen($b) - strlen($a);
238 + });
239 + foreach ($urls as $url) {
240 + if (isset($urlReplacements[$url])) {
241 + $commentDescription = str_replace($url, $urlReplacements[$url], $commentDescription);
242 + }
243 + }
244 + }
106 245
107 - return preg_match($regex, $url);
246 + // Then replace mentions
247 + if (!empty($mentionReplacements)) {
248 + foreach ($mentionReplacements as $mention => $replacement) {
249 + $commentDescription = str_replace($mention, $replacement, $commentDescription);
250 + }
251 + }
252 +
253 + return $commentDescription;
254 +
255 + } catch (\Exception $e) {
256 + return $commentDescription; // Return original text if processing fails
257 + }
108 258 }
109 259
110 260 public function checkIfCommentHaveLinks($comment)
111 261 {
112 - $lines = preg_split('/\R/', $comment); // Split by any kind of line break
113 - $commentHasLinks = false;
262 + if (empty($comment)) {
263 + return '';
264 + }
114 265
115 - foreach ($lines as &$line) {
116 - $words = preg_split('/[ ]+/', $line); // Split by spaces within each line
117 - foreach ($words as $index => $word) {
118 - $cleanWord = wp_kses_post($word);
119 - if ($this->isValidUrl($cleanWord)) {
120 - $commentHasLinks = true;
121 - $words[$index] = '<a class="fbs_link" target="_blank" href="' . esc_url($cleanWord) . '">' . esc_url($cleanWord) . '</a>';
266 + try {
267 + // Ensure UTF-8 encoding
268 + $comment = mb_convert_encoding($comment, 'UTF-8', 'auto');
269 +
270 + // Extract all URLs from the text
271 + $urls = $this->extractUrls($comment);
272 + $hasLinks = false;
273 +
274 + // Replace URLs with links
275 + if (!empty($urls)) {
276 + foreach ($urls as $url) {
277 + if ($this->isValidUrl($url)) {
278 + $hasLinks = true;
279 + $replacement = sprintf(
280 + '<a class="fbs_link" target="_blank" rel="noopener noreferrer" href="%1$s">%1$s</a>',
281 + esc_url($url)
282 + );
283 + $comment = str_replace($url, $replacement, $comment);
284 + }
122 285 }
123 286 }
124 - // Rejoin words in this line
125 - $line = implode(' ', $words);
126 - }
127 287
128 - if ($commentHasLinks) {
129 - return implode("\n", $lines); // Rejoin lines with new lines preserved
130 - } else {
131 - return $comment; // Return original comment if no links were found
288 + return $hasLinks ? $comment : $comment;
289 +
290 + } catch (\Exception $e) {
291 + return $comment; // Return original text if processing fails
132 292 }
133 293 }
134 294
295 + /**
296 + * Validate every new image before attaching uploads or removing existing images.
297 + */
135 298 public function attachCommentImages($comment, $imageIds)
136 299 {
300 + $imageIds = $this->normalizeCommentImageIds($imageIds);
137 301
138 - foreach ($imageIds as $imageId)
139 - {
140 - $attachmentObject = CommentImage::findOrFail($imageId);
141 - if($attachmentObject) {
142 - if ($attachmentObject->object_id == $comment->id && $attachmentObject->object_type == Constant::COMMENT_IMAGE) {
143 - continue;
144 - }
302 + $commentImages = CommentImage::where('object_id', $comment->id)
303 + ->where('object_type', Constant::COMMENT_IMAGE)
304 + ->get();
305 +
306 + $attachedImageIds = [];
307 + foreach ($commentImages as $commentImage) {
308 + if (in_array((int) $commentImage->id, $imageIds, true)) {
309 + $attachedImageIds[] = (int) $commentImage->id;
310 + }
311 + }
312 +
313 + $newImageIds = array_values(array_diff($imageIds, $attachedImageIds));
314 + if (!empty($newImageIds)) {
315 + $attachmentObjects = $this->assertCommentImagesAttachable(
316 + $newImageIds,
317 + $comment->board_id,
318 + $comment->task_id
319 + );
320 +
321 + foreach ($attachmentObjects as $attachmentObject) {
145 322 $attachmentObject->object_id = $comment->id;
146 323 $attachmentObject->object_type = Constant::COMMENT_IMAGE;
147 324 $attachmentObject->save();
148 325 }
149 326 }
150 - //if(in_array("banana", $imageIds))
151 - $commentImages = CommentImage::where('object_id', $comment->id)->where('object_type', Constant::COMMENT_IMAGE)->get();
152 327
153 328 foreach ($commentImages as $commentImage) {
154 - if(!in_array($commentImage->id, $imageIds)) {
329 + if (!in_array((int) $commentImage->id, $imageIds, true)) {
330 + $deletedImage = clone $commentImage;
155 331 $commentImage->delete();
332 + //do_action('fluent_boards/comment_image_deleted', $deletedImage);
156 333 }
157 334 }
158 335 }
159 336
160 - public function update($commentData, $comment_id, $mentionData)
337 + /**
338 + * Allow retained images on this comment and validate all newly supplied uploads.
339 + */
340 + public function assertCommentImagesAttachableForComment($comment, $imageIds)
161 341 {
162 - $comment = Comment::findOrFail($comment_id);
342 + $imageIds = $this->normalizeCommentImageIds($imageIds);
163 343
344 + if (empty($imageIds)) {
345 + return [];
346 + }
347 +
348 + $commentImages = CommentImage::where('object_id', $comment->id)
349 + ->where('object_type', Constant::COMMENT_IMAGE)
350 + ->get();
351 +
352 + foreach ($commentImages as $commentImage) {
353 + $key = array_search((int) $commentImage->id, $imageIds, true);
354 + if ($key !== false) {
355 + unset($imageIds[$key]);
356 + }
357 + }
358 +
359 + return $this->assertCommentImagesAttachable(
360 + array_values($imageIds),
361 + $comment->board_id,
362 + $comment->task_id
363 + );
364 + }
365 +
366 + /**
367 + * Reject the entire image list unless every upload is unbound and owned by this actor, board, and task.
368 + */
369 + public function assertCommentImagesAttachable($imageIds, $boardId, $taskId)
370 + {
371 + $imageIds = $this->normalizeCommentImageIds($imageIds);
372 +
373 + if (empty($imageIds)) {
374 + return [];
375 + }
376 +
377 + $currentUserId = get_current_user_id();
378 + if (!$currentUserId) {
379 + throw new \Exception(esc_html__('Invalid comment image attachment', 'fluent-boards'));
380 + }
381 +
382 + $attachments = CommentImage::whereIn('id', $imageIds)
383 + ->where('object_id', 0)
384 + ->where('object_type', Constant::COMMENT_IMAGE)
385 + ->get();
386 +
387 + if (count($attachments) !== count($imageIds)) {
388 + throw new \Exception(esc_html__('Invalid comment image attachment', 'fluent-boards'));
389 + }
390 +
391 + foreach ($attachments as $attachment) {
392 + if (!$this->commentImageScopeMatches($attachment, $boardId, $taskId, $currentUserId)) {
393 + throw new \Exception(esc_html__('Invalid comment image attachment', 'fluent-boards'));
394 + }
395 + }
396 +
397 + return $attachments;
398 + }
399 +
400 + /**
401 + * Fail closed for legacy uploads without recorded board, task, and uploader ownership.
402 + */
403 + private function commentImageScopeMatches($attachment, $boardId, $taskId, $userId)
404 + {
405 + $settings = is_array($attachment->settings) ? $attachment->settings : [];
406 + $scope = isset($settings['comment_image_scope']) && is_array($settings['comment_image_scope'])
407 + ? $settings['comment_image_scope']
408 + : [];
409 +
410 + return intval($scope['board_id'] ?? 0) === intval($boardId)
411 + && intval($scope['task_id'] ?? 0) === intval($taskId)
412 + && intval($scope['created_by'] ?? 0) === intval($userId);
413 + }
414 +
415 + /**
416 + * Record trusted upload ownership in attachment metadata before saving.
417 + */
418 + public function applyCommentImageScope($attachment, $boardId, $taskId, $createdBy = null)
419 + {
420 + $settings = is_array($attachment->settings) ? $attachment->settings : [];
421 + $settings['comment_image_scope'] = [
422 + 'board_id' => intval($boardId),
423 + 'task_id' => intval($taskId),
424 + 'created_by' => intval($createdBy === null ? get_current_user_id() : $createdBy),
425 + ];
426 + $attachment->settings = $settings;
427 +
428 + return $attachment;
429 + }
430 +
431 + /**
432 + * Normalize submitted image IDs and remove duplicates before validating the full list.
433 + */
434 + private function normalizeCommentImageIds($imageIds)
435 + {
436 + if (!is_array($imageIds)) {
437 + return [];
438 + }
439 +
440 + return array_values(array_filter(array_unique(array_map('intval', $imageIds))));
441 + }
442 +
443 + public function update($commentData, $comment_id, $mentionData, $boardId = null)
444 + {
445 + $comment = $boardId ? $this->findCommentOnBoard($comment_id, $boardId) : Comment::findOrFail($comment_id);
446 +
164 447 if ($comment->created_by != get_current_user_id()) {
165 448 return false;
166 449 }
167 450
168 - $allMentionedIds = array_merge($comment->settings['mentioned_id'] ?? [], $mentionData ?? []);
451 + $effectiveBoardId = absint($comment->board_id ?: $boardId);
452 + $notificationService = new NotificationService();
453 + $existingMentionedIds = array_values(array_unique(array_filter(array_map('absint', (array) ($comment->settings['mentioned_id'] ?? [])))));
454 + $newMentionedIds = array_values(array_unique(array_filter(array_map('absint', (array) $mentionData))));
455 + $requestedMentionedIds = array_values(array_unique(array_merge($existingMentionedIds, $newMentionedIds)));
456 + $allMentionedIds = $notificationService->resolveBoardMentionUserIds($effectiveBoardId, $requestedMentionedIds);
169 457
458 + if (array_diff($newMentionedIds, $allMentionedIds)) {
459 + throw new \Exception(esc_html__('One or more mentioned users are not members of this board', 'fluent-boards'), 403);
460 + }
461 +
170 462 if ($allMentionedIds) {
171 463 $processedDescription = $this->processMentionAndLink($commentData['description'], $allMentionedIds);
172 - } else {
464 + } elseif(!$allMentionedIds) {
173 465 $processedDescription = $this->checkIfCommentHaveLinks($commentData['description']);
174 466 }
175 467
176 468 $oldComment = $comment->settings['raw_description'] ?? $comment->description;
@@ -183,9 +475,9 @@
183 475 $tempSettings['mentioned_id'] = $allMentionedIds;
184 476 $comment->settings = $tempSettings;
185 477 } else {
186 478 $comment->settings = [
187 - 'raw_description' => $commentData['raw_description'],
479 + 'raw_description' => $commentData['description'],
188 480 'mentioned_id' => $allMentionedIds
189 481 ];
190 482 }
191 483 $comment->save();
@@ -196,27 +488,22 @@
196 488
197 489 return $comment;
198 490 }
199 491
200 - public function delete($comment_id)
492 + public function delete($comment_id, $boardId = null)
201 493 {
202 - $comment = Comment::findOrFail($comment_id);
203 - $taskId = $comment->task_id;
494 + $comment = $boardId ? $this->findCommentOnBoard($comment_id, $boardId) : Comment::findOrFail($comment_id);
204 495
205 496 if ($comment->created_by != get_current_user_id()) {
206 497 return false;
207 498 }
208 499
209 - $deleted = $comment->delete();
500 + // Delete related replies first (model event will handle their images)
501 + $this->relatedReplyDelete($comment_id);
502 +
503 + // Delete the comment (model deleting event will handle images and comments_count)
504 + $comment->delete();
210 505
211 - if ($deleted) {
212 - $this->relatedReplyDelete($comment_id);
213 - $comment->images()->delete();
214 - $task = Task::findOrFail($taskId);
215 - $task->comments_count = $task->comments_count - 1;
216 - $task->save();
217 - }
218 -
219 506 do_action('fluent_boards/comment_deleted', $comment);
220 507 }
221 508
222 509 public function relatedReplyDelete($comment_id)
@@ -224,15 +511,16 @@
224 511 $replies = Comment::where('parent_id', $comment_id)
225 512 ->type('reply')
226 513 ->get();
227 514 foreach ($replies as $reply) {
515 + // Delete reply (model deleting event will handle images)
228 516 $reply->delete();
229 517 }
230 518 }
231 519
232 - public function updateReply($replyData, $id)
520 + public function updateReply($replyData, $id, $boardId = null)
233 521 {
234 - $reply = Comment::findOrFail($id);
522 + $reply = $boardId ? $this->findCommentOnBoard($id, $boardId) : Comment::findOrFail($id);
235 523
236 524 if ($reply->created_by != get_current_user_id()) {
237 525 return false;
238 526 }
@@ -244,11 +532,11 @@
244 532
245 533 return $reply;
246 534 }
247 535
248 - public function deleteReply($id)
536 + public function deleteReply($id, $boardId = null)
249 537 {
250 - $reply = Comment::findOrFail($id);
538 + $reply = $boardId ? $this->findCommentOnBoard($id, $boardId) : Comment::findOrFail($id);
251 539 // $taskId = $reply->task_id;
252 540
253 541 if ($reply->created_by != get_current_user_id()) {
254 542 return false;
@@ -253,8 +541,9 @@
253 541 if ($reply->created_by != get_current_user_id()) {
254 542 return false;
255 543 }
256 544
545 + // Delete reply (model deleting event will handle images)
257 546 $reply->delete();
258 547
259 548 // do_action('fluent_boards/comment_deleted', $taskId);
260 549 }
@@ -259,18 +548,14 @@
259 548 // do_action('fluent_boards/comment_deleted', $taskId);
260 549 }
261 550
262 551 /**
263 - * Adds a task attachment to the specified task.
552 + * Persist an unbound comment upload with trusted board, task, and uploader metadata.
553 + * Legacy uploads without this scope cannot be newly attached to a comment.
264 554 *
265 - * @param int $taskId The ID of the task to which the attachment is added.
266 - * @param string $title The title of the attachment.
267 - * @param string $url The URL of the attachment.
268 - *
269 - * @return Attachment The updated list of task attachments.
270 - * @throws \Exception
555 + * @return CommentImage
271 556 */
272 - public function createCommentImage($data, $boardId)
557 + public function createCommentImage($data, $boardId, $taskId = null)
273 558 {
274 559 /*
275 560 * I will refactor this function later- within March 2024 Last Week
276 561 */
@@ -293,11 +578,14 @@
293 578 $attachment->title = $this->setTitle($attachData['type'], $attachData['name'], $UrlMeta);
294 579 $attachment->file_path = $attachData['type'] != 'url' ? $attachData['file'] : null;
295 580 $attachment->full_url = esc_url($attachData['url']);
296 581 $attachment->file_size = $attachData['size'];
297 - $attachment->settings = $attachData['type'] == 'url' ? [
582 + $settings = $attachData['type'] == 'url' ? [
298 583 'meta' => $UrlMeta
299 - ] : '';
584 + ] : [];
585 + $settings['board_id'] = absint($boardId);
586 + $attachment->settings = $settings;
587 + $this->applyCommentImageScope($attachment, $boardId, $taskId);
300 588 $attachment->driver = 'local';
301 589 $attachment->save();
302 590
303 591 return $attachment;
@@ -307,9 +595,8 @@
307 595 {
308 596 return add_query_arg([
309 597 'fbs' => 1,
310 598 'fbs_type' => 'public_url',
311 - 'fbs_bid' => $boardId,
312 599 'fbs_comment_image' => $attachment->file_hash
313 600 ], site_url('/index.php'));
314 601 }
315 602