| @@ -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(); |