diff --git a/extensions/tags/src/Tag.php b/extensions/tags/src/Tag.php index 2e2aabd4a..5ba5fb3ac 100644 --- a/extensions/tags/src/Tag.php +++ b/extensions/tags/src/Tag.php @@ -22,4 +22,21 @@ class Tag extends Model { return $this->belongsTo('Flarum\Core\Models\Discussion', 'last_discussion_id'); } + + public static function getVisibleTo($user) + { + static $tags; + if (!$tags) { + $tags = static::all(); + } + + $ids = []; + foreach ($tags as $tag) { + if (! $tag->is_restricted || $user->hasPermission('tag'.$tag->id.'.view')) { + $ids[] = $tag->id; + } + } + + return $ids; + } } diff --git a/extensions/tags/src/TagsServiceProvider.php b/extensions/tags/src/TagsServiceProvider.php index daaa4163c..3f368db50 100644 --- a/extensions/tags/src/TagsServiceProvider.php +++ b/extensions/tags/src/TagsServiceProvider.php @@ -1,132 +1,12 @@ extend([ - new ForumAssets([ - __DIR__.'/../js/dist/extension.js', - __DIR__.'/../less/extension.less' - ]), - - new EventSubscribers([ - 'Flarum\Tags\Handlers\DiscussionTaggedNotifier', - 'Flarum\Tags\Handlers\TagSaver', - 'Flarum\Tags\Handlers\TagLoader' - ]), - - new Relationship('Flarum\Core\Models\Discussion', 'tags', function ($model) { - return $model->belongsToMany('Flarum\Tags\Tag', 'discussions_tags'); - }), - - new SerializeRelationship('Flarum\Api\Serializers\DiscussionBasicSerializer', 'hasMany', 'tags', 'Flarum\Tags\TagSerializer'), - - new ApiInclude(['discussions.index', 'discussions.show'], 'tags', true), - - new SerializeRelationship('Flarum\Api\Serializers\ForumSerializer', 'hasMany', 'tags', 'Flarum\Tags\TagSerializer'), - - new ApiInclude(['forum.show'], ['tags', 'tags.lastDiscussion'], true), - new ApiLink(['forum.show'], ['tags.parent'], true), - - (new Permission('discussion.tag')) - ->serialize(), - // ->grant(function ($grant, $user) { - // $grant->where('start_user_id', $user->id); - // // @todo add limitations to time etc. according to a config setting - // }), - - new DiscussionGambit('Flarum\Tags\TagGambit'), - - new PostType('Flarum\Tags\DiscussionTaggedPost') - ]); - - Tag::scopeVisible(function ($query, User $user) { - $query->whereIn('id', $this->getTagsWithPermission($user, 'view')); - }); - - Tag::allow('startDiscussion', function (Tag $tag, User $user) { - if (! $tag->is_restricted || $user->hasPermission('tag'.$tag->id.'.startDiscussion')) { - return true; - } - }); - - Discussion::scopeVisible(function ($query, User $user) { - $query->whereNotExists(function ($query) use ($user) { - return $query->select(app('db')->raw(1)) - ->from('discussions_tags') - ->whereNotIn('tag_id', $this->getTagsWithPermission($user, 'view')) - ->whereRaw('discussion_id = discussions.id'); - }); - }); - - Discussion::allow('*', function (Discussion $discussion, User $user, $action) { - $tags = $discussion->getRelation('tags'); - - if (! count($tags)) { - return; - } - - $restricted = true; - - // If the discussion has a tag that has been restricted, and the user - // has this permission for that tag, then they are allowed. If the - // discussion only has tags that have been restricted, then the user - // *must* have permission for at least one of them. Otherwise, inherit - // global permissions. - foreach ($tags as $tag) { - if ($tag->is_restricted) { - if ($user->hasPermission('tag'.$tag->id.'.discussion.'.$action)) { - return true; - } - } else { - $restricted = false; - } - } - - if ($restricted) { - return false; - } - }); - - Post::allow('*', function (Post $post, User $user, $action) { - return $post->discussion->can($user, $action.'Posts'); - }); - } - - protected function getTagsWithPermission($user, $permission) { - static $tags; - if (!$tags) $tags = Tag::all(); - - $ids = []; - foreach ($tags as $tag) { - if (! $tag->is_restricted || $user->hasPermission('tag'.$tag->id.'.'.$permission)) { - $ids[] = $tag->id; - } - } - - return $ids; - } - /** * Register the service provider. * @@ -139,4 +19,117 @@ class TagsServiceProvider extends ServiceProvider 'Flarum\Tags\EloquentTagRepository' ); } + + /** + * Bootstrap the application events. + * + * @return void + */ + public function boot() + { + $this->extend([ + (new Extend\ForumClient()) + ->assets([ + __DIR__.'/../js/dist/extension.js', + __DIR__.'/../less/extension.less' + ]), + + (new Extend\Model('Flarum\Tags\Tag')) + // Hide tags that the user doesn't have permission to see. + ->scopeVisible(function ($query, User $user) { + $query->whereIn('id', Tag::getVisibleTo($user)); + }) + + // Allow the user to start discussions in tags which aren't + // restricted, or for which the user has explicitly been granted + // permission. + ->allow('startDiscussion', function (Tag $tag, User $user) { + if (! $tag->is_restricted || $user->hasPermission('tag'.$tag->id.'.startDiscussion')) { + return true; + } + }), + + // Expose the complete tag list to clients by adding it as a + // relationship to the /api/forum endpoint. Since the Forum model + // doesn't actually have a tags relationship, we will manually + // load and assign the tags data to it using an event listener. + (new Extend\ApiSerializer('Flarum\Api\Serializers\ForumSerializer')) + ->hasMany('tags', 'Flarum\Tags\TagSerializer'), + + (new Extend\ApiAction('Flarum\Api\Actions\Forum\ShowAction')) + ->addInclude('tags') + ->addInclude('tags.lastDiscussion') + ->addLink('tags.parent'), + + new Extend\EventSubscriber('Flarum\Tags\Handlers\TagLoader'), + + // Extend the Discussion model and API: add the tags relationship + // and modify permissions. + (new Extend\Model('Flarum\Core\Models\Discussion')) + ->belongsToMany('tags', 'Flarum\Tags\Tag', 'discussions_tags') + + // Hide discussions which have tags that the user is not allowed + // to see. + ->scopeVisible(function ($query, User $user) { + $query->whereNotExists(function ($query) use ($user) { + return $query->select(app('db')->raw(1)) + ->from('discussions_tags') + ->whereNotIn('tag_id', Tag::getVisibleTo($user)) + ->whereRaw('discussion_id = discussions.id'); + }); + }) + + // Wrap all discussion permission checks with some logic + // pertaining to the discussion's tags. If the discussion has a + // tag that has been restricted, and the user has this + // permission for that tag, then they are allowed. If the + // discussion only has tags that have been restricted, then the + // user *must* have permission for at least one of them. + ->allow('*', function (Discussion $discussion, User $user, $action) { + $tags = $discussion->getRelation('tags'); + + if (count($tags)) { + $restricted = true; + + foreach ($tags as $tag) { + if ($tag->is_restricted) { + if ($user->hasPermission('tag'.$tag->id.'.discussion.'.$action)) { + return true; + } + } else { + $restricted = false; + } + } + + if ($restricted) { + return false; + } + } + }), + + (new Extend\ApiSerializer('Flarum\Api\Serializers\DiscussionBasicSerializer')) + ->hasMany('tags', 'Flarum\Tags\TagSerializer') + ->attributes(function (&$attributes, $discussion, $user) { + $attributes['canTag'] = $discussion->can($user, 'tag'); + }), + + (new Extend\ApiAction([ + 'Flarum\Api\Actions\Discussions\IndexAction', + 'Flarum\Api\Actions\Discussions\ShowAction' + ])) + ->addInclude('tags'), + + // Add an event subscriber so that tags data is persisted when + // saving a discussion. + new Extend\EventSubscriber('Flarum\Tags\Handlers\TagSaver'), + + // Add a gambit that allows filtering discussions by tag(s). + new Extend\DiscussionGambit('Flarum\Tags\TagGambit'), + + // Add a new post type which indicates when a discussion's tags were + // changed. + new Extend\PostType('Flarum\Tags\DiscussionTaggedPost'), + new Extend\EventSubscriber('Flarum\Tags\Handlers\DiscussionTaggedNotifier') + ]); + } }