diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index d10eed697a..bacfe27bb1 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -1805,132 +1805,7 @@ class BBCode $text = '' . $text . ''; } - $text = preg_replace_callback("/\[(url)\](.*?)\[\/url\]/ism", [self::class, 'sanitizeLinksCallback'], $text); - $text = preg_replace_callback("/\[(url)\=(.*?)\](.*?)\[\/url\]/ism", [self::class, 'sanitizeLinksCallback'], $text); - - // Handle mentions and hashtag links - if ($simple_html == self::DIASPORA) { - // The ! is converted to @ since Diaspora only understands the @ - $text = preg_replace( - "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", - '@$3', - $text - ); - } elseif (in_array($simple_html, [self::OSTATUS, self::ACTIVITYPUB])) { - $text = preg_replace( - "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", - '$1$3', - $text - ); - $text = preg_replace( - "/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism", - '', - $text - ); - } elseif (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::TWITTER_API])) { - $text = preg_replace( - "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", - '$1$3', - $text - ); - } elseif ($simple_html == self::MASTODON_API) { - $text = preg_replace( - "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", - '$1$3', - $text - ); - $text = preg_replace( - "/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism", - '', - $text - ); - } else { - $text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text); - } - - // Bookmarks in red - will be converted to bookmarks in friendica - $text = preg_replace("/#\^\[url\](.*?)\[\/url\]/ism", '[bookmark=$1]$1[/bookmark]', $text); - $text = preg_replace("/#\^\[url\=(.*?)\](.*?)\[\/url\]/ism", '[bookmark=$1]$2[/bookmark]', $text); - $text = preg_replace( - "/#\[url\=.*?\]\^\[\/url\]\[url\=(.*?)\](.*?)\[\/url\]/i", - "[bookmark=$1]$2[/bookmark]", - $text - ); - - if (in_array($simple_html, [self::OSTATUS, self::TWITTER, self::BLUESKY])) { - $text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", [self::class, 'expandLinksCallback'], $text); - //$text = preg_replace("/[^#@!]\[url\=([^\]]*)\](.*?)\[\/url\]/ism", ' $2 [url]$1[/url]', $text); - $text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]', $text); - } - - // Perform URL Search - if (!$for_plaintext && $try_oembed) { - $text = preg_replace_callback("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $try_oembed_callback, $text); - } - - $text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url=$1]$2[/url]', $text); - - // Handle Diaspora posts - $text = preg_replace_callback( - "&\[url=/?posts/([^\[\]]*)\](.*)\[\/url\]&Usi", - function ($match) { - return "[url=" . DI::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]"; - }, - $text - ); - - $text = preg_replace_callback( - "&\[url=/people\?q\=(.*)\](.*)\[\/url\]&Usi", - function ($match) { - return "[url=" . DI::baseUrl() . "/search?search=%40" . $match[1] . "]" . $match[2] . "[/url]"; - }, - $text - ); - - // Server independent link to posts and comments - // See issue: https://github.com/diaspora/diaspora_federation/issues/75 - $expression = "=diaspora://.*?/post/([0-9A-Za-z\-_@.:]{15,254}[0-9A-Za-z])=ism"; - $text = preg_replace($expression, DI::baseUrl() . "/display/$1", $text); - - // Red compatibility, though the link can't be authenticated on Friendica - $text = preg_replace("/\[zrl\=(.*?)\](.*?)\[\/zrl\]/ism", '[url=$1]$2[/url]', $text); - - if ($for_plaintext) { - $text = preg_replace("(\[url\](.*?)\[\/url\])ism", " $1 ", $text); - $text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", [self::class, 'removePictureLinksCallback'], $text); - } - - /* Tag conversion - * Supports: - * - #[url=][/url] - * - [url=]#[/url] - */ - self::performWithEscapedTags($text, ['url', 'share'], function ($text) use ($simple_html) { - $text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function ($matches) use ($simple_html) { - if ($simple_html == self::ACTIVITYPUB) { - return '#' - . XML::escape($matches[1]) . ''; - } else { - return '#'; - } - }, $text); - return $text; - }); - - if (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::DIASPORA, self::OSTATUS, self::MASTODON_API, self::TWITTER_API, self::ACTIVITYPUB])) { - $text = self::shortenLinkDescription($text); - } else { - $text = self::unifyLinks($text); - } - - // We need no target="_blank" rel="noopener noreferrer" for local links - // convert links start with DI::baseUrl() as local link without the target="_blank" rel="noopener noreferrer" attribute - $text = preg_replace("/\[url\=(" . preg_quote(DI::baseUrl(), '/') . ".*?)\](.*?)\[\/url\]/ism", '$2', $text); - - $text = preg_replace("/\[url\=(.*?)\](.*?)\[\/url\]/ism", '$2', $text); + $text = self::convertUrlToHtml($text, $simple_html, $for_plaintext, $try_oembed, $try_oembed_callback); // we may need to restrict this further if it picks up too many strays // link acct:user@host to a webfinger profile redirector @@ -2043,6 +1918,137 @@ class BBCode return trim($text); } + private static function convertUrlToHtml(string $text, int $simple_html, bool $for_plaintext, bool $try_oembed, \Closure $try_oembed_callback): string + { + $text = preg_replace_callback("/\[(url)\](.*?)\[\/url\]/ism", [self::class, 'sanitizeLinksCallback'], $text); + $text = preg_replace_callback("/\[(url)\=(.*?)\](.*?)\[\/url\]/ism", [self::class, 'sanitizeLinksCallback'], $text); + + // Handle mentions and hashtag links + if ($simple_html == self::DIASPORA) { + // The ! is converted to @ since Diaspora only understands the @ + $text = preg_replace( + "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", + '@$3', + $text + ); + } elseif (in_array($simple_html, [self::OSTATUS, self::ACTIVITYPUB])) { + $text = preg_replace( + "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", + '$1$3', + $text + ); + $text = preg_replace( + "/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism", + '', + $text + ); + } elseif (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::TWITTER_API])) { + $text = preg_replace( + "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", + '$1$3', + $text + ); + } elseif ($simple_html == self::MASTODON_API) { + $text = preg_replace( + "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", + '$1$3', + $text + ); + $text = preg_replace( + "/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism", + '', + $text + ); + } else { + $text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text); + } + + if ($for_plaintext) { + $text = preg_replace("(\[url\](.*?)\[\/url\])ism", " $1 ", $text); + $text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", [self::class, 'removePictureLinksCallback'], $text); + } + + // Bookmarks in red - will be converted to bookmarks in friendica + $text = preg_replace("/#\^\[url\](.*?)\[\/url\]/ism", '[bookmark=$1]$1[/bookmark]', $text); + $text = preg_replace("/#\^\[url\=(.*?)\](.*?)\[\/url\]/ism", '[bookmark=$1]$2[/bookmark]', $text); + $text = preg_replace( + "/#\[url\=.*?\]\^\[\/url\]\[url\=(.*?)\](.*?)\[\/url\]/i", + "[bookmark=$1]$2[/bookmark]", + $text + ); + + if (in_array($simple_html, [self::OSTATUS, self::TWITTER, self::BLUESKY])) { + $text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", [self::class, 'expandLinksCallback'], $text); + //$text = preg_replace("/[^#@!]\[url\=([^\]]*)\](.*?)\[\/url\]/ism", ' $2 [url]$1[/url]', $text); + $text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]', $text); + } + + // Perform URL Search + if ($try_oembed) { + $text = preg_replace_callback("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $try_oembed_callback, $text); + } + + $text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url=$1]$2[/url]', $text); + + // Handle Diaspora posts + $text = preg_replace_callback( + "&\[url=/?posts/([^\[\]]*)\](.*)\[\/url\]&Usi", + function ($match) { + return "[url=" . DI::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]"; + }, + $text + ); + + $text = preg_replace_callback( + "&\[url=/people\?q\=(.*)\](.*)\[\/url\]&Usi", + function ($match) { + return "[url=" . DI::baseUrl() . "/search?search=%40" . $match[1] . "]" . $match[2] . "[/url]"; + }, + $text + ); + + // Server independent link to posts and comments + // See issue: https://github.com/diaspora/diaspora_federation/issues/75 + $expression = "=diaspora://.*?/post/([0-9A-Za-z\-_@.:]{15,254}[0-9A-Za-z])=ism"; + $text = preg_replace($expression, DI::baseUrl() . "/display/$1", $text); + + /* Tag conversion + * Supports: + * - #[url=][/url] + * - [url=]#[/url] + */ + self::performWithEscapedTags($text, ['url', 'share'], function ($text) use ($simple_html) { + $text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function ($matches) use ($simple_html) { + if ($simple_html == self::ACTIVITYPUB) { + return '#' + . XML::escape($matches[1]) . ''; + } else { + return '#'; + } + }, $text); + return $text; + }); + + // Red compatibility, though the link can't be authenticated on Friendica + $text = preg_replace("/\[zrl\=(.*?)\](.*?)\[\/zrl\]/ism", '[url=$1]$2[/url]', $text); + + if (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::DIASPORA, self::OSTATUS, self::MASTODON_API, self::TWITTER_API, self::ACTIVITYPUB])) { + $text = self::shortenLinkDescription($text); + } else { + $text = self::unifyLinks($text); + } + + // We need no target="_blank" rel="noopener noreferrer" for local links + // convert links start with DI::baseUrl() as local link without the target="_blank" rel="noopener noreferrer" attribute + $text = preg_replace("/\[url\=(" . preg_quote(DI::baseUrl(), '/') . ".*?)\](.*?)\[\/url\]/ism", '$2', $text); + + $text = preg_replace("/\[url\=(.*?)\](.*?)\[\/url\]/ism", '$2', $text); + return $text; + } + private static function escapeUrl(string $url): string { return str_replace(['[', ']'], ['[', ']'], $url);