From 7a752f2881cf52c7853c3b7bcb4264fb68f6756b Mon Sep 17 00:00:00 2001 From: Finley Ghosh Date: Sat, 12 Jul 2025 23:59:16 +1000 Subject: [PATCH 1/2] Allowing invoices to be sent with CCs --- app/controllers/documents_controller.php | 212 +++++++++++++++++- app/views/elements/document_invoice_view.ctp | 223 ++++++++++++++++++- 2 files changed, 427 insertions(+), 8 deletions(-) diff --git a/app/controllers/documents_controller.php b/app/controllers/documents_controller.php index a7f9fb44..1b3f894c 100755 --- a/app/controllers/documents_controller.php +++ b/app/controllers/documents_controller.php @@ -389,7 +389,6 @@ ENDINSTRUCTIONS; - if(isset($source_document_id)) { //This is not ideal. But nothing else is either. $sourceDoc = $this->Document->find('first', array('conditions' => array('Document.id' => $source_document_id))); @@ -984,21 +983,18 @@ EOT; $this->Email->delivery = 'smtp'; $document = $this->Document->read(null,$id); - if(empty($document['Document']['pdf_filename'])) { $this->Session->setFlash(__('Error. Please generate the PDF before attempting to email it', true)); return; } else { $pdf_dir = Configure::read('pdf_directory'); - $attachment_files = array($pdf_dir.$document['Document']['pdf_filename']); foreach($document['DocumentAttachment'] as $document_attachment) { - $attachment = $this->Document->DocumentAttachment->Attachment->read(null, $document_attachment['attachment_id']); - $attachment_files[] = $attachment['Attachment']['file']; + $attachment = $this->Document->DocumentAttachment->Attachment->read(null, $document_attachment['attachment_id']); + $attachment_files[] = $attachment['Attachment']['file']; } $this->Email->attachments = $attachment_files; - } $enquiry = $this->Document->getEnquiry($document); @@ -1079,6 +1075,209 @@ EOT; } + function format_email($email) { + $email = trim($email); + // Basic RFC 5322 email validation + if (!preg_match('/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/', $email)) { + return ''; + } + return "$email <$email>"; +} + + function parse_email_to_array($input) { + try { + if (empty($input) || !is_string($input)) { + return array(); + } + $input = trim($input); + if ($input === '') { + return array(); + } + if (strpos($input, ',') !== false) { + $parts = explode(',', $input); + $result = array(); + foreach ($parts as $email) { + $email = $this->format_email($email); + if ($email !== '') { + $result[] = $email; + } + } + return $result; + } else { + $result = $this->format_email($input); + $array[] = $result; + return $array; + } + } catch (Exception $e) { + return array(); + } +} + + /** + * Email the PDF(document + attachments) for this Document to custom recipients. + * + * @param int $id - Document ID + * @param string $to - Recipient email address (comma-separated if multiple) + * @param string|null $cc - CC email address(es), optional (comma-separated) + * @param string|null $bcc - BCC email address(es), optional (comma-separated) + */ +function email_pdf_with_custom_recipients($id = null, $to = null, $cc = null, $bcc = null) { + $this->autoRender = false; + + if (empty($to) && !empty($this->params['form']['to'])) { + $to = $this->params['form']['to']; + } + if (empty($cc) && isset($this->params['form']['cc'])) { + $cc = $this->params['form']['cc']; + } + if (empty($bcc) && isset($this->params['form']['bcc'])) { + $bcc = $this->params['form']['bcc']; + } + + // Basic validation + if (empty($id) || empty($to)) { + $msg = 'Document ID and recipient email are required.'; + echo json_encode(array('success' => false, 'message' => $msg)); + return; + } + + $this->Email->smtpOptions = Configure::read('smtp_settings'); + $this->Email->delivery = 'smtp'; + + $document = $this->Document->read(null, $id); + error_log("[email_pdf_with_custom_recipients] Document loaded: " . print_r($document['Document'], true)); + error_log("[email_pdf_with_custom_recipients] DocumentAttachments: " . print_r($document['DocumentAttachment'], true)); + if (empty($document) || empty($document['Document'])) { + $msg = 'Document not found.'; + echo json_encode(array('success' => false, 'message' => $msg)); + return; + } + + if (empty($document['Document']['pdf_filename'])) { + $msg = 'Error. Please generate the PDF before attempting to email it'; + echo json_encode(array('success' => false, 'message' => $msg)); + return; + } + + $pdf_dir = Configure::read('pdf_directory'); + $attachment_files = array($pdf_dir.$document['Document']['pdf_filename']); + foreach($document['DocumentAttachment'] as $document_attachment) { + $attachment = $this->Document->DocumentAttachment->Attachment->read(null, $document_attachment['attachment_id']); + $attachment_files[] = $attachment['Attachment']['file']; + error_log("[email_pdf_with_custom_recipients] Added attachment: " . $attachment['Attachment']['file']); + } + + if (!empty($document['DocumentAttachment'])) { + foreach ($document['DocumentAttachment'] as $document_attachment) { + if (!empty($document_attachment['attachment_id'])) { + $attachment = $this->Document->DocumentAttachment->Attachment->read(null, $document_attachment['attachment_id']); + if (!empty($attachment['Attachment']['file'])) { + $attachment_files[] = $attachment['Attachment']['file']; + error_log("[email_pdf_with_custom_recipients] Added attachment: " . $attachment['Attachment']['file']); + } + } + } + } + error_log("[email_pdf_with_custom_recipients] All attachments: " . print_r($attachment_files, true)); + $this->Email->attachments = $attachment_files; + + $enquiry = $this->Document->getEnquiry($document); + + $toArray = $this->parse_email_to_array($to); + if (empty($toArray)) { + $msg = 'Invalid recipient email address.'; + echo json_encode(array('success' => false, 'message' => $msg)); + return; + } else { + $this->Email->to = implode(', ', $toArray); + + } + $ccArray = $this->parse_email_to_array($cc); + if (!empty($ccArray)) { + $this->Email->cc = $ccArray; + } + $bccArray = $this->parse_email_to_array($bcc); + if (!empty($bccArray)) { + $this->Email->bcc = $bccArray; + } + + $this->Email->replyTo = 'CMC Technologies - Sales '; + $this->Email->from = 'CMC Technologies - Sales '; + $docType = $this->Document->getDocType($document); + $template = $docType . '_email'; + $subject = !empty($enquiry['Enquiry']['title']) ? $enquiry['Enquiry']['title'] . ' ' : 'Document'; + error_log("[email_pdf_with_custom_recipients] Enquiry Title: " . empty($enquiry['Enquiry']['title']) . $enquiry['Enquiry']['title']); + + switch($docType) { + case 'quote': + $subject = !empty($enquiry['Enquiry']['title']) ? "Quotation: " . $enquiry['Enquiry']['title'] : 'Quotation'; + break; + case 'invoice': + $subject = $this->invoice_email_subject($document); + if (!empty($document['Invoice']['id'])) { + $this->set('invoice', $this->Document->Invoice->find('first', array('conditions'=>array('Invoice.id'=>$document['Invoice']['id'])))); + } + if (!empty($document['Invoice']['job_id'])) { + $this->set('job', $this->Document->Invoice->Job->find('first', array('conditions'=>array('Job.id'=>$document['Invoice']['job_id'])))); + } + break; + case 'purchaseOrder': + $subject .= "Purchase Order"; + $primary_contact = null; + if (!empty($document['PurchaseOrder']['principle_id'])) { + $primary_contact = $this->Document->User->find('first', array('conditions'=>array('User.principle_id' => $document['PurchaseOrder']['principle_id'],'User.primary_contact' => 1))); + } + if(empty($primary_contact)) { + $msg = 'Unable to send. No primary Principle Contact'; + echo json_encode(array('success' => false, 'message' => $msg)); + return; + } + $subject = $this->po_email_subject($document['PurchaseOrder']); + if (!empty($document['OrderAcknowledgement']['job_id'])) { + $this->set('job', $this->Document->PurchaseOrder->Job->find('first', array('conditions'=>array('Job.id'=>$document['OrderAcknowledgement']['job_id'])))); + } + break; + case 'orderAck': + $subject = $this->orderack_email_subject($document); + if (!empty($document['OrderAcknowledgement']['job_id'])) { + $this->set('job', $this->Document->OrderAcknowledgement->Job->find('first', array('conditions'=>array('Job.id'=>$document['OrderAcknowledgement']['job_id'])))); + } + if (!empty($document['OrderAcknowledgement']['signature_required'])) { + $template = 'orderAck_email_signature_required'; + } + break; + case 'packingList': + $subject = $this->packing_list_email_subject($document); + break; + } + + $this->Email->template = $template; + $this->Email->subject = $subject; + $this->Email->sendAs = 'both'; + $this->Email->charset = 'iso-8859-1'; + $this->set('enquiry', $enquiry); + $this->set('document', $document); + $this->set('DocFullName', $this->Document->getDocFullName($document['Document']['type'])); + + $sent = false; + try { + $sent = $this->Email->send(); + } catch (Exception $e) { + $msg = 'Exception: ' . $e->getMessage(); + echo json_encode(array('success' => false, 'message' => $msg)); + return; + } + if ($sent) { + echo json_encode(array('success' => true, 'message' => 'The Email has been sent')); + return; + } else { + $msg = 'The Email has NOT been sent'; + echo json_encode(array('success' => false, 'message' => $msg, 'smtp_errors' => $this->Email->smtpError)); + return; + } + echo json_encode(array('success' => false, 'message' => 'No response from email function')); +} + // generateShippingReference builds the Shipping Instructions: with the PO number and job titles. function generateShippingInstructions($document_id) { $this->layout = 'ajax'; @@ -1201,5 +1400,4 @@ ENDINSTRUCTIONS; } return $newDoc[$model]; } - } diff --git a/app/views/elements/document_invoice_view.ctp b/app/views/elements/document_invoice_view.ctp index 20970ace..c2070307 100755 --- a/app/views/elements/document_invoice_view.ctp +++ b/app/views/elements/document_invoice_view.ctp @@ -6,9 +6,147 @@
- +
+ + + + + +

Create new Documents based on this

    @@ -64,3 +202,86 @@ + + From d1acd648012a57873f0b3f924f0808b196e67a09 Mon Sep 17 00:00:00 2001 From: Finley Ghosh Date: Sun, 13 Jul 2025 00:05:19 +1000 Subject: [PATCH 2/2] Adding some more comments for readability --- app/controllers/documents_controller.php | 56 ++++++++++++++---------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/app/controllers/documents_controller.php b/app/controllers/documents_controller.php index 1b3f894c..c164c204 100755 --- a/app/controllers/documents_controller.php +++ b/app/controllers/documents_controller.php @@ -1122,8 +1122,10 @@ EOT; * @param string|null $bcc - BCC email address(es), optional (comma-separated) */ function email_pdf_with_custom_recipients($id = null, $to = null, $cc = null, $bcc = null) { + // Disable automatic rendering of a view $this->autoRender = false; + // Retrieve recipient emails from form data if not provided as arguments if (empty($to) && !empty($this->params['form']['to'])) { $to = $this->params['form']['to']; } @@ -1134,16 +1136,18 @@ function email_pdf_with_custom_recipients($id = null, $to = null, $cc = null, $b $bcc = $this->params['form']['bcc']; } - // Basic validation + // Basic validation for required parameters if (empty($id) || empty($to)) { $msg = 'Document ID and recipient email are required.'; echo json_encode(array('success' => false, 'message' => $msg)); return; } + // Configure SMTP settings for email delivery $this->Email->smtpOptions = Configure::read('smtp_settings'); $this->Email->delivery = 'smtp'; + // Load the document and its attachments $document = $this->Document->read(null, $id); error_log("[email_pdf_with_custom_recipients] Document loaded: " . print_r($document['Document'], true)); error_log("[email_pdf_with_custom_recipients] DocumentAttachments: " . print_r($document['DocumentAttachment'], true)); @@ -1153,20 +1157,21 @@ function email_pdf_with_custom_recipients($id = null, $to = null, $cc = null, $b return; } + // Ensure the PDF has been generated before emailing if (empty($document['Document']['pdf_filename'])) { $msg = 'Error. Please generate the PDF before attempting to email it'; echo json_encode(array('success' => false, 'message' => $msg)); return; } - $pdf_dir = Configure::read('pdf_directory'); - $attachment_files = array($pdf_dir.$document['Document']['pdf_filename']); - foreach($document['DocumentAttachment'] as $document_attachment) { + // Build the list of attachments (PDF + any additional attachments) + $pdf_dir = Configure::read('pdf_directory'); + $attachment_files = array($pdf_dir.$document['Document']['pdf_filename']); + foreach($document['DocumentAttachment'] as $document_attachment) { $attachment = $this->Document->DocumentAttachment->Attachment->read(null, $document_attachment['attachment_id']); $attachment_files[] = $attachment['Attachment']['file']; error_log("[email_pdf_with_custom_recipients] Added attachment: " . $attachment['Attachment']['file']); - } - + } if (!empty($document['DocumentAttachment'])) { foreach ($document['DocumentAttachment'] as $document_attachment) { if (!empty($document_attachment['attachment_id'])) { @@ -1181,33 +1186,38 @@ function email_pdf_with_custom_recipients($id = null, $to = null, $cc = null, $b error_log("[email_pdf_with_custom_recipients] All attachments: " . print_r($attachment_files, true)); $this->Email->attachments = $attachment_files; - $enquiry = $this->Document->getEnquiry($document); + // Get related enquiry for the document + $enquiry = $this->Document->getEnquiry($document); + // Parse and validate recipient email addresses $toArray = $this->parse_email_to_array($to); - if (empty($toArray)) { - $msg = 'Invalid recipient email address.'; - echo json_encode(array('success' => false, 'message' => $msg)); - return; - } else { - $this->Email->to = implode(', ', $toArray); - - } + if (empty($toArray)) { + $msg = 'Invalid recipient email address.'; + echo json_encode(array('success' => false, 'message' => $msg)); + return; + } else { + $this->Email->to = implode(', ', $toArray); + } $ccArray = $this->parse_email_to_array($cc); - if (!empty($ccArray)) { - $this->Email->cc = $ccArray; - } - $bccArray = $this->parse_email_to_array($bcc); + if (!empty($ccArray)) { + $this->Email->cc = $ccArray; + } + $bccArray = $this->parse_email_to_array($bcc); if (!empty($bccArray)) { - $this->Email->bcc = $bccArray; - } + $this->Email->bcc = $bccArray; + } + // Set reply-to and from addresses $this->Email->replyTo = 'CMC Technologies - Sales '; $this->Email->from = 'CMC Technologies - Sales '; + + // Determine document type, email template, and subject $docType = $this->Document->getDocType($document); $template = $docType . '_email'; $subject = !empty($enquiry['Enquiry']['title']) ? $enquiry['Enquiry']['title'] . ' ' : 'Document'; - error_log("[email_pdf_with_custom_recipients] Enquiry Title: " . empty($enquiry['Enquiry']['title']) . $enquiry['Enquiry']['title']); + error_log("[email_pdf_with_custom_recipients] Enquiry Title: " . empty($enquiry['Enquiry']['title']) . $enquiry['Enquiry']['title']); + // Customise subject and template based on document type switch($docType) { case 'quote': $subject = !empty($enquiry['Enquiry']['title']) ? "Quotation: " . $enquiry['Enquiry']['title'] : 'Quotation'; @@ -1251,6 +1261,7 @@ function email_pdf_with_custom_recipients($id = null, $to = null, $cc = null, $b break; } + // Set email template and other parameters $this->Email->template = $template; $this->Email->subject = $subject; $this->Email->sendAs = 'both'; @@ -1259,6 +1270,7 @@ function email_pdf_with_custom_recipients($id = null, $to = null, $cc = null, $b $this->set('document', $document); $this->set('DocFullName', $this->Document->getDocFullName($document['Document']['type'])); + // Attempt to send the email and handle errors $sent = false; try { $sent = $this->Email->send();