X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FContent%2FText%2FBBCode.php;h=8187521582ff253a665b7acd45031d7c3fd54c66;hb=07cea24430420cb0ac2b1d8f870d7292002c7faf;hp=0d83b665cdc13256245cb0c69b60c02ab4535997;hpb=f89a879abae7200896657929c2778098cf8c4b1f;p=friendica.git diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index 0d83b665cd..8187521582 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -21,8 +21,10 @@ use Friendica\Core\Renderer; use Friendica\Core\System; use Friendica\Model\Contact; use Friendica\Model\Event; +use Friendica\Model\Photo; use Friendica\Network\Probe; use Friendica\Object\Image; +use Friendica\Protocol\Activity; use Friendica\Util\Map; use Friendica\Util\Network; use Friendica\Util\ParseUrl; @@ -37,7 +39,7 @@ class BBCode extends BaseObject * * @param string $body Message body * @return array - * 'type' -> Message type ("link", "video", "photo") + * 'type' -> Message type ('link', 'video', 'photo') * 'text' -> Text before the shared message * 'after' -> Text after the shared message * 'image' -> Preview image of the message @@ -55,50 +57,48 @@ class BBCode extends BaseObject if (preg_match_all("(\[class=(.*?)\](.*?)\[\/class\])ism", $body, $attached, PREG_SET_ORDER)) { foreach ($attached as $data) { - if (!in_array($data[1], ["type-link", "type-video", "type-photo"])) { + if (!in_array($data[1], ['type-link', 'type-video', 'type-photo'])) { continue; } - $post["type"] = substr($data[1], 5); + $post['type'] = substr($data[1], 5); $pos = strpos($body, $data[0]); if ($pos > 0) { - $post["text"] = trim(substr($body, 0, $pos)); - $post["after"] = trim(substr($body, $pos + strlen($data[0]))); + $post['text'] = trim(substr($body, 0, $pos)); + $post['after'] = trim(substr($body, $pos + strlen($data[0]))); } else { - $post["text"] = trim(str_replace($data[0], "", $body)); - $post["after"] = ''; + $post['text'] = trim(str_replace($data[0], '', $body)); + $post['after'] = ''; } $attacheddata = $data[2]; - $URLSearchString = "^\[\]"; - - if (preg_match("/\[img\]([$URLSearchString]*)\[\/img\]/ism", $attacheddata, $matches)) { + if (preg_match("/\[img\](.*?)\[\/img\]/ism", $attacheddata, $matches)) { $picturedata = Image::getInfoFromURL($matches[1]); if ($picturedata) { if (($picturedata[0] >= 500) && ($picturedata[0] >= $picturedata[1])) { - $post["image"] = $matches[1]; + $post['image'] = $matches[1]; } else { - $post["preview"] = $matches[1]; + $post['preview'] = $matches[1]; } } } - if (preg_match("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism", $attacheddata, $matches)) { - $post["url"] = $matches[1]; - $post["title"] = $matches[2]; + if (preg_match("/\[bookmark\=(.*?)\](.*?)\[\/bookmark\]/ism", $attacheddata, $matches)) { + $post['url'] = $matches[1]; + $post['title'] = $matches[2]; } - if (!empty($post["url"]) && (in_array($post["type"], ["link", "video"])) - && preg_match("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $attacheddata, $matches)) { - $post["url"] = $matches[1]; + if (!empty($post['url']) && (in_array($post['type'], ['link', 'video'])) + && preg_match("/\[url\=(.*?)\](.*?)\[\/url\]/ism", $attacheddata, $matches)) { + $post['url'] = $matches[1]; } // Search for description if (preg_match("/\[quote\](.*?)\[\/quote\]/ism", $attacheddata, $matches)) { - $post["description"] = $matches[1]; + $post['description'] = $matches[1]; } } } @@ -110,7 +110,7 @@ class BBCode extends BaseObject * * @param string $body Message body * @return array - * 'type' -> Message type ("link", "video", "photo") + * 'type' -> Message type ('link', 'video', 'photo') * 'text' -> Text before the shared message * 'after' -> Text after the shared message * 'image' -> Preview image of the message @@ -121,7 +121,15 @@ class BBCode extends BaseObject */ public static function getAttachmentData($body) { - $data = []; + $data = [ + 'type' => '', + 'text' => '', + 'after' => '', + 'image' => null, + 'url' => '', + 'title' => '', + 'description' => '', + ]; if (!preg_match("/(.*)\[attachment(.*?)\](.*?)\[\/attachment\](.*)/ism", $body, $match)) { return self::getOldAttachmentData($body); @@ -129,9 +137,9 @@ class BBCode extends BaseObject $attributes = $match[2]; - $data["text"] = trim($match[1]); + $data['text'] = trim($match[1]); - $type = ""; + $type = ''; preg_match("/type='(.*?)'/ism", $attributes, $matches); if (!empty($matches[1])) { $type = strtolower($matches[1]); @@ -142,19 +150,19 @@ class BBCode extends BaseObject $type = strtolower($matches[1]); } - if ($type == "") { + if ($type == '') { return []; } - if (!in_array($type, ["link", "audio", "photo", "video"])) { + if (!in_array($type, ['link', 'audio', 'photo', 'video'])) { return []; } - if ($type != "") { - $data["type"] = $type; + if ($type != '') { + $data['type'] = $type; } - $url = ""; + $url = ''; preg_match("/url='(.*?)'/ism", $attributes, $matches); if (!empty($matches[1])) { $url = $matches[1]; @@ -165,11 +173,11 @@ class BBCode extends BaseObject $url = $matches[1]; } - if ($url != "") { - $data["url"] = html_entity_decode($url, ENT_QUOTES, 'UTF-8'); + if ($url != '') { + $data['url'] = html_entity_decode($url, ENT_QUOTES, 'UTF-8'); } - $title = ""; + $title = ''; preg_match("/title='(.*?)'/ism", $attributes, $matches); if (!empty($matches[1])) { $title = $matches[1]; @@ -180,14 +188,14 @@ class BBCode extends BaseObject $title = $matches[1]; } - if ($title != "") { + if ($title != '') { $title = self::convert(html_entity_decode($title, ENT_QUOTES, 'UTF-8'), false, true); $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8'); - $title = str_replace(["[", "]"], ["[", "]"], $title); - $data["title"] = $title; + $title = str_replace(['[', ']'], ['[', ']'], $title); + $data['title'] = $title; } - $image = ""; + $image = ''; preg_match("/image='(.*?)'/ism", $attributes, $matches); if (!empty($matches[1])) { $image = $matches[1]; @@ -198,11 +206,11 @@ class BBCode extends BaseObject $image = $matches[1]; } - if ($image != "") { - $data["image"] = html_entity_decode($image, ENT_QUOTES, 'UTF-8'); + if ($image != '') { + $data['image'] = html_entity_decode($image, ENT_QUOTES, 'UTF-8'); } - $preview = ""; + $preview = ''; preg_match("/preview='(.*?)'/ism", $attributes, $matches); if (!empty($matches[1])) { $preview = $matches[1]; @@ -213,13 +221,13 @@ class BBCode extends BaseObject $preview = $matches[1]; } - if ($preview != "") { - $data["preview"] = html_entity_decode($preview, ENT_QUOTES, 'UTF-8'); + if ($preview != '') { + $data['preview'] = html_entity_decode($preview, ENT_QUOTES, 'UTF-8'); } - $data["description"] = trim($match[3]); + $data['description'] = trim($match[3]); - $data["after"] = trim($match[4]); + $data['after'] = trim($match[4]); return $data; } @@ -237,22 +245,42 @@ class BBCode extends BaseObject */ $has_title = !empty($item['title']); - $plink = defaults($item, 'plink', ''); + $plink = $item['plink'] ?? ''; $post = self::getAttachmentData($body); + // Get all linked images with alternative image description + 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' => $picture[2]]; + } + } + if (!empty($post['images']) && !empty($post['images'][0]['description'])) { + $post['image_description'] = $post['images'][0]['description']; + } + } + + 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 nothing is found, it maybe having an image. - if (!isset($post["type"])) { + if (!isset($post['type'])) { // Simplify image codes $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body); + $body = preg_replace("/\[img\=(.*?)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $body); + $post['text'] = $body; - $URLSearchString = "^\[\]"; - - $body = preg_replace("/\[img\=([$URLSearchString]*)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $body); - - if (preg_match_all("(\[url=([$URLSearchString]*)\]\s*\[img\]([$URLSearchString]*)\[\/img\]\s*\[\/url\])ism", $body, $pictures, PREG_SET_ORDER)) { + if (preg_match_all("(\[url=(.*?)\]\s*\[img\](.*?)\[\/img\]\s*\[\/url\])ism", $body, $pictures, PREG_SET_ORDER)) { if ((count($pictures) == 1) && !$has_title) { - if (!empty($item['object-type']) && ($item['object-type'] == ACTIVITY_OBJ_IMAGE)) { - $data = ['url' => $pictures[0][1], 'type' => 'photo']; + if (!empty($item['object-type']) && ($item['object-type'] == Activity::OBJ_IMAGE)) { + // Replace the preview picture with the real picture + $url = str_replace('-1.', '-0.', $pictures[0][2]); + $data = ['url' => $url, 'type' => 'photo']; } else { // Checking, if the link goes to a picture $data = ParseUrl::getSiteinfoCached($pictures[0][1], true); @@ -261,68 +289,75 @@ class BBCode extends BaseObject // Workaround: // Sometimes photo posts to the own album are not detected at the start. // So we seem to cannot use the cache for these cases. That's strange. - if (($data["type"] != "photo") && strstr($pictures[0][1], "/photos/")) { + if (($data['type'] != 'photo') && strstr($pictures[0][1], "/photos/")) { $data = ParseUrl::getSiteinfo($pictures[0][1], true); } - if ($data["type"] == "photo") { - $post["type"] = "photo"; - if (isset($data["images"][0])) { - $post["image"] = $data["images"][0]["src"]; - $post["url"] = $data["url"]; + if ($data['type'] == 'photo') { + $post['type'] = 'photo'; + if (isset($data['images'][0])) { + $post['image'] = $data['images'][0]['src']; + $post['url'] = $data['url']; } else { - $post["image"] = $data["url"]; + $post['image'] = $data['url']; } - $post["preview"] = $pictures[0][2]; - $post["text"] = trim(str_replace($pictures[0][0], "", $body)); + $post['preview'] = $pictures[0][2]; + $post['text'] = trim(str_replace($pictures[0][0], '', $body)); } else { $imgdata = Image::getInfoFromURL($pictures[0][1]); - if ($imgdata && substr($imgdata["mime"], 0, 6) == "image/") { - $post["type"] = "photo"; - $post["image"] = $pictures[0][1]; - $post["preview"] = $pictures[0][2]; - $post["text"] = trim(str_replace($pictures[0][0], "", $body)); + if ($imgdata && substr($imgdata['mime'], 0, 6) == 'image/') { + $post['type'] = 'photo'; + $post['image'] = $pictures[0][1]; + $post['preview'] = $pictures[0][2]; + $post['text'] = trim(str_replace($pictures[0][0], '', $body)); } } } elseif (count($pictures) > 0) { - $post["type"] = "link"; - $post["url"] = $plink; - $post["image"] = $pictures[0][2]; - $post["text"] = $body; + $post['type'] = 'link'; + $post['url'] = $plink; + $post['image'] = $pictures[0][2]; + $post['text'] = $body; + + foreach ($pictures as $picture) { + $post['text'] = trim(str_replace($picture[0], '', $post['text'])); + } } - } elseif (preg_match_all("(\[img\]([$URLSearchString]*)\[\/img\])ism", $body, $pictures, PREG_SET_ORDER)) { + } elseif (preg_match_all("(\[img\](.*?)\[\/img\])ism", $body, $pictures, PREG_SET_ORDER)) { if ((count($pictures) == 1) && !$has_title) { - $post["type"] = "photo"; - $post["image"] = $pictures[0][1]; - $post["text"] = str_replace($pictures[0][0], "", $body); + $post['type'] = 'photo'; + $post['image'] = $pictures[0][1]; + $post['text'] = str_replace($pictures[0][0], '', $body); } elseif (count($pictures) > 0) { - $post["type"] = "link"; - $post["url"] = $plink; - $post["image"] = $pictures[0][1]; - $post["text"] = $body; + $post['type'] = 'link'; + $post['url'] = $plink; + $post['image'] = $pictures[0][1]; + $post['text'] = $body; + + foreach ($pictures as $picture) { + $post['text'] = trim(str_replace($picture[0], '', $post['text'])); + } } } // Test for the external links - preg_match_all("(\[url\]([$URLSearchString]*)\[\/url\])ism", $body, $links1, PREG_SET_ORDER); - preg_match_all("(\[url\=([$URLSearchString]*)\].*?\[\/url\])ism", $body, $links2, PREG_SET_ORDER); + preg_match_all("(\[url\](.*?)\[\/url\])ism", $post['text'], $links1, PREG_SET_ORDER); + preg_match_all("(\[url\=(.*?)\].*?\[\/url\])ism", $post['text'], $links2, PREG_SET_ORDER); $links = array_merge($links1, $links2); // If there is only a single one, then use it. // This should cover link posts via API. - if ((count($links) == 1) && !isset($post["preview"]) && !$has_title) { - $post["type"] = "link"; - $post["text"] = trim($body); - $post["url"] = $links[0][1]; + if ((count($links) == 1) && !isset($post['preview']) && !$has_title) { + $post['type'] = 'link'; + $post['url'] = $links[0][1]; } // Now count the number of external media links - preg_match_all("(\[vimeo\](.*?)\[\/vimeo\])ism", $body, $links1, PREG_SET_ORDER); - preg_match_all("(\[youtube\\](.*?)\[\/youtube\\])ism", $body, $links2, PREG_SET_ORDER); - preg_match_all("(\[video\\](.*?)\[\/video\\])ism", $body, $links3, PREG_SET_ORDER); - preg_match_all("(\[audio\\](.*?)\[\/audio\\])ism", $body, $links4, PREG_SET_ORDER); + preg_match_all("(\[vimeo\](.*?)\[\/vimeo\])ism", $post['text'], $links1, PREG_SET_ORDER); + preg_match_all("(\[youtube\\](.*?)\[\/youtube\\])ism", $post['text'], $links2, PREG_SET_ORDER); + preg_match_all("(\[video\\](.*?)\[\/video\\])ism", $post['text'], $links3, PREG_SET_ORDER); + preg_match_all("(\[audio\\](.*?)\[\/audio\\])ism", $post['text'], $links4, PREG_SET_ORDER); // Add them to the other external links $links = array_merge($links, $links1, $links2, $links3, $links4); @@ -330,19 +365,19 @@ class BBCode extends BaseObject // Are there more than one? if (count($links) > 1) { // The post will be the type "text", which means a blog post - unset($post["type"]); - $post["url"] = $plink; + unset($post['type']); + $post['url'] = $plink; } - if (!isset($post["type"])) { - $post["type"] = "text"; - $post["text"] = trim($body); + if (!isset($post['type'])) { + $post['type'] = "text"; + $post['text'] = trim($body); } - } elseif (isset($post["url"]) && ($post["type"] == "video")) { - $data = ParseUrl::getSiteinfoCached($post["url"], true); + } elseif (isset($post['url']) && ($post['type'] == 'video')) { + $data = ParseUrl::getSiteinfoCached($post['url'], true); - if (isset($data["images"][0])) { - $post["image"] = $data["images"][0]["src"]; + if (isset($data['images'][0])) { + $post['image'] = $data['images'][0]['src']; } } @@ -359,10 +394,7 @@ class BBCode extends BaseObject */ public static function toPlaintext($text, $keep_urls = true) { - $naked_text = preg_replace('/\[(.+?)\]\s*/','', $text); - if (!$keep_urls) { - $naked_text = preg_replace('#https?\://[^\s<]+[^\s\.\)]#i', '', $naked_text); - } + $naked_text = HTML::toPlaintext(BBCode::convert($text, false, 0, true), 0, !$keep_urls); return $naked_text; } @@ -550,27 +582,27 @@ class BBCode extends BaseObject private static function convertAttachment($return, $simplehtml = false, $tryoembed = true) { $data = self::getAttachmentData($return); - if (empty($data) || empty($data["url"])) { + if (empty($data) || empty($data['url'])) { return $return; } - if (isset($data["title"])) { - $data["title"] = strip_tags($data["title"]); - $data["title"] = str_replace(["http://", "https://"], "", $data["title"]); + if (isset($data['title'])) { + $data['title'] = strip_tags($data['title']); + $data['title'] = str_replace(['http://', 'https://'], '', $data['title']); } else { - $data["title"] = null; + $data['title'] = null; } - if (((strpos($data["text"], "[img=") !== false) || (strpos($data["text"], "[img]") !== false) || Config::get('system', 'always_show_preview')) && !empty($data["image"])) { - $data["preview"] = $data["image"]; - $data["image"] = ""; + if (((strpos($data['text'], "[img=") !== false) || (strpos($data['text'], "[img]") !== false) || Config::get('system', 'always_show_preview')) && !empty($data['image'])) { + $data['preview'] = $data['image']; + $data['image'] = ''; } $return = ''; - if ($simplehtml == 7) { - $return = self::convertUrlForOStatus($data["url"]); + if (in_array($simplehtml, [7, 9])) { + $return = self::convertUrlForActivityPub($data['url']); } elseif (($simplehtml != 4) && ($simplehtml != 0)) { - $return = sprintf('%s
', $data["url"], $data["title"]); + $return = sprintf('%s
', $data['url'], $data['title']); } else { try { if ($tryoembed && OEmbed::isAllowedURL($data['url'])) { @@ -579,28 +611,28 @@ class BBCode extends BaseObject throw new Exception('OEmbed is disabled for this attachment.'); } } catch (Exception $e) { - $data["title"] = defaults($data, 'title', $data['url']); + $data['title'] = ($data['title'] ?? '') ?: $data['url']; if ($simplehtml != 4) { - $return = sprintf('
', $data["type"]); + $return = sprintf('
', $data['type']); } if (!empty($data['title']) && !empty($data['url'])) { - if (!empty($data["image"]) && empty($data["text"]) && ($data["type"] == "photo")) { - $return .= sprintf('', $data["url"], self::proxyUrl($data["image"], $simplehtml), $data["title"]); + if (!empty($data['image']) && empty($data['text']) && ($data['type'] == 'photo')) { + $return .= sprintf('', $data['url'], self::proxyUrl($data['image'], $simplehtml), $data['title']); } else { - if (!empty($data["image"])) { - $return .= sprintf('
', $data["url"], self::proxyUrl($data["image"], $simplehtml), $data["title"]); - } elseif (!empty($data["preview"])) { - $return .= sprintf('
', $data["url"], self::proxyUrl($data["preview"], $simplehtml), $data["title"]); + if (!empty($data['image'])) { + $return .= sprintf('
', $data['url'], self::proxyUrl($data['image'], $simplehtml), $data['title']); + } elseif (!empty($data['preview'])) { + $return .= sprintf('
', $data['url'], self::proxyUrl($data['preview'], $simplehtml), $data['title']); } $return .= sprintf('

%s

', $data['url'], $data['title']); } } - if (!empty($data["description"]) && $data["description"] != $data["title"]) { + if (!empty($data['description']) && $data['description'] != $data['title']) { // Sanitize the HTML by converting it to BBCode - $bbcode = HTML::toBBCode($data["description"]); + $bbcode = HTML::toBBCode($data['description']); $return .= sprintf('
%s
', trim(self::convert($bbcode))); } @@ -614,7 +646,7 @@ class BBCode extends BaseObject } } - return trim(defaults($data, 'text', '') . ' ' . $return . ' ' . defaults($data, 'after', '')); + return trim(($data['text'] ?? '') . ' ' . $return . ' ' . ($data['after'] ?? '')); } public static function removeShareInformation($Text, $plaintext = false, $nolink = false) @@ -624,36 +656,36 @@ class BBCode extends BaseObject if (!$data) { return $Text; } elseif ($nolink) { - return $data["text"] . defaults($data, 'after', ''); + return $data['text'] . ($data['after'] ?? ''); } - $title = htmlentities(defaults($data, 'title', ''), ENT_QUOTES, 'UTF-8', false); - $text = htmlentities($data["text"], ENT_QUOTES, 'UTF-8', false); - if ($plaintext || (($title != "") && strstr($text, $title))) { - $data["title"] = $data["url"]; - } elseif (($text != "") && strstr($title, $text)) { - $data["text"] = $data["title"]; - $data["title"] = $data["url"]; + $title = htmlentities($data['title'] ?? '', ENT_QUOTES, 'UTF-8', false); + $text = htmlentities($data['text'], ENT_QUOTES, 'UTF-8', false); + if ($plaintext || (($title != '') && strstr($text, $title))) { + $data['title'] = $data['url']; + } elseif (($text != '') && strstr($title, $text)) { + $data['text'] = $data['title']; + $data['title'] = $data['url']; } - if (empty($data["text"]) && !empty($data["title"]) && empty($data["url"])) { - return $data["title"] . $data["after"]; + if (empty($data['text']) && !empty($data['title']) && empty($data['url'])) { + return $data['title'] . $data['after']; } // If the link already is included in the post, don't add it again - if (!empty($data["url"]) && strpos($data["text"], $data["url"])) { - return $data["text"] . $data["after"]; + if (!empty($data['url']) && strpos($data['text'], $data['url'])) { + return $data['text'] . $data['after']; } - $text = $data["text"]; + $text = $data['text']; - if (!empty($data["url"]) && !empty($data["title"])) { - $text .= "\n[url=" . $data["url"] . "]" . $data["title"] . "[/url]"; - } elseif (!empty($data["url"])) { - $text .= "\n[url]" . $data["url"] . "[/url]"; + if (!empty($data['url']) && !empty($data['title'])) { + $text .= "\n[url=" . $data['url'] . ']' . $data['title'] . '[/url]'; + } elseif (!empty($data['url'])) { + $text .= "\n[url]" . $data['url'] . '[/url]'; } - return $text . "\n" . $data["after"]; + return $text . "\n" . $data['after']; } /** @@ -663,7 +695,7 @@ class BBCode extends BaseObject * @param array $match Array with the matching values * @return string reformatted link including HTML codes */ - private static function convertUrlForOStatusCallback($match) + private static function convertUrlForActivityPubCallback($match) { $url = $match[1]; @@ -676,15 +708,26 @@ class BBCode extends BaseObject return $match[0]; } - return self::convertUrlForOStatus($url); + return self::convertUrlForActivityPub($url); } /** - * @brief Converts [url] BBCodes in a format that looks fine on OStatus systems. + * @brief Converts [url] BBCodes in a format that looks fine on ActivityPub systems. * @param string $url URL that is about to be reformatted * @return string reformatted link including HTML codes */ - private static function convertUrlForOStatus($url) + private static function convertUrlForActivityPub($url) + { + $html = '%s'; + return sprintf($html, $url, self::getStyledURL($url)); + } + + /** + * Converts an URL in a nicer format (without the scheme and possibly shortened) + * @param string $url URL that is about to be reformatted + * @return string reformatted link + */ + private static function getStyledURL($url) { $parts = parse_url($url); $scheme = $parts['scheme'] . '://'; @@ -694,9 +737,7 @@ class BBCode extends BaseObject $styled_url = substr($styled_url, 0, 30) . "…"; } - $html = '%s'; - - return sprintf($html, $url, $styled_url); + return $styled_url; } /* @@ -901,20 +942,30 @@ class BBCode extends BaseObject $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'); + $attributes[$field] = html_entity_decode($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($attributes['profile'], 0, true); + $default['url'] = $attributes['profile']; + + if (!empty($attributes['author'])) { + $default['name'] = $attributes['author']; + } + + if (!empty($attributes['avatar'])) { + $default['photo'] = $attributes['avatar']; + } + + Contact::getIdForURL($attributes['profile'], 0, true, $default); $author_contact = Contact::getDetailsByURL($attributes['profile']); - $author_contact['addr'] = defaults($author_contact, 'addr' , Protocol::getAddrFromProfileUrl($attributes['profile'])); + $author_contact['addr'] = ($author_contact['addr'] ?? '') ?: Protocol::getAddrFromProfileUrl($attributes['profile']); - $attributes['author'] = defaults($author_contact, 'name' , $attributes['author']); - $attributes['avatar'] = defaults($author_contact, 'micro', $attributes['avatar']); - $attributes['profile'] = defaults($author_contact, 'url' , $attributes['profile']); + $attributes['author'] = ($author_contact['name'] ?? '') ?: $attributes['author']; + $attributes['avatar'] = ($author_contact['micro'] ?? '') ?: $attributes['avatar']; + $attributes['profile'] = ($author_contact['url'] ?? '') ?: $attributes['profile']; if ($attributes['avatar']) { $attributes['avatar'] = ProxyUtils::proxifyUrl($attributes['avatar'], false, ProxyUtils::SIZE_THUMB); @@ -983,16 +1034,9 @@ class BBCode extends BaseObject $text = ($is_quote_share? '
' : '') . '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ':

' . "\n" . $content; break; case 7: // statusnet/GNU Social + case 9: // ActivityPub $text = ($is_quote_share? '
' : '') . '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' @' . $author_contact['addr'] . ': ' . $content . '

' . "\n"; break; - case 9: // Google+ - $text = ($is_quote_share? '
' : '') . '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ':

' . "\n"; - $text .= '

' . $content . '

' . "\n"; - - if ($attributes['link'] != '') { - $text .= '

' . $attributes['link'] . '

'; - } - break; default: // Transforms quoted tweets in rich attachments to avoid nested tweets if (stripos(Strings::normaliseLink($attributes['link']), 'http://twitter.com/') === 0 && OEmbed::isAllowedURL($attributes['link'])) { @@ -1022,7 +1066,8 @@ class BBCode extends BaseObject private static function removePictureLinksCallback($match) { - $text = Cache::get($match[1]); + $cache_key = 'remove:' . $match[1]; + $text = Cache::get($cache_key); if (is_null($text)) { $a = self::getApp(); @@ -1038,10 +1083,10 @@ class BBCode extends BaseObject $a->getProfiler()->saveTimestamp($stamp1, "network", System::callstack()); - if (substr($curl_info["content_type"], 0, 6) == "image/") { - $text = "[url=" . $match[1] . "]" . $match[1] . "[/url]"; + if (substr($curl_info['content_type'], 0, 6) == 'image/') { + $text = "[url=" . $match[1] . ']' . $match[1] . "[/url]"; } else { - $text = "[url=" . $match[2] . "]" . $match[2] . "[/url]"; + $text = "[url=" . $match[2] . ']' . $match[2] . "[/url]"; // if its not a picture then look if its a page that contains a picture link $body = Network::fetchUrl($match[1]); @@ -1059,12 +1104,12 @@ class BBCode extends BaseObject } } - if (strtolower($attr["name"]) == "twitter:image") { - $text = "[url=" . $attr["content"] . "]" . $attr["content"] . "[/url]"; + if (strtolower($attr['name']) == 'twitter:image') { + $text = '[url=' . $attr['content'] . ']' . $attr['content'] . '[/url]'; } } } - Cache::set($match[1], $text); + Cache::set($cache_key, $text); } return $text; @@ -1072,7 +1117,7 @@ class BBCode extends BaseObject private static function expandLinksCallback($match) { - if (($match[3] == "") || ($match[2] == $match[3]) || stristr($match[2], $match[3])) { + if (($match[3] == '') || ($match[2] == $match[3]) || stristr($match[2], $match[3])) { return ($match[1] . "[url]" . $match[2] . "[/url]"); } else { return ($match[1] . $match[3] . " [url]" . $match[2] . "[/url]"); @@ -1081,57 +1126,80 @@ class BBCode extends BaseObject private static function cleanPictureLinksCallback($match) { - $text = Cache::get($match[1]); + $a = self::getApp(); - if (is_null($text)) { - $a = self::getApp(); + // When the picture link is the own photo path then we can avoid fetching the link + $own_photo_url = preg_quote(Strings::normaliseLink($a->getBaseURL()) . '/photos/'); + if (preg_match('|' . $own_photo_url . '.*?/image/|', Strings::normaliseLink($match[1]))) { + if (!empty($match[3])) { + $text = '[img=' . str_replace('-1.', '-0.', $match[2]) . ']' . $match[3] . '[/img]'; + } else { + $text = '[img]' . str_replace('-1.', '-0.', $match[2]) . '[/img]'; + } + return $text; + } - $stamp1 = microtime(true); + $cache_key = 'clean:' . $match[1]; + $text = Cache::get($cache_key); + if (!is_null($text)) { + return $text; + } - $ch = @curl_init($match[1]); - @curl_setopt($ch, CURLOPT_NOBODY, true); - @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - @curl_setopt($ch, CURLOPT_USERAGENT, $a->getUserAgent()); - @curl_exec($ch); - $curl_info = @curl_getinfo($ch); + // Only fetch the header, not the content + $stamp1 = microtime(true); - $a->getProfiler()->saveTimestamp($stamp1, "network", System::callstack()); + $ch = @curl_init($match[1]); + @curl_setopt($ch, CURLOPT_NOBODY, true); + @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + @curl_setopt($ch, CURLOPT_USERAGENT, $a->getUserAgent()); + @curl_exec($ch); + $curl_info = @curl_getinfo($ch); + + $a->getProfiler()->saveTimestamp($stamp1, "network", System::callstack()); - // if its a link to a picture then embed this picture - if (substr($curl_info["content_type"], 0, 6) == "image/") { - $text = "[img]" . $match[1] . "[/img]"; + // if its a link to a picture then embed this picture + if (substr($curl_info['content_type'], 0, 6) == 'image/') { + $text = '[img]' . $match[1] . '[/img]'; + } else { + if (!empty($match[3])) { + $text = '[img=' . $match[2] . ']' . $match[3] . '[/img]'; } else { - $text = "[img]" . $match[2] . "[/img]"; + $text = '[img]' . $match[2] . '[/img]'; + } - // if its not a picture then look if its a page that contains a picture link - $body = Network::fetchUrl($match[1]); + // if its not a picture then look if its a page that contains a picture link + $body = Network::fetchUrl($match[1]); - $doc = new DOMDocument(); - @$doc->loadHTML($body); - $xpath = new DOMXPath($doc); - $list = $xpath->query("//meta[@name]"); - foreach ($list as $node) { - $attr = []; - if ($node->attributes->length) { - foreach ($node->attributes as $attribute) { - $attr[$attribute->name] = $attribute->value; - } + $doc = new DOMDocument(); + @$doc->loadHTML($body); + $xpath = new DOMXPath($doc); + $list = $xpath->query("//meta[@name]"); + foreach ($list as $node) { + $attr = []; + if ($node->attributes->length) { + foreach ($node->attributes as $attribute) { + $attr[$attribute->name] = $attribute->value; } + } - if (strtolower($attr["name"]) == "twitter:image") { - $text = "[img]" . $attr["content"] . "[/img]"; + if (strtolower($attr['name']) == "twitter:image") { + if (!empty($match[3])) { + $text = "[img=" . $attr['content'] . "]" . $match[3] . "[/img]"; + } else { + $text = "[img]" . $attr['content'] . "[/img]"; } } } - Cache::set($match[1], $text); } + Cache::set($cache_key, $text); return $text; } public static function cleanPictureLinks($text) { - $return = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", 'self::cleanPictureLinksCallback', $text); + $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 $return; } @@ -1144,13 +1212,14 @@ class BBCode extends BaseObject * Simple HTML values meaning: * - 0: Friendica display * - 1: Unused - * - 2: Used for Google+, Windows Phone push, Friendica API + * - 2: Used for Windows Phone push, Friendica API * - 3: Used before converting to Markdown in bb2diaspora.php * - 4: Used for WordPress, Libertree (before Markdown), pump.io and tumblr * - 5: Unused * - 6: Unused * - 7: Used for dfrn, OStatus * - 8: Used for WP backlink text setting + * - 9: ActivityPub * * @param string $text * @param bool $try_oembed @@ -1173,7 +1242,7 @@ class BBCode extends BaseObject $try_oembed_callback = function ($match) { $url = $match[1]; - $title = defaults($match, 2, null); + $title = $match[2] ?? null; try { $return = OEmbed::getHTML($url, $title); @@ -1274,7 +1343,7 @@ class BBCode extends BaseObject $text = str_replace($search, $replace, $text); // removing multiplicated newlines - if (Config::get("system", "remove_multiplicated_lines")) { + if (Config::get('system', 'remove_multiplicated_lines')) { $search = ["\n\n\n", "\n ", " \n", "[/quote]\n\n", "\n[/quote]", "[/li]\n", "\n[li]", "\n[ul]", "[/ul]\n", "\n\n[share ", "[/attachment]\n", "\n[h1]", "[/h1]\n", "\n[h2]", "[/h2]\n", "\n[h3]", "[/h3]\n", "\n[h4]", "[/h4]\n", "\n[h5]", "[/h5]\n", "\n[h6]", "[/h6]\n"]; $replace = ["\n\n", "\n", "\n", "[/quote]\n", "[/quote]", "[/li]", "[li]", "[ul]", "[/ul]", "\n[share ", "[/attachment]", @@ -1285,131 +1354,11 @@ class BBCode extends BaseObject } while ($oldtext != $text); } - // Set up the parameters for a URL search string - $URLSearchString = "^\[\]"; - // Set up the parameters for a MAIL search string - $MAILSearchString = $URLSearchString; - // Handle attached links or videos $text = self::convertAttachment($text, $simple_html, $try_oembed); - // if the HTML is used to generate plain text, then don't do this search, but replace all URL of that kind to text - if (!$for_plaintext) { - $text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text); - if ($simple_html == 7) { - $text = preg_replace_callback("/\[url\]([$URLSearchString]*)\[\/url\]/ism", 'self::convertUrlForOStatusCallback', $text); - $text = preg_replace_callback("/\[url\=([$URLSearchString]*)\]([$URLSearchString]*)\[\/url\]/ism", 'self::convertUrlForOStatusCallback', $text); - } - } else { - $text = preg_replace("(\[url\]([$URLSearchString]*)\[\/url\])ism", " $1 ", $text); - $text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", 'self::removePictureLinksCallback', $text); - } - - $text = str_replace(["\r","\n"], ['
', '
'], $text); - - // Remove all hashtag addresses - if ((!$try_oembed || $simple_html) && !in_array($simple_html, [3, 7])) { - $text = preg_replace("/([#@!])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$1$3', $text); - } elseif ($simple_html == 3) { - // The ! is converted to @ since Diaspora only understands the @ - $text = preg_replace("/([@!])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", - '@$3', - $text); - } elseif ($simple_html == 7) { - $text = preg_replace("/([@!])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", - '$1$3', - $text); - } elseif (!$simple_html) { - $text = preg_replace("/([@!])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", - '$1$3', - $text); - } - - // Bookmarks in red - will be converted to bookmarks in friendica - $text = preg_replace("/#\^\[url\]([$URLSearchString]*)\[\/url\]/ism", '[bookmark=$1]$1[/bookmark]', $text); - $text = preg_replace("/#\^\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '[bookmark=$1]$2[/bookmark]', $text); - $text = preg_replace("/#\[url\=[$URLSearchString]*\]\^\[\/url\]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/i", - "[bookmark=$1]$2[/bookmark]", $text); - - if (in_array($simple_html, [2, 6, 7, 8, 9])) { - $text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", "self::expandLinksCallback", $text); - //$Text = preg_replace("/[^#@!]\[url\=([^\]]*)\](.*?)\[\/url\]/ism", ' $2 [url]$1[/url]', $Text); - $text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]',$text); - } - - if ($simple_html == 5) { - $text = preg_replace("/[^#@!]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '[url]$1[/url]', $text); - } - - // Perform URL Search - if ($try_oembed) { - $text = preg_replace_callback("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $try_oembed_callback, $text); - } - - if ($simple_html == 5) { - $text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url]$1[/url]', $text); - } else { - $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=" . System::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]"; - }, $text - ); - - $text = preg_replace_callback( - "&\[url=/people\?q\=(.*)\](.*)\[\/url\]&Usi", - function ($match) { - return "[url=" . System::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, System::baseUrl()."/display/$1", $text); - - /* Tag conversion - * Supports: - * - #[url=][/url] - * - [url=]#[/url] - */ - $text = preg_replace_callback("/(?:#\[url\=[$URLSearchString]*\]|\[url\=[$URLSearchString]*\]#)(.*?)\[\/url\]/ism", function($matches) { - return '#' - . XML::escape($matches[1]) - . ''; - }, $text); - - // We need no target="_blank" for local links - // convert links start with System::baseUrl() as local link without the target="_blank" attribute - $escapedBaseUrl = preg_quote(System::baseUrl(), '/'); - $text = preg_replace("/\[url\](".$escapedBaseUrl."[$URLSearchString]*)\[\/url\]/ism", '$1', $text); - $text = preg_replace("/\[url\=(".$escapedBaseUrl."[$URLSearchString]*)\](.*?)\[\/url\]/ism", '$2', $text); - - $text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '$1', $text); - $text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$2', $text); - - // Red compatibility, though the link can't be authenticated on Friendica - $text = preg_replace("/\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/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 - - $text = 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); - - // Perform MAIL Search - $text = preg_replace("/\[mail\]([$MAILSearchString]*)\[\/mail\]/", '$1', $text); - $text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.*?)\[\/mail\]/", '$2', $text); - // leave open the posibility of [map=something] // this is replaced in Item::prepareBody() which has knowledge of the item location - if (strpos($text, '[/map]') !== false) { $text = preg_replace_callback( "/\[map\](.*?)\[\/map\]/ism", @@ -1419,6 +1368,7 @@ class BBCode extends BaseObject $text ); } + if (strpos($text, '[map=') !== false) { $text = preg_replace_callback( "/\[map=(.*?)\]/ism", @@ -1428,6 +1378,7 @@ class BBCode extends BaseObject $text ); } + if (strpos($text, '[map]') !== false) { $text = preg_replace("/\[map\]/", '

', $text); } @@ -1463,8 +1414,14 @@ class BBCode extends BaseObject // Check for sized text // [size=50] --> font-size: 50px (with the unit). - $text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism", "$2", $text); - $text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text); + if ($simple_html != 3) { + $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", "
$1
", $text); @@ -1494,9 +1451,9 @@ class BBCode extends BaseObject $endlessloop = 0; while ((((strpos($text, "[/list]") !== false) && (strpos($text, "[list") !== false)) || - ((strpos($text, "[/ol]") !== false) && (strpos($text, "[ol]") !== false)) || - ((strpos($text, "[/ul]") !== false) && (strpos($text, "[ul]") !== false)) || - ((strpos($text, "[/li]") !== false) && (strpos($text, "[li]") !== false))) && (++$endlessloop < 20)) { + ((strpos($text, "[/ol]") !== false) && (strpos($text, "[ol]") !== false)) || + ((strpos($text, "[/ul]") !== false) && (strpos($text, "[ul]") !== false)) || + ((strpos($text, "[/li]") !== false) && (strpos($text, "[li]") !== false))) && (++$endlessloop < 20)) { $text = preg_replace("/\[list\](.*?)\[\/list\]/ism", '
    $1
', $text); $text = preg_replace("/\[list=\](.*?)\[\/list\]/ism", '
    $1
', $text); $text = preg_replace("/\[list=1\](.*?)\[\/list\]/ism", '
    $1
', $text); @@ -1519,33 +1476,37 @@ class BBCode extends BaseObject $text = str_replace('[hr]', '
', $text); + if (!$for_plaintext) { + // Autolinker for isolated URLs + $text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text); + } + // This is actually executed in Item::prepareBody() + $nosmile = strpos($text, '[nosmile]') !== false; $text = str_replace('[nosmile]', '', $text); // Check for font change text $text = preg_replace("/\[font=(.*?)\](.*?)\[\/font\]/sm", "$2", $text); // Declare the format for [spoiler] layout - $SpoilerLayout = '
$1
'; + $SpoilerLayout = '
' . L10n::t('Click to open/close') . '$1
'; // Check for [spoiler] text // handle nested quotes $endlessloop = 0; while ((strpos($text, "[/spoiler]") !== false) && (strpos($text, "[spoiler]") !== false) && (++$endlessloop < 20)) { - $text = preg_replace("/\[spoiler\](.*?)\[\/spoiler\]/ism", "$SpoilerLayout", $text); + $text = preg_replace("/\[spoiler\](.*?)\[\/spoiler\]/ism", $SpoilerLayout, $text); } - // Check for [spoiler=Author] text - - $t_wrote = L10n::t('$1 wrote:'); + // Check for [spoiler=Title] text // handle nested quotes $endlessloop = 0; while ((strpos($text, "[/spoiler]")!== false) && (strpos($text, "[spoiler=") !== false) && (++$endlessloop < 20)) { $text = preg_replace("/\[spoiler=[\"\']*(.*?)[\"\']*\](.*?)\[\/spoiler\]/ism", - "
" . $t_wrote . "
$2
", - $text); + '
$1$2
', + $text); } // Declare the format for [quote] layout @@ -1566,8 +1527,8 @@ class BBCode extends BaseObject $endlessloop = 0; while ((strpos($text, "[/quote]")!== false) && (strpos($text, "[quote=") !== false) && (++$endlessloop < 20)) { $text = preg_replace("/\[quote=[\"\']*(.*?)[\"\']*\](.*?)\[\/quote\]/ism", - "

" . $t_wrote . "

$2
", - $text); + "

" . $t_wrote . "

$2
", + $text); } @@ -1588,7 +1549,7 @@ class BBCode extends BaseObject $text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '', $text); $text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '', $text); - $text = preg_replace_callback("/\[img\=([$URLSearchString]*)\](.*?)\[\/img\]/ism", + $text = preg_replace_callback("/\[img\=(.*?)\](.*?)\[\/img\]/ism", function ($matches) use ($simple_html) { $matches[1] = self::proxyUrl($matches[1], $simple_html); $matches[2] = htmlspecialchars($matches[2], ENT_COMPAT); @@ -1614,14 +1575,6 @@ class BBCode extends BaseObject $text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '' . L10n::t('Image/photo') . '', $text); $text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '' . L10n::t('Image/photo') . '', $text); - // Shared content - $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", '
' . L10n::t('Encrypted content') . '
', $text); $text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '
' . L10n::t('Encrypted content') . '
', $text); //$Text = preg_replace("/\[crypt=(.*?)\](.*?)\[\/crypt\]/ism", '
' . L10n::t('Encrypted content') . '
', $Text); @@ -1635,9 +1588,9 @@ class BBCode extends BaseObject $text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text); } else { $text = preg_replace("/\[video\](.*?)\[\/video\]/ism", - '$1', $text); + '$1', $text); $text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", - '$1', $text); + '$1', $text); } // html5 video and audio @@ -1664,7 +1617,7 @@ class BBCode extends BaseObject $text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '', $text); } else { $text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", - 'https://www.youtube.com/watch?v=$1', $text); + 'https://www.youtube.com/watch?v=$1', $text); } if ($try_oembed) { @@ -1679,7 +1632,7 @@ class BBCode extends BaseObject $text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '', $text); } else { $text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", - 'https://vimeo.com/$1', $text); + 'https://vimeo.com/$1', $text); } // oembed tag @@ -1706,10 +1659,122 @@ class BBCode extends BaseObject } // Replace non graphical smilies for external posts - if ($simple_html) { + if (!$nosmile && !$for_plaintext) { $text = Smilies::replace($text); } + if (!$for_plaintext) { + if (in_array($simple_html, [7, 9])) { + $text = preg_replace_callback("/\[url\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text); + $text = preg_replace_callback("/\[url\=(.*?)\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text); + } + } else { + $text = preg_replace("(\[url\](.*?)\[\/url\])ism", " $1 ", $text); + $text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", 'self::removePictureLinksCallback', $text); + } + + $text = str_replace(["\r","\n"], ['
', '
'], $text); + + // Remove all hashtag addresses + if ((!$try_oembed || $simple_html) && !in_array($simple_html, [3, 7, 9])) { + $text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text); + } elseif ($simple_html == 3) { + // The ! is converted to @ since Diaspora only understands the @ + $text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", + '@$3', + $text); + } elseif (in_array($simple_html, [7, 9])) { + $text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", + '$1$3', + $text); + } elseif (!$simple_html) { + $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, [2, 6, 7, 8])) { + $text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", "self::expandLinksCallback", $text); + //$Text = preg_replace("/[^#@!]\[url\=([^\]]*)\](.*?)\[\/url\]/ism", ' $2 [url]$1[/url]', $Text); + $text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]',$text); + } + + if ($simple_html == 5) { + $text = preg_replace("/[^#@!]\[url\=(.*?)\](.*?)\[\/url\]/ism", '[url]$1[/url]', $text); + } + + // Perform URL Search + if ($try_oembed) { + $text = preg_replace_callback("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $try_oembed_callback, $text); + } + + if ($simple_html == 5) { + $text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url]$1[/url]', $text); + } else { + $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=" . System::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]"; + }, $text + ); + + $text = preg_replace_callback( + "&\[url=/people\?q\=(.*)\](.*)\[\/url\]&Usi", + function ($match) { + return "[url=" . System::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, System::baseUrl()."/display/$1", $text); + + /* Tag conversion + * Supports: + * - #[url=][/url] + * - [url=]#[/url] + */ + $text = preg_replace_callback("/(?:#\[url\=.*?\]|\[url\=.*?\]#)(.*?)\[\/url\]/ism", function($matches) { + return '#' + . XML::escape($matches[1]) + . ''; + }, $text); + + // We need no target="_blank" for local links + // convert links start with System::baseUrl() as local link without the target="_blank" attribute + $escapedBaseUrl = preg_quote(System::baseUrl(), '/'); + $text = preg_replace("/\[url\](".$escapedBaseUrl.".*?)\[\/url\]/ism", '$1', $text); + $text = preg_replace("/\[url\=(".$escapedBaseUrl.".*?)\](.*?)\[\/url\]/ism", '$2', $text); + + $text = preg_replace("/\[url\](.*?)\[\/url\]/ism", '$1', $text); + $text = preg_replace("/\[url\=(.*?)\](.*?)\[\/url\]/ism", '$2', $text); + + // Red compatibility, though the link can't be authenticated on Friendica + $text = preg_replace("/\[zrl\=(.*?)\](.*?)\[\/zrl\]/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 + + $text = 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); + + // Perform MAIL Search + $text = preg_replace("/\[mail\](.*?)\[\/mail\]/", '$1', $text); + $text = preg_replace("/\[mail\=(.*?)\](.*?)\[\/mail\]/", '$2', $text); + // Unhide all [noparse] contained bbtags unspacefying them // and triming the [noparse] tag. @@ -1728,21 +1793,36 @@ class BBCode extends BaseObject $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', 'redir', 'cid']; + $allowed_src_protocols = ['//', 'http://', 'https://', '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="' . L10n::t('Invalid source protocol') . '">', $text); // sanitize href attributes (only whitelisted protocols URLs) // default value for backward compatibility - $allowed_link_protocols = Config::get('system', 'allowed_link_protocols', ['ftp', 'mailto', 'gopher', 'cid']); + $allowed_link_protocols = Config::get('system', 'allowed_link_protocols', []); // Always allowed protocol even if config isn't set or not including it - $allowed_link_protocols[] = 'http'; + $allowed_link_protocols[] = '//'; + $allowed_link_protocols[] = 'http://'; + $allowed_link_protocols[] = 'https://'; $allowed_link_protocols[] = '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="' . L10n::t('Invalid link protocol') . '">', $text); + // Shared content + $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); + } + ); + if ($saved_image) { $text = self::interpolateSavedImagesIntoItemBody($text, $saved_image); } @@ -1762,7 +1842,7 @@ class BBCode extends BaseObject // Clean up the HTML by loading and saving the HTML with the DOM. // Bad structured html can break a whole page. // For performance reasons do it only with activated item cache or at export. - if (!$try_oembed || (get_itemcachepath() != "")) { + if (!$try_oembed || (get_itemcachepath() != '')) { $doc = new DOMDocument(); $doc->preserveWhiteSpace = false; @@ -1770,10 +1850,10 @@ class BBCode extends BaseObject $doctype = ''; $encoding = ''; - @$doc->loadHTML($encoding.$doctype."".$text.""); + @$doc->loadHTML($encoding . $doctype . '' . $text . ''); $doc->encoding = 'UTF-8'; $text = $doc->saveHTML(); - $text = str_replace(["", "", $doctype, $encoding], ["", "", "", ""], $text); + $text = str_replace(['', '', $doctype, $encoding], ['', '', '', ''], $text); $text = str_replace('
', '', $text); @@ -1813,9 +1893,9 @@ class BBCode extends BaseObject * @param string $addon The addon for which the abstract is meant for * @return string The abstract */ - public static function getAbstract($text, $addon = "") + public static function getAbstract($text, $addon = '') { - $abstract = ""; + $abstract = ''; $abstracts = []; $addon = strtolower($addon); @@ -1829,7 +1909,7 @@ class BBCode extends BaseObject $abstract = $abstracts[$addon]; } - if ($abstract == "" && preg_match("/\[abstract\](.*?)\[\/abstract\]/ism", $text, $result)) { + if ($abstract == '' && preg_match("/\[abstract\](.*?)\[\/abstract\]/ism", $text, $result)) { $abstract = $result[1]; } @@ -1905,7 +1985,7 @@ class BBCode extends BaseObject // Add all tags that maybe were removed if (preg_match_all("/#\[url\=([$url_search_string]*)\](.*?)\[\/url\]/ism", $original_text, $tags)) { - $tagline = ""; + $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)) { @@ -1923,7 +2003,7 @@ class BBCode extends BaseObject // 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); + $text = str_replace(['
'], ['
'], $text); $stamp1 = microtime(true);