Update APIs, clean up

This commit is contained in:
Toby Zerner 2015-10-08 23:02:41 +10:30
parent ce0f77d858
commit a351c2ef3c
41 changed files with 3489 additions and 593 deletions

View File

@ -9,20 +9,26 @@
* file that was distributed with this source code.
*/
use Flarum\Core\Application;
use Flarum\Tags\Access;
use Flarum\Tags\Listener;
use Flarum\Tags\Tag;
use Illuminate\Contracts\Events\Dispatcher;
return function (Application $app) {
Flarum\Tags\Tag::setValidator($app->make('validator'));
return function (Dispatcher $events) {
$events->subscribe(Listener\AddClientAssets::class);
$events->subscribe(Listener\AddDiscussionTagsRelationship::class);
$events->subscribe(Listener\AddForumTagsRelationship::class);
$events->subscribe(Listener\AddTagsApi::class);
$events->subscribe(Listener\CreatePostWhenTagsAreChanged::class);
$events->subscribe(Listener\FilterDiscussionListByTags::class);
$events->subscribe(Listener\SaveTagsToDatabase::class);
$events->subscribe(Listener\UpdateTagMetadata::class);
$events = $app->make('events');
$events->subscribe(Access\DiscussionPolicy::class);
$events->subscribe(Access\TagPolicy::class);
$events->subscribe(Access\FlagPolicy::class);
$events->subscribe('Flarum\Tags\Listeners\AddClientAssets');
$events->subscribe('Flarum\Tags\Listeners\AddModelRelationship');
$events->subscribe('Flarum\Tags\Listeners\ConfigureDiscussionPermissions');
$events->subscribe('Flarum\Tags\Listeners\ConfigureTagPermissions');
$events->subscribe('Flarum\Tags\Listeners\AddApiAttributes');
$events->subscribe('Flarum\Tags\Listeners\PersistData');
$events->subscribe('Flarum\Tags\Listeners\LogDiscussionTagged');
$events->subscribe('Flarum\Tags\Listeners\UpdateTagMetadata');
$events->subscribe('Flarum\Tags\Listeners\AddTagGambit');
Tag::saving(function ($model) {
$this->app->make('Flarum\Tags\TagValidator')->assertValid($model);
});
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
import EventPost from 'flarum/components/EventPost';
import punctuate from 'flarum/helpers/punctuate';
import punctuateSeries from 'flarum/helpers/punctuateSeries';
import tagsLabel from 'flarum/tags/helpers/tagsLabel';
export default class DiscussionTaggedPost extends EventPost {
@ -41,7 +41,7 @@ export default class DiscussionTaggedPost extends EventPost {
}
return {
action: punctuate(actions),
action: punctuateSeries(actions),
count: added.length + removed.length
};
}

View File

@ -10,16 +10,11 @@
namespace Flarum\Tags\Migrations;
use Flarum\Database\AbstractMigration;
use Illuminate\Database\Schema\Blueprint;
use Flarum\Migrations\Migration;
class CreateDiscussionsTagsTable extends Migration
class CreateDiscussionsTagsTable extends AbstractMigration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$this->schema->create('discussions_tags', function (Blueprint $table) {
@ -29,11 +24,6 @@ class CreateDiscussionsTagsTable extends Migration
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$this->schema->drop('discussions_tags');

View File

@ -10,17 +10,12 @@
namespace Flarum\Tags\Migrations;
use Illuminate\Database\Schema\Blueprint;
use Flarum\Migrations\Migration;
use Flarum\Database\AbstractMigration;
use Flarum\Tags\Tag;
use Illuminate\Database\Schema\Blueprint;
class CreateTagsTable extends Migration
class CreateTagsTable extends AbstractMigration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$this->schema->create('tags', function (Blueprint $table) {
@ -53,11 +48,6 @@ class CreateTagsTable extends Migration
]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$this->schema->drop('tags');

View File

@ -10,16 +10,11 @@
namespace Flarum\Tags\Migrations;
use Flarum\Database\AbstractMigration;
use Illuminate\Database\Schema\Blueprint;
use Flarum\Migrations\Migration;
class CreateUsersTagsTable extends Migration
class CreateUsersTagsTable extends AbstractMigration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$this->schema->create('users_tags', function (Blueprint $table) {
@ -31,11 +26,6 @@ class CreateUsersTagsTable extends Migration
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$this->schema->drop('users_tags');

View File

@ -10,33 +10,23 @@
namespace Flarum\Tags\Migrations;
use Flarum\Migrations\Migration;
use Flarum\Database\AbstractMigration;
class SetDefaultSettings extends Migration
class SetDefaultSettings extends AbstractMigration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$this->settings->set('tags.max_primary_tags', '1');
$this->settings->set('tags.min_primary_tags', '1');
$this->settings->set('tags.max_secondary_tags', '3');
$this->settings->set('tags.min_secondary_tags', '0');
$this->settings->set('flarum-tags.max_primary_tags', '1');
$this->settings->set('flarum-tags.min_primary_tags', '1');
$this->settings->set('flarum-tags.max_secondary_tags', '3');
$this->settings->set('flarum-tags.min_secondary_tags', '0');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$this->settings->delete('tags.max_primary_tags');
$this->settings->delete('tags.max_secondary_tags');
$this->settings->delete('tags.min_primary_tags');
$this->settings->delete('tags.min_secondary_tags');
$this->settings->delete('flarum-tags.max_primary_tags');
$this->settings->delete('flarum-tags.max_secondary_tags');
$this->settings->delete('flarum-tags.min_primary_tags');
$this->settings->delete('flarum-tags.min_secondary_tags');
}
}

View File

@ -0,0 +1,106 @@
<?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\Tags\Access;
use Flarum\Core\Access\AbstractPolicy;
use Flarum\Core\Discussion;
use Flarum\Core\User;
use Flarum\Event\ScopeHiddenDiscussionVisibility;
use Flarum\Tags\Tag;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression;
class DiscussionPolicy extends AbstractPolicy
{
/**
* {@inheritdoc}
*/
protected $model = Discussion::class;
/**
* {@inheritdoc}
*/
public function subscribe(Dispatcher $events)
{
parent::subscribe($events);
$events->listen(ScopeHiddenDiscussionVisibility::class, [$this, 'scopeHiddenDiscussionVisibility']);
}
/**
* @param User $actor
* @param string $ability
* @param Discussion $discussion
* @return bool
*/
public function before(User $actor, $ability, Discussion $discussion)
{
// 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.
$tags = $discussion->tags;
if (count($tags)) {
$restricted = true;
foreach ($tags as $tag) {
if ($tag->is_restricted) {
if ($actor->hasPermission('tag' . $tag->id . '.discussion.' . $ability)) {
return true;
}
} else {
$restricted = false;
}
}
if ($restricted) {
return false;
}
}
}
/**
* @param User $actor
* @param Builder $query
*/
public function find(User $actor, Builder $query)
{
// Hide discussions which have tags that the user is not allowed to see.
$query->whereNotExists(function ($query) use ($actor) {
return $query->select(new Expression(1))
->from('discussions_tags')
->whereIn('tag_id', Tag::getIdsWhereCannot($actor, 'view'))
->where('discussions.id', new Expression('discussion_id'));
});
}
/**
* @param ScopeHiddenDiscussionVisibility $event
*/
public function scopeHiddenDiscussionVisibility(ScopeHiddenDiscussionVisibility $event)
{
// By default, discussions are not visible to the public if they are
// hidden or contain zero comments - unless the actor has a certain
// permission. Since we grant permissions per-tag, we will make
// discussions visible in the tags for which the user has that
// permission.
$event->query->orWhereExists(function ($query) use ($event) {
return $query->select(new Expression(1))
->from('discussions_tags')
->whereIn('tag_id', Tag::getIdsWhereCan($event->actor, $event->permission))
->where('discussions.id', new Expression('discussion_id'));
});
}
}

View File

@ -0,0 +1,43 @@
<?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\Tags\Access;
use Flarum\Core\Access\AbstractPolicy;
use Flarum\Core\User;
use Flarum\Flags\Flag;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression;
class FlagPolicy extends AbstractPolicy
{
/**
* {@inheritdoc}
*/
protected $model = Flag::class;
/**
* @param User $actor
* @param Builder $query
*/
public function find(User $actor, Builder $query)
{
$query
->select('flags.*')
->leftJoin('posts', 'posts.id', '=', 'flags.post_id')
->leftJoin('discussions', 'discussions.id', '=', 'posts.discussion_id')
->whereNotExists(function ($query) use ($actor) {
return $query->select(new Expression(1))
->from('discussions_tags')
->whereIn('tag_id', Tag::getIdsWhereCannot($actor, 'discussion.viewFlags'))
->where('discussions.id', new Expression('discussion_id'));
});
}
}

View File

@ -0,0 +1,47 @@
<?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\Tags\Access;
use Flarum\Core\Access\AbstractPolicy;
use Flarum\Core\User;
use Flarum\Tags\Tag;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
class TagPolicy extends AbstractPolicy
{
/**
* {@inheritdoc}
*/
protected $model = Tag::class;
/**
* @param User $actor
* @param Builder $query
*/
public function find(User $actor, Builder $query)
{
$query->whereNotIn('id', Tag::getIdsWhereCannot($actor, 'view'));
}
/**
* @param User $actor
* @param Tag $tag
* @return bool|null
*/
public function startDiscussion(User $actor, Tag $tag)
{
if (! $tag->is_restricted
|| $actor->hasPermission('tag' . $tag->id . '.startDiscussion')) {
return true;
}
}
}

View File

@ -8,26 +8,27 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Api;
namespace Flarum\Tags\Api\Controller;
use Flarum\Tags\Commands\EditTag;
use Flarum\Api\Actions\SerializeResourceAction;
use Flarum\Api\JsonApiRequest;
use Flarum\Api\Controller\AbstractCreateController;
use Flarum\Tags\Api\Serializer\TagSerializer;
use Flarum\Tags\Command\CreateTag;
use Illuminate\Contracts\Bus\Dispatcher;
use Psr\Http\Message\ServerRequestInterface;
use Tobscure\JsonApi\Document;
class UpdateAction extends SerializeResourceAction
class CreateTagController extends AbstractCreateController
{
/**
* @inheritdoc
*/
public $serializer = TagSerializer::class;
/**
* @var Dispatcher
*/
protected $bus;
/**
* @inheritdoc
*/
public $serializer = 'Flarum\Tags\Api\TagSerializer';
/**
* @param Dispatcher $bus
*/
@ -37,14 +38,12 @@ class UpdateAction extends SerializeResourceAction
}
/**
* @param JsonApiRequest $request
* @param Document $document
* @return \Flarum\Core\Tags\Tag
* {@inheritdoc}
*/
protected function data(JsonApiRequest $request, Document $document)
protected function data(ServerRequestInterface $request, Document $document)
{
return $this->bus->dispatch(
new EditTag($request->get('id'), $request->actor, $request->get('data'))
new CreateTag($request->getAttribute('actor'), array_get($request->getParsedBody(), 'data'))
);
}
}

View File

@ -8,14 +8,14 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Api;
namespace Flarum\Tags\Api\Controller;
use Flarum\Tags\Commands\DeleteTag;
use Flarum\Api\Actions\DeleteAction as BaseDeleteAction;
use Flarum\Api\Request;
use Flarum\Api\Controller\AbstractDeleteController;
use Flarum\Tags\Command\DeleteTag;
use Illuminate\Contracts\Bus\Dispatcher;
use Psr\Http\Message\ServerRequestInterface;
class DeleteAction extends BaseDeleteAction
class DeleteTagController extends AbstractDeleteController
{
/**
* @var Dispatcher
@ -31,14 +31,12 @@ class DeleteAction extends BaseDeleteAction
}
/**
* Delete a tag.
*
* @param Request $request
* {@inheritdoc}
*/
protected function delete(Request $request)
protected function delete(ServerRequestInterface $request)
{
$this->bus->dispatch(
new DeleteTag($request->get('id'), $request->actor)
new DeleteTag(array_get($request->getQueryParams(), 'id'), $request->getAttribute('actor'))
);
}
}

View File

@ -8,23 +8,26 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Api;
namespace Flarum\Tags\Api\Controller;
use Flarum\Api\Actions\Action;
use Flarum\Api\Request;
use Zend\Diactoros\Response\EmptyResponse;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Http\Controller\ControllerInterface;
use Flarum\Tags\Tag;
use Flarum\Core\Exceptions\PermissionDeniedException;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\EmptyResponse;
class OrderAction implements Action
class OrderTagsController implements ControllerInterface
{
public function handle(Request $request)
{
if (! $request->actor->isAdmin()) {
throw new PermissionDeniedException;
}
use AssertPermissionTrait;
$order = $request->get('order');
/**
* {@inheritdoc}
*/
public function handle(ServerRequestInterface $request)
{
$this->assertAdmin($request->getAttribute('actor'));
$order = array_get($request->getParsedBody(), 'order');
Tag::query()->update([
'position' => null,

View File

@ -0,0 +1,53 @@
<?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\Tags\Api\Controller;
use Flarum\Api\Controller\AbstractResourceController;
use Flarum\Tags\Api\Serializer\TagSerializer;
use Flarum\Tags\Command\EditTag;
use Illuminate\Contracts\Bus\Dispatcher;
use Psr\Http\Message\ServerRequestInterface;
use Tobscure\JsonApi\Document;
class UpdateTagController extends AbstractResourceController
{
/**
* @inheritdoc
*/
public $serializer = TagSerializer::class;
/**
* @var Dispatcher
*/
protected $bus;
/**
* @param Dispatcher $bus
*/
public function __construct(Dispatcher $bus)
{
$this->bus = $bus;
}
/**
* {@inheritdoc}
*/
protected function data(ServerRequestInterface $request, Document $document)
{
$id = array_get($request->getQueryParams(), 'id');
$actor = $request->getAttribute('actor');
$data = array_get($request->getParsedBody(), 'data');
return $this->bus->dispatch(
new EditTag($id, $actor, $data)
);
}
}

View File

@ -1,50 +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\Tags\Api;
use Flarum\Tags\Commands\CreateTag;
use Flarum\Api\Actions\CreateAction as BaseCreateAction;
use Flarum\Api\JsonApiRequest;
use Illuminate\Contracts\Bus\Dispatcher;
class CreateAction extends BaseCreateAction
{
/**
* @var Dispatcher
*/
protected $bus;
/**
* @inheritdoc
*/
public $serializer = 'Flarum\Tags\Api\TagSerializer';
/**
* @param Dispatcher $bus
*/
public function __construct(Dispatcher $bus)
{
$this->bus = $bus;
}
/**
* Create a tag according to input from the API request.
*
* @param JsonApiRequest $request
* @return \Flarum\Core\Tags\Tag
*/
protected function create(JsonApiRequest $request)
{
return $this->bus->dispatch(
new CreateTag($request->actor, $request->get('data'))
);
}
}

View File

@ -8,14 +8,21 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Api;
namespace Flarum\Tags\Api\Serializer;
use Flarum\Api\Serializers\Serializer;
use Flarum\Api\Serializer\AbstractSerializer;
use Flarum\Api\Serializer\DiscussionSerializer;
class TagSerializer extends Serializer
class TagSerializer extends AbstractSerializer
{
/**
* {@inheritdoc}
*/
protected $type = 'tags';
/**
* {@inheritdoc}
*/
protected function getDefaultAttributes($tag)
{
$attributes = [
@ -31,8 +38,8 @@ class TagSerializer extends Serializer
'defaultSort' => $tag->default_sort,
'isChild' => (bool) $tag->parent_id,
'isHidden' => (bool) $tag->is_hidden,
'lastTime' => $tag->last_time ? $tag->last_time->toRFC3339String() : null,
'canStartDiscussion' => $tag->can($this->actor, 'startDiscussion')
'lastTime' => $this->formatDate($tag->last_time),
'canStartDiscussion' => $this->actor->can('startDiscussion', $tag)
];
if ($this->actor->isAdmin()) {
@ -42,13 +49,19 @@ class TagSerializer extends Serializer
return $attributes;
}
/**
* @return \Flarum\Api\Relationship\HasOneBuilder
*/
protected function parent()
{
return $this->hasOne('Flarum\Tags\Api\TagSerializer');
return $this->hasOne(TagSerializer::class);
}
/**
* @return \Flarum\Api\Relationship\HasOneBuilder
*/
protected function lastDiscussion()
{
return $this->hasOne('Flarum\Api\Serializers\DiscussionSerializer');
return $this->hasOne(DiscussionSerializer::class);
}
}

View File

@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Commands;
namespace Flarum\Tags\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class CreateTag
{

View File

@ -8,26 +8,14 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Commands;
namespace Flarum\Tags\Command;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Tags\Tag;
use Flarum\Core\Forum;
use Flarum\Events\TagWillBeSaved;
class CreateTagHandler
{
/**
* @var Forum
*/
protected $forum;
/**
* @param Forum $forum
*/
public function __construct(Forum $forum)
{
$this->forum = $forum;
}
use AssertPermissionTrait;
/**
* @param CreateTag $command
@ -38,7 +26,7 @@ class CreateTagHandler
$actor = $command->actor;
$data = $command->data;
$this->forum->assertCan($actor, 'createTag');
$this->assertCan($actor, 'createTag');
$tag = Tag::build(
array_get($data, 'attributes.name'),

View File

@ -8,10 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Commands;
namespace Flarum\Tags\Command;
use Flarum\Tags\Tag;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class DeleteTag
{

View File

@ -8,13 +8,15 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Commands;
namespace Flarum\Tags\Command;
use Flarum\Tags\Tag;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Tags\TagRepository;
class DeleteTagHandler
{
use AssertPermissionTrait;
/**
* @var TagRepository
*/
@ -30,8 +32,8 @@ class DeleteTagHandler
/**
* @param DeleteTag $command
* @return Tag
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
* @return \Flarum\Tags\Tag
* @throws \Flarum\Core\Exception\PermissionDeniedException
*/
public function handle(DeleteTag $command)
{
@ -39,7 +41,7 @@ class DeleteTagHandler
$tag = $this->tags->findOrFail($command->tagId, $actor);
$tag->assertCan($actor, 'delete');
$this->assertCan($actor, 'delete', $tag);
$tag->delete();

View File

@ -8,10 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Commands;
namespace Flarum\Tags\Command;
use Flarum\Core\Tags\Tag;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class EditTag
{

View File

@ -8,13 +8,15 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Commands;
namespace Flarum\Tags\Command;
use Flarum\Tags\Tag;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Tags\TagRepository;
class EditTagHandler
{
use AssertPermissionTrait;
/**
* @var TagRepository
*/
@ -30,8 +32,8 @@ class EditTagHandler
/**
* @param EditTag $command
* @return Tag
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
* @return \Flarum\Tags\Tag
* @throws \Flarum\Core\Exception\PermissionDeniedException
*/
public function handle(EditTag $command)
{
@ -40,7 +42,7 @@ class EditTagHandler
$tag = $this->tags->findOrFail($command->tagId, $actor);
$tag->assertCan($actor, 'edit');
$this->assertCan($actor, 'edit', $tag);
$attributes = array_get($data, 'attributes', []);

View File

@ -8,10 +8,10 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Events;
namespace Flarum\Tags\Event;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Users\User;
use Flarum\Core\Discussion;
use Flarum\Core\User;
class DiscussionWasTagged
{

View File

@ -8,31 +8,37 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Gambits;
namespace Flarum\Tags\Gambit;
use Flarum\Core\Search\AbstractRegexGambit;
use Flarum\Core\Search\AbstractSearch;
use Flarum\Tags\TagRepository;
use Flarum\Core\Search\Search;
use Flarum\Core\Search\RegexGambit;
use Illuminate\Database\Query\Expression;
class TagGambit extends RegexGambit
class TagGambit extends AbstractRegexGambit
{
/**
* {@inheritdoc}
*/
protected $pattern = 'tag:(.+)';
/**
* @var \Flarum\Tags\TagRepository
* @var TagRepository
*/
protected $tags;
/**
* @param \Flarum\Tags\TagRepository $tags
* @param TagRepository $tags
*/
public function __construct(TagRepository $tags)
{
$this->tags = $tags;
}
protected function conditions(Search $search, array $matches, $negate)
/**
* {@inheritdoc}
*/
protected function conditions(AbstractSearch $search, array $matches, $negate)
{
$slugs = explode(',', trim($matches[1], '"'));
@ -41,7 +47,7 @@ class TagGambit extends RegexGambit
foreach ($slugs as $slug) {
if ($slug === 'untagged') {
$query->orWhereNotExists(function ($query) {
$query->select(app('flarum.db')->raw(1))
$query->select(new Expression(1))
->from('discussions_tags')
->where('discussions.id', new Expression('discussion_id'));
});
@ -49,7 +55,7 @@ class TagGambit extends RegexGambit
$id = $this->tags->getIdForSlug($slug);
$query->orWhereExists(function ($query) use ($id) {
$query->select(app('flarum.db')->raw(1))
$query->select(new Expression(1))
->from('discussions_tags')
->where('discussions.id', new Expression('discussion_id'))
->where('tag_id', $id);

View File

@ -8,32 +8,35 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Listeners;
namespace Flarum\Tags\Listener;
use Flarum\Events\RegisterLocales;
use Flarum\Events\BuildClientView;
use Flarum\Events\RegisterForumRoutes;
use Flarum\Event\ConfigureClientView;
use Flarum\Event\ConfigureForumRoutes;
use Illuminate\Contracts\Events\Dispatcher;
class AddClientAssets
{
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(BuildClientView::class, [$this, 'addAssets']);
$events->listen(RegisterForumRoutes::class, [$this, 'addRoutes']);
$events->listen(ConfigureClientView::class, [$this, 'addAssets']);
$events->listen(ConfigureForumRoutes::class, [$this, 'addRoutes']);
}
public function addAssets(BuildClientView $event)
/**
* @param ConfigureClientView $event
*/
public function addAssets(ConfigureClientView $event)
{
if ($event->isForum()) {
$event->addAssets([
__DIR__.'/../../js/forum/dist/extension.js',
__DIR__.'/../../less/forum/extension.less'
]);
$event->addBootstrapper('flarum/tags/main');
$event->addTranslations(['flarum-tags.forum']);
$event->addTranslations('flarum-tags.forum');
}
if ($event->isAdmin()) {
@ -41,14 +44,16 @@ class AddClientAssets
__DIR__.'/../../js/admin/dist/extension.js',
__DIR__.'/../../less/admin/extension.less'
]);
$event->addBootstrapper('flarum/tags/main');
}
}
public function addRoutes(RegisterForumRoutes $event)
/**
* @param ConfigureForumRoutes $event
*/
public function addRoutes(ConfigureForumRoutes $event)
{
$event->get('/t/{slug}', 'tags.forum.tag');
$event->get('/tags', 'tags.forum.tags');
$event->get('/t/{slug}', 'tag');
$event->get('/tags', 'tags');
}
}

View File

@ -0,0 +1,83 @@
<?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\Tags\Listener;
use Flarum\Api\Controller;
use Flarum\Api\Serializer\DiscussionSerializer;
use Flarum\Core\Discussion;
use Flarum\Event\ConfigureApiController;
use Flarum\Event\GetApiRelationship;
use Flarum\Event\GetModelRelationship;
use Flarum\Event\PrepareApiAttributes;
use Flarum\Tags\Tag;
use Illuminate\Contracts\Events\Dispatcher;
class AddDiscussionTagsRelationship
{
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(GetModelRelationship::class, [$this, 'getModelRelationship']);
$events->listen(GetApiRelationship::class, [$this, 'getApiRelationship']);
$events->listen(ConfigureApiController::class, [$this, 'includeTagsRelationship']);
$events->listen(PrepareApiAttributes::class, [$this, 'prepareApiAttributes']);
}
/**
* @param GetModelRelationship $event
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|null
*/
public function getModelRelationship(GetModelRelationship $event)
{
if ($event->isRelationship(Discussion::class, 'tags')) {
return $event->model->belongsToMany(Tag::class, 'discussions_tags', null, null, 'tags');
}
}
/**
* @param GetApiRelationship $event
* @return \Flarum\Api\Relationship\HasManyBuilder|null
*/
public function getApiRelationship(GetApiRelationship $event)
{
if ($event->isRelationship(DiscussionSerializer::class, 'tags')) {
return $event->serializer->hasMany('Flarum\Tags\Api\Serializer\TagSerializer', 'tags');
}
}
/**
* @param ConfigureApiController $event
*/
public function includeTagsRelationship(ConfigureApiController $event)
{
if ($event->isController(Controller\ListDiscussionsController::class)
|| $event->isController(Controller\ShowDiscussionController::class)
|| $event->isController(Controller\CreateDiscussionController::class)) {
$event->addInclude('tags');
}
if ($event->isController(Controller\CreateDiscussionController::class)) {
$event->addInclude('tags.lastDiscussion');
}
}
/**
* @param PrepareApiAttributes $event
*/
public function prepareApiAttributes(PrepareApiAttributes $event)
{
if ($event->isSerializer(DiscussionSerializer::class)) {
$event->attributes['canTag'] = $event->actor->can('tag', $event->model);
}
}
}

View File

@ -0,0 +1,96 @@
<?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\Tags\Listener;
use Flarum\Api\Controller\ShowForumController;
use Flarum\Api\Serializer\ForumSerializer;
use Flarum\Event\ConfigureApiController;
use Flarum\Event\GetApiRelationship;
use Flarum\Event\PrepareApiAttributes;
use Flarum\Event\PrepareApiData;
use Flarum\Settings\SettingsRepository;
use Flarum\Tags\Tag;
use Illuminate\Contracts\Events\Dispatcher;
class AddForumTagsRelationship
{
/**
* @var SettingsRepository
*/
protected $settings;
/**
* @param SettingsRepository $settings
*/
public function __construct(SettingsRepository $settings)
{
$this->settings = $settings;
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(GetApiRelationship::class, [$this, 'getApiRelationship']);
$events->listen(PrepareApiData::class, [$this, 'loadTagsRelationship']);
$events->listen(ConfigureApiController::class, [$this, 'includeTagsRelationship']);
$events->listen(PrepareApiAttributes::class, [$this, 'prepareApiAttributes']);
}
/**
* @param GetApiRelationship $event
* @return \Flarum\Api\Relationship\HasManyBuilder|null
*/
public function getApiRelationship(GetApiRelationship $event)
{
if ($event->isRelationship(ForumSerializer::class, 'tags')) {
return $event->serializer->hasMany('Flarum\Tags\Api\Serializer\TagSerializer', 'tags');
}
}
/**
* @param PrepareApiData $event
*/
public function loadTagsRelationship(PrepareApiData $event)
{
// 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.
if ($event->isController(ShowForumController::class)) {
$event->data['tags'] = Tag::whereVisibleTo($event->actor)->with('lastDiscussion')->get();
}
}
/**
* @param ConfigureApiController $event
*/
public function includeTagsRelationship(ConfigureApiController $event)
{
if ($event->isController(ShowForumController::class)) {
$event->addInclude(['tags', 'tags.lastDiscussion', 'tags.parent']);
}
}
/**
* @param PrepareApiAttributes $event
*/
public function prepareApiAttributes(PrepareApiAttributes $event)
{
if ($event->isSerializer(ForumSerializer::class)) {
$event->attributes['minPrimaryTags'] = $this->settings->get('flarum-tags.min_primary_tags');
$event->attributes['maxPrimaryTags'] = $this->settings->get('flarum-tags.max_primary_tags');
$event->attributes['minSecondaryTags'] = $this->settings->get('flarum-tags.min_secondary_tags');
$event->attributes['maxSecondaryTags'] = $this->settings->get('flarum-tags.max_secondary_tags');
}
}
}

View File

@ -0,0 +1,31 @@
<?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\Tags\Listener;
use Flarum\Tags\Api\Controller;
use Flarum\Event\ConfigureApiRoutes;
use Illuminate\Contracts\Events\Dispatcher;
class AddTagsApi
{
public function subscribe(Dispatcher $events)
{
$events->listen(ConfigureApiRoutes::class, [$this, 'configureApiRoutes']);
}
public function configureApiRoutes(ConfigureApiRoutes $event)
{
$event->post('/tags', 'tags.create', Controller\CreateTagController::class);
$event->post('/tags/order', 'tags.order', Controller\OrderTagsController::class);
$event->patch('/tags/{id}', 'tags.update', Controller\UpdateTagController::class);
$event->delete('/tags/{id}', 'tags.delete', Controller\DeleteTagController::class);
}
}

View File

@ -8,26 +8,35 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Listeners;
namespace Flarum\Tags\Listener;
use Flarum\Events\RegisterPostTypes;
use Flarum\Tags\Posts\DiscussionTaggedPost;
use Flarum\Tags\Events\DiscussionWasTagged;
use Flarum\Event\ConfigurePostTypes;
use Flarum\Tags\Event\DiscussionWasTagged;
use Flarum\Tags\Post\DiscussionTaggedPost;
use Illuminate\Contracts\Events\Dispatcher;
class LogDiscussionTagged
class CreatePostWhenTagsAreChanged
{
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(RegisterPostTypes::class, [$this, 'registerPostType']);
$events->listen(ConfigurePostTypes::class, [$this, 'addPostType']);
$events->listen(DiscussionWasTagged::class, [$this, 'whenDiscussionWasTagged']);
}
public function registerPostType(RegisterPostTypes $event)
/**
* @param ConfigurePostTypes $event
*/
public function addPostType(ConfigurePostTypes $event)
{
$event->register(DiscussionTaggedPost::class);
$event->add(DiscussionTaggedPost::class);
}
/**
* @param DiscussionWasTagged $event
*/
public function whenDiscussionWasTagged(DiscussionWasTagged $event)
{
$post = DiscussionTaggedPost::reply(

View File

@ -8,29 +8,38 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Listeners;
namespace Flarum\Tags\Listener;
use Flarum\Events\RegisterDiscussionGambits;
use Flarum\Events\DiscussionSearchWillBePerformed;
use Illuminate\Contracts\Events\Dispatcher;
use Flarum\Tags\Gambits\TagGambit;
use Flarum\Event\ConfigureDiscussionGambits;
use Flarum\Event\DiscussionSearchWillBePerformed;
use Flarum\Tags\Gambit\TagGambit;
use Flarum\Tags\Tag;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Query\Expression;
class AddTagGambit
class FilterDiscussionListByTags
{
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(RegisterDiscussionGambits::class, [$this, 'registerTagGambit']);
$events->listen(DiscussionSearchWillBePerformed::class, [$this, 'hideTags']);
$events->listen(ConfigureDiscussionGambits::class, [$this, 'addTagGambit']);
$events->listen(DiscussionSearchWillBePerformed::class, [$this, 'hideTagsFromDiscussionList']);
}
public function registerTagGambit(RegisterDiscussionGambits $event)
/**
* @param ConfigureDiscussionGambits $event
*/
public function addTagGambit(ConfigureDiscussionGambits $event)
{
$event->gambits->add('Flarum\Tags\Gambits\TagGambit');
$event->gambits->add(TagGambit::class);
}
public function hideTags(DiscussionSearchWillBePerformed $event)
/**
* @param DiscussionSearchWillBePerformed $event
*/
public function hideTagsFromDiscussionList(DiscussionSearchWillBePerformed $event)
{
$query = $event->search->getQuery();
@ -41,7 +50,7 @@ class AddTagGambit
}
$query->whereNotExists(function ($query) {
return $query->select(app('flarum.db')->raw(1))
return $query->select(new Expression(1))
->from('discussions_tags')
->whereIn('tag_id', Tag::where('is_hidden', 1)->lists('id'))
->where('discussions.id', new Expression('discussion_id'));

View File

@ -8,32 +8,47 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Listeners;
namespace Flarum\Tags\Listener;
use Flarum\Core\Exception\PermissionDeniedException;
use Flarum\Core\Exception\ValidationException;
use Flarum\Event\DiscussionWillBeSaved;
use Flarum\Settings\SettingsRepository;
use Flarum\Tags\Event\DiscussionWasTagged;
use Flarum\Tags\Tag;
use Flarum\Tags\Events\DiscussionWasTagged;
use Flarum\Events\DiscussionWillBeSaved;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Exceptions\PermissionDeniedException;
use Flarum\Core\Settings\SettingsRepository;
use Flarum\Tags\TagCountException;
use Illuminate\Contracts\Events\Dispatcher;
class PersistData
class SaveTagsToDatabase
{
/**
* @var SettingsRepository
*/
protected $settings;
/**
* @param SettingsRepository $settings
*/
public function __construct(SettingsRepository $settings)
{
$this->settings = $settings;
}
public function subscribe($events)
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(DiscussionWillBeSaved::class, [$this, 'whenDiscussionWillBeSaved']);
}
/**
* @param DiscussionWillBeSaved $event
* @throws PermissionDeniedException
* @throws ValidationException
*/
public function whenDiscussionWillBeSaved(DiscussionWillBeSaved $event)
{
// TODO: clean up, prevent discussion from being created without tags
if (isset($event->data['relationships']['tags']['data'])) {
$discussion = $event->discussion;
$actor = $event->actor;
@ -49,7 +64,7 @@ class PersistData
$secondaryCount = 0;
foreach ($newTags as $tag) {
if (! $tag->can($actor, 'startDiscussion')) {
if ($actor->cannot('startDiscussion', $tag)) {
throw new PermissionDeniedException;
}
@ -63,8 +78,6 @@ class PersistData
$this->validatePrimaryTagCount($primaryCount);
$this->validateSecondaryTagCount($secondaryCount);
$oldTags = [];
if ($discussion->exists) {
$oldTags = $discussion->tags()->get();
$oldTagIds = $oldTags->lists('id');
@ -73,32 +86,46 @@ class PersistData
return;
}
$discussion->raise(new DiscussionWasTagged($discussion, $actor, $oldTags->all()));
$discussion->raise(
new DiscussionWasTagged($discussion, $actor, $oldTags->all())
);
}
Discussion::saved(function ($discussion) use ($newTagIds) {
$discussion->afterSave(function ($discussion) use ($newTagIds) {
$discussion->tags()->sync($newTagIds);
});
}
}
/**
* @param $count
* @throws ValidationException
*/
protected function validatePrimaryTagCount($count)
{
$min = $this->settings->get('tags.min_primary_tags');
$max = $this->settings->get('tags.max_primary_tags');
$min = $this->settings->get('flarum-tags.min_primary_tags');
$max = $this->settings->get('flarum-tags.max_primary_tags');
if ($count < $min || $count > $max) {
throw new TagCountException(['tags' => sprintf('Discussion must have between %d and %d primary tags.', $min, $max)]);
throw new ValidationException([
'tags' => sprintf('Discussion must have between %d and %d primary tags.', $min, $max)
]);
}
}
/**
* @param $count
* @throws ValidationException
*/
protected function validateSecondaryTagCount($count)
{
$min = $this->settings->get('tags.min_secondary_tags');
$max = $this->settings->get('tags.max_secondary_tags');
$min = $this->settings->get('flarum-tags.min_secondary_tags');
$max = $this->settings->get('flarum-tags.max_secondary_tags');
if ($count < $min || $count > $max) {
throw new TagCountException(['tags' => sprintf('Discussion must have between %d and %d secondary tags.', $min, $max)]);
throw new ValidationException([
'tags' => sprintf('Discussion must have between %d and %d secondary tags.', $min, $max)
]);
}
}
}

View File

@ -8,22 +8,25 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Listeners;
namespace Flarum\Tags\Listener;
use Flarum\Core\Post;
use Flarum\Event\DiscussionWasDeleted;
use Flarum\Event\DiscussionWasStarted;
use Flarum\Event\PostWasDeleted;
use Flarum\Event\PostWasHidden;
use Flarum\Event\PostWasPosted;
use Flarum\Event\PostWasRestored;
use Flarum\Tags\Event\DiscussionWasTagged;
use Flarum\Tags\Tag;
use Flarum\Tags\Events\DiscussionWasTagged;
use Flarum\Events\DiscussionWasStarted;
use Flarum\Events\DiscussionWasDeleted;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Posts\Post;
use Flarum\Events\PostWasPosted;
use Flarum\Events\PostWasDeleted;
use Flarum\Events\PostWasHidden;
use Flarum\Events\PostWasRestored;
use Illuminate\Contracts\Events\Dispatcher;
class UpdateTagMetadata
{
public function subscribe($events)
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(DiscussionWasStarted::class, [$this, 'whenDiscussionWasStarted']);
$events->listen(DiscussionWasTagged::class, [$this, 'whenDiscussionWasTagged']);
@ -35,20 +38,28 @@ class UpdateTagMetadata
$events->listen(PostWasRestored::class, [$this, 'whenPostWasRestored']);
}
/**
* @param DiscussionWasStarted $event
*/
public function whenDiscussionWasStarted(DiscussionWasStarted $event)
{
$this->updateTags($event->discussion, 1);
}
/**
* @param DiscussionWasTagged $event
*/
public function whenDiscussionWasTagged(DiscussionWasTagged $event)
{
$oldTags = Tag::whereIn('id', array_pluck($event->oldTags, 'id'));
$this->updateTags($event->discussion, -1, $oldTags);
$this->updateTags($event->discussion, 1);
}
/**
* @param DiscussionWasDeleted $event
*/
public function whenDiscussionWasDeleted(DiscussionWasDeleted $event)
{
$this->updateTags($event->discussion, -1);
@ -56,26 +67,43 @@ class UpdateTagMetadata
$event->discussion->tags()->detach();
}
/**
* @param PostWasPosted $event
*/
public function whenPostWasPosted(PostWasPosted $event)
{
$this->updateTags($event->post->discussion);
}
/**
* @param PostWasDeleted $event
*/
public function whenPostWasDeleted(PostWasDeleted $event)
{
$this->updateTags($event->post->discussion);
}
/**
* @param PostWasHidden $event
*/
public function whenPostWasHidden(PostWasHidden $event)
{
$this->updateTags($event->post->discussion);
}
/**
* @param PostWasRestored $event
*/
public function whenPostWasRestored(PostWasRestored $event)
{
$this->updateTags($event->post->discussion);
}
/**
* @param \Flarum\Core\Discussion $discussion
* @param int $delta
* @param Tag[]|null $tags
*/
protected function updateTags($discussion, $delta = 0, $tags = null)
{
if (! $tags) {

View File

@ -1,106 +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\Tags\Listeners;
use Flarum\Events\ApiRelationship;
use Flarum\Events\WillSerializeData;
use Flarum\Events\BuildApiAction;
use Flarum\Events\ApiAttributes;
use Flarum\Events\RegisterApiRoutes;
use Flarum\Api\Actions\Forum;
use Flarum\Api\Actions\Discussions;
use Flarum\Api\Serializers\ForumSerializer;
use Flarum\Api\Serializers\DiscussionSerializer;
use Flarum\Tags\Tag;
class AddApiAttributes
{
public function subscribe($events)
{
$events->listen(ApiRelationship::class, [$this, 'addTagsRelationship']);
$events->listen(WillSerializeData::class, [$this, 'loadTagsRelationship']);
$events->listen(BuildApiAction::class, [$this, 'includeTagsRelationship']);
$events->listen(ApiAttributes::class, [$this, 'addAttributes']);
$events->listen(RegisterApiRoutes::class, [$this, 'addRoutes']);
}
public function addTagsRelationship(ApiRelationship $event)
{
if ($event->serializer instanceof ForumSerializer &&
$event->relationship === 'tags') {
return $event->serializer->hasMany('Flarum\Tags\Api\TagSerializer', 'tags');
}
if ($event->serializer instanceof DiscussionSerializer &&
$event->relationship === 'tags') {
return $event->serializer->hasMany('Flarum\Tags\Api\TagSerializer', 'tags');
}
}
public function loadTagsRelationship(WillSerializeData $event)
{
// 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.
if ($event->action instanceof Forum\ShowAction) {
$forum = $event->data;
$query = Tag::whereVisibleTo($event->request->actor);
$forum->tags = $query->with('lastDiscussion')->get();
$forum->tags_ids = $forum->tags->lists('id');
}
}
public function includeTagsRelationship(BuildApiAction $event)
{
if ($event->action instanceof Forum\ShowAction) {
$event->addInclude('tags');
$event->addInclude('tags.lastDiscussion');
$event->addLink('tags.parent');
}
if ($event->action instanceof Discussions\IndexAction ||
$event->action instanceof Discussions\ShowAction ||
$event->action instanceof Discussions\CreateAction) {
$event->addInclude('tags');
}
if ($event->action instanceof Discussions\CreateAction) {
$event->addInclude('tags.lastDiscussion');
}
}
public function addAttributes(ApiAttributes $event)
{
if ($event->serializer instanceof DiscussionSerializer) {
$event->attributes['canTag'] = $event->model->can($event->actor, 'tag');
}
if ($event->serializer instanceof ForumSerializer) {
$settings = app('Flarum\Core\Settings\SettingsRepository');
$event->attributes['minPrimaryTags'] = $settings->get('tags.min_primary_tags');
$event->attributes['maxPrimaryTags'] = $settings->get('tags.max_primary_tags');
$event->attributes['minSecondaryTags'] = $settings->get('tags.min_secondary_tags');
$event->attributes['maxSecondaryTags'] = $settings->get('tags.max_secondary_tags');
}
}
public function addRoutes(RegisterApiRoutes $event)
{
$event->post('/tags', 'tags.create', 'Flarum\Tags\Api\CreateAction');
$event->post('/tags/order', 'tags.order', 'Flarum\Tags\Api\OrderAction');
$event->patch('/tags/{id}', 'tags.update', 'Flarum\Tags\Api\UpdateAction');
$event->delete('/tags/{id}', 'tags.delete', 'Flarum\Tags\Api\DeleteAction');
}
}

View File

@ -1,31 +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\Tags\Listeners;
use Flarum\Events\ModelRelationship;
use Flarum\Core\Discussions\Discussion;
use Flarum\Tags\Tag;
class AddModelRelationship
{
public function subscribe($events)
{
$events->listen(ModelRelationship::class, [$this, 'addTagsRelationship']);
}
public function addTagsRelationship(ModelRelationship $event)
{
if ($event->model instanceof Discussion &&
$event->relationship === 'tags') {
return $event->model->belongsToMany('Flarum\Tags\Tag', 'discussions_tags', null, null, 'tags');
}
}
}

View File

@ -1,101 +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\Tags\Listeners;
use Flarum\Events\ScopeModelVisibility;
use Flarum\Events\ScopeHiddenDiscussionVisibility;
use Flarum\Events\ModelAllow;
use Flarum\Core\Discussions\Discussion;
use Flarum\Tags\Tag;
use Flarum\Reports\Report;
use Illuminate\Database\Query\Expression;
class ConfigureDiscussionPermissions
{
public function subscribe($events)
{
$events->listen(ScopeModelVisibility::class, [$this, 'scopeDiscussionVisibility']);
$events->listen(ScopeHiddenDiscussionVisibility::class, [$this, 'scopeHiddenDiscussionVisibility']);
$events->listen(ModelAllow::class, [$this, 'allowDiscussionPermissions']);
}
public function scopeDiscussionVisibility(ScopeModelVisibility $event)
{
// Hide discussions which have tags that the user is not allowed to see.
if ($event->model instanceof Discussion) {
$event->query->whereNotExists(function ($query) use ($event) {
return $query->select(new Expression(1))
->from('discussions_tags')
->whereIn('tag_id', Tag::getIdsWhereCannot($event->actor, 'view'))
->where('discussions.id', new Expression('discussion_id'));
});
}
if ($event->model instanceof Flag) {
$event->query
->select('flags.*')
->leftJoin('posts', 'posts.id', '=', 'flags.post_id')
->leftJoin('discussions', 'discussions.id', '=', 'posts.discussion_id')
->whereNotExists(function ($query) use ($event) {
return $query->select(new Expression(1))
->from('discussions_tags')
->whereIn('tag_id', Tag::getIdsWhereCannot($event->actor, 'discussion.viewFlags'))
->where('discussions.id', new Expression('discussion_id'));
});
}
}
public function scopeHiddenDiscussionVisibility(ScopeHiddenDiscussionVisibility $event)
{
// By default, discussions are not visible to the public if they are
// hidden or contain zero comments - unless the actor has a certain
// permission. Since we grant permissions per-tag, we will make
// discussions visible in the tags for which the user has that
// permission.
$event->query->orWhereExists(function ($query) use ($event) {
return $query->select(new Expression(1))
->from('discussions_tags')
->whereIn('tag_id', Tag::getIdsWhereCan($event->actor, $event->permission))
->where('discussions.id', new Expression('discussion_id'));
});
}
public function allowDiscussionPermissions(ModelAllow $event)
{
// 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.
if ($event->model instanceof Discussion) {
$tags = $event->model->tags;
if (count($tags)) {
$restricted = true;
foreach ($tags as $tag) {
if ($tag->is_restricted) {
if ($event->actor->hasPermission('tag' . $tag->id . '.discussion.' . $event->action)) {
return true;
}
} else {
$restricted = false;
}
}
if ($restricted) {
return false;
}
}
}
}
}

View File

@ -1,41 +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\Tags\Listeners;
use Flarum\Events\ScopeModelVisibility;
use Flarum\Events\ModelAllow;
use Flarum\Tags\Tag;
class ConfigureTagPermissions
{
public function subscribe($events)
{
$events->listen(ScopeModelVisibility::class, [$this, 'scopeTagVisibility']);
$events->listen(ModelAllow::class, [$this, 'allowStartDiscussion']);
}
public function scopeTagVisibility(ScopeModelVisibility $event)
{
if ($event->model instanceof Tag) {
$event->query->whereNotIn('id', Tag::getIdsWhereCannot($event->actor, 'view'));
}
}
public function allowStartDiscussion(ModelAllow $event)
{
if ($event->model instanceof Tag) {
if (! $event->model->is_restricted ||
$event->actor->hasPermission('tag' . $event->model->id . '.startDiscussion')) {
return true;
}
}
}
}

View File

@ -8,16 +8,22 @@
* file that was distributed with this source code.
*/
namespace Flarum\Tags\Posts;
namespace Flarum\Tags\Post;
use Flarum\Core\Posts\Post;
use Flarum\Core\Posts\EventPost;
use Flarum\Core\Posts\MergeablePost;
use Flarum\Core\Post;
use Flarum\Core\Post\AbstractEventPost;
use Flarum\Core\Post\MergeableInterface;
class DiscussionTaggedPost extends EventPost implements MergeablePost
class DiscussionTaggedPost extends AbstractEventPost implements MergeableInterface
{
/**
* {@inheritdoc}
*/
public static $type = 'discussionTagged';
/**
* {@inheritdoc}
*/
public function saveAfter(Post $previous)
{
// If the previous post is another 'discussion tagged' post, and it's

View File

@ -10,32 +10,27 @@
namespace Flarum\Tags;
use Flarum\Core\Model;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Groups\Permission;
use Flarum\Core\Support\VisibleScope;
use Flarum\Core\Support\Locked;
use Flarum\Core\Support\ValidatesBeforeSave;
use Flarum\Core\Discussion;
use Flarum\Core\Permission;
use Flarum\Core\Support\ScopeVisibilityTrait;
use Flarum\Database\AbstractModel;
class Tag extends Model
class Tag extends AbstractModel
{
use ValidatesBeforeSave;
use VisibleScope;
use Locked;
protected $table = 'tags';
protected $dates = ['last_time'];
protected $rules = [
'name' => 'required',
'slug' => ['required', 'unique:slug']
];
use ScopeVisibilityTrait;
/**
* Boot the model.
*
* @return void
* {@inheritdoc}
*/
protected $table = 'tags';
/**
* {@inheritdoc}
*/
protected $dates = ['last_time'];
/**
* {@inheritdoc}
*/
public static function boot()
{
@ -71,19 +66,28 @@ class Tag extends Model
return $tag;
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function parent()
{
return $this->belongsTo('Flarum\Tags\Tag', 'parent_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function lastDiscussion()
{
return $this->belongsTo('Flarum\Core\Discussions\Discussion', 'last_discussion_id');
return $this->belongsTo('Flarum\Core\Discussion', 'last_discussion_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function discussions()
{
return $this->belongsToMany('Flarum\Core\Discussions\Discussion', 'discussions_tags');
return $this->belongsToMany('Flarum\Core\Discussion', 'discussions_tags');
}
/**
@ -114,6 +118,11 @@ class Tag extends Model
return $this;
}
/**
* @param $user
* @param $permission
* @return array
*/
public static function getIdsWhereCan($user, $permission)
{
static $tags;
@ -134,6 +143,11 @@ class Tag extends Model
return $ids;
}
/**
* @param $user
* @param $permission
* @return array
*/
public static function getIdsWhereCannot($user, $permission)
{
static $tags;

View File

@ -10,9 +10,8 @@
namespace Flarum\Tags;
use Flarum\Core\User;
use Illuminate\Database\Eloquent\Builder;
use Flarum\Core\Users\User;
use Flarum\Tags\Tag;
class TagRepository
{
@ -23,7 +22,6 @@ class TagRepository
* @param int $id
* @param User $actor
* @return Tag
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findOrFail($id, User $actor = null)

View File

@ -10,8 +10,15 @@
namespace Flarum\Tags;
use Flarum\Core\Exceptions\ValidationException;
use Flarum\Core\Validator\AbstractValidator;
class TagCountException extends ValidationException
class TagValidator extends AbstractValidator
{
/**
* {@inheritdoc}
*/
protected $rules = [
'name' => ['required'],
'slug' => ['required', 'unique:slug']
];
}