mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Updates for mailablej
This commit is contained in:
parent
6384e27e4b
commit
222cbc7703
@ -43,6 +43,7 @@ class CompanyUser extends Pivot
|
||||
'permissions',
|
||||
'notifications',
|
||||
'settings',
|
||||
'react_settings',
|
||||
'is_admin',
|
||||
'is_owner',
|
||||
'is_locked',
|
||||
@ -71,12 +72,12 @@ class CompanyUser extends Pivot
|
||||
|
||||
public function user_pivot()
|
||||
{
|
||||
return $this->hasOne(User::class)->withPivot('permissions', 'settings', 'is_admin', 'is_owner', 'is_locked', 'slack_webhook_url', 'migrating');
|
||||
return $this->hasOne(User::class)->withPivot('permissions', 'settings', 'react_settings', 'is_admin', 'is_owner', 'is_locked', 'slack_webhook_url', 'migrating');
|
||||
}
|
||||
|
||||
public function company_pivot()
|
||||
{
|
||||
return $this->hasOne(Company::class)->withPivot('permissions', 'settings', 'is_admin', 'is_owner', 'is_locked', 'slack_webhook_url', 'migrating');
|
||||
return $this->hasOne(Company::class)->withPivot('permissions', 'settings', 'react_settings', 'is_admin', 'is_owner', 'is_locked', 'slack_webhook_url', 'migrating');
|
||||
}
|
||||
|
||||
public function user()
|
||||
|
@ -11,15 +11,19 @@
|
||||
|
||||
namespace App\Services\Email;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Services\Email\MailBuild;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Bus\Queueable;
|
||||
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\Contracts\Mail\Mailable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use App\DataMapper\Analytics\EmailSuccess;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class BaseMailer implements ShouldQueue
|
||||
{
|
||||
@ -29,27 +33,38 @@ class BaseMailer implements ShouldQueue
|
||||
|
||||
public int $tries = 4;
|
||||
|
||||
public ?string $client_postmark_secret = false;
|
||||
public ?string $client_postmark_secret = null;
|
||||
|
||||
public ?string $client_mailgun_secret = null;
|
||||
|
||||
public ?string $client_mailgun_domain = null;
|
||||
|
||||
public boolean $override = false;
|
||||
public bool $override = false;
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
public function __construct()
|
||||
public Mail $mail;
|
||||
|
||||
private string $mailer = 'default';
|
||||
|
||||
public function __construct(public mixed $invitation, private ?string $db, public MailObject $mail_object)
|
||||
{
|
||||
|
||||
$this->invitation = $invitation;
|
||||
|
||||
$this->company = $invitation->company;
|
||||
|
||||
$this->db = $db;
|
||||
|
||||
$this->mail_object = $mail_object;
|
||||
|
||||
$this->override = $mail_object->override;
|
||||
|
||||
}
|
||||
|
||||
public function handle(MailBuild $builder): void
|
||||
{
|
||||
}
|
||||
|
||||
public function companyCheck()
|
||||
public function companyCheck(): void
|
||||
{
|
||||
|
||||
/* Handle bad state */
|
||||
if(!$this->company)
|
||||
$this->fail();
|
||||
@ -66,17 +81,109 @@ class BaseMailer implements ShouldQueue
|
||||
|
||||
public function configureMailer(): self
|
||||
{
|
||||
$this->setMailDriver();
|
||||
|
||||
$this->mail = Mail::mailer($this->mailer);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the mail driver to use and applies any specific configuration
|
||||
* the the mailable
|
||||
*/
|
||||
private function setMailDriver(): self
|
||||
{
|
||||
|
||||
switch ($this->mail_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();
|
||||
}
|
||||
|
||||
|
||||
public function trySending()
|
||||
{
|
||||
try {
|
||||
|
||||
$mailer
|
||||
->to($this->nmo->to_user->email)
|
||||
->send($this->nmo->mailable);
|
||||
->to($this->mail_object->to_user->email)
|
||||
->send($this->mail_object->mailable);
|
||||
|
||||
/* Count the amount of emails sent across all the users accounts */
|
||||
Cache::increment($this->company->account->key);
|
||||
@ -154,4 +261,3 @@ class BaseMailer implements ShouldQueue
|
||||
|
||||
}
|
||||
}
|
||||
`
|
@ -11,53 +11,90 @@
|
||||
|
||||
namespace App\Services\Email;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Client;
|
||||
use App\Models\Vendor;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Contracts\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailable as MailMailable;
|
||||
use App\Models\Account;
|
||||
use App\Utils\HtmlEngine;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use App\Services\Email\MailMailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Contracts\Mail\Mailable;
|
||||
use App\DataMapper\EmailTemplateDefaults;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
|
||||
/**
|
||||
* Class assumption is that we will be emailing an entity that has an associated Invitation
|
||||
*/
|
||||
class MailBuild
|
||||
{
|
||||
|
||||
/**
|
||||
* The settings object for this email
|
||||
* @var CompanySettings $settings
|
||||
* settings
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* The HTML / Template to use for this email
|
||||
* @var string $template
|
||||
*/
|
||||
/** @var mixed $template */
|
||||
private string $template;
|
||||
|
||||
/**
|
||||
* The locale to use for
|
||||
* translations for this email
|
||||
*/
|
||||
/** @var mixed $locale */
|
||||
private string $locale;
|
||||
|
||||
/** @var mixed $client */
|
||||
private ?Client $client;
|
||||
|
||||
/** @var mixed $vendor */
|
||||
private ?Vendor $vendor;
|
||||
|
||||
public function __construct(public MailEntity $mail_entity)
|
||||
{
|
||||
/**
|
||||
* __construct
|
||||
*
|
||||
* @param mixed $mail_entity
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(public MailEntity $mail_entity){}
|
||||
|
||||
}
|
||||
|
||||
public function run(): Mailable
|
||||
/**
|
||||
* Builds the mailable
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function run(): self
|
||||
{
|
||||
//resolve settings, if client existing - use merged - else default to company
|
||||
$this->settings = $this->mail_entity->company->settings;
|
||||
$this->resolveEntities();
|
||||
$this->mail_entity->mail_object->settings = $this->settings;
|
||||
|
||||
$this->resolveEntities()
|
||||
->setLocale()
|
||||
->setFrom()
|
||||
->setTo()
|
||||
->setTemplate()
|
||||
->setSubject()
|
||||
->setBody()
|
||||
->setReplyTo()
|
||||
->setBcc()
|
||||
->setAttachments()
|
||||
->setMetaData()
|
||||
->setVariables();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mailable to the mailer
|
||||
*
|
||||
* @return Mailable
|
||||
*/
|
||||
public function getMailable(): Mailable
|
||||
{
|
||||
return new MailMailable($this->mail_entity->mail_object); //todo current depends on EmailObject
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve any class entities
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function resolveEntities(): self
|
||||
{
|
||||
|
||||
@ -65,14 +102,13 @@ class MailBuild
|
||||
|
||||
$this->vendor = $this->mail_entity->mail_object->vendor_id ? Vendor::find($this->mail_entity->mail_object->vendor_id) : null;
|
||||
|
||||
$this->locale = $this->mail_entity->company->locale();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the meta data for the Email object
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setMetaData(): self
|
||||
{
|
||||
@ -85,20 +121,28 @@ class MailBuild
|
||||
|
||||
$this->mail_entity->mail_object->whitelabel = $this->mail_entity->company->account->isPaid() ? true : false;
|
||||
|
||||
$this->mail_entity->mail_object->company = $this->mail_entity->company;
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the locale
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setLocale(): self
|
||||
{
|
||||
|
||||
if($this->mail_entity->mail_object->client_id)
|
||||
$this->locale = $this->mail_entity->mail_object->client->locale();
|
||||
elseif($this->mail_entity->mail_object->vendor)
|
||||
$this->locale = $this->mail_entity->mail_object->vendor->locale();
|
||||
if($this->client){
|
||||
$this->locale = $this->client->locale();
|
||||
$this->settings = $this->client->getMergedSettings();
|
||||
$this->mail_entity->mail_object->settings = $this->settings;
|
||||
}
|
||||
elseif($this->vendor)
|
||||
$this->locale = $this->vendor->locale();
|
||||
else
|
||||
$this->locale = $this->mail_entity->company->locale();
|
||||
|
||||
@ -112,12 +156,14 @@ class MailBuild
|
||||
|
||||
/**
|
||||
* Sets the template
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setTemplate(): self
|
||||
{
|
||||
$this->template = $this->mail_entity->mail_object->settings->email_style;
|
||||
$this->template = $this->settings->email_style;
|
||||
|
||||
match($this->mail_entity->mail_object->settings->email_style){
|
||||
match($this->settings->email_style){
|
||||
'light' => $this->template = 'email.template.client',
|
||||
'dark' => $this->template = 'email.template.client',
|
||||
'custom' => $this->template = 'email.template.custom',
|
||||
@ -129,13 +175,27 @@ class MailBuild
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* setTo
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setTo(): self
|
||||
{
|
||||
$this->mail_entity->mail_object->to = [new Address($this->mail_entity->invitation->contact->email, $this->mail_entity->invitation->contact->present()->name())];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the FROM address
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setFrom(): self
|
||||
{
|
||||
|
||||
if(Ninja::isHosted() && $this->mail_entity->mail_object->settings->email_sending_method == 'default'){
|
||||
if(Ninja::isHosted() && $this->settings->email_sending_method == 'default'){
|
||||
$this->mail_entity->mail_object->from = new Address(config('mail.from.address'), $this->mail_entity->company->owner()->name());
|
||||
return $this;
|
||||
}
|
||||
@ -149,40 +209,18 @@ class MailBuild
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the body of the email
|
||||
*/
|
||||
private function setBody(): self
|
||||
{
|
||||
|
||||
if($this->mail_entity->mail_object->body){
|
||||
$this->mail_entity->mail_object->body = $this->mail_entity->mail_object->body;
|
||||
}
|
||||
elseif(strlen($this->mail_entity->mail_object->settings->{$this->mail_entity->mail_object->email_template_body}) > 3){
|
||||
$this->mail_entity->mail_object->body = $this->mail_entity->mail_object->settings->{$this->mail_entity->mail_object->email_template_body};
|
||||
}
|
||||
else{
|
||||
$this->mail_entity->mail_object->body = EmailTemplateDefaults::getDefaultTemplate($this->mail_entity->mail_object->email_template_body, $this->locale);
|
||||
}
|
||||
|
||||
if($this->template == 'email.template.custom'){
|
||||
$this->mail_entity->mail_object->body = (str_replace('$body', $this->mail_entity->mail_object->body, $this->mail_entity->mail_object->settings->email_style_custom));
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subject of the email
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setSubject(): self
|
||||
{
|
||||
|
||||
if ($this->mail_entity->mail_object->subject) //where the user updates the subject from the UI
|
||||
return $this;
|
||||
elseif(strlen($this->mail_entity->mail_object->settings->{$this->mail_entity->mail_object->email_template_subject}) > 3)
|
||||
$this->mail_entity->mail_object->subject = $this->mail_entity->mail_object->settings->{$this->mail_entity->mail_object->email_template_subject};
|
||||
elseif(is_string($this->mail_entity->mail_object->email_template_subject) && strlen($this->settings->{$this->mail_entity->mail_object->email_template_subject}) > 3)
|
||||
$this->mail_entity->mail_object->subject = $this->settings->{$this->mail_entity->mail_object->email_template_subject};
|
||||
else
|
||||
$this->mail_entity->mail_object->subject = EmailTemplateDefaults::getDefaultTemplate($this->mail_entity->mail_object->email_template_subject, $this->locale);
|
||||
|
||||
@ -190,15 +228,75 @@ class MailBuild
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the body of the email
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setBody(): self
|
||||
{
|
||||
|
||||
if($this->mail_entity->mail_object->body){
|
||||
$this->mail_entity->mail_object->body = $this->mail_entity->mail_object->body;
|
||||
}
|
||||
elseif(is_string($this->mail_entity->mail_object->email_template_body) && strlen($this->settings->{$this->mail_entity->mail_object->email_template_body}) > 3){
|
||||
$this->mail_entity->mail_object->body = $this->settings->{$this->mail_entity->mail_object->email_template_body};
|
||||
}
|
||||
else{
|
||||
$this->mail_entity->mail_object->body = EmailTemplateDefaults::getDefaultTemplate($this->mail_entity->mail_object->email_template_body, $this->locale);
|
||||
}
|
||||
|
||||
if($this->template == 'email.template.custom'){
|
||||
$this->mail_entity->mail_object->body = (str_replace('$body', $this->mail_entity->mail_object->body, $this->settings->email_style_custom));
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setAttachments(): self
|
||||
{
|
||||
$attachments = [];
|
||||
|
||||
if ($this->settings->document_email_attachment && $this->mail_entity->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {
|
||||
|
||||
foreach ($this->mail_entity->company->documents as $document) {
|
||||
|
||||
$attachments[] = ['file' => base64_encode($document->getFile()), 'name' => $document->name];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->mail_entity->mail_object->attachments = array_merge($this->mail_entity->mail_object->attachments, $attachments);
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the reply to of the email
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setReplyTo(): self
|
||||
{
|
||||
|
||||
$reply_to_email = str_contains($this->mail_entity->mail_object->settings->reply_to_email, "@") ? $this->mail_entity->mail_object->settings->reply_to_email : $this->mail_entity->company->owner()->email;
|
||||
$reply_to_email = str_contains($this->settings->reply_to_email, "@") ? $this->settings->reply_to_email : $this->mail_entity->company->owner()->email;
|
||||
|
||||
$reply_to_name = strlen($this->mail_entity->mail_object->settings->reply_to_name) > 3 ? $this->mail_entity->mail_object->settings->reply_to_name : $this->mail_entity->company->owner()->present()->name();
|
||||
$reply_to_name = strlen($this->settings->reply_to_name) > 3 ? $this->settings->reply_to_name : $this->mail_entity->company->owner()->present()->name();
|
||||
|
||||
$this->mail_entity->mail_object->reply_to = array_merge($this->mail_entity->mail_object->reply_to, [new Address($reply_to_email, $reply_to_name)]);
|
||||
|
||||
@ -208,13 +306,22 @@ class MailBuild
|
||||
/**
|
||||
* Replaces the template placeholders
|
||||
* with variable values.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setVariables(): self
|
||||
{
|
||||
|
||||
$this->mail_entity->mail_object->body = strtr($this->mail_entity->mail_object->body, $this->mail_entity->mail_object->variables);
|
||||
|
||||
if($this->mail_entity->mail_object->variables){
|
||||
$this->mail_entity->mail_object->subject = strtr($this->mail_entity->mail_object->subject, $this->mail_entity->mail_object->variables);
|
||||
$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);
|
||||
|
||||
if($this->template != 'custom')
|
||||
$this->mail_entity->mail_object->body = $this->parseMarkdownToHtml($this->mail_entity->mail_object->body);
|
||||
@ -224,18 +331,20 @@ class MailBuild
|
||||
|
||||
/**
|
||||
* Sets the BCC of the email
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setBcc(): self
|
||||
{
|
||||
$bccs = [];
|
||||
$bcc_array = [];
|
||||
|
||||
if (strlen($this->mail_entity->mail_object->settings->bcc_email) > 1) {
|
||||
if (strlen($this->settings->bcc_email) > 1) {
|
||||
|
||||
if (Ninja::isHosted() && $this->mail_entity->company->account->isPaid()) {
|
||||
$bccs = array_slice(explode(',', str_replace(' ', '', $this->mail_entity->mail_object->settings->bcc_email)), 0, 2);
|
||||
$bccs = array_slice(explode(',', str_replace(' ', '', $this->settings->bcc_email)), 0, 2);
|
||||
} elseif(Ninja::isSelfHost()) {
|
||||
$bccs = (explode(',', str_replace(' ', '', $this->mail_entity->mail_object->settings->bcc_email)));
|
||||
$bccs = (explode(',', str_replace(' ', '', $this->settings->bcc_email)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,41 +369,18 @@ class MailBuild
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
$attachments = [];
|
||||
|
||||
if ($this->mail_entity->mail_object->settings->document_email_attachment && $this->mail_entity->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {
|
||||
|
||||
foreach ($this->mail_entity->company->documents as $document) {
|
||||
|
||||
$attachments[] = ['file' => base64_encode($document->getFile()), 'name' => $document->name];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->mail_entity->mail_object->attachments = array_merge($this->mail_entity->mail_object->attachments, $attachments);
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the headers for the email
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function setHeaders(): self
|
||||
{
|
||||
if($this->mail_entity->mail_object->invitation_key)
|
||||
$this->mail_entity->mail_object->headers = array_merge($this->mail_entity->mail_object->headers, ['x-invitation-key' => $this->mail_entity->mail_object->invitation_key]);
|
||||
elseif($this->mail_entity->invitation)
|
||||
$this->mail_entity->mail_object->headers = array_merge($this->mail_entity->mail_object->headers, ['x-invitation-key' => $this->mail_entity->invitation->key]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -11,22 +11,52 @@
|
||||
|
||||
namespace App\Services\Email;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Company;
|
||||
use App\Services\Email\MailBuild;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
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 App\DataMapper\Analytics\EmailSuccess;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MailEntity extends BaseMailer implements ShouldQueue
|
||||
class MailEntity implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public Company $company;
|
||||
|
||||
public function __construct(protected ?mixed $invitation, private ?string $db, public MailObject $mail_object)
|
||||
public int $tries = 4;
|
||||
|
||||
public ?string $client_postmark_secret = null;
|
||||
|
||||
public ?string $client_mailgun_secret = null;
|
||||
|
||||
public ?string $client_mailgun_domain = null;
|
||||
|
||||
public bool $override = false;
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
private string $mailer = '';
|
||||
|
||||
public $invitation;
|
||||
|
||||
public Mail $mail;
|
||||
|
||||
private ?string $db;
|
||||
|
||||
public MailObject $mail_object;
|
||||
|
||||
public Mailable $mailable;
|
||||
|
||||
public function __construct($invitation, $db, $mail_object)
|
||||
{
|
||||
|
||||
$this->invitation = $invitation;
|
||||
@ -41,23 +71,224 @@ class MailEntity extends BaseMailer implements ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
public function handle(MailBuild $builder): void
|
||||
public function handle(): void
|
||||
{
|
||||
$builder = new MailBuild($this);
|
||||
|
||||
MultiDB::setDb($this->db);
|
||||
|
||||
$this->companyCheck();
|
||||
|
||||
//construct mailable
|
||||
$builder->run($this);
|
||||
|
||||
$this->mailable = $builder->getMailable();
|
||||
|
||||
$this->setMailDriver()
|
||||
->trySending();
|
||||
|
||||
//spam checks
|
||||
|
||||
//what do we pass into a generaic builder?
|
||||
|
||||
//construct mailer
|
||||
$mailer = $this->configureMailer()
|
||||
->trySending();
|
||||
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
$this->setMailDriver();
|
||||
|
||||
$this->mail = Mail::mailer($this->mailer);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the mail driver to use and applies any specific configuration
|
||||
* the the mailable
|
||||
*/
|
||||
private function setMailDriver(): self
|
||||
{
|
||||
|
||||
switch ($this->mail_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->company->id . '_MAIL_HOST'))
|
||||
{
|
||||
|
||||
config([
|
||||
'mail.mailers.smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'host' => env($this->company->id . '_MAIL_HOST'),
|
||||
'port' => env($this->company->id . '_MAIL_PORT'),
|
||||
'username' => env($this->company->id . '_MAIL_USERNAME'),
|
||||
'password' => env($this->company->id . '_MAIL_PASSWORD'),
|
||||
],
|
||||
]);
|
||||
|
||||
if(env($this->company->id . '_MAIL_FROM_ADDRESS'))
|
||||
{
|
||||
$this->mailable
|
||||
->from(env($this->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->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();
|
||||
}
|
||||
|
||||
|
||||
public function trySending()
|
||||
{
|
||||
try {
|
||||
|
||||
$mail = Mail::mailer($this->mailer);
|
||||
$mail->send($this->mailable);
|
||||
|
||||
/* Count the amount of emails sent across all the users accounts */
|
||||
Cache::increment($this->company->account->key);
|
||||
|
||||
LightLogs::create(new EmailSuccess($this->company->company_key))
|
||||
->send();
|
||||
|
||||
}
|
||||
catch(\Symfony\Component\Mime\Exception\RfcComplianceException $e) {
|
||||
nlog("Mailer failed with a Logic Exception {$e->getMessage()}");
|
||||
$this->fail();
|
||||
$this->cleanUpMailers();
|
||||
$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());
|
||||
return;
|
||||
}
|
||||
catch (\Exception | \Google\Service\Exception $e) {
|
||||
|
||||
nlog("Mailer failed with {$e->getMessage()}");
|
||||
$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(stripos($e->getMessage(), 'code 406') || stripos($e->getMessage(), 'code 300') || stripos($e->getMessage(), 'code 413'))
|
||||
{
|
||||
|
||||
$message = "Either Attachment too large, or recipient has been suppressed.";
|
||||
|
||||
$this->fail();
|
||||
$this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
$this->cleanUpMailers();
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
//only report once, not on all tries
|
||||
if($this->attempts() == $this->tries)
|
||||
{
|
||||
|
||||
/* If the is an entity attached to the message send a failure mailer */
|
||||
if($this->nmo->entity)
|
||||
$this->entityEmailFailed($message);
|
||||
|
||||
/* Don't send postmark failures to Sentry */
|
||||
if(Ninja::isHosted() && (!$e instanceof ClientException))
|
||||
app('sentry')->captureException($e);
|
||||
|
||||
}
|
||||
|
||||
/* Releasing immediately does not add in the backoff */
|
||||
$this->release($this->backoff()[$this->attempts()-1]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function backoff()
|
||||
{
|
||||
return [5, 10, 30, 240];
|
||||
}
|
||||
|
||||
public function failed($exception = null)
|
||||
{
|
||||
|
||||
config(['queue.failed.driver' => null]);
|
||||
|
||||
}
|
||||
}
|
||||
|
107
app/Services/Email/MailMailable.php
Normal file
107
app/Services/Email/MailMailable.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?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\Services\Email\MailObject;
|
||||
|
||||
use Illuminate\Mail\Attachment;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Mail\Mailables\Headers;
|
||||
|
||||
class MailMailable extends Mailable
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(public MailObject $mail_object){}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Envelope
|
||||
*/
|
||||
public function envelope()
|
||||
{
|
||||
return new Envelope(
|
||||
subject: $this->mail_object->subject,
|
||||
tags: [$this->mail_object->company_key],
|
||||
replyTo: $this->mail_object->reply_to,
|
||||
from: $this->mail_object->from,
|
||||
to: $this->mail_object->to,
|
||||
bcc: $this->mail_object->bcc
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Content
|
||||
*/
|
||||
public function content()
|
||||
{
|
||||
return new Content(
|
||||
view: $this->mail_object->html_template,
|
||||
text: $this->mail_object->text_template,
|
||||
with: [
|
||||
'text_body' => strip_tags($this->mail_object->body), //@todo this is a bit hacky here.
|
||||
'body' => $this->mail_object->body,
|
||||
'settings' => $this->mail_object->settings,
|
||||
'whitelabel' => $this->mail_object->whitelabel,
|
||||
'logo' => $this->mail_object->logo,
|
||||
'signature' => $this->mail_object->signature,
|
||||
'company' => $this->mail_object->company,
|
||||
'greeting' => ''
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attachments()
|
||||
{
|
||||
|
||||
$attachments = [];
|
||||
|
||||
foreach($this->mail_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->mail_object->headers,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Services\Email;
|
||||
|
||||
use App\Models\Company;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
|
||||
/**
|
||||
@ -77,11 +78,12 @@ class MailObject
|
||||
|
||||
public array $variables = [];
|
||||
|
||||
public ?string $reminder_template = null;
|
||||
public ?string $template = null;
|
||||
|
||||
public ?string $template_data = null;
|
||||
|
||||
public bool $override = false;
|
||||
|
||||
public ?Company $company = null;
|
||||
|
||||
}
|
@ -44,6 +44,7 @@ class CompanyUserTransformer extends EntityTransformer
|
||||
'permissions' => $company_user->permissions ?: '',
|
||||
'notifications' => $company_user->notifications ? (object) $company_user->notifications : $blank_obj,
|
||||
'settings' => $company_user->settings ? (object) $company_user->settings : $blank_obj,
|
||||
'react_settings' => $company_user->react_settings ? (object) $company_user->react_settings : $blank_obj,
|
||||
'is_owner' => (bool) $company_user->is_owner,
|
||||
'is_admin' => (bool) $company_user->is_admin,
|
||||
'is_locked' => (bool) $company_user->is_locked,
|
||||
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('company_user', function (Blueprint $table) {
|
||||
|
||||
$table->mediumText('react_settings')->nullable();
|
||||
|
||||
\Illuminate\Support\Facades\Artisan::call('ninja:design-update');
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user