| @@ -2,8 +2,9 @@ | ||
| 2 | 2 | |
| 3 | 3 | namespace forge12\contactform7\CF7DoubleOptIn; |
| 4 | 4 | |
| 5 | 5 | |
| 6 | +use Forge12\DoubleOptIn\Consent\ConsentGate; | |
| 6 | 7 | use Forge12\DoubleOptIn\Container\Container; |
| 7 | 8 | use Forge12\DoubleOptIn\EmailTemplates\PlaceholderMapper; |
| 8 | 9 | use Forge12\DoubleOptIn\EventSystem\EventDispatcherInterface; |
| 9 | 10 | use Forge12\DoubleOptIn\Events\Lifecycle\OptInConfirmedEvent; |
| @@ -8,8 +9,9 @@ | ||
| 8 | 9 | use Forge12\DoubleOptIn\EventSystem\EventDispatcherInterface; |
| 9 | 10 | use Forge12\DoubleOptIn\Events\Lifecycle\OptInConfirmedEvent; |
| 10 | 11 | use Forge12\DoubleOptIn\Events\Lifecycle\OptInCreatedEvent; |
| 11 | 12 | use Forge12\DoubleOptIn\Frontend\ErrorNotification; |
| 13 | +use Forge12\DoubleOptIn\Integration\FormIntegrationRegistry; | |
| 12 | 14 | use Forge12\DoubleOptIn\Integration\OptInError; |
| 13 | 15 | use Forge12\DoubleOptIn\Service\RateLimiter; |
| 14 | 16 | use Forge12\Shared\Logger; |
| 15 | 17 | use Forge12\Shared\LoggerInterface; |
| @@ -65,8 +67,34 @@ | ||
| 65 | 67 | */ |
| 66 | 68 | protected ?OptInError $lastCreationError = null; |
| 67 | 69 | |
| 68 | 70 | /** |
| 71 | + * Why the last maybeCreateOptIn() returned null, or null if it did not. | |
| 72 | + * | |
| 73 | + * Callers use it to answer in the form plugin's own words — Elementor | |
| 74 | + * adds the message to its AJAX response when the error must reach the | |
| 75 | + * visitor (OptInError::shouldShowToVisitor()). | |
| 76 | + * | |
| 77 | + * @return OptInError|null | |
| 78 | + */ | |
| 79 | + public function getLastCreationError(): ?OptInError { | |
| 80 | + return $this->lastCreationError; | |
| 81 | + } | |
| 82 | + | |
| 83 | + /** | |
| 84 | + * Remember the refusal and hand it to the frontend toast. | |
| 85 | + * | |
| 86 | + * @param OptInError $error The reason. | |
| 87 | + * @param int $formId The form. | |
| 88 | + * | |
| 89 | + * @return void | |
| 90 | + */ | |
| 91 | + protected function storeCreationError( OptInError $error, int $formId ): void { | |
| 92 | + $this->lastCreationError = $error; | |
| 93 | + ErrorNotification::store( $error, $formId ); | |
| 94 | + } | |
| 95 | + | |
| 96 | + /** | |
| 69 | 97 | * Get the validation status from the last validateOptIn() call. |
| 70 | 98 | * |
| 71 | 99 | * @return string One of: '', 'confirmed', 'already_confirmed', 'expired', 'not_found'. |
| 72 | 100 | */ |
| @@ -620,8 +648,26 @@ | ||
| 620 | 648 | return false; |
| 621 | 649 | } |
| 622 | 650 | |
| 623 | 651 | /** |
| 652 | + * Enable / Disable default mail. | |
| 653 | + * | |
| 654 | + * @param bool $status Enable (true) or disable (false) the default mail. | |
| 655 | + * @param int $postId The ID of the Post / Form. | |
| 656 | + * | |
| 657 | + * @since 2.3.3 | |
| 658 | + */ | |
| 659 | + $sendDefaultMail = (bool) apply_filters( 'f12_cf7_doubleoptin_send_default_mail', true, $OptIn->get_cf_form_id() ); | |
| 660 | + | |
| 661 | + /** | |
| 662 | + * Bind the follow-up plan before the confirmation is saved (see | |
| 663 | + * AbstractFormIntegration::validateOptIn). False when no adapter | |
| 664 | + * handles this integration → previous behaviour below. | |
| 665 | + */ | |
| 666 | + $coordinator = \Forge12\DoubleOptIn\FollowUp\FollowUpCoordinator::instance(); | |
| 667 | + $managed = $coordinator !== null && $coordinator->plan( $OptIn, $sendDefaultMail ); | |
| 668 | + | |
| 669 | + /** | |
| 624 | 670 | * Confirm the OptIn. |
| 625 | 671 | */ |
| 626 | 672 | if ( $this->updateOptInByHash( $hash, 1, $OptIn ) <= 0 ) { |
| 627 | 673 | $this->get_logger()->info( 'OptIn update failed', [ |
| @@ -633,17 +679,23 @@ | ||
| 633 | 679 | } |
| 634 | 680 | |
| 635 | 681 | $this->setValidationStatus( 'confirmed' ); |
| 636 | 682 | |
| 637 | - /** | |
| 638 | - * Enable / Disable default mail. | |
| 639 | - * | |
| 640 | - * @param bool $status Enable (true) or disable (false) the default mail. | |
| 641 | - * @param int $postId The ID of the Post / Form. | |
| 642 | - * | |
| 643 | - * @since 2.3.3 | |
| 644 | - */ | |
| 645 | - if ( ! apply_filters( 'f12_cf7_doubleoptin_send_default_mail', true, $OptIn->get_cf_form_id() ) ) { | |
| 683 | + if ( $managed ) { | |
| 684 | + // Planned actions (skipped ones included) are recorded by the | |
| 685 | + // coordinator; trigger_default_mail is not fired for managed | |
| 686 | + // opt-ins, so no listener can replay them a second time. | |
| 687 | + if ( $sendDefaultMail ) { | |
| 688 | + do_action( 'f12_cf7_doubleoptin_before_send_default_mail', $OptIn ); | |
| 689 | + } | |
| 690 | + $coordinator->run( $OptIn, \Forge12\DoubleOptIn\FollowUp\FollowUpAttempt::TRIGGER_CONFIRM ); | |
| 691 | + if ( $sendDefaultMail ) { | |
| 692 | + do_action( 'f12_cf7_doubleoptin_after_send_default_mail', $OptIn ); | |
| 693 | + } | |
| 694 | + return true; | |
| 695 | + } | |
| 696 | + | |
| 697 | + if ( ! $sendDefaultMail ) { | |
| 646 | 698 | $this->get_logger()->info( 'Default mail disabled for OptIn', [ |
| 647 | 699 | 'plugin' => 'double-opt-in', |
| 648 | 700 | 'form_id' => $OptIn->get_cf_form_id(), |
| 649 | 701 | 'optin_id' => $OptIn->get_id(), |
| @@ -768,16 +820,28 @@ | ||
| 768 | 820 | |
| 769 | 821 | /** |
| 770 | 822 | * Create the OptIn |
| 771 | 823 | * |
| 772 | - * @param int $formId The identifier of the form | |
| 773 | - * @param string $formHtml The HTML code of the form | |
| 774 | - * @param array $parameter The Post Parameter of the form. | |
| 775 | - * @param array $files The Files attached to the form. | |
| 824 | + * @param int $formId The identifier of the form | |
| 825 | + * @param string $formHtml The HTML code of the form | |
| 826 | + * @param array $parameter The Post Parameter of the form. | |
| 827 | + * @param array $files The Files attached to the form. | |
| 828 | + * @param array $knownFields The fields this form declares, `name => label` | |
| 829 | + * or a plain list. Only the consent gate uses | |
| 830 | + * them, to tell an unticked checkbox from a | |
| 831 | + * setting that points at a field which no | |
| 832 | + * longer exists. A shim that knows its form | |
| 833 | + * better than the registry does (Elementor | |
| 834 | + * has the Form_Record in hand) passes them in; | |
| 835 | + * everyone else leaves it empty and | |
| 836 | + * {@see self::resolveKnownFieldNames()} asks | |
| 837 | + * the integration. | |
| 776 | 838 | * |
| 777 | 839 | * @return OptIn|null |
| 778 | 840 | */ |
| 779 | - protected function maybeCreateOptIn( int $formId, string $formHtml, array $parameter, array $files = array() ): ?OptIn { | |
| 841 | + protected function maybeCreateOptIn( int $formId, string $formHtml, array $parameter, array $files = array(), array $knownFields = array() ): ?OptIn { | |
| 842 | + $this->lastCreationError = null; | |
| 843 | + | |
| 780 | 844 | $this->get_logger()->debug( 'maybeCreateOptIn called', [ |
| 781 | 845 | 'plugin' => 'double-opt-in', |
| 782 | 846 | 'class' => __CLASS__, |
| 783 | 847 | 'method' => __METHOD__, |
| @@ -820,9 +884,9 @@ | ||
| 820 | 884 | if ( empty( $recipient ) ) { |
| 821 | 885 | $this->get_logger()->warning( 'No recipient found, skipping OptIn creation', [ |
| 822 | 886 | 'plugin' => 'double-opt-in', |
| 823 | 887 | ] ); |
| 824 | - ErrorNotification::store( | |
| 888 | + $this->storeCreationError( | |
| 825 | 889 | OptInError::fromCode( OptInError::NO_RECIPIENT, [ 'form_id' => $formId ] ), |
| 826 | 890 | $formId |
| 827 | 891 | ); |
| 828 | 892 | return null; |
| @@ -828,8 +892,70 @@ | ||
| 828 | 892 | return null; |
| 829 | 893 | } |
| 830 | 894 | |
| 831 | 895 | /** |
| 896 | + * Consent gate (GDPR Art. 7) — same position in the flow as in | |
| 897 | + * AbstractFormIntegration::createOptIn(): after the recipient is | |
| 898 | + * known, before rate limiting. | |
| 899 | + * | |
| 900 | + * Until 5.4.0 this path had no gate at all. Everything that does | |
| 901 | + * not extend AbstractFormIntegration comes through here — | |
| 902 | + * Elementor, plus the CF7 and Avada legacy shims — so on those | |
| 903 | + * forms the acceptance checkbox was stored as consent proof | |
| 904 | + * without ever having been enforced. The banner in the admin UI | |
| 905 | + * said as much since 5.3.2; this closes it. | |
| 906 | + * | |
| 907 | + * ConsentGate::FIELD_UNKNOWN never rejects. See the class for why | |
| 908 | + * that matters: a stale `consent_field` must not take a site's | |
| 909 | + * registrations offline. | |
| 910 | + */ | |
| 911 | + $consentSnapshot = $this->loadConsentSnapshot( $formId, $formParameter ); | |
| 912 | + $consentField = $consentSnapshot['field']; | |
| 913 | + $consentGate = ConsentGate::evaluate( | |
| 914 | + $consentField, | |
| 915 | + $parameter, | |
| 916 | + $this->resolveKnownFieldNames( $formId, $knownFields ) | |
| 917 | + ); | |
| 918 | + | |
| 919 | + if ( $consentGate === ConsentGate::NOT_GIVEN && ! ConsentGate::isEnforced( $formId, $this->type ) ) { | |
| 920 | + $this->get_logger()->warning( 'Consent gate disabled by filter — accepting an unconfirmed submission', [ | |
| 921 | + 'plugin' => 'double-opt-in', | |
| 922 | + 'form_id' => $formId, | |
| 923 | + 'integration' => $this->type, | |
| 924 | + 'consent_field' => $consentField, | |
| 925 | + ] ); | |
| 926 | + $consentGate = ConsentGate::PASSED; | |
| 927 | + } | |
| 928 | + | |
| 929 | + if ( $consentGate === ConsentGate::NOT_GIVEN ) { | |
| 930 | + $this->get_logger()->info( 'Consent acceptance not given, rejecting OptIn', [ | |
| 931 | + 'plugin' => 'double-opt-in', | |
| 932 | + 'form_id' => $formId, | |
| 933 | + 'integration' => $this->type, | |
| 934 | + 'consent_field' => $consentField, | |
| 935 | + ] ); | |
| 936 | + do_action( 'f12_cf7_doubleoptin_consent_not_given', $formId, $consentField ); | |
| 937 | + $this->storeCreationError( | |
| 938 | + OptInError::fromCode( | |
| 939 | + OptInError::CONSENT_NOT_GIVEN, | |
| 940 | + [ 'form_id' => $formId, 'consent_field' => $consentField ] | |
| 941 | + ), | |
| 942 | + $formId | |
| 943 | + ); | |
| 944 | + return null; | |
| 945 | + } | |
| 946 | + | |
| 947 | + if ( $consentGate === ConsentGate::FIELD_UNKNOWN ) { | |
| 948 | + $this->get_logger()->warning( 'Consent field is not on this form — opt-in accepted without provable consent', [ | |
| 949 | + 'plugin' => 'double-opt-in', | |
| 950 | + 'form_id' => $formId, | |
| 951 | + 'integration' => $this->type, | |
| 952 | + 'consent_field' => $consentField, | |
| 953 | + ] ); | |
| 954 | + do_action( 'f12_doi_consent_field_unknown', $formId, $consentField, $this->type ); | |
| 955 | + } | |
| 956 | + | |
| 957 | + /** | |
| 832 | 958 | * Rate-Limiting: Check IP and email limits before creating OptIn. |
| 833 | 959 | */ |
| 834 | 960 | $rateLimiter = new RateLimiter(); |
| 835 | 961 | $ratSettings = CF7DoubleOptIn::getInstance()->getSettings(); |
| @@ -844,9 +970,9 @@ | ||
| 844 | 970 | 'ip' => $ip, |
| 845 | 971 | 'formId' => $formId, |
| 846 | 972 | ] ); |
| 847 | 973 | do_action( 'f12_cf7_doubleoptin_rate_limited', 'ip', $ip, $formId ); |
| 848 | - ErrorNotification::store( | |
| 974 | + $this->storeCreationError( | |
| 849 | 975 | OptInError::fromCode( OptInError::RATE_LIMIT_IP, [ 'ip' => $ip, 'form_id' => $formId ] ), |
| 850 | 976 | $formId |
| 851 | 977 | ); |
| 852 | 978 | return null; |
| @@ -858,9 +984,9 @@ | ||
| 858 | 984 | 'email' => $recipient, |
| 859 | 985 | 'formId' => $formId, |
| 860 | 986 | ] ); |
| 861 | 987 | do_action( 'f12_cf7_doubleoptin_rate_limited', 'email', $recipient, $formId ); |
| 862 | - ErrorNotification::store( | |
| 988 | + $this->storeCreationError( | |
| 863 | 989 | OptInError::fromCode( OptInError::RATE_LIMIT_EMAIL, [ 'email' => $recipient, 'form_id' => $formId ] ), |
| 864 | 990 | $formId |
| 865 | 991 | ); |
| 866 | 992 | return null; |
| @@ -919,9 +1045,9 @@ | ||
| 919 | 1045 | ! empty( $errorMsg ) ? $errorMsg : OptInError::fromCode( OptInError::RECIPIENT_INVALID )->getMessage(), |
| 920 | 1046 | [ 'email' => $recipient, 'form_id' => $formId ] |
| 921 | 1047 | ); |
| 922 | 1048 | |
| 923 | - ErrorNotification::store( $this->lastCreationError, $formId ); | |
| 1049 | + $this->storeCreationError( $this->lastCreationError, $formId ); | |
| 924 | 1050 | return null; |
| 925 | 1051 | } |
| 926 | 1052 | |
| 927 | 1053 | /** |
| @@ -934,22 +1060,10 @@ | ||
| 934 | 1060 | * AbstractFormIntegration::buildOptInProperties() path includes |
| 935 | 1061 | * both; this legacy path (used by Elementor + the CF7/Avada |
| 936 | 1062 | * legacy compat shims) used to only carry consent_text. |
| 937 | 1063 | */ |
| 938 | - $consentText = ''; | |
| 939 | - $consentField = ''; | |
| 940 | - try { | |
| 941 | - $container = \Forge12\DoubleOptIn\Container\Container::getInstance(); | |
| 942 | - $settingsService = $container->get( \Forge12\DoubleOptIn\FormSettings\FormSettingsService::class ); | |
| 943 | - $formSettings = $settingsService->getSettings( $formId ); | |
| 944 | - $consentText = $formSettings->consentText ?? ''; | |
| 945 | - $consentField = $formSettings->consentField ?? ''; | |
| 946 | - } catch ( \Exception $e ) { | |
| 947 | - $this->get_logger()->debug( 'Could not load consent snapshot from FormSettings', [ | |
| 948 | - 'plugin' => 'double-opt-in', | |
| 949 | - 'error' => $e->getMessage(), | |
| 950 | - ] ); | |
| 951 | - } | |
| 1064 | + $consentText = $consentSnapshot['text']; | |
| 1065 | + $consentField = $consentSnapshot['field']; | |
| 952 | 1066 | |
| 953 | 1067 | /** |
| 954 | 1068 | * Eigenschaften des OptIn-Objekts festlegen |
| 955 | 1069 | */ |
| @@ -998,9 +1112,9 @@ | ||
| 998 | 1112 | ] ); |
| 999 | 1113 | |
| 1000 | 1114 | do_action( 'f12_cf7_doubleoptin_creation_failed', $formId, $recipient ); |
| 1001 | 1115 | |
| 1002 | - ErrorNotification::store( | |
| 1116 | + $this->storeCreationError( | |
| 1003 | 1117 | OptInError::fromCode( OptInError::SAVE_FAILED, [ 'form_id' => $formId ] ), |
| 1004 | 1118 | $formId |
| 1005 | 1119 | ); |
| 1006 | 1120 | |
| @@ -1011,11 +1125,13 @@ | ||
| 1011 | 1125 | /** |
| 1012 | 1126 | * Validate if the optin is enabled. |
| 1013 | 1127 | */ |
| 1014 | 1128 | protected function isOptinEnabled( int $formId ): bool { |
| 1015 | - // Disable optin sending if the optin flag is set. | |
| 1016 | - if ( isset( $_GET['optin'] ) ) { | |
| 1017 | - $this->get_logger()->debug( 'Optin disabled due to optin flag in GET request', [ | |
| 1129 | + // Disable optin sending while our own post-confirmation replay runs. | |
| 1130 | + // Not `isset( $_GET['optin'] )`: that is client input and let a | |
| 1131 | + // submitter switch the double opt-in off. | |
| 1132 | + if ( \Forge12\DoubleOptIn\Integration\AbstractFormIntegration::isReplaying() ) { | |
| 1133 | + $this->get_logger()->debug( 'Optin disabled during post-confirmation replay', [ | |
| 1018 | 1134 | 'plugin' => 'double-opt-in', |
| 1019 | 1135 | 'class' => __CLASS__, |
| 1020 | 1136 | 'method' => __METHOD__, |
| 1021 | 1137 | ] ); |
| @@ -1128,8 +1244,23 @@ | ||
| 1128 | 1244 | return; |
| 1129 | 1245 | } |
| 1130 | 1246 | |
| 1131 | 1247 | /** |
| 1248 | + * Only for this integration's own opt-in, only right after it was | |
| 1249 | + * confirmed in this request, and only when no follow-up adapter | |
| 1250 | + * owns the files. Previously any `?optin=` request — an expired | |
| 1251 | + * link, a second click, another integration's hash — deleted the | |
| 1252 | + * stored files, including ones a pending action still needed. | |
| 1253 | + */ | |
| 1254 | + if ( ! $OptIn->isType( $this->type ) || self::$validationStatus !== 'confirmed' ) { | |
| 1255 | + return; | |
| 1256 | + } | |
| 1257 | + $coordinator = \Forge12\DoubleOptIn\FollowUp\FollowUpCoordinator::instance(); | |
| 1258 | + if ( $coordinator !== null && $coordinator->adapterFor( $OptIn ) !== null ) { | |
| 1259 | + return; | |
| 1260 | + } | |
| 1261 | + | |
| 1262 | + /** | |
| 1132 | 1263 | * Load all files |
| 1133 | 1264 | */ |
| 1134 | 1265 | $files = maybe_unserialize( $OptIn->get_files() ); |
| 1135 | 1266 | |
| @@ -1290,8 +1421,102 @@ | ||
| 1290 | 1421 | $this->get_logger()->warning( 'Failed to dispatch OptInConfirmedEvent', [ |
| 1291 | 1422 | 'plugin' => 'double-opt-in', |
| 1292 | 1423 | 'error' => $e->getMessage(), |
| 1293 | 1424 | ] ); |
| 1425 | + } | |
| 1426 | + } | |
| 1427 | + | |
| 1428 | + /** | |
| 1429 | + * Load the consent snapshot (wording + acceptance field) for a form. | |
| 1430 | + * | |
| 1431 | + * The authoritative source is `FormSettingsService`, not the | |
| 1432 | + * `$formParameter` array this legacy path carries — the settings the | |
| 1433 | + * admin edits in the React UI land in post_meta and only some of them | |
| 1434 | + * make it into `getParameter()`. `$formParameter` is used purely as a | |
| 1435 | + * fallback for the case where the container is not available. | |
| 1436 | + * | |
| 1437 | + * Read once per submission and used twice: by the consent gate before | |
| 1438 | + * the opt-in is created, and by the snapshot that is persisted with | |
| 1439 | + * it. They must agree — a gate that reads a different field than the | |
| 1440 | + * record stores would produce a proof of the wrong checkbox. | |
| 1441 | + * | |
| 1442 | + * @param int $formId The form being submitted. | |
| 1443 | + * @param array $formParameter The legacy form-parameter array. | |
| 1444 | + * | |
| 1445 | + * @return array{text:string,field:string} | |
| 1446 | + */ | |
| 1447 | + protected function loadConsentSnapshot( int $formId, array $formParameter = array() ): array { | |
| 1448 | + $snapshot = [ | |
| 1449 | + 'text' => (string) ( $formParameter['consent_text'] ?? '' ), | |
| 1450 | + 'field' => (string) ( $formParameter['consent_field'] ?? '' ), | |
| 1451 | + ]; | |
| 1452 | + | |
| 1453 | + try { | |
| 1454 | + $container = Container::getInstance(); | |
| 1455 | + $settingsService = $container->get( \Forge12\DoubleOptIn\FormSettings\FormSettingsService::class ); | |
| 1456 | + $formSettings = $settingsService->getSettings( $formId ); | |
| 1457 | + | |
| 1458 | + $snapshot['text'] = (string) ( $formSettings->consentText ?? '' ); | |
| 1459 | + $snapshot['field'] = (string) ( $formSettings->consentField ?? '' ); | |
| 1460 | + } catch ( \Throwable $e ) { | |
| 1461 | + $this->get_logger()->debug( 'Could not load consent snapshot from FormSettings', [ | |
| 1462 | + 'plugin' => 'double-opt-in', | |
| 1463 | + 'form_id' => $formId, | |
| 1464 | + 'error' => $e->getMessage(), | |
| 1465 | + ] ); | |
| 1466 | + } | |
| 1467 | + | |
| 1468 | + return $snapshot; | |
| 1469 | + } | |
| 1470 | + | |
| 1471 | + /** | |
| 1472 | + * The field names this form declares, for the consent gate. | |
| 1473 | + * | |
| 1474 | + * An unticked checkbox never reaches the server, so the payload alone | |
| 1475 | + * cannot distinguish "the visitor left the box alone" from "the | |
| 1476 | + * configured field does not exist any more". The form's own | |
| 1477 | + * definition can, and every integration exposes it through | |
| 1478 | + * `FormIntegrationInterface::getFormFields()`. | |
| 1479 | + * | |
| 1480 | + * A shim may pass the inventory in directly — Elementor does, because | |
| 1481 | + * its `Form_Record` lists every declared field including the empty | |
| 1482 | + * ones, while the composite form ID its `getFormFields()` wants | |
| 1483 | + * (`{postId}_{widgetId}`) is not what this legacy path carries. | |
| 1484 | + * Otherwise we ask the registry for the integration behind | |
| 1485 | + * `$this->type`. | |
| 1486 | + * | |
| 1487 | + * Returning an empty array is a valid answer and means "unknown" — | |
| 1488 | + * the gate then rejects nothing it cannot prove. | |
| 1489 | + * | |
| 1490 | + * @param int $formId The form being submitted. | |
| 1491 | + * @param array $explicit Inventory supplied by the caller, if any. | |
| 1492 | + * | |
| 1493 | + * @return array<int,string> | |
| 1494 | + */ | |
| 1495 | + protected function resolveKnownFieldNames( int $formId, array $explicit = array() ): array { | |
| 1496 | + if ( $explicit !== array() ) { | |
| 1497 | + return ConsentGate::normalizeFieldNames( $explicit ); | |
| 1498 | + } | |
| 1499 | + | |
| 1500 | + if ( $this->type === '' || ! class_exists( FormIntegrationRegistry::class ) ) { | |
| 1501 | + return array(); | |
| 1502 | + } | |
| 1503 | + | |
| 1504 | + try { | |
| 1505 | + $integration = FormIntegrationRegistry::getInstance()->get( $this->type ); | |
| 1506 | + if ( $integration === null ) { | |
| 1507 | + return array(); | |
| 1508 | + } | |
| 1509 | + | |
| 1510 | + return ConsentGate::normalizeFieldNames( $integration->getFormFields( $formId ) ); | |
| 1511 | + } catch ( \Throwable $e ) { | |
| 1512 | + $this->get_logger()->warning( 'Could not read the form field inventory for the consent gate', [ | |
| 1513 | + 'plugin' => 'double-opt-in', | |
| 1514 | + 'form_id' => $formId, | |
| 1515 | + 'error' => $e->getMessage(), | |
| 1516 | + ] ); | |
| 1517 | + | |
| 1518 | + return array(); | |
| 1294 | 1519 | } |
| 1295 | 1520 | } |
| 1296 | 1521 | |
| 1297 | 1522 | /** |