mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Refactor for emails
This commit is contained in:
parent
6a687c2aef
commit
b8d1e0a920
@ -241,7 +241,7 @@ class EmailTemplateDefaults
|
|||||||
public static function emailStatementTemplate()
|
public static function emailStatementTemplate()
|
||||||
{
|
{
|
||||||
|
|
||||||
$statement_message = '<p>$client<br><br>'.self::transformText('client_statement_body').'<br><br>$invoices</p>';
|
$statement_message = '<p>$client<br><br>'.self::transformText('client_statement_body').'<br></p>';
|
||||||
|
|
||||||
return $statement_message;
|
return $statement_message;
|
||||||
|
|
||||||
|
244
app/Services/Email/EmailDefaults.php
Normal file
244
app/Services/Email/EmailDefaults.php
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\Email;
|
||||||
|
|
||||||
|
use App\DataMapper\EmailTemplateDefaults;
|
||||||
|
use App\Models\Account;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Services\Email\EmailObject;
|
||||||
|
use App\Utils\Ninja;
|
||||||
|
use Illuminate\Mail\Mailables\Address;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use League\CommonMark\CommonMarkConverter;
|
||||||
|
use Illuminate\Mail\Attachment;
|
||||||
|
|
||||||
|
class EmailDefaults
|
||||||
|
{
|
||||||
|
protected $settings;
|
||||||
|
|
||||||
|
private string $template;
|
||||||
|
|
||||||
|
private string $locale;
|
||||||
|
|
||||||
|
public function __construct(protected EmailService $email_service, public EmailObject $email_object){}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$this->settings = $this->email_object->settings;
|
||||||
|
|
||||||
|
$this->setLocale()
|
||||||
|
->setFrom()
|
||||||
|
->setTemplate()
|
||||||
|
->setBody()
|
||||||
|
->setSubject()
|
||||||
|
->setReplyTo()
|
||||||
|
->setBcc()
|
||||||
|
->setAttachments()
|
||||||
|
->setMetaData()
|
||||||
|
->setVariables();
|
||||||
|
|
||||||
|
return $this->email_object;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setMetaData(): self
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->email_object->company_key = $this->email_service->company->company_key;
|
||||||
|
|
||||||
|
$this->email_object->logo = $this->email_service->company->present()->logo();
|
||||||
|
|
||||||
|
$this->email_object->signature = $this->email_object->signature ?: $this->settings->email_signature;
|
||||||
|
|
||||||
|
$this->email_object->whitelabel = $this->email_object->company->account->isPaid() ? true : false;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setLocale(): self
|
||||||
|
{
|
||||||
|
|
||||||
|
if($this->email_object->client)
|
||||||
|
$this->locale = $this->email_object->client->locale();
|
||||||
|
elseif($this->email_object->vendor)
|
||||||
|
$this->locale = $this->email_object->vendor->locale();
|
||||||
|
else
|
||||||
|
$this->locale = $this->email_service->company->locale();
|
||||||
|
|
||||||
|
App::setLocale($this->locale);
|
||||||
|
App::forgetInstance('translator');
|
||||||
|
$t = app('translator');
|
||||||
|
$t->replace(Ninja::transformTranslations($this->settings));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setTemplate(): self
|
||||||
|
{
|
||||||
|
$this->template = $this->email_object->settings->email_style;
|
||||||
|
|
||||||
|
match($this->email_object->settings->email_style){
|
||||||
|
'light' => $this->template = 'email.template.client',
|
||||||
|
'dark' => $this->template = 'email.template.client',
|
||||||
|
'custom' => $this->template = 'email.template.custom',
|
||||||
|
default => $this->template = 'email.template.client',
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->email_object->html_template = $this->template;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setFrom(): self
|
||||||
|
{
|
||||||
|
if($this->email_object->from)
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
$this->email_object->from = new Address($this->email_service->company->owner()->email, $this->email_service->company->owner()->name());
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//think about where we do the string replace for variables....
|
||||||
|
private function setBody(): self
|
||||||
|
{
|
||||||
|
|
||||||
|
if($this->email_object->body){
|
||||||
|
$this->email_object->body = $this->email_object->body;
|
||||||
|
}
|
||||||
|
elseif(strlen($this->email_object->settings->{$this->email_object->email_template_body}) > 3){
|
||||||
|
$this->email_object->body = $this->email_object->settings->{$this->email_object->email_template_body};
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$this->email_object->body = EmailTemplateDefaults::getDefaultTemplate($this->email_object->email_template_body, $this->locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->template == 'email.template.custom'){
|
||||||
|
$this->email_object->body = (str_replace('$body', $this->email_object->body, $this->email_object->settings->email_style_custom));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//think about where we do the string replace for variables....
|
||||||
|
private function setSubject(): self
|
||||||
|
{
|
||||||
|
|
||||||
|
if ($this->email_object->subject) //where the user updates the subject from the UI
|
||||||
|
return $this;
|
||||||
|
elseif(strlen($this->email_object->settings->{$this->email_object->email_template_subject}) > 3)
|
||||||
|
$this->email_object->subject = $this->email_object->settings->{$this->email_object->email_template_subject};
|
||||||
|
else
|
||||||
|
$this->email_object->subject = EmailTemplateDefaults::getDefaultTemplate($this->email_object->email_template_subject, $this->locale);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setVariables(): self
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->email_object->body = strtr($this->email_object->body, $this->email_object->variables);
|
||||||
|
|
||||||
|
$this->email_object->subject = strtr($this->email_object->subject, $this->email_object->variables);
|
||||||
|
|
||||||
|
if($this->template != 'custom')
|
||||||
|
$this->email_object->body = $this->parseMarkdownToHtml($this->email_object->body);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setReplyTo(): self
|
||||||
|
{
|
||||||
|
|
||||||
|
$reply_to_email = str_contains($this->email_object->settings->reply_to_email, "@") ? $this->email_object->settings->reply_to_email : $this->email_service->company->owner()->email;
|
||||||
|
|
||||||
|
$reply_to_name = strlen($this->email_object->settings->reply_to_name) > 3 ? $this->email_object->settings->reply_to_name : $this->email_service->company->owner()->present()->name();
|
||||||
|
|
||||||
|
$this->email_object->reply_to = array_merge($this->email_object->reply_to, [new Address($reply_to_email, $reply_to_name)]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setBcc(): self
|
||||||
|
{
|
||||||
|
$bccs = [];
|
||||||
|
$bcc_array = [];
|
||||||
|
|
||||||
|
if (strlen($this->email_object->settings->bcc_email) > 1) {
|
||||||
|
|
||||||
|
if (Ninja::isHosted() && $this->email_service->company->account->isPaid()) {
|
||||||
|
$bccs = array_slice(explode(',', str_replace(' ', '', $this->email_object->settings->bcc_email)), 0, 2);
|
||||||
|
} else {
|
||||||
|
$bccs(explode(',', str_replace(' ', '', $this->email_object->settings->bcc_email)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($bccs as $bcc)
|
||||||
|
{
|
||||||
|
$bcc_array[] = new Address($bcc);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->email_object->bcc = array_merge($this->email_object->bcc, $bcc_array);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildCc()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setAttachments(): self
|
||||||
|
{
|
||||||
|
$attachments = [];
|
||||||
|
|
||||||
|
if ($this->email_object->settings->document_email_attachment && $this->email_service->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {
|
||||||
|
|
||||||
|
foreach ($this->email_service->company->documents as $document) {
|
||||||
|
|
||||||
|
$attachments[] = ['file' => base64_encode($document->getFile()), 'name' => $document->name];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->email_object->attachments = array_merge($this->email_object->attachments, $attachments);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setHeaders(): self
|
||||||
|
{
|
||||||
|
if($this->email_object->invitation_key)
|
||||||
|
$this->email_object->headers = array_merge($this->email_object->headers, ['x-invitation-key' => $this->email_object->invitation_key]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parseMarkdownToHtml(string $markdown): ?string
|
||||||
|
{
|
||||||
|
$converter = new CommonMarkConverter([
|
||||||
|
'allow_unsafe_links' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $converter->convert($markdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
109
app/Services/Email/EmailMailable.php
Normal file
109
app/Services/Email/EmailMailable.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\Email;
|
||||||
|
|
||||||
|
use App\Services\Email\EmailObject;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Attachment;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Mail\Mailables\Headers;
|
||||||
|
|
||||||
|
class EmailMailable extends Mailable
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(public EmailObject $email_object){}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Mail\Mailables\Envelope
|
||||||
|
*/
|
||||||
|
public function envelope()
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
subject: $this->email_object->subject,
|
||||||
|
tags: [$this->email_object->company_key],
|
||||||
|
replyTo: $this->email_object->reply_to,
|
||||||
|
from: $this->email_object->from,
|
||||||
|
to: $this->email_object->to,
|
||||||
|
bcc: $this->email_object->bcc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Mail\Mailables\Content
|
||||||
|
*/
|
||||||
|
public function content()
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
view: $this->email_object->template,
|
||||||
|
text: $this->email_object->text_template,
|
||||||
|
with: [
|
||||||
|
'text_body' => strip_tags($this->email_object->body),
|
||||||
|
'body' => $this->email_object->body,
|
||||||
|
'settings' => $this->email_object->settings,
|
||||||
|
'whitelabel' => $this->email_object->whitelabel,
|
||||||
|
'logo' => $this->email_object->logo,
|
||||||
|
'signature' => $this->email_object->signature,
|
||||||
|
'company' => $this->email_object->company,
|
||||||
|
'greeting' => ''
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function attachments()
|
||||||
|
{
|
||||||
|
|
||||||
|
$attachments = [];
|
||||||
|
|
||||||
|
foreach($this->email_object->attachments as $file)
|
||||||
|
{
|
||||||
|
$attachments[] = Attachment::fromData(fn () => base64_decode($file['file']), $file['name']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $attachments;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message headers.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Mail\Mailables\Headers
|
||||||
|
*/
|
||||||
|
public function headers()
|
||||||
|
{
|
||||||
|
|
||||||
|
return new Headers(
|
||||||
|
messageId: null,
|
||||||
|
references: [],
|
||||||
|
text: $this->email_object->headers,
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
508
app/Services/Email/EmailMailer.php
Normal file
508
app/Services/Email/EmailMailer.php
Normal file
@ -0,0 +1,508 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\Email;
|
||||||
|
|
||||||
|
use App\DataMapper\Analytics\EmailFailure;
|
||||||
|
use App\DataMapper\Analytics\EmailSuccess;
|
||||||
|
use App\DataMapper\EmailTemplateDefaults;
|
||||||
|
use App\Events\Invoice\InvoiceWasEmailedAndFailed;
|
||||||
|
use App\Events\Payment\PaymentWasEmailedAndFailed;
|
||||||
|
use App\Jobs\Util\SystemLogger;
|
||||||
|
use App\Libraries\Google\Google;
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Models\Account;
|
||||||
|
use App\Models\ClientContact;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\InvoiceInvitation;
|
||||||
|
use App\Models\Payment;
|
||||||
|
use App\Models\SystemLog;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\Email\EmailObject;
|
||||||
|
use App\Utils\Ninja;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Mail\Attachment;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Address;
|
||||||
|
use Illuminate\Mail\Mailer;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use League\CommonMark\CommonMarkConverter;
|
||||||
|
use Turbo124\Beacon\Facades\LightLogs;
|
||||||
|
|
||||||
|
class EmailMailer implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
|
||||||
|
|
||||||
|
public $tries = 3; //number of retries
|
||||||
|
|
||||||
|
public $backoff = 30; //seconds to wait until retry
|
||||||
|
|
||||||
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
|
public $override;
|
||||||
|
|
||||||
|
private $mailer;
|
||||||
|
|
||||||
|
protected $client_postmark_secret = false;
|
||||||
|
|
||||||
|
protected $client_mailgun_secret = false;
|
||||||
|
|
||||||
|
protected $client_mailgun_domain = false;
|
||||||
|
|
||||||
|
public function __construct(public EmailService $email_service, public Mailable $email_mailable){}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
MultiDB::setDb($this->email_service->company->db);
|
||||||
|
|
||||||
|
//decode all attachments
|
||||||
|
$this->setMailDriver();
|
||||||
|
|
||||||
|
$mailer = Mail::mailer($this->mailer);
|
||||||
|
|
||||||
|
if($this->client_postmark_secret){
|
||||||
|
nlog("inside postmark config");
|
||||||
|
nlog($this->client_postmark_secret);
|
||||||
|
$mailer->postmark_config($this->client_postmark_secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->client_mailgun_secret){
|
||||||
|
$mailer->mailgun_config($this->client_mailgun_secret, $this->client_mailgun_domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//send email
|
||||||
|
try {
|
||||||
|
|
||||||
|
nlog("Using mailer => ". $this->mailer. " ". now()->toDateTimeString());
|
||||||
|
$mailer->send($this->email_mailable);
|
||||||
|
|
||||||
|
Cache::increment($this->email_service->company->account->key);
|
||||||
|
|
||||||
|
LightLogs::create(new EmailSuccess($this->email_service->company->company_key))
|
||||||
|
->send();
|
||||||
|
|
||||||
|
} catch (\Exception | \RuntimeException | \Google\Service\Exception $e) {
|
||||||
|
|
||||||
|
nlog("error failed with {$e->getMessage()}");
|
||||||
|
|
||||||
|
$this->cleanUpMailers();
|
||||||
|
|
||||||
|
$message = $e->getMessage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post mark buries the proper message in a a guzzle response
|
||||||
|
* this merges a text string with a json object
|
||||||
|
* need to harvest the ->Message property using the following
|
||||||
|
*/
|
||||||
|
if($e instanceof ClientException) { //postmark specific failure
|
||||||
|
|
||||||
|
$response = $e->getResponse();
|
||||||
|
$message_body = json_decode($response->getBody()->getContents());
|
||||||
|
|
||||||
|
if($message_body && property_exists($message_body, 'Message')){
|
||||||
|
$message = $message_body->Message;
|
||||||
|
nlog($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the is an entity attached to the message send a failure mailer */
|
||||||
|
$this->entityEmailFailed($message);
|
||||||
|
|
||||||
|
/* Don't send postmark failures to Sentry */
|
||||||
|
if(Ninja::isHosted() && (!$e instanceof ClientException))
|
||||||
|
app('sentry')->captureException($e);
|
||||||
|
|
||||||
|
$message = null;
|
||||||
|
// $this->email_service = null;
|
||||||
|
// $this->email_mailable = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity notification when an email fails to send
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function entityEmailFailed($message)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(!$this->email_service->email_object->entity_id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch ($this->email_service->email_object->entity_class) {
|
||||||
|
case Invoice::class:
|
||||||
|
$invitation = InvoiceInvitation::withTrashed()->find($this->email_service->email_object->entity_id);
|
||||||
|
if($invitation)
|
||||||
|
event(new InvoiceWasEmailedAndFailed($invitation, $this->email_service->company, $message, $this->email_service->email_object->reminder_template, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||||
|
break;
|
||||||
|
case Payment::class:
|
||||||
|
$payment = Payment::withTrashed()->find($this->email_service->email_object->entity_id);
|
||||||
|
if($payment)
|
||||||
|
event(new PaymentWasEmailedAndFailed($payment, $this->email_service->company, $message, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
# code...
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->email_service->email_object->client_contact instanceof ClientContact)
|
||||||
|
$this->logMailError($message, $this->email_service->email_object->client_contact);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setMailDriver(): self
|
||||||
|
{
|
||||||
|
|
||||||
|
switch ($this->email_service->email_object->settings->email_sending_method) {
|
||||||
|
case 'default':
|
||||||
|
$this->mailer = config('mail.default');
|
||||||
|
break;
|
||||||
|
case 'gmail':
|
||||||
|
$this->mailer = 'gmail';
|
||||||
|
$this->setGmailMailer();
|
||||||
|
return $this;
|
||||||
|
case 'office365':
|
||||||
|
$this->mailer = 'office365';
|
||||||
|
$this->setOfficeMailer();
|
||||||
|
return $this;
|
||||||
|
case 'client_postmark':
|
||||||
|
$this->mailer = 'postmark';
|
||||||
|
$this->setPostmarkMailer();
|
||||||
|
return $this;
|
||||||
|
case 'client_mailgun':
|
||||||
|
$this->mailer = 'mailgun';
|
||||||
|
$this->setMailgunMailer();
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Ninja::isSelfHost())
|
||||||
|
$this->setSelfHostMultiMailer();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows configuration of multiple mailers
|
||||||
|
* per company for use by self hosted users
|
||||||
|
*/
|
||||||
|
private function setSelfHostMultiMailer(): void
|
||||||
|
{
|
||||||
|
|
||||||
|
if (env($this->email_service->company->id . '_MAIL_HOST'))
|
||||||
|
{
|
||||||
|
|
||||||
|
config([
|
||||||
|
'mail.mailers.smtp' => [
|
||||||
|
'transport' => 'smtp',
|
||||||
|
'host' => env($this->email_service->company->id . '_MAIL_HOST'),
|
||||||
|
'port' => env($this->email_service->company->id . '_MAIL_PORT'),
|
||||||
|
'username' => env($this->email_service->company->id . '_MAIL_USERNAME'),
|
||||||
|
'password' => env($this->email_service->company->id . '_MAIL_PASSWORD'),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if(env($this->email_service->company->id . '_MAIL_FROM_ADDRESS'))
|
||||||
|
{
|
||||||
|
$this->email_mailable
|
||||||
|
->from(env($this->email_service->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->email_service->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME')));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure we discard any data that is not required
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function cleanUpMailers(): void
|
||||||
|
{
|
||||||
|
$this->client_postmark_secret = false;
|
||||||
|
|
||||||
|
$this->client_mailgun_secret = false;
|
||||||
|
|
||||||
|
$this->client_mailgun_domain = false;
|
||||||
|
|
||||||
|
//always dump the drivers to prevent reuse
|
||||||
|
app('mail.manager')->forgetMailers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to ensure no cross account
|
||||||
|
* emails can be sent.
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
*/
|
||||||
|
private function checkValidSendingUser($user)
|
||||||
|
{
|
||||||
|
/* Always ensure the user is set on the correct account */
|
||||||
|
if($user->account_id != $this->email_service->company->account_id){
|
||||||
|
|
||||||
|
$this->email_service->email_object->settings->email_sending_method = 'default';
|
||||||
|
return $this->setMailDriver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the sending user
|
||||||
|
* when configuring the Mailer
|
||||||
|
* on behalf of the client
|
||||||
|
*
|
||||||
|
* @return User $user
|
||||||
|
*/
|
||||||
|
private function resolveSendingUser(): ?User
|
||||||
|
{
|
||||||
|
$sending_user = $this->email_service->email_object->settings->gmail_sending_user_id;
|
||||||
|
|
||||||
|
$user = User::find($this->decodePrimaryKey($sending_user));
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures Mailgun using client supplied secret
|
||||||
|
* as the Mailer
|
||||||
|
*/
|
||||||
|
private function setMailgunMailer()
|
||||||
|
{
|
||||||
|
if(strlen($this->email_service->email_object->settings->mailgun_secret) > 2 && strlen($this->email_service->email_object->settings->mailgun_domain) > 2){
|
||||||
|
$this->client_mailgun_secret = $this->email_service->email_object->settings->mailgun_secret;
|
||||||
|
$this->client_mailgun_domain = $this->email_service->email_object->settings->mailgun_domain;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$this->email_service->email_object->settings->email_sending_method = 'default';
|
||||||
|
return $this->setMailDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$user = $this->resolveSendingUser();
|
||||||
|
|
||||||
|
$this->mailable
|
||||||
|
->from($user->email, $user->name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures Postmark using client supplied secret
|
||||||
|
* as the Mailer
|
||||||
|
*/
|
||||||
|
private function setPostmarkMailer()
|
||||||
|
{
|
||||||
|
if(strlen($this->email_service->email_object->settings->postmark_secret) > 2){
|
||||||
|
$this->client_postmark_secret = $this->email_service->email_object->settings->postmark_secret;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$this->email_service->email_object->settings->email_sending_method = 'default';
|
||||||
|
return $this->setMailDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->resolveSendingUser();
|
||||||
|
|
||||||
|
$this->mailable
|
||||||
|
->from($user->email, $user->name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures Microsoft via Oauth
|
||||||
|
* as the Mailer
|
||||||
|
*/
|
||||||
|
private function setOfficeMailer()
|
||||||
|
{
|
||||||
|
$user = $this->resolveSendingUser();
|
||||||
|
|
||||||
|
$this->checkValidSendingUser($user);
|
||||||
|
|
||||||
|
nlog("Sending via {$user->name()}");
|
||||||
|
|
||||||
|
$token = $this->refreshOfficeToken($user);
|
||||||
|
|
||||||
|
if($token)
|
||||||
|
{
|
||||||
|
$user->oauth_user_token = $token;
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
$this->email_service->email_object->settings->email_sending_method = 'default';
|
||||||
|
return $this->setMailDriver();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mailable
|
||||||
|
->from($user->email, $user->name())
|
||||||
|
->withSymfonyMessage(function ($message) use($token) {
|
||||||
|
$message->getHeaders()->addTextHeader('gmailtoken', $token);
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(rand(1,3));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures GMail via Oauth
|
||||||
|
* as the Mailer
|
||||||
|
*/
|
||||||
|
private function setGmailMailer()
|
||||||
|
{
|
||||||
|
|
||||||
|
$user = $this->resolveSendingUser();
|
||||||
|
|
||||||
|
$this->checkValidSendingUser($user);
|
||||||
|
|
||||||
|
nlog("Sending via {$user->name()}");
|
||||||
|
|
||||||
|
$google = (new Google())->init();
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
if ($google->getClient()->isAccessTokenExpired()) {
|
||||||
|
$google->refreshToken($user);
|
||||||
|
$user = $user->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
$google->getClient()->setAccessToken(json_encode($user->oauth_user_token));
|
||||||
|
|
||||||
|
sleep(rand(2,4));
|
||||||
|
}
|
||||||
|
catch(\Exception $e) {
|
||||||
|
$this->logMailError('Gmail Token Invalid', $this->email_service->company->clients()->first());
|
||||||
|
$this->email_service->email_object->settings->email_sending_method = 'default';
|
||||||
|
return $this->setMailDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the user doesn't have a valid token, notify them
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(!$user->oauth_user_token) {
|
||||||
|
$this->email_service->company->account->gmailCredentialNotification();
|
||||||
|
$this->email_service->email_object->settings->email_sending_method = 'default';
|
||||||
|
return $this->setMailDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now that our token is refreshed and valid we can boot the
|
||||||
|
* mail driver at runtime and also set the token which will persist
|
||||||
|
* just for this request.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$token = $user->oauth_user_token->access_token;
|
||||||
|
|
||||||
|
if(!$token) {
|
||||||
|
$this->email_service->company->account->gmailCredentialNotification();
|
||||||
|
$this->email_service->email_object->settings->email_sending_method = 'default';
|
||||||
|
return $this->setMailDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mailable
|
||||||
|
->from($user->email, $user->name())
|
||||||
|
->withSymfonyMessage(function ($message) use($token) {
|
||||||
|
$message->getHeaders()->addTextHeader('gmailtoken', $token);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs any errors to the SystemLog
|
||||||
|
*
|
||||||
|
* @param string $errors
|
||||||
|
* @param App\Models\User | App\Models\Client $recipient_object
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function logMailError($errors, $recipient_object) :void
|
||||||
|
{
|
||||||
|
|
||||||
|
(new SystemLogger(
|
||||||
|
$errors,
|
||||||
|
SystemLog::CATEGORY_MAIL,
|
||||||
|
SystemLog::EVENT_MAIL_SEND,
|
||||||
|
SystemLog::TYPE_FAILURE,
|
||||||
|
$recipient_object,
|
||||||
|
$this->email_service->company
|
||||||
|
))->handle();
|
||||||
|
|
||||||
|
$job_failure = new EmailFailure($this->email_service->company->company_key);
|
||||||
|
$job_failure->string_metric5 = 'failed_email';
|
||||||
|
$job_failure->string_metric6 = substr($errors, 0, 150);
|
||||||
|
|
||||||
|
LightLogs::create($job_failure)
|
||||||
|
->send();
|
||||||
|
|
||||||
|
$job_failure = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to refresh the Microsoft refreshToken
|
||||||
|
*
|
||||||
|
* @param App\Models\User
|
||||||
|
* @return string | bool
|
||||||
|
*/
|
||||||
|
private function refreshOfficeToken($user)
|
||||||
|
{
|
||||||
|
$expiry = $user->oauth_user_token_expiry ?: now()->subDay();
|
||||||
|
|
||||||
|
if($expiry->lt(now()))
|
||||||
|
{
|
||||||
|
$guzzle = new \GuzzleHttp\Client();
|
||||||
|
$url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
|
||||||
|
|
||||||
|
$token = json_decode($guzzle->post($url, [
|
||||||
|
'form_params' => [
|
||||||
|
'client_id' => config('ninja.o365.client_id') ,
|
||||||
|
'client_secret' => config('ninja.o365.client_secret') ,
|
||||||
|
'scope' => 'email Mail.Send offline_access profile User.Read openid',
|
||||||
|
'grant_type' => 'refresh_token',
|
||||||
|
'refresh_token' => $user->oauth_user_refresh_token
|
||||||
|
],
|
||||||
|
])->getBody()->getContents());
|
||||||
|
|
||||||
|
if($token){
|
||||||
|
|
||||||
|
$user->oauth_user_refresh_token = property_exists($token, 'refresh_token') ? $token->refresh_token : $user->oauth_user_refresh_token;
|
||||||
|
$user->oauth_user_token = $token->access_token;
|
||||||
|
$user->oauth_user_token_expiry = now()->addSeconds($token->expires_in);
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
return $token->access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user->oauth_user_token;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function failed($exception = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
84
app/Services/Email/EmailObject.php
Normal file
84
app/Services/Email/EmailObject.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\Email;
|
||||||
|
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Models\ClientContact;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Vendor;
|
||||||
|
use App\Models\VendorContact;
|
||||||
|
use Illuminate\Mail\Mailables\Address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EmailObject.
|
||||||
|
*/
|
||||||
|
class EmailObject
|
||||||
|
{
|
||||||
|
|
||||||
|
public array $to = [];
|
||||||
|
|
||||||
|
public ?Address $from = null;
|
||||||
|
|
||||||
|
public array $reply_to = [];
|
||||||
|
|
||||||
|
public array $cc = [];
|
||||||
|
|
||||||
|
public array $bcc = [];
|
||||||
|
|
||||||
|
public ?string $subject = null;
|
||||||
|
|
||||||
|
public ?string $body = null;
|
||||||
|
|
||||||
|
public array $attachments = [];
|
||||||
|
|
||||||
|
public string $company_key;
|
||||||
|
|
||||||
|
public ?object $settings = null;
|
||||||
|
|
||||||
|
public bool $whitelabel = false;
|
||||||
|
|
||||||
|
public ?string $logo = null;
|
||||||
|
|
||||||
|
public ?string $signature = null;
|
||||||
|
|
||||||
|
public ?string $greeting = null;
|
||||||
|
|
||||||
|
public ?Client $client = null;
|
||||||
|
|
||||||
|
public ?Vendor $vendor = null;
|
||||||
|
|
||||||
|
public ?User $user = null;
|
||||||
|
|
||||||
|
public ?ClientContact $client_contact = null;
|
||||||
|
|
||||||
|
public ?VendorContact $vendor_contact = null;
|
||||||
|
|
||||||
|
public ?string $email_template_body = null;
|
||||||
|
|
||||||
|
public ?string $email_template_subject = null;
|
||||||
|
|
||||||
|
public ?string $html_template = null;
|
||||||
|
|
||||||
|
public ?string $text_template = 'email.template.text';
|
||||||
|
|
||||||
|
public array $headers = [];
|
||||||
|
|
||||||
|
public ?string $invitation_key = null;
|
||||||
|
|
||||||
|
public ?int $entity_id = null;
|
||||||
|
|
||||||
|
public ?string $entity_class = null;
|
||||||
|
|
||||||
|
public array $variables = [];
|
||||||
|
|
||||||
|
}
|
72
app/Services/Email/EmailService.php
Normal file
72
app/Services/Email/EmailService.php
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\Email;
|
||||||
|
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Services\Email\EmailObject;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
|
||||||
|
class EmailService
|
||||||
|
{
|
||||||
|
protected string $mailer;
|
||||||
|
|
||||||
|
protected bool $override;
|
||||||
|
|
||||||
|
public Mailable $mailable;
|
||||||
|
|
||||||
|
public function __construct(public EmailObject $email_object, public Company $company){}
|
||||||
|
|
||||||
|
public function send($override = false) :void
|
||||||
|
{
|
||||||
|
$this->override = $override;
|
||||||
|
|
||||||
|
|
||||||
|
nlog($this->email_object->subject);
|
||||||
|
nlog($this->email_object->body);
|
||||||
|
|
||||||
|
$this->setDefaults()
|
||||||
|
->updateMailable()
|
||||||
|
->email();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendNow($override = false) :void
|
||||||
|
{
|
||||||
|
$this->setDefaults()
|
||||||
|
->updateMailable()
|
||||||
|
->email(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function email($force = false): void
|
||||||
|
{
|
||||||
|
if($force)
|
||||||
|
(new EmailMailer($this, $this->mailable))->handle();
|
||||||
|
else
|
||||||
|
EmailMailer::dispatch($this, $this->mailable)->delay(2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setDefaults(): self
|
||||||
|
{
|
||||||
|
$defaults = new EmailDefaults($this, $this->email_object);
|
||||||
|
$defaults->run();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateMailable()
|
||||||
|
{
|
||||||
|
$this->mailable = new EmailMailable($this->email_object);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -109,7 +109,17 @@ class SchedulerService
|
|||||||
|
|
||||||
$email_object = new EmailObject;
|
$email_object = new EmailObject;
|
||||||
$email_object->to = [new Address($this->client->present()->email(), $this->client->present()->name())];
|
$email_object->to = [new Address($this->client->present()->email(), $this->client->present()->name())];
|
||||||
$email_object->attachments = ['name' => ctrans('texts.statement') . ".pdf", 'file' => base64_encode($pdf)];
|
$email_object->attachments = [['file' => base64_encode($pdf), 'name' => ctrans('texts.statement') . ".pdf"]];
|
||||||
|
$email_object->settings = $this->client->getMergedSettings();
|
||||||
|
$email_object->company = $this->client->company;
|
||||||
|
$email_object->client = $this->client;
|
||||||
|
$email_object->email_template_subject = 'email_subject_statement';
|
||||||
|
$email_object->email_template_body = 'email_template_statement';
|
||||||
|
$email_object->variables = [
|
||||||
|
'$client' => $this->client->present()->name(),
|
||||||
|
'$start_date' => $this->client_start_date,
|
||||||
|
'$end_date' => $this->client_end_date,
|
||||||
|
];
|
||||||
|
|
||||||
return $email_object;
|
return $email_object;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user