<?php /*

 Composr
 Copyright (c) ocProducts, 2004-2016

 See text/EN/licence.txt for full licencing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  ocProducts Ltd
 * @package    core_rich_media
 */

/**
 * Standard code module initialisation function.
 *
 * @ignore
 */
function init__comcode_compiler()
{
    if (!defined('CCP_NO_MANS_LAND')) {
        define('CCP_NO_MANS_LAND', 0);
        define('CCP_IN_TAG_NAME', 1);
        define('CCP_STARTING_TAG', 2);
        define('CCP_IN_TAG_BETWEEN_ATTRIBUTES', 3);
        define('CCP_IN_TAG_ATTRIBUTE_NAME', 4);
        define('CCP_IN_TAG_BETWEEN_ATTRIBUTE_NAME_VALUE_LEFT', 5);
        define('CCP_IN_TAG_BETWEEN_ATTRIBUTE_NAME_VALUE_RIGHT', 6);
        define('CCP_IN_TAG_ATTRIBUTE_VALUE', 7);
        define('CCP_IN_TAG_ATTRIBUTE_VALUE_NO_QUOTE', 8);

        define('MAX_COMCODE_TAG_LOOK_AHEAD_LENGTH', 30);
    }

    // In theory, almost any tag is reversible. However these tags can be converted ROBUSTLY and hence the WYSIWYG editor can manipulate them as HTML rather than having to display as Comcode
    // If the tag is mapped to an array of attributes then those attributes say when it is NOT reversable.
    global $REVERSIBLE_TAGS;
    $REVERSIBLE_TAGS = array(
        'surround' => true, 'cite' => true, 'ins' => true, 'del' => true, 'dfn' => true, 'address' => true, 'abbr' => true, 'acronym' => true, 'list' => true, 'highlight' => true, 'indent' => true, 'b' => true, 'i' => true, 'u' => true, 's' => true, 'sup' => true, 'sub' => true,
        'title' => array('sub'), 'size' => true, 'color' => true, 'font' => true, 'tt' => true, 'img' => array('rollover', 'refresh_time'), 'url' => true, 'email' => true,
        'semihtml' => true, 'html' => true, 'align' => true, 'left' => true, 'center' => true, 'right' => true, 'var' => true, 'samp' => true, 'q' => true,
        'page' => true, 'thumb' => true, 'attachment_safe' => true,
    );
    // The following could conceivably not need to be reversed, as they're pure HTML. However, it's better not to let the WYSIWYG'd HTML get too complex.
    // 'tooltip' => true, 'section' => true, 'section_controller' => true, 'big_tab' => true, 'big_tab_controller' => true, 'tabs' => true, 'tab' => true, 'carousel' => true, 'flash' => true, 'media' => true, 'hide' => true, 'quote' => true, 'ticker' => true, 'jumping' => true

    // The contents of these tags is human readable text. It may be altered for reasons of bork, or word-wrapping, or textcode; they have hard white space
    global $TEXTUAL_TAGS, $TEXTUAL_TAGS_WYSIWYG;
    $TEXTUAL_TAGS = array('overlay' => true, 'tooltip' => true, 'section' => true, 'surround' => true, 'if_in_group' => true, 'cite' => true, 'ins' => true, 'del' => true, 'dfn' => true, 'address' => true, 'abbr' => true, 'acronym' => true, 'list' => true, 'indent' => true, 'align' => true, 'left' => true, 'center' => true, 'right' => true, 'b' => true, 'i' => true, 'u' => true, 's' => true, 'sup' => true, 'sub' => true, 'title' => true, 'size' => true, 'color' => true, 'highlight' => true, 'font' => true, 'box' => true, 'hide' => true, 'quote' => true, 'tab' => true, 'big_tab' => true, 'ticker' => true, 'pulse' => true, 'concept' => true);
    $TEXTUAL_TAGS_WYSIWYG = $TEXTUAL_TAGS + array('tabs' => true, 'carousel' => true, 'media_set' => true, 'codebox' => true, 'code' => true);

    // These tags don't have <br />'s done right after them because they are their own block-end (like a paragraph). They may contain textcode lists and rules
    global $BLOCK_TAGS;
    $BLOCK_TAGS = array('media_set' => true, 'section' => true, 'section_controller' => true, 'tabs' => true, 'tab' => true, 'big_tab' => true, 'big_tab_controller' => true, 'carousel' => true, 'surround' => true, 'contents' => true, 'concepts' => true, 'codebox' => true, 'code' => true, 'list' => true, 'indent' => true, 'align' => true, 'left' => true, 'center' => true, 'right' => true, 'staff_note' => true, 'reference' => true, 'menu' => true, 'title' => true, 'box' => true, 'quote' => true, 'block' => true, 'hide' => true, 'overlay' => true, 'media' => true, 'attachment' => true, 'attachment_safe' => true);

    // These tags can only be used by privileged members
    global $DANGEROUS_TAGS;
    $DANGEROUS_TAGS = array('overlay' => true, 'if_in_group' => true, 'concepts' => true, 'random' => true, 'include' => true, 'block' => true, 'menu' => true); // Don't want people putting menus around, plus the captions aren't escaped

    // These tags have contents that are not interpreted as Comcode (so no HTML tags either), but are formatted for white-space
    global $CODE_TAGS;
    $CODE_TAGS = array(/*'img' => true - no, can be a symbol,*/'flash' => true, 'media' => true, 'thumb' => true, 'menu' => true, 'no_parse' => true, 'code' => true, 'tt' => true, 'samp' => true, 'codebox' => true, 'staff_note' => true, 'section_controller' => true, 'big_tab_controller' => true);

    global $BUTTON_EDITED_TAGS;
    $BUTTON_EDITED_TAGS = array('attachment' => true, 'attachment_safe' => true, 'section_controller' => true, 'big_tab_controller' => true, 'currency' => true, 'block' => true, 'contents' => true, 'concepts' => true, 'media' => true, 'flash' => true, 'menu' => true, 'email' => true, 'reference' => true, 'page' => true, 'thumb' => true, 'snapback' => true, 'post' => true, 'topic' => true, 'include' => true, 'random' => true, 'jumping' => true, 'shocker' => true,);

    // ALSO:
    // See _get_details_comcode_tags function in comcode_add.php

    // Hehe
    global $LEET_FILTER;
    $LEET_FILTER = null;

    global $ALLOWED_COMCODE_ENTITIES;
    $ALLOWED_COMCODE_ENTITIES = array('OElig' => true, 'oelig' => true, 'Scaron' => true, 'scaron' => true, 'Yuml' => true, 'circ' => true, 'tilde' => true, 'ensp' => true, 'emsp' => true, 'thinsp' => true, 'zwnj' => true, 'zwj' => true, 'lrm' => true, 'rlm' => true, 'ndash' => true, 'mdash' => true, 'lsquo' => true, 'rsquo' => true, 'sbquo' => true, 'ldquo' => true, 'rdquo' => true, 'bdquo' => true, 'dagger' => true, 'Dagger' => true, 'hellip' => true, 'permil' => true, 'lsaquo' => true, 'rsaquo' => true, 'euro' => true, 'Agrave' => true, 'Aacute' => true, 'Acirc' => true, 'Atilde' => true, 'Auml' => true, 'Aring' => true, 'AElig' => true, 'Ccedil' => true, 'Egrave' => true, 'Eacute' => true, 'Ecirc' => true, 'Euml' => true, 'Igrave' => true, 'Iacute' => true, 'Icirc' => true, 'Iuml' => true, 'ETH' => true, 'Ntilde' => true, 'Ograve' => true, 'Oacute' => true, 'Ocirc' => true, 'Otilde' => true, 'Ouml' => true, 'Oslash' => true, 'Ugrave' => true, 'Uacute' => true, 'Ucirc' => true, 'Uuml' => true, 'Yacute' => true, 'THORN' => true, 'szlig' => true, 'agrave' => true, 'aacute' => true, 'acirc' => true, 'atilde' => true, 'auml' => true, 'aring' => true, 'aelig' => true, 'ccedil' => true, 'egrave' => true, 'eacute' => true, 'ecirc' => true, 'euml' => true, 'igrave' => true, 'iacute' => true, 'icirc' => true, 'iuml' => true, 'eth' => true, 'ntilde' => true, 'ograve' => true, 'oacute' => true, 'ocirc' => true, 'otilde' => true, 'ouml' => true, 'oslash' => true, 'ugrave' => true, 'uacute' => true, 'ucirc' => true, 'uuml' => true, 'yacute' => true, 'thorn' => true, 'yuml' => true, 'nbsp' => true, 'iexcl' => true, 'curren' => true, 'cent' => true, 'pound' => true, 'yen' => true, 'brvbar' => true, 'sect' => true, 'uml' => true, 'copy' => true, 'ordf' => true, 'laquo' => true, 'not' => true, 'shy' => true, 'reg' => true, 'trade' => true, 'macr' => true, 'deg' => true, 'plusmn' => true, 'sup2' => true, 'sup3' => true, 'acute' => true, 'micro' => true, 'para' => true, 'middot' => true, 'cedil' => true, 'sup1' => true, 'ordm' => true, 'raquo' => true, 'frac14' => true, 'frac12' => true, 'frac34' => true, 'iquest' => true, 'times' => true, 'divide' => true, 'amp' => true, 'lt' => true, 'gt' => true, 'quot' => true, 'rarr' => true, 'larr' => true);

    global $ADVERTISING_BANNERS_CACHE;
    $ADVERTISING_BANNERS_CACHE = null;

    global $NO_LINK_TITLES;
    $NO_LINK_TITLES = false;

    global $COMCODE_ATTACHMENTS, $ATTACHMENTS_ALREADY_REFERENCED;
    $COMCODE_ATTACHMENTS = array();
    $ATTACHMENTS_ALREADY_REFERENCED = array();

    global $MEMBER_MENTIONS_IN_COMCODE;
    $MEMBER_MENTIONS_IN_COMCODE = array();
}

/**
 * Find how Comcode will be represented in the editor.
 *
 * @param  string $tag The Comcode tag
 * @param  ?array $attributes The parameters (null: don't consider)
 * @param  ?Tempcode $embed The contents of the tag (null: don't consider)
 * @param  boolean $html_errors Whether HTML structure errors have been spotted so far (limits how $semiparse_mode rendering works)
 * @param  boolean $conservative Don't add things to WYSIWYG_COMCODE__HTML that may not be in some situations
 * @return integer The Comcode integration style
 */
function wysiwyg_comcode_markup_style($tag, $attributes = null, $embed = null, $html_errors = false, $conservative = false)
{
    global $BUTTON_EDITED_TAGS, $TEXTUAL_TAGS_WYSIWYG, $BLOCK_TAGS, $REVERSIBLE_TAGS, $CODE_TAGS;

    $_button_edited_tags = $BUTTON_EDITED_TAGS;

    if (($tag == 'attachment_safe') && ($embed !== null) && (preg_match('#^new\_\d+$#', $embed->evaluate()) != 0)) {
        $_button_edited_tags['attachment_safe'] = true;
    } else {
        if ((isset($REVERSIBLE_TAGS[$tag])) && (is_bool($REVERSIBLE_TAGS[$tag]))) {
            if (!$html_errors) {
                return WYSIWYG_COMCODE__HTML;
            }
        }

        if ((isset($REVERSIBLE_TAGS[$tag])) && (is_array($REVERSIBLE_TAGS[$tag])) && (!$conservative) && (($attributes === null) || (array_intersect(array_keys($attributes), $REVERSIBLE_TAGS[$tag]) == array()))) {
            if (!$html_errors) {
                return WYSIWYG_COMCODE__HTML;
            }
        }
    }

    if ($tag == 'media' || $tag == 'flash' || $tag == 'attachment' || $tag == 'attachment_safe' || $tag == 'title') {
        if (($attributes !== null) && ((!array_key_exists('wysiwyg_editable', $attributes)) || ($attributes['wysiwyg_editable'] == '0'))) {
            $_button_edited_tags[$tag] = true;
        } else {
            if (!$html_errors) {
                return WYSIWYG_COMCODE__HTML;
            }
        }
    }
    $is_button_edited_tag = isset($_button_edited_tags[$tag]);
    if ($is_button_edited_tag) {
        if (($tag == 'block') && ($embed !== null) && (is_file(get_file_base() . '/sources_custom/miniblocks/' . $embed->evaluate() . '.php'/*Won't have a defined editing UI*/))) {
            $is_button_edited_tag = false;
        }
    }
    if ($is_button_edited_tag) {
        return WYSIWYG_COMCODE__BUTTON;
    }

    if (isset($CODE_TAGS[$tag])) {
        if (!$html_errors) {
            if ($tag == 'staff_note' || $tag == 'code' || $tag == 'codebox') {
                return WYSIWYG_COMCODE__XML_BLOCK;
            } else {
                return WYSIWYG_COMCODE__XML_BLOCK_ANTIESCAPED;
            }
        }
    }

    if ((isset($TEXTUAL_TAGS_WYSIWYG[$tag])) && (isset($BLOCK_TAGS[$tag]))) {
        if (!$html_errors) {
            return WYSIWYG_COMCODE__XML_BLOCK;
        }
    }

    if ((isset($TEXTUAL_TAGS_WYSIWYG[$tag])) && (!isset($BLOCK_TAGS[$tag]))) {
        if (!$html_errors) {
            return WYSIWYG_COMCODE__XML_INLINE;
        }
    }

    if (isset($BLOCK_TAGS[$tag])) {
        if (!$html_errors) {
            return WYSIWYG_COMCODE__STANDOUT_BLOCK;
        }
    }

    if (!$html_errors) {
        return WYSIWYG_COMCODE__STANDOUT_INLINE;
    }

    return WYSIWYG_COMCODE__BUTTON;
}

/**
 * Output a Comcode tag in WYSIWYG-editor format, for tidy editing.
 *
 * @param  string $tag The Comcode tag
 * @param  array $attributes The parameters
 * @param  Tempcode $embed The contents of the tag
 * @param  boolean $semihtml Whether we are in semihtml mode. We are always in semi-parse mode
 * @param  ?integer $method Display method (null: auto-detect)
 * @param  boolean $html_errors Whether HTML structure errors have been spotted so far (limits how $semiparse_mode rendering works)
 * @return ?string The HTML (null: render as native HTML)
 */
function add_wysiwyg_comcode_markup($tag, $attributes, $embed, $semihtml, $method = null, $html_errors = false)
{
    $semihtml = true; // It's effectively always on as we pre-escaped during semi-parse mode

    $params_comcode = '';
    foreach ($attributes as $key => $val) {
        if (is_integer($key)) {
            $key = strval($key);
        }

        if (($key != 'param') || ($val != '')) {
            $params_comcode .= ' ' . $key . '="' . str_replace('"', '\\"', $val) . '"';
        }
    }
    $raw_comcode_start = '[' . $tag . $params_comcode . ']';
    $raw_comcode_end = '[/' . $tag . ']';

    if ($method === null) {
        $method = wysiwyg_comcode_markup_style($tag, $attributes, $embed);
    }

    if ($html_errors && $method != WYSIWYG_COMCODE__XML_BLOCK_ANTIESCAPED) {
        if ($method != WYSIWYG_COMCODE__HTML) {
            $method = WYSIWYG_COMCODE__BUTTON;
        }
    }

    switch ($method) {
        case WYSIWYG_COMCODE__BUTTON:
            if ($semihtml) {
                $_embed_visual = html_entity_decode(strip_tags($embed->evaluate()), ENT_QUOTES, get_charset());
                $_embed_inner = $embed->evaluate();
            } else {
                $_embed_visual = $embed->evaluate();
                $_embed_inner = $embed->evaluate();
            }
            if ($tag == 'block') {
                $comcode_title = do_lang('comcode:COMCODE_EDITABLE_BLOCK', escape_html($_embed_visual));
            } else {
                $comcode_title = do_lang('comcode:COMCODE_EDITABLE_TAG', escape_html($tag));
            }
            $raw_comcode = $raw_comcode_start . $_embed_inner . $raw_comcode_end;
            return '<input class="cms_keep_ui_controlled" size="45" title="' . escape_html($raw_comcode) . '" type="button" value="' . $comcode_title . '" />';

        case WYSIWYG_COMCODE__XML_BLOCK:
        case WYSIWYG_COMCODE__XML_INLINE:
        case WYSIWYG_COMCODE__XML_BLOCK_ESCAPED:
        case WYSIWYG_COMCODE__XML_BLOCK_ANTIESCAPED:
            $params_html = '';
            foreach ($attributes as $key => $val) {
                if (is_integer($key)) {
                    $key = strval($key);
                }

                if (($key != 'param') || ($val != '')) {
                    $params_html .= ' ' . escape_html($key) . '="' . escape_html($val) . '"';
                }
            }

            $out = '';
            if ($method == WYSIWYG_COMCODE__XML_INLINE) {
                $out .= '&#8203;';
            }
            $out .= '<comcode-' . escape_html($tag) . $params_html . '>';
            switch ($method) {
                case WYSIWYG_COMCODE__XML_BLOCK_ANTIESCAPED:
                    if ($semihtml) {
                        $_embed = html_entity_decode($embed->evaluate(), ENT_QUOTES, get_charset());
                    } else {
                        $_embed = $embed->evaluate();
                    }
                    $out .= $_embed;
                    break;

                case WYSIWYG_COMCODE__XML_BLOCK_ESCAPED:
                    $_embed = nl2br(escape_html($embed->evaluate()));
                    $out .= $_embed;
                    break;

                case WYSIWYG_COMCODE__XML_BLOCK:
                case WYSIWYG_COMCODE__XML_INLINE:
                default:
                    if ($semihtml) {
                        $_embed = $embed->evaluate();
                    } else {
                        $_embed = nl2br(escape_html($embed->evaluate()));
                    }
                    $out .= $_embed;
                    break;
            }
            $out .= '</comcode-' . escape_html($tag) . '>';

            if ($method == WYSIWYG_COMCODE__XML_INLINE) {
                $out .= '&#8203;';
            }
            return $out;

        case WYSIWYG_COMCODE__STANDOUT_BLOCK:
            if ($semihtml) {
                $_embed = $embed->evaluate();
            } else {
                $_embed = nl2br(escape_html($embed->evaluate()));
            }
            $out = '';
            $out .= '<kbd title="' . escape_html($tag) . '" class="cms_keep_block">';
            $out .= escape_html($raw_comcode_start) . $_embed . escape_html($raw_comcode_end);
            $out .= '</kbd>';
            return $out;

        case WYSIWYG_COMCODE__STANDOUT_INLINE:
            if ($semihtml) {
                $_embed = $embed->evaluate();
            } else {
                $_embed = nl2br(escape_html($embed->evaluate()));
            }
            $out = '';
            $out .= '&#8203;<kbd title="' . escape_html($tag) . '" class="cms_keep">';
            $out .= escape_html($raw_comcode_start) . $_embed . escape_html($raw_comcode_end);
            $out .= '</kbd>&#8203;';
            return $out;

        case WYSIWYG_COMCODE__HTML:
            return null; // Render as native HTML
    }

    return null;
}

/**
 * Convert the specified Comcode (text format) into a Tempcode tree. You shouldn't output the Tempcode tree to the browser, as it looks really horrible. If you are in a rare case where you need to output directly (not through templates), you should call the evaluate method on the Tempcode object, to convert it into a string.
 *
 * @param  LONG_TEXT $comcode The Comcode to convert
 * @param  MEMBER $source_member The member the evaluation is running as. This is a security issue, and you should only run as an administrator if you have considered where the Comcode came from carefully
 * @param  boolean $as_admin Whether to explicitly execute this with admin rights. There are a few rare situations where this should be done, for data you know didn't come from a member, but is being evaluated by one.
 * @param  ?integer $wrap_pos The position to conduct wordwrapping at (null: do not conduct word-wrapping)
 * @param  ?string $pass_id A special identifier that can identify this resource in a sea of our resources of this class; usually this can be ignored, but may be used to provide a binding between JavaScript in evaluated Comcode, and the surrounding environment (null: no explicit binding)
 * @param  object $connection The database connection to use
 * @param  boolean $semiparse_mode Whether to parse so as to create something that would fit inside a semihtml tag. It means we generate HTML, with Comcode written into it where the tag could never be reverse-converted (e.g. a block).
 * @param  boolean $preparse_mode Whether this is being pre-parsed, to pick up errors before row insertion.
 * @param  boolean $is_all_semihtml Whether to treat this whole thing as being wrapped in semihtml, but apply normal security otherwise.
 * @param  boolean $structure_sweep Whether we are only doing this parse to find the title structure
 * @param  boolean $check_only Whether to only check the Comcode. It's best to use the check_comcode function which will in turn use this parameter.
 * @param  ?array $highlight_bits A list of words to highlight (null: none)
 * @param  ?MEMBER $on_behalf_of_member The member we are running on behalf of, with respect to how attachments are handled; we may use this members attachments that are already within this post, and our new attachments will be handed to this member (null: member evaluating)
 * @param  boolean $in_code_tag Whether the parse context is already in a code tag
 * @return Tempcode The Tempcode generated
 *
 * @ignore
 */
function __comcode_to_tempcode($comcode, $source_member, $as_admin, $wrap_pos, $pass_id, $connection, $semiparse_mode, $preparse_mode, $is_all_semihtml, $structure_sweep, $check_only, $highlight_bits = null, $on_behalf_of_member = null, $in_code_tag = false)
{
    global $LAX_COMCODE;
    if ($LAX_COMCODE === null && function_exists('get_option')) {
        $LAX_COMCODE = (get_option('lax_comcode') === '1');
    }

    init_valid_comcode_tags();
    init_potential_js_naughty_array();

    global $ADVERTISING_BANNERS_CACHE, $ALLOWED_COMCODE_ENTITIES, $CODE_TAGS, $DANGEROUS_TAGS, $VALID_COMCODE_TAGS, $BLOCK_TAGS, $POTENTIAL_JS_NAUGHTY_ARRAY, $TEXTUAL_TAGS, $LEET_FILTER, $IMPORTED_CUSTOM_COMCODE;

    $html_escape_1_strrep_inv = array('&' => true, '"' => true, '\'' => true, '<' => true, '>' => true);

    $print_mode = get_param_integer('wide_print', 0) === 1;

    $preparse_hooks = find_all_hooks('systems', 'comcode_preparse');
    foreach ($preparse_hooks as $preparse_hook => $hook_dir) {
        require_code('hooks/systems/comcode_preparse/' . $preparse_hook, false, $hook_dir == 'sources_custom');
        $preparse_ob = object_factory('Hook_comcode_preparse_' . $preparse_hook, true);
        if (is_null($preparse_ob)) {
            continue;
        }
        $preparse_ob->preparse($comcode);
    }

    // Fix smart quote problems (may be added unintentionally by other software)
    if (get_charset() === 'utf-8') {
        $comcode = preg_replace('#=\xE2\x80\x9C(.*)\xE2\x80\x9D#U', '="$1"', $comcode);
    }

    $len = strlen($comcode);

    if ((php_function_allowed('set_time_limit')) && (ini_get('max_execution_time') != '0')) {
        @set_time_limit(300);
    }

    $allowed_html_seqs = array(
        // HTML tag may actually be used in very limited conditions: only the following HTML seqs will come out as HTML. This is, unless the blacklist filter is used instead.
        '<table>',
        '<table( class="[^"<>]*")?( summary="[^"<>]*")?' . '>',
        '</table>',
        '<tr>',
        '</tr>',
        '<td>',
        '</td>',
        '<th>',
        '</th>',
        '<pre>',
        '</pre>',
        '<address>',
        '</address>',
        '<br\s*/?' . '>',
        '<p>',
        '</p>',
        '<p\s*/>',
        '<div( style=".*")?' . '>',
        '</div>',
        '<b>',
        '</b>',
        '<u>',
        '</u>',
        '<i>',
        '</i>',
        '<em>',
        '</em>',
        '<strong>',
        '</strong>',
        '<li>',
        '</li>',
        '<ul>',
        '</ul>',
        '<ol>',
        '</ol>',
        '<dir>',
        '</dir>',
        '<del>',
        '</del>',
        '<s>',
        '</s>',
        '<font( color="[^"<>]*")?( face="[^"<>]*")?( size="[^"<>]*")?' . '>',
        '</font>',
        '<!--',
        '<h1( class="screen_title")?( id="screen_title")?' . '>',
        '</h1>',
        '<h2>',
        '</h2>',
        '<h3>',
        '</h3>',
        '<h4>',
        '</h4>',
        '<h5>',
        '</h5>',
        '<h6>',
        '</h6>',
        '<img( alt="[^"<>]*")?( border="[^"<>]*")?( class="vertical_alignment")? src="[^"<>]*"( style="vertical-align: (top|middle|bottom|baseline)")?( title="[^"<>]*")?\s*/?' . '>',
        '<a( class="[^"<>]*")? href="[^"<>]*"( rel="[^"<>]*")?( target="[^"<>]*")?( title="[^"<>]*")?( rel="[^"<>]*")?' . '>', // NB: Rel can be tacked on end by another part of Composr
        '</a>',
        '<span>',
        '<span style="color:\s*\#[A-Fa-f0-9]+;?">',
        '<span style="font-family:\s*[\w\-\s,]+;?">',
        '<span style="font-size:\s*[\d\.]+(em|px|pt)?;?">',
        '</span>',
        '<code>',
        '</code>',

        // Used for testing the Comcode parser
        '<composr-test>',
        '</composr-test>',
    );

    $link_terminator_strs = array(' ', "\n", ']', '[', ')', '"', '>', '<', '}', '{', ".\n", ', ', '. ', "'", '&nbsp;', '&quot;', '&rdquo;', '&ldquo;', "\0");
    if (get_charset() == 'utf-8') {
        $nbsp = chr(hexdec('C2')) . chr(hexdec('A0'));
        $link_terminator_strs[] = $nbsp;
    }

    if ($as_admin) {
        $comcode_dangerous = true;
        $comcode_dangerous_html = true;
    } else {
        $comcode_dangerous = (!$GLOBALS['MICRO_BOOTUP']) && (has_privilege($source_member, 'comcode_dangerous'));
        $comcode_dangerous_html = false;
        if ((has_privilege($source_member, 'allow_html')) && (($is_all_semihtml) || (strpos($comcode, '[html') !== false) || (strpos($comcode, '[semihtml') !== false))) {
            $comcode_dangerous_html = true;
        }
    }
    if ($pass_id === null) {
        $pass_id = strval(mt_rand(0, mt_getrandmax())); // This is a unique ID that refers to this specific piece of comcode
    }
    global $COMCODE_ATTACHMENTS;
    if (!array_key_exists($pass_id, $COMCODE_ATTACHMENTS)) {
        $COMCODE_ATTACHMENTS[$pass_id] = array();
    }

    // Tag level
    $current_tag = '';
    $attribute_map = array();
    $tag_output = new Tempcode();
    $continuation = '';
    $close = mixed();

    // HTML tag levels (for tracking how we can compose our WYSIWYG view)
    $html_element_stack = array();
    $html_errors = false;

    // Properties that come from our tag
    $white_space_area = true;
    $textual_area = true;
    $formatting_allowed = true;
    $in_html = false;
    $in_semihtml = $is_all_semihtml;
    $in_separate_parse_section = false; // Not escaped because it has to be passed to a secondary filter
    $code_nest_stack = 0;

    // Our state
    $status = CCP_NO_MANS_LAND;
    $lax = $GLOBALS['LAX_COMCODE'] || function_exists('get_member') && $source_member != get_member() || !has_interesting_post_fields(); // if we don't want to produce errors for technically invalid Comcode
    if ((!$lax) && (substr($comcode, 0, 10) === '[semihtml]')) {
        $lax = true;
    }
    $tag_stack = array(); // Contains tuples of our parser state. Does NOT purely represent the most recent tag, it's a combination of the most recent tag and the characteristics of the second most recent tag
    $pos = 0;
    $line_starting = true;
    $just_ended = false;
    $none_wrap_length = 0;
    $just_new_line = true; // So we can detect lists starting right away
    $just_title = false;
    global $NUM_COMCODE_LINES_PARSED;
    $NUM_COMCODE_LINES_PARSED = 0;
    $queued_tempcode = new Tempcode();

    // Silliness
    $stupidity_mode = get_value('stupidity_mode'); // bork or leet
    if ($comcode_dangerous) {
        $stupidity_mode = get_param_string('stupidity_mode', '');
    }
    if ($stupidity_mode === 'leet') {
        $LEET_FILTER = array(
            'B' => '8',
            'C' => '(',
            'E' => '3',
            'G' => '9',
            'I' => '1',
            'L' => '1',
            'O' => '0',
            'P' => '9',
            'S' => '5',
            'U' => '0',
            'V' => '\/',
            'Z' => '2'
        );
    }

    $has_banners = addon_installed('banners');
    if (function_exists('get_option')) {
        $b_all = (get_option('admin_banners', true) == '1');
    } else {
        $b_all = false;
    }

    // Special textcode
    $emoticons = $GLOBALS['FORUM_DRIVER']->find_emoticons(); // We'll be needing the emoticon array
    $emoticon_start_chars = array();
    foreach (array_keys($emoticons) as $emoticon) {
        if (is_integer($emoticon)) {
            $emoticon = strval($emoticon);
        }
        $emoticon_start_chars[$emoticon[0]] = true;
    }
    $shortcuts = array('(EUR-)' => '&euro;', '{f.}' => '&fnof;', '-|-' => '&dagger;', '=|=' => '&Dagger;', '{%o}' => '&permil;', '{~S}' => '&Scaron;', '{~Z}' => '&#x17D;', '(TM)' => '&trade;', '{~s}' => '&scaron;', '{~z}' => '&#x17E;', '{.Y.}' => '&Yuml;', '(c)' => '&copy;', '(r)' => '&reg;', '---' => '&mdash;', '-->' => '&rarr;', '<--' => '&larr;', '--' => '&ndash;', '...' => '&hellip;');

    // Text syntax possibilities, that get maintained as our cursor moves through the text block
    $list_indent = 0;
    $list_type = 'ul';

    // Security
    if ($is_all_semihtml) {
        filter_html($as_admin, $source_member, $pos, $len, $comcode, false, false); // Pre-filter the whole lot (note that this means during general output we do no additional filtering)
    }

    // Do parsing
    while ($pos < $len) {
        $next = $comcode[$pos];
        ++$pos;

        // State machine
        switch ($status) {
            case CCP_NO_MANS_LAND:
                if (($next === '[') && (isset($comcode[$pos]))) {
                    // Look ahead to make sure it's a valid tag. If it's not then it's considered normal user input, not a tag at all
                    $next_2 = $comcode[$pos];
                    $dif = 0; // '0' if it's an opening tag
                    if ($pos < $len) {
                        if ($next_2 === '/') {
                            $dif = 1; // '1' if it's a closing tag
                        } else {
                            $matches = array();
                            if (($pos + 1 < $len) && (preg_match('#^\s*/#', substr($comcode, $pos), $matches) != 0)) {
                                $dif = strlen($matches[0]); // A closing tag with odd spaces
                            }
                        }
                    }
                    $ahead = ltrim(substr($comcode, $pos + $dif, MAX_COMCODE_TAG_LOOK_AHEAD_LENGTH));
                    $equal_pos = strpos($ahead, '=');
                    $space_pos = strpos($ahead, ' ');
                    $end_pos = strpos($ahead, ']');
                    $lax_end_pos = strpos($ahead, '[');
                    $cl_pos = strpos($ahead, "\n");
                    if ($equal_pos === false) {
                        $equal_pos = MAX_COMCODE_TAG_LOOK_AHEAD_LENGTH + 3;
                    }
                    if ($space_pos === false) {
                        $space_pos = MAX_COMCODE_TAG_LOOK_AHEAD_LENGTH + 3;
                    }
                    if ($end_pos === false) {
                        $end_pos = MAX_COMCODE_TAG_LOOK_AHEAD_LENGTH + 3;
                    }
                    if ($lax_end_pos === false) {
                        $lax_end_pos = MAX_COMCODE_TAG_LOOK_AHEAD_LENGTH + 3;
                    }
                    if ($cl_pos === false) {
                        $cl_pos = MAX_COMCODE_TAG_LOOK_AHEAD_LENGTH + 3;
                    }
                    $use_pos = min($equal_pos, $space_pos, $end_pos, $lax_end_pos, $cl_pos);

                    $potential_tag = trim(strtolower(substr($ahead, 0, $use_pos)));
                    if (($use_pos != 22) && ((!$in_semihtml) || ($dif !== 0) || (($potential_tag != 'html') && ($potential_tag != 'semihtml'))) && ((!$in_html) || (($dif !== 0) && ($potential_tag === 'html'))) && ((!$in_code_tag) || ((isset($CODE_TAGS[$potential_tag])) && ($potential_tag === $current_tag))) && ((!$structure_sweep) || ($potential_tag != 'contents'))) {
                        if ($in_code_tag) {
                            if ($dif !== 0) {
                                $code_nest_stack--;
                            } else {
                                $code_nest_stack++;
                            }
                            $ok = ($code_nest_stack === -1);
                        } else {
                            $ok = true;
                        }

                        if ($ok) {
                            if (!isset($VALID_COMCODE_TAGS[$potential_tag])) {
                                if (!$IMPORTED_CUSTOM_COMCODE) {
                                    _custom_comcode_import($connection);
                                }
                            }
                            if ((isset($VALID_COMCODE_TAGS[$potential_tag])) && (strtolower(substr($ahead, 0, 2)) != 'i ')) { // The "i" bit is just there to block a common annoyance: [i] doesn't take parameters and we don't want "[i think]" (for example) being parsed.
                                $close = false;
                                $current_tag = '';
                                if ($GLOBALS['XSS_DETECT']) {
                                    ocp_mark_as_escaped($continuation);
                                }
                                $tag_output->attach($continuation);
                                $continuation = '';
                                if ((($just_new_line)) || (isset($BLOCK_TAGS[$potential_tag]))) {
                                    list($close_list, $list_indent) = _close_open_lists($list_indent, $list_type);
                                    if ($GLOBALS['XSS_DETECT']) {
                                        ocp_mark_as_escaped($close_list);
                                    }
                                    $tag_output->attach($close_list);
                                }
                                $status = CCP_STARTING_TAG;
                                continue 2;
                            }
                        }
                    } else {
                        if (($use_pos != 22) && ((($in_semihtml) || ($in_html)) && (($potential_tag === 'html') || ($potential_tag === 'semihtml'))) && (!$in_code_tag)) {
                            $ahc = strpos($ahead, ']');
                            if ($ahc !== false) {
                                $pos += $ahc + 1;
                                continue 2;
                            }
                        }
                    }
                }

                if ((($in_html) || ((($in_semihtml) && (!$in_code_tag)) && (($next === '<') || ($next === '>') || ($next === '"'))))) {
                    if ($next === "\n") {
                        ++$NUM_COMCODE_LINES_PARSED;
                    }

                    if (((!$comcode_dangerous_html) || ($semiparse_mode)) && ($next === '<')) {
                        $tag_match = array();
                        if (preg_match('#(/)?(\w+)#A', $comcode, $tag_match, 0, $pos) != 0) {
                            $close_pos = strpos($comcode, '>', $pos);
                            $slash_pos = mixed();
                            $slash_pos = ($close_pos === false) ? false : strrpos(substr($comcode, 0, $close_pos), '/');
                            if ($slash_pos === false || $close_pos === false || $slash_pos + 1 !== $close_pos) { // If not a self-closing tag
                                if ($tag_match[1] === '/') { // Closing
                                    if (array_peek($html_element_stack) === $tag_match[2]) {
                                        array_pop($html_element_stack);
                                    } else {
                                        $html_errors = true;
                                    }
                                } else { // Opening
                                    array_push($html_element_stack, $tag_match[2]);
                                }
                            }
                        }

                        $was_filtered = filter_html_inclusion_list_at_tag_start($comcode, $pos, $continuation, $comcode_dangerous_html, $allowed_html_seqs);
                        if ($was_filtered) {
                            continue 2;
                        }
                    }

                    if ((isset($comcode[$pos])) && ($comcode[$pos] === '!') && (substr($comcode, $pos - 1, 4) === '<!--')) { // To stop shortcut interpretation
                        $continuation .= '<!--';
                        $pos += 3;
                    } else {
                        $continuation .= $next;
                    }
                } else { // Not in HTML or in a code tag [or semihtml code tag] ($formatting_allowed will be false, so ok)
                    // Text-format possibilities
                    if ((($just_new_line) || ($just_ended)) && ($formatting_allowed) && (!$in_code_tag)) {
                        if ($continuation != '') {
                            if ($GLOBALS['XSS_DETECT']) {
                                ocp_mark_as_escaped($continuation);
                            }
                            $tag_output->attach($continuation);
                            $continuation = '';
                        }

                        // List
                        $found_list = false;
                        $old_list_indent = $list_indent;
                        $matches = array();
                        if (($pos + 2 < $len) && (is_numeric($next)) && (((is_numeric($comcode[$pos])) && ($comcode[$pos + 1] === ')') && ($comcode[$pos + 2] === ' ')) || (($comcode[$pos] === ')') && ($comcode[$pos + 1] === ' '))) && ((($list_type === '1') && ($list_indent != 0)) || (preg_match('#[^\n]*\n\d+\) #A', $comcode, $matches, 0, $pos + 1) != 0))) {
                            if (($list_indent != 0) && ($list_type != '1')) {
                                list($temp_tpl, $old_list_indent) = _close_open_lists($list_indent, $list_type);
                                if ($GLOBALS['XSS_DETECT']) {
                                    ocp_mark_as_escaped($temp_tpl);
                                }
                                $tag_output->attach($temp_tpl);
                            }
                            $list_indent = 1;
                            $found_list = true;
                            $scan_pos = $pos;
                            $list_type = '1';
                        } elseif (($pos + 2 < $len) && (ord($next) >= ord('a')) && (ord($next) <= ord('z')) && ($comcode[$pos] === ')') && ($comcode[$pos + 1] === ' ') && ((($list_type === 'a') && ($list_indent != 0)) || (preg_match('#[^\n]*\n[a-z]+\) #A', $comcode, $matches, 0, $pos + 1) != 0))) {
                            if (($list_indent != 0) && ($list_type != 'a')) {
                                list($temp_tpl, $old_list_indent) = _close_open_lists($list_indent, $list_type);
                                if ($GLOBALS['XSS_DETECT']) {
                                    ocp_mark_as_escaped($temp_tpl);
                                }
                                $tag_output->attach($temp_tpl);
                            }
                            $list_indent = 1;
                            $found_list = true;
                            $scan_pos = $pos;
                            $list_type = 'a';
                        } elseif ($next === ' ') {
                            if (($old_list_indent != 0) && ($list_type != 'ul')) {
                                list($temp_tpl, $old_list_indent) = _close_open_lists($list_indent, $list_type);
                                if ($GLOBALS['XSS_DETECT']) {
                                    ocp_mark_as_escaped($temp_tpl);
                                }
                                $tag_output->attach($temp_tpl);
                            }

                            $scan_pos = $pos - 1;
                            $list_indent = 0;
                            while ($scan_pos < $len) {
                                $scan_next = $comcode[$scan_pos];
                                if (($scan_next === '-') && ($scan_pos + 1 < $len) && ($comcode[$scan_pos + 1] === ' ')) {
                                    $found_list = true;
                                    break;
                                } else {
                                    if ($scan_next === ' ') {
                                        ++$list_indent;
                                    } else {
                                        break;
                                    }
                                }
                                ++$scan_pos;
                            }
                            if (!$found_list) {
                                $list_indent = 0;
                            } else {
                                $list_type = 'ul';
                            }
                        } // Rule?
                        else {
                            list($close_list, $list_indent) = _close_open_lists($list_indent, $list_type);
                            if ($GLOBALS['XSS_DETECT']) {
                                ocp_mark_as_escaped($close_list);
                            }
                            $tag_output->attach($close_list);
                            $old_list_indent = 0;

                            if (($next === '-') && (!$just_title)) {
                                $scan_pos = $pos;
                                $found_rule = true;
                                while ($scan_pos < $len) {
                                    $scan_next = $comcode[$scan_pos];
                                    if ($scan_next != '-') {
                                        if ($scan_next === "\n") {
                                            ++$NUM_COMCODE_LINES_PARSED;
                                            break;
                                        } else {
                                            $found_rule = false;
                                        }
                                    }
                                    ++$scan_pos;
                                }
                                if ($found_rule) {
                                    $_temp_tpl = do_template('COMCODE_TEXTCODE_LINE');
                                    $tag_output->attach($_temp_tpl);
                                    $pos = $scan_pos + 1;
                                    $just_ended = true;
                                    $none_wrap_length = 0;
                                    continue 2;
                                }
                            }
                        }

                        // List handling
                        if (($list_indent === $old_list_indent) && ($old_list_indent != 0)) {
                            $temp_tpl = '</li>';
                            if ($GLOBALS['XSS_DETECT']) {
                                ocp_mark_as_escaped($temp_tpl);
                            }
                            $tag_output->attach($temp_tpl);
                        }
                        for ($i = $list_indent; $i < $old_list_indent; ++$i) { // Close any ended
                            $temp_tpl = '</li>';
                            if ($GLOBALS['XSS_DETECT']) {
                                ocp_mark_as_escaped($temp_tpl);
                            }
                            $tag_output->attach($temp_tpl);
                            $temp_tpl = ($list_type === 'ul') ? '</ul>' : '</ol>';
                            if ($GLOBALS['XSS_DETECT']) {
                                ocp_mark_as_escaped($temp_tpl);
                            }
                            $tag_output->attach($temp_tpl);
                        }
                        if (($list_indent < $old_list_indent) && ($list_indent != 0)) { // Go down one final level, because the list tag must have been nested within an li tag (we closed open li tags recursively except for the final one)
                            $temp_tpl = '</li>';
                            if ($GLOBALS['XSS_DETECT']) {
                                ocp_mark_as_escaped($temp_tpl);
                            }
                            $tag_output->attach($temp_tpl);
                        }
                        if ($found_list) {
                            if ((($list_indent - $old_list_indent) > 1) && (!$lax)) {
                                return comcode_parse_error($preparse_mode, array('CCP_LIST_JUMPYNESS'), $pos, $comcode, $check_only);
                            }
                            for ($i = $old_list_indent; $i < $list_indent; ++$i) { // Or open any started
                                switch ($list_type) {
                                    case 'ul':
                                        if ($i < $list_indent - 1) {
                                            $temp_tpl = '<ul><li>';
                                        } else {
                                            $temp_tpl = '<ul>';
                                        }
                                        if ($GLOBALS['XSS_DETECT']) {
                                            ocp_mark_as_escaped($temp_tpl);
                                        }
                                        $tag_output->attach($temp_tpl);
                                        break;
                                    case '1':
                                        if ($i < $list_indent - 1) {
                                            $temp_tpl = '<ol type="1"><li>';
                                        } else {
                                            $temp_tpl = '<ol type="1">';
                                        }
                                        if ($GLOBALS['XSS_DETECT']) {
                                            ocp_mark_as_escaped($temp_tpl);
                                        }
                                        $tag_output->attach($temp_tpl);
                                        break;
                                    case 'a':
                                        if ($i < $list_indent - 1) {
                                            $temp_tpl = '<ol type="a"><li>';
                                        } else {
                                            $temp_tpl = '<ol type="a">';
                                        }
                                        if ($GLOBALS['XSS_DETECT']) {
                                            ocp_mark_as_escaped($temp_tpl);
                                        }
                                        $tag_output->attach($temp_tpl);
                                        break;
                                }
                            }
                            $temp_tpl = '<li>';
                            if ($GLOBALS['XSS_DETECT']) {
                                ocp_mark_as_escaped($temp_tpl);
                            }
                            $tag_output->attach($temp_tpl);
                            $just_ended = true;
                            $none_wrap_length = 0;
                            $next = '';
                            $pos = $scan_pos + 2;
                        }
                    }

                    if (($next === "\n") && ($white_space_area) && ($print_mode) && ($list_indent === 0)) { // We might need to put some queued up stuff here: when we print, we can't float thumbnails
                        $tag_output->attach($queued_tempcode);
                        $queued_tempcode = new Tempcode();
                    }
                    if (($next === "\n") && ($white_space_area) && (!$in_semihtml) && ((!$just_ended) || ($semiparse_mode))) { // Hard-new-lines
                        ++$NUM_COMCODE_LINES_PARSED;
                        $line_starting = true;
                        if ($GLOBALS['XSS_DETECT']) {
                            ocp_mark_as_escaped($continuation);
                        }
                        $tag_output->attach($continuation);
                        $continuation = '';
                        $just_new_line = true;
                        $none_wrap_length = 0;
                        if (($list_indent === 0) && (!$just_ended)) {
                            $temp_tpl = '<br />';
                            if ($GLOBALS['XSS_DETECT']) {
                                ocp_mark_as_escaped($temp_tpl);
                            }
                            $tag_output->attach($temp_tpl);
                        }
                        $just_ended = false;
                    } else {
                        $just_new_line = ($just_ended && $next === "\n"); // Only propagate new line signal if it was a real but non-hard-new-line (i.e. the above if clause did not fire)
                        if (($next === ' ') && ($white_space_area) && (!$in_semihtml)) {
                            if (($line_starting) || (($pos > 1) && ($comcode[$pos - 2] === ' '))) { // Hard spaces
                                $next = '&nbsp;';
                                ++$none_wrap_length;
                            } else {
                                $none_wrap_length = 0;
                            }
                            $continuation .= $next;
                        } elseif (($next === "\t") && ($white_space_area) && (!$in_semihtml)) {
                            if ($GLOBALS['XSS_DETECT']) {
                                ocp_mark_as_escaped($continuation);
                            }
                            $tag_output->attach($continuation);
                            $continuation = '';
                            $tab_tpl = do_template('COMCODE_TEXTCODE_TAB');
                            $_tab_tpl = $tab_tpl->evaluate();
                            $none_wrap_length += strlen($_tab_tpl);
                            $tag_output->attach($tab_tpl);
                        } else {
                            if (($next === ' ') || ($next === "\t") || ($just_ended)) {
                                $none_wrap_length = 0;
                            } else {
                                if (($wrap_pos !== null) && ($none_wrap_length >= $wrap_pos) && ((get_charset() != 'utf-8') || (preg_replace(array('#[\x09\x0A\x0D\x20-\x7E]#', '#[\xC2-\xDF][\x80-\xBF]#', '#\xE0[\xA0-\xBF][\x80-\xBF]#', '#[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}#', '#\xED[\x80-\x9F][\x80-\xBF]#', '#\xF0[\x90-\xBF][\x80-\xBF]{2}#', '#[\xF1-\xF3][\x80-\xBF]{3}#', '#\xF4[\x80-\x8F][\x80-\xBF]{2}#'), array('', '', '', '', '', '', '', ''), $continuation) === '')) && ($textual_area) && (!$in_semihtml)) {
                                    if ($GLOBALS['XSS_DETECT']) {
                                        ocp_mark_as_escaped($continuation);
                                    }
                                    $tag_output->attach($continuation);
                                    $continuation = '';
                                    $temp_tpl = '<br />';
                                    if ($GLOBALS['XSS_DETECT']) {
                                        ocp_mark_as_escaped($temp_tpl);
                                    }
                                    $tag_output->attach($temp_tpl);
                                    $none_wrap_length = 0;
                                } elseif ($textual_area) {
                                    ++$none_wrap_length;
                                }
                            }
                            $line_starting = false;
                            $just_ended = false;

                            $differented = false; // If somehow via lookahead we've changed this to HTML and thus won't use it in raw form

                            // Variable lookahead
                            if ((!$in_code_tag) && (($next === '{') && (isset($comcode[$pos])) && (($comcode[$pos] === '$') || ($comcode[$pos] === '+') || ($comcode[$pos] === '!')))) {
                                $is_basic_symbol = (substr($comcode, $pos, 4) == '$IMG') || (substr($comcode, $pos, 9) == '$BASE_URL'); // Anyone may use, and must parse even in semi-parse mode
                                if ($comcode_dangerous || $is_basic_symbol) {
                                    if ((!$in_code_tag) && ((!$semiparse_mode) || ($is_basic_symbol) || ((!$html_errors) && ($comcode[$pos] === '+')) || (in_tag_stack($tag_stack, array('url', 'img', 'flash', 'media'))))) {
                                        if ($GLOBALS['XSS_DETECT']) {
                                            ocp_mark_as_escaped($continuation);
                                        }
                                        $tag_output->attach($continuation);
                                        $continuation = '';
                                        if ($comcode[$pos] === '+') {
                                            $p_end = strpos($comcode, '{+END}', $pos); // For the end position of the whole enclosed area excluding the closing directive tag
                                            if ($p_end !== false) {
                                                $matches = array();
                                                while ($p_end < $len) {
                                                    $p_portion = substr($comcode, $pos - 1, strpos($comcode, '}', $p_end) - ($pos - 1) + 1);
                                                    if (preg_match_all('#\{\+\s*START\s*,#', $p_portion, $matches) == preg_match_all('#\{\+\s*END\s*\}#', $p_portion, $matches)) {
                                                        break;
                                                    }
                                                    $p_end = strpos($comcode, '{+END}', $p_end + 1);
                                                    if ($p_end === false) {
                                                        $p_end = $len;
                                                    }
                                                }
                                            } else {
                                                $p_end = $len;
                                            }
                                            $p_len = 1; // For the length of the opening directive
                                            while ($pos + $p_len < $len) {
                                                $p_portion = substr($comcode, $pos - 1, $p_len);
                                                if (substr_count(str_replace('{', ' { ', $p_portion), '{') === substr_count(str_replace('}', ' } ', $p_portion), '}')) { // str_replace is to workaround a Quercus bug #4494
                                                    break;
                                                }
                                                $p_len++;
                                            }
                                            $p_len--;

                                            $p_opener = substr($comcode, $pos - 1, $p_len + 1); // Opening part of directive
                                            $p_portion = substr($comcode, $pos + $p_len, $p_end - ($pos + $p_len)); // Contents of directive
                                            $p_closer = substr($comcode, $p_end, strpos($comcode, '}', $p_end) - $p_end + 1); // Closing part of directive

                                            if ($semiparse_mode) {
                                                $ret = new Tempcode();

                                                require_code('xhtml');
                                                if (!$html_errors && preg_replace('#\s#', '', xhtmlise_html($p_portion, true)) === preg_replace('#\s#', '', $p_portion)) {
                                                    $ret->attach('<tempcode params="' . escape_html($p_opener) . '">');
                                                    $p_portion_comcode = comcode_to_tempcode($p_portion, $source_member, $as_admin, $wrap_pos, $pass_id, $connection, $semiparse_mode, $preparse_mode, $in_semihtml, $structure_sweep, $check_only, $highlight_bits, $on_behalf_of_member);
                                                    $ret->attach($p_portion_comcode);
                                                    $ret->attach('</tempcode>');

                                                    $pos = $p_end + 6;
                                                } else {
                                                    // Not aligning with HTML, so cannot do it
                                                    $continuation .= '{';

                                                    $html_errors = true;
                                                }
                                            } else {
                                                require_code('tempcode_compiler');
                                                $ret = template_to_tempcode($p_opener . '{DIRECTIVE_EMBEDMENT}' . $p_closer);
                                                if (preg_match('#^\{\+\s*START\s*,\s*CASES\s*,#', substr($comcode, $pos - 1, 30)) != 0) {
                                                    $ret->singular_bind('DIRECTIVE_EMBEDMENT', make_string_tempcode($p_portion));
                                                } else {
                                                    $p_portion_comcode = comcode_to_tempcode($p_portion, $source_member, $as_admin, $wrap_pos, $pass_id, $connection, $semiparse_mode, $preparse_mode, $in_semihtml, $structure_sweep, $check_only, $highlight_bits, $on_behalf_of_member);
                                                    $ret->singular_bind('DIRECTIVE_EMBEDMENT', $p_portion_comcode);
                                                }

                                                $pos = $p_end + strlen($p_closer);
                                            }
                                        } elseif ($comcode[$pos] === '!') {
                                            $p_len = $pos;
                                            $balance = 1;
                                            while (($p_len < $len) && ($balance != 0)) {
                                                if ($comcode[$p_len] === '{') {
                                                    $balance++;
                                                } elseif ($comcode[$p_len] === '}') {
                                                    $balance--;
                                                }
                                                $p_len++;
                                            }
                                            $ret = new Tempcode();
                                            $less_pos = $pos - 1;
                                            $ret->parse_from($comcode, $less_pos, $p_len);
                                            $pos = $p_len;
                                            if (($ret->parameterless(0)) && ($pos < $len)) { // We want to take the language string ID reference as Comcode if it's a simple language string ID reference with no parameters
                                                $matches = array();
                                                if (preg_match('#\{\!([\w\:]+)(\}|=|$)#U', $comcode, $matches, 0, $less_pos) != 0) { // Hacky code to extract the language string ID
                                                    $temp_lang_string = $matches[1];
                                                    $ret = new Tempcode(); // Put into new Tempcode object to attach to, as what comcode_lang_string returns may be cached yet we may append to $ret
                                                    $ret->attach(static_evaluate_tempcode(comcode_lang_string($temp_lang_string))); // Recreate as a Comcode language string
                                                }
                                            }
                                        } else { // Probably a symbol
                                            $p_len = $pos;
                                            $balance = 1;
                                            while (($p_len < $len) && ($balance != 0)) {
                                                if ($comcode[$p_len] === '{') {
                                                    $balance++;
                                                } elseif ($comcode[$p_len] === '}') {
                                                    $balance--;
                                                }
                                                $p_len++;
                                            }
                                            $ret = new Tempcode();
                                            $less_pos = $pos - 1;
                                            $_variable_comcode = substr($comcode, $less_pos, $p_len - $less_pos);
                                            if ($in_semihtml) {
                                                $_variable_comcode = html_entity_decode($_variable_comcode, ENT_QUOTES, get_charset());
                                            }
                                            $ret->parse_from($_variable_comcode, 0, strlen($_variable_comcode));
                                            $pos = $p_len;

                                            /*if (!$in_html && !$in_semihtml) {     Actually, no, we must explicitly escape symbols in Comcode (except when there's a pre-Tempcode-pass of that Comcode / inside tag attributes / inside non-textual areas)
                                                if (strpos(substr($comcode, $less_pos, $p_len), '*') === false) {
                                                    $ret = escape_html_tempcode($ret);
                                                }
                                            }*/
                                        }
                                        $differented = true;
                                        if (($pos <= $len) || (!$lax)) {
                                            $tag_output->attach($ret);

                                            global $COMCODE_PARSE_TITLE;
                                            $comcode_parse_title_bak = $COMCODE_PARSE_TITLE;

                                            $ret->handle_symbol_preprocessing(); // In case there is a 'SET' in there that was intended for being known by PHP code

                                            $COMCODE_PARSE_TITLE = $comcode_parse_title_bak;
                                        }
                                    }
                                } else {
                                    if (($comcode[$pos] === '$') && ($pos < $len - 2) && ($comcode[$pos + 1] === ',') && (strpos($comcode, '}', $pos) !== false)) {
                                        $pos = strpos($comcode, '}', $pos) + 1;
                                        $differented = true;
                                    }
                                }
                            }

                            // Escaping of comcode tag starts lookahead
                            if (($next === '\\') && (!$in_code_tag)) { // We are changing \[ to [ with the side-effect of blocking a tag start. To get \[ literal, we need the symbols \\[... and add extra \-pairs as needed. We are only dealing with \ and [ (update: and now {) here, it's not a further extended means of escaping.
                                if (($pos != $len) && (($comcode[$pos] === '"') || ($comcode[$pos - 1] === '&') && (substr($comcode, $pos - 1, 6) === '&quot;'))) {
                                    if ($semiparse_mode) {
                                        $continuation .= '\\';
                                    }
                                    if ($comcode[$pos] === '"') {
                                        $continuation .= '"';
                                        ++$pos;
                                    } else {
                                        $continuation .= '&quot;';
                                        $pos += 6;
                                    }
                                    $differented = true;
                                } elseif (($pos != $len) && ($comcode[$pos] === '[')) {
                                    if ($semiparse_mode) {
                                        $continuation .= '\\';
                                    }
                                    $continuation .= '[';
                                    ++$pos;
                                    $differented = true;
                                } elseif (($pos != $len) && ($comcode[$pos] === '{')) {
                                    if ($semiparse_mode) {
                                        $continuation .= '\\';
                                    }
                                    $continuation .= '{';
                                    ++$pos;
                                    $differented = true;
                                } elseif (($pos === $len) || ($comcode[$pos] === '\\')) {
                                    if ($semiparse_mode) {
                                        $continuation .= '\\';
                                    }
                                    $continuation .= '\\';
                                    ++$pos;
                                    $differented = true;
                                }
                            }

                            $not_white_space = ($next != ' ') && ($next != "\n") && ($next != "\t");

                            if (!$differented) {
                                if ((($textual_area) || ($in_semihtml)) && ($not_white_space) && (isset($emoticon_start_chars[$next]))) {
                                    // Emoticon lookahead
                                    foreach ($emoticons as $emoticon => $imgcode) {
                                        if ($in_semihtml) {
                                            $emoticon = ' ' . $emoticon . ' ';
                                        }

                                        if ($next === $emoticon[0]) { // optimisation
                                            if (substr($comcode, $pos - 1, strlen($emoticon)) === $emoticon) {
                                                if ($GLOBALS['XSS_DETECT']) {
                                                    ocp_mark_as_escaped($continuation);
                                                }
                                                $tag_output->attach($continuation);
                                                $continuation = '';
                                                $pos += strlen($emoticon) - 1;
                                                $differented = true;
                                                $tag_output->attach(do_emoticon($imgcode));
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                            if (($not_white_space) && (!$in_code_tag) && (!$differented)) {
                                // Wiki pages
                                if (($pos < $len) && ($next === '[') && ($pos + 1 < $len) && ($comcode[$pos] === '[') && (!$semiparse_mode) && (addon_installed('wiki'))) {
                                    $matches = array();
                                    if (preg_match('#\[([^\[\]]*)\]\]#A', $comcode, $matches, 0, $pos) != 0) {
                                        $wiki_page_name = $matches[1];
                                        if ($GLOBALS['XSS_DETECT']) {
                                            ocp_mark_as_escaped($continuation);
                                        }
                                        $tag_output->attach($continuation);
                                        $continuation = '';
                                        $hash_pos = strpos($wiki_page_name, '#');
                                        if ($hash_pos !== false) {
                                            $jump_to = substr($wiki_page_name, $hash_pos + 1);
                                            $wiki_page_name = substr($wiki_page_name, 0, $hash_pos);
                                        } else {
                                            $jump_to = '';
                                        }
                                        $wiki_page_url = build_url(array('page' => 'wiki', 'type' => 'browse', 'find' => $wiki_page_name), get_module_zone('wiki'));
                                        if ($jump_to != '') {
                                            $wiki_page_url->attach('#' . $jump_to);
                                        }
                                        $tag_output->attach(do_template('COMCODE_WIKI_LINK', array('_GUID' => 'ebcd7ba5290c5b2513272a53b4d666e5', 'URL' => $wiki_page_url, 'TEXT' => $wiki_page_name)));
                                        $pos += strlen($matches[1]) + 3;
                                        $differented = true;
                                    }
                                }

                                // Usernames
                                if (($pos < $len) && ((($next === '{') && ($pos + 1 < $len) && ($comcode[$pos] === '{')) || (($next === '@') && (($pos <= 1) || (trim($comcode[$pos - 2]) === '')) && (get_forum_type() === 'cns'))) && (!$in_code_tag) && (!$semiparse_mode)) {
                                    $matches = array();
                                    $explicit_username = ($next === '{');
                                    if (preg_match($explicit_username ? '#\{([^"{}&\'\$<>]+)\}\}#A' : '#(\w[^\n]*)#A', $comcode, $matches, 0, $pos) != 0) {
                                        $username = $matches[1];

                                        if ($explicit_username) {
                                            if ($username[0] === '?') {
                                                $username_info = true;
                                                $username = substr($username, 1);
                                            } else {
                                                $username_info = false;
                                            }
                                        } else {
                                            $username_info = true;
                                        }

                                        if (!$explicit_username) {
                                            $parts = preg_split('#([^\w])#', $username, -1, PREG_SPLIT_DELIM_CAPTURE);
                                            $username_part = '';
                                            $username_sql = '1=0';
                                            for ($i = 0; $i < count($parts); $i += 2) {
                                                if ($i != 0) {
                                                    $delim = $parts[$i - 1];
                                                    for ($j = 0; $j < strlen($delim); $j++) {
                                                        $username_part .= $delim[$j];
                                                        $username_sql .= ' OR ' . db_string_equal_to('m_username', $username_part);
                                                    }
                                                }
                                                $username_part .= $parts[$i];
                                                $username_sql .= ' OR ' . db_string_equal_to('m_username', $username_part);
                                            }

                                            $this_member_id = mixed();
                                            $results = $GLOBALS['FORUM_DB']->query('SELECT id,m_username FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_members WHERE ' . $username_sql . ' ORDER BY ' . db_function('LENGTH', array('m_username')) . ' DESC', 1);
                                            if (isset($results[0])) {
                                                $this_member_id = $results[0]['id'];
                                                $username = $results[0]['m_username'];
                                            }
                                        } else {
                                            $this_member_id = $GLOBALS['FORUM_DRIVER']->get_member_from_username($username);
                                        }

                                        if (!is_null($this_member_id)) {
                                            if ($GLOBALS['XSS_DETECT']) {
                                                ocp_mark_as_escaped($continuation);
                                            }
                                            $tag_output->attach($continuation);
                                            $continuation = '';

                                            if (!is_guest($this_member_id)) {
                                                $member_url = $GLOBALS['FORUM_DRIVER']->member_profile_url($this_member_id, false, true);
                                                if ((get_forum_type() === 'cns') && ($username_info)) {
                                                    require_lang('cns');
                                                    require_code('cns_members2');
                                                    //$details = render_member_box($this_member_id);
                                                    $comcode_member_link = do_template('COMCODE_MEMBER_LINK', array(
                                                        '_GUID' => 'd8f4f4ac70bd52b3ef9ee74ae9c5e085',
                                                        //'DETAILS' => $details,    Slow for large amounts of Comcode, let it be via AJAX (or Tempcode symbol if still wanted)
                                                        'MEMBER_ID' => strval($this_member_id),
                                                        'USERNAME' => $username,
                                                        'MEMBER_URL' => $member_url,
                                                    ));
                                                    $tag_output->attach($comcode_member_link);

                                                    global $MEMBER_MENTIONS_IN_COMCODE;
                                                    $MEMBER_MENTIONS_IN_COMCODE[] = $this_member_id;
                                                } else {
                                                    $tag_output->attach(hyperlink($member_url, $username, false, true));
                                                }
                                            } else {
                                                $tag_output->attach(escape_html($username));
                                            }

                                            if ($explicit_username) {
                                                $pos += strlen($matches[1]) + 3;
                                            } else {
                                                $pos += strlen($username);
                                            }
                                            $differented = true;
                                        }
                                    }
                                }
                            }

                            // Shortcut lookahead
                            if (!$differented && !$in_code_tag) {
                                if (($in_semihtml) && ($next === '-') && (substr($comcode, $pos - 1, 3) === '-->')) { // To stop shortcut interpretation
                                    $continuation .= '-->';
                                    $pos += 2;
                                    break;
                                }
                                foreach ($shortcuts as $code => $replacement) {
                                    if (($next === $code[0]) && (isset($comcode[$pos])) && ($comcode[$pos] === $code[1]) && (substr($comcode, $pos - 1, strlen($code)) === $code)) {

                                        $passes = true;

                                        if (($code == '--') || ($code == '<--') || ($code == '-->')) {
                                            if ((strpos($comcode, '<!--') !== false)/* || (strpos($comcode, '-->') !== false)*/ || (strpos($comcode, '&lt;!--') !== false)/* || (strpos($comcode, '--&gt;') !== false)*/) {
                                                $passes = false;
                                            }
                                        }

                                        if ($passes) {
                                            if ($GLOBALS['XSS_DETECT']) {
                                                ocp_mark_as_escaped($continuation);
                                            }
                                            $tag_output->attach($continuation);
                                            $continuation = '';
                                            $pos += strlen($code) - 1;
                                            $differented = true;
                                            if ($GLOBALS['XSS_DETECT']) {
                                                ocp_mark_as_escaped($replacement);
                                            }
                                            $tag_output->attach($replacement);
                                            break;
                                        }
                                    }
                                }
                            }

                            // Table syntax
                            if (!$differented && !$in_code_tag) {
                                if (($pos < $len) && ($next == '{') && ($comcode[$pos] === '|')) {
                                    $end_tbl = strpos($comcode, "\n" . '|}', $pos);
                                    if ($end_tbl !== false) {
                                        $end_fst_line_pos = strpos($comcode, "\n", $pos);
                                        $caption = substr($comcode, $pos + 2, max($end_fst_line_pos - $pos - 2, 0));
                                        $pos += strlen($caption) + 1;

                                        $rows = preg_split('#(\|-|\|\})#Um', substr($comcode, $pos, $end_tbl - $pos));
                                        if (preg_match('#(^|\s)floats($|\s)#', $caption) != 0) {
                                            // Fake table...

                                            $caption = preg_replace('#(^|\s)floats($|\s)#', '', $caption);

                                            $ratios = array();
                                            $ratios_matches = array();
                                            if (preg_match('#(^|\s)([\d\.]+%(:[\d\.]+%)*)($|\s)#', $caption, $ratios_matches) != 0) {
                                                $ratios = explode(':', $ratios_matches[2]);
                                                $caption = str_replace($ratios_matches[0], '', $caption);
                                            }

                                            foreach ($rows as $h => $row) {
                                                $cells = preg_split('/(\n\!| \!\!|\n\|| \|\|)/', $row, -1, PREG_SPLIT_DELIM_CAPTURE);
                                                array_shift($cells); // First one is non-existent empty
                                                $spec = true;

                                                $num_cells_in_row = count($cells) / 2;

                                                $inter_padding = 3.0;

                                                $total_box_width = (100.0 - $inter_padding * ($num_cells_in_row - 1));

                                                // Find which to float
                                                $to_float = null;
                                                foreach ($cells as $i => $cell) {
                                                    if (!$spec) {
                                                        if ((strpos($cell, '!') !== false) || (is_null($to_float))) {
                                                            $to_float = $i;
                                                        }
                                                    }
                                                    $spec = !$spec;
                                                }

                                                $tag_output->attach(do_template('COMCODE_FAKE_TABLE_WRAP_START'));

                                                // Do the floated one (if float based)
                                                $i_dir_1 = (($to_float === 1) ? 'left' : 'right');
                                                $i_dir_2 = (($to_float != 1) ? 'left' : 'right');
                                                if ($num_cells_in_row === 1) {
                                                    $padding_amount = '0';
                                                } else {
                                                    $padding_amount = float_to_raw_string($inter_padding, 2);
                                                }
                                                if (preg_match('#(^|\s)wide($|\s)#', $caption) != 0) {
                                                    // Float based...

                                                    $cell_i = ($to_float === 1) ? 0 : (count($cells) - 1);
                                                    if (array_key_exists($cell_i, $ratios)) {
                                                        $width = $ratios[$cell_i];
                                                    } else {
                                                        $width = float_to_raw_string($total_box_width / $num_cells_in_row, 2) . '%';
                                                    }

                                                    $tag_output->attach(do_template('COMCODE_FAKE_TABLE_WIDE_START_CELL', array(
                                                        '_GUID' => 'ced8c3a142f74296a464b085ba6891c9',
                                                        'WIDTH' => $width,
                                                        'FLOAT' => $i_dir_1,
                                                        'PADDING' => ($to_float === 1) ? '' : '-left',
                                                        'PADDING_AMOUNT' => $padding_amount,
                                                    )));
                                                } else {
                                                    // Inline-block based...

                                                    $tag_output->attach(do_template('COMCODE_FAKE_TABLE_START_CELL', array(
                                                        '_GUID' => '90be72fcbb6b9d8a312da0bee5b86cb3',
                                                        'WIDTH' => array_key_exists($to_float, $ratios) ? $ratios[$to_float] : '',
                                                        'FLOAT' => $i_dir_1,
                                                        'PADDING' => ($to_float === 1) ? '' : '-left',
                                                        'PADDING_AMOUNT' => $padding_amount,
                                                    )));
                                                }
                                                $tag_output->attach(comcode_to_tempcode(isset($cells[$to_float]) ? trim($cells[$to_float]) : '', $source_member, $as_admin, $wrap_pos, $pass_id, $connection, $semiparse_mode, $preparse_mode, $in_semihtml, $structure_sweep, $check_only, $highlight_bits, $on_behalf_of_member));
                                                $tag_output->attach(do_template('COMCODE_FAKE_TABLE_END_CELL'));

                                                // Do non-floated ones
                                                $cell_i = 0;
                                                foreach ($cells as $i => $cell) {
                                                    if ($i % 2 === 1) {
                                                        if ($i != $to_float) {
                                                            $padding_amount = float_to_raw_string($inter_padding, 2);

                                                            if (array_key_exists($cell_i, $ratios)) {
                                                                $width = $ratios[$cell_i];
                                                            } else {
                                                                $width = float_to_raw_string($total_box_width / $num_cells_in_row, 2) . '%';
                                                            }

                                                            if (preg_match('#(^|\s)wide($|\s)#', $caption) != 0) {
                                                                // Float based...

                                                                $tag_output->attach(do_template('COMCODE_FAKE_TABLE_WIDE_CELL', array(
                                                                    '_GUID' => '9bac42a1b62c5c9a2f758639fcb3bb2f',
                                                                    'WIDTH' => $width,
                                                                    'FLOAT' => $i_dir_1,
                                                                    'PADDING' => (($to_float === 1) || ($cell_i != 0)) ? '-left' : '',
                                                                    'PADDING_AMOUNT' => $padding_amount,
                                                                )));
                                                            } else {
                                                                // Inline-block based...

                                                                $tag_output->attach(do_template('COMCODE_FAKE_TABLE_CELL', array(
                                                                    '_GUID' => '0f15f9d5554635ed7ac154c9dc5c72b8',
                                                                    'WIDTH' => array_key_exists($cell_i, $ratios) ? $ratios[$cell_i] : '',
                                                                    'FLOAT' => $i_dir_1,
                                                                    'PADDING' => (($to_float === 1) || ($cell_i != 0)) ? '-left' : '',
                                                                    'PADDING_AMOUNT' => $padding_amount,
                                                                )));
                                                            }

                                                            $tag_output->attach(comcode_to_tempcode(trim($cell), $source_member, $as_admin, $wrap_pos, $pass_id, $connection, $semiparse_mode, $preparse_mode, $in_semihtml, $structure_sweep, $check_only, $highlight_bits, $on_behalf_of_member));

                                                            $tag_output->attach(do_template('COMCODE_FAKE_TABLE_END_CELL'));
                                                        }

                                                        $cell_i++;
                                                    }
                                                }

                                                $tag_output->attach(do_template('COMCODE_FAKE_TABLE_WRAP_END'));
                                            }
                                        } else {
                                            // Real table...

                                            $ratios = array();
                                            $ratios_matches = array();
                                            if (preg_match('#(^|\s)([\d\.]+%(:[\d\.]+%)*)($|\s)#', $caption, $ratios_matches) != 0) {
                                                $ratios = explode(':', $ratios_matches[2]);
                                                $caption = str_replace($ratios_matches[0], '', $caption);
                                            }

                                            $wide = false;
                                            $column_sizes = array();
                                            $caption_bits = explode(' ', $caption);
                                            $caption = '';
                                            foreach ($caption_bits as $caption_bit) {
                                                if (($caption === '') && ($caption_bit === 'wide')) {
                                                    $wide = true;
                                                } elseif (($caption === '') && (preg_match('#^\d+(em|px)$#', $caption_bit) != 0)) {
                                                    $column_sizes[] = $caption_bit;
                                                } else {
                                                    if ($caption != '') {
                                                        $caption .= ' ';
                                                    }
                                                    $caption .= $caption_bit;
                                                }
                                            }

                                            $tag_output->attach(do_template('COMCODE_REAL_TABLE_START', array(
                                                '_GUID' => '9fca9672b9d069a0c8a40ebc6e88602b',
                                                'SUMMARY' => $caption,
                                                'WIDE' => $wide,
                                                'COLUMN_SIZES' => $column_sizes,
                                                'COLUMNED_TABLE' => (strpos($rows[0], '|') === false),
                                            )));
                                            $finished_thead = false;
                                            foreach ($rows as $table_row) {
                                                $cells = preg_split('/(\n\!| \!\!|\n\|| \|\|)/', $table_row, -1, PREG_SPLIT_DELIM_CAPTURE);
                                                array_shift($cells); // First one is non-existent empty
                                                $spec = true;
                                                $c_type = '';
                                                $cell_i = 0;
                                                $finished_thead_prior = $finished_thead;
                                                foreach ($cells as $i => $cell) {
                                                    if ($spec) {
                                                        if (strpos($cell, '!') === false) {
                                                            $finished_thead = true;
                                                        }
                                                    }
                                                    $spec = !$spec;
                                                }

                                                $tag_output->attach(do_template('COMCODE_REAL_TABLE_ROW_START', array('_GUID' => '98f20d57692f0bded555a0acb7d55024', 'START_HEAD' => !$finished_thead, 'START_BODY' => (!$finished_thead_prior) && ($finished_thead))));

                                                $spec = true;
                                                foreach ($cells as $i => $cell) {
                                                    if ($spec) {
                                                        $c_type = (strpos($cell, '!') !== false) ? 'th' : 'td';
                                                    } else {
                                                        $_mid = comcode_to_tempcode(trim($cell), $source_member, $as_admin, $wrap_pos, $pass_id, $connection, $semiparse_mode, $preparse_mode, $in_semihtml, $structure_sweep, $check_only, $highlight_bits, $on_behalf_of_member);

                                                        $tag_output->attach(do_template('COMCODE_REAL_TABLE_CELL', array(
                                                            '_GUID' => '6640df8b503f65e3d36f595b0acf7600',
                                                            'WIDTH' => array_key_exists($cell_i, $ratios) ? $ratios[$cell_i] : '',
                                                            'C_TYPE' => $c_type,
                                                            'MID' => $_mid,
                                                            'PADDING' => ($cell_i === 0) ? '' : '-left',
                                                            'PADDING_AMOUNT' => (count($cells) === 2) ? '0' : float_to_raw_string(5.0 / (floatval(count($cells) - 2) / 2.0), 2),
                                                        )));

                                                        $cell_i++;
                                                    }
                                                    $spec = !$spec;
                                                }

                                                $tag_output->attach(do_template('COMCODE_REAL_TABLE_ROW_END', array('_GUID' => 'c3abce83ec5bdbc10d0e80f646c91c23', 'END_HEAD' => !$finished_thead)));
                                            }

                                            $tag_output->attach(do_template('COMCODE_REAL_TABLE_END', array('_GUID' => '6a843e072e92b60cc950f69576231fe1', 'END_BODY' => $finished_thead)));
                                        }

                                        $pos = $end_tbl + 3;
                                        $differented = true;
                                    }
                                }

                                // Advertising
                                if ((!$semiparse_mode) && (!$in_code_tag) && ($has_banners) && (($b_all) || (!has_privilege($source_member, 'banner_free')))) {
                                    // Pick up correctly, including permission filtering
                                    if ($ADVERTISING_BANNERS_CACHE === null) {
                                        $ADVERTISING_BANNERS_CACHE = array();

                                        require_code('banners');
                                        $banner_sql = banner_select_sql(null, true);
                                        $banner_sql .= ' AND t_comcode_inline=1 AND ' . db_string_not_equal_to('b_title_text', '');
                                        $rows = $GLOBALS['SITE_DB']->query($banner_sql, null, null, true);
                                        if (!is_null($rows)) {
                                            // Filter out what we don't have permission for
                                            if (get_option('use_banner_permissions', true) === '1') {
                                                require_code('permissions');
                                                $groups = _get_where_clause_groups($source_member);
                                                if (!is_null($groups)) {
                                                    $perhaps = collapse_1d_complexity('category_name', $GLOBALS['SITE_DB']->query('SELECT DISTINCT category_name FROM ' . get_table_prefix() . 'group_category_access WHERE ' . db_string_equal_to('module_the_name', 'banners') . ' AND (' . $groups . ')', null, null, false, true));
                                                    $new_rows = array();
                                                    foreach ($rows as $row) {
                                                        if (in_array($row['name'], $perhaps)) {
                                                            $new_rows[] = $row;
                                                        }
                                                    }
                                                    $rows = $new_rows;
                                                }
                                            }

                                            foreach ($rows as $row) {
                                                $trigger_text = $row['b_title_text'];
                                                foreach (explode(',', $trigger_text) as $t) {
                                                    if (trim($t) != '') {
                                                        $ADVERTISING_BANNERS_CACHE[trim($t)] = $row;
                                                    }
                                                }
                                            }
                                        }
                                    }

                                    // Apply
                                    if ($ADVERTISING_BANNERS_CACHE !== null) {
                                        foreach ($ADVERTISING_BANNERS_CACHE as $ad_trigger => $ad_bits) {
                                            if (strtolower($next) === strtolower($ad_trigger[0])) { // optimisation
                                                if (strtolower(substr($comcode, $pos - 1, strlen($ad_trigger))) === strtolower($ad_trigger)) {
                                                    $just_banner_row = db_map_restrict($ad_bits, array('name', 'caption'));

                                                    if ($GLOBALS['XSS_DETECT']) {
                                                        ocp_mark_as_escaped($continuation);
                                                    }
                                                    $tag_output->attach($continuation);
                                                    $continuation = '';
                                                    $differented = true;
                                                    $ad_text = show_banner($ad_bits['name'], $ad_bits['b_title_text'], get_translated_tempcode('banners', $just_banner_row, 'caption'), $ad_bits['b_direct_code'], $ad_bits['img_url'], '', $ad_bits['site_url'], $ad_bits['b_type'], $ad_bits['submitter']);
                                                    $embed_output = do_template('COMCODE_TOOLTIP', array('_GUID' => 'a9f4793dc0c1a92cd7d08ae1b87c2308', 'URL' => (url_is_local($ad_bits['site_url']) && ($ad_bits['site_url'] != '')) ? (get_custom_base_url() . '/' . $ad_bits['site_url']) : $ad_bits['site_url'], 'TOOLTIP' => $ad_text, 'CONTENT' => substr($comcode, $pos - 1, strlen($ad_trigger))));
                                                    $pos += strlen($ad_trigger) - 1;
                                                    $tag_output->attach($embed_output);
                                                }
                                            }
                                        }
                                    }
                                }

                                // Search highlighting lookahead
                                if (($highlight_bits !== null) && ($textual_area)) {
                                    foreach ($highlight_bits as $highlight_bit) {
                                        if (strtolower($next) === strtolower($highlight_bit[0])) { // optimisation
                                            if (strtolower(substr($comcode, $pos - 1, strlen($highlight_bit))) === strtolower($highlight_bit)) {
                                                if ($GLOBALS['XSS_DETECT']) {
                                                    ocp_mark_as_escaped($continuation);
                                                }
                                                $tag_output->attach($continuation);
                                                $continuation = '';
                                                $differented = true;
                                                $embed_output = _do_tags_comcode('highlight', array(), escape_html(substr($comcode, $pos - 1, strlen($highlight_bit))), $comcode_dangerous, $pass_id, $pos, $source_member, $as_admin, $connection, $comcode, $structure_sweep, $semiparse_mode, $highlight_bits, null, false, false, $html_errors);
                                                $pos += strlen($highlight_bit) - 1;
                                                $tag_output->attach($embed_output);
                                                break;
                                            }
                                        }
                                    }
                                }
                            }

                            // Link lookahead
                            $apparent_embedded_hyperlink =
                                ($next === 'h') &&
                                (
                                    (substr($comcode, $pos - 1, strlen('http://')) === 'http://') ||
                                    (substr($comcode, $pos - 1, strlen('https://')) === 'https://') ||
                                    (substr($comcode, $pos - 1, strlen('ftp://')) === 'ftp://')
                                ) &&
                                (!$in_code_tag) &&
                                ($not_white_space) &&
                                (!$differented);
                            $in_html_tag = false;
                            if ($apparent_embedded_hyperlink) {
                                if ($in_semihtml) {
                                    // Make sure not within an HTML tag, or the written source of one
                                    $until_now = substr($comcode, 0, $pos - 1);
                                    $a = strrpos($until_now, '<');
                                    $b = strrpos($until_now, '>');
                                    $in_html_tag = ($a !== false) && (($b === false) || ($a > $b));
                                } else {
                                    // Make sure not within the written source of an HTML tag
                                    $until_now = html_entity_decode(substr($comcode, 0, $pos - 1), ENT_QUOTES, get_charset());
                                    $a = strrpos($until_now, '<');
                                    $b = strrpos($until_now, '>');
                                    $in_html_tag = ($a !== false) && (($b === false) || ($a > $b));
                                }
                            }
                            if (
                                $apparent_embedded_hyperlink &&
                                (!$in_html_tag) &&
                                (
                                    ($textual_area) ||
                                    (
                                        ($in_semihtml) &&
                                        ($tag_stack[count($tag_stack) - 1][0] === 'semihtml'/*Only just entered semihtml, we're not inside an unsafe nested Comcode context*/) &&
                                        (strpos($comcode, '<a') === false/*If links are explicit we don't want to detect links*/)
                                    )
                                )
                            ) {
                                // Find the full link portion in the upcoming Comcode
                                $link_end_pos = strlen($comcode);
                                foreach ($link_terminator_strs as $link_terminator_str) {
                                    $link_end_pos_x = strpos($comcode, $link_terminator_str, $pos - 1);
                                    if (($link_end_pos_x !== false) && ($link_end_pos_x < $link_end_pos)) {
                                        $link_end_pos = $link_end_pos_x;
                                    }
                                }
                                $auto_link = substr($comcode, $pos - 1, $link_end_pos - $pos + 1);

                                // Strip down the link, for security reasons
                                $auto_link = preg_replace('#([?&])(keep|for)_session=\w*#', '${1}', $auto_link);

                                if (substr($auto_link, -3) != '://') { // If it's not just a hanging protocol
                                    if (substr($auto_link, -1) === '.') { // Strip trailing dot (dots may be within, but not at the end)
                                        $auto_link = substr($auto_link, 0, strlen($auto_link) - 1);
                                        $link_end_pos--;
                                    }
                                    if (substr($auto_link, -1) === ',') { // Strip trailing commas (commas may be within, but not at the end)
                                        $auto_link = substr($auto_link, 0, strlen($auto_link) - 1);
                                        $link_end_pos--;
                                    }

                                    // Find a media renderer for this link
                                    $embed_output = mixed();
                                    if ($list_indent > 0) {
                                        $embed_output = _do_tags_comcode('url', array(), make_string_tempcode($auto_link), $comcode_dangerous, $pass_id, $pos, $source_member, $as_admin, $connection, $comcode, $structure_sweep, $semiparse_mode, $highlight_bits, null, false, false, $html_errors);
                                    } else {
                                        if (!$check_only) {
                                            $link_handlers = find_all_hooks('systems', 'comcode_link_handlers');
                                            foreach (array_keys($link_handlers) as $link_handler) {
                                                require_code('hooks/systems/comcode_link_handlers/' . $link_handler);
                                                $link_handler_ob = object_factory('Hook_comcode_link_handler_' . $link_handler, true);
                                                if (!is_null($link_handler_ob)) {
                                                    $embed_output = $link_handler_ob->bind($auto_link, $comcode_dangerous, $pass_id, $pos, $source_member, $as_admin, $connection, $comcode, $structure_sweep, $semiparse_mode, $highlight_bits);
                                                }
                                            }
                                        }
                                    }

                                    // If it was successfully rendered as media, put this into the output stream rather than the written link
                                    if (!is_null($embed_output)) {
                                        if ($GLOBALS['XSS_DETECT']) {
                                            ocp_mark_as_escaped($continuation);
                                        }
                                        $tag_output->attach($continuation);
                                        $continuation = '';
                                        if (!$semiparse_mode) {
                                            $tag_output->attach($embed_output);
                                        } else {
                                            $tag_output->attach($in_semihtml ? $auto_link : escape_html($auto_link));
                                        }
                                        $pos += $link_end_pos - $pos;
                                        $differented = true;
                                    }
                                }
                            }

                            // Output next character but with a lot of special HTML entity consideration
                            if (!$differented) {
                                if (($stupidity_mode != '') && ($textual_area)) {
                                    if (($stupidity_mode === 'leet') && (mt_rand(0, 1) === 1)) {
                                        if (array_key_exists(strtoupper($next), $LEET_FILTER)) {
                                            $next = $LEET_FILTER[strtoupper($next)];
                                        }
                                    } elseif (($stupidity_mode === 'bork') && (mt_rand(0, 60) === 1)) {
                                        $next .= '-bork-bork-bork-';
                                    }
                                }
                                if ((!$in_separate_parse_section) && ((!$in_semihtml) || ((!$comcode_dangerous_html)/*If we don't support HTML and we haven't done the all_semihtml pre-filter at the top*/ && (!$is_all_semihtml)))) { // Display char. We try and support entities
                                    if ($next === '&') {
                                        $matches = array();
                                        $entity = preg_match('#(\#)?([\w]*);#iA', $comcode, $matches, 0, $pos) != 0; // If it is a SAFE entity, use it

                                        if (($entity) && (!$in_code_tag)) {
                                            if (($matches[1] === '') && (($in_semihtml) || (isset($ALLOWED_COMCODE_ENTITIES[$matches[2]])))) {
                                                // Explicitly white-listed
                                                $pos += strlen($matches[2]) + 1;
                                                $continuation .= '&' . $matches[2] . ';';
                                            } elseif ((is_numeric($matches[2])) && ($matches[1] === '#')) {
                                                // Implicitly white-listed
                                                $matched_entity = intval(base_convert($matches[2], 16, 10));
                                                if (($matched_entity < 127) && (array_key_exists(chr($matched_entity), $POTENTIAL_JS_NAUGHTY_ARRAY))) {
                                                    $continuation .= '&amp;';
                                                } else {
                                                    $pos += strlen($matches[2]) + 2;
                                                    $continuation .= '&#' . $matches[2] . ';';
                                                }
                                            } else {
                                                // Security
                                                $continuation .= '&amp;';
                                            }
                                        } else {
                                            // Security
                                            $continuation .= ($in_code_tag && $in_semihtml/*Will be filtered later so don't apply security now*/) ? $next : '&amp;';
                                        }
                                    } else {
                                        if (($next == '<') && (!$in_separate_parse_section) && ($in_semihtml)) {
                                            $was_filtered = filter_html_inclusion_list_at_tag_start($comcode, $pos, $continuation, $comcode_dangerous_html, $allowed_html_seqs);
                                            if ($was_filtered) {
                                                continue 2;
                                            }
                                        }

                                        // Escaping only for character preservation
                                        $continuation .= (isset($html_escape_1_strrep_inv[$next]) && !($in_code_tag && $in_semihtml)) ? escape_html($next) : $next;
                                    }
                                } else {
                                    if (($next == '<') && (!$in_separate_parse_section) && ($in_semihtml)) {
                                        $was_filtered = filter_html_inclusion_list_at_tag_start($comcode, $pos, $continuation, $comcode_dangerous_html, $allowed_html_seqs);
                                        if ($was_filtered) {
                                            continue 2;
                                        }
                                    }

                                    // Simple flow through
                                    $continuation .= $next;
                                }
                            }
                        }
                    }
                }
                break;

            case CCP_IN_TAG_NAME:
                if ($next === '=') {
                    $status = CCP_IN_TAG_BETWEEN_ATTRIBUTE_NAME_VALUE_RIGHT;
                    $current_attribute_name = 'param';
                } elseif ((!$close) && (trim($next) === '')) {
                    $status = CCP_IN_TAG_BETWEEN_ATTRIBUTES;
                } elseif ($next === '[') {
                    if (!$lax) {
                        return comcode_parse_error($preparse_mode, array('CCP_TAG_OPEN_ANOMALY'), $pos, $comcode, $check_only);
                    }

                    $next = ']';
                    $pos--;
                }
                if ($next === ']') {
                    if ($close) {
                        if ($formatting_allowed) {
                            list($close_list, $list_indent) = _close_open_lists($list_indent, $list_type);
                            if ($GLOBALS['XSS_DETECT']) {
                                ocp_mark_as_escaped($close_list);
                            }
                            $tag_output->attach($close_list);
                        }

                        if (count($tag_stack) === 0) {
                            if ($current_tag === 'semihtml') { // Ignore, as people in WYSIWYG often incorrectly try and nest semihtml tags
                                $status = CCP_NO_MANS_LAND;
                                break;
                            }

                            if ($lax) {
                                $status = CCP_NO_MANS_LAND;
                                break;
                            }
                            return comcode_parse_error($preparse_mode, array('CCP_NO_CLOSE', $current_tag), strrpos(substr($comcode, 0, $pos), '['), $comcode, $check_only);
                        }
                        $has_it = false;
                        foreach (array_reverse($tag_stack) as $t) {
                            if ($t[0] === $current_tag) {
                                $has_it = true;
                                break;
                            }
                            if (($in_semihtml) && (($current_tag === 'html') || ($current_tag === 'semihtml'))) {// Only search one level for this
                                break;
                            }
                        }
                        if ($has_it) {
                            $_last = array_pop($tag_stack);
                            if ($_last[0] != $current_tag) {
                                if (!$lax) {
                                    return comcode_parse_error($preparse_mode, array('CCP_NO_CLOSE_MATCH', $current_tag, $_last[0]), $pos, $comcode, $check_only);
                                }
                                do {
                                    $embed_output = _do_tags_comcode($_last[0], $_last[1], $tag_output, $comcode_dangerous, $pass_id, $pos, $source_member, $as_admin, $connection, $comcode, $structure_sweep, $semiparse_mode, null, null, $in_semihtml, $is_all_semihtml, $html_errors);
                                    $in_code_tag = false;
                                    $white_space_area = $_last[3];
                                    $in_separate_parse_section = $_last[4];
                                    $formatting_allowed = $_last[5];
                                    $textual_area = $_last[6];
                                    $tag_output = $_last[2];
                                    $tag_output->attach($embed_output);
                                    $comcode_dangerous = $_last[7];
                                    $comcode_dangerous_html = $_last[8];

                                    if (count($tag_stack) === 0) { // Hmm, it was never open. So let's pretend this tag close never happened
                                        $status = CCP_NO_MANS_LAND;
                                        break 2;
                                    }
                                    $_last = array_pop($tag_stack);
                                } while ($_last[0] != $current_tag);
                            }
                        } else {
                            $extraneous_semihtml = ((!$is_all_semihtml) && (!$in_semihtml)) || (($current_tag != 'html') && ($current_tag != 'semihtml'));
                            if ((!$lax) && ($extraneous_semihtml)) {
                                $_last = array_pop($tag_stack);
                                return comcode_parse_error($preparse_mode, array('CCP_NO_CLOSE_MATCH', $current_tag, $_last[0]), $pos, $comcode, $check_only);
                            }
                            $status = CCP_NO_MANS_LAND;
                            break;
                        }

                        // Do the comcode for this tag
                        if ($in_semihtml) { // We need to perform some work to clean up the Comcode tag's attributes, as they generally do not support Comcode/HTML themselves (are plain text)
                            foreach ($_last[1] as $index => $conv) {
                                $_last[1][$index] = @html_entity_decode(str_replace('<br />', "\n", $conv), ENT_QUOTES, get_charset());
                            }
                        }

                        if (!$check_only) {
                            $_structure_sweep = false;
                            if ($structure_sweep) {
                                $_structure_sweep = !in_tag_stack($tag_stack, array('title'));
                            }
                            $embed_output = _do_tags_comcode($_last[0], $_last[1], $tag_output, $comcode_dangerous, $pass_id, $pos, $source_member, $as_admin, $connection, $comcode, $_structure_sweep, $semiparse_mode, $highlight_bits, null, $in_semihtml, $is_all_semihtml, $html_errors);
                        } else {
                            $embed_output = new Tempcode();
                        }

                        $in_code_tag = false;
                        $white_space_area = $_last[3];
                        $in_separate_parse_section = $_last[4];
                        $formatting_allowed = $_last[5];
                        $textual_area = $_last[6];
                        $tag_output = $_last[2];
                        $comcode_dangerous = $_last[7];
                        $comcode_dangerous_html = $_last[8];
                        $tag_output->attach($embed_output);
                        $just_ended = isset($BLOCK_TAGS[$current_tag]);

                        if ($current_tag === 'title') {
                            if ((strlen($comcode) > $pos + 1) && ($comcode[$pos] === "\n") && ($comcode[$pos + 1] === "\n")) { // Linux newline
                                $NUM_COMCODE_LINES_PARSED += 2;
                                $pos += 2;
                                $just_new_line = true;
                            }
                        }

                        if ($current_tag === 'html') {
                            $in_html = false;
                        } elseif ($current_tag === 'semihtml') {
                            $in_semihtml = false;
                        }
                        $status = CCP_NO_MANS_LAND;
                    } else {
                        if ($current_tag === 'title') {
                            $just_new_line = false;
                            list($close_list, $list_indent) = _close_open_lists($list_indent, $list_type);
                            if ($GLOBALS['XSS_DETECT']) {
                                ocp_mark_as_escaped($close_list);
                            }
                            $tag_output->attach($close_list);
                        }

                        array_push($tag_stack, array($current_tag, $attribute_map, $tag_output, $white_space_area, $in_separate_parse_section, $formatting_allowed, $textual_area, $comcode_dangerous, $comcode_dangerous_html));

                        list($tag_output, $comcode_dangerous, $comcode_dangerous_html, $white_space_area, $formatting_allowed, $in_separate_parse_section, $textual_area, $attribute_map, $status, $in_html, $in_semihtml, $pos, $in_code_tag) = _opened_tag($as_admin, $source_member, $attribute_map, $current_tag, $pos, $comcode_dangerous, $comcode_dangerous_html, $in_separate_parse_section, $in_html, $in_semihtml, $close, $len, $comcode);
                        if ($in_code_tag) {
                            $code_nest_stack = 0;
                        }
                    }
                } elseif ($status === CCP_IN_TAG_NAME) {
                    if (trim($next) != '') {
                        $current_tag .= strtolower($next);
                    }
                }
                break;

            case CCP_STARTING_TAG:
                if ($next === '[') { // Can't actually occur though
                    if (!$lax) {
                        return comcode_parse_error($preparse_mode, array('CCP_TAG_OPEN_ANOMALY'), $pos, $comcode, $check_only);
                    }

                    $status = CCP_NO_MANS_LAND;
                    $pos--;
                } elseif ($next === ']') { // Can't actual occur though
                    if (!$lax) {
                        return comcode_parse_error($preparse_mode, array('CCP_TAG_CLOSE_ANOMALY'), $pos, $comcode, $check_only);
                    }

                    $status = CCP_NO_MANS_LAND;
                } elseif ($next === '/') {
                    $close = true;
                } elseif (trim($next) != '') {
                    $current_tag .= strtolower($next);
                    $status = CCP_IN_TAG_NAME;
                }
                break;

            case CCP_IN_TAG_BETWEEN_ATTRIBUTES:
                if ($next === ']') {
                    if ($current_tag === 'title') {
                        $just_new_line = false;
                        list($close_list, $list_indent) = _close_open_lists($list_indent, $list_type);
                        if ($GLOBALS['XSS_DETECT']) {
                            ocp_mark_as_escaped($close_list);
                        }
                        $tag_output->attach($close_list);
                    }

                    array_push($tag_stack, array($current_tag, $attribute_map, $tag_output, $white_space_area, $in_separate_parse_section, $formatting_allowed, $textual_area, $comcode_dangerous, $comcode_dangerous_html));

                    list($tag_output, $comcode_dangerous, $comcode_dangerous_html, $white_space_area, $formatting_allowed, $in_separate_parse_section, $textual_area, $attribute_map, $status, $in_html, $in_semihtml, $pos, $in_code_tag) = _opened_tag($as_admin, $source_member, $attribute_map, $current_tag, $pos, $comcode_dangerous, $comcode_dangerous_html, $in_separate_parse_section, $in_html, $in_semihtml, $close, $len, $comcode);
                    if ($in_code_tag) {
                        $code_nest_stack = 0;
                    }
                } elseif ($next === '[') {
                    if (!$lax) {
                        return comcode_parse_error($preparse_mode, array('CCP_TAG_OPEN_ANOMALY'), $pos, $comcode, $check_only);
                    }

                    if ($current_tag === 'title') {
                        $just_new_line = false;
                        list($close_list, $list_indent) = _close_open_lists($list_indent, $list_type);
                        if ($GLOBALS['XSS_DETECT']) {
                            ocp_mark_as_escaped($close_list);
                        }
                        $tag_output->attach($close_list);
                    }

                    array_push($tag_stack, array($current_tag, $attribute_map, $tag_output, $white_space_area, $in_separate_parse_section, $formatting_allowed, $textual_area, $comcode_dangerous, $comcode_dangerous_html));

                    list($tag_output, $comcode_dangerous, $comcode_dangerous_html, $white_space_area, $formatting_allowed, $in_separate_parse_section, $textual_area, $attribute_map, $status, $in_html, $in_semihtml, $pos, $in_code_tag) = _opened_tag($as_admin, $source_member, $attribute_map, $current_tag, $pos, $comcode_dangerous, $comcode_dangerous_html, $in_separate_parse_section, $in_html, $in_semihtml, $close, $len, $comcode);
                    if ($in_code_tag) {
                        $code_nest_stack = 0;
                    }

                    $pos--;
                } elseif ($next == '=') {
                    $status = CCP_IN_TAG_BETWEEN_ATTRIBUTE_NAME_VALUE_RIGHT;
                    $current_attribute_name = 'param';
                } elseif (trim($next) != '') {
                    $status = CCP_IN_TAG_ATTRIBUTE_NAME;
                    $current_attribute_name = $next;
                }
                break;

            case CCP_IN_TAG_ATTRIBUTE_NAME:
                if ($next === '[') {
                    $status = CCP_NO_MANS_LAND;
                    $pos--;
                    if (!$lax) {
                        return comcode_parse_error($preparse_mode, array('CCP_TAG_OPEN_ANOMALY'), $pos, $comcode, $check_only);
                    }

                    if ($current_tag === 'title') {
                        $just_new_line = false;
                        list($close_list, $list_indent) = _close_open_lists($list_indent, $list_type);
                        if ($GLOBALS['XSS_DETECT']) {
                            ocp_mark_as_escaped($close_list);
                        }
                        $tag_output->attach($close_list);
                    }

                    array_push($tag_stack, array($current_tag, $attribute_map, $tag_output, $white_space_area, $in_separate_parse_section, $formatting_allowed, $textual_area, $comcode_dangerous, $comcode_dangerous_html));

                    list($tag_output, $comcode_dangerous, $comcode_dangerous_html, $white_space_area, $formatting_allowed, $in_separate_parse_section, $textual_area, $attribute_map, $status, $in_html, $in_semihtml, $pos, $in_code_tag) = _opened_tag($as_admin, $source_member, $attribute_map, $current_tag, $pos, $comcode_dangerous, $comcode_dangerous_html, $in_separate_parse_section, $in_html, $in_semihtml, $close, $len, $comcode);
                    if ($in_code_tag) {
                        $code_nest_stack = 0;
                    }
                } elseif ($next === ']') {
                    if (($attribute_map === array()) && (!$lax)) {
                        return comcode_parse_error($preparse_mode, array('CCP_TAG_CLOSE_ANOMALY'), $pos, $comcode, $check_only);
                    }

                    if ($attribute_map != array()) {
                        $at_map_keys = array_keys($attribute_map);
                        $old_attribute_name = $at_map_keys[count($at_map_keys) - 1];
                        $attribute_map[$old_attribute_name] .= ' ' . $current_attribute_name;
                    }

                    array_push($tag_stack, array($current_tag, $attribute_map, $tag_output, $white_space_area, $in_separate_parse_section, $formatting_allowed, $textual_area, $comcode_dangerous, $comcode_dangerous_html));

                    list($tag_output, $comcode_dangerous, $comcode_dangerous_html, $white_space_area, $formatting_allowed, $in_separate_parse_section, $textual_area, $attribute_map, $status, $in_html, $in_semihtml, $pos, $in_code_tag) = _opened_tag($as_admin, $source_member, $attribute_map, $current_tag, $pos, $comcode_dangerous, $comcode_dangerous_html, $in_separate_parse_section, $in_html, $in_semihtml, $close, $len, $comcode);
                    if ($in_code_tag) {
                        $code_nest_stack = 0;
                    }
                } elseif ($next === '=') {
                    $status = CCP_IN_TAG_BETWEEN_ATTRIBUTE_NAME_VALUE_RIGHT;
                } elseif ($next != ' ') {
                    $current_attribute_name .= strtolower($next);
                } else {
                    $status = CCP_IN_TAG_BETWEEN_ATTRIBUTE_NAME_VALUE_LEFT;
                }
                break;

            case CCP_IN_TAG_BETWEEN_ATTRIBUTE_NAME_VALUE_LEFT:
                if ($next === '=') {
                    $status = CCP_IN_TAG_BETWEEN_ATTRIBUTE_NAME_VALUE_RIGHT;
                } elseif (trim($next) != '') {
                    if (!$lax) {
                        return comcode_parse_error($preparse_mode, array('CCP_ATTRIBUTE_ERROR', $current_attribute_name, $current_tag), $pos, $comcode, $check_only);
                    }

                    if ($next === '[') {
                        $status = CCP_IN_TAG_BETWEEN_ATTRIBUTES;
                        $pos--;
                    } elseif ($next === ']') {
                        $status = CCP_IN_TAG_BETWEEN_ATTRIBUTES;
                        $pos--;
                    }
                }
                break;

            case CCP_IN_TAG_BETWEEN_ATTRIBUTE_NAME_VALUE_RIGHT:
                if ($next === '[') { // Can't actually occur though
                    if (!$lax) {
                        return comcode_parse_error($preparse_mode, array('CCP_TAG_OPEN_ANOMALY'), $pos, $comcode, $check_only);
                    }

                    $status = CCP_IN_TAG_BETWEEN_ATTRIBUTES;
                    $pos--;
                } elseif ($next === ']') { // Can't actually occur though
                    if (!$lax) {
                        return comcode_parse_error($preparse_mode, array('CCP_TAG_CLOSE_ANOMALY'), $pos, $comcode, $check_only);
                    }

                    $status = CCP_IN_TAG_BETWEEN_ATTRIBUTES;
                    $pos--;
                } elseif (($next === '"') || (($in_semihtml) && ($comcode[$pos - 1] === '&') && (substr($comcode, $pos - 1, 6) === '&quot;'))) {
                    if (($in_semihtml) && ($comcode[$pos - 1] === '&') && (substr($comcode, $pos - 1, 6) === '&quot;')) {
                        $pos += 5;
                    }
                    $status = CCP_IN_TAG_ATTRIBUTE_VALUE;
                    $current_attribute_value = '';
                } elseif ($next != '') {
                    $status = CCP_IN_TAG_ATTRIBUTE_VALUE_NO_QUOTE;
                    $current_attribute_value = $next;
                }
                break;

            case CCP_IN_TAG_ATTRIBUTE_VALUE_NO_QUOTE:
                if ($next === ' ') {
                    $status = CCP_IN_TAG_BETWEEN_ATTRIBUTES;
                    if ((isset($attribute_map[$current_attribute_name])) && (!$lax)) {
                        return comcode_parse_error($preparse_mode, array('CCP_DUPLICATE_ATTRIBUTES', $current_attribute_name, $current_tag), $pos, $comcode, $check_only);
                    }
                    $attribute_map[$current_attribute_name] = $current_attribute_value;
                } elseif ($next === ']') {
                    if ((isset($attribute_map[$current_attribute_name])) && (!$lax)) {
                        return comcode_parse_error($preparse_mode, array('CCP_DUPLICATE_ATTRIBUTES', $current_attribute_name, $current_tag), $pos, $comcode, $check_only);
                    }

                    $status = CCP_IN_TAG_BETWEEN_ATTRIBUTES;
                    $attribute_map[$current_attribute_name] = $current_attribute_value;
                    $pos--;
                } else {
                    $current_attribute_value .= $next;
                }
                break;

            case CCP_IN_TAG_ATTRIBUTE_VALUE:
                if (($next === '"') || (($in_semihtml) && ($comcode[$pos - 1] === '&') && (substr($comcode, $pos - 1, 6) === '&quot;'))) {
                    if (($in_semihtml) && ($comcode[$pos - 1] === '&') && (substr($comcode, $pos - 1, 6) === '&quot;')) {
                        $pos += 5;
                    }
                    $status = CCP_IN_TAG_BETWEEN_ATTRIBUTES;
                    if ((isset($attribute_map[$current_attribute_name])) && (!$lax)) {
                        return comcode_parse_error($preparse_mode, array('CCP_DUPLICATE_ATTRIBUTES', $current_attribute_name, $current_tag), $pos, $comcode, $check_only);
                    }
                    $attribute_map[$current_attribute_name] = $current_attribute_value;
                } else {
                    if ($next === '\\') {
                        if ($comcode[$pos] === '"') {
                            $current_attribute_value .= '"';
                            ++$pos;
                        } elseif (($comcode[$pos - 1] === '&') && (substr($comcode, $pos - 1, 6) === '&quot;')) {
                            $current_attribute_value .= '&quot;';
                            $pos += 6;
                        } elseif ($comcode[$pos] === '\\') {
                            $current_attribute_value .= '\\';
                            ++$pos;
                        } else {
                            $current_attribute_value .= $next;
                        }
                    } else {
                        $current_attribute_value .= $next;
                    }
                }
                break;
        }
    }
    if ($GLOBALS['XSS_DETECT']) {
        ocp_mark_as_escaped($continuation);
    }
    $tag_output->attach($continuation);
    $continuation = '';

    list($close_list, $list_indent) = _close_open_lists($list_indent, $list_type);
    if ($GLOBALS['XSS_DETECT']) {
        ocp_mark_as_escaped($close_list);
    }
    $tag_output->attach($close_list);

    if (($status != CCP_NO_MANS_LAND) || (count($tag_stack) != 0)) {
        if (!$lax) {
            $stack_top = array_pop($tag_stack);
            return comcode_parse_error($preparse_mode, array('CCP_BROKEN_END', is_null($stack_top) ? $current_tag : $stack_top[0]), $pos, $comcode, $check_only);
        } else {
            while (count($tag_stack) > 0) {
                $_last = array_pop($tag_stack);
                $embed_output = _do_tags_comcode($_last[0], $_last[1], $tag_output, $comcode_dangerous, $pass_id, $pos, $source_member, $as_admin, $connection, $comcode, $structure_sweep, $semiparse_mode, null, null, $in_semihtml, $is_all_semihtml, $html_errors);
                $in_code_tag = false;
                $white_space_area = $_last[3];
                $in_separate_parse_section = $_last[4];
                $formatting_allowed = $_last[5];
                $textual_area = $_last[6];
                $tag_output = $_last[2];
                $tag_output->attach($embed_output);
                $comcode_dangerous = $_last[7];
                $comcode_dangerous_html = $_last[8];
            }
        }
    }

    /*if ($html_element_stack !== array()) {    Not actually needed
        $html_errors = true;
    }*/

    // CkEditor trims finally <br>'s making editing hard, so do a FUDGE
    if (($semiparse_mode) && (substr(rtrim($comcode), -8) == '[/quote]')) {
        $tag_output->attach('&nbsp;');
    }

    return $tag_output;
}

/**
 * Find if any of some tags are in the stack.
 *
 * @param  array $tag_stack The tag stack
 * @param  array $tags The tags
 * @return boolean Whether one is present
 */
function in_tag_stack($tag_stack, $tags)
{
    foreach ($tag_stack as $_temp) {
        if (in_array($_temp[0], $tags)) {
            return true;
        }
    }
    return false;
}

/**
 * Helper function for setting up and juggling variables after reaching a new Comcode tag.
 *
 * @param  boolean $as_admin Whether to explicitly execute this with admin rights. There are a few rare situations where this should be done, for data you know didn't come from a member, but is being evaluated by one.
 * @param  MEMBER $source_member The member the evaluation is running as. This is a security issue, and you should only run as an administrator if you have considered where the Comcode came from carefully
 * @param  array $attribute_map The attribute map of the tag
 * @param  string $current_tag The identifier for the tag
 * @param  integer $pos The offset of the tag in the Comcode
 * @param  boolean $comcode_dangerous Whether the parser allows dangerous Comcode
 * @param  boolean $comcode_dangerous_html Whether the parser allows dangerous HTML
 * @param  boolean $in_separate_parse_section Whether the parser is/was in a separate parse section (e.g. a 'code' tag)
 * @param  boolean $in_html Whether the parser is/was in an HTML region
 * @param  boolean $in_semihtml Whether the parser is/was in a Semi-HTML region
 * @param  boolean $close Whether the tag is a closing tag
 * @param  integer $len The length of the Comcode
 * @param  LONG_TEXT $comcode The Comcode being parsed
 * @return array A tuple of new parser settings.
 *
 * @ignore
 */
function _opened_tag($as_admin, $source_member, $attribute_map, $current_tag, $pos, $comcode_dangerous, $comcode_dangerous_html, $in_separate_parse_section, $in_html, $in_semihtml, $close, &$len, &$comcode)
{
    global $BLOCK_TAGS, $TEXTUAL_TAGS, $CODE_TAGS;

    $block_tag = isset($BLOCK_TAGS[$current_tag]);

    if (($block_tag) && ($pos < $len) && ($comcode[$pos] === "\n")) {
        ++$pos;
        global $NUM_COMCODE_LINES_PARSED;
        ++$NUM_COMCODE_LINES_PARSED;
    }

    $tag_output = new Tempcode();
    $textual_area = isset($TEXTUAL_TAGS[$current_tag]);

    $white_space_area = $textual_area;
    if (((($current_tag === 'code') || ($current_tag === 'codebox')) && (isset($attribute_map['param'])) && ((strtolower($attribute_map['param']) === 'php') || (file_exists(get_file_base() . '/sources_custom/geshi/' . filter_naughty(($attribute_map['param'] === 'HTML') ? 'html5' : strtolower($attribute_map['param'])) . '.php')))) || ($current_tag === 'attachment') || ($current_tag === 'attachment_safe') || ($current_tag === 'menu')) {
        $in_separate_parse_section = true;
    } else {
        // Code tags are white space area, but not textual area
        if (isset($CODE_TAGS[$current_tag])) {
            $white_space_area = true;
        }
    }

    $in_code_tag = isset($CODE_TAGS[$current_tag]);

    $attribute_map = array();

    $formatting_allowed = (($textual_area ? 1 : 0) & ($block_tag ? 1 : 0)) != 0;

    if ($current_tag === 'html') {
        $in_html = !$close;
    } elseif ($current_tag === 'semihtml') {
        $in_semihtml = !$close;
    }
    $status = CCP_NO_MANS_LAND;

    if (($current_tag === 'html') || ($current_tag === 'semihtml')) { // New state meaning we need to filter the contents
        if (($in_html) || ($in_semihtml)) {
            filter_html($as_admin, $source_member, $pos, $len, $comcode, $in_html, $in_semihtml);
        }
    }

    if (($current_tag === 'quote') && (count($attribute_map) > 0)) {
        $comcode_dangerous = false;
        $comcode_dangerous_html = false;
    }

    return array($tag_output, $comcode_dangerous, $comcode_dangerous_html, $white_space_area, $formatting_allowed, $in_separate_parse_section, $textual_area, $attribute_map, $status, $in_html, $in_semihtml, $pos, $in_code_tag);
}

/**
 * Filter HTML harshly from an inclusion-list for safety.
 *
 * @param  LONG_TEXT $comcode The Comcode being parsed
 * @param  integer $pos The offset of the tag in the Comcode
 * @param  string $continuation The current text buffer for the parser
 * @param  boolean $comcode_dangerous_html Whether the parser allows dangerous HTML
 * @param  array $allowed_html_seqs List of allowed HTML sequences
 * @return boolean Whether filtering happened (with jump-ahead in parser)
 */
function filter_html_inclusion_list_at_tag_start($comcode, &$pos, &$continuation, $comcode_dangerous_html, $allowed_html_seqs)
{
    if (!$comcode_dangerous_html) {
        // Special filtering required
        $close = strpos($comcode, '>', $pos - 1);
        $portion = substr($comcode, $pos - 1, $close - $pos + 2);
        $seq_ok = false;
        foreach ($allowed_html_seqs as $allowed_html_seq) {
            if (preg_match('#^' . $allowed_html_seq . '$#', $portion) != 0) {
                $seq_ok = true;
                break;
            }
        }
        if (!$seq_ok) {
            if ((substr($portion, -2) != '/>'/*not self closing*/) && (substr($portion, 0, 2) != '</'/*not closing*/)) {
                // It's a regular opening tag.
                // Maybe then we can strip it down to a raw tag? At least then we won't lose everything, esp tag balance.
                $matches = array();
                if (preg_match('#^<(\w+)(\s[^<>]*|)>$#i', $portion, $matches) != 0) {
                    $tag = strtolower($matches[1]);
                    if (($tag == 'img') && (preg_match('#^<img(\s[^<>]*|)\ssrc="([^<>\"]*)"(\s[^<>]*|)>$#i', $portion, $matches) != 0)) {
                        $continuation .= '<img src="' . $matches[2] . '">';
                        $seq_ok = true;
                    } elseif (($tag == 'a') && (preg_match('#^<a(\s[^<>]*|)\shref="([^<>\"]*)"(\s[^<>]*|)>$#i', $portion, $matches) != 0)) {
                        $continuation .= '<a href="' . $matches[2] . '">';
                        $seq_ok = true;
                    } else {
                        foreach ($allowed_html_seqs as $allowed_html_seq) {
                            if ($allowed_html_seq == '<' . $tag . '>') {
                                $seq_ok = true;
                                break;
                            }
                        }
                        if ($seq_ok) {
                            $continuation .= '<' . $tag . '>';
                        }
                    }

                    if ($seq_ok) {
                        // These are allowed through as a special case
                        $continuation = str_replace('{$BASE_URL}', get_base_url(), $continuation);
                        $continuation = str_replace('{$BASE_URL*}', escape_html(get_base_url()), $continuation);
                    }
                }
            }

            if (!$seq_ok) {
                $continuation .= '<!--filtered; no ' . do_lang('permissions:PRIVILEGE_allow_html') . '-->';
            }

            if ($close !== false) {
                $pos = $close + 1;
            }

            return true;
        }
    }

    return false;
}

/**
 * Filter HTML for safety.
 *
 * @param  boolean $as_admin Whether to explicitly execute this with admin rights. There are a few rare situations where this should be done, for data you know didn't come from a member, but is being evaluated by one.
 * @param  MEMBER $source_member The member the evaluation is running as. This is a security issue, and you should only run as an administrator if you have considered where the Comcode came from carefully
 * @param  integer $pos The offset of the tag in the Comcode
 * @param  integer $len The length of the Comcode
 * @param  LONG_TEXT $comcode The Comcode being parsed
 * @param  boolean $in_html Whether the parser is/was in an HTML region
 * @param  boolean $in_semihtml Whether the parser is/was in a Semi-HTML region
 */
function filter_html($as_admin, $source_member, $pos, &$len, &$comcode, $in_html, $in_semihtml)
{
    if ((!$as_admin) && (!has_privilege($source_member, 'use_very_dangerous_comcode'))) {
        init_potential_js_naughty_array();

        global $POTENTIAL_JS_NAUGHTY_ARRAY;

        $comcode = preg_replace('#(\\\\)+(\[/(html|semihtml)\])#', '\2', $comcode); // Stops sneaky trying to trick the end of the HTML tag to hack this function

        if (($in_html) && ($in_semihtml)) {
            $ahead_end = max(stripos($comcode, '[/html]', $pos), stripos($comcode, '[/semihtml]', $pos));
        } elseif ($in_html) {
            $ahead_end = stripos($comcode, '[/html]', $pos);
        } elseif ($in_semihtml) {
            $ahead_end = stripos($comcode, '[/semihtml]', $pos);
        } else {
            $ahead_end = false;
        }
        if ($ahead_end === false) {
            $ahead_end = strlen($comcode);
        }
        $ahead = substr($comcode, $pos, $ahead_end - $pos);

        require_code('input_filter');
        hard_filter_input_data__html($ahead); // NB: If we have multiple HTML sections this will guard against it by closing partly open tags

        // Tidy up
        $comcode = substr($comcode, 0, $pos) . $ahead . substr($comcode, $ahead_end);
        $len = strlen($comcode);
    }
}

/**
 * Get HTML to close any open lists.
 *
 * @param  integer $list_indent The depth level of lists that we need to close
 * @param  string $list_type List-type code
 * @set    ul a 1
 * @return array The output needed to close the lists, and the new list indentation (always zero). Done like this so we can use 'list' to set both at once in the main parser.
 *
 * @ignore
 */
function _close_open_lists($list_indent, $list_type)
{
    $tag_output = '';
    for ($i = 0; $i < $list_indent; ++$i) { // Close any lists that exist
        $tag_output .= '</li>';
        $temp_tpl = ($list_type === 'ul') ? '</ul>' : '</ol>';
        $tag_output .= $temp_tpl;
    }
    $list_indent = 0;
    return array($tag_output, $list_indent);
}

/**
 * Parse a single tag. For use separately, not used by main parser.
 *
 * @param  string $data The data being parsed
 * @param  string $tag The tag we're expecting to see here / a regexp
 * @return array A map of parsed attributes
 */
function parse_single_comcode_tag($data, $tag = '\w+')
{
    $attributes = array();
    $_attributes = preg_replace('#^\[' . $tag . '\s*#', '', preg_replace('#\[/' . $tag . '\]$#Us', '', $data));
    if (($_attributes != '') && ($_attributes != $data/*if it matched*/)) {
        if (substr($_attributes, 0, 1) === '=') {
            $_attributes = 'param' . $_attributes;
        }
        $current_attribute = '';
        $current_value = '';
        $in_attribute = false;
        for ($i = 0; $i < strlen($_attributes); $i++) {
            $next = $_attributes[$i];
            if (!$in_attribute) {
                if ($next === '=') {
                    $in_attribute = true;
                    if (($i != strlen($_attributes) - 1) && ($_attributes[$i + 1] === '"')) {
                        $i++; // Skip opening "
                    }
                    $current_value = '';
                } elseif ($next === ']') {
                    $attributes[''] = substr($_attributes, $i + 1);
                    break;
                } else {
                    $current_attribute .= $next;
                }
            } else {
                if ($next === '"') {
                    $in_attribute = false;
                    if (($i != strlen($_attributes) - 1) && ($_attributes[$i + 1] === ' ')) {
                        $i++; // Skip space
                    }
                    $attributes[$current_attribute] = str_replace(array('\\[', '\\]', '\\{', '\\}', '\\\''), array('[', ']', '{', '}', '\''), $current_value);
                    $current_attribute = '';
                } else {
                    $current_value .= $next;
                }
            }
        }
    }

    return $attributes;
}
