From 70a66a9529176e58d4c3d5c607962bb953997af3 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Sat, 15 Dec 2018 17:23:48 +0100 Subject: [PATCH] Get rid of event subscribers that resolve services too early Refs flarum/core#1578. --- extensions/mentions/extend.php | 30 +++- .../src/Listener/AddFilterByMentions.php | 14 +- .../AddPostMentionedByRelationship.php | 76 ---------- .../src/Listener/FilterVisiblePosts.php | 92 ++++++++++++ .../UpdateMentionsMetadataWhenInvisible.php | 46 ++++++ .../UpdateMentionsMetadataWhenVisible.php | 87 +++++++++++ .../Listener/UpdatePostMentionsMetadata.php | 141 ------------------ .../Listener/UpdateUserMentionsMetadata.php | 139 ----------------- 8 files changed, 253 insertions(+), 372 deletions(-) create mode 100755 extensions/mentions/src/Listener/FilterVisiblePosts.php create mode 100755 extensions/mentions/src/Listener/UpdateMentionsMetadataWhenInvisible.php create mode 100755 extensions/mentions/src/Listener/UpdateMentionsMetadataWhenVisible.php delete mode 100755 extensions/mentions/src/Listener/UpdatePostMentionsMetadata.php delete mode 100755 extensions/mentions/src/Listener/UpdateUserMentionsMetadata.php diff --git a/extensions/mentions/extend.php b/extensions/mentions/extend.php index 7b0ca69c7..aa6966fc9 100644 --- a/extensions/mentions/extend.php +++ b/extensions/mentions/extend.php @@ -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); diff --git a/extensions/mentions/src/Listener/AddFilterByMentions.php b/extensions/mentions/src/Listener/AddFilterByMentions.php index 993fce78d..761c8111c 100755 --- a/extensions/mentions/src/Listener/AddFilterByMentions.php +++ b/extensions/mentions/src/Listener/AddFilterByMentions.php @@ -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') diff --git a/extensions/mentions/src/Listener/AddPostMentionedByRelationship.php b/extensions/mentions/src/Listener/AddPostMentionedByRelationship.php index fbf34b4ec..616dd3ad9 100755 --- a/extensions/mentions/src/Listener/AddPostMentionedByRelationship.php +++ b/extensions/mentions/src/Listener/AddPostMentionedByRelationship.php @@ -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; - })); - } - } - } } diff --git a/extensions/mentions/src/Listener/FilterVisiblePosts.php b/extensions/mentions/src/Listener/FilterVisiblePosts.php new file mode 100755 index 000000000..cd18d2dd2 --- /dev/null +++ b/extensions/mentions/src/Listener/FilterVisiblePosts.php @@ -0,0 +1,92 @@ + + * + * 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; + })); + } + } + } +} diff --git a/extensions/mentions/src/Listener/UpdateMentionsMetadataWhenInvisible.php b/extensions/mentions/src/Listener/UpdateMentionsMetadataWhenInvisible.php new file mode 100755 index 000000000..130b09ea7 --- /dev/null +++ b/extensions/mentions/src/Listener/UpdateMentionsMetadataWhenInvisible.php @@ -0,0 +1,46 @@ + + * + * 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([]); + } +} diff --git a/extensions/mentions/src/Listener/UpdateMentionsMetadataWhenVisible.php b/extensions/mentions/src/Listener/UpdateMentionsMetadataWhenVisible.php new file mode 100755 index 000000000..6fb632cde --- /dev/null +++ b/extensions/mentions/src/Listener/UpdateMentionsMetadataWhenVisible.php @@ -0,0 +1,87 @@ + + * + * 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]); + } + } +} diff --git a/extensions/mentions/src/Listener/UpdatePostMentionsMetadata.php b/extensions/mentions/src/Listener/UpdatePostMentionsMetadata.php deleted file mode 100755 index 3778177b8..000000000 --- a/extensions/mentions/src/Listener/UpdatePostMentionsMetadata.php +++ /dev/null @@ -1,141 +0,0 @@ - - * - * 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]); - } - } -} diff --git a/extensions/mentions/src/Listener/UpdateUserMentionsMetadata.php b/extensions/mentions/src/Listener/UpdateUserMentionsMetadata.php deleted file mode 100755 index 4a3af234e..000000000 --- a/extensions/mentions/src/Listener/UpdateUserMentionsMetadata.php +++ /dev/null @@ -1,139 +0,0 @@ - - * - * 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); - } -}