<?php
/**
 * This file is part of b2evolution - {@link http://b2evolution.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2009-2016 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2009 by The Evo Factory - {@link http://www.evofactory.com/}.
 *
 * Released under GNU GPL License - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @package messaging
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );

load_class( '_core/model/dataobjects/_dataobject.class.php', 'DataObject' );

/**
 * Thread Class
 *
 */
class Thread extends DataObject
{
	var $title = '';
	var $datemodified;
	var $recipients = '';

	/**
	 * Number unread messages
	 * @var integer
	 */
	var $num_unread_messages;

	/**
	 * Recipients IDs lazy filled
	 *
	 * @var array
	 */
	var $recipients_list;


	/**
	 * Constructor
	 * @param db_row database row
	 */
	function __construct( $db_row = NULL )
	{
		// Call parent constructor:
		parent::__construct( 'T_messaging__thread', 'thrd_', 'thrd_ID', 'datemodified' );

		if( $db_row != NULL )
		{
			$this->ID           = $db_row->thrd_ID;
			$this->title        = $db_row->thrd_title;
			$this->datemodified = $db_row->thrd_datemodified;
		}
		else
		{	// New Thread
			global $Session;

			// erwin > Added check for $Session to enable creation of threads in cases where there is
			// no Session available such as generation of sample private conversation during install
			if( $Session )
			{	// check if there is unsaved Thread object stored in Session
				$unsaved_Thread = $Session->get( 'core.unsaved_Thread' );
				if( !empty( $unsaved_Thread ) )
				{	// unsaved thread exists, delete it from Session
					$Session->delete( 'core.unsaved_Thread' );
					$this->title = $unsaved_Thread['title'];
					$this->text = $unsaved_Thread['text'];
				}
			}

			$logins = array();

			$user_login = param( 'user_login', 'string', '' );
			if( !empty( $user_login ) )
			{	// Set recipient list from $user_login
				$logins[] = $user_login;
			}
			else
			{	// Set recipients from Contacts form
				global $DB, $current_User;

				$recipients = param( 'recipients', 'string', '' );
				$group_ID = param( 'group_ID', 'integer', 0 );

				if( !empty( $recipients ) )
				{	// Selected users
					$recipients = explode( ',', $recipients );
					foreach( $recipients as $r => $recipient )
					{
						if( (int)trim( $recipient ) == 0 )
						{	// remove bad data
							unset( $recipients[$r] );
						}
					}
					if( count( $recipients ) > 0 )
					{
						$SQL = new SQL();
						$SQL->SELECT( 'user_ID, user_login' );
						$SQL->FROM( 'T_messaging__contact' );
						$SQL->FROM_add( 'LEFT JOIN T_users ON mct_to_user_ID = user_ID' );
						$SQL->WHERE( 'mct_from_user_ID = '.$current_User->ID );
						$SQL->WHERE_and( 'mct_to_user_ID IN ('.implode( ',', $recipients ).')' );
						// asimo> If A user block B user it means that A user doesn't want to receive private message from B,
						// but A user still should be able to send a message to B.
						//$SQL->WHERE_and( 'mct_blocked = 0' );
						$SQL->ORDER_BY( 'user_login' );

						$logins = $DB->get_assoc( $SQL->get() );
					}
				}
				else if( $group_ID > 0 )
				{	// All users from one group
					$SQL = new SQL();
					$SQL->SELECT( 'user_ID, user_login' );
					$SQL->FROM( 'T_messaging__contact_groupusers' );
					$SQL->FROM_add( 'LEFT JOIN T_users ON cgu_user_ID = user_ID' );
					$SQL->FROM_add( 'LEFT JOIN T_messaging__contact_groups ON cgu_cgr_ID = cgr_ID' );
					$SQL->FROM_add( 'LEFT JOIN T_messaging__contact ON mct_from_user_ID = cgr_user_ID AND mct_to_user_ID = user_ID' );
					$SQL->WHERE( 'cgr_user_ID = '.$current_User->ID );
					$SQL->WHERE_and( 'cgr_ID = '.$DB->quote( $group_ID ) );
					// asimo> If A user block B user it means that A user doesn't want to receive private message from B,
					// but A user still should be able to send a message to B.
					//$SQL->WHERE_and( 'mct_blocked = 0' );
					$SQL->ORDER_BY( 'user_login' );

					$logins = $DB->get_assoc( $SQL->get() );
				}
			}

			$this->recipients = implode( ', ', $logins );

			if( !empty( $logins ) )
			{	// Set this var to initialize the preselected users for fbautocomplete jQuery plugin
				global $recipients_selected;
				foreach( $logins as $user_ID => $user_login )
				{
					$recipients_selected[] = array(
						'id'    => $user_ID,
						'login' => $user_login
					);
				}
			}
		}
	}


	/**
	 * Get this class db table config params
	 *
	 * @return array
	 */
	static function get_class_db_config()
	{
		static $thread_db_config;

		if( !isset( $thread_db_config ) )
		{
			$thread_db_config = array_merge( parent::get_class_db_config(),
				array(
					'dbtablename'        => 'T_messaging__thread',
					'dbprefix'           => 'thrd_',
					'dbIDname'           => 'thrd_ID',
				)
			);
		}

		return $thread_db_config;
	}


	/**
	 * Get delete cascade settings
	 *
	 * @return array
	 */
	static function get_delete_cascades()
	{
		return array(
				array( 'table'=>'T_messaging__message', 'fk'=>'msg_thread_ID', 'msg'=>T_('%d messages in thread'),
						'class'=>'Message', 'class_path'=>'messaging/model/_message.class.php' ),
				array( 'table'=>'T_messaging__threadstatus', 'fk'=>'tsta_thread_ID', 'msg'=>T_('%d read statuses in thread') ),
			);
	}


	/**
	 * Load data from Request form fields.
	 * @return boolean true if loaded data seems valid.
	 */
	function load_from_Request()
	{
		global $thrd_recipients, $thrd_recipients_array;

		// Recipients
		$this->set_string_from_param( 'recipients', empty( $thrd_recipients_array ) ? true : false );

		// Title
		param( 'thrd_title', 'string' );
		param_check_not_empty( 'thrd_title', T_('Please enter a subject') );
		$this->set_from_Request( 'title', 'thrd_title' );

		// Message
		param_check_not_empty( 'msg_text', T_('Please enter a message') );

		$this->param_check__recipients( 'thrd_recipients', $thrd_recipients, $thrd_recipients_array );

		return ! param_errors_detected();
	}


	/**
	 * Set param value
	 *
	 * By default, all values will be considered strings
	 *
	 * @param string parameter name
	 * @param mixed parameter value
	 * @param boolean true to set to NULL if empty value
	 * @return boolean true, if a value has been set; false if it has not changed
	 */
	function set( $parname, $parvalue, $make_null = false )
	{
		switch( $parname )
		{
			case 'recipients':
				$this->recipients = $parvalue;
				break;
			case 'title':
			default:
				return $this->set_param( $parname, 'string', $parvalue, $make_null );
		}
	}


	/**
	 * Check if recipients available in database
	 *
	 * @param string Input name
	 * @param string Recipients logins separated with comma (Used for browsers without JavaScript)
	 * @param string Recipients logins in array format (Used with jQuery plugin fbautocomplete)
	 * @return boolean true if all recipients allow the current User to contact them, false otherwise
	 */
	function param_check__recipients( $var, $recipients, $recipients_array )
	{
		global $DB, $current_User, $UserSettings, $Messages;

		if( !empty( $recipients_array ) )
		{	// These data is created by jQuery plugin fbautocomplete
			$recipients_list = $recipients_array['login'];
		}
		else
		{	// For browsers without JavaScript
			// split recipients into array using comma separator
			$recipients_list = array();
			$recipients = trim( str_replace( ',', ' ', $recipients ) );
			foreach( explode(' ', $recipients) as $recipient )
			{
				$login = trim($recipient);
				if( ! empty( $login ) )
				{
					$recipients_list[] = utf8_strtolower( $login );
				}
			}
		}

		$recipients_list = array_unique( $recipients_list );

		$error_msg = '';

		// check has recipients list login of current user
		if( in_array( $current_User->login, $recipients_list ) )
		{
			$error_msg = sprintf( T_( 'You cannot send threads to yourself: %s' ), $current_User->login );
		}

		// load recipient User objects
		$UserCache = & get_UserCache();
		$UserCache->load_where( 'user_login IN ( "'.implode( '","', $recipients_list ).'" )' );

		// check are recipients available in database
		$this->recipients_list = array();
		$unavailable_recipients_list = array();
		$closed_recipients_list = array();
		$status_restricted_recipients = array();
		$recipients_without_perm = array();
		$recipients_from_different_country = array();
		$recipients_restricted_pm = array();
		// check if recipient user enable private messages only if sender user doesn't have 'delete' messaging permission
		$check_enable_pm = ! check_user_perm( 'perm_messaging', 'delete' );
		$cross_country_restrict = has_cross_country_restriction( 'contact' );
		foreach( $recipients_list as $recipient )
		{
			$recipient_User = $UserCache->get_by_login( $recipient, false );
			if( $recipient_User === false )
			{ // user doesn't exists
				$unavailable_recipients_list[] = $recipient;
				continue;
			}

			if( !$recipient_User->check_status( 'can_receive_pm' ) )
			{ // user status restrict to receive private messages
				if( $recipient_User->check_status( 'is_closed' ) )
				{ // user account was closed
					$closed_recipients_list[] = $recipient;
					continue;
				}

				$status_restricted_recipients[] = $recipient;
				continue;
			}

			if( !$recipient_User->check_perm( 'perm_messaging', 'reply' ) )
			{ // user doesn't have permission to read private messages
				$recipients_without_perm[] = $recipient;
				continue;
			}

			if( $cross_country_restrict && $current_User->ctry_ID != $recipient_User->ctry_ID )
			{ // user can contact with other users only from the same coutnry, but this recipient country is different
				$recipients_from_different_country[] = $recipient;
				continue;
			}

			if( !$UserSettings->get( 'enable_PM', $recipient_User->ID ) )
			{ // recipient doesn't want to receive private messages
				$recipients_restricted_pm[] = $recipient;
				if( $check_enable_pm )
				{ // sender is not a user with delete ( "admin" ) messaging permission, so this user can't be in the recipients list
					continue;
				}
			}

			// recipient is correct, add to recipient list
			$this->recipients_list[] = $recipient_User->ID;
		}

		if ( count( $unavailable_recipients_list ) > 0 )
		{
			if ( ! empty( $error_msg ) )
			{
				$error_msg .= '<br />';
			}
			$error_msg .= sprintf( 'The following users were not found: %s', implode( ', ', $unavailable_recipients_list ) );
		}

		if ( count( $closed_recipients_list ) > 0 )
		{
			if ( ! empty( $error_msg ) )
			{
				$error_msg .= '<br />';
			}
			$error_msg .= sprintf( 'The following users no longer exist: %s', implode( ', ', $closed_recipients_list ) );
		}

		if ( count( $status_restricted_recipients ) > 0 )
		{
			if ( ! empty( $error_msg ) )
			{
				$error_msg .= '<br />';
			}
			$error_msg .= sprintf( 'The following users status currently does not permit to receive private messages: %s', implode( ', ', $status_restricted_recipients ) );
		}

		if ( count( $recipients_without_perm ) > 0 )
		{
			if ( ! empty( $error_msg ) )
			{
				$error_msg .= '<br />';
			}
			$error_msg .= sprintf( 'The following users have no permission to read private messages: %s', implode( ', ', $recipients_without_perm ) );
		}

		if( count( $recipients_from_different_country ) > 0 )
		{
			if ( ! empty( $error_msg ) )
			{
				$error_msg .= '<br />';
			}
			$error_msg .= sprintf( 'You are not allowed to contact with the following users: %s', implode( ', ', $recipients_from_different_country ) );
		}

		$restricted_pm_count = count( $recipients_restricted_pm );
		if ( $restricted_pm_count > 0 )
		{ // there is at least one recipient who doesn't want to receive private messages
			if( $check_enable_pm )
			{ // sender is not a user with delete ( "admin" ) messaging permission, so this user can't be in the recipients list
				if ( ! empty( $error_msg ) )
				{
					$error_msg .= '<br />';
				}
				$error_msg .= sprintf( 'The following users don\'t want to receive private messages: %s', implode( ', ', $recipients_restricted_pm ) );
			}
			else
			{ // send is an admin
				$manual_link = get_manual_link( 'messaging', T_( 'See manual' ).'.' );
				if( $restricted_pm_count > 1 )
				{ // more then one recipient don't want to receive private messages
					$note = sprintf( T_( 'Users &laquo;%s&raquo; do not allow receiving private messages. Message has been sent anyway because you are an administrator.' ), implode( ', ', $recipients_restricted_pm ) );
				}
				else
				{ // one recipient doesn't want to receive private messages
					$note = sprintf( T_( 'User &laquo;%s&raquo; does not allow receiving private messages. Message has been sent anyway because you are an administrator.' ), $recipients_restricted_pm[0] );
				}
				// add note
				$Messages->add( $note.$manual_link, 'note' );
			}
		}

		// Here we select those recipients who has blocked the sender. Note that users with 'delete' messaging permission can't be blocked!
		$blocked_contacts = check_blocked_contacts( $this->recipients_list );
		if( !empty( $blocked_contacts ) )
		{ // There is at least one blocked recipient
			if ( ! empty( $error_msg ) )
			{
				$error_msg .= '<br />';
			}
			$error_msg .= T_( 'The following users don\'t want you to contact them at this time: ' ).' '.implode( ', ', $blocked_contacts );
		}

		if( empty( $error_msg ) )
		{ // no errors yet
			$recipients_count = count( $recipients_list );
			if( ( $recipients_count > 1 ) && ( param( 'thrdtype', 'string', 'discussion' ) != 'discussion' ) )
			{ // user want's to send more then one individual messages, check if is allowed
				list( $max_new_threads, $new_threads_count ) = get_todays_thread_settings();
				if( ( !empty( $max_new_threads ) ) && ( ( $max_new_threads - $new_threads_count ) < $recipients_count ) )
				{ // user has a create thread limit, and recipients number exceed that limit
					if ( ! empty( $error_msg ) )
					{
						$error_msg .= '<br />';
					}
					$error_msg .= sprintf( T_( 'You are unable to send %d individual messages, because it exceeds your remaining daily limit of %d.' ), $recipients_count, $max_new_threads - $new_threads_count );
				}
			}
		}

		if( ! empty( $error_msg ) )
		{	// show error
			param_error( $var, $error_msg );
			return false;
		}

		return true;
	}


	/**
	 * Delete thread and dependencies from database
	 */
	function dbdelete()
	{
		if( $this->ID == 0 ) debug_die( 'Non persistant object cannot be deleted!' );

		return parent::dbdelete();
	}


	/**
	 * Load recipients of the current thread
	 *
	 * @return recipients list
	 */
	function load_recipients()
	{
		global $DB;

		if( empty( $this->recipients_list ) && ( !empty( $this->ID ) ) )
		{
			$SQL = new SQL();
			$SQL->SELECT( 'tsta_user_ID' );
			$SQL->FROM( 'T_messaging__threadstatus' );
			$SQL->WHERE( 'tsta_thread_ID = '.$this->ID );

			$this->recipients_list = array();
			foreach( $DB->get_results( $SQL->get() ) as $row )
			{
				$this->recipients_list[] = $row->tsta_user_ID;
			}
		}

		return $this->recipients_list;
	}


	/**
	 * Check permission on a persona
	 *
	 * @return boolean true if granted
	 */
	function check_perm( $action, $assert = true )
	{
		return check_user_perm( 'perm_messaging', $action, $assert );
	}


	/**
	 * Check if user is recipient of the current thread
	 *
	 * @param user ID
	 * @return boolean true if user is recipient, false otherwise
	 */
	function check_thread_recipient( $user_ID )
	{
		$this->load_recipients();
		return in_array( $user_ID, $this->recipients_list );
	}


	/**
	 * Check if current User is allowed to reply on this thread.
	 * Users are allowed to reply only to those threads where they are involved and there is at least one user between the recipients who didn't block the User.
	 * Note: Currently it doesn't matter if the sender is in the recipients user's contact list or not except in that case when the sender is blocked.
	 *
	 * @return boolean true if current User is allowed, false otherwise
	 */
	function check_allow_reply()
	{
		global $DB, $current_User;

		// load thread recipients
		$this->load_recipients();

		// check if user is involved in recipients list
		if( ! $this->check_thread_recipient( $current_User->ID ) )
		{ // Deny to write a new reply for not involved users
			// asimo> We may call debug_die() here because this is not a correct state of the application
			param_error( '', T_('You cannot post a message in a thread you\'re not involved in.') );
			return false;
		}

		// check if all of the recipients are closed
		$UserCache = & get_UserCache();
		$UserCache->load_where( 'user_ID IN ( '.implode( ',', $this->recipients_list ).' )' );
		$all_closed = true;
		foreach( $this->recipients_list as $recipient_ID )
		{
			if( $recipient_ID == $current_User->ID )
			{ // skip current User
				continue;
			}
			$recipient_User = $UserCache->get_by_ID( $recipient_ID, false );
			if( $recipient_User && $recipient_User->check_status( 'can_receive_pm' ) )
			{ // this recipient exists and status allows to receive private messages
				$all_closed = false;
				break;
			}
		}
		if( $all_closed )
		{ // all recipients are closed or deleted
			param_error( '', T_( 'You cannot reply because all the other users involved in this conversation have closed their account.' ) );
			return false;
		}

		if( check_user_perm( 'perm_messaging', 'delete' ) )
		{ // users with delete permission are always able to reply to a conversation where they are involved
			return true;
		}

		$SQL = new SQL( 'Count all users whou are involved in the given thread but not the current User and didn\'t block the current User' );

		$SQL->SELECT( 'count( ts.tsta_user_ID )' );
		$SQL->FROM( 'T_messaging__threadstatus ts
								LEFT JOIN T_messaging__contact mc ON ts.tsta_user_ID = mc.mct_from_user_ID
											AND mc.mct_to_user_ID = '.$current_User->ID );
		// don't select current User
		$SQL->WHERE( 'ts.tsta_user_ID <> '.$current_User->ID );
		// restrict to the given thread
		$SQL->WHERE_and( 'ts.tsta_thread_ID ='.$this->ID );
		// sender is not blocked or is not present in all recipient's contact list
		$SQL->WHERE_and( '( mc.mct_blocked IS NULL OR mc.mct_blocked = 0 )' );

		if( $DB->get_var( $SQL ) > 0 )
		{ // there is at least one recipient who accept the reply
			return true;
		}

		// all recipients have blocked the current User
		param_error( '', T_( 'The recipient(s) do not want you to contact them at this time.' ) );
		return false;
	}
}

?>