diff --git a/app/Events/Email/InvalidEmailDetected.php b/app/Events/Email/InvalidEmailDetected.php new file mode 100644 index 000000000..d8ba13657 --- /dev/null +++ b/app/Events/Email/InvalidEmailDetected.php @@ -0,0 +1,51 @@ +email = $email; + + $this->error = $error; + + $this->setContact(); + + $this->setUser(); + } + + public function setContact() + { + $contact = Contact::email($this->email)->enabled()->first(); + + if (empty($contact)) { + return; + } + + $this->contact = $contact; + } + + public function setUser() + { + $user = User::email($this->email)->enabled()->first(); + + if (empty($user)) { + return; + } + + $this->user = $user; + } +} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 7878ebe75..0bb396a0b 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -3,6 +3,7 @@ namespace App\Exceptions; use Akaunting\Money\Exceptions\UnexpectedAmountException; +use App\Events\Email\InvalidEmailDetected; use App\Exceptions\Http\Resource as ResourceException; use Illuminate\Auth\AuthenticationException; use Illuminate\Database\Eloquent\ModelNotFoundException; @@ -15,6 +16,7 @@ use Illuminate\View\ViewException; use Symfony\Component\Debug\Exception\FatalThrowableError; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Mailer\Exception\HttpTransportException as MailerHttpTransportException; use Throwable; class Handler extends ExceptionHandler @@ -194,6 +196,21 @@ class Handler extends ExceptionHandler } } + if ($exception instanceof MailerHttpTransportException) { + /** + * Couldn't access the SentMessage object to get the email address + * https://symfony.com/doc/current/mailer.html#debugging-emails + * + * https://codespeedy.com/extract-email-addresses-from-a-string-in-php + * https://phpliveregex.com/p/IMG + */ + preg_match("/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}/", $exception->getMessage(), $matches); + + if (! empty($matches[0])) { + event(new InvalidEmailDetected($matches[0], $exception->getMessage())); + } + } + return parent::render($request, $exception); } diff --git a/app/Listeners/Email/DisablePersonDueToInvalidEmail.php b/app/Listeners/Email/DisablePersonDueToInvalidEmail.php new file mode 100644 index 000000000..de20c3a82 --- /dev/null +++ b/app/Listeners/Email/DisablePersonDueToInvalidEmail.php @@ -0,0 +1,35 @@ +disableContact($event); + + $this->disableUser($event); + } + + public function disableContact(Event $event): void + { + if (empty($event->contact)) { + return; + } + + $event->contact->enabled = false; + $event->contact->save(); + } + + public function disableUser(Event $event): void + { + if (empty($event->user)) { + return; + } + + $event->user->enabled = false; + $event->user->save(); + } +} diff --git a/app/Listeners/Email/SendInvalidEmailNotification.php b/app/Listeners/Email/SendInvalidEmailNotification.php new file mode 100644 index 000000000..d5812fce6 --- /dev/null +++ b/app/Listeners/Email/SendInvalidEmailNotification.php @@ -0,0 +1,61 @@ +users; + + $this->notifyAdminsAboutInvalidContactEmail($event, $users); + + $this->notifyAdminsAboutInvalidUserEmail($event, $users); + } + + public function notifyAdminsAboutInvalidContactEmail(Event $event, $users): void + { + if (empty($event->contact)) { + return; + } + + if ($event->contact->isCustomer() || $event->contact->isVendor() || $event->contact->isEmployee()) { + $type = trans('general.' . Str::plural($event->contact->type), 1); + } else { + $type = ucfirst($event->contact->type); + } + + foreach ($users as $user) { + if ($user->cannot('read-notifications')) { + continue; + } + + $user->notify(new InvalidEmail($event->email, $type, $event->error)); + } + } + + public function notifyAdminsAboutInvalidUserEmail(Event $event, $users): void + { + if (empty($event->user)) { + return; + } + + $type = trans('general.users', 1); + + foreach ($users as $user) { + if ($user->cannot('read-notifications')) { + continue; + } + + if ($user->email == $event->email) { + continue; + } + + $user->notify(new InvalidEmail($event->email, $type, $event->error)); + } + } +} diff --git a/app/Models/Auth/User.php b/app/Models/Auth/User.php index 0ca68e85d..b1b679489 100644 --- a/app/Models/Auth/User.php +++ b/app/Models/Auth/User.php @@ -244,6 +244,11 @@ class User extends Authenticatable implements HasLocalePreference return $query->wherePermissionIs('read-admin-panel'); } + public function scopeEmail($query, $email) + { + return $query->where('email', '=', $email); + } + /** * Attach company_ids attribute to model. * diff --git a/app/Notifications/Email/InvalidEmail.php b/app/Notifications/Email/InvalidEmail.php new file mode 100644 index 000000000..f458c4f15 --- /dev/null +++ b/app/Notifications/Email/InvalidEmail.php @@ -0,0 +1,76 @@ +email = $email; + + $this->type = $type; + + $this->error = $error; + + $this->onQueue('notifications'); + } + + /** + * Get the notification's channels. + * + * @param mixed $notifiable + * @return array|string + */ + public function via($notifiable) + { + return ['mail', 'database']; + } + + /** + * Build the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + $dashboard_url = route('dashboard', ['company_id' => company_id()]); + + return (new MailMessage) + ->subject(trans('notifications.email.invalid.title', ['type' => $this->type])) + ->line(trans('notifications.email.invalid.description', ['email' => $this->email])) + ->line($this->error) + ->action(trans_choice('general.dashboards', 1), $dashboard_url); + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + 'title' => trans('notifications.menu.invalid_email.title', ['type' => $this->type]), + 'description' => trans('notifications.menu.invalid_email.description', ['email' => $this->email]), + 'email' => $this->email, + ]; + } +} diff --git a/app/Providers/Event.php b/app/Providers/Event.php index 1f3a6b808..1049d0268 100644 --- a/app/Providers/Event.php +++ b/app/Providers/Event.php @@ -108,6 +108,10 @@ class Event extends Provider 'App\Listeners\Email\ReportTooManyEmailsSent', 'App\Listeners\Email\TellFirewallTooManyEmailsSent', ], + 'App\Events\Email\InvalidEmailDetected' => [ + 'App\Listeners\Email\DisablePersonDueToInvalidEmail', + 'App\Listeners\Email\SendInvalidEmailNotification', + ], ]; /** diff --git a/app/Traits/Contacts.php b/app/Traits/Contacts.php index a529b6b6b..68bdf90fb 100644 --- a/app/Traits/Contacts.php +++ b/app/Traits/Contacts.php @@ -18,6 +18,13 @@ trait Contacts return in_array($type, $this->getVendorTypes()); } + public function isEmployee() + { + $type = $this->type ?? $this->contact->type ?? $this->model->type ?? 'employee'; + + return in_array($type, $this->getEmployeeTypes()); + } + public function getCustomerTypes($return = 'array') { return $this->getContactTypes('customer', $return); @@ -28,6 +35,11 @@ trait Contacts return $this->getContactTypes('vendor', $return); } + public function getEmployeeTypes($return = 'array') + { + return $this->getContactTypes('employee', $return); + } + public function getContactTypes($index, $return = 'array') { $types = (string) setting('contact.type.' . $index); @@ -45,6 +57,11 @@ trait Contacts $this->addContactType($new_type, 'vendor'); } + public function addEmployeeType($new_type) + { + $this->addContactType($new_type, 'employee'); + } + public function addContactType($new_type, $index) { $types = explode(',', setting('contact.type.' . $index)); diff --git a/config/setting.php b/config/setting.php index 2741bcdaf..79267ff23 100644 --- a/config/setting.php +++ b/config/setting.php @@ -171,6 +171,7 @@ return [ 'type' => [ 'customer' => env('SETTING_FALLBACK_CONTACT_TYPE_CUSTOMER', Contact::CUSTOMER_TYPE), 'vendor' => env('SETTING_FALLBACK_CONTACT_TYPE_VENDOR', Contact::VENDOR_TYPE), + 'employee' => env('SETTING_FALLBACK_CONTACT_TYPE_EMPLOYEE', Contact::EMPLOYEE_TYPE), ], ], 'transaction' => [ diff --git a/resources/lang/en-GB/general.php b/resources/lang/en-GB/general.php index 849f76b98..373f92d87 100644 --- a/resources/lang/en-GB/general.php +++ b/resources/lang/en-GB/general.php @@ -72,6 +72,7 @@ return [ 'attachments' => 'Attachment|Attachments', 'histories' => 'History|Histories', 'your_notifications' => 'Your notification|Your notifications', + 'employees' => 'Employee|Employees', 'welcome' => 'Welcome', 'banking' => 'Banking', diff --git a/resources/lang/en-GB/notifications.php b/resources/lang/en-GB/notifications.php index 79cac3a8d..b9013cdad 100644 --- a/resources/lang/en-GB/notifications.php +++ b/resources/lang/en-GB/notifications.php @@ -63,6 +63,17 @@ return [ ], + 'email' => [ + + 'invalid' => [ + + 'title' => 'Invalid :type Email', + 'description' => ':email email address has been reported as invalid and the person has been disabled. Please check the following error message and fix the email address:', + + ], + + ], + 'menu' => [ 'export_completed' => [ @@ -177,6 +188,13 @@ return [ ], + 'invalid_email' => [ + + 'title' => 'Invalid :type Email', + 'description' => ':email email address has been reported as invalid and the person has been disabled. Please check and fix the email address.', + + ], + ], 'messages' => [