PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.6.3
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.6.3
5.6.2 5.6.3 5.6.1 5.6.0 5.5.0 5.4.0 5.3.2 5.3.1 5.1.6 5.1.5 trunk 2.1.5 2.11 2.12 2.13 2.15 3.0.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.51 3.0.60 3.0.61 3.0.62 All 38 releases
← All changes | compatibility/OptInFrontend.class.php +87 -18 5.4.0 → 5.6.3 View file →
@@ -67,8 +67,34 @@
67 67 */
68 68 protected ?OptInError $lastCreationError = null;
69 69
70 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 + /**
71 97 * Get the validation status from the last validateOptIn() call.
72 98 *
73 99 * @return string One of: '', 'confirmed', 'already_confirmed', 'expired', 'not_found'.
74 100 */
@@ -622,8 +648,26 @@
622 648 return false;
623 649 }
624 650
625 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 + /**
626 670 * Confirm the OptIn.
627 671 */
628 672 if ( $this->updateOptInByHash( $hash, 1, $OptIn ) <= 0 ) {
629 673 $this->get_logger()->info( 'OptIn update failed', [
@@ -635,17 +679,23 @@
635 679 }
636 680
637 681 $this->setValidationStatus( 'confirmed' );
638 682
639 - /**
640 - * Enable / Disable default mail.
641 - *
642 - * @param bool $status Enable (true) or disable (false) the default mail.
643 - * @param int $postId The ID of the Post / Form.
644 - *
645 - * @since 2.3.3
646 - */
647 - 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 ) {
648 698 $this->get_logger()->info( 'Default mail disabled for OptIn', [
649 699 'plugin' => 'double-opt-in',
650 700 'form_id' => $OptIn->get_cf_form_id(),
651 701 'optin_id' => $OptIn->get_id(),
@@ -788,8 +838,10 @@
788 838 *
789 839 * @return OptIn|null
790 840 */
791 841 protected function maybeCreateOptIn( int $formId, string $formHtml, array $parameter, array $files = array(), array $knownFields = array() ): ?OptIn {
842 + $this->lastCreationError = null;
843 +
792 844 $this->get_logger()->debug( 'maybeCreateOptIn called', [
793 845 'plugin' => 'double-opt-in',
794 846 'class' => __CLASS__,
795 847 'method' => __METHOD__,
@@ -832,9 +884,9 @@
832 884 if ( empty( $recipient ) ) {
833 885 $this->get_logger()->warning( 'No recipient found, skipping OptIn creation', [
834 886 'plugin' => 'double-opt-in',
835 887 ] );
836 - ErrorNotification::store(
888 + $this->storeCreationError(
837 889 OptInError::fromCode( OptInError::NO_RECIPIENT, [ 'form_id' => $formId ] ),
838 890 $formId
839 891 );
840 892 return null;
@@ -881,9 +933,9 @@
881 933 'integration' => $this->type,
882 934 'consent_field' => $consentField,
883 935 ] );
884 936 do_action( 'f12_cf7_doubleoptin_consent_not_given', $formId, $consentField );
885 - ErrorNotification::store(
937 + $this->storeCreationError(
886 938 OptInError::fromCode(
887 939 OptInError::CONSENT_NOT_GIVEN,
888 940 [ 'form_id' => $formId, 'consent_field' => $consentField ]
889 941 ),
@@ -918,9 +970,9 @@
918 970 'ip' => $ip,
919 971 'formId' => $formId,
920 972 ] );
921 973 do_action( 'f12_cf7_doubleoptin_rate_limited', 'ip', $ip, $formId );
922 - ErrorNotification::store(
974 + $this->storeCreationError(
923 975 OptInError::fromCode( OptInError::RATE_LIMIT_IP, [ 'ip' => $ip, 'form_id' => $formId ] ),
924 976 $formId
925 977 );
926 978 return null;
@@ -932,9 +984,9 @@
932 984 'email' => $recipient,
933 985 'formId' => $formId,
934 986 ] );
935 987 do_action( 'f12_cf7_doubleoptin_rate_limited', 'email', $recipient, $formId );
936 - ErrorNotification::store(
988 + $this->storeCreationError(
937 989 OptInError::fromCode( OptInError::RATE_LIMIT_EMAIL, [ 'email' => $recipient, 'form_id' => $formId ] ),
938 990 $formId
939 991 );
940 992 return null;
@@ -993,9 +1045,9 @@
993 1045 ! empty( $errorMsg ) ? $errorMsg : OptInError::fromCode( OptInError::RECIPIENT_INVALID )->getMessage(),
994 1046 [ 'email' => $recipient, 'form_id' => $formId ]
995 1047 );
996 1048
997 - ErrorNotification::store( $this->lastCreationError, $formId );
1049 + $this->storeCreationError( $this->lastCreationError, $formId );
998 1050 return null;
999 1051 }
1000 1052
1001 1053 /**
@@ -1060,9 +1112,9 @@
1060 1112 ] );
1061 1113
1062 1114 do_action( 'f12_cf7_doubleoptin_creation_failed', $formId, $recipient );
1063 1115
1064 - ErrorNotification::store(
1116 + $this->storeCreationError(
1065 1117 OptInError::fromCode( OptInError::SAVE_FAILED, [ 'form_id' => $formId ] ),
1066 1118 $formId
1067 1119 );
1068 1120
@@ -1073,11 +1125,13 @@
1073 1125 /**
1074 1126 * Validate if the optin is enabled.
1075 1127 */
1076 1128 protected function isOptinEnabled( int $formId ): bool {
1077 - // Disable optin sending if the optin flag is set.
1078 - if ( isset( $_GET['optin'] ) ) {
1079 - $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', [
1080 1134 'plugin' => 'double-opt-in',
1081 1135 'class' => __CLASS__,
1082 1136 'method' => __METHOD__,
1083 1137 ] );
@@ -1186,8 +1240,23 @@
1186 1240 'class' => __CLASS__,
1187 1241 'method' => __METHOD__,
1188 1242 'hash' => $hash,
1189 1243 ] );
1244 + return;
1245 + }
1246 +
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 ) {
1190 1259 return;
1191 1260 }
1192 1261
1193 1262 /**