PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.8.1
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.8.1
5.8.0 5.8.1 5.7.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 All 41 releases
double-opt-in / src / Service / OptInMailTracker.php

OptInMailTracker.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 5.8.1, at src/Service/OptInMailTracker.php

135 lines 4.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Records whether the confirmation mail of an opt-in reached the mail server.
4 *
5 * Up to 5.7 no integration looked at the result of sending the confirmation
6 * mail: the opt-in was stored first, the mail went out (or not), and a
7 * failure stayed invisible — the most frequent support topic. This tracker
8 * needs no change in the form add-ons:
9 *
10 * 1. OptInCreatedEvent (fired by every integration before sending) names
11 * the opt-in and its recipient.
12 * 2. `wp_mail_failed` for that recipient marks it failed, with the error.
13 * 3. `f12_cf7_doubleoptin_sent` (fired by every integration after the
14 * send attempt) marks it sent, unless it failed in between.
15 *
16 * Paths that know the result themselves (CF7's return value, resends) report
17 * it through the action `f12_doi_optin_mail_result`.
18 *
19 * @package Forge12\DoubleOptIn\Service
20 * @since 5.8.0
21 */
22
23 declare( strict_types=1 );
24
25 namespace Forge12\DoubleOptIn\Service;
26
27 use Forge12\DoubleOptIn\Events\Lifecycle\OptInCreatedEvent;
28 use Forge12\DoubleOptIn\EventSystem\EventDispatcherInterface;
29 use Forge12\DoubleOptIn\Repository\OptInMailStatusRepository;
30
31 if ( ! defined( 'ABSPATH' ) ) {
32 exit;
33 }
34
35 class OptInMailTracker {
36
37 public const RESULT_ACTION = 'f12_doi_optin_mail_result';
38
39 /** @var OptInMailStatusRepository */
40 private $repository;
41
42 /** @var int */
43 private $currentId = 0;
44
45 /** @var string */
46 private $currentRecipient = '';
47
48 /** @var bool Whether the current opt-in already has a recorded outcome. */
49 private $recorded = false;
50
51 public function __construct( OptInMailStatusRepository $repository ) {
52 $this->repository = $repository;
53 }
54
55 public function register( EventDispatcherInterface $dispatcher ): void {
56 $dispatcher->addListener( OptInCreatedEvent::class, array( $this, 'onCreated' ) );
57 add_action( 'wp_mail_failed', array( $this, 'onMailFailed' ) );
58 add_action( 'f12_cf7_doubleoptin_sent', array( $this, 'onSent' ), 99, 0 );
59 add_action( self::RESULT_ACTION, array( $this, 'record' ), 10, 3 );
60 }
61
62 public function onCreated( OptInCreatedEvent $event ): void {
63 $this->currentId = $event->getOptInId();
64 $this->currentRecipient = self::normalise( $event->getEmail() );
65 $this->recorded = false;
66 }
67
68 /**
69 * @param mixed $error A WP_Error from wp_mail().
70 */
71 public function onMailFailed( $error ): void {
72 if ( $this->currentId <= 0 || $this->recorded || ! is_object( $error ) || ! method_exists( $error, 'get_error_message' ) ) {
73 return;
74 }
75 $data = method_exists( $error, 'get_error_data' ) ? $error->get_error_data() : array();
76 $to = is_array( $data ) && isset( $data['to'] ) ? (array) $data['to'] : array();
77
78 $recipients = array_map( array( self::class, 'normalise' ), $to );
79 if ( ! in_array( $this->currentRecipient, $recipients, true ) ) {
80 // Another mail of the same request (a notification, a reminder).
81 return;
82 }
83
84 $this->record( $this->currentId, false, (string) $error->get_error_message() );
85 }
86
87 public function onSent(): void {
88 if ( $this->currentId <= 0 || $this->recorded ) {
89 return;
90 }
91 $this->record( $this->currentId, true );
92 }
93
94 /**
95 * Store an outcome. Also the handler of `f12_doi_optin_mail_result`.
96 *
97 * @param mixed $optInId Opt-in ID.
98 * @param mixed $sent Whether wp_mail() accepted the mail.
99 * @param mixed $error Error text for a failure.
100 */
101 public function record( $optInId, $sent, $error = '' ): void {
102 $optInId = (int) $optInId;
103 if ( $optInId <= 0 ) {
104 return;
105 }
106 // First outcome of the current opt-in wins: wp_mail_failed carries
107 // the mail server's own message and fires before the integration
108 // reports its (generic) result.
109 if ( $optInId === $this->currentId && $this->recorded ) {
110 return;
111 }
112 $this->repository->mark(
113 $optInId,
114 $sent ? OptInMailStatusRepository::SENT : OptInMailStatusRepository::FAILED,
115 $sent ? '' : ( (string) $error !== '' ? (string) $error : __( 'The mail could not be handed to the mail server.', 'double-opt-in' ) )
116 );
117 if ( $optInId === $this->currentId ) {
118 $this->recorded = true;
119 }
120 }
121
122 /**
123 * Lower-case address out of "Name <[email protected]>" or "[email protected]".
124 *
125 * @param mixed $address
126 */
127 public static function normalise( $address ): string {
128 $address = (string) $address;
129 if ( preg_match( '/<([^>]+)>/', $address, $m ) ) {
130 $address = $m[1];
131 }
132 return strtolower( trim( $address ) );
133 }
134 }
135