| @@ -128,9 +128,9 @@ | ||
| 128 | 128 | 'remind_at' => 'sanitize_text_field', |
| 129 | 129 | 'scope' => 'sanitize_text_field', |
| 130 | 130 | 'source' => 'sanitize_text_field', |
| 131 | 131 | 'source_id' => 'sanitize_text_field', |
| 132 | - 'description' => 'wp_kses_post', | |
| 132 | + 'description' => 'fluent_boards_sanitize_description', | |
| 133 | 133 | 'due_date' => 'sanitize_text_field', |
| 134 | 134 | 'start_at' => 'sanitize_text_field', |
| 135 | 135 | 'log_minutes' => 'sanitize_text_field', |
| 136 | 136 | 'last_completed' => 'sanitize_text_field', |
| @@ -154,9 +154,9 @@ | ||
| 154 | 154 | 'board_id' => 'intval', |
| 155 | 155 | 'title' => 'sanitize_text_field', |
| 156 | 156 | 'parent_id' => 'intval', |
| 157 | 157 | 'type' => 'sanitize_text_field', |
| 158 | - 'description' => 'wp_kses_post', | |
| 158 | + 'description' => 'fluent_boards_sanitize_description', | |
| 159 | 159 | 'currency' => 'sanitize_text_field', |
| 160 | 160 | 'image_url' => 'sanitize_url', |
| 161 | 161 | 'is_auth_require' => 'intval', |
| 162 | 162 | 'crm_contact_id' => 'intval', |
| @@ -162,8 +162,9 @@ | ||
| 162 | 162 | 'crm_contact_id' => 'intval', |
| 163 | 163 | 'id' => 'sanitize_text_field', |
| 164 | 164 | 'is_image' => 'rest_sanitize_boolean', |
| 165 | 165 | 'color' => 'sanitize_text_field', // sanitize_hex_color doesn't work when color code is greater than 6 characters |
| 166 | + 'reset' => 'rest_sanitize_boolean', | |
| 166 | 167 | 'created_by' => 'intval', |
| 167 | 168 | ]; |
| 168 | 169 | |
| 169 | 170 | return self::sanitizeData($data, $fieldMaps); |
| @@ -197,8 +198,9 @@ | ||
| 197 | 198 | { |
| 198 | 199 | $fieldMaps = [ |
| 199 | 200 | 'bg_color' => 'sanitize_text_field', |
| 200 | 201 | 'color' => 'sanitize_text_field', |
| 202 | + 'color_preset' => 'sanitize_key', | |
| 201 | 203 | 'label' => 'sanitize_text_field', |
| 202 | 204 | 'boardId' => 'intval', |
| 203 | 205 | 'task_id' => 'intval', |
| 204 | 206 | 'meta_value' => 'intval', |
| @@ -214,18 +216,48 @@ | ||
| 214 | 216 | 'stage' => 'sanitize_text_field', |
| 215 | 217 | 'newPosition' => 'intval', |
| 216 | 218 | 'priority' => 'sanitize_text_field', |
| 217 | 219 | 'type' => 'sanitize_text_field', |
| 220 | + 'description' => 'fluent_boards_sanitize_description', | |
| 221 | + 'group_id' => 'intval', | |
| 218 | 222 | 'board_id' => 'intval', |
| 219 | 223 | 'created_by' => 'intval', |
| 220 | 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', | |
| 221 | 230 | ]; |
| 222 | 231 | |
| 223 | - 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; | |
| 224 | 243 | } |
| 225 | 244 | |
| 245 | + /** Store full custom-field messages in LONGTEXT while keeping legacy VARCHAR columns bounded. */ | |
| 226 | 246 | public static function createActivity($data) |
| 227 | 247 | { |
| 248 | + if (!empty($data['settings']['custom_field_id'])) { | |
| 249 | + $payload = []; | |
| 250 | + foreach (['column', 'old_value', 'new_value', 'description'] as $key) { | |
| 251 | + $payload[$key] = $data[$key] ?? null; | |
| 252 | + } | |
| 253 | + $data['settings']['custom_field_activity_payload'] = true; | |
| 254 | + $data['description'] = wp_json_encode($payload); | |
| 255 | + $data['column'] = Constant::ACTIVITY_CUSTOM_FIELD; | |
| 256 | + $data['old_value'] = null; | |
| 257 | + $data['new_value'] = null; | |
| 258 | + } | |
| 259 | + | |
| 228 | 260 | return Activity::create($data); |
| 229 | 261 | } |
| 230 | 262 | |
| 231 | 263 | public static function sanitizeTaskMeta($data) |
| @@ -316,8 +348,9 @@ | ||
| 316 | 348 | } |
| 317 | 349 | return fluent_boards_page_url() . 'boards/' . $boardId; |
| 318 | 350 | } |
| 319 | 351 | |
| 352 | + /** Format a CRM contact, avoiding a database lookup for unassociated tasks. */ | |
| 320 | 353 | public static function crm_contact($id) |
| 321 | 354 | { |
| 322 | 355 | if (!defined('FLUENTCRM')) { |
| 323 | 356 | return ''; |
| @@ -322,8 +355,12 @@ | ||
| 322 | 355 | if (!defined('FLUENTCRM')) { |
| 323 | 356 | return ''; |
| 324 | 357 | } |
| 325 | 358 | |
| 359 | + if (!$id) { | |
| 360 | + return null; | |
| 361 | + } | |
| 362 | + | |
| 326 | 363 | $contact = \FluentCrm\App\Models\Subscriber::with(['tags', 'lists'])->find($id); |
| 327 | 364 | |
| 328 | 365 | if (!$contact) { |
| 329 | 366 | return null; |
| @@ -416,9 +453,10 @@ | ||
| 416 | 453 | |
| 417 | 454 | return $users; |
| 418 | 455 | } |
| 419 | 456 | |
| 420 | - public static function sanitizeUsersArray($users, $boardId = null) | |
| 457 | + // Callers formatting multiple lists may supply a resolved board-manager result. | |
| 458 | + public static function sanitizeUsersArray($users, $boardId = null, $isBoardManager = null) | |
| 421 | 459 | { |
| 422 | 460 | if (current_user_can('list_users')) { |
| 423 | 461 | return $users; |
| 424 | 462 | } |
| @@ -424,9 +462,9 @@ | ||
| 424 | 462 | } |
| 425 | 463 | |
| 426 | 464 | $sanitizedUsers = []; |
| 427 | 465 | |
| 428 | - if(!PermissionManager::isBoardManager($boardId)) //Todo: may create permission security issue, will be modified later | |
| 466 | + if (!($isBoardManager ?? PermissionManager::isBoardManager($boardId))) | |
| 429 | 467 | { |
| 430 | 468 | $currentUser = wp_get_current_user(); |
| 431 | 469 | if($currentUser && isset($currentUser->user_email)){ |
| 432 | 470 | $currentUserEmail = $currentUser->user_email; |
| @@ -481,18 +519,26 @@ | ||
| 481 | 519 | public static function getPriorityOptions() |
| 482 | 520 | { |
| 483 | 521 | return [ |
| 484 | 522 | [ |
| 485 | - 'id' => 'low', | |
| 486 | - 'title' => 'Low' | |
| 523 | + 'id' => '', | |
| 524 | + 'title' => 'No priority' | |
| 487 | 525 | ], |
| 488 | 526 | [ |
| 527 | + 'id' => 'urgent', | |
| 528 | + 'title' => 'Urgent' | |
| 529 | + ], | |
| 530 | + [ | |
| 531 | + 'id' => 'high', | |
| 532 | + 'title' => 'High' | |
| 533 | + ], | |
| 534 | + [ | |
| 489 | 535 | 'id' => 'medium', |
| 490 | 536 | 'title' => 'Medium' |
| 491 | 537 | ], |
| 492 | 538 | [ |
| 493 | - 'id' => 'high', | |
| 494 | - 'title' => 'High' | |
| 539 | + 'id' => 'low', | |
| 540 | + 'title' => 'Low' | |
| 495 | 541 | ], |
| 496 | 542 | ]; |
| 497 | 543 | } |
| 498 | 544 | |
| @@ -570,8 +616,9 @@ | ||
| 570 | 616 | 'repeat_in' => 'intval', |
| 571 | 617 | 'repeat_type' => 'sanitize_text_field', |
| 572 | 618 | 'repeat_when_complete' => 'intval', |
| 573 | 619 | 'selected_month' => 'sanitize_text_field', |
| 620 | + 'selected_stage' => 'intval', | |
| 574 | 621 | 'board_id' => 'intval', |
| 575 | 622 | 'time' => 'sanitize_text_field', |
| 576 | 623 | 'time_zone' => 'sanitize_text_field', |
| 577 | 624 | 'next_repeat_date' => 'sanitize_text_field', |
| @@ -580,8 +627,27 @@ | ||
| 580 | 627 | |
| 581 | 628 | return self::sanitizeData($data, $fieldMaps); |
| 582 | 629 | } |
| 583 | 630 | |
| 631 | + /** | |
| 632 | + * Sanitize the author snapshot supplied by an external task integration. | |
| 633 | + * | |
| 634 | + * @param mixed $author | |
| 635 | + * @return array | |
| 636 | + */ | |
| 637 | + private static function sanitizeExternalTaskAuthor($author) | |
| 638 | + { | |
| 639 | + if (!is_array($author)) { | |
| 640 | + return []; | |
| 641 | + } | |
| 642 | + | |
| 643 | + return array_filter([ | |
| 644 | + 'name' => sanitize_text_field($author['name'] ?? ''), | |
| 645 | + 'email' => sanitize_email($author['email'] ?? ''), | |
| 646 | + 'photo' => esc_url_raw($author['photo'] ?? ''), | |
| 647 | + ]); | |
| 648 | + } | |
| 649 | + | |
| 584 | 650 | public static function sanitizeTaskForWebHook($data) |
| 585 | 651 | { |
| 586 | 652 | $fieldMaps = [ |
| 587 | 653 | 'title' => 'sanitize_text_field', |
| @@ -595,9 +661,10 @@ | ||
| 595 | 661 | 'lead_value' => 'doubleval', |
| 596 | 662 | 'remind_at' => 'sanitize_text_field', |
| 597 | 663 | 'scope' => 'sanitize_text_field', |
| 598 | 664 | 'source' => 'sanitize_text_field', |
| 599 | - 'description' => 'wp_kses_post', | |
| 665 | + 'source_id' => 'sanitize_text_field', | |
| 666 | + 'description' => 'fluent_boards_sanitize_description', | |
| 600 | 667 | 'due_date' => 'sanitize_text_field', |
| 601 | 668 | 'start_at' => 'sanitize_text_field', |
| 602 | 669 | 'log_minutes' => 'sanitize_text_field', |
| 603 | 670 | 'last_completed' => 'sanitize_text_field', |
| @@ -610,9 +677,19 @@ | ||
| 610 | 677 | 'position' => 'intval' |
| 611 | 678 | |
| 612 | 679 | ]; |
| 613 | 680 | |
| 614 | - return self::sanitizeData($data, $fieldMaps); | |
| 681 | + $data = self::sanitizeData($data, $fieldMaps); | |
| 682 | + | |
| 683 | + if (isset($data['settings']) && is_array($data['settings']) && isset($data['settings']['author'])) { | |
| 684 | + $data['settings'] = [ | |
| 685 | + 'author' => self::sanitizeExternalTaskAuthor($data['settings']['author']), | |
| 686 | + ]; | |
| 687 | + } else { | |
| 688 | + unset($data['settings']); | |
| 689 | + } | |
| 690 | + | |
| 691 | + return $data; | |
| 615 | 692 | } |
| 616 | 693 | |
| 617 | 694 | |
| 618 | 695 | public static function taskReminderTypes() |
| @@ -647,11 +724,13 @@ | ||
| 647 | 724 | 'deleted' => __('deleted', 'fluent-boards'), |
| 648 | 725 | 'archived' => __('archived', 'fluent-boards'), |
| 649 | 726 | 'restored' => __('restored', 'fluent-boards'), |
| 650 | 727 | 'set' => __('set', 'fluent-boards'), |
| 728 | + 'moved' => __('moved', 'fluent-boards'), | |
| 651 | 729 | ]; |
| 652 | 730 | |
| 653 | 731 | $columnTranslations = [ |
| 732 | + Constant::ACTIVITY_CUSTOM_FIELD => __('custom field', 'fluent-boards'), | |
| 654 | 733 | 'task' => __('task', 'fluent-boards'), |
| 655 | 734 | 'description' => __('description', 'fluent-boards'), |
| 656 | 735 | 'board' => __('board', 'fluent-boards'), |
| 657 | 736 | 'assignee' => __('assignee', 'fluent-boards'), |
| @@ -668,11 +747,15 @@ | ||
| 668 | 747 | 'the associate email' => __('the associate email', 'fluent-boards'), |
| 669 | 748 | 'attachment' => __('attachment', 'fluent-boards'), |
| 670 | 749 | 'repeat task' => __('repeat task', 'fluent-boards'), |
| 671 | 750 | 'Repeat Task' => __('Repeat Task', 'fluent-boards'), |
| 751 | + 'tasks' => __('tasks', 'fluent-boards'), | |
| 672 | 752 | ]; |
| 673 | 753 | |
| 674 | 754 | foreach ($activities as $activity) { |
| 755 | + $activity->action_key = $activity->action; | |
| 756 | + $activity->column_key = $activity->column; | |
| 757 | + | |
| 675 | 758 | if (isset($actionTranslations[$activity->action])) { |
| 676 | 759 | $activity->action = $actionTranslations[$activity->action]; |
| 677 | 760 | } |
| 678 | 761 | if (isset($columnTranslations[$activity->column])) { |