mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 07:07:31 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			409 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			409 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * Invoice Ninja (https://invoiceninja.com).
 | |
|  *
 | |
|  * @link https://github.com/invoiceninja/invoiceninja source repository
 | |
|  *
 | |
|  * @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
 | |
|  *
 | |
|  * @license https://www.elastic.co/licensing/elastic-license
 | |
|  */
 | |
| 
 | |
| namespace App\Services\Email;
 | |
| 
 | |
| use App\DataMapper\EmailTemplateDefaults;
 | |
| use App\Jobs\Entity\CreateRawPdf;
 | |
| use App\Jobs\Invoice\CreateUbl;
 | |
| use App\Models\Account;
 | |
| use App\Models\Expense;
 | |
| use App\Models\Invoice;
 | |
| use App\Models\Task;
 | |
| use App\Utils\Ninja;
 | |
| use App\Utils\Traits\MakesHash;
 | |
| use Illuminate\Mail\Mailables\Address;
 | |
| use Illuminate\Support\Facades\App;
 | |
| use League\CommonMark\CommonMarkConverter;
 | |
| 
 | |
| class EmailDefaults
 | |
| {
 | |
|     use MakesHash;
 | |
|     /**
 | |
|      * The settings object for this email
 | |
|      * @var \App\DataMapper\CompanySettings $settings
 | |
|      */
 | |
|     protected $settings;
 | |
| 
 | |
|     /**
 | |
|      * The HTML / Template to use for this email
 | |
|      * @var string $template
 | |
|      */
 | |
|     private string $template;
 | |
| 
 | |
|     /**
 | |
|      * The locale to use for
 | |
|      * translations for this email
 | |
|      */
 | |
|     private string $locale;
 | |
| 
 | |
|     /**
 | |
|      * @param Email $email job class
 | |
|      */
 | |
|     public function __construct(protected Email $email)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Entry point for generating
 | |
|      * the defaults for the email object
 | |
|      *
 | |
|      * @return EmailObject $email_object The email object
 | |
|      */
 | |
|     public function run()
 | |
|     {
 | |
|         $this->settings = $this->email->email_object->settings;
 | |
| 
 | |
|         $this->setLocale()
 | |
|              ->setFrom()
 | |
|              ->setTo()
 | |
|              ->setCc()
 | |
|              ->setTemplate()
 | |
|              ->setBody()
 | |
|              ->setSubject()
 | |
|              ->setReplyTo()
 | |
|              ->setBcc()
 | |
|              ->setAttachments()
 | |
|              ->setVariables()
 | |
|              ->setHeaders();
 | |
|         return $this->email->email_object;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the locale
 | |
|      */
 | |
|     private function setLocale(): self
 | |
|     {
 | |
|         if ($this->email->email_object->client) {
 | |
|             $this->locale = $this->email->email_object->client->locale();
 | |
|         } elseif ($this->email->email_object->vendor) {
 | |
|             $this->locale = $this->email->email_object->vendor->locale();
 | |
|         } else {
 | |
|             $this->locale = $this->email->company->locale();
 | |
|         }
 | |
| 
 | |
|         App::setLocale($this->locale);
 | |
|         App::forgetInstance('translator');
 | |
|         $t = app('translator');
 | |
|         $t->replace(Ninja::transformTranslations($this->settings));
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the template
 | |
|      */
 | |
|     private function setTemplate(): self
 | |
|     {
 | |
|         $this->template = $this->email->email_object->settings->email_style;
 | |
| 
 | |
|         match ($this->email->email_object->settings->email_style) {
 | |
|             'plain' => $this->template = 'email.template.plain',
 | |
|             'light' => $this->template = $this->email->email_object->company->account->isPremium() ? 'email.template.client_premium' : 'email.template.client',
 | |
|             'dark' => $this->template = $this->email->email_object->company->account->isPremium() ? 'email.template.client_premium' :'email.template.client',
 | |
|             'custom' => $this->template = 'email.template.custom',
 | |
|             default => $this->template = $this->email->email_object->company->account->isPremium() ? 'email.template.client_premium' :'email.template.client',
 | |
|         };
 | |
| 
 | |
|         $this->email->email_object->html_template = $this->template;
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the FROM address
 | |
|      */
 | |
|     private function setFrom(): self
 | |
|     {
 | |
|         if (Ninja::isHosted() && in_array($this->email->email_object->settings->email_sending_method,['default', 'mailgun'])) {
 | |
|             if ($this->email->company->account->isPaid() && property_exists($this->email->email_object->settings, 'email_from_name') && strlen($this->email->email_object->settings->email_from_name) > 1) {
 | |
|                 $email_from_name = $this->email->email_object->settings->email_from_name;
 | |
|             } else {
 | |
|                 $email_from_name = $this->email->company->present()->name();
 | |
|             }
 | |
| 
 | |
|             $this->email->email_object->from = new Address(config('mail.from.address'), $email_from_name);
 | |
| 
 | |
|             return $this;
 | |
|         }
 | |
| 
 | |
|         if ($this->email->email_object->from) {
 | |
|             return $this;
 | |
|         }
 | |
| 
 | |
|         $this->email->email_object->from = new Address(config('mail.from.address'), config('mail.from.name'));
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the To address
 | |
|      */
 | |
|     private function setTo(): self
 | |
|     {
 | |
|         if ($this->email->email_object->to) {
 | |
|             return $this;
 | |
|         }
 | |
| 
 | |
|         $this->email->email_object->to = [new Address($this->email->email_object->contact->email, $this->email->email_object->contact->present()->name())];
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the body of the email
 | |
|      */
 | |
|     private function setBody(): self
 | |
|     {
 | |
| 
 | |
|         if (strlen($this->email->email_object->body) > 3) {
 | |
|             // A Custom Message has been set in the email screen.
 | |
|         } elseif (strlen($this->email->email_object->settings?->{$this->email->email_object->email_template_body}) > 3) {
 | |
|             // A body has been saved in the settings.
 | |
|             $this->email->email_object->body = $this->email->email_object->settings?->{$this->email->email_object->email_template_body};
 | |
|         } else {
 | |
|             // Default template to be used
 | |
|             $this->email->email_object->body = EmailTemplateDefaults::getDefaultTemplate($this->email->email_object->email_template_body, $this->locale);
 | |
|         }
 | |
| 
 | |
|         $breaks = ["<br />","<br>","<br/>"];
 | |
|         $this->email->email_object->text_body = str_ireplace($breaks, "\r\n", $this->email->email_object->body);
 | |
|         $this->email->email_object->text_body = strip_tags($this->email->email_object->text_body);
 | |
|         $this->email->email_object->text_body = str_replace(['$view_button','$viewButton'], '$view_url', $this->email->email_object->text_body);
 | |
| 
 | |
|         if ($this->template == 'email.template.custom') {
 | |
|             $this->email->email_object->body = (str_replace('$body', $this->email->email_object->body, str_replace(["\r","\n"], "", $this->email->email_object->settings->email_style_custom)));
 | |
|         }
 | |
| 
 | |
|         return $this;
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the subject of the email
 | |
|      */
 | |
|     private function setSubject(): self
 | |
|     {
 | |
|         if ($this->email->email_object->subject) { //where the user updates the subject from the UI
 | |
|             return $this;
 | |
|         } elseif (strlen($this->email->email_object->settings?->{$this->email->email_object->email_template_subject}) > 3) {
 | |
|             $this->email->email_object->subject = $this->email->email_object->settings?->{$this->email->email_object->email_template_subject};
 | |
|         } else {
 | |
|             $this->email->email_object->subject = EmailTemplateDefaults::getDefaultTemplate($this->email->email_object->email_template_subject, $this->locale);
 | |
|         }
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the reply to of the email
 | |
|      */
 | |
|     private function setReplyTo(): self
 | |
|     {
 | |
|         $reply_to_email = $this->email->company->owner()->email;
 | |
|         $reply_to_name = $this->email->company->owner()->present()->name();
 | |
| 
 | |
|         if(str_contains($this->email->email_object->settings->reply_to_email, "@")) {
 | |
|             $reply_to_email = $this->email->email_object->settings->reply_to_email;
 | |
|         } elseif(isset($this->email->email_object->invitation->user)) {
 | |
|             $reply_to_email = $this->email->email_object->invitation->user->email;
 | |
|         }
 | |
| 
 | |
|         if(strlen($this->email->email_object->settings->reply_to_name) > 3) {
 | |
|             $reply_to_name = $this->email->email_object->settings->reply_to_name;
 | |
|         } elseif(isset($this->email->email_object->invitation->user)) {
 | |
|             $reply_to_name = $this->email->email_object->invitation->user->present()->name();
 | |
|         }
 | |
| 
 | |
|         $this->email->email_object->reply_to = array_merge($this->email->email_object->reply_to, [new Address($reply_to_email, $reply_to_name)]);
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Replaces the template placeholders
 | |
|      * with variable values.
 | |
|      */
 | |
|     public function setVariables(): self
 | |
|     {
 | |
| 
 | |
|         $this->email->email_object->body = strtr($this->email->email_object->body, $this->email->email_object->variables);
 | |
| 
 | |
|         $this->email->email_object->text_body = strtr($this->email->email_object->text_body, $this->email->email_object->variables);
 | |
| 
 | |
|         $this->email->email_object->subject = strtr($this->email->email_object->subject, $this->email->email_object->variables);
 | |
| 
 | |
| 
 | |
|         //06-06-2023 ensure we do not parse markdown in custom templates
 | |
|         if ($this->template != 'custom' && $this->template != 'email.template.custom') {
 | |
|             $this->email->email_object->body = $this->parseMarkdownToHtml($this->email->email_object->body);
 | |
|         }
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the BCC of the email
 | |
|      */
 | |
|     private function setBcc(): self
 | |
|     {
 | |
|         $bccs = [];
 | |
|         $bcc_array = [];
 | |
| 
 | |
|         if (strlen($this->email->email_object->settings->bcc_email) > 1) {
 | |
|             if (Ninja::isHosted() && $this->email->company->account->isPaid()) {
 | |
|                 $bccs = array_slice(explode(',', str_replace(' ', '', $this->email->email_object->settings->bcc_email)), 0, 5);
 | |
|             } else {
 | |
|                 $bccs = (explode(',', str_replace(' ', '', $this->email->email_object->settings->bcc_email)));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         foreach ($bccs as $bcc) {
 | |
|             $bcc_array[] = new Address($bcc);
 | |
|         }
 | |
| 
 | |
|         $this->email->email_object->bcc = array_merge($this->email->email_object->bcc, $bcc_array);
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the CC of the email
 | |
|      */
 | |
|     private function setCc(): self
 | |
|     {
 | |
|         return $this;
 | |
|         // return $this->email->email_object->cc;
 | |
|         // return [
 | |
|         // ];
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the attachments for the email
 | |
|      *
 | |
|      * Note that we base64 encode these, as they
 | |
|      * sometimes may not survive serialization.
 | |
|      *
 | |
|      * We decode these in the Mailable later
 | |
|      */
 | |
|     private function setAttachments(): self
 | |
|     {
 | |
|         $documents = [];
 | |
| 
 | |
|         /* Return early if the user cannot attach documents */
 | |
|         if (!$this->email->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT) || $this->email->email_object->email_template_subject == 'email_subject_statement') {
 | |
|             return $this;
 | |
|         }
 | |
| 
 | |
|         /** Purchase Order / Invoice / Credit / Quote PDF  */
 | |
|         if ($this->email->email_object->settings->pdf_email_attachment) {
 | |
|             $pdf = ((new CreateRawPdf($this->email->email_object->invitation))->handle());
 | |
|             $this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode($pdf), 'name' => $this->email->email_object->entity->numberFormatter().'.pdf']]);
 | |
|         }
 | |
| 
 | |
|         /** UBL xml file */
 | |
|         if ($this->email->email_object->settings->ubl_email_attachment && $this->email->email_object->entity instanceof Invoice) {
 | |
|             $ubl_string = (new CreateUbl($this->email->email_object->entity))->handle();
 | |
| 
 | |
|             if ($ubl_string) {
 | |
|                 $this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode($ubl_string), 'name' => $this->email->email_object->entity->getFileName('xml')]]);
 | |
|             }
 | |
|         }
 | |
|         /** E-Invoice xml file */
 | |
|         if ($this->email->email_object->settings->enable_e_invoice && $this->email->email_object->entity instanceof Invoice) {
 | |
|             $xml_string = $this->email->email_object->entity->service()->getEInvoice();
 | |
| 
 | |
|             if($xml_string) {
 | |
|                 $this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode($xml_string), 'name' => explode(".", $this->email->email_object->entity->getFileName('xml'))[0]."-e_invoice.xml"]]);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (!$this->email->email_object->settings->document_email_attachment || !$this->email->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {
 | |
|             return $this;
 | |
|         }
 | |
| 
 | |
|         /* Company Documents */
 | |
|         $this->email->email_object->documents = array_merge($this->email->email_object->documents, $this->email->company->documents()->where('is_public', true)->pluck('id')->toArray());
 | |
| 
 | |
|         /** Entity Documents */
 | |
|         if ($this->email->email_object->entity?->documents) {
 | |
|             $this->email->email_object->documents = array_merge($this->email->email_object->documents, $this->email->email_object->entity->documents()->where('is_public', true)->pluck('id')->toArray());
 | |
|         }
 | |
| 
 | |
|         /** Recurring Invoice Documents */
 | |
|         if ($this->email->email_object->entity instanceof Invoice && $this->email->email_object->entity->recurring_id != null) {
 | |
|             $this->email->email_object->documents = array_merge($this->email->email_object->documents, $this->email->email_object->entity->recurring_invoice->documents()->where('is_public', true)->pluck('id')->toArray());
 | |
|         }
 | |
| 
 | |
|         /** Task / Expense Documents */
 | |
|         if ($this->email->email_object->entity instanceof Invoice) {
 | |
|             $expense_ids = [];
 | |
|             $task_ids = [];
 | |
| 
 | |
|             foreach ($this->email->email_object->entity->line_items as $item) {
 | |
|                 if (property_exists($item, 'expense_id')) {
 | |
|                     $expense_ids[] = $item->expense_id;
 | |
|                 }
 | |
| 
 | |
|                 if (property_exists($item, 'task_id')) {
 | |
|                     $task_ids[] = $item->task_id;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (count($expense_ids) > 0) {
 | |
|                 Expense::query()->whereIn('id', $this->transformKeys($expense_ids))
 | |
|                         ->where('invoice_documents', 1)
 | |
|                         ->cursor()
 | |
|                         ->each(function ($expense) {
 | |
|                             $this->email->email_object->documents = array_merge($this->email->email_object->documents, $expense->documents()->where('is_public', true)->pluck('id')->toArray());
 | |
|                         });
 | |
|             }
 | |
| 
 | |
|             if (count($task_ids) > 0 && $this->email->company->invoice_task_documents) {
 | |
|                 Task::query()->whereIn('id', $this->transformKeys($task_ids))
 | |
|                     ->cursor()
 | |
|                     ->each(function ($task) {
 | |
|                         $this->email->email_object->documents = array_merge($this->email->email_object->documents, $task->documents()->where('is_public', true)->pluck('id')->toArray());
 | |
|                     });
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the headers for the email
 | |
|      */
 | |
|     private function setHeaders(): self
 | |
|     {
 | |
|         if ($this->email->email_object->invitation_key) {
 | |
|             $this->email->email_object->headers = array_merge($this->email->email_object->headers, ['x-invitation' => $this->email->email_object->invitation_key]);
 | |
|         }
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Converts any markdown to HTML in the email
 | |
|      *
 | |
|      * @param  string $markdown The body to convert
 | |
|      * @return string           The parsed markdown response
 | |
|      */
 | |
|     private function parseMarkdownToHtml(string $markdown): ?string
 | |
|     {
 | |
|         $converter = new CommonMarkConverter([
 | |
|             'allow_unsafe_links' => false,
 | |
|         ]);
 | |
| 
 | |
|         return $converter->convert($markdown);
 | |
|     }
 | |
| }
 |