PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 2.15.3
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v2.15.3
3.3.1 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 All 138 releases
← All changes | includes/Core/Util/MailNotifier.php +74 -260 3.3.12.15.3 View file →
@@ -1,112 +1,67 @@
1 1 <?php
2 2
3 3 namespace BitCode\BitForm\Core\Util;
4 4
5 -if (!defined('ABSPATH')) {
6 - exit;
7 -}
8 -
9 -use BitCode\BitForm\Admin\Form\Helpers;
10 5 use BitCode\BitForm\Core\Database\FormEntryMetaModel;
11 -use BitCode\BitForm\Core\Form\FormManager;
12 6 use BitCode\BitForm\Core\Messages\EmailTemplateHandler;
7 +use BitCode\BitForm\Core\Messages\PdfTemplateHandler;
8 +use BitCode\BitFormPro\Admin\AppSetting\Pdf;
13 9
14 10 final class MailNotifier
15 11 {
16 12 public static function notify($notifyDetails, $formID, $fieldValue, $entryID, $isDblOptin = false, $logId = '')
17 13 {
18 - $apiResponse = new ApiResponse();
19 - $formManager = FormManager::getInstance($formID);
20 - $entryDetails = ['formId' => $formID, 'entryId' => $entryID, 'fieldValues' => $fieldValue];
21 14 $emailTemplateHandler = new EmailTemplateHandler($formID);
22 15 $attachments = [];
23 - $tempPdfLinks = [];
16 + $tempPdfLink = '';
17 + if (!empty($notifyDetails->pdfId) && is_string($notifyDetails->pdfId)) {
18 + $pdfTemplateID = json_decode($notifyDetails->pdfId)->id;
19 + $pdfTemplateHandler = new PdfTemplateHandler($formID);
20 +
21 + $pdfTemplate = $pdfTemplateHandler->getById($pdfTemplateID);
22 + $pdfBody = FieldValueHandler::replaceFieldWithValue($pdfTemplate[0]->body, $fieldValue);
23 +
24 + $pdfSetting = json_decode($pdfTemplate[0]->setting);
25 +
26 + $path = BITFORMS_CONTENT_DIR . DIRECTORY_SEPARATOR . 'pdf';
27 + $fileName = 'bit-form-pdf-' . $formID . '-' . $entryID;
28 +
29 + if (!is_dir($path)) {
30 + mkdir($path, 0777, true);
31 + }
32 +
33 + if (class_exists('\BitCode\BitFormPro\Admin\AppSetting\Pdf')) {
34 + $generatedPdf = Pdf::getInstance()->generator($pdfSetting, $pdfBody, $path, $entryID, 'F');
35 +
36 + // $pdfFile = $path . DIRECTORY_SEPARATOR . $fileName . '.pdf';
37 +
38 + if (!is_wp_error($generatedPdf) && file_exists($generatedPdf)) {
39 + $attachments[] = $generatedPdf;
40 + $tempPdfLink = $generatedPdf;
41 + } else {
42 + Log::debug_log('Error in generating PDF: ' . $generatedPdf->get_error_message());
43 + }
44 + }
45 + }
46 +
24 47 if (is_string($notifyDetails->id)) {
25 - $mailTemplateID = Utilities::jsonObj($notifyDetails->id)->id ?? null;
48 + $mailTemplateID = json_decode($notifyDetails->id)->id;
26 49 $mailTemplate = $emailTemplateHandler->getATemplate($mailTemplateID);
27 50 if (!is_wp_error($mailTemplate)) {
28 - // Honor template enable/disable: a disabled email template is never sent.
29 - if (isset($mailTemplate[0]->status) && empty($mailTemplate[0]->status)) {
30 - return;
31 - }
32 51 $mailTo = FieldValueHandler::validateMailArry($notifyDetails->to, $fieldValue);
33 - // Conditional email routing (Pro): add recipients resolved from value-based rules in the template config.
34 - $mailTo = apply_filters('bitform_email_conditional_routing', $mailTo, $mailTemplate[0], $fieldValue, $formID);
35 52 if (!empty($mailTo)) {
36 53 $from_name = '';
37 54 if (isset($notifyDetails->from_name) && !empty($notifyDetails->from_name)) {
38 55 $from_name = $notifyDetails->from_name;
39 56 }
57 + (new MailConfig())->sendMail(['from_name' => $from_name]);
58 + $mailSubject = FieldValueHandler::replaceFieldWithValue($mailTemplate[0]->sub, $fieldValue);
59 + $mailBody = FieldValueHandler::replaceFieldWithValue($mailTemplate[0]->body, $fieldValue);
60 +
40 61 $mailHeaders = [
41 - // 'Content-Type: text/html; charset=UTF-8',
42 - // $embeddedMailHeader
62 + // "Content-Type: text/html; charset=UTF-8",
43 63 ];
44 - $from_mail = '';
45 - if (!empty($notifyDetails->from)) {
46 - $fromMail = FieldValueHandler::validateMailArry($notifyDetails->from, $fieldValue);
47 - $headerFromName = !empty($notifyDetails->from_name) ? $notifyDetails->from_name : explode('@', $fromMail[0])[0];
48 - $mailHeaders[] = "FROM: $headerFromName " . '<' . sanitize_email($fromMail[0]) . '>';
49 - $from_mail = $fromMail[0];
50 - }
51 - (new MailConfig())->sendMail(['from_name' => $from_name, 'from_email' => $from_mail]);
52 - // Translate before smart-tag replacement. One language per submission,
53 - // shared by the admin and submitter emails.
54 - $mailSubjectTemplate = (string) apply_filters(
55 - 'bitform_translate_form_string',
56 - (string) $mailTemplate[0]->sub,
57 - 'mail-sub-' . $mailTemplateID,
58 - $formID
59 - );
60 - $mailSubject = FieldValueHandler::replaceFieldWithValue($mailSubjectTemplate, $fieldValue, $formID);
61 -
62 - // allow developers to modify email subject
63 - $mailSubject = apply_filters(
64 - 'bitform_filter_email_subject',
65 - $mailSubject,
66 - [
67 - 'form_id' => $formID,
68 - 'entry_id' => $entryID,
69 - 'field_values' => $fieldValue,
70 - 'template' => $mailTemplate[0] ?? null,
71 - 'is_double_optin' => (bool) $isDblOptin,
72 - ]
73 - );
74 -
75 - $mailBody = (string) apply_filters(
76 - 'bitform_translate_form_string',
77 - (string) $mailTemplate[0]->body,
78 - 'mail-body-' . $mailTemplateID,
79 - $formID
80 - );
81 - if (class_exists('\BitCode\BitFormPro\Admin\DownloadFile')) {
82 - $downloadFile = new \BitCode\BitFormPro\Admin\DownloadFile();
83 - $mailBody = $downloadFile->replacePdfShortCodeToLink($mailBody, $formID, $entryID);
84 - $mailBody = $downloadFile->replaceShortCodeToPdfPassword($mailBody, $formID, $entryID);
85 - }
86 -
87 - $mailBody = FieldValueHandler::replaceFieldWithValue($mailBody, $fieldValue, $formID);
88 - // Signature images: embed inline (cid:) so they render for non-logged-in recipients.
89 - // Must run before changeImagePathInHTMLString so the src is still the raw filename.
90 - $cidMap = [];
91 - $sigBasePath = FileHandler::getEntriesFileUploadDir($formID, $entryID) . DIRECTORY_SEPARATOR;
92 - $mailBody = self::embedSignatureImages($mailBody, $formManager, $fieldValue, $sigBasePath, $cidMap);
93 - $webUrl = Helpers::getWebPathWithEncryptedEntryId($formID, $entryID);
94 - $mailBody = FieldValueHandler::changeImagePathInHTMLString($mailBody, $webUrl);
95 - $mailBody = FieldValueHandler::changeHrefPathInHTMLString($mailBody, $webUrl); // replace anchor tag href with constructed weburl
96 -
97 - // allow developers to modify email body
98 - $mailBody = apply_filters(
99 - 'bitform_filter_email_body',
100 - $mailBody,
101 - [
102 - 'form_id' => $formID,
103 - 'entry_id' => $entryID,
104 - 'field_values' => $fieldValue,
105 - 'template' => $mailTemplate[0] ?? null,
106 - 'is_double_optin' => (bool) $isDblOptin,
107 - ]
108 - );
109 64 if (!empty($notifyDetails->replyto)) {
110 65 $mailReplyTo = FieldValueHandler::validateMailArry($notifyDetails->replyto, $fieldValue);
111 66 if (is_array($mailReplyTo)) {
112 67 foreach ($mailReplyTo as $key => $emailAddress) {
@@ -117,11 +72,11 @@
117 72 }
118 73 }
119 74 $oldMailBody = $mailBody;
120 75 $data = [];
121 - if ($isDblOptin && true === has_filter('bitform_email_body_text')) {
76 + if ($isDblOptin && true === has_filter('bf_email_body_text')) {
122 77 $urlParams = $formID . '_' . $entryID . '_' . $logId;
123 - $data = apply_filters('bitform_email_body_text', $mailBody, $urlParams);
78 + $data = apply_filters('bf_email_body_text', $mailBody, $urlParams);
124 79 $mailBody = $data['mailbody'];
125 80 }
126 81
127 82 if (!empty($notifyDetails->bcc)) {
@@ -143,128 +98,53 @@
143 98 } else {
144 99 $mailHeaders[] = 'Cc: ' . sanitize_email($mailCC);
145 100 }
146 101 }
147 - // PDF generation is a Pro feature; Pro hooks this filter and returns the generated
148 - // file path(s) — an array (one per configured template), or a single path string
149 - // from Pro versions that predate multi-PDF. Generated here (not before the
150 - // template/recipient checks) so disabled templates and empty recipients never
151 - // generate PDFs — and never leak temp files.
152 - $tempPdfLinks = apply_filters('bitform_generate_pdf_attachment', [], $notifyDetails, $entryDetails, $logId);
153 - $tempPdfLinks = is_array($tempPdfLinks) ? $tempPdfLinks : [$tempPdfLinks];
154 - $tempPdfLinks = array_values(array_unique(array_filter($tempPdfLinks, static function ($pdfPath) {
155 - return !empty($pdfPath) && is_string($pdfPath) && file_exists($pdfPath);
156 - })));
157 - $attachments = array_merge($attachments, $tempPdfLinks);
102 + if (!empty($notifyDetails->from)) {
103 + $mailFrom = FieldValueHandler::validateMailArry($notifyDetails->from, $fieldValue);
104 + $fromName = !empty($notifyDetails->fromName) ? $notifyDetails->fromName : explode('@', $mailFrom[0])[0];
105 + $mailHeaders[] = "FROM: $fromName " . '<' . sanitize_email($mailFrom[0]) . '>';
106 + }
107 +
158 108 if (!empty($notifyDetails->attachment)) {
159 - $fileFldKeys = $notifyDetails->attachment;
160 - $fileBasePath = FileHandler::getEntriesFileUploadDir($formID, $entryID) . DIRECTORY_SEPARATOR;
161 -
162 - // Normalize to array for consistent processing
163 - $fileFldKeys = is_array($fileFldKeys) ? $fileFldKeys : [$fileFldKeys];
164 -
165 - foreach ($fileFldKeys as $fldKey) {
166 - $repeaterFieldKey = $formManager->isRepeatedField($fldKey);
167 -
168 - if ($repeaterFieldKey) {
169 - // Handle repeated file field
170 - FileHandler::processRepeaterAttachment(
171 - $repeaterFieldKey,
172 - $fldKey,
173 - $fieldValue,
174 - $fileBasePath,
175 - $attachments
176 - );
177 - } else {
178 - // Handle regular file field
179 - FileHandler::processRegularAttachment(
180 - $fldKey,
181 - $fieldValue,
182 - $fileBasePath,
183 - $attachments
184 - );
109 + $files = $notifyDetails->attachment;
110 + $fileBasePath = BITFORMS_UPLOAD_DIR . DIRECTORY_SEPARATOR . $formID . DIRECTORY_SEPARATOR . $entryID . DIRECTORY_SEPARATOR;
111 + if (is_array($files)) {
112 + foreach ($files as $file) {
113 + if (isset($fieldValue[$file])) {
114 + if (is_array($fieldValue[$file])) {
115 + foreach ($fieldValue[$file] as $singleFile) {
116 + if (\is_readable("{$fileBasePath}{$singleFile}")) {
117 + $attachments[] = "{$fileBasePath}{$singleFile}";
118 + }
119 + }
120 + } elseif (\is_readable("{$fileBasePath}{$fieldValue[$file]}")) {
121 + $attachments[] = "{$fileBasePath}{$fieldValue[$file]}";
122 + }
123 + }
185 124 }
186 - }
187 - }
188 - // WordPress Media Library attachments (stored as attachment IDs in the template config).
189 - if (!empty($notifyDetails->mediaAttachment)) {
190 - $mediaIds = is_array($notifyDetails->mediaAttachment) ? $notifyDetails->mediaAttachment : [$notifyDetails->mediaAttachment];
191 - foreach ($mediaIds as $mediaId) {
192 - $mediaPath = get_attached_file(absint($mediaId));
193 - if ($mediaPath && file_exists($mediaPath)) {
194 - $attachments[] = $mediaPath;
125 + } elseif (isset($fieldValue[$files])) {
126 + if (is_array($fieldValue[$files])) {
127 + foreach ($fieldValue[$files] as $singleFile) {
128 + if (\is_readable("{$fileBasePath}{$singleFile}")) {
129 + $attachments[] = "{$fileBasePath}{$singleFile}";
130 + }
131 + }
132 + } elseif (\is_readable("{$fileBasePath}{$fieldValue[$files]}")) {
133 + $attachments[] = "{$fileBasePath}{$fieldValue[$files]}";
195 134 }
196 135 }
197 136 }
198 137 $mailBody = stripcslashes($mailBody);
199 138 $mailSubject = stripcslashes($mailSubject);
200 - $embedCb = static function ($phpmailer) use ($cidMap) {
201 - foreach ($cidMap as $cid => $info) {
202 - try {
203 - $phpmailer->addEmbeddedImage($info['path'], $cid, $info['name']);
204 - } catch (\Throwable $e) {
205 - Log::debug_log("[Signature Embed] failed for {$info['path']} - " . $e->getMessage());
206 - }
207 - }
208 - };
209 - if (!empty($cidMap)) {
210 - add_action('phpmailer_init', $embedCb);
211 - }
212 139 add_filter('wp_mail_content_type', [self::class, 'filterMailContentType']);
213 - $fromNameCb = null;
214 - if (!empty($from_name)) {
215 - $fromNameCb = static function () use ($from_name) {
216 - return $from_name;
217 - };
218 - add_filter('wp_mail_from_name', $fromNameCb);
219 - }
220 140 $status = wp_mail($mailTo, $mailSubject, $mailBody, $mailHeaders, $attachments);
221 -
222 141 if (!$status) {
223 - Log::debug_log([
224 - 'status' => 'error',
225 - 'code' => 'mail_not_sent',
226 - 'message' => 'Mail not sent',
227 - 'inputDetails' => [
228 - 'to' => $mailTo,
229 - 'subject' => $mailSubject,
230 - 'body' => $mailBody,
231 - 'headers' => $mailHeaders,
232 - 'attachments' => $attachments,
233 - 'notifyDetails' => $notifyDetails,
234 - 'formID' => $formID,
235 - 'entryID' => $entryID,
236 - 'isDblOptin' => $isDblOptin,
237 - 'logId' => $logId
238 - ],
239 - 'responseDetails' => $status
240 - ]);
241 - $apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'smtp'], 'errors', 'Mail dose not send successfully', $entryDetails);
242 - } else {
243 - Log::debug_log([
244 - 'status' => 'success',
245 - 'code' => 'mail_sent',
246 - 'message' => 'Mail successfully sent',
247 - 'inputDetails' => [
248 - 'to' => $mailTo,
249 - 'subject' => $mailSubject,
250 - 'body' => $mailBody,
251 - 'headers' => $mailHeaders,
252 - 'attachments' => $attachments,
253 - 'notifyDetails' => $notifyDetails,
254 - 'formID' => $formID,
255 - 'entryID' => $entryID,
256 - 'isDblOptin' => $isDblOptin,
257 - 'logId' => $logId
258 - ],
259 - 'responseDetails' => $status
260 - ]);
261 - $apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'smtp'], 'success', 'Mail successfully send.', $entryDetails);
142 + $status = wp_mail($mailTo, $mailSubject, $mailBody, $mailHeaders);
262 143 }
263 144 if ($status && $isDblOptin && false !== strpos($oldMailBody, 'entry_confirmation_url')) {
264 145 $entryMeta = new FormEntryMetaModel();
265 - $apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'smtp'], 'success', 'Mail successfully send.', $entryDetails);
266 - // Form entry meta insert; meta_key/meta_value required to store dynamic field data per entry.
146 +
267 147 $entryMeta->insert(
268 148 [
269 149 'bitforms_form_entry_id' => $entryID,
270 150 'meta_key' => 'entry_confirm_activation',
@@ -272,19 +152,13 @@
272 152 ]
273 153 );
274 154 }
275 155 remove_filter('wp_mail_content_type', [self::class, 'filterMailContentType']);
276 - if (null !== $fromNameCb) {
277 - remove_filter('wp_mail_from_name', $fromNameCb);
278 - }
279 - if (!empty($cidMap)) {
280 - remove_action('phpmailer_init', $embedCb);
281 - }
282 156 }
283 157 }
284 158 }
285 159
286 - foreach ($tempPdfLinks as $tempPdfLink) {
160 + if (!empty($tempPdfLink)) {
287 161 wp_delete_file($tempPdfLink);
288 162 }
289 163 }
290 164
@@ -290,66 +164,6 @@
290 164
291 165 public static function filterMailContentType()
292 166 {
293 167 return 'text/html; charset=UTF-8';
294 - }
295 -
296 - /**
297 - * Rewrite signature <img> tags to inline cid: references and collect the files
298 - * to embed. Only signature-field images that are local & readable are embedded;
299 - * external URLs and non-signature images are left untouched. When the form has
300 - * no signature (or none is in the body) $cidMap stays empty and nothing changes.
301 - */
302 - private static function embedSignatureImages($html, $formManager, $fieldValue, $baseDir, &$cidMap)
303 - {
304 - if (empty($html)) {
305 - return $html;
306 - }
307 -
308 - // Collect signature filenames for this entry.
309 - $sigFiles = [];
310 - foreach ($formManager->getFields() as $key => $detail) {
311 - if (!isset($detail['type']) || 'signature' !== $detail['type']) {
312 - continue;
313 - }
314 - $val = isset($fieldValue[$key]) ? $fieldValue[$key] : '';
315 - foreach ((array) $val as $fn) {
316 - $fn = is_string($fn) ? trim($fn) : '';
317 - if ('' !== $fn && 'signature-failed.png' !== $fn) {
318 - $sigFiles[basename($fn)] = true;
319 - }
320 - }
321 - }
322 - if (empty($sigFiles)) {
323 - return $html;
324 - }
325 -
326 - return preg_replace_callback(
327 - '/<img\s+[^>]*src=[\'"]([^\'"]+)[\'"][^>]*>/i',
328 - function ($m) use ($baseDir, $sigFiles, &$cidMap) {
329 - $src = $m[1];
330 - if (filter_var($src, FILTER_VALIDATE_URL)) {
331 - return $m[0]; // external URL, leave as-is
332 - }
333 - $name = basename($src);
334 - if (!isset($sigFiles[$name])) {
335 - return $m[0]; // not a signature image
336 - }
337 - $file = $baseDir . $name;
338 - if (!is_readable($file)) {
339 - return $m[0];
340 - }
341 - $cid = 'bfsig_' . md5($file);
342 - $cidMap[$cid] = ['path' => $file, 'name' => $name];
343 -
344 - // Replace only the src attribute value (leave alt untouched).
345 - return preg_replace(
346 - '/(src=[\'"])' . preg_quote($src, '/') . '([\'"])/i',
347 - '${1}cid:' . $cid . '${2}',
348 - $m[0],
349 - 1
350 - );
351 - },
352 - $html
353 - );
354 168 }
355 169 }