Skip to content

Commit

Permalink
Get rid of event subscribers that resolve services too early
Browse files Browse the repository at this point in the history
Refs flarum/core#1578.
  • Loading branch information
franzliedke committed Dec 16, 2018
1 parent 743f3f1 commit 9165321
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 372 deletions.
30 changes: 27 additions & 3 deletions extend.php
Expand Up @@ -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;

Expand All @@ -25,10 +36,23 @@
->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);
Expand Down
14 changes: 1 addition & 13 deletions src/Listener/AddFilterByMentions.php
Expand Up @@ -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')
Expand Down
76 changes: 0 additions & 76 deletions src/Listener/AddPostMentionedByRelationship.php
Expand Up @@ -13,32 +13,15 @@

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
*/
Expand All @@ -47,7 +30,6 @@ public function subscribe(Dispatcher $events)
$events->listen(GetModelRelationship::class, [$this, 'getModelRelationship']);
$events->listen(GetApiRelationship::class, [$this, 'getApiRelationship']);
$events->listen(WillGetData::class, [$this, 'includeRelationships']);
$events->listen(WillSerializeData::class, [$this, 'filterVisiblePosts']);
}

/**
Expand Down Expand Up @@ -117,62 +99,4 @@ public function includeRelationships(WillGetData $event)
]);
}
}

/**
* 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;
}));
}
}
}
}
92 changes: 92 additions & 0 deletions src/Listener/FilterVisiblePosts.php
@@ -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;
}));
}
}
}
}
46 changes: 46 additions & 0 deletions src/Listener/UpdateMentionsMetadataWhenInvisible.php
@@ -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([]);
}
}

0 comments on commit 9165321

Please sign in to comment.