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 +27 -101 5.6.2 → 5.5.0 View file →
@@ -9,12 +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 -use Forge12\DoubleOptIn\Frontend\ErrorNotification;
17 13 use forge12\contactform7\CF7DoubleOptIn\Category;
18 14 use forge12\contactform7\CF7DoubleOptIn\CF7DoubleOptIn;
19 15 use forge12\contactform7\CF7DoubleOptIn\HTMLSelect;
20 16 use forge12\contactform7\CF7DoubleOptIn\OptIn;
@@ -88,9 +84,9 @@
88 84
89 85 // Confirmation mail hooks
90 86 add_action( 'f12_cf7_doubleoptin_before_send_default_mail', array( $this, 'beforeSendDefaultMail' ) );
91 87 add_action( 'f12_cf7_doubleoptin_after_send_default_mail', array( $this, 'afterSendDefaultMail' ) );
92 - add_action( 'f12_cf7_doubleoptin_trigger_default_mail', array( $this, 'onTriggerDefaultMail' ) );
88 + add_action( 'f12_cf7_doubleoptin_trigger_default_mail', array( $this, 'sendConfirmationMail' ) );
93 89
94 90 // File hand-off + pending-cleanup. CF7 attaches files to the
95 91 // confirmation mail in attachExtraAttachments (hooked on
96 92 // wpcf7_before_send_mail during sendConfirmationMail). Cleanup
@@ -249,10 +245,10 @@
249 245 )
250 246 );
251 247
252 248 if ( ! $this->isOptInEnabled( $formId ) ) {
253 - // Our own post-confirmation replay: attach the stored files.
254 - if ( self::isReplaying() ) {
249 + // Handle file attachments for confirmation
250 + if ( isset( $_GET['optin'] ) ) {
255 251 $this->attachStoredFiles( $submission );
256 252 }
257 253 return;
258 254 }
@@ -287,19 +283,14 @@
287 283 // Always prevent the original CF7 mail from being sent when opt-in creation fails
288 284 add_filter( 'wpcf7_skip_mail', '__return_true' );
289 285
290 286 $error = self::getLastError();
291 - // Abort with the reason instead of CF7's "sent": CF7 then keeps the
292 - // visitor's input and shows the message in the form. A refused
293 - // consent always takes this path (OptInError::isAlwaysShown()).
294 - if ( $error && $error->shouldShowToVisitor( (int) $formId ) ) {
287 + if ( $error && apply_filters( 'f12_cf7_doubleoptin_show_validation_error', false ) ) {
295 288 $message = apply_filters( 'f12_cf7_doubleoptin_error_message', $error->getMessage(), $error, $formId );
296 289 if ( method_exists( $submission, 'set_response' ) ) {
297 290 $submission->set_response( $message );
298 291 }
299 292 $abort = true;
300 - // The form shows it now — no second copy in the toast.
301 - ErrorNotification::forget();
302 293 }
303 294 return;
304 295 }
305 296
@@ -370,51 +361,9 @@
370 361 /**
371 362 * {@inheritdoc}
372 363 */
373 364 public function sendConfirmationMail( OptIn $optIn ): void {
374 - self::runAsReplay(
375 - function () use ( $optIn ) {
376 - $this->replaySubmission( $optIn );
377 - }
378 - );
379 - }
380 -
381 - /**
382 - * Listener on the global `f12_cf7_doubleoptin_trigger_default_mail`.
383 - * That action fires for every integration's opt-in; this used to run
384 - * the CF7 submission for Elementor opt-ins too, overwriting $_POST.
385 - * Managed opt-ins go through the follow-up coordinator, so a second
386 - * trigger never sends twice.
387 - *
388 - * @param OptIn $optIn The confirmed opt-in.
389 - *
390 - * @since 5.6.0
391 - */
392 - public function onTriggerDefaultMail( OptIn $optIn ): void {
393 - if ( ! $optIn->isType( $this->getIdentifier() ) ) {
394 - return;
395 - }
396 -
397 - $coordinator = FollowUpCoordinator::instance();
398 - if ( $coordinator !== null && $coordinator->plan( $optIn, true ) ) {
399 - $coordinator->run( $optIn, FollowUpAttempt::TRIGGER_LEGACY );
400 - return;
401 - }
402 -
403 - $this->sendConfirmationMail( $optIn );
404 - }
405 -
406 - /**
407 - * Re-run the stored submission through CF7 and report what CF7 says.
408 - *
409 - * Must run inside {@see runAsReplay()} so our own
410 - * `wpcf7_before_send_mail` listener processes it as the confirmed
411 - * submission (attachments) instead of creating a new opt-in.
412 - *
413 - * @since 5.6.0
414 - */
415 - public function replaySubmission( OptIn $optIn ): FollowUpResult {
416 - if ( ! $this->isAvailable() || ! class_exists( '\\WPCF7_ContactForm' ) || ! class_exists( '\\WPCF7_Submission' ) ) {
365 + if ( ! $this->isAvailable() ) {
417 366 $this->getLogger()->warning(
418 367 'CF7 not available for confirmation mail',
419 368 array(
420 369 'plugin' => 'double-opt-in',
@@ -419,11 +368,18 @@
419 368 array(
420 369 'plugin' => 'double-opt-in',
421 370 )
422 371 );
423 - return FollowUpResult::failedRetryable( 'integration_unavailable' );
372 + return;
424 373 }
425 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
426 382 $contactForm = \WPCF7_ContactForm::get_instance( $optIn->get_cf_form_id() );
427 383 if ( ! $contactForm ) {
428 384 $this->getLogger()->warning(
429 385 'CF7 form not found for confirmation mail',
@@ -431,52 +387,31 @@
431 387 'plugin' => 'double-opt-in',
432 388 'form_id' => $optIn->get_cf_form_id(),
433 389 )
434 390 );
435 - return FollowUpResult::failedPermanent( 'form_missing' );
391 + return;
436 392 }
437 393
438 - $data = maybe_unserialize( $optIn->get_content() );
439 - if ( ! is_array( $data ) ) {
440 - return FollowUpResult::failedPermanent( 'payload_missing' );
441 - }
394 + // Add attachment hook
395 + add_action( 'wpcf7_before_send_mail', array( $this, 'attachExtraAttachments' ), 10, 3 );
442 396
443 - $previousPost = $_POST;
444 - $previousOptIn = $this->currentOptIn;
445 - $this->currentOptIn = $optIn;
446 - $status = '';
397 + // Disable validation and spam checks before creating submission
398 + $this->beforeSendConfirmationMail();
447 399
448 - try {
449 - $_POST = SanitizeHelper::sanitize_array( $data );
400 + // Create submission and send mail
401 + $submission = \WPCF7_Submission::get_instance( $contactForm );
450 402
451 - // Disable validation and spam checks before creating submission
452 - $this->beforeSendConfirmationMail();
403 + // Re-enable validation and spam checks
404 + $this->afterSendConfirmationMail();
453 405
454 - // Create submission and send mail. Attachments are added by
455 - // onSubmit() → attachStoredFiles() while isReplaying().
456 - $submission = \WPCF7_Submission::get_instance( $contactForm );
457 -
458 - if ( is_object( $submission ) && method_exists( $submission, 'get_status' ) ) {
459 - $status = (string) $submission->get_status();
460 - }
461 - } finally {
462 - // Re-enable validation and spam checks, restore request state.
463 - $this->afterSendConfirmationMail();
464 - $_POST = $previousPost;
465 - $this->currentOptIn = $previousOptIn;
466 - }
467 -
468 406 $this->getLogger()->info(
469 407 'Confirmation mail triggered via CF7',
470 408 array(
471 - 'plugin' => 'double-opt-in',
472 - 'form_id' => $optIn->get_cf_form_id(),
473 - 'optin_id' => $optIn->get_id(),
474 - 'cf7_status' => $status,
409 + 'plugin' => 'double-opt-in',
410 + 'form_id' => $optIn->get_cf_form_id(),
411 + 'optin_id' => $optIn->get_id(),
475 412 )
476 413 );
477 -
478 - return CF7FollowUpAdapter::mapStatus( $status );
479 414 }
480 415
481 416 /**
482 417 * Handle opt-in confirmation from URL.
@@ -537,11 +472,10 @@
537 472 *
538 473 * @return void
539 474 */
540 475 private function attachStoredFiles( $submission ): void {
541 - // The opt-in being replayed — not the one named in the URL, which
542 - // a cron or admin retry does not have (and a visitor controls).
543 - $optIn = $this->currentOptIn;
476 + $hash = sanitize_text_field( $_GET['optin'] );
477 + $optIn = OptIn::get_by_hash( $hash );
544 478
545 479 if ( ! $optIn ) {
546 480 return;
547 481 }
@@ -603,16 +537,8 @@
603 537 *
604 538 * @since 4.3.0
605 539 */
606 540 public function cleanupPendingAfterMail( OptIn $optIn ): void {
607 - // Managed opt-ins: CF7FollowUpAdapter::onSettled() cleans up only
608 - // once the mail was actually handed over. This hook fires after
609 - // the attempt regardless of its outcome.
610 - $coordinator = FollowUpCoordinator::instance();
611 - if ( $coordinator !== null && $coordinator->adapterFor( $optIn ) !== null ) {
612 - return;
613 - }
614 -
615 541 // Template-method's $hash arg is unused inside processFilesOnConfirm;
616 542 // passing an empty string keeps the contract tight.
617 543 $this->processFilesOnConfirm( '', $optIn );
618 544 }