From a4f2d40d7564acaa19359f5b3ef094ba182fa7f9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 15 Feb 2023 11:04:47 +1100 Subject: [PATCH] Working on emailers --- app/Http/Controllers/EmailController.php | 3 + app/Http/Middleware/TokenAuth.php | 1 - app/Jobs/Entity/EmailEntity.php | 6 - app/Models/Quote.php | 12 ++ app/Providers/MultiDBProvider.php | 1 - app/Services/Email/BaseMailer.php | 263 ----------------------- app/Services/Email/MailBuild.php | 75 +++++-- app/Services/Email/MailObject.php | 2 + app/Services/Quote/SendEmail.php | 15 +- 9 files changed, 89 insertions(+), 289 deletions(-) delete mode 100644 app/Services/Email/BaseMailer.php diff --git a/app/Http/Controllers/EmailController.php b/app/Http/Controllers/EmailController.php index b7b0582c90a3..25dca243d6bf 100644 --- a/app/Http/Controllers/EmailController.php +++ b/app/Http/Controllers/EmailController.php @@ -23,6 +23,7 @@ use App\Models\Invoice; use App\Models\PurchaseOrder; use App\Models\Quote; use App\Models\RecurringInvoice; +use App\Services\Email\MailObject; use App\Transformers\CreditTransformer; use App\Transformers\InvoiceTransformer; use App\Transformers\PurchaseOrderTransformer; @@ -127,6 +128,8 @@ class EmailController extends BaseController 'body' => $body, ]; + $mo = new MailObject; + if(Ninja::isHosted() && !$entity_obj->company->account->account_sms_verified) return response(['message' => 'Please verify your account to send emails.'], 400); diff --git a/app/Http/Middleware/TokenAuth.php b/app/Http/Middleware/TokenAuth.php index b312e1df5263..abd52376f098 100644 --- a/app/Http/Middleware/TokenAuth.php +++ b/app/Http/Middleware/TokenAuth.php @@ -73,7 +73,6 @@ class TokenAuth | session */ app('queue')->createPayloadUsing(function () use ($company_token) { - nlog("setting DB ". $company_token->company->db); return ['db' => $company_token->company->db]; }); diff --git a/app/Jobs/Entity/EmailEntity.php b/app/Jobs/Entity/EmailEntity.php index 96e8b088f55b..dda2c0711fab 100644 --- a/app/Jobs/Entity/EmailEntity.php +++ b/app/Jobs/Entity/EmailEntity.php @@ -11,19 +11,14 @@ namespace App\Jobs\Entity; -use App\Events\Invoice\InvoiceReminderWasEmailed; -use App\Events\Invoice\InvoiceWasEmailed; use App\Events\Invoice\InvoiceWasEmailedAndFailed; -use App\Jobs\Mail\EntityFailedSendMailer; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; use App\Libraries\MultiDB; use App\Mail\TemplateEmail; -use App\Models\Activity; use App\Models\Company; use App\Models\CreditInvitation; use App\Models\InvoiceInvitation; -use App\Models\PurchaseOrderInvitation; use App\Models\QuoteInvitation; use App\Models\RecurringInvoiceInvitation; use App\Utils\HtmlEngine; @@ -34,7 +29,6 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Mail; use Illuminate\Support\Str; /*Multi Mailer implemented*/ diff --git a/app/Models/Quote.php b/app/Models/Quote.php index 03c09ae60a77..f03d8c9ff5d9 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -351,4 +351,16 @@ class Quote extends BaseModel { return ctrans('texts.quote'); } + + /** + * calculateTemplate + * + * @param string $entity_string + * @return string + */ + public function calculateTemplate(string $entity_string): string + { + return $entity_string; + } + } diff --git a/app/Providers/MultiDBProvider.php b/app/Providers/MultiDBProvider.php index 72beb2abc594..f00a1d436693 100644 --- a/app/Providers/MultiDBProvider.php +++ b/app/Providers/MultiDBProvider.php @@ -37,7 +37,6 @@ class MultiDBProvider extends ServiceProvider JobProcessing::class, function ($event) { if (isset($event->job->payload()['db'])) { - nlog("Settings DB: " . $event->job->payload()['db']); MultiDB::setDb($event->job->payload()['db']); } } diff --git a/app/Services/Email/BaseMailer.php b/app/Services/Email/BaseMailer.php deleted file mode 100644 index 1fd066d5a8a5..000000000000 --- a/app/Services/Email/BaseMailer.php +++ /dev/null @@ -1,263 +0,0 @@ -invitation = $invitation; - - $this->company = $invitation->company; - - $this->db = $db; - - $this->mail_object = $mail_object; - - $this->override = $mail_object->override; - - } - - - 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->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->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); - - 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]); - - } -} diff --git a/app/Services/Email/MailBuild.php b/app/Services/Email/MailBuild.php index 8799c7472c42..e9e9b4aa902b 100644 --- a/app/Services/Email/MailBuild.php +++ b/app/Services/Email/MailBuild.php @@ -13,13 +13,18 @@ namespace App\Services\Email; use App\Models\Task; use App\Utils\Ninja; +use App\Models\Quote; use App\Models\Client; +use App\Models\Credit; use App\Models\Vendor; use App\Models\Account; use App\Models\Expense; +use App\Models\Invoice; use App\Utils\HtmlEngine; use App\Models\ClientContact; +use App\Models\PurchaseOrder; use App\Models\VendorContact; +use App\Utils\Traits\MakesHash; use App\Utils\VendorHtmlEngine; use App\Jobs\Entity\CreateRawPdf; use Illuminate\Support\Facades\App; @@ -30,7 +35,6 @@ use Illuminate\Contracts\Mail\Mailable; use App\DataMapper\EmailTemplateDefaults; use League\CommonMark\CommonMarkConverter; use App\Jobs\Vendor\CreatePurchaseOrderPdf; -use App\Utils\Traits\MakesHash; class MailBuild { @@ -118,6 +122,30 @@ class MailBuild $this->vendor = $vendor_contact?->vendor; + if($this->mail_entity?->invitation){ + + + if($this->mail_entity->invitation?->invoice){ + $this->mail_entity->mail_object->entity_string = 'invoice'; + $this->mail_entity->mail_object->entity_class = Invoice::class; + } + + if($this->mail_entity->invitation?->quote){ + $this->mail_entity->mail_object->entity_string = 'quote'; + $this->mail_entity->mail_object->entity_class = Quote::class; + } + + if($this->mail_entity->invitation?->credit){ + $this->mail_entity->mail_object->entity_string = 'credit'; + $this->mail_entity->mail_object->entity_class = Credit::class; + } + + if($this->mail_entity->invitation?->puchase_order){ + $this->mail_entity->mail_object->entity_string = 'purchase_order'; + $this->mail_entity->mail_object->entity_class = PurchaseOrder::class; + } + } + return $this; } @@ -261,7 +289,7 @@ class MailBuild 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); + $this->mail_entity->mail_object->subject = EmailTemplateDefaults::getDefaultTemplate($this->resolveBaseEntityTemplate(), $this->locale); return $this; @@ -282,7 +310,7 @@ class MailBuild $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); + $this->mail_entity->mail_object->body = EmailTemplateDefaults::getDefaultTemplate($this->resolveBaseEntityTemplate('body'), $this->locale); } if($this->template == 'email.template.custom'){ @@ -292,6 +320,29 @@ class MailBuild return $this; } + + /** + * Where no template is explicitly passed, we need to infer by the entity type - + * which is hopefully resolvable. + * + * @param string $type + * @return string + */ + private function resolveBaseEntityTemplate(string $type = 'subject'): string + { + //handle statements being emailed + //handle custom templates these types won't have a resolvable entity_string + if(!$this->mail_entity->mail_object->entity_string) + return 'email_template_invoice'; + + match($type){ + 'subject' => $template = "email_subject_{$this->mail_entity->mail_object->entity_string}", + 'body' => $template = "email_template_{$this->mail_entity->mail_object->entity_string}", + default => $template = "email_template_invoice", + }; + + return $template; + } /** * Sets the attachments for the email @@ -365,24 +416,18 @@ class MailBuild } - if($this->mail_entity->invitation?->invoice) - $entity = 'invoice'; - - if($this->mail_entity->invitation?->quote) - $entity = 'quote'; - - if($this->mail_entity->invitation?->credit) - $entity = 'credit'; + if(!$this->mail_entity->mail_object->entity_string) + return $this; $pdf = ((new CreateRawPdf($this->mail_entity->invitation, $this->mail_entity->invitation->company->db))->handle()); nlog($this->mail_entity->mail_object->attachments); - $this->mail_entity->mail_object->attachments = array_merge($this->mail_entity->mail_object->attachments, [['file' => base64_encode($pdf), 'name' => $this->mail_entity->invitation->{$entity}->numberFormatter().'.pdf']]); + $this->mail_entity->mail_object->attachments = array_merge($this->mail_entity->mail_object->attachments, [['file' => base64_encode($pdf), 'name' => $this->mail_entity->invitation->{$this->mail_entity->mail_object->entity_string}->numberFormatter().'.pdf']]); if ($this->client->getSetting('document_email_attachment') !== false && $this->mail_entity->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) { - $this->attachDocuments($this->mail_entity->invitation->{$entity}->documents); + $this->attachDocuments($this->mail_entity->invitation->{$this->mail_entity->mail_object->entity_string}->documents); } @@ -391,12 +436,12 @@ class MailBuild - if($this->settings->ubl_email_attachment && $entity == 'invoice') + if($this->settings->ubl_email_attachment && $this->mail_entity->mail_object->entity_string == 'invoice') { } - if($entity == 'invoice') + if($this->mail_entity->mail_object->entity_string == 'invoice') { $line_items = $this->mail_entity->invitation->invoice->line_items; diff --git a/app/Services/Email/MailObject.php b/app/Services/Email/MailObject.php index 8f31f10a71df..30ace3436ec7 100644 --- a/app/Services/Email/MailObject.php +++ b/app/Services/Email/MailObject.php @@ -78,6 +78,8 @@ class MailObject public ?string $entity_class = null; + public ?string $entity_string = null; + public array $variables = []; public ?string $template = null; diff --git a/app/Services/Quote/SendEmail.php b/app/Services/Quote/SendEmail.php index 0c1226b3410a..b060616429ff 100644 --- a/app/Services/Quote/SendEmail.php +++ b/app/Services/Quote/SendEmail.php @@ -13,6 +13,8 @@ namespace App\Services\Quote; use App\Jobs\Entity\EmailEntity; use App\Models\ClientContact; +use App\Services\Email\MailEntity; +use App\Services\Email\MailObject; class SendEmail { @@ -37,15 +39,22 @@ class SendEmail */ public function run() { + nlog($this->reminder_template); + nlog("is there a template"); + if (! $this->reminder_template) { $this->reminder_template = $this->quote->calculateTemplate('quote'); } - + + $mo = new MailObject(); + $this->quote->service()->markSent()->save(); - $this->quote->invitations->each(function ($invitation) { + $this->quote->invitations->each(function ($invitation) use ($mo){ if (! $invitation->contact->trashed() && $invitation->contact->email) { - EmailEntity::dispatch($invitation, $invitation->company, $this->reminder_template); + // EmailEntity::dispatch($invitation, $invitation->company, $this->reminder_template); + + MailEntity::dispatch($invitation, $invitation->company->db, $mo); } });