PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.2.1
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.2.1
2.2.1 2.2.0 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 All 44 releases
← All changes | app/Api/Classes/Tasks.php +143 -30 1.95 → 2.2.1 View file →
@@ -36,16 +36,8 @@
36 36 class Tasks
37 37 {
38 38 private $instance = null;
39 39
40 - private $allowedInstanceMethods = [
41 - 'all',
42 - 'get',
43 - 'find',
44 - 'first',
45 - 'paginate'
46 - ];
47 -
48 40 public function __construct(Task $instance)
49 41 {
50 42 $this->instance = $instance;
51 43 }
@@ -66,9 +58,9 @@
66 58 return [];
67 59 }
68 60
69 61 //checking if current user has access to board
70 - if (!PermissionManager::userHasPermission($board_id)) {
62 + if (!$this->canReadBoard($board_id)) {
71 63 return false;
72 64 }
73 65
74 66 $query = Task::query()
@@ -108,10 +100,14 @@
108 100 {
109 101 if (!empty($id)) {
110 102 $task = Task::where('id', $id)->first();
111 103
104 + if (!$task) {
105 + return false;
106 + }
107 +
112 108 //checking if current user has access to board
113 - if (!PermissionManager::userHasPermission($task->board_id)) {
109 + if (!$this->canReadBoard($task->board_id)) {
114 110 return false;
115 111 }
116 112 return $task;
117 113 }
@@ -143,9 +139,9 @@
143 139 $permittedTasks = [];
144 140
145 141 foreach ($tasks as $task) {
146 142 //checking if current user has access to board
147 - if (PermissionManager::userHasPermission($task->board_id)) {
143 + if ($this->canReadBoard($task->board_id)) {
148 144 $permittedTasks[] = $task;
149 145 }
150 146 }
151 147
@@ -154,8 +150,14 @@
154 150 return false;
155 151 }
156 152
157 153
154 + /**
155 + * Create a task only when the current user can write to the target board.
156 + *
157 + * @param array $data
158 + * @return Task|false
159 + */
158 160 public function create($data)
159 161 {
160 162 if (empty($data)) {
161 163 return false;
@@ -164,8 +166,17 @@
164 166 if (empty($data['title']) || empty($data['board_id']) || empty($data['stage_id'])) {
165 167 return false;
166 168 }
167 169
170 + if (!$this->canWriteBoard($data['board_id'])) {
171 + return false;
172 + }
173 +
174 + $stage = Stage::where('id', $data['stage_id'])->where('board_id', $data['board_id'])->first();
175 + if (!$stage) {
176 + return false;
177 + }
178 +
168 179 $taskData = Helper::sanitizeTaskForWebHook($data);
169 180 if (is_string($data['assignees'])) {
170 181 $data['assignees'] = json_decode($data['assignees'], true);
171 182 if (!is_array($data['assignees'])) {
@@ -186,10 +197,10 @@
186 197
187 198
188 199
189 200
190 - if(!empty($taskData['priority']) && !in_array($taskData['priority'], ['low', 'medium', 'high'])) {
191 - $taskData['priority'] = 'low';
201 + if(!empty($taskData['priority']) && !in_array($taskData['priority'], $this->getAllowedTaskPriorities(), true)) {
202 + $taskData['priority'] = '';
192 203 }
193 204 if(!empty($taskData['status']) && !in_array($taskData['status'], ['open', 'closed'])) {
194 205 $taskData['status'] = 'open';
195 206 }
@@ -265,9 +276,9 @@
265 276 'board_id',
266 277 'stage_id',
267 278 'parent_id',
268 279 'status', // open | closed
269 - 'priority', // low | medium | high
280 + 'priority', // urgent | high | medium | low | blank
270 281 'source',
271 282 'source_id',
272 283 'description',
273 284 'due_at',
@@ -372,9 +383,9 @@
372 383 return false;
373 384 }
374 385
375 386 //checking if current user has access to board
376 - if (!PermissionManager::userHasPermission($task->board_id)) {
387 + if (!$this->canWriteBoard($task->board_id)) {
377 388 return false;
378 389 }
379 390
380 391 $boardlabelIds = $board->labels->pluck('id')->toArray();
@@ -410,9 +421,9 @@
410 421 return false;
411 422 }
412 423
413 424 //checking if current user has access to board
414 - if (!PermissionManager::userHasPermission($task->board_id)) {
425 + if (!$this->canWriteBoard($task->board_id)) {
415 426 return false;
416 427 }
417 428
418 429 $task->labels()->detach($labelIds);
@@ -437,9 +448,9 @@
437 448 return false;
438 449 }
439 450
440 451 //checking if current user has access to board
441 - if (!PermissionManager::userHasPermission($task->board_id)) {
452 + if (!$this->canWriteBoard($task->board_id)) {
442 453 return false;
443 454 }
444 455
445 456 $stage = Stage::findOrFail($stageId);
@@ -458,10 +469,25 @@
458 469
459 470 return $task;
460 471 }
461 472
473 + /**
474 + * Update a task property only when the current user can write to the task board.
475 + *
476 + * @param int $taskId
477 + * @param string $property
478 + * @param mixed $value
479 + * @return Task|false
480 + */
462 481 public function updateProperty($taskId, $property, $value)
463 482 {
483 + $taskId = absint($taskId);
484 + $property = sanitize_text_field($property);
485 +
486 + if (!$taskId || !$property) {
487 + return false;
488 + }
489 +
464 490 $taskService = new TaskService();
465 491 $task = Task::where('id', $taskId)->first();
466 492
467 493 if(!$task) {
@@ -467,16 +493,52 @@
467 493 if(!$task) {
468 494 return false;
469 495 }
470 496
497 + if (!$this->canWriteBoard($task->board_id)) {
498 + return false;
499 + }
500 +
471 501 $allowedColumns = ['title', 'description', 'due_at', 'priority', 'status', 'source', 'source_id', 'crm_contact_id'];
472 502
473 - if(!in_array($property, $allowedColumns)) {
503 + if(!in_array($property, $allowedColumns, true)) {
474 504 return false;
475 505 }
476 506
477 - $taskService->updateTaskProperty($task, $property, $value);
507 + switch ($property) {
508 + case 'description':
509 + $sanitizedValue = fluent_boards_sanitize_description($value);
510 + break;
478 511
512 + case 'due_at':
513 + $sanitizedValue = $value === null || $value === '' ? null : sanitize_text_field((string) $value);
514 + break;
515 +
516 + case 'priority':
517 + $sanitizedValue = $value === null ? null : sanitize_text_field((string) $value);
518 + if ($sanitizedValue !== null && $sanitizedValue !== '' && !in_array($sanitizedValue, $this->getAllowedTaskPriorities(), true)) {
519 + return false;
520 + }
521 + break;
522 +
523 + case 'status':
524 + $sanitizedValue = sanitize_text_field((string) $value);
525 + if (!in_array($sanitizedValue, ['open', 'closed'], true)) {
526 + return false;
527 + }
528 + break;
529 +
530 + case 'crm_contact_id':
531 + $sanitizedValue = $value === null || $value === '' ? null : absint($value);
532 + break;
533 +
534 + default:
535 + $sanitizedValue = sanitize_text_field((string) $value);
536 + break;
537 + }
538 +
539 + $task = $taskService->updateTaskProperty($property, $sanitizedValue, $task);
540 +
479 541 return $task;
480 542
481 543 }
482 544
@@ -548,8 +610,9 @@
548 610 }
549 611
550 612 $data['board_id'] = $boardId;
551 613 $data['parent_id'] = $task->id;
614 + unset($data['started_at']);
552 615
553 616 // Ensure group_id is provided and valid
554 617 if (!empty($data['group_id'])) {
555 618 // Validate that the group belongs to the parent task
@@ -567,9 +630,9 @@
567 630 if (!$group) {
568 631 $group = TaskMeta::create([
569 632 'task_id' => $task->id,
570 633 'key' => Constant::SUBTASK_GROUP_NAME,
571 - 'value' => __('Subtask Group 1', 'fluent-boards')
634 + 'value' => __('Untitled Group', 'fluent-boards')
572 635 ]);
573 636 }
574 637
575 638 $data['group_id'] = $group->id;
@@ -583,9 +646,9 @@
583 646 if (!$group) {
584 647 $group = TaskMeta::create([
585 648 'task_id' => $task->id,
586 649 'key' => Constant::SUBTASK_GROUP_NAME,
587 - 'value' => __('Subtask Group 1', 'fluent-boards')
650 + 'value' => __('Untitled Group', 'fluent-boards')
588 651 ]);
589 652 }
590 653
591 654 $data['group_id'] = $group->id;
@@ -619,9 +682,19 @@
619 682 {
620 683 if (!$this->hasProAndBoardAccess($boardId)) {
621 684 return false;
622 685 }
623 - return $this->updateProperty($subtaskId, $property, $value);
686 +
687 + $subtask = Task::where('id', $subtaskId)->where('parent_id', $taskId)->where('board_id', $boardId)->first();
688 + if (!$subtask) {
689 + return false;
690 + }
691 +
692 + if ($property === 'started_at') {
693 + $value = null;
694 + }
695 +
696 + return $this->updateProperty($subtask->id, $property, $value);
624 697 }
625 698
626 699
627 700 /**
@@ -636,8 +709,12 @@
636 709 return false;
637 710 }
638 711
639 712 $subtask = Task::where('id', $subtaskId)->where('parent_id', $taskId)->where('board_id', $boardId)->first();
713 + if (!$subtask) {
714 + return false;
715 + }
716 +
640 717 $deletedTask = clone $subtask;
641 718
642 719 $options = null;
643 720 //if we need to do something before a task is deleted
@@ -654,22 +731,58 @@
654 731 * @return bool
655 732 */
656 733 private function hasProAndBoardAccess($boardId)
657 734 {
658 - return defined('FLUENT_BOARDS_PRO_VERSION') && PermissionManager::userHasPermission($boardId);
735 + return defined('FLUENT_BOARDS_PRO_VERSION') && $this->canWriteBoard($boardId);
659 736 }
660 737
661 738
662 - public function getInstance()
739 + /**
740 + * Block raw model proxy calls so board access cannot be bypassed.
741 + *
742 + * @param string $method
743 + * @param array $params
744 + * @throws \Exception
745 + */
746 + public function __call($method, $params)
663 747 {
664 - return $this->instance;
748 + throw new \Exception(esc_html(sprintf('Method %s does not exist.', $method)));
665 749 }
666 750
667 - public function __call($method, $params)
751 + /**
752 + * Check if the current user can read a board.
753 + *
754 + * @param int $boardId
755 + * @return bool
756 + */
757 + private function canReadBoard($boardId)
668 758 {
669 - if (in_array($method, $this->allowedInstanceMethods)) {
670 - return call_user_func_array([$this->instance, $method], $params);
671 - }
759 + return PermissionManager::userHasBoardPermission($boardId, 'GET');
760 + }
672 761
673 - throw new \Exception(esc_html(sprintf('Method %s does not exist.', $method)));
762 + /**
763 + * Check if the current user can write to a board.
764 + *
765 + * @param int $boardId
766 + * @return bool
767 + */
768 + private function canWriteBoard($boardId)
769 + {
770 + return PermissionManager::userHasBoardPermission($boardId, 'POST');
771 + }
772 +
773 + /**
774 + * Get priority keys allowed by the task priority filter.
775 + *
776 + * @return array
777 + */
778 + private function getAllowedTaskPriorities()
779 + {
780 + return array_map('strval', array_keys(apply_filters('fluent_boards/task_priorities', [
781 + '' => __('No priority', 'fluent-boards'),
782 + 'urgent' => __('Urgent', 'fluent-boards'),
783 + 'high' => __('High', 'fluent-boards'),
784 + 'medium' => __('Medium', 'fluent-boards'),
785 + 'low' => __('Low', 'fluent-boards'),
786 + ])));
674 787 }
675 788 }