<?php
/**
- * @copyright Copyright (C) 2010-2022, the Friendica project
+ * @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
// Update this value to the current date whenever changes are made to BBCode::convert
const VERSION = '2021-07-28';
- const INTERNAL = 0;
- const EXTERNAL = 1;
- const API = 2;
- const DIASPORA = 3;
- const CONNECTORS = 4;
- const OSTATUS = 7;
- const TWITTER = 8;
- const BACKLINK = 8;
- const ACTIVITYPUB = 9;
+ const INTERNAL = 0;
+ const EXTERNAL = 1;
+ const MASTODON_API = 2;
+ const DIASPORA = 3;
+ const CONNECTORS = 4;
+ const TWITTER_API = 5;
+ const OSTATUS = 7;
+ const TWITTER = 8;
+ const BACKLINK = 8;
+ const ACTIVITYPUB = 9;
const TOP_ANCHOR = '<br class="top-anchor">';
const BOTTOM_ANCHOR = '<br class="button-anchor">';
+
+ const PREVIEW_NONE = 0;
+ const PREVIEW_NO_IMAGE = 1;
+ const PREVIEW_LARGE = 2;
+ const PREVIEW_SMALL = 3;
+
/**
* Fetches attachment data that were generated the old way
*
// Get all linked images with alternative image description
if (preg_match_all("/\[img=(http[^\[\]]*)\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
- if (Photo::isLocal($picture[1])) {
- $post['images'][] = ['url' => str_replace('-1.', '-0.', $picture[1]), 'description' => $picture[2]];
+ if ($id = Photo::getIdForName($picture[1])) {
+ $post['images'][] = ['url' => str_replace('-1.', '-0.', $picture[1]), 'description' => $picture[2], 'id' => $id];
} else {
$post['remote_images'][] = ['url' => $picture[1], 'description' => $picture[2]];
}
if (preg_match_all("/\[img\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
- if (Photo::isLocal($picture[1])) {
- $post['images'][] = ['url' => str_replace('-1.', '-0.', $picture[1]), 'description' => ''];
+ if ($id = Photo::getIdForName($picture[1])) {
+ $post['images'][] = ['url' => str_replace('-1.', '-0.', $picture[1]), 'description' => '', 'id' => $id];
} else {
$post['remote_images'][] = ['url' => $picture[1], 'description' => ''];
}
// Simplify image codes
$post['text'] = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $post['text']);
$post['text'] = preg_replace("/\[img\=(.*?)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $post['text']);
-
+
// if nothing is found, it maybe having an image.
if (!isset($post['type'])) {
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img\]([^\[]+?)\[/img\]\s*\[/url\]#ism", $post['text'], $pictures, PREG_SET_ORDER)) {
private static function proxyUrl(string $image, int $simplehtml = self::INTERNAL, int $uriid = 0, string $size = ''): string
{
// Only send proxied pictures to API and for internal display
- if (!in_array($simplehtml, [self::INTERNAL, self::API])) {
+ if (!in_array($simplehtml, [self::INTERNAL, self::MASTODON_API, self::TWITTER_API])) {
return $image;
} elseif ($uriid > 0) {
return Post\Link::getByLink($uriid, $image, $size);
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
- public static function convertAttachment(string $text, int $simplehtml = self::INTERNAL, bool $tryoembed = true, array $data = [], int $uriid = 0): string
+ public static function convertAttachment(string $text, int $simplehtml = self::INTERNAL, bool $tryoembed = true, array $data = [], int $uriid = 0, int $preview_mode = self::PREVIEW_LARGE): string
{
DI::profiler()->startRecording('rendering');
$data = $data ?: self::getAttachmentData($text);
$return = sprintf('<div class="type-%s">', $data['type']);
}
+ if ($preview_mode == self::PREVIEW_NO_IMAGE) {
+ unset($data['image']);
+ unset($data['preview']);
+ }
+
if (!empty($data['title']) && !empty($data['url'])) {
+ $preview_class = $preview_mode == self::PREVIEW_LARGE ? 'attachment-image' : 'attachment-preview';
if (!empty($data['image']) && empty($data['text']) && ($data['type'] == 'photo')) {
- $return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="attachment-image" /></a>', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']);
+ $return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="' . $preview_class . '" /></a>', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']);
} else {
if (!empty($data['image'])) {
- $return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="attachment-image" /></a><br>', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']);
+ $return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="' . $preview_class . '" /></a><br>', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']);
} elseif (!empty($data['preview'])) {
$return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="attachment-preview" /></a><br>', $data['url'], self::proxyUrl($data['preview'], $simplehtml, $uriid), $data['title']);
}
/**
* @param string $text A BBCode string
* @return array Empty array if no share tag is present or the following array, missing attributes end up empty strings:
- * - comment: Text before the opening share tag
- * - shared : Text inside the share tags
- * - author : (Optional) Display name of the shared author
- * - profile: (Optional) Profile page URL of the shared author
- * - avatar : (Optional) Profile picture URL of the shared author
- * - link : (Optional) Canonical URL of the shared post
- * - posted : (Optional) Date the shared post was initially posted ("Y-m-d H:i:s" in GMT)
- * - guid : (Optional) Shared post GUID if any
+ * - comment : Text before the opening share tag
+ * - shared : Text inside the share tags
+ * - author : (Optional) Display name of the shared author
+ * - profile : (Optional) Profile page URL of the shared author
+ * - avatar : (Optional) Profile picture URL of the shared author
+ * - link : (Optional) Canonical URL of the shared post
+ * - posted : (Optional) Date the shared post was initially posted ("Y-m-d H:i:s" in GMT)
+ * - message_id: (Optional) Shared post URI if any
+ * - guid : (Optional) Shared post GUID if any
*/
public static function fetchShareAttributes(string $text): array
{
DI::profiler()->startRecording('rendering');
+ if (preg_match('~(.*?)\[share](.*)\[/share]~ism', $text, $matches)) {
+ DI::profiler()->stopRecording();
+ return [
+ 'author' => '',
+ 'profile' => '',
+ 'avatar' => '',
+ 'link' => '',
+ 'posted' => '',
+ 'guid' => '',
+ 'message_id' => trim($matches[2]),
+ 'comment' => trim($matches[1]),
+ 'shared' => '',
+ ];
+ }
// See Issue https://github.com/friendica/friendica/issues/10454
// Hashtags in usernames are expanded to links. This here is a quick fix.
$text = preg_replace('~([@!#])\[url=.*?](.*?)\[/url]~ism', '$1$2', $text);
private static function extractShareAttributes(string $shareString): array
{
$attributes = [];
- foreach (['author', 'profile', 'avatar', 'link', 'posted', 'guid'] as $field) {
+ foreach (['author', 'profile', 'avatar', 'link', 'posted', 'guid', 'message_id'] as $field) {
preg_match("/$field=(['\"])(.+?)\\1/ism", $shareString, $matches);
$attributes[$field] = html_entity_decode($matches[2] ?? '', ENT_QUOTES, 'UTF-8');
}
return $attributes;
}
+ /**
+ * Remove the share block
+ *
+ * @param string $body
+ * @return string
+ */
+ public static function removeSharedData(string $body): string
+ {
+ return trim(preg_replace("/\s*\[share.*?\].*?\[\/share\]\s*/ism", '', $body));
+ }
+
/**
* This function converts a [share] block to text according to a provided callback function whose signature is:
*
);
DI::profiler()->stopRecording();
- return $return;
+ return trim($return);
}
/**
$mention = $attributes['author'] . ' (' . ($author_contact['addr'] ?? '') . ')';
switch ($simplehtml) {
- case self::API:
+ case self::MASTODON_API:
+ case self::TWITTER_API:
$text = ($is_quote_share? '<br>' : '') .
'<b><a href="' . $attributes['link'] . '">' . html_entity_decode('♲', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . "</a>:</b><br>\n" .
'<blockquote class="shared_content" dir="auto">' . $content . '</blockquote>';
/**
* Callback: Expands links from given $match array
*
- * @param arrat $match Array with link match
+ * @param array $match Array with link match
* @return string BBCode
*/
private static function expandLinksCallback(array $match): string
/**
* Callback: Cleans picture links
*
- * @param arrat $match Array with link match
+ * @param array $match Array with link match
* @return string BBCode
*/
private static function cleanPictureLinksCallback(array $match): string
public static function cleanPictureLinks(string $text): string
{
DI::profiler()->startRecording('rendering');
- $return = preg_replace_callback("&\[url=([^\[\]]*)\]\[img=(.*)\](.*)\[\/img\]\[\/url\]&Usi", 'self::cleanPictureLinksCallback', $text);
- $return = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", 'self::cleanPictureLinksCallback', $return);
+ $return = preg_replace_callback("&\[url=([^\[\]]*)\]\[img=(.*)\](.*)\[\/img\]\[\/url\]&Usi", [self::class, 'cleanPictureLinksCallback'], $text);
+ $return = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", [self::class, 'cleanPictureLinksCallback'], $return);
DI::profiler()->stopRecording();
return $return;
}
{
DI::profiler()->startRecording('rendering');
$regexp = "/([@!])\[url\=([^\[\]]*)\].*?\[\/url\]/ism";
- $body = preg_replace_callback($regexp, ['self', 'mentionCallback'], $body);
+ $body = preg_replace_callback($regexp, [self::class, 'mentionCallback'], $body);
DI::profiler()->stopRecording();
return $body;
}
$text = str_replace(">", ">", $text);
// remove some newlines before the general conversion
- $text = preg_replace("/\s?\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "[share$1]$2[/share]", $text);
- $text = preg_replace("/\s?\[quote(.*?)\]\s?(.*?)\s?\[\/quote\]\s?/ism", "[quote$1]$2[/quote]", $text);
+ $text = preg_replace("/\s?\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1]$2[/share]\n", $text);
+ $text = preg_replace("/\s?\[quote(.*?)\]\s?(.*?)\s?\[\/quote\]\s?/ism", "\n[quote$1]$2[/quote]\n", $text);
// 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) {
/// @todo Have a closer look at the different html modes
// Handle attached links or videos
- if (in_array($simple_html, [self::API, self::ACTIVITYPUB])) {
+ if (in_array($simple_html, [self::MASTODON_API, self::TWITTER_API, self::ACTIVITYPUB])) {
$text = self::removeAttachment($text);
} elseif (!in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::CONNECTORS])) {
$text = self::removeAttachment($text, true);
$text = preg_replace("/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism",
'<a href="$2" class="mention hashtag" rel="tag">$1<span>$3</span></a>',
$text);
- } elseif (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::API])) {
+ } elseif (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::TWITTER_API])) {
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
'<bdi>$1<a href="$2" class="userinfo mention" title="$3">$3</a></bdi>',
$text);
+ } elseif ($simple_html == self::MASTODON_API) {
+ $text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
+ '<a class="u-url mention status-link" href="$2" rel="nofollow noopener noreferrer" target="_blank" title="$3">$1<span>$3</span></a>',
+ $text);
+ $text = preg_replace("/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism",
+ '<a class="mention hashtag status-link" href="$2" rel="tag">$1<span>$3</span></a>',
+ $text);
} else {
$text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text);
}
if (!$for_plaintext) {
- if (in_array($simple_html, [self::OSTATUS, self::API, self::ACTIVITYPUB])) {
- $text = preg_replace_callback("/\[url\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text);
- $text = preg_replace_callback("/\[url\=(.*?)\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text);
+ if (in_array($simple_html, [self::OSTATUS, self::MASTODON_API, self::TWITTER_API, self::ACTIVITYPUB])) {
+ $text = preg_replace_callback("/\[url\](.*?)\[\/url\]/ism", [self::class, 'convertUrlForActivityPubCallback'], $text);
+ $text = preg_replace_callback("/\[url\=(.*?)\](.*?)\[\/url\]/ism", [self::class, 'convertUrlForActivityPubCallback'], $text);
}
} else {
$text = preg_replace("(\[url\](.*?)\[\/url\])ism", " $1 ", $text);
- $text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", 'self::removePictureLinksCallback', $text);
+ $text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", [self::class, 'removePictureLinksCallback'], $text);
}
// Bookmarks in red - will be converted to bookmarks in friendica
"[bookmark=$1]$2[/bookmark]", $text);
if (in_array($simple_html, [self::OSTATUS, self::TWITTER])) {
- $text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", "self::expandLinksCallback", $text);
+ $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);
}
$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://', 'redir/', 'cid:'];
+ $allowed_src_protocols = ['//', 'http://', 'https://', 'contact/redir/', 'cid:'];
array_walk($allowed_src_protocols, function(&$value) { $value = preg_quote($value, '#');});
$allowed_link_protocols[] = '//';
$allowed_link_protocols[] = 'http://';
$allowed_link_protocols[] = 'https://';
- $allowed_link_protocols[] = 'redir/';
+ $allowed_link_protocols[] = 'contact/redir/';
array_walk($allowed_link_protocols, function(&$value) { $value = preg_quote($value, '#');});
$url_search_string = "^\[\]";
$text = preg_replace_callback(
"/([@!])\[(.*?)\]\(([$url_search_string]*?)\)/ism",
- ['self', 'bbCodeMention2DiasporaCallback'],
+ [self::class, 'bbCodeMention2DiasporaCallback'],
$text
);
}
DI::profiler()->startRecording('rendering');
$ret = [];
- self::performWithEscapedTags($string, ['noparse', 'pre', 'code', 'img'], function ($string) use (&$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);
* @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
+ 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) .
$header .= "' guid='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $guid);
}
+ if ($uri) {
+ $header .= "' message_id='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $uri);
+ }
+
$header .= "']";
DI::profiler()->stopRecording();
// Bypass attachment if parse url for a comment
if (!$tryAttachment) {
DI::profiler()->stopRecording();
- return "\n" . '[url=' . $url . ']' . $siteinfo['title'] . '[/url]';
+ return "\n" . '[url=' . $url . ']' . ($siteinfo['title'] ?? $url) . '[/url]';
}
// Format it as BBCode attachment