mirror of
https://github.com/flarum/core.git
synced 2025-08-09 01:46:35 +02:00
Extract new Flarum\Discussion namespace
This commit is contained in:
464
src/Discussion/Discussion.php
Normal file
464
src/Discussion/Discussion.php
Normal file
@@ -0,0 +1,464 @@
|
||||
<?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\Discussion;
|
||||
|
||||
use Flarum\Core\DiscussionState;
|
||||
use Flarum\Post\MergeableInterface;
|
||||
use Flarum\Foundation\EventGeneratorTrait;
|
||||
use Flarum\Database\ScopeVisibilityTrait;
|
||||
use Flarum\Database\AbstractModel;
|
||||
use Flarum\Discussion\Event\Deleted;
|
||||
use Flarum\Discussion\Event\Hidden;
|
||||
use Flarum\Discussion\Event\Renamed;
|
||||
use Flarum\Discussion\Event\Restored;
|
||||
use Flarum\Discussion\Event\Started;
|
||||
use Flarum\Post\Event\Deleted;
|
||||
use Flarum\Event\ScopePostVisibility;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\User\Guest;
|
||||
use Flarum\User\User;
|
||||
use Flarum\Util\Str;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $title
|
||||
* @property string $slug
|
||||
* @property int $comments_count
|
||||
* @property int $participants_count
|
||||
* @property int $number_index
|
||||
* @property \Carbon\Carbon $start_time
|
||||
* @property int|null $start_user_id
|
||||
* @property int|null $start_post_id
|
||||
* @property \Carbon\Carbon|null $last_time
|
||||
* @property int|null $last_user_id
|
||||
* @property int|null $last_post_id
|
||||
* @property int|null $last_post_number
|
||||
* @property \Carbon\Carbon|null $hide_time
|
||||
* @property int|null $hide_user_id
|
||||
* @property DiscussionState|null $state
|
||||
* @property \Illuminate\Database\Eloquent\Collection $posts
|
||||
* @property \Illuminate\Database\Eloquent\Collection $comments
|
||||
* @property \Illuminate\Database\Eloquent\Collection $participants
|
||||
* @property Post|null $startPost
|
||||
* @property User|null $startUser
|
||||
* @property Post|null $lastPost
|
||||
* @property User|null $lastUser
|
||||
* @property \Illuminate\Database\Eloquent\Collection $readers
|
||||
* @property bool $is_private
|
||||
*/
|
||||
class Discussion extends AbstractModel
|
||||
{
|
||||
use EventGeneratorTrait;
|
||||
use ScopeVisibilityTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $table = 'discussions';
|
||||
|
||||
/**
|
||||
* An array of posts that have been modified during this request.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $modifiedPosts = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $dates = ['start_time', 'last_time', 'hide_time'];
|
||||
|
||||
/**
|
||||
* Casts properties to a specific type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'is_private' => 'boolean'
|
||||
];
|
||||
|
||||
/**
|
||||
* The user for which the state relationship should be loaded.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
protected static $stateUser;
|
||||
|
||||
/**
|
||||
* Boot the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::deleted(function ($discussion) {
|
||||
$discussion->raise(new Deleted($discussion));
|
||||
|
||||
// Delete all of the posts in the discussion. Before we delete them
|
||||
// in a big batch query, we will loop through them and raise a
|
||||
// PostWasDeleted event for each post.
|
||||
$posts = $discussion->posts()->allTypes();
|
||||
|
||||
foreach ($posts->get() as $post) {
|
||||
$discussion->raise(new Deleted($post));
|
||||
}
|
||||
|
||||
$posts->delete();
|
||||
|
||||
// Delete all of the 'state' records for all of the users who have
|
||||
// read the discussion.
|
||||
$discussion->readers()->detach();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new discussion. Raises the DiscussionWasStarted event.
|
||||
*
|
||||
* @param string $title
|
||||
* @param User $user
|
||||
* @return static
|
||||
*/
|
||||
public static function start($title, User $user)
|
||||
{
|
||||
$discussion = new static;
|
||||
|
||||
$discussion->title = $title;
|
||||
$discussion->start_time = time();
|
||||
$discussion->start_user_id = $user->id;
|
||||
|
||||
$discussion->setRelation('startUser', $user);
|
||||
|
||||
$discussion->raise(new Started($discussion));
|
||||
|
||||
return $discussion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename the discussion. Raises the DiscussionWasRenamed event.
|
||||
*
|
||||
* @param string $title
|
||||
* @return $this
|
||||
*/
|
||||
public function rename($title)
|
||||
{
|
||||
if ($this->title !== $title) {
|
||||
$oldTitle = $this->title;
|
||||
$this->title = $title;
|
||||
|
||||
$this->raise(new Renamed($this, $oldTitle));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the discussion.
|
||||
*
|
||||
* @param User $actor
|
||||
* @return $this
|
||||
*/
|
||||
public function hide(User $actor = null)
|
||||
{
|
||||
if (! $this->hide_time) {
|
||||
$this->hide_time = time();
|
||||
$this->hide_user_id = $actor ? $actor->id : null;
|
||||
|
||||
$this->raise(new Hidden($this));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the discussion.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function restore()
|
||||
{
|
||||
if ($this->hide_time !== null) {
|
||||
$this->hide_time = null;
|
||||
$this->hide_user_id = null;
|
||||
|
||||
$this->raise(new Restored($this));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the discussion's start post details.
|
||||
*
|
||||
* @param Post $post
|
||||
* @return $this
|
||||
*/
|
||||
public function setStartPost(Post $post)
|
||||
{
|
||||
$this->start_time = $post->time;
|
||||
$this->start_user_id = $post->user_id;
|
||||
$this->start_post_id = $post->id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the discussion's last post details.
|
||||
*
|
||||
* @param Post $post
|
||||
* @return $this
|
||||
*/
|
||||
public function setLastPost(Post $post)
|
||||
{
|
||||
$this->last_time = $post->time;
|
||||
$this->last_user_id = $post->user_id;
|
||||
$this->last_post_id = $post->id;
|
||||
$this->last_post_number = $post->number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh a discussion's last post details.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function refreshLastPost()
|
||||
{
|
||||
if ($lastPost = $this->comments()->latest('time')->first()) {
|
||||
$this->setLastPost($lastPost);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the discussion's comments count.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function refreshCommentsCount()
|
||||
{
|
||||
$this->comments_count = $this->comments()->count();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the discussion's participants count.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function refreshParticipantsCount()
|
||||
{
|
||||
$this->participants_count = $this->participants()->count('users.id');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a post, attempting to merge it with the discussion's last post.
|
||||
*
|
||||
* The merge logic is delegated to the new post. (As an example, a
|
||||
* DiscussionRenamedPost will merge if adjacent to another
|
||||
* DiscussionRenamedPost, and delete if the title has been reverted
|
||||
* completely.)
|
||||
*
|
||||
* @param \Flarum\Post\MergeableInterface $post The post to save.
|
||||
* @return Post The resulting post. It may or may not be the same post as
|
||||
* was originally intended to be saved. It also may not exist, if the
|
||||
* merge logic resulted in deletion.
|
||||
*/
|
||||
public function mergePost(MergeableInterface $post)
|
||||
{
|
||||
$lastPost = $this->posts()->latest('time')->first();
|
||||
|
||||
$post = $post->saveAfter($lastPost);
|
||||
|
||||
return $this->modifiedPosts[] = $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the posts that have been modified during this request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getModifiedPosts()
|
||||
{
|
||||
return $this->modifiedPosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the discussion's posts.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function posts()
|
||||
{
|
||||
return $this->hasMany('Flarum\Post\Post');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the discussion's posts, but only ones which
|
||||
* are visible to the given user.
|
||||
*
|
||||
* @param User $user
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function postsVisibleTo(User $user)
|
||||
{
|
||||
$relation = $this->posts();
|
||||
|
||||
static::$dispatcher->fire(
|
||||
new ScopePostVisibility($this, $relation->getQuery(), $user)
|
||||
);
|
||||
|
||||
return $relation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the discussion's publicly-visible comments.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function comments()
|
||||
{
|
||||
return $this->postsVisibleTo(new Guest)->where('type', 'comment');
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the discussion's participants (a list of unique users who have
|
||||
* posted in the discussion).
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function participants()
|
||||
{
|
||||
return User::join('posts', 'posts.user_id', '=', 'users.id')
|
||||
->where('posts.discussion_id', $this->id)
|
||||
->select('users.*')
|
||||
->distinct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the discussion's first post.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function startPost()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Post\Post', 'start_post_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the discussion's author.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function startUser()
|
||||
{
|
||||
return $this->belongsTo('Flarum\User\User', 'start_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the discussion's last post.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function lastPost()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Post\Post', 'last_post_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the discussion's most recent author.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function lastUser()
|
||||
{
|
||||
return $this->belongsTo('Flarum\User\User', 'last_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the discussion's readers.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function readers()
|
||||
{
|
||||
return $this->belongsToMany('Flarum\User\User', 'users_discussions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the discussion's state for a particular
|
||||
* user.
|
||||
*
|
||||
* If no user is passed (i.e. in the case of eager loading the 'state'
|
||||
* relation), then the static `$stateUser` property is used.
|
||||
*
|
||||
* @see Discussion::setStateUser()
|
||||
*
|
||||
* @param User|null $user
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function state(User $user = null)
|
||||
{
|
||||
$user = $user ?: static::$stateUser;
|
||||
|
||||
return $this->hasOne('Flarum\Core\DiscussionState')->where('user_id', $user ? $user->id : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the state model for a user, or instantiate a new one if it does not
|
||||
* exist.
|
||||
*
|
||||
* @param User $user
|
||||
* @return \Flarum\Core\DiscussionState
|
||||
*/
|
||||
public function stateFor(User $user)
|
||||
{
|
||||
$state = $this->state($user)->first();
|
||||
|
||||
if (! $state) {
|
||||
$state = new DiscussionState;
|
||||
$state->discussion_id = $this->id;
|
||||
$state->user_id = $user->id;
|
||||
}
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user for which the state relationship should be loaded.
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public static function setStateUser(User $user)
|
||||
{
|
||||
static::$stateUser = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the discussion title.
|
||||
*
|
||||
* This automatically creates a matching slug for the discussion.
|
||||
*
|
||||
* @param string $title
|
||||
*/
|
||||
protected function setTitleAttribute($title)
|
||||
{
|
||||
$this->attributes['title'] = $title;
|
||||
$this->slug = Str::slug($title);
|
||||
}
|
||||
}
|
104
src/Discussion/DiscussionMetadataUpdater.php
Executable file
104
src/Discussion/DiscussionMetadataUpdater.php
Executable file
@@ -0,0 +1,104 @@
|
||||
<?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\Discussion;
|
||||
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Post\Event\Deleted;
|
||||
use Flarum\Post\Event\Hidden;
|
||||
use Flarum\Post\Event\Posted;
|
||||
use Flarum\Post\Event\Restored;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
class DiscussionMetadataUpdater
|
||||
{
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
*/
|
||||
public function subscribe(Dispatcher $events)
|
||||
{
|
||||
$events->listen(Posted::class, [$this, 'whenPostWasPosted']);
|
||||
$events->listen(Deleted::class, [$this, 'whenPostWasDeleted']);
|
||||
$events->listen(Hidden::class, [$this, 'whenPostWasHidden']);
|
||||
$events->listen(Restored::class, [$this, 'whenPostWasRestored']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Posted $event
|
||||
*/
|
||||
public function whenPostWasPosted(Posted $event)
|
||||
{
|
||||
$discussion = $event->post->discussion;
|
||||
|
||||
if ($discussion && $discussion->exists) {
|
||||
$discussion->refreshCommentsCount();
|
||||
$discussion->refreshLastPost();
|
||||
$discussion->refreshParticipantsCount();
|
||||
$discussion->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Flarum\Post\Event\Deleted $event
|
||||
*/
|
||||
public function whenPostWasDeleted(Deleted $event)
|
||||
{
|
||||
$this->removePost($event->post);
|
||||
|
||||
$discussion = $event->post->discussion;
|
||||
|
||||
if ($discussion && $discussion->posts()->count() === 0) {
|
||||
$discussion->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Flarum\Post\Event\Hidden $event
|
||||
*/
|
||||
public function whenPostWasHidden(Hidden $event)
|
||||
{
|
||||
$this->removePost($event->post);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Restored $event
|
||||
*/
|
||||
public function whenPostWasRestored(Restored $event)
|
||||
{
|
||||
$discussion = $event->post->discussion;
|
||||
|
||||
if ($discussion && $discussion->exists) {
|
||||
$discussion->refreshCommentsCount();
|
||||
$discussion->refreshParticipantsCount();
|
||||
$discussion->refreshLastPost();
|
||||
$discussion->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Post $post
|
||||
*/
|
||||
protected function removePost(Post $post)
|
||||
{
|
||||
$discussion = $post->discussion;
|
||||
|
||||
if ($discussion && $discussion->exists) {
|
||||
$discussion->refreshCommentsCount();
|
||||
$discussion->refreshParticipantsCount();
|
||||
|
||||
if ($discussion->last_post_id == $post->id) {
|
||||
$discussion->refreshLastPost();
|
||||
}
|
||||
|
||||
$discussion->save();
|
||||
}
|
||||
}
|
||||
}
|
130
src/Discussion/DiscussionPolicy.php
Normal file
130
src/Discussion/DiscussionPolicy.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?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\Discussion;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Event\ScopeHiddenDiscussionVisibility;
|
||||
use Flarum\Event\ScopePrivateDiscussionVisibility;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\AbstractPolicy;
|
||||
use Flarum\User\Gate;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class DiscussionPolicy extends AbstractPolicy
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $model = Discussion::class;
|
||||
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @var Gate
|
||||
*/
|
||||
protected $gate;
|
||||
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
* @param Gate $gate
|
||||
* @param Dispatcher $events
|
||||
*/
|
||||
public function __construct(SettingsRepositoryInterface $settings, Gate $gate, Dispatcher $events)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
$this->gate = $gate;
|
||||
$this->events = $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param string $ability
|
||||
* @return bool|null
|
||||
*/
|
||||
public function after(User $actor, $ability)
|
||||
{
|
||||
if ($actor->hasPermission('discussion.'.$ability)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
*/
|
||||
public function find(User $actor, Builder $query)
|
||||
{
|
||||
// Hide private discussions per default.
|
||||
$query->where(function ($query) use ($actor) {
|
||||
$query->where('discussions.is_private', false);
|
||||
|
||||
$this->events->fire(
|
||||
new ScopePrivateDiscussionVisibility($query, $actor)
|
||||
);
|
||||
});
|
||||
|
||||
if ($actor->cannot('viewDiscussions')) {
|
||||
$query->whereRaw('FALSE');
|
||||
} elseif (! $actor->hasPermission('discussion.hide')) {
|
||||
$query->where(function ($query) use ($actor) {
|
||||
$query->whereNull('discussions.hide_time')
|
||||
->where('comments_count', '>', 0)
|
||||
->orWhere('start_user_id', $actor->id);
|
||||
|
||||
$this->events->fire(
|
||||
new ScopeHiddenDiscussionVisibility($query, $actor, 'discussion.hide')
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param \Flarum\Discussion\Discussion $discussion
|
||||
* @return bool|null
|
||||
*/
|
||||
public function rename(User $actor, Discussion $discussion)
|
||||
{
|
||||
if ($discussion->start_user_id == $actor->id) {
|
||||
$allowRenaming = $this->settings->get('allow_renaming');
|
||||
|
||||
if ($allowRenaming === '-1'
|
||||
|| ($allowRenaming === 'reply' && $discussion->participants_count <= 1)
|
||||
|| ($discussion->start_time->diffInMinutes(new Carbon) < $allowRenaming)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param \Flarum\Discussion\Discussion $discussion
|
||||
* @return bool|null
|
||||
*/
|
||||
public function hide(User $actor, Discussion $discussion)
|
||||
{
|
||||
if ($discussion->start_user_id == $actor->id && $discussion->participants_count <= 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
67
src/Discussion/DiscussionRenamedNotifier.php
Executable file
67
src/Discussion/DiscussionRenamedNotifier.php
Executable file
@@ -0,0 +1,67 @@
|
||||
<?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\Discussion;
|
||||
|
||||
use Flarum\Core\Notification\DiscussionRenamedBlueprint;
|
||||
use Flarum\Core\Notification\NotificationSyncer;
|
||||
use Flarum\Post\DiscussionRenamedPost;
|
||||
use Flarum\Discussion\Event\Renamed;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
class DiscussionRenamedNotifier
|
||||
{
|
||||
/**
|
||||
* @var NotificationSyncer
|
||||
*/
|
||||
protected $notifications;
|
||||
|
||||
/**
|
||||
* @param NotificationSyncer $notifications
|
||||
*/
|
||||
public function __construct(NotificationSyncer $notifications)
|
||||
{
|
||||
$this->notifications = $notifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
*/
|
||||
public function subscribe(Dispatcher $events)
|
||||
{
|
||||
$events->listen(Renamed::class, [$this, 'whenDiscussionWasRenamed']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Flarum\Discussion\Event\Renamed $event
|
||||
*/
|
||||
public function whenDiscussionWasRenamed(Renamed $event)
|
||||
{
|
||||
$post = DiscussionRenamedPost::reply(
|
||||
$event->discussion->id,
|
||||
$event->actor->id,
|
||||
$event->oldTitle,
|
||||
$event->discussion->title
|
||||
);
|
||||
|
||||
$post = $event->discussion->mergePost($post);
|
||||
|
||||
if ($event->discussion->start_user_id !== $event->actor->id) {
|
||||
$blueprint = new DiscussionRenamedBlueprint($post);
|
||||
|
||||
if ($post->exists) {
|
||||
$this->notifications->sync($blueprint, [$event->discussion->startUser]);
|
||||
} else {
|
||||
$this->notifications->delete($blueprint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
75
src/Discussion/DiscussionRepository.php
Normal file
75
src/Discussion/DiscussionRepository.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?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\Discussion;
|
||||
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
|
||||
class DiscussionRepository
|
||||
{
|
||||
/**
|
||||
* Get a new query builder for the discussions table.
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
public function query()
|
||||
{
|
||||
return Discussion::query();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a discussion by ID, optionally making sure it is visible to a
|
||||
* certain user, or throw an exception.
|
||||
*
|
||||
* @param int $id
|
||||
* @param User $user
|
||||
* @return \Flarum\Discussion\Discussion
|
||||
*/
|
||||
public function findOrFail($id, User $user = null)
|
||||
{
|
||||
$query = Discussion::where('id', $id);
|
||||
|
||||
return $this->scopeVisibleTo($query, $user)->firstOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IDs of discussions which a user has read completely.
|
||||
*
|
||||
* @param User $user
|
||||
* @return array
|
||||
*/
|
||||
public function getReadIds(User $user)
|
||||
{
|
||||
return Discussion::leftJoin('users_discussions', 'users_discussions.discussion_id', '=', 'discussions.id')
|
||||
->where('user_id', $user->id)
|
||||
->where('read_number', '>=', new Expression('last_post_number'))
|
||||
->lists('id')
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include records that are visible to a user.
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param User $user
|
||||
* @return Builder
|
||||
*/
|
||||
protected function scopeVisibleTo(Builder $query, User $user = null)
|
||||
{
|
||||
if ($user !== null) {
|
||||
$query->whereVisibleTo($user);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
25
src/Discussion/DiscussionValidator.php
Normal file
25
src/Discussion/DiscussionValidator.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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\Discussion;
|
||||
|
||||
use Flarum\Foundation\AbstractValidator;
|
||||
|
||||
class DiscussionValidator extends AbstractValidator
|
||||
{
|
||||
protected $rules = [
|
||||
'title' => [
|
||||
'required',
|
||||
'min:3',
|
||||
'max:80'
|
||||
]
|
||||
];
|
||||
}
|
38
src/Discussion/Event/Deleted.php
Normal file
38
src/Discussion/Event/Deleted.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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\Discussion\Event;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\User\User;
|
||||
|
||||
class Deleted
|
||||
{
|
||||
/**
|
||||
* @var \Flarum\Discussion\Discussion
|
||||
*/
|
||||
public $discussion;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Discussion\Discussion $discussion
|
||||
* @param User $actor
|
||||
*/
|
||||
public function __construct(Discussion $discussion, User $actor = null)
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
$this->actor = $actor;
|
||||
}
|
||||
}
|
51
src/Discussion/Event/Deleting.php
Normal file
51
src/Discussion/Event/Deleting.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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\Discussion\Event;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\User\User;
|
||||
|
||||
class Deleting
|
||||
{
|
||||
/**
|
||||
* The discussion that is going to be deleted.
|
||||
*
|
||||
* @var Discussion
|
||||
*/
|
||||
public $discussion;
|
||||
|
||||
/**
|
||||
* The user who is performing the action.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* Any user input associated with the command.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param Discussion $discussion
|
||||
* @param User $actor
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(Discussion $discussion, User $actor, array $data = [])
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
38
src/Discussion/Event/Hidden.php
Normal file
38
src/Discussion/Event/Hidden.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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\Discussion\Event;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\User\User;
|
||||
|
||||
class Hidden
|
||||
{
|
||||
/**
|
||||
* @var \Flarum\Discussion\Discussion
|
||||
*/
|
||||
public $discussion;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Discussion\Discussion $discussion
|
||||
* @param User $actor
|
||||
*/
|
||||
public function __construct(Discussion $discussion, User $actor = null)
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
$this->actor = $actor;
|
||||
}
|
||||
}
|
45
src/Discussion/Event/Renamed.php
Normal file
45
src/Discussion/Event/Renamed.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\Discussion\Event;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\User\User;
|
||||
|
||||
class Renamed
|
||||
{
|
||||
/**
|
||||
* @var Discussion
|
||||
*/
|
||||
public $discussion;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $oldTitle;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Discussion\Discussion $discussion
|
||||
* @param User $actor
|
||||
* @param string $oldTitle
|
||||
*/
|
||||
public function __construct(Discussion $discussion, $oldTitle, User $actor = null)
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
$this->oldTitle = $oldTitle;
|
||||
$this->actor = $actor;
|
||||
}
|
||||
}
|
38
src/Discussion/Event/Restored.php
Normal file
38
src/Discussion/Event/Restored.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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\Discussion\Event;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\User\User;
|
||||
|
||||
class Restored
|
||||
{
|
||||
/**
|
||||
* @var \Flarum\Discussion\Discussion
|
||||
*/
|
||||
public $discussion;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Discussion\Discussion $discussion
|
||||
* @param User $actor
|
||||
*/
|
||||
public function __construct(Discussion $discussion, User $actor = null)
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
$this->actor = $actor;
|
||||
}
|
||||
}
|
51
src/Discussion/Event/Saving.php
Normal file
51
src/Discussion/Event/Saving.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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\Discussion\Event;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\User\User;
|
||||
|
||||
class Saving
|
||||
{
|
||||
/**
|
||||
* The discussion that will be saved.
|
||||
*
|
||||
* @var \Flarum\Discussion\Discussion
|
||||
*/
|
||||
public $discussion;
|
||||
|
||||
/**
|
||||
* The user who is performing the action.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* Any user input associated with the command.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Discussion\Discussion $discussion
|
||||
* @param User $actor
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(Discussion $discussion, User $actor, array $data = [])
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
38
src/Discussion/Event/Started.php
Normal file
38
src/Discussion/Event/Started.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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\Discussion\Event;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\User\User;
|
||||
|
||||
class Started
|
||||
{
|
||||
/**
|
||||
* @var \Flarum\Discussion\Discussion
|
||||
*/
|
||||
public $discussion;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Discussion\Discussion $discussion
|
||||
* @param User $actor
|
||||
*/
|
||||
public function __construct(Discussion $discussion, User $actor = null)
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
$this->actor = $actor;
|
||||
}
|
||||
}
|
30
src/Discussion/Event/UserDataSaving.php
Normal file
30
src/Discussion/Event/UserDataSaving.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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\Discussion\Event;
|
||||
|
||||
use Flarum\Core\DiscussionState;
|
||||
|
||||
class UserDataSaving
|
||||
{
|
||||
/**
|
||||
* @var DiscussionState
|
||||
*/
|
||||
public $state;
|
||||
|
||||
/**
|
||||
* @param DiscussionState $state
|
||||
*/
|
||||
public function __construct(DiscussionState $state)
|
||||
{
|
||||
$this->state = $state;
|
||||
}
|
||||
}
|
30
src/Discussion/Event/UserRead.php
Normal file
30
src/Discussion/Event/UserRead.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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\Discussion\Event;
|
||||
|
||||
use Flarum\Core\DiscussionState;
|
||||
|
||||
class UserRead
|
||||
{
|
||||
/**
|
||||
* @var DiscussionState
|
||||
*/
|
||||
public $state;
|
||||
|
||||
/**
|
||||
* @param DiscussionState $state
|
||||
*/
|
||||
public function __construct(DiscussionState $state)
|
||||
{
|
||||
$this->state = $state;
|
||||
}
|
||||
}
|
53
src/Discussion/Search/DiscussionSearch.php
Normal file
53
src/Discussion/Search/DiscussionSearch.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?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\Discussion\Search;
|
||||
|
||||
use Flarum\Core\Search\AbstractSearch;
|
||||
|
||||
/**
|
||||
* An object which represents the internal state of a search for discussions:
|
||||
* the search query, the user performing the search, the fallback sort order,
|
||||
* relevant post information, and a log of which gambits have been used.
|
||||
*/
|
||||
class DiscussionSearch extends AbstractSearch
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultSort = ['lastTime' => 'desc'];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $relevantPostIds = [];
|
||||
|
||||
/**
|
||||
* Get the related IDs for each result.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function getRelevantPostIds()
|
||||
{
|
||||
return $this->relevantPostIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relevant post IDs for the results.
|
||||
*
|
||||
* @param array $relevantPostIds
|
||||
* @return void
|
||||
*/
|
||||
public function setRelevantPostIds(array $relevantPostIds)
|
||||
{
|
||||
$this->relevantPostIds = $relevantPostIds;
|
||||
}
|
||||
}
|
138
src/Discussion/Search/DiscussionSearcher.php
Normal file
138
src/Discussion/Search/DiscussionSearcher.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?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\Discussion\Search;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\DiscussionRepository;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use Flarum\Post\PostRepository;
|
||||
use Flarum\Core\Search\ApplySearchParametersTrait;
|
||||
use Flarum\Core\Search\GambitManager;
|
||||
use Flarum\Core\Search\SearchCriteria;
|
||||
use Flarum\Core\Search\SearchResults;
|
||||
use Flarum\Event\ConfigureDiscussionSearch;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
/**
|
||||
* Takes a DiscussionSearchCriteria object, performs a search using gambits,
|
||||
* and spits out a DiscussionSearchResults object.
|
||||
*/
|
||||
class DiscussionSearcher
|
||||
{
|
||||
use ApplySearchParametersTrait;
|
||||
|
||||
/**
|
||||
* @var GambitManager
|
||||
*/
|
||||
protected $gambits;
|
||||
|
||||
/**
|
||||
* @var DiscussionRepository
|
||||
*/
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @var PostRepository
|
||||
*/
|
||||
protected $posts;
|
||||
|
||||
/**
|
||||
* @param GambitManager $gambits
|
||||
* @param DiscussionRepository $discussions
|
||||
* @param PostRepository $posts
|
||||
*/
|
||||
public function __construct(
|
||||
GambitManager $gambits,
|
||||
DiscussionRepository $discussions,
|
||||
PostRepository $posts
|
||||
) {
|
||||
$this->gambits = $gambits;
|
||||
$this->discussions = $discussions;
|
||||
$this->posts = $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchCriteria $criteria
|
||||
* @param int|null $limit
|
||||
* @param int $offset
|
||||
* @param array $load An array of relationships to load on the results.
|
||||
* @return SearchResults
|
||||
*/
|
||||
public function search(SearchCriteria $criteria, $limit = null, $offset = 0, array $load = [])
|
||||
{
|
||||
$actor = $criteria->actor;
|
||||
|
||||
$query = $this->discussions->query()->whereVisibleTo($actor);
|
||||
|
||||
// Construct an object which represents this search for discussions.
|
||||
// Apply gambits to it, sort, and paging criteria. Also give extensions
|
||||
// an opportunity to modify it.
|
||||
$search = new DiscussionSearch($query->getQuery(), $actor);
|
||||
|
||||
$this->gambits->apply($search, $criteria->query);
|
||||
$this->applySort($search, $criteria->sort);
|
||||
$this->applyOffset($search, $offset);
|
||||
$this->applyLimit($search, $limit + 1);
|
||||
|
||||
// TODO: inject dispatcher
|
||||
event(new ConfigureDiscussionSearch($search, $criteria));
|
||||
|
||||
// Execute the search query and retrieve the results. We get one more
|
||||
// results than the user asked for, so that we can say if there are more
|
||||
// results. If there are, we will get rid of that extra result.
|
||||
$discussions = $query->get();
|
||||
|
||||
$areMoreResults = $limit > 0 && $discussions->count() > $limit;
|
||||
|
||||
if ($areMoreResults) {
|
||||
$discussions->pop();
|
||||
}
|
||||
|
||||
// The relevant posts relationship isn't a typical Eloquent
|
||||
// relationship; rather, we need to extract that information from our
|
||||
// search object. We will delegate that task and prevent Eloquent
|
||||
// from trying to load it.
|
||||
if (in_array('relevantPosts', $load)) {
|
||||
$this->loadRelevantPosts($discussions, $search);
|
||||
|
||||
$load = array_diff($load, ['relevantPosts', 'relevantPosts.discussion', 'relevantPosts.user']);
|
||||
}
|
||||
|
||||
Discussion::setStateUser($actor);
|
||||
$discussions->load($load);
|
||||
|
||||
return new SearchResults($discussions, $areMoreResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load relevant posts onto each discussion using information from the
|
||||
* search.
|
||||
*
|
||||
* @param Collection $discussions
|
||||
* @param DiscussionSearch $search
|
||||
*/
|
||||
protected function loadRelevantPosts(Collection $discussions, DiscussionSearch $search)
|
||||
{
|
||||
$postIds = [];
|
||||
foreach ($search->getRelevantPostIds() as $relevantPostIds) {
|
||||
$postIds = array_merge($postIds, array_slice($relevantPostIds, 0, 2));
|
||||
}
|
||||
|
||||
$posts = $postIds ? $this->posts->findByIds($postIds, $search->getActor())->load('user')->all() : [];
|
||||
|
||||
foreach ($discussions as $discussion) {
|
||||
$discussion->relevantPosts = array_filter($posts, function ($post) use ($discussion) {
|
||||
return $post->discussion_id == $discussion->id;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
24
src/Discussion/Search/Fulltext/DriverInterface.php
Normal file
24
src/Discussion/Search/Fulltext/DriverInterface.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?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\Discussion\Search\Fulltext;
|
||||
|
||||
interface DriverInterface
|
||||
{
|
||||
/**
|
||||
* Return an array of arrays of post IDs, grouped by discussion ID, which
|
||||
* match the given string.
|
||||
*
|
||||
* @param string $string
|
||||
* @return array
|
||||
*/
|
||||
public function match($string);
|
||||
}
|
36
src/Discussion/Search/Fulltext/MySqlFulltextDriver.php
Normal file
36
src/Discussion/Search/Fulltext/MySqlFulltextDriver.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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\Discussion\Search\Fulltext;
|
||||
|
||||
use Flarum\Core\Post;
|
||||
|
||||
class MySqlFulltextDriver implements DriverInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function match($string)
|
||||
{
|
||||
$discussionIds = Post::where('type', 'comment')
|
||||
->whereRaw('MATCH (`content`) AGAINST (? IN BOOLEAN MODE)', [$string])
|
||||
->orderByRaw('MATCH (`content`) AGAINST (?) DESC', [$string])
|
||||
->lists('discussion_id', 'id');
|
||||
|
||||
$relevantPostIds = [];
|
||||
|
||||
foreach ($discussionIds as $postId => $discussionId) {
|
||||
$relevantPostIds[$discussionId][] = $postId;
|
||||
}
|
||||
|
||||
return $relevantPostIds;
|
||||
}
|
||||
}
|
59
src/Discussion/Search/Gambit/AuthorGambit.php
Normal file
59
src/Discussion/Search/Gambit/AuthorGambit.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\User\UserRepository;
|
||||
use Flarum\Core\Search\AbstractRegexGambit;
|
||||
use Flarum\Core\Search\AbstractSearch;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use LogicException;
|
||||
|
||||
class AuthorGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'author:(.+)';
|
||||
|
||||
/**
|
||||
* @var \Flarum\User\UserRepository
|
||||
*/
|
||||
protected $users;
|
||||
|
||||
/**
|
||||
* @param \Flarum\User\UserRepository $users
|
||||
*/
|
||||
public function __construct(UserRepository $users)
|
||||
{
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
$usernames = trim($matches[1], '"');
|
||||
$usernames = explode(',', $usernames);
|
||||
|
||||
$ids = [];
|
||||
foreach ($usernames as $username) {
|
||||
$ids[] = $this->users->getIdForUsername($username);
|
||||
}
|
||||
|
||||
$search->getQuery()->whereIn('start_user_id', $ids, 'and', $negate);
|
||||
}
|
||||
}
|
45
src/Discussion/Search/Gambit/CreatedGambit.php
Normal file
45
src/Discussion/Search/Gambit/CreatedGambit.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Core\Search\AbstractRegexGambit;
|
||||
use Flarum\Core\Search\AbstractSearch;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use LogicException;
|
||||
|
||||
class CreatedGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'created:(\d{4}\-\d\d\-\d\d)(\.\.(\d{4}\-\d\d\-\d\d))?';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
// If we've just been provided with a single YYYY-MM-DD date, then find
|
||||
// discussions that were started on that exact date. But if we've been
|
||||
// provided with a YYYY-MM-DD..YYYY-MM-DD range, then find discussions
|
||||
// that were started during that period.
|
||||
if (empty($matches[3])) {
|
||||
$search->getQuery()->whereDate('start_time', $negate ? '!=' : '=', $matches[1]);
|
||||
} else {
|
||||
$search->getQuery()->whereBetween('start_time', [$matches[1], $matches[3]], 'and', $negate);
|
||||
}
|
||||
}
|
||||
}
|
54
src/Discussion/Search/Gambit/FulltextGambit.php
Normal file
54
src/Discussion/Search/Gambit/FulltextGambit.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?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\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Core\Search\AbstractSearch;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use Flarum\Discussion\Search\Fulltext\DriverInterface;
|
||||
use Flarum\Core\Search\GambitInterface;
|
||||
use LogicException;
|
||||
|
||||
class FulltextGambit implements GambitInterface
|
||||
{
|
||||
/**
|
||||
* @var \Flarum\Discussion\Search\Fulltext\DriverInterface
|
||||
*/
|
||||
protected $fulltext;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Discussion\Search\Fulltext\DriverInterface $fulltext
|
||||
*/
|
||||
public function __construct(DriverInterface $fulltext)
|
||||
{
|
||||
$this->fulltext = $fulltext;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(AbstractSearch $search, $bit)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
$relevantPostIds = $this->fulltext->match($bit);
|
||||
|
||||
$discussionIds = array_keys($relevantPostIds);
|
||||
|
||||
$search->setRelevantPostIds($relevantPostIds);
|
||||
|
||||
$search->getQuery()->whereIn('id', $discussionIds);
|
||||
|
||||
$search->setDefaultSort(['id' => $discussionIds]);
|
||||
}
|
||||
}
|
43
src/Discussion/Search/Gambit/HiddenGambit.php
Normal file
43
src/Discussion/Search/Gambit/HiddenGambit.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?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\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Core\Search\AbstractRegexGambit;
|
||||
use Flarum\Core\Search\AbstractSearch;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use LogicException;
|
||||
|
||||
class HiddenGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'is:hidden';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
$search->getQuery()->where(function ($query) use ($negate) {
|
||||
if ($negate) {
|
||||
$query->whereNull('hide_time')->where('comments_count', '>', 0);
|
||||
} else {
|
||||
$query->whereNotNull('hide_time')->orWhere('comments_count', 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
63
src/Discussion/Search/Gambit/UnreadGambit.php
Normal file
63
src/Discussion/Search/Gambit/UnreadGambit.php
Normal 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\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Discussion\DiscussionRepository;
|
||||
use Flarum\Core\Search\AbstractRegexGambit;
|
||||
use Flarum\Core\Search\AbstractSearch;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use LogicException;
|
||||
|
||||
class UnreadGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'is:unread';
|
||||
|
||||
/**
|
||||
* @var \Flarum\Discussion\DiscussionRepository
|
||||
*/
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Discussion\DiscussionRepository $discussions
|
||||
*/
|
||||
public function __construct(DiscussionRepository $discussions)
|
||||
{
|
||||
$this->discussions = $discussions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
$actor = $search->getActor();
|
||||
|
||||
if ($actor->exists) {
|
||||
$readIds = $this->discussions->getReadIds($actor);
|
||||
|
||||
$search->getQuery()->where(function ($query) use ($readIds, $negate, $actor) {
|
||||
if (! $negate) {
|
||||
$query->whereNotIn('id', $readIds)->where('last_time', '>', $actor->read_time ?: 0);
|
||||
} else {
|
||||
$query->whereIn('id', $readIds)->orWhere('last_time', '<=', $actor->read_time ?: 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user