<?php
/*
Officity - Web application platform - Version 6.0 - 2011-07-05

François Dispaux, Boris Verdeyen, Thomas Hermant,
Jérémie Roy, Grégory Meurice, Abdelila Harbi, 
Marc Mignonsin, Jonathan Sanchez, Julien Gonzalez, Pierre Fouchez

Sushee and Officity is © Copyright 2011 Nectil SA.

`/sushee-source/sushee/private/babeler_server.php` is part of Officity.

Sushee is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Officity and Sushee are distributed in the hope that they will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Sushee. If not, see <http://www.gnu.org/licenses/>.
*/
require_once(dirname(__FILE__)."/../common/encoding_functions.inc.php");
require_once(dirname(__FILE__)."/../common/config.inc.php");
require_once(dirname(__FILE__)."/../common/db_config.inc.php");
require_once(dirname(__FILE__)."/../common/db_functions.inc.php");
require_once(dirname(__FILE__).'/../common/XML.class.php');



define("header_xml",'<?xml version="1.0" encoding="utf-8"?>');
define("bab_error_xml_malformed",1);
define("bab_error_authentication_failure",2);
define("bab_unrecognized_resident",6);
define("bab_not_joinable",3);
define("bab_unrecognized_event_type",4);
define("bab_unrecognized_param",5);
define("bab_double_connection",7);
define("bab_history_msg",8);
define("bab_noop_msg",9);

set_time_limit(0);
if(!isset($GLOBALS["generic_backoffice_db"]))
	$GLOBALS["generic_backoffice_db"]='backoffice';

class babeler_server{
	var $users_residency = array();
	var $resident_users = array();
	var $resident_groups = array();
	var $user_details = array();
	var $sock = null;
    var $clientsocks = array();
	var $output_errors = true;
	var $log_errors = true;
	var $logfile_path = '../../Files/.babeler.log';
	
	function connect(){
		// verifying log file is writable
		$logfile_path = $this->logfile_path;
		if($logfile_path[0]!='/'){
			$logfile_path = dirname(__FILE__).'/'.$logfile_path;
		}
		$logdir = dirname($logfile_path);
		$logfile_writable = is_writable($logfile_path);
		$logdir_writable = is_writable($logdir);
		if ( (file_exists($logfile_path) && $logfile_writable) || (!file_exists($logfile_path) && $logdir_writable) ){
			$this->log_errors = true;
		}else{
			$this->log_errors = false;
			if(!$logfile_writable)
				echo "Log file ".$logfile_path." is not writable\n";
			if(!$logdir_writable)
				echo "Log dir ".$logdir." is not writable\n";
		}
		
		$ip_addr = '0.0.0.0';
		$port = 1857;
		if (false === ($this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)))
			return $this->errlog("connect()", "Couldn't create socket");
		socket_setopt($this->sock, SOL_SOCKET, SO_REUSEADDR, 1);
		if (!socket_bind($this->sock, $ip_addr, $port))
			return $this->errlog("connect()", "Couldn't bind socket ".socket_strerror(socket_last_error($this->sock)));
		if (!socket_listen($this->sock, 10))
			return $this->errlog("connect()", "Couldn't listen on socket");
		
		$this->log("Server started");

		return 1;
	}
	function disconnect(){
		$this->on_shutdown();

		foreach($this->clientsocks as $i => $x){
		  socket_shutdown($this->clientsocks[$i], 2);
		  socket_close($this->clientsocks[$i]);
		  unset($this->clientsocks[$i]);
		}
		
		if ($this->sock){
		  socket_shutdown($this->sock, 2);
		  socket_close($this->sock);
		  $this->sock = null;
		}
		
		$this->log("Server shut down");
	}
	function listen(){
		while(true){
			$read   = $this->clientsocks;
			$read[] = $this->sock;
			
			if (false === socket_select($read, $write = null, $except = null, 1/*null*/))//waiting for one second that something happens
				return $this->errlog("execute", "Couldn't select socket");
			
			// Accept connection
			if (in_array($this->sock, $read)){
				$i = count($this->clientsocks) ? Array_LastIndex($this->clientsocks)+1 : 0;
				
				if (false === ($this->clientsocks[$i] = socket_accept($this->sock)))
				  return $this->errlog("execute", "Couldn't accept connection");
				socket_setopt($this->clientsocks[$i], SOL_SOCKET, SO_REUSEADDR, 1);
				
				$this->log("Connection accepted from socket_index $i");
			}
			
		  foreach($this->clientsocks as $i => $x){
			  if (in_array($this->clientsocks[$i], $read)){
				  	$msg = socket_read($this->clientsocks[$i], 4096, PHP_BINARY_READ);
					if (($msg === false) || ($msg === '')){
						// Close connection
						socket_shutdown($this->clientsocks[$i], 2);
						socket_close($this->clientsocks[$i]);
						unset($this->clientsocks[$i]);
						
						$this->log("Connection closed by socket_index $i");
						$this->on_disconnect($i);
					}else{
						// Process message
						$msg = substr($msg, 0, strlen($msg));
						//$this->log("Receive from socket_index $i: $msg");
						$this->on_read($i, $msg);
					}
			  }else{ // only if we have not managed its request
				if($this->get_waiting_offline_msg($i)>0){
					$this->write_offline_msg($i);
				}
			  }
			}
		}
	}
	function write_offline_msg($socket_index){
		$db_conn = $this->get_db_connection($socket_index);
		if($db_conn){
			$this->log('write_offline_msg');
			$nectilID = $this->get_user_nectilID($socket_index);
			$history_sql = 'SELECT * FROM `babeler_logs` WHERE `TargetID` ='.$nectilID.' AND `Read` = 0 ORDER BY `ID` ASC LIMIT '.$this->get_sent_offline_msg($socket_index).',1';
			$this->log($history_sql);
			$history_row = $db_conn->GetRow($history_sql);
			if($history_row){
				$event = $this->get_history_xml($socket_index,$history_row);
				if($this->write($socket_index,$event)){
					// mark it as read, if it was successfuly sent
					$this->set_waiting_offline_msg($socket_index,$this->get_waiting_offline_msg($socket_index)-1);
				}
			}
		}
	}
	
	
	
	function write($socket_index, $msg){
		if (isset($this->clientsocks[$socket_index])){
			$msg = header_xml.$msg;
			$msg.=chr(0); // octet 0 pour flash
			$res = socket_write($this->clientsocks[$socket_index], $msg, strlen($msg));
			$msg_type = $this->get_message_type($msg);
			if($msg_type!=bab_history_msg && $msg_type!=bab_noop_msg)
				$this->log("Send to socket $socket_index, contact ".$this->get_user_nectilID($socket_index).": $msg");
			if($res === false)
				return false;
			else 
				return true;
		}
	}
	
	function get_message_type($msg){
		if(strpos($msg,'<HISTORY')!==false){
			return bab_history_msg;
		}else if(strpos($msg,'<EVENT type="noop"')!==false){
			return bab_noop_msg;
		}else
			return false; // we dont care
	}
	
	function write_all($socket_index,$msg){
		$this->log('write all');
		$user_socket_indexes = $this->get_resident_users($socket_index);
		//foreach($this->clientsocks as $i => $x) $this->write($i, $msg);
		foreach($user_socket_indexes as $i){
			if($i!=$socket_index)
				$this->write($i, $msg);
		}
	}
	function write_group($socket_index,$msg){
		$this->log('write group');
		$user_socket_indexes = $this->get_user_groupmembers_sockets($socket_index);
		foreach($user_socket_indexes as $i){
			if($i!=$socket_index)
				$this->write($i, $msg);
		}
	}
	function write_not_group($socket_index,$msg){
		$this->log('write not group');
		$user_socket_indexes = $this->get_user_not_groupmembers_sockets($socket_index);
		foreach($user_socket_indexes as $i){
			if($i!=$socket_index)
				$this->write($i, $msg);
		}
	}
	function write_error($socket_index,$code,$msg){
		$this->write($socket_index,'<EVENT type="error" code="'.$code.'"><TEXT>'.$msg.'</TEXT></EVENT>');
	}
	function write_success($socket_index,$msg){
		$this->write($socket_index,'<EVENT type="success"><TEXT>'.$msg.'</TEXT></EVENT>');
	}
	
	function log($msg){
		if($this->output_errors){
			echo $msg."\n";
		}
		if($this->log_errors){
			$perm = 'a+';
			$logfile_path = $this->logfile_path;
			if(file_exists($logfile_path) && filesize($logfile_path)>1048566000)
				$perm = 'w+';
			$file = fopen($logfile_path, $perm); // binary update mode
			if($file){
				fwrite($file, "\r\n---------------------------------------------------\r\n".date("Y-m-d H:i:s")." ".$msg);
				fclose($file);
			}
		}
	}
	function errlog($location, $msg){
		$this->log("Error in babeler_server $location with message '$msg'");
		return 0;
    }
	function on_shutdown() {
		
	}
	
	function unregister($socket_index){
		$resident = $this->users_residency[$socket_index];
		$nectilID = $this->user_details[$socket_index]['ID'];
		foreach($this->user_details[$socket_index]['groups'] as $nectil_groupID){
			unset($this->resident_groups[$resident][$nectil_groupID][$nectilID]);
			if(sizeof($this->resident_groups[$resident][$nectil_groupID])==0)
				unset($this->resident_groups[$resident][$nectil_groupID]);
		}
		unset($this->users_residency[$socket_index]);
		unset($this->user_details[$socket_index]);
		unset($this->resident_users[$resident][$nectilID]);
	}
	
    function on_disconnect($socket_index) {
		$this->write_goodbye($socket_index);
		$this->unregister($socket_index);
	}
	function write_goodbye($socket_index){
		$visibility = $this->get_user_visibility($socket_index);
		$msg = $this->get_goodbye_msg($socket_index);
		if($visibility=='team')
			$this->write_group($socket_index,$msg);
		else if($visibility=='public')
			$this->write_all($socket_index,$msg);
	}
	function get_goodbye_msg($socket_index){
		$nectilID = $this->get_user_nectilID($socket_index);
		return '<EVENT type="goodbye"><CONTACT ID="'.$nectilID.'"/></EVENT>';
	}
	function write_first_welcome($socket_index){
		$visibility = $this->get_user_visibility($socket_index);
		$this->log('visibility is '.$visibility);
		$msg = $this->get_welcome_msg($socket_index);
		if($visibility=='team')
			$this->write_group($socket_index,$msg);
		else if($visibility=='public')
			$this->write_all($socket_index,$msg);
	}
	function get_welcome_msg($socket_index){
		$user_infos_xml = $this->get_user_infos_xml($socket_index);
		return '<EVENT type="welcome">'.$user_infos_xml.'</EVENT>';
	}
	function register_user($socket_index,$resident,$user_infos){
		if(!isset($this->resident_users[$resident])){
			$this->resident_users[$resident] = array();
		}
		$this->users_residency[$socket_index]=$resident;
		$nectilID = $user_infos['ID'];
		$user_infos['socket_index']=$socket_index;
		$user_infos['start'] = date('Y-m-d H:i:s');
		$this->user_details[$socket_index] = &$user_infos;
		$this->resident_users[$resident][$nectilID] = &$user_infos;
		$this->log('new user '.$nectilID.' on socket_index '.$socket_index.' for resident '.$resident.'');
	}
	
	function get_connection_start($socket_index){
		$user_infos = $this->get_user_infos($socket_index);
		return $user_infos['start'];
	}
	
	function get_user_infos($socket_index){
		if(isset($this->user_details[$socket_index]))
			return $this->user_details[$socket_index];
		else
			return false;
	}
	function get_user_nectilID($socket_index){
		if(isset($this->user_details[$socket_index]))
			return $this->user_details[$socket_index]['ID'];
		else
			return false;
	}
	function get_user_visibility($socket_index){
		if(isset($this->user_details[$socket_index]))
			return $this->user_details[$socket_index]['visibility'];
		else
			return false;
	}
	function get_user_residency($socket_index){
		if(isset($this->users_residency[$socket_index]))
			return $this->users_residency[$socket_index];
		else
			return false;
	}
	function get_resident_users($socket_index){
		$indexes = array();
		$resident = $this->get_user_residency($socket_index);
		if(isset($this->resident_users[$resident])){
			foreach($this->resident_users[$resident] as $nectilID=>$user_infos){
				$indexes[]=$user_infos['socket_index'];
			}
		}else
			$this->log('No user registered for this resident (socket_index '.$socket_index.')');
		$this->log(sizeof($indexes).' users registered in resident '.$resident.' (socket_index '.$socket_index.')');
		return $indexes;
	}
	
	
	function get_user_socket($socket_index,$nectilID,$resident=false){
		if(!$resident)
			$resident = $this->get_user_residency($socket_index);
		if($resident){
			$recipient_infos = $this->resident_users[$resident][$nectilID];
		}else{
			$this->log('The resident for this user could not be determined (socket_index '.$socket_index.')');
			return false;
		}
		if($recipient_infos){
			return $recipient_infos['socket_index'];
		}else{
			$this->log('Could not retrieve users (ID='.$nectilID.') information (socket_index '.$socket_index.')');
			return false;
		}
	}
	function register_user_in_group($socket_index,$nectil_groupID){
		$resident = $this->get_user_residency($socket_index);
		$nectilID = $this->get_user_nectilID($socket_index);
		$this->resident_groups[$resident][$nectil_groupID][$nectilID]=$socket_index;
		$this->user_details[$socket_index]['groups'][$nectil_groupID]=$nectil_groupID;
	}
	function get_user_groups($socket_index){
		return $this->user_details[$socket_index]['groups'];
	}
	function get_groupmembers_sockets($socket_index,$nectil_groupID){ // socket of the members of only one specific group
		$resident = $this->get_user_residency($socket_index);
		return $this->resident_groups[$resident][$nectil_groupID];
		
	}
	function get_user_groupmembers_sockets($socket_index){// socket of the members of all the groups where the user belongs
		$indexes = array();
		$groups = $this->get_user_groups($socket_index);
		if($groups){
			foreach($groups as $nectil_groupID){
				$groupmembers = $this->get_groupmembers_sockets($socket_index,$nectil_groupID);
				foreach($groupmembers as $groupmember_socket){
					if(!in_array($groupmember_socket,$indexes))
						$indexes[]=$groupmember_socket;
				}
			}
		}
		return $indexes;
	}
	function get_user_not_groupmembers_sockets($socket_index){// socket of the users that are not part of one user's group
		$indexes = array();
		$groups = $this->get_user_groups($socket_index);
		$resident = $this->get_user_residency($socket_index);
		foreach($this->resident_users[$resident] as $nectilID=>$user_infos){
			$not_in_any_group = true;
			$user_groups = $this->get_user_groups($user_infos['socket_index']);
			$common_groups = array_intersect ($user_groups,$groups);
			if(sizeof($common_groups)>0)
				$not_in_any_group = false;
			if($not_in_any_group)
				$indexes[]=$user_infos['socket_index'];
		}
		return $indexes;
	}
	
	function getresident($url){
		$db_conn=specific_db_connect($GLOBALS["generic_backoffice_db"]);
		if(!$db_conn){
			$this->log('couldnot connect to database');
			return false;
		}
		if(strpos($url,'http://www.')===false)
		$with_www = str_replace('http://','http://www.',$url);
		else
		$with_www = $url;
		$eol_db = "\r\n";
		$eol_flash = "\n";
		$condition_url2 = "( URL2=\"".$url."\" 
		OR URL2 LIKE \"".$url."$eol_db%\" 
		OR URL2 LIKE \"%$eol_db".$url."\" 
		OR URL2 LIKE \"%$eol_db".$url."$eol_db%\" 
		OR URL2 LIKE \"".$url."$eol_flash%\" 
		OR URL2 LIKE \"%$eol_flash".$url."\" 
		OR URL2 LIKE \"%$eol_flash".$url."$eol_flash%\" 
		OR URL2 LIKE \"".$with_www."$eol_db%\" 
		OR URL2 LIKE \"%$eol_db".$with_www."\" 
		OR URL2 LIKE \"%$eol_db".$with_www."$eol_db%\"
		OR URL2 LIKE \"".$with_www."$eol_flash%\" 
		OR URL2 LIKE \"%$eol_flash".$with_www."\" 
		OR URL2 LIKE \"%$eol_flash".$with_www."$eol_flash%\"
		OR URL2=\"".$with_www."\")";

		$sql = "SELECT ID,Published,Denomination,DbName,URL2,Profile FROM residents WHERE ( (URL=\"".$url."\" OR URL=\"".$with_www."\" ) OR ( ".$condition_url2." ) )	AND Activity=1;"; // AND (ExpirationDate=\"0000-00-00\" OR ExpirationDate > \"".$GLOBALS["sushee_today"]."\" OR ExpirationDate=\"0000-01-01\")
		$this->log($sql);
		$row = $db_conn->GetRow($sql);
		return $row;
	}
	
	function get_waiting_offline_msg($socket_index){
		return $this->user_details[$socket_index]['waiting_offline_msgs'];
	}
	
	function get_sent_offline_msg($socket_index){
		return ($this->user_details[$socket_index]['total_offline_msgs'] - $this->user_details[$socket_index]['waiting_offline_msgs']);
	}
	
	function set_waiting_offline_msg($socket_index,$nbr){
		$this->user_details[$socket_index]['waiting_offline_msgs'] = $nbr;
	}
	
	function login($socket_index,$xml){
		$root_xpath = '/*[1]';
		$url = $xml->getData($root_xpath.'/URL[1]');
		$nodename = $xml->nodename($root_xpath);
		$isReconnect = ($nodename == 'RECONNECT');
		if(substr($url,-1,1)=='/')
			$url = substr($url,0,-1);
		
		$login = decode_from_xml($xml->getData($root_xpath.'/LOGIN[1]'));
		$password = mysql_password(decode_from_xml($xml->getData($root_xpath.'/PASSWORD[1]')));
		$visibility = $xml->getData($root_xpath.'/VISIBILITY[1]');
		// trying to find a corresponding resident, otherwise using the main database
		$resident_db_row = $this->getresident($url);
		
		
		if(!$resident_db_row){
			$db_name = $GLOBALS['generic_backoffice_db'];
		}else{
			$db_name = $resident_db_row['DbName'];
		}
		$db_conn = specific_db_connect($db_name);
		$this->log('connecting to DB '.$db_name);
		$resident = $db_name;
		$authen_sql = 'SELECT `ID`,`FirstName`,`LastName`,`Denomination`,`Preview` FROM `contacts` WHERE `Email1`="'.encode_for_db($login).'" AND `Password`="'.encode_for_db($password).'" AND `Password`!="" AND `Activity`=1';
		$contact_db_row = $db_conn->getRow($authen_sql);
		if($contact_db_row){
			$wasConnected = false;
			$this->log('User recognized');
			// disconnecting previous session of the same contact
			if(!$isReconnect){
				$former_socket = $this->get_user_socket($socket_index,$contact_db_row['ID'],$resident/* giving him th resident, because he wont be able to retrieven the user isnt yer registered */);
				if($former_socket!==false){
					$warning = '<FORMER_CONNECTION>';
					$warning.='<CREATION>'.$this->get_connection_start($former_socket).'</CREATION>';
					$ip = '';
					$ipGot = socket_getpeername($former_socket,$ip);
					$warning.='<IP>'.$ip.'</IP>';
					$warning.= '</FORMER_CONNECTION>';
					$this->log('User was already connected : deconnecting its former bab');
					//$this->write_msg($socket_index,$former_socket,'Connected on another machine');
					$this->write_error($former_socket,bab_double_connection,'Connected on another machine');
					$this->unregister($former_socket);
					socket_shutdown($former_socket, 2);
					socket_close($former_socket);
					$wasConnected = true;
				}else{
					$this->log('One and only one connection for this user ');
				}
			}
			$user_infos = $contact_db_row;
			$user_infos['visibility']=$visibility;
			if(!$isReconnect){
				$user_infos['waiting_offline_msgs']=0;
				// keeping the number of offline msgs, to send them after (we send them after, one by one, to avoid to block the messenging server every time a user connects)
				$history_sql = 'SELECT COUNT(`ID`) AS waiting_offline_msgs FROM `babeler_logs` WHERE `TargetID` ='.$user_infos['ID'].' AND `Read` = 0';
				$history_row = $db_conn->GetRow($history_sql);
				if($history_row){
					$user_infos['waiting_offline_msgs']=$history_row['waiting_offline_msgs'];
					$user_infos['total_offline_msgs']=$user_infos['waiting_offline_msgs'];
				}
				$this->log($user_infos['waiting_offline_msgs'].' offline msgs');
			}
			$this->register_user($socket_index,$resident,$user_infos);
			$other_users_socket_indexes = $this->get_resident_users($socket_index);
			$msg = '<STATE>';
			
			$teams='<TEAMS>';
			$groups_rs = $db_conn->Execute('SELECT g.`Denomination`,g.`ID` FROM `groups` AS g LEFT JOIN `dependencies` AS dep ON(g.`ID`=dep.`OriginID`) WHERE g.`IsTeam`=1 AND g.`Activity`=1 AND dep.`DependencyTypeID`=1 AND dep.`TargetID`='.$user_infos['ID']);
			while($group_row = $groups_rs->FetchRow()){
				$teams.='<GROUP ID="'.$group_row['ID'].'"><DENOMINATION>'.encode_to_xml($group_row['Denomination']).'</DENOMINATION><MEMBERS>';
				$this->register_user_in_group($socket_index,$group_row['ID']);
				$contactIDs_sql = 'SELECT `TargetID` FROM `dependencies` WHERE `DependencyTypeID`=1 AND `OriginID`='.$group_row['ID'];
				$contactIDs_rs = $db_conn->Execute($contactIDs_sql);
				if($contactIDs_rs){
					$IDs = array();
					while($IDrow = $contactIDs_rs->FetchRow()){
						if($IDrow['TargetID']!=$user_infos['ID'])
							$IDs[]=$IDrow['TargetID'];
					}
					if(sizeof($IDs)>0){
						$contacts_recup = 'SELECT `ID`,`FirstName`,`LastName`,`Denomination`,`Preview` FROM `contacts` WHERE `ID` IN('.implode(',',$IDs).')';
						$members_rs = $db_conn->Execute($contacts_recup);
						if($members_rs){
							while($member_row = $members_rs->FetchRow()){
								$member_row['connected']=false;
								$member_socket = $this->get_user_socket($socket_index,$member_row['ID']);
								if($member_socket!==false){ // he's connected, checking its visibility
									if($this->get_user_visibility($member_socket)!='none')
										$member_row['connected']=true;
								}
								$teams.=$this->get_user_xml($member_row);
							}
						}
					}
				}
				$teams.='</MEMBERS></GROUP>';
			}
			$teams.='</TEAMS>';
			// creating the agora after the groups, in order to have the groups composed and to know if a user is visible for another
			$agora = '<AGORA>';
			foreach($other_users_socket_indexes as $one_other_user_socket_index){
				if($one_other_user_socket_index !=$socket_index){
					$other_user_id = $this->get_user_id($one_other_user_socket_index);
					if($this->is_user_visible($socket_index,$other_user_id)){
						$agora.= $this->get_user_infos_xml($one_other_user_socket_index);
					}
				}
					
			}
			$agora.= '</AGORA>';
			
			$msg.=$agora;
			$msg.=$teams;
			$msg.=$warning;
			$msg.= '</STATE>';
			$this->write($socket_index,$msg);
			if(!$wasConnected){
				$this->write_first_welcome($socket_index); // it's the function write_first_welcome that manage to who the messages will be sent
			}
		}else{
			$this->log('User NOT recognized');
			$this->log($authen_sql);
			$this->write_error($socket_index,bab_error_authentication_failure,'Authentication failure');
			return;
		}
	}
	function get_user_id($socket_index){
		$user_infos = $this->get_user_infos($socket_index);
		return $user_infos['ID'];
	}
	
	function get_user_infos_xml($socket_index){
		$user_infos = $this->get_user_infos($socket_index);
		return $this->get_user_xml($user_infos);
	}
	function get_user_xml($user_infos){
		if($user_infos['connected']===false)
			$connected = '0';
		else
			$connected = '1';
		return '<CONTACT ID="'.$user_infos['ID'].'" connected="'.$connected.'"><ALIAS>'.encode_to_xml($user_infos['Denomination']).'</ALIAS><FIRSTNAME>'.encode_to_xml($user_infos['FirstName']).'</FIRSTNAME><LASTNAME>'.encode_to_xml($user_infos['LastName']).'</LASTNAME><PREVIEW>'.encode_to_xml($user_infos['Preview']).'</PREVIEW></CONTACT>';
	}
	function event($socket_index,$xml){
		$root_xpath = '/EVENT[1]';
		$type = $xml->getData($root_xpath.'/@type');
		switch($type){
			case 'msg':
				$recipient = $xml->getData($root_xpath.'/CONTACT[1]/@ID');
				$msg = $xml->getData($root_xpath.'/TEXT');
				if($recipient){
					if(!is_numeric($recipient)){
						$this->write_error($socket_index,bab_unrecognized_param,'Unrecognized contact ID');
					}else{
						$groupID = 0;
						$recipient_socket_index = $this->get_user_socket($socket_index,$recipient);
						$read = 0;
						if($recipient_socket_index!==false){
							$this->write_msg($socket_index,$recipient_socket_index,$msg);
						}else{
							$this->write_error($socket_index,bab_not_joinable,'User not joinable');
						}
						$this->save_msg($socket_index,$recipient,$groupID,$msg,$read);
					}
				}else{
					$recipient_group = $xml->getData($root_xpath.'/GROUP[1]/@ID');
					if(!is_numeric($recipient_group)){
						$this->write_error($socket_index,bab_unrecognized_param,'Unrecognized group ID');
					}else{
						$groupID = $recipient_group;
						$this->write_team($socket_index,$recipient_group,$msg);
					}
				}
				break;
			case 'team':
				$this->set_visibility($socket_index,'team');
				break;
			case 'public':
				$this->set_visibility($socket_index,'public');
				break;
			case 'none':
				$this->set_visibility($socket_index,'none');
				break;
			case 'noop':
				$this->write_noop($socket_index);
				break;
			case 'read':
				$this->mark_chat_as_read($socket_index,$xml);
				break;
			default:
				$this->write_error($socket_index,bab_unrecognized_event_type,'Unrecognized event type');
		}
	}
	
	function mark_chat_as_read($socket_index,$xml){
		$root_xpath = '/EVENT[1]';
		$writer = $xml->getData($root_xpath.'/CONTACT[1]/@ID');
		if($writer){
			$this->mark_contact_chat_as_read($socket_index,$writer);
		}else{
			$recipient_group = $xml->getData($root_xpath.'/GROUP[1]/@ID');
			if($recipient_group)
				$this->mark_group_chat_as_read($socket_index,$recipient_group);
		}
	}
	
	function mark_contact_chat_as_read($socket_index,$writerID){
		$db_conn = $this->get_db_connection($socket_index);
		$userID = $this->get_user_nectilID($socket_index);
		if($userID){
			$readSql = 'UPDATE `babeler_logs` SET `Read` = 1 WHERE `OriginID`=\''.$writerID.'\' AND `TargetID`=\''.$userID.'\'';
			$this->log($readSql);
			$db_conn->Execute($readSql);
		}
	}
	
	function mark_group_chat_as_read($socket_index,$recipient_groupID){
		$db_conn = $this->get_db_connection($socket_index);
		$userID = $this->get_user_nectilID($socket_index);
		if($userID){
			$readSql = 'UPDATE `babeler_logs` SET `Read` = 1 WHERE `GroupID`=\''.$recipient_groupID.'\' AND `TargetID`=\''.$userID.'\'';
			$this->log($readSql);
			$db_conn->Execute($readSql);
		}
			
	}
	
	function set_visibility($socket_index,$visibility){
		$former_visibility = $this->user_details[$socket_index]['visibility'];
		$this->user_details[$socket_index]['visibility']=$visibility;
		if($former_visibility!=$visibility){
			if($former_visibility == 'none'){
				$this->write_first_welcome($socket_index); // write_first_welcome to who we must send the welcome, as at login
			}else if($former_visibility == 'team' && $visibility == 'public'){
				$msg = $this->get_welcome_msg($socket_index);
				$this->write_not_group($socket_index,$msg);
			}else if($former_visibility == 'public' && $visibility == 'team'){
				$msg = $this->get_goodbye_msg($socket_index);
				$this->write_not_group($socket_index,$msg);
			}else if($former_visibility=='team' && $visibility == 'none'){
				$msg = $this->get_goodbye_msg($socket_index);
				$this->write_group($socket_index,$msg);
			}else{
				$msg = $this->get_goodbye_msg($socket_index);
				$this->write_all($socket_index,$msg);
			}
		}
	}
	function write_msg($socket_index,$recipient_socket_index,$msg){
		$user_infos = $this->get_user_infos($socket_index);
		
		$this->write($recipient_socket_index,'<EVENT type="msg"><CONTACT ID="'.$user_infos['ID'].'"/><TEXT>'.$msg.'</TEXT></EVENT>');
	}
	function write_noop($socket_index){
		$this->write($socket_index,'<EVENT type="noop"/>');
	}
	function write_team_msg($socket_index,$recipient_socket_index,$msg,$groupID){
		$user_infos = $this->get_user_infos($socket_index);
		
		$this->write($recipient_socket_index,'<EVENT type="msg"><CONTACT ID="'.$user_infos['ID'].'"/><GROUP ID="'.$groupID.'"/><TEXT>'.$msg.'</TEXT></EVENT>');
	}
	function write_team($socket_index,$groupID,$msg){
		$db_conn = $this->get_db_connection($socket_index);
		if($db_conn){
			$senderID = $this->get_user_nectilID($socket_index);
			$sql = 'SELECT dep.`TargetID` FROM `dependencies` AS dep WHERE dep.`DependencyTypeID`=1 AND dep.`OriginID`=\''.$groupID.'\'';
			$groupmembers_rs = $db_conn->Execute($sql);
			$this->log($sql);
			if($groupmembers_rs){
				while($member_row = $groupmembers_rs->FetchRow()){
					$recipient = $member_row['TargetID'];
					if($senderID != $recipient){
						$recipient_socket_index = $this->get_user_socket($socket_index,$recipient);
						$read = 0;
						if($recipient_socket_index!==false){
							$this->write_team_msg($socket_index,$recipient_socket_index,$msg,$groupID);
						}
						$this->save_msg($socket_index,$recipient,$groupID,$msg,$read);
					}
				}
			}else{
				$this->write_error($socket_index,bab_unrecognized_param,'Unrecognized group ID');
				$this->log($db_conn->ErrorMsg());
			}
		}
	}
	
	function save_msg($socket_index,$recipient,$groupID,$msg,$read){
		$db_conn = $this->get_db_connection($socket_index);
		if($db_conn){
			$insert_sql = 'INSERT INTO `babeler_logs`(`Year`,`Month`,`Day`,`Hour`,`Minute`,`Second`,`OriginID`,`TargetID`,`GroupID`,`Msg`,`Read`) VALUES('.date('Y').','.date('m').','.date('d').','.date('H').','.date('i').','.date('s').','.$this->get_user_nectilID($socket_index).','.$recipient.','.$groupID.',"'.encodeQuote(decode_from_xml($msg)).'",'.$read.');';
			$db_conn->Execute($insert_sql);
			$this->log($insert_sql);
		}
	}
	
	function user_is_authentified($socket_index){
		if($this->get_user_infos($socket_index))
			return true;
		else
			return false;
	}
	function history($socket_index,$xml){
		$db_conn = $this->get_db_connection($socket_index);
		$root_xpath = '/HISTORY[1]';
		$nectilID = $this->get_user_nectilID($socket_index);
		$recipient_nectilID = $xml->getData($root_xpath.'/CONTACT[1]/@ID');
		$page = $xml->getData($root_xpath.'/@page');
		if($recipient_nectilID){
			$display = 100;
			if($page){
				$start = ($page-1)*$display;
			}else{
				$page = 1;
				$start = 0;
			}
			$history_sql_where = 'WHERE `OriginID` IN('.$nectilID.','.$recipient_nectilID.') AND TargetID IN('.$nectilID.','.$recipient_nectilID.')';
			$history_sql = 'SELECT * FROM `babeler_logs` '.$history_sql_where.' ORDER BY ID DESC LIMIT '.$start.',100';
			$history_count_sql = 'SELECT COUNT(`ID`) AS entries FROM `babeler_logs` '.$history_sql_where;
			$this->log($history_sql);
			$history_rs = $db_conn->Execute($history_sql);
			$entries_row = $db_conn->getRow($history_count_sql);
			$entries = $entries_row['entries'];
			$pages = ceil($entries / 100);
			$history = '<HISTORY type="history" pages="'.$pages.'" page="'.$page.'">';
			$events = '';
			if($history_rs){
				while($history_row = $history_rs->FetchRow()){
					$events = $this->get_history_xml($socket_index,$history_row).$events;
				}
			}
			$history.=$events;
			$history.= '</HISTORY>';
			$this->write($socket_index,$history);
		}else
			$this->write_error($socket_index,bab_error_xml_malformed,'XML malformed');
	}
	function get_db_connection($socket_index){
		$resident = $this->get_user_residency($socket_index);
		$db_conn = specific_db_connect($resident);
		return $db_conn;
	}
	function get_history_xml($socket_index,$history_row){
		$history = '';
		$history.='<EVENT type="msg" read="'.$history_row['Read'].'">';
		if($this->is_user_visible($socket_index,$history_row['OriginID'])){
			$history.='<CONTACT ID="'.$history_row['OriginID'].'"/>';
		}else{
			$other_user_socket_index = $this->get_user_socket($socket_index,$nectilID); // present but not visible
			if($other_user_socket_index){
				$other_user_infos = $this->get_user_infos($other_user_socket_index);
				$other_user_infos['connected'] = false;
				$history.=$this->get_user_xml($other_user_infos);
			}else{ // not present (really!)
				$db_conn = $this->get_db_connection($socket_index);
				if($db_conn){
					$contact_db_row = $db_conn->getRow('SELECT `ID`,`FirstName`,`LastName`,`Denomination`,`Preview` FROM `contacts` WHERE `ID`='.$history_row['OriginID']);
					if(!$contact_db_row)
						return;
					$contact_db_row['connected'] = false;
					$history.=$this->get_user_xml($contact_db_row);
				}else
					return;
			}
		}
		if($history_row['GroupID']>0){
			$history.='<GROUP ID="'.$history_row['GroupID'].'"/>';
		}
		$history.='<TEXT>'.encode_to_xml($history_row['Msg']).'</TEXT>';
		$history.='<CREATIONDATE>'.$history_row['Year'].'-'.pad_date_element($history_row['Month']).'-'.pad_date_element($history_row['Day']).' '.pad_date_element($history_row['Hour']).':'.pad_date_element($history_row['Minute']).':'.pad_date_element($history_row['Second']).'</CREATIONDATE>';
		$history.='</EVENT>';
		return $history;
	}
	function is_user_visible($socket_index,$nectilID){
		$other_user_socket_index = $this->get_user_socket($socket_index,$nectilID);
		if($other_user_socket_index===false){
			return false;
		}
			
		$other_user_infos = $this->get_user_infos($other_user_socket_index);
		if($other_user_infos['visibility']=='none'){
			return false;
		}else if($other_user_infos['visibility']=='team'){
			$other_user_groups = $this->get_user_groups($other_user_socket_index);
			$user_groups = $this->get_user_groups($socket_index);
			$visible = false;
			foreach($other_user_groups as $nectil_groupID){
				if(isset($user_groups[$nectil_groupID])){
					$visible = true; // one group in common
					break;
				}
			}
			return $visible;
		}else
			return true;
		
	}
    function on_read($socket_index, $msg) {
		$msg_type = $this->get_message_type($msg);
		if($msg_type!=bab_noop_msg)
			$this->log("Receive from socket_index $socket_index: $msg");
		$msg = utf8_To_UnicodeEntities($msg);
		$xml = new XML($msg);
		if(!$xml->loaded){
			$this->write_error($socket_index,bab_error_xml_malformed,'XML malformed');
			return;
		}
		$nodename = $xml->nodename('/*[1]');
		//$this->log('Message type is '.$nodename);
		if($nodename=='CONNECT' || $nodename=='RECONNECT'){
			$this->login($socket_index,$xml);
		}else if($nodename=='policy-file-request'){
			$this->write($socket_index,'<?xml version="1.0"?><cross-domain-policy><allow-access-from domain="*" to-ports="80,1857,443" /></cross-domain-policy>');
		}else{
			if(!$this->user_is_authentified($socket_index))
				return;
			if($nodename=='EVENT')
				$this->event($socket_index,$xml);
			else if($nodename=='HISTORY')
				$this->history($socket_index,$xml);
		}
	}
}
function Array_LastIndex(&$arr){
	end($arr);
	list($i, $x) = each($arr);
	return $i;
}
function pad_date_element($month){
	switch($month){
		case 1:return "01";break;
		case 2:return "02";break;
		case 3:return "03";break;
		case 4:return "04";break;
		case 5:return "05";break;
		case 6:return "06";break;
		case 7:return "07";break;
		case 8:return "08";break;
		case 9:return "09";break;
		default:return $month;
	}
}

$server = new babeler_server();

if($server->connect()){
	$server->log('start listening');
	$server->listen();
}else
	die();

?>
