From 78c8658f499d1349753e51b9bc31b8704c20b9b0 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Fri, 11 Jul 2025 14:02:54 +0200 Subject: [PATCH 01/12] feat(emails): implement send email functionality --- resources/lang/en/email.php | 30 ++++ resources/lang/hr/email.php | 30 ++++ resources/lang/sl/email.php | 30 ++++ resources/lang/sr/email.php | 30 ++++ .../filament/pages/send-email-user.blade.php | 9 + .../views/mail/send-email-to-user.blade.php | 97 ++++++++++ src/Filament/Actions/SendEmailAction.php | 20 +++ src/Filament/Actions/SendEmailTableAction.php | 21 +++ src/Filament/Resources/UserResource.php | 3 + .../Resources/UserResource/Pages/ViewUser.php | 2 + src/Filament/Traits/SendEmailActionTrait.php | 147 ++++++++++++++++ src/Mail/SendEmailToUser.php | 83 +++++++++ src/Policies/UserPolicy.php | 8 + tests/Feature/UserEmailTest.php | 165 ++++++++++++++++++ 14 files changed, 675 insertions(+) create mode 100644 resources/lang/en/email.php create mode 100644 resources/lang/hr/email.php create mode 100644 resources/lang/sl/email.php create mode 100644 resources/lang/sr/email.php create mode 100644 resources/views/filament/pages/send-email-user.blade.php create mode 100644 resources/views/mail/send-email-to-user.blade.php create mode 100644 src/Filament/Actions/SendEmailAction.php create mode 100644 src/Filament/Actions/SendEmailTableAction.php create mode 100644 src/Filament/Traits/SendEmailActionTrait.php create mode 100644 src/Mail/SendEmailToUser.php create mode 100644 tests/Feature/UserEmailTest.php diff --git a/resources/lang/en/email.php b/resources/lang/en/email.php new file mode 100644 index 0000000..e2dab40 --- /dev/null +++ b/resources/lang/en/email.php @@ -0,0 +1,30 @@ + 'Send Email', + 'send_email_to' => 'Send Email to :name', + 'sender' => 'Sender', + 'recipient' => 'Recipient', + 'cc' => 'CC', + 'cc_help' => 'Additional recipients', + 'bcc' => 'BCC', + 'bcc_help' => 'Hidden recipients', + 'subject' => 'Subject', + 'subject_placeholder' => 'Enter email subject', + 'message' => 'Message', + 'message_placeholder' => 'Enter message content...', + 'send' => 'Send', + 'cancel' => 'Cancel', + 'email_sent' => 'Email sent', + 'email_sent_to' => 'Email to :email has been successfully sent.', + 'permission_denied' => 'Permission denied', + 'permission_denied_message' => 'You do not have permission to send emails.', + 'error' => 'Error', + 'send_error_message' => 'An error occurred while sending the email: :error', + 'email_form_title' => 'Send Email', + 'email_form_description' => 'Send an email to the selected user', + 'your_email' => 'Your email address', + 'recipient_email' => 'Recipient email address', + 'invalid_cc_email' => 'One or more CC email addresses are invalid.', + 'invalid_bcc_email' => 'One or more BCC email addresses are invalid.', +]; diff --git a/resources/lang/hr/email.php b/resources/lang/hr/email.php new file mode 100644 index 0000000..cf01042 --- /dev/null +++ b/resources/lang/hr/email.php @@ -0,0 +1,30 @@ + 'Pošaljite e-mail', + 'send_email_to' => 'Pošaljite e-mail korisniku :name', + 'sender' => 'Pošaljitelj', + 'recipient' => 'Primatelj', + 'cc' => 'CC', + 'cc_help' => 'Dodatni primatelji', + 'bcc' => 'BCC', + 'bcc_help' => 'Skriveni primatelji', + 'subject' => 'Predmet', + 'subject_placeholder' => 'Unesite predmet e-maila', + 'message' => 'Poruka', + 'message_placeholder' => 'Unesite sadržaj poruke...', + 'send' => 'Pošaljite', + 'cancel' => 'Otkaži', + 'email_sent' => 'E-mail poslan', + 'email_sent_to' => 'E-mail poslan na :email je uspješno poslan.', + 'permission_denied' => 'Dozvola odbijena', + 'permission_denied_message' => 'Nemate dozvolu za slanje e-mailova.', + 'error' => 'Greška', + 'send_error_message' => 'Došlo je do greške prilikom slanja e-maila: :error', + 'email_form_title' => 'Pošaljite e-mail', + 'email_form_description' => 'Pošaljite e-mail odabranom korisniku', + 'your_email' => 'Vaša e-mail adresa', + 'recipient_email' => 'E-mail adresa primatelja', + 'invalid_cc_email' => 'Jedna ili više CC e-mail adresa nije valjana.', + 'invalid_bcc_email' => 'Jedna ili više BCC e-mail adresa nije valjana.', +]; diff --git a/resources/lang/sl/email.php b/resources/lang/sl/email.php new file mode 100644 index 0000000..b625f6d --- /dev/null +++ b/resources/lang/sl/email.php @@ -0,0 +1,30 @@ + 'Pošlji e-pošto', + 'send_email_to' => 'Pošlji e-pošto uporabniku :name', + 'sender' => 'Pošiljatelj', + 'recipient' => 'Prejemnik', + 'cc' => 'Kp (CC)', + 'cc_help' => 'Dodatni prejemniki', + 'bcc' => 'Skp (BCC)', + 'bcc_help' => 'Skriti prejemniki', + 'subject' => 'Zadeva', + 'subject_placeholder' => 'Vnesite zadevo sporočila', + 'message' => 'Sporočilo', + 'message_placeholder' => 'Vnesite vsebino sporočila...', + 'send' => 'Pošlji', + 'cancel' => 'Prekliči', + 'email_sent' => 'E-pošta poslana', + 'email_sent_to' => 'E-pošta je bila uspešno poslana na :email.', + 'permission_denied' => 'Dostop zavrnjen', + 'permission_denied_message' => 'Nimate dovoljenja za pošiljanje e-pošte.', + 'error' => 'Napaka', + 'send_error_message' => 'Prišlo je do napake pri pošiljanju e-pošte: :error', + 'email_form_title' => 'Pošiljanje e-pošte', + 'email_form_description' => 'Pošljite e-pošto izbranemu uporabniku', + 'your_email' => 'Vaš e-poštni naslov', + 'recipient_email' => 'Prejemnik e-pošte', + 'invalid_cc_email' => 'En ali več CC e-poštnih naslovov ni veljavnih.', + 'invalid_bcc_email' => 'En ali več BCC e-poštnih naslovov ni veljavnih.', +]; diff --git a/resources/lang/sr/email.php b/resources/lang/sr/email.php new file mode 100644 index 0000000..2beb9e9 --- /dev/null +++ b/resources/lang/sr/email.php @@ -0,0 +1,30 @@ + 'Pošalji imejl', + 'send_email_to' => 'Pošalji imejl korisniku :name', + 'sender' => 'Pošaljilac', + 'recipient' => 'Primalac', + 'cc' => 'CC', + 'cc_help' => 'Dodatni primaoci', + 'bcc' => 'BCC', + 'bcc_help' => 'Skriveni primaoci', + 'subject' => 'Predmet', + 'subject_placeholder' => 'Unesite predmet imejla', + 'message' => 'Poruka', + 'message_placeholder' => 'Unesite sadržaj poruke...', + 'send' => 'Pošalji', + 'cancel' => 'Otkaži', + 'email_sent' => 'Imejl poslan', + 'email_sent_to' => 'Imejl poslan na :email je uspešno poslan.', + 'permission_denied' => 'Dozvola odbijena', + 'permission_denied_message' => 'Nemate dozvolu za slanje imejlova.', + 'error' => 'Greška', + 'send_error_message' => 'Došlo je do greške prilikom slanja imejla: :error', + 'email_form_title' => 'Pošalji imejl', + 'email_form_description' => 'Pošalji imejl odabranom korisniku', + 'your_email' => 'Vaša imejl adresa', + 'recipient_email' => 'Imejl adresa primaoca', + 'invalid_cc_email' => 'Jedna ili više CC imejl adresa nije validna.', + 'invalid_bcc_email' => 'Jedna ili više BCC imejl adresa nije validna.', +]; diff --git a/resources/views/filament/pages/send-email-user.blade.php b/resources/views/filament/pages/send-email-user.blade.php new file mode 100644 index 0000000..29c94dd --- /dev/null +++ b/resources/views/filament/pages/send-email-user.blade.php @@ -0,0 +1,9 @@ + + + {{ $this->form }} + + + + \ No newline at end of file diff --git a/resources/views/mail/send-email-to-user.blade.php b/resources/views/mail/send-email-to-user.blade.php new file mode 100644 index 0000000..11fd8e4 --- /dev/null +++ b/resources/views/mail/send-email-to-user.blade.php @@ -0,0 +1,97 @@ + + + + + + {{ $subject ?? __('eclipse::email.email_sent') }} + + + +
+
+

{{ config('app.name') }}

+
+ + @if($sender) +
+

{{ __('eclipse::email.sender') }}: {{ $sender->name }} ({{ $sender->email }})

+
+ @endif + +
+

{{ __('eclipse::email.message') }}:

+
{{ $messageContent }}
+
+ + +
+ + \ No newline at end of file diff --git a/src/Filament/Actions/SendEmailAction.php b/src/Filament/Actions/SendEmailAction.php new file mode 100644 index 0000000..36d473d --- /dev/null +++ b/src/Filament/Actions/SendEmailAction.php @@ -0,0 +1,20 @@ +authorize(fn () => auth()->user()->can('sendEmail', User::class)) + ); + } +} diff --git a/src/Filament/Actions/SendEmailTableAction.php b/src/Filament/Actions/SendEmailTableAction.php new file mode 100644 index 0000000..26cd588 --- /dev/null +++ b/src/Filament/Actions/SendEmailTableAction.php @@ -0,0 +1,21 @@ +authorize(fn () => auth()->user()->can('sendEmail', User::class)) + ->visible(fn ($record) => ! $record->trashed()) + ); + } +} diff --git a/src/Filament/Resources/UserResource.php b/src/Filament/Resources/UserResource.php index 10d5055..818bdd9 100644 --- a/src/Filament/Resources/UserResource.php +++ b/src/Filament/Resources/UserResource.php @@ -3,6 +3,7 @@ namespace Eclipse\Core\Filament\Resources; use BezhanSalleh\FilamentShield\Contracts\HasShieldPermissions; +use Eclipse\Core\Filament\Actions\SendEmailTableAction; use Eclipse\Core\Filament\Exports\TableExport; use Eclipse\Core\Filament\Resources; use Eclipse\Core\Models\User; @@ -213,6 +214,7 @@ public static function table(Table $table): Table Tables\Actions\ActionGroup::make([ Tables\Actions\ViewAction::make(), Tables\Actions\EditAction::make(), + SendEmailTableAction::make(), Impersonate::make() ->grouped() ->redirectTo(route('filament.admin.tenant')), @@ -357,6 +359,7 @@ public static function getPermissionPrefixes(): array 'force_delete', 'force_delete_any', 'impersonate', + 'send_email', ]; } } diff --git a/src/Filament/Resources/UserResource/Pages/ViewUser.php b/src/Filament/Resources/UserResource/Pages/ViewUser.php index d7f450e..d695f67 100644 --- a/src/Filament/Resources/UserResource/Pages/ViewUser.php +++ b/src/Filament/Resources/UserResource/Pages/ViewUser.php @@ -2,6 +2,7 @@ namespace Eclipse\Core\Filament\Resources\UserResource\Pages; +use Eclipse\Core\Filament\Actions\SendEmailAction; use Eclipse\Core\Filament\Resources\UserResource; use Filament\Actions; use Filament\Resources\Pages\ViewRecord; @@ -15,6 +16,7 @@ protected function getHeaderActions(): array { return [ Actions\EditAction::make(), + SendEmailAction::make(), Impersonate::make() ->record($this->getRecord()) ->redirectTo(route('filament.admin.tenant')), diff --git a/src/Filament/Traits/SendEmailActionTrait.php b/src/Filament/Traits/SendEmailActionTrait.php new file mode 100644 index 0000000..dd089f0 --- /dev/null +++ b/src/Filament/Traits/SendEmailActionTrait.php @@ -0,0 +1,147 @@ +description(__('eclipse::email.email_form_description')) + ->schema([ + Grid::make() + ->schema([ + TextInput::make('sender_email') + ->label(__('eclipse::email.sender')) + ->default(fn () => auth()->user()->email) + ->disabled() + ->dehydrated(false) + ->helperText(__('eclipse::email.your_email')), + + TextInput::make('recipient_email') + ->label(__('eclipse::email.recipient')) + ->default(fn ($record = null) => $record?->email) + ->disabled() + ->dehydrated(false) + ->helperText(__('eclipse::email.recipient_email')), + ]) + ->columns(2), + + Grid::make() + ->schema([ + TagsInput::make('cc') + ->label(__('eclipse::email.cc')) + ->helperText(__('eclipse::email.cc_help')) + ->placeholder('email1@example.com') + ->splitKeys(['Enter', 'Tab', ' ', ',']) + ->nestedRecursiveRules(['email']), + + TagsInput::make('bcc') + ->label(__('eclipse::email.bcc')) + ->helperText(__('eclipse::email.bcc_help')) + ->placeholder('email1@example.com') + ->splitKeys(['Enter', 'Tab', ' ', ',']) + ->nestedRecursiveRules(['email']), + ]) + ->columns(2), + + TextInput::make('subject') + ->label(__('eclipse::email.subject')) + ->required() + ->maxLength(255) + ->placeholder(__('eclipse::email.subject_placeholder')), + + Textarea::make('message') + ->label(__('eclipse::email.message')) + ->required() + ->rows(8) + ->placeholder(__('eclipse::email.message_placeholder')), + + Hidden::make('recipient_id') + ->default(fn ($record = null) => $record?->id), + ]) + ->columns(1), + ]; + } + + protected static function getEmailActionClosure(): \Closure + { + return function (array $data) { + try { + Validator::make($data, [ + 'cc' => ['nullable', 'array'], + 'cc.*' => ['email'], + 'bcc' => ['nullable', 'array'], + 'bcc.*' => ['email'], + ], [ + 'cc.*.email' => __('eclipse::email.invalid_cc_email'), + 'bcc.*.email' => __('eclipse::email.invalid_bcc_email'), + ])->validate(); + + $recipient = User::findOrFail($data['recipient_id']); + + $ccEmails = ! empty($data['cc']) ? implode(',', $data['cc']) : null; + $bccEmails = ! empty($data['bcc']) ? implode(',', $data['bcc']) : null; + + Mail::queue(new SendEmailToUser( + $recipient, + $data['subject'], + $data['message'], + $ccEmails, + $bccEmails, + auth()->user() + )); + + Notification::make() + ->title(__('eclipse::email.email_sent')) + ->body(__('eclipse::email.email_sent_to', ['email' => $recipient->email])) + ->success() + ->sendToDatabase(auth()->user()) + ->broadcast([auth()->user()]); + } catch (\Illuminate\Validation\ValidationException $e) { + $errors = collect($e->errors())->flatten()->implode(' '); + + Notification::make() + ->title(__('eclipse::email.error')) + ->body(__('eclipse::email.send_error_message', ['error' => $errors])) + ->danger() + ->sendToDatabase(auth()->user()) + ->broadcast([auth()->user()]); + } catch (\Exception $e) { + Notification::make() + ->title(__('eclipse::email.error')) + ->body(__('eclipse::email.send_error_message', ['error' => $e->getMessage()])) + ->danger() + ->sendToDatabase(auth()->user()) + ->broadcast([auth()->user()]); + } + }; + } + + protected static function configureEmailAction($action) + { + return $action + ->label(fn () => __('eclipse::email.send_email')) + ->icon('heroicon-o-envelope') + ->form(static::getEmailFormSchema()) + ->action(static::getEmailActionClosure()) + ->modalHeading(fn ($record) => __('eclipse::email.send_email_to', ['name' => $record->name])) + ->modalSubmitActionLabel(__('eclipse::email.send')) + ->modalCancelActionLabel(__('eclipse::email.cancel')) + ->modalWidth('2xl') + ->modalFooterActionsAlignment('end'); + } +} diff --git a/src/Mail/SendEmailToUser.php b/src/Mail/SendEmailToUser.php new file mode 100644 index 0000000..dfd0ade --- /dev/null +++ b/src/Mail/SendEmailToUser.php @@ -0,0 +1,83 @@ +emailSubject, + to: [$this->recipient->email] + ); + + if ($this->ccEmails) { + $ccEmailsArray = array_filter(array_map('trim', explode(',', $this->ccEmails))); + if (! empty($ccEmailsArray)) { + $envelope = $envelope->cc($ccEmailsArray); + } + } + + if ($this->bccEmails) { + $bccEmailsArray = array_filter(array_map('trim', explode(',', $this->bccEmails))); + if (! empty($bccEmailsArray)) { + $envelope = $envelope->bcc($bccEmailsArray); + } + } + + return $envelope; + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + view: 'eclipse::mail.send-email-to-user', + with: [ + 'recipient' => $this->recipient, + 'messageContent' => $this->emailMessage, + 'sender' => $this->sender, + 'subject' => $this->emailSubject, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/src/Policies/UserPolicy.php b/src/Policies/UserPolicy.php index 42c1211..125befe 100644 --- a/src/Policies/UserPolicy.php +++ b/src/Policies/UserPolicy.php @@ -100,4 +100,12 @@ public function impersonate(User $user): bool { return $user->can('impersonate_user'); } + + /** + * Determine whether the user can send emails to other users. + */ + public function sendEmail(User $user): bool + { + return $user->can('send_email_user'); + } } diff --git a/tests/Feature/UserEmailTest.php b/tests/Feature/UserEmailTest.php new file mode 100644 index 0000000..c3939c4 --- /dev/null +++ b/tests/Feature/UserEmailTest.php @@ -0,0 +1,165 @@ +set_up_super_admin_and_tenant(); + + // Create additional permissions + Permission::firstOrCreate(['name' => 'send_email_user']); + + // Create role with email permission + $this->emailRole = Role::firstOrCreate(['name' => 'email_sender']); + $this->emailRole->givePermissionTo(['send_email_user', 'view_any_user']); + + // Create role without email permission + $this->regularRole = Role::firstOrCreate(['name' => 'regular_user']); + $this->regularRole->givePermissionTo(['view_any_user']); + + // Create users with site association + $site = \Eclipse\Core\Models\Site::first(); + + $this->authorizedUser = User::factory()->create(); + $this->authorizedUser->assignRole($this->emailRole); + $this->authorizedUser->sites()->attach($site); + + $this->unauthorizedUser = User::factory()->create(); + $this->unauthorizedUser->assignRole($this->regularRole); + $this->unauthorizedUser->sites()->attach($site); + + $this->recipientUser = User::factory()->create(); + $this->recipientUser->sites()->attach($site); +}); + +test('authorized user has send email permission', function () { + $this->actingAs($this->authorizedUser); + + expect($this->authorizedUser->can('sendEmail', User::class))->toBeTrue(); +}); + +test('unauthorized user does not have send email permission', function () { + $this->actingAs($this->unauthorizedUser); + + expect($this->unauthorizedUser->can('sendEmail', User::class))->toBeFalse(); +}); + +test('send email action requires authorization', function () { + // Test authorization through the policy directly + $this->actingAs($this->authorizedUser); + expect($this->authorizedUser->can('sendEmail', User::class))->toBeTrue(); + + $this->actingAs($this->unauthorizedUser); + expect($this->unauthorizedUser->can('sendEmail', User::class))->toBeFalse(); + + // Test that action is properly configured + $action = \Eclipse\Core\Filament\Actions\SendEmailTableAction::make(); + expect($action->getName())->toBe('sendEmail'); + expect($action->getIcon())->toBe('heroicon-o-envelope'); +}); + +test('send email action visibility rules', function () { + $this->actingAs($this->authorizedUser); + + // Test that action has the proper visibility configuration + $action = \Eclipse\Core\Filament\Actions\SendEmailTableAction::make(); + + // Action should be properly configured + expect($action)->not->toBeNull(); + expect($action->getName())->toBe('sendEmail'); + expect($action->getIcon())->toBe('heroicon-o-envelope'); + + // Test that trashed user detection works + expect($this->recipientUser->trashed())->toBeFalse(); + + $this->recipientUser->delete(); + expect($this->recipientUser->trashed())->toBeTrue(); +}); + +test('send email functionality queues mail', function () { + Queue::fake(); + Mail::fake(); + + $this->actingAs($this->authorizedUser); + + // Send email directly using the Mail class + $emailData = [ + 'subject' => 'Test Subject', + 'message' => 'Test message content', + 'cc' => 'cc1@example.com, cc2@example.com', + 'bcc' => 'bcc1@example.com', + ]; + + Mail::queue(new SendEmailToUser( + $this->recipientUser, + $emailData['subject'], + $emailData['message'], + $emailData['cc'], + $emailData['bcc'], + $this->authorizedUser + )); + + // Assert email was queued + Mail::assertQueued(SendEmailToUser::class, function ($mail) use ($emailData) { + return $mail->recipient->id === $this->recipientUser->id + && $mail->emailSubject === $emailData['subject'] + && $mail->emailMessage === $emailData['message'] + && $mail->ccEmails === $emailData['cc'] + && $mail->bccEmails === $emailData['bcc'] + && $mail->sender->id === $this->authorizedUser->id; + }); +}); + +test('email template renders correctly', function () { + $mail = new SendEmailToUser( + $this->recipientUser, + 'Test Subject', + 'Test message content', + 'cc@example.com', + 'bcc@example.com', + $this->authorizedUser + ); + + $view = $mail->content()->view; + $data = $mail->content()->with; + + expect($view)->toBe('eclipse::mail.send-email-to-user'); + expect($data['recipient']->id)->toBe($this->recipientUser->id); + expect($data['messageContent'])->toBe('Test message content'); + expect($data['sender']->id)->toBe($this->authorizedUser->id); + expect($data['subject'])->toBe('Test Subject'); +}); + +test('email envelope has correct recipients', function () { + $mail = new SendEmailToUser( + $this->recipientUser, + 'Test Subject', + 'Test message content', + 'cc1@example.com, cc2@example.com', + 'bcc1@example.com, bcc2@example.com', + $this->authorizedUser + ); + + $envelope = $mail->envelope(); + + expect($envelope->subject)->toBe('Test Subject'); + + // Test recipients + expect($envelope->to)->toHaveCount(1); + expect($envelope->to[0]->address)->toBe($this->recipientUser->email); + + // Test CC + expect($envelope->cc)->toHaveCount(2); + expect($envelope->cc[0]->address)->toBe('cc1@example.com'); + expect($envelope->cc[1]->address)->toBe('cc2@example.com'); + + // Test BCC + expect($envelope->bcc)->toHaveCount(2); + expect($envelope->bcc[0]->address)->toBe('bcc1@example.com'); + expect($envelope->bcc[1]->address)->toBe('bcc2@example.com'); +}); From 1a495bdfc178ac6d3a936c387f7375eae3e0b094 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Fri, 11 Jul 2025 14:47:53 +0200 Subject: [PATCH 02/12] chore(emails): implement email sent listener --- resources/lang/en/email.php | 2 + resources/lang/hr/email.php | 2 + resources/lang/sl/email.php | 2 + resources/lang/sr/email.php | 2 + src/EclipseServiceProvider.php | 4 ++ src/Filament/Traits/SendEmailActionTrait.php | 4 +- .../SendEmailSuccessNotification.php | 52 +++++++++++++++++++ src/Mail/SendEmailToUser.php | 33 ++++++++++++ 8 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/Listeners/SendEmailSuccessNotification.php diff --git a/resources/lang/en/email.php b/resources/lang/en/email.php index e2dab40..0229ef7 100644 --- a/resources/lang/en/email.php +++ b/resources/lang/en/email.php @@ -15,6 +15,8 @@ 'message_placeholder' => 'Enter message content...', 'send' => 'Send', 'cancel' => 'Cancel', + 'email_queued' => 'Email queued', + 'email_queued_to' => 'Email to :email has been queued for sending.', 'email_sent' => 'Email sent', 'email_sent_to' => 'Email to :email has been successfully sent.', 'permission_denied' => 'Permission denied', diff --git a/resources/lang/hr/email.php b/resources/lang/hr/email.php index cf01042..37b5f70 100644 --- a/resources/lang/hr/email.php +++ b/resources/lang/hr/email.php @@ -15,6 +15,8 @@ 'message_placeholder' => 'Unesite sadržaj poruke...', 'send' => 'Pošaljite', 'cancel' => 'Otkaži', + 'email_queued' => 'E-mail je u redu za slanje', + 'email_queued_to' => 'E-mail za :email je dodan u red za slanje.', 'email_sent' => 'E-mail poslan', 'email_sent_to' => 'E-mail poslan na :email je uspješno poslan.', 'permission_denied' => 'Dozvola odbijena', diff --git a/resources/lang/sl/email.php b/resources/lang/sl/email.php index b625f6d..2a7596f 100644 --- a/resources/lang/sl/email.php +++ b/resources/lang/sl/email.php @@ -15,6 +15,8 @@ 'message_placeholder' => 'Vnesite vsebino sporočila...', 'send' => 'Pošlji', 'cancel' => 'Prekliči', + 'email_queued' => 'E-pošta v čakalni vrsti', + 'email_queued_to' => 'E-pošta za :email je dodana v čakalno vrsto za pošiljanje.', 'email_sent' => 'E-pošta poslana', 'email_sent_to' => 'E-pošta je bila uspešno poslana na :email.', 'permission_denied' => 'Dostop zavrnjen', diff --git a/resources/lang/sr/email.php b/resources/lang/sr/email.php index 2beb9e9..8484377 100644 --- a/resources/lang/sr/email.php +++ b/resources/lang/sr/email.php @@ -15,6 +15,8 @@ 'message_placeholder' => 'Unesite sadržaj poruke...', 'send' => 'Pošalji', 'cancel' => 'Otkaži', + 'email_queued' => 'Imejl je u redu za slanje', + 'email_queued_to' => 'Imejl za :email je dodat u red za slanje.', 'email_sent' => 'Imejl poslan', 'email_sent_to' => 'Imejl poslan na :email je uspešno poslan.', 'permission_denied' => 'Dozvola odbijena', diff --git a/src/EclipseServiceProvider.php b/src/EclipseServiceProvider.php index ffeb8a8..197fbef 100644 --- a/src/EclipseServiceProvider.php +++ b/src/EclipseServiceProvider.php @@ -8,6 +8,7 @@ use Eclipse\Core\Console\Commands\ClearCommand; use Eclipse\Core\Console\Commands\DeployCommand; use Eclipse\Core\Console\Commands\PostComposerUpdate; +use Eclipse\Core\Listeners\SendEmailSuccessNotification; use Eclipse\Core\Models\Locale; use Eclipse\Core\Models\User; use Eclipse\Core\Models\User\Permission; @@ -21,6 +22,7 @@ use Filament\Tables\Columns\Column; use Illuminate\Auth\Events\Login; use Illuminate\Database\Eloquent\Model; +use Illuminate\Mail\Events\MessageSent; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Event; @@ -79,6 +81,8 @@ public function register(): self } }); + Event::listen(MessageSent::class, SendEmailSuccessNotification::class); + $this->app->register(AdminPanelProvider::class); if ($this->app->environment('local')) { diff --git a/src/Filament/Traits/SendEmailActionTrait.php b/src/Filament/Traits/SendEmailActionTrait.php index dd089f0..2939aaa 100644 --- a/src/Filament/Traits/SendEmailActionTrait.php +++ b/src/Filament/Traits/SendEmailActionTrait.php @@ -106,8 +106,8 @@ protected static function getEmailActionClosure(): \Closure )); Notification::make() - ->title(__('eclipse::email.email_sent')) - ->body(__('eclipse::email.email_sent_to', ['email' => $recipient->email])) + ->title(__('eclipse::email.email_queued')) + ->body(__('eclipse::email.email_queued_to', ['email' => $recipient->email])) ->success() ->sendToDatabase(auth()->user()) ->broadcast([auth()->user()]); diff --git a/src/Listeners/SendEmailSuccessNotification.php b/src/Listeners/SendEmailSuccessNotification.php new file mode 100644 index 0000000..87df59b --- /dev/null +++ b/src/Listeners/SendEmailSuccessNotification.php @@ -0,0 +1,52 @@ +message; + + $headers = $message->getHeaders(); + + if (! $headers->has('X-Eclipse-Email-Type') || + $headers->get('X-Eclipse-Email-Type')->getBodyAsString() !== 'SendEmailToUser') { + return; + } + + if (! $headers->has('X-Eclipse-Sender-ID')) { + return; + } + + $senderId = $headers->get('X-Eclipse-Sender-ID')->getBodyAsString(); + if (empty($senderId)) { + return; + } + + if (! $headers->has('X-Eclipse-Recipient-Email')) { + return; + } + + $recipientEmail = $headers->get('X-Eclipse-Recipient-Email')->getBodyAsString(); + + $sender = User::find($senderId); + if (! $sender) { + return; + } + + Notification::make() + ->title(__('eclipse::email.email_sent')) + ->body(__('eclipse::email.email_sent_to', ['email' => $recipientEmail])) + ->success() + ->sendToDatabase($sender) + ->broadcast([$sender]); + } +} diff --git a/src/Mail/SendEmailToUser.php b/src/Mail/SendEmailToUser.php index dfd0ade..99ad150 100644 --- a/src/Mail/SendEmailToUser.php +++ b/src/Mail/SendEmailToUser.php @@ -3,11 +3,13 @@ namespace Eclipse\Core\Mail; use Eclipse\Core\Models\User; +use Filament\Notifications\Notification; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; +use Illuminate\Mail\Mailables\Headers; use Illuminate\Queue\SerializesModels; class SendEmailToUser extends Mailable implements ShouldQueue @@ -55,6 +57,20 @@ public function envelope(): Envelope return $envelope; } + /** + * Get the message headers. + */ + public function headers(): Headers + { + return new Headers( + text: [ + 'X-Eclipse-Email-Type' => 'SendEmailToUser', + 'X-Eclipse-Sender-ID' => $this->sender?->id ?? '', + 'X-Eclipse-Recipient-Email' => $this->recipient->email, + ] + ); + } + /** * Get the message content definition. */ @@ -80,4 +96,21 @@ public function attachments(): array { return []; } + + /** + * Handle a job failure. + */ + public function failed(\Throwable $exception): void + { + if ($this->sender) { + Notification::make() + ->title(__('eclipse::email.error')) + ->body(__('eclipse::email.send_error_message', [ + 'error' => $exception->getMessage(), + ])) + ->danger() + ->sendToDatabase($this->sender) + ->broadcast([$this->sender]); + } + } } From 06a3ca0aa4a8f456d66f8b0b0bc197826f3a9d67 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Wed, 16 Jul 2025 14:57:58 +0200 Subject: [PATCH 03/12] chore(emails): add reply-to, refactor send email action, use WYSIWYG editor --- .../views/mail/send-email-to-user.blade.php | 2 +- src/Filament/Actions/SendEmailAction.php | 152 +++++++++++++++++- src/Filament/Actions/SendEmailTableAction.php | 10 +- src/Filament/Traits/SendEmailActionTrait.php | 147 ----------------- src/Mail/SendEmailToUser.php | 21 ++- 5 files changed, 174 insertions(+), 158 deletions(-) delete mode 100644 src/Filament/Traits/SendEmailActionTrait.php diff --git a/resources/views/mail/send-email-to-user.blade.php b/resources/views/mail/send-email-to-user.blade.php index 11fd8e4..aa41a2b 100644 --- a/resources/views/mail/send-email-to-user.blade.php +++ b/resources/views/mail/send-email-to-user.blade.php @@ -85,7 +85,7 @@

{{ __('eclipse::email.message') }}:

-
{{ $messageContent }}
+
{!! $messageContent !!}