diff options
Diffstat (limited to 'include/markdown.php')
-rw-r--r-- | include/markdown.php | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/include/markdown.php b/include/markdown.php new file mode 100644 index 000000000..b4656f769 --- /dev/null +++ b/include/markdown.php @@ -0,0 +1,532 @@ +<?php +/** + * @file include/markdown.php + * @brief Some functions for BB conversions for Diaspora protocol. + */ + +require_once("include/oembed.php"); +require_once("include/event.php"); +require_once("library/markdown.php"); +require_once("include/html2bbcode.php"); +require_once("include/bbcode.php"); +require_once("library/markdownify/markdownify.php"); + + +function get_bb_tag_pos($s, $name, $occurance = 1) { + + if($occurance < 1) + $occurance = 1; + + $start_open = -1; + for($i = 1; $i <= $occurance; $i++) { + if( $start_open !== false) + $start_open = strpos($s, '[' . $name, $start_open + 1); // allow [name= type tags + } + + if( $start_open === false) + return false; + + $start_equal = strpos($s, '=', $start_open); + $start_close = strpos($s, ']', $start_open); + + if( $start_close === false) + return false; + + $start_close++; + + $end_open = strpos($s, '[/' . $name . ']', $start_close); + + if( $end_open === false) + return false; + + $res = array( 'start' => 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 share_shield($m) { + return str_replace($m[1],'!=+=+=!' . base64url_encode($m[1]) . '=+!=+!=',$m[0]); +} + +function share_unshield($m) { + $x = str_replace(array('!=+=+=!','=+!=+!='),array('',''),$m[1]); + return str_replace($m[1], base64url_decode($x), $m[0]); +} + + +function diaspora_mention_callback($matches) { + + $webbie = $matches[2] . '@' . $matches[3]; + $link = ''; + if($webbie) { + $r = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash where hubloc_addr = '%s' limit 1", + dbesc($webbie) + ); + if(! $r) { + $x = discover_by_webbie($webbie); + if($x) { + $r = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash where hubloc_addr = '%s' limit 1", + dbesc($webbie) + ); + } + } + if($r) + $link = $r[0]['xchan_url']; + } + if(! $link) + $link = 'https://' . $matches[3] . '/u/' . $matches[2]; + + if($r && $r[0]['hubloc_network'] === 'zot') + return '@[zrl=' . $link . ']' . trim($matches[1]) . ((substr($matches[0],-1,1) === '+') ? '+' : '') . '[/zrl]' ; + else + return '@[url=' . $link . ']' . trim($matches[1]) . ((substr($matches[0],-1,1) === '+') ? '+' : '') . '[/url]' ; + +} + + +/** + * @brief + * + * We don't want to support a bbcode specific markdown interpreter + * and the markdown library we have is pretty good, but provides HTML output. + * So we'll use that to convert to HTML, then convert the HTML back to bbcode, + * and then clean up a few Diaspora specific constructs. + * + * @param string $s + * @param boolean $use_zrl default false + * @return string + */ +function markdown_to_bb($s, $use_zrl = false) { + + $s = str_replace("
","\r",$s); + $s = str_replace("
\n>","",$s); + + if(is_array($s)) { + btlogger('markdown_to_bb called with array. ' . print_r($s,true), LOGGER_NORMAL, LOG_WARNING); + return ''; + } + + $s = html_entity_decode($s,ENT_COMPAT,'UTF-8'); + + // if empty link text replace with the url + $s = preg_replace("/\[\]\((.*?)\)/ism",'[$1]($1)',$s); + + // first try plustags + + $s = preg_replace_callback('/\@\{(.+?)\; (.+?)\@(.+?)\}\+/','diaspora_mention_callback',$s); + + $s = preg_replace_callback('/\@\{(.+?)\; (.+?)\@(.+?)\}/','diaspora_mention_callback',$s); + + // Escaping the hash tags - doesn't always seem to work + // $s = preg_replace('/\#([^\s\#])/','\\#$1',$s); + // This seems to work + $s = preg_replace('/\#([^\s\#])/','#$1',$s); + + $s = Markdown($s); + + $s = str_replace("\r","",$s); + + $s = str_replace('#','#',$s); + + $s = html2bbcode($s); + + // protect the recycle symbol from turning into a tag, but without unescaping angles and naked ampersands + $s = str_replace('♲',html_entity_decode('♲',ENT_QUOTES,'UTF-8'),$s); + + // Convert everything that looks like a link to a link + if($use_zrl) { + $s = str_replace(array('[img','/img]'),array('[zmg','/zmg]'),$s); + $s = preg_replace("/([^\]\=]|^)(https?\:\/\/)([a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\(\)]+)/ism", '$1[zrl=$2$3]$2$3[/zrl]',$s); + } + else { + $s = preg_replace("/([^\]\=]|^)(https?\:\/\/)([a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\(\)]+)/ism", '$1[url=$2$3]$2$3[/url]',$s); + } + + // remove duplicate adjacent code tags + $s = preg_replace("/(\[code\])+(.*?)(\[\/code\])+/ism","[code]$2[/code]", $s); + + // Don't show link to full picture (until it is fixed) + $s = scale_external_images($s, false); + + call_hooks('markdown_to_bb',$s); + + return $s; +} + + +function stripdcode_br_cb($s) { + return '[code]' . str_replace('<br />', "\n\t", $s[1]) . '[/code]'; +} + + +////////////////////// +// The following "diaspora_ul" and "diaspora_ol" are only appropriate for the +// pre-Markdownify conversion. If Markdownify isn't used, use the non-Markdownify +// versions below +////////////////////// +/* +function diaspora_ul($s) { + // Replace "[*]" followed by any number (including zero) of + // spaces by "* " to match Diaspora's list format + if( strpos($s[0], "[list]") === 0 ) + return '<ul class="listbullet" style="list-style-type: circle;">' . preg_replace("/\[\*\]( *)/", "* ", $s[1]) . '</ul>'; + elseif( strpos($s[0], "[ul]") === 0 ) + return '<ul class="listbullet" style="list-style-type: circle;">' . preg_replace("/\[\*\]( *)/", "* ", $s[1]) . '</ul>'; + else + return $s[0]; +} + + +function diaspora_ol($s) { + // A hack: Diaspora will create a properly-numbered ordered list even + // if you use '1.' for each element of the list, like: + // 1. First element + // 1. Second element + // 1. Third element + if( strpos($s[0], "[list=1]") === 0 ) + return '<ul class="listdecimal" style="list-style-type: decimal;">' . preg_replace("/\[\*\]( *)/", "1. ", $s[1]) . '</ul>'; + elseif( strpos($s[0], "[list=i]") === 0 ) + return '<ul class="listlowerroman" style="list-style-type: lower-roman;">' . preg_replace("/\[\*\]( *)/", "1. ", $s[1]) . '</ul>'; + elseif( strpos($s[0], "[list=I]") === 0 ) + return '<ul class="listupperroman" style="list-style-type: upper-roman;">' . preg_replace("/\[\*\]( *)/", "1. ", $s[1]) . '</ul>'; + elseif( strpos($s[0], "[list=a]") === 0 ) + return '<ul class="listloweralpha" style="list-style-type: lower-alpha;">' . preg_replace("/\[\*\]( *)/", "1. ", $s[1]) . '</ul>'; + elseif( strpos($s[0], "[list=A]") === 0 ) + return '<ul class="listupperalpha" style="list-style-type: upper-alpha;">' . preg_replace("/\[\*\]( *)/", "1. ", $s[1]) . '</ul>'; + elseif( strpos($s[0], "[ol]") === 0 ) + return '<ul class="listdecimal" style="list-style-type: decimal;">' . preg_replace("/\[\*\]( *)/", "1. ", $s[1]) . '</ul>'; + else + return $s[0]; +} +*/ + +////////////////////// +// Non-Markdownify versions of "diaspora_ol" and "diaspora_ul" +////////////////////// +/** + * @brief + * + * Replace "[\\*]" followed by any number (including zero) of + * spaces by "* " to match Diaspora's list format. + * + * @param string $s + * @return string + */ +function diaspora_ul($s) { + return preg_replace("/\[\\\\\*\]( *)/", "* ", $s[1]); +} + +/** + * @brief + * + * A hack: Diaspora will create a properly-numbered ordered list even + * if you use '1.' for each element of the list, like: + * \code + * 1. First element + * 1. Second element + * 1. Third element + * \endcode + * @param string $s + * @return string + */ +function diaspora_ol($s) { + return preg_replace("/\[\\\\\*\]( *)/", "1. ", $s[1]); +} + +function bb2dmention_callback($match) { + + $r = q("select xchan_addr from xchan where xchan_url = '%s'", + dbesc($match[2]) + ); + + if($r) + return '@{' . $match[3] . ' ; ' . $r[0]['xchan_addr'] . '}'; + + return '@' . $match[3]; +} + + +function bb2diaspora_itemwallwall(&$item,$uplink = false) { + + // We will provide wallwall (embedded author on the Diaspora side) if + // 1. It is a wall-to-wall post + // 2. A comment arrived which has no Diaspora signature info + + + $wallwall = false; + $author_exists = true; + + if(! array_key_exists('author',$item)) { + $author_exists = false; + logger('bb2diaspora_itemwallwall: no author'); + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($item['author_xchan']) + ); + if($r) + $item['author'] = $r[0]; + } + + $has_meta = false; + if($item['diaspora_meta'] || get_iconfig($item,'diaspora','fields')) + $has_meta = true; + + if($item['author_xchan'] != $item['owner_xchan']) { + if($item['mid'] == $item['parent_mid']) + $wallwall = true; + else { + if(! $has_meta) { + $wallwall = true; + } + } + } + + if($uplink) + $wallwall = true; + + if(($wallwall) && (is_array($item['author'])) && $item['author']['xchan_url'] && $item['author']['xchan_name'] && $item['author']['xchan_photo_s']) { + logger('bb2diaspora_itemwallwall: wall to wall post',LOGGER_DEBUG); + // post will come across with the owner's identity. Throw a preamble onto the post to indicate the true author. + $item['body'] = "\n\n" + . '[quote]' + . '[img]' . $item['author']['xchan_photo_s'] . '[/img]' + . ' ' + . '[url=' . $item['author']['xchan_url'] . '][b]' . $item['author']['xchan_name'] . '[/b][/url]' . "\n\n" + . $item['body'] + . '[/quote]'; + } + + // $item['author'] might cause a surprise further down the line if it wasn't expected to be here. + + if(! $author_exists) + unset($item['author']); +} + + +function bb2diaspora_itembody($item, $force_update = false, $have_channel = false, $uplink = false) { + + + if(! get_iconfig($item,'diaspora','fields')) { + $force_update = true; + } + + $matches = array(); + + if(($item['diaspora_meta']) && (! $force_update)) { + $diaspora_meta = json_decode($item['diaspora_meta'],true); + if($diaspora_meta) { + if(array_key_exists('iv',$diaspora_meta)) { + $key = get_config('system','prvkey'); + $meta = json_decode(crypto_unencapsulate($diaspora_meta,$key),true); + } + else { + $meta = $diaspora_meta; + } + if($meta) { + logger('bb2diaspora_itembody: cached '); + $newitem = $item; + $newitem['body'] = $meta['body']; + return $newitem['body']; + } + } + } + + create_export_photo_body($item); + + $newitem = $item; + + if(array_key_exists('item_obscured',$item) && intval($item['item_obscured'])) { + $key = get_config('system','prvkey'); + $b = json_decode($item['body'],true); + // if called from diaspora_process_outbound, this decoding has already been done. + // Everything else that calls us will not yet be decoded. + if($b && is_array($b) && array_key_exists('iv',$b)) { + $newitem['title'] = (($item['title']) ? crypto_unencapsulate(json_decode($item['title'],true),$key) : ''); + $newitem['body'] = (($item['body']) ? crypto_unencapsulate(json_decode($item['body'],true),$key) : ''); + } + } + + if(! $have_channel) + bb2diaspora_itemwallwall($newitem,$uplink); + + $title = $newitem['title']; + $body = preg_replace('/\#\^http/i', 'http', $newitem['body']); + + // protect tags and mentions from hijacking + + if(intval(get_pconfig($item['uid'],'system','prevent_tag_hijacking'))) { + $new_tag = html_entity_decode('⋕',ENT_COMPAT,'UTF-8'); + $new_mention = html_entity_decode('@',ENT_COMPAT,'UTF-8'); + + // #-tags + $body = preg_replace('/\#\[url/i', $new_tag . '[url', $body); + $body = preg_replace('/\#\[zrl/i', $new_tag . '[zrl', $body); + // @-mentions + $body = preg_replace('/\@\!?\[url/i', $new_mention . '[url', $body); + $body = preg_replace('/\@\!?\[zrl/i', $new_mention . '[zrl', $body); + } + + // remove multiple newlines + do { + $oldbody = $body; + $body = str_replace("\n\n\n", "\n\n", $body); + } while ($oldbody != $body); + + $body = bb2diaspora($body); + + if(strlen($title)) + $body = "## " . $title . "\n\n" . $body; + + if($item['attach']) { + $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item['attach'], $matches, PREG_SET_ORDER); + if($cnt) { + $body .= "\n" . t('Attachments:') . "\n"; + foreach($matches as $mtch) { + $body .= '[' . $mtch[3] . '](' . $mtch[1] . ')' . "\n"; + } + } + } + +// logger('bb2diaspora_itembody : ' . $body, LOGGER_DATA); + + return html_entity_decode($body); +} + +function bb2diaspora($Text,$preserve_nl = false, $fordiaspora = true) { + + // Re-enabling the converter again. + // The bbcode parser now handles youtube-links (and the other stuff) correctly. + // Additionally the html code is now fixed so that lists are now working. + + /* + * Transform #tags, strip off the [url] and replace spaces with underscore + */ + $Text = preg_replace_callback('/#\[([zu])rl\=(\w+.*?)\](\w+.*?)\[\/[(zu)]rl\]/i', create_function('$match', + 'return \'#\'. str_replace(\' \', \'_\', $match[3]);' + ), $Text); + + $Text = preg_replace('/#\^\[([zu])rl\=(\w+.*?)\](\w+.*?)\[\/([zu])rl\]/i', '[$1rl=$2]$3[/$4rl]', $Text); + + $Text = preg_replace_callback('/\@\!?\[([zu])rl\=(\w+.*?)\](\w+.*?)\[\/([zu])rl\]/i', 'bb2dmention_callback', $Text); + + // strip map tags, as the rendering is performed in bbcode() and the resulting output + // is not compatible with Diaspora (at least in the case of openstreetmap and probably + // due to the inclusion of an html iframe) + + $Text = preg_replace("/\[map\=(.*?)\]/ism", '$1', $Text); + $Text = preg_replace("/\[map\](.*?)\[\/map\]/ism", '$1', $Text); + + // Converting images with size parameters to simple images. Markdown doesn't know it. + $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $Text); + + // the following was added on 10-January-2012 due to an inability of Diaspora's + // new javascript markdown processor to handle links with images as the link "text" + // It is not optimal and may be removed if this ability is restored in the future + //if ($fordiaspora) + // $Text = preg_replace("/\[url\=([^\[\]]*)\]\s*\[img\](.*?)\[\/img\]\s*\[\/url\]/ism", + // "[url]$1[/url]\n[img]$2[/img]", $Text); + + // Convert it to HTML - don't try oembed + $Text = bbcode($Text, $preserve_nl, false); + + // Markdownify does not preserve previously escaped html entities such as <> and &. + + $Text = str_replace(array('<','>','&'),array('&_lt_;','&_gt_;','&_amp_;'),$Text); + + // Now convert HTML to Markdown + $md = new Markdownify(false, false, false); + $Text = $md->parseString($Text); + + + + // It also adds backslashes to our attempt at getting around the html entity preservation for some weird reason. + + $Text = str_replace(array('&\\_lt\\_;','&\\_gt\\_;','&\\_amp\\_;'),array('<','>','&'),$Text); + + // If the text going into bbcode() has a plain URL in it, i.e. + // with no [url] tags around it, it will come out of parseString() + // looking like: <http://url.com>, which gets removed by strip_tags(). + // So take off the angle brackets of any such URL + $Text = preg_replace("/<http(.*?)>/is", "http$1", $Text); + + // Remove empty zrl links + $Text = preg_replace("/\[zrl\=\].*?\[\/zrl\]/is", "", $Text); + + // Remove all unconverted tags + $Text = strip_tags($Text); + + // Remove any leading or trailing whitespace, as this will mess up + // the Diaspora signature verification and cause the item to disappear + + $Text = trim($Text); + + call_hooks('bb2diaspora',$Text); + + return $Text; +} + +function unescape_underscores_in_links($m) { + $y = str_replace('\\_','_', $m[2]); + return('[' . $m[1] . '](' . $y . ')'); +} + +function format_event_diaspora($ev) { + + if(! ((is_array($ev)) && count($ev))) + return ''; + + $bd_format = t('l F d, Y \@ g:i A') ; // Friday January 18, 2011 @ 8 AM + + $o = t('$Projectname event notification:') . "\n"; + + $o .= '**' . (($ev['summary']) ? bb2diaspora($ev['summary']) : bb2diaspora($ev['desc'])) . '**' . "\n"; + + $o .= t('Starts:') . ' ' . '[' + . (($ev['adjust']) ? day_translate(datetime_convert('UTC', 'UTC', + $ev['start'] , $bd_format )) + : day_translate(datetime_convert('UTC', 'UTC', + $ev['start'] , $bd_format))) + . '](' . z_root() . '/localtime/?f=&time=' . urlencode(datetime_convert('UTC','UTC',$ev['start'])) . ")\n"; + + if(! $ev['nofinish']) + $o .= t('Finishes:') . ' ' . '[' + . (($ev['adjust']) ? day_translate(datetime_convert('UTC', 'UTC', + $ev['finish'] , $bd_format )) + : day_translate(datetime_convert('UTC', 'UTC', + $ev['finish'] , $bd_format ))) + . '](' . z_root() . '/localtime/?f=&time=' . urlencode(datetime_convert('UTC','UTC',$ev['finish'])) . ")\n"; + + if(strlen($ev['location'])) + $o .= t('Location:') . bb2diaspora($ev['location']) + . "\n"; + + $o .= "\n"; + + return $o; +} |