From 38311a4a02ef5d16a0b10a953a345cd7f2da6336 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Sat, 15 Jun 2019 22:24:51 -0400 Subject: [PATCH 1/3] 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); + } } From 84ccaf50fa547d8c336bc1e635f2dcfe2c09255b Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Sat, 15 Jun 2019 22:26:29 -0400 Subject: [PATCH 2/3] Allow brackets in URLs - Remove obsolete $URLSearchString variable - Add related tests --- src/Content/Text/BBCode.php | 65 ++++++++++++--------------- tests/src/Content/Text/BBCodeTest.php | 8 ++++ 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index f51d7f27b..2113c6fa2 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -72,9 +72,7 @@ class BBCode extends BaseObject $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]); @@ -87,12 +85,12 @@ class BBCode extends BaseObject } } - if (preg_match("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism", $attacheddata, $matches)) { + 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)) { + && preg_match("/\[url\=(.*?)\](.*?)\[\/url\]/ism", $attacheddata, $matches)) { $post["url"] = $matches[1]; } @@ -245,11 +243,9 @@ class BBCode extends BaseObject // Simplify image codes $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body); - $URLSearchString = "^\[\]"; + $body = preg_replace("/\[img\=(.*?)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $body); - $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)) { // Replace the preview picture with the real picture @@ -293,7 +289,7 @@ class BBCode extends BaseObject $post["image"] = $pictures[0][2]; $post["text"] = $body; } - } 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]; @@ -307,8 +303,8 @@ class BBCode extends BaseObject } // 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", $body, $links1, PREG_SET_ORDER); + preg_match_all("(\[url\=(.*?)\].*?\[\/url\])ism", $body, $links2, PREG_SET_ORDER); $links = array_merge($links1, $links2); @@ -1281,11 +1277,6 @@ 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); @@ -1471,7 +1462,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); @@ -1597,11 +1588,11 @@ class BBCode extends BaseObject if (!$for_plaintext) { $text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text); if (in_array($simple_html, [7, 9])) { - $text = preg_replace_callback("/\[url\]([$URLSearchString]*)\[\/url\]/ism", 'self::convertUrlForOStatusCallback', $text); - $text = preg_replace_callback("/\[url\=([$URLSearchString]*)\]([$URLSearchString]*)\[\/url\]/ism", 'self::convertUrlForOStatusCallback', $text); + $text = preg_replace_callback("/\[url\](.*?)\[\/url\]/ism", 'self::convertUrlForOStatusCallback', $text); + $text = preg_replace_callback("/\[url\=(.*?)\](.*?)\[\/url\]/ism", 'self::convertUrlForOStatusCallback', $text); } } else { - $text = preg_replace("(\[url\]([$URLSearchString]*)\[\/url\])ism", " $1 ", $text); + $text = preg_replace("(\[url\](.*?)\[\/url\])ism", " $1 ", $text); $text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", 'self::removePictureLinksCallback', $text); } @@ -1609,26 +1600,26 @@ class BBCode extends BaseObject // Remove all hashtag addresses if ((!$try_oembed || $simple_html) && !in_array($simple_html, [3, 7, 9])) { - $text = preg_replace("/([#@!])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$1$3', $text); + $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\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", + $text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '@$3', $text); } elseif (in_array($simple_html, [7, 9])) { - $text = preg_replace("/([@!])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", + $text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text); } elseif (!$simple_html) { - $text = preg_replace("/([@!])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", + $text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/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", + $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])) { @@ -1638,7 +1629,7 @@ class BBCode extends BaseObject } if ($simple_html == 5) { - $text = preg_replace("/[^#@!]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '[url]$1[/url]', $text); + $text = preg_replace("/[^#@!]\[url\=(.*?)\](.*?)\[\/url\]/ism", '[url]$1[/url]', $text); } // Perform URL Search @@ -1677,7 +1668,7 @@ class BBCode extends BaseObject * - #[url=][/url] * - [url=]#[/url] */ - $text = preg_replace_callback("/(?:#\[url\=[$URLSearchString]*\]|\[url\=[$URLSearchString]*\]#)(.*?)\[\/url\]/ism", function($matches) { + $text = preg_replace_callback("/(?:#\[url\=.*?\]|\[url\=.*?\]#)(.*?)\[\/url\]/ism", function($matches) { return '#' @@ -1688,14 +1679,14 @@ class BBCode extends BaseObject // 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\](".$escapedBaseUrl.".*?)\[\/url\]/ism", '$1', $text); + $text = preg_replace("/\[url\=(".$escapedBaseUrl.".*?)\](.*?)\[\/url\]/ism", '$2', $text); - $text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '$1', $text); - $text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/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\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '$2', $text); + $text = preg_replace("/\[zrl\=(.*?)\](.*?)\[\/zrl\]/ism", '$2', $text); // we may need to restrict this further if it picks up too many strays @@ -1704,8 +1695,8 @@ class BBCode extends BaseObject $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); + $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. diff --git a/tests/src/Content/Text/BBCodeTest.php b/tests/src/Content/Text/BBCodeTest.php index f74aebaab..3efb40893 100644 --- a/tests/src/Content/Text/BBCodeTest.php +++ b/tests/src/Content/Text/BBCodeTest.php @@ -113,6 +113,14 @@ class BBCodeTest extends MockedTest 'data' => html_entity_decode('http://example.com ', ENT_QUOTES, 'UTF-8'), 'assertHTML' => false ], + 'bug-7271-query-string-brackets' => [ + 'data' => 'https://example.com/search?q=square+brackets+[url]', + 'assertHTML' => true + ], + 'bug-7271-path-brackets' => [ + 'data' => 'http://example.com/path/to/file[3].html', + 'assertHTML' => true + ], ]; } From da56e0a54105760be744ab68545c282f7cc140c9 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Sat, 15 Jun 2019 22:27:20 -0400 Subject: [PATCH 3/3] Add tests folder to typo console coverage - Use correct config object in Console\Typo --- src/Console/Typo.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Console/Typo.php b/src/Console/Typo.php index 5f5fa0ba6..216d05723 100644 --- a/src/Console/Typo.php +++ b/src/Console/Typo.php @@ -43,7 +43,7 @@ HELP; throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments'); } - $php_path = BaseObject::getApp()->getConfigCache()->get('config', 'php_path', 'php'); + $php_path = BaseObject::getApp()->getConfig()->get('config', 'php_path', 'php'); if ($this->getOption('v')) { $this->out('Directory: src'); @@ -57,6 +57,18 @@ HELP; } } + if ($this->getOption('v')) { + $this->out('Directory: tests'); + } + + $Iterator = new \RecursiveDirectoryIterator('tests'); + + foreach (new \RecursiveIteratorIterator($Iterator) as $file) { + if (substr($file, -4) === '.php') { + $this->checkFile($php_path, $file); + } + } + if ($this->getOption('v')) { $this->out('Directory: mod'); }