<?php
/**
 * @brief		Chatbox+ Application Class
 * @author		<a href='https://ipsviet.com'>onlyME</a>
 * @copyright	(c) 2018 onlyME
 * @package		Invision Community
 * @subpackage	Chatbox+
 * @since		18 Sep 2018
 * @version		
 */
 
namespace IPS\chatbox;

/**
 * Chatbox+ Application Class
 */
class _Application extends \IPS\Application
{
	//===========================================================================
	// [Node] Get Icon
	//===========================================================================	
	protected function get__icon()
	{
		return 'comments';
	}	
	
	/**
	 * Default front navigation
	 *
	 * @code
	 	
	 	// Each item...
	 	array(
			'key'		=> 'Example',		// The extension key
			'app'		=> 'core',			// [Optional] The extension application. If ommitted, uses this application	
			'config'	=> array(...),		// [Optional] The configuration for the menu item
			'title'		=> 'SomeLangKey',	// [Optional] If provided, the value of this language key will be copied to menu_item_X
			'children'	=> array(...),		// [Optional] Array of child menu items for this item. Each has the same format.
		)
	 	
	 	return array(
		 	'rootTabs' 		=> array(), // These go in the top row
		 	'browseTabs'	=> array(),	// These go under the Browse tab on a new install or when restoring the default configuraiton; or in the top row if installing the app later (when the Browse tab may not exist)
		 	'browseTabsEnd'	=> array(),	// These go under the Browse tab after all other items on a new install or when restoring the default configuraiton; or in the top row if installing the app later (when the Browse tab may not exist)
		 	'activityTabs'	=> array(),	// These go under the Activity tab on a new install or when restoring the default configuraiton; or in the top row if installing the app later (when the Activity tab may not exist)
		)
	 * @endcode
	 * @return array
	 */
	public function defaultFrontNavigation()
	{
		return array(
			'rootTabs'		=> array(),
			'browseTabs'	=> array( array( 'key' => 'Chatbox' ) ),
			'browseTabsEnd'	=> array(),
			'activityTabs'	=> array()
		);
	}	
	
	/*-------------------------------------------------------------------------*/
	// Styles & Skins
	/*-------------------------------------------------------------------------*/	
	public function styles()
	{
		return array(
			'standard'		=> 'chatbox_standard',
			'condensed'		=> 'chatbox_condensed',
			'bubbles'		=> 'chatbox_bubbles',
			'talkie'		=> 'chatbox_talkie',
		);
	}	

	public function skins()
	{
		return array( 'white', 'black', 'aqua', 'maroon', 'navy', 'olive' );
	}	
	
	/*-------------------------------------------------------------------------*/
	// Check and parse message
	/*-------------------------------------------------------------------------*/	
	public function checkMSG( $msg, $opts )
	{
		# Format text
		$msg = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', "", $msg);
		$msg = preg_replace('/^<\?php(.*)(\?>)?$/s', '$1', $msg);
		
		if ( $opts['use_editor'] != 1 )
		{
			$msg = $this->formatText( $msg );
		}

		# Is empty?
		if ( !( isset( $msg ) && ( mb_strlen(trim($msg) ) > 0) ) )
		{
			\IPS\Output::i()->json( array( 'type' => 'error', 'message' => \IPS\Member::loggedIn()->language()->addToStack( 'chatbox_error_empty_msg' ) ) );		
		}
		
		# Check length
		if ( $opts['length'] && mb_strlen( $msg ) > $opts['length'] )
		{
			\IPS\Output::i()->json( array( 'type' => 'error', 'message' => \IPS\Member::loggedIn()->language()->addToStack('chatbox_error_maxlength', FALSE, array( 'sprintf' => $opts['length'] ) ) ) );			
		}
		
		# Link Filter
		if ( \IPS\Settings::i()->ipb_url_filter_option != 'none' )
		{
			preg_match_all('~[a-z]+://\S+~', $msg, $match);
			
			if ( $match[0] )
			{
				foreach( $match[0] as $url )
				{
					if ( !$this->checkURL( $url ) )
					{	
						\IPS\Output::i()->json( array( 'type' => 'error', 'message' => \IPS\Member::loggedIn()->language()->addToStack('chatbox_error_links', FALSE, array( 'sprintf' => $url ) ) ) );			
						break;
					}
				}
			}
		}	
			
		# Prevent flooding special chars
		$temp = preg_split('//u', $msg, null, PREG_SPLIT_NO_EMPTY);
		
		foreach( $temp as $a )
		{
			if ( preg_match( '/[^\w\s]+/u', $a ) )
			{
				$cnt++;
				if ( $cnt >= 8 )
				{
					\IPS\Output::i()->json( array( 'type' => 'error', 'message' => \IPS\Member::loggedIn()->language()->addToStack( 'chatbox_error_badchars' ) ) );
					exit;
				}
			}
			else
			{
				$cnt = 0;
			}
		}
		
		# Done
		return $msg;
	}	
		
	/*-------------------------------------------------------------------------*/
	// Text Format
	/*-------------------------------------------------------------------------*/	
	public function formatText( $txt )
	{
		# Remove tags	
		$txt = htmlspecialchars( $txt, ENT_DISALLOWED, 'UTF-8', FALSE );

		# Bad word
		$txt = $this->badWords( $txt );

		# Done
		return $txt;
	}
	
	
	/*-------------------------------------------------------------------------*/
	// Check bad words
	/*-------------------------------------------------------------------------*/
	protected function badWords( $txt )
	{
		if ( \IPS\Member::loggedIn()->group['g_bypass_badwords'] )
		{
			return $txt;
		}
		
		$looseProf = array();
		$exactProf = array();
		
		foreach( \IPS\core\Profanity::getProfanity() AS $profanity )
		{
			if ( $profanity->action == 'swap' )
			{
				if ( $profanity->m_exact )
				{
					$exactProf[ $profanity->type ] = $profanity->swop;
				}
				else
				{
					$looseProf[ $profanity->type ] = $profanity->swop;
				}
			}
		}
		
		foreach ( $exactProf as $bad => $good )
		{
			$txt = preg_replace( '/(^|\b|\s)' . preg_quote( $bad, '/' ) . '(\b|\s|!|\?|\.|,|$)/iu', "\\1" . $good . "\\2", $txt );
		}
		
		$txt = str_ireplace( array_keys( $looseProf ), array_values( $looseProf ), $txt );

		return $txt;
	}
		
	/*-------------------------------------------------------------------------*/
	// Check URL
	/*-------------------------------------------------------------------------*/
	protected function checkURL( $url )
	{
		$filter_action = \IPS\Settings::i()->url_filter_action;
		\IPS\Settings::i()->url_filter_action = 'block';
		$checked = \IPS\Text\Parser::isAllowedUrl( $url );
		\IPS\Settings::i()->url_filter_action = $filter_action;
		return $checked;
	}
	
	/*-------------------------------------------------------------------------*/
	// Parse Content
	/*-------------------------------------------------------------------------*/
	public function parseContent( $opts, $txt )
	{
		$txt = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', "", $txt);
		$txt = preg_replace('/^<\?php(.*)(\?>)?$/s', '$1', $txt);
		
		if ( !$opts['use_editor'] )
		{
			$txt = preg_replace('/' . \IPS\Text\Parser::EMOJI_REGEX . '/u', "<span class=\"ipsEmoji\">$0</span>", $txt);
			$txt = \IPS\Output::i()->replaceEmojiWithImages( $txt );
			$txt = preg_replace('#<img src="([^"]*)".*?/?>#i', ' $1 ', $txt);
		}
		else
		{
			$txt = \IPS\Output::i()->replaceEmojiWithImages( $txt );
		}
		
		return $txt;
	}

	/*-------------------------------------------------------------------------*/
	// Chatbox bar
	/*-------------------------------------------------------------------------*/
	public function chatBar()
	{
		if ( \IPS\Dispatcher::i()->controllerLocation != 'front' || \IPS\Request::i()->isAjax() )
		{
			return;
		}
		
		if ( \IPS\Request::i()->do == 'initialMfa' || \IPS\Request::i()->_mfaLogin > 0 || ( \IPS\Request::i()->app == 'core' && \IPS\Request::i()->controller == 'register' ) || ( \IPS\Request::i()->app == 'nexus' && \IPS\Request::i()->do == 'register' ) )
		{
			return;
		}
		
		$canShow = true;
		
		if ( isset( \IPS\Settings::i()->chatbox_bar_expages ) )
		{
			$inPage = (string) \IPS\Request::i()->url();

			foreach ( explode( "\n", str_replace( "\r", "", \IPS\Settings::i()->chatbox_bar_expages ) ) as $url )
			{
				if ( $url == $inPage || preg_match( "~^" . str_replace( '\*', '.*?', preg_quote( $url ) ) . "$~i", htmlspecialchars_decode( $inPage ) ) )
				{
					$canShow = false;
					break;
				}				
			}	
		}		
		
		if ( $canShow )
		{
			return \IPS\Theme::i()->getTemplate( 'global', 'chatbox', 'front' )->chatBar();
		}
	}
		
	/*-------------------------------------------------------------------------*/
	// create Title
	/*-------------------------------------------------------------------------*/		
	public function autoTitle( $msg )
	{
		$msg = strip_tags( $msg );
		$msg = preg_replace('/\r|\n/', ' ', $msg); // remove new lines
		$msg = preg_replace('/[\s]+/mu', ' ', $msg); // remove new lines
		$msg = trim( \mb_substr( $msg, 0, 40 ) );

		return $msg;
	}	
	

	/*-------------------------------------------------------------------------*/
	// Get user group format
	/*-------------------------------------------------------------------------*/	
	public function userGroupFormat( $data, $name )
	{
		$prefix = \is_object( $data ) ? $data->prefix : $data['prefix'];
		$suffix = \is_object( $data ) ? $data->suffix : $data['suffix'];
		
		return ( $prefix . htmlspecialchars( $name, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE ) . $suffix );
	}
	
	/*-------------------------------------------------------------------------*/
	// Check user online
	/*-------------------------------------------------------------------------*/
	protected $sessionData	= NULL;
	
	public function isOnline( $data )
	{
		$memID = \is_object( $data ) ? $data->member_id : $data['member_id'];
		$lastActivity = \is_object( $data ) ? $data->last_activity : $data['last_activity'];
		
		if( !$memID )
		{
			return FALSE;
		}

		if ( $this->sessionData[ $memID ] !== NULL )
		{
			return $this->sessionData[ $memID ];
		}

	    $this->sessionData[ $memID ] = \IPS\Session\Store::i()->getLatestMemberSession( $memID );

		if( $this->sessionData[ $memID ] === FALSE )
		{
			return FALSE;
		}
		
		if ( $this->sessionData[ $memID ]['login_type'] == \IPS\Session\Front::LOGIN_TYPE_ANONYMOUS && ! \IPS\Member::loggedIn()->isAdmin() )
		{
			return FALSE;
		}
		
		$diff = \IPS\DateTime::ts( $lastActivity )->diff( \IPS\DateTime::create() );
		if ( $diff->y or $diff->m or $diff->d or $diff->h or $diff->i > 15 )
		{
			return FALSE;
		}
		
		return TRUE;
	}
	
	/*-------------------------------------------------------------------------*/
	// Member Group Cache
	/*-------------------------------------------------------------------------*/
	public function memGroupCache()
	{	
		if ( !isset( \IPS\Data\Store::i()->groups ) )
		{
			\IPS\Data\Store::i()->groups = iterator_to_array( \IPS\Db::i()->select( 'core_groups.*', 'core_groups', NULL, 'core_sys_lang_words.word_custom' )->join( 'core_sys_lang_words', array( "lang_id=? AND word_app=? AND word_key=CONCAT( 'core_group_', core_groups.g_id )", \IPS\Member::loggedIn()->language()->id, 'core' ) )->setKeyField( 'g_id' ) );
		}

		return \IPS\Data\Store::i()->groups;
	}

	/*-------------------------------------------------------------------------*/
	// Join core_member table
	/*-------------------------------------------------------------------------*/
	public function joinAuthor( $chats )
	{	
		$groups = $this->memGroupCache();
		
		foreach( $chats as $row )
		{
			if ( $row->member_id > 0 )
			{
				$mIDs[] = $row->member_id;
			}
		}
		
		if ( $mIDs )
		{
			$memIDs = array_unique($mIDs);

			foreach( \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->in( 'member_id', $memIDs, FALSE ) ) ) as $mem )
			{
				$mem['groupData'] = $groups[ $mem['member_group_id'] ];
				$members[ $mem['member_id'] ] = $mem;
			}
			
			$members[0] = array( 'groupData' => $groups[ \IPS\Settings::i()->guest_group ] );
		}
		
		return $members;
	}
		
	/*-------------------------------------------------------------------------*/
	// Show uploaded files URL
	/*-------------------------------------------------------------------------*/
	public function showFiles( $chats, $from )
	{	
		foreach( $chats as $row )
		{
			$msgIDs[] = \is_object( $row ) ? $row->id : $row['chat_id'];
		}
		
		$files = array();
		
		$where[] = array( 'file_from=?', $from );
		$where[] = array( \IPS\Db::i()->in( 'file_msg_id', $msgIDs, FALSE ) );

		foreach( \IPS\Db::i()->select( '*', 'chatbox_files', $where ) as $record )
		{
			$img = $record['file_thumb'] ? $record['file_thumb'] : $record['file_location'];
			
			try
			{
				$data[ $record['file_msg_id'] ][] = (string) \IPS\File::get( 'chatbox_File', $img )->url->setScheme( ( mb_substr( \IPS\Settings::i()->base_url, 0, 5 ) === 'https' ) ? 'https' : 'http' );
				$files[ $record['file_msg_id'] ] = ' ' . implode( ' ', $data[ $record['file_msg_id'] ] );
			}
			catch( \Exception $e ){}
		}

		return $files;
	}
		
	/*-------------------------------------------------------------------------*/
	// Process Upload Files
	/*-------------------------------------------------------------------------*/	
	public function uploader( $name, $obj )
	{
		return new \IPS\Helpers\Form\Upload( $name, $obj ? iterator_to_array( $obj->images() ) : NULL, FALSE, array( 
			'multiple'			=> TRUE,
			'allowedFileTypes' 	=> array( 'jpg', 'jpeg', 'jpe', 'png', 'gif', 'webp', 'mp3', 'wav', 'mp4' ),
			'maxFileSize' 		=> (int) \IPS\Settings::i()->chatbox_upload_file_size,
			'maxFiles' 			=> 1,
			'template'			=> "chatbox.upload.file",
			'storageExtension' 	=> 'chatbox_File', 
		) );
	}
	
	/*-------------------------------------------------------------------------*/
	// Process Upload Files
	/*-------------------------------------------------------------------------*/	 
	public function processUploadFiles( $files, $type, $msgID )
	{
		# Get any records we had before in case we need to delete them
		$where = array( array( 'file_from=? AND file_msg_id=?', $type, $msgID ) );
		$existing = iterator_to_array( \IPS\Db::i()->select( '*', 'chatbox_files', $where )->setKeyField( 'file_location' ) );

		# Loop through the values we have
		foreach ( $files as $file )
		{
			if ( !isset( $existing[ (string) $file ] ) )
			{
				\IPS\Db::i()->insert( 'chatbox_files', array(
					'file_msg_id'		=> $msgID,
					'file_from'			=> $type,
					'file_location'		=> (string) $file,
					'file_thumb'		=> $file->isImage() ? $file->thumbnail( 'chatbox_File', 210, 120 ) : NULL,
					'file_realname'		=> $file->originalFilename,
				) );
				
				\IPS\Db::i()->delete( 'core_files_temp', array( 'contents=?', (string) $file ) );
			}
			unset( $existing[ (string) $file ] );
		}
		
		# Delete any that we don't have any more
		if ( $existing )
		{
			foreach ( $existing as $location => $row )
			{
				try
				{
					\IPS\File::get( 'chatbox_File', $location )->delete();
				}
				catch ( \Exception $e ) { }

				if( $row['file_thumb'] )
				{
					try
					{
						\IPS\File::get( 'chatbox_File', $row['file_thumb'] )->delete();
					}
					catch ( \Exception $e ) { }
				}

				\IPS\Db::i()->delete( 'chatbox_files', array( 'file_id=?', $row['file_id'] ) );			
			}
		}		
	}
	
	/*-------------------------------------------------------------------------*/
	// Delete uploaded file from a message
	/*-------------------------------------------------------------------------*/
	public function deleteFilesFromMSG( $type, $msgID )
	{
		$where = array( array( 'file_from=? AND file_msg_id=?', $type, $msgID ) );
		
		foreach ( new \IPS\File\Iterator( \IPS\Db::i()->select( 'file_location', 'chatbox_files', $where ), 'chatbox_File' ) as $file )
		{
			try
			{
				$file->delete();
			}
			catch ( \Exception $e ) { }
		}

		foreach ( new \IPS\File\Iterator( \IPS\Db::i()->select( 'file_thumb', 'chatbox_files', $where ), 'chatbox_File' ) as $file )
		{
			try
			{
				$file->delete();
			}
			catch ( \Exception $e ) { }
		}
		
		\IPS\Db::i()->delete( 'chatbox_files', array( 'file_msg_id=?', $msgID ) );		
	}
	
	/*-------------------------------------------------------------------------*/
	// Filter Msg By Member ID
	/*-------------------------------------------------------------------------*/	 
	protected $_ignored = NULL;
	
	public function filterMems()
	{
		$data = array();
		
		if( \IPS\Member::loggedIn()->member_id && \IPS\Settings::i()->chatbox_ignore == 1 )
		{
			if ( $this->_ignored === NULL )
			{
				$where = array( 'ignore_owner_id=?', \IPS\Member::loggedIn()->member_id );
				$this->_ignored = iterator_to_array( \IPS\Db::i()->select( 'ignore_ignore_id, ignore_id', 'chatbox_ignored_users', $where )->setValueField( 'ignore_ignore_id' ) );		
			}
			
			$data['ignored'] = $this->_ignored;
		}
		
		return $data;
	}		

	
}