Get rid of event subscribers that resolve services too early

Refs flarum/core#1578.
This commit is contained in:
Franz Liedke 2018-12-15 17:23:48 +01:00
parent c2cabd7c2e
commit 70a66a9529
8 changed files with 253 additions and 372 deletions

View File

@ -9,10 +9,21 @@
* file that was distributed with this source code.
*/
use Flarum\Api\Event\WillSerializeData;
use Flarum\Api\Serializer\PostSerializer;
use Flarum\Event\ConfigureNotificationTypes;
use Flarum\Event\ConfigurePostsQuery;
use Flarum\Extend;
use Flarum\Formatter\Event\Rendering;
use Flarum\Mentions\ConfigureMentions;
use Flarum\Mentions\Listener;
use Flarum\Mentions\Notification\PostMentionedBlueprint;
use Flarum\Mentions\Notification\UserMentionedBlueprint;
use Flarum\Post\Event\Deleted;
use Flarum\Post\Event\Hidden;
use Flarum\Post\Event\Posted;
use Flarum\Post\Event\Restored;
use Flarum\Post\Event\Revised;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\View\Factory;
@ -25,10 +36,23 @@ return [
->configure(ConfigureMentions::class),
function (Dispatcher $events, Factory $views) {
$events->listen(WillSerializeData::class, Listener\FilterVisiblePosts::class);
$events->subscribe(Listener\AddPostMentionedByRelationship::class);
$events->subscribe(Listener\UpdatePostMentionsMetadata::class);
$events->subscribe(Listener\UpdateUserMentionsMetadata::class);
$events->subscribe(Listener\AddFilterByMentions::class);
$events->listen(ConfigureNotificationTypes::class, function (ConfigureNotificationTypes $event) {
$event->add(PostMentionedBlueprint::class, PostSerializer::class, ['alert']);
$event->add(UserMentionedBlueprint::class, PostSerializer::class, ['alert']);
});
$events->listen(
[Posted::class, Restored::class, Revised::class],
Listener\UpdateMentionsMetadataWhenVisible::class
);
$events->listen(
[Deleted::class, Hidden::class],
Listener\UpdateMentionsMetadataWhenInvisible::class
);
$events->listen(ConfigurePostsQuery::class, Listener\AddFilterByMentions::class);
$events->listen(Rendering::class, Listener\FormatPostMentions::class);
$events->listen(Rendering::class, Listener\FormatUserMentions::class);

View File

@ -12,22 +12,10 @@
namespace Flarum\Mentions\Listener;
use Flarum\Event\ConfigurePostsQuery;
use Illuminate\Contracts\Events\Dispatcher;
class AddFilterByMentions
{
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(ConfigurePostsQuery::class, [$this, 'addFilter']);
}
/**
* @param ConfigurePostsQuery $event
*/
public function addFilter(ConfigurePostsQuery $event)
public function handle(ConfigurePostsQuery $event)
{
if ($mentionedId = array_get($event->filter, 'mentioned')) {
$event->query->join('post_mentions_user', 'posts.id', '=', 'post_mentions_user.post_id')

View File

@ -13,32 +13,15 @@ namespace Flarum\Mentions\Listener;
use Flarum\Api\Controller;
use Flarum\Api\Event\WillGetData;
use Flarum\Api\Event\WillSerializeData;
use Flarum\Api\Serializer\BasicPostSerializer;
use Flarum\Event\GetApiRelationship;
use Flarum\Event\GetModelRelationship;
use Flarum\Post\CommentPost;
use Flarum\Post\Post;
use Flarum\Post\PostRepository;
use Flarum\User\User;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\Collection;
class AddPostMentionedByRelationship
{
/**
* @var PostRepository
*/
protected $posts;
/**
* @param PostRepository $posts
*/
public function __construct(PostRepository $posts)
{
$this->posts = $posts;
}
/**
* @param Dispatcher $events
*/
@ -47,7 +30,6 @@ class AddPostMentionedByRelationship
$events->listen(GetModelRelationship::class, [$this, 'getModelRelationship']);
$events->listen(GetApiRelationship::class, [$this, 'getApiRelationship']);
$events->listen(WillGetData::class, [$this, 'includeRelationships']);
$events->listen(WillSerializeData::class, [$this, 'filterVisiblePosts']);
}
/**
@ -117,62 +99,4 @@ class AddPostMentionedByRelationship
]);
}
}
/**
* Apply visibility permissions to API data.
*
* Each post in an API document has a relationship with posts that have
* mentioned it (mentionedBy). This listener will manually filter these
* additional posts so that the user can't see any posts which they don't
* have access to.
*
* @param WillSerializeData $event
*/
public function filterVisiblePosts(WillSerializeData $event)
{
// Firstly we gather a list of posts contained within the API document.
// This will vary according to the API endpoint that is being accessed.
if ($event->isController(Controller\ShowDiscussionController::class)) {
$posts = $event->data->posts;
} elseif ($event->isController(Controller\ShowPostController::class)
|| $event->isController(Controller\CreatePostController::class)
|| $event->isController(Controller\UpdatePostController::class)) {
$posts = [$event->data];
} elseif ($event->isController(Controller\ListPostsController::class)) {
$posts = $event->data;
}
if (isset($posts)) {
$posts = new Collection($posts);
$posts = $posts->filter(function ($post) {
return $post instanceof CommentPost;
});
// Load all of the users that these posts mention. This way the data
// will be ready to go when we need to sub in current usernames
// during the rendering process.
$posts->load(['mentionsUsers', 'mentionsPosts.user']);
// Construct a list of the IDs of all of the posts that these posts
// have been mentioned in. We can then filter this list of IDs to
// weed out all of the ones which the user is not meant to see.
$ids = [];
foreach ($posts as $post) {
$ids = array_merge($ids, $post->mentionedBy->pluck('id')->all());
}
$ids = $this->posts->filterVisibleIds($ids, $event->actor);
// Finally, go back through each of the posts and filter out any
// of the posts in the relationship data that we now know are
// invisible to the user.
foreach ($posts as $post) {
$post->setRelation('mentionedBy', $post->mentionedBy->filter(function ($post) use ($ids) {
return array_search($post->id, $ids) !== false;
}));
}
}
}
}

View File

@ -0,0 +1,92 @@
<?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\Mentions\Listener;
use Flarum\Api\Controller;
use Flarum\Api\Event\WillSerializeData;
use Flarum\Post\CommentPost;
use Flarum\Post\PostRepository;
use Illuminate\Database\Eloquent\Collection;
class FilterVisiblePosts
{
/**
* @var PostRepository
*/
protected $posts;
/**
* @param PostRepository $posts
*/
public function __construct(PostRepository $posts)
{
$this->posts = $posts;
}
/**
* Apply visibility permissions to API data.
*
* Each post in an API document has a relationship with posts that have
* mentioned it (mentionedBy). This listener will manually filter these
* additional posts so that the user can't see any posts which they don't
* have access to.
*
* @param WillSerializeData $event
*/
public function handle(WillSerializeData $event)
{
// Firstly we gather a list of posts contained within the API document.
// This will vary according to the API endpoint that is being accessed.
if ($event->isController(Controller\ShowDiscussionController::class)) {
$posts = $event->data->posts;
} elseif ($event->isController(Controller\ShowPostController::class)
|| $event->isController(Controller\CreatePostController::class)
|| $event->isController(Controller\UpdatePostController::class)) {
$posts = [$event->data];
} elseif ($event->isController(Controller\ListPostsController::class)) {
$posts = $event->data;
}
if (isset($posts)) {
$posts = new Collection($posts);
$posts = $posts->filter(function ($post) {
return $post instanceof CommentPost;
});
// Load all of the users that these posts mention. This way the data
// will be ready to go when we need to sub in current usernames
// during the rendering process.
$posts->load(['mentionsUsers', 'mentionsPosts.user']);
// Construct a list of the IDs of all of the posts that these posts
// have been mentioned in. We can then filter this list of IDs to
// weed out all of the ones which the user is not meant to see.
$ids = [];
foreach ($posts as $post) {
$ids = array_merge($ids, $post->mentionedBy->pluck('id')->all());
}
$ids = $this->posts->filterVisibleIds($ids, $event->actor);
// Finally, go back through each of the posts and filter out any
// of the posts in the relationship data that we now know are
// invisible to the user.
foreach ($posts as $post) {
$post->setRelation('mentionedBy', $post->mentionedBy->filter(function ($post) use ($ids) {
return array_search($post->id, $ids) !== false;
}));
}
}
}
}

View File

@ -0,0 +1,46 @@
<?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\Mentions\Listener;
use Flarum\Mentions\Notification\UserMentionedBlueprint;
use Flarum\Notification\NotificationSyncer;
use Flarum\Post\Event\Deleted;
use Flarum\Post\Event\Hidden;
class UpdateMentionsMetadataWhenInvisible
{
/**
* @var NotificationSyncer
*/
protected $notifications;
/**
* @param NotificationSyncer $notifications
*/
public function __construct(NotificationSyncer $notifications)
{
$this->notifications = $notifications;
}
/**
* @param Deleted|Hidden $event
*/
public function handle($event)
{
// Remove user mentions
$event->post->mentionsUsers()->sync([]);
$this->notifications->sync(new UserMentionedBlueprint($event->post), []);
// Remove post mentions
$event->post->mentionsPosts()->sync([]);
}
}

View File

@ -0,0 +1,87 @@
<?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\Mentions\Listener;
use Flarum\Mentions\Notification\PostMentionedBlueprint;
use Flarum\Mentions\Notification\UserMentionedBlueprint;
use Flarum\Notification\NotificationSyncer;
use Flarum\Post\Event\Posted;
use Flarum\Post\Event\Restored;
use Flarum\Post\Event\Revised;
use Flarum\Post\Post;
use Flarum\User\User;
use s9e\TextFormatter\Utils;
class UpdateMentionsMetadataWhenVisible
{
/**
* @var NotificationSyncer
*/
protected $notifications;
/**
* @param NotificationSyncer $notifications
*/
public function __construct(NotificationSyncer $notifications)
{
$this->notifications = $notifications;
}
/**
* @param Posted|Restored|Revised $event
*/
public function handle($event)
{
$content = $event->post->parsedContent;
$this->syncUserMentions(
$event->post,
Utils::getAttributeValues($content, 'USERMENTION', 'id')
);
$this->syncPostMentions(
$event->post,
Utils::getAttributeValues($content, 'POSTMENTION', 'id')
);
}
protected function syncUserMentions(Post $post, array $mentioned)
{
$post->mentionsUsers()->sync($mentioned);
$users = User::whereIn('id', $mentioned)
->get()
->filter(function ($user) use ($post) {
return $post->isVisibleTo($user) && $user->id !== $post->user->id;
})
->all();
$this->notifications->sync(new UserMentionedBlueprint($post), $users);
}
protected function syncPostMentions(Post $reply, array $mentioned)
{
$reply->mentionsPosts()->sync($mentioned);
$posts = Post::with('user')
->whereIn('id', $mentioned)
->get()
->filter(function ($post) use ($reply) {
return $post->user && $post->user->id !== $reply->user_id;
})
->all();
foreach ($posts as $post) {
$this->notifications->sync(new PostMentionedBlueprint($post, $reply), [$post->user]);
}
}
}

View File

@ -1,141 +0,0 @@
<?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\Mentions\Listener;
use Flarum\Api\Serializer\PostSerializer;
use Flarum\Event\ConfigureNotificationTypes;
use Flarum\Mentions\Notification\PostMentionedBlueprint;
use Flarum\Notification\NotificationSyncer;
use Flarum\Post\Event\Deleted;
use Flarum\Post\Event\Hidden;
use Flarum\Post\Event\Posted;
use Flarum\Post\Event\Restored;
use Flarum\Post\Event\Revised;
use Flarum\Post\Post;
use Illuminate\Contracts\Events\Dispatcher;
use s9e\TextFormatter\Utils;
class UpdatePostMentionsMetadata
{
/**
* @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(ConfigureNotificationTypes::class, [$this, 'addNotificationType']);
$events->listen(Posted::class, [$this, 'whenPosted']);
$events->listen(Revised::class, [$this, 'whenRevised']);
$events->listen(Hidden::class, [$this, 'whenHidden']);
$events->listen(Restored::class, [$this, 'whenRestored']);
$events->listen(Deleted::class, [$this, 'whenDeleted']);
}
/**
* @param ConfigureNotificationTypes $event
*/
public function addNotificationType(ConfigureNotificationTypes $event)
{
$event->add(PostMentionedBlueprint::class, PostSerializer::class, ['alert']);
}
/**
* @param Posted $event
*/
public function whenPosted(Posted $event)
{
$this->replyBecameVisible($event->post);
}
/**
* @param Revised $event
*/
public function whenRevised(Revised $event)
{
$this->replyBecameVisible($event->post);
}
/**
* @param Hidden $event
*/
public function whenHidden(Hidden $event)
{
$this->replyBecameInvisible($event->post);
}
/**
* @param Restored $event
*/
public function whenRestored(Restored $event)
{
$this->replyBecameVisible($event->post);
}
/**
* @param Deleted $event
*/
public function whenDeleted(Deleted $event)
{
$this->replyBecameInvisible($event->post);
}
/**
* @param Post $reply
*/
protected function replyBecameVisible(Post $reply)
{
$mentioned = Utils::getAttributeValues($reply->parsedContent, 'POSTMENTION', 'id');
$this->sync($reply, $mentioned);
}
/**
* @param Post $reply
*/
protected function replyBecameInvisible(Post $reply)
{
$this->sync($reply, []);
}
/**
* @param Post $reply
* @param array $mentioned
*/
protected function sync(Post $reply, array $mentioned)
{
$reply->mentionsPosts()->sync($mentioned);
$posts = Post::with('user')
->whereIn('id', $mentioned)
->get()
->filter(function ($post) use ($reply) {
return $post->user && $post->user->id !== $reply->user_id;
})
->all();
foreach ($posts as $post) {
$this->notifications->sync(new PostMentionedBlueprint($post, $reply), [$post->user]);
}
}
}

View File

@ -1,139 +0,0 @@
<?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\Mentions\Listener;
use Flarum\Api\Serializer\PostSerializer;
use Flarum\Event\ConfigureNotificationTypes;
use Flarum\Mentions\Notification\UserMentionedBlueprint;
use Flarum\Notification\NotificationSyncer;
use Flarum\Post\Event\Deleted;
use Flarum\Post\Event\Hidden;
use Flarum\Post\Event\Posted;
use Flarum\Post\Event\Restored;
use Flarum\Post\Event\Revised;
use Flarum\Post\Post;
use Flarum\User\User;
use Illuminate\Contracts\Events\Dispatcher;
use s9e\TextFormatter\Utils;
class UpdateUserMentionsMetadata
{
/**
* @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(ConfigureNotificationTypes::class, [$this, 'addNotificationType']);
$events->listen(Posted::class, [$this, 'whenPosted']);
$events->listen(Revised::class, [$this, 'whenRevised']);
$events->listen(Hidden::class, [$this, 'whenHidden']);
$events->listen(Restored::class, [$this, 'whenRestored']);
$events->listen(Deleted::class, [$this, 'whenDeleted']);
}
/**
* @param ConfigureNotificationTypes $event
*/
public function addNotificationType(ConfigureNotificationTypes $event)
{
$event->add(UserMentionedBlueprint::class, PostSerializer::class, ['alert']);
}
/**
* @param Posted $event
*/
public function whenPosted(Posted $event)
{
$this->postBecameVisible($event->post);
}
/**
* @param Revised $event
*/
public function whenRevised(Revised $event)
{
$this->postBecameVisible($event->post);
}
/**
* @param Hidden $event
*/
public function whenHidden(Hidden $event)
{
$this->postBecameInvisible($event->post);
}
/**
* @param Restored $event
*/
public function whenRestored(Restored $event)
{
$this->postBecameVisible($event->post);
}
/**
* @param Deleted $event
*/
public function whenDeleted(Deleted $event)
{
$this->postBecameInvisible($event->post);
}
/**
* @param Post $post
*/
protected function postBecameVisible(Post $post)
{
$mentioned = Utils::getAttributeValues($post->parsedContent, 'USERMENTION', 'id');
$this->sync($post, $mentioned);
}
/**
* @param Post $post
*/
protected function postBecameInvisible(Post $post)
{
$this->sync($post, []);
}
/**
* @param Post $post
* @param array $mentioned
*/
protected function sync(Post $post, array $mentioned)
{
$post->mentionsUsers()->sync($mentioned);
$users = User::whereIn('id', $mentioned)
->get()
->filter(function ($user) use ($post) {
return $post->isVisibleTo($user) && $user->id !== $post->user->id;
})
->all();
$this->notifications->sync(new UserMentionedBlueprint($post), $users);
}
}