diff --git a/extensions/tags/js/src/components/tag-discussion-modal.js b/extensions/tags/js/src/components/tag-discussion-modal.js index 429d5990b..26e38d7fb 100644 --- a/extensions/tags/js/src/components/tag-discussion-modal.js +++ b/extensions/tags/js/src/components/tag-discussion-modal.js @@ -11,7 +11,7 @@ export default class TagDiscussionModal extends FormModal { constructor(props) { super(props); - this.tags = sortTags(app.store.all('tags')); + this.tags = sortTags(app.store.all('tags').filter(tag => tag.canStartDiscussion())); this.selected = m.prop([]); if (this.props.selectedTags) { @@ -28,6 +28,8 @@ export default class TagDiscussionModal extends FormModal { } addTag(tag) { + if (!tag.canStartDiscussion()) return; + var selected = this.selected(); var parent = tag.parent(); if (parent) { diff --git a/extensions/tags/js/src/models/tag.js b/extensions/tags/js/src/models/tag.js index 85b010da5..fc8b075bd 100644 --- a/extensions/tags/js/src/models/tag.js +++ b/extensions/tags/js/src/models/tag.js @@ -21,4 +21,6 @@ Tag.prototype.discussionsCount = Model.prop('discussionsCount'); Tag.prototype.lastTime = Model.prop('lastTime', Model.date); Tag.prototype.lastDiscussion = Model.one('lastDiscussion'); +Tag.prototype.canStartDiscussion = Model.prop('canStartDiscussion'); + export default Tag; diff --git a/extensions/tags/migrations/2015_02_24_000000_create_tags_table.php b/extensions/tags/migrations/2015_02_24_000000_create_tags_table.php index f74187ff7..855043944 100644 --- a/extensions/tags/migrations/2015_02_24_000000_create_tags_table.php +++ b/extensions/tags/migrations/2015_02_24_000000_create_tags_table.php @@ -26,6 +26,7 @@ class CreateTagsTable extends Migration $table->integer('position')->nullable(); $table->integer('parent_id')->unsigned()->nullable(); $table->string('default_sort', 50)->nullable(); + $table->boolean('is_restricted')->default(0); $table->integer('discussions_count')->unsigned()->default(0); $table->integer('last_time')->unsigned()->nullable(); diff --git a/extensions/tags/src/Handlers/TagLoader.php b/extensions/tags/src/Handlers/TagLoader.php new file mode 100755 index 000000000..71bac1fc4 --- /dev/null +++ b/extensions/tags/src/Handlers/TagLoader.php @@ -0,0 +1,25 @@ +listen('Flarum\Api\Events\WillRespond', __CLASS__.'@whenWillRespond'); + } + + public function whenWillRespond(WillRespond $event) + { + if ($event->action instanceof ForumShowAction) { + $forum = $event->data; + + $query = Tag::whereVisibleTo($event->request->actor->getUser()); + + $forum->tags = $query->with('lastDiscussion')->get(); + $forum->tags_ids = $forum->tags->lists('id'); + } + } +} diff --git a/extensions/tags/src/Handlers/TagSaver.php b/extensions/tags/src/Handlers/TagSaver.php index d2d107259..739318e6c 100755 --- a/extensions/tags/src/Handlers/TagSaver.php +++ b/extensions/tags/src/Handlers/TagSaver.php @@ -1,9 +1,11 @@ can($user, 'startDiscussion')) { + throw new PermissionDeniedException; + } + } + $oldTags = []; if ($discussion->exists) { diff --git a/extensions/tags/src/Tag.php b/extensions/tags/src/Tag.php index 40db19454..2e2aabd4a 100644 --- a/extensions/tags/src/Tag.php +++ b/extensions/tags/src/Tag.php @@ -1,9 +1,14 @@ actor->getUser(); + $attributes = [ 'name' => $tag->name, 'description' => $tag->description, @@ -31,7 +33,8 @@ class TagSerializer extends BaseSerializer 'position' => $tag->position === null ? null : (int) $tag->position, 'defaultSort' => $tag->default_sort, 'isChild' => (bool) $tag->parent_id, - 'lastTime' => $tag->last_time ? $tag->last_time->toRFC3339String() : null + 'lastTime' => $tag->last_time ? $tag->last_time->toRFC3339String() : null, + 'canStartDiscussion' => $tag->can($user, 'startDiscussion') ]; return $this->extendAttributes($tag, $attributes); @@ -44,6 +47,6 @@ class TagSerializer extends BaseSerializer protected function lastDiscussion() { - return $this->hasOne('Flarum\Api\Serializers\DiscussionSerializer'); + return $this->hasOne('Flarum\Api\Serializers\DiscussionBasicSerializer'); } } diff --git a/extensions/tags/src/TagsServiceProvider.php b/extensions/tags/src/TagsServiceProvider.php index 4a0b97768..daaa4163c 100644 --- a/extensions/tags/src/TagsServiceProvider.php +++ b/extensions/tags/src/TagsServiceProvider.php @@ -6,9 +6,13 @@ use Flarum\Extend\EventSubscribers; use Flarum\Extend\Relationship; use Flarum\Extend\SerializeRelationship; use Flarum\Extend\ApiInclude; +use Flarum\Extend\ApiLink; use Flarum\Extend\Permission; use Flarum\Extend\DiscussionGambit; use Flarum\Extend\PostType; +use Flarum\Core\Models\Discussion; +use Flarum\Core\Models\Post; +use Flarum\Core\Models\User; class TagsServiceProvider extends ServiceProvider { @@ -27,7 +31,8 @@ class TagsServiceProvider extends ServiceProvider new EventSubscribers([ 'Flarum\Tags\Handlers\DiscussionTaggedNotifier', - 'Flarum\Tags\Handlers\TagSaver' + 'Flarum\Tags\Handlers\TagSaver', + 'Flarum\Tags\Handlers\TagLoader' ]), new Relationship('Flarum\Core\Models\Discussion', 'tags', function ($model) { @@ -38,25 +43,88 @@ class TagsServiceProvider extends ServiceProvider new ApiInclude(['discussions.index', 'discussions.show'], 'tags', true), - new Relationship('Flarum\Core\Models\Forum', 'tags', function ($model) { - return Tag::query(); - }), - new SerializeRelationship('Flarum\Api\Serializers\ForumSerializer', 'hasMany', 'tags', 'Flarum\Tags\TagSerializer'), - new ApiInclude(['forum.show'], ['tags', 'tags.parent', 'tags.lastDiscussion'], true), + 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 - }), + ->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; } /**