mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
doc blocks for entity mailers
This commit is contained in:
parent
5b5458d70a
commit
4c7a7e4c05
@ -16,6 +16,9 @@ use App\Models\Client;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Account;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\VendorContact;
|
||||
use App\Utils\VendorHtmlEngine;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use App\Services\Email\MailMailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
@ -45,6 +48,11 @@ class MailBuild
|
||||
/** @var mixed $vendor */
|
||||
private ?Vendor $vendor;
|
||||
|
||||
/** @var mixed $html_engine */
|
||||
private mixed $html_engine;
|
||||
|
||||
/** @var mixed $variables */
|
||||
private array $variables = [];
|
||||
/**
|
||||
* __construct
|
||||
*
|
||||
@ -61,11 +69,9 @@ class MailBuild
|
||||
public function run(): self
|
||||
{
|
||||
//resolve settings, if client existing - use merged - else default to company
|
||||
$this->settings = $this->mail_entity->company->settings;
|
||||
$this->mail_entity->mail_object->settings = $this->settings;
|
||||
|
||||
$this->resolveEntities()
|
||||
->setLocale()
|
||||
->setMetaData()
|
||||
->setFrom()
|
||||
->setTo()
|
||||
->setTemplate()
|
||||
@ -74,7 +80,6 @@ class MailBuild
|
||||
->setReplyTo()
|
||||
->setBcc()
|
||||
->setAttachments()
|
||||
->setMetaData()
|
||||
->setVariables();
|
||||
|
||||
return $this;
|
||||
@ -98,9 +103,13 @@ class MailBuild
|
||||
private function resolveEntities(): self
|
||||
{
|
||||
|
||||
$this->client = $this->mail_entity->mail_object->client_id ? Client::find($this->mail_entity->mail_object->client_id) : null;
|
||||
$client_contact = $this->mail_entity?->invitation?->client_contact_id ? ClientContact::withTrashed()->find($this->mail_entity->invitation->client_contact_id) : null;
|
||||
|
||||
$this->vendor = $this->mail_entity->mail_object->vendor_id ? Vendor::find($this->mail_entity->mail_object->vendor_id) : null;
|
||||
$this->client = $client_contact?->client;
|
||||
|
||||
$vendor_contact = $this->mail_entity?->invitation?->vendor_contact_id ? VendorContact::withTrashed()->find($this->mail_entity->invitation->vendor_contact_id) : null;
|
||||
|
||||
$this->vendor = $vendor_contact?->vendor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -127,9 +136,10 @@ class MailBuild
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the locale
|
||||
* Sets the settings object depending on context
|
||||
* Sets the HTML variables depending on context
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
@ -137,14 +147,31 @@ class MailBuild
|
||||
{
|
||||
|
||||
if($this->client){
|
||||
|
||||
$this->locale = $this->client->locale();
|
||||
$this->settings = $this->client->getMergedSettings();
|
||||
$this->mail_entity->mail_object->settings = $this->settings;
|
||||
|
||||
if($this->mail_entity->invitation)
|
||||
$this->variables = (new HtmlEngine($this->mail_entity->invitation))->makeValues();
|
||||
|
||||
}
|
||||
elseif($this->vendor)
|
||||
elseif($this->vendor){
|
||||
|
||||
$this->locale = $this->vendor->locale();
|
||||
else
|
||||
$this->settings = $this->mail_entity->company->settings;
|
||||
|
||||
if($this->mail_entity->invitation)
|
||||
$this->variables = (new VendorHtmlEngine($this->mail_entity->invitation))->makeValues();
|
||||
|
||||
|
||||
}
|
||||
else{
|
||||
$this->locale = $this->mail_entity->company->locale();
|
||||
$this->settings = $this->mail_entity->company->settings;
|
||||
}
|
||||
|
||||
$this->mail_entity->mail_object->settings = $this->settings;
|
||||
|
||||
|
||||
App::setLocale($this->locale);
|
||||
App::forgetInstance('translator');
|
||||
@ -318,10 +345,8 @@ class MailBuild
|
||||
$this->mail_entity->mail_object->body = strtr($this->mail_entity->mail_object->body, $this->mail_entity->mail_object->variables);
|
||||
}
|
||||
|
||||
$variables = (new HtmlEngine($this->mail_entity->invitation))->makeValues();
|
||||
|
||||
$this->mail_entity->mail_object->subject = strtr($this->mail_entity->mail_object->subject, $variables);
|
||||
$this->mail_entity->mail_object->body = strtr($this->mail_entity->mail_object->body, $variables);
|
||||
$this->mail_entity->mail_object->subject = strtr($this->mail_entity->mail_object->subject, $this->variables);
|
||||
$this->mail_entity->mail_object->body = strtr($this->mail_entity->mail_object->body, $this->variables);
|
||||
|
||||
if($this->template != 'custom')
|
||||
$this->mail_entity->mail_object->body = $this->parseMarkdownToHtml($this->mail_entity->mail_object->body);
|
||||
|
@ -11,29 +11,31 @@
|
||||
|
||||
namespace App\Services\Email;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Company;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Libraries\Google\Google;
|
||||
use App\Services\Email\MailBuild;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use App\DataMapper\Analytics\EmailSuccess;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
||||
class MailEntity implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
|
||||
|
||||
public Company $company;
|
||||
|
||||
public MailObject $mail_object;
|
||||
|
||||
public Mailable $mailable;
|
||||
|
||||
public Mail $mail;
|
||||
@ -48,71 +50,42 @@ class MailEntity implements ShouldQueue
|
||||
|
||||
private string $mailer = '';
|
||||
|
||||
public $invitation;
|
||||
|
||||
private ?string $db;
|
||||
|
||||
public int $tries = 4;
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
public function __construct($invitation, $db, $mail_object)
|
||||
{
|
||||
|
||||
$this->invitation = $invitation;
|
||||
|
||||
$this->company = $invitation->company;
|
||||
|
||||
$this->db = $db;
|
||||
|
||||
$this->mail_object = $mail_object;
|
||||
|
||||
$this->override = $mail_object->override;
|
||||
|
||||
}
|
||||
public function __construct(public mixed $invitation, private ?string $db, public MailObject $mail_object){}
|
||||
|
||||
/**
|
||||
* Handle the job
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->db);
|
||||
|
||||
$this->companyCheck();
|
||||
/* Where there are no invitations, we need to harvest the company and also use the correct context to build the mailable*/
|
||||
$this->company = $this->invitation->company;
|
||||
|
||||
$this->override = $this->mail_object->override;
|
||||
|
||||
$builder = new MailBuild($this);
|
||||
|
||||
//construct mailable
|
||||
/* Construct Mailable */
|
||||
$builder->run($this);
|
||||
|
||||
$this->mailable = $builder->getMailable();
|
||||
|
||||
//spam checks
|
||||
/* Email quality checks */
|
||||
if($this->preFlightChecksFail())
|
||||
return;
|
||||
|
||||
/* Try sending email */
|
||||
$this->setMailDriver()
|
||||
->trySending();
|
||||
|
||||
|
||||
//what do we pass into a generaic builder?
|
||||
|
||||
//construct mailer
|
||||
|
||||
}
|
||||
|
||||
public function companyCheck(): void
|
||||
{
|
||||
/* Handle bad state */
|
||||
if(!$this->company)
|
||||
$this->fail();
|
||||
|
||||
/* Handle deactivated company */
|
||||
if($this->company->is_disabled && !$this->override)
|
||||
$this->fail();
|
||||
|
||||
/* To handle spam users we drop all emails from flagged accounts */
|
||||
if(Ninja::isHosted() && $this->company->account && $this->company->account->is_flagged)
|
||||
$this->fail();
|
||||
|
||||
}
|
||||
|
||||
public function configureMailer(): self
|
||||
@ -136,22 +109,22 @@ class MailEntity implements ShouldQueue
|
||||
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;
|
||||
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;
|
||||
@ -208,14 +181,18 @@ class MailEntity implements ShouldQueue
|
||||
|
||||
$this->client_mailgun_domain = false;
|
||||
|
||||
//always dump the drivers to prevent reuse
|
||||
app('mail.manager')->forgetMailers();
|
||||
}
|
||||
|
||||
|
||||
public function trySending()
|
||||
/**
|
||||
* Attempts to send the email
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function trySending(): void
|
||||
{
|
||||
try {
|
||||
try {
|
||||
|
||||
$mail = Mail::mailer($this->mailer);
|
||||
$mail->send($this->mailable);
|
||||
@ -231,14 +208,14 @@ class MailEntity implements ShouldQueue
|
||||
nlog("Mailer failed with a Logic Exception {$e->getMessage()}");
|
||||
$this->fail();
|
||||
$this->cleanUpMailers();
|
||||
$this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
// $this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
return;
|
||||
}
|
||||
catch(\Symfony\Component\Mime\Exception\LogicException $e){
|
||||
nlog("Mailer failed with a Logic Exception {$e->getMessage()}");
|
||||
$this->fail();
|
||||
$this->cleanUpMailers();
|
||||
$this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
// $this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
return;
|
||||
}
|
||||
catch (\Exception | \Google\Service\Exception $e) {
|
||||
@ -257,7 +234,7 @@ class MailEntity implements ShouldQueue
|
||||
$message = "Either Attachment too large, or recipient has been suppressed.";
|
||||
|
||||
$this->fail();
|
||||
$this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
// $this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
$this->cleanUpMailers();
|
||||
|
||||
return;
|
||||
@ -270,7 +247,7 @@ class MailEntity implements ShouldQueue
|
||||
|
||||
/* If the is an entity attached to the message send a failure mailer */
|
||||
if($this->mail_object->entity_id)
|
||||
$this->entityEmailFailed($message);
|
||||
// $this->entityEmailFailed($message);
|
||||
|
||||
/* Don't send postmark failures to Sentry */
|
||||
if(Ninja::isHosted() && (!$e instanceof ClientException))
|
||||
@ -291,6 +268,17 @@ class MailEntity implements ShouldQueue
|
||||
*/
|
||||
public function preFlightChecksFail(): bool
|
||||
{
|
||||
/* Handle bad state */
|
||||
if(!$this->company)
|
||||
return true;
|
||||
|
||||
/* Handle deactivated company */
|
||||
if($this->company->is_disabled && !$this->override)
|
||||
return true;
|
||||
|
||||
/* To handle spam users we drop all emails from flagged accounts */
|
||||
if(Ninja::isHosted() && $this->company->account && $this->company->account->is_flagged)
|
||||
return true;
|
||||
|
||||
/* On the hosted platform we set default contacts a @example.com email address - we shouldn't send emails to these types of addresses */
|
||||
if($this->hasInValidEmails())
|
||||
@ -301,19 +289,19 @@ class MailEntity implements ShouldQueue
|
||||
return false;
|
||||
|
||||
/* On the hosted platform, if the user is over the email quotas, we do not send the email. */
|
||||
if($this->company->account && $this->company->account->emailQuotaExceeded())
|
||||
if(Ninja::isHosted() && $this->company->account && $this->company->account->emailQuotaExceeded())
|
||||
return true;
|
||||
|
||||
/* If the account is verified, we allow emails to flow */
|
||||
if($this->company->account && $this->company->account->is_verified_account) {
|
||||
if(Ninja::isHosted() && $this->company->account && $this->company->account->is_verified_account) {
|
||||
|
||||
//11-01-2022
|
||||
|
||||
/* Continue to analyse verified accounts in case they later start sending poor quality emails*/
|
||||
// if(class_exists(\Modules\Admin\Jobs\Account\EmailQuality::class))
|
||||
// (new \Modules\Admin\Jobs\Account\EmailQuality($this->nmo, $this->company))->run();
|
||||
// (new \Modules\Admin\Jobs\Account\EmailQuality($this->mail_object, $this->company))->run();
|
||||
|
||||
// return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -343,6 +331,8 @@ class MailEntity implements ShouldQueue
|
||||
*/
|
||||
private function hasInValidEmails(): bool
|
||||
{
|
||||
if(Ninja::isSelfHost())
|
||||
return false;
|
||||
|
||||
foreach($this->mail_object->to as $address_object)
|
||||
{
|
||||
@ -361,6 +351,229 @@ class MailEntity implements ShouldQueue
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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->company->account_id){
|
||||
|
||||
$this->mail_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->mail_object->settings->gmail_sending_user_id;
|
||||
|
||||
if($sending_user == "0")
|
||||
$user = $this->company->owner();
|
||||
else
|
||||
$user = User::find($this->decodePrimaryKey($sending_user));
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures Mailgun using client supplied secret
|
||||
* as the Mailer
|
||||
*/
|
||||
private function setMailgunMailer()
|
||||
{
|
||||
if(strlen($this->mail_object->settings->mailgun_secret) > 2 && strlen($this->mail_object->settings->mailgun_domain) > 2){
|
||||
$this->client_mailgun_secret = $this->mail_object->settings->mailgun_secret;
|
||||
$this->client_mailgun_domain = $this->mail_object->settings->mailgun_domain;
|
||||
}
|
||||
else{
|
||||
$this->mail_object->settings->email_sending_method = 'default';
|
||||
return $this->setMailDriver();
|
||||
}
|
||||
|
||||
$user = $this->resolveSendingUser();
|
||||
|
||||
$sending_email = (isset($this->mail_object->settings->custom_sending_email) && stripos($this->mail_object->settings->custom_sending_email, "@")) ? $this->mail_object->settings->custom_sending_email : $user->email;
|
||||
$sending_user = (isset($this->mail_object->settings->email_from_name) && strlen($this->mail_object->settings->email_from_name) > 2) ? $this->mail_object->settings->email_from_name : $user->name();
|
||||
|
||||
$this->mailable
|
||||
->from($sending_email, $sending_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures Postmark using client supplied secret
|
||||
* as the Mailer
|
||||
*/
|
||||
private function setPostmarkMailer()
|
||||
{
|
||||
if(strlen($this->mail_object->settings->postmark_secret) > 2){
|
||||
$this->client_postmark_secret = $this->mail_object->settings->postmark_secret;
|
||||
}
|
||||
else{
|
||||
$this->mail_object->settings->email_sending_method = 'default';
|
||||
return $this->setMailDriver();
|
||||
}
|
||||
|
||||
$user = $this->resolveSendingUser();
|
||||
|
||||
$sending_email = (isset($this->mail_object->settings->custom_sending_email) && stripos($this->mail_object->settings->custom_sending_email, "@")) ? $this->mail_object->settings->custom_sending_email : $user->email;
|
||||
$sending_user = (isset($this->mail_object->settings->email_from_name) && strlen($this->mail_object->settings->email_from_name) > 2) ? $this->mail_object->settings->email_from_name : $user->name();
|
||||
|
||||
$this->mailable
|
||||
->from($sending_email, $sending_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->mail_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);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
// $this->logMailError('Gmail Token Invalid', $this->company->clients()->first());
|
||||
$this->mail_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->company->account->gmailCredentialNotification();
|
||||
$this->mail_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->company->account->gmailCredentialNotification();
|
||||
$this->mail_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);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to refresh the Microsoft refreshToken
|
||||
*
|
||||
* @param App\Models\User
|
||||
* @return string | boool
|
||||
*/
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Backoff time
|
||||
*
|
||||
@ -379,9 +592,6 @@ class MailEntity implements ShouldQueue
|
||||
*/
|
||||
public function failed($exception = null)
|
||||
{
|
||||
nlog("dying now");
|
||||
|
||||
config(['queue.failed.driver' => null]);
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user