<?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__site()
{
    global $REQUEST_PAGE_NEST_LEVEL;
    $REQUEST_PAGE_NEST_LEVEL = 0;

    global $REDIRECTED_TO_CACHE;
    $REDIRECTED_TO_CACHE = null;

    // Get ready for breadcrumbs
    $bcl = get_option('breadcrumb_crop_length');
    if (!defined('BREADCRUMB_CROP_LENGTH')) {
        define('BREADCRUMB_CROP_LENGTH', intval($bcl));
    }

    global $NON_CANONICAL_PARAMS;
    // We only bother listing ones the software itself may inject - otherwise admin responsible for their own curation of canonical settings
    $NON_CANONICAL_PARAMS = array('wide_high' => false, 'wide' => false, 'wide_print' => false, 'filtered' => false, 'utheme' => false, 'active_filter' => true, 'redirected' => true, 'redirect_url' => true, 'redirect' => true, 'redirect_passon' => true);
    inform_non_canonical_parameter('#^(.*_)?(max|start|sort)$#');
    if (function_exists('get_value')) {
        $is_non_canonical = false;
        $canonical_keep_params = explode(',', is_null(get_value('canonical_keep_params')) ? 'keep_devtest' : get_value('canonical_keep_params'));
        foreach (array_merge(array_keys($_GET), array('keep_session'/*may be inserted later*/)) as $key) {
            if ((is_string($key)) && (substr($key, 0, 5) == 'keep_') && (!@in_array($key, $canonical_keep_params))) {
                $NON_CANONICAL_PARAMS[$key] = true;
                $is_non_canonical = true;
            }
        }
        /*
        Doing this redirect is too risky. Some code (including user code) may redirect back to inject missing parameters.
        if (($is_non_canonical) && (get_bot_type() !== null)) { // Force bots onto the canonical URL if there were non-standard keep parameters, as they may ignore even the canonical meta tag.
            $non_canonical = array();
            if (is_array($NON_CANONICAL_PARAMS)) {
                foreach (array_keys($NON_CANONICAL_PARAMS) as $n) {
                    $non_canonical[$n] = null;
                }
            }
            set_http_status_code('301');
            require_code('urls');
            header('Location: ' . escape_header(get_self_url(true, false, $non_canonical)));
            exit();
        }
        */
    }

    global $PAGE_STRING, $LAST_COMCODE_PARSED_TITLE;
    $PAGE_STRING = null;
    $LAST_COMCODE_PARSED_TITLE = '';

    global $PT_PAIR_CACHE_CP;
    $PT_PAIR_CACHE_CP = array();

    global $ATTACH_MESSAGE_CALLED;
    $ATTACH_MESSAGE_CALLED = 0;

    load_zone_data();

    // SEO redirection
    require_code('urls');
    if (can_try_url_schemes()) {
        $ruri = cms_srv('REQUEST_URI');

        $url_scheme = get_option('url_scheme');
        if (($url_scheme == 'PG') || ($url_scheme == 'HTM')) {
            if (
                (!headers_sent()) &&
                (running_script('index')) &&
                ($GLOBALS['RELATIVE_PATH'] == get_zone_name()/*i.e. a proper zone*/) &&
                (cms_srv('REQUEST_METHOD') != 'POST') &&
                (get_param_integer('keep_failover', null) !== 0) &&
                ((strpos($ruri, '/pg/') === false) || ($url_scheme != 'PG')) &&
                ((strpos($ruri, '.htm') === false) || ($url_scheme != 'HTM')) &&
                ($ruri != '/')
            ) {
                require_code('permissions');
                set_http_status_code('301');
                header('Location: ' . escape_header(get_self_url(true)));
                exit();
            }
        }
    }

    // Search engine having session in URL, we don't like this
    if ((get_bot_type() !== null) && (cms_srv('REQUEST_METHOD') != 'POST') && (get_param_string('keep_session', null) !== null)) {
        //Too risky, what if something sets it at run-time. Relying on canonical URL is better.
        //set_http_status_code('301');
        //header('Location: ' . escape_header(get_self_url(true, false, array('keep_session' => null, 'keep_print' => null))));
        //exit();
    }

    $cli = ((php_function_allowed('php_sapi_name')) && (php_sapi_name() == 'cli') && (cms_srv('REMOTE_ADDR') == ''));
    if ((running_script('index')) && (!$cli)) {
        $access_host = preg_replace('#:.*#', '', cms_srv('HTTP_HOST'));

        // Detect bad access domain
        global $SITE_INFO;
        if (($access_host != '') && ((isset($_SERVER['HTTP_HOST'])) || (isset($_ENV['HTTP_HOST']))) && (empty($GLOBALS['EXTERNAL_CALL']))) {
            $parsed_base_url = parse_url(get_base_url());

            if ((array_key_exists('host', $parsed_base_url)) && (strtolower($parsed_base_url['host']) != strtolower($access_host))) {
                if (empty($SITE_INFO['ZONE_MAPPING_' . get_zone_name()])) {
                    if ((!is_null($GLOBALS['FORUM_DRIVER'])) && ($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member()))) {
                        attach_message(do_lang_tempcode('BAD_ACCESS_DOMAIN', escape_html($parsed_base_url['host']), escape_html($access_host)), 'warn');
                    }

                    set_http_status_code('301');
                    header('Location: ' . escape_header(get_self_url(true, false)));
                    exit();
                }
            }
        }

        // Detect bad access protocol
        if ((get_value('access_protocol_redirect') === '1') && ((substr(get_base_url(), 0, 8) == 'https://') && (!tacit_https()) || (substr(get_base_url(), 0, 7) == 'http://') && (tacit_https()))) {
            set_http_status_code('301');
            header('Location: ' . escape_header(get_self_url(true, false)));
            exit();
        }

        if (get_value('disable_cookie_checks') !== '1') {
            // Detect bad cookie domain (reasonable approximation)
            $cookie_domain = @ltrim(get_cookie_domain(), '.');
            if (!empty($cookie_domain) && !empty($access_host)) {
                if (substr($access_host, -strlen($cookie_domain)) != $cookie_domain) {
                    attach_message(do_lang_tempcode('INCORRECT_COOKIE_DOMAIN', escape_html($cookie_domain), escape_html($access_host)), 'warn');
                }
            }

            // Detect bad cookie path
            $cookie_path = get_cookie_path();
            $access_path = cms_srv('SCRIPT_NAME');
            if (!empty($cookie_path) && !empty($access_path)) {
                if (substr($access_path, 0, strlen($cookie_path)) != $cookie_path) {
                    attach_message(do_lang_tempcode('INCORRECT_COOKIE_PATH', escape_html($cookie_path), escape_html($access_path)), 'warn');
                }
            }
        }
    }

    if (running_script('index')) {
        process_url_monikers(get_page_name());
    }

    // Bulk advance loading
    global $SMART_CACHE;
    if (isset($SMART_CACHE)) {
        $_comcode_pages_needed = $SMART_CACHE->get('comcode_pages_needed');
        if ($_comcode_pages_needed !== null) {
            $comcode_pages_needed = array();
            foreach ($_comcode_pages_needed as $_comcode_page_needed => $_) {
                $comcode_pages_needed[] = unserialize($_comcode_page_needed);
            }

            if (count($comcode_pages_needed) < 20) {
                _load_comcodes_page_from_cache($comcode_pages_needed);
            }
        }
    }

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

/**
 * Load up details for the current zone.
 */
function check_has_page_access()
{
    $real_zone = load_zone_data();

    // The most important security check
    global $SESSION_CONFIRMED_CACHE;
    get_member(); // Make sure we've loaded our backdoor if installed
    require_code('permissions');
    global $ZONE;
    if ($ZONE['zone_require_session'] == 1) {
        set_no_clickjacking_csp();
    }
    if (($ZONE['zone_name'] != '') && (!is_httpauth_login()) && ((get_session_id() == '') || (!$SESSION_CONFIRMED_CACHE)) && ($ZONE['zone_require_session'] == 1) && (get_page_name() != 'login') && (!is_guest())) {
        access_denied((($real_zone == 'data') || (has_zone_access(get_member(), $ZONE['zone_name']))) ? 'ZONE_ACCESS_SESSION' : 'ZONE_ACCESS', $ZONE['zone_name'], true);
    } else {
        if (($real_zone == 'data') || (has_zone_access(get_member(), $ZONE['zone_name']))) {
            if ((running_script('index')) && /*Actually we will allow Guest denying to the front page even though that is a bit weird ((get_page_name()!=$ZONE['zone_default_page']) || ($real_zone!='')) && */
                (!has_page_access(get_member(), get_page_name(), $ZONE['zone_name'], true))
            ) {
                access_denied('PAGE_ACCESS');
            }
        } else {
            if (get_page_name() != 'login') {
                access_denied('ZONE_ACCESS', $ZONE['zone_name'], true);
            }
        }
    }
}

/**
 * Load up details for the current zone.
 *
 * @return  ID_TEXT     The "real" zone name (not actually the zone name, but the zone name wants details to load for).
 */
function load_zone_data()
{
    global $ZONE, $RELATIVE_PATH;
    $zone_name = get_zone_name();
    $real_zone = (($RELATIVE_PATH == '_tests') || ($RELATIVE_PATH == 'data') || ($RELATIVE_PATH == 'data_custom')) ? get_param_string('zone', '') : $zone_name;
    /** A map of the current zone that is running.
     *
     * @global array $ZONE
     */
    if ($ZONE === null) {
        $ZONE = persistent_cache_get(array('ZONE', $real_zone));

        if ($ZONE === null) {
            find_all_zones(); // Optimisation, this will load up our zone list which *usually* includes caching of current zone, as this likely is needed somewhere anyway

            global $ALL_ZONES_TITLED_CACHE;
            if (isset($ALL_ZONES_TITLED_CACHE[$real_zone][3])) {
                $ZONE = $ALL_ZONES_TITLED_CACHE[$real_zone][3];
            }
        }

        if ($ZONE === null) {
            $zones = $GLOBALS['SITE_DB']->query_select('zones', array('*'), array('zone_name' => $real_zone), '', 1);

            // Auto-create zone
            if ((!array_key_exists(0, $zones)) && (is_dir(get_file_base() . '/' . $real_zone . '/' . 'pages'))) {
                $zones = $GLOBALS['SITE_DB']->query_select('zones', array('*'), array('zone_name' => preg_replace('#\d#', '', $real_zone)), '', 1);
                if (array_key_exists(0, $zones)) {
                    // Ah, we have a zone with the same name, except the numbers stripped -- this can be a template
                    $map = $zones[0];
                    $map['zone_name'] = $real_zone;
                    $map = insert_lang('zone_title', get_translated_text($zones[0]['zone_title']), 1) + $map;
                    $map = insert_lang('zone_header_text', get_translated_text($zones[0]['zone_header_text']), 1) + $map;

                    $access = $GLOBALS['SITE_DB']->query_select('group_zone_access', array('group_id'), array('zone_name' => $zones[0]['zone_name']));
                    foreach ($access as $a) {
                        $GLOBALS['SITE_DB']->query_insert('group_zone_access', array('group_id' => $a['group_id'], 'zone_name' => $real_zone));
                    }
                } else {
                    $zone_default_page = 'start';
                    if ($real_zone == 'forum') { // A bit of an architectural fudge, but people get confused why it doesn't come back the same
                        $zone_default_page = 'forumview';
                    }
                    if ($real_zone == 'docs') {
                        $zone_default_page = 'tutorials';
                    }
                    $map = array(
                        'zone_name' => $real_zone,
                        'zone_default_page' => $zone_default_page,
                        'zone_theme' => 'default',
                        'zone_require_session' => 0,
                    );
                    $map += insert_lang('zone_title', $real_zone, 1);
                    $map += insert_lang('zone_header_text', $real_zone, 1);
                }
                $GLOBALS['SITE_DB']->query_insert('zones', $map);
                $zones = $GLOBALS['SITE_DB']->query_select('zones', array('*'), array('zone_name' => $real_zone), '', 1);
            }

            if (array_key_exists(0, $zones)) {
                $ZONE = $zones[0];
                persistent_cache_set(array('ZONE', $real_zone), $ZONE);
            }

            // Missing zone
            if ($ZONE === null) {
                $zones = $GLOBALS['SITE_DB']->query_select('zones', array('*'), array('zone_name' => ''), '', 1);
                $ZONE = $zones[0];
                warn_exit(do_lang_tempcode('BAD_ZONE', escape_html($real_zone)));
            }
            unset($zones);
        }
    }

    return $real_zone;
}

/**
 * Attach some extra JavaScript to <head>. Don't use this too commonly, it's not a 'tidy' way of doing things.
 *
 * @sets_output_state
 *
 * @param  mixed $data JavaScript to attach (Tempcode or string)
 */
function attach_to_javascript($data)
{
    global $JAVASCRIPT;
    if ($JAVASCRIPT === null) {
        $JAVASCRIPT = new Tempcode();
    }
    $JAVASCRIPT->attach($data);
}

/**
 * Attach some XHTML to the screen header.
 *
 * @sets_output_state
 *
 * @param  mixed $data XHTML to attach (Tempcode or string)
 */
function attach_to_screen_header($data)
{
    global $EXTRA_HEAD;
    if ($EXTRA_HEAD === null) {
        $EXTRA_HEAD = new Tempcode();
    }
    $EXTRA_HEAD->attach($data);
}

/**
 * Mark another parameter non-canonical, so that Google won't consider it when indexing URLs.
 *
 * @sets_output_state
 *
 * @param  ID_TEXT $param Parameter name
 * @param  boolean $block_page_from_static_cache_if_present If set to true and if this parameter is present in the URL then no static cache save will happen
 */
function inform_non_canonical_parameter($param, $block_page_from_static_cache_if_present = true)
{
    global $NON_CANONICAL_PARAMS;

    if ($param[0] == '#') { // A regexp
        foreach (array_keys($_GET) as $key) {
            if (is_numeric($key)) {
                $key = strval($key);
            }

            if (preg_match($param, $key) != 0) {
                inform_non_canonical_parameter($key);
            }
        }
    } else {
        $NON_CANONICAL_PARAMS[$param] = $block_page_from_static_cache_if_present;
    }
}

/**
 * Attach a message to the page output.
 *
 * @sets_output_state
 *
 * @param  mixed $message The type of special message
 * @param  ID_TEXT $type The template to use
 * @set    inform notice warn
 * @param  boolean $put_in_helper_panel Whether to put into the helper panel instead of the normal header area
 * @return string Blank string so it can be chained in the Tempcode compiler. You will rarely want to use this return value. It's kind of a failsafe.
 */
function attach_message($message, $type = 'inform', $put_in_helper_panel = false)
{
    if ((error_reporting() == 0) && ($type == 'warn')) {
        return ''; // Suppressing errors
    }

    static $am_looping = false;
    if ($am_looping) {
        return ''; // Was a lang lookup error and got in an infinite loop of attaching errors about missing lang errors (because each iteration causes a reevaluation of past messages)
    }
    $am_looping = true;

    global $ATTACH_MESSAGE_CALLED, $ATTACHED_MESSAGES, $ATTACHED_MESSAGES_RAW, $LATE_ATTACHED_MESSAGES;

    if (!isset($ATTACH_MESSAGE_CALLED)) {
        return ''; // Still starting up
    }

    foreach ($ATTACHED_MESSAGES_RAW as $last) {
        if (array(strip_tags(is_object($last[0]) ? $last[0]->evaluate() : $last[0]), $last[1]) == array(strip_tags(is_object($message) ? $message->evaluate() : $message), $type)) {
            $am_looping = false;
            return ''; // Already shown
        }
    }

    $ATTACH_MESSAGE_CALLED++;
    if ($ATTACH_MESSAGE_CALLED > 5) {
        critical_error('EMERGENCY', is_object($message) ? $message->evaluate() : escape_html($message));
    }

    if (($type == 'warn') && (strlen(is_object($message) ? $message->evaluate() : $message) < 130)) {
        require_code('failure');

        $webservice_result = get_webservice_result($message);
        if ($webservice_result !== null) {
            if ((is_object($message)) && ($message->pure_lang)) {
                $message = $message->evaluate();
            } elseif (is_object($message)) {
                $message = escape_html($message->evaluate());
            } else {
                $message = escape_html($message);
            }
            $message = do_template('CROP_TEXT_MOUSE_OVER', array(
                '_GUID' => '36ccd2ff13867ed000a46f15df0a4a22',
                'TEXT_LARGE' => $webservice_result,
                'TEXT_SMALL' => protect_from_escaping($message),
            ));
            $message = protect_from_escaping($message);
        }
    }

    if ((get_param_integer('keep_fatalistic', 0) == 1) && ($type == 'warn')) {
        fatal_exit($message);
    }

    $message_tpl = do_template('MESSAGE', array(
        '_GUID' => 'ec843c8619d21fbeeb512686ea300a17',
        'TYPE' => $type,
        'MESSAGE' => is_string($message) ? escape_html($message) : $message
    ));

    if ($put_in_helper_panel) {
        set_helper_panel_text($message_tpl, true, false);
    } else {
        $ATTACHED_MESSAGES_RAW[] = array($message, $type);
        if ($GLOBALS['TEMPCODE_OUTPUT_STARTED']) {
            if ($LATE_ATTACHED_MESSAGES === null) {
                $LATE_ATTACHED_MESSAGES = new Tempcode();
            }
            $LATE_ATTACHED_MESSAGES->attach($message_tpl);
        } else {
            if ($ATTACHED_MESSAGES === null) {
                $ATTACHED_MESSAGES = new Tempcode();
            }
            $ATTACHED_MESSAGES->attach($message_tpl);
        }
    }

    $ATTACH_MESSAGE_CALLED--;

    if ($GLOBALS['REFRESH_URL'][0] != '') {
        $GLOBALS['SITE_DB']->query_insert('messages_to_render', array(
            'r_session_id' => get_session_id(),
            'r_message' => is_object($message) ? $message->evaluate() : escape_html($message),
            'r_type' => $type,
            'r_time' => time(),
        ));
    }

    $am_looping = false;
    return '';
}

/**
 * Get the relative URL to the logo for the current zone.
 *
 * @param  ?ID_TEXT $zone_name The zone being operated within (null: auto-detect)
 * @return URLPATH The relative URL to the logo for the current zone
 */
function get_logo_url($zone_name = null)
{
    if (!addon_installed('zone_logos')) {
        return find_theme_image('logo/-logo');
    }

    global $ZONE;
    if ($zone_name === null) {
        $zone_name = $ZONE['zone_name'];
    }

    $image = find_theme_image('logo/' . $zone_name . '-logo', true);
    if ($image == '') {// Fallback to welcome zone logo
        $image = find_theme_image('logo/-logo');
    }

    return $image;
}

/**
 * Get the Tempcode for the breadcrumbs.
 *
 * @param  boolean $show_self Whether to show a self segment
 * @return Tempcode The breadcrumbs
 */
function breadcrumbs($show_self = true)
{
    static $out = null;
    if ($out !== null) {
        return $out;
    }

    global $BREADCRUMB_SET_PARENTS, $BREADCRUMBS;

    // Special hard-coded link to sitemap structure for Admin and CMS zones
    $zone = get_zone_name();
    if ((($zone == 'adminzone') || ($zone == 'cms')) && (get_option('deeper_admin_breadcrumbs') == '1') && (get_page_name() != 'login')) {
        require_code('site_adminzone');
        $BREADCRUMB_SET_PARENTS = array_merge(adminzone_extended_breadcrumbs(), $BREADCRUMB_SET_PARENTS);
    }

    // Substitutions
    if ((addon_installed('breadcrumbs')) && (function_exists('xml_parser_create'))) {
        require_code('breadcrumbs');
        $BREADCRUMB_SET_PARENTS[] = array(null, $GLOBALS['BREADCRUMB_SET_SELF']);
        $BREADCRUMB_SET_PARENTS = load_breadcrumb_substitutions($BREADCRUMB_SET_PARENTS);
        array_pop($BREADCRUMB_SET_PARENTS);
    }

    // Render
    if ($BREADCRUMBS === null) {
        $BREADCRUMBS = breadcrumbs_get_default_stub($show_self);
    }
    $out = new Tempcode();
    $out->attach($BREADCRUMBS);

    return $out;
}

/**
 * Get the Tempcode for the default breadcrumbs stub. This isn't entirely a default, because it does work with breadcrumb_set_parents. We refer to it as a default as it is possible to override the whole breadcrumbs environment via the special BREADCRUMBS global variable.
 *
 * @param  boolean $link_to_self_entrypoint Whether we'll be providing a link to where we are currently at
 * @return Tempcode The default breadcrumb stub
 */
function breadcrumbs_get_default_stub($link_to_self_entrypoint = true)
{
    global $BREADCRUMB_SET_PARENTS, $DISPLAYED_TITLE, $BREADCRUMB_SET_SELF;

    $stub = new Tempcode();

    // Specified parents
    $stub->attach(breadcrumb_segments_to_tempcode($BREADCRUMB_SET_PARENTS, $link_to_self_entrypoint));

    // Self-link
    if ($link_to_self_entrypoint) {
        $label = ($BREADCRUMB_SET_SELF === null) ? protect_from_escaping($DISPLAYED_TITLE) : $BREADCRUMB_SET_SELF;
        if ($label !== null) {
            $label_eval = is_object($label) ? $label->evaluate() : $label;
            $last_breadcrumb_label_eval = mixed();
            $last_breadcrumb_label_eval = (count($BREADCRUMB_SET_PARENTS) == 0) ? '' : $BREADCRUMB_SET_PARENTS[count($BREADCRUMB_SET_PARENTS) - 1][1];
            if (is_object($last_breadcrumb_label_eval)) {
                $last_breadcrumb_label_eval = $last_breadcrumb_label_eval->evaluate();
            }
            if ($label_eval != $last_breadcrumb_label_eval) {
                if (count($BREADCRUMB_SET_PARENTS) != 0) {
                    $stub->attach(do_template('BREADCRUMB_SEPARATOR'));
                }

                $stub->attach(do_template('BREADCRUMB_LONE_WRAP', array('_GUID' => '769236ef4f20a0a05cee6d7a335eaf9f', 'LABEL' => $label)));
            }
        }
    }

    return $stub;
}

/**
 * Convert breadcrumb segments to Tempcode breadcrumbs.
 *
 * @param  array $segments The segments in array format
 * @param  ?mixed $link_to_self_entrypoint Whether we'll be providing a link to where we are currently at (by reference, gets set to false in some circumstances) (null: don't save by reference)
 * @return Tempcode The segments in Tempcode0
 */
function breadcrumb_segments_to_tempcode($segments, &$link_to_self_entrypoint = null)
{
    $out = new Tempcode();

    foreach ($segments as $i => $bit) {
        list($entry_point, $label) = $bit;
        $tooltip = isset($bit[2]) ? $bit[2] : '';
        if (!(((is_string($tooltip)) && ($tooltip == '')) || ((is_object($tooltip)) && ($tooltip->is_empty())))) {
            $link_to_self_entrypoint = false; // Tooltip implies that we are defining end-point ourselves
        }

        if ($i != 0) {
            $out->attach(do_template('BREADCRUMB_SEPARATOR'));
        }

        if ($entry_point == '') {
            $out->attach(do_template('BREADCRUMB_LONE_WRAP', array('_GUID' => 'fcf371ee4a071ebdd170dc16a55f36dd', 'LABEL' => $label)));

            $link_to_self_entrypoint = false; // Empty part implies that we are defining end-point ourselves
        } else {
            $url = page_link_to_tempcode_url($entry_point);

            $out->attach(do_template('BREADCRUMB_LINK_WRAP', array('_GUID' => 'f7e8a83d61bde871ab182dec7da84ccc', 'LABEL' => $label, 'URL' => $url, 'TOOLTIP' => $tooltip)));
        }
    }

    return $out;
}

/**
 * Put a list of parents in for the breadcrumbs.
 *
 * @sets_output_state
 *
 * @param  array $parents The list of parent entry points (pairs: entry point, title)
 */
function breadcrumb_set_parents($parents)
{
    global $BREADCRUMB_SET_PARENTS;
    $BREADCRUMB_SET_PARENTS = $parents;
}

/**
 * Set the current title.
 *
 * @sets_output_state
 *
 * @param  mixed $title The title (Tempcode or string)
 */
function breadcrumb_set_self($title)
{
    global $BREADCRUMB_SET_SELF;
    $BREADCRUMB_SET_SELF = $title;
}

/**
 * Set the feed (RSS/Atom) URL.
 *
 * @sets_output_state
 *
 * @param  URLPATH $url The URL
 */
function set_feed_url($url)
{
    global $FEED_URL;
    $FEED_URL = $url;
}

/**
 * Set the helper panel text.
 *
 * @sets_output_state
 *
 * @param  Tempcode $text The text
 * @param  boolean $append Whether to append
 * @param  boolean $put_in_box Whether to add a box around the parameter
 */
function set_helper_panel_text($text, $append = true, $put_in_box = true)
{
    if ($put_in_box) {
        $text = put_in_standard_box($text);
    }

    global $HELPER_PANEL_TEXT;
    if ($append) {
        if (is_string($HELPER_PANEL_TEXT)) {
            $HELPER_PANEL_TEXT = make_string_tempcode($HELPER_PANEL_TEXT);
        }
        $HELPER_PANEL_TEXT->attach($text);
    } else {
        $HELPER_PANEL_TEXT = $text;
    }
}

/**
 * Set the helper panel tutorial.
 *
 * @sets_output_state
 *
 * @param  ID_TEXT $tutorial The page name of the tutorial (must be an existing one on the brand site, i.e. compo.sr)
 */
function set_helper_panel_tutorial($tutorial)
{
    global $HELPER_PANEL_TUTORIAL;
    $HELPER_PANEL_TUTORIAL = $tutorial;
}

/**
 * Sets the short title, used for screen header text if set.
 * Does not do anything if output streaming is on and already started.
 *
 * @sets_output_state
 *
 * @param  string $title The short title
 */
function set_short_title($title)
{
    global $SHORT_TITLE, $FORCE_SET_TITLE;
    if (!$FORCE_SET_TITLE) {
        $SHORT_TITLE = ($title == '') ? null : $title;
    }
}

/**
 * Process URL monikers, changing 'id' GET param to be correct.
 *
 * @param  ID_TEXT $page The page name to do it for
 * @param  boolean $redirect_if_non_canonical Do a redirect if we're not on the canonical URL
 */
function process_url_monikers($page, $redirect_if_non_canonical = true)
{
    static $run_once = false;
    if ($run_once) {
        return;
    }
    $run_once = true;

    $zone = get_zone_name();
    $type = get_param_string('type', null, true);
    $url_id = get_param_string('id', null, true);

    if (url_monikers_enabled()) {
        // Monikers relative to the zone
        $page_place = _request_page($page, $zone);
        if (($page_place !== false) && ($page_place[0] == 'REDIRECT')) {
            $page = $page_place[1]['r_to_page'];
            $zone = $page_place[1]['r_to_zone'];
            $page_place = _request_page($page_place[1]['r_to_page'], $page_place[1]['r_to_zone']);
        }
        if (($page_place === false) || ((substr($page_place[0], 0, 7) == 'COMCODE') && ($type !== null/*looking deeper than a normal Comcode page*/))) {
            // This code branch is finding absolute monikers (easy case), or monikers pointing to a Comcode page (special case)...

            // Reassemble source URL moniker from incorrectly-derived URL components
            $url_moniker = '';
            $url_moniker .= get_param_string('page', '', true); /* Has to be unadulterated, $page has /s/-/_ */
            if ($type !== null) {
                $url_moniker .= '/' . $type;
            }
            if ($url_id !== null) {
                $url_moniker .= '/' . $url_id;
            }

            // ... and query it
            $sql = 'SELECT * FROM ' . get_table_prefix() . 'url_id_monikers WHERE ';
            $sql .= db_string_equal_to('m_moniker', '/' . $url_moniker) . ' OR '; // Absolute
            $sql .= db_string_equal_to('m_moniker', $url_moniker) . ' AND ' . db_string_equal_to('m_resource_type', '') . ' AND ' . db_string_equal_to('m_resource_id', $zone) . ' OR '; // Comcode page
            // (dash replacement...)
            $sql .= db_string_equal_to('m_moniker', '/' . str_replace('-', '_', $url_moniker)) . ' OR '; // Absolute
            $sql .= db_string_equal_to('m_moniker', str_replace('-', '_', $url_moniker)) . ' AND ' . db_string_equal_to('m_resource_type', '') . ' AND ' . db_string_equal_to('m_resource_id', $zone); // Comcode page
            $monikers = $GLOBALS['SITE_DB']->query($sql, 1);
            if (array_key_exists(0, $monikers)) {
                if (_request_page($monikers[0]['m_resource_page'], $zone) !== false) { // ... if operable within the zone we're in
                    // Bind to correct new values
                    global $PAGE_NAME_CACHE, $GETTING_PAGE_NAME;
                    $PAGE_NAME_CACHE = $monikers[0]['m_resource_page'];
                    $GETTING_PAGE_NAME = false;
                    if ($monikers[0]['m_resource_type'] == '') {
                        $_GET['page'] = $monikers[0]['m_resource_page'];
                        unset($_GET['type']);
                        unset($_GET['id']);
                    } else {
                        $_GET['page'] = $monikers[0]['m_resource_page'];
                        $_GET['type'] = $monikers[0]['m_resource_type'];
                        $_GET['id'] = $monikers[0]['m_resource_id'];
                    }

                    // Check for (and handle) deprecation
                    handle_moniker_deprecation_redirect($zone, $monikers[0], $redirect_if_non_canonical, true);

                    return;
                }
            }
        }

        // Does this URL arrangement support monikers? We find out by interrogating hooks to see if the moniker can sit under the module's zone+page+type stub.
        if ($url_id !== null) { // NB: Comcode page monikers would have been handled in the code above
            $type = get_param_string('type', 'browse');
            $looking_for = '_SEARCH:' . $page . ':' . $type . ':_WILD';
            $hooks = find_all_hooks('systems', 'content_meta_aware');
            $ob_info = null;
            foreach (array_keys($hooks) as $hook) {
                require_code('content');
                $ob = get_content_object($hook);
                if ($ob === null) {
                    continue;
                }
                $ob_info = $ob->info();
                if ($ob_info === null) {
                    continue;
                }
                $ob_info['view_page_link_pattern'] = preg_replace('#:[^:]*$#', ':_WILD', $ob_info['view_page_link_pattern']);

                if (($ob_info['view_page_link_pattern'] == $looking_for) && ($ob_info['support_url_monikers'])) {
                    if (is_numeric($url_id)) { // We definitely did not go to a moniker URL (as the URL ID was numeric). Lookup and redirect to the true moniker URL.
                        $correct_moniker = find_id_moniker(array('page' => $page, 'type' => get_param_string('type', 'browse'), 'id' => $url_id), $zone);
                        if (($correct_moniker !== null) && ($correct_moniker != $url_id) && (get_param_integer('keep_failover', null) !== 0) && (cms_srv('REQUEST_METHOD') != 'POST')) { // test is very unlikely to fail. Will only fail if the title of the resource was numeric - in which case the moniker was chosen to be the ID (NOT the number in the title, as that would have created ambiguity).
                            set_http_status_code('301');
                            $_new_url = build_url(array('page' => '_SELF', 'id' => $correct_moniker), '_SELF', null, true);
                            $new_url = $_new_url->evaluate();
                            header('Location: ' . escape_header($new_url));
                            exit();
                        }
                    } else {
                        // Look up the moniker row
                        $table = 'url_id_monikers';
                        if (strpos(get_db_type(), 'mysql') !== false) {
                            $table .= ' FORCE INDEX (uim_moniker)';
                        }
                        $monikers = $GLOBALS['SITE_DB']->query_select($table, array('*'), array('m_resource_page' => $page, 'm_resource_type' => get_param_string('type', 'browse'), 'm_moniker' => $url_id));
                        if (!array_key_exists(0, $monikers)) { // Uh oh
                            // Assume that it wasn't a moniker after all
                            if (!$ob_info['id_field_numeric']) {
                                return;
                            }

                            // Okay it was deleted or never existed then?! Just set to -1 as nothing will have that ID, and we'll get an error from the module when bootstrapping is fully finished
                            $_GET['id'] = '-1';
                            return;
                        }

                        // Map back 'id'
                        $_GET['id'] = $monikers[0]['m_resource_id']; // We need to know the ID number rather than the moniker

                        // Check for (and handle) deprecation
                        handle_moniker_deprecation_redirect($zone, $monikers[0], $redirect_if_non_canonical);
                    }
                    return;
                }
            }
        }
    }
}

/**
 * Redirect to the latest moniker, if appropriate.
 *
 * @param  ID_TEXT $zone The zone
 * @param  array $moniker_row The current moniker row
 * @param  boolean $redirect_if_non_canonical Do a redirect if we're not on the canonical URL
 * @param  boolean $is_complete_url_under_zone Is the moniker representing the full URL (as opposed to being underneath a module)?
 */
function handle_moniker_deprecation_redirect($zone, $moniker_row, $redirect_if_non_canonical, $is_complete_url_under_zone = false)
{
    $deprecated = $moniker_row['m_deprecated'] == 1;
    if (($redirect_if_non_canonical) && ($deprecated) && (cms_srv('REQUEST_METHOD') != 'POST') && (get_param_integer('keep_failover', null) !== 0)) {
        $_moniker_row = $moniker_row;
        unset($_moniker_row['id']);
        unset($_moniker_row['m_moniker']);
        unset($_moniker_row['m_moniker_reversed']);
        unset($_moniker_row['m_manually_chosen']);
        $_moniker_row['m_deprecated'] = 0;
        $correct_moniker_rows = $GLOBALS['SITE_DB']->query_select('url_id_monikers', array('m_moniker'), $_moniker_row, '', 1);
        $correct_moniker = array_key_exists(0, $correct_moniker_rows) ? $correct_moniker_rows[0]['m_moniker'] : null;
        if (($correct_moniker !== null) && ($correct_moniker != $moniker_row['m_moniker'])) { // Just in case database corruption means ALL are deprecated
            set_http_status_code('301');
            if ($is_complete_url_under_zone) {
                $_new_url = build_url(array('page' => $correct_moniker), '_SELF', null, true);
            } else {
                $_new_url = build_url(array('page' => '_SELF', 'id' => $correct_moniker), '_SELF', null, true);
            }
            $new_url = $_new_url->evaluate();
            header('Location: ' . escape_header($new_url));
            exit();
        }
    }
}

/**
 * This is it - the start of rendering of a website page.
 * Take in all inputs, sends them to the correct functions to process, gathers up all the outputs, sticks them together and echoes them.
 */
function do_site()
{
    // Any messages to output?
    if (get_param_integer('redirected', 0) == 1) {
        $messages = $GLOBALS['SITE_DB']->query_select('messages_to_render', array('r_message', 'r_type'), array('r_session_id' => get_session_id(),), 'ORDER BY r_time DESC');
        foreach ($messages as $message) {
            if ($GLOBALS['XSS_DETECT']) {
                ocp_mark_as_escaped($message['r_message']);
            }
            attach_message(protect_from_escaping($message['r_message']), $message['r_type']);
        }
        if (count($messages) != 0) {
            $GLOBALS['SITE_DB']->query('DELETE FROM ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'messages_to_render WHERE ' . db_string_equal_to('r_session_id', get_session_id()) . ' OR r_time<' . strval(time() - 60 * 60));
        }
    }

    $out_evaluated = null;
    global $ZONE;

    // Note any special handling needed
    $special_page_type = get_param_string('special_page_type', 'view');
    $keep_markers = get_param_integer('keep_markers', 0);
    $show_edit_links = get_param_integer('show_edit_links', 0);
    global $KEEP_MARKERS, $SHOW_EDIT_LINKS;
    /** Whether we will be embedding template markers in the output.
     *
     * @sets_output_state
     *
     * @global ?array $KEEP_MARKERS
     */
    $KEEP_MARKERS = ($keep_markers == 1) || ($special_page_type == 'show_markers');
    if (($KEEP_MARKERS) && (!headers_sent())) {
        header('Content-type: text/html; charset=' . get_charset());
    }
    /** Whether we will be embedding edit links in the output.
     *
     * @sets_output_state
     *
     * @global ?array $SHOW_EDIT_LINKS
     */
    $SHOW_EDIT_LINKS = ($show_edit_links == 1) || ($special_page_type == 'show_edit_links');
    if ($SHOW_EDIT_LINKS) {
        require_css('themes_editor');
    }
    if (($special_page_type != 'view') && ($special_page_type != 'show_markers')) {
        require_code('view_modes');
        initialise_special_page_types($special_page_type);
    }
    $doing_special_page_type = ($special_page_type != 'view') && ($special_page_type != 'show_markers') && ($special_page_type != 'show_edit_links') && ($special_page_type != 'memory') && ((has_privilege(get_member(), 'view_profiling_modes')) || ($GLOBALS['IS_ACTUALLY_ADMIN']));

    // Allow the site to be closed
    $site_closed = get_option('site_closed');
    if (($site_closed == '1') && (!has_privilege(get_member(), 'access_closed_site')) && (!$GLOBALS['IS_ACTUALLY_ADMIN'])) {
        require_code('site2');
        closed_site();
    }

    // Warning about whether the Setup Wizard still needs running
    $zone = get_zone_name();
    if ((($zone == 'adminzone') || ($zone == 'cms')) && (get_param_integer('wide_high', 0) == 0)) {
        if ((get_param_integer('cancel_sw_warn', 0) == 1) || (!addon_installed('setupwizard'))) {
            set_value('setupwizard_completed', '1');
        } else {
            $_done_sw_once = get_value('setupwizard_completed');
            $done_sw_once = !is_null($_done_sw_once);
            if ((!$done_sw_once) && (get_page_name() != 'admin_setupwizard') && (has_actual_page_access(get_member(), 'admin_setupwizard'))) {
                require_lang('config');
                $setupwizard_url = build_url(array('page' => 'admin_setupwizard'), get_module_zone('admin_setupwizard'));
                $cancel_sw_url = get_self_url(false, true, array('cancel_sw_warn' => 1));
                attach_message(do_lang_tempcode('SETUPWIZARD_NOT_RUN', escape_html($setupwizard_url->evaluate()), escape_html($cancel_sw_url->evaluate())), 'notice');
            }
        }
    }

    // Warning if dev-mode is on
    if (($GLOBALS['DEV_MODE']) && (get_param_integer('wide_high', 0) == 0) && (get_param_integer('keep_hide_dev_mode_message', 0) == 0)) {
        static $done_message = false;
        if (!$done_message) {
            attach_message(do_lang_tempcode('DEV_MODE_ON'), 'notice');
            $done_message = true;
        }
    }

    // Web Standards mode?
    $webstandards_check = get_param_integer('keep_webstandards_check', get_param_integer('webstandards_check', 0));
    $show_edit_links = get_param_integer('show_edit_links', 0);
    $webstandards_mode = (
        (($GLOBALS['IS_ACTUALLY_ADMIN']) || ($GLOBALS['FORUM_DRIVER']->is_staff(get_member()))) && (($special_page_type == 'code') || (($webstandards_check == 1) && ($GLOBALS['REFRESH_URL'][0] == '') && ($show_edit_links == 0)))); // Not a permission - a matter of performance
    if ($webstandards_mode) {
        $GLOBALS['OUTPUT_STREAMING'] = false;
    }

    // Output streaming?
    $out = globalise(null, null, '', true);

    // Load up our frames into strings. Note that the header and the footer are fixed already.
    $middle = request_page(get_page_name(), true, null, null, false, false, $out);
    if ($middle->is_empty_shell()) {
        set_http_status_code('404');

        $title = get_screen_title('ERROR_OCCURRED');
        $text = do_lang_tempcode('NO_PAGE_OUTPUT');
        $middle = warn_screen($title, $text, false);
    }
    $out->singular_bind('MIDDLE', $middle);

    if (!$GLOBALS['OUTPUT_STREAMING']) {
        $out->handle_symbol_preprocessing();
    }

    // Web Standards mode
    if ($webstandards_mode) {
        require_code('view_modes');
        $out_evaluated = $out->evaluate(null);
        check_xhtml_webstandards($out_evaluated, ($special_page_type == 'code' && (get_param_integer('preview_mode', null) === null)), get_param_integer('preview_mode', 0));
    }

    // Caching for spiders
    if ($GLOBALS['STATIC_CACHE_ENABLED']) {
        $out2 = clone $out; // This is needed to stop things messing up during output streaming
        save_static_caching($out2);
    }

    // Something to do now rather than output normal screen?
    if ($doing_special_page_type) {
        special_page_types($special_page_type, $out, $out_evaluated);
    }
    if ((in_safe_mode()) && (!isset($GLOBALS['SITE_INFO']['safe_mode']))) {
        global $SITE_INFO;
        $safe_mode_via_config = (isset($SITE_INFO['safe_mode'])) && ($SITE_INFO['safe_mode'] == '1');
        $disable_safe_mode_url = get_self_url(true, true, array('keep_safe_mode' => $safe_mode_via_config ? 0 : null));
        attach_message(do_lang_tempcode('CURRENTLY_HAS_KEEP_SAFE_MODE', escape_html($disable_safe_mode_url)), 'notice');
    }
    if (get_param_integer('keep_fatalistic', 0) == 1) {
        $disable_fatalistic_url = get_self_url(true, true, array('keep_fatalistic' => null));
        attach_message(do_lang_tempcode('CURRENTLY_HAS_KEEP_FATALISTIC', escape_html($disable_fatalistic_url)), 'notice');
    }

    // We calculated the time before outputting so that latency and bandwidth do not adversely affect the result
    global $PAGE_START_TIME, $PAGE_STRING;
    $page_generation_time = (microtime(true) - $PAGE_START_TIME) * 1000.0;

    // Output
    if (!$GLOBALS['QUICK_REDIRECT']) {
        if ($out_evaluated !== null) {
            echo $out_evaluated;
        } else {
            if ($GLOBALS['OUTPUT_STREAMING']) {
                $middle->handle_symbol_preprocessing();
            }
            $GLOBALS['FINISHING_OUTPUT'] = true;
            /*if (get_option('gzip_output')=='1')  Does not work well
                    ob_start('_compress_html_output');*/
            $out->evaluate_echo(null);
        }
    }

    // Finally, stats
    if ($PAGE_STRING !== null) {
        log_stats($PAGE_STRING, intval($page_generation_time));
    }

    // When someone hits the Admin Zone
    if (($ZONE !== null) && ($ZONE['zone_name'] == 'adminzone')) {
        // Security feature admins can turn on
        if (get_page_name() == 'start') {
            require_code('notifications');
            $current_username = $GLOBALS['FORUM_DRIVER']->get_username(get_member());
            $subject = do_lang('AFA_NOTIFICATION_MAIL_SUBJECT', $current_username, get_site_name(), get_ip_address());
            $mail = do_notification_lang('AFA_NOTIFICATION_MAIL', comcode_escape(get_site_name()), comcode_escape($current_username), comcode_escape(get_ip_address()));
            dispatch_notification('adminzone_dashboard_accessed', null, $subject, $mail);
        }

        // Track very basic details of what sites use Composr
        if ((!running_locally()) && (get_option('call_home') == '1')) {
            $timeout_before = @ini_get('default_socket_timeout');
            safe_ini_set('default_socket_timeout', '3');
            require_code('version2');
            require_code('files2');
            cache_and_carry('http_download_file', array('http://compo.sr/uploads/website_specific/compo.sr/scripts/user.php?url=' . urlencode(get_base_url()) . '&name=' . urlencode(get_site_name()) . '&version=' . urlencode(get_version_dotted()), null, false), 60 * 24/*once a day*/);
            safe_ini_set('default_socket_timeout', $timeout_before);
        }
    }

    // Little disk space check
    $last_space_check = get_value('last_space_check');
    if (($last_space_check === null) || (intval($last_space_check) < time() - 60 * 60 * 3)) {
        if (!$GLOBALS['SITE_DB']->table_is_locked('values')) {
            set_value('last_space_check', strval(time()));
        }

        if (php_function_allowed('disk_free_space')) {
            $low_space_check = intval(get_option('low_space_check')) * 1024 * 1024;
            $disk_space = @disk_free_space(get_file_base());
            if ((is_integer($disk_space)) && ($disk_space < $low_space_check)) {
                require_code('notifications');
                $subject = do_lang('LOW_DISK_SPACE_SUBJECT', null, null, null, get_site_default_lang());
                $message = do_notification_lang('LOW_DISK_SPACE_MAIL', strval(intval(round($disk_space / 1024 / 1024))), null, null, get_site_default_lang());
                dispatch_notification('low_disk_space', null, $subject, $message, null, A_FROM_SYSTEM_PRIVILEGED);
            }
        }
    }
}

/**
 * Do any static cache saving that we want to do.
 *
 * @param  mixed $out Output to cache (Tempcode or string)
 * @param  string $mime_type Mime type to use
 */
function save_static_caching($out, $mime_type = 'text/html')
{
    // We cannot cache if there's a redirect
    $headers_sent = headers_list();
    foreach ($headers_sent as $header) {
        if (preg_match('#^Location:#i', $header) != 0) {
            return;
        }
    }

    global $SITE_INFO;
    if ((cms_srv('REQUEST_METHOD') != 'POST') && (isset($SITE_INFO['fast_spider_cache'])) && ($SITE_INFO['fast_spider_cache'] != '0') && (is_guest()) && (!$GLOBALS['IS_ACTUALLY_ADMIN'])) {
        if ((get_zone_name() == '') && (get_zone_default_page('') == get_page_name()) && (count(array_diff(array_keys($_GET), array('page', 'keep_session', 'keep_devtest', 'keep_failover'))) > 0)) {
            /*TODO: Reenable in v11
            if ($debugging) {
                if (php_function_allowed('error_log')) {
                    @error_log('SC save: No, home page has spurious parameters, likely a bot probing');
                }
            }*/

            return;
        }

        $bot_type = get_bot_type();
        $supports_failover_mode = (isset($SITE_INFO['failover_mode'])) && ($SITE_INFO['failover_mode'] != 'off');
        $supports_guest_caching = (isset($SITE_INFO['any_guest_cached_too'])) && ($SITE_INFO['any_guest_cached_too'] == '1');
        require_code('static_cache');
        if ((($bot_type !== null) || ($supports_failover_mode) || ($supports_guest_caching)) && (can_static_cache())) {
            $url = static_cache_current_url();
            $fast_cache_path = get_custom_file_base() . '/caches/guest_pages/' . md5($url);
            $fast_cache_path_failover_mode = $fast_cache_path;
            if ($bot_type === null) {
                $fast_cache_path .= '__non-bot';
            }
            if (!array_key_exists('js_on', $_COOKIE)) {
                $fast_cache_path .= '__no-js';
            }
            if (is_mobile()) {
                $fast_cache_path .= '__mobile';
                $fast_cache_path_failover_mode .= '__mobile';
            }
            $fast_cache_path_failover_mode .= '__failover_mode';

            if (is_object($out)) {
                $out_evaluated = $out->evaluate(null);
                $static_cache = $out_evaluated;
            } else {
                $static_cache = $out;
            }

            if (!$GLOBALS['STATIC_CACHE_ENABLED']) {
                return; // Something in the output tree decided this was not cacheable
            }

            // Remove any sessions etc
            $static_cache = preg_replace('#(&|&amp;|&amp;amp;|%3Aamp%3A|\?)?(keep_session|keep_devtest|keep_failover)(=|%3D)\w+#', '', $static_cache);

            // Add URL identifier
            $static_cache .= "\n\n" . '<!-- Cached URL ' . htmlentities($url) . ' -->';

            // Add mime type
            $static_cache .= "\n\n" . '<!-- Mime type ' . htmlentities($mime_type) . ' -->';
            $file_extension = ($mime_type == 'text/xml') ? '.xml' : '.htm';

            // Cache, but only if we want to
            //  If it's a noindex page we don't (to limit cache size). That is a deep page a bot took a look at, and we even told the bot it was not important.
            if (strpos($static_cache, '<meta name="robots" content="noindex') === false) {
                if (!is_file($fast_cache_path . $file_extension) || filemtime($fast_cache_path . $file_extension) < time() - 60 * 60 * 5) {
                    write_static_cache_file($fast_cache_path . $file_extension, $static_cache, true);
                }

                if ($supports_failover_mode) {
                    if (!is_file($fast_cache_path_failover_mode . $file_extension) || filemtime($fast_cache_path_failover_mode . $file_extension) < time() - 60 * 60 * 5) {
                        // Add failover messages
                        if (!empty($SITE_INFO['failover_message_place_after'])) {
                            $static_cache = str_replace($SITE_INFO['failover_message_place_after'], $SITE_INFO['failover_message_place_after'] . $SITE_INFO['failover_message'], $static_cache);
                        }
                        if (!empty($SITE_INFO['failover_message_place_before'])) {
                            $static_cache = str_replace($SITE_INFO['failover_message_place_before'], $SITE_INFO['failover_message'] . $SITE_INFO['failover_message_place_before'], $static_cache);
                        }

                        // Disable all form controls
                        $static_cache = preg_replace('#<(textarea|input|select|button)#', '<$1 disabled="disabled"', $static_cache);

                        write_static_cache_file($fast_cache_path_failover_mode . $file_extension, $static_cache, false);
                    }

                    if (!empty($SITE_INFO['failover_apache_rewritemap_file'])) {
                        $url_stem = $url;
                        $url_stem = str_replace(get_base_url(true) . '/', '', $url_stem);
                        $url_stem = str_replace(get_base_url(false) . '/', '', $url_stem);
                        if (preg_match('#^' . $SITE_INFO['failover_apache_rewritemap_file'] . '$#', $url_stem) != 0) {
                            if (is_mobile()) {
                                $rewritemap_file = get_custom_file_base() . '/data_custom/failover_rewritemap__mobile.txt';
                            } else {
                                $rewritemap_file = get_custom_file_base() . '/data_custom/failover_rewritemap.txt';
                            }
                            $rewritemap_file_contents = cms_file_get_contents_safe($rewritemap_file);
                            if (strpos($rewritemap_file_contents, "\n" . $url_stem . ' ') === false) {
                                require_code('files');
                                $rewritemap_file_contents .= "\n" . $url_stem . ' ' . $fast_cache_path . '__failover_mode' . $file_extension;
                                cms_file_put_contents_safe($rewritemap_file, $rewritemap_file_contents, FILE_WRITE_FIX_PERMISSIONS);
                            }
                        }
                    }
                }
            }
        }
    }
}

/**
 * Write out a static cache file.
 *
 * @param  PATH $fast_cache_path Cache file path
 * @param  string $out_evaluated Cache contents
 * @param  boolean $support_gzip Whether to allow gzipping
 */
function write_static_cache_file($fast_cache_path, $out_evaluated, $support_gzip)
{
    require_code('files');
    cms_file_put_contents_safe($fast_cache_path, $out_evaluated, FILE_WRITE_FIX_PERMISSIONS);
    if ((function_exists('gzencode')) && (php_function_allowed('ini_set')) && ($support_gzip)) {
        cms_file_put_contents_safe($fast_cache_path . '.gz', gzencode($out_evaluated, 9), FILE_WRITE_FIX_PERMISSIONS);
        //unlink($fast_cache_path); Actually, we should not assume all user agents support gzip
    }
}

/**
 * Take the specified parameters, and try to find the corresponding page, then execute a function to load the page (load_html_page/load_comcode_page).
 *
 * @param  ID_TEXT $codename The codename of the page to load
 * @param  boolean $required Whether it is required for this page to exist (shows an error if it doesn't) -- otherwise, it will just return null
 * @param  ?ID_TEXT $zone The zone the page is being loaded in (null: as shown by access URL)
 * @param  ?ID_TEXT $page_type The type of page - for if you know it (null: don't know it)
 * @param  boolean $being_included Whether the page is being included from another
 * @param  boolean $no_redirect_check Whether to not check for redirects (normally you would)
 * @param  ?object $out Semi-filled output template (null: definitely not doing output streaming)
 * @return Tempcode The page
 */
function request_page($codename, $required, $zone = null, $page_type = null, $being_included = false, $no_redirect_check = false, &$out = null)
{
    global $SITE_INFO;

    if ($zone === null) {
        $zone = get_zone_name();
    }

    if (($zone == 'site') && (get_option('collapse_user_zones') == '1')) {
        $zone = ''; // Might have been explicitly said in Tempcode, for example
    }

    $details = _request_page($codename, $zone, $page_type, null, $no_redirect_check);

    global $REQUEST_PAGE_NEST_LEVEL;
    $REQUEST_PAGE_NEST_LEVEL++;
    if ($REQUEST_PAGE_NEST_LEVEL > 20) {
        $REQUEST_PAGE_NEST_LEVEL = 0;
        attach_message(do_lang_tempcode('STOPPED_RECURSIVE_RESOURCE_INCLUDE', escape_html($codename), escape_html(do_lang('PAGE'))), 'warn');
        return new Tempcode();
    }

    // Run hooks, if any exist
    $hooks = find_all_hooks('systems', 'upon_page_load');
    foreach ($hooks as $hook => $hook_dir) {
        require_code('hooks/systems/upon_page_load/' . filter_naughty_harsh($hook), false, $hook_dir == 'sources_custom');
        $ob = object_factory('upon_page_load' . filter_naughty_harsh($hook), true);
        if ($ob === null) {
            continue;
        }
        $ob->run($codename, $required, $zone, $page_type, $being_included, $details);
    }

    if ($details === false) {
        if ($required) {
            if (get_option('url_scheme') == 'SIMPLE') {
                $details = _request_page('404', '', null, null, false);
            }
        }

        if ($details === false) {
            if ($required) {
                require_code('site2');
                $ret = page_not_found($codename, $zone);
                $REQUEST_PAGE_NEST_LEVEL--;
                return $ret;
            }
            $REQUEST_PAGE_NEST_LEVEL--;
            return new Tempcode();
        }
    }

    switch ($details[0]) {
        case 'MODULES_CUSTOM':
            $path = isset($details[3]) ? $details[3] : zone_black_magic_filterer($details[1] . (($details[1] == '') ? '' : '/') . 'pages/modules_custom/' . $details[2] . '.php', true);
            $ret = load_module_page($path, $details[2], $out);
            $REQUEST_PAGE_NEST_LEVEL--;
            return $ret;
        case 'MODULES':
            $path = isset($details[3]) ? $details[3] : zone_black_magic_filterer($details[1] . (($details[1] == '') ? '' : '/') . 'pages/modules/' . $details[2] . '.php', true);
            $ret = load_module_page($path, $details[2], $out);
            $REQUEST_PAGE_NEST_LEVEL--;
            return $ret;
        case 'COMCODE_CUSTOM':
            $path = isset($details[4]) ? $details[4] : zone_black_magic_filterer($details[1] . (($details[1] == '') ? '' : '/') . 'pages/comcode_custom/' . $details[3] . '/' . $details[2] . '.txt', true);
            if (((isset($SITE_INFO['no_disk_sanity_checks'])) && ($SITE_INFO['no_disk_sanity_checks'] == '1') && (get_custom_file_base() == get_file_base())) || (@is_file(get_custom_file_base() . '/' . $path))) {
                $ret = load_comcode_page($path, $details[1], $details[2], get_custom_file_base(), $being_included, $out);
                $REQUEST_PAGE_NEST_LEVEL--;
                return $ret;
            }
        // else roll on, as probably been deleted since persistent cache was filled
        case 'COMCODE_CUSTOM_PURE':
            $path = isset($details[4]) ? $details[4] : zone_black_magic_filterer($details[1] . (($details[1] == '') ? '' : '/') . 'pages/comcode_custom/' . $details[3] . '/' . $details[2] . '.txt', true);
            if (((isset($SITE_INFO['no_disk_sanity_checks'])) && ($SITE_INFO['no_disk_sanity_checks'] == '1')) || (@is_file(get_file_base() . '/' . $path))) {
                $ret = load_comcode_page($path, $details[1], $details[2], get_file_base(), $being_included, $out);
                $REQUEST_PAGE_NEST_LEVEL--;
                return $ret;
            }
        // else roll on, as probably been deleted since persistent cache was filled
        case 'COMCODE':
            $path = isset($details[4]) ? $details[4] : zone_black_magic_filterer($details[1] . (($details[1] == '') ? '' : '/') . 'pages/comcode/' . $details[3] . '/' . $details[2] . '.txt', true);
            if (((isset($SITE_INFO['no_disk_sanity_checks'])) && ($SITE_INFO['no_disk_sanity_checks'] == '1')) || (@is_file(get_file_base() . '/' . $path))) {
                $ret = load_comcode_page($path, $details[1], $details[2], null, $being_included, $out);
                $REQUEST_PAGE_NEST_LEVEL--;
                return $ret;
            }
        // else roll on, as probably been deleted since persistent cache was filled
        case 'HTML_CUSTOM':
            require_code('site_html_pages');
            $path = isset($details[4]) ? $details[4] : zone_black_magic_filterer($details[1] . (($details[1] == '') ? '' : '/') . 'pages/html_custom/' . $details[3] . '/' . $details[2] . '.htm', true);
            $ret = make_string_tempcode(load_html_page($path, null, $out));
            $REQUEST_PAGE_NEST_LEVEL--;
            return $ret;
        case 'HTML':
            require_code('site_html_pages');
            $path = isset($details[4]) ? $details[4] : zone_black_magic_filterer($details[1] . (($details[1] == '') ? '' : '/') . 'pages/html/' . $details[3] . '/' . $details[2] . '.htm', true);
            $ret = make_string_tempcode(load_html_page($path, null, $out));
            $REQUEST_PAGE_NEST_LEVEL--;
            return $ret;
        case 'MINIMODULES_CUSTOM':
            $path = isset($details[3]) ? $details[3] : zone_black_magic_filterer($details[1] . (($details[1] == '') ? '' : '/') . 'pages/minimodules_custom/' . $codename . '.php', true);
            $ret = load_minimodule_page($path, $out);
            $REQUEST_PAGE_NEST_LEVEL--;
            return $ret;
        case 'MINIMODULES':
            $path = isset($details[3]) ? $details[3] : zone_black_magic_filterer($details[1] . (($details[1] == '') ? '' : '/') . 'pages/minimodules/' . $codename . '.php', true);
            $ret = load_minimodule_page($path, $out);
            $REQUEST_PAGE_NEST_LEVEL--;
            return $ret;
        case 'REDIRECT':
            $redirect = $details[1];
            if ($required) {
                global $REDIRECTED_TO_CACHE;
                $REDIRECTED_TO_CACHE = $redirect;
            }
            if (strpos($redirect['r_to_page'], ':') !== false) {
                $bits = page_link_decode($redirect['r_to_zone'] . ':' . $redirect['r_to_page']);
            } else {
                $bits = array($redirect['r_to_zone'], array('page' => $redirect['r_to_page']));
            }
            // Transparent redirection?
            if (($redirect['r_is_transparent'] == 1) || ($being_included)) {
                if (($being_included) && (!has_page_access(get_member(), $redirect['r_to_page'], $redirect['r_to_zone'], true))) {
                    if ($being_included) {
                        return new Tempcode();
                    }

                    access_denied('PAGE_ACCESS');
                }

                foreach ($bits[1] as $key => $val) {
                    if ($key != 'page') {
                        $_GET[$key] = @get_magic_quotes_gpc() ? addslashes($val) : $val;
                    }
                }
                if (($redirect['r_to_page'] != $codename) || ($redirect['r_to_zone'] != $zone)) {
                    $ret = request_page($redirect['r_to_page'], $required, $redirect['r_to_zone'], null, $being_included, true/*Don't want redirect loops*/, $out);
                    $REQUEST_PAGE_NEST_LEVEL--;
                    return $ret;
                }
            } else {
                $title = get_screen_title('REDIRECTING');
                $url = build_url($bits[1], $redirect['r_to_zone'], null, true);
                set_http_status_code('301');
                $ret = redirect_screen($title, $url, do_lang_tempcode('REDIRECTED_LINK'), true);
                $REQUEST_PAGE_NEST_LEVEL--;
                return $ret;
            }
    }

    $REQUEST_PAGE_NEST_LEVEL--;
    return new Tempcode(); // should never get here
}

/**
 * Take the specified parameters, and try to find the corresponding page (caching front end).
 *
 * @param  ID_TEXT $codename The codename of the page to load
 * @param  ID_TEXT $zone The zone the page is being loaded in
 * @param  ?ID_TEXT $page_type The type of page - for if you know it (null: don't know it)
 * @param  ?LANGUAGE_NAME $lang Language name (null: user's language)
 * @param  boolean $no_redirect_check Whether to not check for redirects (normally you would)
 * @return ~array A list of details (false: page not found)
 */
function _request_page($codename, $zone, $page_type = null, $lang = null, $no_redirect_check = false)
{
    $details = persistent_cache_get(array('PAGE_INFO', $codename, $zone, $page_type, $lang, $no_redirect_check));
    if ($details === null || $details === false/*caching negativity breaks things if the page subsequently appears - even adding a page does a pre-check so would net a cached false*/) {
        $details = __request_page($codename, $zone, $page_type, $lang, $no_redirect_check);
        persistent_cache_set(array('PAGE_INFO', $codename, $zone, $page_type, $lang, $no_redirect_check), $details);
    }
    return $details;
}

/**
 * Take the specified parameters, and try to find the corresponding page.
 *
 * @param  ID_TEXT $codename The codename of the page to load
 * @param  ID_TEXT $zone The zone the page is being loaded in
 * @param  ?ID_TEXT $page_type The type of page - for if you know it (null: don't know it)
 * @param  ?LANGUAGE_NAME $lang Language name (null: user's language)
 * @param  boolean $no_redirect_check Whether to not check for redirects (normally you would)
 * @return ~array A list of details (false: page not found)
 * @ignore
 */
function __request_page($codename, $zone, $page_type = null, $lang = null, $no_redirect_check = false)
{
    if ($lang === null) {
        $lang = user_lang();
    }

    if ($codename == 'login') { // Special case
        $login_zone = get_module_zone('login');
        $path = zone_black_magic_filterer($login_zone . (($login_zone == '') ? '' : '/') . 'pages/modules_custom/' . $codename . '.php', true);
        if (@is_file(get_file_base() . '/' . $path)) {
            return array('MODULES_CUSTOM', $login_zone, $codename, $path);
        }
        $path = zone_black_magic_filterer($login_zone . (($login_zone == '') ? '' : '/') . 'pages/modules/' . $codename . '.php', true);
        if (@is_file(get_file_base() . '/' . $path)) {
            return array('MODULES', $login_zone, $codename, $path);
        }
    }

    // Redirect
    if ((!$no_redirect_check) && (get_value('no_priority_redirects') !== '1') && (addon_installed('redirects_editor'))) {
        $test = _request_page__redirects($codename, $zone);
        if ($test !== false) {
            return $test;
        }
    }

    // If we know the page type
    if ($page_type !== null) {
        switch ($page_type) {
            case 'modules_custom':
                if (!in_safe_mode()) {
                    $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/modules_custom/' . $codename . '.php', true);
                    if (@is_file(get_file_base() . '/' . $path)) {
                        return array('MODULES_CUSTOM', $zone, $codename, $path);
                    }
                }
                break;
            case 'modules':
                $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/modules/' . $codename . '.php', true);
                if (@is_file(get_file_base() . '/' . $path)) {
                    return array('MODULES', $zone, $codename, $path);
                }
                break;
            case 'comcode_custom':
                if (!in_safe_mode()) {
                    if (get_param_integer('keep_theme_test', 0) == 1) {
                        $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/comcode_custom/' . $lang . '/' . $GLOBALS['FORUM_DRIVER']->get_theme() . '__' . $codename . '.txt', true);
                        if (@is_file(get_custom_file_base() . '/' . $path)) {
                            return array('COMCODE_CUSTOM', $zone, $GLOBALS['FORUM_DRIVER']->get_theme() . '__' . $codename, $lang, $path);
                        }
                    }

                    $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/comcode_custom/' . $lang . '/' . $codename . '.txt', true);
                    if (@is_file(get_custom_file_base() . '/' . $path)) {
                        return array('COMCODE_CUSTOM', $zone, $codename, $lang, $path);
                    }
                    if (get_custom_file_base() != get_file_base()) { // For multisite installs we also will search the root site's Custom Comcode pages
                        $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/comcode_custom/' . $lang . '/' . $codename . '.txt', true);
                        if (@is_file(get_file_base() . '/' . $path)) {
                            return array('COMCODE_CUSTOM_PURE', $zone, $codename, $lang, $path);
                        }
                    }
                }
            //break;
            case 'comcode':
                $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/comcode/' . $lang . '/' . $codename . '.txt', true);
                if (@is_file(get_file_base() . '/' . $path)) {
                    return array('COMCODE', $zone, $codename, $lang, $path);
                }
                break;
            case 'html_custom':
                if (!in_safe_mode()) {
                    $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/html_custom/' . $lang . '/' . $codename . '.htm', true);
                    if (@is_file(get_custom_file_base() . '/' . $path)) {
                        return array('HTML_CUSTOM', $zone, $codename, $lang, $path);
                    }
                }
                break;
            case 'html':
                $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/html/' . $lang . '/' . $codename . '.htm', true);
                if (@is_file(get_file_base() . '/' . $path)) {
                    return array('HTML', $zone, $codename, $lang, $path);
                }
                break;
            case 'minimodules_custom':
                if (!in_safe_mode()) {
                    $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/minimodules_custom/' . $lang . '/' . $codename . '.php', true);
                    if (@is_file(get_file_base() . '/' . $path)) {
                        return array('MINIMODULES_CUSTOM', $zone, $codename, $path);
                    }
                }
                break;
            case 'minimodules':
                $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/minimodules/' . $lang . '/' . $codename . '.php', true);
                if (@is_file(get_file_base() . '/' . $path)) {
                    return array('MINIMODULES', $zone, $codename, $path);
                }
                break;
        }

        return false;
    }

    // We have a priority list to find our page
    if (!in_safe_mode()) {
        $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/minimodules_custom/' . $codename . '.php', true);
        if (@is_file(get_file_base() . '/' . $path)) {
            return array('MINIMODULES_CUSTOM', $zone, $codename, $path);
        }
    }
    if (!in_safe_mode()) {
        $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/modules_custom/' . $codename . '.php', true);
        if (@is_file(get_file_base() . '/' . $path)) {
            return array('MODULES_CUSTOM', $zone, $codename, $path);
        }
    }
    $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/modules/' . $codename . '.php', true);
    if (@is_file(get_file_base() . '/' . $path)) {
        return array('MODULES', $zone, $codename, $path);
    }
    if (!in_safe_mode()) {
        if (get_param_integer('keep_theme_test', 0) == 1) {
            $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/comcode_custom/' . $lang . '/' . $GLOBALS['FORUM_DRIVER']->get_theme() . '__' . $codename . '.txt', true);
            if (@is_file(get_custom_file_base() . '/' . $path)) {
                return array('COMCODE_CUSTOM', $zone, $GLOBALS['FORUM_DRIVER']->get_theme() . '__' . $codename, $lang, $path);
            }
        }

        $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/comcode_custom/' . $lang . '/' . $codename . '.txt', true);
        if (@is_file(get_custom_file_base() . '/' . $path)) {
            return array('COMCODE_CUSTOM', $zone, $codename, $lang, $path);
        }
        if (get_custom_file_base() != get_file_base()) { // For multisite installs we also will search the root site's Custom Comcode pages
            $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/comcode_custom/' . $lang . '/' . $codename . '.txt', true);
            if (@is_file(get_file_base() . '/' . $path)) {
                return array('COMCODE_CUSTOM_PURE', $zone, $codename, $lang, $path);
            }
        }
    }
    if (!in_safe_mode()) {
        $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/html_custom/' . $lang . '/' . $codename . '.htm', true);
        if (@is_file(get_custom_file_base() . '/' . $path)) {
            return array('HTML_CUSTOM', $zone, $codename, $lang, $path);
        }
    }
    $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/minimodules/' . $codename . '.php', true);
    if (@is_file(get_file_base() . '/' . $path)) {
        return array('MINIMODULES', $zone, $codename, $path);
    }

    // As a fallback consider it might not yet have been translated
    $fallback_lang = fallback_lang();
    $site_lang = get_site_default_lang();
    $langs_to_try = array();
    if ($lang != $site_lang) {
        $langs_to_try[] = $site_lang;
    }
    if ($lang != $fallback_lang) {
        $langs_to_try[] = $fallback_lang;
    }
    foreach ($langs_to_try as $fallback_lang) {
        if (!in_safe_mode()) {
            $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/comcode_custom/' . $fallback_lang . '/' . $codename . '.txt', true);
            if (@is_file(get_custom_file_base() . '/' . $path)) {
                return array('COMCODE_CUSTOM', $zone, $codename, $fallback_lang, $path);
            }
            if (get_custom_file_base() != get_file_base()) { // For multisite installs we also will search the root site's Custom Comcode pages
                $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/comcode_custom/' . $fallback_lang . '/' . $codename . '.txt', true);
                if (@is_file(get_file_base() . '/' . $path)) {
                    return array('COMCODE_CUSTOM_PURE', $zone, $codename, $fallback_lang, $path);
                }
            }
            $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/html_custom/' . $fallback_lang . '/' . $codename . '.htm', true);
            if (@is_file(get_custom_file_base() . '/' . $path)) {
                return array('HTML_CUSTOM', $zone, $codename, $fallback_lang, $path);
            }
        }
    }

    // Or check for default pages
    $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/comcode/' . $lang . '/' . $codename . '.txt', true);
    if (@is_file(get_file_base() . '/' . $path)) {
        return array('COMCODE', $zone, $codename, $lang, $path);
    }
    $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/html/' . $lang . '/' . $codename . '.htm', true);
    if (@is_file(get_file_base() . '/' . $path)) {
        return array('HTML', $zone, $codename, $lang, $path);
    }
    foreach ($langs_to_try as $fallback_lang) {
        $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/comcode/' . $fallback_lang . '/' . $codename . '.txt', true);
        if (@is_file(get_file_base() . '/' . $path)) {
            return array('COMCODE', $zone, $codename, $fallback_lang, $path);
        }
        $path = zone_black_magic_filterer($zone . (($zone == '') ? '' : '/') . 'pages/html/' . $fallback_lang . '/' . $codename . '.htm', true);
        if (@is_file(get_file_base() . '/' . $path)) {
            return array('HTML', $zone, $codename, $fallback_lang, $path);
        }
    }

    // Redirect
    if ((!$no_redirect_check) && (addon_installed('redirects_editor'))) {
        $test = _request_page__redirects($codename, $zone, true/*include wildcards as this is lowest priority*/);
        if ($test !== false) {
            return $test;
        }
    }

    return false;
}

/**
 * Take the specified parameters, and try to find a redirect for the corresponding page.
 *
 * @param  ID_TEXT $codename The codename of the page to load
 * @param  ID_TEXT $zone The zone the page is being loaded in
 * @param  boolean $wildcard_mode Whether to also search in wildcard mode
 * @return ~array A list of details (false: page not found)
 * @ignore
 */
function _request_page__redirects($codename, $zone, $wildcard_mode = false)
{
    $codename = strtolower($codename);

    static $internal_cache = array();
    if (isset($internal_cache[$codename][$zone][$wildcard_mode])) {
        return $internal_cache[$codename][$zone][$wildcard_mode];
    }

    global $REDIRECT_CACHE;
    if ($REDIRECT_CACHE === null) {
        load_redirect_cache();
    }
    if (isset($REDIRECT_CACHE)) {
        if (isset($REDIRECT_CACHE[$zone][$codename])) {
            $redirect = array($REDIRECT_CACHE[$zone][$codename]);
        } elseif (($wildcard_mode) && (isset($REDIRECT_CACHE['*'][$codename]))) {
            $redirect = array($REDIRECT_CACHE['*'][$codename]);
        } else {
            $redirect = array();
        }
    } else {
        $query = 'SELECT * FROM ' . get_table_prefix() . 'redirects WHERE (' . db_string_equal_to('r_from_zone', $zone);
        if ($wildcard_mode) {
            $query .= ' OR ' . db_string_equal_to('r_from_zone', '*');
        }
        $query .= ') AND ' . db_string_equal_to('r_from_page', $codename);
        $query .= ' ORDER BY r_from_zone DESC'; // The ordering ensures '*' comes last, as we want to deprioritise this
        $redirect = $GLOBALS['SITE_DB']->query($query, 1, null, false, true);
    }

    if (isset($redirect[0])) {
        $ret = array('REDIRECT', $redirect[0]);
        $internal_cache[$codename][$zone][$wildcard_mode] = $ret;
        return $ret;
    }

    $internal_cache[$codename][$zone][$wildcard_mode] = false;
    return false;
}

/**
 * Get a Comcode page from the cache.
 *
 * @param  ID_TEXT $codename The codename of the page
 * @param  ID_TEXT $zone The zone the page is being loaded from
 * @param  ID_TEXT $theme The theme
 * @return array The page row
 */
function load_comcode_page_from_cache($codename, $zone, $theme)
{
    $tuple = array($codename, $zone, $theme);

    global $SMART_CACHE;
    if ($SMART_CACHE !== null) {
        $SMART_CACHE->append('comcode_pages_needed', serialize($tuple));
    }

    $ret = _load_comcodes_page_from_cache(array($tuple));
    return isset($ret[0]) ? $ret[0] : null;
}

/**
 * Wraps load_comcode_page_from_cache for bulk loading.
 *
 * @param  array $pages Details of pages to load
 * @return array Database rows
 *
 * @ignore
 */
function _load_comcodes_page_from_cache($pages)
{
    global $COMCODE_PAGE_RUNTIME_CACHE;

    $ret = array();

    $needs_query = false;

    $sql = 'SELECT * FROM ' . get_table_prefix() . 'cached_comcode_pages a JOIN ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'comcode_pages b ON a.the_page=b.the_page AND a.the_zone=b.the_zone';
    $sql .= ' WHERE 1=0';
    foreach ($pages as $page) {
        $sz = serialize($page);
        if (isset($COMCODE_PAGE_RUNTIME_CACHE[$sz])) {
            $ret[] = $COMCODE_PAGE_RUNTIME_CACHE[$sz];
        } else {
            list($codename, $zone, $theme) = $page;

            $sql .= ' OR ';
            $sql .= db_string_equal_to('a.the_page', $codename) . ' AND ' . db_string_equal_to('a.the_zone', $zone) . ' AND ' . db_string_equal_to('the_theme', $theme);

            $needs_query = true;
        }
    }
    if ($needs_query) {
        $_ret = $GLOBALS['SITE_DB']->query($sql, null, null, false, false, array('string_index' => 'LONG_TRANS__COMCODE', 'cc_page_title' => '?SHORT_TRANS'));
        foreach ($_ret as $_page) {
            $sz = serialize(array($_page['the_page'], $_page['the_zone'], $_page['the_theme']));
            $COMCODE_PAGE_RUNTIME_CACHE[$sz] = $_page;
            $ret[] = $_page;
        }
    }

    return $ret;
}

/**
 * Get the parsed contents of a Comcode page.
 *
 * @param  PATH $string The relative (to Composr's base directory) path to the page (e.g. pages/comcode/EN/start.txt)
 * @param  ID_TEXT $zone The zone the page is being loaded from
 * @param  ID_TEXT $codename The codename of the page
 * @param  ?PATH $file_base The file base to load from (null: standard)
 * @param  boolean $being_included Whether the page is being included from another
 * @param  ?object $out Semi-filled output template (null: definitely not doing output streaming)
 * @return Tempcode The page
 */
function load_comcode_page($string, $zone, $codename, $file_base = null, $being_included = false, &$out = null)
{
    if ($file_base === null) {
        $file_base = get_file_base();
    }

    if (!$being_included) {
        $GLOBALS['TITLE_CALLED'] = true;
    }

    $is_panel = (substr($codename, 0, 6) == 'panel_') || ((strpos($codename, 'panel_') !== false) && (get_param_integer('keep_theme_test', 0) == 1));

    if ($zone == '' && $codename == '404') {
        set_http_status_code('404');
    }

    if (
        (
            ($is_panel) ||
            ($codename[0] == '_') ||
            ($zone . ':' . $codename == ':404') ||

            // FUDGE. Sculpt what comes up in Google a bit. We don't want really meta contextual help muddying search results
            ($codename == 'rules') ||
            ($zone . ':' . $codename == ':recommend_help') ||
            ($zone . ':' . $codename == ':popup_blockers') ||
            ($zone . ':' . $codename == ':help') ||
            ($zone . ':' . $codename == ':userguide_chatcode') ||
            ($zone . ':' . $codename == ':userguide_comcode') ||
            ($zone . ':' . $codename == 'site:popup_blockers') ||
            ($zone . ':' . $codename == 'site:help') ||
            ($zone . ':' . $codename == 'site:userguide_chatcode') ||
            ($zone . ':' . $codename == 'site:userguide_comcode')
        ) &&
        (get_page_name() == $codename/*Top-level*/)
    ) {
        attach_to_screen_header('<meta name="robots" content="noindex" />'); // XHTMLXHTML
    }

    if ($zone == 'adminzone') {
        require_code('site_adminzone');
        adminzone_special_cases($codename);
    }

    if ($codename == 'sitemap') {
        set_feed_url('?mode=comcode_pages&select=' . urlencode($zone));
    }

    global $PAGE_STRING, $COMCODE_PARSE_TITLE, $LAST_COMCODE_PARSED_TITLE;
    $COMCODE_PARSE_TITLE = null;
    if ($PAGE_STRING === null && !$being_included && !$is_panel) {
        $PAGE_STRING = $string;
    }

    $new_comcode_page_row = array(
        'the_zone' => $zone,
        'the_page' => $codename,
        'p_parent_page' => '',
        'p_validated' => 1,
        'p_edit_date' => null,
        'p_add_date' => null,
        'p_submitter' => null,
        'p_show_as_edit' => 0,
        'p_order' => 0,
    );

    if ((has_caching_for('comcode_page')) && (get_param_integer('keep_print', 0) == 0)) {
        $support_smart_decaching = support_smart_decaching();

        if (is_browser_decaching()) {
            $comcode_page = $GLOBALS['SITE_DB']->query_select('cached_comcode_pages', array('string_index', 'cc_page_title'), array('the_page' => $codename, 'the_zone' => $zone, 'the_theme' => $GLOBALS['FORUM_DRIVER']->get_theme()), '', 1, 0, false, array());
            if (isset($comcode_page[0])) {
                if ($comcode_page[0]['string_index'] !== null) {
                    delete_lang($comcode_page[0]['string_index']);
                }
                $GLOBALS['SITE_DB']->query_delete('cached_comcode_pages', array('the_page' => $codename, 'the_zone' => $zone));
                $GLOBALS['COMCODE_PAGE_RUNTIME_CACHE'] = array();
            }
        }
        $theme = $GLOBALS['FORUM_DRIVER']->get_theme();
        if ($GLOBALS['PERSISTENT_CACHE'] !== null) {
            if ($support_smart_decaching) {
                $mtime = filemtime($file_base . '/' . $string);
                if ($mtime > time()) {
                    $mtime = time(); // Timezone error, we have to assume that cache is ok rather than letting us get in a loop decaching the file. It'll get fixed automatically in a few hours when the hours of the timezone difference passes.
                }
                $pcache = persistent_cache_get(array('COMCODE_PAGE', $codename, $zone, $theme, user_lang()), $mtime);
            } else {
                $pcache = persistent_cache_get(array('COMCODE_PAGE', $codename, $zone, $theme, user_lang()));
            }
        } else {
            $pcache = null;
        }
        if ($pcache === null) {
            $comcode_page_row = load_comcode_page_from_cache($codename, $zone, $theme);
            if ($comcode_page_row !== null) {
                if ($support_smart_decaching) {
                    $mtime = filemtime($file_base . '/' . $string);
                    if ($mtime > time()) {
                        $mtime = time(); // Timezone error, we have to assume that cache is ok rather than letting us get in a loop decaching the file. It'll get fixed automatically in a few hours when the hours of the timezone difference passes.
                    }
                }
                if ((!$support_smart_decaching) || ((($comcode_page_row['p_edit_date'] !== null) && ($comcode_page_row['p_edit_date'] >= $mtime)) || (($comcode_page_row['p_edit_date'] === null) && ($comcode_page_row['p_add_date'] !== null) && ($comcode_page_row['p_add_date'] >= $mtime)))) { // Make sure it has not been edited since last edited or created
                    // Optimised path for empty panels when no super-fast persistent cache
                    if ($support_smart_decaching/*only should do an fstat if this is enabled*/ && ($being_included || $is_panel) && $GLOBALS['PERSISTENT_CACHE'] === null && filesize($file_base . '/' . $string) == 0) {
                        return new Tempcode();
                    }

                    $just_comcode_page_row = db_map_restrict($comcode_page_row, array('the_page', 'the_zone', 'the_theme', 'string_index'));
                    $db_set = get_translated_tempcode('cached_comcode_pages', $just_comcode_page_row, 'string_index', null, user_lang(), true, true/*,true*/);
                } else {
                    $mtime = filemtime($file_base . '/' . $string);
                    if ($mtime > time()) {
                        $mtime = time(); // Timezone error, we have to assume that cache is ok rather than letting us get in a loop decaching the file. It'll get fixed automatically in a few hours when the hours of the timezone difference passes.
                    }
                    $GLOBALS['SITE_DB']->query_update('comcode_pages', array('p_edit_date' => $mtime), array('the_page' => $codename, 'the_zone' => $zone), '', 1);
                    $GLOBALS['SITE_DB']->query_delete('cached_comcode_pages', array('the_zone' => $zone, 'the_page' => $codename));
                    delete_lang($comcode_page_row['string_index']);
                    $GLOBALS['COMCODE_PAGE_RUNTIME_CACHE'] = array();

                    $db_set = null;
                    $comcode_page_row = null;
                }
            } else {
                $db_set = null;
                $comcode_page_row = null;
            }
            if ($db_set !== null) {
                $index = $comcode_page_row['string_index'];
                $title_to_use = $comcode_page_row['cc_page_title'];
                if ($title_to_use !== null) {
                    $title_to_use = get_translated_text($title_to_use, null, null, true);
                    if ($title_to_use === null) {
                        $title_to_use = $codename;
                    }
                }
                $html = $db_set;
                $raw_comcode = get_translated_text($comcode_page_row['string_index']);
            } else {
                $comcode_page = $GLOBALS['SITE_DB']->query_select('comcode_pages', array('*'), array('the_page' => $codename, 'the_zone' => $zone), '', 1);
                if (isset($comcode_page[0])) {
                    $comcode_page_row = $comcode_page[0];
                }

                require_code('site2');
                $new_comcode_page_row['p_add_date'] = filectime($file_base . '/' . $string);
                list($html, $title_to_use, $comcode_page_row, $raw_comcode) = _load_comcode_page_not_cached($string, $zone, $codename, $file_base, $comcode_page_row, $new_comcode_page_row, $being_included);
            }

            persistent_cache_set(array('COMCODE_PAGE', $codename, $zone, $theme, user_lang()), array($html, $title_to_use, $comcode_page_row, $raw_comcode));
        } else {
            list($html, $title_to_use, $comcode_page_row, $raw_comcode) = $pcache;
        }
    } else {
        require_code('site2');
        $new_comcode_page_row['p_add_date'] = filectime($file_base . '/' . $string);
        list($html, $comcode_page_row, $title_to_use, $raw_comcode) = _load_comcode_page_cache_off($string, $zone, $codename, $file_base, $new_comcode_page_row, $being_included);
    }
    $filtered_title_to_use = mixed();
    if ((!$is_panel) && (!$being_included)) {
        if (($title_to_use !== null) && ($title_to_use != '')) {
            get_screen_title($title_to_use, false); // Little hack - this will force DISPLAYED_TITLE to get set.
            $filtered_title_to_use = strip_html($title_to_use);
        }
        seo_meta_load_for('comcode_page', $zone . ':' . $codename, $filtered_title_to_use);
    }
    $LAST_COMCODE_PARSED_TITLE = $title_to_use;

    if (($html->is_empty_shell()) && ($being_included || $is_panel)) {
        return $html;
    }

    if ((function_exists('has_edit_comcode_page_permission')) && (has_edit_comcode_page_permission($zone, $codename, $comcode_page_row['p_submitter']))) {
        $redirect = get_self_url(true, false, array('redirect' => null, 'redirected' => null));
        if ((($codename == 'panel_left') || ($codename == 'panel_right')) && (has_js()) && (has_actual_page_access(get_member(), 'admin_zones'))) {
            $edit_url = build_url(array('page' => 'admin_zones', 'type' => '_editor', 'id' => get_zone_name(), 'redirect' => $redirect), get_module_zone('admin_zones'));
        } else {
            $edit_url = build_url(array('page' => 'cms_comcode_pages', 'type' => '_edit', 'page_link' => $zone . ':' . $codename, /*'lang' => user_lang(), */'redirect' => $redirect), get_module_zone('cms_comcode_pages'));
        }
    } else {
        $edit_url = new Tempcode();
    }
    $add_child_url = new Tempcode();
    if (has_add_comcode_page_permission($zone)) {
        if (strpos($raw_comcode, 'main_comcode_page_children') !== false) {
            $add_child_url = (get_option('is_on_comcode_page_children') == '1') ? build_url(array('page' => 'cms_comcode_pages', 'type' => '_edit', 'parent_page' => $codename, 'page_link' => $zone . ':'/*Don't make too many assumptions about user flow , 'lang' => user_lang()*//*, 'redirect' => $redirect*/), get_module_zone('cms_comcode_pages')) : new Tempcode();
        }
    }

    $warning_details = new Tempcode();
    if (($comcode_page_row['p_validated'] !== null) && ($comcode_page_row['p_validated'] == 0) && (!$being_included)) {
        require_code('site2');
        $warning_details = get_page_warning_details($zone, $codename, $edit_url);
    }

    if ((!$is_panel) && ($title_to_use !== null) && (!$being_included)) {
        global $PT_PAIR_CACHE_CP;
        $PT_PAIR_CACHE_CP[$codename]['cc_page_title'] = ($title_to_use === null) ? do_lang_tempcode('NA_EM') : protect_from_escaping(escape_html($title_to_use));
        $PT_PAIR_CACHE_CP[$codename]['p_parent_page'] = $comcode_page_row['p_parent_page'];
        $comcode_breadcrumbs = comcode_breadcrumbs($codename, $zone, get_param_string('keep_page_root', ''), ($comcode_page_row['p_parent_page'] == '') || !has_privilege(get_member(), 'open_virtual_roots'));
        breadcrumb_set_parents($comcode_breadcrumbs);

        set_extra_request_metadata(array(
            'title' => ($title_to_use == '') ? null : ('[semihtml]' . $title_to_use . '[/semihtml]'),
            'identifier' => $zone . ':' . $codename,
        ), $comcode_page_row, 'comcode_page', $zone . ':' . $codename);
    }

    if (($GLOBALS['OUTPUT_STREAMING']) && ($out !== null)) {
        $GLOBALS['TEMPCODE_CURRENT_PAGE_OUTPUTTING'] = $out;

        $out->evaluate_echo(null, true);
    }

    global $SCREEN_TEMPLATE_CALLED;
    $st = $SCREEN_TEMPLATE_CALLED;
    $ret = do_template('COMCODE_PAGE_SCREEN', array(
        '_GUID' => '0fc4fe4f27e54aaaa2b7e4848c02bacb',
        'IS_PANEL' => $is_panel,
        'BEING_INCLUDED' => $being_included,
        'SUBMITTER' => strval($comcode_page_row['p_submitter']),
        'TAGS' => (get_option('show_content_tagging') == '0') ? /*optimisation, can be intensive with many page includes*/
            new Tempcode() : get_loaded_tags('comcode_pages'),
        'WARNING_DETAILS' => $warning_details,
        'EDIT_DATE_RAW' => ($comcode_page_row['p_edit_date'] === null) ? '' : strval($comcode_page_row['p_edit_date']),
        'SHOW_AS_EDIT' => $comcode_page_row['p_show_as_edit'] == 1,
        'CONTENT' => $html,
        'EDIT_URL' => $edit_url,
        'ADD_CHILD_URL' => $add_child_url,
        'NAME' => $codename,
        'NATIVE_ZONE' => $zone,
    ));
    if (($is_panel) || ($being_included)) {
        $SCREEN_TEMPLATE_CALLED = $st;
    }
    return $ret;
}

/**
 * Get a route from a known Comcode page back to the declared root of the tree.
 *
 * @param  ID_TEXT $the_page The Comcode page name
 * @param  ID_TEXT $the_zone The Comcode page zone
 * @param  ID_TEXT $root The virtual root
 * @param  boolean $no_link_for_me_sir Whether not to put a link at this point in the navigation tree (usually, because the viewer is already at it)
 * @param  integer $jumps The number of jumps we have gone through so far (cuts out after 10 as a failsafe)
 * @return array The breadcrumbs
 */
function comcode_breadcrumbs($the_page, $the_zone, $root = '', $no_link_for_me_sir = true, $jumps = 0)
{
    // Cut off broken recursion
    if ($jumps == 10) {
        return array(); // Probably a loop
    }
    if ($the_page == '') {
        return array();
    }

    // Find link
    $map = array('page' => $the_page);
    if ($jumps == 0) {
        $map['keep_page_root'] = $the_page;
    } elseif ($root != '') {
        $map['keep_page_root'] = $root;
    }
    $page_link = build_page_link($map, $the_zone);

    // Find title
    global $PT_PAIR_CACHE_CP;
    if (!array_key_exists($the_page, $PT_PAIR_CACHE_CP)) {
        $page_rows = $GLOBALS['SITE_DB']->query_select('cached_comcode_pages a JOIN ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'comcode_pages b ON (a.the_page=b.the_page AND a.the_zone=b.the_zone)', array('cc_page_title', 'p_parent_page'), array('a.the_page' => $the_page, 'a.the_zone' => $the_zone), '', 1, null, false, array('cc_page_title' => '?SHORT_TRANS'));
        if (!array_key_exists(0, $page_rows)) {
            global $DISPLAYED_TITLE;

            push_output_state();
            $DISPLAYED_TITLE = new Tempcode();
            request_page($the_page, false, $the_zone, null, true); // It's not cached, force the issue and then try again...
            $temp_title = $DISPLAYED_TITLE;
            restore_output_state();

            $page_rows = $GLOBALS['SITE_DB']->query_select('cached_comcode_pages a JOIN ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'comcode_pages b ON (a.the_page=b.the_page AND a.the_zone=b.the_zone)', array('cc_page_title', 'p_parent_page'), array('a.the_page' => $the_page, 'a.the_zone' => $the_zone), '', 1, null, false, array('cc_page_title' => '?SHORT_TRANS'));
            if (!array_key_exists(0, $page_rows)) { // Oh well, fallback (maybe page doesn't exist yet, ?)...
                $PT_PAIR_CACHE_CP[$the_page] = array();
                $PT_PAIR_CACHE_CP[$the_page]['cc_page_title'] = $temp_title->evaluate();
                if ($PT_PAIR_CACHE_CP[$the_page]['cc_page_title'] == '') {
                    $PT_PAIR_CACHE_CP[$the_page]['cc_page_title'] = $the_page;
                }
                $PT_PAIR_CACHE_CP[$the_page]['p_parent_page'] = '';
            }
        }
        if (array_key_exists(0, $page_rows)) {
            $PT_PAIR_CACHE_CP[$the_page] = $page_rows[0];
            $_title = get_translated_text($PT_PAIR_CACHE_CP[$the_page]['cc_page_title'], null, null, true);
            if ($_title === null) {
                $_title = $the_page;
            }
            $PT_PAIR_CACHE_CP[$the_page]['cc_page_title'] = $_title;
        }
    }
    $title = $PT_PAIR_CACHE_CP[$the_page]['cc_page_title'];
    if ($title === null) {
        $title = $the_page;
    }

    // End of recursion
    if ($the_page == $root) {
        if ($no_link_for_me_sir) {
            return array();
        }
        return array(array($page_link, is_object($title) ? $title : protect_from_escaping(escape_html($title))));
    }

    // Cut off broken recursion
    if ($PT_PAIR_CACHE_CP[$the_page]['p_parent_page'] == $the_page) {
        fatal_exit(do_lang_tempcode('RECURSIVE_TREE_CHAIN', escape_html($the_page), 'comcode_page'));
    }

    // Our point in the chain
    $segments = array();
    if (!$no_link_for_me_sir) {
        $segments[] = array($page_link, is_object($title) ? $title : protect_from_escaping(escape_html($title)), ($jumps == 0) ? do_lang('VIRTUAL_ROOT') : '');
    } else {
        if (!(((is_string($title)) && ($title == '')) || ((is_object($title)) && ($title->is_empty())))) {
            $segments[] = array('', is_object($title) ? $title : protect_from_escaping(escape_html($title)));
        }
    }

    // Further recursion
    $below = comcode_breadcrumbs($PT_PAIR_CACHE_CP[$the_page]['p_parent_page'], $the_zone, $root, false, $jumps + 1);
    return array_merge($below, $segments);
}

/**
 * Log statistics for the page view.
 *
 * @param  string $string The string to the page file
 * @param  integer $pg_time The time taken for page loading in milliseconds
 */
function log_stats($string, $pg_time)
{
    if (!addon_installed('stats')) {
        return;
    }

    if ((get_option('site_closed') == '1') && (get_option('stats_when_closed') == '0')) {
        return;
    }

    if ((get_option('super_logging') == '1') || (get_param_string('track', null) !== null)) {
        $get = substr(flatten_slashed_array($_GET), 0, 255);
        $post2 = $_POST;
        unset($post2['password']);
        unset($post2['password_confirm']);
        unset($post2['decrypt']);
        $post = flatten_slashed_array($post2);
    } else {
        $get = '';
        $post = '';
    }
    $page = $string;
    $ip = get_ip_address();
    $session_id = get_session_id();
    global $IS_ACTUALLY;
    $member = ($IS_ACTUALLY === null) ? get_member() : $IS_ACTUALLY;
    $time = time();
    $referer = cms_mb_substr(cms_srv('HTTP_REFERER'), 0, 255);
    $browser = cms_mb_substr(get_browser_string(), 0, 255);
    $os = cms_mb_substr(get_os_string(), 0, 255);
    if ($os === null) {
        $os = '';
    }

    if ((get_option('bot_stats') == '1') && ((stripos($browser, 'http:') !== false) || (stripos($browser, 'bot') !== false) || (get_bot_type() !== null))) {
        return;
    }

    $GLOBALS['SITE_DB']->query_insert('stats', array(
        'access_denied_counter' => 0,
        'browser' => $browser,
        'operating_system' => $os,
        'the_page' => $page,
        'ip' => $ip,
        'session_id' => $session_id,
        'member_id' => $member,
        'date_and_time' => $time,
        'referer' => $referer,
        's_get' => $get,
        'post' => $post,
        'milliseconds' => intval($pg_time),
    ), false, true);
    if (mt_rand(0, 100) == 1) {
        if (!$GLOBALS['SITE_DB']->table_is_locked('stats')) {
            $GLOBALS['SITE_DB']->query('DELETE FROM ' . get_table_prefix() . 'stats WHERE date_and_time<' . strval(time() - 60 * 60 * 24 * intval(get_option('stats_store_time'))), 500/*to reduce lock times*/);
        }
    }

    global $SITE_INFO;
    if (isset($SITE_INFO['throttle_bandwidth_views_per_meg'])) {
        if (!$GLOBALS['SITE_DB']->table_is_locked('values')) {
            set_value('page_views', strval(intval(get_value('page_views')) + 1));
        }
    }
}

/* *
 * Output filter to compress HTML.
 *
 * @param  string $data The HTML to filter
 * @return string Compressed HTML
 */
/*f unction _compress_html_output($data)
{
    return preg_replace(array('#>[ \t]+#', '#[ \t]+<#', '#\n[ \t]+\r?\n#', '#\n+#'), array('> ', ' <', "\n", "\n"), $data);
}*/
