$formID, 'entryId' => $entryID, 'fieldValues' => $fieldValue];
$emailTemplateHandler = new EmailTemplateHandler($formID);
$attachments = [];
$tempPdfLink = '';
$pdfPassForEmail = '';
if (!empty($notifyDetails->pdfId) && is_string($notifyDetails->pdfId)) {
$pdfTemplateID = json_decode($notifyDetails->pdfId)->id;
$pdfTemplateHandler = new PdfTemplateHandler($formID);
$pdfTemplate = $pdfTemplateHandler->getById($pdfTemplateID);
// Bail out if the template lookup failed (WP_Error) or returned no row.
// Without this guard json_decode($pdfTemplate[0]->setting) fatals and the whole email is lost.
if (is_wp_error($pdfTemplate) || empty($pdfTemplate[0]) || !isset($pdfTemplate[0]->setting)) {
$apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'pdf'], 'errors', 'PDF template not found, skipping PDF attachment.', $entryDetails);
Log::debug_log([
'status' => 'error',
'code' => 'pdf_template_not_found',
'message' => 'PDF template not found, skipping PDF attachment',
'inputDetails' => [
'notifyDetails' => $notifyDetails,
'formID' => $formID,
'entryID' => $entryID,
'pdfTemplateID' => $pdfTemplateID,
],
'responseDetails' => is_wp_error($pdfTemplate) ? $pdfTemplate->get_error_message() : 'empty result',
]);
} else {
$pdfSetting = json_decode($pdfTemplate[0]->setting);
$path = BITFORMS_CONTENT_DIR . DIRECTORY_SEPARATOR . 'pdf';
// $fileName = 'bit-form-pdf-' . $formID . '-' . $entryID;
if (!is_dir($path)) {
wp_mkdir_p($path);
}
if (class_exists('\BitCode\BitFormPro\Admin\AppSetting\Pdf')) {
$serverPath = Helpers::getFullPathWithEncryptedEntryId($formID, $entryID);
$webPath = Helpers::getWebPathWithEncryptedEntryId($formID, $entryID);
if (isset($pdfSetting->password)) {
if (isset($pdfSetting->password->static) && $pdfSetting->password->static && !empty($pdfSetting->password->pass)) {
$pass = FieldValueHandler::replaceFieldWithValue($pdfSetting->password->pass, $fieldValue);
$pdfSetting->password->pass = $pass;
} elseif (isset($pdfSetting->password->dynamic)) {
$pass = Helpers::PDFPassHash($entryID);
$pdfSetting->password->pass = $pass;
}
}
if (isset($pdfSetting->pdfFileName)) {
$pdfSetting->pdfFileName = FieldValueHandler::replaceFieldWithValue($pdfSetting->pdfFileName, $fieldValue);
// allow developers to modify PDF filename
$pdfSetting->pdfFileName = apply_filters(
'bitform_filter_pdf_filename',
$pdfSetting->pdfFileName,
[
'form_id' => $formID,
'entry_id' => $entryID,
'field_values' => $fieldValue,
'pdf_setting' => $pdfSetting,
'template' => $pdfTemplate[0] ?? null,
]
);
}
$fieldValue['entry_id'] = $entryID;
$pdfBody = FieldValueHandler::replaceFieldWithValue($pdfTemplate[0]->body, $fieldValue, $formID);
$pdfBody = FieldValueHandler::changeImagePathInHTMLString($pdfBody, $serverPath);
$pdfBody = FieldValueHandler::changeHrefPathInHTMLString($pdfBody, $webPath); // replace anchor tag href with constructed weburl
// allow developers to modify PDF body
$pdfBody = apply_filters(
'bitform_filter_pdf_body',
$pdfBody,
[
'form_id' => $formID,
'entry_id' => $entryID,
'field_values' => $fieldValue,
'pdf_setting' => $pdfSetting,
'template' => $pdfTemplate[0] ?? null,
]
);
$generatedPdf = \BitCode\BitFormPro\Admin\AppSetting\Pdf::getInstance()->generator($pdfSetting, $pdfBody, $path, $entryID, 'F');
if (!is_wp_error($generatedPdf) && file_exists($generatedPdf)) {
$attachments[] = $generatedPdf;
$tempPdfLink = $generatedPdf;
$apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'pdf'], 'success', 'PDF successfully generated.', $entryDetails);
Log::debug_log([
'status' => 'success',
'code' => 'pdf_generated',
'message' => 'PDF successfully generated',
'inputDetails' => [
'notifyDetails' => $notifyDetails,
'formID' => $formID,
'entryID' => $entryID,
'isDblOptin' => $isDblOptin,
'logId' => $logId
],
'responseDetails' => $generatedPdf
]);
} else {
$apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'pdf'], 'errors', 'Error in generating PDF.', $entryDetails);
Log::debug_log([
'status' => 'error',
'code' => 'pdf_generation_error',
'message' => 'Error in generating PDF',
'inputDetails' => [
'notifyDetails' => $notifyDetails,
'formID' => $formID,
'entryID' => $entryID,
'isDblOptin' => $isDblOptin,
'logId' => $logId
],
'responseDetails' => $generatedPdf->get_error_message()
]);
}
}
}
}
if (is_string($notifyDetails->id)) {
$mailTemplateID = json_decode($notifyDetails->id)->id;
$mailTemplate = $emailTemplateHandler->getATemplate($mailTemplateID);
if (!is_wp_error($mailTemplate)) {
$mailTo = FieldValueHandler::validateMailArry($notifyDetails->to, $fieldValue);
if (!empty($mailTo)) {
$from_name = '';
if (isset($notifyDetails->from_name) && !empty($notifyDetails->from_name)) {
$from_name = $notifyDetails->from_name;
}
$mailHeaders = [
// 'Content-Type: text/html; charset=UTF-8',
// $embeddedMailHeader
];
$from_mail = '';
if (!empty($notifyDetails->from)) {
$fromMail = FieldValueHandler::validateMailArry($notifyDetails->from, $fieldValue);
$headerFromName = !empty($notifyDetails->fromName) ? $notifyDetails->fromName : explode('@', $fromMail[0])[0];
$mailHeaders[] = "FROM: $headerFromName " . '<' . sanitize_email($fromMail[0]) . '>';
$from_mail = $fromMail[0];
}
(new MailConfig())->sendMail(['from_name' => $from_name, 'from_email' => $from_mail]);
$mailSubject = FieldValueHandler::replaceFieldWithValue($mailTemplate[0]->sub, $fieldValue, $formID);
// allow developers to modify email subject
$mailSubject = apply_filters(
'bitform_filter_email_subject',
$mailSubject,
[
'form_id' => $formID,
'entry_id' => $entryID,
'field_values' => $fieldValue,
'template' => $mailTemplate[0] ?? null,
'is_double_optin' => (bool) $isDblOptin,
]
);
$mailBody = $mailTemplate[0]->body;
if (class_exists('\BitCode\BitFormPro\Admin\DownloadFile')) {
$downloadFile = new \BitCode\BitFormPro\Admin\DownloadFile();
$mailBody = $downloadFile->replacePdfShortCodeToLink($mailBody, $formID, $entryID);
$mailBody = $downloadFile->replaceShortCodeToPdfPassword($mailBody, $formID, $entryID);
}
$mailBody = FieldValueHandler::replaceFieldWithValue($mailBody, $fieldValue, $formID);
// Signature images: embed inline (cid:) so they render for non-logged-in recipients.
// Must run before changeImagePathInHTMLString so the src is still the raw filename.
$cidMap = [];
$sigBasePath = FileHandler::getEntriesFileUploadDir($formID, $entryID) . DIRECTORY_SEPARATOR;
$mailBody = self::embedSignatureImages($mailBody, $formManager, $fieldValue, $sigBasePath, $cidMap);
$webUrl = Helpers::getWebPathWithEncryptedEntryId($formID, $entryID);
$mailBody = FieldValueHandler::changeImagePathInHTMLString($mailBody, $webUrl);
$mailBody = FieldValueHandler::changeHrefPathInHTMLString($mailBody, $webUrl); // replace anchor tag href with constructed weburl
// allow developers to modify email body
$mailBody = apply_filters(
'bitform_filter_email_body',
$mailBody,
[
'form_id' => $formID,
'entry_id' => $entryID,
'field_values' => $fieldValue,
'template' => $mailTemplate[0] ?? null,
'is_double_optin' => (bool) $isDblOptin,
]
);
if (!empty($notifyDetails->replyto)) {
$mailReplyTo = FieldValueHandler::validateMailArry($notifyDetails->replyto, $fieldValue);
if (is_array($mailReplyTo)) {
foreach ($mailReplyTo as $key => $emailAddress) {
$mailHeaders[] = 'Reply-To: ' . explode('@', $emailAddress)[0] . '<' . sanitize_email($emailAddress) . '>';
}
} else {
$mailHeaders[] = 'Reply-To: ' . explode('@', $mailReplyTo)[0] . '<' . sanitize_email($mailReplyTo) . '>';
}
}
$oldMailBody = $mailBody;
$data = [];
if ($isDblOptin && true === has_filter('bitform_email_body_text')) {
$urlParams = $formID . '_' . $entryID . '_' . $logId;
$data = apply_filters('bitform_email_body_text', $mailBody, $urlParams);
$mailBody = $data['mailbody'];
}
if (!empty($notifyDetails->bcc)) {
$mailBCC = FieldValueHandler::validateMailArry($notifyDetails->bcc, $fieldValue);
if (is_array($mailBCC)) {
foreach ($mailBCC as $key => $emailAddress) {
$mailHeaders[] = 'Bcc: ' . sanitize_email($emailAddress);
}
} else {
$mailHeaders[] = 'Bcc: ' . sanitize_email($mailBCC);
}
}
if (!empty($notifyDetails->cc)) {
$mailCC = FieldValueHandler::validateMailArry($notifyDetails->cc, $fieldValue);
if (is_array($mailCC)) {
foreach ($mailCC as $key => $emailAddress) {
$mailHeaders[] = 'Cc: ' . sanitize_email($emailAddress);
}
} else {
$mailHeaders[] = 'Cc: ' . sanitize_email($mailCC);
}
}
if (!empty($notifyDetails->attachment)) {
$fileFldKeys = $notifyDetails->attachment;
$fileBasePath = FileHandler::getEntriesFileUploadDir($formID, $entryID) . DIRECTORY_SEPARATOR;
// Normalize to array for consistent processing
$fileFldKeys = is_array($fileFldKeys) ? $fileFldKeys : [$fileFldKeys];
foreach ($fileFldKeys as $fldKey) {
$repeaterFieldKey = $formManager->isRepeatedField($fldKey);
if ($repeaterFieldKey) {
// Handle repeated file field
FileHandler::processRepeaterAttachment(
$repeaterFieldKey,
$fldKey,
$fieldValue,
$fileBasePath,
$attachments
);
} else {
// Handle regular file field
FileHandler::processRegularAttachment(
$fldKey,
$fieldValue,
$fileBasePath,
$attachments
);
}
}
}
$mailBody = stripcslashes($mailBody);
$mailSubject = stripcslashes($mailSubject);
$embedCb = static function ($phpmailer) use ($cidMap) {
foreach ($cidMap as $cid => $info) {
try {
$phpmailer->addEmbeddedImage($info['path'], $cid, $info['name']);
} catch (\Throwable $e) {
Log::debug_log("[Signature Embed] failed for {$info['path']} - " . $e->getMessage());
}
}
};
if (!empty($cidMap)) {
add_action('phpmailer_init', $embedCb);
}
add_filter('wp_mail_content_type', [self::class, 'filterMailContentType']);
$status = wp_mail($mailTo, $mailSubject, $mailBody, $mailHeaders, $attachments);
if (!$status) {
Log::debug_log([
'status' => 'error',
'code' => 'mail_not_sent',
'message' => 'Mail not sent',
'inputDetails' => [
'to' => $mailTo,
'subject' => $mailSubject,
'body' => $mailBody,
'headers' => $mailHeaders,
'attachments' => $attachments,
'notifyDetails' => $notifyDetails,
'formID' => $formID,
'entryID' => $entryID,
'isDblOptin' => $isDblOptin,
'logId' => $logId
],
'responseDetails' => $status
]);
$apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'smtp'], 'errors', 'Mail dose not send successfully', $entryDetails);
} else {
Log::debug_log([
'status' => 'success',
'code' => 'mail_sent',
'message' => 'Mail successfully sent',
'inputDetails' => [
'to' => $mailTo,
'subject' => $mailSubject,
'body' => $mailBody,
'headers' => $mailHeaders,
'attachments' => $attachments,
'notifyDetails' => $notifyDetails,
'formID' => $formID,
'entryID' => $entryID,
'isDblOptin' => $isDblOptin,
'logId' => $logId
],
'responseDetails' => $status
]);
$apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'smtp'], 'success', 'Mail successfully send.', $entryDetails);
}
if ($status && $isDblOptin && false !== strpos($oldMailBody, 'entry_confirmation_url')) {
$entryMeta = new FormEntryMetaModel();
$apiResponse->apiResponse($logId, '', ['type' => 'record', 'type_name' => 'smtp'], 'success', 'Mail successfully send.', $entryDetails);
// Form entry meta insert; meta_key/meta_value required to store dynamic field data per entry.
$entryMeta->insert(
[
'bitforms_form_entry_id' => $entryID,
'meta_key' => 'entry_confirm_activation',
'meta_value' => $data['token']
]
);
}
remove_filter('wp_mail_content_type', [self::class, 'filterMailContentType']);
if (!empty($cidMap)) {
remove_action('phpmailer_init', $embedCb);
}
}
}
}
if (!empty($tempPdfLink)) {
wp_delete_file($tempPdfLink);
}
}
public static function filterMailContentType()
{
return 'text/html; charset=UTF-8';
}
/**
* Rewrite signature
tags to inline cid: references and collect the files
* to embed. Only signature-field images that are local & readable are embedded;
* external URLs and non-signature images are left untouched. When the form has
* no signature (or none is in the body) $cidMap stays empty and nothing changes.
*/
private static function embedSignatureImages($html, $formManager, $fieldValue, $baseDir, &$cidMap)
{
if (empty($html)) {
return $html;
}
// Collect signature filenames for this entry.
$sigFiles = [];
foreach ($formManager->getFields() as $key => $detail) {
if (!isset($detail['type']) || 'signature' !== $detail['type']) {
continue;
}
$val = isset($fieldValue[$key]) ? $fieldValue[$key] : '';
foreach ((array) $val as $fn) {
$fn = is_string($fn) ? trim($fn) : '';
if ('' !== $fn && 'signature-failed.png' !== $fn) {
$sigFiles[basename($fn)] = true;
}
}
}
if (empty($sigFiles)) {
return $html;
}
return preg_replace_callback(
'/
]*src=[\'"]([^\'"]+)[\'"][^>]*>/i',
function ($m) use ($baseDir, $sigFiles, &$cidMap) {
$src = $m[1];
if (filter_var($src, FILTER_VALIDATE_URL)) {
return $m[0]; // external URL, leave as-is
}
$name = basename($src);
if (!isset($sigFiles[$name])) {
return $m[0]; // not a signature image
}
$file = $baseDir . $name;
if (!is_readable($file)) {
return $m[0];
}
$cid = 'bfsig_' . md5($file);
$cidMap[$cid] = ['path' => $file, 'name' => $name];
// Replace only the src attribute value (leave alt untouched).
return preg_replace(
'/(src=[\'"])' . preg_quote($src, '/') . '([\'"])/i',
'${1}cid:' . $cid . '${2}',
$m[0],
1
);
},
$html
);
}
}