', $text);
}
return $text;
}
private static function convertEmojisToHtml(string $text, int $simple_html): string
{
// Mastodon Emoji (internal tag, do not document for users)
if ($simple_html == self::MASTODON_API) {
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '$2', $text);
} else {
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '', $text);
}
return $text;
}
private static function convertStylesToHtml(string $text, int $simple_html): string
{
// Markdown is designed to pass through HTML elements that it can't handle itself,
// so that the other system would parse the original HTML element.
// But Diaspora has chosen not to do this and doesn't parse HTML elements.
// So we need to make some changes here.
if ($simple_html == BBCode::DIASPORA) {
$elements = ['big', 'small'];
foreach ($elements as $bbcode) {
$text = preg_replace("(\[" . $bbcode . "\](.*?)\[\/" . $bbcode . "\])ism", '$1', $text);
}
$elements = [
'del' => 's', 'ins' => 'em', 'kbd' => 'code', 'mark' => 'strong',
'samp' => 'code', 'u' => 'em', 'var' => 'em'
];
foreach ($elements as $bbcode => $html) {
$text = preg_replace("(\[" . $bbcode . "\](.*?)\[\/" . $bbcode . "\])ism", '<' . $html . '>$1' . $html . '>', $text);
}
}
// Several easy to replace HTML elements
// @todo add the new elements to the documentation by the end of 2024 so that most systems will support them.
$elements = [
'b', 'del', 'em', 'i', 'ins', 'kbd', 'mark',
's', 'samp', 'small', 'strong', 'sub', 'sup', 'u', 'var'
];
foreach ($elements as $element) {
$text = preg_replace("(\[" . $element . "\](.*?)\[\/" . $element . "\])ism", '<' . $element . '>$1' . $element . '>', $text);
}
$text = preg_replace("(\[big\](.*?)\[\/big\])ism", "$1", $text);
// Check for over-line text
$text = preg_replace("(\[o\](.*?)\[\/o\])ism", '$1', $text);
// Check for colored text
$text = preg_replace("(\[color=(.*?)\](.*?)\[\/color\])ism", "$2", $text);
// Check for sized text
// [size=50] --> font-size: 50px (with the unit).
if ($simple_html != self::DIASPORA) {
$text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism", '$2', $text);
$text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", '$2', $text);
} else {
// Issue 2199: Diaspora doesn't interpret the construct above, nor the or element
$text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text);
}
// Check for centered text
$text = preg_replace("(\[center\](.*?)\[\/center\])ism", '
", $text);
return $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::ACTIVITYPUB])) {
$text = preg_replace(
"/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
'$1$3',
$text
);
$text = preg_replace(
"/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism",
'$1$3',
$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",
'$1$3',
$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::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 '#'
. XML::escape($matches[1]) . '';
}
}, $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::MASTODON_API, self::TWITTER_API, self::ACTIVITYPUB])) {
$text = self::shortenLinkDescription($text, $simple_html);
} 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);
// we may need to restrict this further if it picks up too many strays
// link acct:user@host to a webfinger profile redirector
return preg_replace('/acct:([^@]+)@((?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63})/', 'acct:$1@$2', $text);
}
private static function escapeUrl(string $url): string
{
return self::escapeContent(self::idnUrl($url));
}
private static function escapeContent(string $url): string
{
return str_replace(['[', ']'], ['[', ']'], $url);
}
private static function idnUrl(string $url): string
{
$parts = parse_url($url);
if (empty($parts['host'])) {
return $url;
}
$parts['host'] = idn_to_ascii(urldecode($parts['host']));
try {
return (string)Uri::fromParts($parts);
} catch (\Throwable $th) {
Logger::notice('Exception on unparsing url', ['url' => $url, 'parts' => $parts, 'code' => $th->getCode(), 'message' => $th->getMessage()]);
return $url;
}
}
private static function unifyLinks(string $text): string
{
return preg_replace_callback(
"/\[url\](.*?)\[\/url\]/ism",
function ($match) {
return "[url=" . self::escapeUrl($match[1]) . "]" . $match[1] . "[/url]";
},
$text
);
}
private static function shortenLinkDescription(string $text, int $simple_html): string
{
if ($simple_html == self::INTERNAL) {
$max_length = DI::config()->get('system', 'display_link_length');
} else {
$max_length = 30;
}
$text = preg_replace_callback(
"/\[url\](.*?)\[\/url\]/ism",
function ($match) use ($max_length) {
return "[url=" . self::escapeUrl($match[1]) . "]" . Strings::getStyledURL($match[1], $max_length) . "[/url]";
},
$text
);
$text = preg_replace_callback(
"/\[url\=(.*?)\](.*?)\[\/url\]/ism",
function ($match) use ($max_length) {
if ($match[1] == $match[2]) {
return "[url=" . self::escapeUrl($match[1]) . "]" . Strings::getStyledURL($match[2], $max_length) . "[/url]";
} else {
return "[url=" . self::escapeUrl($match[1]) . "]" . $match[2] . "[/url]";
}
},
$text
);
return $text;
}
private static function convertMailToHtml(string $text): string
{
$text = preg_replace_callback("/\[(mail)\](.*?)\[\/mail\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
$text = preg_replace("/\[mail\](.*?)\[\/mail\]/", '$1', $text);
$text = preg_replace("/\[mail\=(.*?)\](.*?)\[\/mail\]/", '$2', $text);
return $text;
}
private static function convertSharesToHtml(string $text, int $simple_html, bool $try_oembed, int $uriid): string
{
// Shared content
// when the content is meant exporting to other systems then remove the avatar picture since this doesn't really look good on these systems
if (!$try_oembed) {
$text = preg_replace("/\[share(.*?)avatar\s?=\s?'.*?'\s?(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1$2]$3[/share]", $text);
}
$text = self::convertShare(
$text,
function (array $attributes, array $author_contact, $content, $is_quote_share) use ($simple_html) {
return self::convertShareCallback($attributes, $author_contact, $content, $is_quote_share, $simple_html);
},
$uriid
);
return $text;
}
private static function cleanupHtml(string $text): string
{
/// @todo What is the meaning of these lines?
$text = preg_replace('/\[\&\;([#a-z0-9]+)\;\]/', '&$1;', $text);
$text = preg_replace('/\&\#039\;/', '\'', $text);
// Currently deactivated, it made problems with " inside of alt texts.
//$text = preg_replace('/\"\;/', '"', $text);
// fix any escaped ampersands that may have been converted into links
$text = preg_replace('/\<([^>]*?)(src|href)=(.*?)\&\;(.*?)\>/ism', '<$1$2=$3&$4>', $text);
// sanitizes src attributes (http and redir URLs for displaying in a web page, cid used for inline images in emails)
$allowed_src_protocols = ['//', 'http://', 'https://', 'contact/redir/', 'cid:'];
array_walk($allowed_src_protocols, function (&$value) {
$value = preg_quote($value, '#');
});
$text = preg_replace(
'#<([^>]*?)(src)="(?!' . implode('|', $allowed_src_protocols) . ')(.*?)"(.*?)>#ism',
'<$1$2=""$4 data-original-src="$3" class="invalid-src" title="' . DI::l10n()->t('Invalid source protocol') . '">',
$text
);
// sanitize href attributes (only allowlisted protocols URLs)
// default value for backward compatibility
$allowed_link_protocols = DI::config()->get('system', 'allowed_link_protocols', []);
// Always allowed protocol even if config isn't set or not including it
$allowed_link_protocols[] = '//';
$allowed_link_protocols[] = 'http://';
$allowed_link_protocols[] = 'https://';
$allowed_link_protocols[] = 'contact/redir/';
array_walk($allowed_link_protocols, function (&$value) {
$value = preg_quote($value, '#');
});
$regex = '#<([^>]*?)(href)="(?!' . implode('|', $allowed_link_protocols) . ')(.*?)"(.*?)>#ism';
$text = preg_replace($regex, '<$1$2="javascript:void(0)"$4 data-original-href="$3" class="invalid-href" title="' . DI::l10n()->t('Invalid link protocol') . '">', $text);
return $text;
}
/**
* Strips the "abstract" tag from the provided text
*
* @param string $text The text with BBCode
* @return string The same text - but without "abstract" element
*/
public static function stripAbstract(string $text): string
{
DI::profiler()->startRecording('rendering');
$text = self::performWithEscapedTags($text, ['code', 'noparse', 'nobb', 'pre'], function ($text) {
$text = preg_replace("/[\s|\n]*\[abstract\].*?\[\/abstract\][\s|\n]*/ism", ' ', $text);
$text = preg_replace("/[\s|\n]*\[abstract=.*?\].*?\[\/abstract][\s|\n]*/ism", ' ', $text);
return $text;
});
DI::profiler()->stopRecording();
return $text;
}
/**
* Returns the value of the "abstract" element
*
* @param string $text The text that maybe contains the element
* @param string $addon The addon for which the abstract is meant for
* @return string The abstract
*/
public static function getAbstract(string $text, string $addon = ''): string
{
DI::profiler()->startRecording('rendering');
$addon = strtolower($addon);
$abstract = self::performWithEscapedTags($text, ['code', 'noparse', 'nobb', 'pre'], function ($text) use ($addon) {
if ($addon && preg_match('#\[abstract=' . preg_quote($addon, '#') . '](.*?)\[/abstract]#ism', $text, $matches)) {
return $matches[1];
}
if (preg_match("#\[abstract](.*?)\[/abstract]#ism", $text, $matches)) {
return $matches[1];
}
return '';
});
DI::profiler()->stopRecording();
return $abstract;
}
/**
* Callback function to replace a Friendica style mention in a mention for Diaspora
*
* @param array $match Matching values for the callback
* [1] = Mention type (! or @)
* [2] = Name
* [3] = Address
* @return string Replaced mention
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
private static function bbCodeMention2DiasporaCallback(array $match): string
{
$contact = Contact::getByURL($match[3], false, ['addr']);
if (empty($contact['addr'])) {
return $match[0];
}
$mention = $match[1] . '{' . $match[2] . '; ' . $contact['addr'] . '}';
return $mention;
}
/**
* Converts a BBCode text into Markdown
*
* This function converts a BBCode item body to be sent to Markdown-enabled
* systems like Diaspora and Libertree
*
* @param string $text
* @param bool $for_diaspora Diaspora requires more changes than Libertree
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function toMarkdown(string $text, bool $for_diaspora = true): string
{
DI::profiler()->startRecording('rendering');
$original_text = $text;
// Since Diaspora is creating a summary for links, this function removes them before posting
if ($for_diaspora) {
$text = self::removeShareInformation($text);
}
/**
* Transform #tags, strip off the [url] and replace spaces with underscore
*/
$url_search_string = "^\[\]";
$text = preg_replace_callback(
"/#\[url\=([$url_search_string]*)\](.*?)\[\/url\]/i",
function ($matches) {
return '#' . str_replace(' ', '_', $matches[2]);
},
$text
);
// Converting images with size parameters to simple images. Markdown doesn't know it.
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $text);
// Convert it to HTML - don't try oembed
if ($for_diaspora) {
$text = self::convertForUriId(0, $text, self::DIASPORA);
// Add all tags that maybe were removed
if (preg_match_all("/#\[url\=([$url_search_string]*)\](.*?)\[\/url\]/ism", $original_text, $tags)) {
$tagline = '';
foreach ($tags[2] as $tag) {
$tag = html_entity_decode($tag, ENT_QUOTES, 'UTF-8');
if (!strpos(html_entity_decode($text, ENT_QUOTES, 'UTF-8'), '#' . $tag)) {
$tagline .= '#' . $tag . ' ';
}
}
$text = $text . ' ' . $tagline;
}
} else {
$text = self::convertForUriId(0, $text, self::CONNECTORS);
}
// If a link is followed by a quote then there should be a newline before it
// Maybe we should make this newline at every time before a quote.
$text = str_replace(['
'], ['
'], $text);
// The converter doesn't convert these elements
$text = str_replace(['
', '
'], ['
', '
'], $text);
// Now convert HTML to Markdown
$text = HTML::toMarkdown($text);
// Libertree has a problem with escaped hashtags.
$text = str_replace(['\#'], ['#'], $text);
// Remove any leading or trailing whitespace, as this will mess up
// the Diaspora signature verification and cause the item to disappear
$text = trim($text);
if ($for_diaspora) {
$url_search_string = "^\[\]";
$text = preg_replace_callback(
"/([@!])\[(.*?)\]\(([$url_search_string]*?)\)/ism",
[self::class, 'bbCodeMention2DiasporaCallback'],
$text
);
}
Hook::callAll('bb2diaspora', $text);
DI::profiler()->stopRecording();
return $text;
}
/**
* Pull out all #hashtags and @person tags from $string.
*
* We also get @person@domain.com - which would make
* the regex quite complicated as tags can also
* end a sentence. So we'll run through our results
* and strip the period from any tags which end with one.
* Returns array of tags found, or empty array.
*
* @param string $string Post content
* @return array List of tag and person names
*/
public static function getTags(string $string): array
{
DI::profiler()->startRecording('rendering');
$ret = [];
self::performWithEscapedTags($string, ['noparse', 'pre', 'code', 'img', 'attachment'], function ($string) use (&$ret) {
// Convert hashtag links to hashtags
$string = preg_replace('/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism', '#$2 ', $string);
// Force line feeds at bbtags
$string = str_replace(['[', ']'], ["\n[", "]\n"], $string);
// ignore anything in a bbtag
$string = preg_replace('/\[(.*?)\]/sm', '', $string);
// Match full names against @tags including the space between first and last
// We will look these up afterward to see if they are full names or not recognisable.
if (preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/', $string, $matches)) {
foreach ($matches[1] as $match) {
if (strstr($match, ']')) {
// we might be inside a bbcode color tag - leave it alone
continue;
}
if (substr($match, -1, 1) === '.') {
$ret[] = substr($match, 0, -1);
} else {
$ret[] = $match;
}
}
}
// Otherwise pull out single word tags. These can be @nickname, @first_last
// and #hash tags.
if (preg_match_all('/([!#@][^\^ \x0D\x0A,;:?\']*[^\^ \x0D\x0A,;:?!\'.])/', $string, $matches)) {
foreach ($matches[1] as $match) {
if (strstr($match, ']')) {
// we might be inside a bbcode color tag - leave it alone
continue;
}
// try not to catch url fragments
if (strpos($string, $match) && preg_match('/[a-zA-z0-9\/]/', substr($string, strpos($string, $match) - 1, 1))) {
continue;
}
$ret[] = $match;
}
}
});
DI::profiler()->stopRecording();
return array_unique($ret);
}
/**
* Expand tags to URLs, checks the tag is at the start of a line or preceded by a non-word character
*
* @param string $body HTML/BBCode
* @return string body with expanded tags
*/
public static function expandTags(string $body): string
{
return preg_replace_callback(
"/(?<=\W|^)([!#@])([^\^ \x0D\x0A,;:?'\"]*[^\^ \x0D\x0A,;:?!'\".])/",
function (array $match) {
switch ($match[1]) {
case '!':
case '@':
$contact = Contact::getByURL($match[2]);
if (!empty($contact)) {
return $match[1] . '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]';
} else {
return $match[1] . $match[2];
}
break;
case '#':
default:
return $match[1] . '[url=' . DI::baseUrl() . '/search?tag=' . urlencode($match[2]) . ']' . $match[2] . '[/url]';
}
},
$body
);
}
/**
* Perform a custom function on a text after having escaped blocks enclosed in the provided tag list.
*
* @param string $text HTML/BBCode
* @param array $tagList A list of tag names, e.g ['noparse', 'nobb', 'pre']
* @param callable $callback
* @return string
* @see Strings::performWithEscapedBlocks
*/
public static function performWithEscapedTags(string $text, array $tagList, callable $callback): string
{
$tagList = array_map('preg_quote', $tagList);
return Strings::performWithEscapedBlocks($text, '#\[(?:' . implode('|', $tagList) . ').*?\[/(?:' . implode('|', $tagList) . ')]#ism', $callback);
}
/**
* Replaces mentions in the provided message body in BBCode links for the provided user and network if any
*
* @param string $body HTML/BBCode
* @param int $profile_uid Profile user id
* @param string $network Network name
* @return string HTML/BBCode with inserted images
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function setMentions(string $body, $profile_uid = 0, $network = '')
{
DI::profiler()->startRecording('rendering');
$body = self::performWithEscapedTags($body, ['noparse', 'pre', 'code', 'img'], function ($body) use ($profile_uid, $network) {
$tags = self::getTags($body);
$tagged = [];
foreach ($tags as $tag) {
$tag_type = substr($tag, 0, 1);
if ($tag_type == Tag::TAG_CHARACTER[Tag::HASHTAG]) {
continue;
}
/*
* If we already tagged 'Robert Johnson', don't try and tag 'Robert'.
* Robert Johnson should be first in the $tags array
*/
foreach ($tagged as $nextTag) {
if (stristr($nextTag, $tag . ' ')) {
continue 2;
}
}
if (($success = Item::replaceTag($body, $profile_uid, $tag, $network)) && $success['replaced']) {
$tagged[] = $tag;
}
}
return $body;
});
DI::profiler()->stopRecording();
return $body;
}
/**
* @param string $author Author display name
* @param string $profile Author profile URL
* @param string $avatar Author profile picture URL
* @param string $link Post source URL
* @param string $posted Post created date
* @param string|null $guid Post guid (if any)
* @param string|null $uri Post uri (if any)
* @return string
* @TODO Rewrite to handle over whole record array
*/
public static function getShareOpeningTag(string $author, string $profile, string $avatar, string $link, string $posted, string $guid = null, string $uri = null): string
{
DI::profiler()->startRecording('rendering');
$header = "[share author='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $author) .
"' profile='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $profile) .
"' avatar='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $avatar) .
"' link='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $link) .
"' posted='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $posted);
if ($guid) {
$header .= "' guid='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $guid);
}
if ($uri) {
$header .= "' message_id='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $uri);
}
$header .= "']";
DI::profiler()->stopRecording();
return $header;
}
/**
* Returns the BBCode relevant to embed the provided URL in a post body.
* For media type, it will return [img], [video] and [audio] tags.
* For regular web pages, it will either output a [bookmark] tag if title and description were provided,
* an [attachment] tag or a simple [url] tag depending on $tryAttachment.
*
* @param string $url
* @param bool $tryAttachment
* @param string|null $title
* @param string|null $description
* @param string|null $tags
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @see ParseUrl::getSiteinfoCached
*/
public static function embedURL(string $url, bool $tryAttachment = true, string $title = null, string $description = null, string $tags = null): string
{
DI::profiler()->startRecording('rendering');
DI::logger()->info($url);
// If there is already some content information submitted we don't
// need to parse the url for content.
if (!empty($title) && !empty($description)) {
$title = str_replace(["\r", "\n"], ['', ''], $title);
$description = '[quote]' . trim($description) . '[/quote]' . "\n";
$str_tags = '';
if (!empty($tags)) {
$arr_tags = ParseUrl::convertTagsToArray($tags);
if (count($arr_tags)) {
$str_tags = "\n" . implode(' ', $arr_tags) . "\n";
}
}
$result = sprintf('[bookmark=%s]%s[/bookmark]%s', $url, ($title) ? $title : $url, $description) . $str_tags;
DI::logger()->info('(unparsed): returns: ' . $result);
DI::profiler()->stopRecording();
return $result;
}
$siteinfo = ParseUrl::getSiteinfoCached($url);
if (in_array($siteinfo['type'], ['image', 'video', 'audio'])) {
switch ($siteinfo['type']) {
case 'video':
$bbcode = "\n" . '[video]' . $url . '[/video]' . "\n";
break;
case 'audio':
$bbcode = "\n" . '[audio]' . $url . '[/audio]' . "\n";
break;
default:
$bbcode = "\n" . '[img=' . $url . '][/img]' . "\n";
break;
}
DI::profiler()->stopRecording();
return $bbcode;
}
unset($siteinfo['keywords']);
// Bypass attachment if parse url for a comment
if (!$tryAttachment) {
DI::profiler()->stopRecording();
return "\n" . '[url=' . $url . ']' . ($siteinfo['title'] ?? $url) . '[/url]';
}
// Format it as BBCode attachment
$bbcode = "\n" . PageInfo::getFooterFromData($siteinfo);
DI::profiler()->stopRecording();
return $bbcode;
}
}