diff --git a/app/Http/Controllers/EmailController.php b/app/Http/Controllers/EmailController.php index 4ecad444a932..b18360285b73 100644 --- a/app/Http/Controllers/EmailController.php +++ b/app/Http/Controllers/EmailController.php @@ -21,8 +21,6 @@ use Illuminate\Http\Response; use App\Utils\Traits\MakesHash; use App\Jobs\Entity\EmailEntity; use App\Models\RecurringInvoice; -use App\Services\Email\MailEntity; -use App\Services\Email\MailObject; use App\Services\Email\EmailObject; use App\Events\Quote\QuoteWasEmailed; use App\Transformers\QuoteTransformer; @@ -121,7 +119,6 @@ class EmailController extends BaseController $entity_obj = $entity::withTrashed()->with('invitations')->find($request->input('entity_id')); $subject = $request->has('subject') ? $request->input('subject') : ''; $body = $request->has('body') ? $request->input('body') : ''; - $entity_string = strtolower(class_basename($entity_obj)); $template = str_replace('email_template_', '', $request->input('template')); $data = [ @@ -133,10 +130,10 @@ class EmailController extends BaseController $mo->subject = strlen($subject) > 3 ? $subject : null; $mo->body = strlen($body) > 3 ? $body : null; $mo->entity_id = $request->input('entity_id'); - $mo->template = $template; + $mo->template = $template; //full template name in use $mo->entity_class = $this->resolveClass($entity); - $mo->email_template_body = "email_template_{$template}"; - $mo->email_template_subject = "email_subject_{$template}"; + $mo->email_template_body = $template; + $mo->email_template_subject = str_replace("template", "subject", $template); if (Ninja::isHosted() && !$entity_obj->company->account->account_sms_verified) { return response(['message' => 'Please verify your account to send emails.'], 400); @@ -146,7 +143,7 @@ class EmailController extends BaseController return $this->sendPurchaseOrder($entity_obj, $data, $template); } - $entity_obj->invitations->each(function ($invitation) use ($data, $entity_string, $entity_obj, $template, $mo) { + $entity_obj->invitations->each(function ($invitation) use ($data, $entity_obj, $template, $mo) { if (! $invitation->contact->trashed() && $invitation->contact->email) { $entity_obj->service()->markSent()->save(); @@ -155,7 +152,7 @@ class EmailController extends BaseController $mo->invitation_id = $invitation->id; Email::dispatch($mo, $invitation->company); - // MailEntity::dispatch($invitation, $invitation->company->db, $mo); + } }); @@ -214,10 +211,14 @@ class EmailController extends BaseController private function resolveClass(string $entity): string { + match($entity){ 'invoice' => $class = Invoice::class, + 'App\Models\Invoice' => $class = Invoice::class, 'credit' => $class = Credit::class, + 'App\Models\Credit' => $class = Credit::class, 'quote' => $class = Quote::class, + 'App\Models\Quote' => $class = Quote::class, 'purchase_order' => $class = PurchaseOrder::class, 'purchaseOrder' => $class = PurchaseOrder::class, 'App\Models\PurchaseOrder' => $class = PurchaseOrder::class, @@ -225,5 +226,6 @@ class EmailController extends BaseController }; return $class; + } } diff --git a/app/Services/Email/EmailDefaults.php b/app/Services/Email/EmailDefaults.php index 9d13bd5ce9ab..0178a8b7cee9 100644 --- a/app/Services/Email/EmailDefaults.php +++ b/app/Services/Email/EmailDefaults.php @@ -134,7 +134,6 @@ class EmailDefaults */ private function setTo(): self { - if ($this->email->email_object->to) { return $this; } @@ -150,10 +149,13 @@ class EmailDefaults private function setBody(): self { if ($this->email->email_object->body) { - $this->email->email_object->body = $this->email->email_object->body; + // A Custom Message has been set in the email screen. + return $this; } elseif (strlen($this->email->email_object->settings?->{$this->email->email_object->email_template_body}) > 3) { + // A body has been saved in the settings. $this->email->email_object->body = $this->email->email_object->settings?->{$this->email->email_object->email_template_body}; } else { + // Default template to be used $this->email->email_object->body = EmailTemplateDefaults::getDefaultTemplate($this->email->email_object->email_template_body, $this->locale); } @@ -207,7 +209,7 @@ class EmailDefaults if ($this->template != 'custom') { $this->email->email_object->body = $this->parseMarkdownToHtml($this->email->email_object->body); } -nlog($this->email->email_object->subject); + nlog($this->email->email_object->subject); return $this; } diff --git a/app/Services/Email/MailBuild.php b/app/Services/Email/MailBuild.php deleted file mode 100644 index 51ac2a89e452..000000000000 --- a/app/Services/Email/MailBuild.php +++ /dev/null @@ -1,570 +0,0 @@ -resolveEntities() - ->setLocale() - ->setMetaData() - ->setFrom() - ->setTo() - ->setTemplate() - ->setSubject() - ->setBody() - ->setReplyTo() - ->setBcc() - ->setAttachments() - ->setVariables(); - - return $this; - } - - /** - * Returns the mailable to the mailer - * - * @return Mailable - */ - public function getMailable(): Mailable - { - return new MailMailable($this->mail_entity->mail_object); - } - - /** - * Resolve any class entities - * - * @return self - */ - private function resolveEntities(): self - { - $client_contact = $this->mail_entity?->invitation?->client_contact_id ? ClientContact::withTrashed()->find($this->mail_entity->invitation->client_contact_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; - - 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; - } - - /** - * Sets the locale - * Sets the settings object depending on context - * Sets the HTML variables depending on context - * - * @return self - */ - private function setLocale(): self - { - if ($this->client) { - $this->locale = $this->client->locale(); - $this->settings = $this->client->getMergedSettings(); - - if ($this->mail_entity->invitation) { - $this->variables = (new HtmlEngine($this->mail_entity->invitation))->makeValues(); - } - } elseif ($this->vendor) { - $this->locale = $this->vendor->locale(); - $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'); - $t = app('translator'); - $t->replace(Ninja::transformTranslations($this->settings)); - - return $this; - } - - /** - * Sets the meta data for the Email object - * - * @return self - */ - private function setMetaData(): self - { - $this->mail_entity->mail_object->company_key = $this->mail_entity->company->company_key; - - $this->mail_entity->mail_object->logo = $this->mail_entity->company->present()->logo(); - - $this->mail_entity->mail_object->signature = $this->mail_entity->mail_object->signature ?: $this->settings->email_signature; - - $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 template - * - * @return self - */ - private function setTemplate(): self - { - $this->template = $this->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', - default => $this->template = 'email.template.client', - }; - - $this->mail_entity->mail_object->html_template = $this->template; - - return $this; - } - - /** - * setTo - * - * @return self - */ - private function setTo(): self - { - $this->mail_entity->mail_object->to = array_merge( - $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->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; - } - - if ($this->mail_entity->mail_object->from) { - return $this; - } - - $this->mail_entity->mail_object->from = new Address($this->mail_entity->company->owner()->email, $this->mail_entity->company->owner()->name()); - - 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 (is_string($this->mail_entity->mail_object->email_template) && strlen($this->settings->{$this->resolveBaseEntityTemplate()}) > 3) { - $this->mail_entity->mail_object->subject = $this->settings->{$this->resolveBaseEntityTemplate()}; - } else { - $this->mail_entity->mail_object->subject = EmailTemplateDefaults::getDefaultTemplate($this->resolveBaseEntityTemplate(), $this->locale); - } - - return $this; - } - - /** - * 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) && strlen($this->settings->{$this->resolveBaseEntityTemplate('body')}) > 3) { - $this->mail_entity->mail_object->body = $this->settings->{$this->resolveBaseEntityTemplate('body')}; - } else { - $this->mail_entity->mail_object->body = EmailTemplateDefaults::getDefaultTemplate($this->resolveBaseEntityTemplate('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; - } - - /** - * 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 - { - if ($this->mail_entity->mail_object->email_template) { - match ($type) { - 'subject' => $template = "email_subject_{$this->mail_entity->mail_object->email_template}", - 'body' => $template = "email_template_{$this->mail_entity->mail_object->email_template}", - default => $template = "email_template_invoice", - }; - - return $template; - } - - //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 - * - * 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 - { - $this->setContextAttachments(); - - if ($this->settings->document_email_attachment && $this->mail_entity->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) { - $this->attachDocuments($this->mail_entity->company->documents); - } - - return $this; - } - - private function attachDocuments($documents): self - { - foreach ($documents as $document) { - if ($document->size > $this->max_attachment_size) { - $this->mail_entity->mail_object->attachment_links = array_merge($this->mail_entity->mail_object->attachment_links, [[" $document->hash]) ."'>". $document->name .""]]); - } else { - $this->mail_entity->mail_object->attachments = array_merge($this->mail_entity->mail_object->attachments, [['file' => base64_encode($document->getFile()), 'name' => $document->name]]); - } - } - - return $this; - } - - /** - * Depending on context we may need to resolve the - * attachment dependencies. - * - * ie. Resolve the entity. - * ie. Resolve if we should attach the Entity PDF - * ie. Create the Entity PDF - * ie. Inject the PDF - * - * @return self - */ - private function setContextAttachments(): self - { - if (!$this->settings->pdf_email_attachment || !$this->mail_entity->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { - return $this; - } - - if ($this->mail_entity->invitation?->purchase_order) { - $pdf = (new CreatePurchaseOrderPdf($this->mail_entity->invitation))->rawPdf(); - - $this->mail_entity->mail_object->attachments = array_merge($this->mail_entity->mail_object->attachments, [['file' => base64_encode($pdf), 'name' => $this->mail_entity->invitation->purchase_order->numberFormatter().'.pdf']]); - - if ($this->vendor->getSetting('document_email_attachment') !== false && $this->mail_entity->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) { - $this->attachDocuments($this->mail_entity->invitation->purchase_order->documents); - } - - return $this; - } - - if (!$this->mail_entity->mail_object->entity_string) { - return $this; - } - - $pdf = ((new CreateRawPdf($this->mail_entity->invitation, $this->mail_entity->invitation->company->db))->handle()); - - $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->{$this->mail_entity->mail_object->entity_string}->documents); - } - - return $this; - - - - - if ($this->settings->ubl_email_attachment && $this->mail_entity->mail_object->entity_string == 'invoice') { - } - - if ($this->mail_entity->mail_object->entity_string == 'invoice') { - $line_items = $this->mail_entity->invitation->invoice->line_items; - - foreach ($line_items as $item) { - $expense_ids = []; - - if (property_exists($item, 'expense_id')) { - $expense_ids[] = $item->expense_id; - } - - if (count($expense_ids) > 0) { - $expenses = Expense::whereIn('id', $this->transformKeys($expense_ids)) - ->where('invoice_documents', 1) - ->cursor() - ->each(function ($expense) { - $this->attachDocuments($expense->documents); - }); - } - - $task_ids = []; - - if (property_exists($item, 'task_id')) { - $task_ids[] = $item->task_id; - } - - if (count($task_ids) > 0 && $this->mail_entity->company->invoice_task_documents) { - $tasks = Task::whereIn('id', $this->transformKeys($task_ids)) - ->cursor() - ->each(function ($task) { - $this->attachDocuments($task->documents); - }); - } - } - } - - - return $this; - } - - - /** - * Sets the reply to of the email - * - * @return self - */ - private function setReplyTo(): self - { - $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->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)]); - - return $this; - } - - /** - * Replaces the template placeholders - * with variable values. - * - * @return self - */ - public function setVariables(): self - { - 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); - } - - $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); - } - - return $this; - } - - /** - * Sets the BCC of the email - * - * @return self - */ - private function setBcc(): self - { - $bccs = []; - $bcc_array = []; - - if (strlen($this->settings->bcc_email) > 1) { - if (Ninja::isHosted() && $this->mail_entity->company->account->isPaid()) { - $bccs = array_slice(explode(',', str_replace(' ', '', $this->settings->bcc_email)), 0, 2); - } elseif (Ninja::isSelfHost()) { - $bccs = (explode(',', str_replace(' ', '', $this->settings->bcc_email))); - } - } - - foreach ($bccs as $bcc) { - $bcc_array[] = new Address($bcc); - } - - $this->mail_entity->mail_object->bcc = array_merge($this->mail_entity->mail_object->bcc, $bcc_array); - - return $this; - } - - /** - * Sets the CC of the email - * @todo at some point.... - */ - private function buildCc() - { - return [ - - ]; - } - - - /** - * 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; - } - - /** - * Converts any markdown to HTML in the email - * - * @param string $markdown The body to convert - * @return string The parsed markdown response - */ - private function parseMarkdownToHtml(string $markdown): ?string - { - $converter = new CommonMarkConverter([ - 'allow_unsafe_links' => false, - ]); - - return $converter->convert($markdown); - } -} diff --git a/app/Services/Email/MailEntity.php b/app/Services/Email/MailEntity.php deleted file mode 100644 index 64629e57f523..000000000000 --- a/app/Services/Email/MailEntity.php +++ /dev/null @@ -1,589 +0,0 @@ -db); - - /* 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 */ - $builder->run($this); - - $this->mailable = $builder->getMailable(); - - /* Email quality checks */ - if ($this->preFlightChecksFail()) { - return; - } - - /* Try sending email */ - $this->setMailDriver() - ->configureMailer() - ->trySending(); - } - - /** - * configureMailer - * - * @return self - */ - public function configureMailer(): self - { - $this->mail = Mail::mailer($this->mailer); - - if ($this->client_postmark_secret) { - $this->mail->postmark_config($this->client_postmark_secret); - } - - if ($this->client_mailgun_secret) { - $this->mail->mailgun_config($this->client_mailgun_secret, $this->client_mailgun_domain); - } - - 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; - - app('mail.manager')->forgetMailers(); - } - - - /** - * Attempts to send the email - * - * @return void - */ - public function trySending(): void - { - try { - $this->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->mail_object->entity_id) { - // $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]); - } - } - - /** - * On the hosted platform we scan all outbound email for - * spam. This sequence processes the filters we use on all - * emails. - */ - 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()) { - return true; - } - - /* GMail users are uncapped */ - if (in_array($this->mail_object->settings->email_sending_method, ['gmail', 'office365', 'client_postmark', 'client_mailgun'])) { - return false; - } - - /* On the hosted platform, if the user is over the email quotas, we do not send the email. */ - if (Ninja::isHosted() && $this->company->account && $this->company->account->emailQuotaExceeded()) { - return true; - } - - /* If the account is verified, we allow emails to flow */ - 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->mail_object, $this->company))->run(); - - return false; - } - - - /* On the hosted platform if the user has not verified their account we fail here - but still check what they are trying to send! */ - if ($this->company->account && !$this->company->account->account_sms_verified) { - if (class_exists(\Modules\Admin\Jobs\Account\EmailFilter::class)) { - (new \Modules\Admin\Jobs\Account\EmailFilter($this->mail_object, $this->company))->run(); - } - - return true; - } - - /* On the hosted platform we actively scan all outbound emails to ensure outbound email quality remains high */ - if (class_exists(\Modules\Admin\Jobs\Account\EmailFilter::class)) { - (new \Modules\Admin\Jobs\Account\EmailFilter($this->mail_object, $this->company))->run(); - } - - return false; - } - - - /** - * Checks if emails are have some illegal / required characters. - * - * @return bool - */ - private function hasInValidEmails(): bool - { - if (Ninja::isSelfHost()) { - return false; - } - - foreach ($this->mail_object->to as $address_object) { - if (strpos($address_object->address, '@example.com') !== false) { - return true; - } - - if (!str_contains($address_object->address, "@")) { - return true; - } - - if ($address_object->address == " ") { - return true; - } - } - - - 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 - * - * @return void - */ - public function backoff() - { - return [5, 10, 30, 240]; - } - - /** - * Failed handler - * - * @param mixed $exception - * @return void - */ - public function failed($exception = null) - { - config(['queue.failed.driver' => null]); - } -} diff --git a/app/Services/Email/MailMailable.php b/app/Services/Email/MailMailable.php deleted file mode 100644 index 31fb9e3b238c..000000000000 --- a/app/Services/Email/MailMailable.php +++ /dev/null @@ -1,100 +0,0 @@ -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' => str_replace("
", "\n", 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, - ); - } -} diff --git a/app/Services/Email/MailObject.php b/app/Services/Email/MailObject.php deleted file mode 100644 index d42a71a12ccb..000000000000 --- a/app/Services/Email/MailObject.php +++ /dev/null @@ -1,89 +0,0 @@ -=8.40.0 || ^7.0", - "php": "^7.2 || ^8.0", - "swagger-api/swagger-ui": "^3.0", - "symfony/yaml": "^5.0", - "zircote/swagger-php": "3.*" - }, - "require-dev": { - "mockery/mockery": "1.*", - "orchestra/testbench": "6.* || 5.*", - "php-coveralls/php-coveralls": "^2.0", - "phpunit/phpunit": "9.*" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "L5Swagger\\L5SwaggerServiceProvider" - ], - "aliases": { - "L5Swagger": "L5Swagger\\L5SwaggerFacade" - } - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ], - "psr-4": { - "L5Swagger\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Darius Matulionis", - "email": "darius@matulionis.lt" - } - ], - "description": "OpenApi or Swagger integration to Laravel", - "keywords": [ - "api", - "documentation", - "laravel", - "openapi", - "specification", - "swagger", - "ui" - ], - "support": { - "issues": "https://github.com/DarkaOnLine/L5-Swagger/issues", - "source": "https://github.com/DarkaOnLine/L5-Swagger/tree/8.1.0" - }, - "funding": [ - { - "url": "https://github.com/DarkaOnLine", - "type": "github" - } - ], - "time": "2022-01-07T09:08:44+00:00" - }, { "name": "dnoegel/php-xdg-base-dir", "version": "v0.1.1", @@ -13717,30 +13637,30 @@ }, { "name": "doctrine/annotations", - "version": "1.14.3", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af" + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", - "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", "shasum": "" }, "require": { - "doctrine/lexer": "^1 || ^2", + "doctrine/lexer": "^2 || ^3", "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", + "php": "^7.2 || ^8.0", "psr/cache": "^1 || ^2 || ^3" }, "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.0", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6", + "symfony/cache": "^5.4 || ^6", "vimeo/psalm": "^4.10" }, "suggest": { @@ -13787,9 +13707,9 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.14.3" + "source": "https://github.com/doctrine/annotations/tree/2.0.1" }, - "time": "2023-02-01T09:20:38+00:00" + "time": "2023-02-02T22:02:53+00:00" }, { "name": "doctrine/instantiator", @@ -14096,27 +14016,27 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.14.2", + "version": "v3.14.4", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "14f0541651841b63640e7aafad041ad55dc7aa88" + "reference": "1b3d9dba63d93b8a202c31e824748218781eae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/14f0541651841b63640e7aafad041ad55dc7aa88", - "reference": "14f0541651841b63640e7aafad041ad55dc7aa88", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/1b3d9dba63d93b8a202c31e824748218781eae6b", + "reference": "1b3d9dba63d93b8a202c31e824748218781eae6b", "shasum": "" }, "require": { "composer/semver": "^3.3", "composer/xdebug-handler": "^3.0.3", - "doctrine/annotations": "^1.14.2 || ^2", - "doctrine/lexer": "^2", + "doctrine/annotations": "^2", + "doctrine/lexer": "^2 || ^3", "ext-json": "*", "ext-tokenizer": "*", "php": "^7.4 || ^8.0", - "sebastian/diff": "^4.0", + "sebastian/diff": "^4.0 || ^5.0", "symfony/console": "^5.4 || ^6.0", "symfony/event-dispatcher": "^5.4 || ^6.0", "symfony/filesystem": "^5.4 || ^6.0", @@ -14174,7 +14094,7 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.14.2" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.14.4" }, "funding": [ { @@ -14182,7 +14102,7 @@ "type": "github" } ], - "time": "2023-01-29T23:47:01+00:00" + "time": "2023-02-09T21:49:13+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -16836,67 +16756,6 @@ ], "time": "2023-01-03T19:28:04+00:00" }, - { - "name": "swagger-api/swagger-ui", - "version": "v3.52.5", - "source": { - "type": "git", - "url": "https://github.com/swagger-api/swagger-ui.git", - "reference": "f1ad60dc92e7edb0898583e16c3e66fe3e9eada2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/f1ad60dc92e7edb0898583e16c3e66fe3e9eada2", - "reference": "f1ad60dc92e7edb0898583e16c3e66fe3e9eada2", - "shasum": "" - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Anna Bodnia", - "email": "anna.bodnia@gmail.com" - }, - { - "name": "Buu Nguyen", - "email": "buunguyen@gmail.com" - }, - { - "name": "Josh Ponelat", - "email": "jponelat@gmail.com" - }, - { - "name": "Kyle Shockey", - "email": "kyleshockey1@gmail.com" - }, - { - "name": "Robert Barnwell", - "email": "robert@robertismy.name" - }, - { - "name": "Sahar Jafari", - "email": "shr.jafari@gmail.com" - } - ], - "description": " Swagger UI is a collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.", - "homepage": "http://swagger.io", - "keywords": [ - "api", - "documentation", - "openapi", - "specification", - "swagger", - "ui" - ], - "support": { - "issues": "https://github.com/swagger-api/swagger-ui/issues", - "source": "https://github.com/swagger-api/swagger-ui/tree/v3.52.5" - }, - "time": "2021-10-14T14:25:14+00:00" - }, { "name": "symfony/polyfill-php81", "version": "v1.27.0", @@ -17038,81 +16897,6 @@ ], "time": "2023-02-14T08:44:56+00:00" }, - { - "name": "symfony/yaml", - "version": "v5.4.21", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "3713e20d93e46e681e51605d213027e48dab3469" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/3713e20d93e46e681e51605d213027e48dab3469", - "reference": "3713e20d93e46e681e51605d213027e48dab3469", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<5.3" - }, - "require-dev": { - "symfony/console": "^5.3|^6.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.21" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-02-21T19:46:44+00:00" - }, { "name": "theseer/tokenizer", "version": "1.2.1", @@ -17321,79 +17105,6 @@ }, "abandoned": "symfony/filesystem", "time": "2015-12-17T08:42:14+00:00" - }, - { - "name": "zircote/swagger-php", - "version": "3.3.7", - "source": { - "type": "git", - "url": "https://github.com/zircote/swagger-php.git", - "reference": "e8c3bb316e385e93a0c7e8b2aa0681079244c381" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/e8c3bb316e385e93a0c7e8b2aa0681079244c381", - "reference": "e8c3bb316e385e93a0c7e8b2aa0681079244c381", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^1.7", - "ext-json": "*", - "php": ">=7.2", - "psr/log": "^1.1 || ^2.0 || ^3.0", - "symfony/finder": ">=2.2", - "symfony/yaml": ">=3.3" - }, - "require-dev": { - "composer/package-versions-deprecated": "1.11.99.2", - "friendsofphp/php-cs-fixer": "^2.17 || ^3.0", - "phpunit/phpunit": ">=8.5.14" - }, - "bin": [ - "bin/openapi" - ], - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "OpenApi\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Robert Allen", - "email": "zircote@gmail.com" - }, - { - "name": "Bob Fanger", - "email": "bfanger@gmail.com", - "homepage": "https://bfanger.nl" - }, - { - "name": "Martin Rademacher", - "email": "mano@radebatz.net", - "homepage": "https://radebatz.net" - } - ], - "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", - "homepage": "https://github.com/zircote/swagger-php/", - "keywords": [ - "api", - "json", - "rest", - "service discovery" - ], - "support": { - "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/3.3.7" - }, - "time": "2023-01-03T21:17:10+00:00" } ], "aliases": [], diff --git a/config/liap.php b/config/liap.php index 1eaa57a8aa04..b7f91958ab8e 100644 --- a/config/liap.php +++ b/config/liap.php @@ -39,7 +39,7 @@ return [ | @see https://imdhemy.com/laravel-iap-docs/docs/credentials/google-play | */ - 'google_application_credentials' => env('GOOGLE_APPLICATION_CREDENTIALS',false), + 'google_application_credentials' => env('GOOGLE_APPLICATION_CREDENTIALS', base_path('VERSION.txt')), /* |-------------------------------------------------------------------------- diff --git a/config/ninja.php b/config/ninja.php index 9b7cb534c74b..937a6fb60062 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -213,5 +213,4 @@ return [ 'config_name' => env("YODLEE_CONFIG_NAME", false), ], 'licenses' => env('LICENSES',false), - 'google_service_account' => env('GOOGLE_APPLICATION_CREDENTIALS', false), ];