PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.5.0
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.5.0
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/Integration/CF7Integration.php +26 -94 5.6.1 → 5.5.0 View file →
@@ -9,11 +9,8 @@
9 9 namespace Forge12\DoubleOptIn\Integration;
10 10
11 11 use Forge12\DoubleOptIn\Container\Container;
12 12 use Forge12\DoubleOptIn\EmailTemplates\PlaceholderMapper;
13 -use Forge12\DoubleOptIn\FollowUp\FollowUpAttempt;
14 -use Forge12\DoubleOptIn\FollowUp\FollowUpCoordinator;
15 -use Forge12\DoubleOptIn\FollowUp\FollowUpResult;
16 13 use forge12\contactform7\CF7DoubleOptIn\Category;
17 14 use forge12\contactform7\CF7DoubleOptIn\CF7DoubleOptIn;
18 15 use forge12\contactform7\CF7DoubleOptIn\HTMLSelect;
19 16 use forge12\contactform7\CF7DoubleOptIn\OptIn;
@@ -87,9 +84,9 @@
87 84
88 85 // Confirmation mail hooks
89 86 add_action( 'f12_cf7_doubleoptin_before_send_default_mail', array( $this, 'beforeSendDefaultMail' ) );
90 87 add_action( 'f12_cf7_doubleoptin_after_send_default_mail', array( $this, 'afterSendDefaultMail' ) );
91 - add_action( 'f12_cf7_doubleoptin_trigger_default_mail', array( $this, 'onTriggerDefaultMail' ) );
88 + add_action( 'f12_cf7_doubleoptin_trigger_default_mail', array( $this, 'sendConfirmationMail' ) );
92 89
93 90 // File hand-off + pending-cleanup. CF7 attaches files to the
94 91 // confirmation mail in attachExtraAttachments (hooked on
95 92 // wpcf7_before_send_mail during sendConfirmationMail). Cleanup
@@ -248,10 +245,10 @@
248 245 )
249 246 );
250 247
251 248 if ( ! $this->isOptInEnabled( $formId ) ) {
252 - // Our own post-confirmation replay: attach the stored files.
253 - if ( self::isReplaying() ) {
249 + // Handle file attachments for confirmation
250 + if ( isset( $_GET['optin'] ) ) {
254 251 $this->attachStoredFiles( $submission );
255 252 }
256 253 return;
257 254 }
@@ -364,51 +361,9 @@
364 361 /**
365 362 * {@inheritdoc}
366 363 */
367 364 public function sendConfirmationMail( OptIn $optIn ): void {
368 - self::runAsReplay(
369 - function () use ( $optIn ) {
370 - $this->replaySubmission( $optIn );
371 - }
372 - );
373 - }
374 -
375 - /**
376 - * Listener on the global `f12_cf7_doubleoptin_trigger_default_mail`.
377 - * That action fires for every integration's opt-in; this used to run
378 - * the CF7 submission for Elementor opt-ins too, overwriting $_POST.
379 - * Managed opt-ins go through the follow-up coordinator, so a second
380 - * trigger never sends twice.
381 - *
382 - * @param OptIn $optIn The confirmed opt-in.
383 - *
384 - * @since 5.6.0
385 - */
386 - public function onTriggerDefaultMail( OptIn $optIn ): void {
387 - if ( ! $optIn->isType( $this->getIdentifier() ) ) {
388 - return;
389 - }
390 -
391 - $coordinator = FollowUpCoordinator::instance();
392 - if ( $coordinator !== null && $coordinator->plan( $optIn, true ) ) {
393 - $coordinator->run( $optIn, FollowUpAttempt::TRIGGER_LEGACY );
394 - return;
395 - }
396 -
397 - $this->sendConfirmationMail( $optIn );
398 - }
399 -
400 - /**
401 - * Re-run the stored submission through CF7 and report what CF7 says.
402 - *
403 - * Must run inside {@see runAsReplay()} so our own
404 - * `wpcf7_before_send_mail` listener processes it as the confirmed
405 - * submission (attachments) instead of creating a new opt-in.
406 - *
407 - * @since 5.6.0
408 - */
409 - public function replaySubmission( OptIn $optIn ): FollowUpResult {
410 - if ( ! $this->isAvailable() || ! class_exists( '\\WPCF7_ContactForm' ) || ! class_exists( '\\WPCF7_Submission' ) ) {
365 + if ( ! $this->isAvailable() ) {
411 366 $this->getLogger()->warning(
412 367 'CF7 not available for confirmation mail',
413 368 array(
414 369 'plugin' => 'double-opt-in',
@@ -413,11 +368,18 @@
413 368 array(
414 369 'plugin' => 'double-opt-in',
415 370 )
416 371 );
417 - return FollowUpResult::failedRetryable( 'integration_unavailable' );
372 + return;
418 373 }
419 374
375 + $this->currentOptIn = $optIn;
376 +
377 + // Restore POST data
378 + $data = maybe_unserialize( $optIn->get_content() );
379 + $_POST = SanitizeHelper::sanitize_array( $data );
380 +
381 + // Get CF7 form
420 382 $contactForm = \WPCF7_ContactForm::get_instance( $optIn->get_cf_form_id() );
421 383 if ( ! $contactForm ) {
422 384 $this->getLogger()->warning(
423 385 'CF7 form not found for confirmation mail',
@@ -425,52 +387,31 @@
425 387 'plugin' => 'double-opt-in',
426 388 'form_id' => $optIn->get_cf_form_id(),
427 389 )
428 390 );
429 - return FollowUpResult::failedPermanent( 'form_missing' );
391 + return;
430 392 }
431 393
432 - $data = maybe_unserialize( $optIn->get_content() );
433 - if ( ! is_array( $data ) ) {
434 - return FollowUpResult::failedPermanent( 'payload_missing' );
435 - }
394 + // Add attachment hook
395 + add_action( 'wpcf7_before_send_mail', array( $this, 'attachExtraAttachments' ), 10, 3 );
436 396
437 - $previousPost = $_POST;
438 - $previousOptIn = $this->currentOptIn;
439 - $this->currentOptIn = $optIn;
440 - $status = '';
397 + // Disable validation and spam checks before creating submission
398 + $this->beforeSendConfirmationMail();
441 399
442 - try {
443 - $_POST = SanitizeHelper::sanitize_array( $data );
400 + // Create submission and send mail
401 + $submission = \WPCF7_Submission::get_instance( $contactForm );
444 402
445 - // Disable validation and spam checks before creating submission
446 - $this->beforeSendConfirmationMail();
403 + // Re-enable validation and spam checks
404 + $this->afterSendConfirmationMail();
447 405
448 - // Create submission and send mail. Attachments are added by
449 - // onSubmit() → attachStoredFiles() while isReplaying().
450 - $submission = \WPCF7_Submission::get_instance( $contactForm );
451 -
452 - if ( is_object( $submission ) && method_exists( $submission, 'get_status' ) ) {
453 - $status = (string) $submission->get_status();
454 - }
455 - } finally {
456 - // Re-enable validation and spam checks, restore request state.
457 - $this->afterSendConfirmationMail();
458 - $_POST = $previousPost;
459 - $this->currentOptIn = $previousOptIn;
460 - }
461 -
462 406 $this->getLogger()->info(
463 407 'Confirmation mail triggered via CF7',
464 408 array(
465 - 'plugin' => 'double-opt-in',
466 - 'form_id' => $optIn->get_cf_form_id(),
467 - 'optin_id' => $optIn->get_id(),
468 - 'cf7_status' => $status,
409 + 'plugin' => 'double-opt-in',
410 + 'form_id' => $optIn->get_cf_form_id(),
411 + 'optin_id' => $optIn->get_id(),
469 412 )
470 413 );
471 -
472 - return CF7FollowUpAdapter::mapStatus( $status );
473 414 }
474 415
475 416 /**
476 417 * Handle opt-in confirmation from URL.
@@ -531,11 +472,10 @@
531 472 *
532 473 * @return void
533 474 */
534 475 private function attachStoredFiles( $submission ): void {
535 - // The opt-in being replayed — not the one named in the URL, which
536 - // a cron or admin retry does not have (and a visitor controls).
537 - $optIn = $this->currentOptIn;
476 + $hash = sanitize_text_field( $_GET['optin'] );
477 + $optIn = OptIn::get_by_hash( $hash );
538 478
539 479 if ( ! $optIn ) {
540 480 return;
541 481 }
@@ -597,16 +537,8 @@
597 537 *
598 538 * @since 4.3.0
599 539 */
600 540 public function cleanupPendingAfterMail( OptIn $optIn ): void {
601 - // Managed opt-ins: CF7FollowUpAdapter::onSettled() cleans up only
602 - // once the mail was actually handed over. This hook fires after
603 - // the attempt regardless of its outcome.
604 - $coordinator = FollowUpCoordinator::instance();
605 - if ( $coordinator !== null && $coordinator->adapterFor( $optIn ) !== null ) {
606 - return;
607 - }
608 -
609 541 // Template-method's $hash arg is unused inside processFilesOnConfirm;
610 542 // passing an empty string keeps the contract tight.
611 543 $this->processFilesOnConfirm( '', $optIn );
612 544 }