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/BoardService.php +100 -27 2.0.02.1.0 View file →
@@ -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 ];
@@ -164,12 +165,23 @@
164 165
165 166 return $board;
166 167 }
167 168
169 + /**
170 + * Attach a user-owned board to its creator with Board Admin preferences.
171 + *
172 + * @param Board $board
173 + * @return void
174 + */
168 175 public function setCurrentUserPreferencesOnBoardCreate($board)
169 176 {
177 + $creatorId = absint($board->created_by);
178 + if (!$creatorId) {
179 + return;
180 + }
181 +
170 182 $board->users()->attach(
171 - $board->created_by,
183 + $creatorId,
172 184 [
173 185 'object_type' => Constant::OBJECT_TYPE_BOARD_USER,
174 186 'settings' => maybe_serialize([
175 187 Constant::IS_BOARD_ADMIN => true
@@ -361,14 +373,29 @@
361 373
362 374 return $isAlreadyMember ?? false;
363 375 }
364 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 + */
365 383 public function addMembersInBoard($boardId, $memberId, $isViewerOnly = null)
366 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 +
367 393 $board = Board::find($boardId);
394 + $boardMember = User::find($memberId);
368 395
369 - if (!$board) {
370 - return false;
396 + if (!$board || !$boardMember) {
397 + return null;
371 398 }
372 399 $isAlreadyMember = $this->isAlreadyMember($boardId, $memberId);
373 400 if($isAlreadyMember) {
374 401 return false;
@@ -388,9 +415,8 @@
388 415 'settings' => maybe_serialize($settings),
389 416 'preferences' => maybe_serialize(Constant::BOARD_NOTIFICATION_TYPES)
390 417 ]
391 418 );
392 - $boardMember = User::find($memberId);
393 419 if(!$isViewerOnly) {
394 420 do_action('fluent_boards/board_member_added', $boardId, $boardMember);
395 421 } else {
396 422 do_action('fluent_boards/board_viewer_added', $boardId, $boardMember);
@@ -511,20 +537,31 @@
511 537
512 538 /**
513 539 * Change or clear the board background.
514 540 *
515 - * @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
516 546 * @return array|string
547 + * @throws \Exception
517 548 */
518 549 public function setBoardBackground($backgroundData, $board_id)
519 550 {
520 - $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 +
521 558 $oldBackground = $board->background;
522 559
523 560 if (!empty($backgroundData['reset'])) {
524 561 $board->background = '';
525 562 $board->save();
526 - do_action('fluent_boards/board_background_updated', $board_id, $oldBackground);
563 + do_action('fluent_boards/board_background_updated', $boardId, $oldBackground);
527 564
528 565 return $board->background;
529 566 }
530 567
@@ -532,26 +569,34 @@
532 569 if (!is_array($background)) {
533 570 $background = [];
534 571 }
535 572
536 - // if board background has color
537 - 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'];
538 591 $background['color'] = $backgroundData['color'];
539 592 $background['image_url'] = null;
540 593 $background['is_image'] = false;
541 594 }
542 595
543 - // if board background has image
544 - if (isset($backgroundData['image_url'])) {
545 - $background['image_url'] = $backgroundData['image_url'];
546 - $background['is_image'] = true;
547 - $background['color'] = null;
548 - }
549 - $background['id'] = $backgroundData['id'];
550 -
551 596 $board->background = $background;
552 597 $board->save();
553 - do_action('fluent_boards/board_background_updated', $board_id, $oldBackground);
598 + do_action('fluent_boards/board_background_updated', $boardId, $oldBackground);
554 599
555 600 return $board->background;
556 601 }
557 602
@@ -764,9 +809,15 @@
764 809
765 810 $this->updateRecentBoardCheckMeta();
766 811 }
767 812
768 - return Board::whereIn('id', $recentBoardIds)->excludeTemplates()->withCount('completedTasks')->with(['stages', 'users'])->get();
813 + return Board::whereIn('id', $recentBoardIds)
814 + ->whereNull('archived_at')
815 + ->excludeTemplates()
816 + ->availableInCurrentInstall()
817 + ->withCount('completedTasks')
818 + ->with(['stages', 'users'])
819 + ->get();
769 820 }
770 821
771 822 public function getRecentBoardCheckMeta($userId = null){
772 823 if (!$userId) {
@@ -893,11 +944,35 @@
893 944 ->where('key', Constant::BOARD_INVITATION)
894 945 ->get();
895 946 }
896 947
897 - 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)
898 955 {
899 - 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();
900 975 }
901 976
902 977 public function hasDataChanged($boardId, $includeArchived = false, $since = null)
903 978 {
@@ -1209,13 +1284,11 @@
1209 1284 * @return array{all: int, pinned: int, archived: int}
1210 1285 */
1211 1286 public function getBoardCounts($userId)
1212 1287 {
1213 - $baseQuery = Board::byAccessUser($userId)->excludeTemplates();
1214 -
1215 - if (!defined('FLUENT_ROADMAP')) {
1216 - $baseQuery = $baseQuery->where('type', 'to-do');
1217 - }
1288 + $baseQuery = Board::byAccessUser($userId)
1289 + ->excludeTemplates()
1290 + ->availableInCurrentInstall();
1218 1291
1219 1292 $counts = [
1220 1293 'all' => (clone $baseQuery)->whereNull('archived_at')->count(),
1221 1294 'pinned' => 0,