3 use BookStack\Activity\ActivityType;
4 use BookStack\Activity\Models\Tag;
5 use BookStack\Activity\Notifications\Messages\BaseActivityNotification;
6 use BookStack\Entities\Models\Page;
7 use BookStack\Facades\Theme;
8 use BookStack\Theming\ThemeEvents;
9 use BookStack\Users\Models\Role;
10 use BookStack\Users\Models\User;
11 use Illuminate\Notifications\Messages\MailMessage;
13 // This customization notifies page updates to users within roles named via a tag on a parent book.
14 // For example, If a tag, with name `Notify` and value `Admins, Viewers` is applied to a book, updates to pages within
15 // will be notified via email to all users within the "Admins" and "Viewers" roles.
16 // Note: This is not officially supported, may break upon update, and the email sending may slow down operations.
17 // Also, users could be spammed with emails on repeated updates.
18 // Also, might hit email system rate-limits.
19 // Also, this relies on role names being stable.
21 // This notification class represents the notification that'll be sent to users.
22 // The text of the notification can be customized within the 'toMail' function.
23 class PageUpdateNotification extends BaseActivityNotification {
24 public function toMail(User $notifiable): MailMessage
26 /** @var Page $page */
27 $page = $this->detail;
28 $updater = $this->user;
30 return (new MailMessage())
31 ->subject('BookStack page update notification')
32 ->line("The page \"{$page->name}\" has been updated by \"{$updater->name}\"")
33 ->action('View Page', $page->getUrl());
37 // This function does the work of sending notifications to the
38 // relevant users that are in roles denoted by a tag on the parent book.
39 function notifyRequiredUsers(Page $page) {
41 // Get our relevant tag
42 /** @var ?Tag $notifyTag */
43 $notifyTag = Tag::query()
44 ->where('entity_type', '=', 'book')
45 ->where('entity_id', '=', $page->book_id)
46 ->where('name', '=', 'notify')
52 // Get the roles named via the tag
53 $roleNames = array_filter(array_map(fn ($str) => trim($str), explode(',', $notifyTag->value)));
54 $roleIds = Role::query()->whereIn('display_name', $roleNames)->pluck('id');
55 if (count($roleNames) === 0 || $roleIds->isEmpty()) {
59 // Get the users we want to notify, and the user that updated the page
60 $usersToNotify = User::query()
61 ->whereExists(function ($query) use ($roleIds) {
62 $query->select('user_id')
64 ->whereColumn('users.id', '=', 'role_user.user_id')
65 ->whereIn('role_id', $roleIds);
67 ->where('id', '!=', $page->updated_by)
69 $updater = User::query()->findOrFail($page->updated_by);
71 // Send a notification to each of the users we want to notify
72 foreach ($usersToNotify as $user) {
73 $user->notify(new PageUpdateNotification($page, $updater));
74 usleep(100000); // Slight 0.1s delay to help rate limit abuse
78 // Listen to page update events and kick-start our notification logic
79 Theme::listen(ThemeEvents::ACTIVITY_LOGGED, function (string $type, $detail) {
80 if ($type === ActivityType::PAGE_UPDATE && $detail instanceof Page) {
81 notifyRequiredUsers($detail);