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 / DoiMailHeaders.php

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

150 lines 4.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Lets extensions add headers to the mails of the double opt-in itself.
4 *
5 * @package Forge12\DoubleOptIn\Service
6 * @since 5.8.0
7 */
8
9 declare( strict_types=1 );
10
11 namespace Forge12\DoubleOptIn\Service;
12
13 use Forge12\DoubleOptIn\Events\Lifecycle\OptInCreatedEvent;
14 use Forge12\DoubleOptIn\EventSystem\EventDispatcherInterface;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20 /**
21 * The confirmation mail leaves through five integrations and two resend
22 * paths, each building its own headers. Instead of a hook in each of
23 * them, this recognises the mail on its way through wp_mail() — the way
24 * OptInMailTracker recognises its failure — and runs its headers through
25 * `f12_doi_mail_headers`. addon-opt-out adds List-Unsubscribe there.
26 *
27 * A mail is expected after an opt-in is created (`confirmation`) or when
28 * a sender announces one (`resend`, `reminder`). The first wp_mail() to
29 * that recipient is it; the expectation then ends, as it does when the
30 * integration reports the confirmation mail as sent. Other mails of the
31 * request — notifications, a second form — pass untouched.
32 *
33 * @api Core API 4.6.0
34 */
35 class DoiMailHeaders {
36
37 public const FILTER = 'f12_doi_mail_headers';
38
39 public const KIND_CONFIRMATION = 'confirmation';
40 public const KIND_RESEND = 'resend';
41 public const KIND_REMINDER = 'reminder';
42
43 /** @var int */
44 private $optInId = 0;
45
46 /** @var string */
47 private $recipient = '';
48
49 /** @var string */
50 private $kind = '';
51
52 public function register( EventDispatcherInterface $dispatcher ): void {
53 $dispatcher->addListener(
54 OptInCreatedEvent::class,
55 function ( OptInCreatedEvent $event ): void {
56 $this->expect( $event->getOptInId(), $event->getEmail(), self::KIND_CONFIRMATION );
57 }
58 );
59 add_filter( 'wp_mail', array( $this, 'filterMail' ), 20 );
60 add_action( 'f12_cf7_doubleoptin_sent', array( $this, 'forget' ), 100, 0 );
61 }
62
63 /**
64 * Announce that the next mail to $recipient is a double opt-in mail.
65 */
66 public function expect( int $optInId, string $recipient, string $kind ): void {
67 $this->optInId = $optInId;
68 $this->recipient = OptInMailTracker::normalise( $recipient );
69 $this->kind = $kind;
70 }
71
72 public function forget(): void {
73 $this->optInId = 0;
74 $this->recipient = '';
75 $this->kind = '';
76 }
77
78 /**
79 * @param mixed $atts wp_mail() arguments.
80 *
81 * @return mixed
82 */
83 public function filterMail( $atts ) {
84 if ( $this->optInId <= 0 || ! is_array( $atts ) ) {
85 return $atts;
86 }
87
88 $to = $atts['to'] ?? array();
89 $to = is_array( $to ) ? $to : explode( ',', (string) $to );
90 if ( ! in_array( $this->recipient, array_map( array( OptInMailTracker::class, 'normalise' ), $to ), true ) ) {
91 return $atts;
92 }
93
94 $optInId = $this->optInId;
95 $kind = $this->kind;
96 $this->forget();
97
98 // Nobody listening: the mail leaves exactly as built.
99 if ( ! has_filter( self::FILTER ) ) {
100 return $atts;
101 }
102
103 $headers = self::lines( $atts['headers'] ?? array() );
104
105 /**
106 * Headers of a double opt-in mail (confirmation, resend, reminder).
107 *
108 * Return header lines (`Name: value`). Line breaks inside a line
109 * are removed.
110 *
111 * @since 5.8.0
112 *
113 * @param string[] $headers Header lines as they stand.
114 * @param int $optInId The opt-in the mail belongs to.
115 * @param string $kind confirmation, resend or reminder.
116 */
117 $filtered = apply_filters( 'f12_doi_mail_headers', $headers, $optInId, $kind );
118
119 $atts['headers'] = is_array( $filtered ) ? self::lines( $filtered ) : $headers;
120
121 return $atts;
122 }
123
124 /**
125 * wp_mail() takes headers as a newline-separated string or an array.
126 *
127 * @param mixed $headers
128 *
129 * @return string[]
130 */
131 private static function lines( $headers ): array {
132 if ( ! is_array( $headers ) ) {
133 $headers = explode( "\n", str_replace( "\r\n", "\n", (string) $headers ) );
134 }
135
136 $lines = array();
137 foreach ( $headers as $line ) {
138 if ( ! is_scalar( $line ) ) {
139 continue;
140 }
141 $line = trim( str_replace( array( "\r", "\n" ), ' ', (string) $line ) );
142 if ( $line !== '' && strpos( $line, ':' ) !== false ) {
143 $lines[] = $line;
144 }
145 }
146
147 return $lines;
148 }
149 }
150