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

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