PluginProbe
The Innovative Form Builder – IvyForms / 1.5
The Innovative Form Builder – IvyForms v1.5
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
← All changes | backend/src/Services/Mailer/MailerService.php +98 -9 1.4.1 → 1.5 View file →
@@ -16,12 +16,13 @@
16 16 * Send an email using WordPress wp_mail function
17 17 *
18 18 * @param Notification $notification The notification object with email data
19 19 * @param mixed[] $formData The submitted form data for template variables
20 + * @param string[] $attachments Absolute local file paths to attach
20 21 *
21 22 * @return bool Whether the email was sent successfully
22 23 */
23 - public function sendEmail(Notification $notification, array $formData): bool
24 + public function sendEmail(Notification $notification, array $formData, array $attachments = []): bool
24 25 {
25 26 // Extract notification data
26 27 $contact = $notification->getNotificationContact();
27 28 $toAddresses = $this->validatedAddresses($contact->getReceiver());
@@ -49,13 +50,28 @@
49 50 if ($cc !== '') {
50 51 $visibleHeaders[] = 'Cc: ' . $cc;
51 52 }
52 53
53 - // Send email using WordPress function
54 - $sent = wp_mail($to, $subject, $message, $visibleHeaders);
54 + $attachments = array_values(array_filter(
55 + $attachments,
56 + static function ($path): bool {
57 + return is_string($path) && $path !== '' && is_readable($path);
58 + }
59 + ));
55 60
56 - if ($sent && $bccAddresses !== []) {
57 - $sent = $this->sendBccCopies($bccAddresses, $subject, $message, $headers);
61 + $deliveredWithBcc = false;
62 + $sent = $this->sendPrimaryEmail(
63 + $to,
64 + $subject,
65 + $message,
66 + $visibleHeaders,
67 + $bccAddresses,
68 + $deliveredWithBcc,
69 + $attachments
70 + );
71 +
72 + if ($sent && !$deliveredWithBcc && $bccAddresses !== []) {
73 + $sent = $this->sendBccCopies($bccAddresses, $subject, $message, $headers, $attachments);
58 74 }
59 75
60 76 return $sent;
61 77 }
@@ -83,21 +99,73 @@
83 99 return $headers;
84 100 }
85 101
86 102 /**
103 + * SMTP can hide Bcc from To/Cc while still showing To/Cc to the Bcc
104 + * recipient. mail()/sendmail cannot, so those transports send copies later.
105 + *
106 + * @param string[] $headers
107 + * @param string[] $bccAddresses
108 + * @param string[] $attachments
109 + */
110 + private function sendPrimaryEmail(
111 + string $to,
112 + string $subject,
113 + string $message,
114 + array $headers,
115 + array $bccAddresses,
116 + bool &$deliveredWithBcc,
117 + array $attachments = []
118 + ): bool {
119 + $parsedBcc = [];
120 + foreach ($bccAddresses as $bccAddress) {
121 + $parsedBcc[] = $this->parseMailbox($bccAddress);
122 + }
123 +
124 + $attachBcc = static function (\PHPMailer\PHPMailer\PHPMailer $phpmailer) use (
125 + $parsedBcc,
126 + &$deliveredWithBcc
127 + ): void {
128 + if ($phpmailer->Mailer !== 'smtp' || $parsedBcc === []) {
129 + return;
130 + }
131 +
132 + foreach ($parsedBcc as $mailbox) {
133 + $phpmailer->addBCC($mailbox['email'], $mailbox['name']);
134 + }
135 +
136 + $deliveredWithBcc = true;
137 + };
138 +
139 + add_action('phpmailer_init', $attachBcc, 1000);
140 + try {
141 + return wp_mail($to, $subject, $message, $headers, $attachments);
142 + } finally {
143 + remove_action('phpmailer_init', $attachBcc, 1000);
144 + }
145 + }
146 +
147 + /**
87 148 * PHPMailer keeps the Bcc header in the message on the mail()/sendmail/qmail
88 149 * transports, so a shared send would expose Bcc addresses to the To and Cc
89 - * recipients. Each Bcc recipient therefore gets its own copy.
150 + * recipients. Each Bcc recipient therefore gets its own copy, without the
151 + * original To/Cc replayed as a Cc header (mail() would deliver to those too).
90 152 *
91 153 * @param string[] $bccAddresses
92 154 * @param string[] $headers
155 + * @param string[] $attachments
93 156 */
94 - private function sendBccCopies(array $bccAddresses, string $subject, string $message, array $headers): bool
95 - {
157 + private function sendBccCopies(
158 + array $bccAddresses,
159 + string $subject,
160 + string $message,
161 + array $headers,
162 + array $attachments = []
163 + ): bool {
96 164 $sent = true;
97 165
98 166 foreach ($bccAddresses as $bccAddress) {
99 - $sent = wp_mail($bccAddress, $subject, $message, $headers) && $sent;
167 + $sent = wp_mail($bccAddress, $subject, $message, $headers, $attachments) && $sent;
100 168 }
101 169
102 170 return $sent;
103 171 }
@@ -219,6 +287,27 @@
219 287 }
220 288 }
221 289
222 290 return $validated;
291 + }
292 +
293 + /**
294 + * Split a mailbox into the address PHPMailer expects and an optional name.
295 + *
296 + * @return array{email: string, name: string}
297 + */
298 + private function parseMailbox(string $address): array
299 + {
300 + $address = trim($address);
301 + if (preg_match('/^(.*)<([^>]+)>\s*$/', $address, $match)) {
302 + return [
303 + 'email' => trim($match[2]),
304 + 'name' => trim($match[1], " \t\"'"),
305 + ];
306 + }
307 +
308 + return [
309 + 'email' => $address,
310 + 'name' => '',
311 + ];
223 312 }
224 313 }