<?php

namespace IPS\chatbox\Rooms;

/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
	header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
	exit;
}

/**
 * RSS Exports
 */
class _Room extends \IPS\Node\Model
{
	use \IPS\Content\ClubContainer;
	
	/**
	 * @brief	[ActiveRecord] Multiton Store
	 */
	protected static $multitons;
	
	/**
	 * @brief	[ActiveRecord] Database Table
	 */
	public static $databaseTable = 'chatbox_rooms';
	
	/**
	 * @brief	[ActiveRecord]	Database Prefix
	 */
	public static $databasePrefix = 'room_';
			
	/**
	 * @brief	[Node] Order Database Column
	 */
	public static $databaseColumnOrder = 'position';
		
	/**
	 * @brief	[Node] Node Title
	 */
	public static $nodeTitle = 'chatbox_rooms';
	
	/**
	 * @brief	Content Item Class
	 */
	public static $contentItemClass = 'IPS\chatbox\Rooms\Chat';
	
	/**
	 * @brief	[Node] Title prefix.  If specified, will look for a language key with "{$key}_title" as the key
	 */
	public static $titleLangPrefix = 'chatbox_room_';
	
	/**
	 * @brief	[Node] Description suffix.  If specified, will look for a language key with "{$titleLangPrefix}_{$id}_{$descriptionLangSuffix}" as the key
	 */
	public static $descriptionLangSuffix = '_desc';
	
	/**
	 * @brief	[Node] Enabled/Disabled Column
	 */
	public static $databaseColumnEnabledDisabled = 'enabled';
	
	/**
	 * @brief	[Node] ACP Restrictions
	 * @code
	 	array(
	 		'app'		=> 'core',				// The application key which holds the restrictrions
	 		'module'	=> 'foo',				// The module key which holds the restrictions
	 		'map'		=> array(				// [Optional] The key for each restriction - can alternatively use "prefix"
	 			'add'			=> 'foo_add',
	 			'edit'			=> 'foo_edit',
	 			'permissions'	=> 'foo_perms',
	 			'delete'		=> 'foo_delete'
	 		),
	 		'all'		=> 'foo_manage',		// [Optional] The key to use for any restriction not provided in the map (only needed if not providing all 4)
	 		'prefix'	=> 'foo_',				// [Optional] Rather than specifying each  key in the map, you can specify a prefix, and it will automatically look for restrictions with the key "[prefix]_add/edit/permissions/delete"
	 * @encode
	 */
	protected static $restrictions = array(
		'app'		=> 'chatbox',
		'module'	=> 'chatbox',
		'all'	 	=> 'rooms_manage',
	);
	
	/**
	 * @brief	The map of permission columns
	 */
	public static $permissionMap = array(
		'view' 				=> 'view',
		'read'				=> 2,
	);	
	
	/**
	 * @brief	[Node] URL Base
	 */
	public static $urlBase = 'app=chatbox&module=chatbox&controller=room&id=';
	
	/**
	 * @brief	[Node] SEO Template
	 */
	public static $urlTemplate = 'chatbox_room';
	
	/** 
	 * @brief	[Node] App for permission index
	 */
	public static $permApp = 'chatbox';
	
	/** 
	 * @brief	[Node] Type for permission index
	 */
	public static $permType = 'room';
	
	/**
	 * @brief	[Node] Moderator Permission
	 */
	public static $modPerm = 'chatbox_rooms';
		
	/**
	 * @brief	SEO Title Column
	 */
	public static $seoTitleColumn = 'seo_name';
	
	/**
	 * Get URL for doing stuff
	 *
	 * @param	string|NULL		$action		Action
	 * @return	\IPS\Http\Url
	 */
	public function url( $action=null )
	{
		$url = parent::url( $action );
		
		if ( $action )
		{
			return $url->setQueryString( 'do', $action );
		}
		return $url;
	}
	
	/**
	 * Get Lang
	 */
	public function get__title()
	{
		if ( \IPS\Member::loggedIn()->language()->checkKeyExists( 'chatbox_room_' . $this->id ) )
		{
			return \IPS\Member::loggedIn()->language()->addToStack('chatbox_room_' . $this->id);
		}
		else return $this->id;
	}
	
	/**
	 * [Node] Does the currently logged in user have permission to edit permissions for this node?
	 *
	 * @return	bool
	 */
	public function canManagePermissions()
	{
		return false;
	}
	
	/**
	 * [Node] Does the currently logged in user have permission to mass move/delete content in this node?
	 *
	 * @return	bool
	 */
	public function canMassManageContent()
	{
		return FALSE;
	}
	
	/**
	 * Get data store
	 *
	 * @return	array
	 */
	public static function getStore()
	{
		if ( !isset( \IPS\Data\Store::i()->chatbox_rooms ) )
		{
			\IPS\Data\Store::i()->chatbox_rooms = iterator_to_array( 
				\IPS\Db::i()->select( '*', static::$databaseTable, NULL, "room_club_id ASC, room_position ASC, room_id ASC" )->setKeyField( 'room_id' ) 
			);
		}
		
		return \IPS\Data\Store::i()->chatbox_rooms;
	}

	/**
	 * @brief	[ActiveRecord] Attempt to load from cache
	 * @note	If this is set to TRUE you MUST define a getStore() method to return the objects from cache
	 */
	protected static $loadFromCache = TRUE;
	
	/**
	 * @brief	[ActiveRecord] Caches
	 * @note	Defined cache keys will be cleared automatically as needed
	 */
	protected $caches = array( 'chatbox_rooms' );
	
	
	/**
	 * [Node] Add/Edit Form
	 *
	 * @param	\IPS\Helpers\Form	$form	The form
	 * @return	void
	 */
	public function form( &$form )
	{
		$opts = $this->options();

		$groups = \IPS\Member\Group::groups();
		$noGuest = $groups;
		unset( $noGuest[ \IPS\Settings::i()->guest_group ] );	
			
		# General settings
		$form->addTab( 'room_tab_general' );
		$form->add( new \IPS\Helpers\Form\Translatable( 'room_name', NULL, TRUE, array( 'app' => 'chatbox', 'key' => ( $this->_id ) ? "chatbox_room_{$this->_id}" : NULL ) ) );
		$form->add( new \IPS\Helpers\Form\Translatable( 'room_description', NULL, FALSE, array( 'app' => 'chatbox', 'key' => ( $this->_id ) ? "chatbox_room_{$this->_id}_description" : NULL, 'textArea' => TRUE ) ) );
		$form->add( new \IPS\Helpers\Form\Text( 'room_icon', $this->icon ? $this->icon : 'comments' ) );
		$form->add( new \IPS\Helpers\Form\YesNo( 'room_show_rules', $this->show_rules, FALSE, array( 'togglesOn' => array('room_rules') ) ) );
		$form->add( new \IPS\Helpers\Form\Translatable( 'room_rules', NULL, FALSE, array( 'app' => 'chatbox', 'key' => ( $this->id ? "chatbox_room_{$this->id}_rules" : NULL ), 'editor' => array( 'app' => 'chatbox', 'key' => 'Room', 'autoSaveKey' => ( $this->id ? "chatbox-room-{$this->id}-rules" : "chatbox-room-new-rules" ), 'attachIds' => $this->id ? array( $this->id, NULL, 'chatbox_rules' ) : NULL, 'minimize' => 'chatbox_rules_placeholder' ) ), NULL, NULL, NULL, 'room_rules' ) );
		$form->add( new \IPS\Helpers\Form\Upload( 'room_notifile', $this->notifile ? \IPS\File::get( 'chatbox_Nofifile', $this->notifile ) : NULL, FALSE, array( 
			'allowedFileTypes'	=> array( 'mp3' ),
			'maxFileSize'		=> 1,
			'storageExtension' 	=> 'chatbox_Nofifile',
		), NULL, NULL, NULL, 'room_notifile' ) );

		# Appearance
		$form->addTab( 'room_tab_style' );
		$form->add( new \IPS\Helpers\Form\Number( 'chatbox_height', $opts['height'] ? \intval( $opts['height'] ) : 420, TRUE, array( 'min' => 200 ) ) );
		$form->add( new \IPS\Helpers\Form\Select( 'chatbox_style', $opts ? $opts['style'] : 'standard', FALSE, array( 'options' => \IPS\Application::load('chatbox')->styles() ) ) );	
		foreach( \IPS\Application::load('chatbox')->skins() as $bg )
		{
			$skinClass = $bg != 'white' ? $bg . '_box' : 'ipsType_sectionTitle white_box';
			$skinSelect[ $bg ] = "<div class='ipsBox {$skinClass}' style='height: 20px; width: 80px;'></div>";
		}		
		$form->add( new \IPS\Helpers\Form\Radio( 'chatbox_skin', $opts['skin'] ? $opts['skin'] : 'white', FALSE, array( 'options' => $skinSelect, 'parse' => 'raw' ) ) );		
		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_allow_select_skins', $opts['allow_select_skins'], FALSE, array(), NULL, NULL, NULL, 'chatbox_allow_select_skins' ) );
		
		# Chat
		$form->addTab( 'room_tab_chat' );
		$form->add( new \IPS\Helpers\Form\Select( 'chatbox_sort', $opts ? $opts['sort'] : 'asc', FALSE, array( 'options' => array(
			'asc'	=> 'chatbox_asc_sort',
			'desc'	=> 'chatbox_desc_sort'
		) ) ) );		

		$form->add( new \IPS\Helpers\Form\Number( 'chatbox_limit', $opts['limit'] ? \intval( $opts['limit'] ) : 20, TRUE, array( 'min' => 5 ) ) );
		$form->add( new \IPS\Helpers\Form\Number( 'chatbox_interval', $opts['interval'] ? \intval( $opts['interval'] ) : 10000, TRUE, array( 'min' => 2000 ) ) );
		$form->add( new \IPS\Helpers\Form\Number( 'chatbox_flood', isset( $opts['flood'] ) ? \intval( $opts['flood'] ) : 5, FALSE, array( 'unlimited' => 0, 'unlimitedLang' => 'none' ) ) );
		$form->add( new \IPS\Helpers\Form\Number( 'chatbox_length', isset( $opts['length'] ) ? \intval( $opts['length'] ) : 300, FALSE, array( 'unlimited' => 0, 'unlimitedLang' => 'unlimited' ) ) );
		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_autoload', $opts['autoload'], FALSE ) );
		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_showUserList', $opts['showUserList'], FALSE ) );
		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_hideAnon', $opts['hideAnon'], FALSE ) );
		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_auto_guest', $opts['auto_guest'], FALSE ) );
		
		# Input
		$form->addTab('chatbox_tab_input_media');
		//$form->addHeader('chatbox_tab_input');
		$form->add( new \IPS\Helpers\Form\Select( 'chatbox_use_editor', (int) $opts['use_editor'], FALSE, array(
			'options'	=> array( 0	=> 'chatbox_use_clean_text', 1 => 'chatbox_use_ckeditor' ),
			'toggles'	=> array(
				'0'	=> array( 'chatbox_allow_img', 'chatbox_allow_video', 'chatbox_allow_audio', 'chatbox_giphy', 'chatbox_autogrow', 'chatbox_voice' ),
				'1'	=> array( 'chatbox_enter_editor', 'chatbox_editor_upload' )
			)
		) ) );	
		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_editor_upload', $opts['editor_upload'], FALSE, array(), NULL, NULL, NULL, 'chatbox_editor_upload' ) );
		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_enter_editor', $opts['enter_editor'], FALSE, array(), NULL, NULL, NULL, 'chatbox_enter_editor' ) );
		
		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_autogrow', $opts['autogrow'], FALSE, array(), NULL, NULL, NULL, 'chatbox_autogrow' ) );	

		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_allow_video', $opts['allow_video'], FALSE, array(), NULL, NULL, NULL, 'chatbox_allow_video' ) );
		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_allow_audio', $opts['allow_audio'], FALSE, array(), NULL, NULL, NULL, 'chatbox_allow_audio' ) );

		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_allow_img', $opts['allow_img'], FALSE, array( 'togglesOn' => array( 'room_can_upload' ) ), NULL, NULL, NULL, 'chatbox_allow_img' ) );
		$form->add( new \IPS\Helpers\Form\Select( 'room_can_upload', array_filter( explode( ',', $this->can_upload ) ), FALSE, array( 'options' => $groups, 'parse' => 'normal', 'multiple' => TRUE ), NULL, NULL, NULL, 'room_can_upload' ) );
		
		if ( \method_exists( '\IPS\core\modules\front\system\plugins', 'bimGiphy' ) )
		{
			$form->add( new \IPS\Helpers\Form\Select( 'chatbox_giphy', $opts['giphy'] ? $opts['giphy'] : 'no', FALSE, array( 'options' => array( 'no' => 'chatbox_giphy_no', 'button' => 'chatbox_giphy_button', 'cmd' => 'chatbox_giphy_cmd', 'both' => 'chatbox_giphy_both' ) ), NULL, NULL, NULL, 'chatbox_giphy' ) );	
		}
		else
		{
			$form->add( new \IPS\Helpers\Form\Custom( 'chatbox_giphy', 'no', FALSE, array( 'getHtml' => function(){
				return "<div class='ipsPadding:half'>" . \IPS\Member::loggedIn()->language()->addToStack('chatbox_giphy_req') . "</div>";
			} ) ) );
		}

		if ( \IPS\Application::appIsEnabled('musicbox') )
		{
			$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_musicbox', $opts['musicbox'], FALSE, array(), NULL, NULL, NULL, 'chatbox_musicbox' ) );
		}
		else
		{
			$form->add( new \IPS\Helpers\Form\Custom( 'chatbox_musicbox', FALSE, FALSE, array( 'getHtml' => function(){
				return "<div class='ipsPadding:half'>" . \IPS\Member::loggedIn()->language()->addToStack('chatbox_musicbox_req') . "</div>";
			} ) ) );
		}
		
		if ( \method_exists( '\IPS\core\modules\front\system\plugins', 'sendVoiceToChatbox' ) )
		{
			$form->add( new \IPS\Helpers\Form\Custom( 'chatbox_voice', FALSE, FALSE, array( 'getHtml' => function(){
				try
				{	
					$id = \IPS\Db::i()->select( 'plugin_id', 'core_plugins', array( 'plugin_name=? OR plugin_location=?', 'Voice Messages for Chatbox+', 'voicemsgforchatbox' ) )->first();
				}
				catch( \UnderflowException $e ) { }
				return "<div class='ipsPadding:half'>" . \IPS\Member::loggedIn()->language()->addToStack('chatbox_voice_on', FALSE, array( 'sprintf' => array( $id ) ) ) . "</div>";
			} ) ) );	
		}
		else
		{
			$form->add( new \IPS\Helpers\Form\Custom( 'chatbox_voice', FALSE, FALSE, array( 'getHtml' => function(){
				return "<div class='ipsPadding:half'>" . \IPS\Member::loggedIn()->language()->addToStack('chatbox_voice_req') . "</div>";
			} ) ) );
		}		
		
		# System messages
		$form->addTab('chatbox_tab_auto_msg');
		
		$form->addHeader('chatbox_header_sysMSG');
		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_announcement_on', $opts['announcement_on'] ) );
		$form->add( new \IPS\Helpers\Form\YesNo( 'chatbox_online_on', $opts['online_on'] ) );
		
		$form->add( new \IPS\Helpers\Form\Member( 'chatbox_sys_bot', $opts['sys_bot'] > 0 ? \IPS\Member::load( $opts['sys_bot'] ) : NULL, FALSE, array( 
			'multiple' 	=> 1
		), NULL, NULL, NULL, 'chatbox_sys_bot' ) );		
		
				
		$form->addHeader('chatbox_header_botMSG');
		$form->add( new \IPS\Helpers\Form\YesNo( 'room_auto_msg_on', $this->auto_msg_on, FALSE, array(
			'togglesOn' => array( 'room_auto_msg', 'room_auto_msg_user' )
		) ) );
		$form->add( new \IPS\Helpers\Form\Member( 'room_auto_msg_user', $this->auto_msg_user > 0 ? \IPS\Member::load( $this->auto_msg_user ) : NULL, TRUE, array( 
			'multiple' 	=> 1
		), NULL, NULL, NULL, 'room_auto_msg_user' ) );
		$form->add( new \IPS\Helpers\Form\Stack( 'room_auto_msg', $this->auto_msg ? json_decode( $this->auto_msg, TRUE ) : array(), TRUE, array( 
			'stackFieldType' 	=> 'IPS\chatbox\Form\AutoMSG',
			'maxItems'			=> 5,
		), NULL, NULL, NULL, 'room_auto_msg' ) );
		
		# Nexus
		if ( \IPS\Application::appIsEnabled('nexus') )
		{		
			$form->addTab( 'chatbox_header_nexus' );
			$form->add( new \IPS\Helpers\Form\YesNo( 'room_nexus_on', $this->nexus_on, FALSE, array( 'togglesOn' => array( 'room_nexus_donate_can_add', 'room_nexus_donate_goal', 'room_nexus_donate_superchat' ) ) ) );		
			$form->add( new \IPS\Helpers\Form\Select( 'room_nexus_donate_can_add', $this->nexus_donate_can_add ? array_filter( explode( ',', $this->nexus_donate_can_add ) ) : FALSE, FALSE, array( 'options' => $noGuest, 'parse' => 'normal', 'multiple' => TRUE ), NULL, NULL, NULL, 'room_nexus_donate_can_add' ) );			
			$form->add( new \IPS\Helpers\Form\Node( 'room_nexus_donate_goal', $this->nexus_donate_goal ?:0, TRUE, array( 'class' => '\IPS\nexus\Donation\Goal' ), NULL, NULL, NULL, 'room_nexus_donate_goal' ) );
			$form->add( new \IPS\Helpers\Form\Stack( 'room_nexus_donate_superchat', $this->nexus_donate_superchat ? json_decode( $this->nexus_donate_superchat, TRUE ) : array(), TRUE, array( 
				'stackFieldType' 	=> 'IPS\chatbox\Form\Donation',
				'maxItems'			=> 10,
			), NULL, NULL, NULL, 'room_nexus_donate_superchat' ) );			
		}
		
		# Advanced options
		$form->addTab( 'chatbox_header_adv' );
		$form->add( new \IPS\Helpers\Form\Number( 'room_auto_del', $this->auto_del, FALSE, array( 'min' => 0 ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('chatbox_suff_days') ) );
		
		# Permissions
		$form->addTab( 'room_tab_permissions' );
		$form->add( new \IPS\Helpers\Form\Select( 'room_can_manage', array_filter( explode( ',', $this->can_manage ) ), FALSE, array( 'options' => $groups, 'parse' => 'normal', 'multiple' => TRUE ) ) );		
		$form->add( new \IPS\Helpers\Form\Select( 'room_can_view', array_filter( explode( ',', $this->can_view ) ), FALSE, array( 'options' => $groups, 'parse' => 'normal', 'multiple' => TRUE ) ) );		
		$form->add( new \IPS\Helpers\Form\Select( 'room_can_chat', array_filter( explode( ',', $this->can_chat ) ), FALSE, array( 'options' => $groups, 'parse' => 'normal', 'multiple' => TRUE ) ) );
		$form->add( new \IPS\Helpers\Form\Select( 'room_can_see_online', array_filter( explode( ',', $this->can_see_online ) ), FALSE, array( 'options' => $groups, 'parse' => 'normal', 'multiple' => TRUE ) ) );
		$form->add( new \IPS\Helpers\Form\Select( 'room_can_archive', array_filter( explode( ',', $this->can_archive ) ), FALSE, array( 'options' => $groups, 'parse' => 'normal', 'multiple' => TRUE ) ) );
		$form->add( new \IPS\Helpers\Form\Select( 'room_can_bypass_flood', array_filter( explode( ',', $this->can_bypass_flood ) ), FALSE, array( 'options' => $groups, 'parse' => 'normal', 'multiple' => TRUE ) ) );
	}
	
	/**
	 * [Node] Format form values from add/edit form for save
	 *
	 * @param	array	$values	Values from the form
	 * @return	array
	 */
	public function formatFormValues( $values )
	{
		if ( !$this->_id )
		{
			$this->save();
			\IPS\File::claimAttachments( 'chatbox-room-new-rules', $this->id, NULL, 'chatbox_rules' );
		}
		
		# Custom Language Strings
		if ( array_key_exists( 'room_name', $values ) )
		{
			\IPS\Lang::saveCustom( 'chatbox', "chatbox_room_{$this->_id}", $values['room_name'] );
			
			if ( \is_array( $values['room_name'] ) )
			{
				reset( $values['room_name'] );
				$this->seo_name = \IPS\Http\Url\Friendly::seoTitle( $values['room_name'][ key( $values['room_name'] ) ] );
			}
			else
			{
				$this->seo_name = \IPS\Http\Url\Friendly::seoTitle( $values['room_name'] );
			}
			
			unset( $values['room_name'] );
		}
		
		if ( array_key_exists( 'room_description', $values ) )
		{
			\IPS\Lang::saveCustom( 'chatbox', "chatbox_room_{$this->_id}_description", $values['room_description'] );	
			unset( $values['room_description'] );
		}
		
		# Rules
		if ( array_key_exists( 'room_rules', $values ) )
		{
			\IPS\Lang::saveCustom( 'chatbox', "chatbox_room_{$this->_id}_rules", $values['room_rules'] );
			unset( $values['room_rules'] );
		}
		
		# Sound
		$this->notifile = $values['room_notifile'] ? (string) $values['room_notifile'] : NULL;
		unset( $values['room_notifile'] );
		
		# Auto message
		$this->auto_msg_user = $values['room_auto_msg_user']->member_id;
		unset( $values['room_auto_msg_user'] );
		
		$stackData = array();
		
		foreach( $values['room_auto_msg'] as $stack )
		{
			$stack['time'] = ( $stack['time'] >= 10 ) ? $stack['time'] : 10;
			$stack['last'] = time();
			if ( $stack['msg'] )
			{
				$stackData[] = $stack;
			}				
		}

		$this->auto_msg = $stackData ? json_encode( $stackData ) : NULL;
		unset( $values['room_auto_msg'] );
		
		# Nexus
		if ( \IPS\Application::appIsEnabled( 'nexus' ) )
		{
			$this->nexus_donate_goal = $values['room_nexus_donate_goal'] ? $values['room_nexus_donate_goal']->id : 0;
			unset( $values['room_nexus_donate_goal'] );
			
			$scData = array();
			
			foreach( $values['room_nexus_donate_superchat'] as $sc )
			{
				$sc['color'] = $sc['color'];
				$sc['amount'] = $sc['amount'];
				if ( $sc['color'] && $sc['amount'] > 0 )
				{
					$scData[] = $sc;
				}				
			}

			$this->nexus_donate_superchat = $scData ? json_encode( $scData ) : NULL;
			unset( $values['room_nexus_donate_superchat'] );	

			if ( isset( \IPS\Data\Store::i()->chatbox_rooms_data ) )
			{
				unset( \IPS\Data\Store::i()->chatbox_rooms_data );
			}			
		}

		# Other options
		foreach( \array_keys( $values ) as $key )
		{
			if ( \mb_strpos( $key, 'chatbox_' ) !== FALSE )
			{
				$name = \str_replace( 'chatbox_', '', $key );
				$dataOpts[ $name ] = $key == "chatbox_sys_bot" ? $values[ $key ]->member_id : $values[ $key ];
				unset( $values[ $key ] );
			}
		}

		if ( $dataOpts )
		{
			$this->options = json_encode( $dataOpts );
		}
		
		# Save
		$this->save();
		
		# Pass to parent
		return parent::formatFormValues( $values );
	}
	
	/*-------------------------------------------------------------------------*/
	// Options
	/*-------------------------------------------------------------------------*/		
	protected $_opts = NULL;
	
	public function options()
	{
		if ( $this->_opts[ $this->id ] !== NULL )
		{
			return $this->_opts[ $this->id ];
		}
		
		$data = array();
		
		if ( $this->club() )
		{
			$data = json_decode( \IPS\Settings::i()->room_clubs_configs, TRUE );
			$sound = \IPS\Settings::i()->room_clubs_notifile;		
		}
		else
		{
			$data = json_decode( $this->options, true );
			$sound = $this->notifile;			
		}

		if ( !$data )
		{
			$data = array (
				'style' 				=> 'standard',
				'skin' 					=> 'white',
				'height' 				=> 300,
				'sort' 					=> 'asc',
				'limit' 				=> 20,
				'interval' 				=> 10000,
				'flood' 				=> 5,
				'length' 				=> 300,
			);
		}
		
		if ( $sound )
		{
			try
			{
				$data['soundfile'] = (string) \IPS\File::get( 'chatbox_Nofifile', $sound )->url;
			}
			catch( \Exception $ex ) { }			
		}

		$this->_opts[ $this->id ] = $data;
		
		return $this->_opts[ $this->id ];		
	}

	/*-------------------------------------------------------------------------*/
	// Auto messages
	/*-------------------------------------------------------------------------*/		
	protected $_autoMsg = NULL;
	
	public function autoMsgData()
	{
		if ( $this->auto_msg_on != 1  || !$this->auto_msg )
		{
			return NULL;
		}
		
		if ( $this->_autoMsg[ $this->id ] !== NULL )
		{
			return $this->_autoMsg[ $this->id ];
		}
		
		$mem = \IPS\Member::load( $this->auto_msg_user );
		
		$data['bot'] = array(
			'chatterID' 		=> $mem->member_id,
			'chatterName' 		=> $mem->name,
			'chatterGroup' 		=> $mem->member_group_id,
			'chatterKey' 		=> "user@" . $mem->member_id,
			'chatterPhoto' 		=> (string) $mem->photo,
			'chatterNameFormat' => $mem->member_group_id ? \IPS\Member\Group::load( $mem->member_group_id )->formatName( $mem->name ) : $mem->name,
		);
		
		$data['messages'] = json_decode( $this->auto_msg, TRUE );
		$this->_autoMsg[ $this->id ] = $data;
		return $this->_autoMsg[ $this->id ];		
	}
	
	/**
	 * [ActiveRecord] Delete Record
	 *
	 * @return	void
	 */
	public function delete()
	{
		\IPS\File::unclaimAttachments( 'chatbox_Room', $this->id );
		
		# Delete uploaded sound
		try
		{
			\IPS\File::get( 'chatbox_Nofifile', $this->notifile )->delete();
		}
		catch( \Exception $ex ) { }
		
		# Delete language bits
		foreach ( array( 'room_name' => "chatbox_room_{$this->_id}", 'room_rules' => "chatbox_room_{$this->id}_rules" ) as $fieldKey => $langKey )
		{
			\IPS\Lang::deleteCustom( 'chatbox', $langKey );
		}
		
		# Delete cache
		$roomData = 'chatbox_room_' . $this->id;
		if ( isset( \IPS\Data\Store::i()->$roomData ) )
		{
			unset( \IPS\Data\Store::i()->$roomData );
		}
		if ( isset( \IPS\Data\Store::i()->chatbox_rooms_data ) )
		{
			$cacheMsg = \IPS\Data\Store::i()->chatbox_rooms_data;
			unset( $cacheMsg[ $this->id ] );
			\IPS\Data\Store::i()->chatbox_rooms_data = $cacheMsg;
		}
		
		# Delete messages
		$this->cleanRoom();
		
		# Delete room
		parent::delete();
	}
	
	/**
	 * Delete all messages in room
	 *
	 * @return	void
	 */	
	public function cleanRoom()
	{
		\IPS\Task::queue( 'chatbox', 'deleteMsg', array( 'room' => $this->id ), 4 );
	}

	/* !Clubs */
	
	/**
	 * Get acp language string
	 *
	 * @return	string
	 */
	public static function clubAcpTitle()
	{
		return 'chatbox_room';
	}
	
	/**
	 * Set form for creating a node of this type in a club
	 *
	 * @param	\IPS\Helpers\Form	$form	Form object
	 * @return	void
	 */
	public function clubForm( \IPS\Helpers\Form $form )
	{
		$itemClass = static::$contentItemClass;

		$form->addTab( 'room_tab_general' );
		$form->add( new \IPS\Helpers\Form\Text( 'club_node_name', $this->_id ? $this->_title : NULL, TRUE, array( 'maxLength' => 255 ) ) );
		$form->add( new \IPS\Helpers\Form\YesNo( 'room_show_rules', $this->show_rules, FALSE, array( 'togglesOn' => array('room_rules') ) ) );
		$form->add( new \IPS\Helpers\Form\Translatable( 'room_rules', NULL, FALSE, array( 'app' => 'chatbox', 'key' => ( $this->id ? "chatbox_room_{$this->id}_rules" : NULL ), 'editor' => array( 'app' => 'chatbox', 'key' => 'Room', 'autoSaveKey' => ( $this->id ? "chatbox-room-{$this->id}-rules" : "chatbox-room-new-rules" ), 'attachIds' => $this->id ? array( $this->id, NULL, 'chatbox_rules' ) : NULL, 'minimize' => 'chatbox_rules_placeholder' ) ), NULL, NULL, NULL, 'room_rules' ) );
	}
	
	/**
	 * Class-specific routine when saving club form
	 *
	 * @param	\IPS\Member\Club	$club	The club
	 * @param	array				$values	Values
	 * @return	void
	 */
	public function _saveClubForm( \IPS\Member\Club $club, $values )
	{
		# Name
		if ( $values['club_node_name'] )
		{
			\IPS\Lang::saveCustom( 'chatbox', "chatbox_room_{$this->_id}", $values['club_node_name'] );
			$this->seo_name = \IPS\Http\Url\Friendly::seoTitle( $values['club_node_name'] );
		}		

		# Rules
		$this->show_rules = (int) $values['room_show_rules'];
		unset( $values['room_show_rules'] );
		
		if ( array_key_exists( 'room_rules', $values ) )
		{
			\IPS\Lang::saveCustom( 'chatbox', "chatbox_room_{$this->_id}_rules", $values['room_rules'] );
			
			unset( $values['room_rules'] );
		}

		if ( !$this->_id )
		{
			$this->save();
			\IPS\File::claimAttachments( 'chatbox-room-new-rules', $this->id, NULL, 'chatbox_rules' );
		}
	}
	
	/**
	 * Permissions
	 *
	 * @return	boolean
	 */	
	protected $_isBlocked = NULL;
	
	public function isBlocked()
	{
		if ( $this->_isBlocked !== NULL )
		{
			return $this->_isBlocked;
		}
		
		$data = $this->getBlockedData();
	
		$this->_isBlocked = false;
		
		if ( !$this->_canManage() )
		{
			foreach( $data as $k => $v )
			{
				if ( $k == $this->userKey() )
				{
					$this->_isBlocked = $v;
					break;						
				}
			}
		}
		
		return $this->_isBlocked;
	}
	
	public function getBlockedData()
	{
		$data = array();
		
		if ( $this->blocked )
		{
			$data = json_decode( $this->blocked, true );
		}
		
		# Auto unblock
		$save = false;
		foreach( $data as $k => $v )
		{
			if ( $v['unblock'] > 0 && time() - $v['time'] >= $v['unblock']*60 )
			{
				$save = true;
				unset( $data[ $k ] );
			}
		}
		
		if ( $save )
		{
			$this->blocked = $data ? json_encode( $data ) : null;
			$this->save();		
		}
		
		return $data;
	}
	
	public function _canView( $member=NULL )
	{
		$member = $member ?: \IPS\Member::loggedIn();
		
		if ( $this->enabled != 1 )
		{
			return FALSE;
		}
		
		if ( $member->member_id > 0 && ( $member->restrict_post !== 0 || $member->temp_ban !== 0 ) )
		{
			return FALSE;
		}
		
		if ( $this->_canManage( $member ) OR $member->inGroup( explode( ",", $this->can_view ) ) )
		{
			return TRUE;		
		}
		
		$club = $this->club();
		
		if ( $club && $club->canRead() )
		{
			return TRUE;
		}		

		return FALSE;	
	}

	public function canChat( $member=NULL )
	{
		$member = $member ?: \IPS\Member::loggedIn();
		
		if ( $this->enabled != 1 )
		{
			return FALSE;
		}
		
		if ( $member->member_id > 0 && ( $member->restrict_post !== 0 || $member->temp_ban !== 0 ) )
		{
			return FALSE;
		}
		
		if ( $this->_canManage($member) OR $member->inGroup( explode( ",", $this->can_chat ) ) )
		{
			return TRUE;		
		}

		$club = $this->club();
		
		if ( $club && $club->canPost() )
		{
			return TRUE;
		}		
		
		return FALSE;		
	}	
	
	public function _canManage( $member=NULL )
	{
		$member = $member ?: \IPS\Member::loggedIn();
		

		$club = $this->club();
		
		if ( $club )
		{
			if ( $club->isLeader() || $club->isModerator() )
			{
				return TRUE;
			}	
		}
		
		if ( $member->isAdmin() || $member->inGroup( explode( ",", $this->can_manage ) ) )
		{
			return TRUE;		
		}

		return FALSE;		
	}
	
	public function canBypassFlood( $member=NULL )
	{
		$member = $member ?: \IPS\Member::loggedIn();
		
		if ( $this->club() )
		{
			$groups = \IPS\Settings::i()->room_clubs_can_bypass_flood;
		}
		else
		{
			$groups = $this->can_bypass_flood;
		}

		if ( $member->inGroup( explode( ",", $groups ) ) )
		{
			return TRUE;		
		}		
		
		return FALSE;		
	}
	
	public function canEditMsg( $chat, $member=NULL )
	{
		$member = $member ?: \IPS\Member::loggedIn();	
		
		if ( $member->group['g_edit_cutoff'] )
		{
			if( \IPS\DateTime::ts( $chat->time )->add( new \DateInterval( "PT{$member->group['g_edit_cutoff']}M" ) ) <= \IPS\DateTime::create() )
			{
				return FALSE;
			}
		}
			
		if ( $this->_canManage( $member ) || \IPS\chatbox\Rooms\Chat::modPermission( 'edit', NULL, $this ) )
		{
			return TRUE;		
		}

		if ( $chat->member_id > 0 && $member->member_id == $chat->member_id )
		{
			$perm = $member->_group['g_edit_posts'];
			return ( $perm == 1 OR \in_array( 'IPS\chatbox\Rooms\Chat', explode( ',', $perm ) ) ) ? TRUE : FALSE;			
		}
		
		return FALSE;		
	}
	
	public function _canDelete( $chatterID=0, $member=NULL )
	{
		$member = $member ?: \IPS\Member::loggedIn();
		
		if ( $this->_canManage( $member ) || \IPS\chatbox\Rooms\Chat::modPermission( 'delete', NULL, $this ) )
		{
			return TRUE;		
		}
		
		if ( $chatterID > 0 && $member->member_id == $chatterID )
		{
			$perm = $member->_group['g_delete_own_posts'];
			return ( $perm == 1 OR \in_array( 'IPS\chatbox\Rooms\Chat', explode( ',', $perm ) ) ) ? TRUE : FALSE;
		}
		
		return FALSE;		
	}		
	
	public function _canSeeOnline( $member=NULL )
	{
		$member = $member ?: \IPS\Member::loggedIn();
		
		if ( $this->club() )
		{
			$groups = \IPS\Settings::i()->room_clubs_can_see_online;
		}
		else
		{
			$groups = $this->can_see_online;
		}

		if ( $member->inGroup( explode( ",", $groups ) ) )
		{
			return TRUE;		
		}		
		
		return FALSE;
	}
	
	public function _canPlaySong( $member=NULL )
	{
		if ( \IPS\Application::appIsEnabled( 'musicbox' ) && $this->options()['musicbox'] == 1 )
		{
			return TRUE;
		}

		return FALSE;
	}	
	
	public function _canViewArchive( $member=NULL )
	{
		$member = $member ?: \IPS\Member::loggedIn();
		
		if ( $this->club() )
		{
			$groups = \IPS\Settings::i()->room_clubs_can_archive;
		}
		else
		{
			$groups = $this->can_archive;
		}

		if ( $member->inGroup( explode( ",", $groups ) ) )
		{
			return TRUE;		
		}		
		
		return FALSE;
	}	

	public function onDonate()
	{
		if ( ! \IPS\Application::appIsEnabled( 'nexus' ) || $this->nexus_on != 1 || $this->nexus_donate_goal == 0 )
		{
			return FALSE;
		}

		return TRUE;		
	}
	
	public function canDonate( $member=NULL )
	{
		$member = $member ?: \IPS\Member::loggedIn();
		
		if ( ! $this->onDonate() || !$member->inGroup( explode( ",", $this->nexus_donate_can_add ) ) || !$member->member_id  )
		{
			return FALSE;
		}

		return TRUE;	
	}
	
	public function canUpload( $member=NULL )
	{
		$member = $member ?: \IPS\Member::loggedIn();
		
		$opts = $this->options();
		
		if ( !$opts['allow_img'] || $opts['use_editor'] )
		{
			return FALSE;
		}
		
		if ( $this->club() )
		{
			$groups = \IPS\Settings::i()->room_clubs_can_upload;
		}
		else
		{
			$groups = $this->can_upload;
		}

		if ( $member->inGroup( explode( ",", $groups ) ) )
		{
			return TRUE;		
		}

		return FALSE;	
	}
	
	/**
	 * Get the rules
	 *
	 * @return	string
	 */
	public function get__rules()
	{
		if ( \IPS\Member::loggedIn()->language()->checkKeyExists( 'chatbox_room_' . $this->_id . '_rules' ) )
		{
			return \IPS\Member::loggedIn()->language()->addToStack( 'chatbox_room_' . $this->_id . '_rules' );
		}		
	}
	
	/**
	 * Get the rules
	 *
	 * @return	string
	 */
	public function get__description()
	{
		if ( \IPS\Member::loggedIn()->language()->checkKeyExists( 'chatbox_room_' . $this->_id . '_description' ) )
		{
			return \IPS\Member::loggedIn()->language()->addToStack( 'chatbox_room_' . $this->_id . '_description' );
		}
	}	

	
	/**
	 * [Node] Set whether or not this node is enabled
	 *
	 * @param	bool|int	$enabled	Whether to set it enabled or disabled
	 * @return	void
	 */
	public function set__enabled( $enabled )
	{
		parent::set__enabled( $enabled );
		self::cacheAjax( 'lastUpdateRooms', true );
	}
	
	/**
	 * [Node] Set whether or not this node is disabled
	 *
	 * @param	bool|int	$disabled	Whether to set it enabled or disabled
	 * @return	void
	 */
	public function set__disabled( $disabled )
	{
		parent::set__disabled( $disabled );
		self::cacheAjax( 'lastUpdateRooms', true );
	}		

	/*-------------------------------------------------------------------------*/
	// Update cache for ajax requests
	/*-------------------------------------------------------------------------*/	
	public static function cacheAjax( $type, $reset=false )
	{
		$cache = array();
		
		if ( isset( \IPS\Data\Store::i()->chatbox_ajax ) )
		{
			$cache = \IPS\Data\Store::i()->chatbox_ajax;
		}
		
		if ( !$cache[ $type ] || $reset )
		{
			$cache[ $type ] = time();
			\IPS\Data\Store::i()->chatbox_ajax = $cache;
		}

		return $cache[ $type ];
	}
	
	/*-------------------------------------------------------------------------*/
	// Get online users
	/*-------------------------------------------------------------------------*/	
	public function online()
	{
		$opts 		= $this->options();
		$chatters 	= $this->getChatters( FALSE );
		$total 		= $chatters ? \count( $chatters ) : 0;
		
		if ( $total > 0 )
		{
			$cnt = 0;
			$checkedPing = false;

			foreach( $chatters as $k => $u )
			{
				if ( !$checkedPing )
				{
					$checkedPing = true;
					if ( $u['last_ping'] <= \IPS\DateTime::create()->sub( new \DateInterval( 'PT300S' ) )->getTimeStamp() )
					{
						return 0;
					}					
				}
				if ( ( !$u['is_anon'] || !$opts['hideAnon'] ) || ( $u['chatterID'] == \IPS\Member::loggedIn()->member_id ) )
				{
					$cnt++;
				}
			}
			
			if ( \IPS\Member::loggedIn()->isAdmin() )
			{
				return $total;
			}
			
			return $cnt;
		}
		
		return 0;
	}
		
	/*-------------------------------------------------------------------------*/
	// Get info from datastore
	/*-------------------------------------------------------------------------*/	
	public function getChatters( $sorting=TRUE )
	{
		$datastoreName = 'chatbox_room_' . $this->id;
		
		$chatters = array();

		if ( isset( \IPS\Data\Store::i()->$datastoreName ) )
		{
			$chatters = \IPS\Data\Store::i()->$datastoreName;
			
			if ( $sorting )
			{
				$myKey = $this->userKey();
				
				$me = $chatters[ $myKey ];
				
				if ( $me )
				{
					unset( $chatters[ $myKey ] );
				}
				
				if ( $chatters )
				{
					uasort($chatters, function ($row1, $row2) {
						return $row2['last_ping'] - $row1['last_ping'];
					});			
					$users = $me ? array_merge( array( $myKey => $me ), $chatters ) : $chatters;
				}
				else
				{
					$users = $me ? array( $myKey => $me ) : array();
				}
				
				$chatters = $users;
			}			
		}

		# Add CHAT button to online list
		if ( \in_array( \IPS\Request::i()->do, array( 'getMSG', 'chattersList' ) ) && \IPS\chatbox\Member\Conversation::_canStart() )
		{
			foreach( $chatters as $uKey => $u )
			{
				if ( $u['chatterID'] == 0 || \IPS\Member::loggedIn()->member_id == $u['chatterID'] || ! \in_array( $u['chatterGroup'], explode( ",", \IPS\Settings::i()->chatbox_con_get ) ) || \in_array( $u['chatterGroup'], explode( ",", \IPS\Settings::i()->chatbox_con_ban_groups ) ) || \in_array( $u['chatterID'], explode( ",", \IPS\Settings::i()->chatbox_con_ban_users ) ) )
				{
					continue;
				}
				$chatters[ $uKey ]['privateChat'] = true;
			}
		}
		
		return $chatters;
	}
	
	public function saveChatters( $chatters )
	{
		$datastoreName = 'chatbox_room_' . $this->id;
		\IPS\Data\Store::i()->$datastoreName = $chatters;
		
		self::cacheAjax( 'lastUpdateRooms', true );
	}

	/*-------------------------------------------------------------------------*/
	// Remove inactive users
	/*-------------------------------------------------------------------------*/		
	public function removeInactiveUsers()
	{
		$opts = $this->options();
		
		$chatters = $this->getChatters();
		
		$canSave = false;
		
		foreach( $chatters as $k => $user )
		{
			if ( $user['last_ping'] <= \IPS\DateTime::create()->sub( new \DateInterval( 'PT300S' ) )->getTimeStamp() )
			{
				if ( $this->options()['online_on'] )
				{
					if ( !$user['is_anon'] || !$opts['hideAnon'] )
					{
						$isGuest = (int) $user['chatterID'] > 0 ? false : true;
						$userName = $isGuest ? $user['chatterName'] : \IPS\Theme::i()->getTemplate( 'room', 'chatbox', 'front' )->chatterName(
							\IPS\Member\Group::load( $user['chatterGroup'] )->formatName( $user['chatterName'] ), 
							$user['chatterID'], 
							NULL 
						);					
						$this->addSystemMsg( 'leave', $userName, $isGuest, $user['last_ping'] + 300 );
					}
				}
				unset( $chatters[$k] );
				$canSave = true;
			}
		}
		
		if ( $canSave )
		{
			$this->saveChatters( $chatters );
		}
	}
	
	/*-------------------------------------------------------------------------*/
	// Get user info
	/*-------------------------------------------------------------------------*/	
	protected $_userInfo = NULL;
	
	public function userInfo()
	{
		if ( $this->_userInfo !== NULL )
		{
			return $this->_userInfo;
		}
		
		$key = $this->userKey();
		$mem = \IPS\Member::loggedIn();
		
		if ( $mem->member_id > 0 )
		{
			$chatterID = $mem->member_id;
			$chatterName = $mem->name;
			$chatterGroup = $mem->member_group_id;
			$chatterPhoto = (string) $mem->photo;
			$is_anon = $mem->isOnlineAnonymously();
		}
		else
		{
			$chatterID = 0;
			$chatterName = $this->checkName( str_replace( 'guest@', '', $key ) );
			$chatterGroup = \IPS\Settings::i()->guest_group;
			$is_anon = false;
		}

		$chatterNameFormat = \IPS\Application::load('chatbox')->userGroupFormat( $mem->group, $chatterName );
		
		$chatter = array(
			'key'				=> $key,
			'chatterID'			=> $chatterID,
			'chatterName'		=> $chatterName,
			'chatterGroup' 		=> $chatterGroup,
			'chatterPhoto'		=> $chatterPhoto,
			'chatterNameFormat'	=> $chatterNameFormat,
			'last_ping'			=> time(),
			'last_chat'			=> 0,
			'is_anon'			=> $is_anon,
			'ip'				=> \IPS\Request::i()->ipAddress(),
		);
			
		$this->_userInfo = $chatter;
		
		return $this->_userInfo;
	}
	
	/*-------------------------------------------------------------------------*/
	// Mark message as edited or deleted
	/*-------------------------------------------------------------------------*/
	public function markMsg( $id, $content=NULL )
	{
		$datastoreName = 'chatbox_room_' . $this->id;
		
		if ( ! \IPS\Data\Store::i()->$datastoreName )
		{
			return;
		}
		
		foreach( \IPS\Data\Store::i()->$datastoreName as $key => $val )
		{
			if ( $key != $this->userKey() )
			{
				$val['update'][ $id ] = $content;
			}
			
			$cache[ $key ] = $val;
		}
		
		$this->saveChatters( $cache );
	}
	
	/*-------------------------------------------------------------------------*/
	// Check input name
	/*-------------------------------------------------------------------------*/		
	public function checkName( $txt )
	{
		$name = stripslashes( htmlspecialchars( $txt ) );
		
		$length = mb_strlen($name);

		if ( \is_numeric($name) || !ctype_alnum($name) || !( (3 <= $length) && ($length <= 25)) )
		{
			\IPS\Request::i()->setCookie( 'chatbox_guestName', null );
		
			if ( \IPS\Request::i()->isAjax() )
			{
				\IPS\Output::i()->json( array( 
					'type' 		=> 'errorGuestName', 
					'message' 	=> \IPS\Member::loggedIn()->language()->addToStack( 'chatbox_error_name' ),
				) );
				exit;
			}
			exit;
		}
		else
		{
			\IPS\Request::i()->setCookie( 'chatbox_guestName', $name, \IPS\DateTime::create()->add( new \DateInterval( 'P3D' ) ) );
		}
		
		return $name;
	}	
	
	/*-------------------------------------------------------------------------*/
	// Generate key for user
	/*-------------------------------------------------------------------------*/		
	protected $_userKey = NULL;
	
	public function userKey()
	{	
		if ( $this->_userKey !== NULL )
		{
			return $this->_userKey;
		}
		
		if ( \IPS\Member::loggedIn()->member_id > 0 )
		{
			$key = "user@" . \IPS\Member::loggedIn()->member_id;	
		}
		else
		{
			$key = "guest@" . \IPS\Request::i()->cookie['chatbox_guestName'];	
		}
		
		$this->_userKey = $key;
		return $this->_userKey;
	}

	/*-------------------------------------------------------------------------*/
	// Get announcement
	/*-------------------------------------------------------------------------*/	
	public function announcement()
	{
		$lang = \IPS\Member::loggedIn()->language();
		
		if ( $lang->checkKeyExists( 'chatbox_room_' . $this->id . '_announcement_title' ) )
		{
			$title = $lang->get( 'chatbox_room_' . $this->id . '_announcement_title' );
		}
		if ( $lang->checkKeyExists( 'chatbox_room_' . $this->id . '_announcement' ) )
		{
			$msg = $lang->get( 'chatbox_room_' . $this->id . '_announcement' );
		}

		return array( $title, $msg, $this->announcement_full );
	}	
	
	/*-------------------------------------------------------------------------*/
	// Add system msg
	/*-------------------------------------------------------------------------*/		
	public function addSystemMsg( $type, $content=NULL, $isGuest=TRUE, $time=NULL )
	{
		if ( $type == 'announcement' )
		{
			if ( !$this->announcement()[0] || $this->announcement()[0] == '' )
			{
				$content = 'remove';
			}
		}
		
		if ( $type == 'join' || $type == 'leave' )
		{
			if ( !$content )
			{
				return;
			}
			if ( $isGuest )
			{
				$guestName = $content;
			}
		}

		$t = $time ? $time : time();

		$id = \IPS\Db::i()->insert( 'chatbox_room_posts', array(
			'chat_room'			=> $this->id,
			'chat_member_id'	=> -1,
			'chat_guestName'	=> $guestName,
			'chat_content'		=> $content,
			'chat_sys'			=> $type,
			'chat_time'			=> $t,
		) );

		$this->cacheLastMsg( $id, $t );
	}
	
	/*-------------------------------------------------------------------------*/
	// Add notification for new registration
	/*-------------------------------------------------------------------------*/	
	public static function newRegNotify( $member )
	{
		# Options
		$opts = json_decode( \IPS\Settings::i()->chatbox_notifications, TRUE );	
		if ( !$opts['register_notify'] )
		{
			return;
		}
		
		# Language
		$lang = \IPS\Member::loggedIn()->language();

		# Format system message
		$authorUrl = \IPS\Theme::i()->getTemplate( 'room', 'chatbox', 'front' )->chatterName(
			\IPS\Member\Group::load( $member->member_group_id )->formatName( $member->name ), 
			$member->member_id, 
			NULL 
		);
		
		$data = array( $authorUrl );
		$time = time();
		
		$id = \IPS\Db::i()->insert( 'chatbox_room_posts', array(
			'chat_room'					=> 0,
			'chat_in_multiple_rooms'	=> $opts['rooms'],
			'chat_member_id'			=> -1,
			'chat_content'				=> json_encode( $data ),
			'chat_sys'					=> 'registration',
			'chat_time'					=> $time,
		) );
		
		// Cache latest id for all rooms
		if ( $opts['rooms'] )
		{
			foreach( \IPS\chatbox\Rooms\Room::getStore() as $row )
			{
				if ( \in_array( $row['room_id'], explode( ",", $opts['rooms'] ) ) )
				{
					$room = \IPS\chatbox\Rooms\Room::constructFromData( $row );
					$room->cacheLastMsg( $id, $time, $row['room_id'] );
				}				
			}			
		}
	}
	
	/*-------------------------------------------------------------------------*/
	// Add notification for new content
	/*-------------------------------------------------------------------------*/	
	public static function newContentNotify( $obj )
	{
		# Options
		$opts = json_decode( \IPS\Settings::i()->chatbox_notifications, TRUE );	
		
		# Language
		$lang = \IPS\Member::loggedIn()->language();

		# Is enable?
		$iName = str_replace( '\\', '_', \get_class( $obj ) );
		if ( !$opts[ $iName ] )
		{
			return;
		}
		
		# Content Item
		$item = ( $obj instanceof \IPS\Content\Item ) ? $obj : $obj->item();
		
		# Check container
		if ( isset( $item::$containerNodeClass ) )
		{
			$con = $item->containerWrapper( TRUE );
			$cName = str_replace( '\\', '_', $item::$containerNodeClass );
			if ( $opts[ $cName ] !== '0' && ! \in_array( $con->_id, explode( ',', $opts[ $cName ] ) ) )
			{
				return;
			}
		}
		
		# Format system message
		if( $obj instanceof \IPS\Content\Item && $con )
		{
			$conUrl = " " . $lang->get( 'chatbox_sys_con_in' ) . " " . \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( (string) $con->url(), TRUE, $lang->get( $con->_titleLanguageKey ), FALSE );	
		}

		$newUrl = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( (string) $obj->url(), TRUE, $item->mapped( 'title' ), FALSE );
		
		if ( $item->isAnonymous() )
		{
			$authorUrl = \IPS\Member::loggedIn()->language()->get( 'post_anonymously_placename' );
		}
		else
		{
			$author = $obj->author();
			$authorUrl = \IPS\Theme::i()->getTemplate( 'room', 'chatbox', 'front' )->chatterName(
				\IPS\Member\Group::load( $obj->author()->member_group_id )->formatName( $obj->author()->name ), 
				$obj->author()->member_id, 
				NULL 
			);
		}
		$oClass = str_replace( '\\', '_', \get_class( $obj ) );

		if ( mb_strpos( $oClass, '_cms_' ) )
		{
			if ( mb_strpos( $oClass, '_Records_Comment' ) )
			{
				$idf = $lang->get( 'chatbox_idf_comment' );
			}
			elseif ( mb_strpos( $oClass, '_Records_Review' ) )
			{
				$idf = $lang->get( 'chatbox_idf_review' );
			}
			else
			{
				$idf = $lang->get( str_replace( "_su_", "_ia_", $obj::$title ) );
			}
		}
		else
		{
			$idf = $lang->get( '__indefart_' . $obj::$title );
		}
		
		$data = array( 
			$authorUrl, $idf, $conUrl . ": " . $newUrl
		);
		
		$time = time();
		$id = \IPS\Db::i()->insert( 'chatbox_room_posts', array(
			'chat_room'					=> 0,
			'chat_in_multiple_rooms'	=> $opts['rooms'],
			'chat_member_id'			=> -1,
			'chat_content'				=> json_encode( $data ),
			'chat_sys'					=> 'content',
			'chat_time'					=> $time,
		) );
		
		# Cache the last msg
		if ( $opts['rooms'] )
		{
			foreach( \IPS\chatbox\Rooms\Room::getStore() as $row )
			{
				if ( \in_array( $row['room_id'], explode( ",", $opts['rooms'] ) ) )
				{
					$room = \IPS\chatbox\Rooms\Room::constructFromData( $row );
					$room->cacheLastMsg( $id, $time, $row['room_id'] );
				}				
			}
		}
	}	

	/*-------------------------------------------------------------------------*/
	// Cache latest ID
	/*-------------------------------------------------------------------------*/	
	public function cacheLastMsg( $id=0, $time=NULL, $rID=0 )
	{
		if ( \IPS\Settings::i()->chatbox_cache != 1 || \IPS\CIC2 ) // disable cache
		{
			return array( -1, 0 );
		}
		
		$cache = array();
		
		if ( isset( \IPS\Data\Store::i()->chatbox_rooms_data ) )
		{
			$cache = \IPS\Data\Store::i()->chatbox_rooms_data;
		}
		
		$roomID = $rID > 0 ? $rID : $this->id;
		
		if ( !$cache[ $roomID ] || !$cache[ $roomID ]['lastChat'] )
		{
			$where[] = array( '( chat_room=? OR FIND_IN_SET( ?, chat_in_multiple_rooms ) )', $roomID, $roomID ); 
			try
			{
				$row 	= \IPS\Db::i()->select( '*', 'chatbox_room_posts', $where, 'chat_id DESC' )->first();
				$id 	= $row['chat_id'];
				$time 	= $row['chat_time'];
			}
			catch ( \UnderflowException $e ) {}
			
			$cache[ $roomID ] = array( 'lastChat' => $id, 'time' => $time );
			\IPS\Data\Store::i()->chatbox_rooms_data = $cache;
		}
	
		if ( $id > 0 && $cache[ $roomID ]['lastChat'] < $id )
		{
			$cache[ $roomID ]['lastChat'] = $id;
			$cache[ $roomID ]['time'] = $time;
			\IPS\Data\Store::i()->chatbox_rooms_data = $cache;
		}
	
		return $cache[ $roomID ];
	}	
	
	/*-------------------------------------------------------------------------*/
	// Cache donation
	/*-------------------------------------------------------------------------*/	
	public function cacheDonation()
	{
		$cache = array();
		if ( isset( \IPS\Data\Store::i()->chatbox_rooms_data ) )
		{
			$cache = \IPS\Data\Store::i()->chatbox_rooms_data;
		}
		if ( !$cache[ $this->id ] || !isset( $cache[ $this->id ]['donation'] ) )
		{
			try
			{
				$goal = \IPS\nexus\Donation\Goal::load( $this->nexus_donate_goal );
			}
			catch ( \OutOfRangeException $e )
			{
				$this->nexus_on = 0;
				$this->save();
			}
			foreach( \IPS\Db::i()->select( 'SUM(dl_amount) as amount, dl_member', 'nexus_donate_logs', array( 'dl_goal=?', $this->nexus_donate_goal ), NULL, NULL, 'dl_member' ) as $row )
			{
				$donation[ $row['dl_member'] ] = array( 
					'amount' 	=> $row['amount'], 
					'money' 	=> (string) new \IPS\nexus\Money( $row['amount'], $goal->currency ) 
				);
			}
			$cache[ $this->id ]['donation'] = $donation;
			\IPS\Data\Store::i()->chatbox_rooms_data = $cache;
		}

		return $cache[ $this->id ]['donation'];
	}
	
	/**
	 * Fetch available rooms
	 */
	protected static $_publicRooms = NULL;

	public static function publicRooms( $includeClubs = FALSE )
	{
		if ( static::$_publicRooms == NULL )
		{
			$rooms = array();
			
			foreach( \IPS\chatbox\Rooms\Room::getStore() as $row )
			{
				$room = \IPS\chatbox\Rooms\Room::constructFromData( $row );
				static::$_publicRooms[] = $room;
			}
		}
		
		foreach( static::$_publicRooms as $r )
		{
			if ( !$includeClubs && $r->club() )
			{
				continue;
			}

			if ( $r->enabled )
			{
				$data[] = $r;
			}			
		}
		
		return $data;
	}	

	/**
	 * Display chatbox
	 */
	public function jsVars()
	{
		# JS vars
		$opts = $this->options();

		$data = array(
			'roomname' 			=> $this->_title,
			'interval' 			=> (int) $opts['interval'],
			'flood' 			=> (int) $opts['flood'],
			'limit' 			=> (int) $opts['limit'],		
			'length' 			=> (int) $opts['length'],
			'height' 			=> $opts['height'],
			'sort' 				=> $opts['sort'],
			'style' 			=> $opts['style'],
			'giphy' 			=> $opts['giphy'],		
			'autogrow' 			=> $opts['autogrow'],		
			'allow_img' 		=> $opts['allow_img'] ? true : false,
			'allow_video' 		=> $opts['allow_video'] ? true : false,		
			'allow_audio' 		=> $opts['allow_audio'] ? true : false,		
			'allow_select_skins'=> $opts['allow_select_skins'] ? true : false,		
			'autoload' 			=> $opts['autoload'] ? true : false,		
			'canManage' 		=> $this->_canManage() ? true : false,
			'canBypassFlood' 	=> $this->canBypassFlood() ? true : false,
			'hideAnon' 			=> $opts['hideAnon'] ? true : false,
			'use_editor' 		=> $opts['use_editor'] ? true : false,
			'enter_editor' 		=> $opts['enter_editor'] ? true : false,	
			'auto_guest' 		=> $opts['auto_guest'] ? true : false,	
			'soundfile' 		=> $opts['soundfile']? $opts['soundfile'] : NULL,
			'musicbox' 			=> $this->_canPlaySong(),
			'song' 				=> $this->_canPlaySong() && $this->song ? $this->song : NULL,
			'automsg' 			=> $this->autoMsgData() ? $this->autoMsgData() : NULL,
			'alwaysON'			=> \IPS\Request::i()->full == '1' ? true : false,
			'lastchat' 			=> 0,
		);
		
		if ( $this->onDonate() && $this->nexus_donate_superchat )
		{
			$scData = json_decode( $this->nexus_donate_superchat, true );
			
			usort($scData, function ($v1, $v2) {
				return $v2['amount'] <=> $v1['amount'];
			});

			foreach( $scData as $row )
			{
				$sc[] = array( 'amount' => $row['amount'], 'color' => $row['color'] );
			}

			$data['superchat'] = $sc;
		}
		
		if ( $opts['sys_bot'] > 0 )
		{
			$bot = \IPS\Member::load( $opts['sys_bot'] );
			$data['bot_id'] = $bot->member_id;
			$data['bot_name'] = $bot->name;
			$data['bot_formatName'] = \IPS\Application::load('chatbox')->userGroupFormat( $bot->group, $bot->name );
			$data['bot_photo'] = $bot->photo;
			$data['bot_url'] = (string) $bot->url();
		}
		
		# Ajax when changing room
		if ( \IPS\Request::i()->isAjax() )
		{
			return $data;
		}
		
		# Return js vars
		$vars[ 'chatbox_' . $this->id ] = $data;
		\IPS\Output::i()->endBodyCode .= \IPS\Theme::i()->getTemplate( 'embed', 'chatbox', 'front' )->chatvars( $vars );
	}
	
	public function show( $widgetConf=NULL, $inGlobal=false, $openFromBar=false )
	{
		# Is Club enabled?
		if ( $this->club() && \IPS\Settings::i()->room_clubs != 1 )
		{
			return;
		}

		# JS Vars
		$this->jsVars();
		
		# Check rules
		if( $this->_canView() && $this->show_rules && \IPS\Request::i()->cookie[ 'chatbox_bypass_rule_' . $this->id ] != 1 )
		{
			$proceed = FALSE;

			$form = new \IPS\Helpers\Form( 'chatbox_rules', 'chatbox_accept_rules', $this->url() );
			$form->class = 'ipsForm_vertical';
			$form->hiddenValues['proceed'] = TRUE;

			if( $values = $form->values() )
			{
				if( $values['proceed'] )
				{
					$proceed = TRUE;
					
					\IPS\Request::i()->setCookie( 'chatbox_bypass_rule_' . $this->id, 1, \IPS\DateTime::create()->add( new \DateInterval( 'P3D' ) ) );
					
					$inGlobal = \IPS\Request::i()->inGlobal == 1 ? TRUE : FALSE;				
					$openFromBar = \IPS\Request::i()->openFromBar == 1 ? TRUE : FALSE;				
			
					if ( \IPS\Request::i()->inWidget )
					{
						$widgetConf = array( 'orientation' => \IPS\Request::i()->inWidget );
					}					
					
					\IPS\Output::i()->json( array( 'html' => $this->show( $widgetConf, $inGlobal, $openFromBar ) ) );
					exit;
				}
			}

			/* If we haven't agreed yet, show the form */
			if( !$proceed )
			{
				$this->showRules = $form;
				
				$ruleHTML = \IPS\Theme::i()->getTemplate( 'room', 'chatbox', 'front' )->room( $this, $widgetConf, $inGlobal, $openFromBar );
				
				if ( \IPS\Request::i()->full != '1' )
				{
					return $ruleHTML;
				}
				else
				{
					\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->blankTemplate( $ruleHTML ), 200, 'text/html' );
				}
			}
		}
		
		# Auto generate a name for guest
		// if ( $this->options()['auto_guest'] == 1 )
		// {
			// if ( ! \IPS\Member::loggedIn()->member_id && $this->_canView() && $this->canChat() && ! \IPS\Request::i()->cookie['chatbox_guestName'] )
			// {
				// $this->checkName( "Guest" . time() );
			// }
		// }
		
		# Disable sound by default?
		if ( \IPS\Settings::i()->chatbox_sound_disable == 1 && !isset( \IPS\Request::i()->cookie[ 'chatbox_nosound_' . $this->id ] ) )
		{
			\IPS\Request::i()->setCookie( 'chatbox_nosound_' . $this->id, 1, NULL, FALSE );
		}		
		
		# Output
		return \IPS\Theme::i()->getTemplate( 'room', 'chatbox', 'front' )->room( $this, $widgetConf, $inGlobal, $openFromBar );
	}

	/**
	 * Load Editor
	 */	
	public function editor( $chat=NULL )
	{
		# Options
		$opts = $this->options();
		
		# Form
		if ( $chat )
		{
			$btn = 'chatbox_edit';
		}
		else
		{
			$url = $this->url();
			$btn = 'chatbox_send';
		}
		
		$form = new \IPS\Helpers\Form( 'cbForm', $btn, $url );
		$form->class = 'ipsForm_vertical chatboxEditor';
		
		foreach ( \IPS\chatbox\Rooms\Chat::formElements( $chat, $this, $chat ? TRUE : FALSE ) AS $k => $element )
		{
			$form->add( $element );
		}
		
		# Save
		if ( $values = $form->values() )
		{
			$files = $values[ 'file_room_' . $this->id ];
			
			if ( !$chat )
			{
				# Check perm
				if ( ! $this->canChat() )
				{
					\IPS\Output::i()->json( array( 'type' => 'error', 'message' => \IPS\Member::loggedIn()->language()->addToStack( 'chatbox_error_no_perm_chat' ) ) );
				}
				if ( $this->isBlocked() )
				{
					\IPS\Output::i()->json( array( 'type' => 'isBlocked', 'message' => \IPS\Member::loggedIn()->language()->addToStack( 'chatbox_kick_out' ) ) );
				}
		
				# MSG
				$msg = $values[ 'chatMSG_' . $this->id ];
				
				# Check and parse msg
				if ( !$files || $msg )
				{
					$msg = \IPS\Application::load('chatbox')->checkMSG( $msg, $opts );
				}

				# Check user in room
				$chatters = $this->getChatters();
				
				$key = $this->userKey();
				
				if ( ! isset( $chatters[ $key ] ) )
				{
					$chatters = $this->addUser();
				}

				# Check Flood
				if ( $opts['flood'] > 0 && !$this->_canManage() && !$this->canBypassFlood() )
				{
					if ( $chatters[ $key ]['last_chat'] > 0 )
					{
						$lastChat = $chatters[ $key ]['last_chat'];
					}
					else
					{
						try
						{
							$lastChat = \IPS\Db::i()->select( 'chat_time', 'chatbox_room_posts', array( 'chat_member_id=? AND chat_room=?', \IPS\Member::loggedIn()->member_id, $this->id ), 'chat_time DESC', 1 )->first();
							$chatters[ $key ]['last_chat'] = $lastChat;
							$this->saveChatters( $chatters );							
						}
						catch ( \UnderflowException $e ) 
						{
							$lastChat = 0;
						}
					}

					if ( time() - $lastChat < $opts['flood'] )
					{
						$wait = $opts['flood'] - ( time() - $lastChat );
						\IPS\Output::i()->json( array( 'type' => 'error', 'message' => \IPS\Member::loggedIn()->language()->addToStack('chatbox_error_flood', FALSE, array( 'sprintf' => $wait ) ) ) );
					}
				}
						
				# Save message
				$values['chatbox_room'] = $this->id;
				$values['chatMSG_' . $this->id] = $msg;
				$chat = \IPS\chatbox\Rooms\Chat::createFromForm( $values, $this );

				if ( $this->canUpload() )
				{
					\IPS\Application::load('chatbox')->processUploadFiles( $files, 'room', $chat->id );
				}
				
				# Cache latest ID
				$this->cacheLastMsg( $chat->id, $chat->time );
				
				# Save last time chat to datastore
				$chatters[ $key ]['last_chat'] = $chat->time;
				$this->saveChatters( $chatters );	
				
				# Done
				\IPS\Output::i()->json( 'OK' );							
			}
			else
			{
				if ( \IPS\Request::i()->useForm == 1 )
				{
					$varmsg = "chatMSG_" . $this->id . "_" . $chat->id;
					$content = \IPS\Request::i()->$varmsg;
				}
				else 
				{
					$content = \IPS\Request::i()->newMsg;
				}

				# Check message
				if ( !$files || $content )
				{
					\IPS\Application::load('chatbox')->checkMSG( $content, $opts );
				}
				
				$values['chatMSG_' . $this->id ] = $content;
				unset( $values['chatMSG_' . $this->id . '_' . $chat->id ] );				

				# Save	
				$chat->processForm( $values );
				$chat->save();
				$chat->processAfterEdit( $values );	
				
				if ( $this->canUpload() )
				{
					\IPS\Application::load('chatbox')->processUploadFiles( $files, 'room', $chat->id );
					if ( $files )
					{
						$content = \IPS\Application::load('chatbox')->parseContent( $opts, $content ) . ' ' . (string) reset($files)->url;
					}
				}

				# Save to datastore to update for all online users
				$this->markMsg( $chat->id, $content );
			
				# Done
				if ( \IPS\Request::i()->useForm == 1 )
				{
					\IPS\Output::i()->redirect( isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : \IPS\Http\Url::internal( '' ) );
				}
				else
				{
					\IPS\Output::i()->json( array( 'content' => $content ) );
				}
			}
		}
		
		return $form;		
	}
	
	/**
	 * Show the first room in bar
	 */		
	public static function showPopupRoom()
	{
		if ( \IPS\Settings::i()->chatbox_bar_room != 1 )
		{
			return;
		}
		
		if ( \IPS\Settings::i()->chatbox_bar_con == 1 && \IPS\chatbox\Member\Conversation::_canUse() )
		{
			return;
		}
		
		try
		{
			$room = \IPS\chatbox\Rooms\Room::load( self::getMainRoom() );
			
			if ( $room->_canView() )
			{
				return (string) $room->show( NULL, TRUE );
			}
		}
		catch( \OutOfRangeException $e ) 
		{
			\IPS\Request::i()->setCookie( 'chatbox_inRoom', NULL );
		}	
	}
	
	protected static $_getGlobalRooms = NULL;
	
	public static function getGlobalRooms()
	{
		if ( static::$_getGlobalRooms !== NULL )
		{
			return static::$_getGlobalRooms;
		}	

		$rooms = self::publicRooms();
		
		if ( $rooms )
		{
			foreach( $rooms as $room )
			{
				if ( \in_array( $room->id, explode(",", \IPS\Settings::i()->chatbox_bar_show_rooms) ) && !$room->isBlocked() && $room->_canView() )
				{
					static::$_getGlobalRooms[] = $room;
				}
			}
		}		
		
		return static::$_getGlobalRooms;
	}
	
	protected static function getMainRoom()
	{
		if ( \IPS\Request::i()->cookie['chatbox_inRoom'] )
		{
			return \IPS\Request::i()->cookie['chatbox_inRoom'];
		}
		
		$rooms = self::getGlobalRooms();
		
		if ( $rooms )
		{
			return $rooms[0]->id;
		}

		return 0;
	}
	
	/*-------------------------------------------------------------------------*/
	// online users in all rooms
	/*-------------------------------------------------------------------------*/	
	public static function onlineInRooms()
	{
		$count = 0;
		
		foreach( \IPS\chatbox\Rooms\Room::publicRooms() as $room )
		{
			if ( $room->_canView() )
			{
				$count = $count + $room->online();
			}
		}

		return $count;
	}	

	/*-------------------------------------------------------------------------*/
	// add user to data list
	/*-------------------------------------------------------------------------*/	 
	public function addUser()
	{
		# Options
		$opts = $this->options();
		
		# Get chatters from datastore
		$chatters = $this->getChatters();
		
		$key = $this->userKey();

		# Add user to datastore
		if ( !isset( $chatters[ $key ] ) )
		{
			$chatters[ $key ] = $this->userInfo();
			
			if ( $this->options()['online_on'] && \IPS\Request::i()->joined != 1 )
			{
				if ( !$chatters[ $key ]['is_anon'] || !$opts['hideAnon'] )
				{
					$isGuest = (int) $chatters[ $key ]['chatterID'] > 0 ? false : true;
					
					$userName = $isGuest ? $chatters[ $key ]['chatterName'] : \IPS\Theme::i()->getTemplate( 'room', 'chatbox', 'front' )->chatterName(
						\IPS\Member\Group::load( $chatters[ $key ]['chatterGroup'] )->formatName( $chatters[ $key ]['chatterName'] ), 
						$chatters[ $key ]['chatterID'], 
						NULL 
					);
					
					$this->addSystemMsg( 'join', $userName, $isGuest );					
				}
			}
			
			$this->saveChatters( $chatters );
		}

		return $chatters;
	}	
	
}