mirror of
https://github.com/flarum/core.git
synced 2025-07-25 18:51:40 +02:00
Implement notifications
This commit is contained in:
14
src/Core/Commands/ReadNotificationCommand.php
Normal file
14
src/Core/Commands/ReadNotificationCommand.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class ReadNotificationCommand
|
||||
{
|
||||
public $notificationId;
|
||||
|
||||
public $user;
|
||||
|
||||
public function __construct($notificationId, $user)
|
||||
{
|
||||
$this->notificationId = $notificationId;
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@ use Flarum\Core\Models\Model;
|
||||
use Flarum\Core\Models\Forum;
|
||||
use Flarum\Core\Models\User;
|
||||
use Flarum\Core\Models\Discussion;
|
||||
use Flarum\Core\Models\Notification;
|
||||
use Flarum\Core\Search\GambitManager;
|
||||
|
||||
class CoreServiceProvider extends ServiceProvider
|
||||
@@ -25,6 +26,7 @@ class CoreServiceProvider extends ServiceProvider
|
||||
|
||||
$this->registerEventHandlers($events);
|
||||
$this->registerPostTypes();
|
||||
$this->registerNotificationTypes();
|
||||
$this->registerPermissions();
|
||||
$this->registerGambits();
|
||||
$this->setupModels();
|
||||
@@ -112,11 +114,16 @@ class CoreServiceProvider extends ServiceProvider
|
||||
CommentPost::setFormatter($this->app['flarum.formatter']);
|
||||
}
|
||||
|
||||
public function registerNotificationTypes()
|
||||
{
|
||||
Notification::addType('renamed', 'Flarum\Core\Models\Discussion');
|
||||
}
|
||||
|
||||
public function registerEventHandlers($events)
|
||||
{
|
||||
$events->subscribe('Flarum\Core\Handlers\Events\DiscussionMetadataUpdater');
|
||||
$events->subscribe('Flarum\Core\Handlers\Events\UserMetadataUpdater');
|
||||
$events->subscribe('Flarum\Core\Handlers\Events\RenamedPostCreator');
|
||||
$events->subscribe('Flarum\Core\Handlers\Events\DiscussionRenamedNotifier');
|
||||
$events->subscribe('Flarum\Core\Handlers\Events\EmailConfirmationMailer');
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,28 @@
|
||||
<?php namespace Flarum\Core\Handlers\Commands;
|
||||
|
||||
use Flarum\Core\Models\Notification;
|
||||
use Flarum\Core\Exceptions\PermissionDeniedException;
|
||||
use Flarum\Core\Support\DispatchesEvents;
|
||||
|
||||
class ReadNotificationCommandHandler
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
public function handle($command)
|
||||
{
|
||||
$user = $command->user;
|
||||
|
||||
if (! $user->exists) {
|
||||
throw new PermissionDeniedException;
|
||||
}
|
||||
|
||||
$notification = Notification::where('user_id', $user->id)->findOrFail($command->notificationId);
|
||||
|
||||
$notification->read();
|
||||
|
||||
$notification->save();
|
||||
$this->dispatchEventsFor($notification);
|
||||
|
||||
return $notification;
|
||||
}
|
||||
}
|
61
src/Core/Handlers/Events/DiscussionRenamedNotifier.php
Executable file
61
src/Core/Handlers/Events/DiscussionRenamedNotifier.php
Executable file
@@ -0,0 +1,61 @@
|
||||
<?php namespace Flarum\Core\Handlers\Events;
|
||||
|
||||
use Flarum\Core\Events\DiscussionWasRenamed;
|
||||
use Flarum\Core\Models\RenamedPost;
|
||||
use Flarum\Core\Models\Notification;
|
||||
|
||||
class DiscussionRenamedNotifier
|
||||
{
|
||||
/**
|
||||
* Register the listeners for the subscriber.
|
||||
*
|
||||
* @param Illuminate\Events\Dispatcher $events
|
||||
* @return array
|
||||
*/
|
||||
public function subscribe($events)
|
||||
{
|
||||
$events->listen('Flarum\Core\Events\DiscussionWasRenamed', __CLASS__.'@whenDiscussionWasRenamed');
|
||||
}
|
||||
|
||||
public function whenDiscussionWasRenamed(DiscussionWasRenamed $event)
|
||||
{
|
||||
$post = $this->createRenamedPost($event);
|
||||
|
||||
$event->discussion->postWasAdded($post);
|
||||
|
||||
$this->createRenamedNotification($event, $post);
|
||||
}
|
||||
|
||||
protected function createRenamedPost(DiscussionWasRenamed $event)
|
||||
{
|
||||
$post = RenamedPost::reply(
|
||||
$event->discussion->id,
|
||||
$event->user->id,
|
||||
$event->oldTitle,
|
||||
$event->discussion->title
|
||||
);
|
||||
|
||||
$post->save();
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
protected function createRenamedNotification(DiscussionWasRenamed $event, RenamedPost $post)
|
||||
{
|
||||
if ($event->discussion->start_user_id === $event->user->id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$notification = Notification::notify(
|
||||
$event->discussion->start_user_id,
|
||||
'renamed',
|
||||
$event->user->id,
|
||||
$event->discussion->id,
|
||||
['number' => $post->number, 'oldTitle' => $event->oldTitle]
|
||||
);
|
||||
|
||||
$notification->save();
|
||||
|
||||
return $notification;
|
||||
}
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
<?php namespace Flarum\Core\Handlers\Events;
|
||||
|
||||
use Flarum\Core\Events\DiscussionWasRenamed;
|
||||
use Flarum\Core\Models\RenamedPost;
|
||||
|
||||
class RenamedPostCreator
|
||||
{
|
||||
/**
|
||||
* Register the listeners for the subscriber.
|
||||
*
|
||||
* @param Illuminate\Events\Dispatcher $events
|
||||
* @return array
|
||||
*/
|
||||
public function subscribe($events)
|
||||
{
|
||||
$events->listen('Flarum\Core\Events\DiscussionWasRenamed', __CLASS__.'@whenDiscussionWasRenamed');
|
||||
}
|
||||
|
||||
public function whenDiscussionWasRenamed(DiscussionWasRenamed $event)
|
||||
{
|
||||
$post = RenamedPost::reply(
|
||||
$event->discussion->id,
|
||||
$event->user->id,
|
||||
$event->oldTitle,
|
||||
$event->discussion->title
|
||||
);
|
||||
|
||||
$post->save();
|
||||
|
||||
$event->discussion->postWasAdded($post);
|
||||
}
|
||||
}
|
135
src/Core/Models/Notification.php
Normal file
135
src/Core/Models/Notification.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php namespace Flarum\Core\Models;
|
||||
|
||||
use Flarum\Core\Support\MappedMorphTo;
|
||||
|
||||
class Notification extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'notifications';
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['time'];
|
||||
|
||||
/**
|
||||
* A map of notification types, as specified in the `type` column, to
|
||||
* their subject classes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $types = [];
|
||||
|
||||
public static function notify($userId, $type, $senderId, $subjectId, $data)
|
||||
{
|
||||
$notification = new static;
|
||||
|
||||
$notification->user_id = $userId;
|
||||
$notification->sender_id = $senderId;
|
||||
$notification->type = $type;
|
||||
$notification->subject_id = $subjectId;
|
||||
$notification->data = $data;
|
||||
$notification->time = time();
|
||||
|
||||
return $notification;
|
||||
}
|
||||
|
||||
public function read()
|
||||
{
|
||||
$this->is_read = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize the data attribute.
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
public function getDataAttribute($value)
|
||||
{
|
||||
return json_decode($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the data attribute.
|
||||
*
|
||||
* @param string $value
|
||||
*/
|
||||
public function setDataAttribute($value)
|
||||
{
|
||||
$this->attributes['data'] = json_encode($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the notification's recipient.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Core\Models\User', 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the notification's sender.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function sender()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Core\Models\User', 'sender_id');
|
||||
}
|
||||
|
||||
public function subject()
|
||||
{
|
||||
$name = 'subject';
|
||||
$typeColumn = 'type';
|
||||
$idColumn = 'subject_id';
|
||||
|
||||
// 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
|
||||
// there are multiple types in the morph and we can't use single queries.
|
||||
if (is_null($type = $this->$typeColumn))
|
||||
{
|
||||
return new MappedMorphTo(
|
||||
$this->newQuery(), $this, $idColumn, null, $typeColumn, $name, static::$types
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
// we will pass in the appropriate values so that it behaves as expected.
|
||||
else
|
||||
{
|
||||
$class = static::$types[$type];
|
||||
$instance = new $class;
|
||||
|
||||
return new MappedMorphTo(
|
||||
$instance->newQuery(), $this, $idColumn, $instance->getKeyName(), $typeColumn, $name, static::$types
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTypes()
|
||||
{
|
||||
return static::$types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a notification type and its subject class.
|
||||
*
|
||||
* @param string $type
|
||||
* @param string $class
|
||||
* @return void
|
||||
*/
|
||||
public static function addType($type, $class)
|
||||
{
|
||||
static::$types[$type] = $class;
|
||||
}
|
||||
}
|
@@ -51,7 +51,7 @@ class User extends Model
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['join_time', 'last_seen_time', 'read_time'];
|
||||
protected $dates = ['join_time', 'last_seen_time', 'read_time', 'notification_read_time'];
|
||||
|
||||
/**
|
||||
* The hasher with which to hash passwords.
|
||||
@@ -198,6 +198,18 @@ class User extends Model
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all notifications as read by setting the user's notification_read_time.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function markNotificationsAsRead()
|
||||
{
|
||||
$this->notification_read_time = time();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given password matches the user's password.
|
||||
*
|
||||
@@ -303,6 +315,11 @@ class User extends Model
|
||||
return (bool) $count;
|
||||
}
|
||||
|
||||
public function getUnreadNotificationsCount()
|
||||
{
|
||||
return $this->notifications()->where('time', '>', $this->notification_read_time ?: 0)->where('is_read', 0)->count(\DB::raw('DISTINCT type, subject_id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether or not the user is an administrator.
|
||||
*
|
||||
@@ -343,6 +360,16 @@ class User extends Model
|
||||
return $this->belongsToMany('Flarum\Core\Models\Group', 'users_groups');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the user's notifications.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function notifications()
|
||||
{
|
||||
return $this->hasMany('Flarum\Core\Models\Notification');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the user's permissions.
|
||||
*
|
||||
|
25
src/Core/Repositories/EloquentNotificationRepository.php
Normal file
25
src/Core/Repositories/EloquentNotificationRepository.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php namespace Flarum\Core\Repositories;
|
||||
|
||||
use Flarum\Core\Models\Notification;
|
||||
use DB;
|
||||
|
||||
class EloquentNotificationRepository implements NotificationRepositoryInterface
|
||||
{
|
||||
public function findByUser($userId, $count = null, $start = 0)
|
||||
{
|
||||
$primaries = Notification::select(DB::raw('MAX(id) AS id'), DB::raw('SUM(is_read = 0) AS unread_count'))
|
||||
->where('user_id', $userId)
|
||||
->whereIn('type', array_keys(Notification::getTypes()))
|
||||
->groupBy('type', 'subject_id')
|
||||
->orderBy('time', 'desc')
|
||||
->skip($start)
|
||||
->take($count);
|
||||
|
||||
return Notification::with('subject')
|
||||
->select('notifications.*', 'p.unread_count')
|
||||
->mergeBindings($primaries->getQuery())
|
||||
->join(DB::raw('('.$primaries->toSql().') p'), 'notifications.id', '=', 'p.id')
|
||||
->orderBy('time', 'desc')
|
||||
->get();
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
<?php namespace Flarum\Core\Repositories;
|
||||
|
||||
interface NotificationRepositoryInterface
|
||||
{
|
||||
public function findByUser($userId, $count = null, $start = 0);
|
||||
}
|
@@ -15,4 +15,9 @@ class Actor
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function isAuthenticated()
|
||||
{
|
||||
return (bool) $this->user;
|
||||
}
|
||||
}
|
||||
|
43
src/Core/Support/MappedMorphTo.php
Normal file
43
src/Core/Support/MappedMorphTo.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php namespace Flarum\Core\Support;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MappedMorphTo extends MorphTo {
|
||||
|
||||
/**
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $types;
|
||||
|
||||
/**
|
||||
* Create a new morph to relationship instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $foreignKey
|
||||
* @param string $otherKey
|
||||
* @param string $type
|
||||
* @param string $relation
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Builder $query, Model $parent, $foreignKey, $otherKey, $type, $relation, $types)
|
||||
{
|
||||
$this->types = $types;
|
||||
|
||||
parent::__construct($query, $parent, $foreignKey, $otherKey, $type, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new model instance by type.
|
||||
*
|
||||
* @param string $type
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function createModelByType($type)
|
||||
{
|
||||
return new $this->types[$type];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user