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()
|
||||
{
|
||||
|
||||
$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;
|
||||
|
||||
|
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->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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user