From 38311a4a02ef5d16a0b10a953a345cd7f2da6336 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Sat, 15 Jun 2019 22:24:51 -0400 Subject: [PATCH] Move link conversion to the end of BBCode::convert - Addresses https://github.com/friendica/friendica/issues/7271 - Add related tests --- src/Content/Text/BBCode.php | 609 +++++++++++++------------- tests/src/Content/Text/BBCodeTest.php | 43 ++ 2 files changed, 348 insertions(+), 304 deletions(-) diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index 146dedf47..f51d7f27b 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -1289,6 +1289,310 @@ class BBCode extends BaseObject // Handle attached links or videos $text = self::convertAttachment($text, $simple_html, $try_oembed); + // 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", + function ($match) use ($simple_html) { + return str_replace($match[0], '

' . Map::byLocation($match[1], $simple_html) . '

', $match[0]); + }, + $text + ); + } + + if (strpos($text, '[map=') !== false) { + $text = preg_replace_callback( + "/\[map=(.*?)\]/ism", + function ($match) use ($simple_html) { + return str_replace($match[0], '

' . Map::byCoordinates(str_replace('/', ' ', $match[1]), $simple_html) . '

', $match[0]); + }, + $text + ); + } + + if (strpos($text, '[map]') !== false) { + $text = preg_replace("/\[map\]/", '

', $text); + } + + // 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); + + // Check for paragraph + $text = preg_replace("(\[p\](.*?)\[\/p\])ism", '

$1

', $text); + + // Check for bold text + $text = preg_replace("(\[b\](.*?)\[\/b\])ism", '$1', $text); + + // Check for Italics text + $text = preg_replace("(\[i\](.*?)\[\/i\])ism", '$1', $text); + + // Check for Underline text + $text = preg_replace("(\[u\](.*?)\[\/u\])ism", '$1', $text); + + // Check for strike-through text + $text = preg_replace("(\[s\](.*?)\[\/s\])ism", '$1', $text); + + // Check for over-line text + $text = preg_replace("(\[o\](.*?)\[\/o\])ism", '$1', $text); + + // Check for colored text + $text = preg_replace("(\[color=(.*?)\](.*?)\[\/color\])ism", "$2", $text); + + // Check for sized text + // [size=50] --> font-size: 50px (with the unit). + $text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism", "$2", $text); + $text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text); + + // Check for centered text + $text = preg_replace("(\[center\](.*?)\[\/center\])ism", "
$1
", $text); + + // Check for list text + $text = str_replace("[*]", "
  • ", $text); + + // Check for style sheet commands + $text = preg_replace_callback( + "(\[style=(.*?)\](.*?)\[\/style\])ism", + function ($match) { + return "" . $match[2] . ""; + }, + $text + ); + + // Check for CSS classes + $text = preg_replace_callback( + "(\[class=(.*?)\](.*?)\[\/class\])ism", + function ($match) { + return "" . $match[2] . ""; + }, + $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); + $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 border=1\](.*?)\[\/table\]/sm", '$1
    ', $text); + $text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '$1
    ', $text); + + $text = str_replace('[hr]', '
    ', $text); + + // This is actually executed in Item::prepareBody() + + $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
    '; + + // 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); + } + + // Check for [spoiler=Author] text + + $t_wrote = L10n::t('$1 wrote:'); + + // 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); + } + + // Declare the format for [quote] layout + $QuoteLayout = '
    $1
    '; + + // Check for [quote] text + // handle nested quotes + $endlessloop = 0; + while ((strpos($text, "[/quote]") !== false) && (strpos($text, "[quote]") !== false) && (++$endlessloop < 20)) { + $text = preg_replace("/\[quote\](.*?)\[\/quote\]/ism", "$QuoteLayout", $text); + } + + // Check for [quote=Author] text + + $t_wrote = L10n::t('$1 wrote:'); + + // 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); + } + + + // [img=widthxheight]image source[/img] + $text = preg_replace_callback( + "/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", + function ($matches) use ($simple_html) { + if (strpos($matches[3], "data:image/") === 0) { + return $matches[0]; + } + + $matches[3] = self::proxyUrl($matches[3], $simple_html); + return "[img=" . $matches[1] . "x" . $matches[2] . "]" . $matches[3] . "[/img]"; + }, + $text + ); + + $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", + function ($matches) use ($simple_html) { + $matches[1] = self::proxyUrl($matches[1], $simple_html); + $matches[2] = htmlspecialchars($matches[2], ENT_COMPAT); + return '' . $matches[2] . ''; + }, + $text); + + // Images + // [img]pathtoimage[/img] + $text = preg_replace_callback( + "/\[img\](.*?)\[\/img\]/ism", + function ($matches) use ($simple_html) { + if (strpos($matches[1], "data:image/") === 0) { + return $matches[0]; + } + + $matches[1] = self::proxyUrl($matches[1], $simple_html); + return "[img]" . $matches[1] . "[/img]"; + }, + $text + ); + + $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); + + // Try to Oembed + if ($try_oembed) { + $text = preg_replace("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4).*?)\[\/video\]/ism", '', $text); + $text = preg_replace("/\[audio\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mp3).*?)\[\/audio\]/ism", '', $text); + + $text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", $try_oembed_callback, $text); + $text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text); + } else { + $text = preg_replace("/\[video\](.*?)\[\/video\]/ism", + '$1', $text); + $text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", + '$1', $text); + } + + // html5 video and audio + + + if ($try_oembed) { + $text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '', $text); + } else { + $text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '$1', $text); + } + + // Youtube extensions + if ($try_oembed) { + $text = preg_replace_callback("/\[youtube\](https?:\/\/www.youtube.com\/watch\?v\=.*?)\[\/youtube\]/ism", $try_oembed_callback, $text); + $text = preg_replace_callback("/\[youtube\](www.youtube.com\/watch\?v\=.*?)\[\/youtube\]/ism", $try_oembed_callback, $text); + $text = preg_replace_callback("/\[youtube\](https?:\/\/youtu.be\/.*?)\[\/youtube\]/ism", $try_oembed_callback, $text); + } + + $text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/watch\?v\=(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text); + $text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/embed\/(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text); + $text = preg_replace("/\[youtube\]https?:\/\/youtu.be\/(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text); + + 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); + } + + if ($try_oembed) { + $text = preg_replace_callback("/\[vimeo\](https?:\/\/player.vimeo.com\/video\/[0-9]+).*?\[\/vimeo\]/ism", $try_oembed_callback, $text); + $text = preg_replace_callback("/\[vimeo\](https?:\/\/vimeo.com\/[0-9]+).*?\[\/vimeo\]/ism", $try_oembed_callback, $text); + } + + $text = preg_replace("/\[vimeo\]https?:\/\/player.vimeo.com\/video\/([0-9]+)(.*?)\[\/vimeo\]/ism", '[vimeo]$1[/vimeo]', $text); + $text = preg_replace("/\[vimeo\]https?:\/\/vimeo.com\/([0-9]+)(.*?)\[\/vimeo\]/ism", '[vimeo]$1[/vimeo]', $text); + + 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); + } + + // oembed tag + $text = OEmbed::BBCode2HTML($text); + + // Avoid triple linefeeds through oembed + $text = str_replace("


    ", "

    ", $text); + + // If we found an event earlier, strip out all the event code and replace with a reformatted version. + // Replace the event-start section with the entire formatted event. The other bbcode is stripped. + // 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 ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) { + $sub = Event::getHTML($ev, $simple_html); + + $text = preg_replace("/\[event\-summary\](.*?)\[\/event\-summary\]/ism", '', $text); + $text = preg_replace("/\[event\-description\](.*?)\[\/event\-description\]/ism", '', $text); + $text = preg_replace("/\[event\-start\](.*?)\[\/event\-start\]/ism", $sub, $text); + $text = preg_replace("/\[event\-finish\](.*?)\[\/event\-finish\]/ism", '', $text); + $text = preg_replace("/\[event\-location\](.*?)\[\/event\-location\]/ism", '', $text); + $text = preg_replace("/\[event\-adjust\](.*?)\[\/event\-adjust\]/ism", '', $text); + $text = preg_replace("/\[event\-id\](.*?)\[\/event\-id\]/ism", '', $text); + } + + // Replace non graphical smilies for external posts + if ($simple_html) { + $text = Smilies::replace($text); + } + // 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); @@ -1385,7 +1689,7 @@ class BBCode extends BaseObject // 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\=(".$escapedBaseUrl."[$URLSearchString]*)\](.*?)\[\/url\]/ism", '$2', $text); $text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '$1', $text); $text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$2', $text); @@ -1403,309 +1707,6 @@ class BBCode extends BaseObject $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", - function ($match) use ($simple_html) { - return str_replace($match[0], '

    ' . Map::byLocation($match[1], $simple_html) . '

    ', $match[0]); - }, - $text - ); - } - if (strpos($text, '[map=') !== false) { - $text = preg_replace_callback( - "/\[map=(.*?)\]/ism", - function ($match) use ($simple_html) { - return str_replace($match[0], '

    ' . Map::byCoordinates(str_replace('/', ' ', $match[1]), $simple_html) . '

    ', $match[0]); - }, - $text - ); - } - if (strpos($text, '[map]') !== false) { - $text = preg_replace("/\[map\]/", '

    ', $text); - } - - // 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); - - // Check for paragraph - $text = preg_replace("(\[p\](.*?)\[\/p\])ism", '

    $1

    ', $text); - - // Check for bold text - $text = preg_replace("(\[b\](.*?)\[\/b\])ism", '$1', $text); - - // Check for Italics text - $text = preg_replace("(\[i\](.*?)\[\/i\])ism", '$1', $text); - - // Check for Underline text - $text = preg_replace("(\[u\](.*?)\[\/u\])ism", '$1', $text); - - // Check for strike-through text - $text = preg_replace("(\[s\](.*?)\[\/s\])ism", '$1', $text); - - // Check for over-line text - $text = preg_replace("(\[o\](.*?)\[\/o\])ism", '$1', $text); - - // Check for colored text - $text = preg_replace("(\[color=(.*?)\](.*?)\[\/color\])ism", "$2", $text); - - // Check for sized text - // [size=50] --> font-size: 50px (with the unit). - $text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism", "$2", $text); - $text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text); - - // Check for centered text - $text = preg_replace("(\[center\](.*?)\[\/center\])ism", "
    $1
    ", $text); - - // Check for list text - $text = str_replace("[*]", "
  • ", $text); - - // Check for style sheet commands - $text = preg_replace_callback( - "(\[style=(.*?)\](.*?)\[\/style\])ism", - function ($match) { - return "" . $match[2] . ""; - }, - $text - ); - - // Check for CSS classes - $text = preg_replace_callback( - "(\[class=(.*?)\](.*?)\[\/class\])ism", - function ($match) { - return "" . $match[2] . ""; - }, - $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); - $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 border=1\](.*?)\[\/table\]/sm", '$1
    ', $text); - $text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '$1
    ', $text); - - $text = str_replace('[hr]', '
    ', $text); - - // This is actually executed in Item::prepareBody() - - $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
    '; - - // 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); - } - - // Check for [spoiler=Author] text - - $t_wrote = L10n::t('$1 wrote:'); - - // 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); - } - - // Declare the format for [quote] layout - $QuoteLayout = '
    $1
    '; - - // Check for [quote] text - // handle nested quotes - $endlessloop = 0; - while ((strpos($text, "[/quote]") !== false) && (strpos($text, "[quote]") !== false) && (++$endlessloop < 20)) { - $text = preg_replace("/\[quote\](.*?)\[\/quote\]/ism", "$QuoteLayout", $text); - } - - // Check for [quote=Author] text - - $t_wrote = L10n::t('$1 wrote:'); - - // 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); - } - - - // [img=widthxheight]image source[/img] - $text = preg_replace_callback( - "/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", - function ($matches) use ($simple_html) { - if (strpos($matches[3], "data:image/") === 0) { - return $matches[0]; - } - - $matches[3] = self::proxyUrl($matches[3], $simple_html); - return "[img=" . $matches[1] . "x" . $matches[2] . "]" . $matches[3] . "[/img]"; - }, - $text - ); - - $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", - function ($matches) use ($simple_html) { - $matches[1] = self::proxyUrl($matches[1], $simple_html); - $matches[2] = htmlspecialchars($matches[2], ENT_COMPAT); - return '' . $matches[2] . ''; - }, - $text); - - // Images - // [img]pathtoimage[/img] - $text = preg_replace_callback( - "/\[img\](.*?)\[\/img\]/ism", - function ($matches) use ($simple_html) { - if (strpos($matches[1], "data:image/") === 0) { - return $matches[0]; - } - - $matches[1] = self::proxyUrl($matches[1], $simple_html); - return "[img]" . $matches[1] . "[/img]"; - }, - $text - ); - - $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); - - // Try to Oembed - if ($try_oembed) { - $text = preg_replace("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4).*?)\[\/video\]/ism", '', $text); - $text = preg_replace("/\[audio\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mp3).*?)\[\/audio\]/ism", '', $text); - - $text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", $try_oembed_callback, $text); - $text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text); - } else { - $text = preg_replace("/\[video\](.*?)\[\/video\]/ism", - '$1', $text); - $text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", - '$1', $text); - } - - // html5 video and audio - - - if ($try_oembed) { - $text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '', $text); - } else { - $text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '$1', $text); - } - - // Youtube extensions - if ($try_oembed) { - $text = preg_replace_callback("/\[youtube\](https?:\/\/www.youtube.com\/watch\?v\=.*?)\[\/youtube\]/ism", $try_oembed_callback, $text); - $text = preg_replace_callback("/\[youtube\](www.youtube.com\/watch\?v\=.*?)\[\/youtube\]/ism", $try_oembed_callback, $text); - $text = preg_replace_callback("/\[youtube\](https?:\/\/youtu.be\/.*?)\[\/youtube\]/ism", $try_oembed_callback, $text); - } - - $text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/watch\?v\=(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text); - $text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/embed\/(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text); - $text = preg_replace("/\[youtube\]https?:\/\/youtu.be\/(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text); - - 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); - } - - if ($try_oembed) { - $text = preg_replace_callback("/\[vimeo\](https?:\/\/player.vimeo.com\/video\/[0-9]+).*?\[\/vimeo\]/ism", $try_oembed_callback, $text); - $text = preg_replace_callback("/\[vimeo\](https?:\/\/vimeo.com\/[0-9]+).*?\[\/vimeo\]/ism", $try_oembed_callback, $text); - } - - $text = preg_replace("/\[vimeo\]https?:\/\/player.vimeo.com\/video\/([0-9]+)(.*?)\[\/vimeo\]/ism", '[vimeo]$1[/vimeo]', $text); - $text = preg_replace("/\[vimeo\]https?:\/\/vimeo.com\/([0-9]+)(.*?)\[\/vimeo\]/ism", '[vimeo]$1[/vimeo]', $text); - - 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); - } - - // oembed tag - $text = OEmbed::BBCode2HTML($text); - - // Avoid triple linefeeds through oembed - $text = str_replace("


    ", "

    ", $text); - - // If we found an event earlier, strip out all the event code and replace with a reformatted version. - // Replace the event-start section with the entire formatted event. The other bbcode is stripped. - // 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 ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) { - $sub = Event::getHTML($ev, $simple_html); - - $text = preg_replace("/\[event\-summary\](.*?)\[\/event\-summary\]/ism", '', $text); - $text = preg_replace("/\[event\-description\](.*?)\[\/event\-description\]/ism", '', $text); - $text = preg_replace("/\[event\-start\](.*?)\[\/event\-start\]/ism", $sub, $text); - $text = preg_replace("/\[event\-finish\](.*?)\[\/event\-finish\]/ism", '', $text); - $text = preg_replace("/\[event\-location\](.*?)\[\/event\-location\]/ism", '', $text); - $text = preg_replace("/\[event\-adjust\](.*?)\[\/event\-adjust\]/ism", '', $text); - $text = preg_replace("/\[event\-id\](.*?)\[\/event\-id\]/ism", '', $text); - } - - // Replace non graphical smilies for external posts - if ($simple_html) { - $text = Smilies::replace($text); - } - // Unhide all [noparse] contained bbtags unspacefying them // and triming the [noparse] tag. diff --git a/tests/src/Content/Text/BBCodeTest.php b/tests/src/Content/Text/BBCodeTest.php index 864a3794d..f74aebaab 100644 --- a/tests/src/Content/Text/BBCodeTest.php +++ b/tests/src/Content/Text/BBCodeTest.php @@ -134,4 +134,47 @@ class BBCodeTest extends MockedTest $this->assertNotEquals($assert, $output); } } + + public function dataBBCodes() + { + return [ + 'bug-7271-condensed-space' => [ + 'expectedHtml' => '', + 'text' => '[ol][*] http://example.com/[/ol]', + ], + 'bug-7271-condensed-nospace' => [ + 'expectedHtml' => '', + 'text' => '[ol][*]http://example.com/[/ol]', + ], + 'bug-7271-indented-space' => [ + 'expectedHtml' => '', + 'text' => '[ul] +[*] http://example.com/ +[/ul]', + ], + 'bug-7271-indented-nospace' => [ + 'expectedHtml' => '', + 'text' => '[ul] +[*]http://example.com/ +[/ul]', + ], + ]; + } + + /** + * Test convert bbcodes to HTML + * @dataProvider dataBBCodes + * + * @param string $expectedHtml Expected HTML output + * @param string $text BBCode text + * @param int $simpleHtml BBCode::convert method $simple_html parameter value, optional. + * @param bool $forPlaintext BBCode::convert method $for_plaintext parameter value, optional. + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function testConvert($expectedHtml, $text, $simpleHtml = 0, $forPlaintext = false) + { + $actual = BBCode::convert($text, false, $simpleHtml, $forPlaintext); + + $this->assertEquals($expectedHtml, $actual); + } }