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

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