From 7f951b94efd4973e6bc7b7e0e92dabeed408d381 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 30 Apr 2023 20:25:47 +1000 Subject: [PATCH] remove redundant classses --- app/Services/Client/ClientService.php | 4 - app/Services/Email/EmailDefaults.php | 2 - app/Services/Email/EmailMailer.php | 502 ----------------------- app/Services/Email/EmailService.php | 163 -------- tests/Feature/Email/EmailServiceTest.php | 164 -------- 5 files changed, 835 deletions(-) delete mode 100644 app/Services/Email/EmailMailer.php delete mode 100644 app/Services/Email/EmailService.php delete mode 100644 tests/Feature/Email/EmailServiceTest.php diff --git a/app/Services/Client/ClientService.php b/app/Services/Client/ClientService.php index b8a5a8a6279c..5167c4af0938 100644 --- a/app/Services/Client/ClientService.php +++ b/app/Services/Client/ClientService.php @@ -16,7 +16,6 @@ use App\Models\Credit; use App\Models\Payment; use App\Services\Email\Email; use App\Services\Email\EmailObject; -use App\Services\Email\EmailService; use App\Utils\Number; use App\Utils\Traits\MakesDates; use Illuminate\Mail\Mailables\Address; @@ -167,9 +166,6 @@ class ClientService { $this->client_start_date = $this->translateDate($options['start_date'], $this->client->date_format(), $this->client->locale()); $this->client_end_date = $this->translateDate($options['end_date'], $this->client->date_format(), $this->client->locale()); - - // $email_service = new EmailService($this->buildStatementMailableData($pdf), $this->client->company); - // $email_service->send(); $email_object = $this->buildStatementMailableData($pdf); Email::dispatch($email_object, $this->client->company); diff --git a/app/Services/Email/EmailDefaults.php b/app/Services/Email/EmailDefaults.php index a1b8816b8066..79f1612915bf 100644 --- a/app/Services/Email/EmailDefaults.php +++ b/app/Services/Email/EmailDefaults.php @@ -23,13 +23,11 @@ use App\Jobs\Invoice\CreateUbl; use App\Utils\Traits\MakesHash; use App\Jobs\Entity\CreateRawPdf; use Illuminate\Support\Facades\App; -use App\Jobs\Invoice\CreateEInvoice; use Illuminate\Mail\Mailables\Address; use Illuminate\Support\Facades\Storage; use App\DataMapper\EmailTemplateDefaults; use League\CommonMark\CommonMarkConverter; use App\Jobs\Vendor\CreatePurchaseOrderPdf; -use App\Services\Invoice\GetInvoiceXInvoice; class EmailDefaults { diff --git a/app/Services/Email/EmailMailer.php b/app/Services/Email/EmailMailer.php deleted file mode 100644 index a3e52bc49aed..000000000000 --- a/app/Services/Email/EmailMailer.php +++ /dev/null @@ -1,502 +0,0 @@ -email_service->company->db); - - /* Perform final checks */ - if ($this->email_service->preFlightChecksFail()) { - return; - } - - /* Boot the required driver*/ - $this->setMailDriver(); - - /* Init the mailer*/ - $mailer = Mail::mailer($this->mailer); - - /* Additional configuration if using a client third party mailer */ - if ($this->client_postmark_secret) { - $mailer->postmark_config($this->client_postmark_secret); - } - - if ($this->client_mailgun_secret) { - $mailer->mailgun_config($this->client_mailgun_secret, $this->client_mailgun_domain); - } - - /* Attempt the send! */ - try { - nlog("Using mailer => ". $this->mailer. " ". now()->toDateTimeString()); - - $mailer->send($this->email_mailable); - - Cache::increment("email_quota".$this->email_service->company->account->key); - - LightLogs::create(new EmailSuccess($this->email_service->company->company_key)) - ->send(); - } catch (\Exception | \RuntimeException | \Google\Service\Exception $e) { - nlog("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 ($e instanceof ClientException) { //postmark specific failure - $response = $e->getResponse(); - $message_body = json_decode($response->getBody()->getContents()); - - if ($message_body && property_exists($message_body, 'Message')) { - $message = $message_body->Message; - nlog($message); - } - - $this->fail(); - $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 */ - $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 */ - sleep(rand(0, 3)); - - $this->release($this->backoff()[$this->attempts()-1]); - - $message = null; - } - - $this->cleanUpMailers(); - } - - /** - * Entity notification when an email fails to send - * - * @todo - rewrite this - * @param string $message - * @return void - */ - private function entityEmailFailed($message) - { - if (!$this->email_service->email_object->entity_id) { - return; - } - - switch ($this->email_service->email_object->entity_class) { - case Invoice::class: - $invitation = InvoiceInvitation::withTrashed()->find($this->email_service->email_object->entity_id); - if ($invitation) { - event(new InvoiceWasEmailedAndFailed($invitation, $this->email_service->company, $message, $this->email_service->email_object->reminder_template, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); - } - break; - case Payment::class: - $payment = Payment::withTrashed()->find($this->email_service->email_object->entity_id); - if ($payment) { - event(new PaymentWasEmailedAndFailed($payment, $this->email_service->company, $message, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); - } - break; - default: - # code... - break; - } - - if ($this->email_service->email_object->client_contact instanceof ClientContact) { - $this->logMailError($message, $this->email_service->email_object->client_contact); - } - } - - /** - * Sets the mail driver to use and applies any specific configuration - * the the mailable - */ - private function setMailDriver(): self - { - switch ($this->email_service->email_object->settings->email_sending_method) { - case 'default': - $this->mailer = config('mail.default'); - break; - case 'gmail': - $this->mailer = 'gmail'; - $this->setGmailMailer(); - return $this; - case 'office365': - $this->mailer = 'office365'; - $this->setOfficeMailer(); - return $this; - case 'client_postmark': - $this->mailer = 'postmark'; - $this->setPostmarkMailer(); - return $this; - case 'client_mailgun': - $this->mailer = 'mailgun'; - $this->setMailgunMailer(); - return $this; - - default: - break; - } - - if (Ninja::isSelfHost()) { - $this->setSelfHostMultiMailer(); - } - - return $this; - } - - /** - * Allows configuration of multiple mailers - * per company for use by self hosted users - */ - private function setSelfHostMultiMailer(): void - { - if (env($this->email_service->company->id . '_MAIL_HOST')) { - config([ - 'mail.mailers.smtp' => [ - 'transport' => 'smtp', - 'host' => env($this->email_service->company->id . '_MAIL_HOST'), - 'port' => env($this->email_service->company->id . '_MAIL_PORT'), - 'username' => env($this->email_service->company->id . '_MAIL_USERNAME'), - 'password' => env($this->email_service->company->id . '_MAIL_PASSWORD'), - ], - ]); - - if (env($this->email_service->company->id . '_MAIL_FROM_ADDRESS')) { - $this->email_mailable - ->from(env($this->email_service->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->email_service->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME'))); - } - } - } - - - /** - * Ensure we discard any data that is not required - * - * @return void - */ - private function cleanUpMailers(): void - { - $this->client_postmark_secret = false; - - $this->client_mailgun_secret = false; - - $this->client_mailgun_domain = false; - - //always dump the drivers to prevent reuse - app('mail.manager')->forgetMailers(); - } - - /** - * Check to ensure no cross account - * emails can be sent. - * - * @param User $user - */ - private function checkValidSendingUser($user) - { - /* Always ensure the user is set on the correct account */ - if ($user->account_id != $this->email_service->company->account_id) { - $this->email_service->email_object->settings->email_sending_method = 'default'; - - return $this->setMailDriver(); - } - } - - /** - * Resolves the sending user - * when configuring the Mailer - * on behalf of the client - * - * @return User $user - */ - private function resolveSendingUser(): ?User - { - $sending_user = $this->email_service->email_object->settings->gmail_sending_user_id; - - if ($sending_user == "0") { - $user = $this->email_service->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->email_service->email_object->settings->mailgun_secret) > 2 && strlen($this->email_service->email_object->settings->mailgun_domain) > 2) { - $this->client_mailgun_secret = $this->email_service->email_object->settings->mailgun_secret; - $this->client_mailgun_domain = $this->email_service->email_object->settings->mailgun_domain; - } else { - $this->email_service->email_object->settings->email_sending_method = 'default'; - return $this->setMailDriver(); - } - - $user = $this->resolveSendingUser(); - - $sending_email = (isset($this->email_service->email_object->settings->custom_sending_email) && stripos($this->email_service->email_object->settings->custom_sending_email, "@")) ? $this->email_service->email_object->settings->custom_sending_email : $user->email; - $sending_user = (isset($this->email_service->email_object->settings->email_from_name) && strlen($this->email_service->email_object->settings->email_from_name) > 2) ? $this->email_service->email_object->settings->email_from_name : $user->name(); - - $this->email_mailable - ->from($sending_email, $sending_user); - } - - /** - * Configures Postmark using client supplied secret - * as the Mailer - */ - private function setPostmarkMailer() - { - if (strlen($this->email_service->email_object->settings->postmark_secret) > 2) { - $this->client_postmark_secret = $this->email_service->email_object->settings->postmark_secret; - } else { - $this->email_service->email_object->settings->email_sending_method = 'default'; - return $this->setMailDriver(); - } - - $user = $this->resolveSendingUser(); - - $sending_email = (isset($this->email_service->email_object->settings->custom_sending_email) && stripos($this->email_service->email_object->settings->custom_sending_email, "@")) ? $this->email_service->email_object->settings->custom_sending_email : $user->email; - $sending_user = (isset($this->email_service->email_object->settings->email_from_name) && strlen($this->email_service->email_object->settings->email_from_name) > 2) ? $this->email_service->email_object->settings->email_from_name : $user->name(); - - $this->email_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->email_service->email_object->settings->email_sending_method = 'default'; - return $this->setMailDriver(); - } - - $this->email_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->email_service->company->clients()->first()); - $this->email_service->email_object->settings->email_sending_method = 'default'; - return $this->setMailDriver(); - } - - /** - * If the user doesn't have a valid token, notify them - */ - - if (!$user->oauth_user_token) { - $this->email_service->company->account->gmailCredentialNotification(); - $this->email_service->email_object->settings->email_sending_method = 'default'; - return $this->setMailDriver(); - } - - /* - * Now that our token is refreshed and valid we can boot the - * mail driver at runtime and also set the token which will persist - * just for this request. - */ - - $token = $user->oauth_user_token->access_token; - - if (!$token) { - $this->email_service->company->account->gmailCredentialNotification(); - $this->email_service->email_object->settings->email_sending_method = 'default'; - return $this->setMailDriver(); - } - - $this->email_mailable - ->from($user->email, $user->name()) - ->withSymfonyMessage(function ($message) use ($token) { - $message->getHeaders()->addTextHeader('gmailtoken', $token); - }); - } - - /** - * Logs any errors to the SystemLog - * - * @param string $errors - * @param App\Models\User | App\Models\Client $recipient_object - * @return void - */ - private function logMailError($errors, $recipient_object) :void - { - (new SystemLogger( - $errors, - SystemLog::CATEGORY_MAIL, - SystemLog::EVENT_MAIL_SEND, - SystemLog::TYPE_FAILURE, - $recipient_object, - $this->email_service->company - ))->handle(); - - $job_failure = new EmailFailure($this->email_service->company->company_key); - $job_failure->string_metric5 = 'failed_email'; - $job_failure->string_metric6 = substr($errors, 0, 150); - - LightLogs::create($job_failure) - ->send(); - - $job_failure = null; - } - - /** - * Attempts to refresh the Microsoft refreshToken - * - * @param App\Models\User - * @return mixed - */ - private function refreshOfficeToken(User $user): mixed - { - $expiry = $user->oauth_user_token_expiry ?: now()->subDay(); - - if ($expiry->lt(now())) { - $guzzle = new \GuzzleHttp\Client(); - $url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token'; - - $token = json_decode($guzzle->post($url, [ - 'form_params' => [ - 'client_id' => config('ninja.o365.client_id') , - 'client_secret' => config('ninja.o365.client_secret') , - 'scope' => 'email Mail.Send offline_access profile User.Read openid', - 'grant_type' => 'refresh_token', - 'refresh_token' => $user->oauth_user_refresh_token - ], - ])->getBody()->getContents()); - - if ($token) { - $user->oauth_user_refresh_token = property_exists($token, 'refresh_token') ? $token->refresh_token : $user->oauth_user_refresh_token; - $user->oauth_user_token = $token->access_token; - $user->oauth_user_token_expiry = now()->addSeconds($token->expires_in); - $user->save(); - - return $token->access_token; - } - - return false; - } - - return $user->oauth_user_token; - } - - public function failed($exception = null) - { - } -} diff --git a/app/Services/Email/EmailService.php b/app/Services/Email/EmailService.php deleted file mode 100644 index 731815deaefc..000000000000 --- a/app/Services/Email/EmailService.php +++ /dev/null @@ -1,163 +0,0 @@ -override = $override; - - $this->setDefaults() - ->updateMailable() - ->email(); - } - - public function sendNow($force = false) :void - { - $this->setDefaults() - ->updateMailable() - ->email($force); - } - - private function email($force = false): void - { - if ($force) { - (new EmailMailer($this, $this->mailable))->handle(); - } else { - EmailMailer::dispatch($this, $this->mailable)->delay(2); - } - } - - private function setDefaults(): self - { - $defaults = new EmailDefaults($this, $this->email_object); - $defaults->run(); - - return $this; - } - - private function updateMailable() - { - $this->mailable = new EmailMailable($this->email_object); - - return $this; - } - - /** - * On the hosted platform we scan all outbound email for - * spam. This sequence processes the filters we use on all - * emails. - * - * @return bool - */ - public function preFlightChecksFail(): bool - { - /* If we are migrating data we don't want to fire any emails */ - if ($this->company->is_disabled && !$this->override) { - return true; - } - - if (Ninja::isSelfHost()) { - return false; - } - - /* To handle spam users we drop all emails from flagged accounts */ - if ($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->email_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 ($this->company->account && $this->company->account->emailQuotaExceeded()) { - return true; - } - - /* If the account is verified, we allow emails to flow */ - if ($this->company->account && $this->company->account->is_verified_account) { - //11-01-2022 - - /* Continue to analyse verified accounts in case they later start sending poor quality emails*/ - // if(class_exists(\Modules\Admin\Jobs\Account\EmailQuality::class)) - // (new \Modules\Admin\Jobs\Account\EmailQuality($this->nmo, $this->company))->run(); - - 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)) { - return (new \Modules\Admin\Jobs\Account\EmailFilter($this->email_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)) { - return (new \Modules\Admin\Jobs\Account\EmailFilter($this->email_object, $this->company))->run(); - } - - return false; - } - - private function hasInValidEmails(): bool - { - foreach ($this->email_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; - } -} diff --git a/tests/Feature/Email/EmailServiceTest.php b/tests/Feature/Email/EmailServiceTest.php deleted file mode 100644 index 17efec114d43..000000000000 --- a/tests/Feature/Email/EmailServiceTest.php +++ /dev/null @@ -1,164 +0,0 @@ -markTestSkipped('Skipped :: test not needed in this environment'); - } - - $this->makeTestData(); - - $this->email_object = new EmailObject(); - $this->email_object->to = [new Address("testing@gmail.com", "Cool Name")]; - $this->email_object->attachments = []; - $this->email_object->settings = $this->client->getMergedSettings(); - $this->email_object->company = $this->client->company; - $this->email_object->client = $this->client; - $this->email_object->email_template_subject = 'email_subject_statement'; - $this->email_object->email_template_body = 'email_template_statement'; - $this->email_object->variables = [ - '$client' => $this->client->present()->name(), - '$start_date' => '2022-01-01', - '$end_date' => '2023-01-01', - ]; - - $this->email_service = new EmailService($this->email_object, $this->company); - } - - public function testScanEmailsAttemptedFromVerifiedAccounts() - { - $email_filter = new \Modules\Admin\Jobs\Account\EmailFilter($this->email_object, $this->client->company); - - Cache::put($this->account->key, 1); - - config(['ninja.environment' => 'hosted']); - - $this->account->account_sms_verified = true; - $this->account->is_verified_account = false; - $this->account->save(); - - $this->assertFalse($this->email_service->preFlightChecksFail()); - - collect($email_filter->getSpamKeywords())->each(function ($spam_subject) { - $this->email_object->subject = $spam_subject; - - $this->assertTrue($this->email_service->preFlightChecksFail()); - }); - } - - - - public function scanEmailsAttemptedFromUnverifiedAccounts() - { - config(['ninja.environment' => 'hosted']); - - Cache::put($this->account->key, 1); - - $this->account->account_sms_verified = false; - $this->account->save(); - - $this->assertTrue($this->email_service->preFlightChecksFail()); - } - - - public function testVerifiedAccountsSkipFilters() - { - config(['ninja.environment' => 'hosted']); - - Cache::put($this->account->key, 1); - - $this->account->is_verified_account = true; - $this->account->save(); - - $this->assertFalse($this->email_service->preFlightChecksFail()); - } - - public function testFlaggedInvalidEmailsPrevented() - { - config(['ninja.environment' => 'hosted']); - - Cache::put($this->account->key, 1); - - $this->email_object->to = [new Address("user@example.com", "Cool Name")]; - - $this->assertTrue($this->email_service->preFlightChecksFail()); - - - collect([ - 'user@example.com', - '', - 'bademail', - 'domain.com', - ])->each(function ($email) { - $this->email_object->to = [new Address($email, "Cool Name")]; - - $this->assertTrue($this->email_service->preFlightChecksFail()); - }); - } - - public function testFlaggedAccountsPrevented() - { - Cache::put($this->account->key, 1); - - config(['ninja.environment' => 'hosted']); - - $this->account->is_flagged = true; - $this->account->save(); - - $this->assertTrue($this->email_service->preFlightChecksFail()); - } - - public function testPreFlightChecksHosted() - { - Cache::put($this->account->key, 1); - - config(['ninja.environment' => 'hosted']); - - $this->assertFalse($this->email_service->preFlightChecksFail()); - } - - public function testPreFlightChecksSelfHost() - { - Cache::put($this->account->key, 1); - - config(['ninja.environment' => 'selfhost']); - - $this->assertFalse($this->email_service->preFlightChecksFail()); - } -}