X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FContent%2FText%2FBBCode.php;h=a38d2de7856ec39f7c24c38c36d7a5af6c92eee2;hb=7e6ba0b4d271735bbf7474fb75a7cf43b787b678;hp=949f9d9cf085919b0e14f007e1244ea5a0d76dae;hpb=61deaec4be85cb44d16bb32d59d49dc7f559cf72;p=friendica.git diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index 949f9d9cf0..a38d2de785 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -36,14 +36,10 @@ use Friendica\Core\Renderer; use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Event; -use Friendica\Model\Photo; use Friendica\Model\Post; use Friendica\Model\Tag; use Friendica\Network\HTTPClient\Client\HttpClientAccept; use Friendica\Network\HTTPClient\Client\HttpClientOptions; -use Friendica\Object\Image; -use Friendica\Protocol\Activity; -use Friendica\Util\Images; use Friendica\Util\Map; use Friendica\Util\ParseUrl; use Friendica\Util\Proxy; @@ -74,77 +70,6 @@ class BBCode const PREVIEW_LARGE = 2; const PREVIEW_SMALL = 3; - /** - * Fetches attachment data that were generated the old way - * - * @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 - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - */ - private static function getOldAttachmentData(string $body): array - { - $post = []; - - // Simplify image codes - $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body); - - 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'])) { - continue; - } - - $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]))); - } else { - $post['text'] = trim(str_replace($data[0], '', $body)); - $post['after'] = ''; - } - - $attacheddata = $data[2]; - - if (preg_match("/\[img\](.*?)\[\/img\]/ism", $attacheddata, $matches)) { - - $picturedata = Images::getInfoFromURLCached($matches[1]); - - if ($picturedata) { - if (($picturedata[0] >= 500) && ($picturedata[0] >= $picturedata[1])) { - $post['image'] = $matches[1]; - } else { - $post['preview'] = $matches[1]; - } - } - } - - 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\=(.*?)\](.*?)\[\/url\]/ism", $attacheddata, $matches)) { - $post['url'] = $matches[1]; - } - - // Search for description - if (preg_match("/\[quote\](.*?)\[\/quote\]/ism", $attacheddata, $matches)) { - $post['description'] = $matches[1]; - } - } - } - return $post; - } - /** * Fetches attachment data that were generated with the "attachment" element * @@ -178,7 +103,7 @@ class BBCode if (!preg_match("/(.*)\[attachment(.*?)\](.*?)\[\/attachment\](.*)/ism", $body, $match)) { DI::profiler()->stopRecording(); - return self::getOldAttachmentData($body); + return []; } $attributes = $match[2]; @@ -253,183 +178,6 @@ class BBCode return $data; } - public static function getAttachedData(string $body, array $item = []): array - { - /* - - text: - - type: link, video, photo - - title: - - url: - - image: - - description: - - (thumbnail) - */ - - DI::profiler()->startRecording('rendering'); - $has_title = !empty($item['title']); - $plink = $item['plink'] ?? ''; - $post = self::getAttachmentData($body); - - // 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 ($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 (!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 ($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' => '']; - } - } - } - - if (!isset($post['type'])) { - $post['text'] = $body; - } - - // 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)) { - if ((count($pictures) == 1) && !$has_title && !Photo::isLocal($pictures[0][2])) { - if (!empty($item['object-type']) && ($item['object-type'] == Activity\ObjectType::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]); - } - - // 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/')) { - $data = ParseUrl::getSiteinfo($pictures[0][1]); - } - - 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['preview'] = $pictures[0][2]; - $post['text'] = trim(str_replace($pictures[0][0], '', $post['text'])); - } else { - $imgdata = Images::getInfoFromURLCached($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], '', $post['text'])); - } - } - } elseif (count($pictures) > 0) { - if (count($pictures) > 4) { - $post['type'] = 'link'; - $post['url'] = $plink; - } else { - $post['type'] = 'photo'; - } - - $post['image'] = $pictures[0][2]; - - foreach ($pictures as $picture) { - $post['text'] = trim(str_replace($picture[0], '', $post['text'])); - } - } - } elseif (preg_match_all("(\[img\](.*?)\[\/img\])ism", $post['text'], $pictures, PREG_SET_ORDER)) { - if ($has_title) { - $post['type'] = 'link'; - $post['url'] = $plink; - } else { - $post['type'] = 'photo'; - } - - $post['image'] = $pictures[0][1]; - foreach ($pictures as $picture) { - $post['text'] = trim(str_replace($picture[0], '', $post['text'])); - } - } - - // Test for the external links - 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['url'] = $links[0][1]; - } - - // Simplify "video" element - $post['text'] = preg_replace('(\[video.*?\ssrc\s?=\s?([^\s\]]+).*?\].*?\[/video\])ism', '[video]$1[/video]', $post['text']); - - // Now count the number of external media links - 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); - - // 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; - } - - if (!isset($post['type'])) { - $post['type'] = 'text'; - } - - if (($post['type'] == 'photo') && empty($post['images']) && !empty($post['remote_images'])) { - $post['images'] = $post['remote_images']; - $post['image'] = $post['images'][0]['url']; - if (!empty($post['images']) && !empty($post['images'][0]['description'])) { - $post['image_description'] = $post['images'][0]['description']; - } - } - unset($post['remote_images']); - } elseif (isset($post['url']) && ($post['type'] == 'video')) { - $data = ParseUrl::getSiteinfoCached($post['url']); - - if (isset($data['images'][0])) { - $post['image'] = $data['images'][0]['src']; - } - } elseif (preg_match_all("#\[url=([^\]]+?)\]\s*\[img\]([^\[]+?)\[/img\]\s*\[/url\]#ism", $post['text'], $pictures, PREG_SET_ORDER)) { - foreach ($pictures as $picture) { - $post['text'] = trim(str_replace($picture[0], '', $post['text'])); - } - } - - DI::profiler()->stopRecording(); - return $post; - } - /** * Remove [attachment] BBCode and replaces it with a regular [url] * @@ -439,7 +187,8 @@ class BBCode */ public static function replaceAttachment(string $body, bool $no_link_desc = false): string { - return preg_replace_callback("/\s*\[attachment (.*?)\](.*?)\[\/attachment\]\s*/ism", + return preg_replace_callback( + "/\s*\[attachment (.*?)\](.*?)\[\/attachment\]\s*/ism", function ($match) use ($body, $no_link_desc) { $attach_data = self::getAttachmentData($match[0]); if (empty($attach_data['url'])) { @@ -451,7 +200,9 @@ class BBCode } else { return " \n[url=" . $attach_data['url'] . ']' . $attach_data['title'] . "[/url]\n"; } - }, $body); + }, + $body + ); } /** @@ -956,8 +707,11 @@ class BBCode // We're depending on the property of 'foreach' (specified on the PHP website) that // it loops over the array starting from the first element and going sequentially // to the last element - $newbody = str_replace('[$#saved_image' . $cnt . '#$]', - '' . DI::l10n()->t('Image/photo') . '', $newbody); + $newbody = str_replace( + '[$#saved_image' . $cnt . '#$]', + '' . DI::l10n()->t('Image/photo') . '', + $newbody + ); $cnt++; } @@ -1112,8 +866,7 @@ class BBCode $attributes[$field] = html_entity_decode($matches[2] ?? '', ENT_QUOTES, 'UTF-8'); } - $img_str = ' $value) { if (!empty($value)) { $img_str .= ' ' . $key . '="' . htmlspecialchars($value, ENT_COMPAT) . '"'; @@ -1150,13 +903,13 @@ class BBCode switch ($simplehtml) { case self::MASTODON_API: case self::TWITTER_API: - $text = ($is_quote_share? '
' : '') . - '' . html_entity_decode('♲', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ":
\n" . - '
' . $content . '
'; + $text = ($is_quote_share ? '
' : '') . + '' . html_entity_decode('♲', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ":
\n" . + '
' . $content . '
'; break; case self::DIASPORA: if (stripos(Strings::normaliseLink($attributes['link']), 'http://twitter.com/') === 0) { - $text = ($is_quote_share? '
' : '') . '

' . $attributes['link'] . '

' . "\n"; + $text = ($is_quote_share ? '
' : '') . '

' . $attributes['link'] . '

' . "\n"; } else { $headline = '

♲ ' . $attributes['author'] . ':

' . "\n"; @@ -1164,7 +917,7 @@ class BBCode $headline = '

♲ ' . $attributes['author'] . ' - ' . $attributes['posted'] . ' GMT

' . "\n"; } - $text = ($is_quote_share? '
' : '') . $headline . '
' . trim($content) . '
' . "\n"; + $text = ($is_quote_share ? '
' : '') . $headline . '
' . trim($content) . '
' . "\n"; if (empty($attributes['posted']) && !empty($attributes['link'])) { $text .= '

[Source]

' . "\n"; @@ -1177,18 +930,18 @@ class BBCode $headline .= DI::l10n()->t('%2$s %3$s', $attributes['link'], $mention, $attributes['posted']); $headline .= ':

' . "\n"; - $text = ($is_quote_share? '
' : '') . $headline . '
' . trim($content) . '
' . "\n"; + $text = ($is_quote_share ? '
' : '') . $headline . '
' . trim($content) . '
' . "\n"; break; case self::OSTATUS: - $text = ($is_quote_share? '
' : '') . '

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

' . "\n"; + $text = ($is_quote_share ? '
' : '') . '

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

' . "\n"; break; case self::ACTIVITYPUB: $author = '@' . $author_contact['addr'] . ':'; $text = '
' . html_entity_decode('♲', ENT_QUOTES, 'UTF-8') . ' ' . $author . '
' . $content . '
' . "\n"; break; default: - $text = ($is_quote_share? "\n" : ''); + $text = ($is_quote_share ? "\n" : ''); $contact = Contact::getByURL($attributes['profile'], false, ['network']); $network = $contact['network'] ?? Protocol::PHANTOM; @@ -1500,8 +1253,7 @@ class BBCode * $match[1] = $url * $match[2] = $title or absent */ - $try_oembed_callback = function (array $match) - { + $try_oembed_callback = function (array $match) { $url = $match[1]; $title = $match[2] ?? ''; @@ -1554,18 +1306,24 @@ class BBCode } // Remove linefeeds inside of the table elements. See issue #6799 - $search = ["\n[th]", "[th]\n", " [th]", "\n[/th]", "[/th]\n", "[/th] ", + $search = [ + "\n[th]", "[th]\n", " [th]", "\n[/th]", "[/th]\n", "[/th] ", "\n[td]", "[td]\n", " [td]", "\n[/td]", "[/td]\n", "[/td] ", "\n[tr]", "[tr]\n", " [tr]", "[tr] ", "\n[/tr]", "[/tr]\n", " [/tr]", "[/tr] ", "\n[hr]", "[hr]\n", " [hr]", "[hr] ", "\n[attachment ", " [attachment ", "\n[/attachment]", "[/attachment]\n", " [/attachment]", "[/attachment] ", - "[table]\n", "[table] ", " [table]", "\n[/table]", " [/table]", "[/table] "]; - $replace = ["[th]", "[th]", "[th]", "[/th]", "[/th]", "[/th]", + "[table]\n", "[table] ", " [table]", "\n[/table]", " [/table]", "[/table] ", + " \n", "\t\n", "[/li]\n", "\n[li]", "\n[*]", + ]; + $replace = [ + "[th]", "[th]", "[th]", "[/th]", "[/th]", "[/th]", "[td]", "[td]", "[td]", "[/td]", "[/td]", "[/td]", "[tr]", "[tr]", "[tr]", "[tr]", "[/tr]", "[/tr]", "[/tr]", "[/tr]", "[hr]", "[hr]", "[hr]", "[hr]", "[attachment ", "[attachment ", "[/attachment]", "[/attachment]", "[/attachment]", "[/attachment]", - "[table]", "[table]", "[table]", "[/table]", "[/table]", "[/table]"]; + "[table]", "[table]", "[table]", "[/table]", "[/table]", "[/table]", + "\n", "\n", "[/li]", "[li]", "[*]", + ]; do { $oldtext = $text; $text = str_replace($search, $replace, $text); @@ -1581,10 +1339,14 @@ class BBCode // removing multiplicated newlines if (DI::config()->get('system', 'remove_multiplicated_lines')) { - $search = ["\n\n\n", "\n ", " \n", "[/quote]\n\n", "\n[/quote]", "[/li]\n", "\n[li]", "\n[*]", "\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]", - "[h1]", "[/h1]", "[h2]", "[/h2]", "[h3]", "[/h3]", "[h4]", "[/h4]", "[h5]", "[/h5]", "[h6]", "[/h6]"]; + $search = [ + "\n\n\n", "[/quote]\n\n", "\n[/quote]", "\n[ul]", "[/ul]\n", "\n[ol]", "[/ol]\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", "[/quote]\n", "[/quote]", "[ul]", "[/ul]", "[ol]", "[/ol]", "\n[share ", "[/attachment]", + "[h1]", "[/h1]", "[h2]", "[/h2]", "[h3]", "[/h3]", "[h4]", "[/h4]", "[h5]", "[/h5]", "[h6]", "[/h6]" + ]; do { $oldtext = $text; $text = str_replace($search, $replace, $text); @@ -1638,12 +1400,12 @@ class BBCode } // Check for headers - $text = preg_replace("(\[h1\](.*?)\[\/h1\])ism", '

$1

', $text); - $text = preg_replace("(\[h2\](.*?)\[\/h2\])ism", '

$1

', $text); - $text = preg_replace("(\[h3\](.*?)\[\/h3\])ism", '

$1

', $text); - $text = preg_replace("(\[h4\](.*?)\[\/h4\])ism", '

$1

', $text); - $text = preg_replace("(\[h5\](.*?)\[\/h5\])ism", '
$1
', $text); - $text = preg_replace("(\[h6\](.*?)\[\/h6\])ism", '
$1
', $text); + $text = preg_replace("(\[h1\](.*?)\[\/h1\])ism", '

$1

', $text); + $text = preg_replace("(\[h2\](.*?)\[\/h2\])ism", '

$1

', $text); + $text = preg_replace("(\[h3\](.*?)\[\/h3\])ism", '

$1

', $text); + $text = preg_replace("(\[h4\](.*?)\[\/h4\])ism", '

$1

', $text); + $text = preg_replace("(\[h5\](.*?)\[\/h5\])ism", '

$1

', $text); + $text = preg_replace("(\[h6\](.*?)\[\/h6\])ism", '

$1

', $text); // Check for paragraph $text = preg_replace("(\[p\](.*?)\[\/p\])ism", '

$1

', $text); @@ -1696,36 +1458,37 @@ class BBCode // @deprecated since 2021.12, left for backward-compatibility reasons $text = preg_replace("(\[class=(.*?)\](.*?)\[\/class\])ism", '$2', $text); // Add HTML new lines + $text = str_replace("\n\n", '

', $text); $text = str_replace("\n", '
', $text); // handle nested lists $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)) { - $text = preg_replace("/\[list\](.*?)\[\/list\]/ism", '

', $text); - $text = preg_replace("/\[list=\](.*?)\[\/list\]/ism", '', $text); - $text = preg_replace("/\[list=1\](.*?)\[\/list\]/ism", '', $text); - $text = preg_replace("/\[list=((?-i)i)\](.*?)\[\/list\]/ism", '', $text); - $text = preg_replace("/\[list=((?-i)I)\](.*?)\[\/list\]/ism", '', $text); - $text = preg_replace("/\[list=((?-i)a)\](.*?)\[\/list\]/ism", '', $text); - $text = preg_replace("/\[list=((?-i)A)\](.*?)\[\/list\]/ism", '', $text); - $text = preg_replace("/\[ul\](.*?)\[\/ul\]/ism", '', $text); - $text = preg_replace("/\[ol\](.*?)\[\/ol\]/ism", '', $text); + ((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", '

', $text); + $text = preg_replace("/\[list=\](.*?)\[\/list\]/ism", '

', $text); + $text = preg_replace("/\[list=1\](.*?)\[\/list\]/ism", '

', $text); + $text = preg_replace("/\[list=((?-i)i)\](.*?)\[\/list\]/ism", '

', $text); + $text = preg_replace("/\[list=((?-i)I)\](.*?)\[\/list\]/ism", '

', $text); + $text = preg_replace("/\[list=((?-i)a)\](.*?)\[\/list\]/ism", '

', $text); + $text = preg_replace("/\[list=((?-i)A)\](.*?)\[\/list\]/ism", '

', $text); + $text = preg_replace("/\[ul\](.*?)\[\/ul\]/ism", '

', $text); + $text = preg_replace("/\[ol\](.*?)\[\/ol\]/ism", '

    $1

', $text); $text = preg_replace("/\[li\](.*?)\[\/li\]/ism", '

  • $1
  • ', $text); } $text = preg_replace("/\[th\](.*?)\[\/th\]/sm", '$1', $text); $text = preg_replace("/\[td\](.*?)\[\/td\]/sm", '$1', $text); $text = preg_replace("/\[tr\](.*?)\[\/tr\]/sm", '$1', $text); - $text = preg_replace("/\[table\](.*?)\[\/table\]/sm", '$1
    ', $text); + $text = preg_replace("/\[table\](.*?)\[\/table\]/sm", '

    $1

    ', $text); - $text = preg_replace("/\[table border=1\](.*?)\[\/table\]/sm", '$1
    ', $text); - $text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '$1
    ', $text); + $text = preg_replace("/\[table border=1\](.*?)\[\/table\]/sm", '

    $1

    ', $text); + $text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '

    $1

    ', $text); - $text = str_replace('[hr]', '


    ', $text); + $text = str_replace('[hr]', '


    ', $text); if (!$for_plaintext) { $text = self::performWithEscapedTags($text, ['url', 'img', 'audio', 'video', 'youtube', 'vimeo', 'share', 'attachment', 'iframe', 'bookmark'], function ($text) { @@ -1750,14 +1513,16 @@ class BBCode // handle nested quotes $endlessloop = 0; - while ((strpos($text, "[/spoiler]")!== false) && (strpos($text, "[spoiler=") !== false) && (++$endlessloop < 20)) { - $text = preg_replace("/\[spoiler=[\"\']*(.*?)[\"\']*\](.*?)\[\/spoiler\]/ism", + while ((strpos($text, "[/spoiler]") !== false) && (strpos($text, "[spoiler=") !== false) && (++$endlessloop < 20)) { + $text = preg_replace( + "/\[spoiler=[\"\']*(.*?)[\"\']*\](.*?)\[\/spoiler\]/ism", '

    $1$2
    ', - $text); + $text + ); } // Declare the format for [quote] layout - $QuoteLayout = '
    $1
    '; + $QuoteLayout = '

    $1

    '; // Check for [quote] text // handle nested quotes @@ -1772,10 +1537,12 @@ class BBCode // handle nested quotes $endlessloop = 0; - while ((strpos($text, "[/quote]")!== false) && (strpos($text, "[quote=") !== false) && (++$endlessloop < 20)) { - $text = preg_replace("/\[quote=[\"\']*(.*?)[\"\']*\](.*?)\[\/quote\]/ism", - "

    " . $t_wrote . "

    $2
    ", - $text); + while ((strpos($text, "[/quote]") !== false) && (strpos($text, "[quote=") !== false) && (++$endlessloop < 20)) { + $text = preg_replace( + "/\[quote=[\"\']*(.*?)[\"\']*\](.*?)\[\/quote\]/ism", + "

    " . $t_wrote . "

    $2
    ", + $text + ); } @@ -1796,18 +1563,25 @@ class BBCode $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\=(.*?)\](.*?)\[\/img\]/ism", + $text = preg_replace_callback( + "/\[[iz]mg\=(.*?)\](.*?)\[\/[iz]mg\]/ism", function ($matches) use ($simple_html, $uriid) { $matches[1] = self::proxyUrl($matches[1], $simple_html, $uriid); - $matches[2] = htmlspecialchars($matches[2], ENT_COMPAT); - return '' . $matches[2] . ''; + $alt = htmlspecialchars($matches[2], ENT_COMPAT); + // Fix for Markdown problems with Diaspora, see issue #12701 + if (($simple_html != self::DIASPORA) || strpos($matches[2], '"') === false) { + return '' . $alt . ''; + } else { + return '' . $alt . ''; + } }, - $text); + $text + ); // Images // [img]pathtoimage[/img] $text = preg_replace_callback( - "/\[img\](.*?)\[\/img\]/ism", + "/\[[iz]mg\](.*?)\[\/[iz]mg\]/ism", function ($matches) use ($simple_html, $uriid) { if (strpos($matches[1], "data:image/") === 0) { return $matches[0]; @@ -1824,8 +1598,8 @@ class BBCode $text = self::convertImages($text, $simple_html, $uriid); - $text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism", '
    ' . DI::l10n()->t('Encrypted content') . '
    ', $text); - $text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '
    ' . DI::l10n()->t('Encrypted content') . '
    ', $text); + $text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism", '
    ' . DI::l10n()->t('Encrypted content') . '
    ', $text); + $text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '
    ' . DI::l10n()->t('Encrypted content') . '
    ', $text); //$text = preg_replace("/\[crypt=(.*?)\](.*?)\[\/crypt\]/ism", '
    ' . DI::l10n()->t('Encrypted content') . '
    ', $text); // Simplify "video" element @@ -1833,20 +1607,32 @@ class BBCode if ($try_oembed) { // html5 video and audio - $text = preg_replace("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4).*?)\[\/video\]/ism", - '', $text); + $text = preg_replace( + "/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4).*?)\[\/video\]/ism", + '', + $text + ); $text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", $try_oembed_callback, $text); $text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text); - $text = preg_replace("/\[video\](.*?)\[\/video\]/ism", - '$1', $text); + $text = preg_replace( + "/\[video\](.*?)\[\/video\]/ism", + '$1', + $text + ); $text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '', $text); } else { - $text = preg_replace("/\[video\](.*?)\[\/video\]/ism", - '$1', $text); - $text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", - '$1', $text); + $text = preg_replace( + "/\[video\](.*?)\[\/video\]/ism", + '$1', + $text + ); + $text = preg_replace( + "/\[audio\](.*?)\[\/audio\]/ism", + '$1', + $text + ); } // Backward compatibility, [iframe] support has been removed in version 2020.12 @@ -1868,8 +1654,11 @@ class BBCode if ($try_oembed) { $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); + $text = preg_replace( + "/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", + 'https://www.youtube.com/watch?v=$1', + $text + ); } if ($try_oembed) { @@ -1883,8 +1672,11 @@ class BBCode if ($try_oembed) { $text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '', $text); } else { - $text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", - 'https://vimeo.com/$1', $text); + $text = preg_replace( + "/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", + 'https://vimeo.com/$1', + $text + ); } // oembed tag @@ -1920,27 +1712,39 @@ class BBCode // Handle mentions and hashtag links if ($simple_html == self::DIASPORA) { // The ! is converted to @ since Diaspora only understands the @ - $text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", + $text = preg_replace( + "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '@$3', - $text); + $text + ); } elseif (in_array($simple_html, [self::OSTATUS, self::ACTIVITYPUB])) { - $text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", + $text = preg_replace( + "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', - $text); - $text = preg_replace("/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism", + $text + ); + $text = preg_replace( + "/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism", '', - $text); + $text + ); } elseif (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::TWITTER_API])) { - $text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", + $text = preg_replace( + "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', - $text); + $text + ); } elseif ($simple_html == self::MASTODON_API) { - $text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", + $text = preg_replace( + "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', - $text); - $text = preg_replace("/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism", + $text + ); + $text = preg_replace( + "/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism", '', - $text); + $text + ); } else { $text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text); } @@ -1958,8 +1762,11 @@ class BBCode // 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); + $text = preg_replace( + "/#\[url\=.*?\]\^\[\/url\]\[url\=(.*?)\](.*?)\[\/url\]/i", + "[bookmark=$1]$2[/bookmark]", + $text + ); if (in_array($simple_html, [self::OSTATUS, self::TWITTER])) { $text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", [self::class, 'expandLinksCallback'], $text); @@ -1979,20 +1786,22 @@ class BBCode "&\[url=/?posts/([^\[\]]*)\](.*)\[\/url\]&Usi", function ($match) { return "[url=" . DI::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]"; - }, $text + }, + $text ); $text = preg_replace_callback( "&\[url=/people\?q\=(.*)\](.*)\[\/url\]&Usi", function ($match) { return "[url=" . DI::baseUrl() . "/search?search=%40" . $match[1] . "]" . $match[2] . "[/url]"; - }, $text + }, + $text ); // Server independent link to posts and comments // See issue: https://github.com/diaspora/diaspora_federation/issues/75 $expression = "=diaspora://.*?/post/([0-9A-Za-z\-_@.:]{15,254}[0-9A-Za-z])=ism"; - $text = preg_replace($expression, DI::baseUrl()."/display/$1", $text); + $text = preg_replace($expression, DI::baseUrl() . "/display/$1", $text); /* Tag conversion * Supports: @@ -2000,7 +1809,7 @@ class BBCode * - [url=]#[/url] */ self::performWithEscapedTags($text, ['url', 'share'], function ($text) use ($simple_html) { - $text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function($matches) use ($simple_html) { + $text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function ($matches) use ($simple_html) { if ($simple_html == self::ACTIVITYPUB) { return '#' @@ -2017,8 +1826,8 @@ class BBCode // We need no target="_blank" rel="noopener noreferrer" for local links // convert links start with DI::baseUrl() as local link without the target="_blank" rel="noopener noreferrer" attribute $escapedBaseUrl = preg_quote(DI::baseUrl(), '/'); - $text = preg_replace("/\[url\](".$escapedBaseUrl.".*?)\[\/url\]/ism", '$1', $text); - $text = preg_replace("/\[url\=(".$escapedBaseUrl.".*?)\](.*?)\[\/url\]/ism", '$2', $text); + $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); @@ -2049,10 +1858,15 @@ class BBCode // sanitizes src attributes (http and redir URLs for displaying in a web page, cid used for inline images in emails) $allowed_src_protocols = ['//', 'http://', 'https://', 'contact/redir/', 'cid:']; - array_walk($allowed_src_protocols, function(&$value) { $value = preg_quote($value, '#');}); + array_walk($allowed_src_protocols, function (&$value) { + $value = preg_quote($value, '#'); + }); - $text = preg_replace('#<([^>]*?)(src)="(?!' . implode('|', $allowed_src_protocols) . ')(.*?)"(.*?)>#ism', - '<$1$2=""$4 data-original-src="$3" class="invalid-src" title="' . DI::l10n()->t('Invalid source protocol') . '">', $text); + $text = preg_replace( + '#<([^>]*?)(src)="(?!' . implode('|', $allowed_src_protocols) . ')(.*?)"(.*?)>#ism', + '<$1$2=""$4 data-original-src="$3" class="invalid-src" title="' . DI::l10n()->t('Invalid source protocol') . '">', + $text + ); // sanitize href attributes (only allowlisted protocols URLs) // default value for backward compatibility @@ -2064,7 +1878,9 @@ class BBCode $allowed_link_protocols[] = 'https://'; $allowed_link_protocols[] = 'contact/redir/'; - array_walk($allowed_link_protocols, function(&$value) { $value = preg_quote($value, '#');}); + array_walk($allowed_link_protocols, function (&$value) { + $value = preg_quote($value, '#'); + }); $regex = '#<([^>]*?)(href)="(?!' . implode('|', $allowed_link_protocols) . ')(.*?)"(.*?)>#ism'; $text = preg_replace($regex, '<$1$2="javascript:void(0)"$4 data-original-href="$3" class="invalid-href" title="' . DI::l10n()->t('Invalid link protocol') . '">', $text); @@ -2074,7 +1890,8 @@ class BBCode $text, function (array $attributes, array $author_contact, $content, $is_quote_share) use ($simple_html) { return self::convertShareCallback($attributes, $author_contact, $content, $is_quote_share, $simple_html); - }, $uriid + }, + $uriid ); $text = self::interpolateSavedImagesIntoItemBody($uriid, $text, $saved_image); @@ -2089,13 +1906,14 @@ class BBCode // Additionally, [pre] tags preserve spaces $text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", function ($match) { - return str_replace([' ', "\n"], [' ', "
    "], htmlentities($match[1], ENT_NOQUOTES,'UTF-8')); + return str_replace([' ', "\n"], [' ', "
    "], htmlentities($match[1], ENT_NOQUOTES, 'UTF-8')); }, $text); return $text; }); // Escaped code - $text = preg_replace_callback("#\[code(?:=([^\]]*))?\](.*?)\[\/code\]#ism", + $text = preg_replace_callback( + "#\[code(?:=([^\]]*))?\](.*?)\[\/code\]#ism", function ($matches) { if (strpos($matches[2], "\n") !== false) { $return = '
    ' . htmlentities(trim($matches[2], "\n\r"), ENT_NOQUOTES, 'UTF-8') . '
    '; @@ -2111,8 +1929,8 @@ class BBCode // Default iframe allowed domains/path $allowedIframeDomains = [ DI::baseUrl()->getHost() - . (DI::baseUrl()->getPath() ? '/' . DI::baseUrl()->getPath() : '') - . '/oembed/', # The path part has to change with the source in Content\Oembed::iframe + . (DI::baseUrl()->getPath() ? '/' . DI::baseUrl()->getPath() : '') + . '/oembed/', # The path part has to change with the source in Content\Oembed::iframe 'www.youtube.com/embed/', 'player.vimeo.com/video/', ]; @@ -2124,6 +1942,10 @@ class BBCode : [] ); + if (strpos($text, '

    ') !== false || strpos($text, '

    ') !== false) { + $text = '

    ' . $text . '

    '; + } + $text = HTML::purify($text, $allowedIframeDomains); DI::profiler()->stopRecording(); @@ -2225,7 +2047,8 @@ class BBCode * Transform #tags, strip off the [url] and replace spaces with underscore */ $url_search_string = "^\[\]"; - $text = preg_replace_callback("/#\[url\=([$url_search_string]*)\](.*?)\[\/url\]/i", + $text = preg_replace_callback( + "/#\[url\=([$url_search_string]*)\](.*?)\[\/url\]/i", function ($matches) { return '#' . str_replace(' ', '_', $matches[2]); }, @@ -2360,7 +2183,8 @@ class BBCode */ public static function expandTags(string $body): string { - return preg_replace_callback("/(?<=\W|^)([!#@])([^\^ \x0D\x0A,;:?'\"]*[^\^ \x0D\x0A,;:?!'\".])/", + return preg_replace_callback( + "/(?<=\W|^)([!#@])([^\^ \x0D\x0A,;:?'\"]*[^\^ \x0D\x0A,;:?!'\".])/", function (array $match) { switch ($match[1]) { case '!': @@ -2377,7 +2201,9 @@ class BBCode default: return $match[1] . '[url=' . DI::baseUrl() . '/search?tag=' . $match[2] . ']' . $match[2] . '[/url]'; } - }, $body); + }, + $body + ); } /**