array('open' => $start_open, 'close' => $start_close), 'end' => array('open' => $end_open, 'close' => $end_open + strlen('[/' . $name . ']')) ); if ($start_equal !== false) { $res['start']['equal'] = $start_equal + 1; } return $res; } function bb_tag_preg_replace($pattern, $replace, $name, $s) { $string = $s; $occurrence = 1; $pos = get_bb_tag_pos($string, $name, $occurrence); while ($pos !== false && $occurrence < 1000) { $start = substr($string, 0, $pos['start']['open']); $subject = substr($string, $pos['start']['open'], $pos['end']['close'] - $pos['start']['open']); $end = substr($string, $pos['end']['close']); if ($end === false) { $end = ''; } $subject = preg_replace($pattern, $replace, $subject); $string = $start . $subject . $end; $occurrence++; $pos = get_bb_tag_pos($string, $name, $occurrence); } return $string; } function tryoembed($match) { $url = ((count($match) == 2) ? $match[1] : $match[2]); $o = Oembed::fetch_url($url); if ($o['type'] == 'error') { return $match[0]; } $html = Oembed::format_object($o); return $html; } function nakedoembed($match) { $url = ((count($match) == 2) ? $match[1] : $match[2]); $strip_url = strip_escaped_zids($url); // this function no longer performs oembed on naked links // because they author may have created naked links intentionally. // Now it just strips zids on naked links. return str_replace($url, $strip_url, $match[0]); } function tryzrlaudio($match) { $link = $match[1]; $zrl = isOWAEnabled($link); if ($zrl) { $link = zid($link); } return ''; } function tryzrlvideo($match) { $link = $match[1]; $zrl = isOWAEnabled($link); if ($zrl) { $link = zid($link); } $static_link = get_config('system', 'video_default_poster', 'images/video_poster.jpg'); if ($static_link) { $poster = 'poster="' . escape_tags($static_link) . '" ' ; } return ''; } function videowithopts($match) { $link = $match[2]; $zrl = isOWAEnabled($link); if ($zrl) { $link = zid($link); } $attributes = $match[1]; $poster = ""; preg_match("/poster='(.*?)'/ism", $attributes, $matches); if (isset($matches[1]) && $matches[1] != "") { $poster = 'poster="' . (($zrl) ? zid($matches[1]) : $matches[1]) . '"'; } preg_match("/poster=\"\;(.*?)\"\;/ism", $attributes, $matches); if (isset($matches[1]) && $matches[1] != "") { $poster = 'poster="' . (($zrl) ? zid($matches[1]) : $matches[1]) . '"'; } preg_match("/poster=\\\"(.*?)\\\"/ism", $attributes, $matches); if (isset($matches[1]) && $matches[1] != "") { $poster = 'poster="' . (($zrl) ? zid($matches[1]) : $matches[1]) . '"'; } $x = preg_match("/title='(.*?)'/ism", $attributes, $matches); if ($x) { $title = $matches[1]; } $x = preg_match("/title=\"\;(.*?)\"\;/ism", $attributes, $matches); if ($x) { $title = $matches[1]; } // This regex permits backslash-quote escapes inside the alt text. $x = preg_match('/title="([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"/ism', $attributes, $matches); if ($x) { $title = $matches[1]; } $title = (($title) ? 'title="' . htmlspecialchars(str_replace('\\"', '"', $title), ENT_QUOTES, 'UTF-8', false) . '"' : ''); return ''; } // [noparse][i]italic[/i][/noparse] turns into // [noparse][ i ]italic[ /i ][/noparse], // to hide them from parser. function bb_spacefy($st) { $whole_match = $st[0]; $captured = $st[1]; $spacefied = preg_replace("/\[(.*?)\]/", "[ $1 ]", $captured); $new_str = str_replace($captured, $spacefied, $whole_match); return $new_str; } // The previously spacefied [noparse][ i ]italic[ /i ][/noparse], // now turns back and the [noparse] tags are trimmed // returning [i]italic[/i] function bb_unspacefy_and_trim($st) { //$whole_match = $st[0]; $captured = $st[1]; $unspacefied = preg_replace("/\[ (.*?)\ ]/", "[$1]", $captured); return $unspacefied; } function bb_extract_images($body) { $saved_image = []; $orig_body = $body; $new_body = ''; $cnt = 0; $img_start = strpos($orig_body, '[img'); $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false); $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false); while (($img_st_close !== false) && ($img_end !== false)) { $img_st_close++; // make it point to AFTER the closing bracket $img_end += $img_start; if (! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) { // This is an embedded image $saved_image[$cnt] = substr($orig_body, $img_start + $img_st_close, $img_end - ($img_start + $img_st_close)); $new_body = $new_body . substr($orig_body, 0, $img_start) . '[$#saved_image' . $cnt . '#$]'; $cnt++; } else { $new_body = $new_body . substr($orig_body, 0, $img_end + strlen('[/img]')); } $orig_body = substr($orig_body, $img_end + strlen('[/img]')); if ($orig_body === false) { // in case the body ends on a closing image tag $orig_body = ''; } $img_start = strpos($orig_body, '[img'); $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false); $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false); } $new_body = $new_body . $orig_body; return array('body' => $new_body, 'images' => $saved_image); } function bb_replace_images($body, $images) { $newbody = $body; $cnt = 0; if (! $images) { return $newbody; } foreach ($images as $image) { // 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 . '#$]', '' . t('Image/photo') . '', $newbody); $cnt++; } // logger('replace_images: ' . $newbody); return $newbody; } /** * @brief Parses crypt BBCode. * * @param array $match * @return string HTML code */ function bb_parse_crypt($match) { $matches = []; $hint = ''; $algorithm = ''; $payload = $match[1]; if (isset($match[2])) { // backwards compatibility $attributes = $match[1]; $payload = $match[2]; preg_match("/alg='(.*?)'/ism", $attributes, $matches); $algorithm = $matches[1] ?? ''; if (!$algorithm) { preg_match("/alg=\"\;(.*?)\"\;/ism", $attributes, $matches); $algorithm = $matches[1] ?? ''; } if (!$algorithm) { preg_match("/alg=\\\"(.*?)\\\"/ism", $attributes, $matches); $algorithm = $matches[1] ?? ''; } $matches = []; preg_match("/hint='(.*?)'/ism", $attributes, $matches); $hint = $matches[1] ?? ''; if (!$hint) { preg_match("/hint=\"\;(.*?)\"\;/ism", $attributes, $matches); $hint = $matches[1] ?? ''; } if (!$hint) { preg_match("/hint=\\\"(.*?)\\\"/ism", $attributes, $matches); $hint = $matches[1] ?? ''; } } $x = random_string(32); $onclick = 'onclick="sodium_decrypt(\'' . $payload . '\',\'#' . $x . '\');"'; if (in_array($algorithm, ['AES-128-CCM', 'rot13', 'triple-rot13'])) { // backwards compatibility $onclick = 'onclick="hz_decrypt(\'' . $algorithm . '\',\'' . $hint . '\',\'' . $payload . '\',\'#' . $x . '\');"'; } $label = t('Encrypted content'); $Text = '
' . $label . '


' . bb_parse_b64_crypt($match); return $Text; } /** * @brief Returns raw base64 encoded crypt content. * * @param array $match * @return string */ function bb_parse_b64_crypt($match) { $r = '-----BEGIN ENCRYPTED MESSAGE-----' . "\n"; $r .= base64_encode($match[1]) . (($match[2]) ? "." . $match[2] : '') . "\n"; $r .= '-----END ENCRYPTED MESSAGE-----' . "\n"; $r = '' . str_replace("\n", '
', wordwrap($r, 75, "\n", true)) . '
'; return $r; } function bb_parse_app($match) { $app = Apps::app_decode($match[1]); if ($app) { return Apps::app_render($app); } } function bb_parse_app_ap($match) { $app = Apps::app_decode($match[1]); if ($app) { return sprintf(t('(Embedded app \'%s\' could not be displayed).'), $app['name']) . EOL . '' . $app['url'] . '' . EOL; } } function bb_svg($match) { $params = str_replace(['
', '"'], [ '', '"'], $match[1]); $Text = str_replace([ '[',']' ], [ '<','>' ], $match[2]); $output = '' . str_replace(['
', '"', ' '], [ '', '"', ' '], $Text) . ''; $purify = new SvgSanitizer(); $purify->loadXML($output); $purify->sanitize(); $output = $purify->saveSVG(); $output = preg_replace("/\<\?xml(.*?)\?\>/", '', $output); return $output; } function bb_svg_export($match) { $params = str_replace(['
', '"'], [ '', '"'], $match[1]); $Text = str_replace([ '[',']' ], [ '<','>' ], $match[2]); $output = '' . str_replace(['
', '"', ' '], [ '', '"', ' '], $Text) . ''; $purify = new SvgSanitizer(); $purify->loadXML($output); $purify->sanitize(); $output = $purify->saveSVG(); $output = preg_replace("/\<\?xml(.*?)\?\>/", '', $output); $output = 'svg'; return $output; } function bb_parse_element($match) { $j = json_decode(base64url_decode($match[1]), true); if ($j && local_channel()) { $text = sprintf(t('Install %1$s element %2$s'), translate_design_element($j['type']), $j['pagetitle']); $o = EOL . '' . EOL; } else { $text = sprintf(t('This post contains an installable %s element, however you lack permissions to install it on this site.'), translate_design_element($j['type'])) . $j['pagetitle']; $o = EOL . $text . EOL; } return $o; } function translate_design_element($type) { switch ($type) { case 'webpage': $ret = t('webpage'); break; case 'layout': $ret = t('layout'); break; case 'block': $ret = t('block'); break; case 'menu': $ret = t('menu'); break; } return $ret; } function bb_format_attachdata($body) { $data = getAttachmentData($body); if ($data) { $txt = ''; if ($data['url'] && $data['title']) { $txt .= "\n\n" . '[url=' . $data['url'] . ']' . $data['title'] . '[/url]'; } else { if ($data['url']) { $txt .= "\n\n" . $data['url']; } if ($data['title']) { $txt .= "\n\n" . $data['title']; } } if ($data['preview']) { $txt .= "\n\n" . '[img]' . $data['preview'] . '[/img]'; } if ($data['image']) { $txt .= "\n\n" . '[img]' . $data['image'] . '[/img]'; } $txt .= "\n\n" . $data['text']; return preg_replace('/\[attachment(.*?)\](.*?)\[\/attachment\]/ism', $txt, $body); } return $body; } function getAttachmentData($body) { $data = []; if (! preg_match("/\[attachment(.*?)\](.*?)\[\/attachment\]/ism", $body, $match)) { return null; } $attributes = $match[1]; $data["text"] = trim($match[2]); $type = ""; preg_match("/type='(.*?)'/ism", $attributes, $matches); if (x($matches, 1)) { $type = strtolower($matches[1]); } preg_match('/type=\"\;(.*?)\"\;/ism', $attributes, $matches); if (x($matches, 1)) { $type = strtolower($matches[1]); } preg_match('/type=\\\"(.*?)\\\"/ism', $attributes, $matches); if (x($matches, 1)) { $type = strtolower($matches[1]); } if ($type == "") { return []; } if (!in_array($type, ["link", "audio", "photo", "video"])) { return []; } if ($type != "") { $data["type"] = $type; } $url = ""; preg_match("/url='(.*?)'/ism", $attributes, $matches); if (x($matches, 1)) { $url = $matches[1]; } preg_match('/url=\"\;(.*?)\"\;/ism', $attributes, $matches); if (x($matches, 1)) { $url = $matches[1]; } preg_match('/url=\\\"(.*?)\\\"/ism', $attributes, $matches); if (x($matches, 1)) { $url = $matches[1]; } if ($url != "") { $data["url"] = html_entity_decode($url, ENT_QUOTES, 'UTF-8'); } $title = ""; preg_match("/title='(.*?)'/ism", $attributes, $matches); if (x($matches, 1)) { $title = $matches[1]; } preg_match('/title=\"\;(.*?)\"\;/ism', $attributes, $matches); if (x($matches, 1)) { $title = $matches[1]; } preg_match('/title=\\\"(.*?)\\\"/ism', $attributes, $matches); if (x($matches, 1)) { $title = $matches[1]; } if ($title != "") { $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8'); $title = str_replace(["[", "]"], ["[", "]"], $title); $data["title"] = $title; } $image = ""; preg_match("/image='(.*?)'/ism", $attributes, $matches); if (x($matches, 1)) { $image = $matches[1]; } preg_match('/image=\"\;(.*?)\"\;/ism', $attributes, $matches); if (x($matches, 1)) { $image = $matches[1]; } preg_match('/image=\\\"(.*?)\\\"/ism', $attributes, $matches); if (x($matches, 1)) { $image = $matches[1]; } if ($image != "") { $data["image"] = html_entity_decode($image, ENT_QUOTES, 'UTF-8'); } $preview = ""; preg_match("/preview='(.*?)'/ism", $attributes, $matches); if (x($matches, 1)) { $preview = $matches[1]; } preg_match('/preview=\"\;(.*?)\"\;/ism', $attributes, $matches); if (x($matches, 1)) { $preview = $matches[1]; } preg_match('/preview=\\\"(.*?)\\\"/ism', $attributes, $matches); if (x($matches, 1)) { $preview = $matches[1]; } if ($preview != "") { $data["preview"] = html_entity_decode($preview, ENT_QUOTES, 'UTF-8'); } $data["description"] = trim($match[3]); $data["after"] = trim($match[4]); return $data; } function bb_ShareAttributes($match) { $matches = []; $attributes = $match[1]; $author = ""; preg_match("/author='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") { $author = urldecode($matches[1]); } $link = ""; preg_match("/link='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") { $link = $matches[1]; } $avatar = ""; preg_match("/avatar='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") { $avatar = $matches[1]; } $profile = ""; preg_match("/profile='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") { $profile = $matches[1]; } $posted = ""; preg_match("/posted='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") { $posted = $matches[1]; } $auth = ""; preg_match("/auth='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") { if ($matches[1] === 'true') { $auth = true; } else { $auth = false; } } if ($auth === EMPTY_STR) { $auth = isOWAEnabled($profile); } // message_id is never used, do we still need it? $message_id = ""; preg_match("/message_id='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") { $message_id = $matches[1]; } if (! $message_id) { preg_match("/guid='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") { $message_id = $matches[1]; } } $reldate = '' . Time::convert('UTC', date_default_timezone_get(), $posted, 'r') . ''; // I was going to wrap this in
to make it semantically correct HTML and permit cross-platform styling, but doing so results in text/plain // projects stripping out the content completely. Hence that effort was reverted. $headline = '
'; if ($avatar != "") { $headline .= '' . htmlspecialchars($author, ENT_COMPAT, 'UTF-8', false) . ' '; } if (strpos($link, '/cards/')) { $type = t('card'); } elseif (strpos($link, '/album/')) { $type = t('album'); } elseif (strpos($link, '/articles/')) { $type = t('article'); } else { $type = t('post'); } // Bob Smith wrote the following post 2 hours ago $fmt = sprintf( t('%1$s wrote the following %2$s %3$s'), '' . $author . '', '' . $type . '', $reldate ); $headline .= '' . $fmt . '
'; return $headline . '
' . trim($match[2]) . '
'; } function bb_location($match) { // not yet implemented } /** * @brief Returns an iframe from $match[1]. * * @param array $match * @return string HTML iframe with content of $match[1] */ function bb_iframe($match) { $sandbox = ((strpos($match[1], App::get_hostname())) ? ' sandbox="allow-scripts" ' : ''); return ''; } function bb_ShareAttributesSimple($match) { $matches = []; $attributes = $match[1]; $author = ""; preg_match("/author='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") { $author = html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8'); } preg_match('/author="(.*?)"/ism', $attributes, $matches); if ($matches[1] != "") { $author = $matches[1]; } $profile = ""; preg_match("/profile='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") { $profile = $matches[1]; } preg_match('/profile="(.*?)"/ism', $attributes, $matches); if ($matches[1] != "") { $profile = $matches[1]; } $text = html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . ' ' . $author . ':
' . $match[2] . '
'; return($text); } function rpost_callback($match) { if ($match[2]) { return str_replace($match[0], Libzot::get_rpost_path(App::get_observer()) . '&title=' . urlencode($match[2]) . '&body=' . urlencode($match[3]), $match[0]); } else { return str_replace($match[0], Libzot::get_rpost_path(App::get_observer()) . '&body=' . urlencode($match[3]), $match[0]); } } function bb_map_latlon($match) { return str_replace($match[0], '
' . generate_map(floatval($match[1]),floatval($match[2])) . '
', $match[0]); } function bb_map_link($match) { $x = str_replace(['/', ','], [' ', ' '], $match[1]); $tmp = explode(' ', $x); if (count($tmp) > 1) { $lat = $tmp[0]; $lon = $tmp[1]; } // the extra space in the following line is intentional return str_replace($match[0], '' . t('Show map') . '', $match[0]); } function bb_map_coords($match) { $x = str_replace(['/', ','], [' ', ' '], $match[1]); $tmp = explode(' ', $x); if (count($tmp) > 1) { $lat = $tmp[0]; $lon = $tmp[1]; } // the extra space in the following line is intentional return str_replace($match[0], '
' . generate_map($lat,$lon) . '
', $match[0]); } function bb_map_location($match) { // the extra space in the following line is intentional return str_replace($match[0], '
' . generate_named_map($match[1]) . '
', $match[0]); } function bb_qr($match) { $str = $match[1]; return str_replace($match[0], '' . $str . '', $match[0]); } function bb_opentag($match) { $openclose = (($match[2]) ? '' . $match[1] . '' : t('Click to open/close')); $text = (($match[2]) ? $match[2] : $match[1]); $rnd = mt_rand(); return ''; } function bb_spoilertag($match) { $openclose = (($match[2]) ? '' . $match[1] . ' ' . t('spoiler') . '' : t('Click to open/close')); $text = (($match[2]) ?: $match[1]); $rnd = mt_rand(); return ''; } function bb_summary($match) { // return $match[1] . $match[2] . EOL . EOL . $match[3]; return $match[1] . '
' .$match[2] . '' . '
' . $match[3] . '
'; } function bb_definitionList($match) { // $match[1] is the markup styles for the "terms" in the definition list. // $match[2] is the content between the [dl]...[/dl] tags $classes = ''; if (stripos($match[1], "b") !== false) { $classes .= 'dl-terms-bold '; } if (stripos($match[1], "i") !== false) { $classes .= 'dl-terms-italic '; } if (stripos($match[1], "u") !== false) { $classes .= 'dl-terms-underline '; } if (stripos($match[1], "l") !== false) { $classes .= 'dl-terms-large '; } if (stripos($match[1], "m") !== false) { $classes .= 'dl-terms-monospace '; } if (stripos($match[1], "h") !== false) { $classes .= 'dl-horizontal '; // dl-horizontal is already provided by bootstrap } if (strlen($classes) === 0) { $classes = "dl-terms-plain"; } // The bbcode transformation will be: // [*=term-text] description-text =>
term-text
description-text // then after all replacements have been made, the extra
at the start of the // first line can be removed. HTML5 allows the tag to be missing from the end of the last line. // Using '(?\n"; $eatLeadingSpaces = '(?: |[ \t])*'; // prevent spaces infront of [*= from adding another line to the previous element $listElements = preg_replace('/^(\n|
)/', '', $match[2]); // ltrim the first newline $listElements = preg_replace( '/' . $eatLeadingSpaces . '\[\*=([[:print:]]*?)(?$1
', $listElements ); // Unescape any \] inside the
tags $listElements = preg_replace_callback('/
(.*?)<\/dt>/ism', 'bb_definitionList_unescapeBraces', $listElements); // Remove the extra at the start of the string, if there is one. $firstOpenTag = strpos($listElements, '
'); $firstCloseTag = strpos($listElements, $closeDescriptionTag); if ($firstCloseTag !== false && ($firstOpenTag === false || ($firstCloseTag < $firstOpenTag))) { $listElements = preg_replace('/<\/dd>/ism', '', $listElements, 1); } return '
' . $listElements . '
'; } function bb_definitionList_unescapeBraces($match) { return '
' . str_replace('\]', ']', $match[1]) . '
'; } function bb_checklist($match) { $str = $match[1]; $str = str_replace("[]", "
  • ", $str); $str = str_replace("[x]", "
  • ", $str); return '
      ' . $str . '
    '; } /** * @brief Sanitize style properties from BBCode to HTML. * * @param array|string $input * @return string A HTML span tag with the styles. */ function bb_sanitize_style($input) { // whitelist array: property => limits (0 = no limitation) $w = array( // color properties "color" => 0, "background-color" => 0, // box properties "padding" => array("px" => 100, "%" => 0, "em" => 2, "ex" => 2, "mm" => 0, "cm" => 0, "in" => 0, "pt" => 0, "pc" => 0), "margin" => array("px" => 100, "%" => 0, "em" => 2, "ex" => 2, "mm" => 0, "cm" => 0, "in" => 0, "pt" => 0, "pc" => 0), "border" => array("px" => 100, "%" => 0, "em" => 2, "ex" => 2, "mm" => 0, "cm" => 0, "in" => 0, "pt" => 0, "pc" => 0), "float" => 0, "clear" => 0, // text properties "text-decoration" => 0, ); // determine if input is string or array $input_is_array = is_array($input); $css = []; $css_string = (($input_is_array) ? $input[1] : $input); $a = explode(';', $css_string); foreach ($a as $parts) { list($k, $v) = explode(':', $parts); $css[ trim($k) ] = trim($v); } // sanitize properties $b = array_merge(array_diff_key($css, $w), array_diff_key($w, $css)); $css = array_diff_key($css, $b); $css_string_san = ''; foreach ($css as $key => $value) { if ($w[$key] != null) { foreach ($w[$key] as $limit_key => $limit_value) { //sanitize values if (strpos($value, $limit_key)) { $value = preg_replace_callback( "/(\S.*?)$limit_key/ism", function ($match) use ($limit_value, $limit_key) { if ($match[1] > $limit_value) { return $limit_value . $limit_key; } else { return $match[1] . $limit_key; } }, $value ); } } } $css_string_san .= $key . ":" . $value . "; "; } if ($input_is_array) { return '' . $input[2] . ''; } return $css_string_san; } function obnetwork_callback($matches) { $observer = App::get_observer(); if ($observer && $observer['xchan_network'] === $matches[1]) { return $matches[2]; } return ''; } function obnetwork_necallback($matches) { $observer = App::get_observer(); if ($observer && $observer['xchan_network'] === $matches[1]) { return ''; } return $matches[2]; } function oblanguage_callback($matches) { if (strlen($matches[1]) == 2) { $compare = strtolower(substr(App::$language, 0, 2)); } else { $compare = strtolower(App::$language); } if ($compare === strtolower($matches[1])) { return $matches[2]; } return ''; } function oblanguage_necallback($matches) { if (strlen($matches[1]) == 2) { $compare = strtolower(substr(App::$language, 0, 2)); } else { $compare = strtolower(App::$language); } if ($compare !== strtolower($matches[1])) { return $matches[2]; } return ''; } function bb_observer($Text) { $observer = App::get_observer(); if ((str_contains($Text, '[/observer]')) || (str_contains($Text, '[/rpost]'))) { if ($observer) { $Text = preg_replace("/\[observer\=1\](.*?)\[\/observer\]/ism", '$1', $Text); $Text = preg_replace("/\[observer\=0\].*?\[\/observer\]/ism", '', $Text); $Text = preg_replace_callback("/\[rpost(=(.*?))?\](.*?)\[\/rpost\]/ism", 'rpost_callback', $Text); } else { $Text = preg_replace("/\[observer\=1\].*?\[\/observer\]/ism", '', $Text); $Text = preg_replace("/\[observer\=0\](.*?)\[\/observer\]/ism", '$1', $Text); $Text = preg_replace("/\[rpost(=.*?)?\](.*?)\[\/rpost\]/ism", '', $Text); } } $channel = App::get_channel(); if (str_contains($Text, '[/channel]')) { if ($channel) { $Text = preg_replace("/\[channel\=1\](.*?)\[\/channel\]/ism", '$1', $Text); $Text = preg_replace("/\[channel\=0\].*?\[\/channel\]/ism", '', $Text); } else { $Text = preg_replace("/\[channel\=1\].*?\[\/channel\]/ism", '', $Text); $Text = preg_replace("/\[channel\=0\](.*?)\[\/channel\]/ism", '$1', $Text); } } return $Text; } function bb_imgoptions($match) { // $Text = preg_replace_callback("/\[([zi])mg([ \=])(.*?)\](.*?)\[\/[zi]mg\]/ism",'bb_imgoptions',$Text); // alt text cannot contain ']' // [img|zmg=wwwxhhh float=left|right alt=alt text]url[/img|zmg] // [img|zmg width="nnn" height="nnn" alt="xyz" style="float: abc;"]url[/img|zmg] $local_match = null; $width = 0; $float = false; $alt = false; $src = false; $style = EMPTY_STR; $attributes = $match[3]; $x = preg_match("/alt='(.*?)'/ism", $attributes, $matches); if ($x) { $alt = $matches[1]; } $x = preg_match("/alt=\"\;(.*?)\"\;/ism", $attributes, $matches); if ($x) { $alt = $matches[1]; } // This regex permits backslash-quote escapes inside the alt text. $x = preg_match('/alt="([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"/ism', $attributes, $matches); if ($x) { $alt = $matches[1]; } $x = preg_match("/width=([0-9]*)/ism", $attributes, $matches); if ($x) { $width = bb_xss($matches[1]); } $x = preg_match("/width='(.*?)'/ism", $attributes, $matches); if ($x) { $width = bb_xss($matches[1]); } $x = preg_match("/width=\"\;(.*?)\"\;/ism", $attributes, $matches); if ($x) { $width = bb_xss($matches[1]); } $x = preg_match("/width=\\\"(.*?)\\\"/ism", $attributes, $matches); if ($x) { $width = bb_xss($matches[1]); } $x = preg_match("/height=([0-9]*)/ism", $attributes, $matches); if ($x) { $height = bb_xss($matches[1]); } $x = preg_match("/height='(.*?)'/ism", $attributes, $matches); if ($x) { $height = bb_xss($matches[1]); } $x = preg_match("/height=\"\;(.*?)\"\;/ism", $attributes, $matches); if ($x) { $height = bb_xss($matches[1]); } $x = preg_match("/height=\\\"(.*?)\\\"/ism", $attributes, $matches); if ($x) { $height = bb_xss($matches[1]); } $x = preg_match("/style='(.*?)'/ism", $attributes, $matches); if ($x) { $style = bb_sanitize_style($matches[1]); } $x = preg_match("/style=\"\;(.*?)\"\;/ism", $attributes, $matches); if ($x) { $style = bb_sanitize_style($matches[1]); } $x = preg_match("/style=\\\"(.*?)\\\"/ism", $attributes, $matches); if ($x) { $style = bb_sanitize_style($matches[1]); } // legacy img options if ($match[2] === '=') { if (str_starts_with($attributes, 'http')) { $alt = $match[4]; $src = $match[3]; } else { // pull out (optional) legacy size declarations first if (preg_match("/([0-9]*)x([0-9]*)/ism", $match[3], $local_match)) { $width = intval($local_match[1]); } $match[3] = substr($match[3], strpos($match[3], ' ')); } } // then (optional) legacy float specifiers if ($n = str_contains($match[3], 'float=left')) { $float = 'left'; $match[3] = substr($match[3], $n + 10); } if ($n = str_contains($match[3], 'float=right')) { $float = 'right'; $match[3] = substr($match[3], $n + 11); } // finally alt text which extends to the close of the tag if ((! $alt) && ($n = str_contains($match[3], 'alt='))) { $alt = substr($match[3], $n + 4); } // now assemble the resulting img tag from these components $output = ''; 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) { $token = strtok($matches[1],'= '); if (!$token) { $token = $matches[1]; } if ($token === $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 'censored'; } 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('%s', $headingLevel, $anchor, $header, $headingLevel); } function md_codeblock($content) { $language = !empty($content[3]) ? filter_var($content[3], FILTER_SANITIZE_STRING) : ''; $class = !empty($language) ? sprintf(' class="%s language-%s"', $language, $language) : ''; // Build one block so that we not render each paragraph separately. $content = str_replace("\n", '
    ', $content[4]); return sprintf('
    %s
    ', $class, bb_code_protect($content)); } function md_italic($content) { return '' . $content[1] . $content[3] . ''; } function md_del($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="' . htmlentities(filter_var($content[2], FILTER_SANITIZE_STRING)) . '"'; $title = str_replace('alt=', 'title=', $alt); } /** @noinspection HtmlRequiredAltAttribute */ return sprintf('', $url, $alt, $title); } 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 "" . $matches[1] . "" . "\n"; } function bb_fixtable_lf($match) { // bbcode version (1 arg) // 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[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 '' . $x . ''; } function bb_colortag($matches) { return '' . $matches[2] . ''; } function bb_fonttag($matches) { return '' . $matches[2] . ''; } function bb_sizetag($matches) { return '' . $matches[2] . ''; } function bb_hltag($matches) { return '' . $matches[2] . ''; } function bb_nakedlinks($Text) { $urlchars = '[a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\@\(\)]'; $Text = preg_replace_callback('/\[url(.*?)\[\/(url)\]/ism', '\red_escape_codeblock', $Text); $Text = preg_replace_callback('/\[zrl(.*?)\[\/(zrl)\]/ism', '\red_escape_codeblock', $Text); $Text = preg_replace_callback('/\[svg(.*?)\[\/(svg)\]/ism', '\red_escape_codeblock', $Text); $Text = preg_replace_callback('/\[img(.*?)\[\/(img)\]/ism', '\red_escape_codeblock', $Text); $Text = preg_replace_callback('/\[zmg(.*?)\[\/(zmg)\]/ism', '\red_escape_codeblock', $Text); $Text = preg_replace_callback('/\[audio(.*?)\[\/(audio)\]/ism', '\red_escape_codeblock', $Text); $Text = preg_replace_callback('/\[video(.*?)\[\/(video)\]/ism', '\red_escape_codeblock', $Text); $Text = preg_replace_callback('/\[oembed(.*?)\[\/(oembed)\]/ism', '\red_escape_codeblock', $Text); if (str_contains($Text, 'http')) { $Text = preg_replace("/([^\]\='" . '"' . "\;\/]\()(https?\:\/\/$urlchars+)/ismu", '$1$2', $Text); } $Text = preg_replace_callback('/\[\$b64url(.*?)\[\/(url)\]/ism', '\red_unescape_codeblock', $Text); $Text = preg_replace_callback('/\[\$b64zrl(.*?)\[\/(zrl)\]/ism', '\red_unescape_codeblock', $Text); $Text = preg_replace_callback('/\[\$b64svg(.*?)\[\/(svg)\]/ism', '\red_unescape_codeblock', $Text); $Text = preg_replace_callback('/\[\$b64img(.*?)\[\/(img)\]/ism', '\red_unescape_codeblock', $Text); $Text = preg_replace_callback('/\[\$b64zmg(.*?)\[\/(zmg)\]/ism', '\red_unescape_codeblock', $Text); $Text = preg_replace_callback('/\[\$b64audio(.*?)\[\/(audio)\]/ism', '\red_unescape_codeblock', $Text); $Text = preg_replace_callback('/\[\$b64video(.*?)\[\/(video)\]/ism', '\red_unescape_codeblock', $Text); $Text = preg_replace_callback('/\[\$b64oembed(.*?)\[\/(oembed)\]/ism', '\red_unescape_codeblock', $Text); return $Text; } function bb_xss($s) { // don't allow functions of any kind $s = str_replace([ '(', ')' ], [ '', '' ], $s); // don't allow injection of multiple params if (str_contains($s, ';')) { return substr($s, 0, strpos($s, ';')); } return $s; } function bbtopoll($s) { $pl = []; $match = ''; if (! preg_match("/\[poll=(.*?)\](.*?)\[\/poll\]/ism", $s, $match)) { return null; } $pl['poll_id'] = $match[1]; $pl['poll_question'] = $match[2]; $match = []; if (preg_match_all("/\[poll\-answer=(.*?)\](.*?)\[\/poll\-answer\]/is", $s, $match, PREG_SET_ORDER)) { $pl['answer'] = []; foreach ($match as $m) { $ans = [ 'answer_id' => $m[1], 'answer_text' => $m[2] ]; $pl['answer'][] = $ans; } } return $pl; } function parseIdentityAwareHTML($Text) { // Hide all [noparse] contained bbtags by spacefying them if (str_contains($Text, '[noparse]')) { $Text = preg_replace_callback("/\[noparse\](.*?)\[\/noparse\]/ism", 'bb_spacefy', $Text); } if (str_contains($Text, '[nobb]')) { $Text = preg_replace_callback("/\[nobb\](.*?)\[\/nobb\]/ism", 'bb_spacefy', $Text); } if (str_contains($Text, '[pre]')) { $Text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'bb_spacefy', $Text); } // process [observer] tags before we do anything else because we might // be stripping away stuff that then doesn't need to be worked on anymore $observer = App::get_observer(); if ((str_contains($Text, '[/observer]')) || (str_contains($Text, '[/rpost]'))) { $Text = preg_replace_callback("/\[observer\.language\=(.*?)\](.*?)\[\/observer\]/ism", 'oblanguage_callback', $Text); $Text = preg_replace_callback("/\[observer\.language\!\=(.*?)\](.*?)\[\/observer\]/ism", 'oblanguage_necallback', $Text); $Text = preg_replace_callback("/\[observer\.network\=(.*?)\](.*?)\[\/observer\]/ism", 'obnetwork_callback', $Text); $Text = preg_replace_callback("/\[observer\.network\!\=(.*?)\](.*?)\[\/observer\]/ism", 'obnetwork_necallback', $Text); if ($observer) { $Text = preg_replace("/\[observer\=1\](.*?)\[\/observer\]/ism", '$1', $Text); $Text = preg_replace("/\[observer\=0\].*?\[\/observer\]/ism", '', $Text); $Text = preg_replace_callback("/\[rpost(=(.*?))?\](.*?)\[\/rpost\]/ism", 'rpost_callback', $Text); } else { $Text = preg_replace("/\[observer\=1\].*?\[\/observer\]/ism", '', $Text); $Text = preg_replace("/\[observer\=0\](.*?)\[\/observer\]/ism", '$1', $Text); $Text = preg_replace("/\[rpost(=.*?)?\](.*?)\[\/rpost\]/ism", '', $Text); } } // replace [observer.baseurl] if ($observer) { $s1 = ''; $s2 = ''; $obsBaseURL = $observer['xchan_connurl']; $obsBaseURL = preg_replace("/\/poco\/.*$/", '', $obsBaseURL); $Text = str_replace('[observer.baseurl]', $obsBaseURL, $Text); $Text = str_replace('[observer.url]', $observer['xchan_url'], $Text); $Text = str_replace('[observer.name]', $s1 . $observer['xchan_name'] . $s2, $Text); $Text = str_replace('[observer.address]', $s1 . $observer['xchan_addr'] . $s2, $Text); $Text = str_replace('[observer.webname]', substr($observer['xchan_addr'], 0, strpos($observer['xchan_addr'], '@')), $Text); $Text = str_replace('[observer.photo]', $s1 . '[zmg]' . $observer['xchan_photo_l'] . '[/zmg]' . $s2, $Text); $Text = str_replace('[observer.baseurl/]', $obsBaseURL, $Text); $Text = str_replace('[observer.url/]', $observer['xchan_url'], $Text); $Text = str_replace('[observer.name/]', $s1 . $observer['xchan_name'] . $s2, $Text); $Text = str_replace('[observer.address/]', $s1 . $observer['xchan_addr'] . $s2, $Text); $Text = str_replace('[observer.webname/]', substr($observer['xchan_addr'], 0, strpos($observer['xchan_addr'], '@')), $Text); $Text = str_replace('[observer.photo/]', $s1 . '[zmg]' . $observer['xchan_photo_l'] . '[/zmg]' . $s2, $Text); } else { $Text = str_replace('[observer.baseurl]', '', $Text); $Text = str_replace('[observer.url]', '', $Text); $Text = str_replace('[observer.name]', '', $Text); $Text = str_replace('[observer.address]', '', $Text); $Text = str_replace('[observer.webname]', '', $Text); $Text = str_replace('[observer.photo]', '', $Text); $Text = str_replace('[observer.baseurl/]', '', $Text); $Text = str_replace('[observer.url/]', '', $Text); $Text = str_replace('[observer.name/]', '', $Text); $Text = str_replace('[observer.address/]', '', $Text); $Text = str_replace('[observer.webname/]', '', $Text); $Text = str_replace('[observer.photo/]', '', $Text); } $Text = str_replace(array('[baseurl]','[baseurl/]','[sitename]','[sitename/]'), array(z_root(),z_root(), get_config('system', 'sitename'),get_config('system', 'sitename')), $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); } return $Text; } function bbcode($Text, $options = []) { if (! is_array($options)) { $options = []; } if (is_array($Text)) { btlogger('Text is array: ' . print_r($Text, true)); } $cache = ((array_key_exists('cache', $options)) ? $options['cache'] : false); $newwin = ((array_key_exists('newwin', $options)) ? $options['newwin'] : true); $export = ((array_key_exists('export', $options)) ? $options['export'] : false); $activitypub = ((array_key_exists('activitypub', $options)) ? $options['activitypub'] : false); $censored = ((array_key_exists('censored', $options)) ? $options['censored'] : false); $plain = ((array_key_exists('plain', $options)) ? $options['plain'] : false); $bbonly = ((array_key_exists('bbonly', $options)) ? $options['bbonly'] : false); if ($activitypub) { $export = true; } $target = (($newwin) ? ' target="_blank" ' : ''); Hook::call('bbcode_filter', $Text); // Hide all [noparse] contained bbtags by spacefying them if (str_contains($Text, '[noparse]')) { $Text = preg_replace_callback("/\[noparse\](.*?)\[\/noparse\]/ism", 'bb_spacefy', $Text); } if (str_contains($Text, '[nobb]')) { $Text = preg_replace_callback("/\[nobb\](.*?)\[\/nobb\]/ism", 'bb_spacefy', $Text); } if (str_contains($Text, '[pre]')) { $Text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'bb_spacefy', $Text); } $Text = preg_replace("/\[ltr\](.*?)\[\/ltr\]/ism",'
    $1
    ', $Text); $Text = preg_replace("/\[rtl\](.*?)\[\/rtl\]/ism",'
    $1
    ', $Text); $Text = bb_format_attachdata($Text); // If we find any event code, turn it into an event. // After we're finished processing the bbcode we'll // replace all of the event code with a reformatted version. $ev = bbtoevent($Text); // and the same with polls $pl = bbtopoll($Text); // process [observer] tags before we do anything else because we might // be stripping away stuff that then doesn't need to be worked on anymore if ($cache || $export) { $observer = false; } else { $observer = App::get_observer(); } if ((str_contains($Text, '[/observer]')) || (str_contains($Text, '[/rpost]'))) { $Text = preg_replace_callback("/\[observer\.language\=(.*?)\](.*?)\[\/observer\]/ism", 'oblanguage_callback', $Text); $Text = preg_replace_callback("/\[observer\.language\!\=(.*?)\](.*?)\[\/observer\]/ism", 'oblanguage_necallback', $Text); if ($observer) { $Text = preg_replace_callback("/\[observer\.network\=(.*?)\](.*?)\[\/observer\]/ism", 'obnetwork_callback', $Text); $Text = preg_replace_callback("/\[observer\.network\!\=(.*?)\](.*?)\[\/observer\]/ism", 'obnetwork_necallback', $Text); } else { $Text = preg_replace("/\[observer\.network(.*?)\](.*?)\[\/observer\]/ism", '', $Text); } if ($observer) { $Text = preg_replace("/\[observer\=1\](.*?)\[\/observer\]/ism", '$1', $Text); $Text = preg_replace("/\[observer\=0\].*?\[\/observer\]/ism", '', $Text); $Text = preg_replace_callback("/\[rpost(=(.*?))?\](.*?)\[\/rpost\]/ism", 'rpost_callback', $Text); } else { $Text = preg_replace("/\[observer\=1\].*?\[\/observer\]/ism", '', $Text); $Text = preg_replace("/\[observer\=0\](.*?)\[\/observer\]/ism", '$1', $Text); $Text = preg_replace("/\[rpost(=.*?)?\](.*?)\[\/rpost\]/ism", '', $Text); } } if ($cache || $export) { $channel = false; } else { $channel = App::get_channel(); } if (str_contains($Text, '[/channel]')) { if ($channel) { $Text = preg_replace("/\[channel\=1\](.*?)\[\/channel\]/ism", '$1', $Text); $Text = preg_replace("/\[channel\=0\].*?\[\/channel\]/ism", '', $Text); } else { $Text = preg_replace("/\[channel\=1\].*?\[\/channel\]/ism", '', $Text); $Text = preg_replace("/\[channel\=0\](.*?)\[\/channel\]/ism", '$1', $Text); } } $x = bb_extract_images($Text); $Text = $x['body']; $saved_images = $x['images']; if (! $export) { $Text = str_replace(array('[baseurl]','[baseurl/]','[sitename]','[sitename/]'), array(z_root(),z_root(), get_config('system', 'sitename'),get_config('system', 'sitename')), $Text); } // Replace any html brackets with HTML Entities to prevent executing HTML or script // Don't use strip_tags here because it breaks [url] search by replacing & with amp // These are no longer needed since we run the content through purify_html() // $Text = str_replace("<", "<", $Text); // $Text = str_replace(">", ">", $Text); // Check for [code] text here, before the linefeeds are messed with. // The highlighter will unescape and re-escape the content. if (str_contains($Text, '[code=')) { $Text = preg_replace_callback("/\[code=(.*?)\](.*?)\[\/code\]/ism", function ($match) use ($options) { return bb_code_protect(text_highlight($match[2], strtolower($match[1]), $options)); }, $Text); } $Text = preg_replace_callback("/\[table\](.*?)\[\/table\]/ism", 'bb_fixtable_lf', $Text); $Text = preg_replace_callback("/\(.*?)\<\/table\>/ism", 'ht_fixtable_lf', $Text); $Text = str_replace("\r\n", "\n", $Text); if ($bbonly) { $Text = escape_tags($Text); } // Convert new line chars to html
    tags $Text = str_replace("\n", '
    ', $Text); $Text = str_replace(array("\t", " "), array("    ", "  "), $Text); // Check for [code] text if (str_contains($Text, '[code]')) { $Text = preg_replace_callback("/\[code\](.*?)\[\/code\]/ism", 'bb_code', $Text); } // Check for [code options] text if (str_contains($Text, '[code ')) { $Text = preg_replace_callback("/\[code(.*?)\](.*?)\[\/code\]/ism", 'bb_code_options', $Text); } // Set up the parameters for a URL search string $URLSearchString = "^\[\]"; // Set up the parameters for a MAIL search string $MAILSearchString = $URLSearchString; // replace [observer.baseurl] if ($observer) { $s1 = ''; $s2 = ''; $obsBaseURL = $observer['xchan_connurl']; $obsBaseURL = preg_replace("/\/poco\/.*$/", '', $obsBaseURL); $Text = str_replace('[observer.baseurl]', $obsBaseURL, $Text); $Text = str_replace('[observer.url]', $observer['xchan_url'], $Text); $Text = str_replace('[observer.name]', $s1 . $observer['xchan_name'] . $s2, $Text); $Text = str_replace('[observer.address]', $s1 . $observer['xchan_addr'] . $s2, $Text); $Text = str_replace('[observer.webname]', substr($observer['xchan_addr'], 0, strpos($observer['xchan_addr'], '@')), $Text); $Text = str_replace('[observer.photo]', $s1 . '[zmg]' . $observer['xchan_photo_l'] . '[/zmg]' . $s2, $Text); $Text = str_replace('[observer.baseurl/]', $obsBaseURL, $Text); $Text = str_replace('[observer.url/]', $observer['xchan_url'], $Text); $Text = str_replace('[observer.name/]', $s1 . $observer['xchan_name'] . $s2, $Text); $Text = str_replace('[observer.address/]', $s1 . $observer['xchan_addr'] . $s2, $Text); $Text = str_replace('[observer.webname/]', substr($observer['xchan_addr'], 0, strpos($observer['xchan_addr'], '@')), $Text); $Text = str_replace('[observer.photo/]', $s1 . '[zmg]' . $observer['xchan_photo_l'] . '[/zmg]' . $s2, $Text); } else { $Text = str_replace('[observer.baseurl]', '', $Text); $Text = str_replace('[observer.url]', '', $Text); $Text = str_replace('[observer.name]', '', $Text); $Text = str_replace('[observer.address]', '', $Text); $Text = str_replace('[observer.webname]', '', $Text); $Text = str_replace('[observer.photo]', '', $Text); $Text = str_replace('[observer.baseurl/]', '', $Text); $Text = str_replace('[observer.url/]', '', $Text); $Text = str_replace('[observer.name/]', '', $Text); $Text = str_replace('[observer.address/]', '', $Text); $Text = str_replace('[observer.webname/]', '', $Text); $Text = str_replace('[observer.photo/]', '', $Text); } // Perform URL Search $count = 0; while (str_contains($Text, '[/share]') && $count < 10) { $Text = preg_replace_callback("/\[share(.*?)\](.*?)\[\/share\]/ism", 'bb_ShareAttributes', $Text); $count++; } if (str_contains($Text, '[/url]')) { $Text = preg_replace("/\#\^\[url\]([$URLSearchString]*?)\[\/url\]/ism", '$1', $Text); $Text = preg_replace("/\#\^\[url\=([$URLSearchString]*?)\](.*?)\[\/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\]([$URLSearchString]*?)\[\/url\]/ism", '$1', $Text); $Text = preg_replace("/\@(\!?)\[url\=([$URLSearchString]*?)\](.*?)\[\/url\]/ism", '@$1$3', $Text); $Text = preg_replace("/\!(\!?)\[url\=([$URLSearchString]*?)\](.*?)\[\/url\]/ism", '!$1$3', $Text); $Text = preg_replace("/\[url\=([$URLSearchString]*?)\](.*?)\[\/url\]/ism", '$2', $Text); } if (str_contains($Text, '[/zrl]')) { // render hubzilla bookmarks as normal links $Text = preg_replace("/\#\^\[zrl\]([$URLSearchString]*?)\[\/zrl\]/ism", '$1', $Text); $Text = preg_replace("/\#\^\[zrl\=([$URLSearchString]*?)\](.*?)\[\/zrl\]/ism", '$2', $Text); $Text = preg_replace("/\#\[zrl\]([$URLSearchString]*?)\[\/zrl\]/ism", '#$1', $Text); $Text = preg_replace("/\#\[zrl\=([$URLSearchString]*?)\](.*?)\[\/zrl\]/ism", '#$2', $Text); $Text = preg_replace("/\[zrl\]([$URLSearchString]*?)\[\/zrl\]/ism", '$1', $Text); $Text = preg_replace("/\@(\!?)\[zrl\=([$URLSearchString]*?)\](.*?)\[\/zrl\]/ism", '@$1$3', $Text); $Text = preg_replace("/\!(\!?)\[zrl\=([$URLSearchString]*?)\](.*?)\[\/zrl\]/ism", '!$1$3', $Text); $Text = preg_replace("/\[zrl\=([$URLSearchString]*?)\](.*?)\[\/zrl\]/ism", '$2', $Text); } // named anchors do not work well in conversational text, as it is often collapsed by a "showmore" script. // Included here for completeness. if (str_contains($Text, '[/anchor]')) { $Text = preg_replace("/\[anchor\](.*?)\[\/anchor\]/ism", '', $Text); } if (str_contains($Text, '[/goto]')) { $Text = preg_replace("/\[goto=(.*?)\](.*?)\[\/goto\]/ism", '$2', $Text); } // Perform MAIL Search if (str_contains($Text, '[/mail]')) { $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 prepare_body() which has knowledge of the item location if ($export) { $Text = str_replace([ '[map]','[/map]' ], [ '','' ], $Text); $Text = preg_replace_callback("/\[map=(.*?)\]/ism", 'bb_map_link', $Text); } else { if (str_contains($Text, '[/map]')) { $Text = preg_replace_callback("/\[map\](.*?)\[\/map\]/ism", 'bb_map_location', $Text); } if (str_contains($Text, '[map=')) { $Text = preg_replace_callback("/\[map=(.*?)\/\]/ism", 'bb_map_coords', $Text); $Text = preg_replace_callback("/\[map=(.*?)\]/ism", 'bb_map_coords', $Text); } if (str_contains($Text, '[map]')) { $Text = preg_replace("/\[map\/\]/", '
    ', $Text); $Text = preg_replace("/\[map\]/", '
    ', $Text); } } // Check for bold text if (str_contains($Text, '[b]')) { $Text = preg_replace("(\[b\](.*?)\[\/b\])ism", '$1', $Text); } // Check for Italics text if (str_contains($Text, '[i]')) { $Text = preg_replace("(\[i](.*?)\[/i])ism", '$1', $Text); } // Check for Underline text if (str_contains($Text, '[u]')) { $Text = preg_replace("(\[u](.*?)\[/u])ism", '$1', $Text); } // Check for strike-through text if (str_contains($Text, '[s]')) { $Text = preg_replace("(\[s\](.*?)\[\/s\])ism", '$1', $Text); } // Check for over-line text if (str_contains($Text, '[o]')) { $Text = preg_replace("(\[o\](.*?)\[\/o\])ism", '$1', $Text); } if (str_contains($Text, '[sup]')) { $Text = preg_replace("(\[sup\](.*?)\[\/sup\])ism", '$1', $Text); } if (str_contains($Text, '[sub]')) { $Text = preg_replace("(\[sub\](.*?)\[\/sub\])ism", '$1', $Text); } // Check for colored text if (str_contains($Text, '[/color]')) { $Text = preg_replace_callback("(\[color=(.*?)\](.*?)\[\/color\])ism", 'bb_colortag', $Text); } // Check for highlighted text if (str_contains($Text, '[/hl]')) { $Text = preg_replace("(\[hl\](.*?)\[\/hl\])ism", "$1", $Text); $Text = preg_replace_callback("(\[mark=(.*?)\](.*?)\[\/mark\])ism", 'bb_hltag', $Text); } // Check for highlighted text if (str_contains($Text, '[/mark]')) { $Text = preg_replace("(\[mark\](.*?)\[\/mark\])ism", "$1", $Text); $Text = preg_replace_callback("(\[mark=(.*?)\](.*?)\[\/mark\])ism", 'bb_hltag', $Text); } // Check for sized text // [size=50] --> font-size: 50px (with the unit). if (str_contains($Text, '[/size]')) { $Text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism", "$2", $Text); $Text = preg_replace_callback("(\[size=(.*?)\](.*?)\[\/size\])ism", 'bb_sizetag', $Text); } // Check for h1 if (str_contains($Text, '[h1]')) { $Text = preg_replace("(\[h1\](.*?)\[\/h1\])ism", '

    $1

    ', $Text); $Text = str_replace('
    ', '', $Text); } // Check for h2 if (str_contains($Text, '[h2]')) { $Text = preg_replace("(\[h2\](.*?)\[\/h2\])ism", '

    $1

    ', $Text); $Text = str_replace('
    ', '', $Text); } // Check for h3 if (str_contains($Text, '[h3]')) { $Text = preg_replace("(\[h3\](.*?)\[\/h3\])ism", '

    $1

    ', $Text); $Text = str_replace('
    ', '', $Text); } // Check for h4 if (str_contains($Text, '[h4]')) { $Text = preg_replace("(\[h4\](.*?)\[\/h4\])ism", '

    $1

    ', $Text); $Text = str_replace('
    ', '', $Text); } // Check for h5 if (str_contains($Text, '[h5]')) { $Text = preg_replace("(\[h5\](.*?)\[\/h5\])ism", '
    $1
    ', $Text); $Text = str_replace('
    ', '', $Text); } // Check for h6 if (str_contains($Text, '[h6]')) { $Text = preg_replace("(\[h6\](.*?)\[\/h6\])ism", '
    $1
    ', $Text); $Text = str_replace('
    ', '', $Text); } // Check for table of content without params while (str_contains($Text, '[toc]')) { $toc_id = 'toc-' . random_string(10); $Text = preg_replace("/\[toc\]/ism", '
      ', $Text, 1); $Text = preg_replace("/\[toc\/\]/ism", '
        ', $Text, 1); } // Check for table of content with params while (str_contains($Text, '[toc')) { $toc_id = 'toc-' . random_string(10); /** @noinspection HtmlUnknownAttribute */ $Text = preg_replace("/\[toc([^\]]+?)\/\]/ism", '
          ', $Text, 1); /** @noinspection HtmlUnknownAttribute */ $Text = preg_replace("/\[toc([^\]]+?)\]/ism", '
            ', $Text, 1); } // Check for centered text if (str_contains($Text, '[/center]')) { $Text = preg_replace("(\[center\](.*?)\[\/center\])ism", "
            $1
            ", $Text); } // Check for footer if (str_contains($Text, '[/footer]')) { $Text = preg_replace("(\[footer\](.*?)\[\/footer\])ism", "
            $1
            ", $Text); } // Check for bdi if (str_contains($Text, '[/bdi]')) { $Text = preg_replace("(\[bdi\](.*?)\[\/bdi\])ism", "$1", $Text); } // Check for list text $Text = preg_replace("/
            \[\*\]/ism", "[*]", $Text); $Text = str_replace("[*]", "
          • ", $Text); // handle nested lists $endlessloop = 0; while ( (((str_contains($Text, "[/list]")) && (str_contains($Text, "[list"))) || ((str_contains($Text, "[/ol]")) && (str_contains($Text, "[ol]"))) || ((str_contains($Text, "[/ul]")) && (str_contains($Text, "[ul]"))) || ((str_contains($Text, "[/dl]")) && (str_contains($Text, "[dl"))) || ((str_contains($Text, "[/li]")) && (str_contains($Text, "[li]")))) && (++$endlessloop < 20) ) { $Text = preg_replace("/\[list\](.*?)\[\/list\]/ism", '
              $1
            ', $Text); $Text = preg_replace("/\[list=\](.*?)\[\/list\]/ism", '
              $1
            ', $Text); $Text = preg_replace("/\[list=1\](.*?)\[\/list\]/ism", '
              $1
            ', $Text); $Text = preg_replace("/\[list=((?-i)i)\](.*?)\[\/list\]/ism", '
              $2
            ', $Text); $Text = preg_replace("/\[list=((?-i)I)\](.*?)\[\/list\]/ism", '
              $2
            ', $Text); $Text = preg_replace("/\[list=((?-i)a)\](.*?)\[\/list\]/ism", '
              $2
            ', $Text); $Text = preg_replace("/\[list=((?-i)A)\](.*?)\[\/list\]/ism", '
              $2
            ', $Text); $Text = preg_replace("/\[ul\](.*?)\[\/ul\]/ism", '
              $1
            ', $Text); $Text = preg_replace("/\[ol\](.*?)\[\/ol\]/ism", '
              $1
            ', $Text); $Text = preg_replace("/\[\/li\]
            \[li\]/ism", '[/li][li]', $Text); $Text = preg_replace("/\[li\](.*?)\[\/li\]/ism", '
          • $1
          • ', $Text); // [dl] tags have an optional [dl terms="bi"] form where bold/italic/underline/mono/large // etc. style may be specified for the "terms" in the definition list. The quotation marks // are also optional. The regex looks intimidating, but breaks down as: // "[dl" "]" "[/dl]" // where optional-termStyles are: "terms=" $Text = preg_replace_callback('/\[dl[[:space:]]*(?:terms=(?:"|")?([a-zA-Z]+)(?:"|")?)?\](.*?)\[\/dl\]/ism', 'bb_definitionList', $Text); } // Friendica generates this if (str_contains($Text, '[/abstract]')) { $Text = preg_replace("/\[abstract\](.*?)\[\/abstract\]/ism", '

            $1

            ', $Text); } if (str_contains($Text, '[checklist]')) { $Text = preg_replace_callback("/\[checklist\](.*?)\[\/checklist\]/ism", 'bb_checklist', $Text); } $loop = 0; while (str_contains($Text, '[/table]') && str_contains($Text, "[table") && ++$loop < 20) { $Text = preg_replace("/\[table\](.*?)\[\/table\]/ism", '$1
            ', $Text); $Text = preg_replace("/\[table border=1\](.*?)\[\/table\]/ism", '$1
            ', $Text); $Text = preg_replace("/\[table border=0\](.*?)\[\/table\]/ism", '$1
            ', $Text); } if (str_contains($Text, '[th]')) { $Text = preg_replace("/\[th\](.*?)\[\/th\]/ism", '$1', $Text); } if (str_contains($Text, '[td]')) { $Text = preg_replace("/\[td\](.*?)\[\/td\]/ism", '$1', $Text); } if (str_contains($Text, '[tr]')) { $Text = preg_replace("/\[tr\](.*?)\[\/tr\]/ism", '$1', $Text); } if (str_contains($Text, '[tbody]')) { $Text = preg_replace("/\[tbody\](.*?)\[\/tbody\]/ism", '$1', $Text); } $Text = str_replace('
            ', "\n", $Text); $Text = str_replace('[hr]', '
            ', $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 if ($export) { $Text = str_replace(['[spoiler]','[/spoiler]','[open]','[/open]'],[ '', '', '', ''], $Text); } else { $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); } // Replace naked urls $Text = bb_nakedlinks($Text); // Images // [img]pathtoimage[/img] if (str_contains($Text, '[/img]')) { $Text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '' . t('Image/photo') . '', $Text); } if (str_contains($Text, '[/zmg]')) { $Text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '' . t('Image/photo') . '', $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); $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); } // Markdown processing if (!$bbonly) { // the bbcode tag 'nomd' will bypass markdown processing for any given text region $Text = preg_replace_callback('#\[nomd\](.*?)\[\/nomd\]#ism', 'md_protect', $Text); $Text = preg_replace_callback("/\[code(.*?)\](.*?)\[\/code\]/ism", 'bb_code_preprotect', $Text); $Text = str_replace('
            ', "\n", $Text); // Perform some markdown conversions before translating linefeeds so as to keep the regexes manageable // The preceding character check in bold/italic sequences is so we don't mistake underscore/asterisk in the middle of conversational text as an italic trigger. $Text = preg_replace_callback('#(^|\n| )(?$3',$Text); // markdown inline code blocks must be preceded by space or linebreak $Text = preg_replace('#(^|\n| )(?$2', $Text); $Text = preg_replace('#(\\\)\##', '#', $Text); // strip backslash escape for inline code $Text = preg_replace('#(\\\)`#', '`', $Text); $Text = preg_replace('#<\/code><\/pre>\n
            | .*?>)#', '
            ', $Text); // blockquotes $Text = preg_replace('#^(>)+ +(.*?)$#m', '
            $2
            ', $Text); $Text = preg_replace('#^(\>)+ +(.*?)$#m', '
            $2
            ', $Text); $Text = preg_replace('#\n
            #', "\n", $Text); // links $Text = preg_replace_callback('#!\[[^\]]*\]\((.*?)(?=\"|\))(\".*\")?\)(?!`)#', 'md_image', $Text); $Text = preg_replace('#\[([^\[]+)\]\((?:javascript:)?([^\)]+)\)(?!`)#', '$1', $Text); // unordered lists $matches = []; // Ignore if there is only one list element as it could be a false positive. // if (preg_match_all('#^(? 1) { $Text = preg_replace('#^(?
          • $1
          • ', $Text); // strip the backslash escape if present $Text = preg_replace('#^(\\\)([*\-+]) #m', '$2', $Text); // } // order lists $Text = preg_replace('#^(?
          • $2
          • ', $Text); $Text = preg_replace('#^(\\\)(\d+[\.\)])#m', '$2', $Text); $Text = preg_replace('/\s*<\/(ol|ul)>\n+<\1>\s*/', '', $Text); $Text = str_replace("\n", '
            ', $Text); $Text = bb_code_preunprotect($Text); } // oembed tag $Text = Oembed::bbcode2html($Text, $export, $target); // 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); $Text = str_replace('
            ', '', $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']; }