<?php

/**
 * 用于检测业务代码死循环或者长时间阻塞等问题
 * 如果发现业务卡死，可以将下面declare打开（去掉//注释），并执行php start.php reload
 * 然后观察一段时间workerman.log看是否有process_timeout异常
 */
//declare(ticks=1);

use \GatewayWorker\Lib\Gateway;
use \Workerman\Lib\Timer;


//系统程序根路径, 必须定义, 用于防翻墙、文件调用等
define('ROOT', dirname(dirname(dirname(__FILE__))).'/');

//加载核心文件
require(ROOT . 'includes/core.workerman.php');


/**
 * 主逻辑
 * 主要是处理 onConnect onMessage onClose 三个方法
 * onConnect 和 onClose 如果不需要可以不用实现并删除
 */
class Events
{
	/**
	 * 全局数据共享类
	 * @var object
	 */
	public static $global;

	/**
	 * 数据库访问对象
	 * @var object
	 */
	public static $DB;

	/**
	 * 系统设置数组
	 * @var array
	 */
	public static $_CFG;

	/**
	 * robot机器人数组
	 * @var array
	 */
	public static $robot;

    /**
     * 当businessWorker进程启动时触发。每个进程生命周期内都只会触发一次
     * onWorkerStart
     * 
     * @param string $businessWorker businessWorker进程实例
     */
    public static function onWorkerStart($businessWorker)
    {
		//实例化全局数据对象
		self::$global = new GlobalData\Client('127.0.0.1:8518');

		if(isset(self::$global->robot)) return true; //全局机器人变量已经存在时返回

		//开启机器人
		$robot = self::$robot;

		//保存机器人数据为全局变量, Linux下多进程共享
		self::$global->robot = $robot;
    }


    /**
     * 当客户端连接时触发
     * 如果业务不需此回调可以删除onConnect
     * 
     * @param string $client_id 连接id
     */
    public static function onConnect($client_id)
    {
        // 连接到来后，定时6秒关闭这个链接，需要6秒内发认证并删除定时器阻止关闭连接的执行
        $_SESSION['auth_timer_id'] = Timer::add(6, function($client_id){

            Gateway::closeClient($client_id);

        }, array($client_id), false);
	}
    
   /**
    * 当客户端发来消息时触发
    * @param string $client_id 连接id
    * @param string $data 具体消息
    */
   public static function onMessage($client_id, $data)
   {
		// 将json数据转换为对象
        $data = json_decode($data);

        switch($data->type)
        {
            case 'ping': //心跳
				
				break;

            case 'msg': //消息

				if(empty($data->msg)) return;
				$msg = $data->msg;

				//超过 2k 字节
				if(strlen($msg) > 2048) $msg = "... too long ..."; 

				//客服群聊
				if($data->sendto == 'team'){

					self::supportChat($client_id, htmlspecialchars($msg)); //处理html特殊字符, 无需mysql过滤

				//客人发送给客服
				}elseif($data->sendto == "back"){

					$msg = forceString($msg);

					self::sayToSupport($client_id, $msg);

				//客服发送给客人
				}elseif($data->sendto == "front"){

					$gid = forceInt($data->gid);
					$msg = forceString($msg);

					self::sayToGuest($client_id, $gid, $msg);
				}

				break;

			case 'login': //登录

				//客服登录验证
				if($data->from == 'backend'){

					self::adminLogin($client_id, $data);

				//访客登录验证
				}elseif($data->from == 'front'){

					self::guestLogin($client_id, $data);
				}

                break;

            case 's_handle': //客服操作

				self::supportHandle($client_id, $data);

				break;

            case 'g_handle': //访客操作(发起的)

				self::guestHandle($client_id, $data);

				break;

            case 'webrtc': //实时通讯, 无需处理, 仅发送给对方
				if(empty($data->event)) return;

				$event = $data->event; //传递给对方的事件(动作)
				$msg = Iif(empty($data->msg), "", $data->msg); //事件附加信息(可能为空)

				//客服
				if(isset($_SESSION['type']) AND isset($_SESSION['aid'])){

					$gid = $_SESSION['gid'];
					if(!$gid) return; //未接通返回

					$guest = self::getSessionByGid($gid);
					if(!$guest) return;

					self::sendToClient($guest['client_id'], array('x' => 4, 'e' => $event, 'm' => $msg));

				//访客
				}elseif(isset($_SESSION['gid']) AND isset($_SESSION['aid'])){

					if(!$_SESSION['aid']) return; //未接通返回

					$aix = $_SESSION['aix']; //客服的连接索引

					$support = Gateway::getSession($aix);
					if(!$support) return;

					self::sendToClient($aix, array('x' => 4, 'e' => $event, 'm' => $msg));

				//非法 关闭
				}else{
					Gateway::closeClient($_SESSION['client_id']);
				}
				
				break;

			default: //非法连接断开
				Gateway::closeClient($client_id);

				break;
        }

   }
   
   /**
    * 当用户连接断开时触发
    * @param string $client_id 连接id
    */
   public static function onClose($client_id)
   {
		$session = $_SESSION; //先将$_SESSION赋值保存, 否则异步操作$_SESSION可能丢失

		//客服离线时, type为客服专有的session数组key(客服类型)
		if(isset($session['type']) AND $session['grid']){

			$aid = $session['aid'];

			//给组内其他客服发送离线信息
			$dd = array('x' => 2, 'a' => 2, 'aid' => $aid, 'i' => $session['fullname']);
			Gateway::sendToGroup($session['team_id'], json_encode($dd)); //worker组的客服组id, 避免重复, 客服前使用a_识别

			//给正在通话的客人发送离线通知
			$guest = false;
			$gid = $session['gid'];
			if($gid) $guest = self::getSessionByGid($gid);
			if($guest) self::sendToClient($guest['client_id'], array('x' => 6, 'a' => 2));

			//设置客服为离线状态
			self::$DB->exe("UPDATE " . TABLE_PREFIX . "admin SET online = 0 WHERE aid = '$aid'");

			return true;
		}

		//访客离线时, 排队取号成功, 且非重复登录的客人触发的离线(解决访客重复连接的问题)
		if(isset($session['gid']) AND $session['ticket_no'] AND $session['grid']){
			$dd = array('x' => 6, 'a' => 3, 'g' => $session['gid']); //给客服发送客人离线通知

			if($session['aix'] == self::$robot['client_id']){ //排队中, 给客服组发送离线通知

				$team_id = "a_" . $session['grid']; //worker客服组id

				Gateway::sendToGroup($team_id, json_encode($dd));

			}else{ //访客正在通话中, 仅给当前客服发送离线通知

				self::sendToClient($session['aix'], $dd);

				//删除此客人的session(通话时才有上传session和upload授权)
				self::$DB->exe("UPDATE " . TABLE_PREFIX . "guest SET upload = 0, session = ''  WHERE gid = '" . $session['gid'] . "' ");
			}
		}
   }


   /**
    * 给单个连接发送数据
    * @param string $client_id
    * @param array $data
    */
   private static function sendToClient($client_id, $data)
   {
		if(!$data) return false;

		Gateway::sendToClient($client_id, json_encode($data));
   }


   /**
    * 获取访客的SESSION
    * @param int $gid 访客id
    * 
    * @return array
    */
   private static function getSessionByGid($gid)
   {
		$gixs = Gateway::getClientIdByUid($gid); //客人的连接id, 数组
		$gix = $gixs[0]; //仅取第一个客人

		if(empty($gixs) OR !$gix) return array();

		return Gateway::getSession($gix);
   }


   /**
    * checkAdminRepeatConnect 检测客服重复连接
    * 
    * @param string $work_id  work客服id
    * @param int $group_id  客服组id
    * 
    * @return boolean
    */
   private static function checkAdminRepeatConnect($work_id, $group_id)
   {
	   $admins = Gateway::getClientIdByUid($work_id); //返回连接id数组
	   $re = false;

	   foreach($admins AS $client_id){
			$support = Gateway::getSession($client_id);

			if($support['grid'] <> $group_id){ //关闭非同组的连接
				self::sendToClient($client_id, array('x' => 6, 'a' => 4)); //发送一个废弃连接的信息
				Gateway::closeClient($client_id); //关闭连接
			}elseif($support['gid']){ //通话中
				$re = true;
			}else{
				//清除重复连接的客服的组id, 用于阻止给同组客服发送离线通知
				Gateway::updateSession($client_id, array('grid' => 0));

				//先给此连接的客服发送一个废弃连接的信息
				self::sendToClient($client_id, array('x' => 2, 'a' => 7, 'i' => ""));
				Gateway::closeClient($client_id); //关闭连接
			}
	   }

	   return $re;
   }

   /**
    * adminLogin 客服登录
    * @param string $client_id 连接id
    * @param object $data 登录时请求的数据对象
    */
   private static function adminLogin($client_id, $data)
   {
		$admin_id = forceInt($data->admin_id);
		$mobile = forceInt($data->mobile); //0来自web, 1来自移动端

		$agent = $data->agent; //浏览器
		$session_id = $data->session_id;

		//过滤非法字符
		if(!$admin_id OR !isAlnum($session_id) OR !isAlnum($agent)){
			Gateway::closeClient($client_id); //用户不合法, 断开连接, 不通知
			return false;
		}

		$sql = "SELECT a.aid, a.type, a.grid, a.fullname, a.fullname_en, a.post, a.post_en, g.sort, g.activated, g.groupname, g.groupname_en
					FROM " . TABLE_PREFIX . "session s
					LEFT JOIN " . TABLE_PREFIX . "admin a ON a.aid = s.aid
					LEFT JOIN " . TABLE_PREFIX . "group g ON a.grid = g.id
					WHERE s.sid    = '$session_id'
					AND s.aid = '$admin_id'
					AND s.agent = '$agent'
					AND a.activated = 1
					AND g.activated = 1";

		$admin = self::$DB->getOne($sql);
		if(!$admin OR !$admin['aid']){
			Gateway::closeClient($client_id); //用户不合法, 断开连接, 不通知
			return false;
		}

		// 认证成功，删除 6秒关闭连接 的定时器
		Timer::del($_SESSION['auth_timer_id']);

		//登录成功后
		$group_id = $admin['grid']; //客服组id
		$avatar = GetAvatar($admin_id, 1); //获取客服头像, 返回文件名
		$gid = 0; //初始化访客gid, 如果不为0, 则表示当前客服与访客正在通话中

		$work_id = "a_{$admin_id}"; //worker用户id, 区分客服和客人, 避免重复, 客服前使用a_识别
		$team_id = "a_{$group_id}"; //worker客服组id, 避免重复, 客服前使用a_识别

		//处理客服登录后的各种状态：重复连接、断线重连、通话中等情况
		if(Gateway::isUidOnline($work_id)){ //重复连接
			//如果客服在通话中, 禁止重复连接
			if(self::checkAdminRepeatConnect($work_id, $group_id) === true){
				self::sendToClient($client_id, array('x' => 2, 'a' => 7, 'i' => "isCalling"));
				Gateway::closeClient($client_id); //断开连接
				return false;
			}
		}

		//本组在线的客服数组(包括自己), 以及正在排队或正与自己通话的客人数组(但不包括与其它客服正在通话的客人)
		$guest_list = array();
		$admin_list = array();
		
		//其它连接:  断线重连或新连接
		//检查是否存在与此客服通话的客人
		$getGuests = Gateway::getClientSessionsByGroup($group_id);

		foreach($getGuests AS $ix => $gst) {
			if($gst['ticket_no'] AND $gst['aid'] == $admin_id) { //正在与当前客服通话中的客人
				$gid = $gst['gid']; //记录通话中的访客gid

				Gateway::updateSession($gst['client_id'], array('aix' => $client_id)); //更新客人session中正在通话的客服连接索引
				self::sendToClient($gst['client_id'], array('x' => 6, 'a' => 1)); //给通话中的客人发送上线通知
			}

			//获取本组正在排队的客人, 以及和当前登录客服正在通话的客人(不包含正在与其它客服通话的客人)
			if($gst['ticket_no'] AND (!$gst['aid'] OR $gst['aid'] == $admin_id)){
				$guest_list[] = array('g' => $gst['gid'], 'oid' => $gst['oid'], 'tn' => $gst['ticket_no'], 'n' => $gst['fullname'], 'iz' => $gst['ipzone'], 'l' => $gst['lang'], 'mb' => $gst['mb'], 'fr' => "");
			}
		}

		//更新在线状态
		self::$DB->exe("UPDATE " . TABLE_PREFIX . "admin SET online = 1  WHERE aid = '$admin_id'");

		//绑定work用户id不能重复, 客服前使用a_识别
		Gateway::bindUid($client_id, $work_id);

		//记录客服信息
		$_SESSION['client_id'] = $client_id; //客服的连接id
		$_SESSION['work_id'] = $work_id; //worker用户id
		$_SESSION['team_id'] = $team_id; //worker客服组id

		$_SESSION['aid'] = $admin_id;
		$_SESSION['type'] = $admin['type']; //客服类型: 1 管理员, 2组长, 0 客服
		$_SESSION['grid'] = $group_id; //客服组id
		$_SESSION['gid'] = $gid; //访客的gid, 如果不为0则表示通话中

		$_SESSION['fullname'] = $admin['fullname']; //昵称
		$_SESSION['fullname_en'] = $admin['fullname_en'];
		$_SESSION['post'] = $admin['post']; //职位
		$_SESSION['post_en'] = $admin['post_en'];

		$_SESSION['busy'] = 0; //是否忙碌(挂起状态)
		$_SESSION['avatar'] = $avatar; //头像文件名
		$_SESSION['mobile'] = $mobile; //0来自web, 1来自移动端

		//先通知本组已登录的其他客服
		$dd = array('x' => 2, 'a' => 1, 'aid' => $admin_id, 't' => $admin['type'], 'n' => $admin['fullname'], 'p' => $admin['post'], 'av' => $avatar, 'mb' => $mobile);
		Gateway::sendToGroup($team_id, json_encode($dd));

		//加入客服组
		Gateway::joinGroup($client_id, $team_id);

		//获取本组在线客服数据(包括自己)
		$getAdmins = Gateway::getClientSessionsByGroup($team_id);

		foreach($getAdmins AS $ix => $session) {
			$admin_list[] = array('aid' => $session['aid'], 't' => $session['type'], 'n' => $session['fullname'], 'p' => $session['post'], 'av' => $session['avatar'], 'b' => $session['busy'], 'mb' => $session['mobile']);
		}

		//获取聊天记录
		$recs = array();
		if($gid) $recs = self::getChatRecords($gid, $group_id);

		//iceConfig配置
		$iceConfig = array('stun' => self::$_CFG['STUN_Server'], 'turn' => self::$_CFG['TURN_Server'], 'user' => self::$_CFG['TURN_User'], 'pass' => self::$_CFG['TURN_Pass']);

		//将相关信息发送给当前登录的客服自己
		$dd = array('x' => 2, 'a' => 8, 'aid' => $admin_id, 'aix' => $client_id, 'gid' => $gid, 'ice' => $iceConfig, 'alist' => $admin_list, 'glist' => $guest_list, 'recs' => $recs);
		self::sendToClient($client_id, $dd);
   }


   /**
    * supportChat 客服群聊
    * @param string $client_id 连接id
    * @param string $msg 消息
    */
   private static function supportChat($client_id, $msg)
   {
		self::checkSupport(); //验证客服

		//管理员或组长特殊指令查询运行数据, 仅包含本组内相关数据
		if($_SESSION['type'] <> 0){
			$spec = 0;
			$titile = "运行 {$msg} 数据查询结果:";

			switch($msg){

				case 'all':
					$spec = 1;

					$admins = Gateway::getUidCountByGroup($_SESSION['team_id']); //本组所有客服人数
					$guests = 0; //本组所有客人数
					$guest_callings = 0; //通话中
					$guest_tickets = 0; //已取号(含通话中)

					$allguests = Gateway::getClientSessionsByGroup($_SESSION['grid']);
					foreach($allguests as $gix => $guest) {
						$guests += 1;

						if($guest['aid']) $guest_callings += 1;
						if($guest['ticket_no']) $guest_tickets += 1;
					}

					$guest_waitings = $guest_tickets - $guest_callings; //排队等候中(不含通话中)
					$guest_notickets = $guests - $guest_tickets; //未取号

					$team_totals = $admins + $guests; //本组全部

					$msg = Iif($_SESSION['type'] == 1, "全部在线 = " . Gateway::getAllClientIdCount() . "<br>"); //管理员专有

					$msg .= "本组在线 = {$team_totals}<br>本组客服 = {$admins}<br>本组访客 = {$guests}<br>通话中 = {$guest_callings}<br>取号等候 = {$guest_waitings}<br>未取号 = {$guest_notickets}";

					break;

				case 'admin':
					$spec = 1;
					$admins = 0; //本组所有客服
					$admin_callings = 0; //通话中
					$admin_busies = 0; //挂起

					$getAdmins = Gateway::getClientSessionsByGroup($_SESSION['team_id']);

					foreach($getAdmins AS $admin){
						$admins += 1;
						if($admin['gid']) $admin_callings += 1;
						if($admin['busy']) $admin_busies += 1;

					}

					$msg = "本组客服 = {$admins}<br>通话中 = {$admin_callings}<br>挂起中 = {$admin_busies}";

					break;

				case 'guest':
					$spec = 1;
					$guests = 0; //本组所有客人数
					$guest_callings = 0; //通话中
					$guest_tickets = 0; //已取号(含通话中)

					$allguests = Gateway::getClientSessionsByGroup($_SESSION['grid']);
					foreach($allguests as $gix => $guest) {
						$guests += 1;

						if($guest['aid']) $guest_callings += 1;
						if($guest['ticket_no']) $guest_tickets += 1;
					}

					$guest_waitings = $guest_tickets - $guest_callings; //排队等候中(不含通话中)
					$guest_notickets = $guests - $guest_tickets; //未取号

					$msg = "本组访客 = {$guests}<br>通话中 = {$guest_callings}<br>取号等候 = {$guest_waitings}<br>未取号 = {$guest_notickets}";

					break;

				case 'robot':
					$spec = 1;
					$guests = 0; //本组所有客人数
					$guest_callings = 0; //通话中
					$guest_robots = 0; //机器人服务中

					$allguests = Gateway::getClientSessionsByGroup($_SESSION['grid']);
					foreach($allguests as $gix => $guest) {
						$guests += 1;
						if($guest['aid']) $guest_callings += 1;
					}

					$guest_robots = $guests - $guest_callings;

					$msg = "本组访客 = {$guests}<br>通话中 = {$guest_callings}<br>机器人服务中 = {$guest_robots}";

					break;
			}

			//仅将查询数据发送给自己
			if($spec){
				$dd = array('x' => 1, 'aid'=> $_SESSION['aid'], 'av'=> $_SESSION['avatar'], 'n'=> $_SESSION['fullname'], 'p'=> $titile, 't' => $_SESSION['type'], 'i' => $msg);

				self::sendToClient($client_id, $dd);
				return true;
			}
		}

		$dd = array('x' => 1, 'aid'=> $_SESSION['aid'], 'av'=> $_SESSION['avatar'], 'n'=> $_SESSION['fullname'], 'p'=> $_SESSION['post'], 't' => $_SESSION['type'], 'i' => $msg);

		Gateway::sendToGroup($_SESSION['team_id'], json_encode($dd));
   }


   /**
    * sayToGuest 客服给客人发消息
    * @param string $client_id 连接id
    * @param int $gid 访客id
    * @param string $msg 消息
    */
   private static function sayToGuest($client_id, $gid, $msg)
   {
		self::checkSupport(); //验证客服

		if(!$gid OR !$msg) return;

		$guest = self::getSessionByGid($gid);

		if(!$guest) return;

		self::sendToClient($guest['client_id'], array('x' => 5, 'a' => 1, 'i' => $msg)); //给客人
		self::sendToClient($client_id, array('x' => 5, 'a' => 1, 'g' => $gid, 'i' => $msg)); //给自己

		$toname = Iif($guest['fullname'], $guest['fullname'], Iif($guest['lang'], '客人', 'Guest') . $gid);

		self::$DB->exe("INSERT INTO " . TABLE_PREFIX . "msg (type, grid, fromid, fromname, toid, toname, msg, time)
				VALUES (1, '" . $_SESSION['grid'] . "', '" . $_SESSION['aid'] . "', '" . $_SESSION['fullname'] . "', '$gid', '$toname', '$msg', '" . time() . "')");
   }


   /**
    * pickupGuest 接通访客语音通话
    * @param int $gid 访客gid
    * @param int $group_id 客服组id
    * @param int &$lang 引用参数, 改变外部变量值
    * 
    * @return boolean
    */
   private static function pickupGuest($gid, $group_id, &$lang)
   {
		if(!$gid OR !$group_id OR $_SESSION['gid']) return false; //当前客服正在通话(未挂断上一个通话)

		$guest = self::getSessionByGid($gid);
		if(!$guest OR $guest['aid'] OR $guest['grid'] <> $group_id) return false; //验证客人

		//验证此客人的排队票号是否为最小的未接通的票号(暂时不写此项验证代码)
		//$ticket_no = $guest['ticket_no'];

		$aid = $_SESSION['aid'];
		$session = md5(uniqid(COOKIE_KEY. microtime())); //只有通话中的访客才有session会话, 用于验证上传图片或文件

		if($guest['lang']){ //中文
			$lang = 1;
			$a_n = $_SESSION['fullname'];
			$a_p = $_SESSION['post'];
		}else{
			$lang = 0;
			$a_n = $_SESSION['fullname_en'];
			$a_p = $_SESSION['post_en'];
		}

		$avatar = $_SESSION['avatar'];

		//更新客人
		Gateway::updateSession($guest['client_id'], array('aid' => $aid, 'aix' => $_SESSION['client_id']));

		//获取聊天记录
		$recs = self::getChatRecords($gid, $group_id);

		//给客人发送通知		
		$dd = array('x' => 3, 'a' => 1, 'aid' => $aid, 'sess' => $session, 'an' => $a_n, 'p' => $a_p, 'av' => $avatar, 'recs' => $recs); 
		self::sendToClient($guest['client_id'], $dd);

		self::$DB->exe("UPDATE " . TABLE_PREFIX . "guest SET aid = '$aid', calls = (calls + 1), session = '$session' WHERE gid = '$gid'");

		return Iif($recs, $recs, true); //返回聊天记录或true
   }


   /**
    * supportHandle 管理员操作
    * @param string $client_id 连接id
    * @param object $data 数据对象
    */
   private static function supportHandle($client_id, $data)
   {
		self::checkSupport(); //验证客服

		$operate = $data->operate;

		switch($operate){

			case 'pickup': //请求接听访客
				$gid = forceInt($data->gid);
				$group_id = $_SESSION['grid'];
				$lang = 1; //中文

				$results = self::pickupGuest($gid, $group_id, $lang); //返回聊天记录, true OR false

				if($results){ //接通成功
					$_SESSION['gid'] = $gid; //更新客服

					if(is_array($results)){ //有聊天记录时, 只给接通的客服发送记录
						$getAdmins = Gateway::getClientSessionsByGroup($_SESSION['team_id']);
						foreach($getAdmins as $aix => $adm) {
							$dd = array('x' => 3, 'a' => 1, 'aid' => $_SESSION['aid'], 'g' => $gid, 'lang' => $lang, 'recs' => Iif($adm['aid'] == $_SESSION['aid'], $results, array()));
							self::sendToClient($aix, $dd);
						}					
					}else{ //没有聊天记录时
						$dd = array('x' => 3, 'a' => 1, 'aid' => $_SESSION['aid'], 'g' => $gid, 'lang' => $lang, 'recs' => array());
						Gateway::sendToGroup($_SESSION['team_id'], json_encode($dd)); //接通成功时给全组发通知
					}

				}else{ //接通失败
					$dd = array('x' => 3, 'a' => 2, 'g' => $gid);
					self::sendToClient($client_id, $dd); //接通失败时只给自己发信息
				}

				break;

			case 'hangup': //请求挂断访客
				$gid = forceInt($data->gid);
				$total_talk_time = forceInt($data->ttt); //通话时间, 秒
				$group_id = $_SESSION['grid'];
				$session_gid = $_SESSION['gid'];

				//更新客服及通知
				$_SESSION['gid'] = 0;
				self::sendToClient($client_id, array('x' => 3, 'a' => 3, 'g' => $gid));

				//挂断访客
				$guest = self::getSessionByGid($gid);
				if($guest AND $guest['aid'] == $_SESSION['aid'] AND $guest['grid'] == $group_id){
					$gix = $guest['client_id'];					
					Gateway::updateSession($gix, array('grid' => 0, 'ticket_no' => 0, 'aid' => 0, 'aix' => self::$robot['client_id'])); //grid设置为0, 用于阻止给客服组发送离线信息
					self::sendToClient($gix, array('x' => 3, 'a' => 3));
					Gateway::closeClient($gix); //断开连接

					//更新其它访客的排队等候人数
					$waitingGuests = array();

					//获取当前客服组中所有访客的session
					$sessions = Gateway::getClientSessionsByGroup($group_id);
					foreach($sessions as $gix => $sess) {
						if($sess['ticket_no'] AND $sess['gid'] <> $gid){
							if($sess['aid']) $gix = 0; //不给正在通话的访客发送更新等候人数的通知
							$waitingGuests[$sess['ticket_no']] = $gix;
						}
					}

					ksort($waitingGuests); //按ticket_no排序

					$tw = 0; //等候人数
					foreach($waitingGuests as $tn => $gix) {
						if($gix)self::sendToClient($gix, array('x' => 6, 'a' => 6, 'tw' => $tw)); //不给正在通话的访客发送更新等候人数的通知
						$tw += 1;
					}
				}

				//更新通话时间等
				if($total_talk_time AND $gid == $session_gid){
					self::$DB->exe("UPDATE " . TABLE_PREFIX . "guest SET upload = 0, totaltime = (totaltime + {$total_talk_time}), session = '' WHERE gid = '$gid'");
				}

				break;

			case 'get_guest': //获取客人信息

				$gid = forceInt($data->guestid);

				if($gid AND $gid == $_SESSION['gid']){
					$guest = self::$DB->getOne("SELECT lastip, ipzone, fromurl, grade, fullname, remark FROM " . TABLE_PREFIX . "guest WHERE gid = '$gid'");
					if(!empty($guest)) {
						$dd = array('x' => 2, 'a' => 5, 'g' => $gid, 'd' => $guest);
						self::sendToClient($client_id, $dd); //返回数据给自己
					}
				}

				break;

			case 'save_guest': //保存客人信息

				$gid = forceInt($data->guestid);

				if($gid AND $gid == $_SESSION['gid']){
					$msg = $data->msg;

					$grade = forceInt($msg->grade);
					$fullname = forceString($msg->fullname);
					$remark = forceString($msg->remark);

					//更新在线访客的姓名
					$guest = self::getSessionByGid($gid);
					if($guest) Gateway::updateSession($guest['client_id'], array('fullname' => $fullname));

					//保存到数据库
					self::$DB->exe("UPDATE " . TABLE_PREFIX . "guest SET grade = '$grade', fullname = '$fullname', remark = '$remark' WHERE gid = '$gid'");

					$dd = array('x' => 2, 'a' => 6, 'g' => $gid, 'fullname' => $fullname); //返回数据给自己
					self::sendToClient($client_id, $dd);
				}

				break;

			case 'save_phrase': //保存快捷回复
				$aid = $_SESSION['aid'];
				$isDel = forceInt($data->del);

				//删除
				if($isDel){
					$pid = forceInt($data->pid);
					self::$DB->exe("DELETE FROM " . TABLE_PREFIX . "phrase WHERE aid = '$aid' AND pid = '$pid'");
					return true;
				}

				$lang = forceInt($data->lang);
				$msg = forceString($data->msg);

				if(!$msg) return false;

				self::$DB->exe("INSERT INTO " . TABLE_PREFIX . "phrase (aid, activated, lang, msg) VALUES ('$aid', 1, '$lang', '$msg')");

				$lastid = self::$DB->insert_id;
				self::$DB->exe("UPDATE " . TABLE_PREFIX . "phrase SET sort = '$lastid' WHERE pid = '$lastid'");

				$dd = array('x' => 2, 'a' => 9, 'pid' => $lastid, 'lang' => $lang, 'msg' => $msg); //返回数据给自己
				self::sendToClient($client_id, $dd);

				break;

			case 'auth_upload': //授权上传文件及解除授权

				$gid = forceInt($data->guestid);
				$auth = forceInt($data->auth);

				$auth = Iif($auth, 1, 0);
				$reply = Iif($auth, 1, 3); //发送给访客的信息

				$guest = self::getSessionByGid($gid);
				if(!$guest OR $guest['aid'] <> $_SESSION['aid']) return false;

				$gix = $guest['client_id'];

				self::sendToClient($gix, array('x' => 7, 'a' => $reply)); //给客人发送授权通知
				Gateway::updateSession($gix, array('au' => $auth));

				self::$DB->exe("UPDATE " . TABLE_PREFIX . "guest SET upload = '$auth' WHERE gid = '$gid'"); //授权上传记录在案

				break;

			case 'setbusy': //挂起及解除

				$value = forceInt($data->value);

				if($value){ //挂起
					$reply = 3;
					$_SESSION['busy'] = 1;
				}else{ //解除
					$reply = 4;
					$_SESSION['busy'] = 0;
				}

				Gateway::sendToGroup($_SESSION['team_id'], json_encode(array('x' => 2, 'a' => $reply, 'aid' =>$_SESSION['aid'])));

				break;
		}
   }


   /**
    * 客服验证
    * 
    * @return null
    */
   private static function checkSupport()
   {
		//客服类型type: 1 管理员, 2组长, 0 客服
		if(isset($_SESSION['aid']) AND isset($_SESSION['type'])) return;

		//非法操作关闭之
		Gateway::closeClient($_SESSION['client_id']);
   }


   /**
    * 管理员验证
    * 
    * @return null
    */
   private static function checkAdmin()
   {
		//客服类型type: 1 管理员, 2组长, 0 客服
		if(isset($_SESSION['aid']) AND isset($_SESSION['type']) AND $_SESSION['type'] == 1) return;

		//非法操作关闭之
 		Gateway::closeClient($_SESSION['client_id']);
  }

   /**
    * 客服组设置操作验证, 验证管理员或组长
    * 
    * @return null
    */
   private static function checkTeamSetting()
   {
		//客服类型type: 1 管理员, 2组长, 0 客服
		if(isset($_SESSION['aid']) AND isset($_SESSION['type']) AND $_SESSION['type'] <> 0) return;

		//非法操作关闭之
 		Gateway::closeClient($_SESSION['client_id']);
   }


	/**
    * getChatRecords 获取访客的对话记录
    * @param int $gid 访客id
    * @param int $group_id 客服组
    * 
    * return array
    */
   private static function getChatRecords($gid, $group_id)
   {
	   $recs = array();

		$limit = forceInt(self::$_CFG['Record']);

		if($limit){
			$records = self::$DB->query("SELECT type, fromid, fromname, msg, filetype, time FROM " . TABLE_PREFIX . "msg WHERE grid = '$group_id' AND ((type = 0 AND fromid = '$gid') OR (type = 1 AND toid = '$gid')) ORDER BY mid DESC LIMIT $limit");

			while($r = self::$DB->fetch($records)){
				$recs[] = array('t' =>$r['type'], 'fid' =>$r['fromid'], 'f' =>$r['fromname'], 'm' => $r['msg'], 'ft' => $r['filetype'], 'd' => displayDate($r['time'], 'H:i:s', 1));
			}

			$recs = array_reverse($recs); //数组反转一下
		}

		return $recs;
   }

	/**
    * guestLogin 访客登录验证
    * @param string $client_id 连接id
    * @param object $data 登录时请求的数据对象
    */
	private static function guestLogin($client_id, $data)
	{
		$key = $data->key;
		$code = $data->code;
		$decode = authCode($code, 'DECODE', $key);
		if($decode != md5(WEBSITE_KEY . self::$_CFG['KillRobotCode'])){
			Gateway::closeClient($client_id); //非法连接关闭之
			return false;
		}

		// 认证成功，删除 6秒关闭连接 的定时器
		Timer::del($_SESSION['auth_timer_id']);

		$lastip = $_SERVER['REMOTE_ADDR']; //获得连接的ip

		//WeCaller防火墙
		if(preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/", $lastip)){
			$is_banned = self::$DB->getOne("SELECT fid FROM " . TABLE_PREFIX . "firewall WHERE ip = '$lastip' AND expire > " . time());
			if($is_banned){
				self::sendToClient($client_id, array('x' => 6, 'a' => 6)); //访客显示被踢出
				Gateway::closeClient($client_id); //关闭连接

				self::$DB->exe("UPDATE " . TABLE_PREFIX . "firewall SET bans = (bans + 1) WHERE fid = " . $is_banned['fid']); //记录次数
				return false;
			}
		}

		//客人验证成功
		$gid = forceInt($data->gid); //如果有访客id
		$oid = forceInt($data->oid); //原调用网站用户id ---接口
		$aid = 0; //初始化客服id(如果不为0则表示通话中)

		$group_id = forceInt($data->grid); //客服组id
		$fullname = forceString(html($data->fn)); //客人姓名, 先转换成html再过虑, 否则会&等符号会被重复过虑

		$ticket_no = forceInt($data->tn); //当前访客排队票号(如果不为0, 则为断线重连)
		$ticket_waitings = 0; //当前票号需要等多少号

		$support = null; //当前服务的客服或机器人
		$session = ""; //默认无上传session会话

		//检查非默认客服组是否存在或关闭
		if($group_id <> 1){
			$checkgroup = self::$DB->getOne("SELECT id FROM " . TABLE_PREFIX . "group WHERE id = '$group_id' AND activated = 1");

			if(!$checkgroup){
				if(self::$_CFG['AutoTrans']){
					$group_id = 1; //客服组不存在或已关闭时，自动转接至默认客服组
				}else{
					self::sendToClient($client_id, array('x' => 6, 'a' => 9, 'i' => "noGroup"));
					Gateway::closeClient($client_id); //关闭连接
					return false;
				}
			}
		}
		
		$guest = false;
		$recs = array(); //对话记录

		//如果有原网站用户id
		if($oid){
			$guest = self::$DB->getOne("SELECT gid, aid, grid, fullname, upload FROM " . TABLE_PREFIX . "guest WHERE oid = '$oid'");
			if(!$guest) $oid = 0; //重设oid为0
		}

		//获取访客数据
		if($guest){
			$gid = $guest['gid']; //继续使用原gid
			if(!$fullname AND $guest['fullname']) $fullname = $guest['fullname']; //以传送来的昵称为准
		}elseif($gid){
			$guest = self::$DB->getOne("SELECT gid, aid, grid, fullname, upload FROM " . TABLE_PREFIX . "guest WHERE gid = '$gid'");

			if($guest) {
				if(!$fullname AND $guest['fullname']) $fullname = $guest['fullname'];
			}else{
				$gid = 0; //重设gid为0
			}
		}

		//处理访客登录后的各种状态：重复连接、断线重连、通话中等情况
		if($gid AND Gateway::isUidOnline($gid)){ //重复连接
			$re = self::checkGuestRepeatConnect($gid, $group_id);

			if($re === true){ //如果访客已经在通话中, 禁止重复连接
				self::sendToClient($client_id, array('x' => 6, 'a' => 9, 'i' => "isCalling"));
				Gateway::closeClient($client_id); //断开连接
				return false;
			}

			$ticket_no = $re; //已取号(合法)

		}elseif($gid AND $ticket_no){ //已取号的断线重连

			//检查是否已经在通话中
			$getAdmins = Gateway::getClientSessionsByGroup("a_{$group_id}");

			foreach($getAdmins AS $ix => $admin) {
				if($admin['gid'] == $gid) {
					$aid = $admin['aid']; //访客通话中
					$support = $admin; //记录正在服务的客服session
					break;
				}
			}

			//不在通话中, 则验证断线重连的排队票号是否合法
			if(!$aid AND !self::checkRelinkTicket($ticket_no, $group_id)) $ticket_no = 0; //不合法, 设置为0重新取号
		}
		
		//分配访客给客服或机器人
		if($aid){
			//通话中, 说明已经分配给客服
			$aix = $support['client_id']; //客服的连接id

			//产生一个session会话记录, 用于验证上传图片或文件, 以免产生非法操作
			$session = md5(uniqid(COOKIE_KEY. microtime()));

			//获取聊天记录
			$recs = self::getChatRecords($gid, $group_id);

		}else{
			//将访客分配给机器人
			$support = self::$global->robot;
			$aix = self::$robot['client_id']; //机器人的连接id

			//访客排队取号
			if($ticket_no){

				//已取号时(即重复连接且已取过号), 重新计算等多少号
				$ticket_waitings = self::getTicketWaitings($ticket_no, $group_id); //当前票号需要等多少号

			}else{ //未取号时取号
				$get_ticket = self::getTicket($group_id);

				//取号失败(号满时)
				if($get_ticket === false){
					self::sendToClient($client_id, array('x' => 6, 'a' => 9, 'i' => "noTicket"));
					Gateway::closeClient($client_id); //关闭连接
					return false;
				}else{
					//机器人服务(无号) 或 取号成功(有号)
					$ticket_no = $get_ticket['ticket_no'];
					$ticket_waitings = $get_ticket['ticket_waitings']; //当前票号需要等多少号
				}
			}
		}

		//记录访客信息
		$lang = forceInt($data->lang);
		$fromurl = forceString($data->fromurl);
		$browser = forceString($data->agent);
		$mobile = forceInt($data->mobile); //是否为移动端
		$timenow = time();

		//获取并处理IP地址归属地
		$ipzone = trim(convertipTiny($lastip));
		if($ipzone != "中国") $ipzone = str_replace(array("中国", "台湾", "香港", "澳门"), array("", "中国台湾", "中国香港", "中国澳门"), $ipzone);

		//更新访客信息
		if($gid){ //老客人更新信息(包括断线重连的访客)

			//每次连接均设置上传文件权限为0, 也就上传文件总是需要授权
			self::$DB->exe("UPDATE " . TABLE_PREFIX . "guest SET grid = '$group_id', oid = '$oid', upload = '0', lang ='$lang', logins = (logins + 1), last = '$timenow', lastip = '$lastip', ipzone = '$ipzone', browser = '$browser', mobile = '$mobile', fromurl = '$fromurl', fullname = '$fullname', session = '$session' WHERE gid = '$gid'");

		}else{ //添加新客人

			self::$DB->exe("INSERT INTO " . TABLE_PREFIX . "guest (grid, oid, lang, last, lastip, ipzone, browser, mobile, fromurl, fullname, remark, session)
					VALUES ('$group_id', '$oid', '$lang', '$timenow', '$lastip', '$ipzone', '$browser', '$mobile', '$fromurl', '$fullname', '', '$session')");

			$gid = self::$DB->insert_id; //新客人的gid号
		}

		//绑定访客id
		Gateway::bindUid($client_id, $gid);

		//记录访客信息
		$_SESSION['client_id'] = $client_id;
		$_SESSION['gid'] = $gid;
		$_SESSION['grid'] = $group_id; //访客组id, 即客服组id
		$_SESSION['oid'] = $oid; //原网站用户id ----暂时无用
		$_SESSION['fullname'] = $fullname;
		$_SESSION['ipzone'] = $ipzone;
		$_SESSION['mb'] = $mobile;
		$_SESSION['au'] = 0; //初绐无上传权文件权限
		$_SESSION['lang'] = $lang; //语言

		$_SESSION['aid'] = $aid; //正在通话的客服id或0
		$_SESSION['aix'] = $aix; //客服或机器人的client_id

		$_SESSION['ticket_no'] = $ticket_no; //排队票号
		$_SESSION['ticket_waitings'] = $ticket_waitings; //当前票号需要等多少号

		//加入访客组
		Gateway::joinGroup($client_id, $group_id);

		//发送客人登录成功通知, 及客服信息
		if($lang){ //中文
			$a_n = $support['fullname'];
			$a_p = $support['post'];
		}else{
			$a_n = $support['fullname_en'];
			$a_p = $support['post_en'];
		}

		$avatar = $support['avatar'];

		//iceConfig配置
		$iceConfig = array('stun' => self::$_CFG['STUN_Server'], 'turn' => self::$_CFG['TURN_Server'], 'user' => self::$_CFG['TURN_User'], 'pass' => self::$_CFG['TURN_Pass']);

		//登录成功返回信息给自己
		$dd = array('x' => 6, 'a' => 8, 'gid' => $gid, 'aid' => $aid, 'tn' => $ticket_no, 'tw' => $ticket_waitings, 'sess' => $session, 'fn' => $fullname, 'an' => $a_n, 'p' => $a_p, 'av' => $avatar, 'ice' => $iceConfig, 'recs' => $recs);
		self::sendToClient($client_id, $dd);

		//给客服发上线通知
		$dd = array('x' => 6, 'a' => 8, 'g' => $gid, 'oid' => $oid, 'tn' => $ticket_no, 'n' => $fullname, 'fr' => $fromurl, 'iz' => $ipzone, 'l' => $lang, 'mb' => $mobile);

		if($aid){
			//给正在通话中的客服发送上线通知(访客断线重连)
			self::sendToClient($aix, $dd);
		}elseif($ticket_no){
			//将已取号的访客登录信息发送给本组所有客服(不包括已挂起的客服)
			if(!isset($getAdmins)) $getAdmins = Gateway::getClientSessionsByGroup("a_{$group_id}");
			foreach($getAdmins as $aix => $admin) {
				if(!$admin['busy']) self::sendToClient($aix, $dd);
			}
		}
	}


	/**
    * getServingSupports 获取正在服务中的客服人数(不包括在线但挂起的客服)
	*
    * @param int $group_id 客服组id
	*
    * @return int
    */
	private static function getServingSupports($group_id)
	{
		$num = 0;

		if(!$group_id) return $num;

		$team_id = "a_{$group_id}"; //worker客服组id, 避免重复, 客服前使用a_识别

		//获取当前客服组中所有客服的session
		$sessions = Gateway::getClientSessionsByGroup($team_id);

		foreach($sessions as $client_id => $session) {
			if(!$session['busy']) $num += 1; //未挂起, 在线客服数加1
		}

		return $num;
	}


   /**
    * 客人验证
    * 
    * @return null
    */
	private static function checkGuest()
	{
		if(isset($_SESSION['gid']) AND isset($_SESSION['aid'])) return;

		//非法操作关闭之
		Gateway::closeClient($_SESSION['client_id']);
	}


   /**
    * sayToSupport 客人给客服发消息
    * @param string $client_id 连接id
    * @param string $msg 消息
    */
   private static function sayToSupport($client_id, $msg)
   {
		self::checkGuest(); //验证客人

		if(!$msg) return;

		$aix = $_SESSION['aix']; //客服的连接索引

		//此客人由机器人服务时转交给机器人
		if($aix == self::$robot['client_id']){
			self::sendToClient($client_id, array('x' => 5, 'a' => 2)); //返回给客人自己一条简单信息
			self::supportByRobot($client_id, $msg);
			return true;
		}

		$support = Gateway::getSession($aix);
		if(!$support) return;

		self::sendToClient($client_id, array('x' => 5, 'a' => 2)); //返回给客人自己一条简单信息

		$dd = array('x' => 5, 'a' => 2, 'g' => $_SESSION['gid'], 'i' => $msg);
		self::sendToClient($aix, $dd); //将信息发给客服

		$fromid = $_SESSION['gid'];
		$fromname = Iif($_SESSION['fullname'], $_SESSION['fullname'], Iif($_SESSION['lang'], '客人', 'Guest') . $fromid);

		self::$DB->exe("INSERT INTO " . TABLE_PREFIX . "msg (type, grid, fromid, fromname, toid, toname, msg, time)
				VALUES (0, '" . $_SESSION['grid'] . "', '$fromid', '$fromname', '" . $_SESSION['aid'] . "', '" . $support['fullname'] . "', '$msg', '" . time() . "')");
   }


   /**
    * 机器人自动回复
    * @param string $client_id 连接id
    * @param string $msg  消息
    * @param int $special  指访客的特殊操作  1: 请求回电话;    2: 未定
    */
	private static function supportByRobot($client_id, $msg, $special = 0){

		$lang = $_SESSION['lang']; //访客语言
		$robot = self::$global->robot;

		//全为表情符号回复表情
		$msg = trim(preg_replace("/\[:(\d+):\]/i", '', $msg));
		if(!$msg){
			self::sendToClient($client_id, array('x' => 5, 'a' => 1, 'i' => "[:2:][:6:][:4:][:3:]", 'av' => "robot/11.png")); //给客人
			return true;
		}

		//免费版机器人功能受限
		if($lang){
			$num = count($robot["no_answers"]);
			$select = rand(0, $num - 1);
			$reply = $robot["no_answers"][$select];
		}else{
			$num = count($robot["no_answers_en"]);
			$select = rand(0, $num - 1);
			$reply = $robot["no_answers_en"][$select];
		}

		self::sendToClient($client_id, array('x' => 5, 'a' => 1, 'i' => "{$reply}", 'av' => "robot/3.png")); //给客人
	}


   /**
    * guestHandle 访客发起的相关操作
    * @param string $client_id 连接id
    * @param object $data 数据对象
    */
   private static function guestHandle($client_id, $data)
   {
		self::checkGuest(); //验证客人

		$operate = $data->operate;

		switch($operate){

			case 'rating': //服务评价

				$gid = $_SESSION['gid']; //访客id
				$aid = $_SESSION['aid']; //客服id

				$score = forceInt($data->star);
				$msg = forceString($data->msg);

				if(!$score) return false;

				//每天仅允许评价2次
				$max_rating = 2;
				$sql_time = time() - 3600*24;
				$result = self::$DB->getOne("SELECT COUNT(rid) AS nums FROM " . TABLE_PREFIX . "rating WHERE gid = '{$gid}' AND aid = '{$aid}' AND time > '{$sql_time}'");

				if($result AND $result['nums'] >= $max_rating){
					self::sendToClient($client_id, array('x' => 6, 'a' => 7, 's' => 2)); //失败 评价超每天限制的数量
					return false;
				}

				self::$DB->exe("INSERT INTO " . TABLE_PREFIX . "rating (gid, aid, score, msg, time) VALUES ('$gid', '$aid', '$score', '$msg', '" . time() . "')");
				self::sendToClient($client_id, array('x' => 6, 'a' => 7, 's' => 1)); //成功 返回给客人自己

				break;

			case 'offline': //访客自动离线

				self::sendToClient($client_id, array('x' => 6, 'a' => 5)); //返回给自己
				Gateway::closeClient($client_id); //断开连接

				break;
		}
   }


   /**
    * checkGuestRepeatConnect 检测访客重复连接
    * 
    * @param int $gid 访客id
    * @param int $group_id 客服组id
    * 
    * @return mix
    */
   private static function checkGuestRepeatConnect($gid, $group_id)
   {
	   $client_ids = Gateway::getClientIdByUid($gid); //返回连接id数组

	   $re = false;
	   $ticket_no = 0;

	   foreach($client_ids AS $client_id){
			$guest = Gateway::getSession($client_id);

			if($guest['grid'] <> $group_id){ //关闭非同组的连接
				self::sendToClient($client_id, array('x' => 6, 'a' => 4)); //发送一个废弃连接的信息
				Gateway::closeClient($client_id); //关闭连接
				continue;
			}elseif($guest['aid']){ //通话中
				$re = true;
				continue;
			}

			//已经排队成功, 获取最小的排队票号
			if($guest['ticket_no']){
				if(!$ticket_no OR $ticket_no > $guest['ticket_no'])  $ticket_no = $guest['ticket_no'];

				//清除重复连接且已经取号成功的客人的grid, 用于阻止给客服组发送离线信息
				Gateway::updateSession($client_id, array('grid' => 0));
			}

			//给此连接的访客发送一个废弃连接的信息
			self::sendToClient($client_id, array('x' => 6, 'a' => 4));
			Gateway::closeClient($client_id); //关闭连接
	   }

	   if($re){
		   return $re;
	   }else{
		   return $ticket_no;
	   }
   }


   /**
    * getTicket 访客排队取号
    * 
    * @param int $group_id 客服组id
    * 
    * @return mix
    */
   private static function getTicket($group_id)
   {
		//获取本组服务中的客服人数(不包括已挂起的客服)
		$online_supports = self::getServingSupports($group_id);

		$ticket_no = 0; //取得的票号
		$ticket_waitings = 0; //当前票号需要等多少号

		//有客服在线时, 检测是否超过排队人数限制
		if($online_supports){
			//当前用户组全部排队访客数
			$check_tickets = self::getGuestWaitings($group_id);

			$MaxWaitings = forceInt(self::$_CFG['MaxWaitings']);
			if(!$MaxWaitings)  $MaxWaitings = 5;

			if($check_tickets['waitings'] >= $online_supports * $MaxWaitings){

				return false; //排队总人数超过允许排队总数时, 连接将断开

			}else{

				$ticket_no = $check_tickets['max_ticket'] + 1; //取得的票号
				$ticket_waitings = $check_tickets['waitings']; //当前票号需要等多少号

			}
		}

		return array('ticket_no' => $ticket_no, 'ticket_waitings' => $ticket_waitings);
   }


	/**
    * getGuestWaitings 获取正在排队的访客人数及最大票号(包括正在通话中的访客)
	*
    * @param int $group_id 客服组id
	*
    * @return array
    */
	private static function getGuestWaitings($group_id)
	{
		$num = 0;
		$max_ticket = 0;

		//获取当前客服组中所有访客的session
		$sessions = Gateway::getClientSessionsByGroup($group_id);

		foreach($sessions as $client_id => $session) {
			if($session['ticket_no']){
				$num += 1;

				if($session['ticket_no'] > $max_ticket) $max_ticket = $session['ticket_no']; //获取最大的排队票号
			}
		}

		return array('waitings' => $num, 'max_ticket' => $max_ticket);
	}


	/**
    * checkRelinkTicket 验证断线重连的排队票号是否合法, 解决插队的问题(不含重复连接的情况)
	*
    * @param int $ticket_no 访客某排队票号
    * @param int $group_id 客服组id
	*
    * @return boolean
    */
	private static function checkRelinkTicket($ticket_no, $group_id)
	{
		if(!self::getServingSupports($group_id)) return false; //无服务客服, 此票号无效

		$prev_guest = null; //等检测票号前一位访客的session
		$next_guest = null; //等检测票号后一位访客的session

		//获取当前客服组中所有访客的session
		$sessions = Gateway::getClientSessionsByGroup($group_id);

		foreach($sessions as $client_id => $session) {

			if($session['ticket_no'] == $ticket_no) return false; //如果在线访客中有此票号, 则待验票号非法

			if($session['ticket_no'] < $ticket_no AND (!$prev_guest OR $prev_guest['ticket_no'] < $session['ticket_no'])){
				$prev_guest = $session; //查找前面最大票号的session
			}elseif($session['ticket_no'] > $ticket_no AND (!$next_guest OR $next_guest['ticket_no'] > $session['ticket_no'])){
				$next_guest = $session; //查找后面最小票号的session
			}
		}

		if(!$next_guest) return true; //如果后面没人排队, 此票号合法
		if($next_guest['aid']) return false; //如果后一位已经在通话中, 此票号非法

		return true; //其它情况合法
	}


	/**
    * getTicketWaitings 获取某排队票号前面有多少人数
	*
    * @param int $ticket_no 访客某排队票号
    * @param int $group_id 客服组id
	*
    * @return int
    */
	private static function getTicketWaitings($ticket_no, $group_id)
	{
		$num = 0;

		//获取当前客服组中所有访客的session
		$sessions = Gateway::getClientSessionsByGroup($group_id);

		foreach($sessions as $client_id => $session) {
			if($session['ticket_no'] AND $session['ticket_no'] < $ticket_no){
				$num += 1;
			}
		}

		return $num;
	}

}


?>