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;
$occurance = 1;
$pos = get_bb_tag_pos($string, $name, $occurance);
while($pos !== false && $occurance < 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;
$occurance++;
$pos = get_bb_tag_pos($string, $name, $occurance);
}
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]);
$o = oembed_fetch_url($url);
if ($o['type'] == 'error')
return $match[0];
return '[embed]' . $url . '[/embed]';
}
function tryzrlaudio($match) {
$link = $match[1];
$zrl = is_matrix_url($link);
if($zrl)
$link = zid($link);
return '';
}
function tryzrlvideo($match) {
$link = $match[1];
$zrl = is_matrix_url($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 '';
}
// [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 = array();
$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 . '#$]', '', $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 = array();
$attributes = $match[1];
$algorithm = "";
preg_match("/alg='(.*?)'/ism", $attributes, $matches);
if ($matches[1] != "")
$algorithm = $matches[1];
preg_match("/alg=\"\;(.*?)\"\;/ism", $attributes, $matches);
if ($matches[1] != "")
$algorithm = $matches[1];
$hint = "";
preg_match("/hint='(.*?)'/ism", $attributes, $matches);
if ($matches[1] != "")
$hint = $matches[1];
preg_match("/hint=\"\;(.*?)\"\;/ism", $attributes, $matches);
if ($matches[1] != "")
$hint = $matches[1];
$x = random_string();
$Text = '
';
return $Text;
}
function bb_parse_app($match) {
$app = Zotlabs\Lib\Apps::app_decode($match[1]);
if ($app)
return Zotlabs\Lib\Apps::app_render($app);
}
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_ShareAttributes($match) {
$matches = array();
$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];
// 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 = '' . datetime_convert('UTC', date_default_timezone_get(), $posted, 'r') . '';
$headline = '
';
if ($avatar != "")
$headline .= '';
if(strpos($link,'/cards/'))
$type = t('card');
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 . '
';
$text = $headline . '
' . trim($match[2]) . '
';
return $text;
}
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 = array();
$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], get_rpost_path(App::get_observer()) . '&title=' . urlencode($match[2]) . '&body=' . urlencode($match[3]), $match[0]);
} else {
return str_replace($match[0], get_rpost_path(App::get_observer()) . '&body=' . urlencode($match[3]), $match[0]);
}
}
function bb_map_coords($match) {
// the extra space in the following line is intentional
return str_replace($match[0],'
';
}
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, '
', $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);
}
if (strpos($Text,'[checklist]') !== false) {
$Text = preg_replace_callback("/\[checklist\](.*?)\[\/checklist\]/ism", 'bb_checklist', $Text);
}
if (strpos($Text,'[th]') !== false) {
$Text = preg_replace("/\[th\](.*?)\[\/th\]/sm", '
', $Text);
$Text = preg_replace_callback("/\[crypt (.*?)\](.*?)\[\/crypt\]/ism", 'bb_parse_crypt', $Text);
}
if(strpos($Text,'[/app]') !== false) {
$Text = preg_replace_callback("/\[app\](.*?)\[\/app\]/ism",'bb_parse_app', $Text);
}
if(strpos($Text,'[/element]') !== false) {
$Text = preg_replace_callback("/\[element\](.*?)\[\/element\]/ism",'bb_parse_element', $Text);
}
// html5 video and audio
if (strpos($Text,'[/video]') !== false) {
$Text = preg_replace_callback("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mpeg|mpg))\[\/video\]/ism", 'tryzrlvideo', $Text);
}
if (strpos($Text,'[/audio]') !== false) {
$Text = preg_replace_callback("/\[audio\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mp3|opus|m4a))\[\/audio\]/ism", 'tryzrlaudio', $Text);
}
if (strpos($Text,'[/zvideo]') !== false) {
$Text = preg_replace_callback("/\[zvideo\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mpeg|mpg))\[\/zvideo\]/ism", 'tryzrlvideo', $Text);
}
if (strpos($Text,'[/zaudio]') !== false) {
$Text = preg_replace_callback("/\[zaudio\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mp3|opus|m4a))\[\/zaudio\]/ism", 'tryzrlaudio', $Text);
}
// Try to Oembed
if ($tryoembed) {
if (strpos($Text,'[/video]') !== false) {
$Text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", 'tryoembed', $Text);
}
if (strpos($Text,'[/audio]') !== false) {
$Text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", 'tryoembed', $Text);
}
if (strpos($Text,'[/zvideo]') !== false) {
$Text = preg_replace_callback("/\[zvideo\](.*?)\[\/zvideo\]/ism", 'tryoembed', $Text);
}
if (strpos($Text,'[/zaudio]') !== false) {
$Text = preg_replace_callback("/\[zaudio\](.*?)\[\/zaudio\]/ism", 'tryoembed', $Text);
}
}
// if video couldn't be embedded, link to it instead.
if (strpos($Text,'[/video]') !== false) {
$Text = preg_replace("/\[video\](.*?)\[\/video\]/", '$1', $Text);
}
if (strpos($Text,'[/audio]') !== false) {
$Text = preg_replace("/\[audio\](.*?)\[\/audio\]/", '$1', $Text);
}
if (strpos($Text,'[/zvideo]') !== false) {
$Text = preg_replace("/\[zvideo\](.*?)\[\/zvideo\]/", '$1', $Text);
}
if (strpos($Text,'[/zaudio]') !== false) {
$Text = preg_replace("/\[zaudio\](.*?)\[\/zaudio\]/", '$1', $Text);
}
// if ($tryoembed){
// if (strpos($Text,'[/iframe]') !== false) {
// $Text = preg_replace_callback("/\[iframe\](.*?)\[\/iframe\]/ism", 'bb_iframe', $Text);
// }
// } else {
// if (strpos($Text,'[/iframe]') !== false) {
// $Text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '$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 ((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\-adjust\](.*?)\[\/event\-adjust\]/ism",'',$Text);
$Text = str_replace("\0",'$',$Text);
}
// Unhide all [noparse] contained bbtags unspacefying them
// and triming the [noparse] tag.
if (strpos($Text,'[noparse]') !== false) {
$Text = preg_replace_callback("/\[noparse\](.*?)\[\/noparse\]/ism", 'bb_unspacefy_and_trim', $Text);
}
if (strpos($Text,'[nobb]') !== false) {
$Text = preg_replace_callback("/\[nobb\](.*?)\[\/nobb\]/ism", 'bb_unspacefy_and_trim', $Text);
}
if (strpos($Text,'[pre]') !== false) {
$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 = preg_replace('/\[\&\;([#a-z0-9]+)\;\]/', '&$1;', $Text);
// fix any escaped ampersands that may have been converted into links
if(strpos($Text,'&') !== false)
$Text = preg_replace("/\<(.*?)(src|href)=(.*?)\&\;(.*?)\>/ism", '<$1$2=$3&$4>', $Text);
// This is subtle - it's an XSS filter. It only accepts links with a protocol scheme and where
// the scheme begins with z (zhttp), h (http(s)), f (ftp(s)), m (mailto), t (tel) and named anchors.
$Text = preg_replace("/\<(.*?)(src|href)=\"[^zhfmt#](.*?)\>/ism", '<$1$2="">', $Text);
$Text = bb_replace_images($Text, $saved_images);
call_hooks('bbcode', $Text);
return $Text;
}