PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.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 1.40 1.41 All 42 releases
← All changes | app/Services/Helper.php +195 -14 1.91.62.1.0 View file →
@@ -53,8 +53,67 @@
53 53
54 54 return $data;
55 55 }
56 56
57 + /**
58 + * Normalize a nullable datetime value so task flows can safely persist NULL.
59 + *
60 + * @param mixed $value
61 + * @return mixed|null
62 + */
63 + public static function normalizeDateValue($value)
64 + {
65 + if ($value === null || is_bool($value)) {
66 + return null;
67 + }
68 +
69 + if (is_string($value)) {
70 + $value = trim($value);
71 +
72 + if ($value === '') {
73 + return null;
74 + }
75 +
76 + $normalizedValue = strtolower($value);
77 +
78 + if (in_array($normalizedValue, ['none', 'null'], true)) {
79 + return null;
80 + }
81 +
82 + if (in_array($value, ['0000-00-00', '0000-00-00 00:00:00'], true)) {
83 + return null;
84 + }
85 +
86 + if (preg_match('/^(\d{4})-/', $value, $matches) && (int) $matches[1] < 1900) {
87 + return null;
88 + }
89 +
90 + if (strtotime($value) === false) {
91 + return null;
92 + }
93 + }
94 +
95 + return $value;
96 + }
97 +
98 + /**
99 + * Normalize a list of nullable datetime keys inside an attribute array.
100 + *
101 + * @param array $data
102 + * @param array $dateKeys
103 + * @return array
104 + */
105 + public static function normalizeDates($data, $dateKeys = [])
106 + {
107 + foreach ($dateKeys as $dateKey) {
108 + if (array_key_exists($dateKey, $data)) {
109 + $data[$dateKey] = self::normalizeDateValue($data[$dateKey]);
110 + }
111 + }
112 +
113 + return $data;
114 + }
115 +
57 116 public static function sanitizeTask($data)
58 117 {
59 118 $fieldMaps = [
60 119 'title' => 'sanitize_text_field',
@@ -68,9 +127,10 @@
68 127 'lead_value' => 'doubleval',
69 128 'remind_at' => 'sanitize_text_field',
70 129 'scope' => 'sanitize_text_field',
71 130 'source' => 'sanitize_text_field',
72 - 'description' => 'wp_kses_post',
131 + 'source_id' => 'sanitize_text_field',
132 + 'description' => 'fluent_boards_sanitize_description',
73 133 'due_date' => 'sanitize_text_field',
74 134 'start_at' => 'sanitize_text_field',
75 135 'log_minutes' => 'sanitize_text_field',
76 136 'last_completed' => 'sanitize_text_field',
@@ -94,9 +154,9 @@
94 154 'board_id' => 'intval',
95 155 'title' => 'sanitize_text_field',
96 156 'parent_id' => 'intval',
97 157 'type' => 'sanitize_text_field',
98 - 'description' => 'wp_kses_post',
158 + 'description' => 'fluent_boards_sanitize_description',
99 159 'currency' => 'sanitize_text_field',
100 160 'image_url' => 'sanitize_url',
101 161 'is_auth_require' => 'intval',
102 162 'crm_contact_id' => 'intval',
@@ -102,8 +162,9 @@
102 162 'crm_contact_id' => 'intval',
103 163 'id' => 'sanitize_text_field',
104 164 'is_image' => 'rest_sanitize_boolean',
105 165 'color' => 'sanitize_text_field', // sanitize_hex_color doesn't work when color code is greater than 6 characters
166 + 'reset' => 'rest_sanitize_boolean',
106 167 'created_by' => 'intval',
107 168 ];
108 169
109 170 return self::sanitizeData($data, $fieldMaps);
@@ -137,8 +198,9 @@
137 198 {
138 199 $fieldMaps = [
139 200 'bg_color' => 'sanitize_text_field',
140 201 'color' => 'sanitize_text_field',
202 + 'color_preset' => 'sanitize_key',
141 203 'label' => 'sanitize_text_field',
142 204 'boardId' => 'intval',
143 205 'task_id' => 'intval',
144 206 'meta_value' => 'intval',
@@ -154,14 +216,31 @@
154 216 'stage' => 'sanitize_text_field',
155 217 'newPosition' => 'intval',
156 218 'priority' => 'sanitize_text_field',
157 219 'type' => 'sanitize_text_field',
220 + 'description' => 'fluent_boards_sanitize_description',
221 + 'group_id' => 'intval',
158 222 'board_id' => 'intval',
159 223 'created_by' => 'intval',
160 224 'due_date' => 'sanitize_text_field',
225 + 'due_at' => 'sanitize_text_field',
226 + 'started_at' => 'sanitize_text_field',
227 + 'reminder_type' => 'sanitize_text_field',
228 + 'remind_at' => 'sanitize_text_field',
229 + 'add_to_top' => 'rest_sanitize_boolean',
161 230 ];
162 231
163 - return self::sanitizeData($data, $fieldMaps);
232 + $data = self::sanitizeData($data, $fieldMaps);
233 +
234 + if (!empty($data['assignees']) && is_array($data['assignees'])) {
235 + $data['assignees'] = array_slice(array_filter(array_map('intval', $data['assignees'])), 0, 1);
236 + }
237 +
238 + if (!empty($data['labels']) && is_array($data['labels'])) {
239 + $data['labels'] = array_filter(array_map('intval', $data['labels']));
240 + }
241 +
242 + return $data;
164 243 }
165 244
166 245 public static function createActivity($data)
167 246 {
@@ -323,12 +402,12 @@
323 402 }
324 403
325 404 public static function sanitizeUserCollections($users)
326 405 {
327 - if (empty($users) || !is_array($users)) {
406 + if (empty($users)) {
328 407 return $users;
329 408 }
330 -
409 +
331 410 foreach ($users as $key => $user) {
332 411 if (is_object($user) && isset($user->pivot)) {
333 412 $settings = maybe_unserialize($user->pivot->settings);
334 413 $user->role = Arr::get($settings, 'is_admin')
@@ -342,16 +421,24 @@
342 421 if (current_user_can('list_users')) {
343 422 return $users;
344 423 }
345 424
346 - if ($users) {
425 + if (is_object($users) && method_exists($users, 'makeHidden')) {
347 426 $users->makeHidden(['user_email', 'user_nicename', 'user_registered', 'user_url', 'user_status']);
427 + } elseif (is_array($users)) {
428 + foreach ($users as &$user) {
429 + if (is_array($user)) {
430 + unset($user['user_email'], $user['user_nicename'], $user['user_registered'], $user['user_url'], $user['user_status']);
431 + }
432 + }
433 + unset($user);
348 434 }
349 435
350 436 return $users;
351 437 }
352 438
353 - public static function sanitizeUsersArray($users, $boardId = null)
439 + // Callers formatting multiple lists may supply a resolved board-manager result.
440 + public static function sanitizeUsersArray($users, $boardId = null, $isBoardManager = null)
354 441 {
355 442 if (current_user_can('list_users')) {
356 443 return $users;
357 444 }
@@ -357,9 +444,9 @@
357 444 }
358 445
359 446 $sanitizedUsers = [];
360 447
361 - if(!PermissionManager::isBoardManager($boardId)) //Todo: may create permission security issue, will be modified later
448 + if (!($isBoardManager ?? PermissionManager::isBoardManager($boardId)))
362 449 {
363 450 $currentUser = wp_get_current_user();
364 451 if($currentUser && isset($currentUser->user_email)){
365 452 $currentUserEmail = $currentUser->user_email;
@@ -414,18 +501,26 @@
414 501 public static function getPriorityOptions()
415 502 {
416 503 return [
417 504 [
418 - 'id' => 'low',
419 - 'title' => 'Low'
505 + 'id' => '',
506 + 'title' => 'No priority'
420 507 ],
421 508 [
509 + 'id' => 'urgent',
510 + 'title' => 'Urgent'
511 + ],
512 + [
513 + 'id' => 'high',
514 + 'title' => 'High'
515 + ],
516 + [
422 517 'id' => 'medium',
423 518 'title' => 'Medium'
424 519 ],
425 520 [
426 - 'id' => 'high',
427 - 'title' => 'High'
521 + 'id' => 'low',
522 + 'title' => 'Low'
428 523 ],
429 524 ];
430 525 }
431 526
@@ -503,8 +598,9 @@
503 598 'repeat_in' => 'intval',
504 599 'repeat_type' => 'sanitize_text_field',
505 600 'repeat_when_complete' => 'intval',
506 601 'selected_month' => 'sanitize_text_field',
602 + 'selected_stage' => 'intval',
507 603 'board_id' => 'intval',
508 604 'time' => 'sanitize_text_field',
509 605 'time_zone' => 'sanitize_text_field',
510 606 'next_repeat_date' => 'sanitize_text_field',
@@ -513,8 +609,27 @@
513 609
514 610 return self::sanitizeData($data, $fieldMaps);
515 611 }
516 612
613 + /**
614 + * Sanitize the author snapshot supplied by an external task integration.
615 + *
616 + * @param mixed $author
617 + * @return array
618 + */
619 + private static function sanitizeExternalTaskAuthor($author)
620 + {
621 + if (!is_array($author)) {
622 + return [];
623 + }
624 +
625 + return array_filter([
626 + 'name' => sanitize_text_field($author['name'] ?? ''),
627 + 'email' => sanitize_email($author['email'] ?? ''),
628 + 'photo' => esc_url_raw($author['photo'] ?? ''),
629 + ]);
630 + }
631 +
517 632 public static function sanitizeTaskForWebHook($data)
518 633 {
519 634 $fieldMaps = [
520 635 'title' => 'sanitize_text_field',
@@ -528,9 +643,10 @@
528 643 'lead_value' => 'doubleval',
529 644 'remind_at' => 'sanitize_text_field',
530 645 'scope' => 'sanitize_text_field',
531 646 'source' => 'sanitize_text_field',
532 - 'description' => 'wp_kses_post',
647 + 'source_id' => 'sanitize_text_field',
648 + 'description' => 'fluent_boards_sanitize_description',
533 649 'due_date' => 'sanitize_text_field',
534 650 'start_at' => 'sanitize_text_field',
535 651 'log_minutes' => 'sanitize_text_field',
536 652 'last_completed' => 'sanitize_text_field',
@@ -543,9 +659,19 @@
543 659 'position' => 'intval'
544 660
545 661 ];
546 662
547 - return self::sanitizeData($data, $fieldMaps);
663 + $data = self::sanitizeData($data, $fieldMaps);
664 +
665 + if (isset($data['settings']) && is_array($data['settings']) && isset($data['settings']['author'])) {
666 + $data['settings'] = [
667 + 'author' => self::sanitizeExternalTaskAuthor($data['settings']['author']),
668 + ];
669 + } else {
670 + unset($data['settings']);
671 + }
672 +
673 + return $data;
548 674 }
549 675
550 676
551 677 public static function taskReminderTypes()
@@ -561,6 +687,61 @@
561 687
562 688 $allowedTypes = apply_filters('fluent_boards/task_reminder_types', $allowedTypes);
563 689
564 690 return $allowedTypes;
691 + }
692 +
693 + public static function translateActivities($activities)
694 + {
695 + $actionTranslations = [
696 + 'changed' => __('changed', 'fluent-boards'),
697 + 'updated' => __('updated', 'fluent-boards'),
698 + 'added' => __('added', 'fluent-boards'),
699 + 'removed' => __('removed', 'fluent-boards'),
700 + 'created' => __('created', 'fluent-boards'),
701 + 'closed' => __('closed', 'fluent-boards'),
702 + 'reopened' => __('reopened', 'fluent-boards'),
703 + 'joined' => __('joined', 'fluent-boards'),
704 + 'left' => __('left', 'fluent-boards'),
705 + 'cloned' => __('cloned', 'fluent-boards'),
706 + 'deleted' => __('deleted', 'fluent-boards'),
707 + 'archived' => __('archived', 'fluent-boards'),
708 + 'restored' => __('restored', 'fluent-boards'),
709 + 'set' => __('set', 'fluent-boards'),
710 + 'moved' => __('moved', 'fluent-boards'),
711 + ];
712 +
713 + $columnTranslations = [
714 + 'task' => __('task', 'fluent-boards'),
715 + 'description' => __('description', 'fluent-boards'),
716 + 'board' => __('board', 'fluent-boards'),
717 + 'assignee' => __('assignee', 'fluent-boards'),
718 + 'label' => __('label', 'fluent-boards'),
719 + 'Due Date' => __('Due Date', 'fluent-boards'),
720 + 'Start Date' => __('Start Date', 'fluent-boards'),
721 + 'priority' => __('priority', 'fluent-boards'),
722 + 'comment' => __('comment', 'fluent-boards'),
723 + 'a reply' => __('a reply', 'fluent-boards'),
724 + 'subtask' => __('subtask', 'fluent-boards'),
725 + 'subtask group' => __('subtask group', 'fluent-boards'),
726 + 'subtask group title' => __('subtask group title', 'fluent-boards'),
727 + 'stage' => __('stage', 'fluent-boards'),
728 + 'the associate email' => __('the associate email', 'fluent-boards'),
729 + 'attachment' => __('attachment', 'fluent-boards'),
730 + 'repeat task' => __('repeat task', 'fluent-boards'),
731 + 'Repeat Task' => __('Repeat Task', 'fluent-boards'),
732 + 'tasks' => __('tasks', 'fluent-boards'),
733 + ];
734 +
735 + foreach ($activities as $activity) {
736 + $activity->action_key = $activity->action;
737 + $activity->column_key = $activity->column;
738 +
739 + if (isset($actionTranslations[$activity->action])) {
740 + $activity->action = $actionTranslations[$activity->action];
741 + }
742 + if (isset($columnTranslations[$activity->column])) {
743 + $activity->column = $columnTranslations[$activity->column];
744 + }
745 + }
565 746 }
566 747 }