Callback function for parsing an xml command tag
and turn it into real html content

@param array $matches Matches array of preg_replace_callback

@return string Tag/Object content
554057
protected function xml_command($matches)
    {
        $command = strtolower($matches[1]);
        $attrib  = html::parse_attrib_string($matches[2]);

        // empty output if required condition is not met
        if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) {
            return '';
        }

        // localize title and summary attributes
        if ($command != 'button' && !empty($attrib['title']) && $this->app->text_exists($attrib['title'])) {
            $attrib['title'] = $this->app->gettext($attrib['title']);
        }
        if ($command != 'button' && !empty($attrib['summary']) && $this->app->text_exists($attrib['summary'])) {
            $attrib['summary'] = $this->app->gettext($attrib['summary']);
        }

        // execute command
        switch ($command) {
            // return a button
            case 'button':
                if ($attrib['name'] || $attrib['command']) {
                    return $this->button($attrib);
                }
                break;

            // frame
            case 'frame':
                return $this->frame($attrib);
                break;

            // show a label
            case 'label':
                if ($attrib['expression'])
                    $attrib['name'] = $this->eval_expression($attrib['expression']);

                if ($attrib['name'] || $attrib['command']) {
                    $vars = $attrib + array('product' => $this->config->get('product_name'));
                    unset($vars['name'], $vars['command']);

                    $label   = $this->app->gettext($attrib + array('vars' => $vars));
                    $quoting = !empty($attrib['quoting']) ? strtolower($attrib['quoting']) : (rcube_utils::get_boolean((string)$attrib['html']) ? 'no' : '');

                    // 'noshow' can be used in skins to define new labels
                    if ($attrib['noshow']) {
                        return '';
                    }

                    switch ($quoting) {
                        case 'no':
                        case 'raw':
                            break;
                        case 'javascript':
                        case 'js':
                            $label = rcube::JQ($label);
                            break;
                        default:
                            $label = html::quote($label);
                            break;
                    }

                    return $label;
                }
                break;

            case 'add_label':
                $this->add_label($attrib['name']);
                break;

            // include a file
            case 'include':
                $old_base_path = $this->base_path;
                if (!empty($attrib['skin_path'])) $attrib['skinpath'] = $attrib['skin_path'];
                if ($path = $this->get_skin_file($attrib['file'], $skin_path, $attrib['skinpath'])) {
                    // set base_path to core skin directory (not plugin's skin)
                    $this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path);
                    $path = realpath(RCUBE_INSTALL_PATH . $path);
                }

                if (is_readable($path)) {
                    if ($this->config->get('skin_include_php')) {
                        $incl = $this->include_php($path);
                    }
                    else {
                      $incl = file_get_contents($path);
                    }
                    $incl = $this->parse_conditions($incl);
                    $incl = $this->parse_xml($incl);
                    $incl = $this->fix_paths($incl);
                    $this->base_path = $old_base_path;
                    return $incl;
                }
                break;

            case 'plugin.include':
                $hook = $this->app->plugins->exec_hook("template_plugin_include", $attrib);
                return $hook['content'];

            // define a container block
            case 'container':
                if ($attrib['name'] && $attrib['id']) {
                    $this->command('gui_container', $attrib['name'], $attrib['id']);
                    // let plugins insert some content here
                    $hook = $this->app->plugins->exec_hook("template_container", $attrib);
                    return $hook['content'];
                }
                break;

            // return code for a specific application object
            case 'object':
                $object  = strtolower($attrib['name']);
                $content = '';

                // we are calling a class/method
                if (($handler = $this->object_handlers[$object]) && is_array($handler)) {
                    if (is_callable($handler)) {
                        // We assume that objects with src attribute are internal (in most
                        // cases this is a watermark frame). We need this to make sure assets_path
                        // is added to the internal assets paths
                        $external = empty($attrib['src']);
                        $content  = call_user_func($handler, $attrib);
                    }
                }
                // execute object handler function
                else if (function_exists($handler)) {
                    $content = call_user_func($handler, $attrib);
                }
                else if ($object == 'doctype') {
                    $content = html::doctype($attrib['value']);
                }
                else if ($object == 'logo') {
                    $attrib += array('alt' => $this->xml_command(array('', 'object', 'name="productname"')));

                    if ($logo = $this->config->get('skin_logo')) {
                        if (is_array($logo)) {
                            if ($template_logo = $logo[$this->template_name]) {
                                $attrib['src'] = $template_logo;
                            }
                            elseif ($template_logo = $logo['*']) {
                                $attrib['src'] = $template_logo;
                            }
                        }
                        else {
                            $attrib['src'] = $logo;
                        }
                    }

                    $content = html::img($attrib);
                }
                else if ($object == 'productname') {
                    $name = $this->config->get('product_name', 'Roundcube Webmail');
                    $content = html::quote($name);
                }
                else if ($object == 'version') {
                    $ver = (string)RCMAIL_VERSION;
                    if (is_file(RCUBE_INSTALL_PATH . '.svn/entries')) {
                        if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs))
                          $ver .= ' [SVN r'.$regs[1].']';
                    }
                    else if (is_file(RCUBE_INSTALL_PATH . '.git/index')) {
                        if (preg_match('/Date:\s+([^\n]+)/', @shell_exec('git log -1'), $regs)) {
                            if ($date = date('Ymd.Hi', strtotime($regs[1]))) {
                                $ver .= ' [GIT '.$date.']';
                            }
                        }
                    }
                    $content = html::quote($ver);
                }
                else if ($object == 'steptitle') {
                  $content = html::quote($this->get_pagetitle());
                }
                else if ($object == 'pagetitle') {
                    if ($this->devel_mode && !empty($_SESSION['username']))
                        $title = $_SESSION['username'].' :: ';
                    else if ($prod_name = $this->config->get('product_name'))
                        $title = $prod_name . ' :: ';
                    else
                        $title = '';
                    $title .= $this->get_pagetitle();
                    $content = html::quote($title);
                }

                // exec plugin hooks for this template object
                $hook = $this->app->plugins->exec_hook("template_object_$object", $attrib + array('content' => $content));

                if (strlen($hook['content']) && !empty($external)) {
                    $object_id                 = uniqid('TEMPLOBJECT:', true);
                    $this->objects[$object_id] = $hook['content'];
                    $hook['content']           = $object_id;
                }

                return $hook['content'];

            // return code for a specified eval expression
            case 'exp':
                return html::quote($this->eval_expression($attrib['expression']));

            // return variable
            case 'var':
                $var = explode(':', $attrib['name']);
                $name = $var[1];
                $value = '';

                switch ($var[0]) {
                    case 'env':
                        $value = $this->env[$name];
                        break;
                    case 'config':
                        $value = $this->config->get($name);
                        if (is_array($value) && $value[$_SESSION['storage_host']]) {
                            $value = $value[$_SESSION['storage_host']];
                        }
                        break;
                    case 'request':
                        $value = rcube_utils::get_input_value($name, rcube_utils::INPUT_GPC);
                        break;
                    case 'session':
                        $value = $_SESSION[$name];
                        break;
                    case 'cookie':
                        $value = htmlspecialchars($_COOKIE[$name]);
                        break;
                    case 'browser':
                        $value = $this->browser->{$name};
                        break;
                }

                if (is_array($value)) {
                    $value = implode(', ', $value);
                }

                return html::quote($value);

            case 'form':
                return $this->form_tag($attrib);
        }
        return '';
    }
323740
public function callbackReplace($matches)
    {
        if ($matches[1] !== '') {
            return $matches[0]; // don't modify existing <a href="">links</a> and <img src="">
        }
        $url = $matches[2];
        $urlWithPrefix = $matches[2];
        if (strpos($url, '@') !== false) {
            $urlWithPrefix = 'mailto:'.$url;
        } elseif (strpos($url, 'https://') === 0) {
            $urlWithPrefix = $url;
        } elseif (strpos($url, 'http://') !== 0) {
            $urlWithPrefix = 'http://'.$url;
        }

        return '<a href="'.$urlWithPrefix.'">'.$url.'</a>';
    }
930060
protected function _replaceParamsCallback($matches)
    {
        $key   = substr($matches[0], 1);
        $value = $this->_parameters[$key];

        if(!$value instanceof DatabaseQuerySelect) {
            $value = is_object($value) ? (string) $value : $value;
            $replacement = $this->getDriver()->quoteValue($value);
        }
        else $replacement = '('.$value.')';

        return is_array($value) ? '('.$replacement.')' : $replacement;
    }
1064656
private function createLinkOrSeeTagFromRegexMatch(array $match)
    {
        list($completeMatch, $tagName, $tagContent) = $match;

        return Tag::createInstance('@' . $tagName . ' ' . $tagContent);
    }
390116
public function compile($matches)
    {
        if (in_array($matches[1], ["include", "includes"])) {
            // todo: Fix dis shet :c
        } else {
            if (isset($matches[5])) {
                $params    = $matches[5];
                $newParams = [];

                foreach (explode(",", $params) as $param) {
                    $newParams[] = preg_replace_callback("/(\w+)=(.*)/is", function ($m) {
                        if (! preg_match("/^(?:\"|')/is", $m[2])) {
                            $m[2] = "\"$m[2]\"";
                        }

                        return "\"{$m[1]}\" => {$m[2]}";
                    }, $param);
                }

                return $this->compiler->wrap("echo \$__env->render(\"{$matches[3]}\", [" . join(",", $newParams) . "])");
            } else {
                return $this->compiler->wrap("echo \$__env->render(\"{$matches[3]}\")");
            }
        }
    }
1318504
protected function literal(array $matches)
    {
        // hash from matches
        $hash = '[!' . __FUNCTION__ . '::read(' . \crc32($matches[1]) . ')!]';

        // save hash and value to literals array
        $this->literals[$hash] = $matches[1];

        // return hash value for replace
        return $hash;
    }
930002
protected function _translate($matches)
    {
        $replacement = '';
        $translator = $this->getObject('translator');

        switch ($matches[0])
        {
            case 'D':
                $replacement = $translator->translate(strtoupper($this->_date->format('D')));
                break;

            case 'l':
                $replacement = $translator->translate(strtoupper($this->_date->format('l')));
                break;

            case 'F':
                $replacement = $translator->translate(strtoupper($this->_date->format('F')));
                break;

            case 'M':
                $replacement = $translator->translate(strtoupper($this->_date->format('F').'_SHORT'));
                break;
        }

        $replacement = preg_replace('/([a-z])/i', '\\\\$1', $replacement);

        return $replacement;
    }
1156999
protected function replaceCallback($matches)
    {
        if (!empty($matches['beforeSkip']) && !empty($matches['afterSkip'])) {
            return trim($matches[0], '{!} ');
        }
        // check: count quotes does not parity
        if (!NumericHelper::isParity(mb_substr_count($matches[0], '`', 'UTF-8'))) {
            return $matches[0];
        }
        $matches[0] = preg_replace_callback(
            '/
                \\s*(?P<sugar> (?!`)\*(?!`) | (?!`)\*\*(?!`) | (?!`)\/(?!`) | (?!`)\%(?!`) |
                \\s+(?!`)mod(?!`)\\s+ | (?!`)\+(?!`) | (?!`)\-(?!`) | (?!`)\|(?!`) | (?!`)\&(?!`) |
                (?!`)\^(?!`) | (?!`)\>\>(?!`) | (?!`)\<\<(?!`) |
                (?!`)\|\|(?!`) | (?!`)\&\&(?!`) | \\s+(?!`)' . $this->_getInlineConditionNames() . '(?!`)\\s+ |`\\s+\?\\s+|`\\s+\:\\s+)\\s*`
            /x',
            [$this, 'replaceSugar'],
            $matches[0]);
        // replace `=` tpl mnemonics
        $matches[0] = preg_replace('/`([\!\<\>]?)[\=]+`/', '`$1&#61;`', $matches[0]);
        // replace `text` to ““text””
        $matches[0] = preg_replace(['/=\\s*\`/', '/\`/'], ['=““', '””'], $matches[0]);
        // replacement of internal recursion on {{{...}}}
        $i = 0;
        $dataRecursive = [];
        $matches[0] = preg_replace_callback(
            '/\“\“(?:[^\“\”]++|(?R))*\”\”/iu',
            function ($value) use (&$dataRecursive, &$i) {
                $key = '{{{' . $i . '}}}';
                $value = current($value);
                $dataRecursive[$key] = $value;
                $i++;

                return $key;
            },
            $matches[0]
        );
        // Search params is variable of template
        $params = $this->_searchParams($matches[0], $dataRecursive);
        // Search of filters (modifiers)
        $filters = $this->_searchFilters($matches[0], $dataRecursive);
        $matches['name'] = trim($matches['name']);
        $params = Serialize::unserializeRecursive($params);
        // returns cache
        list($cacheKey, $cacheExpire, $cacheTags) = $this->calculateCacheParams($params);
        if (($resultCache = $this->getCacheContent($cacheKey)) !== false) {
            return $resultCache;
        }
        $filters = Serialize::unserializeRecursive($filters);
        $sanitize = $this->sanitize;
        if (isset($params['sanitize'])) {
            $sanitize = $params['sanitize'];
        } elseif(!empty($matches['sanitizeDisable'])) {
            $sanitize = self::SANITIZE_DISABLE;
        }

        // chunk
        if ($matches['type'] === '$') {
            $result = $this->getChunk($matches['name'], $params);
            // alias
        } elseif ($matches['type'] === '@@') {
            $result = Alias::getAlias("@{$matches['name']}");
            // placeholder
        } elseif ($matches['type'] === '+') {
            $result = $this->getPlaceholder($matches['name'], $sanitize);
            // constant
        } elseif ($matches['type'] === '++') {
            $result = $this->getConst($matches['name'], $sanitize);
            // extension
        } elseif ($matches['type'] === '#') {
            $result =
                $this->getExtension($matches['name'], $params, $sanitize);
            //  i18n
        } elseif ($matches['type'] === '%') {
            $result = $this->_calculateI18N(
                $matches['name'],
                Helper::getValue($params['placeholders'], []),
                Helper::getValue($params['locale']),
                Helper::getValue($params['category'])
            );
            // link
        } elseif ($matches['type'] === '~') {
            $result = $this->_calculateLink($matches['name'], $params);
            // snippet
        } elseif (empty($matches['type'])) {
            $result = $this->getSnippet($matches['name'], $params, $sanitize);
        } else {
            return $matches[0];
        }
        // make a filter
        if (!empty($filters)) {
            $result = $this->makeFilter($result, $filters);
        }
        if ($this->autoSerialize) {
            if (is_array($result) || (is_object($result) && !$result instanceof \Closure)) {
                $result = @serialize($result);
            }
        }
        if (!is_scalar($result) && !empty($result)) {
            throw new TemplateException('Wrong type is var: ' . Json::encode($result));
        }
        // sets a content to cache
        $this->setCacheContent(
            $cacheKey,
            $result,
            $cacheExpire,
            $cacheTags ?: []
        );

        return $result;
    }
280567
public function _doCodeBlocks_callback($matches)
	{
		$codeblock = $this->outdent($matches[1]);
		if(($codeblock = $this->highlightCodeBlock($codeblock)) !== null)
			return "\n\n".$this->hashBlock($codeblock)."\n\n";
		else
			return parent::_doCodeBlocks_callback($matches);
	}
31584
protected function doReplaceNameInExpression($matches)
    {
        $key = $matches[0];
        list($column, $realFullColumnName) = $this->getColumnFromName($key);

        if ($column instanceof ColumnMap) {
            $this->replacedColumns[] = $column;
            $this->foundMatch = true;

            if (false !== strpos($key, '.')) {
                list($tableName, $columnName) = explode('.', $key);
                $realColumnName = substr($realFullColumnName, strrpos($realFullColumnName, '.') + 1);
                if (isset($this->aliases[$tableName])) {
                    //don't replace a alias with their real table name
                    return $this->quoteIdentifier($tableName.'.'.$realColumnName);
                }
            }

            return $this->quoteIdentifier($realFullColumnName);
        }

        return $this->quoteIdentifier($key);
    }
2158
protected static function sanitizeNaughtyHTML($matches)
    {
        // First, escape unclosed tags
        if (empty($matches[ 'closeTag' ])) {
            return '&lt;' . $matches[ 1 ];
        } // Is the element that we caught naughty? If so, escape it
        elseif (in_array(strtolower($matches[ 'tagName' ]), self::getConfig('naughty_tags'), true)) {
            return '&lt;' . $matches[ 1 ] . '&gt;';
        } // For other tags, see if their attributes are "evil" and strip those
        elseif (isset($matches[ 'attributes' ])) {
            // We'll store the already fitlered attributes here
            $attributes = [];

            // Attribute-catching pattern
            $attributesPattern = '#'
                . '(?<name>[^\s\042\047>/=]+)'
                // attribute characters
                // optional attribute-value
                . '(?:\s*=(?<value>[^\s\042\047=><`]+|\s*\042[^\042]*\042|\s*\047[^\047]*\047|\s*(?U:[^\s\042\047=><`]*)))'
                // attribute-value separator
                . '#i';

            // Blacklist pattern for evil attribute names
            $is_evil_pattern = '#^(' . implode('|', self::getConfig('evil_attributes')) . ')$#i';

            // Each iteration filters a single attribute
            do {
                // Strip any non-alpha characters that may preceed an attribute.
                // Browsers often parse these incorrectly and that has been a
                // of numerous XSS issues we've had.
                $matches[ 'attributes' ] = preg_replace('#^[^a-z]+#i', '', $matches[ 'attributes' ]);

                if ( ! preg_match($attributesPattern, $matches[ 'attributes' ], $attribute, PREG_OFFSET_CAPTURE)) {
                    // No (valid) attribute found? Discard everything else inside the tag
                    break;
                }

                if (
                    // Is it indeed an "evil" attribute?
                    preg_match($is_evil_pattern, $attribute[ 'name' ][ 0 ])
                    // Or does it have an equals sign, but no value and not quoted? Strip that too!
                    OR (trim($attribute[ 'value' ][ 0 ]) === '')
                ) {
                    $attributes[] = 'xss=removed';
                } else {
                    $attributes[] = $attribute[ 0 ][ 0 ];
                }

                $matches[ 'attributes' ] = substr(
                    $matches[ 'attributes' ],
                    $attribute[ 0 ][ 1 ] + strlen($attribute[ 0 ][ 0 ])
                );
            } while ($matches[ 'attributes' ] !== '');
            $attributes = empty($attributes)
                ? ''
                : ' ' . implode(' ', $attributes);

            return '<' . $matches[ 'slash' ] . $matches[ 'tagName' ] . $attributes . '>';
        }

        return $matches[ 0 ];
    }
390986
public function match(array $matches) {
        $params = explode(' ', $matches[1]);
        $paramsNum = count($params);
        $this->params[] = $params[0];

        if (1 == $paramsNum) {
            return sprintf($this->defaultRegx['char'], '+');
        }
        else if (2 == $paramsNum) {
            return sprintf($this->defaultRegx[$params[1]], '+');
        }
        else if (3 == $paramsNum) {
            return sprintf($this->defaultRegx[$params[1]], $params[2] > 0 ? '{' . $params[2] . '}' : '*');
        }
        else if (4 == $paramsNum) {
            return sprintf($this->defaultRegx[$params[1]], '{' . $params[2] . ',' . $params[3] . '}');
        }
    }
944642
protected function convertSection(array $matches)
    {
        $params = array();
        $index  = 1;
        while (!empty($matches[$index]) && $index < 13) {
            $params[$matches[$index]] = $matches[$index + 1];
            $index += 2;
        }

        return str_replace('[' . trim($params['name'], '"\'') . ']', '[$' . trim($params['name'], '"\'') . ']', $matches[0]);
    }
262643
protected function autolink($matches)
	{
		$href = $matches[0];

		if (substr($href, 0, 1) == '!')
		{
			return substr($href, 1);
		}

		$href = str_replace('"', '', $href);
		$href = str_replace("'", '', $href);
		$href = str_replace('&#8221', '', $href);

		$h = array('h', 'm', 'f', 'g', 'n');
		if (!in_array(substr($href, 0, 1), $h))
		{
			$href = substr($href, 1);
		}
		$name = trim($href);
		if (substr($name, 0, 7) == 'mailto:')
		{
			$name = substr($name, 7, strlen($name));
			$name = Str::obfuscate($name);

			$href = 'mailto:' . $name;
		}
		$l = sprintf(
			' <a class="ext-link" href="%s" rel="external">%s</a>', $href, $name
		);
		return $l;
	}
398233
private static function htmlAttrMatcher($matches) {

        $chr = $matches[0];
        $ord = ord($chr);

        //The following replaces characters undefined in HTML with the hex entity for the Unicode replacement character.
        if(($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r") || ($ord >= 0x7f && $ord <= 0x9f)) {
            return '&#xFFFD;';
        }

        if(strlen($chr) > 1) {
            $chr = static :: convert($chr, 'UTF-8', 'UTF-32BE');
        }

        $hex = bin2hex($chr);
        $ord = hexdec($hex);

        $entities = [

	        34 => 'quot', //Quotation mark
	        38 => 'amp',  //Ampersand
	        60 => 'lt',   //Less-than sign
	        62 => 'gt',   //Greater-than sign
	    ];

        if(true === isset($entities[$ord])) {
            return '&' . $entities[$ord] . ';';
        }

        if($ord > 255) {
            return sprintf('&#x%04X;', $ord);
        }

        return sprintf('&#x%02X;', $ord);
    }
1044859
private function func_arr_params_callback($matches) {
    	$func = $matches[1];
    	$params_str = $matches[2];
        $in_matches = [];
    	preg_match_all("/([a-z_]+)\=[\"\']?([^\"\']+)[\"\']?/i", stripslashes($params_str), $in_matches, PREG_SET_ORDER);
    	$params = null;
    	foreach($in_matches as $v) {
    		$params[$v[1]] = $v[2];
    	}
    	return "<?php print_r( " . $func . "(" . ArrayHelper::toRaw($params) . ') ); ?>';
    }
616025
protected function _replaceParamsCallback($matches)
    {
        $key   = substr($matches[0], 1);
        $value = $this->_parameters[$key];

        if(!$value instanceof KDatabaseQuerySelect) {
            $value = is_object($value) ? (string) $value : $value;
            $replacement = $this->getAdapter()->quoteValue($value);
        }
        else $replacement = '('.$value.')';

        return is_array($value) ? '('.$replacement.')' : $replacement;
    }