mirror of
https://github.com/flarum/framework.git
synced 2025-03-22 04:55:16 +08:00
[mentions] feat: group mentions (#3658)
* wip: group mentions * Apply fixes from StyleCI * chore: format * group mention autocomplete * chore: format * remove console.log * implement notifications * prevent guest and member groups from being mentioned * Update extensions/mentions/less/forum.less Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * rename displayname to groupname * Update extensions/mentions/src/Formatter/FormatGroupMentions.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * remove redundant unparse * simplify migrations * add group deleted translation * Apply fixes from StyleCI * handle everything falsy * Include icon in group mention preview * remove box-shadow from autocomplete group results * Add color to preview * chore: format * Remove box shadow from group autocomplete results * Update extensions/mentions/migrations/2022_10_21_000000_create_post_mentions_groups_table.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * remove unneeded migration * prevent former group icon from beingdisplayed * add group searcher with permissions * Apply fixes from StyleCI * Search groups based on canSearchGroups permission * Don't include virtual groups in results * Add search groups translation * Revert "remove unneeded migration" This reverts commit 9347665baa1820ed0875c6b2dd22b14fbc2768e7. * Revert "Update extensions/mentions/migrations/2022_10_21_000000_create_post_mentions_groups_table.php" This reverts commit 8406d51df276d8f14917bfdc45a86e6b847784b7. * add searchGroups permission to tests * Apply fixes from StyleCI * Add default searchGroups permission * Apply fixes from StyleCI * Update extensions/mentions/js/src/forum/addComposerAutocomplete.js Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update extensions/mentions/migrations/2022_10_21_000000_create_post_mentions_groups_table.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * remove unneeded migration, correct table table * correct table name in down migration * Remove group searcher * Apply fixes from StyleCI * Remove group searching from composer autocomplete * Add mentionGroups permission * Apply fixes from StyleCI * prevent post preview from rendering a group mention when user does not have permission * remove test changes * wip: expose ServerRequestInterface to textformatter parse() * Apply fixes from StyleCI * Set post content properly * php 7.x compatibility * begin adding groupmention tests * Apply fixes from StyleCI * test virtual groups don't mention * Apply fixes from StyleCI * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update extensions/mentions/extend.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update extensions/mentions/extend.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * requested changes * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/src/Search/SearchServiceProvider.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/src/Extend/Formatter.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * remove default permission migration * try using datetime column instead of timestamp * Apply fixes from StyleCI * chore: remove commented code * add tests * Apply fixes from StyleCI * Pass actor to parser instead of ServerRequest * Allow for to be null * Update framework/core/src/Extend/Formatter.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * pass actor instead of request * Apply fixes from StyleCI * actor instead of request * remove serverrequest * Apply fixes from StyleCI * remove dupe actor * Update extensions/mentions/src/Formatter/CheckPermissions.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * fix type in comment * group does not have the relation, post does * test: invalid, deleted, fresh data mentions Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> * Apply fixes from StyleCI * fix: group mentions don't work when editing posts Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> Co-authored-by: StyleCI Bot <bot@styleci.io> Co-authored-by: Sami Mazouz <sychocouldy@gmail.com>
This commit is contained in:
parent
cdc76567d4
commit
827e905f8e
@ -12,10 +12,11 @@ namespace Flarum\Mentions;
|
||||
use Flarum\Api\Controller;
|
||||
use Flarum\Api\Serializer\BasicPostSerializer;
|
||||
use Flarum\Api\Serializer\BasicUserSerializer;
|
||||
use Flarum\Api\Serializer\CurrentUserSerializer;
|
||||
use Flarum\Api\Serializer\GroupSerializer;
|
||||
use Flarum\Api\Serializer\PostSerializer;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Mentions\Notification\PostMentionedBlueprint;
|
||||
use Flarum\Mentions\Notification\UserMentionedBlueprint;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Post\Event\Deleted;
|
||||
use Flarum\Post\Event\Hidden;
|
||||
use Flarum\Post\Event\Posted;
|
||||
@ -37,13 +38,16 @@ return [
|
||||
->configure(ConfigureMentions::class)
|
||||
->render(Formatter\FormatPostMentions::class)
|
||||
->render(Formatter\FormatUserMentions::class)
|
||||
->render(Formatter\FormatGroupMentions::class)
|
||||
->unparse(Formatter\UnparsePostMentions::class)
|
||||
->unparse(Formatter\UnparseUserMentions::class),
|
||||
->unparse(Formatter\UnparseUserMentions::class)
|
||||
->parse(Formatter\CheckPermissions::class),
|
||||
|
||||
(new Extend\Model(Post::class))
|
||||
->belongsToMany('mentionedBy', Post::class, 'post_mentions_post', 'mentions_post_id', 'post_id')
|
||||
->belongsToMany('mentionsPosts', Post::class, 'post_mentions_post', 'post_id', 'mentions_post_id')
|
||||
->belongsToMany('mentionsUsers', User::class, 'post_mentions_user', 'post_id', 'mentions_user_id'),
|
||||
->belongsToMany('mentionsUsers', User::class, 'post_mentions_user', 'post_id', 'mentions_user_id')
|
||||
->belongsToMany('mentionsGroups', Group::class, 'post_mentions_group', 'post_id', 'mentions_group_id'),
|
||||
|
||||
new Extend\Locales(__DIR__.'/locale'),
|
||||
|
||||
@ -51,25 +55,28 @@ return [
|
||||
->namespace('flarum-mentions', __DIR__.'/views'),
|
||||
|
||||
(new Extend\Notification())
|
||||
->type(PostMentionedBlueprint::class, PostSerializer::class, ['alert'])
|
||||
->type(UserMentionedBlueprint::class, PostSerializer::class, ['alert']),
|
||||
->type(Notification\PostMentionedBlueprint::class, PostSerializer::class, ['alert'])
|
||||
->type(Notification\UserMentionedBlueprint::class, PostSerializer::class, ['alert'])
|
||||
->type(Notification\GroupMentionedBlueprint::class, PostSerializer::class, ['alert']),
|
||||
|
||||
(new Extend\ApiSerializer(BasicPostSerializer::class))
|
||||
->hasMany('mentionedBy', BasicPostSerializer::class)
|
||||
->hasMany('mentionsPosts', BasicPostSerializer::class)
|
||||
->hasMany('mentionsUsers', BasicUserSerializer::class),
|
||||
->hasMany('mentionsUsers', BasicUserSerializer::class)
|
||||
->hasMany('mentionsGroups', GroupSerializer::class),
|
||||
|
||||
(new Extend\ApiController(Controller\ShowDiscussionController::class))
|
||||
->addInclude(['posts.mentionedBy', 'posts.mentionedBy.user', 'posts.mentionedBy.discussion'])
|
||||
->load([
|
||||
'posts.mentionsUsers', 'posts.mentionsPosts', 'posts.mentionsPosts.user', 'posts.mentionedBy',
|
||||
'posts.mentionedBy.mentionsPosts', 'posts.mentionedBy.mentionsPosts.user', 'posts.mentionedBy.mentionsUsers',
|
||||
'posts.mentionsGroups'
|
||||
]),
|
||||
|
||||
(new Extend\ApiController(Controller\ListDiscussionsController::class))
|
||||
->load([
|
||||
'firstPost.mentionsUsers', 'firstPost.mentionsPosts', 'firstPost.mentionsPosts.user',
|
||||
'lastPost.mentionsUsers', 'lastPost.mentionsPosts', 'lastPost.mentionsPosts.user'
|
||||
'firstPost.mentionsUsers', 'firstPost.mentionsPosts', 'firstPost.mentionsPosts.user', 'firstPost.mentionsGroups',
|
||||
'lastPost.mentionsUsers', 'lastPost.mentionsPosts', 'lastPost.mentionsPosts.user', 'lastPost.mentionsGroups'
|
||||
]),
|
||||
|
||||
(new Extend\ApiController(Controller\ShowPostController::class))
|
||||
@ -80,13 +87,16 @@ return [
|
||||
->load([
|
||||
'mentionsUsers', 'mentionsPosts', 'mentionsPosts.user', 'mentionedBy',
|
||||
'mentionedBy.mentionsPosts', 'mentionedBy.mentionsPosts.user', 'mentionedBy.mentionsUsers',
|
||||
'mentionsGroups'
|
||||
]),
|
||||
|
||||
(new Extend\ApiController(Controller\CreatePostController::class))
|
||||
->addInclude(['mentionsPosts', 'mentionsPosts.mentionedBy']),
|
||||
->addInclude(['mentionsPosts', 'mentionsPosts.mentionedBy'])
|
||||
->addOptionalInclude('mentionsGroups'),
|
||||
|
||||
(new Extend\ApiController(Controller\UpdatePostController::class))
|
||||
->addInclude(['mentionsPosts', 'mentionsPosts.mentionedBy']),
|
||||
->addInclude(['mentionsPosts', 'mentionsPosts.mentionedBy'])
|
||||
->addOptionalInclude('mentionsGroups'),
|
||||
|
||||
(new Extend\ApiController(Controller\AbstractSerializeController::class))
|
||||
->prepareDataForSerialization(FilterVisiblePosts::class),
|
||||
@ -103,4 +113,9 @@ return [
|
||||
|
||||
(new Extend\Filter(PostFilterer::class))
|
||||
->addFilter(Filter\MentionedFilter::class),
|
||||
|
||||
(new Extend\ApiSerializer(CurrentUserSerializer::class))
|
||||
->attribute('canMentionGroups', function (CurrentUserSerializer $serializer, User $user, array $attributes): bool {
|
||||
return $user->can('mentionGroups');
|
||||
})
|
||||
];
|
||||
|
@ -1,10 +1,20 @@
|
||||
import app from 'flarum/admin/app';
|
||||
|
||||
app.initializers.add('flarum-mentions', function () {
|
||||
app.extensionData.for('flarum-mentions').registerSetting({
|
||||
setting: 'flarum-mentions.allow_username_format',
|
||||
type: 'boolean',
|
||||
label: app.translator.trans('flarum-mentions.admin.settings.allow_username_format_label'),
|
||||
help: app.translator.trans('flarum-mentions.admin.settings.allow_username_format_text'),
|
||||
});
|
||||
app.extensionData
|
||||
.for('flarum-mentions')
|
||||
.registerSetting({
|
||||
setting: 'flarum-mentions.allow_username_format',
|
||||
type: 'boolean',
|
||||
label: app.translator.trans('flarum-mentions.admin.settings.allow_username_format_label'),
|
||||
help: app.translator.trans('flarum-mentions.admin.settings.allow_username_format_text'),
|
||||
})
|
||||
.registerPermission(
|
||||
{
|
||||
permission: 'mentionGroups',
|
||||
label: app.translator.trans('flarum-mentions.admin.permissions.mention_groups_label'),
|
||||
icon: 'fas fa-at',
|
||||
},
|
||||
'start'
|
||||
);
|
||||
});
|
||||
|
@ -10,6 +10,8 @@ import highlight from 'flarum/common/helpers/highlight';
|
||||
import KeyboardNavigatable from 'flarum/forum/utils/KeyboardNavigatable';
|
||||
import { truncate } from 'flarum/common/utils/string';
|
||||
import { throttle } from 'flarum/common/utils/throttleDebounce';
|
||||
import Badge from 'flarum/common/components/Badge';
|
||||
import Group from 'flarum/common/models/Group';
|
||||
|
||||
import AutocompleteDropdown from './fragments/AutocompleteDropdown';
|
||||
import getMentionText from './utils/getMentionText';
|
||||
@ -29,6 +31,7 @@ const throttledSearch = throttle(
|
||||
|
||||
buildSuggestions();
|
||||
});
|
||||
|
||||
searched.push(typedLower);
|
||||
}
|
||||
}
|
||||
@ -66,6 +69,13 @@ export default function addComposerAutocomplete() {
|
||||
const returnedUsers = Array.from(app.store.all('users'));
|
||||
const returnedUserIds = new Set(returnedUsers.map((u) => u.id()));
|
||||
|
||||
// Store groups, but exclude the two virtual groups - 'Guest' and 'Member'.
|
||||
const returnedGroups = Array.from(
|
||||
app.store.all('groups').filter((group) => {
|
||||
return group.id() != Group.GUEST_ID && group.id() != Group.MEMBER_ID;
|
||||
})
|
||||
);
|
||||
|
||||
const applySuggestion = (replacement) => {
|
||||
this.attrs.composer.editor.replaceBeforeCursor(absMentionStart - 1, replacement + ' ');
|
||||
|
||||
@ -124,12 +134,41 @@ export default function addComposerAutocomplete() {
|
||||
);
|
||||
};
|
||||
|
||||
const makeGroupSuggestion = function (group, replacement, content, className = '') {
|
||||
let groupName = group.namePlural().toLowerCase();
|
||||
|
||||
if (typed) {
|
||||
groupName = highlight(groupName, typed);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={'PostPreview ' + className}
|
||||
onclick={() => applySuggestion(replacement)}
|
||||
onmouseenter={function () {
|
||||
dropdown.setIndex($(this).parent().index());
|
||||
}}
|
||||
>
|
||||
<span className="PostPreview-content">
|
||||
<Badge class={`Avatar Badge Badge--group--${group.id()} Badge-icon `} color={group.color()} type="group" icon={group.icon()} />
|
||||
<span className="username">{groupName}</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const userMatches = function (user) {
|
||||
const names = [user.username(), user.displayName()];
|
||||
|
||||
return names.some((name) => name.toLowerCase().substr(0, typed.length) === typed);
|
||||
};
|
||||
|
||||
const groupMatches = function (group) {
|
||||
const names = [group.nameSingular(), group.namePlural()];
|
||||
|
||||
return names.some((name) => name.toLowerCase().substr(0, typed.length) === typed);
|
||||
};
|
||||
|
||||
const buildSuggestions = () => {
|
||||
const suggestions = [];
|
||||
|
||||
@ -141,6 +180,15 @@ export default function addComposerAutocomplete() {
|
||||
|
||||
suggestions.push(makeSuggestion(user, getMentionText(user), '', 'MentionsDropdown-user'));
|
||||
});
|
||||
|
||||
// ... or groups.
|
||||
if (app.session?.user?.canMentionGroups()) {
|
||||
returnedGroups.forEach((group) => {
|
||||
if (!groupMatches(group)) return;
|
||||
|
||||
suggestions.push(makeGroupSuggestion(group, getMentionText(undefined, undefined, group), '', 'MentionsDropdown-group'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If the user is replying to a discussion, or if they are editing a
|
||||
|
@ -1,3 +1,4 @@
|
||||
import GroupMentionedNotification from './components/GroupMentionedNotification';
|
||||
import MentionsUserPage from './components/MentionsUserPage';
|
||||
import PostMentionedNotification from './components/PostMentionedNotification';
|
||||
import UserMentionedNotification from './components/UserMentionedNotification';
|
||||
@ -13,6 +14,7 @@ export default {
|
||||
'mentions/components/MentionsUserPage': MentionsUserPage,
|
||||
'mentions/components/PostMentionedNotification': PostMentionedNotification,
|
||||
'mentions/components/UserMentionedNotification': UserMentionedNotification,
|
||||
'mentions/components/GroupMentionedNotification': GroupMentionedNotification,
|
||||
'mentions/fragments/AutocompleteDropdown': AutocompleteDropdown,
|
||||
'mentions/fragments/PostQuoteButton': PostQuoteButton,
|
||||
'mentions/utils/getCleanDisplayName': getCleanDisplayName,
|
||||
|
@ -0,0 +1,25 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import Notification from 'flarum/forum/components/Notification';
|
||||
import { truncate } from 'flarum/common/utils/string';
|
||||
|
||||
export default class GroupMentionedNotification extends Notification {
|
||||
icon() {
|
||||
return 'fas fa-at';
|
||||
}
|
||||
|
||||
href() {
|
||||
const post = this.attrs.notification.subject();
|
||||
|
||||
return app.route.discussion(post.discussion(), post.number());
|
||||
}
|
||||
|
||||
content() {
|
||||
const user = this.attrs.notification.fromUser();
|
||||
|
||||
return app.translator.trans('flarum-mentions.forum.notifications.group_mentioned_text', { user });
|
||||
}
|
||||
|
||||
excerpt() {
|
||||
return truncate(this.attrs.notification.subject().contentPlain(), 200);
|
||||
}
|
||||
}
|
@ -10,11 +10,16 @@ import addPostQuoteButton from './addPostQuoteButton';
|
||||
import addComposerAutocomplete from './addComposerAutocomplete';
|
||||
import PostMentionedNotification from './components/PostMentionedNotification';
|
||||
import UserMentionedNotification from './components/UserMentionedNotification';
|
||||
import GroupMentionedNotification from './components/GroupMentionedNotification';
|
||||
import UserPage from 'flarum/forum/components/UserPage';
|
||||
import LinkButton from 'flarum/common/components/LinkButton';
|
||||
import MentionsUserPage from './components/MentionsUserPage';
|
||||
import User from 'flarum/common/models/User';
|
||||
import Model from 'flarum/common/Model';
|
||||
|
||||
app.initializers.add('flarum-mentions', function () {
|
||||
User.prototype.canMentionGroups = Model.attribute('canMentionGroups');
|
||||
|
||||
// For every mention of a post inside a post's content, set up a hover handler
|
||||
// that shows a preview of the mentioned post.
|
||||
addPostMentionPreviews();
|
||||
@ -36,6 +41,7 @@ app.initializers.add('flarum-mentions', function () {
|
||||
|
||||
app.notificationComponents.postMentioned = PostMentionedNotification;
|
||||
app.notificationComponents.userMentioned = UserMentionedNotification;
|
||||
app.notificationComponents.groupMentioned = GroupMentionedNotification;
|
||||
|
||||
// Add notification preferences.
|
||||
extend(NotificationGrid.prototype, 'notificationTypes', function (items) {
|
||||
@ -50,6 +56,12 @@ app.initializers.add('flarum-mentions', function () {
|
||||
icon: 'fas fa-at',
|
||||
label: app.translator.trans('flarum-mentions.forum.settings.notify_user_mentioned_label'),
|
||||
});
|
||||
|
||||
items.add('groupMentioned', {
|
||||
name: 'groupMentioned',
|
||||
icon: 'fas fa-at',
|
||||
label: app.translator.trans('flarum-mentions.forum.settings.notify_group_mentioned_label'),
|
||||
});
|
||||
});
|
||||
|
||||
// Add mentions tab in user profile
|
||||
|
@ -1,7 +1,7 @@
|
||||
import getCleanDisplayName, { shouldUseOldFormat } from './getCleanDisplayName';
|
||||
|
||||
/**
|
||||
* Fetches the mention text for a specified user (and optionally a post ID for replies).
|
||||
* Fetches the mention text for a specified user (and optionally a post ID for replies, or group).
|
||||
*
|
||||
* Automatically determines which mention syntax to be used based on the option in the
|
||||
* admin dashboard. Also performs display name clean-up automatically.
|
||||
@ -17,9 +17,13 @@ import getCleanDisplayName, { shouldUseOldFormat } from './getCleanDisplayName';
|
||||
* @example <caption>Using old syntax</caption>
|
||||
* // '@username'
|
||||
* getMentionText(User) // User's username is 'username'
|
||||
*
|
||||
* @example <caption>Group mention</caption>
|
||||
* // '@"Mods"#g4'
|
||||
* getMentionText(undefined, undefined, group) // Group display name is 'Mods', group ID is 4
|
||||
*/
|
||||
export default function getMentionText(user, postId) {
|
||||
if (postId === undefined) {
|
||||
export default function getMentionText(user, postId, group) {
|
||||
if (user !== undefined && postId === undefined) {
|
||||
if (shouldUseOldFormat()) {
|
||||
// Plain @username
|
||||
const cleanText = getCleanDisplayName(user, false);
|
||||
@ -28,9 +32,14 @@ export default function getMentionText(user, postId) {
|
||||
// @"Display name"#UserID
|
||||
const cleanText = getCleanDisplayName(user);
|
||||
return `@"${cleanText}"#${user.id()}`;
|
||||
} else {
|
||||
} else if (user !== undefined && postId !== undefined) {
|
||||
// @"Display name"#pPostID
|
||||
const cleanText = getCleanDisplayName(user);
|
||||
return `@"${cleanText}"#p${postId}`;
|
||||
} else if (group !== undefined) {
|
||||
// @"Name Plural"#gGroupID
|
||||
return `@"${group.namePlural()}"#g${group.id()}`;
|
||||
} else {
|
||||
throw 'No parameters were passed';
|
||||
}
|
||||
}
|
||||
|
@ -31,3 +31,19 @@ export function filterPostMentions(tag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function filterGroupMentions(tag) {
|
||||
if (app.session?.user?.canMentionGroups()) {
|
||||
const group = app.store.getById('groups', tag.getAttribute('id'));
|
||||
|
||||
if (group) {
|
||||
tag.setAttribute('groupname', extractText(group.namePlural()));
|
||||
tag.setAttribute('icon', group.icon());
|
||||
tag.setAttribute('color', group.color());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
tag.invalidate();
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
.PostMention, .UserMention {
|
||||
.PostMention, .UserMention, .GroupMention {
|
||||
background: @control-bg;
|
||||
color: @control-color;
|
||||
border-radius: @border-radius;
|
||||
@ -14,7 +14,7 @@
|
||||
color: @link-color;
|
||||
}
|
||||
}
|
||||
.UserMention, .PostMention {
|
||||
.UserMention, .PostMention, .GroupMention {
|
||||
&--deleted {
|
||||
opacity: 0.8;
|
||||
filter: grayscale(1);
|
||||
@ -97,6 +97,21 @@
|
||||
position: absolute;
|
||||
.Button--color(@tooltip-color, @tooltip-bg);
|
||||
}
|
||||
.GroupMention {
|
||||
color: @body-bg;
|
||||
|
||||
.icon {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
color: @body-bg;
|
||||
}
|
||||
}
|
||||
.MentionsDropdown .Badge {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@media @phone {
|
||||
.MentionsDropdown {
|
||||
|
@ -7,6 +7,9 @@ flarum-mentions:
|
||||
# Translations in this namespace are used by the admin interface.
|
||||
admin:
|
||||
|
||||
# These translations are used in the mentions permissions
|
||||
permissions:
|
||||
mention_groups_label: Mention groups
|
||||
# These translations are used in the mentions Settings page.
|
||||
settings:
|
||||
allow_username_format_label: Allow username mention format (@Username)
|
||||
@ -19,7 +22,7 @@ flarum-mentions:
|
||||
|
||||
# These translations are used by the composer (reply autocompletion function).
|
||||
composer:
|
||||
mention_tooltip: Mention a user or post
|
||||
mention_tooltip: Mention a user, group or post
|
||||
reply_to_post_text: "Reply to #{number}"
|
||||
|
||||
# These translations are used by the Notifications dropdown, a.k.a. "the bell".
|
||||
@ -27,6 +30,7 @@ flarum-mentions:
|
||||
others_text: => core.ref.some_others
|
||||
post_mentioned_text: "{username} replied to your post" # Can be pluralized to agree with the number of users!
|
||||
user_mentioned_text: "{username} mentioned you"
|
||||
group_mentioned_text: "{username} mentioned a group you're a member of"
|
||||
|
||||
# These translations are displayed beneath individual posts.
|
||||
post:
|
||||
@ -41,6 +45,7 @@ flarum-mentions:
|
||||
settings:
|
||||
notify_post_mentioned_label: Someone replies to one of my posts
|
||||
notify_user_mentioned_label: Someone mentions me in a post
|
||||
notify_group_mentioned_label: Someone mentions a group I'm a member of in a post
|
||||
|
||||
# These translations are used in the user profile page and profile popup.
|
||||
user:
|
||||
@ -50,6 +55,9 @@ flarum-mentions:
|
||||
post_mention:
|
||||
deleted_text: "[unknown]"
|
||||
|
||||
group_mention:
|
||||
deleted_text: "[unknown group]"
|
||||
|
||||
# Translations in this namespace are used in emails sent by the forum.
|
||||
email:
|
||||
|
||||
@ -80,4 +88,16 @@ flarum-mentions:
|
||||
---
|
||||
|
||||
{content}
|
||||
# These translations are used in emails sent when a group is mentioned
|
||||
group_mentioned:
|
||||
subject: "{mentioner_display_name} mentioned a group you're a member of in {title}"
|
||||
body: |
|
||||
Hey {recipient_display_name}!
|
||||
|
||||
{mentioner_display_name} mentioned a group you're a member of in {title}.
|
||||
|
||||
{url}
|
||||
|
||||
---
|
||||
|
||||
{content}
|
||||
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
|
||||
return [
|
||||
'up' => function (Builder $schema) {
|
||||
$schema->create('post_mentions_group', function (Blueprint $table) {
|
||||
$table->integer('post_id')->unsigned();
|
||||
$table->integer('mentions_group_id')->unsigned();
|
||||
$table->dateTime('created_at')->useCurrent()->nullable();
|
||||
$table->primary(['post_id', 'mentions_group_id']);
|
||||
|
||||
$table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
|
||||
$table->foreign('mentions_group_id')->references('id')->on('groups')->onDelete('cascade');
|
||||
});
|
||||
},
|
||||
|
||||
'down' => function (Builder $schema) {
|
||||
$schema->drop('post_mentions_group');
|
||||
}
|
||||
];
|
@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Mentions;
|
||||
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
@ -34,6 +35,7 @@ class ConfigureMentions
|
||||
{
|
||||
$this->configureUserMentions($config);
|
||||
$this->configurePostMentions($config);
|
||||
$this->configureGroupMentions($config);
|
||||
}
|
||||
|
||||
private function configureUserMentions(Configurator $config)
|
||||
@ -136,4 +138,49 @@ class ConfigureMentions
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private function configureGroupMentions(Configurator $config)
|
||||
{
|
||||
$tagName = 'GROUPMENTION';
|
||||
|
||||
$tag = $config->tags->add($tagName);
|
||||
$tag->attributes->add('groupname');
|
||||
$tag->attributes->add('icon');
|
||||
$tag->attributes->add('color');
|
||||
$tag->attributes->add('id')->filterChain->append('#uint');
|
||||
|
||||
$tag->template = '
|
||||
<xsl:choose>
|
||||
<xsl:when test="@deleted != 1">
|
||||
<span class="GroupMention" style="background: {@color}">@<xsl:value-of select="@groupname"/><i class="icon {@icon}"></i></span>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<span class="GroupMention GroupMention--deleted" style="background: {@color}">@<xsl:value-of select="@groupname"/><i class="icon {@icon}"></i></span>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>';
|
||||
$tag->filterChain->prepend([static::class, 'addGroupId'])
|
||||
->setJS('function(tag) { return flarum.extensions["flarum-mentions"].filterGroupMentions(tag); }');
|
||||
|
||||
$config->Preg->match('/\B@["|“](?<groupname>((?!"#[a-z]{0,3}[0-9]+).)+)["|”]#g(?<id>[0-9]+)\b/', $tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $tag
|
||||
* @return bool
|
||||
*/
|
||||
public static function addGroupId($tag)
|
||||
{
|
||||
$group = Group::find($tag->getAttribute('id'));
|
||||
|
||||
if (isset($group) && ! in_array($group->id, [Group::GUEST_ID, Group::MEMBER_ID])) {
|
||||
$tag->setAttribute('id', $group->id);
|
||||
$tag->setAttribute('groupname', $group->name_plural);
|
||||
$tag->setAttribute('icon', $group->icon ?? 'fas fa-at');
|
||||
$tag->setAttribute('color', $group->color);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$tag->invalidate();
|
||||
}
|
||||
}
|
||||
|
@ -54,8 +54,8 @@ class FilterVisiblePosts
|
||||
|| $controller instanceof Controller\CreatePostController
|
||||
|| $controller instanceof Controller\UpdatePostController) {
|
||||
$relations = [
|
||||
'mentionsUsers', 'mentionsPosts', 'mentionsPosts.user', 'mentionedBy',
|
||||
'mentionedBy.mentionsPosts', 'mentionedBy.mentionsPosts.user', 'mentionedBy.mentionsUsers'
|
||||
'mentionsUsers', 'mentionsPosts', 'mentionsPosts.user', 'mentionedBy', 'mentionsGroups',
|
||||
'mentionedBy.mentionsPosts', 'mentionedBy.mentionsPosts.user', 'mentionedBy.mentionsUsers', 'mentionedBy.mentionsGroups.group'
|
||||
];
|
||||
|
||||
$posts = [$data];
|
||||
|
26
extensions/mentions/src/Formatter/CheckPermissions.php
Normal file
26
extensions/mentions/src/Formatter/CheckPermissions.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Mentions\Formatter;
|
||||
|
||||
use Flarum\User\User;
|
||||
use s9e\TextFormatter\Parser;
|
||||
|
||||
class CheckPermissions
|
||||
{
|
||||
public function __invoke(Parser $parser, $content, string $text, ?User $actor): string
|
||||
{
|
||||
// Check user has `mentionGroups` permission, if not, remove the `GROUPMENTION` tag from the parser.
|
||||
if ($actor && $actor->cannot('mentionGroups')) {
|
||||
$parser->disableTag('GROUPMENTION');
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
59
extensions/mentions/src/Formatter/FormatGroupMentions.php
Normal file
59
extensions/mentions/src/Formatter/FormatGroupMentions.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Mentions\Formatter;
|
||||
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Post\Post;
|
||||
use s9e\TextFormatter\Renderer;
|
||||
use s9e\TextFormatter\Utils;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class FormatGroupMentions
|
||||
{
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure rendering for group mentions.
|
||||
*
|
||||
* @param \s9e\TextFormatter\Renderer $renderer
|
||||
* @param mixed $context
|
||||
* @param string $xml
|
||||
* @return string
|
||||
*/
|
||||
public function __invoke(Renderer $renderer, $context, string $xml): string
|
||||
{
|
||||
return Utils::replaceAttributes($xml, 'GROUPMENTION', function ($attributes) use ($context) {
|
||||
$group = (($context && isset($context->getRelations()['mentionsGroups'])) || $context instanceof Post)
|
||||
? $context->mentionsGroups->find($attributes['id'])
|
||||
: Group::find($attributes['id']);
|
||||
|
||||
if ($group) {
|
||||
$attributes['groupname'] = $group->name_plural;
|
||||
$attributes['icon'] = $group->icon ?? 'fas fa-at';
|
||||
$attributes['color'] = $group->color;
|
||||
$attributes['deleted'] = false;
|
||||
} else {
|
||||
$attributes['groupname'] = $this->translator->trans('flarum-mentions.forum.group_mention.deleted_text');
|
||||
$attributes['icon'] = '';
|
||||
$attributes['deleted'] = true;
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
});
|
||||
}
|
||||
}
|
@ -40,5 +40,8 @@ class UpdateMentionsMetadataWhenInvisible
|
||||
|
||||
// Remove post mentions
|
||||
$event->post->mentionsPosts()->sync([]);
|
||||
|
||||
// Remove group mentions
|
||||
$event->post->mentionsGroups()->sync([]);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Mentions\Listener;
|
||||
|
||||
use Flarum\Mentions\Notification\GroupMentionedBlueprint;
|
||||
use Flarum\Mentions\Notification\PostMentionedBlueprint;
|
||||
use Flarum\Mentions\Notification\UserMentionedBlueprint;
|
||||
use Flarum\Notification\NotificationSyncer;
|
||||
@ -50,6 +51,11 @@ class UpdateMentionsMetadataWhenVisible
|
||||
$event->post,
|
||||
Utils::getAttributeValues($content, 'POSTMENTION', 'id')
|
||||
);
|
||||
|
||||
$this->syncGroupMentions(
|
||||
$event->post,
|
||||
Utils::getAttributeValues($content, 'GROUPMENTION', 'id')
|
||||
);
|
||||
}
|
||||
|
||||
protected function syncUserMentions(Post $post, array $mentioned)
|
||||
@ -84,4 +90,21 @@ class UpdateMentionsMetadataWhenVisible
|
||||
$this->notifications->sync(new PostMentionedBlueprint($post, $reply), [$post->user]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function syncGroupMentions(Post $post, array $mentioned)
|
||||
{
|
||||
$post->mentionsGroups()->sync($mentioned);
|
||||
$post->unsetRelation('mentionsGroups');
|
||||
|
||||
$users = User::whereHas('groups', function ($query) use ($mentioned) {
|
||||
$query->whereIn('id', $mentioned);
|
||||
})
|
||||
->get()
|
||||
->filter(function (User $user) use ($post) {
|
||||
return $post->isVisibleTo($user) && $user->id !== $post->user_id;
|
||||
})
|
||||
->all();
|
||||
|
||||
$this->notifications->sync(new GroupMentionedBlueprint($post), $users);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Mentions\Notification;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\MailableInterface;
|
||||
use Flarum\Post\Post;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class GroupMentionedBlueprint implements BlueprintInterface, MailableInterface
|
||||
{
|
||||
/**
|
||||
* @var Post
|
||||
*/
|
||||
public $post;
|
||||
|
||||
/**
|
||||
* @param Post $post
|
||||
*/
|
||||
public function __construct(Post $post)
|
||||
{
|
||||
$this->post = $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSubject()
|
||||
{
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFromUser()
|
||||
{
|
||||
return $this->post->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEmailView()
|
||||
{
|
||||
return ['text' => 'flarum-mentions::emails.groupMentioned'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEmailSubject(TranslatorInterface $translator)
|
||||
{
|
||||
return $translator->trans('flarum-mentions.email.group_mentioned.subject', [
|
||||
'{mentioner_display_name}' => $this->post->user->display_name,
|
||||
'{title}' => $this->post->discussion->title
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getType()
|
||||
{
|
||||
return 'groupMentioned';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubjectModel()
|
||||
{
|
||||
return Post::class;
|
||||
}
|
||||
}
|
420
extensions/mentions/tests/integration/api/GroupMentionsTest.php
Normal file
420
extensions/mentions/tests/integration/api/GroupMentionsTest.php
Normal file
@ -0,0 +1,420 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Mentions\Tests\integration\api;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
|
||||
class GroupMentionsTest extends TestCase
|
||||
{
|
||||
use RetrievesAuthorizedUsers;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->extension('flarum-mentions');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
['id' => 3, 'username' => 'potato', 'email' => 'potato@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 4, 'username' => 'toby', 'email' => 'toby@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 5, 'username' => 'bad_user', 'email' => 'bad_user@machine.local', 'is_email_confirmed' => 1],
|
||||
],
|
||||
'discussions' => [
|
||||
['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 3, 'first_post_id' => 4, 'comment_count' => 2],
|
||||
],
|
||||
'posts' => [
|
||||
['id' => 4, 'number' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><p>One of the <GROUPMENTION color="#80349E" groupname="Mods" icon="fas fa-bolt" id="4">@"Mods"#g4</GROUPMENTION> will look at this</p></r>'],
|
||||
['id' => 6, 'number' => 3, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><p><GROUPMENTION color="#80349E" groupname="OldGroupName" icon="fas fa-circle" id="100">@"OldGroupName"#g100</GROUPMENTION></p></r>'],
|
||||
['id' => 7, 'number' => 4, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><p><GROUPMENTION color="#000" groupname="OldGroupName" icon="fas fa-circle" id="11">@"OldGroupName"#g11</GROUPMENTION></p></r>'],
|
||||
],
|
||||
'post_mentions_group' => [
|
||||
['post_id' => 4, 'mentions_group_id' => 4],
|
||||
['post_id' => 7, 'mentions_group_id' => 11],
|
||||
],
|
||||
'group_permission' => [
|
||||
['group_id' => Group::MEMBER_ID, 'permission' => 'postWithoutThrottle'],
|
||||
],
|
||||
'groups' => [
|
||||
[
|
||||
'id' => 10,
|
||||
'name_singular' => 'Hidden',
|
||||
'name_plural' => 'Ninjas',
|
||||
'color' => null,
|
||||
'icon' => 'fas fa-wrench',
|
||||
'is_hidden' => 1
|
||||
],
|
||||
[
|
||||
'id' => 11,
|
||||
'name_singular' => 'Fresh Name',
|
||||
'name_plural' => 'Fresh Name',
|
||||
'color' => '#ccc',
|
||||
'icon' => 'fas fa-users',
|
||||
'is_hidden' => 0
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function rendering_a_valid_group_mention_works()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/posts/4')
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('<p>One of the <span style="background:#80349E" class="GroupMention">@Mods<i class="icon fas fa-bolt"></i></span> will look at this</p>', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsGroups->find(4));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function mentioning_an_invalid_group_doesnt_work()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"InvalidGroup"#g99',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
]
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('@"InvalidGroup"#g99', $response['data']['attributes']['content']);
|
||||
$this->assertStringNotContainsString('GroupMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(0, CommentPost::find($response['data']['id'])->mentionsGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function deleted_group_mentions_render_with_deleted_label()
|
||||
{
|
||||
$deleted_text = $this->app()->getContainer()->make('translator')->trans('flarum-mentions.forum.group_mention.deleted_text');
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/posts/6', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString("@$deleted_text", $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('GroupMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('GroupMention--deleted', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('@OldGroupName', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(0, CommentPost::find($response['data']['id'])->mentionsGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function group_mentions_render_with_fresh_data()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/posts/7', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('@Fresh Name', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('GroupMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('@OldGroupName', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsGroups->find(11));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function mentioning_a_group_as_an_admin_user_works()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"Mods"#g4',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
]
|
||||
]
|
||||
]
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('@Mods', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('fas fa-bolt', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertEquals('@"Mods"#g4', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('GroupMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(1, CommentPost::find($response['data']['id'])->mentionsGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function mentioning_multiple_groups_as_an_admin_user_works()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"Admins"#g1 @"Mods"#g4',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
]
|
||||
]
|
||||
]
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('@Admins', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@Mods', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('fas fa-wrench', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('fas fa-bolt', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertEquals('@"Admins"#g1 @"Mods"#g4', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('GroupMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(2, CommentPost::find($response['data']['id'])->mentionsGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function mentioning_a_virtual_group_as_an_admin_user_does_not_work()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"Members"#g3 @"Guests"#g2',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
]
|
||||
]
|
||||
]
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringNotContainsString('@Members', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('@Guests', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertEquals('@"Members"#g3 @"Guests"#g2', $response['data']['attributes']['content']);
|
||||
$this->assertStringNotContainsString('GroupMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(0, CommentPost::find($response['data']['id'])->mentionsGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function regular_user_does_not_have_group_mention_permission_by_default()
|
||||
{
|
||||
$this->database();
|
||||
$this->assertFalse(User::find(3)->can('mentionGroups'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function regular_user_does_have_group_mention_permission_when_added()
|
||||
{
|
||||
$this->prepareDatabase([
|
||||
'group_permission' => [
|
||||
['group_id' => Group::MEMBER_ID, 'permission' => 'mentionGroups'],
|
||||
]
|
||||
]);
|
||||
|
||||
$this->database();
|
||||
$this->assertTrue(User::find(3)->can('mentionGroups'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function user_without_permission_cannot_mention_groups()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 3,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"Mods"#g4',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringNotContainsString('@Mods', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@"Mods"#g4', $response['data']['attributes']['content']);
|
||||
$this->assertStringNotContainsString('GroupMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(0, CommentPost::find($response['data']['id'])->mentionsGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function user_with_permission_can_mention_groups()
|
||||
{
|
||||
$this->prepareDatabase([
|
||||
'group_permission' => [
|
||||
['group_id' => Group::MEMBER_ID, 'permission' => 'mentionGroups'],
|
||||
]
|
||||
]);
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 3,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"Mods"#g4',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('@Mods', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@"Mods"#g4', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('GroupMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(1, CommentPost::find($response['data']['id'])->mentionsGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function user_with_permission_cannot_mention_hidden_groups()
|
||||
{
|
||||
$this->prepareDatabase([
|
||||
'group_permission' => [
|
||||
['group_id' => Group::MEMBER_ID, 'permission' => 'mentionGroups'],
|
||||
]
|
||||
]);
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 3,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"Ninjas"#g10',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringNotContainsString('@Ninjas', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@"Ninjas"#g10', $response['data']['attributes']['content']);
|
||||
$this->assertStringNotContainsString('GroupMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(0, CommentPost::find($response['data']['id'])->mentionsGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function editing_a_post_that_has_a_mention_works()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('PATCH', '/api/posts/4', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => 'New content with @"Mods"#g4 mention',
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('@Mods', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertEquals('New content with @"Mods"#g4 mention', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('GroupMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsGroups->find(4));
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{!! $translator->trans('flarum-mentions.email.group_mentioned.body', [
|
||||
'{recipient_display_name}' => $user->display_name,
|
||||
'{mentioner_display_name}' => $blueprint->post->user->display_name,
|
||||
'{title}' => $blueprint->post->discussion->title,
|
||||
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->post->discussion_id, 'near' => $blueprint->post->number]),
|
||||
'{content}' => $blueprint->post->content
|
||||
]) !!}
|
@ -52,6 +52,7 @@ class Formatter implements ExtenderInterface, LifecycleInterface
|
||||
* - \s9e\TextFormatter\Parser $parser
|
||||
* - mixed $context
|
||||
* - string $text: The text to be parsed.
|
||||
* - \Flarum\User\User|null $actor. This argument MUST either be nullable, or omitted entirely.
|
||||
*
|
||||
* The callback should return:
|
||||
* - string $text: The text to be parsed.
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\Formatter;
|
||||
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use s9e\TextFormatter\Configurator;
|
||||
@ -83,14 +84,15 @@ class Formatter
|
||||
*
|
||||
* @param string $text
|
||||
* @param mixed $context
|
||||
* @param User|null $user
|
||||
* @return string
|
||||
*/
|
||||
public function parse($text, $context = null)
|
||||
public function parse($text, $context = null, User $user = null)
|
||||
{
|
||||
$parser = $this->getParser($context);
|
||||
|
||||
foreach ($this->parsingCallbacks as $callback) {
|
||||
$text = $callback($parser, $context, $text);
|
||||
$text = $callback($parser, $context, $text, $user);
|
||||
}
|
||||
|
||||
return $parser->parse($text);
|
||||
|
@ -85,7 +85,8 @@ class PostReplyHandler
|
||||
$discussion->id,
|
||||
Arr::get($command->data, 'attributes.content'),
|
||||
$actor->id,
|
||||
$command->ipAddress
|
||||
$command->ipAddress,
|
||||
$command->actor,
|
||||
);
|
||||
|
||||
if ($actor->isAdmin() && ($time = Arr::get($command->data, 'attributes.createdAt'))) {
|
||||
|
@ -44,9 +44,10 @@ class CommentPost extends Post
|
||||
* @param string $content
|
||||
* @param int $userId
|
||||
* @param string $ipAddress
|
||||
* @param User|null $actor
|
||||
* @return static
|
||||
*/
|
||||
public static function reply($discussionId, $content, $userId, $ipAddress)
|
||||
public static function reply($discussionId, $content, $userId, $ipAddress, User $actor = null)
|
||||
{
|
||||
$post = new static;
|
||||
|
||||
@ -57,7 +58,7 @@ class CommentPost extends Post
|
||||
$post->ip_address = $ipAddress;
|
||||
|
||||
// Set content last, as the parsing may rely on other post attributes.
|
||||
$post->content = $content;
|
||||
$post->setContentAttribute($content, $actor);
|
||||
|
||||
$post->raise(new Posted($post));
|
||||
|
||||
@ -74,7 +75,7 @@ class CommentPost extends Post
|
||||
public function revise($content, User $actor)
|
||||
{
|
||||
if ($this->content !== $content) {
|
||||
$this->content = $content;
|
||||
$this->setContentAttribute($content, $actor);
|
||||
|
||||
$this->edited_at = Carbon::now();
|
||||
$this->edited_user_id = $actor->id;
|
||||
@ -145,10 +146,11 @@ class CommentPost extends Post
|
||||
* Parse the content before it is saved to the database.
|
||||
*
|
||||
* @param string $value
|
||||
* @param User $actor
|
||||
*/
|
||||
public function setContentAttribute($value)
|
||||
public function setContentAttribute($value, User $actor = null)
|
||||
{
|
||||
$this->attributes['content'] = $value ? static::$formatter->parse($value, $this) : null;
|
||||
$this->attributes['content'] = $value ? static::$formatter->parse($value, $this, $actor ?? $this->user) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user