Includes testing to cover.
Also added file missing from previous commit.
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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);
+ }
+}
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
+ ) {
}
/**
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.
*/
{
$validated = $this->validate($request, [
'language' => ['required', 'string', 'max:20'],
- 'active' => ['required', 'bool'],
+ 'active' => ['required', 'bool'],
]);
$currentFavoritesStr = setting()->getForCurrentUser('code-language-favourites', '');
'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!',
+];
--- /dev/null
+@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
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']);
$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();