PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.95
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.95
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
fluent-boards / app / Api / Classes / Tasks.php

Tasks.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.95, at app/Api/Classes/Tasks.php

676 lines 18.8 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\Api\Classes;
4
5 defined('ABSPATH') || exit;
6
7 use Exception;
8 use FluentBoards\App\Models\Label;
9 use FluentBoards\App\Models\Stage;
10 use FluentBoards\App\Models\Task;
11 use FluentBoards\App\Models\TaskMeta;
12 use FluentBoards\App\Models\Board;
13 use FluentBoards\App\Services\BoardService;
14 use FluentBoards\App\Services\Helper;
15 use FluentBoards\App\Services\PermissionManager;
16 use FluentBoards\App\Services\TaskService;
17 use FluentBoardsPro\App\Models\TaskAttachment;
18 use FluentBoardsPro\App\Services\AttachmentService;
19 use FluentBoardsPro\App\Services\SubtaskService;
20 use FluentCrm\App\Models\Subscriber;
21
22 use FluentBoards\App\Services\Constant;
23
24
25
26 /**
27 * Tasks Class - PHP API Wrapper
28 *
29 * Tasks API Wrapper Class that can be used as <code>FluentBoardsApi('tasks')</code> to get the class instance
30 *
31 * @package FluentBoards\App\Api\Classes
32 * @namespace FluentBoards\App\Api\Classes
33 *
34 * @version 1.0.0
35 */
36 class Tasks
37 {
38 private $instance = null;
39
40 private $allowedInstanceMethods = [
41 'all',
42 'get',
43 'find',
44 'first',
45 'paginate'
46 ];
47
48 public function __construct(Task $instance)
49 {
50 $this->instance = $instance;
51 }
52
53 /**
54 * Get Tasks by board
55 *
56 * Use:
57 * <code>FluentBoardsApi('tasks')->getTasksByBoard();</code>
58 *
59 * @param string|int $board_id
60 * @param array $with
61 * @return array|Task Model
62 */
63 public function getTasksByBoard($board_id, $with = [])
64 {
65 if (!$board_id) {
66 return [];
67 }
68
69 //checking if current user has access to board
70 if (!PermissionManager::userHasPermission($board_id)) {
71 return false;
72 }
73
74 $query = Task::query()
75 ->where('board_id', $board_id)
76 ->whereNull('parent_id')
77 ->whereNull('archived_at')
78 ->orderBy('due_at', 'ASC');
79
80 if (!empty($with)) {
81 $query->with($with);
82 }
83
84 $tasks = $query->get();
85
86 $tasks->transform(function ($task) {
87 $task->isOverdue = $task->isOverdue();
88 $task->isUpcoming = $task->upcoming();
89 $task->contact = Helper::crm_contact($task->crm_contact_id);
90 $task->is_watching = $task->isWatching();
91 return $task;
92 });
93
94 return $tasks;
95 }
96
97
98 /**
99 * Get Task by id
100 *
101 * Use:
102 * <code>FluentBoardsApi('tasks')->getTask($id);</code>
103 *
104 * @param int|string $id Task id
105 * @return false|Task Model
106 */
107 public function getTask($id)
108 {
109 if (!empty($id)) {
110 $task = Task::where('id', $id)->first();
111
112 //checking if current user has access to board
113 if (!PermissionManager::userHasPermission($task->board_id)) {
114 return false;
115 }
116 return $task;
117 }
118 return false;
119 }
120
121
122 /**
123 * Get Task Created by
124 *
125 * Use:
126 * <code>FluentBoardsApi('tasks')->getTasksCreatedBy($userId);</code>
127 *
128 * @param int|string $userId Task id
129 * @param array $with
130 * @return false|Task Model
131 */
132 public function getTasksCreatedBy($userId, $with = [])
133 {
134 if (!empty($userId)) {
135 $query = Task::where('created_by', $userId);
136
137 if (!empty($with)) {
138 $query->with($with);
139 }
140
141 $tasks = $query->get();
142
143 $permittedTasks = [];
144
145 foreach ($tasks as $task) {
146 //checking if current user has access to board
147 if (PermissionManager::userHasPermission($task->board_id)) {
148 $permittedTasks[] = $task;
149 }
150 }
151
152 return $permittedTasks;
153 }
154 return false;
155 }
156
157
158 public function create($data)
159 {
160 if (empty($data)) {
161 return false;
162 }
163
164 if (empty($data['title']) || empty($data['board_id']) || empty($data['stage_id'])) {
165 return false;
166 }
167
168 $taskData = Helper::sanitizeTaskForWebHook($data);
169 if (is_string($data['assignees'])) {
170 $data['assignees'] = json_decode($data['assignees'], true);
171 if (!is_array($data['assignees'])) {
172 $data['assignees'] = [$data['assignees']]; // Wrap single value in an array
173 }
174 } elseif (is_int($data['assignees'])) {
175 $data['assignees'] = [$data['assignees']]; // Wrap integer in an array
176 }
177
178 if (is_string($data['labels'])) {
179 $data['labels'] = json_decode($data['labels'], true);
180 if (!is_array($data['labels'])) {
181 $data['labels'] = [$data['labels']]; // Wrap single value in an array
182 }
183 } elseif (is_int($data['labels'])) {
184 $data['labels'] = [$data['labels']]; // Wrap integer in an array
185 }
186
187
188
189
190 if(!empty($taskData['priority']) && !in_array($taskData['priority'], ['low', 'medium', 'high'])) {
191 $taskData['priority'] = 'low';
192 }
193 if(!empty($taskData['status']) && !in_array($taskData['status'], ['open', 'closed'])) {
194 $taskData['status'] = 'open';
195 }
196 if(!empty($data['crm_contact_id'])) {
197 $taskData['crm_contact_id'] = $data['crm_contact_id'];
198 }
199
200 if(!empty($data['contact_email']) && empty($data['crm_contact_id'])) {
201 // Find first if the contact exists
202 $contact = FluentCrmApi('contacts')->createOrUpdate([
203 'email' => $data['contact_email'],
204 'first_name' => $data['contact_first_name'],
205 'last_name' => $data['contact_last_name'],
206 'status' => 'subscribed'
207 ]);
208 $taskData['crm_contact_id'] = $contact->id;
209 }
210
211 if(!empty($data['assignees']) && is_array($data['assignees'])) {
212 // check if the assignees are valid, they have to be wp user ids
213 $users = get_users(['include' => $data['assignees']]);
214 $taskData['assignees'] = array_map(function($user) {
215 return $user->ID;
216 }, $users);
217 // after successful task creation we have to add them as board members
218 }
219
220 if(!empty($data['labels']) && is_array($data['labels'])) {
221 $labelIds = [];
222 // check if the labels are valid, they have to be label ids
223 foreach ($data['labels'] as $label) {
224 if(is_numeric($label)) {
225 $labelModel = Label::where('id', $label)->where('board_id', $data['board_id'])->first();
226 if($labelModel) {
227 $labelId = $labelModel->id;
228 $labelIds[] = $labelId;
229 }
230 continue;
231 }
232 if(is_string($label)) {
233 $labelModel = Label::where('board_id', $data['board_id'])
234 ->where(function($query) use ($label) {
235 $query->where('title', $label)
236 ->orWhere('slug', $label);
237 })->first();
238 if($labelModel) {
239 $labelId = $labelModel->id;
240 $labelIds[] = $labelId;
241 }
242 }
243 }
244 $taskData['labels'] = $labelIds;
245 // we have to map the labels after task creation
246 }
247
248 $task = $this->instance->createTask($taskData);
249 // push assignees to board Members
250 $boardService = new BoardService();
251 if(!empty($taskData['assignees'])) {
252 foreach ($taskData['assignees'] as $assigneeId) {
253 $boardService->addMembersInBoard($data['board_id'], $assigneeId);
254 }
255 }
256
257 return $task;
258 }
259
260
261 public function createTask($data = [])
262 {
263 $defaultData = [
264 'title',
265 'board_id',
266 'stage_id',
267 'parent_id',
268 'status', // open | closed
269 'priority', // low | medium | high
270 'source',
271 'source_id',
272 'description',
273 'due_at',
274 'crm_contact_id',
275 // <or>
276 'contact_email',
277 'contact_first_name', // this is the full name
278 'contact_last_name', // this is the full name
279 'contact_status', // default subscribed
280 // </or>
281 'assignees', // WP User IDs / WP User Emails as an array
282 'labels', // array of label ids or titles [1, 'New']
283 ];
284
285 }
286
287 public function addAssignees($task_id, $assignees = [])
288 {
289 if(empty($task_id) || empty($assignees)) {
290 return false;
291 }
292
293 $task = Task::where('id', $task_id)->first();
294 $board = Board::findOrFail($task->board_id);
295
296 if(!PermissionManager::isBoardManager($task->board_id))
297 {
298 if(!PermissionManager::isAdmin())
299 {
300 return false;
301 }
302 }
303
304 if(!$task) {
305 return false;
306 }
307
308 $boardUsersIds = $board->users->pluck('ID')->toArray();
309
310 foreach($assignees as $assignee)
311 {
312 if(in_array($assignee, $boardUsersIds)) { //check if user is a board member
313 $task->assignees()
314 ->syncWithoutDetaching([
315 $assignee => [
316 'object_type' => Constant::OBJECT_TYPE_TASK_ASSIGNEE
317 ]
318 ]);
319 $task->watchers()
320 ->syncWithoutDetaching([
321 $assignee => [
322 'object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH
323 ]
324 ]);
325 }
326 }
327
328 return true;
329 }
330
331 public function removeAssignees($task_id, $assignees = [])
332 {
333 if(empty($task_id) || empty($assignees)) {
334 return false;
335 }
336
337 $task = Task::where('id', $task_id)->first();
338 if(!$task) {
339 return false;
340 }
341
342 if(!PermissionManager::isBoardManager($task->board_id))
343 {
344 if(!PermissionManager::isAdmin())
345 {
346 return false;
347 }
348 }
349
350 $task->assignees()->detach($assignees);
351
352 $task->watchers()->detach($assignees);
353
354 return true;
355 }
356
357
358
359 public function attachLabels($taskId, $labelIds = [])
360 {
361 // do code here
362 if(!$taskId)
363 {
364 return false;
365 }
366
367 $task = Task::findOrFail($taskId);
368 $board = Board::findOrFail($task->board_id);
369
370 if(!$task)
371 {
372 return false;
373 }
374
375 //checking if current user has access to board
376 if (!PermissionManager::userHasPermission($task->board_id)) {
377 return false;
378 }
379
380 $boardlabelIds = $board->labels->pluck('id')->toArray();
381
382 foreach ($labelIds as $labelId)
383 {
384 if(in_array($labelId, $boardlabelIds)) { //check if label is a board label
385 $task->labels()
386 ->syncWithoutDetaching([
387 $labelId => [
388 'object_type' => Constant::OBJECT_TYPE_TASK_LABEL
389 ]
390 ]);
391 }
392 }
393
394 return true;
395
396 }
397
398 public function removeLabels($taskId, $labelIds = [])
399 {
400 // do code here
401 if(!$taskId)
402 {
403 return false;
404 }
405
406 $task = Task::findOrFail($taskId);
407
408 if(!$task)
409 {
410 return false;
411 }
412
413 //checking if current user has access to board
414 if (!PermissionManager::userHasPermission($task->board_id)) {
415 return false;
416 }
417
418 $task->labels()->detach($labelIds);
419
420 return true;
421 }
422
423 /*
424 * Change the stage of a task
425 * @param int $taskId
426 * @param int $stageId - the new stage id
427 * @return Task
428 */
429 public function changeStage($task, $stageId)
430 {
431 // param can be either task object or task Id
432 if(is_numeric($task)) {
433 $task = Task::where('id', $task)->first();
434 }
435
436 if(!$task) {
437 return false;
438 }
439
440 //checking if current user has access to board
441 if (!PermissionManager::userHasPermission($task->board_id)) {
442 return false;
443 }
444
445 $stage = Stage::findOrFail($stageId);
446
447 if($stage->board_id != $task->board_id)
448 {
449 return false;
450 }
451
452 $taskService = new TaskService();
453 $position = $taskService->getLastPositionOfTasks($stageId);
454
455 $task->stage_id = $stageId;
456 $task->position = $position;
457 $task->save();
458
459 return $task;
460 }
461
462 public function updateProperty($taskId, $property, $value)
463 {
464 $taskService = new TaskService();
465 $task = Task::where('id', $taskId)->first();
466
467 if(!$task) {
468 return false;
469 }
470
471 $allowedColumns = ['title', 'description', 'due_at', 'priority', 'status', 'source', 'source_id', 'crm_contact_id'];
472
473 if(!in_array($property, $allowedColumns)) {
474 return false;
475 }
476
477 $taskService->updateTaskProperty($task, $property, $value);
478
479 return $task;
480
481 }
482
483 /**
484 * Create a task attachment
485 * @param int $boardId
486 * @param int $taskId
487 * @param array $data - ['title', 'url'] // url is required
488 * @throws Exception
489 */
490 public function createTaskAttachment(int $boardId, int $taskId, array $data = [])
491 {
492 // check if the user has permission to add attachment
493 if (!$this->hasProAndBoardAccess($boardId) || empty($data) || empty($data['url'])) {
494 return false;
495 }
496
497 $task = Task::where('id', $taskId)->where('board_id', $boardId)->first();
498 if (!$task) {
499 return false;
500 }
501
502 $attachmentData = Helper::sanitizeTaskAttachment($data);
503 return (new AttachmentService())->addTaskAttachment($task->id, $attachmentData);
504 }
505
506
507 /**
508 * delete a task attachment
509 * @param $boardId
510 * @param $taskId
511 * @param $attachmentId
512 * @return bool
513 */
514 public function deleteTaskAttachment( int $boardId, int $taskId, int $attachmentId)
515 {
516 // check if the user has permission to delete attachment
517 if (!$this->hasProAndBoardAccess($boardId)) {
518 return false;
519 }
520
521 $task = Task::where('id', $taskId)->where('board_id', $boardId)->first();
522 if (!$task) {
523 return false;
524 }
525
526 (new AttachmentService())->deleteTaskAttachment($task->id, $attachmentId);
527
528 return true;
529 }
530
531
532 /**
533 * Create a subtask
534 * @param int $boardId
535 * @param int $taskId
536 * @param $data
537 * @return bool|Task
538 */
539 public function createSubtask(int $boardId, int $taskId, $data)
540 {
541 if (!$this->hasProAndBoardAccess($boardId)) {
542 return false;
543 }
544
545 $task = Task::where('id', $taskId)->where('board_id', $boardId)->first();
546 if (!$task) {
547 return false;
548 }
549
550 $data['board_id'] = $boardId;
551 $data['parent_id'] = $task->id;
552
553 // Ensure group_id is provided and valid
554 if (!empty($data['group_id'])) {
555 // Validate that the group belongs to the parent task
556 $group = TaskMeta::where('id', $data['group_id'])
557 ->where('task_id', $task->id)
558 ->where('key', Constant::SUBTASK_GROUP_NAME)
559 ->first();
560
561 if (!$group) {
562 // Invalid group_id provided, create a default group instead
563 $group = TaskMeta::where('task_id', $task->id)
564 ->where('key', Constant::SUBTASK_GROUP_NAME)
565 ->first();
566
567 if (!$group) {
568 $group = TaskMeta::create([
569 'task_id' => $task->id,
570 'key' => Constant::SUBTASK_GROUP_NAME,
571 'value' => __('Subtask Group 1', 'fluent-boards')
572 ]);
573 }
574
575 $data['group_id'] = $group->id;
576 }
577 } else {
578 // No group_id provided, find or create a default group
579 $group = TaskMeta::where('task_id', $task->id)
580 ->where('key', Constant::SUBTASK_GROUP_NAME)
581 ->first();
582
583 if (!$group) {
584 $group = TaskMeta::create([
585 'task_id' => $task->id,
586 'key' => Constant::SUBTASK_GROUP_NAME,
587 'value' => __('Subtask Group 1', 'fluent-boards')
588 ]);
589 }
590
591 $data['group_id'] = $group->id;
592 }
593
594 $subtask = $this->create($data);
595
596 if ($subtask) {
597 // Link subtask to group
598 TaskMeta::create([
599 'task_id' => $subtask->id,
600 'key' => Constant::SUBTASK_GROUP_CHILD,
601 'value' => $data['group_id']
602 ]);
603 }
604
605 return $subtask;
606 }
607
608
609 /**
610 * Update a subtask
611 * @param int $boardId
612 * @param int $taskId
613 * @param int $subtaskId
614 * @param string $property
615 * @param mixed $value
616 *
617 */
618 public function updateSubtask($boardId, $taskId, $subtaskId, $property, $value)
619 {
620 if (!$this->hasProAndBoardAccess($boardId)) {
621 return false;
622 }
623 return $this->updateProperty($subtaskId, $property, $value);
624 }
625
626
627 /**
628 * Delete a subtask
629 * @param int $boardId
630 * @param int $taskId
631 * @param int $subtaskId
632 */
633 public function deleteSubtask($boardId, $taskId, $subtaskId)
634 {
635 if (!$this->hasProAndBoardAccess($boardId)) {
636 return false;
637 }
638
639 $subtask = Task::where('id', $subtaskId)->where('parent_id', $taskId)->where('board_id', $boardId)->first();
640 $deletedTask = clone $subtask;
641
642 $options = null;
643 //if we need to do something before a task is deleted
644 do_action('fluent_boards/before_task_deleted', $subtask, $options);
645
646 ( new SubtaskService() )->deleteSubtask($subtask);
647
648 do_action('fluent_boards/subtask_deleted_activity', $deletedTask->parent_id, $deletedTask->title);
649 }
650
651 /**
652 * Check if the user has pro version and has access to the board
653 * @param $boardId
654 * @return bool
655 */
656 private function hasProAndBoardAccess($boardId)
657 {
658 return defined('FLUENT_BOARDS_PRO_VERSION') && PermissionManager::userHasPermission($boardId);
659 }
660
661
662 public function getInstance()
663 {
664 return $this->instance;
665 }
666
667 public function __call($method, $params)
668 {
669 if (in_array($method, $this->allowedInstanceMethods)) {
670 return call_user_func_array([$this->instance, $method], $params);
671 }
672
673 throw new \Exception(esc_html(sprintf('Method %s does not exist.', $method)));
674 }
675 }
676