mirror of
https://github.com/flarum/framework.git
synced 2025-02-21 03:59:39 +08:00
Extend admin permissions page to allow restriction by tag
Also fix a couple of bugs: - Tag sorting algorithm bug in Safari - Ensure subtag is removed when parent is removed
This commit is contained in:
parent
d63b442227
commit
d0f9115dea
10
extensions/tags/js/admin/Gulpfile.js
Normal file
10
extensions/tags/js/admin/Gulpfile.js
Normal file
@ -0,0 +1,10 @@
|
||||
var gulp = require('flarum-gulp');
|
||||
|
||||
gulp({
|
||||
modules: {
|
||||
'tags': [
|
||||
'../lib/**/*.js',
|
||||
'src/**/*.js'
|
||||
]
|
||||
}
|
||||
});
|
7
extensions/tags/js/admin/package.json
Normal file
7
extensions/tags/js/admin/package.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"gulp": "^3.8.11",
|
||||
"flarum-gulp": "git+https://github.com/flarum/gulp.git"
|
||||
}
|
||||
}
|
63
extensions/tags/js/admin/src/main.js
Normal file
63
extensions/tags/js/admin/src/main.js
Normal file
@ -0,0 +1,63 @@
|
||||
import { extend } from 'flarum/extend';
|
||||
import PermissionGrid from 'flarum/components/PermissionGrid';
|
||||
import PermissionDropdown from 'flarum/components/PermissionDropdown';
|
||||
import Dropdown from 'flarum/components/Dropdown';
|
||||
import Button from 'flarum/components/Button';
|
||||
|
||||
import Tag from 'tags/models/Tag';
|
||||
import tagLabel from 'tags/helpers/tagLabel';
|
||||
import tagIcon from 'tags/helpers/tagIcon';
|
||||
import sortTags from 'tags/utils/sortTags';
|
||||
|
||||
app.initializers.add('tags', app => {
|
||||
app.store.models.tags = Tag;
|
||||
|
||||
extend(PermissionGrid.prototype, 'scopeItems', items => {
|
||||
sortTags(app.store.all('tags'))
|
||||
.filter(tag => tag.isRestricted())
|
||||
.forEach(tag => items.add('tag' + tag.id(), {
|
||||
label: tagLabel(tag),
|
||||
onremove: () => tag.save({isRestricted: false}),
|
||||
render: item => {
|
||||
if (item.permission) {
|
||||
let permission;
|
||||
|
||||
if (item.permission === 'forum.view') {
|
||||
permission = 'view';
|
||||
} else if (item.permission === 'forum.startDiscussion') {
|
||||
permission = 'startDiscussion';
|
||||
} else if (item.permission.indexOf('discussion.') === 0) {
|
||||
permission = item.permission;
|
||||
}
|
||||
|
||||
if (permission) {
|
||||
const props = Object.assign({}, item);
|
||||
props.permission = 'tag' + tag.id() + '.' + permission;
|
||||
|
||||
return PermissionDropdown.component(props);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
extend(PermissionGrid.prototype, 'scopeControlItems', items => {
|
||||
const tags = sortTags(app.store.all('tags').filter(tag => !tag.isRestricted()));
|
||||
|
||||
if (tags.length) {
|
||||
items.add('tag', Dropdown.component({
|
||||
buttonClassName: 'Button Button--text',
|
||||
label: 'Restrict by Tag',
|
||||
icon: 'plus',
|
||||
caretIcon: null,
|
||||
children: tags.map(tag => Button.component({
|
||||
icon: true,
|
||||
children: [tagIcon(tag, {className: 'Button-icon'}), ' ', tag.name()],
|
||||
onclick: () => tag.save({isRestricted: true})
|
||||
}))
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
@ -2,6 +2,9 @@ var gulp = require('flarum-gulp');
|
||||
|
||||
gulp({
|
||||
modules: {
|
||||
'tags': 'src/**/*.js'
|
||||
'tags': [
|
||||
'../lib/**/*.js',
|
||||
'src/**/*.js'
|
||||
]
|
||||
}
|
||||
});
|
||||
|
@ -60,8 +60,8 @@ export default class TagDiscussionModal extends Modal {
|
||||
// Look through the list of selected tags for any tags which have the tag
|
||||
// we just removed as their parent. We'll need to remove them too.
|
||||
this.selected
|
||||
.filter(selected => selected.parent() && selected.parent() === tag)
|
||||
.forEach(this.removeTag);
|
||||
.filter(selected => selected.parent() === tag)
|
||||
.forEach(this.removeTag.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,5 +20,6 @@ export default class Tag extends mixin(Model, {
|
||||
lastTime: Model.attribute('lastTime', Model.transformDate),
|
||||
lastDiscussion: Model.hasOne('lastDiscussion'),
|
||||
|
||||
isRestricted: Model.attribute('isRestricted'),
|
||||
canStartDiscussion: Model.attribute('canStartDiscussion')
|
||||
}) {}
|
@ -15,7 +15,7 @@ export default function sortTags(tags) {
|
||||
} else if (aParent === bParent) {
|
||||
return aPos - bPos;
|
||||
} else if (aParent) {
|
||||
return aParent === b ? -1 : aParent.position() - bPos;
|
||||
return aParent === b ? 1 : aParent.position() - bPos;
|
||||
} else if (bParent) {
|
||||
return bParent === a ? -1 : aPos - bParent.position();
|
||||
}
|
2
extensions/tags/less/admin/extension.less
Normal file
2
extensions/tags/less/admin/extension.less
Normal file
@ -0,0 +1,2 @@
|
||||
@import "../lib/TagLabel.less";
|
||||
@import "../lib/TagIcon.less";
|
@ -1,7 +1,8 @@
|
||||
@import "../lib/TagLabel.less";
|
||||
@import "../lib/TagIcon.less";
|
||||
|
||||
@import "TagCloud.less";
|
||||
@import "TagDiscussionModal.less";
|
||||
@import "TagIcon.less";
|
||||
@import "TagLabel.less";
|
||||
@import "TagTiles.less";
|
||||
|
||||
.DiscussionHero {
|
||||
|
@ -6,6 +6,7 @@
|
||||
border-radius: @border-radius;
|
||||
background: @control-bg;
|
||||
color: @control-color;
|
||||
text-transform: none;
|
||||
|
||||
&.untagged {
|
||||
background: transparent;
|
@ -1,4 +1,4 @@
|
||||
<?php namespace Flarum\Tags;
|
||||
<?php namespace Flarum\Tags\Api;
|
||||
|
||||
use Flarum\Api\Serializers\Serializer;
|
||||
|
||||
@ -8,7 +8,7 @@ class TagSerializer extends Serializer
|
||||
|
||||
protected function getDefaultAttributes($tag)
|
||||
{
|
||||
return [
|
||||
$attributes = [
|
||||
'name' => $tag->name,
|
||||
'description' => $tag->description,
|
||||
'slug' => $tag->slug,
|
||||
@ -23,11 +23,17 @@ class TagSerializer extends Serializer
|
||||
'lastTime' => $tag->last_time ? $tag->last_time->toRFC3339String() : null,
|
||||
'canStartDiscussion' => $tag->can($this->actor, 'startDiscussion')
|
||||
];
|
||||
|
||||
if ($this->actor->isAdmin()) {
|
||||
$attributes['isRestricted'] = (bool) $tag->is_restricted;
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
protected function parent()
|
||||
{
|
||||
return $this->hasOne('Flarum\Tags\TagSerializer');
|
||||
return $this->hasOne('Flarum\Tags\Api\TagSerializer');
|
||||
}
|
||||
|
||||
protected function lastDiscussion()
|
40
extensions/tags/src/Api/UpdateAction.php
Normal file
40
extensions/tags/src/Api/UpdateAction.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php namespace Flarum\Tags\Api;
|
||||
|
||||
use Flarum\Tags\Commands\EditTag;
|
||||
use Flarum\Api\Actions\SerializeResourceAction;
|
||||
use Flarum\Api\JsonApiRequest;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class UpdateAction extends SerializeResourceAction
|
||||
{
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $bus;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $serializer = 'Flarum\Tags\Api\TagSerializer';
|
||||
|
||||
/**
|
||||
* @param Dispatcher $bus
|
||||
*/
|
||||
public function __construct(Dispatcher $bus)
|
||||
{
|
||||
$this->bus = $bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JsonApiRequest $request
|
||||
* @param Document $document
|
||||
* @return \Flarum\Core\Tags\Tag
|
||||
*/
|
||||
protected function data(JsonApiRequest $request, Document $document)
|
||||
{
|
||||
return $this->bus->dispatch(
|
||||
new EditTag($request->get('id'), $request->actor, $request->get('data'))
|
||||
);
|
||||
}
|
||||
}
|
40
extensions/tags/src/Commands/EditTag.php
Normal file
40
extensions/tags/src/Commands/EditTag.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php namespace Flarum\Tags\Commands;
|
||||
|
||||
use Flarum\Core\Tags\Tag;
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class EditTag
|
||||
{
|
||||
/**
|
||||
* The ID of the tag to edit.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tagId;
|
||||
|
||||
/**
|
||||
* The user performing the action.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* The attributes to update on the tag.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param int $tagId The ID of the tag to edit.
|
||||
* @param User $actor The user performing the action.
|
||||
* @param array $data The attributes to update on the tag.
|
||||
*/
|
||||
public function __construct($tagId, User $actor, array $data)
|
||||
{
|
||||
$this->tagId = $tagId;
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
45
extensions/tags/src/Commands/EditTagHandler.php
Normal file
45
extensions/tags/src/Commands/EditTagHandler.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php namespace Flarum\Tags\Commands;
|
||||
|
||||
use Flarum\Tags\Tag;
|
||||
use Flarum\Tags\TagRepository;
|
||||
|
||||
class EditTagHandler
|
||||
{
|
||||
/**
|
||||
* @var TagRepository
|
||||
*/
|
||||
protected $tags;
|
||||
|
||||
/**
|
||||
* @param TagRepository $tags
|
||||
*/
|
||||
public function __construct(TagRepository $tags)
|
||||
{
|
||||
$this->tags = $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EditTag $command
|
||||
* @return Tag
|
||||
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
|
||||
*/
|
||||
public function handle(EditTag $command)
|
||||
{
|
||||
$actor = $command->actor;
|
||||
$data = $command->data;
|
||||
|
||||
$tag = $this->tags->findOrFail($command->tagId, $actor);
|
||||
|
||||
$tag->assertCan($actor, 'edit');
|
||||
|
||||
$attributes = array_get($data, 'attributes', []);
|
||||
|
||||
if (isset($attributes['isRestricted'])) {
|
||||
$tag->is_restricted = (bool) $attributes['isRestricted'];
|
||||
}
|
||||
|
||||
$tag->save();
|
||||
|
||||
return $tag;
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ 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;
|
||||
@ -18,18 +19,19 @@ class AddApiAttributes
|
||||
$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\TagSerializer', 'tags');
|
||||
return $event->serializer->hasMany('Flarum\Tags\Api\TagSerializer', 'tags');
|
||||
}
|
||||
|
||||
if ($event->serializer instanceof DiscussionSerializer &&
|
||||
$event->relationship === 'tags') {
|
||||
return $event->serializer->hasMany('Flarum\Tags\TagSerializer', 'tags');
|
||||
return $event->serializer->hasMany('Flarum\Tags\Api\TagSerializer', 'tags');
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,4 +71,9 @@ class AddApiAttributes
|
||||
$event->attributes['canTag'] = $event->model->can($event->actor, 'tag');
|
||||
}
|
||||
}
|
||||
|
||||
public function addRoutes(RegisterApiRoutes $event)
|
||||
{
|
||||
$event->patch('/tags/{id}', 'tags.update', 'Flarum\Tags\Api\UpdateAction');
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,13 @@ class AddClientAssets
|
||||
'tags.tag_cloud_title',
|
||||
'tags.deleted'
|
||||
]);
|
||||
|
||||
$event->adminAssets([
|
||||
__DIR__.'/../../js/admin/dist/extension.js',
|
||||
__DIR__.'/../../less/admin/extension.less'
|
||||
]);
|
||||
|
||||
$event->adminBootstrapper('tags/main');
|
||||
}
|
||||
|
||||
public function addRoutes(RegisterForumRoutes $event)
|
||||
|
@ -2,9 +2,14 @@
|
||||
|
||||
use Flarum\Core\Model;
|
||||
use Flarum\Core\Discussions\Discussion;
|
||||
use Flarum\Core\Support\VisibleScope;
|
||||
use Flarum\Core\Support\Locked;
|
||||
|
||||
class Tag extends Model
|
||||
{
|
||||
use VisibleScope;
|
||||
use Locked;
|
||||
|
||||
protected $table = 'tags';
|
||||
|
||||
protected $dates = ['last_time'];
|
||||
|
@ -1,11 +1,28 @@
|
||||
<?php namespace Flarum\Tags;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Flarum\Core\Models\User;
|
||||
use Flarum\Core\Users\User;
|
||||
use Flarum\Tags\Tag;
|
||||
|
||||
class TagRepository
|
||||
{
|
||||
/**
|
||||
* Find a tag by ID, optionally making sure it is visible to a certain
|
||||
* user, or throw an exception.
|
||||
*
|
||||
* @param int $id
|
||||
* @param User $actor
|
||||
* @return Tag
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function findOrFail($id, User $actor = null)
|
||||
{
|
||||
$query = Tag::where('id', $id);
|
||||
|
||||
return $this->scopeVisibleTo($query, $actor)->firstOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all tags, optionally making sure they are visible to a
|
||||
* certain user.
|
||||
@ -13,7 +30,7 @@ class TagRepository
|
||||
* @param User|null $user
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function find(User $user = null)
|
||||
public function all(User $user = null)
|
||||
{
|
||||
$query = Tag::newQuery();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user