PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.91.6
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.91.6
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 / Services / BoardService.php

BoardService.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.91.6, at app/Services/BoardService.php

1,201 lines 39.6 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\Services;
4
5 use FluentBoards\App\Models\Activity;
6 use FluentBoards\App\Models\Board;
7 use FluentBoards\App\Models\Comment;
8 use FluentBoards\App\Models\Meta;
9 use FluentBoards\App\Models\Relation;
10 use FluentBoards\App\Models\Stage;
11 use FluentBoards\App\Models\Task;
12 use FluentBoards\App\Models\TaskMeta;
13 use FluentBoards\App\Models\User;
14 use FluentBoards\App\Services\Libs\FileSystem;
15 use FluentBoardsPro\App\Models\Folder;
16
17 class BoardService
18 {
19 public function getBoardsByType($type)
20 {
21 return Board::where('type', $type)->whereNull('archived_at')->orderBy('created_at', 'ASC')->get();
22 }
23
24 public function deleteBoard($boardId)
25 {
26 $board = Board::findOrFail($boardId);
27
28 $options = null;
29 //if we need to do something before a board is deleted
30 do_action('fluent_boards/before_board_deleted', $board, $options);
31
32 //related task delete, task related relations delete
33 $allTaskIdsInBoard = $board->tasks->pluck('id');
34 $taskRelatedRelations = Relation::whereIn('object_id', $allTaskIdsInBoard);
35 $taskRelatedRelations->delete();
36 TaskMeta::whereIn('task_id', $allTaskIdsInBoard)->delete();
37
38 // Delete time tracking records for all tasks in the board
39 (new TaskService())->deleteTimeTrackingRecords($allTaskIdsInBoard->toArray());
40
41 Task::whereIn('id', $allTaskIdsInBoard)->delete();
42
43 // delete all activities
44 Activity::whereIn('object_id', $allTaskIdsInBoard)->where('object_type', Constant::ACTIVITY_TASK)->delete();
45 $board->activities()->delete();
46
47 //removing all Board Settings
48 $board->boardUserEmailNotificationSettings()->detach();
49 $board->boardUserNotificationSettings()->detach();
50 //removing all Board users
51 $board->users()->detach();
52
53 //removing add board stages
54 $board->stages()->delete();
55
56 //removing add board labels
57 $board->labels()->delete();
58
59 //removing all board comments (delete individually to fire model events and clean up images)
60 $comments = $board->comments()->get();
61 foreach ($comments as $comment) {
62 $comment->delete();
63 }
64
65 //removing add board custom fields
66 if (defined('FLUENT_BOARDS_PRO')) {
67 $board->customFields()->delete();
68 }
69
70
71 foreach ($board->notifications as $notification) {
72 $notification->users()->detach();
73 }
74 $board->notifications()->delete();
75 $board->removeBoardFromFolder();
76
77 //delete board related meta
78 $this->deleteBoardMeta($boardId);
79
80 //delete from recently viewed
81 $this->deleteFromRecentlyViewed($boardId);
82
83 //delete webhook data
84 $this->deleteWebhookData($boardId);
85
86 $board->delete();
87 FileSystem::deleteDir('board_'.$boardId);
88 // do_action('fluent_boards/board_deleted', $board);
89 }
90
91 public function fetchBoardMeta($boardId)
92 {
93 $boardMeta = Meta::where('object_id', $boardId)
94 ->where('object_type', 'board')
95 ->where('key', 'is_auth_require')
96 ->orderBy('id', 'desc')->first();
97
98 if ($boardMeta) {
99 $boardMeta->value = maybe_unserialize($boardMeta->value);
100 return $boardMeta;
101 } else {
102 $meta = new Meta();
103 $settingData = array(
104 'is_auth_require_idea_submit' => '',
105 'is_auth_require_voting_commenting' => '',
106 'is_auth_require_reaction' => '',
107 'is_allow_email_along_with_auth' => '',
108 'is_allow_unauthentication_reaction_along_with_auth' => ''
109 );
110 $meta->object_id = $boardId;
111 $meta->object_type = 'board';
112 $meta->key = 'is_auth_require';
113 $meta->value = \maybe_serialize($settingData);
114 $meta->save();
115 $meta->value = $settingData;
116 return $meta;
117 }
118 }
119
120 public function modifyAuthenticationPermission($data, $boardId)
121 {
122 $boardMeta = Meta::where('object_id', $boardId)
123 ->where('object_type', 'board')
124 ->where('key', 'is_auth_require')
125 ->orderBy('id', 'desc')->first();
126
127 if ($boardMeta) {
128 $settings = array(
129 'is_auth_require_idea_submit' => $data['is_auth_require_idea_submit'],
130 'is_auth_require_voting_commenting' => $data['is_auth_require_voting_commenting'],
131 'is_auth_require_reaction' => $data['is_auth_require_reaction'],
132 'is_allow_email_along_with_auth' => $data['is_allow_email_along_with_auth'],
133 'is_allow_unauthentication_reaction_along_with_auth' => $data['is_allow_unauthentication_reaction_along_with_auth']
134 );
135 $boardMeta->value = \maybe_serialize($settings);
136 $boardMeta->save();
137 }
138 return $boardMeta;
139 }
140
141 public function createBoard($boardData)
142 {
143 $boardData = [
144 'title' => $boardData['title'],
145 'type' => $boardData['type'] ? $boardData['type'] : 'to-do',
146 'description' => $boardData['description'],
147 'currency' => isset($boardData['currency']) ? $boardData['currency'] : 'USD',
148 'background' => isset($boardData['background']) ? $boardData['background'] : '',
149 'created_by' => isset($boardData['created_by']) ? $boardData['created_by'] : get_current_user_id()
150 ];
151
152 $boardData = apply_filters('fluent_boards/before_create_board', $boardData);
153
154 $board = Board::create($boardData);
155
156 $this->setCurrentUserPreferencesOnBoardCreate($board);
157
158 return $board;
159 }
160
161 private function setCurrentUserPreferencesOnBoardCreate($board)
162 {
163 $board->users()->attach(
164 $board->created_by,
165 [
166 'object_type' => Constant::OBJECT_TYPE_BOARD_USER,
167 'settings' => maybe_serialize([
168 Constant::IS_BOARD_ADMIN => true
169 ]),
170 'preferences' => maybe_serialize(Constant::BOARD_NOTIFICATION_TYPES)
171 ]
172 );
173 }
174
175 public function removeUserFromBoard($boardId, $userId)
176 {
177
178 $board = Board::findOrFail($boardId);
179 $user = User::findOrFail($userId);
180
181 $board->users()->detach($userId);
182 $board->boardUserNotificationSettings()->detach($userId); //removing notification settings of user in that board
183 $board->boardUserEmailNotificationSettings()->detach($userId); //removing email notification settings of user in that board
184
185 //detacing all tasks of this board from user
186 $taskIdsToDetach = $user->tasks()->where('board_id', $boardId)->get()->pluck('id');
187
188 $user->tasks()->detach($taskIdsToDetach);
189 $user->watchingTasks()->detach($taskIdsToDetach);
190
191 }
192
193 private function removeFromDefaultAssignee($boardId, $user)
194 {
195 $stages = Stage::where('board_id', $boardId)->get();
196 foreach ($stages as $stage) {
197 if (isset($stage->settings['default_task_assignees'])) {
198 if (($key = array_search($user, $stage->settings['default_task_assignees'])) !== false) {
199 unset($stage->settings['default_task_assignees'][$key]);
200 }
201 }
202 }
203 }
204
205 public function removeFromRecentlyOpened($boardId, $userId)
206 {
207 $recentlyOpened = Meta::where('object_id', $userId)
208 ->where('object_type', Constant::OBJECT_TYPE_USER)
209 ->where('key', Constant::USER_RECENT_BOARDS)
210 ->first();
211 if ($recentlyOpened) {
212 $recentBoardIds = $recentlyOpened->value;
213
214 $index = array_search($boardId, $recentBoardIds);
215 array_splice($recentBoardIds, $index, 1);
216
217 $recentlyOpened->value = $recentBoardIds;
218 $recentlyOpened->save();
219 }
220
221 }
222
223 public function updateBoard($board, $data)
224 {
225 if ($data['title']) {
226 $data['title'] = $data['title'];
227 } else {
228 throw new \Exception(esc_html__('Title cannot be empty', 'fluent-boards'));
229 }
230 if (isset($data['description'])) {
231 $data['description'] = $data['description'];
232 }
233 $board->fill($data);
234 $board->save();
235 // do_action('fluent_boards/board_updated', $board);
236 return $board;
237 }
238
239 public function defaultStages()
240 {
241 $stages = [
242 (object)[
243 'group' => 'open',
244 'label' => 'Open',
245 ],
246 (object)[
247 'group' => 'in_progress',
248 'label' => 'In Progress',
249 ],
250 (object)[
251 'group' => 'completed',
252 'label' => 'Completed',
253 ],
254 ];
255
256 return serialize($this->processStages($stages));
257 }
258
259
260 public function repositionStages($boardId, $incomingList)
261 {
262 $oldList = Stage::where('board_id', $boardId)->where('type', 'stage')->whereNull('archived_at')->orderBy('position')->pluck('id');
263
264 foreach ($incomingList as $key => $stage_id) {
265 $stage = Stage::findOrFail($stage_id);
266 $stage->moveToNewPosition($key + 1);
267 }
268 do_action('fluent_boards/board_stages_reordered', $boardId, $oldList);
269 }
270
271 public function processStages($stages)
272 {
273 $processedStages = [];
274 foreach ($stages as $stage) {
275 if (is_object($stage)) {
276 $processedStages[] = (object)[
277 'group' => Helper::snake_case($stage->slug),
278 'label' => sanitize_text_field($stage->label)
279 ];
280 } else {
281 $processedStages[] = (object)[
282 'group' => Helper::snake_case($stage['group']),
283 'label' => sanitize_text_field($stage['label'])
284 ];
285 }
286 }
287 return $processedStages;
288 }
289
290 public function archiveStage($boardId, $stage)
291 {
292 $stage->archived_at = current_time('mysql');
293 $stage->position = 0;
294 $stage->save();
295
296 do_action('fluent_boards/stage_archived', $boardId, $stage);
297 return $stage;
298 }
299
300 public function restoreStage($boardId, $stage)
301 {
302 $stageService = new StageService();
303 $lastStagePosition = $stageService->getLastPositionOfStagesOfBoard($stage->board_id);
304 $stage->archived_at = null;
305 $stage->position = $lastStagePosition ? $lastStagePosition->position + 1 : 1;
306 $stage->save();
307 do_action('fluent_boards/board_stage_restored', $boardId, $stage->title);
308 return $stage;
309 }
310
311 public function getActivities($id, $data)
312 {
313 $per_page = isset($data['per_page']) ? $data['per_page'] : 40;
314 $page = isset($data['page']) ? $data['page'] : 1;
315 return Activity::where('object_id', $id)->where('object_type', Constant::ACTIVITY_BOARD)->with(['user'])
316 ->orderBy('id', 'DESC')
317 ->paginate($per_page, ['*'], 'page', $page);
318 }
319
320 public function isAlreadyMember($boardId, $memberId)
321 {
322 $isAlreadyMember = Relation::where('object_id', $boardId)
323 ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
324 ->where('foreign_id', $memberId)->first();
325
326 return $isAlreadyMember ?? false;
327 }
328
329 public function addMembersInBoard($boardId, $memberId, $isViewerOnly = null)
330 {
331 $board = Board::find($boardId);
332
333 if (!$board) {
334 return false;
335 }
336 $isAlreadyMember = $this->isAlreadyMember($boardId, $memberId);
337 if($isAlreadyMember) {
338 return false;
339 }
340 $settings = Constant::BOARD_USER_SETTINGS;
341
342 if($isViewerOnly === 'yes') {
343 $settings = Constant::BOARD_USER_VIEWER_ONLY_SETTINGS;
344 }
345
346
347
348 $board->users()->attach(
349 $memberId,
350 [
351 'object_type' => Constant::OBJECT_TYPE_BOARD_USER,
352 'settings' => maybe_serialize($settings),
353 'preferences' => maybe_serialize(Constant::BOARD_NOTIFICATION_TYPES)
354 ]
355 );
356 $boardMember = User::find($memberId);
357 if(!$isViewerOnly) {
358 do_action('fluent_boards/board_viewer_added', $boardId, $boardMember);
359 } else {
360 do_action('fluent_boards/board_member_added', $boardId, $boardMember);
361 }
362 return $boardMember;
363 }
364
365 public function makeAdminOfBoard($boardId, $userId)
366 {
367 $boardUser = Relation::where('object_id', $boardId)
368 ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
369 ->where('foreign_id', $userId)->first();
370 $boardUser->settings = [
371 'is_admin' => true
372 ];
373 $boardUser->save();
374
375 $user = User::findOrFail($userId);
376 do_action('fluent_boards/board_admin_added', $boardId, $userId);
377 $user['is_admin'] = true;
378 $user['is_board_admin'] = true;
379 return $user;
380 }
381
382 public function removeAdminFromBoard($boardId, $userId)
383 {
384 $boardUser = Relation::where('object_id', $boardId)
385 ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
386 ->where('foreign_id', $userId)->first();
387
388 $boardUser->settings = [
389 'is_admin' => false
390 ];
391
392 $boardUser->save();
393 $user = User::findOrFail($userId);
394 do_action('fluent_boards/board_admin_removed', $boardId, $userId);
395 $user['is_admin'] = false;
396 $user['is_board_admin'] = false;
397 return $user;
398 }
399
400 public function getUsersOfBoards()
401 {
402 $userBoards = Relation::whereNotNull('board_id')
403 ->where('user_id', get_current_user_id())
404 ->where('status', 'ACTIVE')->get();
405
406 return $userBoards;
407 }
408
409 /**
410 * change board background
411 * @param mixed $backgroundData
412 * @return string
413 */
414
415 public function setBoardBackground($backgroundData, $board_id)
416 {
417 $board = Board::find($board_id);
418 $oldBackground = $board->background;
419 $background = $board->background;
420
421 // if board background has color
422 if (isset($backgroundData['color'])) {
423 $background['color'] = $backgroundData['color'];
424 $background['image_url'] = null;
425 $background['is_image'] = false;
426 }
427
428 // if board background has image
429 if (isset($backgroundData['image_url'])) {
430 $background['image_url'] = $backgroundData['image_url'];
431 $background['is_image'] = true;
432 $background['color'] = null;
433 }
434 $background['id'] = $backgroundData['id'];
435
436 $board->background = $background;
437 $board->save();
438 do_action('fluent_boards/board_background_updated', $board_id, $oldBackground);
439
440 return $board->background;
441 }
442
443
444 /**
445 * Summary of getStageTaskAvailablePositions
446 * @param mixed $board_id
447 * @param mixed $stage_slug
448 * @return array of available positions of the stage with one increased value because if the stage has 10 tasks than it will have 10 position and +1 as last position of the stage
449 */
450 public function getStageTaskAvailablePositions($board_id, $stage_id)
451 {
452 $availablePositions = Task::query()
453 ->where('board_id', $board_id)
454 ->where('parent_id', null)
455 ->where('stage_id', $stage_id)
456 ->whereNull('archived_at')
457 ->orderBy('position', 'asc')
458 ->get()
459 ->pluck('position')->toArray();
460
461 $totalPosition = count($availablePositions);
462 $availablePositions[$totalPosition] = $totalPosition + 1;
463
464 return $availablePositions;
465 }
466
467 public function getAssigneesByBoard($board_id, $search = '')
468 {
469 $assignees = [];
470 $boardUsers = [];
471 $board = Board::with('users')->find($board_id);
472
473 if ($board) {
474 if ($search) {
475 $boardUsers = $board->users->filter(
476 function ($user) use ($search) {
477 return strpos($user->display_name, $search) !== false || strpos($user->user_email, $search) !== false;
478 }
479 );
480 } else {
481 $boardUsers = $board->users;
482 }
483 };
484 foreach ($boardUsers as $user) {
485 $taskAssignee = Relation::where('foreign_id', $user->ID)->where('object_type', 'task_assignee')->exists();
486 if ($taskAssignee) {
487 $assignees[] = $user;
488 }
489 }
490 return $assignees;
491 }
492
493 private function deleteFromRecentlyViewed($boardId)
494 {
495 $recentlyOpened = $this->recentlyViewedByUserQuery()->first();
496 if ($recentlyOpened) {
497 $recentBoardIds = $recentlyOpened->value;
498 if (in_array($boardId, $recentBoardIds)) {
499 $index = array_search($boardId, $recentBoardIds);
500 unset($recentBoardIds[$index]);
501 $recentlyOpened->value = $recentBoardIds;
502 $recentlyOpened->save();
503 }
504 }
505 }
506
507 public function updateRecentBoards($boardId)
508 {
509 $userId = get_current_user_id();
510 $recentlyOpened = $this->recentlyViewedByUserQuery($userId)->first();
511 if (!$recentlyOpened) {
512 $openedBoards = [$boardId];
513 $userMeta = new Meta();
514 $userMeta->object_id = $userId;
515 $userMeta->object_type = Constant::OBJECT_TYPE_USER;
516 $userMeta->key = Constant::USER_RECENT_BOARDS;
517 $userMeta->value = $openedBoards;
518 $userMeta->save();
519 } else {
520 $recentBoardIds = $recentlyOpened->value;
521 // Ensure the value is an array
522 if (!is_array($recentBoardIds)) {
523 $recentBoardIds = [];
524 }
525
526 // Check if the board is already in the list
527 if (!in_array($boardId, $recentBoardIds)) {
528 // If there are already 3 boards, remove the last one
529 if (count($recentBoardIds) >= 3) {
530 array_pop($recentBoardIds);
531 }
532 } else {
533 // Remove the existing board id to move it to the front
534 $index = array_search($boardId, $recentBoardIds);
535 unset($recentBoardIds[$index]);
536 }
537 // Add the board to the beginning of the list
538 array_unshift($recentBoardIds, $boardId);
539
540 // Update the meta value and save it
541 $recentlyOpened->value = $recentBoardIds;
542 $recentlyOpened->save();
543 }
544 }
545
546 public function recentlyViewedByUserQuery($userId = null)
547 {
548 if (!$userId) {
549 $userId = get_current_user_id();
550 }
551
552 return Meta::query()->where('object_id', $userId)
553 ->where('object_type', Constant::OBJECT_TYPE_USER)
554 ->where('key', Constant::USER_RECENT_BOARDS);
555 }
556
557 public function getRecentBoards()
558 {
559 $userId = get_current_user_id();
560
561 $recentBoardIds = $this->recentlyViewedByUserQuery($userId)->value('value');
562
563 if (!$recentBoardIds) {
564 return [];
565 }
566
567 $currentUser = User::find($userId);
568
569 if (!PermissionManager::isAdmin($userId)){
570 $recentBoardIds = array_intersect($recentBoardIds, $currentUser->whichBoards->pluck('id')->toArray());
571 }
572
573 // This is for checking if that board is exists
574 // TODO: we will remove this code in future version
575 if (!$this->recentBoardBackwardCompatibilityCheck()) {
576 foreach ($recentBoardIds as $index => $boardId) {
577 $board = Board::find($boardId);
578 if (!$board) {
579 $this->deleteFromRecentlyViewed($boardId);
580 unset($recentBoardIds[$index]);
581 }
582 }
583
584 $this->updateRecentBoardCheckMeta();
585 }
586
587 return Board::whereIn('id', $recentBoardIds)->withCount('completedTasks')->with(['stages', 'users'])->get();
588 }
589
590 public function getRecentBoardCheckMeta($userId = null){
591 if (!$userId) {
592 $userId = get_current_user_id();
593 }
594
595 return Meta::where('object_id', $userId)
596 ->where('object_type', Constant::OBJECT_TYPE_USER)
597 ->where('key', Constant::FBS_RECENTLY_VIEWED_CHECK)
598 ->first();
599 }
600
601 private function recentBoardBackwardCompatibilityCheck() {
602 $userId = get_current_user_id();
603
604 $checkedMeta = $this->getRecentBoardCheckMeta($userId);
605
606 if (!$checkedMeta) {
607 $recentBoardCheck = new Meta();
608 $recentBoardCheck->object_id = $userId;
609 $recentBoardCheck->object_type = Constant::OBJECT_TYPE_USER;
610 $recentBoardCheck->key = Constant::FBS_RECENTLY_VIEWED_CHECK;
611 $recentBoardCheck->value = 'no';
612 $recentBoardCheck->save();
613
614 return false;
615 } else {
616 if ($checkedMeta->value == 'yes') {
617 return true;
618 } else {
619 return false;
620 }
621 }
622 }
623
624 private function updateRecentBoardCheckMeta()
625 {
626 $checkedMeta = $this->getRecentBoardCheckMeta();
627
628 if ($checkedMeta) {
629 $checkedMeta->value = 'yes';
630 $checkedMeta->save();
631 }
632 }
633
634 public function updateAssociateMember($contactId, $boardId)
635 {
636 $contactOfBoard = $this->getAssociateMember($boardId, true);
637
638 if ($contactOfBoard) {
639 $contactOfBoard->value = $contactId;
640 $contactOfBoard->save();
641 } else {
642 $contactOfBoard = new Meta();
643 $contactOfBoard->object_id = $boardId;
644 $contactOfBoard->object_type = Constant::OBJECT_TYPE_BOARD;
645 $contactOfBoard->key = Constant::BOARD_ASSOCIATED_CRM_CONTACT;
646 $contactOfBoard->value = $contactId;
647 $contactOfBoard->save();
648 }
649
650 $board = Board::findOrFail($boardId);
651 do_action('fluent_boards/contact_added_to_board', $board, $contactId);
652
653 }
654
655 public function getAssociateMember($boardId, $fromUpdateMethod = false)
656 {
657
658 $contactOfBoard = Meta::query()->where('object_id', $boardId)
659 ->where('object_type', Constant::OBJECT_TYPE_BOARD)
660 ->where('key', Constant::BOARD_ASSOCIATED_CRM_CONTACT)
661 ->first();
662
663 if ($fromUpdateMethod) {
664 return $contactOfBoard;
665 }
666
667 if (!$contactOfBoard) {
668 return null;
669 }
670
671 return Helper::crm_contact($contactOfBoard->value);
672
673 // return \FluentCrm\App\Models\Subscriber::find($contactOfBoard->value);
674 }
675
676 public function deleteAssociateMember($boardId, $contact_id)
677 {
678 $contactOfBoard = Meta::query()->where('object_id', $boardId)
679 ->where('object_type', Constant::OBJECT_TYPE_BOARD)
680 ->where('key', Constant::BOARD_ASSOCIATED_CRM_CONTACT)
681 ->where('value', $contact_id)
682 ->first();
683
684 $contactOfBoard->delete();
685 }
686
687 public function sendInvitationToBoard($boardId, $email)
688 {
689 $user = User::query()->where('user_email', $email)->first();
690
691 if ($user) {
692 return $user;
693 }
694
695 $current_user_id = get_current_user_id();
696
697 do_action('fluent_boards/send_invitation', $boardId, $email, $current_user_id);
698
699 return;
700
701 }
702
703 public function getInvitations($boardId)
704 {
705 return Meta::query()->where('object_id', $boardId)
706 ->where('object_type', Constant::OBJECT_TYPE_BOARD)
707 ->where('key', Constant::BOARD_INVITATION)
708 ->get();
709 }
710
711 public function deleteInvitation($invitationId, $boardId = null)
712 {
713 $query = Meta::query();
714 if ($boardId) {
715 $query->where('object_id', $boardId)
716 ->where('object_type', Constant::OBJECT_TYPE_BOARD);
717 }
718 $query->findOrFail($invitationId)->delete();
719 }
720
721 public function hasDataChanged($boardId)
722 {
723 $stages = [];
724 $labels = [];
725 $tasks = [];
726 $taskDeleted = false;
727 $stageDeleted = false;
728 $labelDeleted = false;
729 $oneMinuteAgoTimestamp = current_time('timestamp') - 60; // Get the current timestamp and subtract 60 seconds
730 $oneMinuteAgo = date_i18n('Y-m-d H:i:s', $oneMinuteAgoTimestamp); // Format the timestamp into the desired format in GMT
731
732 $board = Board::find($boardId);
733 if (!$board) {
734 throw new \Exception(esc_html__("Board doesn't exists", 'fluent-boards'));
735 }
736 // if stage in this board has been deleted
737 $stageDeleted = Activity::where('object_id', $boardId)->where('updated_at', '>=', $oneMinuteAgo)->where('action', 'deleted')->where('column', 'stage')->exists();
738
739 if ($stageDeleted) {
740 $stages = $board->stages;
741 } else {
742 $stages = (new StageService())->getLastOneMinuteUpdatedStages($boardId);
743 }
744
745 $labelDeleted = Activity::where('object_id', $boardId)->where('updated_at', '>=', $oneMinuteAgo)->where('action', 'deleted')->where('column', 'label')->exists();
746 if ($labelDeleted) {
747 $labels = $board->labels;
748 } else {
749 $labels = (new LabelService())->getLastOneMinuteUpdatedLabels($boardId);
750 }
751
752 // if task in this board has been deleted
753 $taskDeletedOrMovedFormBoard = Activity::where('object_id', $boardId)
754 ->where('updated_at', '>=', $oneMinuteAgo)
755 ->where(function($query) {
756 $query->where('action', 'deleted')
757 ->orWhere('action', 'moved');
758 })
759 ->where('column', 'task')
760 ->exists();
761 if ($taskDeletedOrMovedFormBoard) {
762 $tasksQuery = Task::query()
763 ->where([
764 'board_id' => $boardId,
765 'parent_id' => null,
766 'archived_at' => null
767 ])
768 ->with(['assignees', 'labels', 'watchers']);
769
770 if (!!defined('FLUENT_BOARDS_PRO_VERSION')) {
771 $tasksQuery->with('customFields');
772 }
773
774 $tasks = $tasksQuery->orderBy('due_at', 'ASC')->get();
775 } else {
776 $tasks = (new TaskService())->getLastOneMinuteUpdatedTasks($boardId);
777 }
778
779 foreach ($tasks as $task) {
780 $task->isOverdue = $task->isOverdue();
781 $task->isUpcoming = $task->upcoming();
782 $task->is_watching = $task->isWatching();
783 $task->contact = Helper::crm_contact($task->crm_contact_id);
784 $task->assignees = Helper::sanitizeUserCollections($task->assignees);
785 $task->watchers = Helper::sanitizeUserCollections($task->watchers);
786 }
787
788 $board->background = \maybe_unserialize($board->background);
789 if(!!defined('FLUENT_BOARDS_PRO_VERSION')) {
790 $board->custom_fields = $board->customFields;
791 }
792
793 return [
794 'board' => $board,
795 'stages' => $stages,
796 'labels' => $labels,
797 'tasks' => $tasks,
798 'taskDeleted' => $taskDeletedOrMovedFormBoard,
799 'stageDeleted' => $stageDeleted,
800 ];
801 }
802
803 public function getAssociatedBoards($associatedId)
804 {
805 $boardIds = Meta::query()->where('value', $associatedId)
806 ->where('object_type', Constant::OBJECT_TYPE_BOARD)
807 ->where('key', Constant::BOARD_ASSOCIATED_CRM_CONTACT)
808 ->pluck('object_id');
809
810 return Board::query()->whereIn('id', $boardIds)->with('stages', 'users')->get();
811 }
812
813 private function deleteBoardMeta($boardId)
814 {
815 Meta::where('object_id', $boardId)
816 ->where('object_type', Constant::OBJECT_TYPE_BOARD)
817 ->delete();
818 }
819
820 public function copyBoard($boardData)
821 {
822 $sourceBoard = Board::findOrFail($boardData['source_board_id']);
823 $boardData['background'] = $sourceBoard->background;
824 $boardData = apply_filters('fluent_boards/before_create_board', $boardData);
825
826 $board = Board::create($boardData);
827
828 $this->setCurrentUserPreferencesOnBoardCreate($board);
829
830 return $board;
831 }
832
833 public function getBoardReports($board_id)
834 {
835 $board = Board::findOrFail($board_id);
836 $taskQuery = Task::where('board_id', $board_id)
837 ->whereNull('parent_id')
838 ->whereNull('archived_at');
839
840 if($board->type == 'roadmap') {
841 $pendingStage = $this->getNewIdeaStage($board->id);
842 return $this->getIdeaReports($taskQuery, $pendingStage);
843 } else {
844 return $this->getTaskReports($taskQuery);
845 }
846 }
847
848 private function getNewIdeaStage($boardId)
849 {
850 return Stage::where('board_id', $boardId)
851 ->where('type', 'stage')
852 ->where('archived_at', null)
853 ->orderBy('position', 'ASC')
854 ->first();
855 }
856
857 public function getAllBoardReports(){
858 $userId = get_current_user_id();
859
860 $taskQuery = Task::whereNull('parent_id')
861 ->whereNull('archived_at')
862 ->whereHas('board', function ($query) {
863 $query->where('type', 'to-do');
864 });
865
866 if (!PermissionManager::isAdmin($userId))
867 {
868 $currentUser = User::find($userId);
869 $relatedBoardIds = $currentUser->whichBoards->where('type', 'to-do')->pluck('id');
870 $taskQuery->whereIn('board_id', $relatedBoardIds);
871 }
872
873 return $this->getTaskReports($taskQuery);
874 }
875
876 private function getTaskReports($taskQuery)
877 {
878 $totalTasksQuery = clone $taskQuery;
879 $completedTaskQuery = clone $taskQuery;
880 $openTaskQuery = clone $taskQuery;
881 $overDueTaskQuery = clone $taskQuery;
882
883 $completedTaskCount = $completedTaskQuery->where('status', 'closed')->count();
884 $openTaskCount = $openTaskQuery->where('status', 'open')->count();
885 $overDueTasks = $overDueTaskQuery->overdue(true)->count();
886 $totalTasks = $totalTasksQuery->count();
887
888 $taskQuery->where('status', 'open');
889
890 $highQuery = clone $taskQuery;
891 $mediumQuery = clone $taskQuery;
892 $lowQuery = clone $taskQuery;
893
894 $high = $highQuery->where('priority', 'high')->count();
895 $low = $mediumQuery->where('priority', 'low')->count();
896 $medium = $lowQuery->where('priority', 'medium')->count();
897
898 $reportData = [
899 'completion' => [
900 'completed' => $completedTaskCount,
901 'incomplete' => $openTaskCount,
902 'overdue' => $overDueTasks,
903 'total' => $totalTasks
904 ],
905 'priority' => [
906 'high' => $high,
907 'medium' => $medium,
908 'low' => $low
909 ]
910
911 ];
912 return $reportData;
913 }
914
915 private function getIdeaReports($taskQuery, $pendingStage)
916 {
917 $pendingIdeaQuery = clone $taskQuery;
918 $completedIdeaQuery = clone $taskQuery;
919 $openIdeaQueryPage = clone $taskQuery;
920 $openIdeaQueryWeb = clone $taskQuery;
921
922 $pendingIdeaCount = $pendingIdeaQuery->where('status', 'open')->where('stage_id', $pendingStage->id)->count();
923 $completedIdeaCount = $completedIdeaQuery->where('status', 'closed')->count();
924 $openIdeaCountPage = $openIdeaQueryPage->where('status', 'open')->where('source', 'page')->count();
925 $openIdeaCountWeb = $openIdeaQueryWeb->where('status', 'open')->where('source', 'web')->count();
926 $totalIdeas = $openIdeaCountPage + $openIdeaCountWeb;
927
928 $taskQuery->where('status', 'open');
929
930 $highQuery = clone $taskQuery;
931 $mediumQuery = clone $taskQuery;
932 $lowQuery = clone $taskQuery;
933
934 $high = $highQuery->where('priority', 'high')->count();
935 $low = $mediumQuery->where('priority', 'low')->count();
936 $medium = $lowQuery->where('priority', 'medium')->count();
937
938 $reportData = [
939 'completion' => [
940 'pending' => $pendingIdeaCount,
941 'completed' => $completedIdeaCount,
942 'ideaFromPage' => $openIdeaCountPage,
943 'total' => $totalIdeas
944 ],
945 'priority' => [
946 'high' => $high,
947 'medium' => $medium,
948 'low' => $low
949 ]
950 ];
951 return $reportData;
952 }
953
954 public function getStageWiseBoardReports($board_id)
955 {
956 $stages = Stage::where('board_id', $board_id)
957 ->where('type', 'stage')
958 ->whereNull('archived_at')
959 ->get();
960
961 foreach ($stages as $stage) {
962 $completedTaskCount = Task::where('stage_id', $stage->id)
963 ->where('status', 'closed')
964 ->count();
965
966 $openTaskCount = Task::where('stage_id', $stage->id)
967 ->whereNull('due_at')
968 ->where('status', 'open')
969 ->count();
970
971 $overDue = Task::where('stage_id', $stage->id)
972 ->whereNotNull('due_at')
973 ->where('status', 'open')
974 ->overdue(true)
975 ->count();
976
977 $stage->report = [
978 'completed' => $completedTaskCount,
979 'incomplete' => $openTaskCount,
980 'overdue' => $overDue
981 ];
982 }
983
984 return $stages;
985 }
986
987 public function archiveBoard($boardId)
988 {
989 $board = Board::findOrFail($boardId);
990 $board->archived_at = current_time('mysql');
991 $board->save();
992
993 do_action('fluent_boards/board_archived', $board);
994 return $board;
995 }
996
997 public function restoreBoard($boardId)
998 {
999 $board = Board::findOrFail($boardId);
1000 $board->archived_at = null;
1001 $board->save();
1002
1003 do_action('fluent_boards/board_restored', $board);
1004 return $board;
1005 }
1006
1007 public function makeMember($boardId, $userId)
1008 {
1009 $boardUser = Relation::where('object_id', $boardId)
1010 ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
1011 ->where('foreign_id', $userId)->first();
1012
1013 $boardUser->settings = [
1014 'is_admin' => false,
1015 'is_viewer_only' => false
1016 ];
1017
1018 $boardUser->save();
1019 $user = User::findOrFail($userId);
1020 do_action('fluent_boards/board_member_added', $boardId, $boardUser);
1021 $user['is_admin'] = false;
1022 $user['is_board_admin'] = false;
1023 return $user;
1024 }
1025
1026 public function makeViewer($boardId, $userId)
1027 {
1028 $boardUser = Relation::where('object_id', $boardId)
1029 ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
1030 ->where('foreign_id', $userId)->first();
1031
1032 $boardUser->settings = [
1033 'is_admin' => false,
1034 'is_viewer_only' => true
1035 ];
1036
1037 $boardUser->save();
1038 $user = User::findOrFail($userId);
1039 do_action('fluent_boards/board_viewer_added', $boardId, $boardUser);
1040 $user['is_admin'] = false;
1041 $user['is_board_admin'] = false;
1042 return $user;
1043 }
1044
1045 private function getUserWisePinnedBoards()
1046 {
1047 $userId = get_current_user_id();
1048
1049 $pinnedBoardMeta = Meta::query()->where('object_id', $userId)
1050 ->where('object_type', Constant::OBJECT_TYPE_USER)
1051 ->where('key', Constant::USER_PINNED_BOARDS)
1052 ->first();
1053
1054 return $pinnedBoardMeta;
1055 }
1056
1057 public function getPinnedBoards()
1058 {
1059 $pinnedBoardMeta = $this->getUserWisePinnedBoards();
1060
1061 if (!$pinnedBoardMeta) {
1062 return [];
1063 } else {
1064 $ids = $pinnedBoardMeta->value;
1065
1066 // Convert to array of integers
1067 $intIds = array_map('intval', $ids);
1068
1069 return Board::whereIn('id', $intIds)->whereNull('archived_at')->get();
1070 }
1071 }
1072
1073 public function pinBoard($boardId)
1074 {
1075 $pinnedBoardMeta = $this->getUserWisePinnedBoards();
1076
1077 if ($pinnedBoardMeta) {
1078 $currentPinnedBoards = $pinnedBoardMeta->value;
1079 if (!in_array($boardId, $currentPinnedBoards)) {
1080 $currentPinnedBoards[] = $boardId;
1081 $pinnedBoardMeta->value = $currentPinnedBoards;
1082 $pinnedBoardMeta->save();
1083 }
1084 } else {
1085 // Create an empty array
1086 $boardIds = [];
1087 $boardIds[] = $boardId;
1088
1089 $meta = new Meta();
1090 $meta->object_id = get_current_user_id();
1091 $meta->object_type = Constant::OBJECT_TYPE_USER;
1092 $meta->key = Constant::USER_PINNED_BOARDS;
1093 $meta->value = $boardIds;
1094 $meta->save();
1095 }
1096 }
1097
1098 /**
1099 * @param $boardId
1100 * @return bool
1101 */
1102 public function unpinBoard($boardId)
1103 {
1104 $pinnedBoardMeta = $this->getUserWisePinnedBoards();
1105
1106 if (!$pinnedBoardMeta) {
1107 return false;
1108 }
1109
1110 $currentPinnedBoards = $pinnedBoardMeta->value;
1111 if (in_array($boardId, $currentPinnedBoards)) {
1112 $index = array_search($boardId, $currentPinnedBoards);
1113 array_splice($currentPinnedBoards, $index, 1);
1114 $pinnedBoardMeta->value = $currentPinnedBoards;
1115 $pinnedBoardMeta->save();
1116 return true;
1117 }
1118
1119 return false;
1120 }
1121
1122 /**
1123 * @param $boardId
1124 * @return bool
1125 * If board id is in user's current pinned boards list
1126 */
1127 public function isPinned($boardId)
1128 {
1129 $pinnedBoardMeta = $this->getUserWisePinnedBoards();
1130
1131 if (!$pinnedBoardMeta) {
1132 return false;
1133 }
1134
1135 $currentPinnedBoards = $pinnedBoardMeta->value;
1136 if (in_array($boardId, $currentPinnedBoards)) {
1137 return true;
1138 }
1139
1140 return false;
1141 }
1142
1143 public function getBoardFolder($boardId)
1144 {
1145 $relation = Relation::where('object_type', Constant::OBJECT_TYPE_FOLDER_BOARD)
1146 ->where('foreign_id', $boardId)
1147 ->first();
1148
1149 if (!$relation) {
1150 return null;
1151 }
1152
1153 return Folder::findOrFail($relation->object_id);
1154 }
1155
1156 public function deleteWebhookData($boardId)
1157 {
1158 $outgoingRelations = Relation::where('object_type', 'outgoing_webhook_board')
1159 ->where('foreign_id', $boardId)
1160 ->get();
1161
1162 foreach ($outgoingRelations as $relation) {
1163 $webhookMetaId = (int) $relation->object_id;
1164
1165 $linkedCount = Relation::where('object_type', 'outgoing_webhook_board')
1166 ->where('object_id', $webhookMetaId)
1167 ->count();
1168
1169 if ($linkedCount === 1) {
1170 Meta::where('id', $webhookMetaId)
1171 ->where('object_type', 'outgoing_webhook')
1172 ->delete();
1173 } else if ($linkedCount > 1) {
1174 $meta = Meta::find($webhookMetaId);
1175 if ($meta && $meta->object_type === 'outgoing_webhook') {
1176 $value = $meta->value;
1177
1178 if (isset($value['board_id'])) {
1179 $boards = $value['board_id'];
1180
1181 if (is_array($boards)) {
1182 $boards = array_values(array_filter($boards, function ($id) use ($boardId) {
1183 return intval($id) !== intval($boardId);
1184 }));
1185 $value['board_id'] = $boards;
1186 } else {
1187 if ($boards !== null && intval($boards) === intval($boardId)) {
1188 $value['board_id'] = [];
1189 }
1190 }
1191
1192 $meta->value = $value;
1193 $meta->save();
1194 }
1195 }
1196 }
1197 $relation->delete();
1198 }
1199 }
1200 }
1201