use Friendica\BaseObject;
use Friendica\Content\OEmbed;
use Friendica\Content\Smilies;
-use Friendica\Core\Addon;
use Friendica\Core\Cache;
use Friendica\Core\Config;
+use Friendica\Core\Hook;
use Friendica\Core\L10n;
+use Friendica\Core\Logger;
use Friendica\Core\Protocol;
+use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Model\Contact;
use Friendica\Model\Event;
use Friendica\Util\Network;
use Friendica\Util\ParseUrl;
use Friendica\Util\Proxy as ProxyUtils;
+use Friendica\Util\Strings;
+use Friendica\Util\XML;
class BBCode extends BaseObject
{
*
* @param string $body Message body
* @return array
- * 'type' -> Message type ("link", "video", "photo")
- * 'text' -> Text before the shared message
- * 'after' -> Text after the shared message
- * 'image' -> Preview image of the message
- * 'url' -> Url to the attached message
- * 'title' -> Title of the attachment
- * 'description' -> Description of the attachment
+ * 'type' -> Message type ("link", "video", "photo")
+ * 'text' -> Text before the shared message
+ * 'after' -> Text after the shared message
+ * 'image' -> Preview image of the message
+ * 'url' -> Url to the attached message
+ * 'title' -> Title of the attachment
+ * 'description' -> Description of the attachment
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function getOldAttachmentData($body)
{
*
* @param string $body Message body
* @return array
- * 'type' -> Message type ("link", "video", "photo")
- * 'text' -> Text before the shared message
- * 'after' -> Text after the shared message
- * 'image' -> Preview image of the message
- * 'url' -> Url to the attached message
- * 'title' -> Title of the attachment
- * 'description' -> Description of the attachment
+ * 'type' -> Message type ("link", "video", "photo")
+ * 'text' -> Text before the shared message
+ * 'after' -> Text after the shared message
+ * 'image' -> Preview image of the message
+ * 'url' -> Url to the attached message
+ * 'title' -> Title of the attachment
+ * 'description' -> Description of the attachment
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function getAttachmentData($body)
{
$type = "";
preg_match("/type='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$type = strtolower($matches[1]);
}
preg_match('/type="(.*?)"/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$type = strtolower($matches[1]);
}
$url = "";
preg_match("/url='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$url = $matches[1];
}
preg_match('/url="(.*?)"/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$url = $matches[1];
}
$title = "";
preg_match("/title='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$title = $matches[1];
}
preg_match('/title="(.*?)"/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$title = $matches[1];
}
$image = "";
preg_match("/image='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$image = $matches[1];
}
preg_match('/image="(.*?)"/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$image = $matches[1];
}
$preview = "";
preg_match("/preview='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$preview = $matches[1];
}
preg_match('/preview="(.*?)"/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$preview = $matches[1];
}
*/
$has_title = !empty($item['title']);
- $plink = (!empty($item['plink']) ? $item['plink'] : '');
+ $plink = defaults($item, 'plink', '');
$post = self::getAttachmentData($body);
// if nothing is found, it maybe having an image.
/**
* @brief Converts a BBCode text into plaintext
*
+ * @param $text
* @param bool $keep_urls Whether to keep URLs in the resulting plaintext
*
* @return string
$c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism', $s, $matches, PREG_SET_ORDER);
if ($c) {
foreach ($matches as $mtch) {
- logger('scale_external_image: ' . $mtch[1]);
+ Logger::log('scale_external_image: ' . $mtch[1]);
$hostname = str_replace('www.', '', substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3));
if (stristr($mtch[1], $hostname)) {
$Image->scaleDown(640);
$new_width = $Image->getWidth();
$new_height = $Image->getHeight();
- logger('scale_external_images: ' . $orig_width . '->' . $new_width . 'w ' . $orig_height . '->' . $new_height . 'h' . ' match: ' . $mtch[0], LOGGER_DEBUG);
+ Logger::log('scale_external_images: ' . $orig_width . '->' . $new_width . 'w ' . $orig_height . '->' . $new_height . 'h' . ' match: ' . $mtch[0], Logger::DEBUG);
$s = str_replace(
$mtch[0],
'[img=' . $new_width . 'x' . $new_height. ']' . $scaled . '[/img]'
: ''),
$s
);
- logger('scale_external_images: new string: ' . $s, LOGGER_DEBUG);
+ Logger::log('scale_external_images: new string: ' . $s, Logger::DEBUG);
}
}
}
* @brief Truncates imported message body string length to max_import_size
* @param string $body
* @return string
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function limitBodySize($body)
{
// than the maximum, then don't waste time looking for the images
if ($maxlen && (strlen($body) > $maxlen)) {
- logger('the total body length exceeds the limit', LOGGER_DEBUG);
+ Logger::log('the total body length exceeds the limit', Logger::DEBUG);
$orig_body = $body;
$new_body = '';
if (($textlen + $img_start) > $maxlen) {
if ($textlen < $maxlen) {
- logger('the limit happens before an embedded image', LOGGER_DEBUG);
+ Logger::log('the limit happens before an embedded image', Logger::DEBUG);
$new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
$textlen = $maxlen;
}
if (($textlen + $img_end) > $maxlen) {
if ($textlen < $maxlen) {
- logger('the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
+ Logger::log('the limit happens before the end of a non-embedded image', Logger::DEBUG);
$new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
$textlen = $maxlen;
}
if (($textlen + strlen($orig_body)) > $maxlen) {
if ($textlen < $maxlen) {
- logger('the limit happens after the end of the last image', LOGGER_DEBUG);
+ Logger::log('the limit happens after the end of the last image', Logger::DEBUG);
$new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
}
} else {
- logger('the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
+ Logger::log('the text size with embedded images extracted did not violate the limit', Logger::DEBUG);
$new_body = $new_body . $orig_body;
}
* Note: Can produce a [bookmark] tag in the returned string
*
* @brief Processes [attachment] tags
- * @param string $return
+ * @param string $return
* @param bool|int $simplehtml
- * @param bool $tryoembed
+ * @param bool $tryoembed
* @return string
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function convertAttachment($return, $simplehtml = false, $tryoembed = true)
{
$data["title"] = $data["url"];
}
- if (($data["text"] == "") && ($data["title"] != "") && ($data["url"] == "")) {
+ if (empty($data["text"]) && !empty($data["title"]) && empty($data["url"])) {
return $data["title"] . $data["after"];
}
/**
* Performs a preg_replace within the boundaries of all named BBCode tags in a text
*
- * @param type $pattern Preg pattern string
- * @param type $replace Preg replace string
- * @param type $name BBCode tag name
- * @param type $text Text to search
+ * @param string $pattern Preg pattern string
+ * @param string $replace Preg replace string
+ * @param string $name BBCode tag name
+ * @param string $text Text to search
* @return string
*/
public static function pregReplaceInTag($pattern, $replace, $name, $text)
}
/**
- * Processes [share] tags
+ * This function converts a [share] block to text according to a provided callback function whose signature is:
*
- * Note: Can produce a [bookmark] tag in the output
+ * function(array $attributes, array $author_contact, string $content, boolean $is_quote_share): string
*
- * @brief Processes [share] tags
- * @param array $share preg_match_callback result array
- * @param bool|int $simplehtml
- * @return string
+ * Where:
+ * - $attributes is an array of attributes of the [share] block itself. Missing keys will be completed by the contact
+ * data lookup
+ * - $author_contact is a contact record array
+ * - $content is the inner content of the [share] block
+ * - $is_quote_share indicates whether there's any content before the [share] block
+ * - Return value is the string that should replace the [share] block in the provided text
+ *
+ * This function is intended to be used by addon connector to format a share block like the target network is expecting it.
+ *
+ * @param string $text A BBCode string
+ * @param callable $callback
+ * @return string The BBCode string with all [share] blocks replaced
*/
- private static function convertShare($share, $simplehtml)
+ public static function convertShare($text, callable $callback)
{
- $attributes = $share[2];
-
- $author = "";
- preg_match("/author='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
- $author = html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8');
- }
-
- preg_match('/author="(.*?)"/ism', $attributes, $matches);
- if (x($matches, 1)) {
- $author = $matches[1];
- }
-
- $profile = "";
- preg_match("/profile='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
- $profile = $matches[1];
- }
-
- preg_match('/profile="(.*?)"/ism', $attributes, $matches);
- if (x($matches, 1)) {
- $profile = $matches[1];
- }
-
- $avatar = "";
- preg_match("/avatar='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
- $avatar = $matches[1];
- }
-
- preg_match('/avatar="(.*?)"/ism', $attributes, $matches);
- if (x($matches, 1)) {
- $avatar = $matches[1];
- }
-
- $link = "";
- preg_match("/link='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
- $link = $matches[1];
- }
-
- preg_match('/link="(.*?)"/ism', $attributes, $matches);
- if (x($matches, 1)) {
- $link = $matches[1];
- }
-
- $posted = "";
-
- preg_match("/posted='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
- $posted = $matches[1];
- }
-
- preg_match('/posted="(.*?)"/ism', $attributes, $matches);
- if (x($matches, 1)) {
- $posted = $matches[1];
- }
+ $return = preg_replace_callback(
+ "/(.*?)\[share(.*?)\](.*?)\[\/share\]/ism",
+ function ($match) use ($callback) {
+ $attribute_string = $match[2];
+
+ $attributes = [];
+ foreach(['author', 'profile', 'avatar', 'link', 'posted'] as $field) {
+ preg_match("/$field=(['\"])(.+?)\\1/ism", $attribute_string, $matches);
+ $attributes[$field] = html_entity_decode(defaults($matches, 2, ''), ENT_QUOTES, 'UTF-8');
+ }
- // We only call this so that a previously unknown contact can be added.
- // This is important for the function "Model\Contact::getDetailsByURL()".
- // This function then can fetch an entry from the contact table.
- Contact::getIdForURL($profile, 0, true);
+ // We only call this so that a previously unknown contact can be added.
+ // This is important for the function "Model\Contact::getDetailsByURL()".
+ // This function then can fetch an entry from the contact table.
+ Contact::getIdForURL($attributes['profile'], 0, true);
- $data = Contact::getDetailsByURL($profile);
+ $author_contact = Contact::getDetailsByURL($attributes['profile']);
+ $author_contact['addr'] = defaults($author_contact, 'addr' , Protocol::getAddrFromProfileUrl($attributes['profile']));
- if (x($data, "name") && x($data, "addr")) {
- $userid_compact = $data["name"] . " (" . $data["addr"] . ")";
- } else {
- $userid_compact = Protocol::getAddrFromProfileUrl($profile, $author);
- }
+ $attributes['author'] = defaults($author_contact, 'name' , $attributes['author']);
+ $attributes['avatar'] = defaults($author_contact, 'micro', $attributes['avatar']);
+ $attributes['profile'] = defaults($author_contact, 'url' , $attributes['profile']);
- if (x($data, "addr")) {
- $userid = $data["addr"];
- } else {
- $userid = Protocol::formatMention($profile, $author);
- }
+ if ($attributes['avatar']) {
+ $attributes['avatar'] = ProxyUtils::proxifyUrl($attributes['avatar'], false, ProxyUtils::SIZE_THUMB);
+ }
- if (x($data, "name")) {
- $author = $data["name"];
- }
+ return $match[1] . $callback($attributes, $author_contact, $match[3], trim($match[1]) != '');
+ },
+ $text
+ );
- if (x($data, "micro")) {
- $avatar = $data["micro"];
- }
+ return $return;
+ }
- $preshare = trim($share[1]);
- if ($preshare != "") {
- $preshare .= "<br />";
- }
+ /**
+ * Default [share] tag conversion callback
+ *
+ * Note: Can produce a [bookmark] tag in the output
+ *
+ * @see BBCode::convertShare()
+ * @param array $attributes [share] block attribute values
+ * @param array $author_contact Contact row of the shared author
+ * @param string $content Inner content of the [share] block
+ * @param boolean $is_quote_share Whether there is content before the [share] block
+ * @param integer $simplehtml Mysterious integer value depending on the target network/formatting style
+ * @return string
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ */
+ private static function convertShareCallback(array $attributes, array $author_contact, $content, $is_quote_share, $simplehtml)
+ {
+ $mention = Protocol::formatMention($attributes['profile'], $attributes['author']);
switch ($simplehtml) {
case 1:
- $text = $preshare . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . ' <a href="' . $profile . '">' . $userid . "</a>: <br />»" . $share[3] . "«";
+ $text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' <a href="' . $attributes['profile'] . '">' . $mention . '</a>: </p>' . "\n" . '«' . $content . '»';
break;
case 2:
- $text = $preshare . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . ' ' . $userid_compact . ": <br />" . $share[3];
+ $text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n" . $content;
break;
case 3: // Diaspora
- $headline = '<b>' . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . $userid . ':</b><br />';
-
- $text = trim($share[1]);
-
- if ($text != "") {
- $text .= "<hr />";
- }
+ $headline = '<p><b>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . $mention . ':</b></p>' . "\n";
- if (stripos(normalise_link($link), 'http://twitter.com/') === 0) {
- $text .= '<br /><a href="' . $link . '">' . $link . '</a>';
+ if (stripos(Strings::normaliseLink($attributes['link']), 'http://twitter.com/') === 0) {
+ $text = ($is_quote_share? '<hr />' : '') . '<p><a href="' . $attributes['link'] . '">' . $attributes['link'] . '</a></p>' . "\n";
} else {
- $text .= $headline . '<blockquote>' . trim($share[3]) . "</blockquote><br />";
+ $text = ($is_quote_share? '<hr />' : '') . $headline . '<blockquote>' . trim($content) . '</blockquote>' . "\n";
- if ($link != "") {
- $text .= '<br /><a href="' . $link . '">[l]</a>';
+ if ($attributes['link'] != '') {
+ $text .= '<p><a href="' . $attributes['link'] . '">[l]</a></p>' . "\n";
}
}
break;
case 4:
- $headline = '<br /><b>' . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8');
- $headline .= L10n::t('<a href="%1$s" target="_blank">%2$s</a> %3$s', $link, $userid, $posted);
- $headline .= ":</b><br />";
+ $headline = '<p><b>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8');
+ $headline .= L10n::t('<a href="%1$s" target="_blank">%2$s</a> %3$s', $attributes['link'], $mention, $attributes['posted']);
+ $headline .= ':</b></p>' . "\n";
- $text = trim($share[1]);
-
- if ($text != "") {
- $text .= "<hr />";
- }
-
- $text .= $headline . '<blockquote class="shared_content">' . trim($share[3]) . "</blockquote><br />";
+ $text = ($is_quote_share? '<hr />' : '') . $headline . '<blockquote class="shared_content">' . trim($content) . '</blockquote>' . "\n";
break;
case 5:
- $text = $preshare . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . ' ' . $userid_compact . ": <br />" . $share[3];
+ $text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n" . $content;
break;
case 7: // statusnet/GNU Social
- $text = $preshare . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . " @" . $userid_compact . ": " . $share[3];
- break;
- case 8: // twitter
- $text = $preshare . "RT @" . $userid_compact . ": " . $share[3];
+ $text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' @' . $author_contact['addr'] . ': ' . $content . '</p>' . "\n";
break;
case 9: // Google+
- $text = $preshare . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . ' ' . $userid_compact . ": <br />" . $share[3];
+ $text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n";
+ $text .= '<p>' . $content . '</p>' . "\n";
- if ($link != "") {
- $text .= "<br /><br />" . $link;
+ if ($attributes['link'] != '') {
+ $text .= '<p>' . $attributes['link'] . '</p>';
}
break;
default:
// Transforms quoted tweets in rich attachments to avoid nested tweets
- if (stripos(normalise_link($link), 'http://twitter.com/') === 0 && OEmbed::isAllowedURL($link)) {
+ if (stripos(Strings::normaliseLink($attributes['link']), 'http://twitter.com/') === 0 && OEmbed::isAllowedURL($attributes['link'])) {
try {
- $oembed = OEmbed::getHTML($link, $preshare);
+ $text = ($is_quote_share? '<br />' : '') . OEmbed::getHTML($attributes['link']);
} catch (Exception $e) {
- $oembed = sprintf('[bookmark=%s]%s[/bookmark]', $link, $preshare);
+ $text = ($is_quote_share? '<br />' : '') . sprintf('[bookmark=%s]%s[/bookmark]', $attributes['link'], $content);
}
-
- $text = $preshare . $oembed;
} else {
- $text = trim($share[1]) . "\n";
-
- $avatar = ProxyUtils::proxifyUrl($avatar, false, ProxyUtils::SIZE_THUMB);
-
- $tpl = get_markup_template('shared_content.tpl');
- $text .= replace_macros($tpl, [
- '$profile' => $profile,
- '$avatar' => $avatar,
- '$author' => $author,
- '$link' => $link,
- '$posted' => $posted,
- '$content' => trim($share[3])
+ $text = ($is_quote_share? "\n" : '');
+
+ $tpl = Renderer::getMarkupTemplate('shared_content.tpl');
+ $text .= Renderer::replaceMacros($tpl, [
+ '$profile' => $attributes['profile'],
+ '$avatar' => $attributes['avatar'],
+ '$author' => $attributes['author'],
+ '$link' => $attributes['link'],
+ '$posted' => $attributes['posted'],
+ '$content' => trim($content)
]);
}
break;
* @param int $simple_html
* @param bool $for_plaintext
* @return string
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
- public static function convert($text, $try_oembed = true, $simple_html = false, $for_plaintext = false)
+ public static function convert($text, $try_oembed = true, $simple_html = 0, $for_plaintext = false)
{
$a = self::getApp();
$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("/\n\[code\]/ism", "[code]", $text);
- $text = preg_replace("/\[\/code\]\n/ism", "[/code]", $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) {
$text = preg_replace("/\[share(.*?)avatar\s?=\s?'.*?'\s?(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1$2]$3[/share]", $text);
// Handle Diaspora posts
$text = preg_replace_callback(
- "&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
+ "&\[url=/?posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
function ($match) {
return "[url=" . System::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]";
}, $text
$expression = "=diaspora://.*?/post/([0-9A-Za-z\-_@.:]{15,254}[0-9A-Za-z])=ism";
$text = preg_replace($expression, System::baseUrl()."/display/$1", $text);
- $text = preg_replace("/([#])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
- '$1<a href="' . System::baseUrl() . '/search?tag=$3" class="tag" title="$3">$3</a>', $text);
-
- $text = preg_replace("/\[url\=([$URLSearchString]*)\]#(.*?)\[\/url\]/ism",
- '#<a href="' . System::baseUrl() . '/search?tag=$2" class="tag" title="$2">$2</a>', $text);
+ /* Tag conversion
+ * Supports:
+ * - #[url=<anything>]<term>[/url]
+ * - [url=<anything>]#<term>[/url]
+ */
+ $text = preg_replace_callback("/(?:#\[url\=[$URLSearchString]*\]|\[url\=[$URLSearchString]*\]#)(.*?)\[\/url\]/ism", function($matches) {
+ return '#<a href="'
+ . System::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
+ . '" class="tag" title="' . XML::escape($matches[1]) . '">'
+ . XML::escape($matches[1])
+ . '</a>';
+ }, $text);
+
+ // We need no target="_blank" for local links
+ // convert links start with System::baseUrl() as local link
+ $escapedBaseUrl = str_replace('://', '\:\/\/', System::baseUrl());
+ $text = preg_replace("/\[url\]($escapedBaseUrl)([$URLSearchString]*)\[\/url\]/ism", '<a href="$1$2">$1$2</a>', $text);
+ $text = preg_replace("/\[url\=($escapedBaseUrl)([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1$2">$3</a>', $text);
+ // convert links that start with / as local link
+ $text = preg_replace("/\[url\](\/[$URLSearchString]*)\[\/url\]/ism", '<a href="'.System::baseUrl().'$1">$1</a>', $text);
+ $text = preg_replace("/\[url\=(\/[$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="'.System::baseUrl().'$1">$2</a>', $text);
$text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" target="_blank">$1</a>', $text);
$text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank">$2</a>', $text);
- //$Text = preg_replace("/\[url\=([$URLSearchString]*)\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" target="_blank">$2</a>', $Text);
// Red compatibility, though the link can't be authenticated on Friendica
$text = preg_replace("/\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<a href="$1" target="_blank">$2</a>', $text);
$text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.*?)\[\/mail\]/", '<a href="mailto:$1">$2</a>', $text);
// leave open the posibility of [map=something]
- // this is replaced in prepare_body() which has knowledge of the item location
+ // this is replaced in Item::prepareBody() which has knowledge of the item location
if (strpos($text, '[/map]') !== false) {
$text = preg_replace_callback(
$text = str_replace('[hr]', '<hr />', $text);
- // This is actually executed in prepare_body()
+ // This is actually executed in Item::prepareBody()
$text = str_replace('[nosmile]', '', $text);
$text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img src="$1" alt="' . L10n::t('Image/photo') . '" />', $text);
// Shared content
- $text = preg_replace_callback("/(.*?)\[share(.*?)\](.*?)\[\/share\]/ism",
- function ($match) use ($simple_html) {
- return self::convertShare($match, $simple_html);
- }, $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);
+ }
+ );
$text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism", '<br/><img src="' .System::baseUrl() . '/images/lock_icon.gif" alt="' . L10n::t('Encrypted content') . '" title="' . L10n::t('Encrypted content') . '" /><br />', $text);
$text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '<br/><img src="' .System::baseUrl() . '/images/lock_icon.gif" alt="' . L10n::t('Encrypted content') . '" title="' . '$1' . ' ' . L10n::t('Encrypted content') . '" /><br />', $text);
// Summary (e.g. title) is required, earlier revisions only required description (in addition to
// start which is always required). Allow desc with a missing summary for compatibility.
- if ((x($ev, 'desc') || x($ev, 'summary')) && x($ev, 'start')) {
+ if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) {
$sub = Event::getHTML($ev, $simple_html);
$text = preg_replace("/\[event\-summary\](.*?)\[\/event\-summary\]/ism", '', $text);
// Replace non graphical smilies for external posts
if ($simple_html) {
- $text = Smilies::replace($text, false, true);
+ $text = Smilies::replace($text);
}
- // Replace inline code blocks
- $text = preg_replace_callback("|(?!<br[^>]*>)<code>([^<]*)</code>(?!<br[^>]*>)|ism",
- function ($match) use ($simple_html) {
- $return = '<key>' . $match[1] . '</key>';
- // Use <code> for Diaspora inline code blocks
- if ($simple_html === 3) {
- $return = '<code>' . $match[1] . '</code>';
- }
- return $return;
- }
- , $text);
-
// Unhide all [noparse] contained bbtags unspacefying them
// and triming the [noparse] tag.
//$Text = str_replace('<br /><li>', '<li>', $Text);
//$Text = str_replace('<br /><ul', '<ul ', $Text);
- Addon::callHooks('bbcode', $text);
+ Hook::callAll('bbcode', $text);
return trim($text);
}
* @brief 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($match)
{
return $match[0];
}
- $mention = '@{' . $match[2] . '; ' . $contact['addr'] . '}';
+ $mention = $match[1] . '{' . $match[2] . '; ' . $contact['addr'] . '}';
return $mention;
}
* @param string $text
* @param bool $for_diaspora Diaspora requires more changes than Libertree
* @return string
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function toMarkdown($text, $for_diaspora = true)
{
if ($for_diaspora) {
$url_search_string = "^\[\]";
$text = preg_replace_callback(
- "/([@]\[(.*?)\])\(([$url_search_string]*?)\)/ism",
+ "/([@!])\[(.*?)\]\(([$url_search_string]*?)\)/ism",
['self', 'bbCodeMention2DiasporaCallback'],
$text
);
}
- Addon::callHooks('bb2diaspora', $text);
+ Hook::callAll('bb2diaspora', $text);
return $text;
}
+
+ /**
+ * @brief 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)
+ {
+ $ret = [];
+
+ // Convert hashtag links to hashtags
+ $string = preg_replace('/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism', '#$2', $string);
+
+ // ignore anything in a code block
+ $string = preg_replace('/\[code\](.*?)\[\/code\]/sm', '', $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;
+ }
+ if (substr($match, -1, 1) === '.') {
+ $match = substr($match,0,-1);
+ }
+ // ignore strictly numeric tags like #1
+ if ((strpos($match, '#') === 0) && ctype_digit(substr($match, 1))) {
+ 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;
+ }
+ }
+
+ return $ret;
+ }
}