PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.35
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.35
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
fluent-boards / app / Services / TaskService.php

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

923 lines 31.0 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\Attachment;
6 use FluentBoards\App\Models\Comment;
7 use FluentBoards\App\Models\NotificationUser;
8 use FluentBoards\App\Models\TaskImage;
9 use FluentBoards\App\Services\Constant;
10 use FluentBoards\App\Models\Stage;
11 use FluentBoards\App\Models\Task;
12 use FluentBoards\App\Models\Board;
13 use FluentBoards\App\Models\TaskMeta;
14 use FluentBoards\App\Models\Activity;
15 use FluentBoards\App\Models\BoardTerm;
16 use FluentBoards\Framework\Support\Arr;
17 use FluentBoardsPro\App\Modules\TimeTracking\TimeTrackingHelper;
18 use FluentBoardsPro\App\Services\ProTaskService;
19 use FluentBoardsPro\App\Services\RemoteUrlParser;
20 use FluentRoadmap\App\Models\IdeaReaction;
21
22 class TaskService
23 {
24 public function createTask($data, $boardId)
25 {
26 $board = Board::select('id', 'type')->find($boardId);
27
28 if (!$board) {
29 throw new \Exception(__("Board doesn't exists", 'fluent-boards'));
30 }
31
32 $stage = Stage::find($data['stage_id']);
33 if (!$stage) {
34 throw new \Exception(__("Stage doesn't exists", 'fluent-boards'));
35 }
36
37 $data['status'] = $stage->defaultTaskStatus();
38
39 if ($board->type == 'roadmap') {
40 $current_user = wp_get_current_user();
41 $settingData = array(
42 'integration_type' => 'feature',
43 'logo' => '',
44 'author' => [
45 'email' => $current_user->user_email // email of who posted this feature
46 ],
47 );
48 $data['settings'] = $settingData;
49 $data['type'] = 'roadmap';
50 }
51
52 $providerPosition = Arr::get($data, 'position');
53
54 $data['position'] = $this->getLastPositionOfTasks($stage->id);
55
56 $data['board_id'] = $boardId;
57
58 $data = array_filter($data);
59 $task = (new Task())->createTask($data);
60
61 $this->manageDefaultAssignees($task, $stage->id);
62
63 if (isset($data['is_template']) && $data['is_template'] == 'yes') {
64 $task->updateMeta(Constant::IS_TASK_TEMPLATE, $data['is_template']);
65 }
66
67 if ($providerPosition) {
68 $task->moveToNewPosition($providerPosition);
69 }
70
71 // $this->taskCreatedAction($task);
72 $this->loadWithRelations($task, ['assignees', 'labels', 'board']);
73
74 return $task;
75 }
76
77 public function loadWithRelations($task, $relations)
78 {
79 if (!is_array($relations)) {
80 return $task;
81 }
82 $task->load($relations); // $relations = ['assignees', 'board'] in this case
83 $task->isOverdue = $task->isOverdue();
84
85 return $task;
86 }
87
88 public function getTasksForBoards($filters = ['overdue', 'upcoming'], $limit = 5, $task_ids = [])
89 {
90 $overDue = $this->getTasksForBoardsByCategory('overdue', $limit, $task_ids);
91 $completed = $this->getTasksForBoardsByCategory('completed', $limit, $task_ids);
92 $upcoming = $this->getTasksForBoardsByCategory('upcoming', $limit, $task_ids);
93 $others = $this->getTasksForBoardsByCategory('others', $limit, $task_ids);
94
95 return [
96 'overdue' => $overDue ?? [],
97 'upcoming' => $upcoming ?? [],
98 'completed' => $completed ?? [],
99 'others' => $others ?? []
100 ];
101 }
102
103 public function getTasksForBoardsByCategory($category, $limit, $taskIds)
104 {
105 unset($taskQuery);
106 $taskQuery = Task::whereIn('id', $taskIds)
107 ->with(['assignees', 'board', 'stage'])
108 ->whereNull('archived_at')
109 ->where('parent_id', null)
110 ->orderBy('due_at', 'ASC');
111
112 if ('overdue' == $category) {
113 $taskQuery->overdue();
114 } elseif ('upcoming' == $category) {
115 $taskQuery->upcoming();
116 } elseif ('others' == $category) {
117 $taskQuery->whereNull('due_at');
118 } elseif ('completed' == $category) {
119 $taskQuery->where('status', 'closed');
120 } else {
121 return [];
122 }
123
124 $tasks = $taskQuery->take($limit)->get();
125
126 return $tasks->toArray();
127 }
128
129 /*
130 * TODO: Refactor this function - For me.
131 */
132 public function updateTaskProperty($col, $value, $task)
133 {
134 $oldTask = clone $task; // normal assigning won't work here. because objects are passed by reference in php
135 $validColumns = [
136 'board_id',
137 'task_type',
138 'reminder_type',
139 'remind_at',
140 'log_minutes',
141 'settings'
142 ];
143
144 if (in_array($col, $validColumns) && $task->{$col} != $value) {
145 $task->{$col} = $value;
146 $task->save();
147 // do_action('fluent_boards/task_prop_changed', $col, $task, $oldTask);
148 } elseif ('assignees' == $col) {
149 if (is_array($value)) {
150 foreach ($value as $id) {
151 $this->updateAssignee($id, $task);
152 }
153 } else {
154 $this->updateAssignee($value, $task);
155 }
156
157 } elseif ('crm_contact_id' == $col) {
158 $this->updateAssociate($value, $task);
159 } elseif ('archived_at' == $col) {
160 $this->updateArchive($value, $task);
161 } elseif ('status' == $col) {
162 $this->updateStatus($value, $task);
163 } elseif ('parent_id' == $col) {
164 $this->updateParent($value, $task);
165 } elseif ('title' == $col) {
166 $this->updateTitle($col, $value, $task, $oldTask);
167 } elseif ('description' == $col) {
168 $this->updateDescription($col, $value, $task, $oldTask);
169 } elseif ($col == 'due_at') {
170 $this->updateDueDate($value, $task);
171 } elseif ($col == 'started_at') {
172 $this->updateStartedDate($value, $task);
173 } elseif ($col == 'priority') {
174 $this->updatePriority($value, $task);
175 } elseif ($col == 'is_watching') {
176 $this->updateObservationOfCurrentUser($value, $task);
177 } elseif ($col == 'last_completed_at') {
178 $isClosed = $value == 'true' || $value === true;
179 if ($isClosed) {
180 $task = $task->close();
181 } else {
182 $task = $task->reopen();
183 }
184 $task->save();
185 } elseif ($col == 'attachment_count') {
186 $settings = $task->settings;
187 $settings['attachment_count'] = $task->attachments()->count();
188 $task->settings = $settings;
189 $task->save();
190 } elseif ($col == 'subtask_count') {
191 $settings = $task->settings;
192 $subtasksCount = Task::where('parent_id', $task->id)->count();
193 $settings['subtask_count'] = $subtasksCount;
194 $task->settings = $settings;
195 $task->save();
196 } elseif ($col == 'is_template') {
197 if (defined('FLUENT_BOARDS_PRO')) {
198 $task->updateMeta(Constant::IS_TASK_TEMPLATE, $value);
199 }
200 }
201
202 return $task;
203 }
204
205 public function updateAssignee($payloadAssigneeId, $task)
206 {
207 $operation = $task->addOrRemoveAssignee($payloadAssigneeId);
208 $task->load('assignees');
209 $task->updated_at = current_time('mysql');
210
211 $task->save();
212
213 if ($operation == 'added') {
214 if ((new NotificationService())->checkIfEmailEnable($payloadAssigneeId, Constant::BOARD_EMAIL_TASK_ASSIGN, $task->board_id)) {
215 $this->sendMailAfterTaskModify('add_assignee', $payloadAssigneeId, $task->id);
216 }
217 // $assigneeIdsToSendEmail = $this->filterAssigneeToSendEmail($task, $idArray, Constant::BOARD_EMAIL_TASK_ASSIGN);
218 // $this->sendMailAfterAddAssignees($assigneeIdsToSendEmail, $task->id);
219 do_action('fluent_boards/task_assignee_added', $task, $payloadAssigneeId);
220 if($payloadAssigneeId != get_current_user_id()){
221 do_action('fluent_boards/assign_another_user', $task, $payloadAssigneeId);
222 }
223 } else {
224 if ((new NotificationService())->checkIfEmailEnable($payloadAssigneeId, Constant::BOARD_EMAIL_REMOVE_FROM_TASK, $task->board_id)) {
225 $this->sendMailAfterTaskModify('remove_assignee', $payloadAssigneeId, $task->id);
226 }
227 do_action('fluent_boards/task_assignee_removed', $task, $payloadAssigneeId);
228 }
229
230 }
231
232 // public function filterAssigneeToSendEmail($task, $newAssigneeIds, $purpose)
233 // {
234 // $toSendEmail = array();
235 // foreach ($newAssigneeIds as $assigneeId) {
236 // if ((new NotificationService())->checkIfEmailEnabled($task->board_id, $assigneeId, $purpose)) {
237 // $toSendEmail[] = $assigneeId;
238 // }
239 // }
240 // return $toSendEmail;
241 // }
242
243 // public function defaultWatchingTaskByNewUsers($task, $newIds)
244 // {
245 // foreach ($newIds as $newId) {
246 // if (!$task->watchers->contains($newId)) {
247 // $task->watchers()->attach(
248 // $newId,
249 // [
250 // 'object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH,
251 // ]
252 // );
253 // }
254 // }
255 // }
256
257 // public function checkIfAnybodyRemovedFromTask($newAssigneeIds, $oldAssigneeIds, $task)
258 // {
259 // $removedAssignees = array_diff($oldAssigneeIds, $newAssigneeIds);
260 // $this->sendMailAfterTaskModify('removed_from_task', $removedAssignees, $task->id);
261 // dd($removedAssignees);
262 // }
263
264 private function updateAssociate($value, $task)
265 {
266 // if task has no crm contact and got value null then return current task
267 if (($task->crm_contact_id == null || $task->crm_contact_id == 0) && $value == null) {
268 return $task;
269 }
270
271 $oldAssociateId = $task->crm_contact_id;
272 $task->crm_contact_id = $value;
273 $task->save();
274 $task->contact = Task::lead_contact($task->crm_contact_id);
275 do_action('fluent_boards/contact_added_to_task', $task);
276 do_action('fluent_boards/associate_user_add_change_remove_activity', $oldAssociateId, $task->crm_contact_id, $task->id);
277 }
278
279 private function updateArchive($value, $task)
280 {
281 if ($value != null) {
282 $task->position = 0;
283 } else {
284 $task->moveToNewPosition(1);
285 }
286 $task->archived_at = $value == null ? null : current_time('mysql');
287 $task->save();
288 do_action('fluent_boards/task_archived', $task);
289 $wathersToSendEmail = (new NotificationService())->filterAssigneeToSendEmail($task->id, Constant::BOARD_EMAIL_TASK_ARCHIVE);
290 $this->sendMailAfterTaskModify('task_archived', $wathersToSendEmail, $task->id);
291 }
292
293 private function updateStatus($value, $task)
294 {
295 if ($value == 'closed') {
296 $task = $task->close();
297 } else {
298 $task = $task->reopen();
299 }
300
301 do_action('fluent_boards/task_completed_activity', $task, $value);
302 }
303
304 private function updateParent($value, $task)
305 {
306 $task->parent_id = $value;
307 $task->save();
308 }
309
310 private function updateTitle($col, $value, $task, $oldTask)
311 {
312 $task->title = $value;
313 $task->save();
314 do_action('fluent_boards/task_content_updated', $task, $col, $oldTask);
315 }
316
317 private function updateDescription($col, $value, $task, $oldTask)
318 {
319 $task->description = $value;
320 $task->save();
321 do_action('fluent_boards/task_content_updated', $task, $col, $oldTask);
322 }
323
324 private function updateDueDate($value, $task)
325 {
326 $oldValue = $task->due_at;
327 $value = $this->filterNullDate($value);
328 $task->due_at = $value;
329 $task->save();
330
331 $task = $task->reopen();
332
333 do_action('fluent_boards/task_due_date_changed', $task, $oldValue);
334
335 $wathersToSendEmail = (new NotificationService())->filterAssigneeToSendEmail($task->id, Constant::BOARD_EMAIL_DUE_DATE_CHANGE);
336 $this->sendMailAfterTaskModify('due_date_update', $wathersToSendEmail, $task->id);
337 }
338
339 private function updateStartedDate($value, $task)
340 {
341 $oldValue = $task->started_at;
342 $value = $this->filterNullDate($value);
343 $task->started_at = $value;
344 $task->save();
345
346 do_action('fluent_boards/task_start_date_changed', $task, $oldValue);
347 }
348
349 private function updatePriority($value, $task)
350 {
351 $oldPriority = $task->priority;
352 $task->priority = $value;
353 $task->save();
354 do_action('fluent_boards/task_priority_changed', $task, $oldPriority);
355 }
356
357 public function updateObservationOfCurrentUser($value, $task)
358 {
359 $currentUserId = get_current_user_id();
360
361 if ($value == 'stop') {
362 $task->watchers()->detach($currentUserId);
363 } else {
364 $task->watchers()->syncWithoutDetaching([$currentUserId => ['object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH]]);
365 }
366 $task->updated_at = current_time('mysql');
367 $task->save();
368
369 if ($value == 'stop') {
370 $task->is_watching = false;
371 } else {
372 $task->is_watching = true;
373 }
374 }
375
376 public function taskCoverPhotoUpdate($taskId, $imagePath)
377 {
378 $task = Task::find($taskId);
379 if (!$task) {
380 return null;
381 }
382
383 $settings = unserialize($task->settings);
384
385 $settings['logo'] = $imagePath;
386 $task->settings = serialize($settings);
387 $task->save();
388
389 return $task;
390 }
391
392 public function taskStatusUpdate($taskId, $integrationType)
393 {
394 $task = Task::find($taskId);
395 if (!$task) {
396 return null;
397 }
398
399 $settings = $task->settings;
400 $settings['integration_type'] = $integrationType;
401 $task->settings = serialize($settings);
402 $task->save();
403
404 return $task;
405 }
406
407 public function assignYourselfInTask($boardId, $taskId)
408 {
409 $task = Task::find($taskId);
410 $authUserId = get_current_user_id();
411
412 $boardService = new BoardService();
413 if (!$boardService->isAlreadyMember($boardId, $authUserId)) {
414 $boardService->addMembersInBoard($boardId, $authUserId);
415 }
416
417 $task->addOrRemoveAssignee($authUserId);
418 do_action('fluent_boards/task_assignee_added', $task, $authUserId);
419 // when user assign himself then he will be watching that task
420 $task->watchers()->syncWithoutDetaching([$authUserId => ['object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH]]);
421
422 $task->load('assignees');
423
424 return $task;
425 }
426
427 public function detachYourselfFromTask($boardId, $taskId)
428 {
429 $task = Task::find($taskId);
430 $authUser = get_current_user_id();
431 $task->addOrRemoveAssignee($authUser);
432 do_action('fluent_boards/task_assignee_removed', $task, $authUser);
433
434 $task->load('assignees');
435
436 return $task;
437 }
438
439 public function deleteTask($task)
440 {
441 $deleted = $task->delete();
442
443 if ($deleted) {
444
445 //task assignees watchers removed
446 $task->watchers()->detach();
447 $task->assignees()->detach();
448
449 //removing all task related notifications
450 $notificationIds = $task->notifications->pluck('id');
451 $task->notifications()->delete();
452 NotificationUser::whereIn('notification_id', $notificationIds)->delete();
453
454 //task labels removed
455 $task->labels()->detach();
456
457 //task custom field value
458 $task->customFields()->detach();
459
460 do_action('fluent_boards/task_deleted', $task);
461 TaskMeta::where('task_id', $task->id)->delete();
462 }
463 }
464
465 public function filterNullDate($date)
466 {
467 if ('0000-00-00 00:00:00' == $date || false === strtotime($date)) {
468 return null;
469 }
470 return $date;
471 }
472
473 // this is invoked when task is moved to another board
474
475 /**
476 * @throws \Exception
477 */
478 public function changeBoardByTask($task, $targetBoardId)
479 {
480 if ($task->board_id == $targetBoardId) {
481 return $task;
482 }
483
484 $oldBoard = Board::find($task->board_id);
485
486 $newBoard = Board::find($targetBoardId);
487 if (!$newBoard) {
488 throw new \Exception('Invalid board id', 400);
489 }
490 $task->board_id = $targetBoardId;
491 $task->save();
492 //delete labels of that task because labels have board dependencies
493 $task->labels()->detach();
494
495 do_action('fluent_boards/task_moved_from_board', $task, $oldBoard, $newBoard);
496
497 return $task;
498 }
499
500
501 public function getIdeaVoteStatistics($taskId)
502 {
503 $reactionTypes = [
504 [
505 'label' => 'Upvote',
506 'type' => 'upvote'
507 ],
508 [
509 'label' => 'Downvote',
510 'type' => 'downvote'
511 ]
512 ];
513
514 $reactionCounts = [];
515
516 foreach ($reactionTypes as $reactionType) {
517 $count = IdeaReaction::where('object_id', $taskId)
518 ->where('object_type', 'idea')
519 ->where('type', $reactionType['type'])
520 ->count();
521
522 $reactionCounts[] = [
523 'label' => $reactionType['label'],
524 'type' => $reactionType['type'],
525 'count' => $count
526 ];
527 }
528
529 return $reactionCounts;
530 }
531
532
533 /**
534 * Summary of getArchivedOrCompletedTasks
535 * this function will return completd tasks or archived tasks based on users input and also can search by name
536 * @param mixed $data
537 * @param mixed $taskType
538 * @return mixed
539 * @throws \Exception
540 */
541 public function getArchivedTasks($data, $boardId)
542 {
543 $per_page = isset($data['per_page']) ? $data['per_page'] : 25;
544 $page = isset($data['page']) ? $data['page'] : 1;
545 $tasksQuery = Task::where('board_id', $boardId)->whereNotNull('archived_at');
546
547 if (isset($data['searchInput'])) {
548 $query = strtolower($data['searchInput']);
549 $firstThreeChars = substr($query, 0, 3);
550
551 if($firstThreeChars == 'id:') {
552 $idPart = substr($query, 3);
553 $idPart = preg_replace('/[^a-zA-Z0-9]/', '', $idPart);
554 $tasksQuery = $tasksQuery->where('id', 'LIKE', '%' . $idPart . '%');
555 } else {
556 $tasksQuery = $tasksQuery->where('title', 'LIKE', '%' . $data['searchInput'] . '%');
557 }
558 }
559
560 // if board_id is not passed then throw an exception
561 if (!$boardId) {
562 throw new \Exception('Board id is required', 'fluent-boards');
563 }
564
565 return $tasksQuery->orderBy('created_at', 'DESC')->with('assignees')->paginate($per_page, ['*'], 'page', $page);
566 }
567
568 public function sendMailAfterTaskModify($column, $assigneeIds, $taskId)
569 {
570 $current_user_id = get_current_user_id();
571 /* this will run in background as soon as possible */
572 /* sending Model or Model Instance won't work here */
573
574 as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_'.$column, [$taskId, $assigneeIds, $current_user_id], 'fluent-boards');
575 }
576
577 public function getStageByTask($task_id)
578 {
579 $task = Task::find($task_id);
580 return $task->stage;
581 }
582
583 public function moveTaskToNextStage($task_id)
584 {
585 $task = Task::findOrFail($task_id);
586
587 $oldStage = $task->stage;
588
589 $nextStage = Stage::where('board_id', $task->board_id)
590 ->where('position', '>', $oldStage->position)
591 ->orderBy('position', 'ASC')
592 ->first();
593
594 if (!$nextStage) {
595 return $task;
596 }
597
598 if ($nextStage->defaultTaskStatus() == 'closed' && $task->status != 'closed') {
599 $task->status = 'closed';
600 if (!$task->last_completed_at) {
601 $task->last_completed_at = current_time('mysql');
602 }
603 }
604
605 $task->stage_id = $nextStage->id;
606 $task->save();
607
608 $task->load(['board', 'stage', 'attachments']);
609
610 $task = $this->loadNextStage($task);
611
612 return $task;
613 }
614
615 public function loadNextStage($task)
616 {
617 $stage = $task->stage;
618 $nextStage = Stage::where('board_id', $task->board_id)
619 ->where('position', '>', $stage->position)
620 ->orderBy('position', 'ASC')
621 ->first();
622
623 $task->nextStage = $nextStage ? $nextStage->title : null;
624 return $task;
625 }
626
627 public function getActivities($taskId, $perPage, $filter = 'newest')
628 {
629 $activityQuery = Activity::where('object_id', $taskId)
630 ->where('object_type', Constant::ACTIVITY_TASK);
631 if ($filter == 'newest') {
632 $activityQuery = $activityQuery->latest();
633 } else if ($filter == 'oldest') {
634 $activityQuery = $activityQuery->oldest();
635 }
636 return $activityQuery->with('user')->paginate($perPage);
637 }
638
639 public function getLastOneMinuteUpdatedTasks($boardId, $lastUpdated = null)
640 {
641 if (!$lastUpdated) {
642 $lastUpdated = gmdate('Y-m-d H:i:s', current_time('timestamp') - 60);
643 }
644
645 $tasks = Task::query()
646 ->where([
647 'board_id' => $boardId,
648 'parent_id' => null,
649 ])
650 ->where('updated_at', '>', $lastUpdated)
651 ->with(['assignees', 'labels', 'watchers', 'taskCustomFields'])
652 ->orderBy('due_at', 'ASC')
653 ->get();
654
655 foreach ($tasks as $task) {
656 $task->isOverdue = $task->isOverdue();
657 $task->isUpcoming = $task->upcoming();
658 $task->is_watching = $task->isWatching();
659 $task->contact = Task::lead_contact($task->crm_contact_id);
660 $task->assignees = Helper::sanitizeUserCollections($task->assignees);
661 $task->watchers = Helper::sanitizeUserCollections($task->watchers);
662 }
663 return $tasks;
664 }
665
666 public function getLastPositionOfTasks($stage_id)
667 {
668 $lastPosition = Task::query()
669 ->where('stage_id', $stage_id)
670 ->where('parent_id', null)
671 ->whereNull('archived_at')
672 ->orderBy('position', 'desc')
673 ->pluck('position')
674 ->first();
675
676 return $lastPosition + 1;
677 }
678
679 public function getAssociatedTasks($associatedId)
680 {
681 $tasks = Task::query()
682 ->where('crm_contact_id', $associatedId)
683 ->with(['board', 'stage', 'assignees', 'labels', 'watchers',])
684 ->orderBy('due_at', 'ASC')
685 ->get();
686
687 foreach ($tasks as $task) {
688 $task->isOverdue = $task->isOverdue();
689 $task->isUpcoming = $task->upcoming();
690 $task->contact = Task::lead_contact($task->crm_contact_id);
691 $task->is_watching = $task->isWatching();
692
693 $task->assignees = Helper::sanitizeUserCollections($task->assignees);
694 $task->watchers = Helper::sanitizeUserCollections($task->watchers);
695
696 if(!!defined('FLUENT_BOARDS_PRO_VERSION')) {
697 $task->time_tracks= [
698 'tracks' => (new ProTaskService())->getTaskTimeTrack($task->board_id, $task->id),
699 'estimated_minutes' => TimeTrackingHelper::getTaskEstimation($task->id)
700 ];
701 }
702
703
704 $subTasks = Task::query()
705 ->where('parent_id', $task->id)
706 ->with(['assignees'])
707 ->whereNull('archived_at')
708 ->orderBy('position', 'ASC')
709 ->get();
710
711 foreach ($subTasks as $subTask) {
712 $subTask->assignees = Helper::sanitizeUserCollections($subTask->assignees);
713 }
714
715 $task->subtasks = $subTasks;
716 }
717
718 return $tasks;
719 }
720
721 public function copyTasks($boardId, $stageMap, $newBoard, $labelMap = [])
722 {
723 $allActiveTasks = Task::where('board_id', $boardId)->whereNull('archived_at')->get();
724 $taskMap = [];
725 $parentTaskCount = 0;
726 foreach ($allActiveTasks as $task) {
727 $newTask = array();
728 $newTask['title'] = $task->title;
729 $newTask['parent_id'] = $task->parent_id ? $taskMap[$task->parent_id] : null;
730 $newTask['description'] = $task->description;
731 $newTask['board_id'] = $newBoard->id;
732 $newTask['stage_id'] = $stageMap[$task->stage_id];
733 $newTask['status'] = $task->status;
734 $newTask['priority'] = $task->priority;
735 $newTask['position'] = $task->position;
736 $newTask['due_at'] = $task->due_at;
737 $newTask = Task::create($newTask);
738 if(!$task->parent_id){
739 ++$parentTaskCount;
740 $taskMap[$task['id']] = $newTask->id;
741 //duplicate labels to task
742 $labelIds = $task->labels->pluck('id')->toArray();
743 if($labelIds){
744 $flipLabelIds = array_flip($labelIds);
745 $labelsToAttach = array_intersect_key($labelMap, $flipLabelIds);
746
747 $newTask->labels()->attach($labelsToAttach, [
748 'object_type' => Constant::OBJECT_TYPE_TASK_LABEL
749 ]);
750 }
751 }
752 }
753
754 $board = Board::findOrFail($newBoard->id);
755 $settings = [];
756 $settings['tasks_count'] = $parentTaskCount;
757 $board->settings = $settings;
758 $board->save();
759 }
760
761 private function subtaskCountUpdate($taskId){
762 $parentTask = Task::findOrFail($taskId);
763 $settings = $parentTask->settings;
764 $settings['subtask_count'] = (int)($settings['subtask_count'] ?? 0) + 1;
765 $parentTask->settings = $settings;
766 $parentTask->save();
767 }
768
769 /**
770 * @param $taskId
771 * @param $perPage
772 * @param $offset
773 * @param string $filter
774 * @return array
775 */
776 public function getCommentsAndActivities($taskId, $perPage, $page, string $filter = 'newest'): array
777 {
778 // Fetch the task
779 $task = Task::findOrFail($taskId);
780
781 // Fetch comments and activities separately
782 $comments = $task->comments()->with('user')->orderBy('created_at', 'desc')->get()->toArray();
783 $activities = $task->activities()
784 ->with('user')
785 ->where(function($query) {
786 $query->whereNotIn('column', [ 'comment', 'a reply'])
787 ->orWhere(function($subQuery) {
788 $subQuery->whereNotIn('action', ['added', 'updated']);
789 });
790 })
791 ->orderBy('created_at', 'desc')
792 ->get()
793 ->toArray();
794
795
796
797 // Merge comments and activities into a single array
798 $commentsAndActivities = array_merge($comments, $activities);
799
800 // Sort the merged array by created_at date in ascending or descending order
801 $order = $filter == 'newest' ? -1 : 1;
802 usort($commentsAndActivities, function ($a, $b) use ($order) {
803 return $order * (strtotime($a['created_at']) - strtotime($b['created_at']));
804 });
805
806 // Paginate the results
807 $offset = ($page - 1) * $perPage; // Calculate the offset for slicing the array
808 $paginatedResults = array_slice($commentsAndActivities, $offset, $perPage);
809
810 // Get the total count of comments and activities
811 $total = count($commentsAndActivities);
812 $lastPage = (int) ceil($total / $perPage);
813
814 // Construct pagination metadata
815 $path = "https://wordpress.test/wp-json/fluent-boards/v2/projects/{$task->board_id}/tasks/{$task->id}/comments-and-activities";
816 return [
817 'current_page' => (int) $page,
818 'data' => $paginatedResults,
819 'first_page_url' => "{$path}?page=1",
820 'from' => $total > 0 ? (int) ($offset + 1) : null,
821 'last_page' => (int) $lastPage,
822 'last_page_url' => "{$path}?page={$lastPage}",
823 'links' => [
824 [
825 'url' => $page > 1 ? "{$path}?page=" . ($page - 1) : null,
826 'label' => 'pagination.previous',
827 'active' => false
828 ],
829 [
830 'url' => "{$path}?page={$page}",
831 'label' => (int) $page,
832 'active' => true
833 ],
834 [
835 'url' => $page < $lastPage ? "{$path}?page=" . ($page + 1) : null,
836 'label' => 'pagination.next',
837 'active' => false
838 ]
839 ],
840 'next_page_url' => $page < $lastPage ? "{$path}?page=" . ($page + 1) : null,
841 'path' => $path,
842 'per_page' => (int) $perPage,
843 'prev_page_url' => $page > 1 ? "{$path}?page=" . ($page - 1) : null,
844 'to' => $total > 0 ? (int) min($offset + $perPage, $total) : null,
845 'total' => (int) $total
846 ];
847 }
848
849 /**
850 * @param $task_id
851 * @param $fileData
852 * @param $type
853 * @return Attachment
854 */
855 public function uploadMediaFileFromWpEditor($task_id, $fileData, $type)
856 {
857 $initialDataData = [
858 'type' => 'url',
859 'url' => '',
860 'name' => '',
861 'size' => 0,
862 ];
863
864 $attachData = array_merge($initialDataData, $fileData);
865 $UrlMeta = [];
866 if($attachData['type'] == 'url') {
867 $UrlMeta = RemoteUrlParser::parse($attachData['url']);
868 }
869 $attachment = new TaskImage();
870 $attachment->object_id = $task_id;
871 $attachment->object_type = $type;
872 $attachment->attachment_type = $attachData['type'];
873 $attachment->title = $this->setTitle($attachData['type'], $attachData['name'], $UrlMeta);
874 $attachment->file_path = $attachData['type'] != 'url' ? $attachData['file'] : null;
875 $attachment->full_url = esc_url($attachData['url']);
876 $attachment->file_size = $attachData['size'];
877 $attachment->settings = $attachData['type'] == 'url' ? [
878 'meta' => $UrlMeta
879 ] : '';
880 $attachment->driver = 'local';
881 $attachment->save();
882 return $attachment;
883 }
884
885
886 /**
887 * @param $type
888 * @param $title
889 * @param $UrlMeta
890 * @return mixed|string
891 */
892 private function setTitle($type, $title, $UrlMeta)
893 {
894 if($type != 'url') {
895 return sanitize_file_name($title);
896 }
897 return $title ?? $UrlMeta['title'] ?? '';
898 }
899
900 public function manageDefaultAssignees($task, $stageId)
901 {
902 $stage = Stage::findOrFail($stageId);
903 if ($stage && isset($stage->settings['default_task_assignees'])) {
904 $defaultAssignees = $stage->settings['default_task_assignees'];
905 foreach ($defaultAssignees as $assigneeId) {
906 $alreadyAssigneeIds = $task->assignees->pluck('ID')->toArray();
907 $IfAlreadyAssignee = in_array($assigneeId, $alreadyAssigneeIds);
908 if (!$IfAlreadyAssignee) {
909 $this->updateAssignee($assigneeId, $task);
910 }
911 }
912 }
913 }
914
915 public function setDefaultAssigneesToEveryTasks($stage)
916 {
917 $tasks = $stage->tasks->whereNull('archived_at');
918 foreach ($tasks as $task) {
919 $this->manageDefaultAssignees($task, $stage->id);
920 }
921 }
922 }
923