PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.1.2
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.1.2
V-3.3.0 3.2.2 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 V3.0.3 V3.0.2 -3.0.1 V_3.0.0 1.1.1 1.1.8 1.2 1.3 1.4 1.4.18 1.5.2 1.9 2.0 2.10.0 2.10.1 2.10.2 All 137 releases
bit-form / includes / Core / Util / MailNotifier.php

MailNotifier.php in Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder 3.1.2, at includes/Core/Util/MailNotifier.php

427 lines 17.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace BitCode\BitForm\Core\Util;
4
5 if (!defined('ABSPATH')) {
6 exit;
7 }
8
9 use BitCode\BitForm\Admin\Form\Helpers;
10 use BitCode\BitForm\Core\Database\FormEntryMetaModel;
11 use BitCode\BitForm\Core\Form\FormManager;
12 use BitCode\BitForm\Core\Messages\EmailTemplateHandler;
13 use BitCode\BitForm\Core\Messages\PdfTemplateHandler;
14
15 final class MailNotifier
16 {
17 public static function notify($notifyDetails, $formID, $fieldValue, $entryID, $isDblOptin = false, $logId = '')
18 {
19 $apiResponse = new ApiResponse();
20 $formManager = FormManager::getInstance($formID);
21 $entryDetails = ['formId' => $formID, 'entryId' => $entryID, 'fieldValues' => $fieldValue];
22 $emailTemplateHandler = new EmailTemplateHandler($formID);
23 $attachments = [];
24 $tempPdfLink = '';
25 $pdfPassForEmail = '';
26 if (!empty($notifyDetails->pdfId) && is_string($notifyDetails->pdfId)) {
27 $pdfTemplateID = json_decode($notifyDetails->pdfId)->id;
28 $pdfTemplateHandler = new PdfTemplateHandler($formID);
29
30 $pdfTemplate = $pdfTemplateHandler->getById($pdfTemplateID);
31
32 // Bail out if the template lookup failed (WP_Error) or returned no row.
33 // Without this guard json_decode($pdfTemplate[0]->setting) fatals and the whole email is lost.
34 if (is_wp_error($pdfTemplate) || empty($pdfTemplate[0]) || !isset($pdfTemplate[0]->setting)) {
35 $apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'pdf'], 'errors', 'PDF template not found, skipping PDF attachment.', $entryDetails);
36 Log::debug_log([
37 'status' => 'error',
38 'code' => 'pdf_template_not_found',
39 'message' => 'PDF template not found, skipping PDF attachment',
40 'inputDetails' => [
41 'notifyDetails' => $notifyDetails,
42 'formID' => $formID,
43 'entryID' => $entryID,
44 'pdfTemplateID' => $pdfTemplateID,
45 ],
46 'responseDetails' => is_wp_error($pdfTemplate) ? $pdfTemplate->get_error_message() : 'empty result',
47 ]);
48 } else {
49 $pdfSetting = json_decode($pdfTemplate[0]->setting);
50
51 $path = BITFORMS_CONTENT_DIR . DIRECTORY_SEPARATOR . 'pdf';
52 // $fileName = 'bit-form-pdf-' . $formID . '-' . $entryID;
53
54 if (!is_dir($path)) {
55 wp_mkdir_p($path);
56 }
57
58 if (class_exists('\BitCode\BitFormPro\Admin\AppSetting\Pdf')) {
59 $serverPath = Helpers::getFullPathWithEncryptedEntryId($formID, $entryID);
60 $webPath = Helpers::getWebPathWithEncryptedEntryId($formID, $entryID);
61
62 if (isset($pdfSetting->password)) {
63 if (isset($pdfSetting->password->static) && $pdfSetting->password->static && !empty($pdfSetting->password->pass)) {
64 $pass = FieldValueHandler::replaceFieldWithValue($pdfSetting->password->pass, $fieldValue);
65 $pdfSetting->password->pass = $pass;
66 } elseif (isset($pdfSetting->password->dynamic)) {
67 $pass = Helpers::PDFPassHash($entryID);
68 $pdfSetting->password->pass = $pass;
69 }
70 }
71 if (isset($pdfSetting->pdfFileName)) {
72 $pdfSetting->pdfFileName = FieldValueHandler::replaceFieldWithValue($pdfSetting->pdfFileName, $fieldValue);
73 // allow developers to modify PDF filename
74 $pdfSetting->pdfFileName = apply_filters(
75 'bitform_filter_pdf_filename',
76 $pdfSetting->pdfFileName,
77 [
78 'form_id' => $formID,
79 'entry_id' => $entryID,
80 'field_values' => $fieldValue,
81 'pdf_setting' => $pdfSetting,
82 'template' => $pdfTemplate[0] ?? null,
83 ]
84 );
85 }
86
87 $fieldValue['entry_id'] = $entryID;
88
89 $pdfBody = FieldValueHandler::replaceFieldWithValue($pdfTemplate[0]->body, $fieldValue, $formID);
90 $pdfBody = FieldValueHandler::changeImagePathInHTMLString($pdfBody, $serverPath);
91 $pdfBody = FieldValueHandler::changeHrefPathInHTMLString($pdfBody, $webPath); // replace anchor tag href with constructed weburl
92
93 // allow developers to modify PDF body
94 $pdfBody = apply_filters(
95 'bitform_filter_pdf_body',
96 $pdfBody,
97 [
98 'form_id' => $formID,
99 'entry_id' => $entryID,
100 'field_values' => $fieldValue,
101 'pdf_setting' => $pdfSetting,
102 'template' => $pdfTemplate[0] ?? null,
103 ]
104 );
105
106 $generatedPdf = \BitCode\BitFormPro\Admin\AppSetting\Pdf::getInstance()->generator($pdfSetting, $pdfBody, $path, $entryID, 'F');
107
108 if (!is_wp_error($generatedPdf) && file_exists($generatedPdf)) {
109 $attachments[] = $generatedPdf;
110 $tempPdfLink = $generatedPdf;
111 $apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'pdf'], 'success', 'PDF successfully generated.', $entryDetails);
112 Log::debug_log([
113 'status' => 'success',
114 'code' => 'pdf_generated',
115 'message' => 'PDF successfully generated',
116 'inputDetails' => [
117 'notifyDetails' => $notifyDetails,
118 'formID' => $formID,
119 'entryID' => $entryID,
120 'isDblOptin' => $isDblOptin,
121 'logId' => $logId
122 ],
123 'responseDetails' => $generatedPdf
124 ]);
125 } else {
126 $apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'pdf'], 'errors', 'Error in generating PDF.', $entryDetails);
127 Log::debug_log([
128 'status' => 'error',
129 'code' => 'pdf_generation_error',
130 'message' => 'Error in generating PDF',
131 'inputDetails' => [
132 'notifyDetails' => $notifyDetails,
133 'formID' => $formID,
134 'entryID' => $entryID,
135 'isDblOptin' => $isDblOptin,
136 'logId' => $logId
137 ],
138 'responseDetails' => $generatedPdf->get_error_message()
139 ]);
140 }
141 }
142 }
143 }
144
145 if (is_string($notifyDetails->id)) {
146 $mailTemplateID = json_decode($notifyDetails->id)->id;
147 $mailTemplate = $emailTemplateHandler->getATemplate($mailTemplateID);
148 if (!is_wp_error($mailTemplate)) {
149 $mailTo = FieldValueHandler::validateMailArry($notifyDetails->to, $fieldValue);
150 if (!empty($mailTo)) {
151 $from_name = '';
152 if (isset($notifyDetails->from_name) && !empty($notifyDetails->from_name)) {
153 $from_name = $notifyDetails->from_name;
154 }
155 $mailHeaders = [
156 // 'Content-Type: text/html; charset=UTF-8',
157 // $embeddedMailHeader
158 ];
159 $from_mail = '';
160 if (!empty($notifyDetails->from)) {
161 $fromMail = FieldValueHandler::validateMailArry($notifyDetails->from, $fieldValue);
162 $headerFromName = !empty($notifyDetails->fromName) ? $notifyDetails->fromName : explode('@', $fromMail[0])[0];
163 $mailHeaders[] = "FROM: $headerFromName " . '<' . sanitize_email($fromMail[0]) . '>';
164 $from_mail = $fromMail[0];
165 }
166 (new MailConfig())->sendMail(['from_name' => $from_name, 'from_email' => $from_mail]);
167 $mailSubject = FieldValueHandler::replaceFieldWithValue($mailTemplate[0]->sub, $fieldValue, $formID);
168
169 // allow developers to modify email subject
170 $mailSubject = apply_filters(
171 'bitform_filter_email_subject',
172 $mailSubject,
173 [
174 'form_id' => $formID,
175 'entry_id' => $entryID,
176 'field_values' => $fieldValue,
177 'template' => $mailTemplate[0] ?? null,
178 'is_double_optin' => (bool) $isDblOptin,
179 ]
180 );
181
182 $mailBody = $mailTemplate[0]->body;
183 if (class_exists('\BitCode\BitFormPro\Admin\DownloadFile')) {
184 $downloadFile = new \BitCode\BitFormPro\Admin\DownloadFile();
185 $mailBody = $downloadFile->replacePdfShortCodeToLink($mailBody, $formID, $entryID);
186 $mailBody = $downloadFile->replaceShortCodeToPdfPassword($mailBody, $formID, $entryID);
187 }
188
189 $mailBody = FieldValueHandler::replaceFieldWithValue($mailBody, $fieldValue, $formID);
190 // Signature images: embed inline (cid:) so they render for non-logged-in recipients.
191 // Must run before changeImagePathInHTMLString so the src is still the raw filename.
192 $cidMap = [];
193 $sigBasePath = FileHandler::getEntriesFileUploadDir($formID, $entryID) . DIRECTORY_SEPARATOR;
194 $mailBody = self::embedSignatureImages($mailBody, $formManager, $fieldValue, $sigBasePath, $cidMap);
195 $webUrl = Helpers::getWebPathWithEncryptedEntryId($formID, $entryID);
196 $mailBody = FieldValueHandler::changeImagePathInHTMLString($mailBody, $webUrl);
197 $mailBody = FieldValueHandler::changeHrefPathInHTMLString($mailBody, $webUrl); // replace anchor tag href with constructed weburl
198
199 // allow developers to modify email body
200 $mailBody = apply_filters(
201 'bitform_filter_email_body',
202 $mailBody,
203 [
204 'form_id' => $formID,
205 'entry_id' => $entryID,
206 'field_values' => $fieldValue,
207 'template' => $mailTemplate[0] ?? null,
208 'is_double_optin' => (bool) $isDblOptin,
209 ]
210 );
211 if (!empty($notifyDetails->replyto)) {
212 $mailReplyTo = FieldValueHandler::validateMailArry($notifyDetails->replyto, $fieldValue);
213 if (is_array($mailReplyTo)) {
214 foreach ($mailReplyTo as $key => $emailAddress) {
215 $mailHeaders[] = 'Reply-To: ' . explode('@', $emailAddress)[0] . '<' . sanitize_email($emailAddress) . '>';
216 }
217 } else {
218 $mailHeaders[] = 'Reply-To: ' . explode('@', $mailReplyTo)[0] . '<' . sanitize_email($mailReplyTo) . '>';
219 }
220 }
221 $oldMailBody = $mailBody;
222 $data = [];
223 if ($isDblOptin && true === has_filter('bitform_email_body_text')) {
224 $urlParams = $formID . '_' . $entryID . '_' . $logId;
225 $data = apply_filters('bitform_email_body_text', $mailBody, $urlParams);
226 $mailBody = $data['mailbody'];
227 }
228
229 if (!empty($notifyDetails->bcc)) {
230 $mailBCC = FieldValueHandler::validateMailArry($notifyDetails->bcc, $fieldValue);
231 if (is_array($mailBCC)) {
232 foreach ($mailBCC as $key => $emailAddress) {
233 $mailHeaders[] = 'Bcc: ' . sanitize_email($emailAddress);
234 }
235 } else {
236 $mailHeaders[] = 'Bcc: ' . sanitize_email($mailBCC);
237 }
238 }
239 if (!empty($notifyDetails->cc)) {
240 $mailCC = FieldValueHandler::validateMailArry($notifyDetails->cc, $fieldValue);
241 if (is_array($mailCC)) {
242 foreach ($mailCC as $key => $emailAddress) {
243 $mailHeaders[] = 'Cc: ' . sanitize_email($emailAddress);
244 }
245 } else {
246 $mailHeaders[] = 'Cc: ' . sanitize_email($mailCC);
247 }
248 }
249 if (!empty($notifyDetails->attachment)) {
250 $fileFldKeys = $notifyDetails->attachment;
251 $fileBasePath = FileHandler::getEntriesFileUploadDir($formID, $entryID) . DIRECTORY_SEPARATOR;
252
253 // Normalize to array for consistent processing
254 $fileFldKeys = is_array($fileFldKeys) ? $fileFldKeys : [$fileFldKeys];
255
256 foreach ($fileFldKeys as $fldKey) {
257 $repeaterFieldKey = $formManager->isRepeatedField($fldKey);
258
259 if ($repeaterFieldKey) {
260 // Handle repeated file field
261 FileHandler::processRepeaterAttachment(
262 $repeaterFieldKey,
263 $fldKey,
264 $fieldValue,
265 $fileBasePath,
266 $attachments
267 );
268 } else {
269 // Handle regular file field
270 FileHandler::processRegularAttachment(
271 $fldKey,
272 $fieldValue,
273 $fileBasePath,
274 $attachments
275 );
276 }
277 }
278 }
279 $mailBody = stripcslashes($mailBody);
280 $mailSubject = stripcslashes($mailSubject);
281 $embedCb = static function ($phpmailer) use ($cidMap) {
282 foreach ($cidMap as $cid => $info) {
283 try {
284 $phpmailer->addEmbeddedImage($info['path'], $cid, $info['name']);
285 } catch (\Throwable $e) {
286 Log::debug_log("[Signature Embed] failed for {$info['path']} - " . $e->getMessage());
287 }
288 }
289 };
290 if (!empty($cidMap)) {
291 add_action('phpmailer_init', $embedCb);
292 }
293 add_filter('wp_mail_content_type', [self::class, 'filterMailContentType']);
294 $status = wp_mail($mailTo, $mailSubject, $mailBody, $mailHeaders, $attachments);
295
296 if (!$status) {
297 Log::debug_log([
298 'status' => 'error',
299 'code' => 'mail_not_sent',
300 'message' => 'Mail not sent',
301 'inputDetails' => [
302 'to' => $mailTo,
303 'subject' => $mailSubject,
304 'body' => $mailBody,
305 'headers' => $mailHeaders,
306 'attachments' => $attachments,
307 'notifyDetails' => $notifyDetails,
308 'formID' => $formID,
309 'entryID' => $entryID,
310 'isDblOptin' => $isDblOptin,
311 'logId' => $logId
312 ],
313 'responseDetails' => $status
314 ]);
315 $apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'smtp'], 'errors', 'Mail dose not send successfully', $entryDetails);
316 } else {
317 Log::debug_log([
318 'status' => 'success',
319 'code' => 'mail_sent',
320 'message' => 'Mail successfully sent',
321 'inputDetails' => [
322 'to' => $mailTo,
323 'subject' => $mailSubject,
324 'body' => $mailBody,
325 'headers' => $mailHeaders,
326 'attachments' => $attachments,
327 'notifyDetails' => $notifyDetails,
328 'formID' => $formID,
329 'entryID' => $entryID,
330 'isDblOptin' => $isDblOptin,
331 'logId' => $logId
332 ],
333 'responseDetails' => $status
334 ]);
335 $apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'smtp'], 'success', 'Mail successfully send.', $entryDetails);
336 }
337 if ($status && $isDblOptin && false !== strpos($oldMailBody, 'entry_confirmation_url')) {
338 $entryMeta = new FormEntryMetaModel();
339 $apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'smtp'], 'success', 'Mail successfully send.', $entryDetails);
340 // Form entry meta insert; meta_key/meta_value required to store dynamic field data per entry.
341 $entryMeta->insert(
342 [
343 'bitforms_form_entry_id' => $entryID,
344 'meta_key' => 'entry_confirm_activation',
345 'meta_value' => $data['token']
346 ]
347 );
348 }
349 remove_filter('wp_mail_content_type', [self::class, 'filterMailContentType']);
350 if (!empty($cidMap)) {
351 remove_action('phpmailer_init', $embedCb);
352 }
353 }
354 }
355 }
356
357 if (!empty($tempPdfLink)) {
358 wp_delete_file($tempPdfLink);
359 }
360 }
361
362 public static function filterMailContentType()
363 {
364 return 'text/html; charset=UTF-8';
365 }
366
367 /**
368 * Rewrite signature <img> tags to inline cid: references and collect the files
369 * to embed. Only signature-field images that are local & readable are embedded;
370 * external URLs and non-signature images are left untouched. When the form has
371 * no signature (or none is in the body) $cidMap stays empty and nothing changes.
372 */
373 private static function embedSignatureImages($html, $formManager, $fieldValue, $baseDir, &$cidMap)
374 {
375 if (empty($html)) {
376 return $html;
377 }
378
379 // Collect signature filenames for this entry.
380 $sigFiles = [];
381 foreach ($formManager->getFields() as $key => $detail) {
382 if (!isset($detail['type']) || 'signature' !== $detail['type']) {
383 continue;
384 }
385 $val = isset($fieldValue[$key]) ? $fieldValue[$key] : '';
386 foreach ((array) $val as $fn) {
387 $fn = is_string($fn) ? trim($fn) : '';
388 if ('' !== $fn && 'signature-failed.png' !== $fn) {
389 $sigFiles[basename($fn)] = true;
390 }
391 }
392 }
393 if (empty($sigFiles)) {
394 return $html;
395 }
396
397 return preg_replace_callback(
398 '/<img\s+[^>]*src=[\'"]([^\'"]+)[\'"][^>]*>/i',
399 function ($m) use ($baseDir, $sigFiles, &$cidMap) {
400 $src = $m[1];
401 if (filter_var($src, FILTER_VALIDATE_URL)) {
402 return $m[0]; // external URL, leave as-is
403 }
404 $name = basename($src);
405 if (!isset($sigFiles[$name])) {
406 return $m[0]; // not a signature image
407 }
408 $file = $baseDir . $name;
409 if (!is_readable($file)) {
410 return $m[0];
411 }
412 $cid = 'bfsig_' . md5($file);
413 $cidMap[$cid] = ['path' => $file, 'name' => $name];
414
415 // Replace only the src attribute value (leave alt untouched).
416 return preg_replace(
417 '/(src=[\'"])' . preg_quote($src, '/') . '([\'"])/i',
418 '${1}cid:' . $cid . '${2}',
419 $m[0],
420 1
421 );
422 },
423 $html
424 );
425 }
426 }
427