<?php /*

 Composr
 Copyright (c) Christopher Graham, 2004-2024

 See docs/LICENSE.md for full licensing 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  Christopher Graham
 * @package    core
 */

/*EXTRA FUNCTIONS: simplexml_load_string*/

/**
 * AJAX script for generating a CSRF token.
 *
 * @ignore
 */
function generate_csrf_token_script()
{
    prepare_backend_response('text/plain');

    cms_ini_set('ocproducts.xss_detect', '0');

    require_code('csrf_filter');
    echo generate_csrf_token();

    cms_safe_exit_flow();
}

/**
 * Prepare to inject COR headers.
 *
 * @ignore
 */
function cor_prepare()
{
    require_code('input_filter');
    $trusted_sites = get_trusted_sites(1);
    if (in_array(parse_url($_SERVER['HTTP_ORIGIN'], PHP_URL_HOST), $trusted_sites)) {
        header('Access-Control-Allow-Origin: ' . /*escape_header  function not needed and may not be loaded yet*/($_SERVER['HTTP_ORIGIN']));

        if ((isset($_SERVER['REQUEST_METHOD'])) && ($_SERVER['REQUEST_METHOD'] == 'OPTIONS')) {
            header('Access-Control-Allow-Credentials: true');

            // Send pre-flight response
            if (isset($_SERVER['ACCESS_CONTROL_REQUEST_HEADERS'])) {
                header('Access-Control-Allow-Headers: ' . /*escape_header  function not needed and may not be loaded yet*/($_SERVER['ACCESS_CONTROL_REQUEST_HEADERS']));
            }
            $methods = 'GET,POST,PUT,HEAD,OPTIONS';
            if (isset($_SERVER['ACCESS_CONTROL_REQUEST_HEADERS'])) {
                $methods .= str_replace("\n", '', str_replace("\r", '', $_SERVER['ACCESS_CONTROL_REQUEST_METHOD']));
            }
            header('Access-Control-Allow-Methods: ' . $methods);

            exit();
        }
    }
}

/**
 * AJAX script for checking if a new username is valid.
 *
 * @ignore
 */
function username_check_script()
{
    prepare_backend_response('text/plain');

    require_code('cns_members_action');
    require_code('cns_members_action2');
    require_lang('cns');

    $username = get_param_string('username', null, INPUT_FILTER_GET_IDENTIFIER);
    if ($username !== null) {
        $username = trim($username);
    }
    $password = post_param_string('password', null, INPUT_FILTER_PASSWORD);
    $email_address = post_param_string('email_address', '', INPUT_FILTER_POST_IDENTIFIER);
    $dob = post_param_date('birthday', true, false);
    $error = cns_check_name_valid($username, null, $password, $email_address, $dob, true);
    if ($error !== null) {
        $error->evaluate_echo();
    }

    cms_safe_exit_flow();
}

/**
 * AJAX script for checking if a username exists.
 *
 * @ignore
 */
function username_exists_script()
{
    prepare_backend_response('text/plain');

    $username = get_param_string('username', false, INPUT_FILTER_GET_IDENTIFIER);
    $member_id = $GLOBALS['FORUM_DRIVER']->get_member_from_username($username);
    if ($member_id === null) {
        echo 'false';
    }

    cms_safe_exit_flow();
}

/**
 * AJAX script for allowing username/author/search-terms home-in.
 *
 * @ignore
 */
function namelike_script()
{
    prepare_backend_response();

    $id = str_replace('*', '%', get_param_string('id', false, INPUT_FILTER_GET_COMPLEX));
    $special = get_param_string('special', '');

    cms_ini_set('ocproducts.xss_detect', '0');

    require_code('xml');

    echo '<?xml version="1.0" encoding="' . escape_html(get_charset()) . '"?' . '>';
    echo '<request><result>';

    if ($special == 'admin_search') {
        $names = [];
        if ($id != '') {
            require_all_lang();
            $hooks = find_all_hook_obs('systems', 'page_groupings', 'Hook_page_groupings_');
            foreach ($hooks as $hook => $object) {
                $info = $object->run();
                foreach ($info as $i) {
                    if ($i === null) {
                        continue;
                    }
                    $n = $i[3];
                    $n_eval = is_object($n) ? $n->evaluate() : $n;
                    if ($n_eval == '') {
                        continue;
                    }
                    if ((stripos($n_eval, $id) !== false) && (has_actual_page_access(get_member(), $i[2][0], $i[2][2]))) {
                        $names[] = '"' . $n_eval . '"';
                    }
                }
            }
            if (count($names) > 10) {
                $names = [];
            }

            cms_mb_asort($names, SORT_NATURAL | SORT_FLAG_CASE);
        }

        foreach ($names as $name) {
            echo '<option value="' . xmlentities($name) . '" displayname="" />';
        }
    } elseif ($special == 'search') {
        if (addon_installed('search')) {
            require_code('search');
            $names = find_search_suggestions($id, get_param_string('search_type', ''));
        } else {
            $names = [];
        }

        foreach ($names as $name) {
            echo '<option value="' . xmlentities($name) . '" displayname="" />';
        }
    } else {
        if ((strlen($id) == 0) && (addon_installed('chat'))) {
            $rows = $GLOBALS['SITE_DB']->query_select('chat_friends', ['member_liked', 'date_and_time'], ['member_likes' => get_member()], 'ORDER BY date_and_time', 100);
            $names = [];
            foreach ($rows as $row) {
                $username = $GLOBALS['FORUM_DRIVER']->get_username($row['member_liked'], false, USERNAME_DEFAULT_NULL);
                if ($username !== null) {
                    $names[$row['member_liked']] = $username;
                }
            }

            cms_mb_asort($names, SORT_NATURAL | SORT_FLAG_CASE);

            foreach ($names as $name) {
                echo '<option value="' . xmlentities($name) . '" displayname="" />';
            }
        } else {
            $names = [];
            if ((addon_installed('authors')) && ($special == 'author')) {
                $num_authors = $GLOBALS['SITE_DB']->query_select_value('authors', 'COUNT(*)');
                $like = ($num_authors < 1000) ? db_encode_like('%' . str_replace('_', '\_', $id) . '%') : db_encode_like(str_replace('_', '\_', $id) . '%'); // performance issue
                $rows = $GLOBALS['SITE_DB']->query('SELECT author FROM ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'authors WHERE author LIKE \'' . $like . '\' ORDER BY author', 15);
                $names = collapse_1d_complexity('author', $rows);

                cms_mb_asort($names, SORT_NATURAL | SORT_FLAG_CASE);

                foreach ($names as $name) {
                    echo '<option value="' . xmlentities($name) . '" displayname="" />';
                }
            } else {
                if ((!addon_installed('authors')) || ($special != 'author') || ($GLOBALS['FORUM_DRIVER']->get_num_members() < 5000)) {
                    $likea = $GLOBALS['FORUM_DRIVER']->get_matching_members($id . '%', 15);
                    if ((count($likea) == 15) && (addon_installed('chat')) && (!is_guest())) {
                        $likea = $GLOBALS['FORUM_DRIVER']->get_matching_members($id . '%', 15, true); // Limit to friends, if possible
                    }

                    foreach ($likea as $l) {
                        if (count($names) < 15) {
                            $names[$GLOBALS['FORUM_DRIVER']->mrow_member_id($l)] = $GLOBALS['FORUM_DRIVER']->mrow_username($l);
                        }
                    }
                }

                cms_mb_asort($names, SORT_NATURAL | SORT_FLAG_CASE);

                foreach ($names as $member_id => $name) {
                    echo '<option value="' . xmlentities($name) . '" displayname="' . xmlentities($GLOBALS['FORUM_DRIVER']->get_username($member_id, true)) . '" />';
                }
            }
        }
    }

    echo '</result></request>';

    cms_safe_exit_flow();
}

/**
 * AJAX script for finding out privileges for the queried resource.
 *
 * @ignore
 */
function find_permissions_script()
{
    prepare_backend_response('text/plain');

    require_code('permissions2');

    $serverid = get_param_string('serverid');
    $x = get_param_string('x');
    $matches = [];
    preg_match('#^access_(\d+)_privilege_(.+)$#', $x, $matches);
    $group_id = intval($matches[1]);
    $privilege = $matches[2];
    require_all_lang();
    echo do_lang('PRIVILEGE_' . $privilege) . '=';
    if ($serverid == '_root') {
        echo has_privilege_group($group_id, $privilege) ? do_lang('YES') : do_lang('NO');
    } else {
        require_code('sitemap');

        $test = find_sitemap_object($serverid);
        if ($test !== null) {
            list($ob,) = $test;

            $privilege_page = $ob->get_privilege_page($serverid);
        } else {
            $privilege_page = '';
        }

        echo has_privilege_group($group_id, $privilege, $privilege_page) ? do_lang('YES') : do_lang('NO');
    }

    cms_safe_exit_flow();
}

/**
 * AJAX script to make a fractional edit to some data.
 *
 * @ignore
 */
function fractional_edit_script()
{
    prepare_backend_response('text/plain');

    $_POST['fractional_edit'] = '1'; // FUDGE

    $zone = get_param_string('zone');
    $page = get_page_name();

    global $SESSION_CONFIRMED_CACHE;
    if ((!$SESSION_CONFIRMED_CACHE) && ($GLOBALS['SITE_DB']->query_select_value('zones', 'zone_require_session', ['zone_name' => $zone]) == 1) && (!is_guest())) {
        return;
    }

    if (!has_actual_page_access(get_member(), $page, $zone)) {
        access_denied('ZONE_ACCESS');
    }

    require_code('failure');
    global $WANT_TEXT_ERRORS;
    $WANT_TEXT_ERRORS = true;

    require_code('site');
    request_page($page, true);

    $supports_comcode = get_param_integer('supports_comcode', 0) == 1;
    $param_name = get_param_string('edit_param_name');
    if (isset($_POST[$param_name . '__altered_rendered_output'])) { // This will potentially be written into by module, as a better thing to render
        $edited = $_POST[$param_name . '__altered_rendered_output'];
    } else {
        $edited = post_param_string($param_name);
        if ($supports_comcode) {
            $_edited = comcode_to_tempcode($edited, get_member());
            $edited = $_edited->evaluate();
        } else {
            $edited = escape_html($edited);
        }
    }
    cms_ini_set('ocproducts.xss_detect', '0');
    echo $edited;

    cms_safe_exit_flow();
}

/**
 * AJAX script to tell if data has been changed.
 *
 * @ignore
 */
function change_detection_script()
{
    prepare_backend_response('text/plain');

    $page = get_page_name();

    $refresh_if_changed = either_param_string('refresh_if_changed');

    $object = get_hook_ob('systems', 'change_detection', filter_naughty_harsh($page), 'Hook_change_detection_');

    $result = $object->run($refresh_if_changed);
    echo $result ? '1' : '0';

    cms_safe_exit_flow();
}

/**
 * AJAX script for recording that something is currently being edited.
 *
 * @ignore
 */
function edit_ping_script()
{
    prepare_backend_response('text/plain');

    $GLOBALS['SITE_DB']->query('DELETE FROM ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'edit_pings WHERE the_time<' . strval(time() - 200));

    $GLOBALS['SITE_DB']->query_delete(
        'edit_pings',
        [
            'the_page' => cms_mb_substr(get_page_name(), 0, 80),
            'the_type' => cms_mb_substr(get_param_string('type'), 0, 80),
            'the_id' => cms_mb_substr(get_param_string('id', '', INPUT_FILTER_GET_COMPLEX), 0, 80),
            'the_member' => get_member(),
        ]
    );
    $GLOBALS['SITE_DB']->query_insert(
        'edit_pings',
        [
            'the_time' => time(),
            'the_page' => cms_mb_substr(get_page_name(), 0, 80),
            'the_type' => cms_mb_substr(get_param_string('type'), 0, 80),
            'the_id' => cms_mb_substr(get_param_string('id', '', INPUT_FILTER_GET_COMPLEX), 0, 80),
            'the_member' => get_member(),
        ]
    );

    echo '1';

    cms_safe_exit_flow();
}

/**
 * AJAX script for dynamically extended selection tree.
 *
 * @ignore
 */
function ajax_tree_script()
{
    // Closed site
    $site_closed = get_option('site_closed');
    if (($site_closed != '0') && (!has_privilege(get_member(), 'access_closed_site')) && (!$GLOBALS['IS_ACTUALLY_ADMIN'])) {
        http_response_code(503);
        prepare_backend_response('text/plain');
        @exit(get_option('closed'));
    }

    prepare_backend_response();

    // NB: We use ajax_tree hooks to power this. Those hooks may or may not use the Sitemap API to get the tree structure. However, the default ones are hard-coded, for better performance.

    require_code('xml');

    $hook = filter_naughty_harsh(get_param_string('hook'));
    require_code('hooks/systems/ajax_tree/' . $hook, true);
    $object = object_factory('Hook_ajax_tree_' . $hook, true);
    if ($object === null) {
        warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
    }
    $id = get_param_string('id', '', INPUT_FILTER_GET_COMPLEX);
    if ($id == '') {
        $id = null;
    }
    cms_ini_set('ocproducts.xss_detect', '0');
    $html_mask = get_param_integer('html_mask', 0) == 1;
    if (!$html_mask) {
        echo '<?xml version="1.0" encoding="' . escape_html(get_charset()) . '"?' . '>';
    }
    echo($html_mask ? '<html>' : '<request>');
    $_options = get_param_string('options', '', INPUT_FILTER_GET_COMPLEX);
    if ($_options == '') {
        $_options = json_encode([]);
    }
    $options = @json_decode($_options, true);
    if (($options === false) || ($options === null)) {
        if ($hook == 'choose_homesite_addon') { // LEGACY: May use old serialized format
            $options = [];
        } else {
            warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
        }
    }
    $val = $object->run($id, $options, get_param_string('default', null, INPUT_FILTER_GET_COMPLEX));
    echo str_replace('</body>', '<br id="ended" /></body>', $val);
    echo($html_mask ? '</html>' : '</request>');

    cms_safe_exit_flow();
}

/**
 * AJAX script for confirming a session is active.
 *
 * @ignore
 */
function confirm_session_script()
{
    prepare_backend_response('text/plain');

    cms_ini_set('ocproducts.xss_detect', '0');

    global $SESSION_CONFIRMED_CACHE;
    if (!$SESSION_CONFIRMED_CACHE) {
        echo $GLOBALS['FORUM_DRIVER']->get_username(get_member());
    }

    cms_safe_exit_flow();
}

/**
 * AJAX script for getting the text of a template, as used by a certain theme.
 *
 * @ignore
 */
function load_template_script()
{
    prepare_backend_response('text/plain');

    if (!has_actual_page_access(get_member(), 'admin_themes')) {
        exit();
    }

    cms_ini_set('ocproducts.xss_detect', '0');

    $theme = filter_naughty(get_param_string('theme'));
    $id = filter_naughty(basename(get_param_string('id')));
    $directory = filter_naughty(get_param_string('directory', dirname(get_param_string('id')), INPUT_FILTER_GET_COMPLEX));

    $x = get_custom_file_base() . '/themes/' . $theme . '/' . $directory . '_custom/' . $id;
    if (!file_exists($x)) {
        $x = get_file_base() . '/themes/' . $theme . '/' . $directory . '/' . $id;
    }
    if (!file_exists($x)) {
        $x = get_custom_file_base() . '/themes/default/' . $directory . '_custom/' . $id;
    }
    if (!file_exists($x)) {
        $x = get_file_base() . '/themes/default/' . $directory . '/' . $id;
    }

    if (file_exists($x)) {
        echo cms_file_get_contents_safe($x, FILE_READ_LOCK | FILE_READ_BOM);
    }

    cms_safe_exit_flow();
}

/**
 * Script for dynamic inclusion of CSS.
 *
 * @ignore
 */
function sheet_script()
{
    cms_ini_set('ocproducts.xss_detect', '0');

    prepare_backend_response('text/css');

    $sheet = get_param_string('sheet');
    if ($sheet != '') {
        $theme = get_param_string('theme', null);
        if (($theme !== null) && (!is_dir(get_file_base() . '/themes/' . $theme)) && (!is_dir(get_custom_file_base() . '/themes/' . $theme))) { // Sanity check
            $theme = null;
        }

        $path = css_enforce(filter_naughty($sheet), $theme);
        if ($path != '') {
            echo @str_replace('../../../', '', cms_file_get_contents_safe($path, FILE_READ_LOCK | FILE_READ_BOM));
        }
    }

    cms_safe_exit_flow();
}

/**
 * Script for dynamic inclusion of JavaScript.
 *
 * @ignore
 */
function script_script()
{
    cms_ini_set('ocproducts.xss_detect', '0');

    prepare_backend_response('text/javascript');

    $script = get_param_string('script');
    if ($script != '') {
        $theme = get_param_string('theme', null);
        if (($theme !== null) && (!is_dir(get_file_base() . '/themes/' . $theme)) && (!is_dir(get_custom_file_base() . '/themes/' . $theme))) { // Sanity check
            $theme = null;
        }

        $path = javascript_enforce(filter_naughty($script), $theme);
        if ($path != '') {
            echo @str_replace('../../../', '', cms_file_get_contents_safe($path, FILE_READ_LOCK | FILE_READ_BOM));
        }
    }

    cms_safe_exit_flow();
}

/**
 * AJAX script for dynamic inclusion of HTML snippets.
 *
 * @ignore
 */
function snippet_script()
{
    prepare_backend_response('text/plain');

    if (get_param_string('zone', null) === null) {
        // If zone not explicitly specified, try and work it out from the page name parameter (if one given)
        global $RELATIVE_PATH, $ZONE;
        $test = get_module_zone(get_page_name());
        if ($test !== null) {
            $RELATIVE_PATH = $test;
            $ZONE = null;
        }
    }

    cms_ini_set('ocproducts.xss_detect', '0');

    $hook = filter_naughty_harsh(get_param_string('snippet'));
    if ((!is_file(get_file_base() . '/sources/hooks/systems/snippets/' . $hook . '.php')) && (!is_file(get_file_base() . '/sources_custom/hooks/systems/snippets/' . $hook . '.php'))) {
        warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
    }
    require_code('hooks/systems/snippets/' . $hook, true);
    $object = object_factory('Hook_snippet_' . $hook);
    $tempcode = $object->run();
    $tempcode->handle_symbol_preprocessing();
    $out = $tempcode->evaluate();

    if ((strpos($out, "\n") !== false) && (strpos($hook, '__text') === false)) { // Is HTML
        if ((!function_exists('simplexml_load_string')) || ((function_exists('simplexml_load_string')) && (@simplexml_load_string('<wrap>' . preg_replace('#&\w+;#', '', $out) . '</wrap>') === false))) { // Optimisation-- check first via optimised native PHP function if possible
            require_code('xhtml');
            $out = xhtmlise_html($out, true);
        }
    }

    echo $out;

    cms_safe_exit_flow();
}
