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 +300 -16 1.302.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',
@@ -60,9 +119,9 @@
60 119 'title' => 'sanitize_text_field',
61 120 'board_id' => 'intval',
62 121 'parent_id' => 'intval',
63 122 'crm_contact_id' => 'intval',
64 - 'task_type' => 'sanitize_text_field',
123 + 'type' => 'sanitize_text_field',
65 124 'stage' => 'sanitize_text_field',
66 125 'reminder_type' => 'sanitize_text_field',
67 126 'priority' => 'sanitize_text_field',
68 127 'lead_value' => 'doubleval',
@@ -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,10 @@
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',
167 + 'created_by' => 'intval',
106 168 ];
107 169
108 170 return self::sanitizeData($data, $fieldMaps);
109 171 }
@@ -134,10 +196,11 @@
134 196
135 197 public static function sanitizeLabel($data)
136 198 {
137 199 $fieldMaps = [
138 - 'bg_color' => 'sanitize_hex_color',
139 - 'color' => 'sanitize_hex_color',
200 + 'bg_color' => 'sanitize_text_field',
201 + 'color' => 'sanitize_text_field',
202 + 'color_preset' => 'sanitize_key',
140 203 'label' => 'sanitize_text_field',
141 204 'boardId' => 'intval',
142 205 'task_id' => 'intval',
143 206 'meta_value' => 'intval',
@@ -152,15 +215,32 @@
152 215 'title' => 'sanitize_text_field',
153 216 'stage' => 'sanitize_text_field',
154 217 'newPosition' => 'intval',
155 218 'priority' => 'sanitize_text_field',
156 - 'task_type' => 'sanitize_text_field',
219 + 'type' => 'sanitize_text_field',
220 + 'description' => 'fluent_boards_sanitize_description',
221 + 'group_id' => 'intval',
157 222 'board_id' => 'intval',
158 223 'created_by' => 'intval',
159 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',
160 230 ];
161 231
162 - 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;
163 243 }
164 244
165 245 public static function createActivity($data)
166 246 {
@@ -322,20 +402,43 @@
322 402 }
323 403
324 404 public static function sanitizeUserCollections($users)
325 405 {
406 + if (empty($users)) {
407 + return $users;
408 + }
409 +
410 + foreach ($users as $key => $user) {
411 + if (is_object($user) && isset($user->pivot)) {
412 + $settings = maybe_unserialize($user->pivot->settings);
413 + $user->role = Arr::get($settings, 'is_admin')
414 + ? 'Admin'
415 + : (Arr::has($settings, 'is_viewer_only') && Arr::get($settings, 'is_viewer_only')
416 + ? 'Viewer'
417 + : 'Member');
418 + }
419 + }
420 +
326 421 if (current_user_can('list_users')) {
327 422 return $users;
328 423 }
329 424
330 - if ($users) {
425 + if (is_object($users) && method_exists($users, 'makeHidden')) {
331 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);
332 434 }
333 435
334 436 return $users;
335 437 }
336 438
337 - 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)
338 441 {
339 442 if (current_user_can('list_users')) {
340 443 return $users;
341 444 }
@@ -341,13 +444,28 @@
341 444 }
342 445
343 446 $sanitizedUsers = [];
344 447
345 - if(!PermissionManager::isBoardManager($boardId)) //Todo: may create permission security issue, will be modified later
448 + if (!($isBoardManager ?? PermissionManager::isBoardManager($boardId)))
346 449 {
450 + $currentUser = wp_get_current_user();
451 + if($currentUser && isset($currentUser->user_email)){
452 + $currentUserEmail = $currentUser->user_email;
453 + }
347 454 foreach ($users as $user) {
348 - unset($user->user_email);
349 - unset($user->email);
455 +
456 + if($user['email'] === $currentUserEmail){
457 + $sanitizedUsers[] = $user;
458 + continue;
459 + }
460 +
461 + if (isset($user['email'])) {
462 + $user['email'] = self::obfuscateEmail($user['email']);
463 + }
464 + if (isset($user['user_email'])) {
465 + $user['user_email'] = self::obfuscateEmail($user['user_email']);
466 + }
467 +
350 468 $sanitizedUsers[] = $user;
351 469 }
352 470 } else {
353 471 foreach ($users as $user) {
@@ -357,22 +475,52 @@
357 475
358 476 return $sanitizedUsers;
359 477 }
360 478
479 + public static function obfuscateEmail($email)
480 + {
481 + if (!is_string($email) || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
482 + return $email; // Not a valid email, return as is
483 + }
484 +
485 + list($name, $domain) = explode('@', $email, 2);
486 +
487 + // Local part: show first 3 chars, then fixed ****
488 + $visibleLocal = substr($name, 0, 3);
489 + $maskedLocal = $visibleLocal . '****';
490 +
491 + // Domain: mask the main label to **** + last 2 chars, keep rest (TLDs) intact
492 + $domainParts = explode('.', $domain);
493 + $mainLabel = $domainParts[0] ?? '';
494 + $tail = strlen($mainLabel) >= 2 ? substr($mainLabel, -2) : $mainLabel;
495 + $domainParts[0] = '****' . $tail; // e.g., example.com -> ****le.com
496 + $maskedDomain = implode('.', $domainParts);
497 +
498 + return $maskedLocal . '@' . $maskedDomain;
499 + }
500 +
361 501 public static function getPriorityOptions()
362 502 {
363 503 return [
364 504 [
365 - 'id' => 'low',
366 - 'title' => 'Low'
505 + 'id' => '',
506 + 'title' => 'No priority'
367 507 ],
368 508 [
509 + 'id' => 'urgent',
510 + 'title' => 'Urgent'
511 + ],
512 + [
513 + 'id' => 'high',
514 + 'title' => 'High'
515 + ],
516 + [
369 517 'id' => 'medium',
370 518 'title' => 'Medium'
371 519 ],
372 520 [
373 - 'id' => 'high',
374 - 'title' => 'High'
521 + 'id' => 'low',
522 + 'title' => 'Low'
375 523 ],
376 524 ];
377 525 }
378 526
@@ -450,8 +598,9 @@
450 598 'repeat_in' => 'intval',
451 599 'repeat_type' => 'sanitize_text_field',
452 600 'repeat_when_complete' => 'intval',
453 601 'selected_month' => 'sanitize_text_field',
602 + 'selected_stage' => 'intval',
454 603 'board_id' => 'intval',
455 604 'time' => 'sanitize_text_field',
456 605 'time_zone' => 'sanitize_text_field',
457 606 'next_repeat_date' => 'sanitize_text_field',
@@ -458,6 +607,141 @@
458 607 'repeat_in_month_type' => 'sanitize_text_field',
459 608 ];
460 609
461 610 return self::sanitizeData($data, $fieldMaps);
611 + }
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 +
632 + public static function sanitizeTaskForWebHook($data)
633 + {
634 + $fieldMaps = [
635 + 'title' => 'sanitize_text_field',
636 + 'board_id' => 'intval',
637 + 'parent_id' => 'intval',
638 + 'crm_contact_id' => 'intval',
639 + 'type' => 'sanitize_text_field',
640 + 'stage' => 'sanitize_text_field',
641 + 'reminder_type' => 'sanitize_text_field',
642 + 'priority' => 'sanitize_text_field',
643 + 'lead_value' => 'doubleval',
644 + 'remind_at' => 'sanitize_text_field',
645 + 'scope' => 'sanitize_text_field',
646 + 'source' => 'sanitize_text_field',
647 + 'source_id' => 'sanitize_text_field',
648 + 'description' => 'fluent_boards_sanitize_description',
649 + 'due_date' => 'sanitize_text_field',
650 + 'start_at' => 'sanitize_text_field',
651 + 'log_minutes' => 'sanitize_text_field',
652 + 'last_completed' => 'sanitize_text_field',
653 + 'is_archived' => 'intval',
654 + 'previous_stage' => 'sanitize_text_field',
655 + 'new_stage' => 'sanitize_text_field',
656 + 'new_index' => 'intval',
657 + 'old_index' => 'intval',
658 + 'new_board_id' => 'intval',
659 + 'position' => 'intval'
660 +
661 + ];
662 +
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;
674 + }
675 +
676 +
677 + public static function taskReminderTypes()
678 + {
679 + $allowedTypes = [
680 + '30_minutes_before' => __('30 minutes before', 'fluent-boards'),
681 + '1_hour_before' => __('1 hour before', 'fluent-boards'),
682 + '2_hours_before' => __('2 hours before', 'fluent-boards'),
683 + '1_day_before' => __('1 day before', 'fluent-boards'),
684 + '2_days_before' => __('2 days before', 'fluent-boards'),
685 + '1_week_before' => __('1 week before', 'fluent-boards'),
686 + ];
687 +
688 + $allowedTypes = apply_filters('fluent_boards/task_reminder_types', $allowedTypes);
689 +
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 + }
462 746 }
463 747 }