| @@ -2,8 +2,9 @@ | ||
| 2 | 2 | |
| 3 | 3 | namespace FluentBoards\App\Services; |
| 4 | 4 | |
| 5 | 5 | use FluentBoards\App\Models\Activity; |
| 6 | +use FluentBoards\App\Models\Attachment; | |
| 6 | 7 | use FluentBoards\App\Models\Board; |
| 7 | 8 | use FluentBoards\App\Models\Comment; |
| 8 | 9 | use FluentBoards\App\Models\Folder; |
| 9 | 10 | use FluentBoards\App\Models\Label; |
| @@ -149,9 +150,9 @@ | ||
| 149 | 150 | { |
| 150 | 151 | $boardData = [ |
| 151 | 152 | 'title' => $boardData['title'], |
| 152 | 153 | 'type' => $boardData['type'] ? $boardData['type'] : 'to-do', |
| 153 | - 'description' => DescriptionMarkdownConverter::normalize($boardData['description']), | |
| 154 | + 'description' => DescriptionMarkdownConverter::normalize($boardData['description'] ?? ''), | |
| 154 | 155 | 'currency' => isset($boardData['currency']) ? $boardData['currency'] : 'USD', |
| 155 | 156 | 'background' => isset($boardData['background']) ? $boardData['background'] : '', |
| 156 | 157 | 'created_by' => isset($boardData['created_by']) ? $boardData['created_by'] : get_current_user_id() |
| 157 | 158 | ]; |
| @@ -372,14 +373,29 @@ | ||
| 372 | 373 | |
| 373 | 374 | return $isAlreadyMember ?? false; |
| 374 | 375 | } |
| 375 | 376 | |
| 377 | + /** | |
| 378 | + * Add a WordPress user to a board. | |
| 379 | + * | |
| 380 | + * @return User|false|null User on success, false for an existing relation, | |
| 381 | + * or null when the board/user does not exist. | |
| 382 | + */ | |
| 376 | 383 | public function addMembersInBoard($boardId, $memberId, $isViewerOnly = null) |
| 377 | 384 | { |
| 385 | + $boardId = intval($boardId); | |
| 386 | + $memberId = intval($memberId); | |
| 387 | + $isViewerOnly = sanitize_text_field((string)$isViewerOnly); | |
| 388 | + | |
| 389 | + if ($boardId <= 0 || $memberId <= 0) { | |
| 390 | + return null; | |
| 391 | + } | |
| 392 | + | |
| 378 | 393 | $board = Board::find($boardId); |
| 394 | + $boardMember = User::find($memberId); | |
| 379 | 395 | |
| 380 | - if (!$board) { | |
| 381 | - return false; | |
| 396 | + if (!$board || !$boardMember) { | |
| 397 | + return null; | |
| 382 | 398 | } |
| 383 | 399 | $isAlreadyMember = $this->isAlreadyMember($boardId, $memberId); |
| 384 | 400 | if($isAlreadyMember) { |
| 385 | 401 | return false; |
| @@ -399,9 +415,8 @@ | ||
| 399 | 415 | 'settings' => maybe_serialize($settings), |
| 400 | 416 | 'preferences' => maybe_serialize(Constant::BOARD_NOTIFICATION_TYPES) |
| 401 | 417 | ] |
| 402 | 418 | ); |
| 403 | - $boardMember = User::find($memberId); | |
| 404 | 419 | if(!$isViewerOnly) { |
| 405 | 420 | do_action('fluent_boards/board_member_added', $boardId, $boardMember); |
| 406 | 421 | } else { |
| 407 | 422 | do_action('fluent_boards/board_viewer_added', $boardId, $boardMember); |
| @@ -522,20 +537,31 @@ | ||
| 522 | 537 | |
| 523 | 538 | /** |
| 524 | 539 | * Change or clear the board background. |
| 525 | 540 | * |
| 526 | - * @param mixed $backgroundData | |
| 541 | + * Image attachments must belong to the target board and use the board | |
| 542 | + * background attachment type before their identifiers can be persisted. | |
| 543 | + * | |
| 544 | + * @param array $backgroundData | |
| 545 | + * @param int $board_id | |
| 527 | 546 | * @return array|string |
| 547 | + * @throws \Exception | |
| 528 | 548 | */ |
| 529 | 549 | public function setBoardBackground($backgroundData, $board_id) |
| 530 | 550 | { |
| 531 | - $board = Board::find($board_id); | |
| 551 | + $boardId = absint($board_id); | |
| 552 | + $board = Board::find($boardId); | |
| 553 | + | |
| 554 | + if (!$board) { | |
| 555 | + throw new \Exception(esc_html__('Board not found.', 'fluent-boards')); | |
| 556 | + } | |
| 557 | + | |
| 532 | 558 | $oldBackground = $board->background; |
| 533 | 559 | |
| 534 | 560 | if (!empty($backgroundData['reset'])) { |
| 535 | 561 | $board->background = ''; |
| 536 | 562 | $board->save(); |
| 537 | - do_action('fluent_boards/board_background_updated', $board_id, $oldBackground); | |
| 563 | + do_action('fluent_boards/board_background_updated', $boardId, $oldBackground); | |
| 538 | 564 | |
| 539 | 565 | return $board->background; |
| 540 | 566 | } |
| 541 | 567 | |
| @@ -543,26 +569,34 @@ | ||
| 543 | 569 | if (!is_array($background)) { |
| 544 | 570 | $background = []; |
| 545 | 571 | } |
| 546 | 572 | |
| 547 | - // if board background has color | |
| 548 | - if (isset($backgroundData['color'])) { | |
| 573 | + // Resolve image metadata from the board-owned attachment, never from the client URL. | |
| 574 | + if (isset($backgroundData['image_url'])) { | |
| 575 | + $attachmentId = absint($backgroundData['id'] ?? 0); | |
| 576 | + $attachment = Attachment::where('id', $attachmentId) | |
| 577 | + ->where('object_id', $boardId) | |
| 578 | + ->where('object_type', Constant::BOARD_BACKGROUND_IMAGE) | |
| 579 | + ->first(); | |
| 580 | + | |
| 581 | + if (!$attachment) { | |
| 582 | + throw new \Exception(esc_html__('Background image not found.', 'fluent-boards')); | |
| 583 | + } | |
| 584 | + | |
| 585 | + $background['id'] = (int) $attachment->id; | |
| 586 | + $background['image_url'] = (new CommentService())->createPublicUrl($attachment, $boardId); | |
| 587 | + $background['is_image'] = true; | |
| 588 | + $background['color'] = null; | |
| 589 | + } elseif (isset($backgroundData['color'])) { | |
| 590 | + $background['id'] = $backgroundData['id']; | |
| 549 | 591 | $background['color'] = $backgroundData['color']; |
| 550 | 592 | $background['image_url'] = null; |
| 551 | 593 | $background['is_image'] = false; |
| 552 | 594 | } |
| 553 | 595 | |
| 554 | - // if board background has image | |
| 555 | - if (isset($backgroundData['image_url'])) { | |
| 556 | - $background['image_url'] = $backgroundData['image_url']; | |
| 557 | - $background['is_image'] = true; | |
| 558 | - $background['color'] = null; | |
| 559 | - } | |
| 560 | - $background['id'] = $backgroundData['id']; | |
| 561 | - | |
| 562 | 596 | $board->background = $background; |
| 563 | 597 | $board->save(); |
| 564 | - do_action('fluent_boards/board_background_updated', $board_id, $oldBackground); | |
| 598 | + do_action('fluent_boards/board_background_updated', $boardId, $oldBackground); | |
| 565 | 599 | |
| 566 | 600 | return $board->background; |
| 567 | 601 | } |
| 568 | 602 | |
| @@ -910,11 +944,35 @@ | ||
| 910 | 944 | ->where('key', Constant::BOARD_INVITATION) |
| 911 | 945 | ->get(); |
| 912 | 946 | } |
| 913 | 947 | |
| 914 | - public function deleteInvitation($invitationId) | |
| 948 | + /** | |
| 949 | + * Delete an invitation only when it belongs to the supplied board. | |
| 950 | + * | |
| 951 | + * The optional second argument lets older Pro releases receive a controlled | |
| 952 | + * error instead of reporting a successful deletion that never happened. | |
| 953 | + */ | |
| 954 | + public function deleteInvitation($boardId, $invitationId = null) | |
| 915 | 955 | { |
| 916 | - Meta::findOrFail($invitationId)->delete(); | |
| 956 | + if ($invitationId === null) { | |
| 957 | + throw new \Exception( | |
| 958 | + __('A board ID is required to delete an invitation.', 'fluent-boards') | |
| 959 | + ); | |
| 960 | + } | |
| 961 | + | |
| 962 | + $boardId = intval($boardId); | |
| 963 | + $invitationId = intval($invitationId); | |
| 964 | + | |
| 965 | + if ($boardId <= 0 || $invitationId <= 0) { | |
| 966 | + return false; | |
| 967 | + } | |
| 968 | + | |
| 969 | + return (bool) Meta::query() | |
| 970 | + ->where('id', $invitationId) | |
| 971 | + ->where('object_id', $boardId) | |
| 972 | + ->where('object_type', Constant::OBJECT_TYPE_BOARD) | |
| 973 | + ->where('key', Constant::BOARD_INVITATION) | |
| 974 | + ->delete(); | |
| 917 | 975 | } |
| 918 | 976 | |
| 919 | 977 | public function hasDataChanged($boardId, $includeArchived = false, $since = null) |
| 920 | 978 | { |