mirror of
https://github.com/flarum/framework.git
synced 2024-11-27 19:13:37 +08:00
More powerful/extensible notifications
- Notifications can be delivered in multiple ways (alert, email) - Different notification types can implement interfaces to allow themselves to be delivered in these various ways - User preferences for each notification type/method combination are automatically registered
This commit is contained in:
parent
49c3fa09e6
commit
bc9be30a02
|
@ -11,7 +11,6 @@ use Flarum\Core\Models\Model;
|
||||||
use Flarum\Core\Models\Forum;
|
use Flarum\Core\Models\Forum;
|
||||||
use Flarum\Core\Models\User;
|
use Flarum\Core\Models\User;
|
||||||
use Flarum\Core\Models\Discussion;
|
use Flarum\Core\Models\Discussion;
|
||||||
use Flarum\Core\Models\Notification;
|
|
||||||
use Flarum\Core\Search\GambitManager;
|
use Flarum\Core\Search\GambitManager;
|
||||||
use League\Flysystem\Adapter\Local;
|
use League\Flysystem\Adapter\Local;
|
||||||
|
|
||||||
|
@ -28,7 +27,6 @@ class CoreServiceProvider extends ServiceProvider
|
||||||
|
|
||||||
$this->registerEventHandlers($events);
|
$this->registerEventHandlers($events);
|
||||||
$this->registerPostTypes();
|
$this->registerPostTypes();
|
||||||
$this->registerNotificationTypes();
|
|
||||||
$this->registerPermissions();
|
$this->registerPermissions();
|
||||||
$this->registerGambits();
|
$this->registerGambits();
|
||||||
$this->setupModels();
|
$this->setupModels();
|
||||||
|
@ -47,6 +45,8 @@ class CoreServiceProvider extends ServiceProvider
|
||||||
*/
|
*/
|
||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
|
$this->app->register('Flarum\Core\Notifications\NotificationServiceProvider');
|
||||||
|
|
||||||
// Register a singleton entity that represents this forum. This entity
|
// Register a singleton entity that represents this forum. This entity
|
||||||
// will be used to check for global forum permissions (like viewing the
|
// will be used to check for global forum permissions (like viewing the
|
||||||
// forum, registering, and starting discussions.)
|
// forum, registering, and starting discussions.)
|
||||||
|
@ -81,14 +81,6 @@ class CoreServiceProvider extends ServiceProvider
|
||||||
'Flarum\Core\Repositories\ActivityRepositoryInterface',
|
'Flarum\Core\Repositories\ActivityRepositoryInterface',
|
||||||
'Flarum\Core\Repositories\EloquentActivityRepository'
|
'Flarum\Core\Repositories\EloquentActivityRepository'
|
||||||
);
|
);
|
||||||
$this->app->bind(
|
|
||||||
'Flarum\Core\Repositories\NotificationRepositoryInterface',
|
|
||||||
'Flarum\Core\Repositories\EloquentNotificationRepository'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->app->singleton('flarum.avatars.storage', function () {
|
|
||||||
return new Local(__DIR__.'/../../ember/public/avatars');
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->app->when('Flarum\Core\Handlers\Commands\UploadAvatarCommandHandler')
|
$this->app->when('Flarum\Core\Handlers\Commands\UploadAvatarCommandHandler')
|
||||||
->needs('League\Flysystem\FilesystemInterface')
|
->needs('League\Flysystem\FilesystemInterface')
|
||||||
|
@ -126,16 +118,10 @@ class CoreServiceProvider extends ServiceProvider
|
||||||
CommentPost::setFormatter($this->app['flarum.formatter']);
|
CommentPost::setFormatter($this->app['flarum.formatter']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerNotificationTypes()
|
|
||||||
{
|
|
||||||
Notification::addType('renamed', 'Flarum\Core\Models\Discussion');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function registerEventHandlers($events)
|
public function registerEventHandlers($events)
|
||||||
{
|
{
|
||||||
$events->subscribe('Flarum\Core\Handlers\Events\DiscussionMetadataUpdater');
|
$events->subscribe('Flarum\Core\Handlers\Events\DiscussionMetadataUpdater');
|
||||||
$events->subscribe('Flarum\Core\Handlers\Events\UserMetadataUpdater');
|
$events->subscribe('Flarum\Core\Handlers\Events\UserMetadataUpdater');
|
||||||
$events->subscribe('Flarum\Core\Handlers\Events\DiscussionRenamedNotifier');
|
|
||||||
$events->subscribe('Flarum\Core\Handlers\Events\EmailConfirmationMailer');
|
$events->subscribe('Flarum\Core\Handlers\Events\EmailConfirmationMailer');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
<?php namespace Flarum\Core\Handlers\Events;
|
<?php namespace Flarum\Core\Handlers\Events;
|
||||||
|
|
||||||
use Flarum\Core\Events\DiscussionWasRenamed;
|
use Flarum\Core\Events\DiscussionWasRenamed;
|
||||||
use Flarum\Core\Models\Notification;
|
|
||||||
use Flarum\Core\Models\DiscussionRenamedPost;
|
use Flarum\Core\Models\DiscussionRenamedPost;
|
||||||
|
use Flarum\Core\Notifications\Types\DiscussionRenamedNotification;
|
||||||
|
use Flarum\Core\Notifications\Notifier;
|
||||||
|
|
||||||
class DiscussionRenamedNotifier
|
class DiscussionRenamedNotifier
|
||||||
{
|
{
|
||||||
|
public function __construct(Notifier $notifier)
|
||||||
|
{
|
||||||
|
$this->notifier = $notifier;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the listeners for the subscriber.
|
* Register the listeners for the subscriber.
|
||||||
*
|
*
|
||||||
|
@ -44,17 +50,13 @@ class DiscussionRenamedNotifier
|
||||||
|
|
||||||
protected function sendNotification(DiscussionWasRenamed $event, DiscussionRenamedPost $post)
|
protected function sendNotification(DiscussionWasRenamed $event, DiscussionRenamedPost $post)
|
||||||
{
|
{
|
||||||
|
$notification = new DiscussionRenamedNotification(
|
||||||
$notification = Notification::notify(
|
$event->discussion->startUser,
|
||||||
$event->discussion->start_user_id,
|
$event->user,
|
||||||
'renamed',
|
$post,
|
||||||
$event->user->id,
|
$event->oldTitle
|
||||||
$event->discussion->id,
|
|
||||||
['number' => $post->number, 'oldTitle' => $event->oldTitle]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$notification->save();
|
$this->notifier->send($notification);
|
||||||
|
|
||||||
return $notification;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,13 @@ class Notification extends Model
|
||||||
protected $dates = ['time'];
|
protected $dates = ['time'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of notification types, as specified in the `type` column, to
|
*
|
||||||
* their subject classes.
|
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected static $types = [];
|
protected static $subjects = [];
|
||||||
|
|
||||||
public static function notify($userId, $type, $senderId, $subjectId, $data)
|
public static function alert($userId, $type, $senderId, $subjectId, $data)
|
||||||
{
|
{
|
||||||
$notification = new static;
|
$notification = new static;
|
||||||
|
|
||||||
|
@ -95,41 +94,41 @@ class Notification extends Model
|
||||||
// If the type value is null it is probably safe to assume we're eager loading
|
// If the type value is null it is probably safe to assume we're eager loading
|
||||||
// the relationship. When that is the case we will pass in a dummy query as
|
// the relationship. When that is the case we will pass in a dummy query as
|
||||||
// there are multiple types in the morph and we can't use single queries.
|
// there are multiple types in the morph and we can't use single queries.
|
||||||
if (is_null($type = $this->$typeColumn))
|
if (is_null($type = $this->$typeColumn)) {
|
||||||
{
|
|
||||||
return new MappedMorphTo(
|
return new MappedMorphTo(
|
||||||
$this->newQuery(), $this, $idColumn, null, $typeColumn, $name, static::$types
|
$this->newQuery(), $this, $idColumn, null, $typeColumn, $name, static::$subjects
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are not eager loading the relationship we will essentially treat this
|
// If we are not eager loading the relationship we will essentially treat this
|
||||||
// as a belongs-to style relationship since morph-to extends that class and
|
// as a belongs-to style relationship since morph-to extends that class and
|
||||||
// we will pass in the appropriate values so that it behaves as expected.
|
// we will pass in the appropriate values so that it behaves as expected.
|
||||||
else
|
else {
|
||||||
{
|
$class = static::$subjects[$type];
|
||||||
$class = static::$types[$type];
|
|
||||||
$instance = new $class;
|
$instance = new $class;
|
||||||
|
|
||||||
return new MappedMorphTo(
|
return new MappedMorphTo(
|
||||||
$instance->newQuery(), $this, $idColumn, $instance->getKeyName(), $typeColumn, $name, static::$types
|
$instance->newQuery(), $this, $idColumn, $instance->getKeyName(), $typeColumn, $name, static::$subjects
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getTypes()
|
public static function getTypes()
|
||||||
{
|
{
|
||||||
return static::$types;
|
return static::$subjects;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a notification type and its subject class.
|
* Register a notification type.
|
||||||
*
|
*
|
||||||
* @param string $type
|
* @param string $type
|
||||||
* @param string $class
|
* @param string $class
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function addType($type, $class)
|
public static function registerType($class)
|
||||||
{
|
{
|
||||||
static::$types[$type] = $class;
|
if ($subject = $class::getSubjectModel()) {
|
||||||
|
static::$subjects[$class::getType()] = $subject;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -356,7 +356,7 @@ class User extends Model
|
||||||
$defaults[$k] = $v['default'];
|
$defaults[$k] = $v['default'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_merge($defaults, (array) json_decode($value, true));
|
return array_merge($defaults, array_only((array) json_decode($value, true), array_keys(static::$preferences)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPreferencesAttribute($value)
|
public function setPreferencesAttribute($value)
|
||||||
|
@ -372,6 +372,16 @@ class User extends Model
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function notificationPreferenceKey($type, $sender)
|
||||||
|
{
|
||||||
|
return 'notify_'.$type.'_'.$sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldNotify($type, $method)
|
||||||
|
{
|
||||||
|
return $this->preference(static::notificationPreferenceKey($type, $method));
|
||||||
|
}
|
||||||
|
|
||||||
public function preference($key, $default = null)
|
public function preference($key, $default = null)
|
||||||
{
|
{
|
||||||
return array_get($this->preferences, $key, $default);
|
return array_get($this->preferences, $key, $default);
|
||||||
|
|
35
src/Core/Notifications/NotificationServiceProvider.php
Normal file
35
src/Core/Notifications/NotificationServiceProvider.php
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?php namespace Flarum\Core\Notifications;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Flarum\Core\Models\User;
|
||||||
|
|
||||||
|
class NotificationServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Bootstrap the application events.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function boot(Dispatcher $events)
|
||||||
|
{
|
||||||
|
$notifier = app('Flarum\Core\Notifications\Notifier');
|
||||||
|
|
||||||
|
$notifier->registerMethod('alert', 'Flarum\Core\Notifications\Senders\NotificationAlerter');
|
||||||
|
$notifier->registerMethod('email', 'Flarum\Core\Notifications\Senders\NotificationEmailer');
|
||||||
|
|
||||||
|
$notifier->registerType('Flarum\Core\Notifications\Types\DiscussionRenamedNotification', ['alert' => true]);
|
||||||
|
|
||||||
|
$events->subscribe('Flarum\Core\Handlers\Events\DiscussionRenamedNotifier');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
$this->app->bind(
|
||||||
|
'Flarum\Core\Repositories\NotificationRepositoryInterface',
|
||||||
|
'Flarum\Core\Repositories\EloquentNotificationRepository'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->app->singleton('Flarum\Core\Notifications\Notifier');
|
||||||
|
}
|
||||||
|
}
|
59
src/Core/Notifications/Notifier.php
Normal file
59
src/Core/Notifications/Notifier.php
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<?php namespace Flarum\Core\Notifications;
|
||||||
|
|
||||||
|
use Flarum\Core\Notifications\Types\Notification;
|
||||||
|
use Flarum\Core\Models\Notification as NotificationModel;
|
||||||
|
use Flarum\Core\Models\User;
|
||||||
|
use Illuminate\Container\Container;
|
||||||
|
|
||||||
|
class Notifier
|
||||||
|
{
|
||||||
|
protected $methods = [];
|
||||||
|
|
||||||
|
protected $types = [];
|
||||||
|
|
||||||
|
protected $container;
|
||||||
|
|
||||||
|
public function __construct(Container $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(Notification $notification)
|
||||||
|
{
|
||||||
|
foreach ($this->methods as $method => $sender) {
|
||||||
|
$sender = $this->container->make($sender);
|
||||||
|
if ($notification->getRecipient()->shouldNotify($notification::getType(), $method) && $sender->compatibleWith($notification)) {
|
||||||
|
$sender->send($notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerMethod($name, $class)
|
||||||
|
{
|
||||||
|
$this->methods[$name] = $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerType($class, $defaultPreferences = [])
|
||||||
|
{
|
||||||
|
$this->types[] = $class;
|
||||||
|
|
||||||
|
NotificationModel::registerType($class);
|
||||||
|
|
||||||
|
foreach ($this->methods as $method => $sender) {
|
||||||
|
$sender = $this->container->make($sender);
|
||||||
|
if ($sender->compatibleWith($class)) {
|
||||||
|
User::registerPreference(User::notificationPreferenceKey($class::getType(), $method), 'boolval', array_get($defaultPreferences, $method, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMethods()
|
||||||
|
{
|
||||||
|
return $this->methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTypes()
|
||||||
|
{
|
||||||
|
return $this->types;
|
||||||
|
}
|
||||||
|
}
|
25
src/Core/Notifications/Senders/NotificationAlerter.php
Normal file
25
src/Core/Notifications/Senders/NotificationAlerter.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php namespace Flarum\Core\Notifications\Senders;
|
||||||
|
|
||||||
|
use Flarum\Core\Notifications\Types\Notification;
|
||||||
|
use Flarum\Core\Models\Notification as NotificationModel;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
class NotificationAlerter implements NotificationSender
|
||||||
|
{
|
||||||
|
public function send(Notification $notification)
|
||||||
|
{
|
||||||
|
$model = NotificationModel::alert(
|
||||||
|
$notification->getRecipient()->id,
|
||||||
|
$notification::getType(),
|
||||||
|
$notification->getSender()->id,
|
||||||
|
$notification->getSubject()->id,
|
||||||
|
$notification->getAlertData()
|
||||||
|
);
|
||||||
|
$model->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compatibleWith($className)
|
||||||
|
{
|
||||||
|
return (new ReflectionClass($className))->implementsInterface('Flarum\Core\Notifications\Types\AlertableNotification');
|
||||||
|
}
|
||||||
|
}
|
29
src/Core/Notifications/Senders/NotificationEmailer.php
Normal file
29
src/Core/Notifications/Senders/NotificationEmailer.php
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<?php namespace Flarum\Core\Notifications\Senders;
|
||||||
|
|
||||||
|
use Flarum\Core\Notifications\Types\Notification;
|
||||||
|
use Flarum\Core\Models\Forum;
|
||||||
|
use Illuminate\Mail\Mailer;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
class NotificationEmailer implements NotificationSender
|
||||||
|
{
|
||||||
|
public function __construct(Mailer $mailer, Forum $forum)
|
||||||
|
{
|
||||||
|
$this->mailer = $mailer;
|
||||||
|
$this->forum = $forum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(Notification $notification)
|
||||||
|
{
|
||||||
|
$this->mailer->send($notification->getEmailView(), ['notification' => $notification], function ($message) use ($notification) {
|
||||||
|
$recipient = $notification->getRecipient();
|
||||||
|
$message->to($recipient->email, $recipient->username)
|
||||||
|
->subject('['.$this->forum->title.'] '.$notification->getEmailSubject());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compatibleWith($class)
|
||||||
|
{
|
||||||
|
return (new ReflectionClass($class))->implementsInterface('Flarum\Core\Notifications\Types\EmailableNotification');
|
||||||
|
}
|
||||||
|
}
|
10
src/Core/Notifications/Senders/NotificationSender.php
Normal file
10
src/Core/Notifications/Senders/NotificationSender.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php namespace Flarum\Core\Notifications\Senders;
|
||||||
|
|
||||||
|
use Flarum\Core\Notifications\Types\Notification;
|
||||||
|
|
||||||
|
interface NotificationSender
|
||||||
|
{
|
||||||
|
public function send(Notification $notification);
|
||||||
|
|
||||||
|
public function compatibleWith($class);
|
||||||
|
}
|
8
src/Core/Notifications/Types/AlertableNotification.php
Normal file
8
src/Core/Notifications/Types/AlertableNotification.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php namespace Flarum\Core\Notifications\Types;
|
||||||
|
|
||||||
|
interface AlertableNotification
|
||||||
|
{
|
||||||
|
public function getAlertData();
|
||||||
|
|
||||||
|
public static function getType();
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php namespace Flarum\Core\Notifications\Types;
|
||||||
|
|
||||||
|
use Flarum\Core\Models\User;
|
||||||
|
use Flarum\Core\Models\DiscussionRenamedPost;
|
||||||
|
|
||||||
|
class DiscussionRenamedNotification extends Notification implements AlertableNotification
|
||||||
|
{
|
||||||
|
public $post;
|
||||||
|
|
||||||
|
public $oldTitle;
|
||||||
|
|
||||||
|
public function __construct(User $recipient, User $sender, DiscussionRenamedPost $post, $oldTitle)
|
||||||
|
{
|
||||||
|
$this->post = $post;
|
||||||
|
$this->oldTitle = $oldTitle;
|
||||||
|
|
||||||
|
parent::__construct($recipient, $sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubject()
|
||||||
|
{
|
||||||
|
return $this->post->discussion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAlertData()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'number' => $this->post->number,
|
||||||
|
'oldTitle' => $this->oldTitle
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getType()
|
||||||
|
{
|
||||||
|
return 'discussionRenamed';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubjectModel()
|
||||||
|
{
|
||||||
|
return 'Flarum\Core\Models\Discussion';
|
||||||
|
}
|
||||||
|
}
|
8
src/Core/Notifications/Types/EmailableNotification.php
Normal file
8
src/Core/Notifications/Types/EmailableNotification.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php namespace Flarum\Core\Notifications\Types;
|
||||||
|
|
||||||
|
interface EmailableNotification
|
||||||
|
{
|
||||||
|
public function getEmailView();
|
||||||
|
|
||||||
|
public function getEmailSubject();
|
||||||
|
}
|
36
src/Core/Notifications/Types/Notification.php
Normal file
36
src/Core/Notifications/Types/Notification.php
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?php namespace Flarum\Core\Notifications\Types;
|
||||||
|
|
||||||
|
use Flarum\Core\Models\User;
|
||||||
|
|
||||||
|
abstract class Notification
|
||||||
|
{
|
||||||
|
protected $recipient;
|
||||||
|
|
||||||
|
protected $sender;
|
||||||
|
|
||||||
|
public function __construct(User $recipient, User $sender)
|
||||||
|
{
|
||||||
|
$this->recipient = $recipient;
|
||||||
|
$this->sender = $sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRecipient()
|
||||||
|
{
|
||||||
|
return $this->recipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSender()
|
||||||
|
{
|
||||||
|
return $this->sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getType()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubjectModel()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user