From b9dec84489fc90cb43ac9c219c77eb82f52d857b Mon Sep 17 00:00:00 2001 From: zotlabs Date: Thu, 3 Oct 2019 22:16:11 -0700 Subject: fix event timezones for zot6 --- Zotlabs/Lib/Activity.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index b74f6b2bc..d1fa8e49e 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -202,6 +202,19 @@ class Activity { $ev = bbtoevent($x['content']); if($ev) { + $t = q("select id from item where resource_type = 'event' and resource_id = '%s'", + dbesc($ev['event_hash']) + ); + if($t) + $tz = get_iconfig($t[0]['id'],'event','timezone','UTC'); + if(! $tz) + $tz = 'UTC'; + + $ev['dtstart'] = datetime_convert($tz,'UTC',$ev['dtstart'], ATOM_TIME); + if (! $ev['nofinish']) { + $ev['dtend'] = datetime_convert($tz,'UTC',$ev['dtend'], ATOM_TIME); + } + $actor = null; if(array_key_exists('author',$x) && array_key_exists('link',$x['author'])) { $actor = $x['author']['link'][0]['href']; -- cgit v1.2.3 From baffa969d387367a3270fa234f2cd3935e82e85d Mon Sep 17 00:00:00 2001 From: zotlabs Date: Fri, 4 Oct 2019 14:28:31 -0700 Subject: another timezone fix --- Zotlabs/Lib/Activity.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index b74f6b2bc..17bf4bfb0 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -202,6 +202,10 @@ class Activity { $ev = bbtoevent($x['content']); if($ev) { + if (! $ev['timezone']) { + $ev['timezone'] = 'UTC'; + } + $actor = null; if(array_key_exists('author',$x) && array_key_exists('link',$x['author'])) { $actor = $x['author']['link'][0]['href']; @@ -212,14 +216,14 @@ class Activity { 'name' => $ev['summary'], // 'summary' => bbcode($ev['summary'], [ 'cache' => true ]), // RFC3339 Section 4.3 - 'startTime' => (($ev['adjust']) ? datetime_convert('UTC','UTC',$ev['dtstart'], ATOM_TIME) : datetime_convert('UTC','UTC',$ev['dtstart'],'Y-m-d\\TH:i:s-00:00')), + 'startTime' => (($ev['adjust']) ? datetime_convert($ev['timezone'],'UTC',$ev['dtstart'], ATOM_TIME) : datetime_convert('UTC','UTC',$ev['dtstart'],'Y-m-d\\TH:i:s-00:00')), 'content' => bbcode($ev['description'], [ 'cache' => true ]), 'location' => [ 'type' => 'Place', 'content' => bbcode($ev['location'], [ 'cache' => true ]) ], 'source' => [ 'content' => format_event_bbcode($ev), 'mediaType' => 'text/bbcode' ], 'actor' => $actor, ]; if(! $ev['nofinish']) { - $y['endTime'] = (($ev['adjust']) ? datetime_convert('UTC','UTC',$ev['dtend'], ATOM_TIME) : datetime_convert('UTC','UTC',$ev['dtend'],'Y-m-d\\TH:i:s-00:00')); + $y['endTime'] = (($ev['adjust']) ? datetime_convert($ev['timezone'],'UTC',$ev['dtend'], ATOM_TIME) : datetime_convert('UTC','UTC',$ev['dtend'],'Y-m-d\\TH:i:s-00:00')); } // copy attachments from the passed object - these are already formatted for ActivityStreams -- cgit v1.2.3 From a5826fec251447c1d9c94700183e9ed458127fba Mon Sep 17 00:00:00 2001 From: zotlabs Date: Thu, 7 Nov 2019 14:51:29 -0800 Subject: svg stuff --- Zotlabs/Lib/SvgSanitizer.php | 150 +++++++++++++++++++++++++++++++++++++++++ Zotlabs/Module/Wall_attach.php | 17 ++++- include/bbcode.php | 21 ++++++ include/text.php | 22 ++++++ 4 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 Zotlabs/Lib/SvgSanitizer.php diff --git a/Zotlabs/Lib/SvgSanitizer.php b/Zotlabs/Lib/SvgSanitizer.php new file mode 100644 index 000000000..c9bafc464 --- /dev/null +++ b/Zotlabs/Lib/SvgSanitizer.php @@ -0,0 +1,150 @@ + [ 'class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'href', 'xlink:href', 'xlink:title' ], + 'circle' => [ 'class', 'clip-path', 'clip-rule', 'cx', 'cy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'r', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform' ], + 'clipPath' => [ 'class', 'clipPathUnits', 'id' ], + 'defs' => [ ], + 'style' => [ 'type' ], + 'desc' => [ ], + 'ellipse' => [ 'class', 'clip-path', 'clip-rule', 'cx', 'cy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform' ], + 'feGaussianBlur' => [ 'class', 'color-interpolation-filters', 'id', 'requiredFeatures', 'stdDeviation' ], + 'filter' => [ 'class', 'color-interpolation-filters', 'filterRes', 'filterUnits', 'height', 'id', 'primitiveUnits', 'requiredFeatures', 'width', 'x', 'xlink:href', 'y' ], + 'foreignObject' => [ 'class', 'font-size', 'height', 'id', 'opacity', 'requiredFeatures', 'style', 'transform', 'width', 'x', 'y' ], + 'g' => [ 'class', 'clip-path', 'clip-rule', 'id', 'display', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'font-family', 'font-size', 'font-style', 'font-weight', 'text-anchor' ], + 'image' => [ 'class', 'clip-path', 'clip-rule', 'filter', 'height', 'id', 'mask', 'opacity', 'requiredFeatures', 'style', 'systemLanguage', 'transform', 'width', 'x', 'xlink:href', 'xlink:title', 'y' ], + 'line' => [ 'class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'x1', 'x2', 'y1', 'y2' ], + 'linearGradient' => [ 'class', 'id', 'gradientTransform', 'gradientUnits', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'x1', 'x2', 'xlink:href', 'y1', 'y2' ], + 'marker' => [ 'id', 'class', 'markerHeight', 'markerUnits', 'markerWidth', 'orient', 'preserveAspectRatio', 'refX', 'refY', 'systemLanguage', 'viewBox' ], + 'mask' => [ 'class', 'height', 'id', 'maskContentUnits', 'maskUnits', 'width', 'x', 'y' ], + 'metadata' => [ 'class', 'id' ], + 'path' => [ 'class', 'clip-path', 'clip-rule', 'd', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform' ], + 'pattern' => [ 'class', 'height', 'id', 'patternContentUnits', 'patternTransform', 'patternUnits', 'requiredFeatures', 'style', 'systemLanguage', 'viewBox', 'width', 'x', 'xlink:href', 'y' ], + 'polygon' => [ 'class', 'clip-path', 'clip-rule', 'id', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'class', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform' ], + 'polyline' => [ 'class', 'clip-path', 'clip-rule', 'id', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform' ], + 'radialGradient' => [ 'class', 'cx', 'cy', 'fx', 'fy', 'gradientTransform', 'gradientUnits', 'id', 'r', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'xlink:href' ], + 'rect' => [ 'class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'id', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'width', 'x', 'y' ], + 'stop' => [ 'class', 'id', 'offset', 'requiredFeatures', 'stop-color', 'stop-opacity', 'style', 'systemLanguage' ], + 'svg' => [ 'class', 'clip-path', 'clip-rule', 'filter', 'id', 'height', 'mask', 'preserveAspectRatio', 'requiredFeatures', 'style', 'systemLanguage', 'viewBox', 'width', 'x', 'xmlns', 'xmlns:se', 'xmlns:xlink', 'y' ], + 'switch' => [ 'class', 'id', 'requiredFeatures', 'systemLanguage' ], + 'symbol' => [ 'class', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'opacity', 'preserveAspectRatio', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'viewBox' ], + 'text' => [ 'class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'text-anchor', 'transform', 'x', 'xml:space', 'y' ], + 'textPath' => [ 'class', 'id', 'method', 'requiredFeatures', 'spacing', 'startOffset', 'style', 'systemLanguage', 'transform', 'xlink:href' ], + 'title' => [ ], + 'tspan' => [ 'class', 'clip-path', 'clip-rule', 'dx', 'dy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'mask', 'opacity', 'requiredFeatures', 'rotate', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'text-anchor', 'textLength', 'transform', 'x', 'xml:space', 'y' ], + 'use' => [ 'class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'id', 'mask', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'transform', 'width', 'x', 'xlink:href', 'y' ], + ]; + + function __construct() { + $this->xmlDoc = new DOMDocument('1.0','UTF-8'); + $this->xmlDoc->preserveWhiteSpace = false; + libxml_use_internal_errors(true); + } + + // load XML SVG + function load($file) { + $this->xmlDoc->load($file); + } + + function loadXML($str) { + if (! $this->xmlDoc->loadXML($str)) { + logger('loadxml: ' . print_r(libxml_get_errors(),true), LOGGER_DEBUG); + return false; + } + return true; + } + + function sanitize() + { + // all elements in xml doc + $allElements = $this->xmlDoc->getElementsByTagName('*'); + + // loop through all elements + for($i = 0; $i < $allElements->length; $i++) + { + $this->removedattrs = []; + + $currentNode = $allElements->item($i); + + // logger('current_node: ' . print_r($currentNode,true)); + + // array of allowed attributes in specific element + $whitelist_attr_arr = self::$whitelist[$currentNode->tagName]; + + // does element exist in whitelist? + if(isset($whitelist_attr_arr)) { + $total = $currentNode->attributes->length; + + for($x = 0; $x < $total; $x++) { + + // get attributes name + $attrName = $currentNode->attributes->item($x)->nodeName; + + // logger('checking: ' . print_r($currentNode->attributes->item($x),true)); + $matches = false; + + // check if attribute isn't in whitelist + if(! in_array($attrName, $whitelist_attr_arr)) { + $this->removedattrs[] = $attrName; + } + // check for disallowed functions + elseif (preg_match_all('/([a-zA-Z0-9]+)[\s]*\(/', + $currentNode->attributes->item($x)->textContent,$matches,PREG_SET_ORDER)) { + if ($attrName === 'text') { + continue; + } + foreach ($matches as $match) { + if(! in_array($match[1],self::$allowed_functions)) { + logger('queue_remove_function: ' . $match[1],LOGGER_DEBUG); + $this->removedattrs[] = $attrName; + } + } + } + } + if ($this->removedattrs) { + foreach ($this->removedattrs as $attr) { + $currentNode->removeAttribute($attr); + logger('removed: ' . $attr, LOGGER_DEBUG); + } + } + + } + + // else remove element + else { + logger('remove_node: ' . print_r($currentNode,true)); + $currentNode->parentNode->removeChild($currentNode); + } + } + return true; + } + + function saveSVG() { + $this->xmlDoc->formatOutput = true; + return($this->xmlDoc->saveXML()); + } +} diff --git a/Zotlabs/Module/Wall_attach.php b/Zotlabs/Module/Wall_attach.php index 780e82950..e1088d18f 100644 --- a/Zotlabs/Module/Wall_attach.php +++ b/Zotlabs/Module/Wall_attach.php @@ -113,7 +113,22 @@ class Wall_attach extends \Zotlabs\Web\Controller { $url = z_root() . '/cloud/' . $channel['channel_address'] . '/' . $r['data']['display_path']; $s = "\n\n" . '[zaudio]' . $url . '[/zaudio]' . "\n\n"; } - + if ($r['data']['filetype'] === 'image/svg+xml') { + $x = @file_get_contents('store/' . $channel['channel_address'] . '/' . $r['data']['os_path']); + if ($x) { + $bb = svg2bb($x); + if ($bb) { + $s .= "\n\n" . $bb; + } + else { + logger('empty return from svgbb'); + } + } + else { + logger('unable to read svg data file: ' . 'store/' . $channel['channel_address'] . '/' . $r['data']['os_path']); + } + } + $s .= "\n\n" . '[attachment]' . $r['data']['hash'] . ',' . $r['data']['revision'] . '[/attachment]' . "\n"; } diff --git a/include/bbcode.php b/include/bbcode.php index bb9144b1d..84f0f3dda 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -4,6 +4,8 @@ * @brief BBCode related functions for parsing, etc. */ +use Zotlabs\Lib\SvgSanitizer; + require_once('include/oembed.php'); require_once('include/event.php'); require_once('include/zot.php'); @@ -267,6 +269,22 @@ function bb_parse_app($match) { return Zotlabs\Lib\Apps::app_render($app); } +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_parse_element($match) { $j = json_decode(base64url_decode($match[1]),true); @@ -1289,6 +1307,9 @@ function bbcode($Text, $options = []) { $Text = preg_replace_callback("/\[zaudio\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mp3|opus|m4a))\[\/zaudio\]/ism", 'tryzrlaudio', $Text); } + // SVG stuff + $Text = preg_replace_callback("/\[svg(.*?)\](.*?)\[\/svg\]/ism", 'bb_svg', $Text); + // Try to Oembed if ($tryoembed) { if (strpos($Text,'[/video]') !== false) { diff --git a/include/text.php b/include/text.php index 54ad9ec7a..2496ca934 100644 --- a/include/text.php +++ b/include/text.php @@ -9,6 +9,8 @@ use Michelf\MarkdownExtra; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; +use Zotlabs\Lib\SvgSanitizer; + require_once("include/bbcode.php"); // random string, there are 86 characters max in text mode, 128 for hex @@ -3648,3 +3650,23 @@ function new_uuid() { return $hash; } + + +function svg2bb($s) { + + $s = preg_replace("/\(.*?)\<(.*?)\<\/text\>/", '$2<$3', $s); + $s = preg_replace("/\(.*?)\>(.*?)\<\/text\>/", '$2>$3', $s); + $s = preg_replace("/\(.*?)\[(.*?)\<\/text\>/", '$2[$3', $s); + $s = preg_replace("/\(.*?)\](.*?)\<\/text\>/", '$2]$3', $s); + $s = utf8_encode($s); + $purify = new SvgSanitizer(); + if ($purify->loadXML($s)) { + $purify->sanitize(); + $output = $purify->saveSVG(); + $output = preg_replace("/\<\?xml(.*?)\>/",'',$output); + $output = preg_replace("/\<\!\-\-(.*?)\-\-\>/",'',$output); + $output = str_replace(['<','>'],['[',']'],$output); + return $output; + } + return EMPTY_STR; +} -- cgit v1.2.3 From 17522b31e9d7f092ecf5565ec28be270af7fad7b Mon Sep 17 00:00:00 2001 From: zotlabs Date: Fri, 8 Nov 2019 13:07:42 -0800 Subject: exempt svg from tag completion --- include/bbcode.php | 4 ++-- include/text.php | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/bbcode.php b/include/bbcode.php index 84f0f3dda..c7dea53c5 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -966,9 +966,9 @@ function bbcode($Text, $options = []) { if (strpos($Text,'http') !== false) { if($tryoembed) { - $Text = preg_replace_callback("/([^\]\='".'"'."\/]|^|\#\^)(https?\:\/\/$urlchars+)/ismu", 'tryoembed', $Text); + $Text = preg_replace_callback("/([^\]\='".'"'."\;\/]|^|\#\^)(https?\:\/\/$urlchars+)/ismu", 'tryoembed', $Text); } - $Text = preg_replace("/([^\]\='".'"'."\/]|^|\#\^)(https?\:\/\/$urlchars+)/ismu", '$1$2', $Text); + $Text = preg_replace("/([^\]\='".'"'."\;\/]|^|\#\^)(https?\:\/\/$urlchars+)/ismu", '$1$2', $Text); } if (strpos($Text,'[/share]') !== false) { diff --git a/include/text.php b/include/text.php index 2496ca934..daa3c4c94 100644 --- a/include/text.php +++ b/include/text.php @@ -844,9 +844,9 @@ function get_tags($s) { $ret = array(); $match = array(); - // ignore anything in a code block - + // ignore anything in a code or svg block $s = preg_replace('/\[code(.*?)\](.*?)\[\/code\]/sm','',$s); + $s = preg_replace('/\[svg(.*?)\](.*?)\[\/svg\]/sm','',$s); // ignore anything in [style= ] $s = preg_replace('/\[style=(.*?)\]/sm','',$s); @@ -3414,18 +3414,20 @@ function cleanup_bbcode($body) { $body = preg_replace_callback('/\[code(.*?)\[\/(code)\]/ism','\red_escape_codeblock',$body); $body = preg_replace_callback('/\[url(.*?)\[\/(url)\]/ism','\red_escape_codeblock',$body); $body = preg_replace_callback('/\[zrl(.*?)\[\/(zrl)\]/ism','\red_escape_codeblock',$body); + $body = preg_replace_callback('/\[svg(.*?)\[\/(svg)\]/ism','\red_escape_codeblock',$body); - $body = preg_replace_callback("/([^\]\='".'"'."\/\{]|^|\#\^)(https?\:\/\/[a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\@\_\~\#\%\$\!\\ + $body = preg_replace_callback("/([^\]\='".'"'."\;\/\{]|^|\#\^)(https?\:\/\/[a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\@\_\~\#\%\$\!\\ +\,\(\)]+)/ismu", '\nakedoembed', $body); - $body = preg_replace_callback("/([^\]\='".'"'."\/\{]|^|\#\^)(https?\:\/\/[a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\@\_\~\#\%\$\!\\ + $body = preg_replace_callback("/([^\]\='".'"'."\;\/\{]|^|\#\^)(https?\:\/\/[a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\@\_\~\#\%\$\!\\ +\,\(\)]+)/ismu", '\red_zrl_callback', $body); $body = preg_replace_callback('/\[\$b64zrl(.*?)\[\/(zrl)\]/ism','\red_unescape_codeblock',$body); $body = preg_replace_callback('/\[\$b64url(.*?)\[\/(url)\]/ism','\red_unescape_codeblock',$body); $body = preg_replace_callback('/\[\$b64code(.*?)\[\/(code)\]/ism','\red_unescape_codeblock',$body); - + $body = preg_replace_callback('/\[\$b64svg(.*?)\[\/(svg)\]/ism','\red_unescape_codeblock',$body); + // fix any img tags that should be zmg $body = preg_replace_callback('/\[img(.*?)\](.*?)\[\/img\]/ism','\red_zrlify_img_callback',$body); -- cgit v1.2.3