diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php
index 496e735534..790a933bb0 100644
--- a/src/Protocol/ActivityPub/Processor.php
+++ b/src/Protocol/ActivityPub/Processor.php
@@ -52,18 +52,14 @@ use Friendica\Util\Strings;
class Processor
{
/**
- * Converts mentions from Pleroma into the Friendica format
+ * Extracts the tag character (#, @, !) from mention links
*
* @param string $body
- *
- * @return string converted body
+ * @return string
*/
- private static function convertMentions($body)
+ protected static function normalizeMentionLinks(string $body): string
{
- $URLSearchString = "^\[\]";
- $body = preg_replace("/\[url\=([$URLSearchString]*)\]([#@!])(.*?)\[\/url\]/ism", '$2[url=$1]$3[/url]', $body);
-
- return $body;
+ return preg_replace('%\[url=([^\[\]]*)]([#@!])(.*?)\[/url]%ism', '$2[url=$1]$3[/url]', $body);
}
/**
@@ -463,7 +459,7 @@ class Processor
$content = self::replaceEmojis($content, $activity['emojis']);
}
- $content = self::convertMentions($content);
+ $content = self::addMentionLinks($content, $activity['tags']);
if (!empty($activity['source'])) {
$item['body'] = $activity['source'];
@@ -1198,4 +1194,34 @@ class Processor
return implode('', $kept_mentions);
}
+
+ /**
+ * Adds links to string mentions
+ *
+ * @param string $body
+ * @param array $tags
+ * @return string
+ */
+ protected static function addMentionLinks(string $body, array $tags): string
+ {
+ // This prevents links to be added again to Pleroma-style mention links
+ $body = self::normalizeMentionLinks($body);
+
+ foreach ($tags as $tag) {
+ if (empty($tag['name']) || empty($tag['type']) || empty($tag['href']) || !in_array($tag['type'], ['Mention', 'Hashtag'])) {
+ continue;
+ }
+
+ $hash = substr($tag['name'], 0, 1);
+ $name = substr($tag['name'], 1);
+ if (!in_array($hash, Tag::TAG_CHARACTER)) {
+ $hash = '';
+ $name = $tag['name'];
+ }
+
+ $body = str_replace($tag['name'], $hash . '[url=' . $tag['href'] . ']' . $name . '[/url]', $body);
+ }
+
+ return $body;
+ }
}
diff --git a/tests/src/Protocol/ActivityPub/ProcessorMock.php b/tests/src/Protocol/ActivityPub/ProcessorMock.php
new file mode 100644
index 0000000000..8d509bf9de
--- /dev/null
+++ b/tests/src/Protocol/ActivityPub/ProcessorMock.php
@@ -0,0 +1,40 @@
+.
+ *
+ */
+
+namespace Friendica\Test\src\Protocol\ActivityPub;
+
+use Friendica\Protocol\ActivityPub\Processor;
+
+/**
+ * Class ProcessorMock
+ *
+ * Exposes protected methods for test in the inherited class
+ *
+ * @method static string addMentionLinks(string $body, array $tags)
+ * @method static string normalizeMentionLinks(string $body)
+ */
+class ProcessorMock extends Processor
+{
+ public static function __callStatic($name, $arguments)
+ {
+ return self::$name(...$arguments);
+ }
+}
diff --git a/tests/src/Protocol/ActivityPub/ProcessorTest.php b/tests/src/Protocol/ActivityPub/ProcessorTest.php
new file mode 100644
index 0000000000..894e90af28
--- /dev/null
+++ b/tests/src/Protocol/ActivityPub/ProcessorTest.php
@@ -0,0 +1,87 @@
+ [
+ 'expected' => '@[url=https://example.com]Example[/url]',
+ 'body' => '[url=https://example.com]@Example[/url]',
+ ],
+ 'one-link-#' => [
+ 'expected' => '#[url=https://example.com]Example[/url]',
+ 'body' => '[url=https://example.com]#Example[/url]',
+ ],
+ 'one-link-!' => [
+ 'expected' => '![url=https://example.com]Example[/url]',
+ 'body' => '[url=https://example.com]!Example[/url]',
+ ],
+ 'wrong-hash-char' => [
+ 'expected' => '[url=https://example.com]%Example[/url]',
+ 'body' => '[url=https://example.com]%Example[/url]',
+ ],
+ 'multiple-links' => [
+ 'expected' => '@[url=https://example.com]Example[/url] #[url=https://example.com]Example[/url] ![url=https://example.com]Example[/url]',
+ 'body' => '[url=https://example.com]@Example[/url] [url=https://example.com]#Example[/url] [url=https://example.com]!Example[/url]',
+ ],
+ 'already-correct-format' => [
+ 'expected' => '@[url=https://example.com]Example[/url] #[url=https://example.com]Example[/url] ![url=https://example.com]Example[/url]',
+ 'body' => '@[url=https://example.com]Example[/url] #[url=https://example.com]Example[/url] ![url=https://example.com]Example[/url]',
+ ],
+ 'mixed-format' => [
+ 'expected' => '@[url=https://example.com]Example[/url] #[url=https://example.com]Example[/url] ![url=https://example.com]Example[/url] @[url=https://example.com]Example[/url] #[url=https://example.com]Example[/url] ![url=https://example.com]Example[/url]',
+ 'body' => '[url=https://example.com]@Example[/url] [url=https://example.com]#Example[/url] [url=https://example.com]!Example[/url] @[url=https://example.com]Example[/url] #[url=https://example.com]Example[/url] ![url=https://example.com]Example[/url]',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataNormalizeMentionLinks
+ *
+ * @param string $expected
+ * @param string $body
+ */
+ public function testNormalizeMentionLinks(string $expected, string $body)
+ {
+ $this->assertEquals($expected, ProcessorMock::normalizeMentionLinks($body));
+ }
+
+ public function dataAddMentionLinks(): array
+ {
+ return [
+ 'issue-10603' => [
+ 'expected' => '@[url=https://social.wake.st/users/liaizon]liaizon@social.wake.st[/url] @[url=https://friendica.mrpetovan.com/profile/hypolite]hypolite@friendica.mrpetovan.com[/url] yes
',
+ 'body' => '@liaizon@social.wake.st @hypolite@friendica.mrpetovan.com yes
',
+ 'tags' => [
+ [
+ 'type' => 'Mention',
+ 'href' => 'https://social.wake.st/users/liaizon',
+ 'name' => '@liaizon@social.wake.st'
+ ],
+ [
+ 'type' => 'Mention',
+ 'href' => 'https://friendica.mrpetovan.com/profile/hypolite',
+ 'name' => '@hypolite@friendica.mrpetovan.com'
+ ]
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataAddMentionLinks
+ *
+ * @param string $expected
+ * @param string $body
+ * @param array $tags
+ */
+ public function testAddMentionLinks(string $expected, string $body, array $tags)
+ {
+ $this->assertEquals($expected, ProcessorMock::addMentionLinks($body, $tags));
+ }
+}