diff --git a/app/Events/Test/TestNotificationChannel.php b/app/Events/Test/OwnerTestNotificationChannel.php similarity index 90% rename from app/Events/Test/TestNotificationChannel.php rename to app/Events/Test/OwnerTestNotificationChannel.php index d40bb90b9e..75d530e391 100644 --- a/app/Events/Test/TestNotificationChannel.php +++ b/app/Events/Test/OwnerTestNotificationChannel.php @@ -27,7 +27,7 @@ namespace FireflyIII\Events\Test; use FireflyIII\Notifications\Notifiables\OwnerNotifiable; use Illuminate\Queue\SerializesModels; -class TestNotificationChannel +class OwnerTestNotificationChannel { use SerializesModels; @@ -39,7 +39,7 @@ class TestNotificationChannel */ public function __construct(string $channel, OwnerNotifiable $owner) { - app('log')->debug(sprintf('Triggered TestNotificationChannel("%s")', $channel)); + app('log')->debug(sprintf('Triggered OwnerTestNotificationChannel("%s")', $channel)); $this->owner = $owner; $this->channel = $channel; } diff --git a/app/Events/Test/UserTestNotificationChannel.php b/app/Events/Test/UserTestNotificationChannel.php new file mode 100644 index 0000000000..4b219071d9 --- /dev/null +++ b/app/Events/Test/UserTestNotificationChannel.php @@ -0,0 +1,46 @@ +debug(sprintf('Triggered UserTestNotificationChannel("%s")', $channel)); + $this->user = $user; + $this->channel = $channel; + } +} diff --git a/app/Handlers/Events/AdminEventHandler.php b/app/Handlers/Events/AdminEventHandler.php index 485fe4e709..3674200b34 100644 --- a/app/Handlers/Events/AdminEventHandler.php +++ b/app/Handlers/Events/AdminEventHandler.php @@ -26,15 +26,15 @@ namespace FireflyIII\Handlers\Events; use FireflyIII\Events\Admin\InvitationCreated; use FireflyIII\Events\NewVersionAvailable; use FireflyIII\Events\Security\UnknownUserAttemptedLogin; -use FireflyIII\Events\Test\TestNotificationChannel; +use FireflyIII\Events\Test\OwnerTestNotificationChannel; use FireflyIII\Notifications\Admin\UnknownUserLoginAttempt; use FireflyIII\Notifications\Admin\UserInvitation; use FireflyIII\Notifications\Admin\VersionCheckResult; use FireflyIII\Notifications\Notifiables\OwnerNotifiable; -use FireflyIII\Notifications\Test\TestNotificationEmail; -use FireflyIII\Notifications\Test\TestNotificationNtfy; -use FireflyIII\Notifications\Test\TestNotificationPushover; -use FireflyIII\Notifications\Test\TestNotificationSlack; +use FireflyIII\Notifications\Test\OwnerTestNotificationEmail; +use FireflyIII\Notifications\Test\OwnerTestNotificationNtfy; +use FireflyIII\Notifications\Test\OwnerTestNotificationPushover; +use FireflyIII\Notifications\Test\OwnerTestNotificationSlack; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Notification; @@ -125,28 +125,28 @@ class AdminEventHandler /** * Sends a test message to an administrator. */ - public function sendTestNotification(TestNotificationChannel $event): void + public function sendTestNotification(OwnerTestNotificationChannel $event): void { Log::debug(sprintf('Now in sendTestNotification("%s")', $event->channel)); switch ($event->channel) { case 'email': - $class = TestNotificationEmail::class; + $class = OwnerTestNotificationEmail::class; break; case 'slack': - $class = TestNotificationSlack::class; + $class = OwnerTestNotificationSlack::class; break; case 'ntfy': - $class = TestNotificationNtfy::class; + $class = OwnerTestNotificationNtfy::class; break; case 'pushover': - $class = TestNotificationPushover::class; + $class = OwnerTestNotificationPushover::class; break; diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index ffda8cdbed..aaac2ca8d5 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -31,6 +31,8 @@ use FireflyIII\Events\Admin\InvitationCreated; use FireflyIII\Events\DetectedNewIPAddress; use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RequestedNewPassword; +use FireflyIII\Events\Test\OwnerTestNotificationChannel; +use FireflyIII\Events\Test\UserTestNotificationChannel; use FireflyIII\Events\UserChangedEmail; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Mail\ConfirmEmailChangeMail; @@ -40,12 +42,21 @@ use FireflyIII\Models\GroupMembership; use FireflyIII\Models\UserGroup; use FireflyIII\Models\UserRole; use FireflyIII\Notifications\Admin\UserRegistration as AdminRegistrationNotification; +use FireflyIII\Notifications\Test\OwnerTestNotificationEmail; +use FireflyIII\Notifications\Test\OwnerTestNotificationNtfy; +use FireflyIII\Notifications\Test\OwnerTestNotificationPushover; +use FireflyIII\Notifications\Test\OwnerTestNotificationSlack; +use FireflyIII\Notifications\Test\UserTestNotificationEmail; +use FireflyIII\Notifications\Test\UserTestNotificationNtfy; +use FireflyIII\Notifications\Test\UserTestNotificationPushover; +use FireflyIII\Notifications\Test\UserTestNotificationSlack; use FireflyIII\Notifications\User\UserLogin; use FireflyIII\Notifications\User\UserNewPassword; use FireflyIII\Notifications\User\UserRegistration as UserRegistrationNotification; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\User; use Illuminate\Auth\Events\Login; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Notification; use Mail; @@ -423,4 +434,59 @@ class UserEventHandler event(new DetectedNewIPAddress($user, $ip)); } } + + /** + * Sends a test message to an administrator. + */ + public function sendTestNotification(UserTestNotificationChannel $event): void + { + Log::debug(sprintf('Now in (user) sendTestNotification("%s")', $event->channel)); + + switch ($event->channel) { + case 'email': + $class = UserTestNotificationEmail::class; + + break; + + case 'slack': + $class = UserTestNotificationSlack::class; + + break; + + case 'ntfy': + $class = UserTestNotificationNtfy::class; + + break; + + case 'pushover': + $class = UserTestNotificationPushover::class; + + break; + + default: + app('log')->error(sprintf('Unknown channel "%s" in (user) sendTestNotification method.', $event->channel)); + + return; + } + Log::debug(sprintf('Will send %s as a notification.', $class)); + + try { + Notification::send($event->user, new $class($event->user)); + } catch (\Exception $e) { // @phpstan-ignore-line + $message = $e->getMessage(); + if (str_contains($message, 'Bcc')) { + app('log')->warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.'); + + return; + } + if (str_contains($message, 'RFC 2822')) { + app('log')->warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.'); + + return; + } + 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/NotificationController.php b/app/Http/Controllers/Admin/NotificationController.php index f1493dfa2d..39351c6e09 100644 --- a/app/Http/Controllers/Admin/NotificationController.php +++ b/app/Http/Controllers/Admin/NotificationController.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Admin; -use FireflyIII\Events\Test\TestNotificationChannel; +use FireflyIII\Events\Test\OwnerTestNotificationChannel; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\NotificationRequest; use FireflyIII\Notifications\Notifiables\OwnerNotifiable; @@ -135,7 +135,7 @@ class NotificationController extends Controller case 'ntfy': $owner = new OwnerNotifiable(); app('log')->debug(sprintf('Now in testNotification("%s") controller.', $channel)); - event(new TestNotificationChannel($channel, $owner)); + event(new OwnerTestNotificationChannel($channel, $owner)); session()->flash('success', (string) trans('firefly.notification_test_executed', ['channel' => $channel])); } diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php index d29ee4b4eb..3f8072d6d5 100644 --- a/app/Http/Controllers/PreferencesController.php +++ b/app/Http/Controllers/PreferencesController.php @@ -23,12 +23,15 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; +use FireflyIII\Events\Test\UserTestNotificationChannel; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Http\Requests\PreferencesRequest; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Preference; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Support\Notifications\UrlValidator; +use FireflyIII\User; use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -110,13 +113,13 @@ class PreferencesController extends Controller // notifications settings $slackUrl = app('preferences')->getEncrypted('slack_webhook_url', '')->data; - $pushoverAppToken = app('preferences')->getEncrypted('pushover_app_token', '')->data; - $pushoverUserToken = app('preferences')->getEncrypted('pushover_user_token', '')->data; + $pushoverAppToken = (string) app('preferences')->getEncrypted('pushover_app_token', '')->data; + $pushoverUserToken = (string) app('preferences')->getEncrypted('pushover_user_token', '')->data; $ntfyServer = app('preferences')->getEncrypted('ntfy_server', 'https://ntfy.sh')->data; - $ntfyTopic = app('preferences')->getEncrypted('ntfy_topic', '')->data; + $ntfyTopic = (string) app('preferences')->getEncrypted('ntfy_topic', '')->data; $ntfyAuth = app('preferences')->get('ntfy_auth', false)->data; $ntfyUser = app('preferences')->getEncrypted('ntfy_user', '')->data; - $ntfyPass = app('preferences')->getEncrypted('ntfy_pass', '')->data; + $ntfyPass = (string) app('preferences')->getEncrypted('ntfy_pass', '')->data; $channels = config('notifications.channels'); $forcedAvailability = []; @@ -175,6 +178,7 @@ class PreferencesController extends Controller 'ntfyServer', 'ntfyTopic', 'ntfyAuth', + 'channels', 'ntfyUser', 'forcedAvailability', 'ntfyPass', @@ -206,7 +210,7 @@ class PreferencesController extends Controller * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function postIndex(Request $request) + public function postIndex(PreferencesRequest $request) { // front page accounts $frontpageAccounts = []; @@ -219,10 +223,8 @@ class PreferencesController extends Controller // extract notifications: $all = $request->all(); - - exit('fix the reference to the available notifications.'); - foreach (config('firefly.available_notifications') as $option) { - $key = sprintf('notification_%s', $option); + foreach (config('notifications.notifications.user') as $key => $info) { + $key = sprintf('notification_%s', $key); if (array_key_exists($key, $all)) { app('preferences')->set($key, true); } @@ -238,15 +240,19 @@ class PreferencesController extends Controller session()->forget('end'); session()->forget('range'); - // slack URL: + // notification settings, cannot be set by the demo user. if (!auth()->user()->hasRole('demo')) { - $url = (string) $request->get('slackUrl'); - if (UrlValidator::isValidWebhookURL($url)) { - app('preferences')->set('slack_webhook_url', $url); - } - if ('' === $url) { - app('preferences')->delete('slack_webhook_url'); + + $variables = ['slack_webhook_url', 'pushover_app_token', 'pushover_user_token', 'ntfy_server', 'ntfy_topic', 'ntfy_user', 'ntfy_pass']; + foreach ($variables as $variable) { + if ('' === $all[$variable]) { + app('preferences')->delete($variable); + } + if ('' !== $all[$variable]) { + app('preferences')->setEncrypted($variable, $all[$variable]); + } } + app('preferences')->set('ntfy_auth', $all['ntfy_auth'] ?? false); } // custom fiscal year @@ -313,4 +319,29 @@ class PreferencesController extends Controller return redirect(route('preferences.index')); } + + public function testNotification(Request $request): mixed + { + + $all = $request->all(); + $channel = $all['channel'] ?? ''; + + switch ($channel) { + default: + session()->flash('error', (string) trans('firefly.notification_test_failed', ['channel' => $channel])); + + break; + + case 'email': + case 'slack': + case 'pushover': + case 'ntfy': + /** @var User $user */ + $user = auth()->user(); + app('log')->debug(sprintf('Now in testNotification("%s") controller.', $channel)); + event(new UserTestNotificationChannel($channel, $user)); + session()->flash('success', (string) trans('firefly.notification_test_executed', ['channel' => $channel])); + } + return ''; + } } diff --git a/app/Http/Requests/PreferencesRequest.php b/app/Http/Requests/PreferencesRequest.php new file mode 100644 index 0000000000..de4f0d174d --- /dev/null +++ b/app/Http/Requests/PreferencesRequest.php @@ -0,0 +1,52 @@ + ['nullable', 'url', 'min:1', new IsValidSlackOrDiscordUrl()], + 'ntfy_server' => ['nullable', 'url', 'min:1'], + 'ntfy_user' => ['required_with:ntfy_pass,ntfy_auth', 'nullable', 'string', 'min:1'], + 'ntfy_pass' => ['required_with:ntfy_user,ntfy_auth', 'nullable', 'string', 'min:1'], + ]; + foreach (config('notifications.notifications.user') as $key => $info) { + $rules[sprintf('notification_%s', $key)] = 'in:0,1'; + } + + return $rules; + } +} diff --git a/app/Notifications/ReturnsSettings.php b/app/Notifications/ReturnsSettings.php index 411a4a29a1..59f8d8b3fb 100644 --- a/app/Notifications/ReturnsSettings.php +++ b/app/Notifications/ReturnsSettings.php @@ -26,6 +26,7 @@ namespace FireflyIII\Notifications; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Support\Facades\FireflyConfig; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; class ReturnsSettings @@ -49,6 +50,13 @@ class ReturnsSettings 'ntfy_pass' => '', ]; + if('user' === $type && null !== $user) { + $settings['ntfy_server'] = Preferences::getEncryptedForUser($user, 'ntfy_server', 'https://ntfy.sh')->data; + $settings['ntfy_topic'] = Preferences::getEncryptedForUser($user, 'ntfy_topic', '')->data; + $settings['ntfy_auth'] = Preferences::getForUser($user, 'ntfy_auth', false)->data; + $settings['ntfy_user'] = Preferences::getEncryptedForUser($user, 'ntfy_user', '')->data; + $settings['ntfy_pass'] = Preferences::getEncryptedForUser($user, 'ntfy_pass', '')->data; + } if ('owner' === $type) { $settings['ntfy_server'] = FireflyConfig::getEncrypted('ntfy_server', 'https://ntfy.sh')->data; $settings['ntfy_topic'] = FireflyConfig::getEncrypted('ntfy_topic', '')->data; diff --git a/app/Notifications/Test/TestNotificationEmail.php b/app/Notifications/Test/OwnerTestNotificationEmail.php similarity index 97% rename from app/Notifications/Test/TestNotificationEmail.php rename to app/Notifications/Test/OwnerTestNotificationEmail.php index a9dc675b71..9ac18ed14b 100644 --- a/app/Notifications/Test/TestNotificationEmail.php +++ b/app/Notifications/Test/OwnerTestNotificationEmail.php @@ -32,7 +32,7 @@ use Illuminate\Notifications\Notification; /** * Class TestNotification */ -class TestNotificationEmail extends Notification +class OwnerTestNotificationEmail extends Notification { use Queueable; diff --git a/app/Notifications/Test/TestNotificationNtfy.php b/app/Notifications/Test/OwnerTestNotificationNtfy.php similarity index 98% rename from app/Notifications/Test/TestNotificationNtfy.php rename to app/Notifications/Test/OwnerTestNotificationNtfy.php index aa7e322af5..4f57590dc2 100644 --- a/app/Notifications/Test/TestNotificationNtfy.php +++ b/app/Notifications/Test/OwnerTestNotificationNtfy.php @@ -36,7 +36,7 @@ use Wijourdil\NtfyNotificationChannel\Channels\NtfyChannel; /** * Class TestNotification */ -class TestNotificationNtfy extends Notification +class OwnerTestNotificationNtfy extends Notification { use Queueable; diff --git a/app/Notifications/Test/TestNotificationPushover.php b/app/Notifications/Test/OwnerTestNotificationPushover.php similarity index 97% rename from app/Notifications/Test/TestNotificationPushover.php rename to app/Notifications/Test/OwnerTestNotificationPushover.php index 2f78fab318..4d71f095ab 100644 --- a/app/Notifications/Test/TestNotificationPushover.php +++ b/app/Notifications/Test/OwnerTestNotificationPushover.php @@ -36,7 +36,7 @@ use NotificationChannels\Pushover\PushoverMessage; /** * Class TestNotification */ -class TestNotificationPushover extends Notification +class OwnerTestNotificationPushover extends Notification { use Queueable; diff --git a/app/Notifications/Test/OwnerTestNotificationSlack.php b/app/Notifications/Test/OwnerTestNotificationSlack.php new file mode 100644 index 0000000000..55dce8f62a --- /dev/null +++ b/app/Notifications/Test/OwnerTestNotificationSlack.php @@ -0,0 +1,86 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Notifications\Test; + +use FireflyIII\Notifications\Notifiables\OwnerNotifiable; +use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Messages\SlackMessage; +use Illuminate\Notifications\Notification; + +// use Illuminate\Notifications\Slack\SlackMessage; + +/** + * Class TestNotification + */ +class OwnerTestNotificationSlack extends Notification +{ + use Queueable; + + private OwnerNotifiable $owner; + + /** + * Create a new notification instance. + */ + public function __construct(OwnerNotifiable $owner) + { + $this->owner = $owner; + } + + /** + * Get the array representation of the notification. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return array + */ + public function toArray(OwnerNotifiable $notifiable) + { + return [ + ]; + } + + /** + * Get the Slack representation of the notification. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function toSlack(OwnerNotifiable $notifiable) + { + return new SlackMessage()->content((string) trans('email.admin_test_subject')); + // return new SlackMessage()->text((string) trans('email.admin_test_subject'))->to($url); + } + + /** + * Get the notification's delivery channels. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return array + */ + public function via(OwnerNotifiable $notifiable) + { + return ['slack']; + } +} diff --git a/app/Notifications/Test/UserTestNotificationEmail.php b/app/Notifications/Test/UserTestNotificationEmail.php new file mode 100644 index 0000000000..d617756849 --- /dev/null +++ b/app/Notifications/Test/UserTestNotificationEmail.php @@ -0,0 +1,95 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Notifications\Test; + +use FireflyIII\Notifications\Notifiables\OwnerNotifiable; +use FireflyIII\User; +use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Notifications\Notification; + +/** + * Class TestNotification + */ +class UserTestNotificationEmail extends Notification +{ + use Queueable; + + private User $user; + + /** + * Create a new notification instance. + */ + public function __construct(User $user) + { + $this->user = $user; + } + + /** + * Get the array representation of the notification. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return array + */ + public function toArray(User $notifiable) + { + return [ + ]; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return MailMessage + */ + public function toMail(User $notifiable) + { + $address = (string) $notifiable->email; + + return (new MailMessage()) + ->markdown('emails.admin-test', ['email' => $address]) + ->subject((string) trans('email.admin_test_subject')) + ; + } + + /** + * Get the notification's delivery channels. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param User $notifiable + * + * @return array + */ + public function via(User $notifiable) + { + return ['mail']; + } +} diff --git a/app/Notifications/Test/UserTestNotificationNtfy.php b/app/Notifications/Test/UserTestNotificationNtfy.php new file mode 100644 index 0000000000..15e31fdafd --- /dev/null +++ b/app/Notifications/Test/UserTestNotificationNtfy.php @@ -0,0 +1,99 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Notifications\Test; + +use FireflyIII\Notifications\ReturnsSettings; +use FireflyIII\User; +use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Notification; +use Ntfy\Message; +use Wijourdil\NtfyNotificationChannel\Channels\NtfyChannel; + +// use Illuminate\Notifications\Slack\SlackMessage; + +/** + * Class TestNotification + */ +class UserTestNotificationNtfy extends Notification +{ + use Queueable; + + public User $user; + + /** + * Create a new notification instance. + */ + public function __construct(User $user) + { + $this->user = $user; + } + + /** + * Get the array representation of the notification. + * + * @param User $notifiable + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return array + */ + public function toArray(User $notifiable) + { + return [ + ]; + } + + public function toNtfy(User $user): Message + { + $settings = ReturnsSettings::getSettings('ntfy', 'user', $user); + + // overrule config. + config(['ntfy-notification-channel.server' => $settings['ntfy_server']]); + config(['ntfy-notification-channel.topic' => $settings['ntfy_topic']]); + + if ($settings['ntfy_auth']) { + // overrule auth as well. + config(['ntfy-notification-channel.authentication.enabled' => true]); + config(['ntfy-notification-channel.authentication.username' => $settings['ntfy_user']]); + config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]); + } + + $message = new Message(); + $message->topic($settings['ntfy_topic']); + $message->title((string) trans('email.admin_test_subject')); + $message->body((string) trans('email.admin_test_message', ['channel' => 'ntfy'])); + $message->tags(['white_check_mark']); + + return $message; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function via(User $user) + { + return [NtfyChannel::class]; + } +} diff --git a/app/Notifications/Test/UserTestNotificationPushover.php b/app/Notifications/Test/UserTestNotificationPushover.php new file mode 100644 index 0000000000..3acb2cf8aa --- /dev/null +++ b/app/Notifications/Test/UserTestNotificationPushover.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Notifications\Test; + +use FireflyIII\Notifications\Notifiables\OwnerNotifiable; +use FireflyIII\User; +use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Notification; +use Illuminate\Support\Facades\Log; +use NotificationChannels\Pushover\PushoverChannel; +use NotificationChannels\Pushover\PushoverMessage; + +// use Illuminate\Notifications\Slack\SlackMessage; + +/** + * Class TestNotification + */ +class UserTestNotificationPushover extends Notification +{ + use Queueable; + + private User $user; + + /** + * Create a new notification instance. + */ + public function __construct(User $user) + { + $this->user = $user; + } + + /** + * Get the array representation of the notification. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return array + */ + public function toArray(User $notifiable) + { + return [ + ]; + } + + public function toPushover(User $notifiable): PushoverMessage + { + Log::debug('Now in (user) toPushover()'); + + return PushoverMessage::create((string)trans('email.admin_test_message', ['channel' => 'Pushover'])) + ->title((string)trans('email.admin_test_subject')) + ; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function via(User $notifiable) + { + return [PushoverChannel::class]; + } +} diff --git a/app/Notifications/Test/TestNotificationSlack.php b/app/Notifications/Test/UserTestNotificationSlack.php similarity index 97% rename from app/Notifications/Test/TestNotificationSlack.php rename to app/Notifications/Test/UserTestNotificationSlack.php index e711de7283..759c06a318 100644 --- a/app/Notifications/Test/TestNotificationSlack.php +++ b/app/Notifications/Test/UserTestNotificationSlack.php @@ -34,7 +34,7 @@ use Illuminate\Notifications\Notification; /** * Class TestNotification */ -class TestNotificationSlack extends Notification +class UserTestNotificationSlack extends Notification { use Queueable; diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 7029bff123..d6931d1da6 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -49,7 +49,8 @@ use FireflyIII\Events\Security\MFAUsedBackupCode; use FireflyIII\Events\Security\UnknownUserAttemptedLogin; use FireflyIII\Events\StoredAccount; use FireflyIII\Events\StoredTransactionGroup; -use FireflyIII\Events\Test\TestNotificationChannel; +use FireflyIII\Events\Test\OwnerTestNotificationChannel; +use FireflyIII\Events\Test\UserTestNotificationChannel; use FireflyIII\Events\TriggeredAuditLog; use FireflyIII\Events\UpdatedAccount; use FireflyIII\Events\UpdatedTransactionGroup; @@ -130,13 +131,16 @@ class EventServiceProvider extends ServiceProvider RequestedNewPassword::class => [ 'FireflyIII\Handlers\Events\UserEventHandler@sendNewPassword', ], + UserTestNotificationChannel::class => [ + 'FireflyIII\Handlers\Events\UserEventHandler@sendTestNotification', + ], // is a User related event. UserChangedEmail::class => [ 'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeConfirmMail', 'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeUndoMail', ], // admin related - TestNotificationChannel::class => [ + OwnerTestNotificationChannel::class => [ 'FireflyIII\Handlers\Events\AdminEventHandler@sendTestNotification', ], NewVersionAvailable::class => [ diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index af9c24c3b9..d9cb791eb9 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -105,6 +105,26 @@ class Preferences return $this->getForUser($user, $name, $default); } + public function getEncryptedForUser(User $user, string $name, null|array|bool|int|string $default = null): ?Preference + { + $result = $this->getForUser($user, $name, $default); + if ('' === $result->data) { + Log::warning(sprintf('Empty encrypted preference found: "%s"', $name)); + + return $result; + } + + try { + $result->data = decrypt($result->data); + } catch (DecryptException $e) { + Log::error(sprintf('Could not decrypt preference "%s": %s', $name, $e->getMessage())); + + return $result; + } + + + return $result; + } public function getForUser(User $user, string $name, null|array|bool|int|string $default = null): ?Preference { diff --git a/app/User.php b/app/User.php index 82484f477a..00ca3d2e74 100644 --- a/app/User.php +++ b/app/User.php @@ -258,38 +258,38 @@ class User extends Authenticatable app('log')->debug(sprintf('in hasAnyRoleInGroup(%s)', implode(', ', $roles))); /** @var Collection $dbRoles */ - $dbRoles = UserRole::whereIn('title', $roles)->get(); + $dbRoles = UserRole::whereIn('title', $roles)->get(); if (0 === $dbRoles->count()) { app('log')->error(sprintf('Could not find role(s): %s. Probably migration mishap.', implode(', ', $roles))); return false; } - $dbRolesIds = $dbRoles->pluck('id')->toArray(); - $dbRolesTitles = $dbRoles->pluck('title')->toArray(); + $dbRolesIds = $dbRoles->pluck('id')->toArray(); + $dbRolesTitles = $dbRoles->pluck('title')->toArray(); /** @var Collection $groupMemberships */ $groupMemberships = $this->groupMemberships()->whereIn('user_role_id', $dbRolesIds)->where('user_group_id', $userGroup->id)->get(); if (0 === $groupMemberships->count()) { app('log')->error(sprintf( - 'User #%d "%s" does not have roles %s in user group #%d "%s"', - $this->id, - $this->email, - implode(', ', $roles), - $userGroup->id, - $userGroup->title - )); + 'User #%d "%s" does not have roles %s in user group #%d "%s"', + $this->id, + $this->email, + implode(', ', $roles), + $userGroup->id, + $userGroup->title + )); return false; } foreach ($groupMemberships as $membership) { app('log')->debug(sprintf( - 'User #%d "%s" has role "%s" in user group #%d "%s"', - $this->id, - $this->email, - $membership->userRole->title, - $userGroup->id, - $userGroup->title - )); + 'User #%d "%s" has role "%s" in user group #%d "%s"', + $this->id, + $this->email, + $membership->userRole->title, + $userGroup->id, + $userGroup->title + )); if (in_array($membership->userRole->title, $dbRolesTitles, true)) { app('log')->debug(sprintf('Return true, found role "%s"', $membership->userRole->title)); @@ -297,13 +297,13 @@ class User extends Authenticatable } } app('log')->error(sprintf( - 'User #%d "%s" does not have roles %s in user group #%d "%s"', - $this->id, - $this->email, - implode(', ', $roles), - $userGroup->id, - $userGroup->title - )); + 'User #%d "%s" does not have roles %s in user group #%d "%s"', + $this->id, + $this->email, + implode(', ', $roles), + $userGroup->id, + $userGroup->title + )); return false; } @@ -355,13 +355,13 @@ class User extends Authenticatable */ public function routeNotificationFor($driver, $notification = null) { - $method = 'routeNotificationFor'.Str::studly($driver); + $method = 'routeNotificationFor' . Str::studly($driver); if (method_exists($this, $method)) { return $this->{$method}($notification); // @phpstan-ignore-line } - $email = $this->email; + $email = $this->email; // see if user has alternative email address: - $pref = app('preferences')->getForUser($this, 'remote_guard_alt_email'); + $pref = app('preferences')->getForUser($this, 'remote_guard_alt_email'); if (null !== $pref) { $email = $pref->data; } @@ -371,8 +371,8 @@ class User extends Authenticatable } return match ($driver) { - 'mail' => $email, - default => null, + 'mail' => $email, + default => null, }; } @@ -392,22 +392,12 @@ class User extends Authenticatable return $this->belongsToMany(Role::class); } - public function routeNotificationForPushover(Notification $notification) + public function routeNotificationForPushover() { - // this check does not validate if the user is owner, Should be done by notification itself. - $appToken = (string) app('fireflyconfig')->getEncrypted('pushover_app_token', '')->data; - $userToken = (string) app('fireflyconfig')->getEncrypted('pushover_user_token', '')->data; + $appToken = (string) app('preferences')->getEncrypted('pushover_app_token', '')->data; + $userToken = (string) app('preferences')->getEncrypted('pushover_user_token', '')->data; - if (property_exists($notification, 'type') && 'owner' === $notification->type) { - return PushoverReceiver::withUserKey($userToken) - ->withApplicationToken($appToken) - ; - } - - throw new FireflyException('No pushover token found.'); - // return PushoverReceiver::withUserKey((string) config('services.pushover.user_token')) - // ->withApplicationToken((string) config('services.pushover.token')); - // return (string) config('services.pushover.token'); + return PushoverReceiver::withUserKey($userToken)->withApplicationToken($appToken); } /** @@ -416,11 +406,11 @@ class User extends Authenticatable 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')->getEncrypted('slack_webhook_url', '')->data; + $res = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data; if (is_array($res)) { $res = ''; } - $res = (string) $res; + $res = (string) $res; if (property_exists($notification, 'type') && 'owner' === $notification->type) { return $res; diff --git a/public/v1/js/ff/preferences/index.js b/public/v1/js/ff/preferences/index.js index ad3a19aa9f..5709280536 100644 --- a/public/v1/js/ff/preferences/index.js +++ b/public/v1/js/ff/preferences/index.js @@ -23,10 +23,19 @@ $(document).ready(function () { "use strict"; if (!Modernizr.inputtypes.date) { - $('input[type="date"]').datepicker( - { - dateFormat: 'yy-mm-dd' - } - ); + $('input[type="date"]').datepicker({ + dateFormat: 'yy-mm-dd' + }); } + $('.submit-test').click(submitTest); }); + +function submitTest(e) { + var current = $(e.currentTarget); + var channel = current.data('channel'); + + $.post(postUrl, {channel: channel}, function () { + window.location.reload(true); + }); + return false; +} diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index ed47e648b3..5a4aa01a2a 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1290,6 +1290,7 @@ return [ 'create_recurring_from_transaction' => 'Create recurring transaction based on transaction', // preferences + 'test_notifications_buttons' => 'To test your configuration, use the buttons below. Please note that the buttons have no spam control.', 'dark_mode_option_browser' => 'Let your browser decide', 'dark_mode_option_light' => 'Always light', 'dark_mode_option_dark' => 'Always dark', @@ -1388,7 +1389,7 @@ return [ 'pref_notifications' => 'Notifications', 'pref_notifications_help' => 'Indicate if these are notifications you would like to get. Some notifications may contain sensitive financial information.', 'pref_notifications_settings' => 'Notifications settings', - 'pref_notifications_settings_help' => 'Use these settings to configure your notification channels. Please note that notifications will be sent to ALL channels.', + 'pref_notifications_settings_help' => 'Use these settings to configure your notification channels. Please note that notifications will be sent to ALL channels. Please save your settings FIRST.', 'slack_url_label' => 'Slack "incoming webhook" URL', 'discord_url_label' => 'Discord webhook URL', @@ -2481,7 +2482,7 @@ return [ 'delete_user' => 'Delete user :email', 'user_deleted' => 'The user has been deleted', 'send_test_email' => 'Send test email message', - 'send_test_email_text' => 'To see if your installation is capable of sending email or posting Slack messages, please press this button. You will not see an error here (if any), the log files will reflect any errors. You can press this button as many times as you like. There is no spam control. The message will be sent to :email and should arrive shortly.', + 'send_test_email_text' => 'To see if your installation is capable of sending a notification, please press this button. You will not see an error here (if any), the log files will reflect any errors. You can press this button as many times as you like. There is no spam control. The message will be sent to :email and should arrive shortly.', 'send_message' => 'Send message', 'send_test_triggered' => 'Test was triggered. Check your inbox and the log files.', 'give_admin_careful' => 'Users who are given admin rights can take away yours. Be careful.', @@ -2514,7 +2515,7 @@ return [ 'notification_channel_name_pushover' => 'Pushover', 'notification_channel_name_gotify' => 'Gotify', 'notification_channel_name_pushbullet' => 'Pushbullet', - 'channel_not_available' => 'not available yet', + 'channel_not_available' => 'not available', 'configure_channel_in_env' => 'needs environment variables', 'test_notification_channel_name_email' => 'Test email', 'test_notification_channel_name_slack' => 'Test Slack', diff --git a/resources/views/preferences/index.twig b/resources/views/preferences/index.twig index 69ca437163..57cc77e50f 100644 --- a/resources/views/preferences/index.twig +++ b/resources/views/preferences/index.twig @@ -307,13 +307,33 @@ {# view range #}

{{ 'pref_notifications'|_ }}

-

{{ 'pref_notifications_help'|_ }}

+

+ {{ 'available_channels_expl'|_ }} +

+ +

{{ 'pref_notifications_help'|_ }}

{% for id, info in notifications %}
@@ -325,8 +345,8 @@

{{ 'pref_notifications_settings'|_ }}

-

{{ 'pref_notifications_settings_help'|_ }}

- {{ ExpandedForm.text('slackUrl',slackUrl,{'label' : 'slack_url_label'|_, helpText: 'slack_discord_double'|_}) }} +

{{ 'pref_notifications_settings_help'|_ }}

+ {{ ExpandedForm.text('slack_webhook_url',slackUrl,{'label' : 'slack_url_label'|_, helpText: 'slack_discord_double'|_}) }} {{ ExpandedForm.text('pushover_app_token', pushoverAppToken, {}) }} {{ ExpandedForm.text('pushover_user_token', pushoverUserToken, {}) }} @@ -336,7 +356,18 @@ {{ ExpandedForm.checkbox('ntfy_auth','1', ntfyAuth, {}) }} {{ ExpandedForm.text('ntfy_user', ntfyUser, {}) }} {{ ExpandedForm.passwordWithValue('ntfy_pass', ntfyPass, {}) }} - +

+ {{ 'pref_notifications_settings_help'|_ }} +

+
+ {% for name,info in channels %} + {% if true == info.enabled and true == forcedAvailability[name] %} + + {{ trans('firefly.test_notification_channel_name_'~name) }} + + {% endif %} + {% endfor %} +
@@ -349,7 +380,7 @@
- +
@@ -357,6 +388,9 @@ {% endblock %} {% block scripts %} + diff --git a/routes/web.php b/routes/web.php index 04b98d3afe..659726d09d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -806,6 +806,7 @@ Route::group( static function (): void { Route::get('', ['uses' => 'PreferencesController@index', 'as' => 'index']); Route::post('', ['uses' => 'PreferencesController@postIndex', 'as' => 'update']); + Route::post('test-notification', ['uses' => 'PreferencesController@testNotification', 'as' => 'test-notification']); } );