mirror of
https://github.com/flarum/framework.git
synced 2024-12-02 06:53:47 +08:00
Enforce min/max tag count settings
This commit is contained in:
parent
92e9b5b414
commit
a042f04fa3
|
@ -3,6 +3,7 @@ import DiscussionPage from 'flarum/components/DiscussionPage';
|
|||
import Button from 'flarum/components/Button';
|
||||
import highlight from 'flarum/helpers/highlight';
|
||||
import classList from 'flarum/utils/classList';
|
||||
import extractText from 'flarum/utils/extractText';
|
||||
|
||||
import tagLabel from 'tags/helpers/tagLabel';
|
||||
import tagIcon from 'tags/helpers/tagIcon';
|
||||
|
@ -24,6 +25,19 @@ export default class TagDiscussionModal extends Modal {
|
|||
} else if (this.props.discussion) {
|
||||
this.props.discussion.tags().map(this.addTag.bind(this));
|
||||
}
|
||||
|
||||
this.minPrimary = app.forum.attribute('minPrimaryTags');
|
||||
this.maxPrimary = app.forum.attribute('maxPrimaryTags');
|
||||
this.minSecondary = app.forum.attribute('minSecondaryTags');
|
||||
this.maxSecondary = app.forum.attribute('maxSecondaryTags');
|
||||
}
|
||||
|
||||
primaryCount() {
|
||||
return this.selected.filter(tag => tag.isPrimary()).length;
|
||||
}
|
||||
|
||||
secondaryCount() {
|
||||
return this.selected.filter(tag => !tag.isPrimary()).length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,17 +89,46 @@ export default class TagDiscussionModal extends Modal {
|
|||
: app.trans('tags.tag_new_discussion_title');
|
||||
}
|
||||
|
||||
getInstruction(primaryCount, secondaryCount) {
|
||||
if (primaryCount < this.minPrimary) {
|
||||
return app.trans('tags.choose_primary_tags', {count: this.minPrimary - primaryCount});
|
||||
} else if (secondaryCount < this.minSecondary) {
|
||||
return app.trans('tags.choose_secondary_tags', {count: this.minSecondary - secondaryCount});
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
content() {
|
||||
let tags = this.tags;
|
||||
const filter = this.filter().toLowerCase();
|
||||
const primaryCount = this.primaryCount();
|
||||
const secondaryCount = this.secondaryCount();
|
||||
|
||||
// Filter out all child tags whose parents have not been selected. This
|
||||
// makes it impossible to select a child if its parent hasn't been selected.
|
||||
tags = tags.filter(tag => {
|
||||
const parent = tag.parent();
|
||||
return parent === false || this.selected.indexOf(parent) !== -1;
|
||||
});
|
||||
|
||||
// If the number of selected primary/secondary tags is at the maximum, then
|
||||
// we'll filter out all other tags of that type.
|
||||
if (primaryCount >= app.forum.attribute('maxPrimaryTags')) {
|
||||
tags = tags.filter(tag => !tag.isPrimary() || this.selected.indexOf(tag) !== -1);
|
||||
}
|
||||
|
||||
if (secondaryCount >= app.forum.attribute('maxSecondaryTags')) {
|
||||
tags = tags.filter(tag => tag.isPrimary() || this.selected.indexOf(tag) !== -1);
|
||||
}
|
||||
|
||||
// If the user has entered text in the filter input, then filter by tags
|
||||
// whose name matches what they've entered.
|
||||
if (filter) {
|
||||
tags = tags.filter(tag => tag.name().substr(0, filter.length).toLowerCase() === filter);
|
||||
}
|
||||
|
||||
if (tags.indexOf(this.index) === -1) {
|
||||
this.index = tags[0];
|
||||
}
|
||||
if (tags.indexOf(this.index) === -1) this.index = tags[0];
|
||||
|
||||
return [
|
||||
<div className="Modal-body">
|
||||
|
@ -103,7 +146,7 @@ export default class TagDiscussionModal extends Modal {
|
|||
)}
|
||||
</span>
|
||||
<input className="FormControl"
|
||||
placeholder={!this.selected.length ? app.trans('tags.discussion_tags_placeholder') : ''}
|
||||
placeholder={extractText(this.getInstruction(primaryCount, secondaryCount))}
|
||||
value={this.filter()}
|
||||
oninput={m.withAttr('value', this.filter)}
|
||||
onkeydown={this.onkeydown.bind(this)}
|
||||
|
@ -115,7 +158,7 @@ export default class TagDiscussionModal extends Modal {
|
|||
{Button.component({
|
||||
type: 'submit',
|
||||
className: 'Button Button--primary',
|
||||
disabled: !this.selected.length,
|
||||
disabled: primaryCount < this.minPrimary || secondaryCount < this.minSecondary,
|
||||
icon: 'check',
|
||||
children: app.trans('tags.confirm')
|
||||
})}
|
||||
|
@ -125,12 +168,9 @@ export default class TagDiscussionModal extends Modal {
|
|||
|
||||
<div className="Modal-footer">
|
||||
<ul className="TagDiscussionModal-list SelectTagList">
|
||||
{tags.map(tag => {
|
||||
if (!filter && tag.parent() && this.selected.indexOf(tag.parent()) === -1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
{tags
|
||||
.filter(tag => filter || !tag.parent() || this.selected.indexOf(tag.parent()) !== -1)
|
||||
.map(tag => (
|
||||
<li data-index={tag.id()}
|
||||
className={classList({
|
||||
pinned: tag.position() !== null,
|
||||
|
@ -154,8 +194,7 @@ export default class TagDiscussionModal extends Modal {
|
|||
</span>
|
||||
) : ''}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
];
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Model from 'flarum/Model';
|
||||
import mixin from 'flarum/utils/mixin';
|
||||
import computed from 'flarum/utils/computed';
|
||||
|
||||
export default class Tag extends mixin(Model, {
|
||||
name: Model.attribute('name'),
|
||||
|
@ -21,5 +22,7 @@ export default class Tag extends mixin(Model, {
|
|||
lastDiscussion: Model.hasOne('lastDiscussion'),
|
||||
|
||||
isRestricted: Model.attribute('isRestricted'),
|
||||
canStartDiscussion: Model.attribute('canStartDiscussion')
|
||||
canStartDiscussion: Model.attribute('canStartDiscussion'),
|
||||
|
||||
isPrimary: computed('position', 'parent', (position, parent) => position !== null && parent === false)
|
||||
}) {}
|
||||
|
|
|
@ -58,8 +58,12 @@
|
|||
cursor: not-allowed;
|
||||
}
|
||||
.TagsInput-selected {
|
||||
.TagLabel {
|
||||
.TagsInput-tag {
|
||||
margin-right: 5px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,12 @@ tags:
|
|||
tag_new_discussion_title: Choose Tags for Your Discussion
|
||||
edit_discussion_tags_title: "Edit Tags for {title}"
|
||||
edit_discussion_tags_link: Edit Tags
|
||||
discussion_tags_placeholder: Choose one or more topics
|
||||
choose_primary_tags:
|
||||
one: Choose a primary tag
|
||||
other: "Choose {count} primary tags"
|
||||
choose_secondary_tags:
|
||||
one: Choose 1 more tag
|
||||
other: "Choose {count} more tags"
|
||||
confirm: Confirm
|
||||
more: More...
|
||||
tag_cloud_title: Tags
|
||||
|
|
|
@ -70,6 +70,15 @@ class AddApiAttributes
|
|||
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)
|
||||
|
|
|
@ -35,7 +35,8 @@ class AddClientAssets
|
|||
'tags.tag_new_discussion_title',
|
||||
'tags.edit_discussion_tags_title',
|
||||
'tags.edit_discussion_tags_link',
|
||||
'tags.discussion_tags_placeholder',
|
||||
'tags.choose_primary_tags',
|
||||
'tags.choose_secondary_tags',
|
||||
'tags.confirm',
|
||||
'tags.more',
|
||||
'tags.tag_cloud_title',
|
||||
|
|
|
@ -5,9 +5,18 @@ 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;
|
||||
|
||||
class PersistData
|
||||
{
|
||||
protected $settings;
|
||||
|
||||
public function __construct(SettingsRepository $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function subscribe($events)
|
||||
{
|
||||
$events->listen(DiscussionWillBeSaved::class, [$this, 'whenDiscussionWillBeSaved']);
|
||||
|
@ -25,13 +34,25 @@ class PersistData
|
|||
$newTagIds[] = (int) $link['id'];
|
||||
}
|
||||
|
||||
$newTags = Tag::whereIn('id', $newTagIds);
|
||||
$newTags = Tag::whereIn('id', $newTagIds)->get();
|
||||
$primaryCount = 0;
|
||||
$secondaryCount = 0;
|
||||
|
||||
foreach ($newTags as $tag) {
|
||||
if (! $tag->can($actor, 'startDiscussion')) {
|
||||
throw new PermissionDeniedException;
|
||||
}
|
||||
|
||||
if ($tag->position !== null && $tag->parent_id === null) {
|
||||
$primaryCount++;
|
||||
} else {
|
||||
$secondaryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->validatePrimaryTagCount($primaryCount);
|
||||
$this->validateSecondaryTagCount($secondaryCount);
|
||||
|
||||
$oldTags = [];
|
||||
|
||||
if ($discussion->exists) {
|
||||
|
@ -50,4 +71,24 @@ class PersistData
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected function validatePrimaryTagCount($count)
|
||||
{
|
||||
$min = $this->settings->get('tags.min_primary_tags');
|
||||
$max = $this->settings->get('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)]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function validateSecondaryTagCount($count)
|
||||
{
|
||||
$min = $this->settings->get('tags.min_secondary_tags');
|
||||
$max = $this->settings->get('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)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
7
extensions/tags/src/TagCountException.php
Normal file
7
extensions/tags/src/TagCountException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php namespace Flarum\Tags;
|
||||
|
||||
use Flarum\Core\Exceptions\ValidationException;
|
||||
|
||||
class TagCountException extends ValidationException
|
||||
{
|
||||
}
|
Loading…
Reference in New Issue
Block a user