<?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
 */

/**
 * Standard code module initialisation function.
 *
 * @ignore
 */
function init__tempcode()
{
    if (defined('ENTITY_ESCAPED')) {
        return;
    }

    if (!defined('ENTITY_ESCAPED')) {
        define('ENTITY_ESCAPED', 1); // HTML entities
        define('SQ_ESCAPED', 2); // Single quotes
        define('DQ_ESCAPED', 3); // Double quotes
        define('NL_ESCAPED', 4); // New lines disappear
        define('CC_ESCAPED', 5); // Comcode
        define('UL_ESCAPED', 6); // URL
        define('JSHTML_ESCAPED', 7); // JavaScript </ -> <\/
        define('NL2_ESCAPED', 8); // New lines go to \n
        define('ID_ESCAPED', 9); // Strings to to usable IDs
        define('NAUGHTY_ESCAPED', 10); // Used as a JavaScript variable name, for example... to prevent code injection
        define('NULL_ESCAPED', 11); // This is useful to mark something that takes strings but does not need escaping (usually because it is escaped further down the line)
        define('FORCIBLY_ENTITY_ESCAPED', 12); // To force a language string to be escaped
        define('CSS_ESCAPED', 13); // To stop CSS injection
        define('UL2_ESCAPED', 14); // rawurlencode
        define('PURE_STRING', 16); // Used to indicating we just put something directly into the output. Works with __toString or normal strings. Does no escaping.

        define('TC_SYMBOL', 0);
        define('TC_KNOWN', 1); // Either Tempcode or string
        define('TC_LANGUAGE_REFERENCE', 2);
        define('TC_PARAMETER', 3); // A late parameter for a compiled template
        define('TC_DIRECTIVE', 4);
    }

    global $XHTML_SPIT_OUT, $NO_EVAL_CACHE, $MEMORY_OVER_SPEED, $TEMPLATE_DISK_ORIGIN_CACHE, $REQUEST_BLOCK_NEST_LEVEL, $LOADED_TPL_CACHE, $KEEP_TPL_FUNCS;
    $XHTML_SPIT_OUT = null;
    $NO_EVAL_CACHE = false;
    $MEMORY_OVER_SPEED = (get_param_integer('keep_memory_over_speed', 0) == 1);
    $TEMPLATE_DISK_ORIGIN_CACHE = array();
    $REQUEST_BLOCK_NEST_LEVEL = 0;
    $LOADED_TPL_CACHE = array();
    $KEEP_TPL_FUNCS = array();

    global $RECORD_TEMPLATES_USED, $RECORDED_TEMPLATES_USED, $RECORD_TEMPLATES_TREE, $POSSIBLY_IN_SAFE_MODE_CACHE, $SCREEN_TEMPLATE_CALLED, $TITLE_CALLED;
    $RECORD_TEMPLATES_USED = false;
    $RECORDED_TEMPLATES_USED = array();
    $RECORD_TEMPLATES_TREE = false;
    /** The name of a template that was called to render the current screen (null: not rendering a screen), auto-populated within the template system. This is tracked during dev mode to confirm that each screen really does wrap itself in a proper screen template.
     *
     * @global ?ID_TEXT $SCREEN_TEMPLATE_CALLED
     */
    $SCREEN_TEMPLATE_CALLED = null;
    /** Whether a title has been called.
     *
     * @global boolean $TITLE_CALLED
     */
    $TITLE_CALLED = false;
    $POSSIBLY_IN_SAFE_MODE_CACHE = (get_param_integer('keep_safe_mode', 0) == 1);

    global $SIMPLE_ESCAPED, $XSS_DETECT;
    $SIMPLE_ESCAPED = array(ENTITY_ESCAPED);
    if ($XSS_DETECT) {
        $SIMPLE_ESCAPED = array(12345); // Don't allow $SIMPLE_ESCAPED to work, as we need to work through full manual escaping
    }

    require_code('symbols');

    global $FULL_RESET_VAR_CODE, $RESET_VAR_CODE;
    // && substr($x, 0, 6) == \'bound_\' removed from the below for performance, not really needed
    $FULL_RESET_VAR_CODE = 'foreach(get_defined_vars() as $x => $_) { if ($x[0]==\'b\' && $x[1]==\'o\') unset($$x); } extract($parameters,EXTR_PREFIX_ALL,\'bound\');';
    $RESET_VAR_CODE = 'extract($parameters,EXTR_PREFIX_ALL,\'bound\');';

    global $IS_TEMPLATE_PREVIEW_OP_CACHE;
    $IS_TEMPLATE_PREVIEW_OP_CACHE = null;

    /** Whether output streaming mode is active.
     *
     * @global boolean $OUTPUT_STREAMING
     */
    global $OUTPUT_STREAMING, $SMART_CACHE;
    $OUTPUT_STREAMING = (function_exists('get_option')) && (get_option('output_streaming') == '1') && (get_param_integer('keep_no_output_streaming', 0) == 0) && (isset($SMART_CACHE)) && (!$SMART_CACHE->empty);
    if ($GLOBALS['SMART_CACHE'] === null || !$GLOBALS['SMART_CACHE']->get_initial_status('CSSS')) {
        $OUTPUT_STREAMING = false;
    } elseif (get_param_string('special_page_type', 'view') != 'view') {
        $OUTPUT_STREAMING = false;
    } elseif (get_param_integer('keep_markers', 0) == 1) {
        $OUTPUT_STREAMING = false;
    } elseif (get_param_integer('show_edit_links', 0) == 1) {
        $OUTPUT_STREAMING = false;
    }

    global $STOP_IF_STUCK, $STUCK_ABORT_SIGNAL, $TEMPCODE_OUTPUT_STARTED, $CSS_OUTPUT_STARTED, $JS_OUTPUT_STARTED, $TEMPCODE_CURRENT_PAGE_OUTPUTTING;
    $STOP_IF_STUCK = false;
    $STUCK_ABORT_SIGNAL = false;
    $TEMPCODE_OUTPUT_STARTED = false;
    $CSS_OUTPUT_STARTED = false;
    $JS_OUTPUT_STARTED = false;
    $TEMPCODE_CURRENT_PAGE_OUTPUTTING = null;
}

/**
 * Simple function to evaluate some Tempcode. Very rarely to be used, only if you can't call a method (e.g. you are copying direct into an array, such as in block caching).
 *
 * @param  Tempcode $ob Tempcode object
 * @return string Evaluated string
 */
function static_evaluate_tempcode($ob)
{
    return $ob->evaluate();
}

/**
 * Escape a string to fit within PHP double quotes TWICE. Needed sometimes when generating code. This function exists for performance reasons.
 *
 * @param  string $in String in
 * @return string Resultant string
 */
function php_addslashes_twice($in)
{
    global $PHP_REP_FROM, $PHP_REP_TO_TWICE;
    return str_replace($PHP_REP_FROM, $PHP_REP_TO_TWICE, $in);
}

/**
 * Create a unique identifer.
 *
 * @return string Unique Identifier
 */
function fast_uniqid()
{
    return uniqid('', true);
}

/**
 * Get a string (natural for Tempcode's stream-based processing-model) representation of a bound Tempcode construct
 *
 * @param  mixed $var Construct (or null if not set)
 * @param  ID_TEXT $origin Where this parameter is referenced, in a compressed reference form
 * @return string Value
 */
function otp($var, $origin = '')
{
    switch (gettype($var)) {
        case 'NULL':
            if ($GLOBALS['STOP_IF_STUCK']) {
                $GLOBALS['STUCK_ABORT_SIGNAL'] = true;
                return '';
            }
            return missing_template_parameter($origin);
        case 'string':
            return $var;
        case 'object':
            return $var->evaluate();
        case 'boolean':
            return $var ? '1' : '0';
        case 'array':
            $cnt = count($var);
            return ($cnt === 0) ? '' : strval($cnt);
    }
    return '';
}

/**
 * Give an error about a missing template parameter
 *
 * @param  ID_TEXT $origin Where this parameter is referenced, in a slash-combined reference form
 * @return string Always ""
 */
function missing_template_parameter($origin)
{
    if ((error_reporting() & E_WARNING) === 0) { // Errors disabled via @ most likely, probably we're doing a test in tempcode_compiler.php
        return '';
    }
    if (strpos($origin, ':') === false) {
        return '';
    }
    list($template_name, $parameter) = ($origin == '') ? array(do_lang('UNKNOWN'), do_lang('UNKNOWN')) : explode(':', $origin, 2);
    if (strtolower($template_name) != $template_name && (!is_file(get_file_base() . '/themes/default/templates/' . $template_name . '.tpl'))) {
        return ''; // Some kind of custom template, will be error prone
    }
    attach_message(do_lang_tempcode('MISSING_TEMPLATE_PARAMETER', escape_html($parameter), escape_html(($template_name == '') ? '???' : $template_name)), 'warn');
    return '';
}

/**
 * Build a conventional Tempcode object
 *
 * @param  integer $type The type of symbol this is (TC_SYMBOL, TC_LANGUAGE_REFERENCE)
 * @set    0 2
 * @param  ID_TEXT $name The name of the symbol
 * @param  ?array $parameters Parameters to the symbol (null: none). In same format as expected by ecv.
 * @param  ?array $escaping Escaping for the symbol (null: none)
 * @return Tempcode Tempcode object.
 */
function build_closure_tempcode($type, $name, $parameters, $escaping = null)
{
    if ($escaping === null) {
        $_escaping = 'array()';
    } else {
        $_escaping = 'array(' . @implode(',', $escaping) . ')';
    }

    $_type = strval($type);

    if ((function_exists('ctype_alnum')) && (ctype_alnum(str_replace(array('_', '-'), array('', ''), $name)))) {
        $_name = $name;
    } else {
        if (preg_match('#^[\w\-]*$#', $name) === 0) {
            $_name = php_addslashes_twice($name);
        } else {
            $_name = $name;
        }
    }

    static $generator_base = null;
    static $generator_num = 0;
    if ($generator_base === null) {
        $generator_base = uniqid('', true);
    }
    $generator_num++;

    $has_tempcode = false;
    foreach ($parameters as $parameter) {
        if (isset($parameter->codename)/*faster than is_object*/) {
            $has_tempcode = true;
        }
    }

    $myfunc = 'do_runtime_' . $generator_base . '_' . strval($generator_num)/*We'll inline it actually rather than calling, for performance   fast_uniqid()*/;
    if ($name === '?' && $type === TC_SYMBOL) {
        $name = 'TERNARY';
    }

    if ($has_tempcode) {
        $funcdef = "\$tpl_funcs['$myfunc']=\"foreach (\\\$parameters as \\\$i=>\\\$p) { if (is_object(\\\$p)) \\\$parameters[\\\$i]=\\\$p->evaluate(); } echo ";
        if (($type === TC_SYMBOL) && (function_exists('ecv_' . $name))) {
            $funcdef .= "ecv_" . $name . "(\\\$cl," . ($_escaping) . ",\\\$parameters);\";\n";
        } else {
            $funcdef .= "ecv(\\\$cl," . ($_escaping) . "," . ($_type) . ",\\\"" . ($_name) . "\\\",\\\$parameters);\";\n";
        }
    } else {
        $_parameters = '';
        if ($parameters !== null) {
            foreach ($parameters as $parameter) {
                if ($_parameters != '') {
                    $_parameters .= ',';
                }
                if (is_bool($parameter)) {
                    $_parameters .= "\\\"" . ($parameter ? '1' : '0') . "\\\"";
                } else {
                    $_parameters .= "\\\"" . php_addslashes_twice($parameter) . "\\\"";
                }
            }
        }

        $funcdef = "\$tpl_funcs['$myfunc']=\"echo ";
        if (($type === TC_SYMBOL) && (function_exists('ecv_' . $name))) {
            $funcdef .= "ecv_" . $name . "(\\\$cl," . ($_escaping) . ",array(" . $_parameters . "));\";\n";
        } else {
            $funcdef .= "ecv(\\\$cl," . ($_escaping) . "," . ($_type) . ",\\\"" . ($_name) . "\\\",array(" . $_parameters . "));\";\n";
        }

        switch ($_name) {
            // Needs parameters for preprocessing, so we won't throw them out
            case 'REQUIRE_CSS':
            case 'REQUIRE_JAVASCRIPT':
            case 'FACILITATE_AJAX_BLOCK_CALL':
            case 'JS_TEMPCODE':
            case 'CSS_TEMPCODE':
            case 'SET':
            case 'SET_TITLE':
            case 'BLOCK':
            case 'PAGE_LINK':
            case 'LOAD_PAGE':
            case 'LOAD_PANEL':
            case 'INSERT_SPAMMER_BLACKHOLE':
                break;

            default:
                $parameters = array();
                break;
        }
    }

    $ret = new Tempcode(array(array($myfunc => $funcdef), array(array(array($myfunc, ($parameters === null) ? array() : $parameters, $type, $name, '')))));
    if ($type === TC_LANGUAGE_REFERENCE) {
        $ret->pure_lang = true;
    }
    return $ret;
}

/**
 * This will create a new Tempcode object that is containing a single specifed symbol
 *
 * @param  ID_TEXT $symbol The ID of the symbol to use
 * @param  ?array $parameters Symbol parameters (null: none)
 * @param  ?array $escape Escaping (null: none)
 * @return Tempcode A symbol Tempcode object
 */
function symbol_tempcode($symbol, $parameters = null, $escape = null)
{
    if ($parameters === null) {
        $parameters = array();
    }

    return build_closure_tempcode(TC_SYMBOL, $symbol, $parameters, $escape);
}

/**
 * This will create a new Tempcode object that is containing a single specifed directive
 *
 * @param  ID_TEXT $directive The ID of the directive to use
 * @param  mixed $content The contents (Tempcode or string)
 * @param  ?array $parameters Directive parameters (null: none)
 * @return Tempcode A directive Tempcode object
 */
function directive_tempcode($directive, $content, $parameters = null)
{
    if ($parameters === null) {
        $parameters = array();
    }
    $parameters[] = $content;

    return build_closure_tempcode(TC_DIRECTIVE, $directive, $parameters);
}

/**
 * Perform a simple loop, that can be inlined in an expression.
 *
 * @param  array $args The template bound parameters
 * @param  array $control_function The loop control function
 * @param  array $main_function The loop execution function
 * @return string Result
 */
function closure_while_loop($args, $control_function, $main_function)
{
    $out = '';
    while (call_user_func_array($control_function, $args)) {
        $out .= call_user_func_array($main_function, $args);
    }
    return $out;
}

/**
 * Evaluate some PHP code to put the result into an expression (code is allowed to have side effects).
 *
 * @param  string $code The code
 * @param  array $parameters Template parameters
 * @return string Result
 */
function closure_eval($code, $parameters)
{
    if (get_value('allow_php_in_templates') !== '1') {
        return do_lang('NO_PHP_IN_TEMPLATES');
    }

    $ret = /*$GLOBALS['DEV_MODE']?debug_eval($code):*/
        eval($code);
    if (!is_string($ret)) {
        $ret = @strval($ret);
    }
    return $ret;
}

/**
 * Perform a simple loop, that can be inlined in an expression.
 *
 * @param  array $param The template bound parameters
 * @param  array $args The loop directive parameters
 * @param  string $main_function The loop execution function
 * @return string Result
 */
function closure_loop($param, $args, $main_function)
{
    $value = '';

    if (isset($param[0])) {
        $array_key = $param[0];
        if ((is_numeric($array_key)) || (strpos($array_key, ',') !== false) || (strpos($array_key, '=') !== false)) {
            $array = array();
            foreach (explode(',', $array_key) as $x) {
                if (strpos($x, '=') !== false) {
                    list($key, $val) = explode('=', $x, 2);
                    if (($GLOBALS['XSS_DETECT']) && (ocp_is_escaped($x))) {
                        ocp_mark_as_escaped($key);
                        ocp_mark_as_escaped($val);
                    }
                    if ($key === '' && isset($array[$key])) {
                        $array[] = $val; // Empty keys: which are done to allow "="s in strings by putting in an empty key
                    } else {
                        $array[$key] = $val;
                    }
                } else {
                    if (($GLOBALS['XSS_DETECT']) && (ocp_is_escaped($x))) {
                        ocp_mark_as_escaped($x);
                    }
                    $array[] = $x;
                }
            }
        } else {
            $array = isset($param['vars'][$array_key]) ? $param['vars'][$array_key] : array();
        }
        if (!is_array($array)) {
            return do_lang('TEMPCODE_NOT_ARRAY'); // Must have this, otherwise will loop over the Tempcode object
        }
        if (isset($param[1 + 1])) { /* NB: +1 is due to there being a non-numeric index here too */
            $columns = intval($param[1]);
            if ($columns == 0) {
                $columns = 1;
            }
            $row_starter = isset($param[2 + 1]) ? $param[2] : '<tr>';
            $row_terminator = isset($param[3 + 1]) ? $param[3] : '</tr>';
            if ($array != array()) {
                $value .= $row_starter;
            }

            // Sorting
            if (isset($param[4 + 1])) {
                $sort_key = $param[4];

                $rev = ((isset($param[5 + 1])) && ($param[5] == 'DESC'));
                if ($sort_key != '') {
                    sort_maps_by($array, $sort_key);
                }
                if ($rev) {
                    $array = array_reverse($array);
                }
            }
        }
        $col = 0;

        $first = true;
        $max_index = count($array) - 1;
        foreach ($array as $go_key => $go) {
            if (!is_array($go)) {
                $go = array('_loop_var' => $go);
            } else {
                $go['_loop_var'] = '(array)'; // In case it's not a list of maps, but just a list
            }

            if ((isset($param[2])) && ($col % $columns == 0) && ($col != 0)) {
                $value .= $row_starter;
            }

            $ps = $go + array('_loop_key' => is_integer($go_key) ? strval($go_key) : $go_key, '_i' => strval($col), '_first' => $first, '_last' => $col == $max_index);
            $args[0] = $ps + $args[0];
            $args[0]['vars'] = $args[0];
            $value .= call_user_func_array($main_function, $args);

            ++$col;
            if ((isset($param[3])) && ($col % $columns == 0)) {
                $value .= $row_terminator;
            }
            $first = false;
        }
        if ((isset($param[2])) && ($col % $columns != 0)) {
            $value .= $row_terminator;
        }
    }

    return $value;
}

/**
 * Convert a string to Tempcode.
 *
 * @param  string $string String
 * @return Tempcode Tempcode
 */
function make_string_tempcode($string)
{
    static $generator_base = null;
    static $generator_num = 0;
    if ($generator_base === null) {
        $generator_base = uniqid('', true);
    }
    $generator_num++;

    $myfunc = 'string_attach_' . $generator_base . '_' . strval($generator_num)/*We'll inline it actually rather than calling, for performance   fast_uniqid()*/
    ;
    $code_to_preexecute = array($myfunc => "\$tpl_funcs['$myfunc']=\"echo \\\"" . php_addslashes_twice($string) . "\\\";\";\n");
    $seq_parts = array(array(array($myfunc, array(), TC_KNOWN, '', '')));
    return new Tempcode(array($code_to_preexecute, $seq_parts));
}

/**
 * Add entity entity escaping to a string/Tempcode.
 *
 * @param  mixed $data String
 * @return Tempcode Tempcode
 */
function escape_html_tempcode($data)
{
    // This is a bit of a hack, but it works. We don't want to have to have a route for altering Tempcode structure (because that has a performance hit, so we piggy-back on recursing through a null language string and add escaping when we do it)
    return build_closure_tempcode(TC_LANGUAGE_REFERENCE, 'dont_escape_trick', array($data), array(FORCIBLY_ENTITY_ESCAPED));
}

/**
 * Apply whatever escaping is requested to the given value.
 *
 * @param  array $escaped A list of escaping to do
 * @param  string $value The string to apply the escapings to
 * @return string Output string (you do not need to collect this, as $value is pass-by-reference -- but this is useful for chaining)
 */
function apply_tempcode_escaping($escaped, &$value)
{
    static $charset = null;
    if ($charset === null) {
        $charset = get_charset();
    }

    global $ESCAPE_HTML_OUTPUT;
    foreach ($escaped as $escape) {
        if ($escape === ENTITY_ESCAPED) {
            if ((!isset($ESCAPE_HTML_OUTPUT[$value])/*not already auto-escaped once*/) || (!function_exists('has_solemnly_declared')) || (has_solemnly_declared(I_UNDERSTAND_XSS)/*no auto-escape*/)) {
                $value = @htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
            }
        } elseif ($escape === FORCIBLY_ENTITY_ESCAPED) {
            $value = @htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
        } elseif ($escape === SQ_ESCAPED) {
            $value = str_replace('&#039;', '\&#039;', str_replace('\'', '\\\'', str_replace('\\', '\\\\', $value)));
        } elseif ($escape === DQ_ESCAPED) {
            $value = str_replace('&quot;', '\&quot;', str_replace('"', '\\"', str_replace('\\', '\\\\', $value)));
        } elseif ($escape === NL_ESCAPED) {
            $value = str_replace(array("\r", "\n"), array('', ''), $value);
        } elseif ($escape === NL2_ESCAPED) {
            $value = str_replace(array("\r", "\n"), array('', '\n'), $value);
        } elseif ($escape === CC_ESCAPED) {
            $value = str_replace('[', '\\[', str_replace('\\', '\\\\', $value));
        } elseif ($escape === UL_ESCAPED) {
            $value = cms_url_encode($value);
        } elseif ($escape === UL2_ESCAPED) {
            $value = rawurlencode($value);
        } elseif ($escape === JSHTML_ESCAPED) {
            $value = str_replace(']]>', ']]\'+\'>', str_replace('</', '<\/', $value));
        } elseif ($escape === ID_ESCAPED) {
            $value = fix_id($value);
        } elseif ($escape === CSS_ESCAPED) {
            $value = preg_replace('#[^\w\#\.\-\%]#', '_', $value);
        } elseif ($escape === NAUGHTY_ESCAPED) {
            $value = filter_naughty_harsh($value, true);
        }
    }
    if (($GLOBALS['XSS_DETECT']) && ($escaped !== array())) {
        ocp_mark_as_escaped($value);
    }

    return $value;
}

/**
 * Apply whatever escaping is requested to the given value.
 *
 * @param  array $escaped A list of escaping to do
 * @param  string $value The string to apply the escapings to
 * @return string Output string
 */
function apply_tempcode_escaping_inline($escaped, $value)
{
    static $charset = null;
    if ($charset === null) {
        $charset = get_charset();
    }

    global $ESCAPE_HTML_OUTPUT;
    foreach ($escaped as $escape) {
        if ($escape === ENTITY_ESCAPED) {
            if ((!isset($ESCAPE_HTML_OUTPUT[$value])/*not already auto-escaped once*/) || (!function_exists('has_solemnly_declared')) || (has_solemnly_declared(I_UNDERSTAND_XSS)/*no auto-escape*/)) {
                $value = @htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
            }
        } elseif ($escape === FORCIBLY_ENTITY_ESCAPED) {
            $value = @htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
        } elseif ($escape === SQ_ESCAPED) {
            $value = str_replace('&#039;', '\&#039;', str_replace('\'', '\\\'', str_replace('\\', '\\\\', $value)));
        } elseif ($escape === DQ_ESCAPED) {
            $value = str_replace('&quot;', '\&quot;', str_replace('"', '\\"', str_replace('\\', '\\\\', $value)));
        } elseif ($escape === NL_ESCAPED) {
            $value = str_replace(array("\r", "\n"), array('', ''), $value);
        } elseif ($escape === NL2_ESCAPED) {
            $value = str_replace(array("\r", "\n"), array('', '\n'), $value);
        } elseif ($escape === CC_ESCAPED) {
            $value = str_replace('[', '\\[', str_replace('\\', '\\\\', $value));
        } elseif ($escape === UL_ESCAPED) {
            $value = cms_url_encode($value);
        } elseif ($escape === UL2_ESCAPED) {
            $value = rawurlencode($value);
        } elseif ($escape === JSHTML_ESCAPED) {
            $value = str_replace(']]>', ']]\'+\'>', str_replace('</', '<\/', $value));
        } elseif ($escape === ID_ESCAPED) {
            $value = fix_id($value);
        } elseif ($escape === CSS_ESCAPED) {
            $value = preg_replace('#[^\w\#\.\-\%]#', '_', $value);
        } elseif ($escape === NAUGHTY_ESCAPED) {
            $value = filter_naughty_harsh($value, true);
        }
    }
    if (($GLOBALS['XSS_DETECT']) && ($escaped !== array())) {
        ocp_mark_as_escaped($value);
    }

    return $value;
}

/**
 * This will create a new Tempcode object that is containing a single specifed language string ID
 *
 * @param  ID_TEXT $lang_string The ID of the language string to use
 * @param  ?mixed $token1 The first token [string or Tempcode] (replaces {1}) (null: none)
 * @param  ?mixed $token2 The second token [string or Tempcode] (replaces {2}) (null: none)
 * @param  ?mixed $token3 The third token (replaces {3}). May be an array of [of string], to allow any number of additional args (null: none)
 * @return Tempcode A language Tempcode object
 */
function do_lang_tempcode($lang_string, $token1 = null, $token2 = null, $token3 = null)
{
    $parameters = array();
    if (isset($token1)) {
        $parameters[] = $token1;
    }
    if (isset($token2)) {
        $parameters[] = $token2;
    }
    if (isset($token3)) {
        if (!is_array($token3)) {
            $parameters[] = $token3;
        } else {
            $parameters = array_merge($parameters, $token3);
        }
    }

    return build_closure_tempcode(TC_LANGUAGE_REFERENCE, $lang_string, $parameters);
}

/**
 * Provide automatic escaping for a template call.
 *
 * @param  array $parameters Template parameters
 */
function kid_gloves_html_escaping(&$parameters)
{
    if (get_value('kid_gloves_html') === '0') {
        return;
    }

    global $KNOWN_TRUE_HTML;

    $param = mixed();
    foreach ($parameters as &$param) {
        if (is_string($param)) {
            if ((strpos($param, "'") !== false) || (strpos($param, '"') !== false) || (strpos($param, '<') !== false) || (strpos($param, '>') !== false)) {
                if (!isset($KNOWN_TRUE_HTML[$param])) {
                    $param = escape_html($param);
                }
            }
        } elseif (is_array($param)) {
            kid_gloves_html_escaping($param);
        }
    }
}

/**
 * Provide automatic escaping for a particular parameter.
 *
 * @param  string $param Parameter
 */
function kid_gloves_html_escaping_singular(&$param)
{
    if (get_value('kid_gloves_html') === '0') {
        return;
    }

    global $KNOWN_TRUE_HTML;

    if ((strpos($param, "'") !== false) || (strpos($param, '"') !== false) || (strpos($param, '<') !== false) || (strpos($param, '>') !== false)) {
        if (!isset($KNOWN_TRUE_HTML[$param])) {
            $param = escape_html($param);
        }
    }
}

/**
 * Work out if we're doing a template preview op.
 */
function fill_template_preview_op_cache()
{
    global $IS_TEMPLATE_PREVIEW_OP_CACHE;
    $IS_TEMPLATE_PREVIEW_OP_CACHE = array_key_exists('template_preview_op', $_POST) && ($_POST['template_preview_op'] == '1') && ((get_page_name() != 'admin_themes') || (get_param_string('type', '') == 'view'));
}

/**
 * Get a Tempcoded version of a Composr template. It is perhaps the most common Composr function to load up templates using do_template, and then attach them together either as parameters to each other, or via the Tempcode attach method.
 *
 * @param  ID_TEXT $codename The codename of the template being loaded
 * @param  ?array $parameters A map of parameters for the template (key to value); you can have any number of parameters of any name, there is no set standard; having a _GUID parameter of random value is a convention (null: no parameters)
 * @param  ?LANGUAGE_NAME $lang The language to load the template in (templates can embed language references) (null: users own language)
 * @param  boolean $light_error Whether to not produce a stack dump if the template is missing
 * @param  ?ID_TEXT $fallback Alternate template to use if the primary one does not exist (null: none)
 * @param  string $suffix File type suffix of template file (e.g. .tpl)
 * @set    .tpl .js .xml .txt .css
 * @param  string $directory Subdirectory type to look in
 * @set    templates javascript xml text css
 * @param  ?ID_TEXT $theme Theme to use (null: current theme)
 * @return Tempcode The Tempcode for this template
 */
function do_template($codename, $parameters = null, $lang = null, $light_error = false, $fallback = null, $suffix = '.tpl', $directory = 'templates', $theme = null)
{
    if (empty($lang)) {
        global $USER_LANG_CACHED;
        $lang = isset($USER_LANG_CACHED) ? $USER_LANG_CACHED : (function_exists('user_lang') ? user_lang() : 'EN');
    }

    if ($GLOBALS['SEMI_DEV_MODE']) {
        if (($codename === strtolower($codename)) && ($directory === 'templates')) {
            fatal_exit('Template names should be in upper case, and the files should be stored in upper case (' . $codename . ').');
        }

        if ($codename !== 'MENU_BRANCH_dropdown'/*optimisation*/) {
            if ((substr($codename, -7) === '_SCREEN') || (substr($codename, -8) === '_OVERLAY') || ($codename === 'POOR_XHTML_WRAPPER')) {
                $GLOBALS['SCREEN_TEMPLATE_CALLED'] = $codename;
            }
        }
    }

    if (($parameters !== null) && (function_exists('has_solemnly_declared')) && (!has_solemnly_declared(I_UNDERSTAND_XSS))) {
        kid_gloves_html_escaping($parameters);
    }

    global $IS_TEMPLATE_PREVIEW_OP_CACHE, $RECORD_TEMPLATES_USED, $RECORD_TEMPLATES_TREE, $RECORDED_TEMPLATES_USED, $RECORD_LANG_STRINGS, $FILE_ARRAY, $KEEP_MARKERS, $SHOW_EDIT_LINKS, $XHTML_SPIT_OUT, $CACHE_TEMPLATES, $FORUM_DRIVER, $POSSIBLY_IN_SAFE_MODE_CACHE, $USER_THEME_CACHE, $TEMPLATE_DISK_ORIGIN_CACHE, $LOADED_TPL_CACHE;
    if ($IS_TEMPLATE_PREVIEW_OP_CACHE === null) {
        fill_template_preview_op_cache();
    }
    $special_treatment = ((($KEEP_MARKERS) || ($SHOW_EDIT_LINKS)) && ($XHTML_SPIT_OUT === null));

    if ($RECORD_TEMPLATES_USED) {
        $RECORDED_TEMPLATES_USED[] = $directory . '/' . $codename . $suffix;
    }

    // Variables we'll need
    if (!isset($theme)) {
        $theme = isset($USER_THEME_CACHE) ? $USER_THEME_CACHE : (((isset($FORUM_DRIVER)) && (is_object($FORUM_DRIVER)) && (method_exists($FORUM_DRIVER, 'get_theme'))) ? filter_naughty($FORUM_DRIVER->get_theme()) : 'default');
    }
    $prefix_default = get_file_base() . '/themes/';
    $prefix = get_custom_file_base() . '/themes/';

    // Is it structurally cached on disk yet?
    if (!isset($TEMPLATE_DISK_ORIGIN_CACHE[$codename][$lang][$theme][$suffix][$directory])) {
        $loaded_this_once = false;
    } else {
        $loaded_this_once = true;
    }
    $_data = mixed();
    $_data = false;

    // Load from run-time cache?
    if (isset($LOADED_TPL_CACHE[$codename][$lang][$theme][$suffix][$directory])) {
        // We have run-time caching
        $_data = $LOADED_TPL_CACHE[$codename][$lang][$theme][$suffix][$directory];
    }

    // Find where template is on disk
    if (!isset($TEMPLATE_DISK_ORIGIN_CACHE[$codename][$lang][$theme][$suffix][$directory])) {
        $found = find_template_place($codename, $lang, $theme, $suffix, $directory);
        $TEMPLATE_DISK_ORIGIN_CACHE[$codename][$lang][$theme][$suffix][$directory] = $found;
    } else {
        $found = $TEMPLATE_DISK_ORIGIN_CACHE[$codename][$lang][$theme][$suffix][$directory];
    }

    // Load from template cache?
    if (($CACHE_TEMPLATES) && ($_data === false) && (/*the following relates to ensuring a full recompile for INCLUDEs except for CSS and JS*/($parameters === null) || ((!$RECORD_TEMPLATES_USED) && (!$RECORD_TEMPLATES_TREE))) && (!$IS_TEMPLATE_PREVIEW_OP_CACHE) && (!$RECORD_LANG_STRINGS/*Tempcode compilation embeds lang strings*/) && ((!$POSSIBLY_IN_SAFE_MODE_CACHE) || (isset($GLOBALS['SITE_INFO']['safe_mode'])) || (!in_safe_mode()))) {
        if ($found !== null) {
            $tcp_path = $prefix . $theme . '/templates_cached/' . $lang . '/' . $codename . $found[2] . '.tcp';
            if ($loaded_this_once) {
                $may_use_cache = true;
            } else {
                // We need to support smart-decaching
                global $SITE_INFO;
                $support_smart_decaching = support_smart_decaching();
                $found_disk_file = false;
                if ($support_smart_decaching) {
                    if (get_custom_file_base() !== get_file_base()) {
                        $file_path = get_custom_file_base() . '/themes/' . $found[0] . $found[1] . $codename . $found[2];
                        if (is_file($file_path)) {
                            $found_disk_file = true;
                        } else {
                            $file_path = get_file_base() . '/themes/' . $found[0] . $found[1] . $codename . $found[2];
                            if (is_file($file_path)) {
                                $found_disk_file = true;
                            }
                        }
                    } else {
                        $file_path = get_custom_file_base() . '/themes/' . $found[0] . $found[1] . $codename . $found[2];
                        if (is_file($file_path)) {
                            $found_disk_file = true;
                        }
                    }
                    if (GOOGLE_APPENGINE) {
                        gae_optimistic_cache(true);
                    }
                    $tcp_time = @filemtime($tcp_path);
                    if (GOOGLE_APPENGINE) {
                        gae_optimistic_cache(false);
                    }
                }

                $may_use_cache = false;
                if ((!$support_smart_decaching) || (($tcp_time !== false) && ($found_disk_file))/*if in install can be found yet no file at path due to running from data.cms*/ && ($found !== null)) {
                    if ((!$support_smart_decaching) || ((filemtime($file_path) < $tcp_time) && ((empty($SITE_INFO['dependency__' . $file_path])) || (dependencies_are_good(explode(',', $SITE_INFO['dependency__' . $file_path]), $tcp_time))))) {
                        $may_use_cache = true;
                    }
                }
            }

            if ($may_use_cache) {
                $_data = new Tempcode();
                $test = $_data->from_assembly_executed($tcp_path, array($codename, $codename, $lang, $theme, $suffix, $directory, $fallback));
                if (!$test) {
                    $_data = false; // failed
                }
            }
        }
    }

    // Compile?
    if ($_data === false) { // No, it's not
        if ($found === null) {
            if ($fallback === null) {
                if ($light_error) {
                    return paragraph(do_lang_tempcode('MISSING_TEMPLATE_FILE', escape_html($directory . '/' . $codename . $suffix)), '34rwefwfdee');
                }
                fatal_exit(do_lang_tempcode('MISSING_TEMPLATE_FILE', escape_html($directory . '/' . $codename . $suffix)));
            } else {
                $result = do_template($fallback, $parameters, $lang, $light_error, null, $suffix, $directory, $theme);
                return $result;
            }
        } else {
            require_code('tempcode_compiler');
            $_data = _do_template($found[0], $found[1], $codename, $codename, $lang, $found[2], $theme);
        }
    }

    if ((($loaded_this_once) || (($suffix == '.tpl') && (substr($codename, -7) !== '_SCREEN'))) && (!isset($LOADED_TPL_CACHE[$codename][$lang][$theme][$suffix][$directory]))) { // On 3rd load (and onwards) it will be fully cached (1st = from disk with smart-decaching, 2nd = from disk [now], 3rd = from run-time cache)
        // Set run-time cache
        $LOADED_TPL_CACHE[$codename][$lang][$theme][$suffix][$directory] = $_data;
    }

    // Optimisation
    if (!isset($parameters)) { // Streamlined if no parameters involved
        $out = new Tempcode();
        $out->codename = $codename;
        $out->code_to_preexecute = $_data->code_to_preexecute;
        if (!$GLOBALS['OUTPUT_STREAMING']) {
            $out->preprocessable_bits = $_data->preprocessable_bits;
        }
        $out->seq_parts = $_data->seq_parts;

        foreach ($out->seq_parts as &$seq_parts_group) {
            foreach ($seq_parts_group as &$seq_part) {
                if ($seq_part[1] !== array()) {
                    $seq_part[1] = array();
                }
            }
        }

        return $out;
    }

    // Bind parameters
    $ret = $_data->bind($parameters, $codename);
    if ($special_treatment) {
        $ret->codename = '(mixed)'; // Stop optimisation that assumes the codename represents the sole content of it
    }

    // Special rendering modes
    if ($special_treatment) {
        if ($KEEP_MARKERS) {
            $__data = new Tempcode();
            $__data->attach('<!-- START-TEMPLATE=' . escape_html($codename) . ' -->');
            $__data->attach($ret);
            $__data->attach('<!-- END-TEMPLATE=' . escape_html($codename) . ' -->');
            $ret = $__data;
        }
        if (($SHOW_EDIT_LINKS) && ($codename !== 'PARAM_INFO') && ($codename !== 'TEMPLATE_EDIT_LINK') && ($codename !== 'GLOBAL_HTML_WRAP'/*For some obscure reason letting this go through causes content to disappear, maybe because it has already started output streaming*/)) {
            $edit_url = build_url(array('page' => 'admin_themes', 'type' => '_edit_templates', 'theme' => $theme, 'f0file' => $directory . '/' . $codename . $suffix), 'adminzone');

            $parameters2 = array();
            foreach ($parameters as $k => $v) {
                if (is_array($v)) {
                    $parameters2[$k] = '(array)';
                } elseif (!is_object($v)) {
                    $parameters2[$k] = $v;
                } else {
                    $parameters2[$k] = $v->evaluate();
                    if (strlen($parameters2[$k]) > 100) {
                        $parameters2[$k] = substr($parameters2[$k], 0, 100) . '...';
                    }
                }
            }
            $param_info = do_template('PARAM_INFO', array('_GUID' => '0070acad5e82e0877ad49e25283d342e', 'MAP' => $parameters2));

            $ret = do_template('TEMPLATE_EDIT_LINK', array('_GUID' => '511ae911d31a5b237a4371ff22fc78fd', 'PARAM_INFO' => $param_info, 'CONTENTS' => $ret, 'CODENAME' => $codename, 'EDIT_URL' => $edit_url));
        }
    }

    return $ret;
}

/**
 * Do a smart decache dependency check for the case of multiple files.
 *
 * @param  array $dep Dependent files (full file paths)
 * @param  TIME $tcp_time Time of cache file
 * @return boolean Whether decache is NOT needed
 */
function dependencies_are_good($dep, $tcp_time)
{
    foreach ($dep as $d) {
        if (@filemtime($d) > $tcp_time) {
            return false;
        }
    }
    return true;
}

/**
 * Certain symbols need preprocessing, before the output stream is made.
 *
 * @param  array $seq_part Symbol details
 * @param  array $children Where we store children stuff
 */
function handle_symbol_preprocessing($seq_part, &$children)
{
    switch ($seq_part[2]) {
        case 'PAGE_LINK':
            $param = $seq_part[3];

            if (isset($param[0])) {
                if (isset($param[0]->codename/*faster than is_object*/)) {
                    $param[0] = $param[0]->evaluate();
                }

                list(, $url_parts,) = page_link_decode($param[0]);

                if ((!isset($url_parts['id'])) && (!array_key_exists('id', $url_parts))) {
                    return;
                }
                if ((!isset($url_parts['type'])) && (!array_key_exists('type', $url_parts))) {
                    $url_parts['type'] = 'browse';
                }
                if ($url_parts['type'] === null) {
                    $url_parts['type'] = 'browse'; // null means "do not take from environment"; so we default it to 'browse' (even though it might actually be left out when URL Schemes are off, we know it cannot be for URL Schemes)
                }
                if (!array_key_exists('page', $url_parts)) {
                    return;
                }
                if ($url_parts['id'] === null) {
                    $url_parts['id'] = strval(db_get_first_id());
                }

                // Does this URL arrangement support monikers?
                global $LOADED_MONIKERS_CACHE;
                if (!isset($LOADED_MONIKERS_CACHE[$url_parts['type']][$url_parts['page']][$url_parts['id']])) {
                    global $CONTENT_OBS;
                    load_moniker_hooks();
                    $found = false;
                    $looking_for = '_SEARCH:' . $url_parts['page'] . ':' . $url_parts['type'] . ':_WILD';

                    $ob_info = isset($CONTENT_OBS[$looking_for]) ? $CONTENT_OBS[$looking_for] : null;
                    if ($ob_info !== null) {
                        $LOADED_MONIKERS_CACHE[$url_parts['type']][$url_parts['page']][$url_parts['id']] = true; // Indicator to preload this
                    }
                }
            }
            return;

        case 'INCLUDE':
            if ($GLOBALS['RECORD_TEMPLATES_USED'] || $GLOBALS['RECORD_TEMPLATES_TREE']) {
                $param = $seq_part[3];

                if (!isset($param[1])) {
                    $param[1] = make_string_tempcode('.tpl');
                }
                if (!isset($param[2])) {
                    $param[2] = make_string_tempcode('templates');
                }

                $tpl_path_descrip = (is_object($param[2]) ? $param[2]->evaluate() : $param[2]) . '/' . (is_object($param[0]) ? $param[0]->evaluate() : $param[0]) . (is_object($param[1]) ? $param[1]->evaluate() : $param[1]);

                if ($GLOBALS['RECORD_TEMPLATES_USED']) {
                    $GLOBALS['RECORDED_TEMPLATES_USED'][] = $tpl_path_descrip;
                }

                if ($GLOBALS['RECORD_TEMPLATES_TREE']) {
                    $param = $seq_part[3];
                    $children[] = array(
                        $tpl_path_descrip,
                        isset($param[1]->children) ? $param[1]->children : array(),
                        isset($param[1]->fresh) ? $param[1]->fresh : false
                    );
                }
            }
            return;

        case 'SET_TITLE':
            $param = $seq_part[3];

            if (array_key_exists(0, $param)) {
                get_screen_title(is_object($param[0]) ? $param[0]->evaluate() : $param[0], false);
            }
            return;

        case 'SET':
            $param = $seq_part[3];

            if (isset($param[1])) {
                global $TEMPCODE_SETGET;
                if (count($param) == 2) {
                    $TEMPCODE_SETGET[isset($param[0]->codename/*faster than is_object*/) ? $param[0]->evaluate() : $param[0]] = $param[1];
                } else {
                    $param_copy = array();
                    foreach ($param as $i => $x) {
                        if ($i !== 0) {
                            $param_copy[] = isset($x->codename/*faster than is_object*/) ? $x->evaluate() : $x;
                        }
                    }
                    $TEMPCODE_SETGET[isset($param[0]->codename/*faster than is_object*/) ? $param[0]->evaluate() : $param[0]] = implode(',', $param_copy);
                }
                if (($GLOBALS['RECORD_TEMPLATES_TREE']) && (is_object($param[1]))) {
                    $children[] = array(':set: ' . (is_object($param[0]) ? $param[0]->evaluate() : $param[0]), isset($param[1]->children) ? $param[1]->children : array(), isset($param[1]->fresh) ? $param[1]->fresh : false);
                }
            }
            return;

        case 'BLOCK':
            $param = $seq_part[3];

            foreach ($param as $i => $p) {
                if (isset($p->codename/*faster than is_object*/)) {
                    $param[$i] = $p->evaluate();
                }
            }

            if ((count($param) == 1) && (strpos($param[0], ',') !== false)) { // NB: This code is also in symbols.php
                $param = block_params_str_to_arr($param[0], true);
            }

            foreach ($param as &$_param) {
                $_param = preg_replace('#^\s*([^\s]+)\s*=#', '$1=', $_param);
            }

            if (in_array('defer=1', $param)) {
                // Nothing has to be done here, except preparing for AJAX
                require_javascript('ajax');
            } else {
                global $REQUEST_BLOCK_NEST_LEVEL;

                global $BLOCKS_CACHE;
                if (isset($BLOCKS_CACHE[serialize($param)])) {
                    $REQUEST_BLOCK_NEST_LEVEL--;
                    return;
                }

                $REQUEST_BLOCK_NEST_LEVEL++;
                if ($REQUEST_BLOCK_NEST_LEVEL > 40) { // 100 caused xdebug error, but Composr will have some overhead in both error handler and other code to get to here. We want xdebug error to not show, but of course to provide the same benefits as that error.
                    $REQUEST_BLOCK_NEST_LEVEL = 0;
                    $BLOCKS_CACHE[serialize($param)] = do_lang_tempcode('INTERNAL_ERROR');
                    attach_message(do_lang_tempcode('STOPPED_RECURSIVE_RESOURCE_INCLUDE', escape_html(is_string($param[0]) ? $param[0] : 'block'), escape_html(do_lang('BLOCK'))), 'warn');
                    return;
                }

                $block_parms = array();
                foreach ($param as &$_param) {
                    $block_parts = explode('=', $_param, 2);
                    if (!isset($block_parts[1])) {
                        $BLOCKS_CACHE[serialize($param)] = make_string_tempcode(do_lang('INTERNAL_ERROR') . ' (bad block parameter: ' . escape_html($_param) . ')');
                        return;
                    }
                    list($key, $val) = $block_parts;
                    $block_parms[$key] = $val;
                }

                if ((isset($_GET['keep_show_loading'])) && ($_GET['keep_show_loading'] == '1')) {
                    require_code('files');
                    $before = memory_get_usage();
                }
                if (isset($block_parms['block'])) {
                    $b_value = do_block(trim($block_parms['block']), $block_parms);
                    if ((isset($_GET['keep_show_loading'])) && ($_GET['keep_show_loading'] == '1')) {
                        if (function_exists('attach_message')) {
                            attach_message('block: ' . $block_parms['block'] . ' (' . clean_file_size(memory_get_usage() - $before) . ' used, now at ' . integer_format(memory_get_usage()) . ')', 'inform');
                        } else {
                            @ob_end_flush();
                            @ob_end_flush();
                            @ob_end_flush();
                            print('<!-- block: ' . htmlentities($block_parms['block']) . ' (' . htmlentities(clean_file_size(memory_get_usage() - $before)) . ' used, now at ' . htmlentities(integer_format(memory_get_usage())) . ') -->' . "\n");
                            flush();
                        }
                    }

                    if ($GLOBALS['RECORD_TEMPLATES_TREE']) {
                        $children[] = array(':block: ' . $block_parms['block'], array(array($b_value->codename, isset($b_value->children) ? $b_value->children : array(), isset($b_value->fresh) ? $b_value->fresh : false)), true);
                    }
                    $b_value->handle_symbol_preprocessing();

                    $BLOCKS_CACHE[serialize($param)] = $b_value;
                }

                $REQUEST_BLOCK_NEST_LEVEL--;
            }

            return;

        case 'REQUIRE_JAVASCRIPT':
            if (isset($param[0])) {
                $param = $seq_part[3];
                foreach ($param as $i => $p) {
                    if (is_object($p)) {
                        $param[$i] = $p->evaluate();
                    }
                }

                require_javascript($param[0]);
            }
            return;

        case 'FACILITATE_AJAX_BLOCK_CALL':
            require_javascript('ajax');
            return;

        case 'CSS_INHERIT':

        case 'REQUIRE_CSS':
            if (isset($param[0])) {
                $param = $seq_part[3];
                foreach ($param as $i => $p) {
                    if (is_object($p)) {
                        $param[$i] = $p->evaluate();
                    }
                }

                require_css($param[0]);
            }
            return;

        case 'TRIM':
        case 'PARAGRAPH':
            $param = $seq_part[3];
            if ((isset($param[0])) && (is_object($param[0]))) {
                if ($GLOBALS['RECORD_TEMPLATES_TREE']) {
                    $param[0]->handle_symbol_preprocessing();
                    $children[] = array(':trim', isset($param[0]->children) ? $param[0]->children : array(), isset($param[0]->fresh) ? $param[0]->fresh : false, true);
                }
            }
            break;

        case 'LOAD_PANEL':
            $param = $seq_part[3];
            foreach ($param as $i => $p) {
                if (is_object($p)) {
                    $param[$i] = $p->evaluate();
                }
            }

            global $PANELS_CACHE;
            if (isset($PANELS_CACHE[serialize($param)])) {
                return;
            }

            if (array_key_exists(0, $param)) {
                if (substr(get_page_name(), 0, 6) !== 'panel_') {
                    if (strpos($param[0], ':') !== false) {
                        $param = array_reverse(explode(':', $param[0], 2));
                    }
                    if (substr($param[0], 0, 6) == 'panel_') {
                        $param[0] = substr($param[0], 6);
                    }

                    global $ZONE;
                    $wide_high = is_wide_high();
                    $wide = is_wide();
                    if ((($wide == 0) || (($wide_high == 0) && (($param[0] == 'bottom') || ($param[0] == 'top')))) && ((get_option('site_closed') == '0') || ($GLOBALS['IS_ACTUALLY_ADMIN']) || (has_privilege(get_member(), 'access_closed_site')))) {
                        if ((isset($_GET['keep_show_loading'])) && ($_GET['keep_show_loading'] == '1')) {
                            require_code('files');
                            $before = memory_get_usage();
                        }
                        $tp_value = request_page('panel_' . $param[0], false, array_key_exists(1, $param) ? $param[1] : null, null);
                        if ((isset($_GET['keep_show_loading'])) && ($_GET['keep_show_loading'] == '1')) {
                            if (function_exists('attach_message')) {
                                attach_message('panel: ' . 'panel_' . $param[0] . ' (' . clean_file_size(memory_get_usage() - $before) . ' bytes used, now at ' . number_format(memory_get_usage()) . ')', 'inform');
                            } else {
                                @ob_end_flush();
                                @ob_end_flush();
                                @ob_end_flush();
                                print('<!-- panel: ' . htmlentities('panel_' . $param[0]) . ' (' . htmlentities(clean_file_size(memory_get_usage() - $before)) . ' bytes used, now at ' . htmlentities(number_format(memory_get_usage())) . ') -->' . "\n");
                                flush();
                            }
                        }

                        $tp_value->handle_symbol_preprocessing();
                        if ($GLOBALS['RECORD_TEMPLATES_TREE']) {
                            $children[] = array(':panel: ' . $param[0], array(array($tp_value->codename, isset($tp_value->children) ? $tp_value->children : array(), isset($tp_value->fresh) ? $tp_value->fresh : false)), true);
                        }

                        $value = $tp_value->evaluate();
                    } else {
                        $value = '';
                    }
                } else {
                    $value = '';
                }
            } else {
                $value = '';
            }

            $PANELS_CACHE[serialize($param)] = $value;

            return;

        case 'JS_TEMPCODE':
            if ($GLOBALS['RECORD_TEMPLATES_TREE']) {
                $param = $seq_part[3];
                foreach ($param as $i => $p) {
                    if (is_object($p)) {
                        $param[$i] = $p->evaluate();
                    }
                }

                $temp = javascript_tempcode(array_key_exists(0, $param) ? $param[0] : null);

                $children[] = array(':container', isset($temp->children) ? $temp->children : array(), isset($temp->fresh) ? $temp->fresh : false);
            }
            return;

        case 'CSS_TEMPCODE':
            if ($GLOBALS['RECORD_TEMPLATES_TREE']) {
                $param = $seq_part[3];

                $temp = css_tempcode();

                $children[] = array(':container', isset($temp->children) ? $temp->children : array(), isset($temp->fresh) ? $temp->fresh : false);
            }
            return;

        case 'LOAD_PAGE':
            $param = $seq_part[3];
            foreach ($param as $i => $p) {
                if (is_object($p)) {
                    $param[$i] = $p->evaluate();
                }
            }

            global $PAGES_CACHE;
            if (array_key_exists(serialize($param), $PAGES_CACHE)) {
                return;
            }

            if (array_key_exists(0, $param)) {
                if (strpos($param[0], ':') !== false) {
                    $param = array_reverse(explode(':', $param[0], 2));
                }

                $being_included = (!array_key_exists(2, $param)) || ($param[2] == '1');
                $virtual_state = (array_key_exists(3, $param)) && ($param[3] == '1');

                if ((isset($_GET['keep_show_loading'])) && ($_GET['keep_show_loading'] == '1')) {
                    require_code('files');
                    $before = memory_get_usage();
                }

                $page = $param[0];
                $zone = array_key_exists(1, $param) ? $param[1] : get_comcode_zone($param[0], false);

                if ($virtual_state) {
                    // Virtualised state, so that any nested main_comcode_page_children blocks execute correctly
                    require_code('urls2');
                    list($old_get, $old_zone, $old_current_script) = set_execution_context(
                        array('page' => $page),
                        $zone
                    );
                }

                $tp_value = request_page($page, false, $zone, null, $being_included);

                if ($virtual_state) {
                    $tp_value = make_string_tempcode($tp_value->evaluate());

                    // Get things back to prior state
                    set_execution_context(
                        $old_get,
                        $old_zone,
                        $old_current_script,
                        false
                    );
                }

                if ((isset($_GET['keep_show_loading'])) && ($_GET['keep_show_loading'] == '1')) {
                    if (function_exists('attach_message')) {
                        attach_message('page: ' . $param[0] . ' (' . clean_file_size(memory_get_usage() - $before) . ' bytes used, now at ' . number_format(memory_get_usage()) . ')', 'inform');
                    } else {
                        @ob_end_flush();
                        @ob_end_flush();
                        @ob_end_flush();
                        print('<!-- page: ' . htmlentities($param[0]) . ' (' . htmlentities(clean_file_size(memory_get_usage() - $before)) . ' bytes used, now at ' . htmlentities(number_format(memory_get_usage())) . ') -->' . "\n");
                        flush();
                    }
                }
                if ($GLOBALS['RECORD_TEMPLATES_TREE']) {
                    $children[] = array(':page: ' . $param[0], isset($tp_value->children) ? $tp_value->children : array(), isset($tp_value->fresh) ? $tp_value->fresh : false);
                }
            } else {
                $tp_value = new Tempcode();
            }

            $PAGES_CACHE[serialize($param)] = $tp_value;

            return;

        case 'FRACTIONAL_EDITABLE':
            require_javascript('ajax');
            require_javascript('fractional_edit');
            return;

        case 'INSERT_SPAMMER_BLACKHOLE':
            if (get_bot_type() === null) {
                $GLOBALS['STATIC_CACHE_ENABLED'] = false;
            }
            return;
    }
}

/**
 * Tempcode (compiled implementation).
 *
 * @package    core
 */
class Tempcode
{
    public $code_to_preexecute;
    public $seq_parts; // List of list of closure pairs: (0) function name, and (1) parameters, (2) type, (3) name         We use a 2D list to make attach ops very fast
    public $preprocessable_bits; // List of tuples: escape (ignored), type (e.g. TC_SYMBOL), name, parameters
    public $pure_lang;
    public $evaluate_echo_offset_group = 0;
    public $evaluate_echo_offset_inner = 0;

    public $codename = ':container'; // The name of the template it came from

    public $preprocessed = false;
    public $cached_output;

    public $children = null;
    public $fresh = null;
    public $is_empty;

    /**
     * Constructor of Tempcode
     *
     * @param  ?array $details Pair: Code to preexecute, Initialisation seq-parts (null: start as empty)
     */
    public function __construct($details = null)
    {
        $this->cached_output = null;

        if (!isset($details)) {
            $this->preprocessable_bits = array();
            $this->seq_parts = array();
            $this->code_to_preexecute = array();
        } else {
            $this->code_to_preexecute = $details[0];
            $this->seq_parts = $details[1];

            if (!$GLOBALS['OUTPUT_STREAMING']) {
                $pp_bits = array();

                foreach ($this->seq_parts as $seq_parts_group) {
                    foreach ($seq_parts_group as $seq_part) {
                        if ($seq_part[2] === TC_SYMBOL) {
                            switch ($seq_part[3]) {
                                case 'REQUIRE_CSS':
                                case 'REQUIRE_JAVASCRIPT':
                                case 'FACILITATE_AJAX_BLOCK_CALL':
                                case 'JS_TEMPCODE':
                                case 'CSS_TEMPCODE':
                                case 'SET':
                                case 'SET_TITLE':
                                case 'BLOCK':
                                case 'PAGE_LINK':
                                case 'LOAD_PAGE':
                                case 'LOAD_PANEL':
                                case 'INSERT_SPAMMER_BLACKHOLE':
                                    $pp_bits[] = array(array(), TC_SYMBOL, $seq_part[3], $seq_part[1]);
                                    break;
                            }
                        } elseif ($seq_part[2] === TC_DIRECTIVE) {
                            switch ($seq_part[3]) {
                                case 'INCLUDE':
                                case 'FRACTIONAL_EDITABLE':
                                    $pp_bits[] = array(array(), TC_DIRECTIVE, $seq_part[3], $seq_part[1]);
                                    break;
                            }
                        }
                        foreach ($seq_part[1] as $param) {
                            if (isset($param->preprocessable_bits)) { // If is a Tempcode object
                                foreach ($param->preprocessable_bits as $b) {
                                    $pp_bits[] = $b;
                                }
                            }
                        }
                    }
                }

                $this->preprocessable_bits = $pp_bits;
            } else {
                $this->preprocessable_bits = array();
            }
        }

        if ($GLOBALS['RECORD_TEMPLATES_TREE']) {
            $this->fresh = true;
            $this->children = array();
        }
    }

    /**
     * PHP magic function to handle serialisation.
     *
     * @return array What is to be serialised
     */
    public function __sleep()
    {
        return array('code_to_preexecute', 'seq_parts', 'preprocessable_bits', 'pure_lang', 'codename');
    }

    /**
     * Remove any internal evaluation cachings within the object.
     */
    public function decache()
    {
        foreach ($this->seq_parts as &$seq_parts_group) {
            foreach ($seq_parts_group as &$seq_part) {
                foreach ($seq_part[1] as $val) {
                    if (isset($val->codename/*faster than is_object*/)) {
                        $val->decache();
                    }
                }
            }
        }
        $this->cached_output = null;
    }

    /**
     * Parse a single symbol from an input stream and append it.
     *
     * @param  string $code Code string (input stream)
     * @param  integer $pos Start position of input string
     * @param  integer $len End position of input string
     */
    public function parse_from($code, $pos, $len)
    {
        $this->cached_output = null;
        require_code('tempcode_compiler');
        $temp = template_to_tempcode(substr($code, $pos, $len - $pos), 0, false, '');
        $this->code_to_preexecute = $temp->code_to_preexecute;
        $this->seq_parts = $temp->seq_parts;
        $this->preprocessable_bits = $temp->preprocessable_bits;
    }

    /**
     * Attach the specified Tempcode to the right of the current Tempcode object.
     *
     * @param  mixed $attach The Tempcode/string to attach
     * @param  boolean $avoid_child_merge If we've already merged the children from what we're attaching into the child tree (at bind stage)
     */
    public function attach($attach, $avoid_child_merge = false)
    {
        if ($attach === '') {
            return;
        }

        unset($this->is_empty);

        $this->cached_output = null;

        if (isset($attach->codename)/*faster than is_object*/) { // Consider it another piece of Tempcode
            foreach ($attach->seq_parts as $seq_part_group) {
                $this->seq_parts[] = $seq_part_group;
            }

            $this->code_to_preexecute += $attach->code_to_preexecute;

            if (!$GLOBALS['OUTPUT_STREAMING']) {
                foreach ($attach->preprocessable_bits as $b) {
                    $this->preprocessable_bits[] = $b;
                }
            }

            if ((!$avoid_child_merge) && ($GLOBALS['RECORD_TEMPLATES_TREE'])) {
                $this->children[] = array($attach->codename, isset($attach->children) ? $attach->children : array(), isset($attach->fresh) ? $attach->fresh : false);
            }
        } else { // Consider it a string
            if (end($this->seq_parts) !== false) {
                $end = &$this->seq_parts[key($this->seq_parts)];
            } else {
                $this->seq_parts[] = array();
                $end = &$this->seq_parts[0];
            }

            static $generator_base = null;
            static $generator_num = 0;
            if ($generator_base === null) {
                $generator_base = uniqid('', true);
            }
            $generator_num++;

            $myfunc = 'string_attach_' . $generator_base . '_' . strval($generator_num);/*We'll inline it actually rather than calling, for performance   fast_uniqid()*/
            $funcdef = "\$tpl_funcs['$myfunc']=\"echo \\\"" . php_addslashes_twice($attach) . "\\\";\";\n";
            $this->code_to_preexecute[$myfunc] = $funcdef;
            $end[] = array($myfunc, array(), TC_KNOWN, '', '');
        }

        $this->codename = '(mixed)';
    }

    /**
     * Assemble the current Tempcode object into a single serialised (compiled) Tempcode storage representation (parameters and certain symbols and not evaluated). The output of the function is language-tied.
     *
     * @return string The assembly result
     */
    public function to_assembly()
    {
        require_code('tempcode_optimiser');
        optimise_tempcode($this);

        return 'return unserialize("' . php_addslashes(serialize(array($this->seq_parts, $this->preprocessable_bits, $this->codename, $this->pure_lang, $this->code_to_preexecute))) . '");' . "\n";
    }

    /**
     * The opposite of to_assembly - it decodes a Tempcode storage representation and turns it into a proper Tempcode object. This version handles the result of evaled code.
     *
     * @param  PATH $file The file to load
     * @param  array $forced_reload_details List of parameters for a forced reload if required
     * @return boolean Success status (it can fail, if the compiled cache file is corrupt)
     */
    public function from_assembly_executed($file, $forced_reload_details)
    {
        if ($GLOBALS['RECORD_TEMPLATES_TREE']) {
            $this->fresh = false;
            $this->children = array();
        }

        $result = tempcode_include($file); // We don't eval on this because we want it to potentially be op-code cached by e.g. Zend Accelerator
        if (!is_array($result)) {
            return false; // May never get here, as PHP fatal errors can't be suppressed or skipped over
        }

        $this->cached_output = null;
        list($this->seq_parts, $this->preprocessable_bits, $this->codename, $this->pure_lang, $this->code_to_preexecute) = $result;
        if ($GLOBALS['OUTPUT_STREAMING']) {
            $this->preprocessable_bits = array();
        }

        if ($forced_reload_details[6] === null) {
            $forced_reload_details[6] = '';
        }
        if ((count($this->code_to_preexecute) > 10) && ($GLOBALS['CACHE_TEMPLATES'])) {
            // We don't actually use $code_to_preexecute, because it uses too much RAM and DB space throwing full templates into the caching. Instead we rewrite to custom load it whenever it's needed. This isn't inefficient due to normal opcode caching and optimizer opcode caching, and because we cache Tempcode object's evaluations at runtime so it can only happen once per screen view.
            $_file = (strpos($file, '\'') === false) ? $file : php_addslashes($file);
            $this->code_to_preexecute[] = 'if (($result=tempcode_include(\'' . $_file . '\'))===false) { $tmp=do_template(\'' . php_addslashes($forced_reload_details[0]) . '\',null,\'' . ((strpos($forced_reload_details[2], '\'') === false) ? $forced_reload_details[2] : php_addslashes($forced_reload_details[2])) . '\',false,\'' . (($forced_reload_details[6] === '') ? '' : ((strpos($forced_reload_details[6], '\'') === false) ? $forced_reload_details[6] : php_addslashes($forced_reload_details[6]))) . '\',\'' . ($forced_reload_details[4]) . '\',\'' . ($forced_reload_details[5]) . '\'); clearstatcache(); $tmp2=$GLOBALS[\'CACHE_TEMPLATES\']; if (!@is_file(\'' . $_file . '\')) { $GLOBALS[\'CACHE_TEMPLATES\']=false; } /*$GLOBALS[\'DEV_MODE\']?debug_eval($tmp->code_to_preexecute):*/eval($tmp->code_to_preexecute); $GLOBALS[\'CACHE_TEMPLATES\']=$tmp2; unset($tmp); }
            else { debug_eval($result[4]); unset($result); }';
            // NB: $GLOBALS[\'CACHE_TEMPLATES\']=false; is in case the template cache has been detected as broken, it prevents this branch running as it would fail again
        }

        if ($GLOBALS['XSS_DETECT']) {
            //$this->_mark_all_as_escaped();
        }

        return true;
    }

    /**
     * Recursively mark all parameters in this Tempcode as escaped. This is needed when loading from cache, as escape tainting data would have been lost.
     *
     * @param  boolean $top_level Whether this is the top-level call
     */
    protected function _mark_all_as_escaped($top_level = true)
    {
        static $done = array();

        foreach ($this->seq_parts as &$seq_parts_group) {
            foreach ($seq_parts_group as &$seq_part) {
                if (!isset($seq_part[1]['_escaped'])) {
                    foreach ($seq_part[1] as &$val) {
                        if (is_string($val)) {
                            ocp_mark_as_escaped($val);
                        } elseif (is_object($val)) {
                            $val->_mark_all_as_escaped(false);
                        }
                    }
                    if (!isset($seq_part[1][0])) { // Only if it's a parameter map, not a parameter list
                        $seq_part[1]['_escaped'] = true; // Temporarily mark as escaped. Many seq_parts share a referenced list of parameters, and its naive/slow to re-mark for each
                    }
                    $done[] = &$seq_part[1];
                }
            }
        }

        if ($top_level) {
            // Remove the escaping markers, as the escaping marking does not persist with serialisation
            foreach ($done as $d) {
                unset($d['_escaped']);
            }
            $done = array();
        }
    }

    /**
     * The opposite of to_assembly - it decodes a Tempcode storage representation and turns it into a proper Tempcode object.
     *
     * @param  string $raw_data The assembled Tempcode
     * @param  boolean $allow_failure Return error code on failure, rather than exiting
     * @return boolean Success status (it can fail, if the compiled cache file is corrupt)
     */
    public function from_assembly(&$raw_data, $allow_failure = false)
    {
        if ($GLOBALS['RECORD_TEMPLATES_TREE']) {
            $this->fresh = false;
            $this->children = array();
        }

        $result = /*$GLOBALS['DEV_MODE']?debug_eval($raw_data):*/@eval($raw_data);
        if ($result === false) {
            if ($allow_failure) {
                return false;
            }
            fatal_exit(@strval($php_errormsg));
        }

        $this->cached_output = null;
        list($this->seq_parts, $this->preprocessable_bits, $this->codename, $this->pure_lang, $this->code_to_preexecute) = $result;
        if ($GLOBALS['OUTPUT_STREAMING']) {
            $this->preprocessable_bits = array();
        }

        if ($GLOBALS['XSS_DETECT']) {
            $this->_mark_all_as_escaped();
        }

        return true;
    }

    /**
     * Find whether a construct within this Tempcode is parameterless.
     *
     * @param  integer $at Offset to the construct
     * @return boolean Whether it is parameterless
     */
    public function parameterless($at)
    {
        $i = 0;
        foreach ($this->seq_parts as $seq_parts_group) {
            foreach ($seq_parts_group as $seq_part) {
                if ($i === $at) {
                    return ($seq_part[1] === array());
                }
                $i++;
            }
        }
        return false;
    }

    /**
     * Bind the parameter bits, or recursively bind children (doesn't change self, returns a bound Tempcode object)
     *
     * @param  array $parameters Map of parameters to bind parameter bits to
     * @param  ID_TEXT $codename The codename of the template this Tempcode is from
     * @return Tempcode The new bound Tempcode object
     */
    public function bind(&$parameters, $codename)
    {
        if (!isset($parameters['_GUID'])) {
            $parameters['_GUID'] = '';

            $trace = debug_backtrace();
            $parameters['_GUID'] = isset($trace[3]) ? ($trace[3]['function'] . '/' . $trace[2]['function']) : (isset($trace[2]) ? $trace[2]['function'] : $trace[1]['function']);
        }

        $out = new Tempcode();
        $out->codename = $codename;
        $out->code_to_preexecute = $this->code_to_preexecute;
        if (!$GLOBALS['OUTPUT_STREAMING']) {
            foreach ($this->preprocessable_bits as $preprocessable_bit) {
                foreach ($preprocessable_bit[3] as $i => $param) {
                    if ((($preprocessable_bit[2] !== 'SET') || (($i >= 1))) && (isset($param->codename/*faster than is_object*/))) {
                        $preprocessable_bit[3][$i] = $param->bind($parameters, '<' . $codename . '>');
                    }
                }
                $out->preprocessable_bits[] = $preprocessable_bit;
            }
        }

        if ($GLOBALS['RECORD_TEMPLATES_TREE']) {
            $out->children = isset($this->children) ? $this->children : array();
            foreach ($parameters as $key => $parameter) {
                if (is_object($parameter)) {
                    if (count($parameter->preprocessable_bits) !== 0) {
                        $parameter->handle_symbol_preprocessing(); // Needed to force children to be populated. Otherwise it is possible but not definite that evaluation will result in children being pushed down.
                    }
                    $out->children[] = array($parameter->codename, isset($parameter->children) ? $parameter->children : array(), isset($parameter->fresh) ? $parameter->fresh : false);
                } elseif ((is_string($parameter)) && ($key === '_GUID')) {
                    $out->children[] = array(':guid', array(array(':' . $parameter, array(), true)), true);
                }
            }
        }
        foreach ($parameters as $key => $parameter) {
            $p_type = gettype($parameter);
            if ($p_type === 'string') {
                // Performance, this is most likely
            } elseif ($p_type === 'object') {
                if (isset($parameter->preprocessable_bits[0])) {
                    if (!$GLOBALS['OUTPUT_STREAMING']) {
                        foreach ($parameter->preprocessable_bits as $b) {
                            $out->preprocessable_bits[] = $b;
                        }
                    }
                } elseif ($parameter->is_empty_shell()) {
                    $parameters[$key] = ''; // Little optimisation to save memory
                }
            } elseif ($p_type === 'boolean') {
                $parameters[$key] = $parameter ? '1' : '0';
            } elseif (($p_type !== 'array') && ($p_type !== 'NULL')) {
                critical_error('PASSON', do_lang('NO_INTEGERS_TEMPLATE', escape_html($key)));
            }
        }

        $out->seq_parts[0] = array();
        foreach ($this->seq_parts as $seq_parts_group) {
            foreach ($seq_parts_group as $seq_part) {
                if ((($seq_part[0][0] !== 's') || (substr($seq_part[0], 0, 14) !== 'string_attach_')) && ($seq_part[2] !== TC_LANGUAGE_REFERENCE)) {
                    $seq_part[1] = &$parameters; // & is to preserve memory
                }
                $out->seq_parts[0][] = $seq_part;
            }
        }

        return $out;
    }

    /**
     * Replace the named parameter with a specific value. Hardly used, but still important. Note that this will bind to all kinds of things that might not normally take named parameters, like symbols; this should not cause problems though.
     *
     * @param  string $parameter Named parameter
     * @param  Tempcode $value Specific value
     */
    public function singular_bind($parameter, $value)
    {
        $this->cached_output = null;

        if ($this->seq_parts === array()) {
            return;
        }

        foreach ($this->seq_parts as &$seq_parts_group) {
            foreach ($seq_parts_group as &$seq_part) {
                if ((($seq_part[0][0] !== 's') || (substr($seq_part[0], 0, 14) !== 'string_attach_')) && ($seq_part[2] !== TC_LANGUAGE_REFERENCE)) {
                    $seq_part[1][$parameter] = $value;
                }
            }
        }

        if (!$GLOBALS['OUTPUT_STREAMING']) {
            if (isset($value->preprocessable_bits)) { // Is Tempcode
                foreach ($value->preprocessable_bits as $b) {
                    $this->preprocessable_bits[] = $b;
                }
            }
        }
    }

    /**
     * Scan this Tempcode for anything that needs to be symbol-preprocessed
     */
    public function handle_symbol_preprocessing()
    {
        if ($GLOBALS['OUTPUT_STREAMING']) {
            return;
        }
        if (isset($this->preprocessed) && $this->preprocessed) {
            return;
        }

        foreach ($this->preprocessable_bits as $seq_part) {
            handle_symbol_preprocessing($seq_part, $this->children);
        }

        $this->preprocessed = true;
    }

    /**
     * Find whether the Tempcode object entirely empty (devoid of anything evaluable), not just evaluates as empty. This is also useful if you want to avoid early evaluation, which will mess up GET/SET flow.
     * Does not perform an evaluation, so will not trigger any early pre-processing or out-of-order evaluation.
     *
     * @return boolean Whether it is entirely empty
     */
    public function is_empty_shell()
    {
        foreach ($this->seq_parts as $seq_parts_group) {
            if (isset($seq_parts_group[0])) {
                return false;
            }
        }
        return true;
    }

    /**
     * Find whether the Tempcode object is blank or not.
     *
     * @return boolean Whether the Tempcode object is empty
     */
    public function is_empty()
    {
        if ($this->cached_output !== null) {
            return strlen($this->cached_output) === 0;
        }
        if (isset($this->is_empty)) {
            return $this->is_empty;
        }

        if ($this->is_empty_shell()) { // Optimisation: empty
            $this->is_empty = true;
            return true;
        }

        ob_start();

        global $NO_EVAL_CACHE, $XSS_DETECT, $USER_LANG_CACHED, $KEEP_TPL_FUNCS, $MEMORY_OVER_SPEED, $FULL_RESET_VAR_CODE, $RESET_VAR_CODE, $DEV_MODE;

        if ($XSS_DETECT) {
            $before = @ini_get('ocproducts.xss_detect');
            safe_ini_set('ocproducts.xss_detect', '0');
        }

        $no_eval_cache_before = $NO_EVAL_CACHE;

        if (isset($USER_LANG_CACHED)) {
            $current_lang = $USER_LANG_CACHED;
        } else {
            if (!function_exists('user_lang')) {
                require_code('lang');
            }
            $current_lang = user_lang();
        }
        $cl = $current_lang;

        $first_of_long = isset($this->seq_parts[0][3]) || isset($this->seq_parts[3]); // We set this to know not to dig right through to determine emptiness, as this wastes cache memory (it's a tradeoff)
        $tpl_funcs = $KEEP_TPL_FUNCS;

        foreach ($this->seq_parts as $seq_parts_group) {
            foreach ($seq_parts_group as $seq_part) {
                $seq_part_0 = $seq_part[0];
                /*if ($DEV_MODE) {
                    if (!isset($tpl_funcs[$seq_part_0])) {
                        debug_eval($this->code_to_preexecute[$seq_part_0], $tpl_funcs);
                    }
                    if (($tpl_funcs[$seq_part_0][0] !== 'e') && (function_exists($tpl_funcs[$seq_part_0]))) {
                        debug_call_user_func($tpl_funcs[$seq_part_0], $seq_part[1], $current_lang, $seq_part[4]);
                    } else {
                        $parameters = $seq_part[1];
                        debug_eval($tpl_funcs[$seq_part_0], $tpl_funcs, $parameters, $cl);
                    }
                } else {*/
                if (!isset($tpl_funcs[$seq_part_0])) {
                    eval($this->code_to_preexecute[$seq_part_0]);
                }
                if (is_callable($tpl_funcs[$seq_part_0])) {
                    call_user_func($tpl_funcs[$seq_part_0], $seq_part[1], $current_lang, $seq_part[4]);
                } else {
                    $parameters = $seq_part[1];
                    eval($tpl_funcs[$seq_part_0]);
                }
                //}

                if ((($first_of_long) || ($MEMORY_OVER_SPEED)) && (ob_get_length() > 0)) { // We only quick exit on the first iteration, as we know we likely didn't spend much time getting to it- anything more and we finish so that we can cache for later use by evaluate/evaluate_echo
                    ob_end_clean();
                    if (!$no_eval_cache_before) {
                        $NO_EVAL_CACHE = $no_eval_cache_before;
                    }
                    if ($XSS_DETECT) {
                        safe_ini_set('ocproducts.xss_detect', $before);
                    }
                    $this->is_empty = false;
                    return false;
                }

                $first_of_long = false;
            }
        }

        $tmp = ob_get_clean();
        if ((!$MEMORY_OVER_SPEED) && (!$NO_EVAL_CACHE) && (!$GLOBALS['STUCK_ABORT_SIGNAL'])) {
            $this->cached_output = $tmp; // Optimisation to store it in here. We don't do the same for evaluate_echo as that's a final use case and hence it would be unnecessarily inefficient to store the result

            global $DECLARATIONS_STATE, $KNOWN_TRUE_HTML;
            if (defined('I_UNDERSTAND_XSS') && !$DECLARATIONS_STATE[I_UNDERSTAND_XSS]) {
                $KNOWN_TRUE_HTML[$tmp] = true;
            }
        }
        if (!$no_eval_cache_before) {
            $NO_EVAL_CACHE = $no_eval_cache_before;
        }
        if ($XSS_DETECT) {
            safe_ini_set('ocproducts.xss_detect', $before);
        }
        $ret = ($tmp === '');
        $this->is_empty = $ret;
        return $ret;
    }

    /**
     * Parses the current Tempcode object, then return the parsed string
     *
     * @return string The evaluated thing.
     */
    public function __toString()
    {
        return $this->evaluate();
    }

    /**
     * Parses the current Tempcode object, then return the parsed string
     *
     * @param  ?LANGUAGE_NAME $current_lang The language to evaluate with (null: current user's language)
     * @return string The evaluated thing. Voila, it's all over!
     */
    public function evaluate($current_lang = null)
    {
        if (isset($this->cached_output)) {
            return $this->cached_output;
        }
        if ($this->is_empty_shell()) { // Optimisation: empty
            $this->cached_output = '';
            return '';
        }

        global $NO_EVAL_CACHE, $MEMORY_OVER_SPEED, $USER_LANG_CACHED, $XSS_DETECT, $KEEP_TPL_FUNCS, $FULL_RESET_VAR_CODE, $RESET_VAR_CODE, $DEV_MODE, $KNOWN_TRUE_HTML, $DECLARATIONS_STATE;

        ob_start();

        if ($XSS_DETECT) {
            $before = @ini_get('ocproducts.xss_detect');
            safe_ini_set('ocproducts.xss_detect', '0');
        }

        if ($current_lang === null) {
            if (isset($USER_LANG_CACHED)) {
                $current_lang = $USER_LANG_CACHED;
            } else {
                if (!function_exists('user_lang')) {
                    require_code('lang');
                }
                $current_lang = user_lang();
            }
        }
        $cl = $current_lang;

        $tpl_funcs = $KEEP_TPL_FUNCS;
        $no_eval_cache_before = $NO_EVAL_CACHE;
        foreach ($this->seq_parts as $seq_parts_group) {
            foreach ($seq_parts_group as $seq_part) {
                $seq_part_0 = $seq_part[0];
                /*if ($DEV_MODE) {
                    if (!isset($tpl_funcs[$seq_part_0])) {
                        debug_eval($this->code_to_preexecute[$seq_part_0], $tpl_funcs);
                    }
                    if (($tpl_funcs[$seq_part_0][0] !== 'e') && (function_exists($tpl_funcs[$seq_part_0]))) {
                        debug_call_user_func($tpl_funcs[$seq_part_0], $seq_part[1], $current_lang, $seq_part[4]);
                    } else {
                        $parameters = $seq_part[1];
                        debug_eval($tpl_funcs[$seq_part_0], $tpl_funcs, $parameters, $cl);
                    }
                } else {*/
                if (!isset($tpl_funcs[$seq_part_0])) {
                    eval($this->code_to_preexecute[$seq_part_0]);
                }
                if (is_callable($tpl_funcs[$seq_part_0])) {
                    call_user_func($tpl_funcs[$seq_part_0], $seq_part[1], $current_lang, $seq_part[4]);
                } else {
                    $parameters = $seq_part[1];
                    eval($tpl_funcs[$seq_part_0]);
                }
                //}
            }
        }

        if ($XSS_DETECT) {
            safe_ini_set('ocproducts.xss_detect', $before);
        }

        $ret = ob_get_clean();

        if ((!$MEMORY_OVER_SPEED) && (!$NO_EVAL_CACHE) && (!$GLOBALS['STUCK_ABORT_SIGNAL'])) {
            $this->cached_output = $ret; // Optimisation to store it in here. We don't do the same for evaluate_echo as that's a final use case and hence it would be unnecessarily inefficient to store the result
        }

        if (!$no_eval_cache_before) {
            $NO_EVAL_CACHE = $no_eval_cache_before;
        }

        if (defined('I_UNDERSTAND_XSS') && !$DECLARATIONS_STATE[I_UNDERSTAND_XSS]) {
            $KNOWN_TRUE_HTML[$ret] = true;
        }

        return $ret;
    }

    /**
     * Parse the current Tempcode object, then echo it to the browser.
     *
     * @param  ?LANGUAGE_NAME $current_lang The language to evaluate with (null: current user's language)
     * @param  boolean $stop_if_stuck Whether to stop if we are stuck of a seq_part with parameters yet-unbound, and to continue from last resume point
     * @return string Blank string. Allows chaining within echo statements
     */
    public function evaluate_echo($current_lang = null, $stop_if_stuck = false)
    {
        if (cms_srv('REQUEST_METHOD') === 'HEAD') {
            return '';
        }

        if ($this->cached_output !== null) {
            echo $this->cached_output;
            $this->cached_output = null; // Won't be needed again
            return '';
        }
        if ($this->is_empty_shell()) { // Optimisation: empty
            $this->cached_output = '';
            return '';
        }

        $cl = $current_lang;
        if ($cl === null) {
            $cl = user_lang();
        }

        global $KEEP_TPL_FUNCS, $FULL_RESET_VAR_CODE, $RESET_VAR_CODE, $STOP_IF_STUCK, $STUCK_ABORT_SIGNAL, $DEV_MODE, $TEMPCODE_OUTPUT_STARTED;
        $TEMPCODE_OUTPUT_STARTED = true;
        $tpl_funcs = $KEEP_TPL_FUNCS;
        $seq_parts_group_cnt = count($this->seq_parts);
        $i = &$this->evaluate_echo_offset_group; // A reference, so evaluate_echo_offset_group will go up naturally via looping of $i
        if ($stop_if_stuck) {
            $stop_if_stuck_bak = $STOP_IF_STUCK;
            $STOP_IF_STUCK = true;
            ob_start();
        }
        $first_i = true;
        for (; $i < $seq_parts_group_cnt; $i++) {
            $seq_parts_group = $this->seq_parts[$i];

            $seq_parts_cnt = count($seq_parts_group);
            if ($first_i) {
                $j = &$this->evaluate_echo_offset_inner;
                $first_i = false;
            } else {
                $j = 0;
            }
            for (; $j < $seq_parts_cnt; $j++) {
                $seq_part = $seq_parts_group[$j];

                $seq_part_0 = $seq_part[0];
                /*if ($DEV_MODE) {
                    if (!isset($tpl_funcs[$seq_part_0])) {
                        debug_eval($this->code_to_preexecute[$seq_part_0], $tpl_funcs);
                    }
                    if (($tpl_funcs[$seq_part_0][0] !== 'e') && (function_exists($tpl_funcs[$seq_part_0]))) {
                        debug_call_user_func($tpl_funcs[$seq_part_0], $seq_part[1], $current_lang, $seq_part[4]);
                    } else {
                        $parameters = $seq_part[1];
                        debug_eval($tpl_funcs[$seq_part_0], $tpl_funcs, $parameters, $cl);
                    }
                } else {*/
                if (!isset($tpl_funcs[$seq_part_0])) {
                    eval($this->code_to_preexecute[$seq_part_0]);
                }
                if (is_callable($tpl_funcs[$seq_part_0])) {
                    call_user_func($tpl_funcs[$seq_part_0], $seq_part[1], $current_lang, $seq_part[4]);
                } else {
                    $parameters = $seq_part[1];
                    eval($tpl_funcs[$seq_part_0]);
                }
                //}

                if ($stop_if_stuck) {
                    if ($STUCK_ABORT_SIGNAL) {
                        $STUCK_ABORT_SIGNAL = false;
                        ob_clean();
                        break 2;
                    } else {
                        ob_flush();
                    }
                }
            }
        }

        if ($stop_if_stuck) {
            $STOP_IF_STUCK = $stop_if_stuck_bak;
            ob_end_flush();
        }

        flush();

        return '';
    }
}

/**
 * A template has not been structurally cached, so compile it and store in the cache.
 *
 * @param  string $id A randomised unique ID
 * @param  string $parameters Parameters
 * @param  string $code Function code
 * @return string The function reference
 *
 * @ignore
 */
function recall_named_function($id, $parameters, $code)
{
    $k = 'TEMPCODE_FUNCTION__' . $id;
    if (!isset($GLOBALS[$k])) {
        $code = 'return function (' . $parameters . ') { ' . $code . ' };';
        $GLOBALS[$k] = eval($code);
    }
    return $GLOBALS[$k];
}

/**
 * Include and evaluate the specified Tempcode file.
 *
 * @param  PATH $filepath The filename of the file to include.
 * @return mixed Success status or returned value.
 *
 * @ignore
 */
function tempcode_include($filepath)
{
    if (GOOGLE_APPENGINE) {
        gae_optimistic_cache(true);
        $ret = @include($filepath);
        gae_optimistic_cache(false);
    } else {
        $ret = @include($filepath);
    }

    return $ret;
}

/**
 * Evaluate some PHP, with ability to better debug.
 * In a way this can also quash problems, so only use when debugging. The "@" before eval turns off attach_message.
 *
 * @param  ?string $code Code to evaluate (null: code not found)
 * @param  ?array $tpl_funcs Evaluation code context (null: N/A)
 * @param  ?array $parameters Evaluation parameters (null: N/A)
 * @param  ?ID_TEXT $cl Language (null: N/A)
 * @return string Result
 *
 * @ignore
 */
function debug_eval($code, &$tpl_funcs = null, $parameters = null, $cl = null)
{
    global $NO_EVAL_CACHE, $XSS_DETECT, $KEEP_TPL_FUNCS, $FULL_RESET_VAR_CODE, $RESET_VAR_CODE;

    if ($code === null) {
        return ''; // HHVM issue
    }
    if ($code === '') {
        return ''; // Blank eval returns false
    }
    $result = @eval($code);
    if ($result === false) {
        if ($GLOBALS['DEV_MODE']) {
            $message = (isset($php_errormsg) ? ($php_errormsg . ' - ') : '') . $code;
            //cms_ob_end_clean(); @exit('!' . $message . '!');
            fatal_exit($message);
        }
        $result = '';
    }
    return $result;
}

/**
 * Call a PHP function, with ability to better debug.
 *
 * @param  string $function Function to call
 * @param  mixed $a First parameter
 * @param  ?mixed $b Second parameter (null: null/none)
 * @param  ?mixed $c Third parameter (null: null/none)
 * @return string Result
 *
 * @ignore
 */
function debug_call_user_func($function, $a, $b = null, $c = null)
{
    return call_user_func($function, $a, $b, $c);
}

/**
 * Reduce down a template parameter to a maximum reasonable length, to avoid too much data being stuck in Tempcode trees.
 *
 * @param  Tempcode $text Text
 * @param  integer $max_length Maximum length
 * @return Tempcode Reduced length version of $text if required
 */
function reasonable_html_reduce($text, $max_length = 1000)
{
    $text_flat = $text->evaluate();
    if (strlen($text_flat) > $max_length) {
        $text = make_string_tempcode(symbol_truncator(array($text_flat, strval($max_length), '0', '1'), 'left'));
    }
    return $text;
}
