<?php /*

 Composr
 Copyright (c) ocProducts, 2004-2016

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


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

*/

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

/**
 * Standard code module initialisation function.
 *
 * @ignore
 */
function init__chat()
{
    global $MEMBERS_BEFRIENDED_CACHE;
    $MEMBERS_BEFRIENDED_CACHE = null;

    global $EFFECT_SETTINGS_ROWS;
    $EFFECT_SETTINGS_ROWS = null;

    if (!defined('CHAT_ACTIVITY_PRUNE')) {
        define('CHAT_ACTIVITY_PRUNE', 25); // How many seconds before doing database cleanup operations, including member timeouts for going offline. NB: This define is duplicated in chat_poller.php for performance
    }
    if (!defined('CHAT_EVENT_PRUNE')) {
        define('CHAT_EVENT_PRUNE', 60 * 60 * 24); // How many seconds to keep event messages for
    }
}

/**
 * Get Tempcode for a chatroom 'feature box' for the given row
 *
 * @param  array $row The database field row of it
 * @param  ID_TEXT $zone The zone to use
 * @param  boolean $give_context Whether to include context (i.e. say WHAT this is, not just show the actual content)
 * @param  ID_TEXT $guid Overridden GUID to send to templates (blank: none)
 * @return Tempcode A box for it, linking to the full page
 */
function render_chat_box($row, $zone = '_SEARCH', $give_context = true, $guid = '')
{
    if (is_null($row)) { // Should never happen, but we need to be defensive
        return new Tempcode();
    }

    require_lang('chat');

    $url = build_url(array('page' => 'chat', 'type' => 'room', 'id' => $row['id']), $zone);

    $_title = $row['room_name'];
    $title = $give_context ? do_lang('CONTENT_IS_OF_TYPE', do_lang('CHATROOM'), $_title) : $_title;

    return do_template('SIMPLE_PREVIEW_BOX', array(
        '_GUID' => ($guid != '') ? $guid : 'dacd41bad78b545f179582f83209c070',
        'ID' => strval($row['id']),
        'TITLE' => $title,
        'TITLE_PLAIN' => $_title,
        'SUMMARY' => '',
        'URL' => $url,
        'FRACTIONAL_EDIT_FIELD_NAME' => $give_context ? null : 'room_name',
        'FRACTIONAL_EDIT_FIELD_URL' => $give_context ? null : '_SEARCH:admin_chat:__edit:' . strval($row['id']),
        'RESOURCE_TYPE' => 'chat',
    ));
}

/**
 * High-level messages script handling
 */
function messages_script()
{
    prepare_for_known_ajax_response();

    get_screen_title('', false); // Force session time to be updated

    require_code('xml');

    // Closed site
    $site_closed = get_option('site_closed');
    if (($site_closed == '1') && (!has_privilege(get_member(), 'access_closed_site')) && (!$GLOBALS['IS_ACTUALLY_ADMIN'])) {
        header('Content-type: text/plain; charset=' . get_charset());
        @exit(get_option('closed'));
    }

    // Check we are allowed here
    //if (!has_actual_page_access(get_member(), 'chat')) access_denied('PAGE_ACCESS');  Actually we'll use room permissions for that; don't want to block the shoutbox

    // Check the action
    $action = get_param_string('action', 'new');

    if ($action == 'all') {
        // Getting all messages (i.e. up to five minutes ago)
        _chat_messages_script_ajax(either_param_integer('room_id'), true);
    } elseif ($action == 'post') {
        // Posting a message
        $message = post_param_string('message');
        _chat_post_message_ajax(either_param_integer('room_id'), $message, post_param_string('font', ''), post_param_string('colour', ''), post_param_integer('first_message', 0));
    } elseif ($action == 'start_im') {
        require_lang('chat');

        $people = post_param_string('people');
        if ($people == '') {
            exit();
        }

        $room = array();
        $may_recycle = (either_param_integer('may_recycle', 0) == 1);
        if ($may_recycle) {
            if (strpos($people, ',') === false) {
                // See if we can find a room to recycle
                $room = $GLOBALS['SITE_DB']->query('SELECT * FROM ' . get_table_prefix() . 'chat_rooms WHERE ' . db_string_equal_to('allow_list', $people . ',' . strval(get_member())) . ' OR ' . db_string_equal_to('allow_list', strval(get_member()) . ',' . $people));
            }
        }

        $extra_xml = '';

        if (!array_key_exists(0, $room)) { // No room to recycle
            require_code('chat2');
            if (strpos($people, ',') === false) {
                $room_name = $GLOBALS['FORUM_DRIVER']->get_username(get_member());
            } else {
                $room_name = do_lang('IM_MULTI', $GLOBALS['FORUM_DRIVER']->get_username(get_member()));
            }
            add_chatroom('', $room_name, get_member(), filter_invites_for_blocking(strval(get_member()) . ',' . $people), '', '', '', user_lang(), 1);
        } else {
            // Resend invite (this is a self-invite)
            $room[0]['room_name'] = $GLOBALS['FORUM_DRIVER']->get_username(intval($people));
            $num_posts = $GLOBALS['SITE_DB']->query_select_value('chat_messages', 'COUNT(*)', array('room_id' => $room[0]['id']));
            $extra_xml = '<chat_invite num_posts="' . strval($num_posts) . '" you="' . strval(get_member()) . '" inviter="' . strval(get_member()) . '" participants="' . xmlentities($people . ',' . strval(get_member())) . '" room_name="' . xmlentities($room[0]['room_name']) . '" avatar_url="">' . strval($room[0]['id']) . '</chat_invite>' . "\n";
        }

        // Send response of new messages, so we get instant result
        _chat_messages_script_ajax(-2, false, either_param_integer('message_id'), either_param_integer('event_id'), $extra_xml);
    } elseif ($action == 'join_im') {
        $room_id = post_param_integer('room_id');
        $room_check = $GLOBALS['SITE_DB']->query_select('chat_rooms', array('id', 'is_im', 'c_welcome', 'allow_list_groups', 'disallow_list_groups', 'allow_list', 'disallow_list', 'room_owner'), array('id' => $room_id), '', 1);
        if (!array_key_exists(0, $room_check)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'chat'));
        }
        $room_row = $room_check[0];
        if (!check_chatroom_access($room_row, true, null, true)) {
            return; // Possibly the room was closed already
        }
        $event_id = $GLOBALS['SITE_DB']->query_insert('chat_events', array(
            'e_type_code' => 'JOIN_IM',
            'e_member_id' => get_member(),
            'e_room_id' => $room_id,
            'e_date_and_time' => time()
        ), true);
        require_code('files');
        cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/modules/chat/chat_last_event.bin', strval($event_id), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);

        // Catch up the current user so that they know who else is in the room just joined...

        $events_output = '';
        $peoplea = explode(',', $room_row['allow_list']);
        foreach ($peoplea as $person) {
            $person = trim($person);
            if ($person == '') {
                continue;
            }
            $member_id = intval($person);
            if ($member_id != get_member()) {
                $username = $GLOBALS['FORUM_DRIVER']->get_username($member_id);
                $avatar_url = $GLOBALS['FORUM_DRIVER']->get_member_avatar_url($member_id);
                if (!is_null($username)) {
                    $events_output .= '<chat_event event_type="PREINVITED_TO_IM" away="' . (chatter_active($member_id) ? '0' : '1') . '" member_id="' . strval($member_id) . '" username="' . xmlentities($username) . '" avatar_url="' . xmlentities($avatar_url) . '" room_id="' . strval($room_id) . '"></chat_event>';
                }
            }
        }

        _chat_messages_script_ajax(-1, false, -1, either_param_integer('event_id'), $events_output);
    } elseif ($action == 'deinvolve_im') {
        $room_id = post_param_integer('room_id');
        $room_check = $GLOBALS['SITE_DB']->query_select('chat_rooms', array('id', 'is_im', 'c_welcome', 'allow_list_groups', 'disallow_list_groups', 'allow_list', 'disallow_list', 'room_owner'), array('id' => $room_id), '', 1);
        if (array_key_exists(0, $room_check)) {
            $room_row = $room_check[0];
            if (check_chatroom_access($room_row, true, null, true)) {
                $allow_list = str_replace(',' . strval(get_member()) . ',', ',', ',' . $room_row['allow_list'] . ',');
                $allow_list = substr($allow_list, 1, strlen($allow_list) - 2);
                $event_id = $GLOBALS['SITE_DB']->query_insert('chat_events', array(
                    'e_type_code' => 'DEINVOLVE_IM',
                    'e_member_id' => get_member(),
                    'e_room_id' => $room_id,
                    'e_date_and_time' => time()
                ), true);
                require_code('files');
                cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/modules/chat/chat_last_event.bin', strval($event_id), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
                if ($allow_list == '') {
                    require_code('chat2');
                    delete_chatroom($room_id);
                } else {
                    $peoplea = explode(',', $allow_list);
                    $room_owner = $room_row['room_owner'];
                    if ($room_owner == get_member()) {
                        $room_owner = intval($peoplea[0]);
                    }
                    $GLOBALS['SITE_DB']->query_update('chat_rooms', array('room_owner' => $room_owner, 'allow_list' => $allow_list), array('id' => $room_id), '', 1);
                }
            }
        }
    } elseif ($action == 'invite_im') {
        $room_id = post_param_integer('room_id');
        $people = post_param_string('people');
        if ($people == '') {
            exit();
        }
        foreach (explode(',', $people) as $person) {
            $person = trim($person);
            if ($person == '') {
                continue;
            }
            $event_id = $GLOBALS['SITE_DB']->query_insert('chat_events', array(
                'e_type_code' => 'PREINVITED_TO_IM',
                'e_member_id' => intval($person),
                'e_room_id' => $room_id,
                'e_date_and_time' => time()
            ), true);
            require_code('files');
            cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/modules/chat/chat_last_event.bin', strval($event_id), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
        }
        $room_check = $GLOBALS['SITE_DB']->query_select('chat_rooms', array('id', 'is_im', 'c_welcome', 'allow_list_groups', 'disallow_list_groups', 'allow_list', 'disallow_list', 'room_owner'), array('id' => $room_id), '', 1);
        if (!array_key_exists(0, $room_check)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'chat'));
        }
        $room_row = $room_check[0];
        if (!check_chatroom_access($room_row, true, null, true)) {
            return; // Possibly the room was closed already
        }
        $allow_list = $room_row['allow_list'];
        $_people = $allow_list . ',' . filter_invites_for_blocking($people);
        $GLOBALS['SITE_DB']->query_update('chat_rooms', array('allow_list' => $_people), array('id' => $room_id), '', 1);
    } else {
        // Getting all new messages (i.e. up to our last refresh time)
        _chat_messages_script_ajax(either_param_integer('room_id'), false, either_param_integer('message_id'), either_param_integer('event_id'));
    }

    cms_safe_exit_flow();
}

/**
 * Find if a member is befriended by the current member.
 *
 * @param  MEMBER $member_id The member being checked
 * @return boolean Whether the member is befriended
 */
function member_befriended($member_id)
{
    if ($member_id == get_member()) {
        return false;
    }
    if (is_guest()) {
        return false;
    }

    global $MEMBERS_BEFRIENDED_CACHE;
    if (is_null($MEMBERS_BEFRIENDED_CACHE)) {
        $MEMBERS_BEFRIENDED_CACHE = collapse_1d_complexity('member_liked', $GLOBALS['SITE_DB']->query_select('chat_friends', array('member_liked'), array('member_likes' => get_member()), '', 100));
    }
    if (count($MEMBERS_BEFRIENDED_CACHE) == 100) { // Ah, too much to preload
        return !is_null($GLOBALS['SITE_DB']->query_select_value_if_there('chat_friends', 'member_liked', array('member_liked' => $member_id, 'member_likes' => get_member())));
    }
    return (in_array($member_id, $MEMBERS_BEFRIENDED_CACHE));
}

/**
 * Filter an invite list to make sure people who are blocking don't get drawn in and hence their blocking unmasked.
 *
 * @param  string $people Comma-separated people list
 * @return string Filtered comma-separated people list
 */
function filter_invites_for_blocking($people)
{
    require_code('users2');
    $_people = explode(',', $people);
    $people_new = array();
    foreach ($_people as $person) {
        $person = trim($person);
        if ($person == '') {
            continue;
        }
        if (!member_blocked(get_member(), intval($person))) {
            $people_new[] = intval($person);
        }
    }
    return implode(',', array_unique($people_new));
}

/**
 * Prune membership of chatroom.
 *
 * @param  ?AUTO_LINK $room_id Room ID (null: all rooms)
 */
function chat_room_prune($room_id)
{
    // Find who may have gone offline
    $extra = '';
    $last_active_prune = intval(get_value('last_active_prune'));
    if ($last_active_prune < time() - CHAT_ACTIVITY_PRUNE) {
        $sql = 'SELECT id,member_id,room_id FROM ' . get_table_prefix() . 'chat_active WHERE date_and_time<' . strval(time() - CHAT_ACTIVITY_PRUNE);
        $sql .= ' AND (member_id<>' . strval(get_member()); // Exception for current member in the room they are in (for situation of them being the last to go and first to come back way later)
        if ($room_id !== null) {
            $sql .= ' OR room_id<>' . strval($room_id);
        }
        $sql .= ')';
        $pruned = $GLOBALS['SITE_DB']->query($sql);
        foreach ($pruned as $p) {
            // Mark activity row for clearing out
            $extra .= ' OR id=' . strval($p['id']);

            // Have they left the lobby? (or site, if it's site-wide IM)
            if (is_null($p['room_id'])) {
                $last_become_active = $GLOBALS['SITE_DB']->query_select_value_if_there('chat_events', 'MAX(e_date_and_time)', array('e_member_id' => $p['member_id'], 'e_type_code' => 'BECOME_ACTIVE', 'e_room_id' => null));
                $last_become_inactive = $GLOBALS['SITE_DB']->query_select_value_if_there('chat_events', 'MAX(e_date_and_time)', array('e_member_id' => $p['member_id'], 'e_type_code' => 'BECOME_INACTIVE', 'e_room_id' => null));
                if ((is_null($last_become_inactive)) || ($last_become_active > $last_become_inactive)) { // If not already marked inactive
                    // Remove old active/inactive events for this member
                    $GLOBALS['SITE_DB']->query_delete('chat_events', array('e_member_id' => $p['member_id'], 'e_type_code' => 'BECOME_ACTIVE', 'e_room_id' => null));
                    $GLOBALS['SITE_DB']->query_delete('chat_events', array('e_member_id' => $p['member_id'], 'e_type_code' => 'BECOME_INACTIVE', 'e_room_id' => null));

                    // Create new BECOME_INACTIVE event
                    $event_id = $GLOBALS['SITE_DB']->query_insert('chat_events', array(
                        'e_type_code' => 'BECOME_INACTIVE',
                        'e_member_id' => $p['member_id'],
                        'e_room_id' => null,
                        'e_date_and_time' => time()
                    ), true);

                    require_code('files');
                    $path = get_custom_file_base() . '/data_custom/modules/chat';
                    cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/modules/chat/chat_last_event.bin', strval($event_id), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
                }
            } else {
                // Make "left room" message
                if (!is_guest($p['member_id'])) {
                    require_code('lang');
                    require_code('tempcode');
                    require_lang('chat');
                    $left_username = $GLOBALS['FORUM_DRIVER']->get_username($p['member_id']);
                    if ($left_username === null) {
                        $left_username = do_lang('UNKNOWN');
                    }
                    $left_room_msg = do_lang('LEFT_CHATROOM', $left_username);
                    if ($left_room_msg != '') {
                        require_code('comcode');
                        $map = array(
                            'system_message' => 1,
                            'ip_address' => get_ip_address(),
                            'room_id' => $p['room_id'],
                            'member_id' => $p['member_id'],
                            'date_and_time' => time(),
                            'text_colour' => get_option('chat_default_post_colour'),
                            'font_name' => get_option('chat_default_post_font'),
                        );
                        $map += insert_lang_comcode('the_message', $left_room_msg, 4);
                        $message_id = $GLOBALS['SITE_DB']->query_insert('chat_messages', $map, true);
                        require_code('files');
                        cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/modules/chat/chat_last_msg.bin', strval($message_id), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
                    }
                }
            }
        }
        set_value('last_active_prune', strval(time()));
    }
    if ($room_id === null) {
        $extra2 = 'room_id IS NULL';
    } else {
        $extra2 = 'room_id=' . strval($room_id);
    }

    // Prune 'active' indication (deletes us, and anything that needs pruning)
    $GLOBALS['SITE_DB']->query('DELETE FROM ' . get_table_prefix() . 'chat_active WHERE (member_id=' . strval(get_member()) . ' AND ' . $extra2 . ')' . $extra, null, null, false, true);

    // Note that *we are still here*
    $GLOBALS['SITE_DB']->query_insert('chat_active', array('member_id' => get_member(), 'date_and_time' => time(), 'room_id' => ($room_id === null) ? null : $room_id));
}

/**
 * Output messages (in XML format) from up to five minutes ago (give somebody who's just joined the chatroom some chat backlog), or the messages posted since the last check.
 *
 * @param  AUTO_LINK $room_id Room ID (or -1 to mean 'all' as used for IM global process, -2 to mean none)
 * @param  boolean $backlog Output the backlog?
 * @param  ?AUTO_LINK $message_id Latest received message ID (null: we're not getting latest messages)
 * @param  ?AUTO_LINK $event_id Latest event ID (null: we're not getting events, but we do request a null event so we can use that as a future reference point)
 * @param  string $events_output Events output to append
 *
 * @ignore
 */
function _chat_messages_script_ajax($room_id, $backlog = false, $message_id = null, $event_id = null, $events_output = '')
{
    if ($event_id == -1 && $room_id > -1/*if we're not checking IMs we don't need historic events*/) {
        $event_id = null;
    }

    require_lang('chat');

    if (addon_installed('securitylogging')) {
        require_lang('submitban');
    }

    $room_check = null;
    $room_row = null;
    if ($room_id >= 0) {
        $room_check = $GLOBALS['SITE_DB']->query_select('chat_rooms', array('id', 'is_im', 'c_welcome', 'allow_list_groups', 'disallow_list_groups', 'allow_list', 'disallow_list', 'room_owner'), array('id' => $room_id), '', 1);

        if (!array_key_exists(0, $room_check)) {
            // This room doesn't exist
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'chat'));
        }
        $room_row = $room_check[0];
        if (!check_chatroom_access($room_row, true)) {
            return; // Possibly the room was closed already
        }
        $welcome = ((array_key_exists(get_member(), get_chatters_in_room($room_id))) || (!$backlog) || (get_param_integer('no_reenter_message', 0) == 1)) ? null : $room_row['c_welcome'];
    } else {
        $room_check = $GLOBALS['SITE_DB']->query('SELECT id,is_im,c_welcome,allow_list_groups,disallow_list_groups,allow_list,disallow_list,room_owner FROM ' . get_table_prefix() . 'chat_rooms WHERE ' . sql_members_in_im_conversation(array(get_member())));

        $welcome = null;
    }

    if ($room_id != -2) { // Some room(s) at least, so do some cleanup
        // Note that *we are still here*
        if ($room_id == -1) {
            $GLOBALS['SITE_DB']->query_update('chat_active', array('date_and_time' => time()), array('member_id' => get_member()));
        } else {
            $GLOBALS['SITE_DB']->query_delete('chat_active', array('member_id' => get_member(), 'room_id' => $room_id), '', 1);
            $GLOBALS['SITE_DB']->query_insert('chat_active', array('member_id' => get_member(), 'date_and_time' => time(), 'room_id' => $room_id));
        }
    }
    chat_room_prune(null);

    $from_id = null;
    $start = null;

    if (!$backlog) {
        $from_id = $message_id;
    }

    $messages = ($room_id == -2) ? array() : chat_get_room_content(($room_id == -1) ? null : $room_id, $room_check, intval(get_option('chat_max_messages_to_show')), false, false, $start, null, $from_id, null, $welcome, true, get_param_integer('no_reenter_message', 0) == 0);
    $stored_id = (array_key_exists(0, $messages)) ? $messages[0]['id'] : null;

    $messages_output = '';
    foreach ($messages as $_message) {
        $edit_url = new Tempcode();
        $chat_ban_url = new Tempcode();
        $chat_unban_url = new Tempcode();
        if ((!is_null($room_check)) && (!is_null($room_row)) && (array_key_exists(0, $room_check))) {
            $moderator = is_chat_moderator($_message['member_id'], $room_id, $room_row['room_owner']);
            $edit_url = build_url(array('page' => 'cms_chat', 'type' => 'edit', 'id' => $_message['id'], 'room_id' => $_message['room_id']), get_module_zone('cms_chat'));
            if (has_privilege(get_member(), 'ban_chatters_from_rooms')) {
                if (check_chatroom_access($room_row, true, $_message['member_id'])) {
                    $chat_ban_url = build_url(array('page' => 'cms_chat', 'type' => 'ban', 'id' => $_message['room_id'], 'member_id' => $_message['member_id']), get_module_zone('cms_chat'));
                    $chat_unban_url = new Tempcode();
                } else {
                    $chat_ban_url = new Tempcode();
                    $chat_unban_url = build_url(array('page' => 'cms_chat', 'type' => 'unban', 'id' => $_message['room_id'], 'member_id' => $_message['member_id']), get_module_zone('cms_chat'));
                }
            }
        } else {
            $moderator = false;
        }

        if ((addon_installed('actionlog')) && (has_actual_page_access(get_member(), 'admin_actionlog')) && (preg_match('#[:\.]#', $_message['ip_address']) != 0)) {
            if (is_guest($_message['member_id'])) {
                $ban_url = build_url(array('page' => 'admin_ip_ban', 'type' => 'toggle_ip_ban', 'id' => $_message['ip_address']), get_module_zone('admin_ip_ban'));
            } else {
                $ban_url = build_url(array('page' => 'admin_ip_ban', 'type' => 'toggle_submitter_ban', 'id' => $_message['member_id']), get_module_zone('admin_ip_ban'));
            }
        } else {
            $ban_url = new Tempcode();
        }

        if (($room_id != -1) && (addon_installed('actionlog')) && (addon_installed('securitylogging')) && ((has_actual_page_access(get_member(), 'admin_actionlog')) || (has_actual_page_access(get_member(), 'cms_chat')))) {
            $staff_actions = do_template('CHAT_STAFF_ACTIONS', array('_GUID' => 'd3fbcaa9eee688452091583ee436e465', 'CHAT_BAN_URL' => $chat_ban_url, 'CHAT_UNBAN_URL' => $chat_unban_url, 'EDIT_URL' => $edit_url, 'BAN_URL' => $ban_url));
        } else {
            $staff_actions = new Tempcode();
        }

        $avatar_url = $GLOBALS['FORUM_DRIVER']->get_member_avatar_url($_message['member_id']);
        if (!is_guest($_message['member_id'])) {
            $user = $GLOBALS['FORUM_DRIVER']->member_profile_hyperlink($_message['member_id'], true, $_message['username'], false);
        } else {
            if (preg_match('#[:\.]#', $_message['ip_address']) != 0) {
                $user = make_string_tempcode(escape_html(do_lang('GUEST') . '-' . substr(md5($_message['ip_address']), 0, 5)));
            } else {
                $user = make_string_tempcode(escape_html($_message['ip_address']));
            }
        }

        $template = do_template('CHAT_MESSAGE', array(
            '_GUID' => '6bcac8d9fdd166cde266f8d23b790b69',
            'SYSTEM_MESSAGE' => strval($_message['system_message']),
            'STAFF' => $moderator,
            'OLD_MESSAGES' => $backlog,
            'AVATAR_URL' => $avatar_url,
            'STAFF_ACTIONS' => $staff_actions,
            'MEMBER' => $user,
            'MEMBER_ID' => strval($_message['member_id']),
            'MESSAGE' => $_message['the_message'],
            'TIME' => $_message['date_and_time_nice'],
            'RAW_TIME' => strval($_message['date_and_time']),
            'FONT_COLOUR' => $_message['text_colour'],
            'FONT_FACE' => $_message['font_name'],
        ));
        $messages_output .= '<div xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" sender_id="' . strval($_message['member_id']) . '" room_id="' . strval($_message['room_id']) . '" id="' . strval($_message['id']) . '" timestamp="' . strval($_message['date_and_time']) . '">' . $template->evaluate() . '</div>';
    }

    // Members update, but only for the room interface
    if ($room_id >= 0) {
        $tpl = get_chatters_in_room_tpl(get_chatters_in_room($room_id));
        $events_output .= '<chat_members_update>' . escape_html($tpl->evaluate()) . '</chat_members_update>';
    }

    // IM events and invitations, but only for the lobby IM interface
    $invitations_output = '';
    if ($room_id < 0) {
        $room_check = list_to_map('id', $GLOBALS['SITE_DB']->query('SELECT * FROM ' . get_table_prefix() . 'chat_rooms WHERE ' . sql_members_in_im_conversation(array(get_member()))));
        foreach ($room_check as $room) {
            if (check_chatroom_access($room, true, null, true)) {
                if ((($room['allow_list'] == strval(get_member()) . ',' . strval(get_member())/*Opened room with self? Weird*/) || ($room['allow_list'] == strval(get_member()))) && (is_null($event_id)/*Only on fresh start, not repeat AJAX requests*/)) { // If it's just you in the room, close that room down
                    require_code('chat2');
                    delete_chatroom($room['id']);
                } else {
                    $people_in_room = array_map('intval', explode(',', $room['allow_list']));
                    if (($room['room_name'] == $GLOBALS['FORUM_DRIVER']->get_username(get_member()) || ($room['room_owner'] == get_member())) && (count($people_in_room) > 0)) { // If room named after us, try and switch reported owner/name to that of other person
                        if ($people_in_room[0] != get_member()) {
                            $room['room_owner'] = $people_in_room[0];
                        } else {
                            if (array_key_exists(1, $people_in_room)) {
                                $room['room_owner'] = $people_in_room[1];
                            }
                        }
                        $test = $GLOBALS['FORUM_DRIVER']->get_username($room['room_owner']);
                        if (!is_null($test)) {
                            $room['room_name'] = $test;
                        }
                    }

                    // Find who else is in room
                    $participants = '';
                    foreach ($people_in_room as $person) {
                        if ($person != get_member()) {
                            if ($participants != '') {
                                $participants .= '';
                            }
                            $participants .= strval($person);
                        }
                    }

                    $num_posts = $GLOBALS['SITE_DB']->query_select_value('chat_messages', 'COUNT(*)', array('room_id' => $room['id']));

                    $avatar_url = $GLOBALS['FORUM_DRIVER']->get_member_avatar_url($room['room_owner']);

                    $invitations_output .= '<chat_invite num_posts="' . strval($num_posts) . '" you="' . strval(get_member()) . '" inviter="' . (is_null($room['room_owner']) ? '' : strval($room['room_owner'])) . '" participants="' . xmlentities($participants) . '" room_name="' . xmlentities($room['room_name']) . '" avatar_url="' . xmlentities($avatar_url) . '">' . strval($room['id']) . '</chat_invite>' . "\n";
                }
            }
        }

        if (!is_null($event_id)) {
            $events = $GLOBALS['SITE_DB']->query('SELECT * FROM ' . get_table_prefix() . 'chat_events WHERE id>' . strval($event_id));
            foreach ($events as $event) {
                if ($event['e_member_id'] == get_member()) {
                    continue;
                }
                if (is_guest($event['e_member_id'])) {
                    continue;
                }

                $send_out = false;
                switch ($event['e_type_code']) {
                    case 'BECOME_INACTIVE':
                    case 'BECOME_ACTIVE':
                        require_code('users2');
                        if ((!member_blocked(get_member(), $event['e_member_id'])) && (member_befriended($event['e_member_id']))) {
                            $send_out = true;
                        }
                        break;
                    case 'PREINVITED_TO_IM':
                    case 'JOIN_IM':
                        if ((array_key_exists($event['e_room_id'], $room_check)) && (/*Check inviter not left*/check_chatroom_access($room_check[$event['e_room_id']], true, $event['e_member_id'], true)) && (check_chatroom_access($room_check[$event['e_room_id']], true, null, true))) {
                            $send_out = true;
                        }
                        break;
                    case 'DEINVOLVE_IM':
                        if ((array_key_exists($event['e_room_id'], $room_check)) && (check_chatroom_access($room_check[$event['e_room_id']], true, null, true))) {
                            $send_out = true;
                        }
                        break;
                    case 'INVITED_TO_IM': // Ignore this one
                        break;
                }
                if ($send_out) {
                    $username = $GLOBALS['FORUM_DRIVER']->get_username($event['e_member_id']);
                    $avatar_url = $GLOBALS['FORUM_DRIVER']->get_member_avatar_url($event['e_member_id']);
                    if (!is_null($username)) {
                        $events_output .= '<chat_event away="' . (chatter_active($event['e_member_id']) ? '0' : '1') . '" event_type="' . $event['e_type_code'] . '" member_id="' . strval($event['e_member_id']) . '" username="' . xmlentities($username) . '" avatar_url="' . xmlentities($avatar_url) . '" room_id="' . (is_null($event['e_room_id']) ? '' : strval($event['e_room_id'])) . '">' . strval($event['id']) . '</chat_event>' . "\n";
                    }
                }
            }
        } else {
            $max_id = $GLOBALS['SITE_DB']->query_select_value('chat_events', 'MAX(id)');
            if (is_null($max_id)) {
                $max_id = db_get_first_id() - 1;
            }
            $events_output .= '<chat_event type="NULL">' . strval($max_id) . '</chat_event>' . "\n";
        }
    }

    $last_msg = $GLOBALS['SITE_DB']->query_select_value('chat_messages', 'MAX(id)');
    $last_event = $GLOBALS['SITE_DB']->query_select_value('chat_events', 'MAX(id)');
    $tracking_output = '<chat_tracking last_msg="' . (is_null($last_msg) ? '' : strval($last_msg)) . '" last_event="' . (is_null($last_event) ? '' : strval($last_event)) . '">' . strval($room_id) . '</chat_tracking>' . "\n";

    header('Cache-Control: no-cache, must-revalidate'); // HTTP/1.1
    header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
    header('Content-Type: application/xml');
    $output = '<' . '?xml version="1.0" encoding="' . get_charset() . '" ?' . '>
' . get_xml_entities() . '
<response>
    <result>
' . $tracking_output . $events_output . $invitations_output . $messages_output . '
    </result>
</response>';
    echo $output;
}

/**
 * Find whether a member is active in chat (i.e. not away).
 *
 * @param  MEMBER $member_id Member ID
 * @param  ?AUTO_LINK $room_id Room ID (null: lobby)
 * @return boolean Whether the member is active
 */
function chatter_active($member_id, $room_id = null)
{
    if (is_null($room_id)) {
        $room_clause = 'room_id IS NULL';
    } else {
        $room_clause = 'room_id=' . strval($room_id);
    }
    $test = $GLOBALS['SITE_DB']->query_value_if_there('SELECT member_id FROM ' . get_table_prefix() . 'chat_active WHERE ' . $room_clause . ' AND date_and_time>=' . strval(time() - CHAT_ACTIVITY_PRUNE) . ' AND member_id=' . (string)$member_id);
    return !is_null($test);
}

/**
 * Find whether a member is a moderator of a chatroom.
 *
 * @param  MEMBER $member_id Member ID
 * @param  AUTO_LINK $room_id Room ID
 * @param  ?MEMBER $room_owner Room owner (null: none)
 * @return boolean Whether the member is a moderator of the chatroom
 */
function is_chat_moderator($member_id, $room_id, $room_owner)
{
    return has_actual_page_access(get_member(), 'cms_chat', null, array('chat', strval($room_id)), array('edit_lowrange_content', ($room_owner == $member_id) ? 'moderate_my_private_rooms' : null));
}

/**
 * Handle an AJAX message posting request.
 *
 * @param  AUTO_LINK $room_id Room ID
 * @param  string $message The message
 * @param  string $font Font name
 * @param  string $colour Font colour
 * @param  BINARY $first_message Whether this is the first message sent out to this room, since some change
 *
 * @ignore
 */
function _chat_post_message_ajax($room_id, $message, $font, $colour, $first_message)
{
    $room_check = $GLOBALS['SITE_DB']->query_select('chat_rooms', array('*'), array('id' => $room_id), '', 1);

    if (!array_key_exists(0, $room_check)) {
        // This room doesn't exist
        warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'chat'));
    }
    $room_row = $room_check[0];
    if (!check_chatroom_access($room_row, true)) {
        require_lang('chat');
        $the_message = do_lang('BANNED_FROM_CHAT');
        $_message = array('system_message' => 1, 'ip_address' => get_ip_address(), 'room_id' => $room_id, 'date_and_time' => time(), 'member_id' => get_member(), 'text_colour' => get_option('chat_default_post_colour'), 'font_name' => get_option('chat_default_post_font'));
        $template = do_template('CHAT_MESSAGE', array(
            '_GUID' => 'f0eb6b037a7cb4b70a114e7e96bde36d',
            'SYSTEM_MESSAGE' => strval($_message['system_message']),
            'STAFF' => false,
            'OLD_MESSAGES' => false,
            'AVATAR_URL' => '',
            'STAFF_ACTIONS' => '',
            'MEMBER' => do_lang('SYSTEM'),
            'MEMBER_ID' => strval($_message['member_id']),
            'MESSAGE' => $the_message,
            'TIME' => get_timezoned_date($_message['date_and_time']),
            'RAW_TIME' => strval($_message['date_and_time']),
            'FONT_COLOUR' => $_message['text_colour'],
            'FONT_FACE' => $_message['font_name'],
        ));
        $messages_output = '<div sender_id="' . strval($_message['member_id']) . '" room_id="' . strval($_message['room_id']) . '" id="123456789" timestamp="' . strval($_message['date_and_time']) . '">' . $template->evaluate() . '</div>';

        prepare_for_known_ajax_response();

        header('Content-Type: application/xml');
        $output = '<' . '?xml version="1.0" encoding="' . get_charset() . '" ?' . '>
<!DOCTYPE xc:content [
<!ENTITY euro "&#8364;">
<!ENTITY ldquo "&#8220;">
<!ENTITY rdquo "&#8221;">
<!ENTITY lsquo "&#8216;">
<!ENTITY rsquo "&#8217;">
<!ENTITY dagger "&#8224;">
<!ENTITY Dagger "&#8225;">
<!ENTITY permil "&#8240;">
<!ENTITY Scaron "&#352;">
<!ENTITY scaron "&#353;">
<!ENTITY Yuml "&#376;">
<!ENTITY ndash "&#8211;">
<!ENTITY mdash "&#8212;">
<!ENTITY hellip "&#8230;">
<!ENTITY copy "&#169;">
<!ENTITY nbsp " ">
<!ENTITY fnof "&#402;">
<!ENTITY reg "&#174;">
<!ENTITY trade "&#8482;">
<!ENTITY raquo "&#187;">
<!ENTITY frac14 "&#188;">
<!ENTITY frac12 "&#189;">
<!ENTITY frac34 "&#190;">
]>

<response>
    <result>
        ' . $messages_output . '
    </result>
</response>';
        echo $output;

        return;
    }

    if ($message == '') {
        $return = '0';
    } else {
        if (chat_post_message($room_id, $message, $font, $colour)) {
            $return = '1';
        } else {
            $return = '0';
        }
    }

    if (($room_row['is_im'] == 1)/* && ($first_message == 1)*/) { // first_message doesn't add much efficiency and a pain to correlate on the client-side
        $invited_already = null;
        $active_members = null;
        $allow_list = explode(',', $room_row['allow_list']);
        foreach ($allow_list as $_allow) {
            $_allow = trim($_allow);
            $allow = intval($_allow);
            if (($allow != $room_row['room_owner']) && ($allow != get_member())) {
                if (is_null($invited_already)) {
                    $invited_already = collapse_1d_complexity('e_member_id', $GLOBALS['SITE_DB']->query_select('chat_events', array('e_member_id'), array('e_room_id' => $room_id, 'e_type_code' => 'INVITED_TO_IM')));
                }
                if (!in_array($allow, $invited_already)) {
                    // Send out invitation if they're not active
                    if (is_null($active_members)) {
                        $active_members = get_chatters_in_room($room_id);
                    }
                    if (!array_key_exists($allow, $active_members)) {
                        $event_id = $GLOBALS['SITE_DB']->query_insert('chat_events', array(
                            'e_type_code' => 'INVITED_TO_IM',
                            'e_member_id' => $allow,
                            'e_room_id' => $room_id,
                            'e_date_and_time' => time()
                        ), true);
                        require_code('files');
                        cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/modules/chat/chat_last_event.bin', strval($event_id), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);

                        require_lang('chat');

                        require_code('notifications');

                        $zone = get_module_zone('chat');
                        $_lobby_url = build_url(array('page' => 'chat'), $zone, null, false, false, true);
                        $lobby_url = $_lobby_url->evaluate();
                        $subject = do_lang('IM_INVITED_SUBJECT', null, null, null, get_lang($allow));
                        $username = $GLOBALS['FORUM_DRIVER']->get_username(get_member());
                        $username2 = $GLOBALS['FORUM_DRIVER']->get_username($allow);
                        $message = do_notification_lang('IM_INVITED_MESSAGE', get_timezoned_date(time(), true), $username, array($lobby_url, $username2, $message, strval($allow)), get_lang($allow));

                        dispatch_notification('im_invited', null, $subject, $message, array($allow), $room_row['room_owner'], 1);
                    }
                }
            }
        }
    }

    /*if ($return == '0') Flood control creates error, but we'd rather see it shown inline
    {
        prepare_for_known_ajax_response();

        header('Content-Type: application/xml');
        $output = '<' . '?xml version="1.0" encoding="' . get_charset() . '" ?' . '>
<!DOCTYPE xc:content [
<!ENTITY euro "&#8364;">
<!ENTITY ldquo "&#8220;">
<!ENTITY rdquo "&#8221;">
<!ENTITY lsquo "&#8216;">
<!ENTITY rsquo "&#8217;">
<!ENTITY dagger "&#8224;">
<!ENTITY Dagger "&#8225;">
<!ENTITY permil "&#8240;">
<!ENTITY Scaron "&#352;">
<!ENTITY scaron "&#353;">
<!ENTITY Yuml "&#376;">
<!ENTITY ndash "&#8211;">
<!ENTITY mdash "&#8212;">
<!ENTITY hellip "&#8230;">
<!ENTITY copy "&#169;">
<!ENTITY nbsp " ">
<!ENTITY fnof "&#402;">
<!ENTITY reg "&#174;">
<!ENTITY trade "&#8482;">
<!ENTITY raquo "&#187;">
<!ENTITY frac14 "&#188;">
<!ENTITY frac12 "&#189;">
<!ENTITY frac34 "&#190;">
]>

<error />';
            echo $output;

        return;
    }*/

    // Send response of new messages, so we get instant result
    _chat_messages_script_ajax(($room_row['is_im'] == 1) ? -1 : $room_id, false, either_param_integer('message_id', 0), either_param_integer('event_id', 0));
}

/**
 * Enter a message into the database for the specified room, and with the specified parameters. The message is filtered for banned words, and is compressed into a Tempcode storage format.
 *
 * @param  AUTO_LINK $room_id The room ID for the message to be posted in
 * @param  LONG_TEXT $message The message body
 * @param  SHORT_TEXT $font_name The font name for the message
 * @param  SHORT_TEXT $text_colour The text colour for the message
 * @param  SHORT_INTEGER $wrap_pos The wrap position for the message
 * @return boolean Whether the message was successfully posted or not
 */
function chat_post_message($room_id, $message, $font_name, $text_colour, $wrap_pos = 60)
{
    // If it contains chatcode then we'll need to disable the word-filter
    if ((strpos($message, '[') !== false) && (strpos($message, ']') !== false)) {
        $wrap_pos = null;
    }

    // Have we been blocked by flood control?
    $is_im = $GLOBALS['SITE_DB']->query_select_value('chat_rooms', 'is_im', array('id' => $room_id));
    if ($is_im == 1) { // No flood control for IMs
        $time_last_message = null;
    } else {
        if (is_guest()) {
            $time_last_map = array('ip_address' => get_ip_address(), 'system_message' => 0);
        } else {
            $time_last_map = array('member_id' => get_member(), 'system_message' => 0);
        }
        $time_last_message = $GLOBALS['SITE_DB']->query_select_value_if_there('chat_messages', 'MAX(date_and_time)', $time_last_map);
        if (!is_null($time_last_message)) {
            $time_left = $time_last_message - time() + intval(get_option('chat_flood_timelimit'));
        }
    }
    if ((is_null($time_last_message)) || ($time_left <= 0)) {
        // Check colour and font
        if ($text_colour == '') {
            $text_colour = get_option('chat_default_post_colour');
        }
        if ($font_name == '') {
            $font_name = get_option('chat_default_post_font');
        }

        // Store as assembled Tempcode
        $map = array(
            'system_message' => 0,
            'ip_address' => get_ip_address(),
            'room_id' => $room_id,
            'member_id' => get_member(),
            'date_and_time' => time(),
            'text_colour' => $text_colour,
            'font_name' => $font_name,
        );
        $map += insert_lang_comcode('the_message', wordfilter_text($message), 4, null, false, null, $wrap_pos);
        $message_id = $GLOBALS['SITE_DB']->query_insert('chat_messages', $map, true);

        require_code('files');
        cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/modules/chat/chat_last_msg.bin', strval($message_id), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);

        // Bot support
        $hooks = find_all_hooks('modules', 'chat_bots');
        foreach (array_keys($hooks) as $hook) {
            require_code('hooks/modules/chat_bots/' . filter_naughty_harsh($hook));
            $ob = object_factory('Hook_chat_bot_' . $hook, true);
            if ((!is_null($ob)) && (method_exists($ob, 'reply_to_any_communication'))) {
                $response = $ob->reply_to_any_communication($room_id, $message);
                if (!is_null($response)) {
                    // Store bots message
                    $map = array(
                        'system_message' => 0,
                        'ip_address' => $hook,
                        'room_id' => $room_id,
                        'member_id' => $GLOBALS['FORUM_DRIVER']->get_guest_id(),
                        'date_and_time' => time(),
                        'text_colour' => get_option('chat_default_post_colour'),
                        'font_name' => get_option('chat_default_post_font'),
                    );
                    $map += insert_lang_comcode('the_message', wordfilter_text($response), 4, null, false, null, $wrap_pos);
                    $bot_message_id = $GLOBALS['SITE_DB']->query_insert('chat_messages', $map, true);

                    require_code('files');
                    cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/modules/chat/chat_last_msg.bin', strval($bot_message_id), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
                }
            }
        }

        // Mirror to private topic, if an IM
        if (($is_im == 1) && (get_forum_type() == 'cns') && (addon_installed('cns_forum'))) {
            $members = array_map('intval', explode(',', $GLOBALS['SITE_DB']->query_select_value('chat_rooms', 'allow_list', array('id' => $room_id))));
            if (count($members) >= 2) {
                require_lang('chat');
                $table = 'f_topics t';
                for ($i = 2; $i < count($members); $i++) {
                    $table .= ' JOIN ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_special_pt_access a' . strval($i) . ' ON a' . strval($i) . '.s_topic_id=t.id AND a' . strval($i) . '.s_member_id=' . strval($members[$i]);
                }
                $topic_id = $GLOBALS['FORUM_DB']->query_select_value_if_there($table, 'id', array('t_cache_first_title' => do_lang('INSTANT_MESSAGING_CONVO'), 't_pt_from' => $members[0], 't_pt_to' => $members[1]));
                if (is_null($topic_id)) {
                    require_code('cns_topics_action');
                    $topic_id = cns_make_topic(null, '', '', 1, 0, 0, 0, 0, $members[0], $members[1], false);
                    for ($i = 2; $i < count($members); $i++) {
                        $GLOBALS['FORUM_DB']->query_insert('f_special_pt_access', array('s_member_id' => $members[$i], 's_topic_id' => $topic_id));
                    }
                    $is_starter = true;
                } else {
                    $is_starter = false;
                }
                require_code('cns_posts_action');
                cns_make_post($topic_id, $is_starter ? do_lang('INSTANT_MESSAGING_CONVO') : '', $message, 0, $is_starter, 1, 0, null, null, null, get_member(), null, null, null, false, true, null, false, '', 0, null, false, true);
                require_code('cns_topics');
                for ($i = 0; $i < count($members); $i++) {
                    cns_ping_topic_read($topic_id, $members[$i]);
                }
            }
        }

        // Update points
        if (addon_installed('points')) {
            require_code('points');
            $_count = point_info(get_member());
            $count = array_key_exists('points_gained_chat', $_count) ? $_count['points_gained_chat'] : 0;
            $GLOBALS['FORUM_DRIVER']->set_custom_field(get_member(), 'points_gained_chat', $count + 1);
        }

        decache('side_shoutbox');

        return true;
    }

    // Flood prevention has blocked us. Send a PM about it
    require_lang('chat');
    $map = array(
        'system_message' => 1,
        'ip_address' => get_ip_address(),
        'room_id' => $room_id,
        'member_id' => get_member(),
        'date_and_time' => time(),
        'text_colour' => get_option('chat_default_post_colour'),
        'font_name' => get_option('chat_default_post_font'),
    );
    $map += insert_lang_comcode('the_message', '[private="' . $GLOBALS['FORUM_DRIVER']->get_username(get_member()) . '"]' . do_lang('FLOOD_CONTROL_BLOCKED', integer_format($time_left)) . '[/private]', 4, null, false, null/*, $wrap_pos*/); // Can't wrap system messages, the Comcode parser won't know 'private' is a real tag so will wrap inside its definition
    $message_id = $GLOBALS['SITE_DB']->query_insert('chat_messages', $map, true);
    require_code('files');
    cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/modules/chat/chat_last_msg.bin', strval($message_id), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
    return false;
}

/**
 * Get the people who have posted a message in the specified room within the last x minutes (defaults to five). Note that this function performs no pruning- the chat lobby will do that. It does do an activity time-range select though.
 *
 * @param   ?AUTO_LINK     $room_id The room ID (null: lobby)
 * @return  array          A map of members in the room. User ID=>Username
 */
function get_chatters_in_room($room_id)
{
    if (is_null($room_id)) {
        $extra2 = 'room_id IS NULL';
    } else {
        $extra2 = 'room_id=' . strval($room_id);
    }
    $active = $GLOBALS['SITE_DB']->query('SELECT DISTINCT a.member_id FROM ' . get_table_prefix() . 'chat_active a LEFT JOIN ' . get_table_prefix() . 'sessions s ON s.member_id=a.member_id WHERE (session_invisible=0 OR session_invisible IS NULL) AND date_and_time>=' . strval(time() - 60 * 10) . ' AND ' . $extra2);

    $found_users = array();
    foreach ($active as $values) {
        $username = $GLOBALS['FORUM_DRIVER']->get_username($values['member_id']);
        if (!is_null($username)) {
            $found_users[$values['member_id']] = $username;
        }
    }
    return $found_users;
}

/**
 * Get some template code showing the number of chatters in a room.
 *
 * @param   array $users A mapping (user=>username) of the chatters in the room
 * @return  Tempcode       The Tempcode
 */
function get_chatters_in_room_tpl($users)
{
    require_code('users2');
    $usernames = new Tempcode();
    $some_users = false;
    foreach ($users as $member_id => $username) {
        if (!member_blocked(get_member(), $member_id)) {
            $some_users = true;
            if (!$usernames->is_empty()) {
                $usernames->attach(escape_html(', '));
            }
            if (!is_guest($member_id)) {
                if (get_forum_type() == 'cns') {
                    require_code('cns_general');
                    require_code('cns_members');

                    $colour = get_group_colour(cns_get_member_primary_group($member_id));
                    $usernames->attach(do_template('CNS_USER_MEMBER', array('_GUID' => 'ef5f13f50d242a49474337b8e979c419', 'FIRST' => $usernames->is_empty(), 'PROFILE_URL' => $GLOBALS['FORUM_DRIVER']->member_profile_url($member_id, true, true), 'MEMBER_ID' => strval($member_id), 'USERNAME' => $username, 'COLOUR' => $colour)));
                } else {
                    $usernames->attach($GLOBALS['FORUM_DRIVER']->member_profile_hyperlink($member_id, true, $username, false));
                }
            } else {
                $usernames->attach(escape_html(do_lang('GUEST')));
            }
        }
    }
    if (!$some_users) {
        $usernames = do_lang_tempcode('NONE_EM');
    }
    return $usernames;
}

/**
 * Get the textual name of the specified chatroom, from its room ID.
 *
 * @param  AUTO_LINK $room_id The room ID
 * @param  boolean $allow_null Allow the chatroom to not be found (i.e. don't die if it can't be)
 * @return ?SHORT_TEXT The room name (null: not found)
 */
function get_chatroom_name($room_id, $allow_null = false)
{
    if ($allow_null) {
        return $GLOBALS['SITE_DB']->query_select_value_if_there('chat_rooms', 'room_name', array('id' => $room_id));
    }
    return $GLOBALS['SITE_DB']->query_select_value('chat_rooms', 'room_name', array('id' => $room_id));
}

/**
 * Get the ID of the specified chatroom, from its room name.
 *
 * @param  SHORT_TEXT $room_name The name of the chatroom
 * @param  boolean $must_not_be_im Make sure the room is not an IM room. If it is an IM room, pretend it does not exist.
 * @return ?AUTO_LINK The ID of the chatroom (null: no such chatroom)
 */
function get_chatroom_id($room_name, $must_not_be_im = false)
{
    $map = array('room_name' => $room_name);
    if ($must_not_be_im) {
        $map['is_im'] = 0;
    }
    return $GLOBALS['SITE_DB']->query_select_value_if_there('chat_rooms', 'id', $map);
}

/**
 * Get an array of all the chatrooms.
 *
 * @return  array       An array of all the chatrooms
 */
function chat_get_all_rooms()
{
    return $GLOBALS['SITE_DB']->query_select('chat_rooms', array('*'), array('is_im' => 0), 'ORDER BY room_name DESC');
}

/**
 * Get a multidimensional array of the content of the specified chatroom.
 * It automatically parses for Comcode, chatcode, banned words, emoticons, and uses complex logic to decide whether or not to show each message; based upon who the member is, the message content, and other such inputs.
 *
 * @param  ?AUTO_LINK $room_id The room ID (null: for all IM)
 * @param  array $_rooms Rooms database rows that we'll need
 * @param  ?integer $max_messages The maximum number of messages to be returned (null: no maximum)
 * @param  boolean $dereference Whether to dereference the returned messages (i.e. lookup the language strings)
 * @param  boolean $downloading Whether to return the messages in a downloadeable format (using the templates for log downloading)
 * @param  ?integer $start The datetime stamp to start gathering messages from (null: all)
 * @param  ?integer $finish The datetime stamp to stop gathering messages at (null: current time)
 * @param  ?integer $uptoid The lowest message ID to return (null: no special lowest number)
 * @param  ?ID_TEXT $zone The zone the chat module is in (null: find it)
 * @param  ?AUTO_LINK $entering_room The language string ID for the "entering room" message (null: not entering the room)
 * @param  boolean $return_my_messages Return the current user's messages?
 * @param  boolean $return_system_messages Return system messages
 * @return array An array of all the messages collected according to the search criteria
 */
function chat_get_room_content($room_id, $_rooms, $max_messages = null, $dereference = false, $downloading = false, $start = null, $finish = null, $uptoid = null, $zone = null, $entering_room = null, $return_my_messages = true, $return_system_messages = true)
{
    if (is_null($zone)) {
        $zone = get_module_zone('chat');
    }

    $rooms = list_to_map('id', $_rooms);

    if (!is_null($entering_room)) {
        $their_username = $GLOBALS['FORUM_DRIVER']->get_username(get_member());

        $_entering_room = get_translated_text($entering_room);
        if ($_entering_room != '') {
            require_code('comcode');
            $map = array(
                'system_message' => 0,
                'ip_address' => get_ip_address(),
                'room_id' => $room_id,
                'member_id' => get_member(),
                'date_and_time' => time(),
                'text_colour' => get_option('chat_default_post_colour'),
                'font_name' => get_option('chat_default_post_font'),
            );
            $map += insert_lang_comcode('the_message', '[private="' . $their_username . '"]' . $_entering_room . '[/private]', 4);
            $message_id = $GLOBALS['SITE_DB']->query_insert('chat_messages', $map, true);
            require_code('files');
            cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/modules/chat/chat_last_msg.bin', strval($message_id), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
        }

        $enter_room_msg = do_lang('ENTERED_THE_CHATROOM', $their_username);
        if ($enter_room_msg != '') {
            require_code('comcode');
            $map = array(
                'system_message' => 1,
                'ip_address' => get_ip_address(),
                'room_id' => $room_id,
                'member_id' => get_member(),
                'date_and_time' => time(),
                'text_colour' => get_option('chat_default_post_colour'),
                'font_name' => get_option('chat_default_post_font'),
            );
            $map += insert_lang_comcode('the_message', $enter_room_msg, 4);
            $message_id = $GLOBALS['SITE_DB']->query_insert('chat_messages', $map, true);
            require_code('files');
            cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/modules/chat/chat_last_msg.bin', strval($message_id), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
        }

        $room_name = $GLOBALS['SITE_DB']->query_select_value('chat_rooms', 'room_name', array('id' => $room_id));
        $room_language = $GLOBALS['SITE_DB']->query_select_value('chat_rooms', 'room_language', array('id' => $room_id));

        require_code('notifications');
        $subject = do_lang('MEC_NOTIFICATION_MAIL_SUBJECT', get_site_name(), $their_username, $room_name, $room_language);
        $room_url = build_url(array('page' => 'chat', 'type' => 'room', 'id' => $room_id), $zone, null, false, false, true);
        $mail = do_notification_lang('MEC_NOTIFICATION_MAIL', comcode_escape(get_site_name()), comcode_escape($their_username), array(comcode_escape($room_name), $room_url->evaluate()), $room_language);
        dispatch_notification('member_entered_chatroom', strval($room_id), $subject, $mail);
    }

    // Load all the room content from the db for one room and replace emoticons and banned words etc.
    if (($downloading) || (!is_null($uptoid)) || (!$return_my_messages)) {
        $query = 'SELECT main.* FROM ' . get_table_prefix() . 'chat_messages main ';
        $where = '';
        if ($room_id !== null) {
            $where .= 'room_id=' . strval($room_id);
        }
        if ($downloading) {
            if ($where != '') {
                $where .= ' AND ';
            }
            $where .= 'date_and_time>=' . strval($start) . ' AND date_and_time<=' . strval($finish);
        }
        if ((!is_null($uptoid)) && ($uptoid != -1)) {
            if (get_db_type() == 'xml') {
                $timestamp = $GLOBALS['SITE_DB']->query_select_value_if_there('chat_messages', 'date_and_time', array('id' => $uptoid));
                if (is_null($timestamp)) {
                    $timestamp = 0;
                }
                if ($where != '') {
                    $where .= ' AND ';
                }
                $where .= 'main.date_and_time>' . strval($timestamp);
            } else {
                if ($where != '') {
                    $where .= ' AND ';
                }
                $where .= 'main.id>' . strval($uptoid);
            }
        }
        if (!$return_my_messages) {
            if ($where != '') {
                $where .= ' AND ';
            }
            $where .= 'member_id<>' . strval(get_member());
        }
        if (!$return_system_messages) {
            if ($where != '') {
                $where .= ' AND ';
            }
            $where .= 'system_message=0';
        }
        $query .= (($where == '') ? '' : ' WHERE ' . $where) . ' ORDER BY date_and_time DESC,id DESC';
        global $TABLE_LANG_FIELDS_CACHE;
        $rows = $GLOBALS['SITE_DB']->query($query, $max_messages, null, false, false, array_key_exists('chat_messages', $TABLE_LANG_FIELDS_CACHE) ? $TABLE_LANG_FIELDS_CACHE['chat_messages'] : array());
    } else {
        $where_array = is_null($room_id) ? array() : array('room_id' => $room_id);
        if (!$return_system_messages) {
            $where_array['system_message'] = 0;
        }
        $rows = $GLOBALS['SITE_DB']->query_select('chat_messages', array('*'), $where_array, 'ORDER BY date_and_time DESC,id DESC', $max_messages);
    }
    $rows = array_reverse($rows);

    $deleted_message_list = array();
    foreach (array_keys($rows) as $i) {
        // Compose what we ultimately need to know about our message
        $rows[$i]['username'] = $GLOBALS['FORUM_DRIVER']->get_username($rows[$i]['member_id']);
        if (is_null($rows[$i]['username'])) {
            $rows[$i]['username'] = do_lang('UNKNOWN');
        }
        $rows[$i]['date_and_time_nice'] = get_timezoned_date($rows[$i]['date_and_time']);
        $just_message_row = db_map_restrict($rows[$i], array('id', 'the_message'));
        $message = get_translated_tempcode('chat_messages', $just_message_row, 'the_message');

        // Extra access check
        if ($room_id === null) {
            $pm_message_deleted =
                (!array_key_exists($rows[$i]['room_id'], $rooms)) ||
                (($rooms[$rows[$i]['room_id']]['is_im'] == 0) && (!check_chatroom_access($rooms[$rows[$i]['room_id']], true))) ||
                (($rooms[$rows[$i]['room_id']]['is_im'] == 1) && (!check_chatroom_access($rooms[$rows[$i]['room_id']], true, null, true)))
                ;
        } else {
            $pm_message_deleted = false;
        }

        // Right... let's scan for chat tags in our Tempcode, such as [private="Philip"]text[/private]
        $chatcode_tags = array('private', 'invite', 'newroom');
        $text = $message->evaluate();
        if (!$pm_message_deleted) {
            foreach ($chatcode_tags as $tag) {
                $pm_matches = array();
                if (preg_match_all('#\[' . $tag . '=&quot;([^&]*)&quot;\]([^\[]*)\[/' . $tag . '\]#', $text, $pm_matches) != 0) { // The quotes will have been escaped to put into HTML; thus &quot;
                    foreach (array_keys($pm_matches[0]) as $key) {
                        $returns = _deal_with_chatcode_tags($text, $tag, $pm_matches[1][$key], $pm_matches[2][$key], $rows[$i]['username'], $max_messages, $zone, $rows[$i]['room_id'], $rows[$i]['system_message']);

                        $pm_message_deleted = ($returns['pm_message_deleted']);
                        if ($pm_message_deleted) {
                            break;
                        }
                        $text = $returns['text'];
                    }
                    if ($pm_message_deleted) {
                        break;
                    }
                }
            }
        }
        if (!$pm_message_deleted) {
            $message = make_string_tempcode($text);
            $rows[$i]['the_message'] = $dereference ? $message->evaluate() : $message;
        } else {
            $deleted_message_list[] = $i;
        }
    }
    $rows = _remove_empty_messages($rows, $deleted_message_list);

    return $rows;
}

/**
 * Parse chat code tags (called multiple times, for each tag).
 *
 * @param  string $text The text we are using
 * @param  string $tag The tag name we are parsing
 * @param  string $pm_user 1st param
 * @param  string $pm_message 2nd param
 * @param  SHORT_TEXT $username The username of who made this chatcode
 * @param  ?integer $max_messages The maximum number of messages to be returned (null: no maximum)
 * @param  ID_TEXT $zone The zone that our chat module is in
 * @param  AUTO_LINK $room_id The room ID the message is in
 * @param  BINARY $system_message Whether this is within a system message
 * @return array A pair: whether the message was deleted, and the new text of the message
 *
 * @ignore
 */
function _deal_with_chatcode_tags($text, $tag, $pm_user, $pm_message, $username, $max_messages, $zone, $room_id, $system_message)
{
    switch ($tag) {
        case 'newroom':
            return _deal_with_chatcode_newroom($pm_user, $pm_message, $username, $text, $max_messages);
        case 'invite':
            return _deal_with_chatcode_invite($pm_user, $pm_message, $username, $text, $zone);
        case 'private':
            return _deal_with_chatcode_private($pm_user, $pm_message, $username, $text, $room_id, $system_message);
    }
    return array(null, null);
}

/**
 * Parse private message chat code tag.
 *
 * @param  string $pm_user The member a private message should be sent to
 * @param  string $pm_message The private message
 * @param  SHORT_TEXT $username The username of who made this chatcode
 * @param  string $text The text we are using
 * @param  AUTO_LINK $room_id The room ID the message is in
 * @param  BINARY $system_message Whether this is within a system message
 * @return array A pair: whether the message was deleted, and the new text of the message
 *
 * @ignore
 */
function _deal_with_chatcode_private($pm_user, $pm_message, $username, $text, $room_id, $system_message)
{
    $pm_message_deleted = false;

    $response_text = '';

    // This deals with the [private="user"]message[/private] tag.

    // Are we the sender, or the receiver?
    $from = $GLOBALS['FORUM_DRIVER']->get_member_from_username($pm_user);
    if (((!is_guest()) && ($from == get_member())) || (($username != 'bot') && ($username == $GLOBALS['FORUM_DRIVER']->get_username(get_member())))) {
        // Handle bot messages
        if ($pm_user == 'bot') {
            $hooks = find_all_hooks('modules', 'chat_bots');
            foreach (array_keys($hooks) as $hook) {
                require_code('hooks/modules/chat_bots/' . filter_naughty_harsh($hook));
                $ob = object_factory('Hook_chat_bot_' . $hook, true);
                if (is_null($ob)) {
                    continue;
                }
                if (method_exists($ob, 'handle_commands')) {
                    $response = $ob->handle_commands($room_id, $pm_message);
                    if (!is_null($response)) {
                        if ($response_text != '') {
                            $response_text .= "\n\n";
                        }
                        $_response = comcode_to_tempcode($response, $from);
                        $response_text .= $_response->evaluate();
                    }
                }
            }
            $text = preg_replace('#\[private=&quot;([^&]*)&quot;\]([^\[]*)\[/private\]#', $response_text, $text, 1);
        } else {
            // Display the message
            $private_code = do_template('CHAT_PRIVATE', array('_GUID' => '96ef50f1442b319b034fe6f68ca50c12', 'SYSTEM_MESSAGE' => strval($system_message), 'MESSAGE' => $pm_message, 'USER' => do_lang_tempcode('CHAT_PRIVATE_TITLE', ($username == $pm_user) ? do_lang_tempcode('USER_SYSTEM') : make_string_tempcode(escape_html($username)))));
            $text = preg_replace('#\[private=&quot;([^&]*)&quot;\]([^\[]*)\[/private\]#', $private_code->evaluate(), $text, 1);
        }
    } else { // No we are not...
        // Replace the message with nothingness, as we're not the sender or receiver
        $text = preg_replace('#\[private=&quot;([^&]*)&quot;\]([^\[]*)\[/private\]#', '', $text, 1);
        if ((is_null($text)) || ($text == '')) {
            $pm_message_deleted = true;
        }
    }

    return array('pm_message_deleted' => $pm_message_deleted, 'text' => $text);
}

/**
 * Parse invitation chat code tag.
 *
 * @param  string $pm_user Comma-separated list of members to invite
 * @param  string $pm_message The room name
 * @param  SHORT_TEXT $username The username of who made this chatcode
 * @param  string $text The text we are using
 * @param  ID_TEXT $zone The zone the chat module is in
 * @return array A pair: whether the message was deleted, and the new text of the message
 *
 * @ignore
 */
function _deal_with_chatcode_invite($pm_user, $pm_message, $username, $text, $zone)
{
    $pm_message_deleted = false;

    // This deals with the [invite="user"]room[/invite] tag
    $quoted_users = explode(',', $pm_user);
    foreach ($quoted_users as $quoted_user) {
        $real_member = (($GLOBALS['FORUM_DRIVER']->get_member_from_username($quoted_user) == get_member()) && (!is_guest($GLOBALS['FORUM_DRIVER']->get_member_from_username($quoted_user))) && (!is_null($GLOBALS['FORUM_DRIVER']->get_member_from_username($quoted_user)))) || ($username == $GLOBALS['FORUM_DRIVER']->get_username(get_member()));
        if ($real_member) {
            $room_id = get_chatroom_id(html_entity_decode($pm_message, ENT_QUOTES, get_charset()), true);
            if (!is_null($room_id)) {
                // Display the invite
                $invite_code = do_template('CHAT_INVITE', array(
                    '_GUID' => '493ac2dcabc763fe03e7eee072dd9629',
                    'USERNAME' => $username,
                    'CHATROOM' => html_entity_decode($pm_message, ENT_QUOTES, get_charset()),
                    'LINK' => hyperlink(build_url(array('page' => 'chat', 'type' => 'room', 'room_id' => strval($room_id)), $zone), do_lang_tempcode('CHAT_INVITE_TEXT_REPLY'), false, false),
                ));
                $text = preg_replace('#\[invite=&quot;([^&]*)&quot;\]([^\[]*)\[/invite\]#', $invite_code->evaluate(), $text, 1);
            }
        }
        if ((!$real_member) || (is_null($room_id))) {
            // Replace the invite with nothingness
            $text = preg_replace('#\[invite=&quot;([^&]*)&quot;\]([^\[]*)\[/invite\]#', '', $text, 1);
            if ((is_null($text)) || ($text == '')) {
                $pm_message_deleted = true;
            }
        }
    }

    return array('pm_message_deleted' => $pm_message_deleted, 'text' => $text);
}

/**
 * Parse room creation chat code tag.
 *
 * @param  string $pm_user The room name
 * @param  string $pm_message Comma-separated list of members to allow in
 * @param  SHORT_TEXT $username The username of who made this chatcode
 * @param  string $text The text we are using
 * @param  ?integer $max_messages The maximum number of messages to be returned (null: no maximum)
 * @return array A pair: whether the message was deleted, and the new text of the message
 *
 * @ignore
 */
function _deal_with_chatcode_newroom($pm_user, $pm_message, $username, $text, $max_messages)
{
    $pm_message_deleted = false;
    if (!has_privilege(get_member(), 'create_private_room')) {
        return array('pm_message_deleted' => $pm_message_deleted, 'text' => $text);
    }

    // This deals with the [newroom="roomname"]allowlist[/newroom] tag
    // We need to send invitations to all the people on the allow list
    // Create the room if it hasn't already been created
    $_row = $GLOBALS['SITE_DB']->query_select('chat_rooms', array('*'), array('room_name' => $pm_user), '', $max_messages);
    if (!array_key_exists(0, $_row)) {
        $new_room_id = $GLOBALS['SITE_DB']->query_insert('chat_rooms', array('is_im' => 0, 'room_name' => $pm_user, 'room_owner' => $GLOBALS['FORUM_DRIVER']->get_member_from_username($username), 'allow_list' => parse_allow_list_input($pm_message), 'disallow_list' => '', 'allow_list_groups' => '', 'disallow_list_groups' => '', 'room_language' => user_lang()) + insert_lang('c_welcome', '', 3), true);
        $rooms = chat_get_all_rooms();
        // For each person in the allow list, insert a private message into every room (except the new one) asking them to join the new room
        $_pm_message = explode(',', $pm_message);
        foreach ($_pm_message as $person) {
            if (($person != $GLOBALS['FORUM_DRIVER']->get_username(get_member())) && ($person != do_lang('GUEST'))) {
                foreach ($rooms as $room) {
                    if ($room['id'] != $new_room_id) {
                        $map = array(
                            'system_message' => 1,
                            'ip_address' => get_ip_address(),
                            'room_id' => $room['id'],
                            'member_id' => get_member(),
                            'date_and_time' => time(),
                            'text_colour' => get_option('chat_default_post_colour'),
                            'font_name' => get_option('chat_default_post_font'),
                        );
                        $map += insert_lang_comcode('the_message', '[invite="' . $person . '"]' . get_chatroom_name($new_room_id) . '[/invite]', 4);
                        $message_id = $GLOBALS['SITE_DB']->query_insert('chat_messages', $map, true);
                        require_code('files');
                        cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/modules/chat/chat_last_msg.bin', strval($message_id), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
                    }
                }
            }
        }
    }
    $text = preg_replace('#\[newroom=&quot;([^&]*)&quot;\]([^\[]*)\[/newroom\]#', '', $text, 1);
    if ((is_null($text)) || ($text == '')) {
        $pm_message_deleted = true;
    }

    return array('pm_message_deleted' => $pm_message_deleted, 'text' => $text);
}

/**
 * Remove any messages from the list of messages that aren't mentioned in the list of message IDs.
 *
 * @param  array $messages Original list of messages
 * @param  array $message_ids List of message IDs to keep
 * @return array A new list of messages
 *
 * @ignore
 */
function _remove_empty_messages($messages, $message_ids)
{
    $new = array();
    foreach ($messages as $i => $message) {
        if (!in_array($i, $message_ids)) {
            $new[] = $message;
        }
    }
    return $new;
}

/**
 * Takes a comma-separated list of usernames, split it up, convert all the usernames to IDs, and put it all back together again.
 *
 * @param   string $_allow A comma-separated list of usernames
 * @return  string         A comma-separated list of member IDs
 */
function parse_allow_list_input($_allow)
{
    if ($_allow == '') {
        return '';
    }
    $allow = explode(',', $_allow);
    $allow2 = '';
    $failed = false;
    foreach ($allow as $person) {
        if (($allow2 != '') && (!$failed)) {
            $allow2 .= ',';
        }
        $temp = $GLOBALS['FORUM_DRIVER']->get_member_from_username(trim($person));
        if (is_null($temp)) {
            $failed = true;
            continue;
        }
        $allow2 .= strval($temp);
        $failed = false;
    }
    return $allow2;
}

/**
 * Check whether a member has access to the chatroom.
 *
 * @param  mixed $room The row of the chatroom to check for access OR its ID (AUTO_LINK)
 * @param  boolean $ret Whether to return false if there is no access (as opposed to bombing out)
 * @param  ?MEMBER $member_id The member to check as (null: current member)
 * @param  boolean $must_be_explicit Whether to also ensure for $member_id having explicit access
 * @return boolean Whether the current member has access to the chatroom
 */
function check_chatroom_access($room, $ret = false, $member_id = null, $must_be_explicit = false)
{
    if (!is_array($room)) {
        $_room = $GLOBALS['SITE_DB']->query_select('chat_rooms', array('id', 'is_im', 'allow_list_groups', 'disallow_list_groups', 'allow_list', 'disallow_list', 'room_owner'), array('id' => $room), '', 1);
        if (!array_key_exists(0, $_room)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'chat'));
        }
        $room = $_room[0];
    }

    if (!$must_be_explicit) {
        if ($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member())) {
            return true;
        }
    } else {
        if ($room['allow_list'] == '') {
            return false;
        }
    }

    // Check disallow list
    $disallow = explode(',', $room['disallow_list']);
    $disallow2 = explode(',', $room['disallow_list_groups']);
    $_disallow = false;

    if (is_null($member_id)) {
        $member_id = get_member();
    }
    $groups = $GLOBALS['FORUM_DRIVER']->get_members_groups($member_id, false, true);

    foreach ($groups as $g) {
        if (in_array(strval($g), $disallow2)) {
            $_disallow = true;
        }
    }

    if ($room['is_im'] == 0) {
        if (!has_category_access($member_id, 'chat', strval($room['id']))) {
            if ($ret) {
                return false;
            }
            require_lang('chat');
            access_denied('CHATROOM_UNAUTHORISED');
        }
    }

    if ((in_array(strval($member_id), $disallow)) || ($_disallow)) {
        if ($ret) {
            return false;
        }
        require_lang('chat');
        access_denied('CHATROOM_UNAUTHORISED');
    }

    // Check allow list
    if (($room['allow_list'] != '') || ($room['allow_list_groups'] != '')) {
        $allow = explode(',', $room['allow_list']);
        $allow2 = explode(',', $room['allow_list_groups']);
        if ($allow == array('')) {
            $allow = array();
        }
        if ($allow2 == array('')) {
            $allow2 = array();
        }

        if ((!in_array(strval($member_id), $allow) && ($room['room_owner'] != $member_id) && (count(array_intersect($allow2, $GLOBALS['FORUM_DRIVER']->get_members_groups($member_id))) == 0))) {
            if ($ret) {
                return false;
            }
            require_lang('chat');
            access_denied('CHATROOM_UNAUTHORISED');
        }
    }

    // Guest check
    if ((is_guest($member_id)) && ($room['allow_list'] != '')) {
        if ($ret) {
            return false;
        }
        require_lang('chat');
        access_denied('CHATROOM_UNAUTHORISED');
    }

    return true;
}

/**
 * Get a template that will set up the chat sound effects as for what this member needs.
 *
 * @return Tempcode Template to set up chat sound effects.
 */
function get_chat_sound_tpl()
{
    require_code('chat_sounds');
    $sound_effects = get_effect_settings(true, null, true);
    return do_template('CHAT_SOUND', array('_GUID' => '102c9574a2563143683970595df74011', 'SOUND_EFFECTS' => $sound_effects));
}

/**
 * Get SQL for finding an IM conversation between certain members.
 *
 * @param array $member_ids The member IDs
 * @return string SQL
 */
function sql_members_in_im_conversation($member_ids)
{
    $sql = 'is_im=1';
    foreach ($member_ids as $member_id) {
        $sql .= ' AND ' . db_function('CONCAT', array('\',\'', 'allow_list', '\',\'')) . ' LIKE \'' . db_encode_like('%,' . strval($member_id) . ',%') . '\'';
    }
    return $sql;
}
