From 100b28707cbda87a57d5f1d31c6e33f88642caa6 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 25 Jul 2023 17:06:48 +0100 Subject: [PATCH] Notifications: added user preference UI & logic Includes testing to cover. Also added file missing from previous commit. --- .../Notifications/NotificationManager.php | 49 +++++++++++++++++++ app/Settings/UserNotificationPreferences.php | 46 +++++++++++++++++ .../Controllers/UserPreferencesController.php | 40 ++++++++++++--- lang/en/preferences.php | 10 +++- .../users/preferences/notifications.blade.php | 45 +++++++++++++++++ routes/web.php | 2 + tests/User/UserPreferencesTest.php | 25 ++++++++++ 7 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 app/Activity/Notifications/NotificationManager.php create mode 100644 app/Settings/UserNotificationPreferences.php create mode 100644 resources/views/users/preferences/notifications.blade.php diff --git a/app/Activity/Notifications/NotificationManager.php b/app/Activity/Notifications/NotificationManager.php new file mode 100644 index 000000000..5864b8456 --- /dev/null +++ b/app/Activity/Notifications/NotificationManager.php @@ -0,0 +1,49 @@ +[] + */ + protected array $handlers = []; + + public function handle(string $activityType, string|Loggable $detail): void + { + $handlersToRun = $this->handlers[$activityType] ?? []; + foreach ($handlersToRun as $handlerClass) { + /** @var NotificationHandler $handler */ + $handler = app()->make($handlerClass); + $handler->handle($activityType, $detail); + } + } + + /** + * @param class-string $handlerClass + */ + public function registerHandler(string $activityType, string $handlerClass): void + { + if (!isset($this->handlers[$activityType])) { + $this->handlers[$activityType] = []; + } + + if (!in_array($handlerClass, $this->handlers[$activityType])) { + $this->handlers[$activityType][] = $handlerClass; + } + } + + public function loadDefaultHandlers(): void + { + $this->registerHandler(ActivityType::PAGE_CREATE, PageCreationNotificationHandler::class); + $this->registerHandler(ActivityType::PAGE_UPDATE, PageUpdateNotificationHandler::class); + $this->registerHandler(ActivityType::COMMENT_CREATE, CommentCreationNotificationHandler::class); + } +} diff --git a/app/Settings/UserNotificationPreferences.php b/app/Settings/UserNotificationPreferences.php new file mode 100644 index 000000000..5b267b533 --- /dev/null +++ b/app/Settings/UserNotificationPreferences.php @@ -0,0 +1,46 @@ +getNotificationSetting('own-page-changes'); + } + + public function notifyOnOwnPageComments(): bool + { + return $this->getNotificationSetting('own-page-comments'); + } + + public function notifyOnCommentReplies(): bool + { + return $this->getNotificationSetting('comment-replies'); + } + + public function updateFromSettingsArray(array $settings) + { + $allowList = ['own-page-changes', 'own-page-comments', 'comment-replies']; + foreach ($settings as $setting => $status) { + if (!in_array($setting, $allowList)) { + continue; + } + + $value = $status === 'true' ? 'true' : 'false'; + setting()->putUser($this->user, 'notifications#' . $setting, $value); + } + } + + protected function getNotificationSetting(string $key): bool + { + return setting()->getUser($this->user, 'notifications#' . $key); + } +} diff --git a/app/Users/Controllers/UserPreferencesController.php b/app/Users/Controllers/UserPreferencesController.php index b20a8aa37..faa99629b 100644 --- a/app/Users/Controllers/UserPreferencesController.php +++ b/app/Users/Controllers/UserPreferencesController.php @@ -3,17 +3,16 @@ namespace BookStack\Users\Controllers; use BookStack\Http\Controller; +use BookStack\Settings\UserNotificationPreferences; use BookStack\Settings\UserShortcutMap; use BookStack\Users\UserRepo; use Illuminate\Http\Request; class UserPreferencesController extends Controller { - protected UserRepo $userRepo; - - public function __construct(UserRepo $userRepo) - { - $this->userRepo = $userRepo; + public function __construct( + protected UserRepo $userRepo + ) { } /** @@ -47,6 +46,35 @@ class UserPreferencesController extends Controller return redirect('/preferences/shortcuts'); } + /** + * Show the notification preferences for the current user. + */ + public function showNotifications() + { + $preferences = (new UserNotificationPreferences(user())); + + return view('users.preferences.notifications', [ + 'preferences' => $preferences, + ]); + } + + /** + * Update the notification preferences for the current user. + */ + public function updateNotifications(Request $request) + { + $data = $this->validate($request, [ + 'preferences' => ['required', 'array'], + 'preferences.*' => ['required', 'string'], + ]); + + $preferences = (new UserNotificationPreferences(user())); + $preferences->updateFromSettingsArray($data['preferences']); + $this->showSuccessNotification(trans('preferences.notifications_update_success')); + + return redirect('/preferences/notifications'); + } + /** * Update the preferred view format for a list view of the given type. */ @@ -123,7 +151,7 @@ class UserPreferencesController extends Controller { $validated = $this->validate($request, [ 'language' => ['required', 'string', 'max:20'], - 'active' => ['required', 'bool'], + 'active' => ['required', 'bool'], ]); $currentFavoritesStr = setting()->getForCurrentUser('code-language-favourites', ''); diff --git a/lang/en/preferences.php b/lang/en/preferences.php index e9a47461b..2ade3a0e4 100644 --- a/lang/en/preferences.php +++ b/lang/en/preferences.php @@ -15,4 +15,12 @@ return [ 'shortcuts_save' => 'Save Shortcuts', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Shortcut preferences have been updated!', -]; \ No newline at end of file + + 'notifications' => 'Notification Preferences', + 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', + 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', + 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', + 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', + 'notifications_save' => 'Save Preferences', + 'notifications_update_success' => 'Notification preferences have been updated!', +]; diff --git a/resources/views/users/preferences/notifications.blade.php b/resources/views/users/preferences/notifications.blade.php new file mode 100644 index 000000000..3a301aeb6 --- /dev/null +++ b/resources/views/users/preferences/notifications.blade.php @@ -0,0 +1,45 @@ +@extends('layouts.simple') + +@section('body') +
+ +
+
+ {{ method_field('put') }} + {{ csrf_field() }} + +

{{ trans('preferences.notifications') }}

+

{{ trans('preferences.notifications_desc') }}

+ +
+
+ @include('form.toggle-switch', [ + 'name' => 'preferences[own-page-changes]', + 'value' => $preferences->notifyOnOwnPageChanges(), + 'label' => trans('preferences.notifications_opt_own_page_changes'), + ]) +
+
+ @include('form.toggle-switch', [ + 'name' => 'preferences[own-page-comments]', + 'value' => $preferences->notifyOnOwnPageComments(), + 'label' => trans('preferences.notifications_opt_own_page_comments'), + ]) +
+
+ @include('form.toggle-switch', [ + 'name' => 'preferences[comment-replies]', + 'value' => $preferences->notifyOnCommentReplies(), + 'label' => trans('preferences.notifications_opt_comment_replies'), + ]) +
+
+ +
+ +
+
+
+ +
+@stop diff --git a/routes/web.php b/routes/web.php index 74ee74a2c..9ea44f03c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -231,6 +231,8 @@ Route::middleware('auth')->group(function () { Route::redirect('/preferences', '/'); Route::get('/preferences/shortcuts', [UserControllers\UserPreferencesController::class, 'showShortcuts']); Route::put('/preferences/shortcuts', [UserControllers\UserPreferencesController::class, 'updateShortcuts']); + Route::get('/preferences/notifications', [UserControllers\UserPreferencesController::class, 'showNotifications']); + Route::put('/preferences/notifications', [UserControllers\UserPreferencesController::class, 'updateNotifications']); Route::patch('/preferences/change-view/{type}', [UserControllers\UserPreferencesController::class, 'changeView']); Route::patch('/preferences/change-sort/{type}', [UserControllers\UserPreferencesController::class, 'changeSort']); Route::patch('/preferences/change-expansion/{type}', [UserControllers\UserPreferencesController::class, 'changeExpansion']); diff --git a/tests/User/UserPreferencesTest.php b/tests/User/UserPreferencesTest.php index e47a259a5..e83df5731 100644 --- a/tests/User/UserPreferencesTest.php +++ b/tests/User/UserPreferencesTest.php @@ -45,6 +45,31 @@ class UserPreferencesTest extends TestCase $this->withHtml($this->get('/'))->assertElementExists('body[component="shortcuts"]'); } + public function test_notification_preferences_updating() + { + $this->asEditor(); + + // View preferences with defaults + $resp = $this->get('/preferences/notifications'); + $resp->assertSee('Notification Preferences'); + + $html = $this->withHtml($resp); + $html->assertFieldHasValue('preferences[comment-replies]', 'false'); + + // Update preferences + $resp = $this->put('/preferences/notifications', [ + 'preferences' => ['comment-replies' => 'true'], + ]); + + $resp->assertRedirect('/preferences/notifications'); + $resp->assertSessionHas('success', 'Notification preferences have been updated!'); + + // View updates to preferences page + $resp = $this->get('/preferences/notifications'); + $html = $this->withHtml($resp); + $html->assertFieldHasValue('preferences[comment-replies]', 'true'); + } + public function test_update_sort_preference() { $editor = $this->users->editor();