1
0
mirror of https://github.com/flarum/core.git synced 2025-08-13 03:44:32 +02:00

Extract Flarum\Notification namespace

This commit is contained in:
Franz Liedke
2017-06-24 14:22:19 +02:00
parent 4a13cd8088
commit b38ade986d
18 changed files with 42 additions and 45 deletions

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Notification\Blueprint;
/**
* A notification BlueprintInterface, when instantiated, represents a notification about
* something. The blueprint is used by the NotificationSyncer to commit the
* notification to the database.
*/
interface BlueprintInterface
{
/**
* Get the user that sent the notification.
*
* @return \Flarum\User\User|null
*/
public function getSender();
/**
* Get the model that is the subject of this activity.
*
* @return \Flarum\Database\AbstractModel|null
*/
public function getSubject();
/**
* Get the data to be stored in the notification.
*
* @return array|null
*/
public function getData();
/**
* Get the serialized type of this activity.
*
* @return string
*/
public static function getType();
/**
* Get the name of the model class for the subject of this activity.
*
* @return string
*/
public static function getSubjectModel();
}

View File

@@ -0,0 +1,70 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Notification\Blueprint;
use Flarum\Post\DiscussionRenamedPost;
class DiscussionRenamedBlueprint implements BlueprintInterface
{
/**
* @var \Flarum\Post\DiscussionRenamedPost
*/
protected $post;
/**
* @param DiscussionRenamedPost $post
*/
public function __construct(DiscussionRenamedPost $post)
{
$this->post = $post;
}
/**
* {@inheritdoc}
*/
public function getSender()
{
return $this->post->user;
}
/**
* {@inheritdoc}
*/
public function getSubject()
{
return $this->post->discussion;
}
/**
* {@inheritdoc}
*/
public function getData()
{
return ['postNumber' => (int) $this->post->number];
}
/**
* {@inheritdoc}
*/
public static function getType()
{
return 'discussionRenamed';
}
/**
* {@inheritdoc}
*/
public static function getSubjectModel()
{
return 'Flarum\Discussion\Discussion';
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Notification\Event;
use Flarum\Notification\Blueprint\BlueprintInterface;
class Sending
{
/**
* The blueprint for the notification.
*
* @var BlueprintInterface
*/
public $blueprint;
/**
* The users that the notification will be sent to.
*
* @var array
*/
public $users;
/**
* @param \Flarum\Notification\Blueprint\BlueprintInterface $blueprint
* @param \Flarum\User\User[] $users
*/
public function __construct(BlueprintInterface $blueprint, array &$users)
{
$this->blueprint = $blueprint;
$this->users = $users;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Notification;
interface MailableInterface
{
/**
* Get the name of the view to construct a notification email with.
*
* @return string
*/
public function getEmailView();
/**
* Get the subject line for a notification email.
*
* @return string
*/
public function getEmailSubject();
}

View File

@@ -0,0 +1,161 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Notification;
use Flarum\Database\AbstractModel;
/**
* Models a notification record in the database.
*
* A notification record is associated with a user, and shows up in their
* notification list. A notification indicates that something has happened that
* the user should know about, like if a user's discussion was renamed by
* someone else.
*
* Each notification record has a *type*. The type determines how the record
* looks in the notifications list, and what *subject* is associated with it.
* For example, the 'discussionRenamed' notification type represents that
* someone renamed a user's discussion. Its subject is a discussion, of which
* the ID is stored in the `subject_id` column.
*
* @property int $id
* @property int $user_id
* @property int|null $sender_id
* @property string $type
* @property int|null $subject_id
* @property mixed|null $data
* @property \Carbon\Carbon $time
* @property bool $is_read
* @property bool $is_deleted
* @property \Flarum\User\User|null $user
* @property \Flarum\User\User|null $sender
* @property \Flarum\Database\AbstractModel|null $subject
*/
class Notification extends AbstractModel
{
/**
* {@inheritdoc}
*/
protected $table = 'notifications';
/**
* {@inheritdoc}
*/
protected $dates = ['time'];
/**
* A map of notification types and the model classes to use for their
* subjects. For example, the 'discussionRenamed' notification type, which
* represents that a user's discussion was renamed, has the subject model
* class 'Flarum\Discussion\Discussion'.
*
* @var array
*/
protected static $subjectModels = [];
/**
* Mark a notification as read.
*
* @return void
*/
public function read()
{
$this->is_read = true;
}
/**
* When getting the data attribute, unserialize the JSON stored in the
* database into a plain array.
*
* @param string $value
* @return mixed
*/
public function getDataAttribute($value)
{
return json_decode($value, true);
}
/**
* When setting the data attribute, serialize it into JSON for storage in
* the database.
*
* @param mixed $value
*/
public function setDataAttribute($value)
{
$this->attributes['data'] = json_encode($value);
}
/**
* Get the subject model for this notification record by looking up its
* type in our subject model map.
*
* @return string|null
*/
public function getSubjectModelAttribute()
{
return array_get(static::$subjectModels, $this->type);
}
/**
* Define the relationship with the notification's recipient.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('Flarum\User\User', 'user_id');
}
/**
* Define the relationship with the notification's sender.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function sender()
{
return $this->belongsTo('Flarum\User\User', 'sender_id');
}
/**
* Define the relationship with the notification's subject.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function subject()
{
return $this->morphTo('subject', 'subjectModel', 'subject_id');
}
/**
* Get the type-to-subject-model map.
*
* @return array
*/
public static function getSubjectModels()
{
return static::$subjectModels;
}
/**
* Set the subject model for the given notification type.
*
* @param string $type The notification type.
* @param string $subjectModel The class name of the subject model for that
* type.
* @return void
*/
public static function setSubjectModel($type, $subjectModel)
{
static::$subjectModels[$type] = $subjectModel;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Notification;
use Flarum\User\User;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Mail\Message;
class NotificationMailer
{
/**
* @var Mailer
*/
protected $mailer;
/**
* @param Mailer $mailer
*/
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
/**
* @param MailableInterface $blueprint
* @param User $user
*/
public function send(MailableInterface $blueprint, User $user)
{
$this->mailer->send(
$blueprint->getEmailView(),
compact('blueprint', 'user'),
function (Message $message) use ($blueprint, $user) {
$message->to($user->email, $user->username)
->subject($blueprint->getEmailSubject());
}
);
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Notification;
use Flarum\User\User;
class NotificationRepository
{
/**
* Find a user's notifications.
*
* @param User $user
* @param int|null $limit
* @param int $offset
* @return \Illuminate\Database\Eloquent\Collection
*/
public function findByUser(User $user, $limit = null, $offset = 0)
{
$primaries = Notification::select(
app('flarum.db')->raw('MAX(id) AS id'),
app('flarum.db')->raw('SUM(is_read = 0) AS unread_count')
)
->where('user_id', $user->id)
->whereIn('type', $user->getAlertableNotificationTypes())
->where('is_deleted', false)
->groupBy('type', 'subject_id')
->orderByRaw('MAX(time) DESC')
->skip($offset)
->take($limit);
return Notification::select('notifications.*', app('flarum.db')->raw('p.unread_count'))
->mergeBindings($primaries->getQuery())
->join(app('flarum.db')->raw('('.$primaries->toSql().') p'), 'notifications.id', '=', app('flarum.db')->raw('p.id'))
->latest('time')
->get();
}
/**
* Mark all of a user's notifications as read.
*
* @param User $user
*
* @return void
*/
public function markAllAsRead(User $user)
{
Notification::where('user_id', $user->id)->update(['is_read' => true]);
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Notification;
use Flarum\Event\ConfigureNotificationTypes;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\User\User;
use ReflectionClass;
class NotificationServiceProvider extends AbstractServiceProvider
{
/**
* {@inheritdoc}
*/
public function boot()
{
$this->registerNotificationTypes();
}
/**
* Register notification types.
*/
public function registerNotificationTypes()
{
$blueprints = [
'Flarum\Notification\Notification\DiscussionRenamedBlueprint' => ['alert']
];
$this->app->make('events')->fire(
new ConfigureNotificationTypes($blueprints)
);
foreach ($blueprints as $blueprint => $enabled) {
Notification::setSubjectModel(
$type = $blueprint::getType(),
$blueprint::getSubjectModel()
);
User::addPreference(
User::getNotificationPreferenceKey($type, 'alert'),
'boolval',
in_array('alert', $enabled)
);
if ((new ReflectionClass($blueprint))->implementsInterface('Flarum\Notification\Notification\MailableInterface')) {
User::addPreference(
User::getNotificationPreferenceKey($type, 'email'),
'boolval',
in_array('email', $enabled)
);
}
}
}
}

View File

@@ -0,0 +1,234 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Notification;
use Carbon\Carbon;
use Flarum\Notification\Event\Sending;
use Flarum\Notification\Blueprint\BlueprintInterface;
use Flarum\User\User;
/**
* The Notification Syncer commits notification blueprints to the database, and
* sends them via email depending on user preference. Where a blueprint
* represents a single notification, the syncer associates it with a particular
* user(s) and makes it available in their inbox.
*/
class NotificationSyncer
{
/**
* Whether or not notifications are being limited to one per user.
*
* @var bool
*/
protected static $onePerUser = false;
/**
* An internal list of user IDs that notifications have been sent to.
*
* @var int[]
*/
protected static $sentTo = [];
/**
* @var NotificationRepository
*/
protected $notifications;
/**
* @var NotificationMailer
*/
protected $mailer;
/**
* @param NotificationRepository $notifications
* @param NotificationMailer $mailer
*/
public function __construct(
NotificationRepository $notifications,
NotificationMailer $mailer
) {
$this->notifications = $notifications;
$this->mailer = $mailer;
}
/**
* Sync a notification so that it is visible to the specified users, and not
* visible to anyone else. If it is being made visible for the first time,
* attempt to send the user an email.
*
* @param \Flarum\Notification\Blueprint\BlueprintInterface $blueprint
* @param User[] $users
* @return void
*/
public function sync(Blueprint\BlueprintInterface $blueprint, array $users)
{
$attributes = $this->getAttributes($blueprint);
// Find all existing notification records in the database matching this
// blueprint. We will begin by assuming that they all need to be
// deleted in order to match the provided list of users.
$toDelete = Notification::where($attributes)->get();
$toUndelete = [];
$newRecipients = [];
// For each of the provided users, check to see if they already have
// a notification record in the database. If they do, we will make sure
// it isn't marked as deleted. If they don't, we will want to create a
// new record for them.
foreach ($users as $user) {
if (! ($user instanceof User)) {
continue;
}
$existing = $toDelete->first(function ($i, $notification) use ($user) {
return $notification->user_id === $user->id;
});
if ($existing) {
$toUndelete[] = $existing->id;
$toDelete->forget($toDelete->search($existing));
} elseif (! static::$onePerUser || ! in_array($user->id, static::$sentTo)) {
$newRecipients[] = $user;
static::$sentTo[] = $user->id;
}
}
// Delete all of the remaining notification records which weren't
// removed from this collection by the above loop. Un-delete the
// existing records that we want to keep.
if (count($toDelete)) {
$this->setDeleted($toDelete->lists('id')->all(), true);
}
if (count($toUndelete)) {
$this->setDeleted($toUndelete, false);
}
// Create a notification record, and send an email, for all users
// receiving this notification for the first time (we know because they
// didn't have a record in the database).
if (count($newRecipients)) {
$this->sendNotifications($blueprint, $newRecipients);
}
}
/**
* Delete a notification for all users.
*
* @param \Flarum\Notification\Blueprint\BlueprintInterface $blueprint
* @return void
*/
public function delete(BlueprintInterface $blueprint)
{
Notification::where($this->getAttributes($blueprint))->update(['is_deleted' => true]);
}
/**
* Restore a notification for all users.
*
* @param BlueprintInterface $blueprint
* @return void
*/
public function restore(BlueprintInterface $blueprint)
{
Notification::where($this->getAttributes($blueprint))->update(['is_deleted' => false]);
}
/**
* Limit notifications to one per user for the entire duration of the given
* callback.
*
* @param callable $callback
* @return void
*/
public function onePerUser(callable $callback)
{
static::$sentTo = [];
static::$onePerUser = true;
$callback();
static::$onePerUser = false;
}
/**
* Create a notification record and send an email (depending on user
* preference) from a blueprint to a list of recipients.
*
* @param \Flarum\Notification\Blueprint\BlueprintInterface $blueprint
* @param User[] $recipients
*/
protected function sendNotifications(Blueprint\BlueprintInterface $blueprint, array $recipients)
{
$now = Carbon::now('utc')->toDateTimeString();
event(new Sending($blueprint, $recipients));
$attributes = $this->getAttributes($blueprint);
Notification::insert(
array_map(function (User $user) use ($attributes, $now) {
return $attributes + [
'user_id' => $user->id,
'time' => $now
];
}, $recipients)
);
if ($blueprint instanceof MailableInterface) {
$this->mailNotifications($blueprint, $recipients);
}
}
/**
* Mail a notification to a list of users.
*
* @param MailableInterface $blueprint
* @param User[] $recipients
*/
protected function mailNotifications(MailableInterface $blueprint, array $recipients)
{
foreach ($recipients as $user) {
if ($user->shouldEmail($blueprint::getType())) {
$this->mailer->send($blueprint, $user);
}
}
}
/**
* Set the deleted status of a list of notification records.
*
* @param int[] $ids
* @param bool $isDeleted
*/
protected function setDeleted(array $ids, $isDeleted)
{
Notification::whereIn('id', $ids)->update(['is_deleted' => $isDeleted]);
}
/**
* Construct an array of attributes to be stored in a notification record in
* the database, given a notification blueprint.
*
* @param \Flarum\Notification\Blueprint\BlueprintInterface $blueprint
* @return array
*/
protected function getAttributes(Blueprint\BlueprintInterface $blueprint)
{
return [
'type' => $blueprint::getType(),
'sender_id' => ($sender = $blueprint->getSender()) ? $sender->id : null,
'subject_id' => ($subject = $blueprint->getSubject()) ? $subject->id : null,
'data' => ($data = $blueprint->getData()) ? json_encode($data) : null
];
}
}