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

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