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

*/

/*EXTRA FUNCTIONS: memory_get_peak_usage|error_get_last*/

/**
 * @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__global2()
{
    fixup_bad_php_env_vars();

    safe_ini_set('log_errors', '1');
    if ((GOOGLE_APPENGINE) && (!appengine_is_live())) {
        @mkdir(get_custom_file_base() . '/data_custom', 0755);
    }
    $error_log_path = get_custom_file_base() . '/data_custom/errorlog.php';
    safe_ini_set('error_log', $error_log_path);
    if (is_file($error_log_path) && filesize($error_log_path) < 17) {
        @file_put_contents($error_log_path, "<" . "?php return; ?" . ">\n", LOCK_EX);
    }

    global $BOOTSTRAPPING, $CHECKING_SAFEMODE, $BROWSER_DECACHEING_CACHE, $CHARSET_CACHE, $TEMP_CHARSET_CACHE, $RELATIVE_PATH, $CURRENTLY_HTTPS_CACHE, $RUNNING_SCRIPT_CACHE, $SERVER_TIMEZONE_CACHE, $HAS_SET_ERROR_HANDLER, $DYING_BADLY, $XSS_DETECT, $SITE_INFO, $IN_MINIKERNEL_VERSION, $EXITING, $FILE_BASE, $CACHE_TEMPLATES, $BASE_URL_HTTP_CACHE, $BASE_URL_HTTPS_CACHE, $WORDS_TO_FILTER_CACHE, $FIELD_RESTRICTIONS, $VALID_ENCODING, $CONVERTED_ENCODING, $MICRO_BOOTUP, $MICRO_AJAX_BOOTUP, $QUERY_LOG, $CURRENT_SHARE_USER, $FIND_SCRIPT_CACHE, $WHAT_IS_RUNNING_CACHE, $DEV_MODE, $SEMI_DEV_MODE, $IS_VIRTUALISED_REQUEST, $FILE_ARRAY, $DIR_ARRAY, $JAVASCRIPTS_DEFAULT, $JAVASCRIPTS, $JAVASCRIPT_BOTTOM, $KNOWN_AJAX, $KNOWN_UTF8, $CSRF_TOKENS, $STATIC_CACHE_ENABLED, $IN_SELF_ROUTING_SCRIPT;

    @ob_end_clean(); // Reset to have no output buffering by default (we'll use it internally, taking complete control)

    // Don't want the browser caching PHP output, explicitly say this
    @header('Expires: Mon, 20 Dec 1998 01:00:00 GMT');
    @header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
    @header('Cache-Control: no-cache, max-age=0');
    @header('Pragma: no-cache'); // for proxies, and also IE
    if (php_function_allowed('session_cache_limiter')) {
        @session_cache_limiter('');
    }

    // Closed site message
    if ((is_file('closed.html')) && (get_param_integer('keep_force_open', 0) == 0)) {
        if ((strpos($_SERVER['SCRIPT_NAME'], 'upgrader.php') === false) && (strpos($_SERVER['SCRIPT_NAME'], 'execute_temp.php') === false) && ((!isset($SITE_INFO['no_extra_closed_file'])) || ($SITE_INFO['no_extra_closed_file'] == '0'))) {
            if ((@strpos($_SERVER['SERVER_SOFTWARE'], 'IIS') === false)) {
                header('HTTP/1.0 503 Service Temporarily Unavailable');
            }
            header('Location: ' . (is_file($RELATIVE_PATH . 'closed.html') ? 'closed.html' : '../closed.html'));
            exit();
        }
    }

    // Initialise some globals
    $JAVASCRIPTS_DEFAULT = array('global' => true, 'transitions' => true, 'modalwindow' => true, 'custom_globals' => true);
    $JAVASCRIPT_BOTTOM = array();
    $RUNNING_SCRIPT_CACHE = array();
    $BROWSER_DECACHEING_CACHE = null;
    $CHARSET_CACHE = null;
    $TEMP_CHARSET_CACHE = null;
    $CURRENTLY_HTTPS_CACHE = null;
    $FIND_SCRIPT_CACHE = array();
    $WHAT_IS_RUNNING_CACHE = current_script();
    $WORDS_TO_FILTER_CACHE = null;
    $FIELD_RESTRICTIONS = null;
    $VALID_ENCODING = false;
    $CONVERTED_ENCODING = false;
    $KNOWN_AJAX = false;
    /** Whether we are loading up in micro-bootup mode (reduced amount of loading for quicker simple responses - mainly no member logins, and assumed non-page output and non-generative code).
     *
     * @global boolean $MICRO_BOOTUP
     */
    if (!isset($MICRO_BOOTUP)) {
        $MICRO_BOOTUP = false;
    }
    /** Whether we are loading up in micro-ajax-bootup mode (reduced amount of loading for quicker simple AJAX responses).
     *
     * @global boolean $MICRO_AJAX_BOOTUP
     */
    if (!isset($MICRO_AJAX_BOOTUP)) {
        $MICRO_AJAX_BOOTUP = false;
    }
    /** Whether we know input text is in UTF8 because it came from an AJAX call (which is always UTF).
     *
     * @global boolean $KNOWN_UTF8
     */
    if (!isset($KNOWN_UTF8)) {
        $KNOWN_UTF8 = false;
    }
    /** Whether we know we need to do CSRF token checks.
     *
     * @global boolean $CSRF_TOKENS
     */
    if (!isset($CSRF_TOKENS)) {
        $CSRF_TOKENS = false;
    }
    /** Whether we enable the static cache for this request.
     *
     * @global boolean $STATIC_CACHE_ENABLED
     */
    if (!isset($STATIC_CACHE_ENABLED)) {
        $STATIC_CACHE_ENABLED = false;
    }
    /** Whether build_url requests point through the running script, as opposed to pointing to an index.php call.
     *
     * @global boolean $IN_SELF_ROUTING_SCRIPT
     */
    if (!isset($IN_SELF_ROUTING_SCRIPT)) {
        $IN_SELF_ROUTING_SCRIPT = (current_script() == 'index')/*LEGACY - ideally just have as false*/;
    }
    $CACHE_TEMPLATES = true;
    $IS_VIRTUALISED_REQUEST = false;
    /** On the quick installer, this presents manifest information about files that exist in the virtual filesystem.
     *
     * @global ?array $FILE_ARRAY
     */
    $FILE_ARRAY = null;
    /** On the quick installer, this presents manifest information about directories that exist in the virtual filesystem.
     *
     * @global ?array $DIR_ARRAY
     */
    $DIR_ARRAY = null;

    // Keep check of our bootstrapping
    $BOOTSTRAPPING = true;
    $CHECKING_SAFEMODE = false;

    // Set cross-domain headers (COR)
    if (isset($_SERVER['HTTP_ORIGIN'])) {
        require_code('ajax');
        cor_prepare();
    }

    if ((running_script('messages')) && (get_param_string('action', 'new') == 'new')) { // Architecturally hackerish chat message precheck (for extra efficiency)
        require_code('chat_poller');
        chat_poller();
    }
    if ((running_script('notifications')) && (@filemtime(get_custom_file_base() . '/data_custom/modules/web_notifications/latest.dat') >= get_param_integer('time_barrier'))) {
        prepare_for_known_ajax_response();

        header('Content-Type: application/xml');

        //  encoding="' . get_charset() . '" not needed due to no data in it
        $output = '<?xml version="1.0" ?' . '><response><result></result></response>';
    }

    // Initialise timezones
    $SERVER_TIMEZONE_CACHE = @date_default_timezone_get();
    if ($SERVER_TIMEZONE_CACHE != 'UTC') {
        date_default_timezone_set('UTC');
    }
    safe_ini_set('date.timezone', 'UTC'); // In case PHP does not have it configured, would produce a warning

    // Initialise some error handling
    error_reporting(E_ALL);
    $HAS_SET_ERROR_HANDLER = false;
    $DYING_BADLY = false; // If Composr is bailing out uncontrollably, setting this will make sure the error hander does not try and suppress

    // Dev mode stuff
    /** Whether the ocProducts version of PHP is running, and hence whether XSS-detection is enabled, and hence whether we may need to carry through additional metadata to make sure it operates correctly. Stored in a global for quick check (good performance).
     *
     * @global boolean $XSS_DETECT
     */
    $XSS_DETECT = function_exists('ocp_mark_as_escaped');
    /** Whether Composr is running in development mode
     *
     * @global boolean $DEV_MODE
     */
    $DEV_MODE = (((!array_key_exists('dev_mode', $SITE_INFO) || ($SITE_INFO['dev_mode'] == '1')) && (is_dir(get_file_base() . '/.git') || (function_exists('ocp_mark_as_escaped')))) && ((!array_key_exists('keep_no_dev_mode', $_GET) || ($_GET['keep_no_dev_mode'] == '0'))));
    /** Whether Composr is running in a more limited development mode, which may make things a bit slower and more verbose, but won't run such severe standard enforcement tricks
     *
     * @global boolean $SEMI_DEV_MODE
     */
    $SEMI_DEV_MODE = (((!array_key_exists('dev_mode', $SITE_INFO) || ($SITE_INFO['dev_mode'] == '1')) && (is_dir(get_file_base() . '/.git') || (function_exists('ocp_mark_as_escaped')))));
    if (php_function_allowed('set_time_limit')) {
        @set_time_limit(isset($SITE_INFO['max_execution_time']) ? intval($SITE_INFO['max_execution_time']) : 60);
    }
    if ($DEV_MODE) {
        if (php_function_allowed('set_time_limit')) {
            @set_time_limit(10);
        }
        //safe_ini_set('ocproducts.type_strictness', '1');  Disabled due to incompatibility with ocP PHP7. v11 will turn back on.
        //safe_ini_set('ocproducts.xss_detect', '1');  Disabled due to incompatibility with ocP PHP7. v11 will turn back on.
    }
    if ($DEV_MODE || $SEMI_DEV_MODE) {
        require_code('developer_tools');
    }

    // Load most basic config
    /** Whether Composr is currently running from the 'minikernel' used during installation
     *
     * @global boolean $IN_MINIKERNEL_VERSION
     */
    $IN_MINIKERNEL_VERSION = false;
    $EXITING = 0;
    if ((array_key_exists('use_cns', $_GET)) && (running_script('upgrader'))) {
        $SITE_INFO['forum_type'] = 'cns';
        $SITE_INFO['cns_table_prefix'] = $SITE_INFO['table_prefix'];
    }

    // The URL to our install (no trailing /)
    $BASE_URL_HTTP_CACHE = null;
    $BASE_URL_HTTPS_CACHE = null;

    require_code_no_override('version');
    if ((!$MICRO_BOOTUP) && (!$MICRO_AJAX_BOOTUP)) {
        // Marker that Composr running
        //@header('X-Powered-By: Composr ' . cms_version_pretty() . ' (PHP ' . phpversion() . ')');
        @header('X-Powered-By: Composr'); // Better to keep it vague, for security reasons

        // Get ready for query logging if requested
        $QUERY_LOG = false;
        if ((isset($_REQUEST['special_page_type'])) && ($_REQUEST['special_page_type'] == 'query')) {
            $QUERY_LOG = true;
        }
    }

    define('STATIC_CACHE__FAST_SPIDER', 1);
    define('STATIC_CACHE__GUEST', 2);
    define('STATIC_CACHE__FAILOVER_MODE', 4);

    // Most critical things
    require_code('global3'); // A lot of support code is present in this
    require_code('web_resources');
    if (!running_script('webdav')) {
        $http_method = cms_srv('REQUEST_METHOD');
        if ($http_method != 'GET' && $http_method != 'POST' && $http_method != 'HEAD' && $http_method != '') {
            header('HTTP/1.0 405 Method Not Allowed');
            exit();
        }
    }
    $force_failover = get_param_integer('keep_failover', null);
    if (((isset($SITE_INFO['failover_mode'])) && ($SITE_INFO['failover_mode'] == 'on' || $SITE_INFO['failover_mode'] == 'auto_on') && ($force_failover !== 0)) || ($force_failover === 1)) {
        $bot_type = get_bot_type();
        require_code('static_cache');
        static_cache((($bot_type !== null) ? STATIC_CACHE__FAST_SPIDER : 0) | STATIC_CACHE__FAILOVER_MODE);
    }
    if ((!$MICRO_BOOTUP) && (!$MICRO_AJAX_BOOTUP)) { // Fast caching for bots and possibly guests
        if (($STATIC_CACHE_ENABLED) && (cms_srv('REQUEST_METHOD') != 'POST')) {
            $bot_type = get_bot_type();
            if (($bot_type !== null) && (!empty($SITE_INFO['fast_spider_cache'])) && ($SITE_INFO['fast_spider_cache'] != '0')) {
                require_code('static_cache');
                static_cache(STATIC_CACHE__FAST_SPIDER);
            }
            if ((isset($SITE_INFO['any_guest_cached_too'])) && ($SITE_INFO['any_guest_cached_too'] == '1') && (count(array_diff_key($_COOKIE, array('__utma' => 0, '__utmc' => 0, '__utmz' => 0, 'has_cookies' => 0, 'last_visit' => 0))) == 0) && ((!isset($SITE_INFO['backdoor_ip'])) || ($SITE_INFO['backdoor_ip'] != @strval($_SERVER['REMOTE_ADDR']))) && (!isset($_GET['keep_session']))) {
                require_code('static_cache');
                static_cache(STATIC_CACHE__GUEST);
            }
        }
    }
    require_code('caches');
    require_code('database'); // There's nothing without the database
    require_code('config'); // Config is needed for much active stuff
    if ((!isset($SITE_INFO['known_suexec'])) || ($SITE_INFO['known_suexec'] == '0')) {
        if (ip_banned(get_ip_address())) {
            critical_error('BANNED');
        }
    }

    // Member startup takes some time
    if (!$MICRO_BOOTUP) {
        load_user_stuff();
    }

    // For any kind of niceness we need these. The order is chosen for complex dependency reasons - don't mess with it
    if (!$MICRO_AJAX_BOOTUP) {
        require_code('themes'); // Output needs to know about themes
        require_code('templates'); // So that we can do error templates
        require_code('tempcode'); // Output is done with Tempcode
        if (!$MICRO_BOOTUP) {
            require_code('comcode'); // Much output goes through comcode
        }
    }
    require_code('zones'); // Zone is needed because zones are where all Composr pages reside

    if ((get_option('collapse_user_zones') == '1') && ($RELATIVE_PATH == 'site')) {
        get_base_url();/*force calculation first*/
        $RELATIVE_PATH = '';
    }
    require_code('users'); // Users are important due to permissions
    if ((!$MICRO_BOOTUP) && (!$MICRO_AJAX_BOOTUP)) { // Fast caching for Guests
        if (($STATIC_CACHE_ENABLED) && (cms_srv('REQUEST_METHOD') != 'POST')) {
            if ((isset($SITE_INFO['any_guest_cached_too'])) && ($SITE_INFO['any_guest_cached_too'] == '1') && (is_guest(null, true)) && (get_param_integer('keep_failover', null) !== 0)) {
                require_code('static_cache');
                static_cache(STATIC_CACHE__GUEST);
            }
        }
    }
    $CACHE_TEMPLATES = has_caching_for('template');
    require_code('lang'); // So that we can do language stuff (e.g. errors). Note that even though we have included a lot so far, we can't really use any of it until lang is loaded. Lang isn't loaded earlier as it itself has a dependency on Tempcode.
    if (!$MICRO_AJAX_BOOTUP) {
        require_code('temporal'); // Date/time functions
        convert_data_encodings(get_param_integer('known_utf8', 0) == 1);
        if (!$MICRO_BOOTUP) {
            // FirePHP console support, only for administrators
            if ((get_param_integer('keep_firephp', 0) == 1) && (($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member())) || ($GLOBALS['IS_ACTUALLY_ADMIN']))) {
                require_code('firephp');
                $GLOBALS['OUTPUT_STREAMING'] = false;
            }

            require_code('permissions'); // So we can check access
        }
    }

    // At this point we can display errors nicely
    /** Whether error display is suppressed
     *
     * @global boolean $SUPPRESS_ERROR_DEATH
     */
    global $SUPPRESS_ERROR_DEATH;
    $SUPPRESS_ERROR_DEATH = false;
    set_error_handler('composr_error_handler');
    if (function_exists('error_get_last')) {
        register_shutdown_function('catch_fatal_errors');
    }
    $HAS_SET_ERROR_HANDLER = true;

    // Initialise members
    if (!$MICRO_BOOTUP) {
        if (method_exists($GLOBALS['FORUM_DRIVER'], 'forum_layer_initialise')) {
            $GLOBALS['FORUM_DRIVER']->forum_layer_initialise();
        }
    }

    // More things to initialise
    if (!$MICRO_BOOTUP) {
        if ((!$IN_MINIKERNEL_VERSION) && (!$MICRO_AJAX_BOOTUP)) {
            has_cookies(); // Will determine at early point whether we have cookie support
            get_num_users_site(); // Will kill site if there are too many users
        }
    }
    require_code('urls'); // URL building is crucial

    // Register Internationalisation settings
    @header('Content-type: text/html; charset=' . get_charset());
    setlocale(LC_ALL, explode(',', do_lang('locale')));
    if (substr(strftime('%M'), 0, 2) == '??') { // Windows may do this because it can't output a utf-8 character set, so gets mangled to question marks by PHP
        setlocale(LC_ALL, explode(',', 'en-GB.UTF-8,en_GB.UTF-8,en-US.UTF-8,en_US.UTF-8,en.UTF-8,en-GB,en_GB,en-US,en_US,en')); // The user will have to define locale_subst correctly
    } 

    // Check RBLs
    $spam_check_level = get_option('spam_check_level');
    if ($spam_check_level == 'EVERYTHING') {
        if (get_option('spam_block_lists') != '') {
            require_code('antispam');
            check_rbls(true);
        }
    }

    // Our logging
    if ((!$MICRO_BOOTUP) && (!$MICRO_AJAX_BOOTUP) && ((get_option('display_php_errors') == '1') || (running_script('upgrader')) || (has_privilege(get_member(), 'see_php_errors')))) {
        safe_ini_set('display_errors', '1');
    } elseif (!$DEV_MODE) {
        safe_ini_set('display_errors', '0');
    }

    // G-zip?
    $page = get_param_string('page', ''); // Not get_page_name for bootstrap order reasons
    if (!in_safe_mode() && $page != 'admin_config') {
        safe_ini_set('zlib.output_compression', (get_option('gzip_output') == '1') ? '2048' : 'Off'); // 2KB buffer is based on capturing repetition while not breaking output streaming
    }
    safe_ini_set('zlib.output_compression_level', '2'); // Compression doesn't get much better after this, but performance drop

    if ((!$MICRO_AJAX_BOOTUP) && (!$MICRO_BOOTUP)) {
        // Before anything gets outputted
        handle_logins();

        require_code('site'); // This powers the site (top level page generation)
        check_has_page_access(); // Make sure we're authorised
    }

    // Check installer not left behind
    if ((!$MICRO_AJAX_BOOTUP) && (!$MICRO_BOOTUP) && ((!isset($SITE_INFO['no_installer_checks'])) || ($SITE_INFO['no_installer_checks'] != '1'))) {
        if ((is_file(get_file_base() . '/install.php')) && (!is_file(get_file_base() . '/install_ok')) && (running_script('index'))) {
            warn_exit(do_lang_tempcode('MUST_DELETE_INSTALLER'));
        }
    }

    if ((!$MICRO_AJAX_BOOTUP) && (!$MICRO_BOOTUP)) {
        // Clear caching if needed
        $changed_base_url = (get_value('last_base_url', null, true) !== get_base_url(false));
        if ((running_script('index')) && ((is_browser_decaching()) || ($changed_base_url))) {
            require_code('caches3');
            auto_decache($changed_base_url);
        }

        // Load requirements for admins
        if (has_zone_access(get_member(), 'adminzone')) {
            $JAVASCRIPTS_DEFAULT['staff'] = true;
            $JAVASCRIPTS_DEFAULT['ajax'] = true;
            if (get_option('bottom_show_commandr_button', true) === '1') {
                $JAVASCRIPTS_DEFAULT['button_commandr'] = true;
            }
        }
        if (get_option('bottom_show_realtime_rain_button', true) === '1') {
            $JAVASCRIPTS_DEFAULT['button_realtime_rain'] = true;
        }
        $JAVASCRIPTS += $JAVASCRIPTS_DEFAULT;
    }
    /*cms_memory_profile('startup');   If debugging with inbuilt profiler
    $func = get_defined_functions();
    print_r($func['user']);*/

    // Pre-load used blocks in bulk
    preload_block_internal_caching();

    // Okay, we've loaded everything critical. Don't need to tell Composr to be paranoid now.
    $BOOTSTRAPPING = false;

    if (($SEMI_DEV_MODE) && (!$MICRO_AJAX_BOOTUP)) { // Lots of code that only runs if you're a programmer. It tries to make sure coding standards are met.
        semi_dev_mode_startup();
    }

    // Reduce down memory limit / raise if requested
    $default_memory_limit = get_value('memory_limit');
    if ((is_null($default_memory_limit)) || ($default_memory_limit == '') || ($default_memory_limit == '0') || ($default_memory_limit == '-1')) {
        $default_memory_limit = '64M';
        if ($GLOBALS['RELATIVE_PATH'] == 'adminzone' || $GLOBALS['RELATIVE_PATH'] == 'cms') {
            $default_memory_limit = '128M';
        }
    } else {
        if (substr($default_memory_limit, -2) == 'MB') {
            $default_memory_limit = substr($default_memory_limit, 0, strlen($default_memory_limit) - 1);
        }
        if ((is_numeric($default_memory_limit)) && (intval($default_memory_limit) < 1024 * 1024 * 16)) {
            $default_memory_limit .= 'M';
        }
    }
    safe_ini_set('memory_limit', $default_memory_limit);
    memory_limit_for_max_param('max');
    if ((isset($GLOBALS['FORUM_DRIVER'])) && ($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member()))) {
        if (get_param_integer('keep_avoid_memory_limit', 0) == 1) {
            disable_php_memory_limit();
        } else {
            $memory_test = get_param_integer('keep_memory_limit_test', 0);
            if (($memory_test != 0) && ($memory_test <= 32)) {
                safe_ini_set('memory_limit', strval($memory_test) . 'M');
            }
        }
    }
    $memory_tracking = get_value('memory_tracking');
    if (!empty($memory_tracking)) {
        register_shutdown_function('memory_tracking');
    }

    if (count(array_diff(array_keys($_POST), array('x', 'y', 'http_referer'/*added by our JS*/))) != 0) {
        // Detect and deal with spammers that triggered the spam blackhole
        if (get_option('spam_blackhole_detection') == '1') {
            $blackhole = post_param_string('y' . md5(get_site_name() . ': antispam'), '');
            if ($blackhole != '') {
                log_hack_attack_and_exit('LAME_SPAM_HACK', '<blackhole>' . $blackhole . '</blackhole>');
            }
        }

        // Check security token, if necessary
        if ($CSRF_TOKENS) {
            require_code('csrf_filter');
            check_csrf_token(post_param_string('csrf_token', null));
        }
    }

    if (!running_script('upgrader')) {
        // Startup hooks
        $startup_hooks = find_all_hooks('systems', 'startup');
        foreach (array_keys($startup_hooks) as $hook) {
            require_code('hooks/systems/startup/' . filter_naughty_harsh($hook));
            $ob = object_factory('Hook_startup_' . filter_naughty_harsh($hook), true);
            if ($ob === null) {
                continue;
            }
            $ob->run($MICRO_BOOTUP, $MICRO_AJAX_BOOTUP, 0);
        }

        // Auto-upgrade
        if (($CURRENT_SHARE_USER !== null) && (float_to_raw_string(cms_version_number()) != get_value('version'))) {
            require_code('upgrade');
            automate_upgrade__safe();
        }
    }

    // For performance testing
    if (get_value('monitor_slow_urls', '0') !== '0') {
        register_shutdown_function('monitor_slow_urls');
    }
}

/**
 * PHP's environment can be a real mess across servers. Cleanup the best we can.
 * See phpstub.php for info on what environmental data we can rely on.
 * See Chris's own comments on http://php.net/manual/en/reserved.variables.server.php also
 */
function fixup_bad_php_env_vars()
{
    // We can trust these to be there
    $script_filename = empty($_SERVER['SCRIPT_FILENAME']) ? $_ENV['SCRIPT_FILENAME'] : $_SERVER['SCRIPT_FILENAME']; // If was not here, was added by our front-end controller script

    // Now derive missing ones...

    $document_root = empty($_SERVER['DOCUMENT_ROOT']) ? (empty($_ENV['DOCUMENT_ROOT']) ? '' : $_ENV['DOCUMENT_ROOT']) : $_SERVER['DOCUMENT_ROOT'];
    if (empty($document_root)) {
        $document_root = '';
        $path_components = explode(DIRECTORY_SEPARATOR, get_file_base());
        foreach ($path_components as $i => $path_component) {
            $document_root .= $path_component . DIRECTORY_SEPARATOR;
            if (in_array($path_component, array('public_html', 'www', 'webroot', 'httpdocs', 'wwwroot', 'Documents'))) {
                break;
            }
        }
        $document_root = substr($document_root, 0, strlen($document_root) - strlen(DIRECTORY_SEPARATOR));
        $_SERVER['DOCUMENT_ROOT'] = $document_root;
    }

    $php_self = empty($_SERVER['PHP_SELF']) ? (empty($_ENV['PHP_SELF']) ? '' : $_ENV['PHP_SELF']) : $_SERVER['PHP_SELF'];
    if ((empty($php_self)) || (/*or corrupt*/strpos($php_self, '.php') === false)) {
        // We're really desparate if we have to derive this, but here we go
        $_SERVER['PHP_SELF'] = '/' . preg_replace('#^' . preg_quote($document_root, '#') . '/#', '', $script_filename);
        $path_info = empty($_SERVER['PATH_INFO']) ? (empty($_ENV['PATH_INFO']) ? '' : $_ENV['PATH_INFO']) : $_SERVER['PATH_INFO'];
        if (!empty($path_info)) { // Add in path-info if we have it
            $_SERVER['PHP_SELF'] .= $path_info;
        }
        $php_self = $_SERVER['PHP_SELF'];
    }

    if ((empty($_SERVER['SCRIPT_NAME'])) && (empty($_ENV['SCRIPT_NAME']))) {
        $_SERVER['SCRIPT_NAME'] = preg_replace('#\.php/.*#', '.php', $php_self); // Same as PHP_SELF except without path-info on the end
    }

    if ((empty($_SERVER['REQUEST_URI'])) && (empty($_ENV['REQUEST_URI']))) {
        if (isset($_SERVER['REDIRECT_URL'])) {
            $_SERVER['REQUEST_URI'] = $_SERVER['REDIRECT_URL'];
            if (strpos($_SERVER['REQUEST_URI'], '?') === false) {
                if (count($_GET) != 0) {
                    $_SERVER['REQUEST_URI'] .= '?' . http_build_query($_GET); // Messy as rewrite URL-embedded parameters will be doubled, but if you've got a broken server don't push it to do rewrites
                }
            }
        } else {
            $_SERVER['REQUEST_URI'] = $php_self; // Same as PHP_SELF, but...
            if (count($_GET) != 0) { // add in query string data if we have it
                $_SERVER['REQUEST_URI'] .= '?' . http_build_query($_GET);
            }

            // ^ NB: May be slight deviation. Default directory index files not considered, i.e. index.php may have been omitted in URL
        }
    }

    if ((empty($_SERVER['QUERY_STRING'])) && (empty($_ENV['QUERY_STRING']))) {
        $_SERVER['QUERY_STRING'] = http_build_query($_GET);
    }
}

/**
 * Use with register_shutdown_function to log slow URLs.
 */
function monitor_slow_urls()
{
    $time = time() - $_SERVER['REQUEST_TIME'];
    if ($time > intval(get_value('monitor_slow_urls'))) {
        require_code('urls');
        if (php_function_allowed('error_log')) {
            error_log('Over time limit @ ' . get_self_url_easy(true) . "\t" . strval($time) . 'secs' . "\t" . date('Y-m-d H:i:s', time()), 0);
        }
    }
}

/**
 * Log excessive memory usage.
 */
function memory_tracking()
{
    $memory_tracking = intval(get_value('memory_tracking'));
    if (memory_get_peak_usage() > 1024 * 1024 * $memory_tracking) {
        if (php_function_allowed('error_log')) {
            error_log('Memory usage above memory_tracking (' . strval($memory_tracking) . 'MB) @ ' . get_self_url_easy(true), 0);
        }
    }
}

/**
 * Get ready for outputting an AJAX response.
 */
function prepare_for_known_ajax_response()
{
    header('Cache-Control: no-cache, must-revalidate'); // HTTP/1.1
    header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past

    convert_data_encodings(true);

    global $KNOWN_AJAX;
    $KNOWN_AJAX = true;
}

/**
 * Raise the PHP memory limit to cater for a requested large result set.
 *
 * @param  ID_TEXT $max_param The max parameter name
 */
function memory_limit_for_max_param($max_param)
{
    $max = get_param_integer($max_param, null); // If making a large request and are an admin, raise PHP memory limit
    if (($max !== null) && ($max > 80) && (function_exists('has_privilege'))) {
        if (has_privilege(get_member(), 'remove_page_split')) {
            $shl = @ini_get('suhosin.memory_limit');
            if (($shl === false) || ($shl == '') || ($shl == '0')) {
                safe_ini_set('memory_limit', '128M');
            }
        }
    }
}

/**
 * Disable the PHP memory limit. Do not use this carelessly, use it if a screen is a bit fat or in an importer, don't use it assuming memory is infinite.
 */
function disable_php_memory_limit()
{
    $shl = @ini_get('suhosin.memory_limit');
    if (($shl === false) || ($shl == '') || ($shl == '0')) {
        // Progressively relax more and more (some PHP installs may block at some point)
        safe_ini_set('memory_limit', '128M');
        safe_ini_set('memory_limit', '256M');
        safe_ini_set('memory_limit', '-1');
    } else {
        if (is_numeric($shl)) {
            $shl .= 'M'; // Units are in MB for this, while PHP's memory limit setting has it in bytes
        }
        safe_ini_set('memory_limit', $shl);
    }
}

/**
 * Get the character set to use. We try and be clever to allow AJAX scripts to avoid loading up language
 *
 * @return string The character set
 */
function get_charset()
{
    global $CHARSET_CACHE, $XSS_DETECT;
    if (isset($CHARSET_CACHE)) {
        return $CHARSET_CACHE;
    }

    global $SITE_INFO;
    if (!empty($SITE_INFO['charset'])) { // An optimisation, if you want to put it in here
        $CHARSET_CACHE = $SITE_INFO['charset'];
        if ($XSS_DETECT) {
            ocp_mark_as_escaped($CHARSET_CACHE);
        }
        return $CHARSET_CACHE;
    }

    global $LANGS_REQUESTED;
    if ((function_exists('do_lang')) && (function_exists('user_lang')) && (isset($LANGS_REQUESTED['critical_error'])) && (isset($LANGS_REQUESTED['global'])) && (!in_safe_mode())) {
        $attempt = do_lang('charset', null, null, null, null, false);
        if ($attempt !== null) {
            $CHARSET_CACHE = trim($attempt);
            return $attempt;
        }
    }

    global $TEMP_CHARSET_CACHE;
    if (isset($TEMP_CHARSET_CACHE)) {
        return $TEMP_CHARSET_CACHE;
    }

    global $SITE_INFO;
    $lang = (!empty($SITE_INFO['default_lang'])) ? $SITE_INFO['default_lang'] : 'EN';
    $path = get_file_base() . '/lang_custom/' . $lang . '/global.ini';
    if (!is_file($path)) {
        $path = get_file_base() . '/lang/' . $lang . '/global.ini';
    }
    if (!is_file($path)) {
        $path = get_file_base() . '/lang/EN/global.ini';
    }
    $file = fopen($path, GOOGLE_APPENGINE ? 'rb' : 'rt');
    $contents = str_replace("\r", "\n", fread($file, 3000));
    fclose($file);
    $matches = array();
    if (preg_match('#\[strings\].*charset=([\w\-]+)\n#s', $contents, $matches) != 0) {
        $TEMP_CHARSET_CACHE = $matches[1];
        if ($XSS_DETECT) {
            ocp_mark_as_escaped($TEMP_CHARSET_CACHE);
        }
        return $TEMP_CHARSET_CACHE;
    }
    $TEMP_CHARSET_CACHE = 'utf-8';
    return $TEMP_CHARSET_CACHE;
}

/**
 * Load stuff that allows user code to work.
 */
function load_user_stuff()
{
    if ((!array_key_exists('FORUM_DRIVER', $GLOBALS)) || ($GLOBALS['FORUM_DRIVER'] === null)) { // Second clause is for Quercus, as it pre-nulls referenced variables
        global $SITE_INFO, $FORUM_DRIVER, $SITE_DB, $FORUM_DB;

        require_code('forum_stub');

        if (empty($SITE_INFO['forum_type'])) {
            $SITE_INFO['forum_type'] = 'cns';
        }
        require_code('forum/' . $SITE_INFO['forum_type']);     // So we can at least get user details
        $class = 'Forum_driver_' . filter_naughty_harsh($SITE_INFO['forum_type']);
        if (class_exists($class . '_sub')) {
            $class .= '_sub';
        }
        /** The active forum driver, through which member and forum interfacing should be done (apart from code that is explicitly only written as part of Conversr)
         *
         * @global object $FORUM_DRIVER
         */
        $FORUM_DRIVER = object_factory($class);
        if (($SITE_INFO['forum_type'] == 'cns') && (!is_on_multi_site_network()) && (!$GLOBALS['DEV_MODE'])) { // NB: In dev mode needs separating so we can properly test our boundaries
            $FORUM_DRIVER->connection = &$SITE_DB;
        } elseif ($SITE_INFO['forum_type'] != 'none') {
            $FORUM_DRIVER->connection = new DatabaseConnector(get_db_forums(), get_db_forums_host(), get_db_forums_user(), get_db_forums_password(), $FORUM_DRIVER->get_drivered_table_prefix());
        }
        $FORUM_DRIVER->MEMBER_ROWS_CACHED = array();
        /** The connection to the active forum database.
         *
         * @global object $FORUM_DB
         */
        $FORUM_DB = mixed();
        $GLOBALS['FORUM_DB'] = &$FORUM_DRIVER->connection; // Done like this to workaround that PHP can't put a reference in a global'd variable
    }
}

/**
 * Composr error catcher for fatal versions. This is hooked in only on PHP5.2 as error_get_last() only works on these versions.
 *
 * @ignore
 */
function catch_fatal_errors()
{
    if (!function_exists('error_get_last')) {
        return;
    }

    $error = error_get_last();

    if (!is_null($error)) {
        if (!array_key_exists('message', $error)) {
            return; // Needed for HHVM
        }
        if (substr($error['message'], 0, 26) == 'Maximum execution time of ') {
            if (function_exists('i_force_refresh')) {
                i_force_refresh();
            }
        }
        //$tmp = $GLOBALS;unset($tmp['GLOBALS']);@var_dump($tmp);@exit();
        //@var_dump(get_defined_functions()); exit(); // Useful for debugging memory problems, finding unneeded stuff that is loaded
        switch ($error['type']) {
            case E_ERROR:
            case E_CORE_ERROR:
            case E_COMPILE_ERROR:
            case E_USER_ERROR:
                $GLOBALS['SUPPRESS_ERROR_DEATH'] = false; // We can't recover as we've lost our execution track. Force a nice death rather than trying to display a recoverable error.
                $GLOBALS['DYING_BADLY'] = true; // Tells composr_error_handler to roll through, definitely an error.
                $GLOBALS['EXITING'] = 2; // Fudge to force a critical error, we're too desparate to show a Tempcode stack trace.
                composr_error_handler($error['type'], $error['message'], $error['file'], $error['line']);
        }
    }
}

/**
 * Composr error handler (hooked into PHP error system).
 *
 * @param  integer $errno The error type-number
 * @param  PATH $errstr The error message
 * @param  string $errfile The file the error occurred in
 * @param  integer $errline The line the error occurred on
 * @return boolean Mark error handled, so PHP's native error handling code does not execute. i.e. false => bubble, true => handled. For errors we intercept we don't return at all so bubbling never happens in such a case. $php_errormsg is only set if we bubble.
 *
 * @ignore
 */
function composr_error_handler($errno, $errstr, $errfile, $errline)
{
    if (((error_reporting() & $errno) !== 0) && (strpos($errstr, 'Illegal length modifier specified')/*Weird random error in dev PHP version*/ === false) || ($GLOBALS['DYING_BADLY'])) {
        // Strip down path for security
        if (substr(str_replace(DIRECTORY_SEPARATOR, '/', $errfile), 0, strlen(get_file_base() . '/')) == str_replace(DIRECTORY_SEPARATOR, '/', get_file_base() . '/')) {
            $errfile = substr($errfile, strlen(get_file_base() . '/'));
        }

        // Work out the error type
        if (!defined('E_RECOVERABLE_ERROR')) {
            define('E_RECOVERABLE_ERROR', 4096);
        }
        switch ($errno) {
            case E_RECOVERABLE_ERROR: // constant not defined in all php versions but we defined it
            case E_USER_ERROR:
            case E_PARSE:
            case E_CORE_ERROR:
            case E_COMPILE_ERROR:
            case E_ERROR:
                $type = 'error';
                $syslog_type = LOG_ERR;
                break;
            case -123: // Hacked in for the memtrack extension, which was buggy
            case E_CORE_WARNING:
            case E_COMPILE_WARNING:
            case E_USER_WARNING:
            case E_WARNING:
                $type = 'warning';
                $syslog_type = LOG_WARNING;
                break;
            case E_USER_NOTICE:
            case E_NOTICE:
                $type = 'notice';
                $syslog_type = LOG_NOTICE;
                break;
            //case E_STRICT: (constant not defined in all php versions)
            //case E_DEPRECATED: (constant not defined in all php versions)
            //case E_USER_DEPRECATED: (constant not defined in all php versions)
            default: // We don't know the error type, or we know it's incredibly minor, so it's probably best to continue - PHP will output it for staff or if display_php_errors is on
                return false;
        }

        $GLOBALS['DYING_BADLY'] = false; // So error suppress works again
        if (strpos($errstr, 'Allowed memory') !== false) {
            global $REQUIRED_CODE;
            if (!array_key_exists('failure', $REQUIRED_CODE)) {
                $php_error_label = $errstr . ' in ' . $errfile . ' on line ' . strval($errline) . ' @ ' . get_self_url_easy(true); // We really want to know the URL where this is happening (normal PHP error logging does not include it)!
                if ((function_exists('syslog')) && (GOOGLE_APPENGINE)) {
                    syslog($syslog_type, $php_error_label);
                }
                if (php_function_allowed('error_log')) {
                    @error_log('PHP ' . ucwords($type) . ': ' . $php_error_label, 0);
                }
                critical_error('EMERGENCY', $errstr . escape_html(' [' . $errfile . ' at ' . strval($errline) . ']'));
            }
        }
        require_code('failure');
        _composr_error_handler($type, $errno, $errstr, $errfile, $errline, $syslog_type);
    }

    return false;
}

/**
 * Find whether the browser session is set to be doing a hard cache-empty refresh.
 *
 * @return boolean Whether the browser session is set to be doing a hard cache-empty refresh
 */
function is_browser_decaching()
{
    global $BROWSER_DECACHEING_CACHE;
    if ($BROWSER_DECACHEING_CACHE !== null) {
        return $BROWSER_DECACHEING_CACHE;
    }

    if (GOOGLE_APPENGINE) {
        return false; // Decaching by mistake is real-bad when Google Cloud Storage is involved
    }

    if (defined('DO_PLANNED_DECACHE')) { // Used by decache.sh
        $config_file_orig = cms_file_get_contents_safe(get_file_base() . '/_config.php');
        $config_file = $config_file_orig;
        $config_file = rtrim(str_replace('define(\'DO_PLANNED_DECACHE\', true);', '', $config_file)) . "\n\n";
        require_code('files');
        cms_file_put_contents_safe(get_file_base() . '/_config.php', $config_file, FILE_WRITE_FIX_PERMISSIONS);
        return true;
    }

    if (get_value('ran_once') === null) { // Track whether Composr has run at least once
        set_value('ran_once', '1');
        return true;
    }

    return false;    // This technique stopped working well, Chrome sends cache-control too freely

    /*
    $header_method = (array_key_exists('HTTP_CACHE_CONTROL', $_SERVER)) && ($_SERVER['HTTP_CACHE_CONTROL'] == 'no-cache') && (cms_srv('REQUEST_METHOD') != 'POST') && ((!function_exists('browser_matches')));
    $BROWSER_DECACHEING = (($header_method) && ((array_key_exists('FORUM_DRIVER', $GLOBALS)) && (has_actual_page_access(get_member(), 'admin_cleanup')) || ($GLOBALS['IS_ACTUALLY_ADMIN'])));
    return $BROWSER_DECACHEING;
    */
}

/**
 * Find out what script is running.
 *
 * @return ID_TEXT The script running (usually 'index')
 */
function current_script()
{
    // Strip down current URL so we can do a simple compare
    global $WHAT_IS_RUNNING_CACHE;
    if ($WHAT_IS_RUNNING_CACHE === null) {
        $script_name = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : (isset($_ENV['SCRIPT_NAME']) ? $_ENV['SCRIPT_NAME'] : '');
        $stripped_current_url = basename($script_name);
        $WHAT_IS_RUNNING_CACHE = substr($stripped_current_url, 0, strpos($stripped_current_url, '.'));
    }
    return $WHAT_IS_RUNNING_CACHE;
}

/**
 * Find whether a certain script is being run to get here.
 *
 * @param  string $is_this_running Script filename (canonically we want NO .php file type suffix)
 * @return boolean Whether the script is running
 */
function running_script($is_this_running)
{
    // First check cache
    global $RUNNING_SCRIPT_CACHE;
    if (isset($RUNNING_SCRIPT_CACHE[$is_this_running . '.php'])) {
        return $RUNNING_SCRIPT_CACHE[$is_this_running . '.php'];
    }
    if (isset($RUNNING_SCRIPT_CACHE[$is_this_running])) {
        return $RUNNING_SCRIPT_CACHE[$is_this_running];
    }

    // Do the stem compare
    $answer = (current_script() === $is_this_running);

    // Cache and return result
    $RUNNING_SCRIPT_CACHE[$is_this_running] = $answer;
    return $answer;
}

/**
 * This is a intended to output an informational exit at the same time as terminating execution
 *
 * @param  mixed $text The error message (string or Tempcode)
 * @param  ?boolean $support_match_key_messages Whether match key messages / redirects should be supported (null: detect)
 * @return mixed Never returns (i.e. exits)
 */
function inform_exit($text, $support_match_key_messages = null)
{
    require_code('failure'); // It's in failure.php although this isn't REALLY failure. Still it's an exceptional event so we can't justify loading the code as global.
    _generic_exit($text, 'INFORM_SCREEN', $support_match_key_messages);
}

/**
 * This is a less-revealing alternative to fatal_exit, that is used for user-errors/common-corruption-scenarios
 *
 * @param  mixed $text The error message (string or Tempcode)
 * @param  boolean $support_match_key_messages Whether match key messages / redirects should be supported
 * @return mixed Never returns (i.e. exits)
 */
function warn_exit($text, $support_match_key_messages = false)
{
    require_code('failure');
    suggest_fatalistic();
    _generic_exit($text, 'WARN_SCREEN', $support_match_key_messages);
    if (running_script('cron_bridge')) {
        relay_error_notification(is_object($text) ? $text->evaluate() : escape_html($text), false, 'error_occurred_cron');
    }
}

/**
 * Do a fatal exit, echo the header (if possible) and an error message, followed by a debugging back-trace.
 * It also adds an entry to the error log, for reference.
 *
 * @param  mixed $text The error message (string or Tempcode)
 * @return mixed Never returns (i.e. exits)
 */
function fatal_exit($text)
{
    require_code('failure');
    _fatal_exit($text);
}

/**
 * Log a hackattack, then displays an error message. It also attempts to send an e-mail to the staff alerting them of the hackattack.
 *
 * @param  ID_TEXT $reason The reason for the hack attack. This has to be a language string ID
 * @param  SHORT_TEXT $reason_param_a A parameter for the hack attack language string (this should be based on a unique ID, preferably)
 * @param  SHORT_TEXT $reason_param_b A more illustrative parameter, which may be anything (e.g. a title)
 * @param  boolean $silent Whether to silently log the hack rather than also exiting
 * @param  boolean $instant_ban Whether a ban should be immediate
 * @return mixed Never returns (i.e. exits)
 */
function log_hack_attack_and_exit($reason, $reason_param_a = '', $reason_param_b = '', $silent = false, $instant_ban = false)
{
    require_code('failure');
    _log_hack_attack_and_exit($reason, $reason_param_a, $reason_param_b, $silent, $instant_ban);
}

/**
 * Get the major version of your installation.
 *
 * @return integer The major version number of your installation
 */
function cms_version()
{
    return intval(cms_version_number());
}

/**
 * Get the full string version of Composr that you are running, in 'pretty' format.
 * This is (and must be kept) equivalent to get_version_pretty__from_dotted(get_version_dotted())
 *
 * @return string The string saying the full Composr version number
 */
function cms_version_pretty()
{
    $minor = cms_version_minor();
    $dotted = strval(cms_version()) . (($minor == '') ? '' : '.' . $minor);
    return preg_replace('#\.(alpha|beta|RC)#', ' ${1}', $dotted);
}

/**
 * Get the domain the website is installed on (preferably, without any www). The domain is used for e-mail defaults among other things.
 *
 * @return string The domain of the website
 */
function get_domain()
{
    global $SITE_INFO;
    $ret = (!empty($SITE_INFO['domain'])) ? $SITE_INFO['domain'] : '';

    // Ah, no explicit setting, so derive...
    if ($ret == '') {
        // Derive from base URL
        if (!empty($SITE_INFO['base_url'])) {
            $matches = array();
            preg_match('#://([^/\#]+)#', $SITE_INFO['base_url'], $matches);
            $ret = preg_replace('#^www\.#', '', $matches[1]);
        }

        // Derive from other possibilities. Note that we can't use cms_srv due to bootstrap order (it's in global3.php)
        if (!empty($_SERVER['HTTP_HOST'])) {
            return preg_replace('#^www\.#', '', $_SERVER['HTTP_HOST']);
        }
        if (!empty($_ENV['HTTP_HOST'])) {
            return preg_replace('#^www\.#', '', $_ENV['HTTP_HOST']);
        }
        if (function_exists('gethostname')) {
            return preg_replace('#^www\.#', '', gethostname());
        }
        if (!empty($_SERVER['SERVER_ADDR'])) {
            return preg_replace('#^www\.#', '', $_SERVER['SERVER_ADDR']);
        }
        if (!empty($_ENV['SERVER_ADDR'])) {
            return preg_replace('#^www\.#', '', $_ENV['SERVER_ADDR']);
        }
        if (!empty($_SERVER['LOCAL_ADDR'])) {
            return preg_replace('#^www\.#', '', $_SERVER['LOCAL_ADDR']);
        }
        if (!empty($_ENV['LOCAL_ADDR'])) {
            return preg_replace('#^www\.#', '', $_ENV['LOCAL_ADDR']);
        }
        return 'localhost';
    }
    return $ret;
}

/**
 * Get the type of forums installed.
 *
 * @return string The type of forum installed
 */
function get_forum_type()
{
    global $SITE_INFO;
    if (!isset($SITE_INFO['forum_type'])) {
        $SITE_INFO['forum_type'] = 'cns';
    }
    if ($SITE_INFO['forum_type'] === 'ocf') {
        $SITE_INFO['forum_type'] = 'cns'; // LEGACY
    }
    return $SITE_INFO['forum_type'];
}

/**
 * Get the installed forum base URL.
 *
 * @param  boolean $forum_base Whether to get the base directory of the forum. Unless running Conversr, this makes no difference - if possibly running Conversr, you need to think about this parameter: are you trying to reach the MSN-central-site or just a link to the forums?
 * @return URLPATH The installed forum base URL
 */
function get_forum_base_url($forum_base = false)
{
    global $SITE_INFO;

    if (empty($SITE_INFO['board_prefix'])) {
        $SITE_INFO['board_prefix'] = get_base_url();
    }
    $forum_type = get_forum_type();
    if ($forum_type === 'none') {
        return '';
    }
    $needs_forum_strip = (substr($SITE_INFO['board_prefix'], -6) === '/forum') && (substr(get_base_url(), -6) !== '/forum');
    if (($forum_type === 'cns') && (!$forum_base) && ($needs_forum_strip)) {
        return substr($SITE_INFO['board_prefix'], 0, strlen($SITE_INFO['board_prefix']) - 6);
    }
    if (($forum_type === 'cns') && ($forum_base) && (!$needs_forum_strip)) {
        return $SITE_INFO['board_prefix'] . '/forum';
    }
    return $SITE_INFO['board_prefix'];
}

/**
 * Get the Composr cookie path.
 *
 * @return ?string The Composr cookie path (null: no special path, global)
 */
function get_cookie_path()
{
    global $SITE_INFO;
    $ret = array_key_exists('cookie_path', $SITE_INFO) ? $SITE_INFO['cookie_path'] : '/';
    return ($ret == '') ? null : $ret;
}

/**
 * Get the Composr cookie domain.
 *
 * @return ?string The Composr cookie domain (null: current domain)
 */
function get_cookie_domain()
{
    global $SITE_INFO;
    $ret = array_key_exists('cookie_domain', $SITE_INFO) ? $SITE_INFO['cookie_domain'] : null;
    return ($ret == '') ? null : $ret;
}

/**
 * Get the number of days to store our cookies.
 *
 * @return integer The number of days to store our cookies
 */
function get_cookie_days()
{
    global $SITE_INFO;
    return array_key_exists('cookie_domain', $SITE_INFO) ? intval($SITE_INFO['cookie_days']) : 120;
}

/**
 * Get the site name.
 *
 * @return string The name of the site
 */
function get_site_name()
{
    return get_option('site_name');
}

/**
 * Find whether we are running in safe mode.
 *
 * @return boolean Whether we are in safe mode
 */
function in_safe_mode()
{
    global $SITE_INFO;
    if (!empty($SITE_INFO['safe_mode'])) {
        if (!isset($_GET['keep_safe_mode'])) {
            return ($SITE_INFO['safe_mode'] == '1'); // Useful for testing HPHP support, and generally more robust and fast
        }
    }

    $backdoor_ip = ((!empty($SITE_INFO['backdoor_ip'])) && (cms_srv('REMOTE_ADDR') == $SITE_INFO['backdoor_ip']) && (cms_srv('HTTP_X_FORWARDED_FOR') == ''));

    global $CHECKING_SAFEMODE, $REQUIRED_CODE;
    if (!$backdoor_ip) {
        if (!isset($REQUIRED_CODE['lang']) || !$REQUIRED_CODE['lang']) {
            return false; // Too early. We can get in horrible problems when doing get_member() below if lang hasn't loaded yet
        }
        if ($CHECKING_SAFEMODE) {
            return false; // Stops infinite loops (e.g. Check safe mode > Check access > Check usergroups > Check implicit usergroup hooks > Check whether to look at custom implicit usergroup hooks [i.e. if not in safe mode])
        }
    }
    $CHECKING_SAFEMODE = true;
    $ret = ((get_param_integer('keep_safe_mode', 0) == 1) && ($backdoor_ip || (isset($GLOBALS['IS_ACTUALLY_ADMIN']) && ($GLOBALS['IS_ACTUALLY_ADMIN'])) || (!array_key_exists('FORUM_DRIVER', $GLOBALS)) || ($GLOBALS['FORUM_DRIVER'] === null) || (!function_exists('get_member')) || (empty($GLOBALS['MEMBER_CACHED'])) || ($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member()))));
    $CHECKING_SAFEMODE = false;
    return $ret;
}

/**
 * Get server environment variables.
 *
 * @param  string $key The variable name
 * @return string The variable value ('' means unknown)
 */
function cms_srv($key)
{
    if (isset($_SERVER[$key])) {
        return /*stripslashes*/
            ($_SERVER[$key]);
    }
    if ((isset($_ENV)) && (isset($_ENV[$key]))) {
        return /*stripslashes*/
            ($_ENV[$key]);
    }

    if ($key == 'HTTP_HOST') {
        if (!empty($_SERVER['HTTP_HOST'])) {
            return $_SERVER['HTTP_HOST'];
        }
        if (!empty($_ENV['HTTP_HOST'])) {
            return $_ENV['HTTP_HOST'];
        }
        if (function_exists('gethostname')) {
            return gethostname();
        }
        if (!empty($_SERVER['SERVER_ADDR'])) {
            return $_SERVER['SERVER_ADDR'];
        }
        if (!empty($_ENV['SERVER_ADDR'])) {
            return $_ENV['SERVER_ADDR'];
        }
        if (!empty($_SERVER['LOCAL_ADDR'])) {
            return $_SERVER['LOCAL_ADDR'];
        }
        if (!empty($_ENV['LOCAL_ADDR'])) {
            return $_ENV['LOCAL_ADDR'];
        }
        return 'localhost';
    }

    if ($key == 'SERVER_ADDR') { // IIS issue
        return cms_srv('LOCAL_ADDR');
    }

    return '';
}

/**
 * Find the URL to a certain entry point script, located in the root directory, top level of a zone directory, data directory, or data_custom directory.
 * Why this function? Because Composr allows these to be moved around between zone directories, to suit site .htaccess requirements).
 *
 * @param  string $name The codename of the needed script
 * @param  boolean $append_keep Whether to append keep variables
 * @param  integer $base_url_code Code representing what base URL type to use (0=guess, 1=http, 2=https)
 * @set 0 1 2
 * @return URLPATH The URL to the script
 */
function find_script($name, $append_keep = false, $base_url_code = 0)
{
    $append = '';
    if ($append_keep) {
        $keep = symbol_tempcode('KEEP', array('1'));
        $append .= $keep->evaluate();
    }

    global $FIND_SCRIPT_CACHE;
    if ($FIND_SCRIPT_CACHE === array()) {
        if (function_exists('persistent_cache_get')) {
            $FIND_SCRIPT_CACHE = persistent_cache_get('SCRIPT_PLACES');
        }
        if ($FIND_SCRIPT_CACHE === null) {
            $FIND_SCRIPT_CACHE = array();
        }
    }
    if (isset($FIND_SCRIPT_CACHE[$name][$append_keep][$base_url_code])) {
        return $FIND_SCRIPT_CACHE[$name][$append_keep][$base_url_code] . $append;
    }

    $zones = array(get_zone_name());
    if (!in_safe_mode()) {
        $zones[] = 'data_custom';
    }
    $zones[] = 'data';
    $zones = array_merge($zones, find_all_zones());
    foreach ($zones as $zone) {
        if (is_file(get_file_base() . '/' . $zone . (($zone == '') ? '' : '/') . $name . '.php')) {
            $ret = get_base_url() . '/' . $zone . (($zone == '') ? '' : '/') . $name . '.php';
            $FIND_SCRIPT_CACHE[$name][$append_keep][$base_url_code] = $ret;
            if (function_exists('persistent_cache_set')) {
                persistent_cache_set('SCRIPT_PLACES', $FIND_SCRIPT_CACHE);
            }
            return $ret . $append;
        }
    }
    $ret = get_base_url(($base_url_code == 0) ? null : ($base_url_code == 2)) . '/site/' . $name . '.php';
    $FIND_SCRIPT_CACHE[$name][$append_keep][$base_url_code] = $ret;
    if (function_exists('persistent_cache_set')) {
        persistent_cache_set('SCRIPT_PLACES', $FIND_SCRIPT_CACHE);
    }
    return $ret . $append;
}

/**
 * Get the base URL (the minimum fully qualified URL to our installation).
 *
 * @param  ?boolean $https Whether to get the HTTPS base URL (null: do so only if the current page uses the HTTPS base URL)
 * @param  ?ID_TEXT $zone_for The zone the link is for (null: root zone)
 * @return URLPATH The base-url
 */
function get_base_url($https = null, $zone_for = null)
{
    if ($https === null) { // If we don't know, we go by what the current page is
        global $CURRENTLY_HTTPS_CACHE;
        $https = $CURRENTLY_HTTPS_CACHE;
        if ($https === null) {
            require_code('urls');
            if (running_script('index')) {
                if (!addon_installed('ssl')) {
                    $https = tacit_https();
                } else {
                    $https = ((tacit_https()) || (function_exists('is_page_https')) && (function_exists('get_zone_name')) && (is_page_https(get_zone_name(), get_page_name())));
                }
            } else {
                $https = function_exists('tacit_https') && tacit_https();
            }
            $CURRENTLY_HTTPS_CACHE = $https;
        }
    }

    global $BASE_URL_HTTP_CACHE, $BASE_URL_HTTPS_CACHE, $VIRTUALISED_ZONES_CACHE;

    if ($VIRTUALISED_ZONES_CACHE === null) {
        require_code('zones');
        get_zone_name();
    }

    if (($BASE_URL_HTTP_CACHE !== null) && (!$https) && ((!$VIRTUALISED_ZONES_CACHE) || ($zone_for === null))) {
        return $BASE_URL_HTTP_CACHE . (empty($zone_for) ? '' : ('/' . $zone_for));
    }
    if (($BASE_URL_HTTPS_CACHE !== null) && ($https) && ((!$VIRTUALISED_ZONES_CACHE) || ($zone_for === null))) {
        return $BASE_URL_HTTPS_CACHE . (empty($zone_for) ? '' : ('/' . $zone_for));
    }

    global $SITE_INFO;
    if ((!isset($SITE_INFO)) || (empty($SITE_INFO['base_url']))) { // Try and autodetect the base URL if it's not configured
        $domain = get_domain();
        $script_name_path = dirname(isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : (isset($_ENV['SCRIPT_NAME']) ? $_ENV['SCRIPT_NAME'] : ''));
        if (($GLOBALS['RELATIVE_PATH'] === '') || (strpos($script_name_path, $GLOBALS['RELATIVE_PATH']) !== false)) {
            $script_name_path = preg_replace('#/' . preg_quote($GLOBALS['RELATIVE_PATH'], '#') . '$#', '', $script_name_path);
        } else {
            $cnt = substr_count($GLOBALS['RELATIVE_PATH'], '/');
            for ($i = 0; $i <= $cnt; $i++) {
                $script_name_path = dirname($script_name_path);
            }
        }
        $SITE_INFO['base_url'] = (tacit_https() ? 'https://' : 'http://') . $domain . str_replace('%2F', '/', rawurlencode($script_name_path));
    }

    // Lookup
    $base_url = $SITE_INFO['base_url'];
    global $CURRENT_SHARE_USER;
    if ($CURRENT_SHARE_USER !== null) {
        // Put in access domain, in case there is a custom domain attached to the site
        $domain = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_ENV['HTTP_HOST']) ? $_ENV['HTTP_HOST'] : '');
        $base_url = preg_replace('#^http(s)?://([\w]+\.)?' . preg_quote($SITE_INFO['custom_share_domain'], '#') . '#', 'http$1://' . $domain, $base_url);
    }
    $found_mapping = false;
    if ($VIRTUALISED_ZONES_CACHE) { // Special searching if we are doing a complex zone scheme
        $zone_doing = ($zone_for === null) ? '' : str_replace('/', '', $zone_for);

        if (!empty($SITE_INFO['ZONE_MAPPING_' . $zone_doing])) {
            $domain = $SITE_INFO['ZONE_MAPPING_' . $zone_doing][0];
            $path = $SITE_INFO['ZONE_MAPPING_' . $zone_doing][1];
            $base_url = ((strpos($base_url, 'https://') === false) ? 'http://' : 'https://') . $domain;
            if ($path !== '') {
                $base_url .= '/' . $path;
            }
            $found_mapping = true;
        }
    }

    // Work out correct variant
    if ($https) {
        $base_url = 'https://' . preg_replace('#^\w*://#', '', $base_url);
        if ((!$VIRTUALISED_ZONES_CACHE) || ($zone_for === null)) {
            $BASE_URL_HTTPS_CACHE = $base_url;
        }
    } elseif ((!$VIRTUALISED_ZONES_CACHE) || ($zone_for === null)) {
        $BASE_URL_HTTP_CACHE = $base_url;
    }

    if (!$found_mapping) { // Scope inside the correct zone
        $base_url .= (empty($zone_for) ? '' : ('/' . $zone_for));
    }

    // Done
    return $base_url;
}

/**
 * Get the base URL (the minimum fully qualified URL to our personal data installation). For a shared install, or a GAE-install, this is different to the base-url.
 *
 * @param  ?boolean $https Whether to get the HTTPS base URL (null: do so only if the current page uses the HTTPS base URL)
 * @return URLPATH The base-url
 */
function get_custom_base_url($https = null)
{
    global $SITE_INFO;
    if (!empty($SITE_INFO['custom_base_url'])) {
        return $SITE_INFO['custom_base_url'];
    }
    if (empty($SITE_INFO['custom_base_url_stub'])) {
        return get_base_url($https);
    }

    // Note that HTTPS is not supported for shared installs
    $u = current_share_user();
    if ($u === null) {
        return get_base_url($https);
    }
    return $SITE_INFO['custom_base_url_stub'] . '/' . $u;
}

/**
 * Function to get a base URL for an Conversr relative-URL. The situation is complex as it needs to take into account Conversr multi-site-network's, locally defined theme images, and shared-installs (Demonstratr style).
 *
 * @param  URLPATH $at Short base URL we need to probe
 * @return URLPATH The appropriate base-url
 */
function get_complex_base_url($at)
{
    return ((get_forum_base_url() != get_base_url()) ? get_forum_base_url() : ((substr($at, 0, 22) === 'themes/default/images/') ? get_base_url() : get_custom_base_url()));
}

/**
 * Get a parameter value (either POST *or* GET, i.e. like $_REQUEST[$name]), or the default if neither can be found.
 * Implements additional security over the direct PHP access mechanism which should not be used.
 * Use with caution, as this has very limited CSRF protection compared to post_param_string.
 *
 * @param  ID_TEXT $name The name of the parameter to get
 * @param  ?~mixed $default The default value to give the parameter if the parameter value is not defined (null: allow missing parameter) (false: give error on missing parameter)
 * @return ?string The parameter value (null: missing)
 */
function either_param_string($name, $default = false)
{
    $ret = __param(array_merge($_POST, $_GET), $name, $default);
    if ($ret === null) {
        return null;
    }

    if ($ret === $default) {
        if ($default === null) {
            return null;
        }

        return $ret;
    }

    if (strpos($ret, ':') !== false && function_exists('cms_url_decode_post_process')) {
        $ret = cms_url_decode_post_process($ret);
    }

    require_code('input_filter');
    check_input_field_string($name, $ret, true);

    return $ret;
}

/**
 * Get the value of the specified POST parameter (i.e. like $_POST[$name]) if it is passed, or the default otherwise.
 * Implements additional security over the direct PHP access mechanism which should not be used.
 *
 * @param  ID_TEXT $name The name of the parameter to get
 * @param  ?~mixed $default The default value to give the parameter if the parameter value is not defined (null: allow missing parameter) (false: give error on missing parameter)
 * @param  boolean $html Whether we are cleaning for HTML rather than Comcode/plain-text
 * @param  boolean $conv_from_wysiwyg Whether to convert WYSIWYG contents to Comcode automatically
 * @return ?string The parameter value (null: missing)
 */
function post_param_string($name, $default = false, $html = false, $conv_from_wysiwyg = true)
{
    $ret = __param($_POST, $name, $default, false, true);

    if ($ret === null) {
        return null;
    }
    if ((trim($ret) === '') && ($default !== '') && (array_key_exists('require__' . $name, $_POST)) && ($_POST['require__' . $name] !== '0')) {
        if ($default === null) {
            return null;
        }

        require_code('failure');
        improperly_filled_in_post($name);
    }

    if (($ret !== '') && (addon_installed('wordfilter'))) {
        if ($name !== 'password') {
            require_code('wordfilter');
            if ($ret !== $default) {
                $ret = check_wordfilter($ret, $name);
            }
        }
    }
    if ($ret !== null) {
        $ret = unixify_line_format($ret, null, $html);

        if (post_param_integer($name . '_download_associated_media', 0) === 1) {
            require_code('comcode_cleanup');
            download_associated_media($ret);
        }
    }

    if ((isset($_POST[$name . '__is_wysiwyg'])) && ($_POST[$name . '__is_wysiwyg'] === '1') && ($conv_from_wysiwyg)) {
        if (trim($ret) === '') {
            $ret = '';
        } else {
            require_code('comcode_from_html');
            $ret = trim(semihtml_to_comcode($ret));
        }
    } else {
        if ((substr($ret, 0, 10) === '[semihtml]') && (substr(trim($ret), -11) === '[/semihtml]')) {
            $_ret = trim($ret);
            $_ret = substr($_ret, 10, strlen($_ret) - 11 - 10);
            if (strpos($_ret, '[semihtml') === false) {
                require_code('comcode_from_html');
                $ret = trim(semihtml_to_comcode($_ret));
            }
        }
    }

    require_code('input_filter');

    if ((!$GLOBALS['BOOTSTRAPPING']) && (!$GLOBALS['MICRO_AJAX_BOOTUP'])) {
        if ($ret !== $default) {
            check_posted_field($name, $ret);
        }

        // Custom fields.xml filter system
        $ret = filter_form_field_default($name, $ret, true);
    }

    if ($ret === $default) {
        return $ret;
    }

    if (strpos($ret, ':') !== false && function_exists('cms_url_decode_post_process')) {
        $ret = cms_url_decode_post_process($ret);
    }

    check_input_field_string($name, $ret, true);

    return $ret;
}

/**
 * Get the value of the specified GET parameter (i.e. like $_GET[$name]) if it is passed, or the default otherwise.
 * Implements additional security over the direct PHP access mechanism which should not be used.
 *
 * @param  ID_TEXT $name The name of the parameter to get
 * @param  ?~mixed $default The default value to give the parameter if the parameter value is not defined (null: allow missing parameter) (false: give error on missing parameter)
 * @param  boolean $no_security Whether to skip the security check. Does not currently do anything
 * @return ?string The parameter value (null: missing)
 */
function get_param_string($name, $default = false, $no_security = false)
{
    $ret = __param($_GET, $name, $default);
    if (($ret === '') && (isset($_GET['require__' . $name])) && ($default !== $ret) && ($_GET['require__' . $name] !== '0')) {
        // We didn't give some required input
        $GLOBALS['HTTP_STATUS_CODE'] = '400';
        if (!headers_sent()) {
            if ((!browser_matches('ie')) && (strpos(cms_srv('SERVER_SOFTWARE'), 'IIS') === false)) {
                header('HTTP/1.0 400 Bad Request');
            }
        }
        warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
    }

    if ($ret === $default) {
        return $ret;
    }

    if (strpos($ret, ':') !== false && function_exists('cms_url_decode_post_process')) {
        $ret = cms_url_decode_post_process($ret);
    }

    require_code('input_filter');
    check_input_field_string($name, $ret);

    if ($ret === false) { // Should not happen, but have seen in the wild via malicious bots sending corrupt URLs
        $ret = $default;
    }

    return $ret;
}

/**
 * Helper function to load up a GET/POST parameter.
 *
 * @param  array $array The array we're extracting parameters from
 * @param  string $name The name of the parameter
 * @param  ?~mixed $default The default value to give the parameter if the parameter value is not defined (null: allow missing parameter) (false: give error on missing parameter)
 * @param  boolean $integer Whether the parameter has to be an integer
 * @param  ?boolean $posted Whether the parameter is a POST parameter (null: undetermined)
 * @return string The value of the parameter
 *
 * @ignore
 */
function __param($array, $name, $default, $integer = false, $posted = false)
{
    if ((!isset($array[$name])) || ($array[$name] === false) || (($integer) && ($array[$name] === ''))) {
        if ($default !== false) {
            return $default;
        }

        require_code('failure');
        improperly_filled_in($name, $posted, $array);
    }

    $val = $array[$name];
    if (is_array($val)) {
        $val = trim(implode(',', $val), ' ,');
    }

    static $mq = null;
    if ($mq === null) {
        $mq = get_magic_quotes_gpc();
    }
    if ($mq) {
        $val = stripslashes($val);
    }

    return $val;
}

/**
 * Do a wildcard match by converting to a regular expression.
 *
 * @param  string $context The haystack
 * @param  string $word The needle (a wildcard expression)
 * @param  boolean $full_cover Whether full-coverance is required
 * @return boolean Whether we have a match
 */
function simulated_wildcard_match($context, $word, $full_cover = false)
{
    $rexp = str_replace('%', '.*', str_replace('_', '.', str_replace('\\?', '.', str_replace('\\*', '.*', preg_quote($word)))));
    if ($full_cover) {
        $rexp = '^' . $rexp . '$';
    }

    return preg_match('#' . str_replace('#', '\#', $rexp) . '#i', $context) != 0;
}

/**
 * This function is the integeric partner of either_param_string, as it returns the value as an integer.
 * You should always use integer specified versions when inputting integers, for the added security that type validation allows. If the value is of the wrong type, it indicates a hack attempt and will be logged.
 *
 * @param  ID_TEXT $name The name of the parameter to get
 * @param  ?~mixed $default The default value to give the parameter if the parameter value is not defined or the empty string (null: allow missing parameter) (false: give error on missing parameter)
 * @return ?integer The parameter value (null: not set, and null given as default)
 */
function either_param_integer($name, $default = false)
{
    $ret = __param(array_merge($_POST, $_GET), $name, ($default === false) ? $default : (($default === null) ? '' : strval($default)), true, null); // $_REQUEST contains cookies too, so can't use
    if (($default === null) && ($ret === '')) {
        return null;
    }
    if (!is_numeric($ret)) {
        require_code('failure');
        $ret = _param_invalid($name, $ret, true);
    }
    $reti = intval($ret);
    if (($reti > 2147483647) || ($reti < -2147483648)) {
        require_code('failure');
        _param_invalid($name, null, true);
    }
    return $reti;
}

/**
 * This function is the integeric partner of post_param_string, as it returns the value as an integer.
 *
 * @param  ID_TEXT $name The name of the parameter to get
 * @param  ?~mixed $default The default value to give the parameter if the parameter value is not defined or the empty string (null: allow missing parameter) (false: give error on missing parameter)
 * @return ?integer The parameter value (null: not set, and null given as default)
 */
function post_param_integer($name, $default = false)
{
    $ret = __param($_POST, $name, ($default === false) ? $default : (($default === null) ? '' : strval($default)), true, true);

    if ((!$GLOBALS['BOOTSTRAPPING']) && (!$GLOBALS['MICRO_AJAX_BOOTUP'])) {
        if (((($default === null) && ($ret === '')) ? null : intval($ret)) !== $default) {
            check_posted_field($name, $ret);
        }

        // Custom fields.xml filter system
        $ret = filter_form_field_default($name, $ret, true);
    }

    if (($default === null) && ($ret === '')) {
        return null;
    }
    if (!is_numeric($ret)) {
        require_code('failure');
        $ret = _param_invalid($name, $ret, true);
    }
    if ($ret === '0') {
        return 0;
    }
    if ($ret === '1') {
        return 1;
    }
    $reti = intval($ret);
    $retf = floatval($reti);
    if (($retf > 2147483647.0) || ($retf < -2147483648.0)) {
        require_code('failure');
        _param_invalid($name, null, true);
    }
    return $reti;
}

/**
 * This function is the integeric partner of get_param_string, as it returns the value as an integer.
 *
 * @param  ID_TEXT $name The name of the parameter to get
 * @param  ?~mixed $default The default value to give the parameter if the parameter value is not defined or the empty string (null: allow missing parameter) (false: give error on missing parameter)
 * @param  boolean $not_string_ok If a string is given, use the default parameter rather than giving an error (only use this if you are suffering from a parameter conflict situation between different parts of Composr)
 * @return ?integer The parameter value (null: not set, and null given as default)
 */
function get_param_integer($name, $default = false, $not_string_ok = false)
{
    $m_default = ($default === false) ? false : (isset($default) ? (($default === 0) ? '0' : strval($default)) : '');
    $ret = __param($_GET, $name, $m_default, true); // do not set $ret to mixed(), breaks bootstrapping
    if ((!isset($default)) && ($ret === '')) {
        return null;
    }
    if (!is_numeric($ret)) {
        if (substr($ret, -1) === '/') {
            $ret = substr($ret, 0, strlen($ret) - 1);
        }
        if (!is_numeric($ret)) { // Bizarre situation (bug in IIS?)
            $matches = array();
            if (preg_match('#^(\d+)\#[\w]*$#', $ret, $matches) !== 0) {
                $ret = $matches[1];
            } else {
                if ($not_string_ok) {
                    return $default;
                }
                require_code('failure');
                $ret = _param_invalid($name, $ret, false);
            }
        }
    }
    if ($ret === '0') {
        return 0;
    }
    if ($ret === '1') {
        return 1;
    }
    $reti = intval($ret);
    $retf = floatval($reti);
    if (($retf > 2147483647.0) || ($retf < -2147483648.0)) {
        require_code('failure');
        _param_invalid($name, null, false);
    }
    return $reti;
}

/**
 * Make sure that lines are seperated by "\n", with no "\r"'s there at all. For Mac data, this will be a flip scenario. For Linux data this will be a null operation. For windows data this will be change from "\r\n" to just "\n". For a realistic scenario, data could have originated on all kinds of platforms, with some editors converting, some situations being inter-platform, and general confusion. Don't make blind assumptions - use this function to clean data, then write clean code that only considers "\n"'s.
 *
 * @param  string $in The data to clean
 * @param  ?ID_TEXT $desired_charset The character set it should be in. We don't do any real conversions using this, only make sure that common problems with fed ISO-8859-1 data are resolved (null: output character set)
 * @param  boolean $html Whether we are cleaning for HTML rather than Comcode/plain-text
 * @param  boolean $from_disk Whether the file is loaded from disk (less conversion needed)
 * @return string The cleaned data
 */
function unixify_line_format($in, $desired_charset = null, $html = false, $from_disk = false)
{
    if ($in === '') {
        return $in;
    }

    if ($desired_charset === null) {
        $desired_charset = get_charset();
    }

    static $bom = null;
    if ($bom === null) {
        $bom = chr(0xEF) . chr(0xBB) . chr(0xBF);
    }
    if (substr($in, 0, 3) == $bom) {
        $in = substr($in, 3);
    }

    static $from = null;
    if ($from === null) {
        $from = array("\r\n", '&#8298;', "\r"); // &#8298; is very odd- seems to come from open office copy & paste
    }
    static $to = null;
    if ($to === null) {
        $to = array("\n", '', "\n");
    }
    $in = str_replace($from, $to, $in);
    return $in;
}

/**
 * Provides an override point for file synchronisation between mirrored servers. Called after any file creation, deletion or edit.
 *
 * @param  PATH $filename File/directory name to sync on (full path)
 */
function sync_file($filename)
{
    global $FILE_BASE, $_MODIFIED_FILES;
    static $has_sync_script = null;
    if (is_null($has_sync_script)) {
        $has_sync_script = is_file($FILE_BASE . '/data_custom/sync_script.php');
    }
    if ((!$has_sync_script) && (!isset($_MODIFIED_FILES))) {
        return;
    }

    require_code('files2');
    _sync_file($filename);
}

/**
 * Provides an override point for file-move synchronisation between mirrored servers. Called after any rename or move action.
 *
 * @param  PATH $old File/directory name to move from (may be full or relative path)
 * @param  PATH $new File/directory name to move to (may be full or relative path)
 */
function sync_file_move($old, $new)
{
    require_code('files2');
    _sync_file_move($old, $new);
}

/**
 * Performs lots of magic to make sure data encodings are converted correctly. Input, and output too (as often stores internally in UTF or performs automatic dynamic conversions from internal to external charsets).
 *
 * @param  boolean $known_utf8 Whether we know we are working in utf-8. This is the case for AJAX calls.
 */
function convert_data_encodings($known_utf8 = false)
{
    global $VALID_ENCODING, $CONVERTED_ENCODING;
    $VALID_ENCODING = true;

    if ($CONVERTED_ENCODING) {
        return; // Already done it
    }

    if (preg_match('#^[\x00-\x7F]*$#', serialize($_POST) . serialize($_GET) . serialize($_FILES)) != 0) { // Simple case, all is ASCII
        $CONVERTED_ENCODING = true;
        return;
    }

    require_code('character_sets');
    _convert_data_encodings($known_utf8);
}
