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 +43 -19 5.1.6 → 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}";
@@ -1439,8 +1450,13 @@
1439 1450
1440 1451 public function getSettings( \WP_REST_Request $request ): \WP_REST_Response {
1441 1452 $defaults = array(
1442 1453 'telemetry' => 1,
1454 + // Optional "Double Opt-In by Forge12" credit on the confirmation
1455 + // page. Defaults to 0 and must stay that way: wordpress.org
1456 + // guideline 10 requires credit links to be off unless the site
1457 + // owner explicitly turns them on.
1458 + 'credit_link' => 0,
1443 1459 'delete' => 12,
1444 1460 'delete_unconfirmed' => 7,
1445 1461 'delete_period' => 'months',
1446 1462 'delete_unconfirmed_period' => 'months',
@@ -1515,8 +1531,13 @@
1515 1531 'type' => 'int',
1516 1532 'min' => 0,
1517 1533 'max' => 1,
1518 1534 ),
1535 + 'credit_link' => array(
1536 + 'type' => 'int',
1537 + 'min' => 0,
1538 + 'max' => 1,
1539 + ),
1519 1540 'privacy_policy_page' => array(
1520 1541 'type' => 'int',
1521 1542 'min' => 0,
1522 1543 ),
@@ -2408,28 +2429,31 @@
2408 2429 // configured, look up the value the user actually submitted.
2409 2430 // Truthy = explicit acknowledgment captured. Falsy = either
2410 2431 // gate wasn't enforced or this is a legacy record.
2411 2432 //
2412 - // Storage shape varies per integration:
2413 - // - CF7 / WPForms / GF (default path) store fields flat
2414 - // at the top level: $content[fieldName] = value.
2415 - // - Avada wraps fields under a `data` sub-key alongside
2416 - // metadata (field_labels, field_types, form_parameter)
2417 - // — its OnSubmit overrides the flat content set by
2418 - // createOptIn(). For Avada records, $content[fieldName]
2419 - // 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:
2420 2435 //
2421 - // Pre-2026-05-01 we only checked the flat shape, so every
2422 - // Avada opt-in showed "User acknowledged: ✗ No" even when
2423 - // the user explicitly checked the GDPR box. The fallback
2424 - // below recognises the Avada shape too — adding a third
2425 - // shape would be the next addition.
2426 - $data['consentAcknowledged'] = ! empty( $data['consentField'] )
2427 - && is_array( $content )
2428 - && (
2429 - ! empty( $content[ $data['consentField'] ] )
2430 - || ! empty( $content['data'][ $data['consentField'] ] ?? null )
2431 - );
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'] );
2432 2456
2433 2457 // Parse mail_optin
2434 2458 $mailOptin = maybe_unserialize( $row['mail_optin'] );
2435 2459 $data['mailOptin'] = is_array( $mailOptin ) ? $mailOptin : array();