diff --git a/app/Events/AdminRequestedTestMessage.php b/app/Events/Test/TestNotificationChannel.php similarity index 62% rename from app/Events/AdminRequestedTestMessage.php rename to app/Events/Test/TestNotificationChannel.php index 4f32c7841c..5ad625e64a 100644 --- a/app/Events/AdminRequestedTestMessage.php +++ b/app/Events/Test/TestNotificationChannel.php @@ -1,8 +1,7 @@ . + * along with this program. If not, see https://www.gnu.org/licenses/. */ declare(strict_types=1); -namespace FireflyIII\Events; +namespace FireflyIII\Events\Test; use FireflyIII\User; use Illuminate\Queue\SerializesModels; -/** - * Class AdminRequestedTestMessage. - */ -class AdminRequestedTestMessage extends Event +class TestNotificationChannel { use SerializesModels; - public User $user; + public User $user; + public string $channel; /** * Create a new event instance. */ - public function __construct(User $user) + public function __construct(string $channel, User $user) { - app('log')->debug(sprintf('Triggered AdminRequestedTestMessage for user #%d (%s)', $user->id, $user->email)); - $this->user = $user; + app('log')->debug(sprintf('Triggered TestNotificationChannel("%s") for user #%d (%s)', $channel, $user->id, $user->email)); + $this->user = $user; + $this->channel = $channel; } } diff --git a/app/Handlers/Events/AdminEventHandler.php b/app/Handlers/Events/AdminEventHandler.php index fb8565b137..9c450a537d 100644 --- a/app/Handlers/Events/AdminEventHandler.php +++ b/app/Handlers/Events/AdminEventHandler.php @@ -26,10 +26,15 @@ namespace FireflyIII\Handlers\Events; use FireflyIII\Events\Admin\InvitationCreated; use FireflyIII\Events\AdminRequestedTestMessage; use FireflyIII\Events\NewVersionAvailable; +use FireflyIII\Events\Test\TestNotificationChannel; use FireflyIII\Notifications\Admin\TestNotification; use FireflyIII\Notifications\Admin\UserInvitation; use FireflyIII\Notifications\Admin\VersionCheckResult; +use FireflyIII\Notifications\Test\TestNotificationDiscord; +use FireflyIII\Notifications\Test\TestNotificationEmail; +use FireflyIII\Notifications\Test\TestNotificationSlack; use FireflyIII\Repositories\User\UserRepositoryInterface; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Notification; /** @@ -39,7 +44,7 @@ class AdminEventHandler { public function sendInvitationNotification(InvitationCreated $event): void { - $sendMail = app('fireflyconfig')->get('notification_invite_created', true)->data; + $sendMail = app('fireflyconfig')->get('notification_invite_created', true)->data; if (false === $sendMail) { return; } @@ -75,7 +80,7 @@ class AdminEventHandler */ public function sendNewVersion(NewVersionAvailable $event): void { - $sendMail = app('fireflyconfig')->get('notification_new_version', true)->data; + $sendMail = app('fireflyconfig')->get('notification_new_version', true)->data; if (false === $sendMail) { return; } @@ -109,17 +114,34 @@ class AdminEventHandler /** * Sends a test message to an administrator. */ - public function sendTestMessage(AdminRequestedTestMessage $event): void + public function sendTestNotification(TestNotificationChannel $event): void { + Log::debug(sprintf('Now in sendTestNotification(#%d, "%s")', $event->user->id, $event->channel)); /** @var UserRepositoryInterface $repository */ $repository = app(UserRepositoryInterface::class); if (!$repository->hasRole($event->user, 'owner')) { + Log::error(sprintf('User #%d is not an owner.', $event->user->id)); return; } + switch($event->channel) { + case 'email': + $class = TestNotificationEmail::class; + break; + case 'slack': + $class = TestNotificationSlack::class; + break; + case 'discord': + $class = TestNotificationDiscord::class; + break; + default: + app('log')->error(sprintf('Unknown channel "%s" in sendTestNotification method.', $event->channel)); + return; + } + Log::debug(sprintf('Will send %s as a notification.', $class)); try { - Notification::send($event->user, new TestNotification($event->user->email)); + Notification::send($event->user, new $class($event->user->email)); } catch (\Exception $e) { // @phpstan-ignore-line $message = $e->getMessage(); if (str_contains($message, 'Bcc')) { @@ -135,5 +157,6 @@ class AdminEventHandler app('log')->error($e->getMessage()); app('log')->error($e->getTraceAsString()); } + Log::debug(sprintf('If you see no errors above this line, test notification was sent over channel "%s"', $event->channel)); } } diff --git a/app/Http/Controllers/Admin/HomeController.php b/app/Http/Controllers/Admin/HomeController.php index 4788f21eda..8c8b3bf2b0 100644 --- a/app/Http/Controllers/Admin/HomeController.php +++ b/app/Http/Controllers/Admin/HomeController.php @@ -67,23 +67,4 @@ class HomeController extends Controller return view('admin.index', compact('title', 'mainTitleIcon', 'email')); } - - /** - * Send a test message to the admin. - * - * @return Redirector|RedirectResponse - */ - public function testMessage() - { - die('disabled.'); - Log::channel('audit')->info('User sends test message.'); - - /** @var User $user */ - $user = auth()->user(); - app('log')->debug('Now in testMessage() controller.'); - event(new AdminRequestedTestMessage($user)); - session()->flash('info', (string)trans('firefly.send_test_triggered')); - - return redirect(route('admin.index')); - } } diff --git a/app/Http/Controllers/Admin/NotificationController.php b/app/Http/Controllers/Admin/NotificationController.php index 75dc522d91..429481333b 100644 --- a/app/Http/Controllers/Admin/NotificationController.php +++ b/app/Http/Controllers/Admin/NotificationController.php @@ -23,9 +23,12 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Admin; +use FireflyIII\Events\Test\TestNotificationChannel; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\NotificationRequest; +use FireflyIII\User; use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; class NotificationController extends Controller @@ -38,30 +41,69 @@ class NotificationController extends Controller $subTitle = (string) trans('firefly.title_owner_notifications'); $subTitleIcon = 'envelope-o'; $slackUrl = app('fireflyconfig')->get('slack_webhook_url', '')->data; - $discordUrl = app('fireflyconfig')->get('discord_webhook_url', '')->data; + $discordUrl = app('fireflyconfig')->get('discord_webhook_url', '')->data; $channels = config('notifications.channels'); // admin notification settings: $notifications = []; foreach (config('notifications.notifications.owner') as $key => $info) { - if($info['enabled']) { + if ($info['enabled']) { $notifications[$key] = app('fireflyconfig')->get(sprintf('notification_%s', $key), true)->data; } } - return view('admin.notifications.index', compact('title', 'subTitle', 'mainTitleIcon', 'subTitleIcon', 'channels', 'slackUrl','discordUrl','notifications')); + return view('admin.notifications.index', compact('title', 'subTitle', 'mainTitleIcon', 'subTitleIcon', 'channels', 'slackUrl', 'discordUrl', 'notifications')); } - public function postIndex(NotificationRequest $request): RedirectResponse { + public function postIndex(NotificationRequest $request): RedirectResponse + { + $all = $request->getAll(); - var_dump($request->getAll()); - exit; - // app('fireflyconfig')->set(sprintf('notification_%s', $key), $value);; + foreach (config('notifications.notifications.owner') as $key => $info) { + if (array_key_exists($key, $all)) { + app('fireflyconfig')->set(sprintf('notification_%s', $key), $all[$key]); + } + } + if ('' === $all['slack_url']) { + app('fireflyconfig')->delete('slack_webhook_url'); + } + if ('' === $all['discord_url']) { + app('fireflyconfig')->delete('discord_webhook_url'); + } + if ('' !== $all['slack_url']) { + app('fireflyconfig')->set('slack_webhook_url', $all['slack_url']); + } + if ('' !== $all['discord_url']) { + app('fireflyconfig')->set('discord_webhook_url', $all['discord_url']); + } - session()->flash('success', (string)trans('firefly.notification_settings_saved')); + session()->flash('success', (string) trans('firefly.notification_settings_saved')); - return redirect(route('admin.index')); + return redirect(route('admin.notification.index')); + } + + public function testNotification(Request $request): RedirectResponse + { + + $all = $request->all(); + $channel = $all['test_submit'] ?? ''; + + switch ($channel) { + default: + session()->flash('error', (string) trans('firefly.notification_test_failed', ['channel' => $channel])); + break; + case 'email': + case 'discord': + case 'slack': + /** @var User $user */ + $user = auth()->user(); + app('log')->debug(sprintf('Now in testNotification("%s") controller.', $channel)); + event(new TestNotificationChannel($channel, $user)); + session()->flash('success', (string) trans('firefly.notification_test_executed', ['channel' => $channel])); + } + + return redirect(route('admin.notification.index')); } } diff --git a/app/Http/Requests/NotificationRequest.php b/app/Http/Requests/NotificationRequest.php index 2f94e047bf..e0a97ac5ce 100644 --- a/app/Http/Requests/NotificationRequest.php +++ b/app/Http/Requests/NotificationRequest.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; +use FireflyIII\Rules\Admin\IsValidDiscordUrl; +use FireflyIII\Rules\Admin\IsValidSlackUrl; use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ConvertsDataTypes; use Illuminate\Foundation\Http\FormRequest; @@ -42,18 +44,9 @@ class NotificationRequest extends FormRequest } $return[$key] = $value; } - $return['discord_url'] = $this->convertString('discordUrl'); - $return['slack_url'] = $this->convertString('slackUrl'); + $return['discord_url'] = $this->convertString('discord_url'); + $return['slack_url'] = $this->convertString('slack_url'); return $return; -// if (UrlValidator::isValidWebhookURL($url)) { -// app('fireflyconfig')->set('slack_webhook_url', $url); -// } -// } -// -// -// var_dump($this->all()); -// exit; -// return []; } /** @@ -61,10 +54,14 @@ class NotificationRequest extends FormRequest */ public function rules(): array { - // fixed - return [ - //'password' => 'required', + $rules = [ + 'discord_url' => ['nullable', 'url', 'min:1', new IsValidDiscordUrl()], + 'slack_url' => ['nullable', 'url', 'min:1', new IsValidSlackUrl()], ]; + foreach (config('notifications.notifications.owner') as $key => $info) { + $rules[sprintf('notification_%s', $key)] = 'in:0,1'; + } + return $rules; } } diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 87779dd2b4..6cdca7d42e 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -170,15 +170,11 @@ class TransactionJournal extends Model public function scopeAfter(EloquentBuilder $query, Carbon $date): EloquentBuilder { - Log::debug(sprintf('scopeAfter("%s")', $date->format('Y-m-d H:i:s'))); - return $query->where('transaction_journals.date', '>=', $date->format('Y-m-d H:i:s')); } public function scopeBefore(EloquentBuilder $query, Carbon $date): EloquentBuilder { - Log::debug(sprintf('scopeBefore("%s")', $date->format('Y-m-d H:i:s'))); - return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')); } diff --git a/app/Notifications/Test/TestNotificationDiscord.php b/app/Notifications/Test/TestNotificationDiscord.php new file mode 100644 index 0000000000..603f1f2561 --- /dev/null +++ b/app/Notifications/Test/TestNotificationDiscord.php @@ -0,0 +1,108 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Notifications\Test; + +use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Notifications\Messages\SlackMessage; +use Illuminate\Notifications\Notification; + +/** + * Class TestNotification + */ +class TestNotificationDiscord extends Notification +{ + use Queueable; + + private string $address; + + /** + * Create a new notification instance. + */ + public function __construct(string $address) + { + $this->address = $address; + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return array + */ + public function toArray($notifiable) + { + return [ + ]; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return MailMessage + */ + public function toMail($notifiable) + { + } + + /** + * Get the Slack representation of the notification. + * + * @param mixed $notifiable + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + */ + public function toSlack($notifiable) { + + // since it's an admin notificaiton, grab the URL from fireflyconfig + $url = app('fireflyconfig')->get('discord_webhook_url', '')->data; + + return (new SlackMessage()) + ->content((string)trans('email.admin_test_subject')) + ->to($url); + } + + /** + * Get the notification's delivery channels. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param mixed $notifiable + * + * @return array + */ + public function via($notifiable) + { + return ['slack']; + } +} diff --git a/app/Notifications/Test/TestNotificationEmail.php b/app/Notifications/Test/TestNotificationEmail.php new file mode 100644 index 0000000000..d29be7c357 --- /dev/null +++ b/app/Notifications/Test/TestNotificationEmail.php @@ -0,0 +1,102 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Notifications\Test; + +use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Notifications\Notification; + +/** + * Class TestNotification + */ +class TestNotificationEmail extends Notification +{ + use Queueable; + + private string $address; + + /** + * Create a new notification instance. + */ + public function __construct(string $address) + { + $this->address = $address; + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return array + */ + public function toArray($notifiable) + { + return [ + ]; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return MailMessage + */ + public function toMail($notifiable) + { + return (new MailMessage()) + ->markdown('emails.admin-test', ['email' => $this->address]) + ->subject((string) trans('email.admin_test_subject')); + } + + /** + * Get the Slack representation of the notification. + * + * @param mixed $notifiable + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + */ + public function toSlack($notifiable) {} + + /** + * Get the notification's delivery channels. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param mixed $notifiable + * + * @return array + */ + public function via($notifiable) + { + return ['mail']; + } +} diff --git a/app/Notifications/Test/TestNotificationSlack.php b/app/Notifications/Test/TestNotificationSlack.php new file mode 100644 index 0000000000..05f035848d --- /dev/null +++ b/app/Notifications/Test/TestNotificationSlack.php @@ -0,0 +1,113 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Notifications\Test; + +use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Notifications\Messages\SlackMessage; +use Illuminate\Notifications\Notification; +//use Illuminate\Notifications\Slack\SlackMessage; + +/** + * Class TestNotification + */ +class TestNotificationSlack extends Notification +{ + use Queueable; + + private string $address; + + /** + * Create a new notification instance. + */ + public function __construct(string $address) + { + $this->address = $address; + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return array + */ + public function toArray($notifiable) + { + return [ + ]; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return MailMessage + */ + public function toMail($notifiable) + { + } + + /** + * Get the Slack representation of the notification. + * + * @param mixed $notifiable + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + */ + public function toSlack($notifiable) { + + // since it's an admin notification, grab the URL from fireflyconfig + $url = app('fireflyconfig')->get('slack_webhook_url', '')->data; + +// return (new SlackMessage) +// ->text((string)trans('email.admin_test_subject')) +// ->to($url); + return (new SlackMessage()) + ->content((string)trans('email.admin_test_subject')) + ->to($url); + + } + + /** + * Get the notification's delivery channels. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param mixed $notifiable + * + * @return array + */ + public function via($notifiable) + { + return ['slack']; + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index b89317fede..bc09ed6fef 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -49,6 +49,7 @@ use FireflyIII\Events\Security\MFANewBackupCodes; use FireflyIII\Events\Security\MFAUsedBackupCode; use FireflyIII\Events\StoredAccount; use FireflyIII\Events\StoredTransactionGroup; +use FireflyIII\Events\Test\TestNotificationChannel; use FireflyIII\Events\TriggeredAuditLog; use FireflyIII\Events\UpdatedAccount; use FireflyIII\Events\UpdatedTransactionGroup; @@ -135,8 +136,8 @@ class EventServiceProvider extends ServiceProvider 'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeUndoMail', ], // admin related - AdminRequestedTestMessage::class => [ - 'FireflyIII\Handlers\Events\AdminEventHandler@sendTestMessage', + TestNotificationChannel::class => [ + 'FireflyIII\Handlers\Events\AdminEventHandler@sendTestNotification', ], NewVersionAvailable::class => [ 'FireflyIII\Handlers\Events\AdminEventHandler@sendNewVersion', diff --git a/app/Rules/Admin/IsValidDiscordUrl.php b/app/Rules/Admin/IsValidDiscordUrl.php new file mode 100644 index 0000000000..f0e03c6447 --- /dev/null +++ b/app/Rules/Admin/IsValidDiscordUrl.php @@ -0,0 +1,32 @@ +translate(); + $message = sprintf('IsValidDiscordUrl: "%s" is not a discord URL.', substr($value, 0, 255)); + Log::debug($message); + Log::channel('audit')->info($message); + } + } +} diff --git a/app/Rules/Admin/IsValidSlackUrl.php b/app/Rules/Admin/IsValidSlackUrl.php new file mode 100644 index 0000000000..57cd1675a1 --- /dev/null +++ b/app/Rules/Admin/IsValidSlackUrl.php @@ -0,0 +1,32 @@ +translate(); + $message = sprintf('IsValidSlackUrl: "%s" is not a discord URL.', substr($value, 0, 255)); + Log::debug($message); + Log::channel('audit')->info($message); + } + } +} diff --git a/app/User.php b/app/User.php index 3e44cb1e43..fa79d3a12e 100644 --- a/app/User.php +++ b/app/User.php @@ -54,6 +54,8 @@ use FireflyIII\Notifications\Admin\TestNotification; use FireflyIII\Notifications\Admin\UserInvitation; use FireflyIII\Notifications\Admin\UserRegistration; use FireflyIII\Notifications\Admin\VersionCheckResult; +use FireflyIII\Notifications\Test\TestNotificationDiscord; +use FireflyIII\Notifications\Test\TestNotificationSlack; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -399,7 +401,7 @@ class User extends Authenticatable /** * Route notifications for the Slack channel. */ - public function routeNotificationForSlack(Notification $notification): string + public function routeNotificationForSlack(Notification $notification): ?string { // this check does not validate if the user is owner, Should be done by notification itself. $res = app('fireflyconfig')->get('slack_webhook_url', '')->data; @@ -407,9 +409,19 @@ class User extends Authenticatable $res = ''; } $res = (string)$res; - if ($notification instanceof TestNotification) { + + // not the best way to do this, but alas. + + if ($notification instanceof TestNotificationSlack) { return $res; } + if ($notification instanceof TestNotificationDiscord) { + $res = app('fireflyconfig')->get('discord_webhook_url', '')->data; + if (is_array($res)) { + $res = ''; + } + return (string)$res; + } if ($notification instanceof UserInvitation) { return $res; } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 5ce9ee368b..a427410ada 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -2483,6 +2483,8 @@ return [ 'owner_notifications' => 'Admin notifications', 'owner_notifications_expl' => 'The following notifications can be enabled or disabled by the administrator. It will be sent over ALL configured channels. Some channels are configured in your environment variables, others can be set here.', 'channel_settings' => 'Settings for notification channels', + 'notification_test_failed' => 'Notification test for channel ":channel" failed. The logs will have more details.', + 'notification_test_executed' => 'Notification test for channel ":channel" executed. Check your logs for details.', 'settings_notifications' => 'Settings for notifications', 'title_owner_notifications' => 'Owner notifications', 'owner_notification_check_user_new_reg' => 'User gets post-registration welcome message', diff --git a/resources/views/admin/notifications/index.twig b/resources/views/admin/notifications/index.twig index 52e5085eb1..2b753f9330 100644 --- a/resources/views/admin/notifications/index.twig +++ b/resources/views/admin/notifications/index.twig @@ -24,8 +24,8 @@ {% endfor %}

{{ 'channel_settings'|_ }}

- {{ ExpandedForm.text('slackUrl', slackUrl, {'label' : 'slack_url_label'|_}) }} - {{ ExpandedForm.text('discordUrl', discordUrl, {'label' : 'discord_url_label'|_}) }} + {{ ExpandedForm.text('slack_url', slackUrl, {'label' : 'slack_url_label'|_}) }} + {{ ExpandedForm.text('discord_url', discordUrl, {'label' : 'discord_url_label'|_}) }}