<?php /*

 ocPortal
 Copyright (c) ocProducts, 2004-2012

 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.
 */
function init__chat()
{
	global $MEMBERS_BEFRIENDED;
	$MEMBERS_BEFRIENDED=NULL;

	global $EFFECT_SETTINGS_ROWS;
	$EFFECT_SETTINGS_ROWS=NULL;

	if (!defined('CHAT_ACTIVITY_PRUNE'))
	{
		define('CHAT_ACTIVITY_PRUNE',25);
		define('CHAT_BACKLOG_TIME',60*5); // 5 minutes of messages if you enter an existing room
		define('CHAT_EVENT_PRUNE',60*60*24);
	}
}

/**
 * High-level messages script handling
 */
function messages_script()
{
	get_page_title('',false); // Force session time to be updated

	// Closed site
	$site_closed=get_option('site_closed');
	if (($site_closed=='1') && (!has_specific_permission(get_member(),'access_closed_site')) && (!$GLOBALS['IS_ACTUALLY_ADMIN']))
	{
		header('Content-Type: text/plain');
		@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('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
		convert_data_encodings(true);
		$message=either_param('message');
		_chat_post_message_ajax(either_param_integer('room_id'),$message,post_param('font',''),post_param('colour',''),post_param_integer('first_message',0));
	}
	elseif ($action=='start_im')
	{
		require_lang('chat');
		$people=get_param('people');
		if ($people=='') exit();
		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);

		// 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'));
	}
	elseif ($action=='join_im')
	{
		$room_id=get_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'));
		if (!check_chatroom_access($room_check[0],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);
		$myfile=@fopen(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat','wb') OR intelligent_write_error(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat');
		fwrite($myfile,strval($event_id));
		fclose($myfile);
		sync_file(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat');

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

		$events_output='';
		$peoplea=explode(',',$room_check[0]['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=get_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'));
		if (!check_chatroom_access($room_check[0],true,NULL,true)) return; // Possibly the room was closed already
		$allow_list=str_replace(','.strval(get_member()).',',',',','.$room_check[0]['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);
		$myfile=@fopen(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat','wb') OR intelligent_write_error(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat');
		fwrite($myfile,strval($event_id));
		fclose($myfile);
		sync_file(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat');
		if ($allow_list=='')
		{
			require_code('chat2');
			delete_chatroom($room_id);
		} else
		{
			$peoplea=explode(',',$allow_list);
			$room_owner=$room_check[0]['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=get_param_integer('room_id');
		$people=get_param('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);
			$myfile=@fopen(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat','wb') OR intelligent_write_error(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat');
			fwrite($myfile,strval($event_id));
			fclose($myfile);
			sync_file(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat');
		}
		$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'));
		if (!check_chatroom_access($room_check[0],true,NULL,true)) return; // Possibly the room was closed already
		$allow_list=$room_check[0]['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'));
	}
}

/**
 * Find if a member is befriended by the current member.
 *
 * @param  MEMBER				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;
	if (is_null($MEMBERS_BEFRIENDED))
	{
		$MEMBERS_BEFRIENDED=collapse_1d_complexity('member_liked',$GLOBALS['SITE_DB']->query_select('chat_buddies',array('member_liked'),array('member_likes'=>get_member()),'',100));
	}
	if (count($MEMBERS_BEFRIENDED)==100) // Ah, too much to preload
	{
		return !is_null($GLOBALS['SITE_DB']->query_value_null_ok('chat_buddies','member_liked',array('member_liked'=>$member_id,'member_likes'=>get_member())));
	}
	return (in_array($member_id,$MEMBERS_BEFRIENDED));
}

/**
 * Outputs the shoutbox iframe.
 *
 * @param  boolean			Whether to get the output instead of outputting it directly
 * @param  ?AUTO_LINK		Chat room ID (NULL: read from environment)
 * @param  ?integer			The maximum number of messages to show (NULL: read from environment)
 * @return ?object			Output (NULL: outputted it already)
 */
function shoutbox_script($ret=false,$room_id=NULL,$num_messages=NULL)
{
	if (is_null($room_id)) $room_id=get_param_integer('room_id');
	if (is_null($num_messages)) $num_messages=get_param_integer('num_messages',5);
	$zone=get_param('zone',get_module_zone('chat'));

	require_lang('chat');
	require_code('chat');
	require_css('chat');
	require_css('side_blocks');

//	if (is_guest()) return; // No guests

	if (is_null($room_id))
	{
		$room_id=$GLOBALS['SITE_DB']->query_value_null_ok('chat_rooms','MIN(id)',array('is_im'=>0/*,'room_language'=>user_lang()*/));
		if (is_null($room_id)) warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
	}

	$room_check=$GLOBALS['SITE_DB']->query_select('chat_rooms',array('*'),array('id'=>$room_id),'',1);
	if (!array_key_exists(0,$room_check)) warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
	if (!check_chatroom_access($room_check[0])) warn_exit(do_lang_tempcode('ACCESS_DENIED__CHATROOM_UNAUTHORISED',escape_html($GLOBALS['FORUM_DRIVER']->get_username(get_member()))));

	// Did a message get sent last time?
	$shoutbox_message=post_param('shoutbox_message','');
	if ($shoutbox_message!='')
	{
		if (!chat_post_message($room_id,$shoutbox_message,get_option('chat_default_post_font'),get_option('chat_default_post_colour'),15))
		{
			// Error. But actually we'll get it from below
		}
	}

	$messages=chat_get_room_content($room_id,$room_check,$num_messages*3,false,false,NULL,NULL,-1,$zone,NULL,true,$shoutbox_message!='');
	$_tpl=array();
	foreach ($messages as $_message)
	{
		$evaluated=$_message['the_message']->evaluate();

		// We are only interested in private-message system messages and flood-control system messages, no other kinds of system message
		if (($_message['system_message']==1) && (strpos($evaluated,'[private')===false) && (preg_match('#'.str_replace('\{1\}','\d+',preg_quote(do_lang('FLOOD_CONTROL_BLOCKED'))).'#',$evaluated)==0)) continue;

		if ((strpos($evaluated,'[private')===false) || (($shoutbox_message!='') && (strpos($evaluated,'[private="'.$GLOBALS['FORUM_DRIVER']->get_username(get_member()).'"]')!==false)))
		{
			$member_id=$GLOBALS['FORUM_DRIVER']->get_member_from_username($_message['username']);
			$member=$GLOBALS['FORUM_DRIVER']->member_profile_hyperlink($member_id,true,$_message['username']);
			$_tpl[]=do_template('BLOCK_SIDE_SHOUTBOX_MESSAGE',array('_GUID'=>'a6f86aa48af7de7ec78423864c82c626','USER'=>$member,'MESSAGE'=>$_message['the_message'],'TIME_RAW'=>strval($_message['date_and_time']),'TIME'=>$_message['date_and_time_nice']));
		}
	}

	$tpl=new ocp_tempcode();
	while (count($_tpl)>$num_messages) array_shift($_tpl);
	foreach ($_tpl as $t)
	{
		$tpl->attach($t);
	}

	if (running_script('shoutbox'))
	{
		$keep=symbol_tempcode('KEEP');
		$_url=find_script('shoutbox').'?room_id='.strval($room_id).'&num_messages='.strval($num_messages).$keep->evaluate();
		if (get_param('utheme','')!='') $_url.='&utheme='.get_param('utheme');
		$url=make_string_tempcode($_url);
	} else
	{
		$url=get_self_url(false,(array_keys($_POST)!=array('shoutbox_message')),array('room_id'=>$room_id));
	}
	$tpl=do_template('BLOCK_SIDE_SHOUTBOX',array('_GUID'=>'080880eb9ebdb7fcdca1ebdae6b1b9aa','MESSAGES'=>$tpl,'URL'=>$url));

	if ($ret) return $tpl;

	$keep=symbol_tempcode('KEEP');
	$echo=do_template('STYLED_HTML_WRAP',array('_GUID'=>'aacac778b145bfe7b063317fbcae7fde','FRAME'=>true,'TARGET'=>'_top','TITLE'=>do_lang_tempcode('SHOUTBOX'),'CONTENT'=>$tpl));
	$echo->evaluate_echo();
	return NULL;
}

/**
 * Pass out chat log files.
 */
function chat_logs_script()
{
	// Closed site
	$site_closed=get_option('site_closed');
	if (($site_closed=='1') && (!has_specific_permission(get_member(),'access_closed_site')) && (!$GLOBALS['IS_ACTUALLY_ADMIN']))
	{
		header('Content-Type: text/plain');
		@exit(get_option('closed'));
	}

	// Check we are allowed here
	if (!has_actual_page_access(get_member(),'chat'))
		access_denied('PAGE_ACCESS');

	require_lang('chat');

	$room=get_param_integer('room',1);
	$start=get_param_integer('start',0);
	$finish=get_param_integer('finish',time());

	$start_date_seed=getdate($start);
	$finish_date_seed=getdate($finish);

	$room_check=$GLOBALS['SITE_DB']->query_select('chat_rooms',array('id','is_im','allow_list','allow_list_groups','disallow_list','disallow_list_groups','room_owner'),array('id'=>$room),'',1);
	if (!array_key_exists(0,$room_check))
	{
		warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
	}
	check_chatroom_access($room_check[0]);

	$start_date=($start==0)?'':strval($start_date_seed['year']).'-'.strval($start_date_seed['mon']).'-'.strval($start_date_seed['mday']).','.strval($start_date_seed['hours']).':'.strval($start_date_seed['minutes']);
	$finish_date=strval($finish_date_seed['year']).'-'.strval($finish_date_seed['mon']).'-'.strval($finish_date_seed['mday']).','.strval($finish_date_seed['hours']).':'.strval($finish_date_seed['minutes']);

	$messages=chat_get_room_content(intval($room),$room_check,NULL,false,true,intval($start),intval($finish),NULL,get_param('zone',get_module_zone('chat')));

	if ((is_null($messages)) || (count($messages)==0))
	{
		// There are no messages
		warn_exit(do_lang_tempcode('NO_ENTRIES'));
	}

	// Build the text file
	$message_contents=new ocp_tempcode();
	foreach ($messages as $_message)
	{
		$message_contents->attach(do_template('CHAT_MESSAGE',array('_GUID'=>'ff22f181850feaba2a062b7edf71e332','STAFF'=>false,'OLD_MESSAGES'=>true,'SYSTEM_MESSAGE'=>strval($_message['system_message']),'AVATAR_URL'=>'','STAFF_ACTIONS'=>'','USER'=>escape_html($_message['username']),'MESSAGE'=>$_message['the_message'],'TIME'=>$_message['date_and_time_nice'],'TIME_RAW'=>strval($_message['date_and_time']),'FONT_COLOUR'=>$_message['text_colour'],'FONT_FACE'=>$_message['font_name'])));
	}

	// Send header
	$room_name=get_chatroom_name($messages[0]['room_id']);
	$filename='chatlog-'.str_replace(' ','',$room_name).'-'.str_replace(':','-',$start_date).'-'.str_replace(':','-',$finish_date).'.html';
	header('Content-Type: application/octet-stream'.'; authoritative=true;');
	if ((strpos($room_name,chr(10))!==false) || (strpos($room_name,chr(13))!==false))
		log_hack_attack_and_exit('HEADER_SPLIT_HACK');
	if (strstr(ocp_srv('HTTP_USER_AGENT'),'MSIE')!==false)
		header('Content-Disposition: filename="'.$filename.'"');
	else
		header('Content-Disposition: attachment; filename="'.$filename.'"');

	//$message=put_in_standard_box($message_contents,do_lang_tempcode('ROOM'));

	$message_contents=do_template('CHAT_LOGS_SCREEN',array('TITLE'=>do_lang('CHAT_LOGS',escape_html(get_site_name()),escape_html($room_name),array(escape_html($start_date),escape_html($finish_date))),'MESSAGES'=>$message_contents));

	echo $message_contents->evaluate();
}

/**
 * Filter an invite list to make sure people who are blocking don't get drawn in and hence their blocking unmasked.
 *
 * @param  string			Comma-separated people list
 * @return string			Filtered comma-separated people list
 */
function filter_invites_for_blocking($people)
{
	$_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 chat room.
 *
 * @param  AUTO_LINK			Room ID (or -1 if all rooms)
 * @param  ?array				The room row (NULL: read in from DB)
 */
function chat_room_prune($room_id,$room_row=NULL)
{
	if (is_null($room_row))
	{
		$_room_row=$GLOBALS['SITE_DB']->query_select('chat_rooms',array('*'),array('id'=>$room_id),'',1);
		if (!array_key_exists(0,$_room_row)) warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
		$room_row=$_room_row[0];
	}

	// Find who may have gone offline
	$extra='';
	$last_active_prune=intval(get_value('last_active_prune'));
	if ($last_active_prune<time()-CHAT_ACTIVITY_PRUNE)
	{
		$pruned=$GLOBALS['SITE_DB']->query('SELECT id,member_id,room_id FROM '.get_table_prefix().'chat_active WHERE date_and_time<'.strval(time()-CHAT_ACTIVITY_PRUNE));
		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']))
			{
				$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);

				if (!file_exists(get_custom_file_base().'/data_custom/modules/chat'))
					@mkdir(get_custom_file_base().'/data_custom/modules/chat',0777);
				$myfile=@fopen(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat','wb') OR intelligent_write_error(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat');
				fwrite($myfile,strval($event_id));
				fclose($myfile);
				sync_file(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat');
			} else
			{
				// Make "left room" message
				if (!is_guest($p['member_id']))
				{
					require_code('lang');
					require_code('tempcode');
					require_lang('chat');
					$left_room_msg=do_lang('LEFT_ROOM',$GLOBALS['FORUM_DRIVER']->get_username($p['member_id']));
					if ($left_room_msg!='')
					{
						$_message_parsed=insert_lang_comcode($left_room_msg,4);
						$message_id=$GLOBALS['SITE_DB']->query_insert('chat_messages',array('system_message'=>1,'ip_address'=>get_ip_address(),'room_id'=>$p['room_id'],'user_id'=>$p['member_id'],'date_and_time'=>time(),'the_message'=>$_message_parsed,'text_colour'=>get_option('chat_default_post_colour'),'font_name'=>get_option('chat_default_post_font')),true);
						$myfile=@fopen(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat','wb') OR intelligent_write_error(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
						fwrite($myfile,strval($message_id));
						fclose($myfile);
						sync_file(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
					}
				}
			}
		}
		set_value('last_active_prune',strval(time()));
	}
	if ($room_id==-1)
	{
		$extra2='room_id IS NULL';
	} else
	{
		$extra2='room_id='.strval($room_id);
	}

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

	// 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==-1)?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 (or -1 to mean 'all' as used for IM global process, -2 to mean none)
 * @param  boolean			Output the backlog?
 * @param  ?AUTO_LINK		Latest received message ID (NULL: we're not getting latest messages)
 * @param  ?AUTO_LINK		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 to append
 */
function _chat_messages_script_ajax($room_id,$backlog=false,$message_id=NULL,$event_id=NULL,$events_output='')
{
	if ($event_id==-1) $event_id=NULL;

	require_lang('chat');
	require_lang('submitban');

	$room_check=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'));
		}
		if (!check_chatroom_access($room_check[0],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_check[0]['c_welcome'];
	} else $welcome=NULL;
	if ($room_id>=0)
	{
		$room_check=$GLOBALS['SITE_DB']->query_select('chat_rooms',array('*'),array('id'=>$room_id));
		if (array_key_exists(0,$room_check))
			chat_room_prune($room_id,$room_check[0]);
	} elseif ($room_id!=-2)
	{
		// Note that *we are still here*
		$GLOBALS['SITE_DB']->query_delete('chat_active',array('member_id'=>get_member(),'room_id'=>($room_id==-1)?NULL:$room_id));
		$GLOBALS['SITE_DB']->query_insert('chat_active',array('member_id'=>get_member(),'date_and_time'=>time(),'room_id'=>($room_id==-1)?NULL:$room_id),'',1);
	}

	if (is_null($room_check))
	{
		$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 is_im=1 AND allow_list LIKE \''.db_encode_like('%'.strval(get_member()).'%').'\'');
	}

	$from_id=NULL;
	$start=NULL;

	if (!$backlog) $from_id=$message_id;
	else $start=time()-CHAT_BACKLOG_TIME;

	$messages=($room_id==-2)?array():chat_get_room_content($room_id,$room_check,20,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 ocp_tempcode();
		$chat_ban_url=new ocp_tempcode();
		$chat_unban_url=new ocp_tempcode();
		if ((!is_null($room_check)) && (array_key_exists(0,$room_check)))
		{
			$moderator=is_chat_moderator($_message['member_id'],$room_id,$room_check[0]['room_owner']);
			$edit_url=build_url(array('page'=>'cms_chat','type'=>'ed','id'=>$_message['id'],'room_id'=>$_message['room_id']),get_module_zone('cms_chat'));
			if (has_specific_permission(get_member(),'ban_chatters_from_rooms'))
			{
				if (check_chatroom_access($room_check[0],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 ocp_tempcode();
				} else
				{
					$chat_ban_url=new ocp_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_actionlog','type'=>'toggle_ip_ban','id'=>$_message['ip_address']),'adminzone');
			} else
			{
				$ban_url=build_url(array('page'=>'admin_actionlog','type'=>'toggle_submitter_ban','id'=>$_message['member_id']),'adminzone');
			}
			//$ban_url=build_url(array('page'=>'admin_actionlog','type'=>'multi_ban','id'=>$_message['ip_address'].':'.$_message['username']),'adminzone');
		}
		else $ban_url=new ocp_tempcode();

		if (($room_id!=-1) && (addon_installed('actionlog')) && ((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 ocp_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']);
		} 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,'USER'=>$user,'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" 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 is_im=1 AND allow_list LIKE \''.db_encode_like('%'.strval(get_member()).'%').'\''));
		foreach ($room_check as $room)
		{
			if (check_chatroom_access($room,true,NULL,true))
			{
				if (($room['allow_list']==strval(get_member())) && (is_null($event_id))) // If it's just you in the room, close that room down
				{
					require_code('chat2');
					delete_chatroom($room['id']);
				} else
				{
					$people_in_room=explode(',',$room['allow_list']);
					if ($room['room_name']==$GLOBALS['FORUM_DRIVER']->get_username(get_member()) && (count($people_in_room)>1)) // If room named after us, try and switch name to that of second person
					{
						$test=$GLOBALS['FORUM_DRIVER']->get_username(intval($people_in_room[1]));
						if (!is_null($test)) $room['room_name']=$test;
					}

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

					$num_posts=$GLOBALS['SITE_DB']->query_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>';
				}
			}
		}

		if (!is_null($event_id))
		{
			$events=$GLOBALS['SITE_DB']->query('SELECT * FROM '.get_table_prefix().'chat_events WHERE id>'.strval((integer)$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':
						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_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>';
				}
			}
		} else
		{
			$max_id=$GLOBALS['SITE_DB']->query_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>';
		}
	}

	if ($messages_output=='')
	{
		$messages_output='<chat_null>'.strval($room_id).'</chat_null>';
	}
	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().'" ?'.'>
<!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>
		'.$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
 * @param  ?AUTO_LINK	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_null_ok_full('SELECT member_id FROM '.get_table_prefix().'chat_active WHERE '.$room_clause.' AND date_and_time>='.strval((integer)time()-CHAT_ACTIVITY_PRUNE).' AND member_id='.(string)$member_id);
	return !is_null($test);
}

/**
 * Find whether a member is a moderator of a chat room.
 *
 * @param  MEMBER			Member ID
 * @param  AUTO_LINK		Room ID
 * @param  ?MEMBER		Room owner (NULL: none)
 * @return boolean		Whether the member is a moderator of the chat room
 */
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
 * @param  string			The message
 * @param  string			Font name
 * @param  string			Font colour
 * @param  BINARY			Whether this is the first message sent out to this room, since some change
 */
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'));
	}
	if (!check_chatroom_access($room_check[0],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,'user_id'=>get_member(),'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('SYSTEM_MESSAGE'=>strval($_message['system_message']),'STAFF'=>false,'OLD_MESSAGES'=>false,'AVATAR_URL'=>'','STAFF_ACTIONS'=>'','USER'=>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 xmlns="http://www.w3.org/1999/xhtml" sender_id="'.strval($_message['member_id']).'" room_id="'.strval($_message['room_id']).'" id="123456789" timestamp="'.strval($_message['date_and_time']).'">'.$template->evaluate().'</div>';

		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().'" ?'.'>
<!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
	{
		//$prefs=@$_COOKIE['ocp_chat_prefs'];
		//$prefs=@explode(';',$prefs);
		//$font=isset($prefs[1])?$prefs[1]:get_option('chat_default_post_font');
		//$colour=isset($prefs[0])?$prefs[0]:get_option('chat_default_post_colour');
		if (chat_post_message($room_id,$message,$font,$colour,60)) $return='1';
		else $return='0';
	}

	if (($room_check[0]['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_check[0]['allow_list']);
		foreach ($allow_list as $_allow)
		{
			$_allow=trim($_allow);
			$allow=intval($_allow);
			if (($allow!=$room_check[0]['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))
					{
						//if (get_forum_type()=='ocf') // Send PT  -- Actually, this'd just get annoying
						//{
						//} else // Send e-mail
						{
							$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);
							$myfile=@fopen(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat','wb') OR intelligent_write_error(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat');
							fwrite($myfile,strval($event_id));
							fclose($myfile);
							sync_file(get_custom_file_base().'/data_custom/modules/chat/chat_last_event.dat');

							require_lang('chat');

							$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_lang('IM_INVITED_MESSAGE',get_timezoned_date(time(),true),$username,array($lobby_url,$username2,$message),get_lang($allow));

							require_code('notifications');
							dispatch_notification('im_invited',NULL,$subject,$message,array($allow),$room_check[0]['room_owner'],1);
						}
					}
				}
			}
		}
	}

	/*if ($return=='0') Flood control creates error, but we'd rather see it shown inline
	{
			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().'" ?'.'>
<!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_check[0]['is_im']==1)?-1:$room_id,false,either_param_integer('message_id'),either_param_integer('event_id'));
}

/**
 * Enter the current member to the chat lobby / Mark them available.
*/
function enter_chat_lobby()
{
	if (is_guest()) return;

	require_css('chat');
	require_lang('chat');
	require_javascript('javascript_ajax');
	require_javascript('javascript_chat');
	require_javascript('javascript_sound');

	if ((!array_key_exists(get_member(),get_chatters_in_room(NULL))) && (!is_invisible()))
	{
		$GLOBALS['SITE_DB']->query_insert('chat_active',array('member_id'=>get_member(),'date_and_time'=>time(),'room_id'=>NULL));

		$GLOBALS['SITE_DB']->query_insert('chat_events',array(
			'e_type_code'=>'BECOME_ACTIVE',
			'e_member_id'=>get_member(),
			'e_room_id'=>NULL,
			'e_date_and_time'=>time()
		));
	}
}

/**
 * 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		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 member_id FROM '.get_table_prefix().'chat_active a LEFT JOIN '.get_table_prefix().'sessions s ON s.the_user=a.member_id WHERE (session_invisible=0 OR session_invisible IS NULL) AND date_and_time>='.strval((integer)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				A mapping (user=>username) of the chatters in the room
 * @return	tempcode			The Tempcode
*/
function get_chatters_in_room_tpl($users)
{
	$usernames=new ocp_tempcode();
	$some_users=false;
	foreach ($users as $member_id=>$username)
	{
		if (!member_blocked(get_member(),$member_id))
		{
			$some_users=true;
			if (!is_guest($member_id))
			{
				if (get_forum_type()=='ocf')
				{
					require_code('ocf_general');
					require_code('ocf_members');

					$colour=get_group_colour(ocf_get_member_primary_group($member_id));
					$usernames->attach(do_template('OCF_USER_MEMBER',array('PROFILE_URL'=>$GLOBALS['FORUM_DRIVER']->member_profile_url($member_id,true,true),'USERNAME'=>$username,'COLOUR'=>$colour)));
				} else
				{
					$usernames->attach($GLOBALS['FORUM_DRIVER']->member_profile_hyperlink($member_id,true,$username));
				}
			} else
			{
				if (!$usernames->is_empty()) $usernames->attach(escape_html(', ')); // NB: OCF_USER_MEMBER would have auto-added comma
				$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		The room ID
 * @param  boolean		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_value_null_ok('chat_rooms','room_name',array('id'=>$room_id));
	}
	return $GLOBALS['SITE_DB']->query_value('chat_rooms','room_name',array('id'=>$room_id));
}

/**
 * Get the id of the specified chatroom, from its room name.
 *
 * @param  SHORT_TEXT		The name of the chatroom
 * @param  boolean			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 chat room)
*/
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_value_null_ok('chat_rooms','id',$map);
}

/**
 * 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		The room ID for the message to be posted in
 * @param  LONG_TEXT		The message body
 * @param  SHORT_TEXT	The font name for the message
 * @param  SHORT_TEXT	The text colour for the message
 * @param  SHORT_INTEGER  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_value('chat_rooms','is_im',array('id'=>$room_id));
	if ($is_im) // No flood control for IMs
	{
		$time_last_message=NULL;
	} else
	{
		$time_last_message=$GLOBALS['SITE_DB']->query_value_null_ok('chat_messages','MAX(date_and_time)',array('user_id'=>get_member(),'system_message'=>0));
		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');

		// Decode colour code
		if (substr($text_colour,0,2)=='0x') $text_colour='#'.substr($text_colour,2,strlen($text_colour)-2);

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

		$myfile=@fopen(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat','wb') OR intelligent_write_error(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
		fwrite($myfile,strval($message_id));
		fclose($myfile);
		sync_file(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');

		// 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
					$_message_parsed=insert_lang_comcode(wordfilter_text($response),4,NULL,false,NULL,$wrap_pos);
					$bot_message_id=$GLOBALS['SITE_DB']->query_insert('chat_messages',array('system_message'=>0,'ip_address'=>$hook,'room_id'=>$room_id,'user_id'=>$GLOBALS['FORUM_DRIVER']->get_guest_id(),'date_and_time'=>time(),'the_message'=>$_message_parsed,'text_colour'=>get_option('chat_default_post_colour'),'font_name'=>get_option('chat_default_post_font')),true);

					$myfile=@fopen(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat','wb') OR intelligent_write_error(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
					fwrite($myfile,strval($bot_message_id));
					fclose($myfile);
					sync_file(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
				}
			}
		}

		// 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');
	$_message_parsed=insert_lang_comcode('[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 it's definition
	$message_id=$GLOBALS['SITE_DB']->query_insert('chat_messages',array('system_message'=>1,'ip_address'=>get_ip_address(),'room_id'=>$room_id,'user_id'=>get_member(),'date_and_time'=>time(),'the_message'=>$_message_parsed,'text_colour'=>get_option('chat_default_post_colour'),'font_name'=>get_option('chat_default_post_font')),true);
	$myfile=@fopen(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat','wb') OR intelligent_write_error(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
	fwrite($myfile,strval($message_id));
	fclose($myfile);
	sync_file(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
	return false;
}

/**
 * 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, smilies, 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.
 * If you set the $dereference flag, all the messages will be dereferenced for you, and if you set the $downloading flag, the array is returned in a format appropriate for things like downloading the chat logs.
 * $start and $finish are used to cutoff the number of messages returned, based on their posting date and time, and the $uptoid variable is used to make the function only return the messages newer than the id specified.
 *
 * @param  AUTO_LINK		The room ID (-1 for IM)
 * @param  array			Rooms database rows that we'll need
 * @param  ?integer		The maximum number of messages to be returned (NULL: no maximum)
 * @param  boolean		Whether to dereference the returned messages
 * @param  boolean		Whether to return the messages in a downloadeable format (using the templates for log downloading)
 * @param  ?integer		The datetime stamp to start gathering messages from (NULL: all)
 * @param  ?integer		The datetime stamp to stop gathering messages at (NULL: current time)
 * @param  ?integer		The lowest message ID to return (NULL: no special lowest number)
 * @param  ?ID_TEXT		The zone the chat module is in (NULL: find it)
 * @param  ?AUTO_LINK	The language ID for the "entering room" message (NULL: not entering the room)
 * @param  boolean		Return the current user's messages?
 * @param  boolean		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,$cutoff=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!='')
		{
			$_message_parsed=insert_lang_comcode('[private="'.$their_username.'"]'.$_entering_room.'[/private]',4);
			$message_id=$GLOBALS['SITE_DB']->query_insert('chat_messages',array('system_message'=>0,'ip_address'=>get_ip_address(),'room_id'=>$room_id,'user_id'=>get_member(),'date_and_time'=>time(),'the_message'=>$_message_parsed,'text_colour'=>get_option('chat_default_post_colour'),'font_name'=>get_option('chat_default_post_font')),true);
			$myfile=@fopen(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat','wb') OR intelligent_write_error(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
			fwrite($myfile,strval($message_id));
			fclose($myfile);
			sync_file(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
		}

		$enter_room_msg=do_lang('ENTERED_THE_ROOM',$their_username);
		if ($enter_room_msg!='')
		{
			$_message_parsed=insert_lang_comcode($enter_room_msg,4);
			$message_id=$GLOBALS['SITE_DB']->query_insert('chat_messages',array('system_message'=>1,'ip_address'=>get_ip_address(),'room_id'=>$room_id,'user_id'=>get_member(),'date_and_time'=>time(),'the_message'=>$_message_parsed,'text_colour'=>get_option('chat_default_post_colour'),'font_name'=>get_option('chat_default_post_font')),true);
			$myfile=@fopen(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat','wb') OR intelligent_write_error(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
			fwrite($myfile,strval($message_id));
			fclose($myfile);
			sync_file(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
		}

		$room_name=$GLOBALS['SITE_DB']->query_value('chat_rooms','room_name',array('id'=>$room_id));
		$room_language=$GLOBALS['SITE_DB']->query_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_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 smilies 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!=-1)
		{
			$where.='room_id='.strval((integer)$room_id);
		}
		if ($downloading)
		{
			if ($where!='') $where.=' AND ';
			$where.='date_and_time>='.strval((integer)$start).' AND date_and_time<='.strval((integer)$finish);
		}
		if ((!is_null($uptoid)) && ($uptoid!=-1))
		{
			if (get_db_type()=='xml')
			{
				$timestamp=$GLOBALS['SITE_DB']->query_value_null_ok('chat_messages','date_and_time',array('id'=>$uptoid));
				if (is_null($timestamp)) $timestamp=0;
				if ($where!='') $where.=' AND ';
				$where.='main.date_and_time>'.strval((integer)$timestamp);
			} else
			{
				if ($where!='') $where.=' AND ';
				$where.='main.id>'.strval((integer)$uptoid);
			}
		}
		if (!$return_my_messages)
		{
			if ($where!='') $where.=' AND ';
			$where.='user_id!='.strval((integer)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;
		$rows=$GLOBALS['SITE_DB']->query($query,$cutoff,NULL,false,false,array_key_exists('chat_messages',$TABLE_LANG_FIELDS)?$TABLE_LANG_FIELDS['chat_messages']:array());
	} else
	{
		$where_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',$cutoff);
	}
	$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]['member_id']=$rows[$i]['user_id']; // unfortunately the table schema was designed before our coding standards solidified
		$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']);
		$message=get_translated_tempcode($rows[$i]['the_message']);

		// Extra access check
		if ($room_id==-1)
		{
			$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)));
		} 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'],$cutoff,$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			The text we are using
 * @param  string			The tag name we are parsing
 * @param  string			1st param
 * @param  string			2nd param
 * @param  SHORT_TEXT	The username of who made this chatcode
 * @param  ?integer		The maximum number of messages to be returned (NULL: no maximum)
 * @param  ID_TEXT		The zone that our chat module is in
 * @param  AUTO_LINK		The room ID the message is in
 * @param  BINARY			Whether this is within a system message
 * @return array			A pair: whether the message was deleted, and the new text of the message
*/
function _deal_with_chatcode_tags($text,$tag,$pm_user,$pm_message,$username,$cutoff,$zone,$room_id,$system_message)
{
	switch ($tag)
	{
		case 'newroom':
			return _deal_with_chatcode_newroom($pm_user,$pm_message,$username,$text,$cutoff);
		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			The member a private message should be sent to
 * @param  string			The private message
 * @param  SHORT_TEXT	The username of who made this chatcode
 * @param  string			The text we are using
 * @param  AUTO_LINK		The room ID the message is in
 * @param  BINARY			Whether this is within a system message
 * @return array			A pair: whether the message was deleted, and the new text of the message
*/
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.=chr(10).chr(10);
						$_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',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			Comma-separated list of members to invite
 * @param  string			The room name
 * @param  SHORT_TEXT	The username of who made this chatcode
 * @param  string			The text we are using
 * @param  ID_TEXT		The zone the chat module is in
 * @return array			A pair: whether the message was deleted, and the new text of the message
*/
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(chat_convert_html_entities($pm_message),true);
			if (!is_null($room_id))
			{
				// Display the invite
				$invite_code=do_template('CHAT_INVITE',array('_GUID'=>'493ac2dcabc763fe03e7eee072dd9629','USERNAME'=>$username,'ROOM'=>chat_convert_html_entities($pm_message),'LINK'=>hyperlink(build_url(array('page'=>'chat','type'=>'room','room_id'=>strval($room_id)),$zone),do_lang_tempcode('CHAT_INVITE_TEXT_REPLY'))));
				$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			The room name
 * @param  string			Comma-separated list of members to allow in
 * @param  SHORT_TEXT	The username of who made this chatcode
 * @param  string			The text we are using
 * @param  ?integer		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
*/
function _deal_with_chatcode_newroom($pm_user,$pm_message,$username,$text,$cutoff)
{
	$pm_message_deleted=false;
	if (!has_specific_permission(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),'',$cutoff);
	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'=>'','c_welcome'=>insert_lang('',3),'room_language'=>user_lang()),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)
					{
						$_message_parsed=insert_lang_comcode('[invite="'.$person.'"]'.get_chatroom_name($new_room_id).'[/invite]',4);
						$message_id=$GLOBALS['SITE_DB']->query_insert('chat_messages',array('ip_address'=>get_ip_address(),'room_id'=>$room['id'],'user_id'=>get_member(),'date_and_time'=>time(),'the_message'=>$_message_parsed,'text_colour'=>get_option('chat_default_post_colour'),'font_name'=>get_option('chat_default_post_font')),true);
						$myfile=@fopen(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat','wb') OR intelligent_write_error(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
						fwrite($myfile,strval($message_id));
						fclose($myfile);
						sync_file(get_custom_file_base().'/data_custom/modules/chat/chat_last_msg.dat');
					}
				}
			}
		}
	}
	$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			Original list of messages
 * @param  array			List of message IDs to keep
 * @return array			A new list of messages
*/
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			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;
}

/**
 * Takes a chat message, and converts some characters from HTML entities into proper characters.
 *
 * @param  string			Chat message to be parsed
 * @return string			Parsed chat message
*/
function chat_convert_html_entities($string)
{
	return str_replace('&#039;',"'",$string);
}

/**
 * Check whether a member has access to the chatroom.
 *
 * @param  mixed				The row of the chat room to check for access OR it's ID (AUTO_LINK)
 * @param  boolean			Whether to return false if there is no access (as opposed to bombing out)
 * @param  ?MEMBER			The member to check as (NULL: current member)
 * @param  boolean			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'));
		$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;
}

/**
 * Prune timed-out private chatrooms.
 *
 * @param  array			The row of the chat room to possibly prune
 * @return boolean		Whether the room was pruned
*/
function handle_chatroom_pruning($row)
{
	$deletion_time=intval(get_option('chat_private_room_deletion_time'));
	if ($deletion_time==0) return false;
	if (($row['allow_list']!='') || (!is_null($row['room_owner'])))
	{
		// As this is a private chatroom, we need to delete it if it has been idle for too long ;-)
		$message=$GLOBALS['SITE_DB']->query_select('chat_messages',array('date_and_time'),array('room_id'=>$row['id']),'ORDER BY date_and_time DESC',1);
		if ((isset($message[0])) && (($message[0]['date_and_time']+($deletion_time*60))<=time()))
		{
			// Delete the room and its messages
			$GLOBALS['SITE_DB']->query_delete('chat_rooms',array('id'=>$row['id']),'',1);
			require_code('chat2');
			delete_chat_messages(array('room_id'=>$row['id']));
			return true;
		}
	}
	return false;
}

/**
 * Get the list of all available chat sound effects.
 *
 * @param  boolean		Map to NULL if it is not overridable.
 * @return map				All available sound effects (mapping between base code, and actual code).
*/
function get_effect_set($only_overridable=false)
{
	$effects=array(
		'message_received'=>'message_received',
		'message_background'=>'message_background',
		'message_initial'=>$only_overridable?NULL:'message_initial',
		'message_sent'=>$only_overridable?NULL:'message_sent',
		'contact_on'=>'contact_on',
		'contact_off'=>'contact_off',
		'invited'=>'invited',
		'you_connect'=>$only_overridable?NULL:'you_connect',
	);

	return $effects;
}

/**
 * Get a list of template mappings for the current member, between sound effect IDs and the URLs to the mp3 fiels.
 *
 * @param  boolean		Whether to use full URLs in the mappings.
 * @param  ?MEMBER		Get settings overridden for this specific member (NULL: global settings).
 * @param  boolean		Get global settings and settings overridden for all members (if this is true we'd expect $for_member to be NULL).
 * @return array			The template mappings.
*/
function get_effect_settings($full_urls=false,$for_member=NULL,$all_members=false)
{
	$effects=get_effect_set(!is_null($for_member));

	global $EFFECT_SETTINGS_ROWS;
	if (is_null($EFFECT_SETTINGS_ROWS))
	{
		$EFFECT_SETTINGS_ROWS=collapse_2d_complexity('s_effect_id','s_url',$GLOBALS['SITE_DB']->query_select('chat_sound_effects',array('s_url','s_effect_id'),array('s_member'=>get_member())));
	}
	$effect_settings=array();
	if ($all_members)
	{
		foreach (array_keys($EFFECT_SETTINGS_ROWS) as $effect_id)
		{
			$matches=array();
			if ((!array_key_exists($effect_id,$effects)) && (preg_match('#^(.*)\_(\d+)$#',$effect_id,$matches)!=0) && (array_key_exists($matches[1],$effects)))
			{
				$effects[$effect_id]=$matches[1];
			}
		}
	}
	foreach ($effects as $effect=>$base_effect_code)
	{
		if (is_null($base_effect_code)) continue;

		if (is_null($for_member)) // Global settings
		{
			if (array_key_exists($effect,$EFFECT_SETTINGS_ROWS))
			{
				$member_setting=$EFFECT_SETTINGS_ROWS[$effect];
			} else
			{
				if (true)/* elseif ($effect!='message_sent')*/
				{
					$member_setting='data_custom/sounds/'.$effect.'.mp3';
					if (!file_exists(get_custom_file_base().'/'.$member_setting))
						$member_setting='data/sounds/'.$effect.'.mp3';
					if (!file_exists(get_file_base().'/'.$member_setting))
						$member_setting='';
				} else $member_setting='';
			}
		} else // Overridden settings
		{
			if (array_key_exists($effect.'_'.strval($for_member),$EFFECT_SETTINGS_ROWS))
			{
				$member_setting=$EFFECT_SETTINGS_ROWS[$effect.'_'.strval($for_member)];
			} else
			{
				$member_setting='-1';
			}
		}
		$effect_settings[$effect]=array(
			'KEY'=>$effect,
			'VALUE'=>(($full_urls&&($member_setting!=''))?(((substr($member_setting,0,12)=='data_custom/')?get_custom_base_url():get_base_url()).'/'):'').$member_setting,
			'EFFECT_TITLE'=>do_lang('CHAT_EFFECT_'.$base_effect_code),
		);
	}
	return $effect_settings;
}

/**
 * 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()
{
	return do_template('CHAT_SOUND',array('SOUND_EFFECTS'=>get_effect_settings(true,NULL,true)));
}
