PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.6.2
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.6.2
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 | src/Admin/AdminRestController.php +33 -19 5.3.1 → 5.6.2 View file →
@@ -14,8 +14,9 @@
14 14 use Forge12\DoubleOptIn\Audit\AuditLogger;
15 15 use Forge12\DoubleOptIn\FormSettings\FormSettingsDTO;
16 16 use Forge12\DoubleOptIn\FormSettings\FormSettingsService;
17 17 use Forge12\DoubleOptIn\FormSettings\FormSettingsValidator;
18 +use Forge12\DoubleOptIn\Integration\SubmittedContent;
18 19 use Forge12\Shared\LoggerInterface;
19 20
20 21 if ( ! defined( 'ABSPATH' ) ) {
21 22 exit;
@@ -736,8 +737,18 @@
736 737 $where[] = 'cf_form_id = %d';
737 738 $params[] = (int) $formId;
738 739 }
739 740
741 + // Confirmed opt-ins whose follow-up actions failed or have an
742 + // unknown outcome — the admin's "needs attention" list.
743 + if ( sanitize_text_field( (string) ( $request->get_param( 'follow_up' ) ?? '' ) ) === 'problem' ) {
744 + $followUpTable = $wpdb->prefix . \Forge12\DoubleOptIn\Repository\FollowUpSchema::TABLE_NAME;
745 + $problems = \Forge12\DoubleOptIn\FollowUp\FollowUpStatus::problematic();
746 + $where[] = "EXISTS (SELECT 1 FROM {$followUpTable} fu WHERE fu.optin_id = {$table}.id AND fu.status IN ("
747 + . implode( ', ', array_fill( 0, count( $problems ), '%s' ) ) . '))';
748 + $params = array_merge( $params, $problems );
749 + }
750 +
740 751 $whereClause = implode( ' AND ', $where );
741 752
742 753 // Count
743 754 $countQuery = "SELECT COUNT(*) FROM {$table} WHERE {$whereClause}";
@@ -2418,28 +2429,31 @@
2418 2429 // configured, look up the value the user actually submitted.
2419 2430 // Truthy = explicit acknowledgment captured. Falsy = either
2420 2431 // gate wasn't enforced or this is a legacy record.
2421 2432 //
2422 - // Storage shape varies per integration:
2423 - // - CF7 / WPForms / GF (default path) store fields flat
2424 - // at the top level: $content[fieldName] = value.
2425 - // - Avada wraps fields under a `data` sub-key alongside
2426 - // metadata (field_labels, field_types, form_parameter)
2427 - // — its OnSubmit overrides the flat content set by
2428 - // createOptIn(). For Avada records, $content[fieldName]
2429 - // is undefined; the value lives at $content['data'][fieldName].
2433 + // Where that value sits differs per integration, and this
2434 + // reader got the list wrong twice:
2430 2435 //
2431 - // Pre-2026-05-01 we only checked the flat shape, so every
2432 - // Avada opt-in showed "User acknowledged: ✗ No" even when
2433 - // the user explicitly checked the GDPR box. The fallback
2434 - // below recognises the Avada shape too — adding a third
2435 - // shape would be the next addition.
2436 - $data['consentAcknowledged'] = ! empty( $data['consentField'] )
2437 - && is_array( $content )
2438 - && (
2439 - ! empty( $content[ $data['consentField'] ] )
2440 - || ! empty( $content['data'][ $data['consentField'] ] ?? null )
2441 - );
2436 + // 2026-05-01 Avada wraps its fields under `data`, so the
2437 + // flat lookup missed and every Avada opt-in
2438 + // showed "User acknowledged: ✗ No" even with
2439 + // the GDPR box explicitly checked.
2440 + // 2026-08-27 Elementor stores the whole $_POST parameter
2441 + // dict, so its fields sit under `form_fields`
2442 + // — the same symptom, one integration further
2443 + // on. The docblock added after the Avada fix
2444 + // had predicted exactly this ("adding a third
2445 + // shape would be the next addition").
2446 + //
2447 + // The shape list now lives in SubmittedContent, shared with
2448 + // OptInFrontend::addPlaceholders() — the other consumer that
2449 + // already knew all of them. A fourth integration with a
2450 + // fourth layout is taught to both at once.
2451 + //
2452 + // The lookup also tolerates a consent_field that the
2453 + // pre-5.3.2 sanitize_key() lowercased, so installations
2454 + // recover from the update without re-saving every form.
2455 + $data['consentAcknowledged'] = SubmittedContent::hasValue( $content, (string) $data['consentField'] );
2442 2456
2443 2457 // Parse mail_optin
2444 2458 $mailOptin = maybe_unserialize( $row['mail_optin'] );
2445 2459 $data['mailOptin'] = is_array( $mailOptin ) ? $mailOptin : array();