| @@ -10,17 +10,31 @@ | ||
| 10 | 10 | use FluentCart\Framework\Support\Arr; |
| 11 | 11 | |
| 12 | 12 | class CheckoutFieldsSchema |
| 13 | 13 | { |
| 14 | + /** | |
| 15 | + * Per-request memo for getFieldsSettings(). In production every HTTP | |
| 16 | + * request runs in a fresh PHP process, so this lives exactly one request. | |
| 17 | + * Long-running processes that simulate multiple requests (test suites, | |
| 18 | + * CLI) must clear it between simulated requests via | |
| 19 | + * resetFieldsCache() — as a function-static it was | |
| 20 | + * unreachable and leaked the first request's field settings into every | |
| 21 | + * subsequent one. | |
| 22 | + */ | |
| 23 | + private static $fieldsCache = null; | |
| 14 | 24 | |
| 25 | + public static function resetFieldsCache(): void | |
| 26 | + { | |
| 27 | + self::$fieldsCache = null; | |
| 28 | + } | |
| 29 | + | |
| 15 | 30 | public static function titleMap(): array |
| 16 | 31 | { |
| 17 | 32 | return [ |
| 18 | - 'full_name' => __('Name', 'fluent-cart'), | |
| 19 | - 'first_name' => __('First Name', 'fluent-cart'), | |
| 20 | - 'last_name' => __('Last Name', 'fluent-cart'), | |
| 21 | - 'email' => __('Email', 'fluent-cart'), | |
| 22 | - 'company_name' => __('Company Name', 'fluent-cart'), | |
| 33 | + 'full_name' => __('Name', 'fluent-cart'), | |
| 34 | + 'first_name' => __('First Name', 'fluent-cart'), | |
| 35 | + 'last_name' => __('Last Name', 'fluent-cart'), | |
| 36 | + 'email' => __('Email', 'fluent-cart'), | |
| 23 | 37 | ]; |
| 24 | 38 | } |
| 25 | 39 | |
| 26 | 40 | public static function typeMap(): array |
| @@ -25,16 +39,25 @@ | ||
| 25 | 39 | |
| 26 | 40 | public static function typeMap(): array |
| 27 | 41 | { |
| 28 | 42 | return [ |
| 29 | - 'full_name' => 'text', | |
| 30 | - 'first_name' => 'text', | |
| 31 | - 'last_name' => 'text', | |
| 32 | - 'email' => 'email', | |
| 33 | - 'company_name' => 'text', | |
| 43 | + 'full_name' => 'text', | |
| 44 | + 'first_name' => 'text', | |
| 45 | + 'last_name' => 'text', | |
| 46 | + 'email' => 'email', | |
| 34 | 47 | ]; |
| 35 | 48 | } |
| 36 | 49 | |
| 50 | + private static function autocompleteMap(): array | |
| 51 | + { | |
| 52 | + return [ | |
| 53 | + 'full_name' => 'name', | |
| 54 | + 'first_name' => 'given-name', | |
| 55 | + 'last_name' => 'family-name', | |
| 56 | + 'email' => 'email', | |
| 57 | + ]; | |
| 58 | + } | |
| 59 | + | |
| 37 | 60 | public static function getNameEmailFieldsSchema($cart = null, $scope = 'render') |
| 38 | 61 | { |
| 39 | 62 | |
| 40 | 63 | $fieldData = []; |
| @@ -54,17 +77,20 @@ | ||
| 54 | 77 | |
| 55 | 78 | $fieldData['full_name'] = $customerName; |
| 56 | 79 | $fieldData['email'] = $customerEmail; |
| 57 | 80 | |
| 58 | - $fieldData['company_name'] = Arr::get($cart->checkout_data, 'form_data.billing_company_name', ''); | |
| 59 | - | |
| 60 | 81 | } |
| 61 | 82 | |
| 62 | 83 | $fieldsSchema = self::getFieldsSettings(); |
| 63 | 84 | |
| 64 | 85 | $titleMap = static::titleMap(); |
| 86 | + $autocompleteMap = static::autocompleteMap(); | |
| 65 | 87 | $nameFields = []; |
| 66 | - foreach (Arr::get($fieldsSchema, 'basic_info') as $fieldKey => $basicField) { | |
| 88 | + $infoFields = Arr::get($fieldsSchema, 'basic_info', []); | |
| 89 | + foreach ($infoFields as $fieldKey => $basicField) { | |
| 90 | + if (!isset($titleMap[$fieldKey])) { | |
| 91 | + continue; | |
| 92 | + } | |
| 67 | 93 | if (Arr::get($basicField, 'enabled', 'no') !== 'yes') { |
| 68 | 94 | continue; |
| 69 | 95 | } |
| 70 | 96 | $key = 'billing_' . $fieldKey; |
| @@ -78,9 +104,9 @@ | ||
| 78 | 104 | 'required' => $isRequired, |
| 79 | 105 | 'label' => '', |
| 80 | 106 | 'aria-label' => $label, |
| 81 | 107 | 'placeholder' => $label . ($isRequired ? ' *' : ''), |
| 82 | - 'autocomplete' => 'organization', | |
| 108 | + 'autocomplete' => isset($autocompleteMap[$fieldKey]) ? $autocompleteMap[$fieldKey] : 'on', | |
| 83 | 109 | 'value' => Arr::get($fieldData, $fieldKey) |
| 84 | 110 | ]; |
| 85 | 111 | } |
| 86 | 112 | |
| @@ -347,8 +373,9 @@ | ||
| 347 | 373 | |
| 348 | 374 | |
| 349 | 375 | $shippingConfig = Arr::get($fieldsConfig, 'shipping_address', []); |
| 350 | 376 | $billingConfig = Arr::get($fieldsConfig, 'billing_address', []); |
| 377 | + $businessConfig = Arr::get($fieldsConfig, 'business_details', []); | |
| 351 | 378 | $basicConfig = Arr::get($fieldsConfig, 'basic_info', []); |
| 352 | 379 | $basicSchema = []; |
| 353 | 380 | |
| 354 | 381 | if (static::isFullNameRequired()) { |
| @@ -398,11 +425,11 @@ | ||
| 398 | 425 | 'postcode' => [ |
| 399 | 426 | 'billing' => self::getRequirementType($billingConfig, 'postcode'), |
| 400 | 427 | 'shipping' => self::getRequirementType($shippingConfig, 'postcode') |
| 401 | 428 | ], |
| 402 | - 'company_name' => [ | |
| 403 | - 'billing' => self::getRequirementType($billingConfig, 'company_name'), | |
| 404 | - 'shipping' => self::getRequirementType($shippingConfig, 'company_name') | |
| 429 | + 'vat_number' => [ | |
| 430 | + 'billing' => self::getRequirementType($businessConfig, 'vat_number'), | |
| 431 | + 'shipping' => '' | |
| 405 | 432 | ], |
| 406 | 433 | 'phone' => [ |
| 407 | 434 | 'billing' => self::getRequirementType($billingConfig, 'phone'), |
| 408 | 435 | 'shipping' => self::getRequirementType($shippingConfig, 'phone') |
| @@ -461,13 +488,33 @@ | ||
| 461 | 488 | 'label' => __('Email Address', 'fluent-cart'), |
| 462 | 489 | 'type' => 'email', |
| 463 | 490 | 'can_alter' => 'no', |
| 464 | 491 | ], |
| 465 | - 'company_name' => [ | |
| 492 | + ], | |
| 493 | + 'business_details' => [ | |
| 494 | + 'company_name' => [ | |
| 466 | 495 | 'label' => __('Company', 'fluent-cart'), |
| 467 | 496 | 'type' => 'text', |
| 468 | 497 | 'can_alter' => 'yes', |
| 469 | - ] | |
| 498 | + ], | |
| 499 | + 'vat_number' => [ | |
| 500 | + 'label' => __('VAT/GST/Tax Number', 'fluent-cart'), | |
| 501 | + 'help_text' => __('Show a VAT/GST/Tax number field at checkout for customers to provide their tax ID.', 'fluent-cart'), | |
| 502 | + 'type' => 'text', | |
| 503 | + 'can_alter' => 'yes', | |
| 504 | + ], | |
| 505 | + 'legal_registration_id' => [ | |
| 506 | + 'label' => __('Legal Registration ID', 'fluent-cart'), | |
| 507 | + 'help_text' => __('Show a legal registration / business ID field at checkout (e.g. company registration number).', 'fluent-cart'), | |
| 508 | + 'type' => 'text', | |
| 509 | + 'can_alter' => 'yes', | |
| 510 | + ], | |
| 511 | + 'b2b_only_mode' => [ | |
| 512 | + 'label' => __('B2B Only Mode', 'fluent-cart'), | |
| 513 | + 'help_text' => __('When enabled, the B2B purchase toggle is hidden and business fields are always shown to all customers.', 'fluent-cart'), | |
| 514 | + 'type' => 'checkbox', | |
| 515 | + 'can_alter' => 'yes', | |
| 516 | + ], | |
| 470 | 517 | ], |
| 471 | 518 | 'billing_address' => [ |
| 472 | 519 | 'country' => [ |
| 473 | 520 | 'label' => __('Country', 'fluent-cart'), |
| @@ -556,8 +603,12 @@ | ||
| 556 | 603 | } |
| 557 | 604 | |
| 558 | 605 | public static function getFieldsSettings() |
| 559 | 606 | { |
| 607 | + if (self::$fieldsCache !== null) { | |
| 608 | + return self::$fieldsCache; | |
| 609 | + } | |
| 610 | + | |
| 560 | 611 | $defaults = [ |
| 561 | 612 | 'basic_info' => [ |
| 562 | 613 | 'full_name' => [ |
| 563 | 614 | 'required' => 'yes', |
| @@ -574,12 +625,25 @@ | ||
| 574 | 625 | 'email' => [ |
| 575 | 626 | 'required' => 'yes', |
| 576 | 627 | 'enabled' => 'yes' |
| 577 | 628 | ], |
| 578 | - 'company_name' => [ | |
| 629 | + ], | |
| 630 | + 'business_details' => [ | |
| 631 | + 'company_name' => [ | |
| 579 | 632 | 'required' => 'no', |
| 580 | 633 | 'enabled' => 'no' |
| 581 | - ] | |
| 634 | + ], | |
| 635 | + 'vat_number' => [ | |
| 636 | + 'required' => 'no', | |
| 637 | + 'enabled' => 'no' | |
| 638 | + ], | |
| 639 | + 'legal_registration_id' => [ | |
| 640 | + 'required' => 'no', | |
| 641 | + 'enabled' => 'no' | |
| 642 | + ], | |
| 643 | + 'b2b_only_mode' => [ | |
| 644 | + 'enabled' => 'no', | |
| 645 | + ], | |
| 582 | 646 | ], |
| 583 | 647 | 'billing_address' => [ |
| 584 | 648 | 'country' => [ |
| 585 | 649 | 'required' => 'yes', |
| @@ -664,10 +728,20 @@ | ||
| 664 | 728 | if (!$savedConfig || !is_array($savedConfig)) { |
| 665 | 729 | return $defaults; |
| 666 | 730 | } |
| 667 | 731 | |
| 668 | - $groupFields = ['basic_info', 'billing_address', 'shipping_address']; | |
| 732 | + // Migrate company_name and vat_number from the old basic_info location for existing stores | |
| 733 | + if (empty($savedConfig['business_details']) && !empty($savedConfig['basic_info'])) { | |
| 734 | + if (!empty($savedConfig['basic_info']['company_name'])) { | |
| 735 | + $savedConfig['business_details']['company_name'] = $savedConfig['basic_info']['company_name']; | |
| 736 | + } | |
| 737 | + if (!empty($savedConfig['basic_info']['vat_number'])) { | |
| 738 | + $savedConfig['business_details']['vat_number'] = $savedConfig['basic_info']['vat_number']; | |
| 739 | + } | |
| 740 | + } | |
| 669 | 741 | |
| 742 | + $groupFields = ['basic_info', 'billing_address', 'shipping_address', 'business_details']; | |
| 743 | + | |
| 670 | 744 | foreach ($defaults as $key => $default) { |
| 671 | 745 | $savedValues = Arr::get($savedConfig, $key, []); |
| 672 | 746 | if (in_array($key, $groupFields)) { |
| 673 | 747 | $savedValues = Arr::only($savedValues, array_keys($default)); |
| @@ -675,9 +749,9 @@ | ||
| 675 | 749 | |
| 676 | 750 | $defaults[$key] = wp_parse_args($savedValues, $default); |
| 677 | 751 | } |
| 678 | 752 | |
| 679 | - return $defaults; | |
| 753 | + return (self::$fieldsCache = $defaults); | |
| 680 | 754 | } |
| 681 | 755 | |
| 682 | 756 | public static function isTermsRequired(): bool |
| 683 | 757 | { |
| @@ -727,6 +801,58 @@ | ||
| 727 | 801 | |
| 728 | 802 | $isFirstNameEnabled = static::isFirstNameEnabled(); |
| 729 | 803 | $isLastNameEnabled = static::isLastNameEnabled(); |
| 730 | 804 | return !($isFirstNameEnabled || $isLastNameEnabled); |
| 805 | + } | |
| 806 | + | |
| 807 | + public static function isVatNumberEnabled(): bool | |
| 808 | + { | |
| 809 | + $fieldSettings = self::getFieldsSettings(); | |
| 810 | + return Arr::get($fieldSettings, 'business_details.vat_number.enabled') === 'yes'; | |
| 811 | + } | |
| 812 | + | |
| 813 | + public static function isCompanyNameEnabled(): bool | |
| 814 | + { | |
| 815 | + $fieldSettings = self::getFieldsSettings(); | |
| 816 | + return Arr::get($fieldSettings, 'business_details.company_name.enabled') === 'yes'; | |
| 817 | + } | |
| 818 | + | |
| 819 | + public static function isCompanyNameRequired(): bool | |
| 820 | + { | |
| 821 | + $fieldSettings = self::getFieldsSettings(); | |
| 822 | + return Arr::get($fieldSettings, 'business_details.company_name.enabled') === 'yes' | |
| 823 | + && Arr::get($fieldSettings, 'business_details.company_name.required') === 'yes'; | |
| 824 | + } | |
| 825 | + | |
| 826 | + public static function isVatNumberRequired(): bool | |
| 827 | + { | |
| 828 | + $fieldSettings = self::getFieldsSettings(); | |
| 829 | + return Arr::get($fieldSettings, 'business_details.vat_number.enabled') === 'yes' | |
| 830 | + && Arr::get($fieldSettings, 'business_details.vat_number.required') === 'yes'; | |
| 831 | + } | |
| 832 | + | |
| 833 | + public static function isLegalRegistrationIdEnabled(): bool | |
| 834 | + { | |
| 835 | + $fieldSettings = self::getFieldsSettings(); | |
| 836 | + return Arr::get($fieldSettings, 'business_details.legal_registration_id.enabled') === 'yes'; | |
| 837 | + } | |
| 838 | + | |
| 839 | + public static function isLegalRegistrationIdRequired(): bool | |
| 840 | + { | |
| 841 | + $fieldSettings = self::getFieldsSettings(); | |
| 842 | + return Arr::get($fieldSettings, 'business_details.legal_registration_id.enabled') === 'yes' | |
| 843 | + && Arr::get($fieldSettings, 'business_details.legal_registration_id.required') === 'yes'; | |
| 844 | + } | |
| 845 | + | |
| 846 | + public static function isB2BOnlyMode(): bool | |
| 847 | + { | |
| 848 | + $fieldSettings = self::getFieldsSettings(); | |
| 849 | + return Arr::get($fieldSettings, 'business_details.b2b_only_mode.enabled') === 'yes'; | |
| 850 | + } | |
| 851 | + | |
| 852 | + public static function hasAnyBusinessFields(): bool | |
| 853 | + { | |
| 854 | + return static::isCompanyNameEnabled() | |
| 855 | + || static::isVatNumberEnabled() | |
| 856 | + || static::isLegalRegistrationIdEnabled(); | |
| 731 | 857 | } |
| 732 | 858 | } |