1
0
mirror of https://github.com/flarum/core.git synced 2025-10-13 07:54:25 +02:00
Files
php-flarum/src/Notification/Notification.php
Franz Liedke 1cda9dca4f Revert BC breaks around notification blueprints
No need for breaking backwards compatibility here - encapsulating the
logic for `getAttributes()` in one place turns out to be quite useful.

Refs #1931.
2020-04-03 11:33:33 +02:00

270 lines
7.8 KiB
PHP

<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Notification;
use Carbon\Carbon;
use Flarum\Database\AbstractModel;
use Flarum\Event\ScopeModelVisibility;
use Flarum\Notification\Blueprint\BlueprintInterface;
use Flarum\User\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
/**
* 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 $from_user_id
* @property string $type
* @property int|null $subject_id
* @property mixed|null $data
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $read_at
* @property \Carbon\Carbon $deleted_at
* @property \Flarum\User\User|null $user
* @property \Flarum\User\User|null $fromUser
* @property \Flarum\Database\AbstractModel|null $subject
*/
class Notification extends AbstractModel
{
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = ['created_at', 'read_at'];
/**
* 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->read_at = Carbon::now();
}
/**
* 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 $this->type ? Arr::get(static::$subjectModels, $this->type) : null;
}
/**
* Define the relationship with the notification's recipient.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* Define the relationship with the notification's sender.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function fromUser()
{
return $this->belongsTo(User::class, 'from_user_id');
}
/**
* Define the relationship with the notification's subject.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function subject()
{
return $this->morphTo('subject', 'subjectModel');
}
/**
* Scope the query to include only notifications whose subjects are visible
* to the given user.
*
* @param Builder $query
* @return Builder
*/
public function scopeWhereSubjectVisibleTo(Builder $query, User $actor)
{
return $query->where(function ($query) use ($actor) {
$classes = [];
foreach (static::$subjectModels as $type => $class) {
$classes[$class][] = $type;
}
foreach ($classes as $class => $types) {
$query->orWhere(function ($query) use ($types, $class, $actor) {
$query->whereIn('type', $types)
->whereExists(function ($query) use ($class, $actor) {
$query->selectRaw(1)
->from((new $class)->getTable())
->whereColumn('id', 'subject_id');
static::$dispatcher->dispatch(
new ScopeModelVisibility($class::query()->setQuery($query), $actor, 'view')
);
});
});
}
});
}
/**
* Scope the query to include only notifications that have the given
* subject.
*
* @param Builder $query
* @param object $model
* @return Builder
*/
public function scopeWhereSubject(Builder $query, $model)
{
return $query->whereSubjectModel(get_class($model))
->where('subject_id', $model->id);
}
/**
* Scope the query to include only notification types that use the given
* subject model.
*
* @param Builder $query
* @param string $class
* @return Builder
*/
public function scopeWhereSubjectModel(Builder $query, string $class)
{
$notificationTypes = array_filter(self::getSubjectModels(), function ($modelClass) use ($class) {
return $modelClass === $class or is_subclass_of($class, $modelClass);
});
return $query->whereIn('type', array_keys($notificationTypes));
}
/**
* Scope the query to find all records matching the given blueprint.
*
* @param Builder $query
* @param BlueprintInterface $blueprint
* @return Builder
*/
public function scopeMatchingBlueprint(Builder $query, BlueprintInterface $blueprint)
{
return $query->where(static::getBlueprintAttributes($blueprint));
}
/**
* Send notifications to the given recipients.
*
* @param User[] $recipients
* @param BlueprintInterface $blueprint
*/
public static function notify(array $recipients, BlueprintInterface $blueprint)
{
$attributes = static::getBlueprintAttributes($blueprint);
$now = Carbon::now('utc')->toDateTimeString();
static::insert(
array_map(function (User $user) use ($attributes, $now) {
return $attributes + [
'user_id' => $user->id,
'created_at' => $now
];
}, $recipients)
);
}
/**
* 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;
}
private static function getBlueprintAttributes(BlueprintInterface $blueprint): array
{
return [
'type' => $blueprint::getType(),
'from_user_id' => ($fromUser = $blueprint->getFromUser()) ? $fromUser->id : null,
'subject_id' => ($subject = $blueprint->getSubject()) ? $subject->id : null,
'data' => ($data = $blueprint->getData()) ? json_encode($data) : null
];
}
}