PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.1.0
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.1.0
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.0, at includes/Core/Util/MailNotifier.php

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