)/', '', $match[2]); // ltrim the first newline $listElements = preg_replace( '/' . $eatLeadingSpaces . '\[\*=([[:print:]]*?)(?$1
- ' . $listElements . '
- ' . $str . '
', "\n" , $alt) : t('Image/photo'), ENT_COMPAT, 'UTF-8') . '" '; $output .= 'src="' . (($src) ? $src : $match[4]) . '" >'; return $output; } function multicode_purify($s) { $s = preg_replace_callback("/\[code(.*?)\](.*?)\[\/code\]/ism", function ($match) { return '[code' . $match[1] . ']' . bb_code_protect($match[2]) . '[/code]'; }, $s); // escape_tags anywhere html is disabled. $s = preg_replace_callback("/\[nohtml\](.*?)\[\/nohtml\]/ism", function ($match) { return escape_tags($match[1]); }, $s); $s = preg_replace_callback('#(^|\n)([`~]{3,})(?: *\.?([a-zA-Z0-9\-.]+))?\n+([\s\S]+?)\n+\2(\n|$)#', function ($match) { return $match[1] . $match[2] . "\n" . bb_code_protect($match[4]) . "\n" . $match[2] . (($match[5]) ? $match[5] : "\n"); }, $s); try { $s = purify_html($s, [ 'escape' ]); } catch (Exception $e) { $s = escape_tags($s); } return bb_code_unprotect($s); } function bb_mdlink_protect($matches) { if ($matches[1] === $matches[3]) { return '[' . $matches[1] . ']' . html_entity_decode('') . '(' . $matches[2] . ')[/' . $matches[3] . ']'; } else { return $matches[0]; } } function bb_code_preprotect($matches) { return '[code' . $matches[1] . ']' . 'b64.^8e%.' . base64_encode(str_replace('
', '|+br+|', $matches[2])) . '.b64.$8e%' . '[/code]'; } function bb_code_preunprotect($s) { return preg_replace_callback('|b64\.\^8e\%\.(.*?)\.b64\.\$8e\%|ism', 'bb_code_unprotect_sub', $s); } function bb_code_protect($s) { return 'b64.^9e%.' . base64_encode(str_replace('
', '|+br+|', $s)) . '.b64.$9e%'; } function bb_code_unprotect($s) { return preg_replace_callback('|b64\.\^9e\%\.(.*?)\.b64\.\$9e\%|ism', 'bb_code_unprotect_sub', $s); } function bb_code_unprotect_sub($match) { $x = str_replace([ '<', '>' ], [ '<', '>' ], base64_decode($match[1])); return str_replace('|+br+|', '
', $x); } function bb_colorbox($match) { if (strpos($match[1], 'zrl')) { $url = zid($match[2]); } else { $url = $match[2]; } return ''; } function bb_code($match) { if (strpos($match[0], "
")) { return '
' . bb_code_protect(trim($match[1])) . '
';
} else {
return '' . bb_code_protect(trim($match[1])) . '
';
}
}
function bb_code_options($match)
{
if (strpos($match[0], "")) { $class = ""; $pre = true; } else { $class = "inline-code"; $pre = false; } if (strpos($match[1], 'nowrap')) { $style = "overflow-x: auto; white-space: pre;"; } else { $style = ""; } if ($pre) { return '
' . bb_code_protect(trim($match[2])) . '
';
} else {
return '' . bb_code_protect(trim($match[2])) . '
';
}
}
function md_protect($match)
{
return bb_code_protect($match[1]);
}
function html_protect($match)
{
return str_replace(['<','>'], ['<','>'], $match[1]);
}
function md_header($content)
{
$headingLevel = strlen($content[1]);
$header = trim($content[2]);
// Build anchor without space, numbers.
$anchor = preg_replace('#[^a-z?!]#', '', strtolower($header));
return sprintf('', $content[4]); return sprintf('
%s
', $class, bb_code_protect($content));
}
function md_italic($content)
{
return '' . $content[1] . $content[3] . '';
}
function md_bold($content)
{
return '' . $content[1] . $content[3] . '';
}
function md_bolditalic($content)
{
return '' . $content[1] . $content[3] . '';
}
/** @noinspection HtmlUnknownAttribute */
function md_image($content)
{
$url = filter_var($content[1], FILTER_SANITIZE_URL);
$alt = '';
if (isset($content[2])) {
$content[2] = str_replace('"', '', $content[2]);
$alt = 'alt="' . filter_var($content[2], FILTER_SANITIZE_STRING) . '"';
}
/** @noinspection HtmlRequiredAltAttribute */
return sprintf('', $url, $alt);
}
function md_topheader($matches)
{
// Terrible hack to check we haven't found an empty list item.
if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) {
return $matches[0];
}
$level = $matches[2][0] == '=' ? 1 : 2;
return "' and turn your neatly crafted tables into a whole lot of // empty space. $x = preg_replace("/\]\s+\[/", '][', $match[1]); $x = str_replace("\\\n", "\n", $x); return '[table]' . $x . '[/table]'; } function ht_fixtable_lf($match) { // HTML version (2 args) // remove extraneous whitespace between table element tags since newlines will all // be converted to '
' and turn your neatly crafted tables into a whole lot of // empty space. $x = preg_replace("/\>\s+\", '><', $match[2]); return '
', $Text); // This is actually executed in prepare_body() $Text = str_replace('[nosmile]', '', $Text); // Check for font change text if (str_contains($Text, '[/font]')) { $Text = preg_replace_callback("/\[font=(.*?)\](.*?)\[\/font\]/sm", 'bb_fonttag', $Text); } if (str_contains($Text, '[/summary]')) { $Text = preg_replace_callback("/^(.*?)\[summary\](.*?)\[\/summary\](.*?)$/ism", 'bb_summary', $Text); } // Check for [spoiler] text $endlessloop = 0; while ((str_contains($Text, "[/spoiler]")) && (str_contains($Text, "[spoiler]")) && (++$endlessloop < 20)) { $Text = preg_replace_callback("/\[spoiler\](.*?)\[\/spoiler\]/ism", 'bb_spoilertag', $Text); } // Check for [spoiler=Author] text $endlessloop = 0; while ((str_contains($Text, "[/spoiler]")) && (str_contains($Text, "[spoiler=")) && (++$endlessloop < 20)) { $Text = preg_replace_callback("/\[spoiler=(.*?)\](.*?)\[\/spoiler\]/ism", 'bb_spoilertag', $Text); } // Check for [open] text $endlessloop = 0; while ((str_contains($Text, "[/open]")) && (str_contains($Text, "[open]")) && (++$endlessloop < 20)) { $Text = preg_replace_callback("/\[open\](.*?)\[\/open\]/ism", 'bb_opentag', $Text); } // Check for [open=Title] text $endlessloop = 0; while ((str_contains($Text, "[/open]")) && (str_contains($Text, "[open=")) && (++$endlessloop < 20)) { $Text = preg_replace_callback("/\[open=(.*?)\](.*?)\[\/open\]/ism", 'bb_opentag', $Text); } // Declare the format for [quote] layout $QuoteLayout = '
$1'; // Check for [quote] text // handle nested quotes $endlessloop = 0; while ((str_contains($Text, "[/quote]")) && (str_contains($Text, "[quote]")) && (++$endlessloop < 20)) { $Text = preg_replace("/\[quote\](.*?)\[\/quote\]/ism", "$QuoteLayout", $Text); } // Check for [quote=Author] text $t_wrote = t('$1 wrote:'); // handle nested quotes $endlessloop = 0; while ((str_contains($Text, "[/quote]")) && (str_contains($Text, "[quote=")) && (++$endlessloop < 20)) { $Text = preg_replace( "/\[quote=[\"\']*(.*?)[\"\']*\](.*?)\[\/quote\]/ism", "" . $t_wrote . "
$2", $Text ); } if ($plain) { $Text = str_replace([ '
','' ], [ '“', '”' ], $Text); } // Images // [img]pathtoimage[/img] if (str_contains($Text, '[/img]')) { $Text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '', $Text); } if (str_contains($Text, '[/zmg]')) { $Text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '', $Text); } $Text = preg_replace_callback("/\[([zi])mg([ \=])(.*?)\](.*?)\[\/[zi]mg\]/ism", 'bb_imgoptions', $Text); if (str_contains($Text,'[/qr]')) { $Text = preg_replace_callback("/\[qr\](.*?)\[\/qr\]/ism", 'bb_qr', $Text); } if ($censored) { // This function in include/misc.php separates images wrapped in links // so the links are still accessible when censored (where clicking the img views it). // Unfortunately this destroys the formatting of reshares by a censored author. // To visit the original links attached to the image, one must turn off safe mode. // Leaving commented out in case somebody has a desire to fix it. // $Text = separate_img_links($Text); $Text = preg_replace_callback("/\/ism", "bb_colorbox", $Text); } // style (sanitized) if (str_contains($Text, '[/style]')) { $Text = preg_replace_callback("(\[style=(.*?)\](.*?)\[\/style\])ism", "bb_sanitize_style", $Text); } // crypt if (str_contains($Text, '[/crypt]')) { if ($activitypub) { $Text = preg_replace_callback("/\[crypt (.*?)\](.*?)\[\/crypt\]/ism", 'bb_parse_b64_crypt', $Text); } else { $Text = preg_replace_callback("/\[crypt (.*?)\](.*?)\[\/crypt\]/ism", 'bb_parse_crypt', $Text); } } if (str_contains($Text, '[/app]')) { if ($activitypub) { $Text = preg_replace_callback("/\[app\](.*?)\[\/app\]/ism", 'bb_parse_app_ap', $Text); } else { $Text = preg_replace_callback("/\[app\](.*?)\[\/app\]/ism", 'bb_parse_app', $Text); } } if (str_contains($Text, '[/element]')) { $Text = preg_replace_callback("/\[element\](.*?)\[\/element\]/ism", 'bb_parse_element', $Text); } // html5 video and audio if (str_contains($Text, '[/video]')) { $Text = preg_replace_callback("/\[video (.*?)\](.*?)\[\/video\]/ism", 'videowithopts', $Text); $Text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", 'tryzrlvideo', $Text); } if (str_contains($Text, '[/audio]')) { $Text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", 'tryzrlaudio', $Text); } if (str_contains($Text, '[/zvideo]')) { $Text = preg_replace_callback("/\[zvideo (.*?)\](.*?)\[\/zvideo\]/ism", 'videowithopts', $Text); $Text = preg_replace_callback("/\[zvideo\](.*?)\[\/zvideo\]/ism", 'tryzrlvideo', $Text); } if (str_contains($Text, '[/zaudio]')) { $Text = preg_replace_callback("/\[zaudio\](.*?)\[\/zaudio\]/ism", 'tryzrlaudio', $Text); } // if video couldn't be embedded, link to it instead. if (str_contains($Text, '[/video]')) { $Text = preg_replace("/\[video\](.*?)\[\/video\]/", '$1', $Text); } if (str_contains($Text, '[/audio]')) { $Text = preg_replace("/\[audio\](.*?)\[\/audio\]/", '$1', $Text); } if (str_contains($Text, '[/zvideo]')) { $Text = preg_replace("/\[zvideo\](.*?)\[\/zvideo\]/", '$1', $Text); } if (str_contains($Text, '[/zaudio]')) { $Text = preg_replace("/\[zaudio\](.*?)\[\/zaudio\]/", '$1', $Text); } // SVG stuff if ($activitypub) { $Text = preg_replace_callback("/\[svg(.*?)\](.*?)\[\/svg\]/ism", 'bb_svg_export', $Text); } else { $Text = preg_replace_callback("/\[svg(.*?)\](.*?)\[\/svg\]/ism", 'bb_svg', $Text); } // oembed tag if (! $export) { $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 ((x($ev, 'desc') || x($ev, 'summary')) && x($ev, 'dtstart')) { $sub = format_event_html($ev); $sub = str_replace('$', "\0", $sub); $Text = preg_replace("/\[event\-start\](.*?)\[\/event\-start\]/ism", $sub, $Text); $Text = preg_replace("/\[event\](.*?)\[\/event\]/ism", '', $Text); $Text = preg_replace("/\[event\-summary\](.*?)\[\/event\-summary\]/ism", '', $Text); $Text = preg_replace("/\[event\-description\](.*?)\[\/event\-description\]/ism", '', $Text); $Text = preg_replace("/\[event\-finish\](.*?)\[\/event\-finish\]/ism", '', $Text); $Text = preg_replace("/\[event\-id\](.*?)\[\/event\-id\]/ism", '', $Text); $Text = preg_replace("/\[event\-location\](.*?)\[\/event\-location\]/ism", '', $Text); $Text = preg_replace("/\[event\-timezone\](.*?)\[\/event\-timezone\]/ism", '', $Text); $Text = preg_replace("/\[event\-adjust\](.*?)\[\/event\-adjust\]/ism", '', $Text); $Text = str_replace("\0", '$', $Text); } // Unhide all [noparse] contained bbtags unspacefying them // and triming the [noparse] tag. if (str_contains($Text, '[noparse]')) { $Text = preg_replace_callback("/\[noparse\](.*?)\[\/noparse\]/ism", 'bb_unspacefy_and_trim', $Text); } if (str_contains($Text, '[nobb]')) { $Text = preg_replace_callback("/\[nobb\](.*?)\[\/nobb\]/ism", 'bb_unspacefy_and_trim', $Text); } if (str_contains($Text, '[pre]')) { $Text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'bb_unspacefy_and_trim', $Text); } // replace escaped links in code= blocks $Text = str_replace('%eY9-!', 'http', $Text); $Text = bb_code_unprotect($Text); // fix any escaped ampersands that may have been converted into links if (str_contains($Text, '&')) { $Text = preg_replace("/\<(.*?)(src|href)=(.*?)\&\;(.*?)\>/ism", '<$1$2=$3&$4>', $Text); } $Text = bb_replace_images($Text, $saved_images); $args = [ 'text' => $Text, 'options' => $options ]; Hook::call('bbcode', $args); return $args['text']; }