]> BookStack Code Mirror - bookstack/commitdiff
Notifications: added user preference UI & logic
authorDan Brown <redacted>
Tue, 25 Jul 2023 16:06:48 +0000 (17:06 +0100)
committerDan Brown <redacted>
Tue, 25 Jul 2023 16:08:40 +0000 (17:08 +0100)
Includes testing to cover.
Also added file missing from previous commit.

app/Activity/Notifications/NotificationManager.php [new file with mode: 0644]
app/Settings/UserNotificationPreferences.php [new file with mode: 0644]
app/Users/Controllers/UserPreferencesController.php
lang/en/preferences.php
resources/views/users/preferences/notifications.blade.php [new file with mode: 0644]
routes/web.php
tests/User/UserPreferencesTest.php

diff --git a/app/Activity/Notifications/NotificationManager.php b/app/Activity/Notifications/NotificationManager.php
new file mode 100644 (file)
index 0000000..5864b84
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+namespace BookStack\Activity\Notifications;
+
+use BookStack\Activity\ActivityType;
+use BookStack\Activity\Models\Loggable;
+use BookStack\Activity\Notifications\Handlers\CommentCreationNotificationHandler;
+use BookStack\Activity\Notifications\Handlers\NotificationHandler;
+use BookStack\Activity\Notifications\Handlers\PageCreationNotificationHandler;
+use BookStack\Activity\Notifications\Handlers\PageUpdateNotificationHandler;
+
+class NotificationManager
+{
+    /**
+     * @var class-string<NotificationHandler>[]
+     */
+    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<NotificationHandler> $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 (file)
index 0000000..5b267b5
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+namespace BookStack\Settings;
+
+use BookStack\Users\Models\User;
+
+class UserNotificationPreferences
+{
+    public function __construct(
+        protected User $user
+    ) {
+    }
+
+    public function notifyOnOwnPageChanges(): bool
+    {
+        return $this->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);
+    }
+}
index b20a8aa37220678e496fa6855a216c076a2ccab7..faa99629b349db365b1f39918f7d91944657bc7c 100644 (file)
@@ -3,17 +3,16 @@
 namespace BookStack\Users\Controllers;
 
 use BookStack\Http\Controller;
 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
 {
 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');
     }
 
         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.
      */
     /**
      * 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'],
     {
         $validated = $this->validate($request, [
             'language' => ['required', 'string', 'max:20'],
-            'active'   => ['required', 'bool'],
+            'active' => ['required', 'bool'],
         ]);
 
         $currentFavoritesStr = setting()->getForCurrentUser('code-language-favourites', '');
         ]);
 
         $currentFavoritesStr = setting()->getForCurrentUser('code-language-favourites', '');
index e9a47461b3d18a42a68699d729acf99b07b9eda5..2ade3a0e487fc49a70c092189690a552ddcb315b 100644 (file)
@@ -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!',
     '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 (file)
index 0000000..3a301ae
--- /dev/null
@@ -0,0 +1,45 @@
+@extends('layouts.simple')
+
+@section('body')
+    <div class="container small my-xl">
+
+        <section class="card content-wrap auto-height">
+            <form action="{{ url('/preferences/notifications') }}" method="post">
+                {{ method_field('put') }}
+                {{ csrf_field() }}
+
+                <h1 class="list-heading">{{ trans('preferences.notifications') }}</h1>
+                <p class="text-small text-muted">{{ trans('preferences.notifications_desc') }}</p>
+
+                <div class="toggle-switch-list">
+                    <div>
+                        @include('form.toggle-switch', [
+                            'name' => 'preferences[own-page-changes]',
+                            'value' => $preferences->notifyOnOwnPageChanges(),
+                            'label' => trans('preferences.notifications_opt_own_page_changes'),
+                        ])
+                    </div>
+                    <div>
+                        @include('form.toggle-switch', [
+                            'name' => 'preferences[own-page-comments]',
+                            'value' => $preferences->notifyOnOwnPageComments(),
+                            'label' => trans('preferences.notifications_opt_own_page_comments'),
+                        ])
+                    </div>
+                    <div>
+                        @include('form.toggle-switch', [
+                            'name' => 'preferences[comment-replies]',
+                            'value' => $preferences->notifyOnCommentReplies(),
+                            'label' => trans('preferences.notifications_opt_comment_replies'),
+                        ])
+                    </div>
+                </div>
+
+                <div class="form-group text-right">
+                    <button class="button">{{ trans('preferences.notifications_save') }}</button>
+                </div>
+            </form>
+        </section>
+
+    </div>
+@stop
index 74ee74a2c77c39c009cf311b8a5bcd77f5b0a0fe..9ea44f03c632b5068c9af968b42618fcd2598b84 100644 (file)
@@ -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::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']);
     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']);
index e47a259a5bad6279b1d2942c0025ec53b90a9ecc..e83df57317b973a3da5cce1053f45b94f7cde2f0 100644 (file)
@@ -45,6 +45,31 @@ class UserPreferencesTest extends TestCase
         $this->withHtml($this->get('/'))->assertElementExists('body[component="shortcuts"]');
     }
 
         $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();
     public function test_update_sort_preference()
     {
         $editor = $this->users->editor();