mirror of
https://github.com/flarum/core.git
synced 2025-07-24 18:21:33 +02:00
New mentions format, decouple usernames from mentions (#65)
* Convert user mentions to new `@"Display Name"#ID` format * Handle deleted user's mentions * Convert post mentions to `@"Display Name"#pID` format * Handle deleted user's post mentions and deleted posts mentions * Clean display name of `"#{letters}{numbers}` (replace with underscore _) * Adapt integration tests to new mention formats * Use a deleted attribute for user mentions * Introduce cleanDisplayName util * Detect new format with autocomplete * Slug needed on rendering only * Invalidate user mention tag when ID is invalid This used to be implicitly done, when there was a username attribute configured, formatter would check that all attributes are available and if not invalidate. since we now only have `displayname` and `id` attributes which are both available from the regex matching, formatter doesn't implicitly invalidate anymore and therefore validates ANY matches. So we explicitly invalidate the tag when the ID does not match a user. * Allow username mention format with a setting * Add tests for turning setting on/off * Move setting check to tag filter Because the configurator caches, changing the setting only takes effect after the cache is cleared. * fix: showing autocomplete at the right moment * Add dockblocks to explain unparsing process
This commit is contained in:
@@ -31,10 +31,15 @@ return [
|
||||
->js(__DIR__.'/js/dist/forum.js')
|
||||
->css(__DIR__.'/less/forum.less'),
|
||||
|
||||
(new Extend\Frontend('admin'))
|
||||
->js(__DIR__.'/js/dist/admin.js'),
|
||||
|
||||
(new Extend\Formatter)
|
||||
->configure(ConfigureMentions::class)
|
||||
->render(Formatter\FormatPostMentions::class)
|
||||
->render(Formatter\FormatUserMentions::class),
|
||||
->render(Formatter\FormatUserMentions::class)
|
||||
->unparse(Formatter\UnparsePostMentions::class)
|
||||
->unparse(Formatter\UnparseUserMentions::class),
|
||||
|
||||
(new Extend\Model(Post::class))
|
||||
->belongsToMany('mentionedBy', Post::class, 'post_mentions_post', 'mentions_post_id', 'post_id')
|
||||
@@ -70,6 +75,9 @@ return [
|
||||
(new Extend\ApiController(Controller\AbstractSerializeController::class))
|
||||
->prepareDataForSerialization(FilterVisiblePosts::class),
|
||||
|
||||
(new Extend\Settings)
|
||||
->serializeToForum('allowUsernameMentionFormat', 'flarum-mentions.allow_username_format', 'boolval'),
|
||||
|
||||
(new Extend\Event())
|
||||
->listen(Posted::class, Listener\UpdateMentionsMetadataWhenVisible::class)
|
||||
->listen(Restored::class, Listener\UpdateMentionsMetadataWhenVisible::class)
|
||||
|
1
extensions/mentions/js/admin.js
Normal file
1
extensions/mentions/js/admin.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from './src/admin';
|
10
extensions/mentions/js/src/admin/index.js
Normal file
10
extensions/mentions/js/src/admin/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
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')
|
||||
});
|
||||
});
|
@@ -10,6 +10,7 @@ import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';
|
||||
import { truncate } from 'flarum/utils/string';
|
||||
|
||||
import AutocompleteDropdown from './fragments/AutocompleteDropdown';
|
||||
import cleanDisplayName from './utils/cleanDisplayName';
|
||||
|
||||
export default function addComposerAutocomplete() {
|
||||
const $container = $('<div class="ComposerBody-mentionsDropdownContainer"></div>');
|
||||
@@ -35,6 +36,7 @@ export default function addComposerAutocomplete() {
|
||||
let relMentionStart;
|
||||
let absMentionStart;
|
||||
let typed;
|
||||
let matchTyped;
|
||||
let searchTimeout;
|
||||
|
||||
// We store users returned from an API here to preserve order in which they are returned
|
||||
@@ -74,6 +76,8 @@ export default function addComposerAutocomplete() {
|
||||
|
||||
if (absMentionStart) {
|
||||
typed = lastChunk.substring(relMentionStart).toLowerCase();
|
||||
matchTyped = typed.match(/^"((?:(?!"#).)+)$/);
|
||||
typed = (matchTyped && matchTyped[1]) || typed;
|
||||
|
||||
const makeSuggestion = function(user, replacement, content, className = '') {
|
||||
const username = usernameHelper(user);
|
||||
@@ -116,7 +120,7 @@ export default function addComposerAutocomplete() {
|
||||
if (!userMatches(user)) return;
|
||||
|
||||
suggestions.push(
|
||||
makeSuggestion(user, '@' + user.username(), '', 'MentionsDropdown-user')
|
||||
makeSuggestion(user, `@"${cleanDisplayName(user)}"#${user.id()}`, '', 'MentionsDropdown-user')
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -142,7 +146,7 @@ export default function addComposerAutocomplete() {
|
||||
.forEach(post => {
|
||||
const user = post.user();
|
||||
suggestions.push(
|
||||
makeSuggestion(user, '@' + user.username() + '#' + post.id(), [
|
||||
makeSuggestion(user, `@"${cleanDisplayName(user)}"#p${post.id()}`, [
|
||||
app.translator.trans('flarum-mentions.forum.composer.reply_to_post_text', {number: post.number()}), ' — ',
|
||||
truncate(post.contentPlain(), 200)
|
||||
], 'MentionsDropdown-post')
|
||||
|
@@ -14,12 +14,12 @@ export default function addPostMentionPreviews() {
|
||||
const parentPost = this.attrs.post;
|
||||
const $parentPost = this.$();
|
||||
|
||||
this.$().on('click', '.UserMention, .PostMention', function (e) {
|
||||
this.$().on('click', '.UserMention:not(.UserMention--deleted), .PostMention:not(.PostMention--deleted)', function (e) {
|
||||
m.route.set(this.getAttribute('href'));
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
this.$('.PostMention').each(function() {
|
||||
this.$('.PostMention:not(.PostMention--deleted)').each(function() {
|
||||
const $this = $(this);
|
||||
const id = $this.data('id');
|
||||
let timeout;
|
||||
|
@@ -0,0 +1,3 @@
|
||||
export default function cleanDisplayName(user) {
|
||||
return user.displayName().replace(/"#[a-z]{0,3}[0-9]+/, '_');
|
||||
};
|
@@ -1,9 +1,10 @@
|
||||
import DiscussionControls from 'flarum/utils/DiscussionControls';
|
||||
import EditPostComposer from 'flarum/components/EditPostComposer';
|
||||
import cleanDisplayName from './cleanDisplayName';
|
||||
|
||||
function insertMention(post, composer, quote) {
|
||||
const user = post.user();
|
||||
const mention = '@' + (user ? user.username() : post.number()) + '#' + post.id() + ' ';
|
||||
const mention = `@"${(user && cleanDisplayName(user)) || app.translator.trans('core.lib.username.deleted_text')}"#p${post.id()}`;
|
||||
|
||||
// If the composer is empty, then assume we're starting a new reply.
|
||||
// In which case we don't want the user to have to confirm if they
|
||||
|
@@ -2,14 +2,22 @@ import username from 'flarum/helpers/username';
|
||||
import extractText from 'flarum/utils/extractText';
|
||||
|
||||
export function filterUserMentions(tag) {
|
||||
const user = app.store.getBy('users', 'username', tag.getAttribute('username'));
|
||||
let user;
|
||||
|
||||
if (app.forum.attribute('allowUsernameMentionFormat') && tag.hasAttribute('username'))
|
||||
user = app.store.getBy('users', 'username', tag.getAttribute('username'));
|
||||
else if (tag.hasAttribute('id'))
|
||||
user = app.store.getById('users', tag.getAttribute('id'));
|
||||
|
||||
if (user) {
|
||||
tag.setAttribute('id', user.id());
|
||||
tag.setAttribute('slug', user.slug());
|
||||
tag.setAttribute('displayname', extractText(username(user)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
tag.invalidate();
|
||||
}
|
||||
|
||||
export function filterPostMentions(tag) {
|
||||
|
@@ -4,6 +4,7 @@
|
||||
border-radius: @border-radius;
|
||||
padding: 2px 5px;
|
||||
border: 0 !important;
|
||||
font-weight: 600;
|
||||
|
||||
blockquote & {
|
||||
background: @body-bg;
|
||||
@@ -13,6 +14,12 @@
|
||||
color: @link-color;
|
||||
}
|
||||
}
|
||||
.UserMention, .PostMention {
|
||||
&--deleted {
|
||||
opacity: 0.8;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
.PostMention {
|
||||
margin: 0 3px;
|
||||
|
||||
|
@@ -4,6 +4,16 @@ flarum-mentions:
|
||||
# UNIQUE KEYS - The following keys are used in only one location each.
|
||||
##
|
||||
|
||||
# Translations in this namespace are used by the admin interface.
|
||||
admin:
|
||||
|
||||
# These translations are used in the mentions Settings page.
|
||||
settings:
|
||||
allow_username_format_label: Allow username mention format (@Username)
|
||||
allow_username_format_text: |
|
||||
The current format for user mentions is @"Display Name"#ID.
|
||||
This setting allows using the old format of @Username, however it will still be converted to the new format upon saving.
|
||||
|
||||
# Translations in this namespace are used by the forum user interface.
|
||||
forum:
|
||||
|
||||
@@ -36,6 +46,10 @@ flarum-mentions:
|
||||
user:
|
||||
mentions_link: Mentions
|
||||
|
||||
# These translations are used in the post mentions
|
||||
post_mention:
|
||||
deleted_text: "[unknown]"
|
||||
|
||||
# Translations in this namespace are used in emails sent by the forum.
|
||||
email:
|
||||
|
||||
|
@@ -0,0 +1,14 @@
|
||||
<?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 Flarum\Database\Migration;
|
||||
|
||||
return Migration::addSettings([
|
||||
'flarum-mentions.allow_username_format' => 1,
|
||||
]);
|
@@ -11,6 +11,7 @@ namespace Flarum\Mentions;
|
||||
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\User;
|
||||
use s9e\TextFormatter\Configurator;
|
||||
|
||||
@@ -42,14 +43,22 @@ class ConfigureMentions
|
||||
$tagName = 'USERMENTION';
|
||||
|
||||
$tag = $config->tags->add($tagName);
|
||||
$tag->attributes->add('username');
|
||||
$tag->attributes->add('displayname');
|
||||
$tag->attributes->add('id')->filterChain->append('#uint');
|
||||
|
||||
$tag->template = '<a href="{$PROFILE_URL}{@username}" class="UserMention">@<xsl:value-of select="@displayname"/></a>';
|
||||
$tag->template = '
|
||||
<xsl:choose>
|
||||
<xsl:when test="@deleted != 1">
|
||||
<a href="{$PROFILE_URL}{@slug}" class="UserMention">@<xsl:value-of select="@displayname"/></a>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<span class="UserMention UserMention--deleted">@<xsl:value-of select="@displayname"/></span>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>';
|
||||
$tag->filterChain->prepend([static::class, 'addUserId'])
|
||||
->setJS('function(tag) { return flarum.extensions["flarum-mentions"].filterUserMentions(tag); }');
|
||||
|
||||
$config->Preg->match('/\B@"(?<displayname>((?!"#[a-z]{0,3}[0-9]+).)+)"#(?<id>[0-9]+)\b/', $tagName);
|
||||
$config->Preg->match('/\B@(?<username>[a-z0-9_-]+)(?!#)/i', $tagName);
|
||||
}
|
||||
|
||||
@@ -60,12 +69,22 @@ class ConfigureMentions
|
||||
*/
|
||||
public static function addUserId($tag)
|
||||
{
|
||||
if ($user = User::where('username', $tag->getAttribute('username'))->first()) {
|
||||
$allow_username_format = (bool) resolve(SettingsRepositoryInterface::class)->get('flarum-mentions.allow_username_format');
|
||||
|
||||
if ($tag->hasAttribute('username') && $allow_username_format) {
|
||||
$user = User::where('username', $tag->getAttribute('username'))->first();
|
||||
} elseif ($tag->hasAttribute('id')) {
|
||||
$user = User::find($tag->getAttribute('id'));
|
||||
}
|
||||
|
||||
if (isset($user)) {
|
||||
$tag->setAttribute('id', $user->id);
|
||||
$tag->setAttribute('displayname', $user->display_name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$tag->invalidate();
|
||||
}
|
||||
|
||||
private function configurePostMentions(Configurator $config)
|
||||
@@ -76,19 +95,26 @@ class ConfigureMentions
|
||||
|
||||
$tag = $config->tags->add($tagName);
|
||||
|
||||
$tag->attributes->add('username');
|
||||
$tag->attributes->add('displayname');
|
||||
$tag->attributes->add('number')->filterChain->append('#uint');
|
||||
$tag->attributes->add('discussionid')->filterChain->append('#uint');
|
||||
$tag->attributes->add('id')->filterChain->append('#uint');
|
||||
|
||||
$tag->template = '<a href="{$DISCUSSION_URL}{@discussionid}/{@number}" class="PostMention" data-id="{@id}"><xsl:value-of select="@displayname"/></a>';
|
||||
$tag->template = '
|
||||
<xsl:choose>
|
||||
<xsl:when test="@deleted != 1">
|
||||
<a href="{$DISCUSSION_URL}{@discussionid}/{@number}" class="PostMention" data-id="{@id}"><xsl:value-of select="@displayname"/></a>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<span class="PostMention PostMention--deleted" data-id="{@id}"><xsl:value-of select="@displayname"/></span>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>';
|
||||
|
||||
$tag->filterChain
|
||||
->prepend([static::class, 'addPostId'])
|
||||
->setJS('function(tag) { return flarum.extensions["flarum-mentions"].filterPostMentions(tag); }');
|
||||
|
||||
$config->Preg->match('/\B@(?<username>[a-z0-9_-]+)#(?<id>\d+)/i', $tagName);
|
||||
$config->Preg->match('/\B@"(?<displayname>((?!"#[a-z]{0,3}[0-9]+).)+)"#p(?<id>[0-9]+)\b/', $tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -12,9 +12,20 @@ namespace Flarum\Mentions\Formatter;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use s9e\TextFormatter\Renderer;
|
||||
use s9e\TextFormatter\Utils;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class FormatPostMentions
|
||||
{
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure rendering for post mentions.
|
||||
*
|
||||
@@ -34,6 +45,17 @@ class FormatPostMentions
|
||||
$attributes['displayname'] = $post->user->display_name;
|
||||
}
|
||||
|
||||
$attributes['deleted'] = false;
|
||||
|
||||
if (! $post) {
|
||||
$attributes['displayname'] = $this->translator->trans('flarum-mentions.forum.post_mention.deleted_text');
|
||||
$attributes['deleted'] = true;
|
||||
}
|
||||
|
||||
if ($post && ! $post->user) {
|
||||
$attributes['displayname'] = $this->translator->trans('core.lib.username.deleted_text');
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
});
|
||||
}
|
||||
|
@@ -9,29 +9,54 @@
|
||||
|
||||
namespace Flarum\Mentions\Formatter;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Flarum\Http\SlugManager;
|
||||
use Flarum\User\User;
|
||||
use s9e\TextFormatter\Renderer;
|
||||
use s9e\TextFormatter\Utils;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class FormatUserMentions
|
||||
{
|
||||
/**
|
||||
* @var SlugManager
|
||||
*/
|
||||
private $slugManager;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
public function __construct(SlugManager $slugManager, TranslatorInterface $translator)
|
||||
{
|
||||
$this->slugManager = $slugManager;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure rendering for user mentions.
|
||||
*
|
||||
* @param s9e\TextFormatter\Renderer $renderer
|
||||
* @param mixed $context
|
||||
* @param string|null $xml
|
||||
* @param Psr\Http\Message\ServerRequestInterface $request
|
||||
* @return string $xml to be rendered
|
||||
*/
|
||||
public function __invoke(Renderer $renderer, $context, $xml, Request $request = null)
|
||||
public function __invoke(Renderer $renderer, $context, string $xml)
|
||||
{
|
||||
$post = $context;
|
||||
|
||||
return Utils::replaceAttributes($xml, 'USERMENTION', function ($attributes) use ($post) {
|
||||
$user = $post->mentionsUsers->find($attributes['id']);
|
||||
|
||||
$attributes['deleted'] = false;
|
||||
|
||||
if ($user) {
|
||||
$attributes['username'] = $user->username;
|
||||
$attributes['slug'] = $this->slugManager->forResource(User::class)->toSlug($user);
|
||||
$attributes['displayname'] = $user->display_name;
|
||||
} else {
|
||||
$attributes['deleted'] = true;
|
||||
$attributes['slug'] = '';
|
||||
$attributes['displayname'] = $this->translator->trans('core.lib.username.deleted_text');
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
|
95
extensions/mentions/src/Formatter/UnparsePostMentions.php
Normal file
95
extensions/mentions/src/Formatter/UnparsePostMentions.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?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 s9e\TextFormatter\Utils;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class UnparsePostMentions
|
||||
{
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure rendering for user mentions.
|
||||
*
|
||||
* @param string $xml
|
||||
* @param mixed $context
|
||||
* @return string $xml to be unparsed
|
||||
*/
|
||||
public function __invoke($context, string $xml)
|
||||
{
|
||||
$xml = $this->updatePostMentionTags($context, $xml);
|
||||
$xml = $this->unparsePostMentionTags($xml);
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates XML post mention tags before unparsing so that unparsing uses new display names.
|
||||
*
|
||||
* @param mixed $context
|
||||
* @param string $xml : Parsed text.
|
||||
* @return string $xml : Updated XML tags;
|
||||
*/
|
||||
protected function updatePostMentionTags($context, string $xml): string
|
||||
{
|
||||
$post = $context;
|
||||
|
||||
return Utils::replaceAttributes($xml, 'POSTMENTION', function ($attributes) use ($post) {
|
||||
$post = $post->mentionsPosts->find($attributes['id']);
|
||||
if ($post && $post->user) {
|
||||
$attributes['displayname'] = $post->user->display_name;
|
||||
}
|
||||
|
||||
if (! $post) {
|
||||
$attributes['displayname'] = $this->translator->trans('flarum-mentions.forum.post_mention.deleted_text');
|
||||
}
|
||||
|
||||
if ($post && ! $post->user) {
|
||||
$attributes['displayname'] = $this->translator->trans('core.lib.username.deleted_text');
|
||||
}
|
||||
|
||||
if (strpos($attributes['displayname'], '"#') !== false) {
|
||||
$attributes['displayname'] = preg_replace('/"#[a-z]{0,3}[0-9]+/', '_', $attributes['displayname']);
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms post mention tags from XML to raw unparsed content with updated format and display name.
|
||||
*
|
||||
* @param string $xml : Parsed text.
|
||||
* @return string : Unparsed text.
|
||||
*/
|
||||
protected function unparsePostMentionTags(string $xml): string
|
||||
{
|
||||
$tagName = 'POSTMENTION';
|
||||
|
||||
if (strpos($xml, $tagName) === false) {
|
||||
return $xml;
|
||||
}
|
||||
|
||||
return preg_replace(
|
||||
'/<'.preg_quote($tagName).'\b[^>]*(?=\bdisplayname="(.*)")[^>]*(?=\bid="([0-9]+)")[^>]*>@[^<]+<\/'.preg_quote($tagName).'>/U',
|
||||
'@"$1"#p$2',
|
||||
$xml
|
||||
);
|
||||
}
|
||||
}
|
91
extensions/mentions/src/Formatter/UnparseUserMentions.php
Normal file
91
extensions/mentions/src/Formatter/UnparseUserMentions.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?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\Utils;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class UnparseUserMentions
|
||||
{
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure rendering for user mentions.
|
||||
*
|
||||
* @param string $xml
|
||||
* @param mixed $context
|
||||
* @return string $xml to be unparsed
|
||||
*/
|
||||
public function __invoke($context, string $xml)
|
||||
{
|
||||
$xml = $this->updateUserMentionTags($context, $xml);
|
||||
$xml = $this->unparseUserMentionTags($xml);
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates XML user mention tags before unparsing so that unparsing uses new display names.
|
||||
*
|
||||
* @param mixed $context
|
||||
* @param string $xml : Parsed text.
|
||||
* @return string $xml : Updated XML tags;
|
||||
*/
|
||||
protected function updateUserMentionTags($context, string $xml): string
|
||||
{
|
||||
$post = $context;
|
||||
|
||||
return Utils::replaceAttributes($xml, 'USERMENTION', function ($attributes) use ($post) {
|
||||
$user = $post->mentionsUsers->find($attributes['id']);
|
||||
|
||||
if ($user) {
|
||||
$attributes['displayname'] = $user->display_name;
|
||||
} else {
|
||||
$attributes['displayname'] = $this->translator->trans('core.lib.username.deleted_text');
|
||||
}
|
||||
|
||||
if (strpos($attributes['displayname'], '"#') !== false) {
|
||||
$attributes['displayname'] = preg_replace('/"#[a-z]{0,3}[0-9]+/', '_', $attributes['displayname']);
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms user mention tags from XML to raw unparsed content with updated format and display name.
|
||||
*
|
||||
* @param string $xml : Parsed text.
|
||||
* @return string : Unparsed text.
|
||||
*/
|
||||
protected function unparseUserMentionTags(string $xml): string
|
||||
{
|
||||
$tagName = 'USERMENTION';
|
||||
|
||||
if (strpos($xml, $tagName) === false) {
|
||||
return $xml;
|
||||
}
|
||||
|
||||
return preg_replace(
|
||||
'/<'.preg_quote($tagName).'\b[^>]*(?=\bdisplayname="(.*)")[^>]*(?=\bid="([0-9]+)")[^>]*>@[^<]+<\/'.preg_quote($tagName).'>/U',
|
||||
'@"$1"#$2',
|
||||
$xml
|
||||
);
|
||||
}
|
||||
}
|
@@ -36,6 +36,7 @@ class PostMentionsTest extends TestCase
|
||||
'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],
|
||||
@@ -43,10 +44,17 @@ class PostMentionsTest extends TestCase
|
||||
'posts' => [
|
||||
['id' => 4, 'number' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="TobyFlarum___" id="5" number="2" discussionid="2" username="toby">@tobyuuu#5</POSTMENTION></r>'],
|
||||
['id' => 5, 'number' => 3, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 4, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="potato" id="4" number="3" discussionid="2" username="potato">@potato#4</POSTMENTION></r>'],
|
||||
['id' => 6, 'number' => 4, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="i_am_a_deleted_user" id="7" number="5" discussionid="2" username="i_am_a_deleted_user">@"i_am_a_deleted_user"#p7</POSTMENTION></r>'],
|
||||
['id' => 7, 'number' => 5, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 2021, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="POTATO$" id="2010" number="7" discussionid="2">@"POTATO$"#2010</POSTMENTION></r>'],
|
||||
['id' => 8, 'number' => 6, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 4, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="i_am_a_deleted_user" id="2020" number="8" discussionid="2" username="i_am_a_deleted_user">@"i_am_a_deleted_user"#p2020</POSTMENTION></r>'],
|
||||
['id' => 9, 'number' => 10, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 5, 'type' => 'comment', 'content' => '<r><p>I am bad</p></r>'],
|
||||
['id' => 10, 'number' => 11, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 4, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="Bad "#p6 User" id="9" number="10" discussionid="2">@"Bad "#p6 User"#p9</POSTMENTION></r>'],
|
||||
],
|
||||
'post_mentions_post' => [
|
||||
['post_id' => 4, 'mentions_post_id' => 5],
|
||||
['post_id' => 5, 'mentions_post_id' => 4]
|
||||
['post_id' => 5, 'mentions_post_id' => 4],
|
||||
['post_id' => 6, 'mentions_post_id' => 7],
|
||||
['post_id' => 10, 'mentions_post_id' => 9]
|
||||
],
|
||||
'settings' => [
|
||||
['key' => 'display_name_driver', 'value' => 'custom_display_name_driver'],
|
||||
@@ -73,7 +81,7 @@ class PostMentionsTest extends TestCase
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function mentioning_a_valid_post_works()
|
||||
public function mentioning_a_valid_post_with_old_format_doesnt_work()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
@@ -98,8 +106,42 @@ class PostMentionsTest extends TestCase
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringNotContainsString('POTATO$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertEquals('@potato#4', $response['data']['attributes']['content']);
|
||||
$this->assertStringNotContainsString('PostMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNull(CommentPost::find($response['data']['id'])->mentionsPosts->find(4));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function mentioning_a_valid_post_with_new_format_works()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"POTATO$"#p4',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('POTATO$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@potato#4', $response['data']['attributes']['content']);
|
||||
$this->assertEquals('@"POTATO$"#p4', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('PostMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsPosts->find(4));
|
||||
}
|
||||
@@ -118,7 +160,7 @@ class PostMentionsTest extends TestCase
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@franzofflarum#215',
|
||||
'content' => '@"franzofflarum"#p215',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
@@ -133,7 +175,7 @@ class PostMentionsTest extends TestCase
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringNotContainsString('FRANZOFFLARUM$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@franzofflarum#215', $response['data']['attributes']['content']);
|
||||
$this->assertEquals('@"franzofflarum"#p215', $response['data']['attributes']['content']);
|
||||
$this->assertStringNotContainsString('PostMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(0, CommentPost::find($response['data']['id'])->mentionsPosts);
|
||||
}
|
||||
@@ -152,7 +194,7 @@ class PostMentionsTest extends TestCase
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@toby#5 @flarum @franzofflarum#220 @potato @potato#4',
|
||||
'content' => '@"TOBY$"#p5 @"flarum"#2015 @"franzofflarum"#220 @"POTATO$"#3 @"POTATO$"#p4',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
@@ -169,7 +211,7 @@ class PostMentionsTest extends TestCase
|
||||
$this->assertStringContainsString('TOBY$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('FRANZOFFLARUM$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('POTATO$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertEquals('@toby#5 @flarum @franzofflarum#220 @potato @potato#4', $response['data']['attributes']['content']);
|
||||
$this->assertEquals('@"TOBY$"#p5 @"flarum"#2015 @"franzofflarum"#220 @"POTATO$"#3 @"POTATO$"#p4', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('PostMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(2, CommentPost::find($response['data']['id'])->mentionsPosts);
|
||||
}
|
||||
@@ -196,12 +238,211 @@ class PostMentionsTest extends TestCase
|
||||
$this->assertStringContainsString('PostMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(1, CommentPost::find($response['data']['id'])->mentionsPosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function post_mentions_unparse_with_fresh_data()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/posts/4', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('@"TOBY$"#p5', $response['data']['attributes']['content']);
|
||||
$this->assertCount(1, CommentPost::find($response['data']['id'])->mentionsPosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function deleted_post_mentions_s_user_unparse_and_render_without_user_data()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
$deleted_text = $this->app()->getContainer()->make('translator')->trans('core.lib.username.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('@"'.$deleted_text.'"#p7', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('PostMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('i_am_a_deleted_user', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('i_am_a_deleted_user', $response['data']['attributes']['content']);
|
||||
$this->assertCount(1, CommentPost::find($response['data']['id'])->mentionsPosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function deleted_post_mentions_unparse_and_render_without_user_data()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
$deleted_text = $this->app()->getContainer()->make('translator')->trans('flarum-mentions.forum.post_mention.deleted_text');
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/posts/7', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString($deleted_text, $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@"'.$deleted_text.'"#p2010', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('PostMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('POTATO$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('POTATO$', $response['data']['attributes']['content']);
|
||||
$this->assertCount(0, CommentPost::find($response['data']['id'])->mentionsPosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function deleted_post_mentions_and_deleted_user_unparse_and_render_without_user_data()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
$deleted_text = $this->app()->getContainer()->make('translator')->trans('flarum-mentions.forum.post_mention.deleted_text');
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/posts/8', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString($deleted_text, $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@"'.$deleted_text.'"#p2020', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('PostMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('POTATO$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('POTATO$', $response['data']['attributes']['content']);
|
||||
$this->assertCount(0, CommentPost::find($response['data']['id'])->mentionsPosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function post_mentions_with_unremoved_bad_string_from_display_names_doesnt_work()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"Bad "#p6 User"#p9',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('POTATO$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertEquals('@"POTATO$"#p6 User"#p9', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('PostMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsPosts->find(6));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function post_mentions_unparsing_removes_bad_display_name_string()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/posts/10', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('Bad "#p6 User', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@"Bad _ User"#p9', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('PostMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsPosts->find(9));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function post_mentions_with_removed_bad_string_from_display_names_works()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"Bad _ User"#p9',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('Bad "#p6 User', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertEquals('@"Bad _ User"#p9', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('PostMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsPosts->find(9));
|
||||
}
|
||||
}
|
||||
|
||||
class CustomOtherDisplayNameDriver implements DriverInterface
|
||||
{
|
||||
public function displayName(User $user): string
|
||||
{
|
||||
if ($user->username === 'bad_user') {
|
||||
return 'Bad "#p6 User';
|
||||
}
|
||||
|
||||
return strtoupper($user->username).'$';
|
||||
}
|
||||
}
|
||||
|
@@ -37,15 +37,19 @@ class UserMentionsTest extends TestCase
|
||||
$this->normalUser(),
|
||||
['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><USERMENTION displayname="TobyFlarum___" id="4" username="toby">@tobyuuu</USERMENTION></r>'],
|
||||
['id' => 6, 'number' => 3, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 4, 'type' => 'comment', 'content' => '<r><USERMENTION displayname="i_am_a_deleted_user" id="2021" username="i_am_a_deleted_user">@"i_am_a_deleted_user"#2021</USERMENTION></r>'],
|
||||
['id' => 10, 'number' => 11, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 5, 'type' => 'comment', 'content' => '<r><USERMENTION displayname="Bad "#p6 User" id="5">@"Bad "#p6 User"#5</USERMENTION></r>'],
|
||||
],
|
||||
'post_mentions_user' => [
|
||||
['post_id' => 4, 'mentions_user_id' => 4]
|
||||
['post_id' => 4, 'mentions_user_id' => 4],
|
||||
['post_id' => 10, 'mentions_user_id' => 5]
|
||||
],
|
||||
'settings' => [
|
||||
['key' => 'display_name_driver', 'value' => 'custom_display_name_driver'],
|
||||
@@ -72,14 +76,60 @@ class UserMentionsTest extends TestCase
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function mentioning_a_valid_user_works()
|
||||
public function mentioning_a_valid_user_with_old_format_doesnt_work_if_off()
|
||||
{
|
||||
$this->prepareDatabase([
|
||||
'settings' => [
|
||||
['key' => 'flarum-mentions.allow_username_format', 'value' => '0']
|
||||
]
|
||||
]);
|
||||
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 2,
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@potato',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringNotContainsString('@POTATO$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@potato', $response['data']['attributes']['content']);
|
||||
$this->assertStringNotContainsString('UserMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(0, CommentPost::find($response['data']['id'])->mentionsUsers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function mentioning_a_valid_user_with_old_format_works_if_on()
|
||||
{
|
||||
$this->prepareDatabase([
|
||||
'settings' => [
|
||||
['key' => 'flarum-mentions.allow_username_format', 'value' => '1']
|
||||
]
|
||||
]);
|
||||
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
@@ -98,7 +148,41 @@ class UserMentionsTest extends TestCase
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('@POTATO$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@potato', $response['data']['attributes']['content']);
|
||||
$this->assertEquals('@"POTATO$"#3', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('UserMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsUsers->find(3));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function mentioning_a_valid_user_with_new_format_works()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"POTATO$"#3',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('@POTATO$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@"POTATO$"#3', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('UserMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsUsers->find(3));
|
||||
}
|
||||
@@ -113,11 +197,11 @@ class UserMentionsTest extends TestCase
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 2,
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@franzofflarum',
|
||||
'content' => '@"franzofflarum"#82',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
@@ -132,7 +216,7 @@ class UserMentionsTest extends TestCase
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringNotContainsString('@FRANZOFFLARUM$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@franzofflarum', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('@"franzofflarum"#82', $response['data']['attributes']['content']);
|
||||
$this->assertStringNotContainsString('UserMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(0, CommentPost::find($response['data']['id'])->mentionsUsers);
|
||||
}
|
||||
@@ -147,11 +231,11 @@ class UserMentionsTest extends TestCase
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 2,
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@toby @potato#4 @franzofflarum @potato',
|
||||
'content' => '@"TOBY$"#4 @"POTATO$"#p4 @"franzofflarum"#82 @"POTATO$"#3',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
@@ -168,7 +252,7 @@ class UserMentionsTest extends TestCase
|
||||
$this->assertStringContainsString('@TOBY$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('@FRANZOFFLARUM$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@POTATO$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertEquals('@toby @potato#4 @franzofflarum @potato', $response['data']['attributes']['content']);
|
||||
$this->assertEquals('@"TOBY$"#4 @"POTATO$"#p4 @"franzofflarum"#82 @"POTATO$"#3', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('UserMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(2, CommentPost::find($response['data']['id'])->mentionsUsers);
|
||||
}
|
||||
@@ -176,7 +260,7 @@ class UserMentionsTest extends TestCase
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function user_mentions_render_with_fresh_data()
|
||||
public function old_user_mentions_still_render()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
@@ -195,12 +279,201 @@ class UserMentionsTest extends TestCase
|
||||
$this->assertStringContainsString('UserMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertCount(1, CommentPost::find($response['data']['id'])->mentionsUsers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function user_mentions_render_with_fresh_data()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"potato_"#3',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('@POTATO$', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('UserMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsUsers->find(3));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function user_mentions_unparse_with_fresh_data()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"potato_"#3',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('@"POTATO$"#3', $response['data']['attributes']['content']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsUsers->find(3));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function deleted_user_mentions_unparse_and_render_without_user_data()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
$deleted_text = $this->app()->getContainer()->make('translator')->trans('core.lib.username.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('@"'.$deleted_text.'"#2021', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('UserMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('UserMention--deleted', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('i_am_a_deleted_user', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringNotContainsString('i_am_a_deleted_user', $response['data']['attributes']['content']);
|
||||
$this->assertCount(0, CommentPost::find($response['data']['id'])->mentionsUsers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function user_mentions_with_unremoved_bad_string_from_display_names_doesnt_work()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"Bad "#p6 User"#5',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringNotContainsString('Bad "#p6 User', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotEquals('@"Bad "#p6 User"#5', $response['data']['attributes']['content']);
|
||||
$this->assertStringNotContainsString('UserMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNull(CommentPost::find($response['data']['id'])->mentionsUsers->find(5));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function user_mentions_unparsing_removes_bad_display_name_string()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/posts/10', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('Bad "#p6 User', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertStringContainsString('@"Bad _ User"#5', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('UserMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsUsers->find(5));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function user_mentions_with_removed_bad_string_from_display_names_works()
|
||||
{
|
||||
$this->app();
|
||||
$this->recalculateDisplayNameDriver();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/posts', [
|
||||
'authenticatedAs' => 1,
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => '@"Bad _ User"#5',
|
||||
],
|
||||
'relationships' => [
|
||||
'discussion' => ['data' => ['id' => 2]],
|
||||
],
|
||||
],
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertStringContainsString('Bad "#p6 User', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertEquals('@"Bad _ User"#5', $response['data']['attributes']['content']);
|
||||
$this->assertStringContainsString('UserMention', $response['data']['attributes']['contentHtml']);
|
||||
$this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsUsers->find(5));
|
||||
}
|
||||
}
|
||||
|
||||
class CustomDisplayNameDriver implements DriverInterface
|
||||
{
|
||||
public function displayName(User $user): string
|
||||
{
|
||||
if ($user->username === 'bad_user') {
|
||||
return 'Bad "#p6 User';
|
||||
}
|
||||
|
||||
return strtoupper($user->username).'$';
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user