Skip to content

Instantly share code, notes, and snippets.

@ssddanbrown
Created December 1, 2022 03:38
Show Gist options
  • Save ssddanbrown/800c762ef53b26700ace2416f3cf0697 to your computer and use it in GitHub Desktop.
Save ssddanbrown/800c762ef53b26700ace2416f3cf0697 to your computer and use it in GitHub Desktop.
bookstack-notify-page-updates-for-tagged-books

This is a hack to BookStack, using the theme system, so that you can configure notifications to be sent to users within roles defined via tags applied to parent books. For example, if a tag with name Notify and value Admins, Viewers is applied to a book, updates to pages within will be notified via email to all users within the "Admins" and "Viewers" roles.

Setup

This uses the logical theme system.

  1. Within the BookStack install folder, you should have a themes folder.
  2. Create a themes/custom/functions.php file with the contents of the functions.php file example below.
  3. Customize the email message, if desired, by editing the lines of text within the toMail part at around lines 36-39.
  4. Add APP_THEME=custom to your .env file.

Note

The sending of emails may slow down page update actions, and these could be noisy if a user edits a page many times quickly. You may run into email system rate-limits with the amount of emails being sent. These customizations are not officially supported any may break upon, or conflict with, future updates. Quickly tested on BookStack v22.11.

<?php
use BookStack\Actions\ActivityType;
use BookStack\Actions\Tag;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Page;
use BookStack\Facades\Theme;
use BookStack\Notifications\MailNotification;
use BookStack\Theming\ThemeEvents;
use Illuminate\Notifications\Messages\MailMessage;
// This customization notifies page updates to users within roles named via a tag on a parent book.
// For example, If a tag, with name `Notify` and value `Admins, Viewers` is applied to a book, updates to pages within
// will be notified via email to all users within the "Admins" and "Viewers" roles.
// Note: This is not officially supported, may break upon update, and the email sending may slow down operations.
// Also, users could be spammed with emails on repeated updates.
// Also, might hit email system rate-limits.
// Also, this relies on role names being stable.
// This notification class represents the notification that'll be sent to users.
// The text of the notification can be customized within the 'toMail' function.
class PageUpdatedNotification extends MailNotification {
protected Page $page;
protected User $updater;
public function __construct(Page $page, User $updater)
{
$this->page = $page;
$this->updater = $updater;
}
public function toMail($notifiable)
{
return (new MailMessage())
->subject('BookStack page update notification')
->line("The page \"{$this->page->name}\" has been updated by \"{$this->updater->name}\"")
->action('View Page', $this->page->getUrl());
}
}
// This function does the work of sending notifications to the
// relevant users that are in roles denoted by a tag on the parent book.
function notifyRequiredUsers(Page $page) {
// Get our relevant tag
/** @var ?Tag $notifyTag */
$notifyTag = Tag::query()
->where('entity_type', '=', 'book')
->where('entity_id', '=', $page->book_id)
->where('name', '=', 'notify')
->first();
if (!$notifyTag) {
return;
}
// Get the roles named via the tag
$roleNames = array_filter(array_map(fn($str) => trim($str) , explode(',', $notifyTag->value)));
$roleIds = Role::query()->whereIn('display_name', $roleNames)->pluck('id');
if (count($roleNames) === 0 || $roleIds->isEmpty()) {
return;
}
// Get the users we want to notify, and the user that updated the page
$usersToNotify = User::query()
->whereExists(function($query) use ($roleIds) {
$query->select('user_id')
->from('role_user')
->whereColumn('users.id', '=', 'role_user.user_id')
->whereIn('role_id', $roleIds);
})
->where('id', '!=', $page->updated_by)
->get();
$updater = User::query()->findOrFail($page->updated_by);
// Send a notification to each of the users we want to notify
foreach ($usersToNotify as $user) {
$user->notify(new PageUpdatedNotification($page, $updater));
usleep(100000); // Slight 0.1s delay to help rate limit abuse
}
}
// Listen to page update events and kick-start our notification logic
Theme::listen(ThemeEvents::ACTIVITY_LOGGED, function(string $type, $detail) {
if ($type === ActivityType::PAGE_UPDATE && $detail instanceof Page) {
notifyRequiredUsers($detail);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment