<?php
/****************************************************************************************
* LiveZilla objects.global.users.inc.php
* 
* 
* Improper changes to this file may cause critical errors.
***************************************************************************************/ 

if(!defined("IN_LIVEZILLA"))
	die();
	
require(LIVEZILLA_PATH . "_lib/objects.global.inc.php");

class BaseUser extends BaseObject
{
	public $SessId;
	public $UserId;
	public $Language;
	public $SystemId;
	public $Messages = array();
	public $Status = 2;
	public $Type;
	public $Folder;
	public $SessionFile;
	public $FirstActive;
	public $LastActive;
    public $IsDynamic = false;
	public $Typing = false;
    public $AutoReplies = array();
    public $FullyLoaded = false;

	function __construct($_userid)
   	{
		$this->UserId = $_userid;
   	}

	function AppendPersonalData()
	{

	}
}

class ChatMember
{
	public $SystemId;
	public $Status;
	public $Declined;
	public $Joined;
	public $Left;
	
	function __construct($_systemId, $_status, $_declined=false, $_joined=0, $_left=0)
	{
		$this->SystemId = $_systemId;
		$this->Status = $_status;
		$this->Declined = $_declined;
		$this->Joined = $_joined;
		$this->Left = $_left;
	}

    function GetXML()
    {
        return "<m i=\"" . base64_encode($this->SystemId) . "\" s=\"".base64_encode($this->Status)."\" d=\"".base64_encode(($this->Declined)?1:0)."\" />\r\n";
    }
}

class UserGroup extends BaseUser
{
	public $Descriptions;
	public $DescriptionArray;
	public $IsExternal;
	public $IsInternal;
	public $IsStandard;
	public $PredefinedMessages = array();
    public $Signatures = array();
	public $Created;
	public $Email;
	public $ChatFunctions;
	public $VisitorFilters;
	public $ChatInputsHidden = array();
	public $ChatInputsMandatory = array();
    public $ChatInputsMasked = array();
    public $ChatInputsCapitalized = array();
    public $ChatPriorities = array();
    public $ChatPrioritySleep;
	public $TicketInputsHidden = array();
	public $TicketInputsMandatory = array();
    public $TicketInputsMasked = array();
    public $TicketInputsCapitalized = array();
    public $TicketAssignment = array();
	public $TicketSenderName;
	public $OpeningHours = array();
	public $Members;
	public $Owner;
    public $TicketEmailOut;
    public $TicketEmailIn = array();
    public $TicketHandleUnknownEmails;
    public $ChatEmailOut;
    public $Position = 0;
    public $SendTicketNotifier = false;

	function __construct()
	{
		if(func_num_args() > 0)
		{
			$this->Id = $this->SystemId = func_get_arg(0);
			$row = (func_num_args() > 1) ? func_get_arg(1) : null;
			
			if(!empty($row))
			{
				if(!empty($row["dynamic"]))
				{
					$this->Owner = $row["owner"];
					$this->IsDynamic = true;
					$this->Descriptions["EN"] = $row["name"];
					$this->LoadMembers();
				}
				else
				{
					$this->Descriptions = @unserialize($row["description"]);
					$this->DescriptionArray = $row["description"];

					$this->IsInternal = !empty($row["internal"]);
					$this->IsExternal = !empty($row["external"]);
					$this->IsStandard = !empty($row["standard"]);

                    if($row["max_chats"] < 1)
                        $this->MaxChatAmount = 9999;
                    else if($row["max_chats"] > 30)
                    {
                        $this->MaxChatsStatus = USER_STATUS_AWAY;
                        $this->MaxChatAmount = $row["max_chats"]-30;
                    }
                    else
                        $this->MaxChatAmount = $row["max_chats"];

                    $this->MaxChats = $row["max_chats"];
					$this->Created = $row["created"];

                    if(!empty($row["opening_hours"]))
					    $this->OpeningHours = @unserialize($row["opening_hours"]);
                    else
                        $this->OpeningHours = array();

					$this->Email = $row["email"];

                    $this->ChatPrioritySleep = !empty($row["priority_sleep"]);
                    $this->SendTicketNotifier = !empty($row["ticket_notifier"]);

                    if(isset($row["position"]))
                        $this->Position = $row["position"];
						
					$this->VisitorFilters = (!empty($row["visitor_filters"])) ? @unserialize($row["visitor_filters"]) : array();
					$this->ChatFunctions = str_split($row["functions"]);

                    $this->ChatInputsHidden = (!empty($row["chat_inputs_hidden"])) ? @unserialize($row["chat_inputs_hidden"]) : array();
                    $this->ChatInputsMandatory = (!empty($row["chat_inputs_required"])) ? @unserialize($row["chat_inputs_required"]) : array();
                    $this->TicketInputsHidden = (!empty($row["ticket_inputs_hidden"])) ? @unserialize($row["ticket_inputs_hidden"]) : array();
                    $this->TicketInputsMandatory = (!empty($row["ticket_inputs_required"])) ? @unserialize($row["ticket_inputs_required"]) : array();

                    $this->ChatInputsMasked = (!empty($row["chat_inputs_masked"])) ? @unserialize($row["chat_inputs_masked"]) : array();
                    $this->TicketInputsMasked = (!empty($row["ticket_inputs_masked"])) ? @unserialize($row["ticket_inputs_masked"]) : array();
                    $this->ChatInputsCapitalized = (!empty($row["chat_inputs_cap"])) ? @unserialize($row["chat_inputs_cap"]) : array();
                    $this->TicketInputsCapitalized = (!empty($row["ticket_inputs_cap"])) ? @unserialize($row["ticket_inputs_cap"]) : array();
                    $this->ChatPriorities = (!empty($row["priorities"])) ? @unserialize($row["priorities"]) : array();

                    $this->TicketAssignment = (!empty($row["ticket_assignment"])) ? @unserialize($row["ticket_assignment"]) : array();
                    $this->TicketEmailIn = @unserialize(@$row["ticket_email_in"]);
                    $this->TicketEmailOut = @$row["ticket_email_out"];
                    $this->TicketHandleUnknownEmails = @$row["ticket_handle_unknown"];
                    $this->ChatEmailOut = @$row["chat_email_out"];
                    $this->TicketSenderName = $row["ticket_sender_name"];
				}
			}
		}
	}

    function GetTicketSenderName($_ticket)
    {
        $opName = "";
        $_ticket->LoadStatus();
        if($_ticket->Editor != null && isset($_ticket->Editor->Editor) && isset(Server::$Operators[$_ticket->Editor->Editor]))
        {
            $opName = Server::$Operators[$_ticket->Editor->Editor]->Fullname;
        }

        if($this->TicketSenderName == "1")
        {
            // group name
            return $this->GetDescription($_ticket->Language,false);
        }
        else if($this->TicketSenderName == "2")
        {
            // operator name
            return $opName;
        }
        else if(strpos($this->TicketSenderName,";") !== false)
        {
            // custom name
            $parts = explode(";",$this->TicketSenderName);
            $text = base64_decode($parts[1]);
            $text = str_replace("%group_title%",$this->GetDescription($_ticket->Language,false),$text);
            $text = str_replace(array("%name%","%operator_name%"),$opName,$text);
            return $text;
        }
        // default mailbox name
        return "";
    }

	function LoadMembers()
	{
        $this->Members = array();
		$result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_GROUP_MEMBERS . "` WHERE `group_id`='" . DBManager::RealEscape($this->Id) . "';");
		if($result)
			while($row = DBManager::FetchArray($result))
				$this->Members[$row["user_id"]] = !empty($row["persistent"]);
	}

    function IsOpeningHour($_ignoreBots=true)
    {
        Server::InitDataBlock(array("INTERNAL"));
        if(!$_ignoreBots && $this->ContainsBot())
            return true;

		$sofday = time() - mktime(0,0,0);

		foreach($this->OpeningHours as $hour)
		{
			if(date("N") == $hour[0])
			{
				if($sofday >= $hour[1] && $sofday <= $hour[2])
					return true;
			}
		}
		return (count($this->OpeningHours) == 0);
	}

    function IsHumanAvailable($_ignoreExternal=false,$_ignoreOpeningHours=false)
    {
        $list = $this->GetAvailableOperators($_ignoreExternal,$_ignoreOpeningHours);
        return count($list) > 0;
    }

	function GetAvailableOperators($_ignoreExternal=false,$_ignoreOpeningHours=false)
	{
        $list = array();
		foreach(Server::$Operators as $internaluser)
			if(in_array($this->Id,$internaluser->Groups) && !$internaluser->IsBot)
			{
				$isex = $internaluser->IsExternal(Server::$Groups, null, array($this->Id), $_ignoreExternal,$_ignoreOpeningHours);
				if($isex && $internaluser->Status < USER_STATUS_OFFLINE)
                    $list[] = $internaluser;
			}
		return $list;
	}

    function GetOperators()
    {
        $list = array();
        foreach(Server::$Operators as $internaluser)
            if(in_array($this->Id,$internaluser->Groups) && !$internaluser->IsBot)
            {
                $list[] = $internaluser;
            }
        return $list;
    }

    function ContainsBot()
    {
        foreach(Server::$Operators as $internaluser)
            if(in_array($this->Id,$internaluser->Groups) && $internaluser->IsBot && !$internaluser->Deactivated)
                return true;
        return false;
    }

	function HasWelcomeManager()
	{
		
		foreach(Server::$Operators as $internaluser)
		{
			if(in_array($this->Id,$internaluser->Groups) && $internaluser->IsBot && $internaluser->WelcomeManager && !$internaluser->Deactivated)
				return true;
		}
		return false;
	}
	
	function LoadPredefinedMessages()
	{
		if(DBManager::$Connected)
		{
			$this->PredefinedMessages = array();
			$result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_PREDEFINED . "` WHERE `group_id`='" . DBManager::RealEscape($this->Id) . "'");
			if($result)
				while($row = DBManager::FetchArray($result))
					$this->PredefinedMessages[strtolower($row["lang_iso"])] = new PredefinedMessage($row["lang_iso"],$row);
            $this->SetDefaultPredefinedMessage();
        }
	}

    function SetDefaultPredefinedMessage()
    {
        $isdefault = false;
        foreach($this->PredefinedMessages as $message)
            if($message->IsDefault)
                $isdefault = true;
        if(!$isdefault)
            if(!empty($this->PredefinedMessages["en"]))
                $this->PredefinedMessages["en"]->IsDefault = true;
    }

    function LoadSignatures()
    {
        if(DBManager::$Connected)
        {
            $this->Signatures = array();
            $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_SIGNATURES . "` WHERE `group_id`='" . DBManager::RealEscape($this->Id) . "'");
            if($result)
                while($row = DBManager::FetchArray($result))
                    $this->Signatures[strtolower($row["id"])] = new Signature($row);
        }
    }

    function Load()
    {
        $this->LoadPredefinedMessages();
        $this->LoadSignatures();
    }

	function GetXML()
	{
		if($this->IsDynamic)
		{
			$xml = "<v i=\"".base64_encode($this->Id)."\" n=\"".base64_encode($this->Descriptions["EN"])."\" o=\"".base64_encode($this->Owner)."\">";
			foreach($this->Members as $member => $persistent)
				$xml .= "<crm i=\"".base64_encode($member)."\" />";
		}
		else
		{
			$xml = "<v oh=\"".base64_encode($this->IsOpeningHour() ? "1" : "0")."\" id=\"".base64_encode($this->Id)."\" p=\"".base64_encode($this->Position)."\" desc=\"".base64_encode($this->DescriptionArray)."\" created=\"".base64_encode($this->Created)."\"  email=\"".base64_encode($this->Email)."\" mc=\"".base64_encode($this->MaxChats)."\" ps=\"".base64_encode($this->ChatPrioritySleep ? "1" : "0")."\" external=\"".base64_encode($this->IsExternal)."\"  internal=\"".base64_encode($this->IsInternal)."\" standard=\"".base64_encode($this->IsStandard)."\" teo=\"".base64_encode($this->TicketEmailOut)."\" tesn=\"".base64_encode($this->TicketSenderName)."\" ceo=\"".base64_encode($this->ChatEmailOut)."\" tino=\"".base64_encode($this->SendTicketNotifier ? 1 : 0)."\" thue=\"".base64_encode($this->TicketHandleUnknownEmails)."\">\r\n";
			if(is_array($this->VisitorFilters))
				foreach($this->VisitorFilters as $filt => $ex)
					$xml .= "<vfilt ex=\"".base64_encode((is_array($ex))?serialize($ex):$ex)."\">".$filt."</vfilt>\r\n";
			
            if(is_array($this->TicketEmailIn))
                foreach($this->TicketEmailIn as $teid)
                    $xml .= "<tei id=\"".base64_encode($teid)."\" />\r\n";
					
			if(is_array($this->PredefinedMessages))
				foreach($this->PredefinedMessages as $premes)
					$xml .= $premes->GetXML();

            if(is_array($this->Signatures))
                foreach($this->Signatures as $sig)
                    $xml .= $sig->GetXML();

			if(is_array($this->OpeningHours))
				foreach($this->OpeningHours as $hour)
					$xml .= "<oh open=\"".base64_encode($hour[1])."\" close=\"".base64_encode($hour[2])."\">".base64_encode($hour[0])."</oh>\r\n";
		}
		return $xml;
	}
	
	function Save()
	{
		if($this->IsDynamic)
			DBManager::Execute(true, "INSERT INTO `" . DB_PREFIX . DATABASE_GROUPS . "` (`id`, `name`, `owner`,`dynamic`, `description`, `opening_hours`,`chat_inputs_hidden`, `ticket_inputs_hidden`, `chat_inputs_required`, `ticket_inputs_required`, `chat_inputs_masked`, `ticket_inputs_masked`, `chat_inputs_cap`, `ticket_inputs_cap`,`visitor_filters`,`ticket_sender_name`,`ticket_email_in`,`ticket_assignment`,`priorities`,`priority_sleep`,`position`) VALUES ('" . DBManager::RealEscape($this->Id) . "', '" . DBManager::RealEscape($this->Descriptions["EN"]) . "','" . DBManager::RealEscape($this->Owner) . "',1,'','','','','','','','','','','','','','','',0,0);");
    }
	
	function Destroy()
	{
		DBManager::Execute(false, "DELETE FROM `" . DB_PREFIX . DATABASE_GROUPS . "` WHERE `id` = '" . DBManager::RealEscape($this->Id) . "' LIMIT 1;");
	}
	
	function RemoveMember($_id)
	{
        if(strpos($_id,"~")!==false)
        {
            $_id = explode("~",$_id);
            $_id = $_id[0];
        }
		DBManager::Execute(true, "DELETE FROM `" . DB_PREFIX . DATABASE_GROUP_MEMBERS . "` WHERE `user_id` LIKE '%" . DBManager::RealEscape($_id) . "%' AND `group_id` = '" . DBManager::RealEscape($this->Id) . "';");
	    CacheManager::FlushKey(DATA_CACHE_KEY_GROUPS);
    }

	function AddMember($_id,$_persistant=false)
	{
		DBManager::Execute(true, "REPLACE INTO `" . DB_PREFIX . DATABASE_GROUP_MEMBERS . "` (`user_id`, `group_id`, `persistent`) VALUES ('" . DBManager::RealEscape($_id) . "', '" . DBManager::RealEscape($this->Id) . "', " . (($_persistant) ? "1" : "0") . ");");
        CacheManager::FlushKey(DATA_CACHE_KEY_GROUPS);
    }

    function GetWaitingLinks($_question,$_language, $html="", $count=0)
    {
        /*
        $knowledgebase = false;
        $answers = ChatAutoReply::GetMatches($this->AutoReplies, $_question, $_language, null, null);
        if(!empty(Server::$Configuration->File["gl_knbq"]) && count($answers) == 0)
        {
            $root = Communication::ReadParameter("ckf","");
            $answers = KnowledgeBase::GetMatches($root,$_question,Visitor::$BrowserLanguage);
            $knowledgebase = true;
            $color = ExternalChat::ReadTextColor();
        }

        if(count($answers)>0)
        {
            foreach($answers as $qa)
            {
                if((!empty($qa->ResourceId) || !empty($qa->Answer)) && $qa->Waiting)
                {
                    $res = KnowledgeBaseEntry::GetById($qa->ResourceId);
                    $target = ($qa->NewWindow) ? "target=\"_blank\" " : "";
                    $html .= "<li>";

                    if($res==null)
                        $html .= $qa->Answer;
                    else if($res["type"] == 2)
                        $html .= "<a class=\"lz_chat_link\" href=\"". $res["value"]. "\" ".$target.">" . $res["title"]. "</a>";
                    else if($res["type"] == 3 || $res["type"] == 4)
                        $html .= "<a class=\"lz_chat_link\" href=\"". LIVEZILLA_URL . "getfile.php?id=" . $res["id"]. "\" ".$target.">" . $res["title"]. "</a>";
                    else
                        $html .= "<b>" . $res["title"]. "</b><br>" . str_replace("<a ", "<a ".$target,str_replace("<A","<a",$res["value"]));

                    $html .= "</li>";
                }
                else if($knowledgebase)
                {
                    $ehtml = trim($qa->GetHTML($color,true,false));
                    if(!empty($ehtml))
                        $html .= $ehtml;
                }

                if(++$count > 5)
                    break;
            }
            if(!empty($html))
            {
                $html = "<div id=\"lz_chat_waiting_links\">" . LocalizationManager::$TranslationStrings["client_while_waiting"] . "<ul>" . $html . "</ul></div>";
                return $html;
            }
        }
        return "";
        */
    }

    function GetDescription($_language="",$_defaultId=true)
    {
        if(!empty($_language) && isset($this->Descriptions[strtoupper($_language)]))
            return base64_decode($this->Descriptions[strtoupper($_language)]);
        else if(isset($this->Descriptions[strtoupper(Server::$Configuration->File["gl_default_language"])]))
           return base64_decode($this->Descriptions[strtoupper(Server::$Configuration->File["gl_default_language"])]);
        else if(isset($this->Descriptions["EN"]))
            return base64_decode($this->Descriptions["EN"]);
        else if(is_array($this->Descriptions))
            return base64_decode(current($this->Descriptions));
        else if($_defaultId)
            return $this->Id;
        else
            return "";
    }

    function GetServerInput($_input,$_chat=true,$default="",&$changed=false)
    {
        $cap = (($_chat) ? isset($this->ChatInputsCapitalized[$_input->Index]) : isset($this->TicketInputsCapitalized[$_input->Index]));
        return $_input->GetServerInput($default,$changed,$cap);
    }

    function GetChatPriority($_operatorId)
    {
        if(!empty($this->ChatPriorities) && isset($this->ChatPriorities[$_operatorId]))
            return $this->ChatPriorities[$_operatorId];
        return 0;
    }

    function TextReplace($_text,$_language)
    {
        $_text = str_replace(array("%group_name%","%group_id%","%TARGETGROUP%"),$this->Id,$_text);
        $_text = str_replace(array("%group_description%","%group_title%"),$this->GetDescription($_language),$_text);
        return $_text;
    }

    static function RemoveNonPersistantMember($_id)
    {
        DBManager::Execute(true, "DELETE FROM `" . DB_PREFIX . DATABASE_GROUP_MEMBERS . "` WHERE `user_id`='" . DBManager::RealEscape($_id) . "' AND `persistent`=0;");
        if(DBManager::GetAffectedRowCount()>0)
            CacheManager::FlushKey(DATA_CACHE_KEY_GROUPS);
    }

    static function IsDynamicGroup()
    {
        foreach(Server::$Groups as $group)
            if($group->IsDynamic)
                return true;
        return false;
    }

    static function PersistentJoin($_userId,$_systemId,$joined=false)
    {
        if(UserGroup::IsDynamicGroup())
        {
            if(!empty(VisitorChat::$DynamicGroup))
            {
                if(isset(Server::$Groups[VisitorChat::$DynamicGroup]))
                {
                    Server::$Groups[VisitorChat::$DynamicGroup]->AddMember($_systemId,false);
                    Server::$Groups[VisitorChat::$DynamicGroup]->LoadMembers();
                    $joined = true;
                }
            }
            else
            {
                $gToJoin = array();
                $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_GROUP_MEMBERS . "` WHERE `persistent`=1 AND `user_id` LIKE '%" . DBManager::RealEscape($_userId) . "%';");
                if($result)
                {
                    while($row = DBManager::FetchArray($result))
                        if($row["user_id"] != $_systemId)
                        {
                            if(!isset($gToJoin[$row["group_id"]]))
                                $gToJoin[$row["group_id"]] = true;
                        }
                        else
                        {
                            $gToJoin[$row["group_id"]] = false;
                            $joined = true;
                        }

                    foreach($gToJoin as $gid => $join)
                        if($join)
                        {
                            Server::$Groups[$gid]->AddMember($_systemId,true);
                            Server::$Groups[$gid]->LoadMembers();
                            $joined = true;
                        }
                }
            }
        }
        return $joined;
    }

    static function RemoveFromAllChatGroups($_systemId)
    {
        foreach(Server::$Groups as $group)
        {
            if($group->IsDynamic && isset($group->Members[$_systemId]))
            {
                $group->RemoveMember($_systemId);
            }
        }
    }

    static function ReadParams()
    {
        $group = "";

        if(!empty($_GET["eg"]))
        {
            $group = Communication::GetParameter("eg","",$c,FILTER_SANITIZE_SPECIAL_CHARS,null,32,false,false);
        }
        else if(!empty($_GET["group"]))
        {
            $group = Communication::GetParameter("group","",$c,FILTER_SANITIZE_SPECIAL_CHARS,null,32,false,false);
        }
        else if(!empty($_GET["intgroup"]))
        {
            // deprecated, compatibility
            $group = Communication::GetParameter("intgroup","",$c,FILTER_SANITIZE_SPECIAL_CHARS,null,32);
        }

        if(!empty($group) && isset(Server::$Groups[$group]))
            return $group;

        return "";
    }
}

class Role
{
    public $Id = "";
    public $Name = "";
    public $Description = "";
    public $Permissions = "";

    function __construct()
    {
        if(func_num_args() == 1)
        {
            $row = func_get_arg(0);
            $this->Id = $row["id"];
            $this->Name = $row["name"];
            $this->Description = $row["description"];
            $this->Permissions = $row["permissions"];
        }
    }

    function GetXML()
    {
        return "<r i=\"".base64_encode($this->Id)."\" d=\"".base64_encode($this->Description)."\" n=\"".base64_encode($this->Name)."\" p=\"".base64_encode($this->Permissions)."\" />";
    }

    function Save()
    {
        DBManager::Execute(true, "REPLACE INTO `" . DB_PREFIX . DATABASE_ROLES. "` (`id`, `name`, `description`, `permissions`) VALUES ('" . DBManager::RealEscape($this->Id) . "', '" . DBManager::RealEscape($this->Name) . "','" . DBManager::RealEscape($this->Description)  . "', '" . DBManager::RealEscape($this->Permissions) . "')");
    }

    function Destroy()
    {
        DBManager::Execute(false, "DELETE FROM `" . DB_PREFIX . DATABASE_ROLES . "` WHERE `id` = '" . DBManager::RealEscape($this->Id) . "' LIMIT 1;");
    }
}

class Operator extends BaseUser
{
    public $Reposts = array();
    public $Name = "";
    public $Fullname = "";
    public $Firstname = "";
	public $Lastname = "";
	public $Webspace = 0;
	public $ClientSystemId;
    public $Token;
	public $Password;
	public $PasswordChangeRequest;
	public $Description;
	public $LCAFile;
	public $Authenticated = false;
	public $ExternalChats;
    public $ExternalChatCount = -1;
	public $PermissionSet;
	public $Groups = array();
	public $GroupsArray = array();
	public $GroupsAway = array();
	public $GroupsHidden;
	public $PredefinedMessages = array();
    public $Signatures = array();
	public $InExternalGroup;
    public $Level = 0;
	public $ProfilePicture;
	public $ProfilePictureTime = 0;
	public $LastChatAllocation;
    public $LastActiveDB;
	public $CanAutoAcceptChats;
	public $LoginIPRange = "";
	public $WebsitesConfig;
	public $SignOffRequest;
	public $IsBot = false;
	public $WelcomeManager = false;
	public $WelcomeManagerOfferHumanChatAfter = 0;
    public $WelcomeMessage = "";
    public $Deactivated;
    public $ClientWeb = false;
    public $AppClient = false;
    public $AppDeviceId = "testid";
    public $AppBackgroundMode = true;
    public $AppOS = "";
    public $MobileExtends = array();
    public $FirstCall = true;
    public $PictureFile;
    public $ChatFile;
    public $LDAP = false;
    public $Color;
    public $Updated = 0;
    public $SaveUpdated = false;
    public $UserAPIURL = "";
    public $StatusText = "";
    public $Skills = "";
    public $Location = "";
    public $Roles = "";
    public $CalculatedPermissionSet = null;
    public $Languages = "";

	function __construct()
   	{
        if(func_num_args() == 2)
        {
            $this->LastActive = 0;
            $this->SystemId = func_get_arg(0);
            $this->UserId = func_get_arg(1);
            $this->ExternalChats = array();
            $this->Type = 1;
            $this->GroupsHidden = array();

            if(defined("FILE_CHAT"))
            {
                $this->PictureFile = $this->GetOperatorPictureFile();
                $this->ChatFile = FILE_CHAT . "?operator=".urlencode($this->UserId);
            }
        }
   	}
	
	function SignOff($_init=true)
	{
        if($_init && ($this->LastActive < (time()-Server::$Configuration->File["timeout_clients"])))
            DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_OPERATORS . "` SET `updated`=".time().",`sign_off`=0,`status`=2,`token`='' WHERE `system_id`='" . DBManager::RealEscape($this->SystemId) . "' LIMIT 1; ");
        else if($_init && $this->Status != USER_STATUS_OFFLINE)
            DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_OPERATORS . "` SET `updated`=".time().",`sign_off`=1,`status`=2,`token`='' WHERE `system_id`='" . DBManager::RealEscape($this->SystemId) . "' LIMIT 1; ");
        else if(!$_init)
            DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_OPERATORS . "` SET `updated`=".time().",`sign_off`=0 WHERE `system_id`='" . DBManager::RealEscape($this->SystemId) . "' LIMIT 1; ");
    }
	
	function GetAutoReplies($_question,$_chat,$_visitor,&$_apiREQobject,&$_apiRESobject)
	{
        $html = "";
        $root = Communication::ReadParameter("ckf","");
        $kbresults = KnowledgeBase::GetMatches($root,$_question,Visitor::$BrowserLanguage,true);

        //$merged = array();
        if($this->IsBot)
        {
            $searchKB = false;
            if(!empty($this->UserAPIURL))
            {
                $_apiREQobject = array();

                $loutREQobj = Post::GetLastAPIObject($_chat->ChatId, "request");
                $loutRESobj = Post::GetLastAPIObject($_chat->ChatId, "response");

                if($loutREQobj != null)
                    $_apiREQobject["RequestNumber"] = $loutREQobj->RequestNumber+1;
                else
                    $_apiREQobject["RequestNumber"] = 0;

                if($loutRESobj != null)
                    $_apiREQobject["ResponseTo"] = $loutRESobj->Id;
                else
                    $_apiREQobject["ResponseTo"] = "";

                $_apiREQobject["Value"] = $_question;
                $_apiREQobject["BotId"] = $this->SystemId;
                $_apiREQobject["BotName"] = $this->Fullname;
                $_apiREQobject["VisitorName"] = $_visitor->VisitorData->Fullname;
                $_apiREQobject["VisitorId"] = $_visitor->UserId;
                $_apiREQobject["GroupId"] = $_chat->GroupId;
                $_apiREQobject["Language"] = $_visitor->Language;
                $_apiREQobject["HumanAvailable"] = false;
                $_apiREQobject["KBMatchCount"] = count($kbresults);

                if($loutRESobj != null && isset($loutRESobj->Tag))
                    $_apiREQobject["Tag"] = $loutRESobj->Tag;

                if(isset(Server::$Groups[$_chat->GroupId]) && Server::$Groups[$_chat->GroupId]->IsHumanAvailable())
                    $_apiREQobject["HumanAvailable"] = true;

                $_apiREQobject["Operators"] = array();
                $_apiREQobject["Customs"] = $_visitor->VisitorData->Customs;

                foreach(Server::$Operators as $op)
                {
                    $obObj = array();
                    $obObj["Id"] = $op->UserId;
                    $obObj["Status"] = $op->Status;
                    $obObj["Fullname"] = $op->Fullname;
                    $obObj["Groups"] = $op->Groups;
                    $_apiREQobject["Operators"][] = $obObj;
                }

                $response = Communication::CallUserAPI($this->UserAPIURL,array("livezilla_user_api_request"=>json_encode($_apiREQobject)));

                if($response != null)
                {
                    $_apiRESobject = json_decode($response);

                    $autoReply = new ChatAutoReply($_apiRESobject->Id,"string",$_apiRESobject->Value);
                    if(isset($_apiRESobject->Select))
                        $autoReply->Select = $_apiRESobject->Select;

                    $html .= $this->ProcessBotUserAPIReplies($autoReply,$_apiRESobject,$_visitor);

                    if(isset($_apiRESobject->SearchKB) && $_apiRESobject->SearchKB)
                    {
                        //if(!empty($autoReply->Answer))
                          //  $html .= "<br><br>";

                        $searchKB = true;
                    }
                }
                else
                    $searchKB = true;
            }
            else
            {
                sleep(1);
                $searchKB = true;
            }

            if($searchKB && $_question != "[__[EXIT]__]")
            {
                $_question = str_replace(array("!",",",".","?","=",")","(","-","_",":","#","~","�"),"",strtolower($_question));
                $answers = ChatAutoReply::GetMatches($kbresults, $_question, Visitor::$BrowserLanguage, $_chat, $this);
                $html .= $this->FormatBotAutoReplies($_chat,$answers);
            }
        }
        return (strlen($html)>0) ? $html : null;
	}

    function ProcessBotUserAPIReplies($_autoReply,$_apiRESobject,$_visitor)
    {
        $html = $_autoReply->Answer;
        $js = "";
        if(isset($_apiRESobject->Data))
        {
            $_visitor->VisitorData->LoadFromBotAPI($_apiRESobject);
            $_visitor->ApplyVisitorData();

            global $OVERLAY;
            $OVERLAY->RepollRequired = true;
        }
        if(isset($_autoReply->Select))
            foreach($_autoReply->Select as $option)
            {
                $html .= "<div onclick=\"";

                if(isset($option->url))
                    $html .="window.open('".$option->url."');OverlayChatWidgetV2.APIButtonClick('".base64_encode($option->return)."');";

                if(isset($option->script))
                    $html .= "eval(lz_global_base64_decode('".base64_encode($option->script)."'));";

                if(isset($option->action))
                {
                    $operatorid = (isset($option->operator)) ? "'" . ($option->operator) . "'" : "null";
                    $groupid = (isset($option->group)) ? "'" . ($option->group). "'" : "null";

                    if($option->action == "chat")
                    {
                        $html .= "OverlayChatWidgetV2.ExitBotChat(true,true,1);OverlayChatWidgetV2.InitChat(".$operatorid.",".$groupid.");";
                    }
                    else if($option->action == "message")
                        $html .= "OverlayChatWidgetV2.SetMode('ticket',true,".$groupid.");";
                }
                if(isset($option->return))
                    $html .= "OverlayChatWidgetV2.APIButtonClick('".base64_encode($option->return)."');";

                $html .= "lz_stop_propagation(arguments[0]);\" class=\"lz_chat_bot_button\">".$option->title."</div>";
            }


        if(isset($_apiRESobject->Action))
        {
            $operatorid = (isset($_apiRESobject->Operator)) ? "'" . $_apiRESobject->Operator . "'" : "null";
            $groupid = (isset($_apiRESobject->Group)) ? "'" . $_apiRESobject->Group . "'" : "null";

            if($_apiRESobject->Action == "chat")
                $js .= "OverlayChatWidgetV2.ExitBotChat(true,true,2);OverlayChatWidgetV2.InitChat(".$operatorid.",".$groupid.");";
            else if($_apiRESobject->Action == "message")
                $js .= "OverlayChatWidgetV2.SetMode('ticket',true,".$groupid.");";
        }

        if(isset($_apiRESobject->Block))
        {
            $js .= "OverlayChatWidgetV2.InputState(false,".To::BoolString($_apiRESobject->Block).");";
        }

        if(!empty($js))
            VisitorMonitoring::$Visitor->AddFunctionCall($js,false);

        return $html;
    }

    function FormatBotAutoReplies($_chat,$_answers,$_alternate=true,$html="",$single="")
    {
        $tth = "";

        if(!empty(Server::$Groups[$_chat->DesiredChatGroup]) && Server::$Groups[$_chat->DesiredChatGroup]->IsHumanAvailable())
        {
            $resultpc = DBManager::Execute(false, "SELECT * FROM `" . DB_PREFIX . DATABASE_POSTS . "` WHERE `chat_id`='" . DBManager::RealEscape($_chat->ChatId) . "' AND `repost`=0 AND `receiver`='" . DBManager::RealEscape($this->SystemId) . "';");
            if($this->WelcomeManager && DBManager::GetRowCount($resultpc) >= $this->WelcomeManagerOfferHumanChatAfter && $this->WelcomeManagerOfferHumanChatAfter < 6)
            {
                $tth = "<div class=\"lz_chat_bot_button keep_alive\" onclick=\"OverlayChatWidgetV2.ExitBotChat(true,true,3);OverlayChatWidgetV2.InitChat();\">".LocalizationManager::$TranslationStrings["client_talk_to_human"]."</div>";
            }
        }

        $lm = (empty(Server::$Configuration->File["gl_dtfbc"])) ? "<br><br><div onclick=\"OverlayChatWidgetV2.SetMode('ticket',true);\" class=\"lz_chat_bot_button keep_alive\">".LocalizationManager::$TranslationStrings["client_leave_a_message"]."</div>" : "";
        if(count($_answers)==0)
        {
            if(empty($lm))
                $tth = "<br><br>" . $tth;

            return LocalizationManager::$TranslationStrings["client_no_bot_result"] . $lm . $tth;
        }
        else if(count($_answers)>0)
        {
            $html .= LocalizationManager::$TranslationStrings["client_your_result"] . "<br>";
            $html .= "<ul class=\"lz_chat_bot_resource\">";
            foreach($_answers as $qa)
            {
                if(!empty($qa->ResourceId))
                {
                    $res = KnowledgeBaseEntry::GetById($qa->ResourceId);

                    $inWidget = !empty($res["in_widget"]);
                    $target = (!$inWidget) ? "target=\"_blank\" " : "";

                    $html .= "<li class=\"lz_chat_bot_button\">";

                    if($res["type"] == 1)
                        $html .= KnowledgeBase::GetURL($res["id"],"",$res["title"],array(),true,true);
                    else if($res["type"] == 2)
                        $html .= "<a class=\"lz_chat_link\" href=\"". $res["value"]. "\" ".$target.">" . $res["title"]. "</a>";
                    else if($res["type"] == 3 || $res["type"] == 4)
                        $html .= "<a class=\"lz_chat_link\" href=\"". LIVEZILLA_URL . "getfile.php?id=" . $res["id"]. "\" ".$target.">" . $res["title"]. "</a>";
                    else
                        $html .= "<b>" . $res["title"]. "</b><br><br>" . str_replace("<a ", "<a ".$target,str_replace("<A","<a",$res["value"]));

                    $html .= "</li>";
                }
                else if(!empty($qa->Answer))
                {
                    $single = $qa->Answer . "<br><br>";
                    break;
                }
            }
            $html .= "</ul>";
        }

        if(!empty($single))
            $html = $single;

        $html = Server::$Groups[$_chat->DesiredChatGroup]->TextReplace($html,Visitor::$BrowserLanguage);
        $html = $_chat->TextReplace($html);
        $html = $this->TextReplace($html);
        $html = Configuration::Replace($html);
        return $html . (($_alternate) ? (LocalizationManager::$TranslationStrings["client_bot_result_found"] . $lm . $tth) : "");
    }

    function FormatHumanAutoReplies($_chat,$_answers)
    {
        /*
        foreach($_answers as $qa)
            if($qa->Send || $qa->SendInactivityTimeInternal > -1 || $qa->SendInactivityTimeExternal > -1)
            {
                $html = "";
                if(empty($qa->Answer))
                {
                    $res = KnowledgeBaseEntry::GetById($qa->ResourceId);
                    $target = ($qa->NewWindow) ? "target=\"_blank\" " : "";

                    if($res["type"] == 2)
                        $html .= "<a class=\"lz_chat_link\" href=\"". $res["value"]. "\" ".$target.">" . $res["title"]. "</a>";
                    else if($res["type"] == 3 || $res["type"] == 4)
                        $html .= "<a class=\"lz_chat_link\" href=\"". LIVEZILLA_URL . "getfile.php?id=" . $res["id"]. "\" ".$target.">" . $res["title"]. "</a>";
                    else
                        $html .= str_replace("<a ", "<a ".$target,str_replace("<A","<a",$res["value"]));
                }
                else
                    $html = $qa->Answer;

                $html = Server::$Groups[$_chat->DesiredChatGroup]->TextReplace($html,Visitor::$BrowserLanguage);
                $html = $_chat->TextReplace($html);
                $html = $this->TextReplace($html);
                $html = Configuration::Replace($html);
                return $html;
            }
        return null;
        */
    }
	
	function Save($_create=false)
	{
		if($_create)
        {
			DBManager::Execute(true, "INSERT INTO `" . DB_PREFIX . DATABASE_OPERATORS . "` (`id`, `system_id`, `firstname`, `lastname`, `email`, `permissions`,`webspace`,`updated`,`status`, `level`, `ip`, `typing`, `groups_status`, `reposts`, `groups`, `languages`, `groups_hidden`, `websites_users`, `websites_config`, `mobile_ex`, `image`, `api_url`, `welcome_message`, `skills`, `location`, `roles`) VALUES ('" . DBManager::RealEscape($this->UserId) . "','" . DBManager::RealEscape($this->SystemId) . "','" . DBManager::RealEscape($this->Firstname) . "','" . DBManager::RealEscape($this->Lastname) . "','" . DBManager::RealEscape($this->Email) . "','" . DBManager::RealEscape($this->PermissionSet) . "','" . DBManager::RealEscape($this->Webspace) . "',".time().", '" . DBManager::RealEscape($this->Status) . "', '" . DBManager::RealEscape($this->Level) . "', '" . DBManager::RealEscape($this->IP) . "', '" . DBManager::RealEscape($this->Typing) . "', '" . DBManager::RealEscape(serialize($this->GroupsAway)) . "','" . DBManager::RealEscape(serialize(/*$this->Reposts*/array())) . "','" . DBManager::RealEscape(base64_encode(serialize($this->Groups))) . "','" . DBManager::RealEscape($this->Language) . "','YTowOnt9','YTowOnt9','YTowOnt9','a:0:{}','','','','" . DBManager::RealEscape($this->Skills) . "','" . DBManager::RealEscape($this->Location) . "','" . DBManager::RealEscape($this->Roles) . "');");
		    $this->ChangePassword($this->Password);
        }
        else
		{
			$ca = (count($this->ExternalChats)==0) ? ",`last_chat_allocation`=0" : "";
            if($this->SaveUpdated)
            {
                $this->SaveUpdated = false;
                $this->AppClient = !empty($_POST["p_app"]);
                $this->ClientWeb = !empty($_POST["p_web"]);
			    DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_OPERATORS . "` SET `updated`=" . time() . ",`first_active`='" . DBManager::RealEscape($this->FirstActive) . "',`groups_status`='" . DBManager::RealEscape(serialize($this->GroupsAway)) . "',`typing`='" . DBManager::RealEscape($this->Typing) . "',`level`='" . DBManager::RealEscape($this->Level) . "',`status`='" . DBManager::RealEscape($this->Status) . "',`ip`='" . DBManager::RealEscape(Communication::GetIP(true)) . "',`lweb`='" . DBManager::RealEscape($this->ClientWeb ? 1 : 0) . "',`lapp`='" . DBManager::RealEscape($this->AppClient ? 1 : 0) . "',`last_active`=" . time() . $ca . " WHERE `system_id`='" . DBManager::RealEscape($this->SystemId) . "' LIMIT 1; ");
            }
            else
                DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_OPERATORS . "` SET `last_active`=" . time() . " WHERE `system_id`='" . DBManager::RealEscape($this->SystemId) . "' LIMIT 1;");
        }
	}

    function SetStatusText($_text)
    {
        DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_OPERATORS . "` SET `status_info`='" . DBManager::RealEscape($_text) . "' WHERE `system_id`='" . DBManager::RealEscape($this->SystemId) . "' LIMIT 1;");
    }

    function GetPublicName(){

        if(isset(Server::$Configuration->File["gl_sfnc"]) && Server::$Configuration->File["gl_sfnc"] === "0")
            $name = $this->Firstname;
        else
            $name = $this->Fullname;

        return $name;
    }

    function Delete()
    {
        DBManager::Execute(true, "DELETE FROM `" . DB_PREFIX . DATABASE_OPERATORS . "` WHERE `id`='" . DBManager::RealEscape($this->UserId) . "' LIMIT 1;");
    }

    function SetDynamicValues($_row)
    {
        $this->AppClient = !empty($_row["lapp"]);
        $this->AppBackgroundMode = !empty($_row["mobile_background"]);
        $this->AppDeviceId = @$_row["mobile_device_id"];
        $this->AppOS = @$_row["mobile_os"];
        $this->ClientWeb = !empty($_row["lweb"]);
        $this->ClientSystemId = @$_row["client_system_id"];
        $this->Token = @$_row["token"];
        $this->LastActiveDB =
        $this->LastActive = $_row["last_active"];
        $this->Updated = isset($_row["updated"]) ? $_row["updated"] : 0;
        $this->Deactivated = ($_row["sign_off"]==2);
        $this->SignOffRequest = !empty($_row["sign_off"]);

        if(!empty($_row["mobile_ex"]))
            $this->MobileExtends = @unserialize($_row["mobile_ex"]);

        $this->Typing = $_row["typing"];
        $this->FirstActive = ($_row["first_active"] < (time() - (int)(Server::$Configuration->File["timeout_clients"] ?? 608))) ? time() : $_row["first_active"];
        $this->LastChatAllocation = $_row["last_chat_allocation"];
        $this->Status = $_row["status"];
        if($_row["status"] != USER_STATUS_OFFLINE)
        {
            if(!empty($_row["mobile_device_id"]) && !empty($_row["mobile_os"]) && strpos($_row["mobile_os"],"desk") === false && $_row["last_active"]>(time()-(30*86400)) && Server::IsAvailable())
                $this->LastActive = time();
            else if($_row["last_active"] < (time() - (int)(Server::$Configuration->File["timeout_clients"] ?? 608)) && !$this->IsBot)
                $this->Status = USER_STATUS_OFFLINE;
            else if($_row["last_active"]<(time()-(Server::$Configuration->File["poll_frequency_clients"]+30)) && !$this->IsBot)
                $this->Status = USER_STATUS_AWAY;
        }
        $this->PasswordChangeRequest = !empty($_row["password_change_request"]);
        $this->UserAPIURL = isset($_row["api_url"]) ? $_row["api_url"] : "";
    }

    function SetValues($_row)
    {
        $this->Email = $_row["email"];
        $this->Webspace = $_row["webspace"];
        $this->Level = $_row["level"];
        $this->Description = $_row["description"];

        $this->Firstname = @$_row["firstname"];
        $this->Lastname = @$_row["lastname"];
        $this->Fullname = trim($this->Firstname . " " . $this->Lastname);

        $this->Language = $_row["languages"];
        $this->ProfilePicture = $_row["image"];
        $this->Groups = @unserialize(base64_decode($_row["groups"]));

        if(isset($_row["max_chats"]))
        {
            if($_row["max_chats"] < 1)
                $this->MaxChatAmount = 9999;
            else if($_row["max_chats"] > 30)
            {
                $this->MaxChatsStatus = USER_STATUS_AWAY;
                $this->MaxChatAmount = $_row["max_chats"]-30;
            }
            else
                $this->MaxChatAmount = $_row["max_chats"];
            $this->MaxChats = $_row["max_chats"];
        }

        if(!empty($this->Groups))
            array_walk($this->Groups,"b64dcode");
        $this->GroupsHidden = @unserialize(base64_decode($_row["groups_hidden"]));
        if(!empty($this->GroupsHidden))
            array_walk($this->GroupsHidden,"b64dcode");

        $this->GroupsArray = $_row["groups"];
        $this->PermissionSet = $_row["permissions"];
        $this->CanAutoAcceptChats = (isset($_row["auto_accept_chats"])) ? $_row["auto_accept_chats"] : 1;
        $this->LoginIPRange = $_row["login_ip_range"];
        $this->IsBot = !empty($_row["bot"]);
        $this->FirstCall = ($_row["first_active"] < (time() - (int)(Server::$Configuration->File["timeout_clients"] ?? 608)));
        $this->Password = $_row["password"];
        $this->SetDynamicValues($_row);
        $this->Level = $_row["level"];
        $this->IP = $_row["ip"];
        $this->LDAP = !empty($_row["ldap"]);
        $this->Color = @$_row["color"];

        $this->StatusText = @$_row["status_info"];
        $this->Skills = @$_row["skills"];
        $this->Location = @$_row["location"];
        $this->Roles = @$_row["roles"];

        if(!empty($_row["groups_status"]))
            $this->GroupsAway = @unserialize($_row["groups_status"]);

        if($this->IsBot)
        {
            $this->FirstCall =
            $this->FirstActive =
            $this->LastActive = time();
            $this->Status = USER_STATUS_ONLINE;
            $this->WelcomeManager = !empty($_row["wm"]);
            $this->WelcomeManagerOfferHumanChatAfter = $_row["wmohca"];
            $this->WelcomeMessage = $_row["welcome_message"];
        }

        if(empty($this->GroupsAway))
            $this->GroupsAway = array();
    }
	
	function Load()
	{
		$this->LoadPredefinedMessages();
        $this->LoadSignatures();
	}

    function LoadUnCacheables()
    {
        if(DBManager::$Connected)
        {
            $result = DBManager::Execute(false, "SELECT * FROM `" . DB_PREFIX . DATABASE_OPERATORS . "` WHERE `system_id`='" . DBManager::RealEscape($this->SystemId) . "';");
            if($result && $row = DBManager::FetchArray($result))
                $this->SetDynamicValues($row);
        }
    }
	
	function SetLastChatAllocation()
	{
		DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_OPERATORS . "` SET `updated`=".time().",`last_chat_allocation`='" . DBManager::RealEscape(time()) . "' WHERE `system_id`='" . DBManager::RealEscape($this->SystemId) . "' LIMIT 1; ");
	}

	function GetExternalObjects()
	{
		$result = DBManager::Execute(true, $d = "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` AS `t1` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` AS `t2` ON `t1`.`chat_id`=`t2`.`chat_id` WHERE `t1`.`exit`=0 AND `t2`.`status`<9;");
        if($result)
			while($row = DBManager::FetchArray($result))
			{
				$chat = new VisitorChat($row);
				if($chat->LastActive<(time()-Server::$Configuration->File["timeout_chats"]) && !(!empty($chat->OperatorId) && Server::$Operators[$chat->OperatorId]->IsBot))
					$chat->ExternalClose();
				else if($row["user_id"] == $this->SystemId)
					$this->ExternalChats[$chat->SystemId] = $chat;
			}
	}

    function HasAccessToTicket($_ticket)
    {
        return ($this->GetPermission(PERMISSION_TICKETS) != PERMISSION_NONE && ($this->IsInGroup($_ticket->Group) || $this->GetPermission(PERMISSION_TICKETS) == PERMISSION_FULL));
    }

    function HasAccessToEmail($_group)
    {
        return ($this->GetPermission(22) != PERMISSION_NONE && ($this->IsInGroup($_group) || $this->GetPermission(22) == PERMISSION_FULL));
    }

    function IsInPushMessageState()
    {
        return (!empty($this->AppDeviceId) && $this->AppBackgroundMode);
    }
	
	function IsInGroupWith($_comparer)
	{
        if(!isset($_comparer->Groups))
            return in_array($_comparer->SystemId,$this->Groups);
		foreach($this->Groups as $gid)
			if(in_array($gid,$_comparer->Groups))
				return true;
		return false;
	}

    function IsInGroup($_groupId)
    {
        return in_array($_groupId,$this->Groups);
    }

    function IsAvailableForChat()
    {
        return $this->Status < USER_STATUS_OFFLINE && ($this->LastActive > (time()-Server::$Configuration->File["timeout_clients"]));
    }

    function PrioritySleep($_groupId)
    {
        if($this->IsBot)
            return false;

        if(isset(Server::$Groups[$_groupId]) && Server::$Groups[$_groupId]->ChatPrioritySleep)
        {
            $myPrio = (isset(Server::$Groups[$_groupId]->ChatPriorities[$this->SystemId])) ? Server::$Groups[$_groupId]->ChatPriorities[$this->SystemId] : 0;
            foreach(Server::$Groups[$_groupId]->ChatPriorities as $systemId => $prio)
            {
                if(!isset(Server::$Operators[$systemId]))
                    continue;
                if($systemId == $this->SystemId || Server::$Operators[$systemId]->IsBot)
                    continue;
                if(!Server::$Operators[$systemId]->IsInGroup($_groupId))
                    continue;
                if($prio > $myPrio && Server::$Operators[$systemId]->IsAvailableForChat() && Server::$Operators[$systemId]->GetMaxChatAmountStatus(Server::$Groups[$_groupId]) != USER_STATUS_AWAY)
                    return true;
            }
        }
        return false;
    }

    function MobileSleep()
    {
        if($this->IsBot)
            return false;

        if(!empty($this->MobileExtends))
        {
            foreach($this->MobileExtends as $sid)
                if(isset(Server::$Operators[$sid]) && Server::$Operators[$sid]->LastActive > (time()-Server::$Configuration->File["timeout_clients"]) && Server::$Operators[$sid]->Status != USER_STATUS_OFFLINE)
                {
                    return true;
                }
        }
        return false;
    }

    function GetMaxChatAmountStatus($_group=null)
    {
        if($this->IsMaxChatAmount() && $this->GetExternalChatAmount() >= $this->MaxChatAmount)
        {
            return $this->MaxChatsStatus;
        }
        else if($_group != null && $_group->IsMaxChatAmount() && $this->GetExternalChatAmount() >= $_group->MaxChatAmount)
        {
            return $_group->MaxChatsStatus;
        }
        return $this->Status;
    }
	
	function IsExternal($_groupList, $_exclude=null, $_include=null, $_ignoreExternal=false, $_ignoreOpeningHours=false, $_ignoreStatus=false)
	{
        if($this->IsBot)
            $_ignoreOpeningHours = true;

        if($this->Deactivated)
            return false;

		Server::InitDataBlock(array("GROUPS"));
        if(!empty($this->Groups))
            foreach($this->Groups as $groupid)
                if(isset(Server::$Groups[$groupid]) && !Server::$Groups[$groupid]->IsDynamic)
                    if($_ignoreStatus || ((((Server::$Groups[$groupid]->IsOpeningHour()||$_ignoreOpeningHours) && !in_array($groupid,$this->GroupsAway) && $this->GetMaxChatAmountStatus(Server::$Groups[$groupid]) != USER_STATUS_AWAY))))
                    {
                        $group_incl = !empty($_include) && in_array($groupid,$_include);
                        $group_excl = (!empty($_exclude) && in_array($groupid,$_exclude)) || (!$group_incl && empty($_exclude) && !empty($_include));

                        if(!empty($_groupList[$groupid]) && ($_groupList[$groupid]->IsExternal || $_ignoreExternal) && ($group_incl || !$group_excl))
                        {
                            $this->InExternalGroup = true;

                            if($this->MobileSleep())
                                return false;

                            if(!empty(VisitorChat::$DynamicGroup))
                                if(isset(Server::$Groups[VisitorChat::$DynamicGroup]) && !isset(Server::$Groups[VisitorChat::$DynamicGroup]->Members[$this->SystemId]))
                                    return false;

                            return true;
                        }
                    }

        return $this->InExternalGroup=false;
	}
	
	function GetGroupList($_excludeAwayGroups=false)
	{
		if(!$_excludeAwayGroups)
			return $this->Groups;
		else
		{
			$groupl = array();
			foreach($this->Groups as $groupid)
				if(!in_array($groupid,$this->GroupsAway))
					$groupl[] = $groupid;
			return $groupl;
		}
	}

    function GetChatPriority($_groupObj)
    {
        if($this->IsInGroup($_groupObj->Id))
            return $_groupObj->GetChatPriority($this->SystemId);
        return 0;
    }
	
	function GetExternalChatAmount()
	{
        if($this->Status == 2)
            return 0;
        if($this->LastActive<(time()-Server::$Configuration->File["timeout_clients"]))
            return 0;
        if($this->ExternalChatCount > -1)
            return $this->ExternalChatCount;
        
		$result = DBManager::Execute(true, "SELECT COUNT(*) as `ccount` FROM `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` AS `t1` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` AS `t2` ON `t1`.`chat_id`=`t2`.`chat_id` WHERE `t1`.`exit`=0 AND `t1`.`internal_declined`=0 AND `t2`.`status`<9 AND `t2`.`user_id`='" . DBManager::RealEscape($this->SystemId) . "';");
        if($row = DBManager::FetchArray($result))
			return $this->ExternalChatCount = $row["ccount"];
		
		return $this->ExternalChatCount = 0;
	}
	
    function LoadSignatures()
    {
        if(DBManager::$Connected)
        {
            $this->Signatures = array();
            $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_SIGNATURES . "` WHERE `operator_id`='" . DBManager::RealEscape($this->SystemId) . "'");
            if($result)
                while($row = DBManager::FetchArray($result))
                    $this->Signatures[strtolower($row["id"])] = new Signature($row);
        }
    }
	
	function IsVisitorFiltered($_visitor, $blmatch=false, $wlmatch=false, $iswl=false)
	{
        $filtered = empty(Server::$Configuration->File["gl_vmac"]) && !$_visitor->IsInChat(true);

         if(!$filtered && !empty(Server::$Configuration->File["gl_hide_inactive"]))
            $filtered = !$_visitor->IsActivity(null,true,false);

		foreach($this->Groups as $groupid)
		{
			if(empty(Server::$Groups[$groupid]->VisitorFilters))
				return $filtered;
			foreach(Server::$Groups[$groupid]->VisitorFilters as $value => $filter)
			{
                $value = str_replace("*","",base64_decode($value));
                $urlmatch=false;

                if($filter=="Whitelist")
                    $iswl = true;

				foreach($_visitor->Browsers as $BROWSER)
				{
					if(count($BROWSER->History)==0)
						$BROWSER->LoadHistory();
						
					for($i = 0;$i < count($BROWSER->History);$i++)
        				if(strpos(strtolower($BROWSER->History[$i]->Url->GetAbsoluteUrl()),strtolower($value)) !== false)
							$urlmatch = true;
				}

                if($filter=="Blacklist" && $urlmatch)
                    $blmatch = true;
				if($filter=="Whitelist" && $urlmatch)
                    $wlmatch = true;
			}
		}
        if($blmatch)
            return true;
        if($wlmatch)
            return false;
        if($iswl)
            return true;

		return $filtered;
	}
	
	function ValidateLoginAttempt($_clear=false)
	{
		if(DBManager::$Connected)
		{
			if(!empty($this->LoginIPRange))
			{
				$match = false;
				$ranges = explode(",",$this->LoginIPRange);
				foreach($ranges as $range)
					if(Communication::GetIP(true) == trim($range) || OperatorRequest::IPMatch(Communication::GetIP(true),trim($range)))
						$match = true;
				if(!$match)
					return false;
			}
            if(!empty($_POST[POST_INTERN_AUTHENTICATION_PASSWORD]))
            {
                $result = DBManager::Execute(true, "SELECT `id`,`password` FROM `" . DB_PREFIX . DATABASE_OPERATOR_LOGINS . "` WHERE `ip`='" . DBManager::RealEscape(Communication::GetIP(true)) . "' AND `user_id`='" . DBManager::RealEscape($this->UserId) . "' AND `time` > '" . DBManager::RealEscape(time() - 86400) . "';");
                if(DBManager::GetRowCount($result) >= MAX_LOGIN_ATTEMPTS)
                {
                    if(!$_clear)
                    {
                        $this->DeleteLoginAttempts();
                        return $this->ValidateLoginAttempt(true);
                    }
                    return false;
                }
            }
		}
        else
            return false;
		return true;
	}

    function ValidateUpdateSession($_token,$_clientSystemId)
    {
        $this->Token = $_token;
        $this->ClientSystemId = $_clientSystemId;
        DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_OPERATORS . "` SET `updated`=".time().",`ip`='".DBManager::RealEscape(Communication::GetIP(true))."',`client_system_id`='" . DBManager::RealEscape($this->ClientSystemId) . "',`token`='" . DBManager::RealEscape($this->Token) . "' WHERE `system_id`='" . DBManager::RealEscape($this->SystemId) . "' LIMIT 1;");
    }

    function ValidateLoginAuthentication()
    {
        if(isset($_POST["p_ldap"]) && !empty(Server::$Configuration->File["gl_ldap"]) && !empty($_POST[POST_INTERN_AUTHENTICATION_PASSWORD]) && Server::$Operators[$this->SystemId]->LDAP)
        {
            require_once(LIVEZILLA_PATH . "_lib/objects.ldap.inc.php");

            $ldapAuth = LDAPManager::ValidateUser($this->UserId,Encoding::Base64UrlDecode($_POST[POST_INTERN_AUTHENTICATION_PASSWORD]));
            if($ldapAuth === true)
            {
                define("VALIDATED_FULL_LOGIN",true);
                return true;
            }
        }
        else if(!empty($this->Password) || isset($_POST["p_ldap"]))
        //else if(!empty($this->Password) && !Server::$Operators[$this->SystemId]->LDAP)
        {
            if(!empty($_POST[POST_INTERN_AUTHENTICATION_PASSWORD]) && !Server::$Operators[$this->SystemId]->LDAP)
            {
                if(!empty($this->Password))
                {
                    if($this->Password == $_POST[POST_INTERN_AUTHENTICATION_PASSWORD])
                    {
                        // 1.) v2 api login >= 5.4.x.;
                        // 2.) v2 comp login when no pw hash api
                        // 3.) pw reset fallback

                        define("VALIDATED_FULL_LOGIN",true);
                        return true;
                    }

                    if(hash("sha256",$this->Password) == $_POST[POST_INTERN_AUTHENTICATION_PASSWORD])
                    {
                        // v2 login >= 5.4.x.;
                        define("VALIDATED_FULL_LOGIN",true);
                        return true;
                    }

                    if(Server::IsPasswordAPI())
                    {
                        if(password_verify($_POST[POST_INTERN_AUTHENTICATION_PASSWORD], $this->Password))
                        {
                            // v3 login >= 7.0.6.x.;
                            define("VALIDATED_FULL_LOGIN",true);
                            return true;
                        }
                        if(password_verify(hash("sha256",$_POST[POST_INTERN_AUTHENTICATION_PASSWORD]), $this->Password))
                        {
                            // v3 api login >= 7.0.6.x.;
                            define("VALIDATED_FULL_LOGIN",true);
                            return true;
                        }
                    }
                    else
                    {
                        /*
                        if($this->Password == hash("sha256",$_POST[POST_INTERN_AUTHENTICATION_PASSWORD]))
                        {
                            // v2 api login >= 7.0.6.x. without hash for PHP < 5.5;
                            return true;
                        }
                        */
                    }
                }
            }

            if(!empty($this->Token) && !empty($_POST[POST_INTERN_AUTHENTICATION_TOKEN]))
            {
                if($this->IsValidToken($_POST[POST_INTERN_AUTHENTICATION_TOKEN]))
                {
                    define("VALIDATED_TOKEN",true);
                    return true;
                }
            }
        }
        return false;
    }

    function IsValidToken($_token)
    {
        if(!empty($_token) && !empty($this->Token))
            if(hash("sha256",$this->Token) == $_token)
                return true;
        return false;
    }

    function SaveLoginAttempt($_password)
    {
        if(DBManager::$Connected)
            DBManager::Execute(true, "INSERT IGNORE INTO `" . DB_PREFIX . DATABASE_OPERATOR_LOGINS . "` (`id` ,`user_id` ,`ip` ,`time` ,`password`) VALUES ('" . DBManager::RealEscape(getId(32)) . "', '" . DBManager::RealEscape($this->UserId) . "', '" . DBManager::RealEscape(Communication::GetIP(true)) . "', '" . DBManager::RealEscape(time()) . "', '" . DBManager::RealEscape($_password) . "');");
    }
	
	function DeleteLoginAttempts()
	{
		if(DBManager::$Connected)
			DBManager::Execute(true, "DELETE FROM `" . DB_PREFIX . DATABASE_OPERATOR_LOGINS . "` WHERE `time`<" . (time() - 86400) . " AND `ip`='" . DBManager::RealEscape(Communication::GetIP(true)) . "' AND `user_id`='" . DBManager::RealEscape($this->UserId) . "';");
	}
	
	function ChangePassword($_password,$_isHashed=false)
	{
		if(OperatorRequest::IsValidated() && Is::Defined("VALIDATED_FULL_LOGIN"))
		{
            if(empty($_password))
                return;

            Logging::SecurityLog("Operator->ChangePassword",$_password,Is::Defined("CALLER_SYSTEM_ID"));

            if(!$_isHashed)
                $_password = $this->HashPassword($_password);

			DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_OPERATORS . "` SET `updated`=".time().",`password`='" . DBManager::RealEscape($_password) . "' WHERE `system_id`='" . DBManager::RealEscape($this->SystemId) . "' LIMIT 1;");
        }
	}
	
	function SetPasswordChangeNeeded($_active=true)
	{
        if(OperatorRequest::IsValidated() && Is::Defined("VALIDATED_FULL_LOGIN"))
		{
            Logging::SecurityLog("Operator->SetPasswordChangeNeeded",intval($_active ? 1 : 0),Is::Defined("CALLER_SYSTEM_ID"));
			$this->PasswordChangeRequest = $_active;
			DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_OPERATORS . "` SET `updated`=".time().",`password_change_request`=".intval($_active ? 1 : 0)." WHERE `system_id`='" . DBManager::RealEscape($this->SystemId) . "' LIMIT 1;");
		}
	}

    function HashPassword($_password)
    {
        if(Server::IsPasswordAPI())
            return password_hash($_password, PASSWORD_DEFAULT);
        return $_password;
    }

    function CalculatePermissions()
    {
        $cpermissions = "";
        $permissionset = "";

        $roles = explode(",",$this->Roles);

        foreach($roles as $key)
        {
            if(!isset(Server::$Roles[$key]))
                continue;

            $role = Server::$Roles[$key];

            $rperms = str_split($role->Permissions);

            foreach($rperms as $index => $rkey)
            {
                $dp = strlen($permissionset) > $index ? intval(substr($permissionset,$index,1)) : 0;
                $rp = intval(substr($role->Permissions,$index,1));

                if($rp > $dp)
                {
                    $cpermissions .= $rp;
                }
                else
                {
                    $cpermissions .= $dp;
                }
            }
            $permissionset = $cpermissions;

            $cpermissions = "";
        }

        return $permissionset;
    }

	function GetPermission($_type, $_fallBack=PERMISSION_NONE)
	{
        if($this->CalculatedPermissionSet == null)
            $this->CalculatedPermissionSet = $this->CalculatePermissions();

        if(strlen($this->CalculatedPermissionSet)>$_type)
		    return substr($this->CalculatedPermissionSet,$_type,1);

        return $_fallBack;
	}
	
	function GetOperatorPictureFile()
	{
		$url = "picture.php?operator=".urlencode($this->UserId);
		return $url;
	}

	function GetLoginReply($_extern,$_oocount=0)
	{
		foreach(Server::$Operators as $internaluser)
			if($internaluser->Status != USER_STATUS_OFFLINE && !$internaluser->IsBot)
				$_oocount++;
		return "<login>\r\n<login_return t=\"".base64_encode($this->Token)."\" oo=\"".base64_encode($_oocount)."\" group=\"".base64_encode($this->GroupsArray)."\" name=\"".base64_encode($this->Fullname)."\" loginid=\"".base64_encode($this->ClientSystemId)."\" cp=\"".base64_encode($this->PasswordChangeRequest)."\" level=\"".base64_encode(0)."\" sess=\"".base64_encode($this->SystemId)."\" extern=\"".base64_encode($_extern)."\" perms=\"".base64_encode($this->PermissionSet)."\" phpv=\"".base64_encode(@phpversion())."\" sip=\"".base64_encode(@$_SERVER["SERVER_ADDR"])."\" uip=\"".base64_encode(@$_SERVER["REMOTE_ADDR"])."\" /></login>";
	}

    function SaveMobileParameters()
    {
        if(!Server::IsServerSetup())
        {
            $cos = (!empty($_POST["p_app_os"])) ? $_POST["p_app_os"] : "";
            $cbg = (!empty($_POST["p_app_background"])) ? 1 : 0;
            $cdi = (!empty($_POST["p_app_device_id"])) ? $_POST["p_app_device_id"] : "";
            if($this->AppDeviceId != $cdi || empty($this->AppBackgroundMode)!=empty($cbg) || $this->AppOS!=$cos)
            {
                if(empty($this->AppBackgroundMode)!=empty($cbg) && empty($cbg))
                    DBManager::Execute(true, "DELETE FROM `" . DB_PREFIX . DATABASE_PUSH_MESSAGES . "` WHERE `device_hash`='" . DBManager::RealEscape(md5($this->AppDeviceId)) . "'; ");

                DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_OPERATORS . "` SET `updated`=".time().",`mobile_os`='" . DBManager::RealEscape($cos) . "',`mobile_device_id`='" . DBManager::RealEscape($cdi) . "',`mobile_background`='" . DBManager::RealEscape($cbg) . "' WHERE `system_id`='" . DBManager::RealEscape($this->SystemId) . "' LIMIT 1; ");
            }
        }
    }

    function AddPushMessage($_chatId, $_chatPartnerId, $_chatPartnerName, $_pushKey, $_pushValue="")
    {
        if(!empty(Server::$Configuration->File["gl_mpm"]) && ($this->LastActiveDB<(time()-Server::$Configuration->File["poll_frequency_clients"]*3)) && $this->Status != 2)
        {
            /*
            $_pushValue = Str::EscapePushMessage($_pushValue);

            if($_pushValue===null)
                return;
            */

            if(!defined("IS_PUSH_MESSAGE"))
                define("IS_PUSH_MESSAGE",true);

            $text = ($_pushKey == 0) ? ("Someone wants to chat with you") : ("New Chat Message");
            $text = Str::Cut($text,90,true);

            DBManager::Execute(true, "DELETE FROM `" . DB_PREFIX . DATABASE_PUSH_MESSAGES . "` WHERE `sent`=1 AND `created` < " . DBManager::RealEscape(time() - 60));
            DBManager::Execute(true, "INSERT IGNORE INTO `" . DB_PREFIX . DATABASE_PUSH_MESSAGES . "` (`id`, `created`, `device_id`, `device_hash`, `device_os`, `chat_id`, `chat_partner_id`, `push_key`, `push_value`, `IP`) VALUES ('" . DBManager::RealEscape(getId(32)) . "', " . time() . ", '" . DBManager::RealEscape($this->AppDeviceId) . "', '" . DBManager::RealEscape(md5($this->AppDeviceId)) . "',  '" . DBManager::RealEscape($this->AppOS) . "', '" . DBManager::RealEscape($_chatId) . "', '" . DBManager::RealEscape($_chatPartnerId) . "', '" . DBManager::RealEscape($_pushKey) . "', '" . DBManager::RealEscape($text) . "', '" . DBManager::RealEscape(Communication::GetIP()) . "');");
        }
    }

    function GetInputMaskLevel($_inputIndex,$_chat=true)
    {
        $lvl = 100;
        foreach($this->Groups as $groupid)
            if(isset(Server::$Groups[$groupid]))
            {
                if(Server::$Groups[$groupid]->IsDynamic)
                    continue;
                if($_chat)
                    $lvl = (isset(Server::$Groups[$groupid]->ChatInputsMasked[$_inputIndex])) ? min($lvl,Server::$Groups[$groupid]->ChatInputsMasked[$_inputIndex]) : 0;
                else
                    $lvl = (isset(Server::$Groups[$groupid]->TicketInputsMasked[$_inputIndex])) ? min($lvl,Server::$Groups[$groupid]->TicketInputsMasked[$_inputIndex]) : 0;
            }
        return ($lvl==100) ? 0 : $lvl;
    }

    function TextReplace($_text)
    {
        $_text = str_replace(array("%operator_name%","%name%"),$this->Fullname,$_text);
        $_text = str_replace(array("%operator_firstname%","%firstname%"),$this->Firstname,$_text);
        $_text = str_replace(array("%operator_lastname%","%lastname%"),$this->Lastname,$_text);
        $_text = str_replace(array("%id%","%operator_id%"),$this->UserId,$_text);
        $_text = str_replace(array("%operator_email%"),$this->Email,$_text);
        return $_text;
    }

    function AffectsStatistic($_allowBot=false)
    {
        return (($_allowBot || !$this->IsBot) /*&& $this->GetPermission(46) == PERMISSION_NONE*/);
    }

    static function GetSystemId($_userId)
    {
        if(is_array(Server::$Operators))
            foreach(Server::$Operators as $sysId => $intern)
                if($intern->UserId == $_userId)
                    return $sysId;
        return null;
    }

    static function GetUserId($_systemId)
    {
        foreach(Server::$Operators as $sysId => $intern)
            if($sysId == $_systemId)
                return $intern->UserId;
        return null;
    }

    static function IPValidate()
    {
        foreach(Server::$Operators as $op)
        {
            if(Communication::GetIP(true) == $op->IP && !empty($op->IP))
                return true;
        }
        return false;
    }

    static function PrepareConnection()
    {
        if(isset($_POST[POST_INTERN_REQUEST]) && $_POST[POST_INTERN_REQUEST]==CALLER_TYPE_INTERNAL)
        {
            Server::$Response = new Response();
            if(!isset($_POST[POST_INTERN_ADMINISTRATE]))
            {
                header("Content-Type: text/xml; charset=UTF-8");
                if(!Server::IsAvailable() && $_POST[POST_INTERN_SERVER_ACTION]==INTERN_ACTION_LOGIN && !isset($_POST[POST_INTERN_ACCESSTEST]))
                {
                    Server::$Response->SetValidationError(LOGIN_REPLY_DEACTIVATED);
                    exit(Server::$Response->GetXML());
                }
            }
        }
    }

    static function ReadParams()
    {
        if(!empty($_POST[POST_EXTERN_REQUESTED_INTERNID]))
            return Communication::GetParameter(POST_EXTERN_REQUESTED_INTERNID,"",$c,FILTER_SANITIZE_SPECIAL_CHARS,null,32);
        else if(!empty($_GET["operator"]))
        {
            $userid = Communication::GetParameter("operator","",$c,FILTER_SANITIZE_SPECIAL_CHARS,null,32,false,false);
            $sysid = Operator::GetSystemId($userid);

            if($sysid != null)
                return $userid;

            if(isset(Server::$Operators[$userid]))
                return Server::$Operators[$userid]->UserId;
        }
        //deprecated, compatibility
        else if(!empty($_GET["intid"]))
        {
            $userid = Communication::GetParameter("intid","",$c,FILTER_SANITIZE_SPECIAL_CHARS,null,32);
            $sysid = Operator::GetSystemId($userid);

            if($sysid != null)
                return $userid;

            if(isset(Server::$Operators[$userid]))
                return Server::$Operators[$userid]->UserId;
        }
        return "";
    }

    static function ValidateToken($_token)
    {
        foreach(Server::$Operators as $op)
            if($op->IsValidToken($_token))
                return true;
        return false;
    }

    static function GetSystemOperator(){
        $sop = new Operator();
        $sop->UserId =
        $sop->Name =
        $sop->SystemId = SYSTEM;
        return $sop;
    }
}

class Visitor extends BaseUser
{
    public $GeoResult = 0;
	public $Browsers;
    public $ChatRequests = null;
	public $Response;
	public $IsChat = false;
	public $SystemInfo;
	public $Resolution;
	public $Host;
	public $Visits = 1;
	public $VisitsDay = 1;
	public $VisitId;
	public $VisitLast;
	public $GeoCity = "";
	public $GeoCountryName;
	public $GeoCountryISO2;
	public $GeoRegion;
	public $GeoLongitude = -522;
	public $GeoLatitude = -522;
	public $GeoTimezoneOffset = "+00:00";
	public $GeoISP;
	public $GeoResultId = 0;
	public $StaticInformation = false;
	public $ExitTime;
	public $Browser;
	public $OperatingSystem;
	public $Javascript;
	public $Signature;
	public $SignatureMismatch;
	public $IsCrawler;
	public $HasAcceptedChatRequest;
	public $HasDeclinedChatRequest;
	public $Comments = null;
    public $RecentVisits = null;
    public $FirstCall = true;
    public $VisitorData;
    public $Edited = 0;
    public $Device = 0;
    public $ScreenSharingStream;

    public static $IsActiveOverlayChat;
    public static $OpenChatExternal;
    public static $BrowserLanguage;
    public static $PollCount = -1;

	function __construct()
   	{
        $this->VisitorData = new UserData();
		$this->VisitId = getId(7);
		$this->Browsers = array();
		$this->UserId = func_get_arg(0);
		$this->FirstActive = time();
		$this->VisitLast = time();
   	}

    function GetRecentXML()
    {
        $xml = "<rdl v=\"".base64_encode($this->UserId)."\">";
        if(is_array($this->RecentVisits))
        {
            $this->LoadComments();
            $xml .= $this->GetCommentsXML();

            $this->LoadChatRequests();
            $xml .= $this->GetInvitesXML();

            foreach($this->RecentVisits as $entrance => $visit_id)
                $xml .= "<rv id=\"".base64_encode($visit_id)."\" e=\"".base64_encode($entrance)."\" />\r\n";
        }
        $xml .= "</rdl>";
        return $xml;
    }

    function LoadRecentVisits()
    {
        $result = DBManager::Execute(true, "SELECT `entrance`,`visit_id` FROM `" . DB_PREFIX . DATABASE_VISITORS . "` WHERE `visit_id`!='" . DBManager::RealEscape($this->VisitId) . "' AND `id`='" . DBManager::RealEscape($this->UserId) . "' ORDER BY `entrance` DESC;");
        while($row = DBManager::FetchArray($result))
        {
            $this->RecentVisits[$row["entrance"]] = $row["visit_id"];
        }
    }
	
	function Load()
	{
		if(func_num_args() == 1)
		{
			$this->SetDetails(func_get_arg(0),false);
		}
		else
		{
			$result = DBManager::Execute(true, "SELECT *,(SELECT count(*) FROM `" . DB_PREFIX . DATABASE_VISITORS . "` WHERE `id`='" . DBManager::RealEscape($this->UserId) . "') as `dcount` FROM `" . DB_PREFIX . DATABASE_VISITORS . "` WHERE `id`='" . DBManager::RealEscape($this->UserId) . "' ORDER BY `entrance` DESC;");
			if(DBManager::GetRowCount($result) >= 1)
				$this->SetDetails(DBManager::FetchArray($result),true);
		}
	}
	
	function SetDetails($_data,$_self,$_userData=true)
	{
		$this->FirstCall = ($_data["closed"] > 0);
		$this->VisitId = $_data["visit_id"];
        $this->Edited = $_data["edited"];

		if($_self && $this->FirstCall)
		{
			$this->Visits = $_data["visits"]+1;
			$this->VisitId = $_data["visit_id"] = getId(7);
			$this->VisitsDay = $_data["dcount"]+1;
			$this->FirstActive = time();
		}
		else
		{
			$this->Visits =	$_data["visits"];
            if($_self)
			    $this->VisitsDay = $_data["dcount"];
            else
                $this->Id = $_data["id"];
			$this->FirstActive = $_data["entrance"];
		}

		$this->VisitLast = $_data["visit_last"];
		$this->ExitTime = $_data["closed"];
		$this->IP = $_data["ip"];
		$this->SystemInfo = $_data["system"];
		$this->Language = $_data["language"];
        $this->Device = @$_data["device"];
		$this->Resolution = $_data["resolution"];
		$this->Host = $_data["host"];
		$this->GeoTimezoneOffset = $_data["timezone"];
		
		if(!empty($_data["longitude"]))
		{
			$this->GeoLongitude = $_data["longitude"];
			$this->GeoLatitude = $_data["latitude"];
		}
		if(!empty($_data["city"]))
			$this->GeoCity = $_data["city"];
		
		$this->GeoCountryISO2 = $_data["country"];
		if(isset($_data["countryname"]))
			$this->GeoCountryName = $_data["countryname"];

		$this->GeoRegion = $_data["region"];
		$this->GeoResultId = $_data["geo_result"];
		$this->GeoISP = $_data["isp"];
		$this->Browser = $_data["browser"];
		$this->OperatingSystem = $_data["system"];
		$this->Javascript = $_data["js"];
        $this->ScreenSharingStream = $_data["ss_stream"];

        if($_userData)
        {
            $this->VisitorData->Id = @$_data["data_id"];
            if(isset($_data["h_fullname"]))
                $this->VisitorData->SetDetails($_data,'data_id');
        }
	}

    function GetEntranceTime()
    {
        $t = time();
        foreach($this->Browsers as $browser)
        {
            $et = ($browser->History != null && isset($browser->History[0])) ? $browser->History[0]->Entrance : $t;
            $t = min($t,$et);
        }
        return $t;
    }

    function GetXML($row)
    {
        $xml = "<v i=\"".base64_encode($this->Id)."\" sss=\"".base64_encode($this->ScreenSharingStream)."\" ed=\"".base64_encode($this->Edited)."\" e=\"".base64_encode($this->FirstActive)."\" res=\"".base64_encode($this->Resolution)."\" ip=\"".base64_encode($this->IP)."\" tzo=\"".base64_encode($this->GeoTimezoneOffset)."\" lat=\"".base64_encode($this->GeoLatitude)."\" long=\"".base64_encode($this->GeoLongitude)."\" city=\"".base64_encode($this->GeoCity)."\" ctryi2=\"".base64_encode($this->GeoCountryISO2)."\" region=\"".base64_encode($this->GeoRegion)."\" js=\"".base64_encode($this->Javascript)."\" lang=\"".base64_encode($this->Language)."\" vts=\"".base64_encode($this->Visits)."\" ho=\"".base64_encode($this->Host)."\" gr=\"".base64_encode($this->GeoResultId)."\" isp=\"".base64_encode($this->GeoISP)."\" sys=\"".base64_encode($this->OperatingSystem)."\" dt=\"".base64_encode($this->Device)."\" bro=\"".base64_encode($this->Browser)."\" vl=\"".base64_encode($this->VisitLast)."\">";
        $ud = new UserData($row["h_fullname"],$row["h_email"],$row["h_company"],$row["h_phone"],@unserialize($row["h_customs"]),$row["h_text"]);
        $xml .= $ud->GetXML();

        $this->LoadComments();
        $xml .= $this->GetCommentsXML();

        $this->LoadChatRequests();
        $xml .= $this->GetInvitesXML();

        return $xml . "</v>";
    }

    function GetInvitesXML(){
        $xml = "";
        if(!empty($this->ChatRequests))
            foreach($this->ChatRequests as $invite)
                $xml .= $invite->GetXML();
        return $xml;
    }

    function GetCommentsXML()
    {
        $xml = "";
        if(!empty($this->Comments))
            foreach($this->Comments as $cid => $carray)
                $xml .=  " <c id=\"".base64_encode($cid)."\" c=\"".base64_encode($carray["created"])."\" o=\"".base64_encode($carray["operator_id"])."\">".base64_encode($carray["comment"])."</c>\r\n";
        return $xml;
    }

	function LoadBrowsers($_expired=false)
	{
		$this->Browsers = array();
        $limiter = (!$_expired) ? " AND `last_active` > ".(time()-Server::$Configuration->File["timeout_track"])." " : "";
        if($result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_BROWSERS . "` WHERE `visit_id`='" . DBManager::RealEscape($this->VisitId) . "' AND `visitor_id`='" . DBManager::RealEscape($this->UserId) . "'" . $limiter . "ORDER BY `created` ASC;"))
        {
            $this->FullyLoaded = true;
            while($row = DBManager::FetchArray($result))
            {
                $browser = $this->CreateBrowser($row,$_expired);
                $this->Browsers[count($this->Browsers)] = $browser;
            }
		}
	}

    function CreateBrowser($_row,$_loadHistory=false,$_loadChat=true)
    {
        if(empty($_row["is_chat"]))
        {
            $browser = new VisitorBrowser($_row["id"],$_row["visitor_id"],$_loadHistory);
            $browser->Query = (!empty($_row["query"])) ? CacheManager::GetDataTableValueFromId(DATABASE_VISITOR_DATA_QUERIES,"query",$_row["query"]) : "";
        }
        else
        {
            $browser = new VisitorChat($_row["visitor_id"],$_row["id"],false);
            if($_loadChat)
            {
                $browser->Load();
                if($browser->LastActive<(time()-Server::$Configuration->File["timeout_chats"]) && !empty($browser->OperatorId) && !Server::$Operators[$browser->OperatorId]->IsBot)
                {
                    $browser->CloseChat();
                    $browser->CloseWindow();
                }
            }
        }
        $browser->Created = $_row["created"];
        $browser->Overlay = !empty($_row["overlay"]);
        $browser->OverlayContainer = !empty($_row["overlay_container"]);
        $browser->LastActive = $_row["last_active"];
        return $browser;
    }

    function ApplyOverlayInputValues($group)
    {
        $newData = (!empty($this->VisitorData)) ? new UserData($this->VisitorData->Fullname,$this->VisitorData->Email,$this->VisitorData->Company,$this->VisitorData->Phone,$this->VisitorData->Customs,$this->VisitorData->Text) : new UserData();
        $newData->LoadFromLogin($group);
        if($this->VisitorData->IsDifference($newData))
        {
            $this->VisitorData = $newData;
            return true;
        }
        return false;
    }

    function LoadComments()
    {
        $this->Comments = array();
        if($result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_COMMENTS . "` WHERE `visitor_id`='" . DBManager::RealEscape($this->UserId) . "' ORDER BY `created` DESC;"))
            while($row = DBManager::FetchArray($result))
                $this->Comments[$row["id"]] = array("created"=>$row["created"],"operator_id"=>$row["operator_id"],"comment"=>$row["comment"]);
    }

    function SaveComment($_operatorId,$_comment)
    {
        DBManager::Execute(true, "INSERT IGNORE INTO `" . DB_PREFIX . DATABASE_VISITOR_COMMENTS . "` (`id`, `visitor_id`, `created`, `operator_id`, `comment`) VALUES ('" . DBManager::RealEscape(getId(32)) . "','" . DBManager::RealEscape($this->UserId) . "','" . DBManager::RealEscape(time()) . "','" . DBManager::RealEscape($_operatorId) . "','" . DBManager::RealEscape($_comment) . "');");
        $this->ForceUpdate();
    }

    function RemoveComment($_comment)
    {
        DBManager::Execute(true, "DELETE FROM `" . DB_PREFIX . DATABASE_VISITOR_COMMENTS . "` WHERE `visitor_id`='" . DBManager::RealEscape($this->UserId) . "' AND `comment`='" . DBManager::RealEscape($_comment) . "';");
        $this->ForceUpdate();
    }

    function ForceUpdate()
    {
        DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITORS . "` SET `edited`='" . DBManager::RealEscape(time()) . "' WHERE `id`='" . DBManager::RealEscape($this->UserId) . "' ORDER BY `entrance` DESC LIMIT 1;");
    }

    function SetLanguage($_lang)
    {
        DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITORS . "` SET `language`='" . DBManager::RealEscape($_lang) . "' WHERE `id`='" . DBManager::RealEscape($this->UserId) . "' ORDER BY `entrance` DESC LIMIT 1;");
    }

	function IsInChatWith($_operator)
	{
        if(is_array(Server::$Chats))
            foreach(Server::$Chats as $chat)
            {
                if($chat->UserId == $this->UserId && $chat->LastActive > (time()-Server::$Configuration->File["timeout_track"]) && $chat->LastActive > (time()-Server::$Configuration->File["timeout_chats"]) && !$chat->Closed)
                    if(isset($chat->Members[$_operator->SystemId]))
                        return true;
            }

		return false;
	}

	function Save($_resolution="",$_color="",$_timezone="",$_lat=0,$_long=0,$_countryiso2="",$_city="",$_region="",$_geotimezone="",$_isp="",$_geosspan=0,$_grid=1,$_js=true,$_fromCookie=false)
	{
        if($this->FirstCall)
		{
			if(!Is::Null(Cookie::Get("visits")) && $this->Visits==1)
				$this->Visits = Cookie::Get("visits")+1;

            Cookie::Set("visits",$this->Visits);

			if(!Is::Null(Cookie::Get("last_visit")))
				$this->VisitLast = Cookie::Get("last_visit");

            Cookie::Set("last_visit",time());

			$this->IP = Communication::GetIP();
    		$this->SystemInfo = ((isset($_SERVER["HTTP_USER_AGENT"])) ? trim($_SERVER["HTTP_USER_AGENT"]) : "");
			
			$localization = LocalizationManager::GetBrowserLocalization();
			$this->Language = $localization[0];
			
			if(!empty($localization[1]))
				$this->GeoCountryISO2 = $localization[1];

			$this->Resolution = (!empty($_resolution) && count($_resolution) == 2 && !empty($_resolution[0]) && !empty($_resolution[1])) ? $_resolution[0] . " x " . $_resolution[1] : "";
			$this->Resolution .= (!empty($_color)) ? " (" . $_color . " Bit)" : "";
			$this->GeoTimezoneOffset = SystemTime::GetLocalTimezone($_timezone);
			$this->GeoResult = 0;

			if(!empty(Server::$Configuration->File["gl_use_ngl"]) && $_js)
			{
                if(!Is::Null(Cookie::Get("geo_data")) && !Is::Null(Cookie::Get(GEO_LATITUDE)))
                {
                    $this->GeoLatitude = Cookie::Get(GEO_LATITUDE);
                    $this->GeoLongitude = Cookie::Get(GEO_LONGITUDE);
                    $this->GeoCountryISO2 = Cookie::Get(GEO_COUNTRY_ISO_2);
                    $this->GeoCity = Cookie::Get(GEO_CITY);
                    $this->GeoRegion = Cookie::Get(GEO_REGION);
                    $this->GeoTimezoneOffset = Cookie::Get(GEO_TIMEZONE);
                    $this->GeoISP = Cookie::Get(GEO_ISP);
                    $_fromCookie = true;
                }
				else if(!empty($_lat) && $_lat > -180)
				{
					Cookie::Set(GEO_LATITUDE,$this->GeoLatitude = $_lat);
					Cookie::Set(GEO_LONGITUDE,$this->GeoLongitude = $_long);
					Cookie::Set(GEO_COUNTRY_ISO_2,$this->GeoCountryISO2 = $_countryiso2);
					Cookie::Set(GEO_CITY,$this->GeoCity = $_city);
					Cookie::Set(GEO_REGION,$this->GeoRegion = $_region);
					Cookie::Set(GEO_TIMEZONE,$this->GeoTimezoneOffset = $_geotimezone);
					Cookie::Set(GEO_ISP,$this->GeoISP = $_isp);
					Cookie::Set("geo_data",time());
				}
				else if(!empty($_lat))
				{
					$this->GeoLatitude = $_lat;
					$this->GeoLongitude = $_long;
				}

				GeoTracking::SpanRemove(false);
				if($_fromCookie)
					$this->GeoResultId = 6;
				else if(!Is::Null($span=GeoTracking::SpanExists()))
				{
					if($span > (time()+CONNECTION_ERROR_SPAN))
						$this->GeoResultId = 5;
					else
						$this->GeoResultId = 4;
				}
				else
				{
					if($_lat == -777)
						$this->GeoResultId = 5;
					else if($_lat == -522)
						$this->GeoResultId = 2;
					else if($_grid != 4)
						$this->GeoResultId = 3;
					else
						$this->GeoResultId = $_grid;
				}
			}
			else
				$this->GeoResultId = 7;
				
			Server::InitDataBlock(array("COUNTRIES"));
			if(isset(Server::$CountryAliases[$this->GeoCountryISO2]))
				$this->GeoCountryISO2 = Server::$CountryAliases[$this->GeoCountryISO2];

			$detector = new DeviceDetector();
			$detector->DetectBrowser($this->Host);

			if($detector->AgentType == AGENT_TYPE_BROWSER || $detector->AgentType == AGENT_TYPE_UNKNOWN)
			{
				$detector->DetectOperatingSystem($this->Host);
				$bid = $this->GetBrowserId($detector->Browser,$detector->AgentType);
				$oid = $this->GetOSId($detector->OperatingSystem);
				$this->CreateSignature();

                $devicetype = 0;
                if($detector->IsTablet)
                    $devicetype = 1;
                else if($detector->IsMobile)
                    $devicetype = 2;

                DBManager::Execute(true, $d= "INSERT IGNORE INTO `" . DB_PREFIX . DATABASE_VISITORS . "` (`id`, `entrance`, `host`,`ip`,`system`,`browser`, `visits`,`visit_id`,`visit_last`,`resolution`, `language`, `country`, `city`, `region`, `isp`, `timezone`, `latitude`, `longitude`, `geo_result`, `js`, `signature`, `data_id`, `device`) VALUES ('" . DBManager::RealEscape($this->UserId) . "', '" . DBManager::RealEscape(time()) . "', '" . DBManager::RealEscape($this->Host) . "', '" . DBManager::RealEscape($this->IP) . "', '" . DBManager::RealEscape($oid) . "','" . DBManager::RealEscape($bid) . "', '" . DBManager::RealEscape($this->Visits) . "', '" . DBManager::RealEscape($this->VisitId) . "','" . DBManager::RealEscape($this->VisitLast) . "', '" . DBManager::RealEscape(CacheManager::GetDataTableIdFromValue(DATABASE_VISITOR_DATA_RESOLUTIONS, "resolution", $this->Resolution, false, 32)) . "', '" . DBManager::RealEscape(substr(strtoupper($this->Language), 0, 5)) . "','" . DBManager::RealEscape($this->GeoCountryISO2) . "', '" . DBManager::RealEscape(CacheManager::GetDataTableIdFromValue(DATABASE_VISITOR_DATA_CITIES, "city", $this->GeoCity, false)) . "', '" . DBManager::RealEscape(CacheManager::GetDataTableIdFromValue(DATABASE_VISITOR_DATA_REGIONS, "region", $this->GeoRegion, false)) . "', '" . DBManager::RealEscape(CacheManager::GetDataTableIdFromValue(DATABASE_VISITOR_DATA_ISPS, "isp", mb_convert_encoding($this->GeoISP ?? "", "UTF-8", "ISO-8859-1"), false)) . "', '" . DBManager::RealEscape($this->GeoTimezoneOffset) . "', '" . DBManager::RealEscape($this->GeoLatitude) . "', '" . DBManager::RealEscape($this->GeoLongitude) . "', '" . DBManager::RealEscape($this->GeoResultId) . "', '" . DBManager::RealEscape($_js ? 1 : 0) . "', '" . DBManager::RealEscape($this->Signature) . "', '" . DBManager::RealEscape($this->VisitorData->Save()) . "', " . intval($devicetype) . ");");

                if($this->VisitsDay > 1 && DBManager::GetAffectedRowCount() == 1)
                    DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITORS . "` SET `visit_latest`=0 WHERE `id`='" . DBManager::RealEscape($this->UserId) . "' AND `visit_id`!='" . DBManager::RealEscape($this->VisitId) . "';");
            }
			else if(STATS_ACTIVE)
			{
				$this->IsCrawler = true;
				Server::$Statistic->ProcessAction(ST_ACTION_LOG_CRAWLER_ACCESS,array($this->GetCrawlerId($detector->Browser),null));
			}
		}
        else if(isset($_GET["pto"]))
        {
            $localization = LocalizationManager::GetBrowserLocalization();
            if(is_array($localization) && !empty($localization[0]) && $localization[0] != $this->Language)
            {
                $this->SetLanguage($localization[0]);
                $this->ForceUpdate();
            }
        }
	}
	
	function ResolveHost()
	{
		$this->Host = Communication::GetHost();
	}
	
    function CreateSignature()
    {
        $sig =
            ($_SERVER["HTTP_USER_AGENT"] ?? "") .
            ($_SERVER["HTTP_ACCEPT"] ?? "") .
            ($_SERVER["HTTP_ACCEPT_LANGUAGE"] ?? "") .
            ($_SERVER["HTTP_ACCEPT_CHARSET"] ?? "");
    
        $this->Signature = md5(Communication::GetIP() . $sig);
    }
	
	function GetCrawlerId($_crawler)
	{
        return CacheManager::GetDataTableIdFromValue(DATABASE_VISITOR_DATA_CRAWLERS,"crawler",$_crawler);
	}

	function GetOSId($_osname)
	{
        return CacheManager::GetDataTableIdFromValue(DATABASE_VISITOR_DATA_SYSTEMS,"system",$_osname);
	}
	
	function GetBrowserId($_browser)
	{
        return CacheManager::GetDataTableIdFromValue(DATABASE_VISITOR_DATA_BROWSERS,"browser",$_browser);
	}

	function SaveTicket($_group,$_country,$_cmb=false,$_custom=true,$_url="",$changed=false)
	{
        $this->Load();

        $isSpam = (!empty(Server::$Configuration->File["gl_sft"]) && Visitor::CreateSPAMFilter($this->UserId));

        $group = Server::$Groups[$_group];

        $browserLangDetect = true; //!((isset(Server::$Configuration->File["gl_dsbl"]) && !Server::$Configuration->File["gl_dsbl"]));

        $lang = strtoupper((!empty($this->Language) && $browserLangDetect) ? $this->Language : Server::$Configuration->File["gl_default_language"]);

        $plang = LocalizationManager::ReadParams();
        if(!empty($plang))
            $lang = strtoupper($plang);

        $pdm = PredefinedMessage::GetByLanguage($group->PredefinedMessages,$lang);
        if($pdm != null)
            $lang = $pdm->LangISO;

        $ticket = null;
        $message = null;
        $saveTicket = true;

		if(!(isset(Server::$Configuration->File["gl_mmvt"]) && empty(Server::$Configuration->File["gl_mmvt"])))
        {
			$existingOpenTicket = Ticket::GetOpenByUserId($this->UserId,$this->FirstActive);
			if($existingOpenTicket != null)
			{
				if($existingOpenTicket->Group == $_group)
				{
					$ticket = $existingOpenTicket;
					$message = new TicketMessage(getId(32),true);
					$ticket->Messages[] = $message;
					$ticket->Language = $lang;
					$ticket->SetLanguage($lang);
					$saveTicket = false;
				}
			}
		}

        if($ticket == null)
        {
		    $ticket = new Ticket(CacheManager::GetObjectId("ticket_id",DATABASE_TICKETS),$lang);
            $message = $ticket->Messages[0];
            $message->Id = $ticket->Id;
        }

        $message->IP = Communication::GetIP();
        $email = $group->GetServerInput(Server::$Inputs[112],false,"",$changed);

        Server::InitDataBlock(array("FILTERS"));

        if(Server::$Filters->MatchEmail($email,""))
        {
            $this->AddFunctionCall("lz_chat_mail_callback(true);",false);
            return false;
        }

		if(!isTicketFlood() && !$isSpam && !(defined("FILTER_ALLOW_TICKETS") && !FILTER_ALLOW_TICKETS))
		{
            Chat::SetTicketId("",$this->UserId,$ticket->Id);
			Server::InitDataBlock(array("INPUTS"));
			$ticket->SenderUserId = $message->SenderUserId = $this->UserId;
			$ticket->Group = $_group;
            $ticket->Channel = ($_cmb) ? 2 : 0;
            $message->Fullname = $this->Browsers[0]->Fullname = $group->GetServerInput(Server::$Inputs[111],false,"",$changed);
            $message->Email = $this->Browsers[0]->Email = $email;
            $message->Company = $this->Browsers[0]->Company = $group->GetServerInput(Server::$Inputs[113],false,"",$changed);
            $message->Phone = $this->Browsers[0]->Phone = $group->GetServerInput(Server::$Inputs[116],false,"",$changed);
            $message->Text = $group->GetServerInput(Server::$Inputs[114],false,"",$changed);
            $message->CallMeBack = $_cmb;
            $message->Country = $_country;
            $message->EmailId = getId(32);
            $message->ChannelId = getId(32);
            $message->Edited = $message->Created = time();
            $message->AttachTemporaryFiles($this->UserId);

            if(empty($_url))
                $_url = Visitor::GetLastURLFromVisitor($this->UserId);

            $message->Subject = $_url;
			$this->Browsers[0]->DesiredChatGroup = $ticket->Group;
			$this->Browsers[0]->SetCookieGroup();

			if($_custom)
            {
				foreach(Server::$Inputs as $index => $input)
				{
 					if($input->Active && $input->Custom)
					{
                        $value = $group->GetServerInput($input,false,"",$changed);

    					if($input->Type != "File" && (!empty($value)||$value==="0"))
                        {
                            $message->Customs[$index] = $value;
                            $this->Browsers[0]->Customs[$index] = $value;
                        }
					}
				}

                $ud = new UserData($message->Fullname,$message->Email,$message->Company,$message->Phone,$message->Customs);
                $ud->Save();
                $ud->SaveToCookie();
            }

			if(Server::$Configuration->File["gl_adct"] == 1 || !(!empty(Server::$Configuration->File["gl_rm_om"]) && Server::$Configuration->File["gl_rm_om_time"] == 0))
            {
                if($saveTicket)
                {
                    $ticket->Save();
                    $ticket->AutoAssignEditor();
                }
                else
                {
                    $message->Save($ticket->Id);
                }
                $ticket->SetLastUpdate(time());
            }

			$this->AddFunctionCall("lz_chat_mail_callback(true);",false);

            if(!empty($_POST[POST_EXTERN_REQUESTED_INTERNID]) && !empty(Server::$Operators[Operator::GetSystemId(Encoding::Base64UrlDecode($_POST[POST_EXTERN_REQUESTED_INTERNID]))]))
            {
                $TicketEditor = new TicketEditor($ticket->Id);
                $TicketEditor->Editor = Operator::GetSystemId(Encoding::Base64UrlDecode($_POST[POST_EXTERN_REQUESTED_INTERNID]));
                $TicketEditor->Status = 0;
                $TicketEditor->Save();
            }

            if(!empty(Server::$Configuration->File["gl_mpm"]))
                foreach(Server::$Operators as $operator)
                    if($operator->IsInPushMessageState())
                        if($operator->HasAccessToTicket($ticket))
                            $operator->AddPushMessage($ticket->Id, $this->SystemId, $message->Fullname, 2, $message->Text);

			return $ticket;
		}
		else
			$this->AddFunctionCall("lz_chat_mail_callback(false);",false);
		return false;
	}
	
	function AddFunctionCall($_call,$_overwrite=false,$_prepend=false)
	{
        $spacer = "";//\r\n\r\n";
		if(empty($this->Response))
			$this->Response = "";
        if($_prepend)
            $this->Response = $spacer . $_call . $spacer . $this->Response;
		else if($_overwrite)
			$this->Response = $spacer . $_call;
		else
			$this->Response .= $spacer . $_call;
	}

    function AddBrowser($_browser)
    {
        for($i=0;$i<count($this->Browsers);$i++)
        {
            if($this->Browsers[$i]->BrowserId == $_browser->BrowserId)
            {
                $this->Browsers[$i] = $_browser;
                return;
            }
        }
        $this->Browsers[$i] = $_browser;
    }

    function GetBrowser($_bid)
    {
        for($i=0;$i<count($this->Browsers);$i++)
        {
            if($this->Browsers[$i]->BrowserId == $_bid)
            {
                return $this->Browsers[$i];
            }
        }
        return null;
    }
	
	function IsActivity($_browser,$_noBotChats=false,$_fromDatabase=true)
	{
		if($this->IsInChat($_noBotChats,null,$_fromDatabase))
			return true;
		if($_browser != null && $_browser->IsActivity())
			return true;
        else if($_browser == null)
            foreach($this->Browsers as $browser)
                if($browser->IsActivity())
                    return true;
		return false;
	}
	
	function IsInChat($_noBotChats=false,$_browser=null,$_fromDatabase=false)
	{
        if(!$_fromDatabase)
        {
            foreach($this->Browsers as $browser)
            {
                if($browser->Type == BROWSER_TYPE_CHAT && $browser->LastActive > (time()-Server::$Configuration->File["timeout_chats"]) && (!empty($browser->ChatId) || $browser->Waiting) && !$browser->Declined)
                {
                    if(!$_noBotChats || (!empty($browser->OperatorId) && !Server::$Operators[$browser->OperatorId]->IsBot) || $browser->Waiting)
                        if(!(!empty($_browser) && $_browser->BrowserId == $browser->BrowserId) || Str::EndsWith($browser->BrowserId,"_OVL"))
                        {
                            return true;
                        }
                }
                if(is_array(Server::$Chats))
                    foreach(Server::$Chats as $chat)
                    {
                        if($chat->UserId == $this->UserId && $chat->LastActive > (time()-Server::$Configuration->File["timeout_track"]) && $chat->LastActive > (time()-Server::$Configuration->File["timeout_chats"]) && !$chat->Closed)
                            return true;
                    }
            }
        }
        else
        {
            $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` WHERE `visitor_id`='" . DBManager::RealEscape($this->UserId) . "' AND `last_active` > " . (time() - Server::$Configuration->File["timeout_chats"]) . " AND (`status` > 0 OR `waiting`=1) AND `internal_declined`=0 AND `exit`=0;");
            while($row = DBManager::FetchArray($result))
            {
                if(!$_noBotChats || (!empty(Server::$Operators[$row["request_operator"]]) && !Server::$Operators[$row["request_operator"]]->IsBot) || !empty($row["waiting"]))
                    if(!(!empty($_browser) && $_browser->BrowserId == $row["browser_id"]) || Str::EndsWith($row["browser_id"],"_OVL"))
                        return true;
            }
        }
		return false;
	}

    function GetLastChatOperator($_noBotChats=false)
    {
        $result = DBManager::Execute(true, "SELECT `chat_id`,`internal_id` FROM `" . DB_PREFIX . DATABASE_CHAT_ARCHIVE . "` WHERE `external_id` = '" . DBManager::RealEscape($this->UserId) . "';");
        while($row = DBManager::FetchArray($result))
            if(!$_noBotChats || (!empty(Server::$Operators[$row["internal_id"]]) && !Server::$Operators[$row["internal_id"]]->IsBot))
                return $row["internal_id"];
        return "";
    }

	function WasInChat($_checkArchive=true)
	{
        $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` WHERE `visitor_id`='" . DBManager::RealEscape($this->UserId) . "' AND `chat_id`>0;");
        if($row = DBManager::FetchArray($result))
        {
            return true;
        }

        if($_checkArchive)
        {
            $lco = $this->GetLastChatOperator(false);
            if(!empty($lco))
                return true;
        }

        return false;
	}
	
	function GetChatRequestResponses()
	{
		if($result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_CHAT_REQUESTS . "` WHERE `receiver_user_id`='" . DBManager::RealEscape($this->UserId) . "' ORDER BY `closed` ASC,`created` DESC;"))
		{
			while($row = DBManager::FetchArray($result))
			{
				if(!empty($row["declined"]))
					$this->HasDeclinedChatRequest = true;
				if(!empty($row["accepted"]))
					$this->HasAcceptedChatRequest = true;
			}
		}
	}

    function LoadChatRequests($_timeout=false)
    {
        if(!is_array($this->ChatRequests))
        {
            $this->ChatRequests = array();
            if($result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_CHAT_REQUESTS . "` WHERE `receiver_user_id`='" . DBManager::RealEscape($this->UserId) . "' ORDER BY `created` DESC;"))
                while($row = DBManager::FetchArray($result))
                {
                    if(!empty($row["declined"]))
                        $this->HasDeclinedChatRequest = true;
                    if(!empty($row["accepted"]))
                        $this->HasAcceptedChatRequest = true;

                    $request = new ChatRequest($row);
                    $found = false;

                    foreach($this->Browsers as $browser)
                        if($browser->BrowserId == $row["receiver_browser_id"])
                        {
                            if($browser->ChatRequests == null)
                                $browser->ChatRequests = array();

                            $browser->ChatRequests[] = $request;

                            $found=true;
                        }
                    if($_timeout && !$found && empty($this->Canceled) && !$request->Closed)
                        $request->Cancel("Timeout","1");

                    $this->ChatRequests[] = $request;
                }
        }
    }

    function GetInputData($_inputIndex,$_chat=true)
    {
        $data = array(111=>$this->VisitorData->Fullname,112=>$this->VisitorData->Email,113=>$this->VisitorData->Company,114=>$this->VisitorData->Text,116=>$this->VisitorData->Phone);
        if(isset($data[$_inputIndex]))
            $value = $data[$_inputIndex];
        else if(isset($this->VisitorData->Customs[$_inputIndex]))
            $value = $this->VisitorData->Customs[$_inputIndex];
        else
            return "";

        if(isset(Server::$Operators[CALLER_SYSTEM_ID]))
        {
            $lvl = Server::$Operators[CALLER_SYSTEM_ID]->GetInputMaskLevel($_inputIndex,$_chat);
            if($lvl > 0)
                return OperatorRequest::MaskData($value,$lvl);
        }
        return $value;
    }

    function TextReplace($_text)
    {
        Server::InitDataBlock(array("COUNTRIES"));
        $_text = str_replace("%external_ip%",$this->IP,$_text);
        $_text = str_replace("%location_city%",CacheManager::GetDataTableValueFromId(DATABASE_VISITOR_DATA_CITIES,"city",$this->GeoCity,false,true),$_text);
        $_text = str_replace("%location_country_iso%",$this->GeoCountryISO2,$_text);
        $_text = str_replace("%location_country%",@Server::$Countries[$this->GeoCountryISO2],$_text);
        $_text = str_replace("%location_region%",CacheManager::GetDataTableValueFromId(DATABASE_VISITOR_DATA_REGIONS,"region",$this->GeoRegion,false,true),$_text);

        $_text = str_replace(array("%external_name%","%USERNAME%"),$this->VisitorData->Fullname,$_text);
        $nameparts = explode(" ",$this->VisitorData->Fullname);

        if(count($nameparts)>2)
        {
            $_text = str_replace(array("%external_firstname%"),trim($nameparts[0]),$_text);
            $nameparts[0] = "";
            $_text = str_replace(array("%external_lastname%"),trim(implode(" ",$nameparts)),$_text);
        }
        else if(count($nameparts)==2)
        {
            $_text = str_replace(array("%external_firstname%"),trim($nameparts[0]),$_text);
            $_text = str_replace(array("%external_lastname%"),trim($nameparts[1]),$_text);
        }
        else if(count($nameparts)==1)
        {
            $_text = str_replace(array("%external_firstname%","%USERNAME%"),trim($nameparts[0]),$_text);
            $_text = str_replace(array("%external_lastname%","%USERNAME%"),"",$_text);
        }
        else
        {
            $_text = str_replace(array("%external_firstname%","%USERNAME%"),"",$_text);
            $_text = str_replace(array("%external_lastname%","%USERNAME%"),"",$_text);
        }


        $_text = str_replace(array("%external_email%","%USEREMAIL%"),$this->VisitorData->Email,$_text);
        $_text = str_replace(array("%external_company%","%USERCOMPANY%"),$this->VisitorData->Company,$_text);
        $_text = str_replace("%external_phone%",$this->VisitorData->Phone,$_text);
        //$_text = str_replace(array("%question%","%USERQUESTION%","%mailtext%"),$this->VisitorData->Text,$_text);

        foreach(Server::$Inputs as $index => $input)
            if($input->Active && $input->Custom)
            {
                if($input->Type == "CheckBox")
                    $_text = str_replace("%custom".($index)."%",((!empty($this->VisitorData->Customs[$index])) ? LocalizationManager::$TranslationStrings["client_yes"] : LocalizationManager::$TranslationStrings["client_no"]),$_text);
                else if(!empty($this->VisitorData->Customs[$index]))
                    $_text = str_replace("%custom".($index)."%",$input->GetClientValue($this->VisitorData->Customs[$index]),$_text);
                else
                    $_text = str_replace("%custom".($index)."%","",$_text);
            }
            else
                $_text = str_replace("%custom".($index)."%","",$_text);


        return $_text;
    }

    function ReloadGroups($_overlay=false,$_preSelect=true)
    {
        Server::InitDataBlock(array("INTERNAL","FILTERS"));

        $grParam = UserGroup::ReadParams();
        $opParam = Operator::ReadParams();

        if(!empty($this->Browsers[0]->GroupId) && Visitor::$IsActiveOverlayChat)
            $this->Browsers[0]->DesiredChatGroup = $this->Browsers[0]->GroupId;
        else if(!empty($grParam) && empty($this->Browsers[0]->DesiredChatGroup))
            $this->Browsers[0]->DesiredChatGroup = $grParam;

        if(!empty($opParam))
            $this->Browsers[0]->SetDesiredChatPartner(Operator::GetSystemId($opParam));

        $groupbuilder = new GroupBuilder($this->Browsers[0]->DesiredChatGroup,$this->Browsers[0]->DesiredChatPartner);
        $groupbuilder->Generate($this,true);

        if(in_array($this->Browsers[0]->DesiredChatGroup,$groupbuilder->GroupValues["groups_hidden"]))
            $this->Browsers[0]->DesiredChatGroup = $grParam;

        if(!empty($opParam))
            $this->Browsers[0]->SetDesiredChatPartner(Operator::GetSystemId($opParam));

        $_preSelect = ($_preSelect) ? Encoding::Base64UrlEncode($this->Browsers[0]->DesiredChatGroup) : "";

        $this->AddFunctionCall("OverlayChatWidgetV2.SetGroups(\"" . $groupbuilder->Result . "\",'". $_preSelect ."');",false,$_overlay);
    }

    function LoadVisitorData()
    {
        $result = DBManager::Execute(true, "SELECT *,`t2`.`id` AS `id` FROM `" . DB_PREFIX . DATABASE_VISITORS . "` AS `t1` INNER JOIN `" . DB_PREFIX . DATABASE_USER_DATA . "` AS `t2` ON `t1`.`data_id`=`t2`.`id` WHERE `t1`.`id`='" . DBManager::RealEscape($this->UserId) . "';");
        if($result)
            if($row = DBManager::FetchArray($result))
            {
                $vd = new UserData();
                $vd->SetDetails($row);
                $this->VisitorData = $vd;
                return !$vd->IsEmpty();
            }
        return false;
    }

    function ApplyVisitorData()
    {
        DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITORS . "` SET `data_id`='" . DBManager::RealEscape($this->VisitorData->Save()) . "' WHERE `id`='" . DBManager::RealEscape($this->UserId) . "';");
        if(DBManager::GetAffectedRowCount()>0)
            $this->ForceUpdate();
    }

    function ToggleScreenSharing($_browserId)
    {
        DBManager::Execute(true, $d = "UPDATE `" . DB_PREFIX . DATABASE_VISITORS . "` SET `ss_stream`='" . DBManager::RealEscape($_browserId) . "' WHERE `id`='" . DBManager::RealEscape($this->UserId) . "' ORDER BY `entrance` DESC LIMIT 1;");
    }

    static function CreateSPAMFilter($_userId,$_base64=true)
    {
        if(!empty(Server::$Configuration->File["gl_sfa"]))
        {
            $filterkeys = array(0=>Server::$Configuration->File["gl_sfv"]);
            if(strpos(Server::$Configuration->File["gl_sfv"],",") !== -1)
                $filterkeys = explode(",",Server::$Configuration->File["gl_sfv"]);

            foreach($filterkeys as $fvalue)
            {
                $fvalue = trim($fvalue);
                foreach($_GET as $gvalue)
                {
                    $ivalue = ($_base64) ? Encoding::Base64UrlDecode($gvalue) : $gvalue;
                    if(Is::WildcardMatch($fvalue,$ivalue,true))
                    {
                        Filter::Create(Communication::GetIP(),$_userId,"AUTO SPAM Filter: " . $fvalue);
                        return true;
                    }
                }
                foreach($_POST as $pvalue)
                {
                    $ivalue = ($_base64) ? Encoding::Base64UrlDecode($pvalue) : $pvalue;
                    if(Is::WildcardMatch($fvalue,$ivalue,true))
                    {
                        Filter::Create(Communication::GetIP(),$_userId,"AUTO SPAM Filter: " . $fvalue);
                        return true;
                    }
                }
            }
        }
        return false;
    }

    static function IDValidate($_id="")
    {
        if(empty($_id))
            return getId(USER_ID_LENGTH);
        else if(strlen($_id) != USER_ID_LENGTH)
            return getId(USER_ID_LENGTH);
        else if(function_exists("ctype_alnum") && !ctype_alnum($_id))
            return getId(USER_ID_LENGTH);
        return $_id;
    }

    static function GetLastURLFromVisitor($_visitorId)
    {
        $result = DBManager::Execute(true, "SELECT `untouched` FROM `" . DB_PREFIX . DATABASE_VISITOR_BROWSERS . "` AS `t1` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_BROWSER_URLS . "` AS `t2` ON `t1`.`id`=`t2`.`browser_id` WHERE `untouched` NOT LIKE '%/chat.php%' AND `t1`.`visitor_id`='" . DBManager::RealEscape($_visitorId) . "' ORDER BY `t2`.`entrance` DESC LIMIT 1;");
        while($row = DBManager::FetchArray($result))
            return $row["untouched"];
        return "";
    }

    static function Build($_fullList=false,$_sqlwhere="",$_limit="",$_created=0)
    {
        if(DBManager::$Connected)
        {
            Server::InitDataBlock(array("COUNTRIES"));
            Server::$Visitors = $tvisitors = $tbrowsers = array();

            if(!$_fullList)
                $_sqlwhere = " WHERE `closed`=0";

            $result = DBManager::Execute(true, "SELECT *,`t1`.`id` AS `id` FROM `" . DB_PREFIX . DATABASE_VISITORS . "` AS `t1` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_DATA_CITIES . "` AS `t3` ON `t1`.`city`=`t3`.`id` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_DATA_REGIONS . "` AS `t4` ON `t1`.`region`=`t4`.`id` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_DATA_ISPS . "` AS `t5` ON `t1`.`isp`=`t5`.`id` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_DATA_SYSTEMS . "` AS `t6` ON `t1`.`system`=`t6`.`id` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_DATA_RESOLUTIONS . "` AS `t8` ON `t1`.`resolution`=`t8`.`id` INNER JOIN `" . DB_PREFIX . DATABASE_USER_DATA . "` AS `t9` ON `t1`.`data_id`=`t9`.`id`" . $_sqlwhere . " ORDER BY `entrance` ASC" . $_limit . ";");

            if(!$result)
                return 0;

            while($row = DBManager::FetchArray($result))
            {
                $fa = $row["entrance"];
                if(isset($tvisitors[$row["id"]]))
                    $fa = min($tvisitors[$row["id"]]->FirstActive,$fa);
                if(!empty(Server::$Countries[$row["country"]]))
                    $row["countryname"] = Server::$Countries[$row["country"]];
                if(!isset($vcount[$row["id"]]))
                    $vcount[$row["id"]]=0;

                $vcount[$row["id"]]++;
                $row["dcount"] = $vcount[$row["id"]];
                $visitor = new Visitor($row["id"]);
                $visitor->Load($row);
                $visitor->FirstActive = $fa;
                $tvisitors[$row["id"]] = $visitor;
                $tvisitors[$row["id"]]->FirstActive = min($tvisitors[$row["id"]]->FirstActive,$row["entrance"]);
            }

            $minEntrance = time();

            if($_fullList)
                $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_BROWSERS . "` WHERE `created` > " . intval($_created) . " ORDER BY `created` ASC;");
            else
                $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_BROWSERS . "` WHERE `last_active` > " . intval(time() - @Server::$Configuration->File["timeout_track"]) . " ORDER BY `created` ASC;");

            if($result)
                while($row = DBManager::FetchArray($result))
                {
                    if(isset($tvisitors[$row["visitor_id"]]) && ($tvisitors[$row["visitor_id"]]->VisitId == $row["visit_id"] || $_fullList))
                    {
                        $browser = $tvisitors[$row["visitor_id"]]->CreateBrowser($row,false,false);
                        $tbrowsers[$browser->BrowserId] = $browser;
                        $minEntrance = min($minEntrance,$row["created"]);
                    }
                }

            if($_fullList)
            {
                if($result = DBManager::Execute(true, "SELECT `" . DB_PREFIX . DATABASE_VISITOR_BROWSER_URLS . "_sorted`.`browser_id`,`" . DB_PREFIX . DATABASE_VISITOR_BROWSER_URLS . "_sorted`.`title` as `url_title`,`treftitle`.`title` as `ref_title`,`turldom`.`domain` as `url_dom`,`turlpath`.`path` as `url_path`,`trefdom`.`domain` as `ref_dom`,`trefpath`.`path` as `ref_path`,`entrance`,`params`,`untouched`,`ref_untouched` FROM (SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_BROWSER_URLS . "` ORDER BY `entrance` ASC) AS `" . DB_PREFIX . DATABASE_VISITOR_BROWSER_URLS . "_sorted` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_DATA_PAGES . "` AS `turl` ON `" . DB_PREFIX . DATABASE_VISITOR_BROWSER_URLS . "_sorted`.`url`=`turl`.`id` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_DATA_PAGES . "` AS `tref` ON `" . DB_PREFIX . DATABASE_VISITOR_BROWSER_URLS . "_sorted`.`referrer`=`tref`.`id` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_DATA_DOMAINS . "` AS `trefdom` ON `tref`.`domain`=`trefdom`.`id` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_DATA_DOMAINS . "` AS `turldom` ON `turl`.`domain`=`turldom`.`id` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_DATA_PATHS . "` AS `trefpath` ON `tref`.`path`=`trefpath`.`id` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_DATA_PATHS . "` AS `turlpath` ON `turl`.`path`=`turlpath`.`id` INNER JOIN `" . DB_PREFIX . DATABASE_VISITOR_DATA_TITLES . "` AS `treftitle` ON `tref`.`title`=`treftitle`.`id`;"))
                    while($row = DBManager::FetchArray($result))
                        if(isset($tbrowsers[$row["browser_id"]]))
                            $tbrowsers[$row["browser_id"]]->History[] = new HistoryURL($row);
            }
            else
            {
                if($result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_BROWSER_URLS . "` WHERE `entrance` >= " . intval($minEntrance) . " ORDER BY `entrance` ASC;"))
                    while($row = DBManager::FetchArray($result))
                        if(isset($tbrowsers[$row["browser_id"]]))
                            $tbrowsers[$row["browser_id"]]->History[] = new HistoryURL($row,true);
            }

            foreach($tbrowsers as $i => $browser)
            {
                $tbrowsers[$i]->SetFirstCall(count($browser->History)==0);
                if(isset($tvisitors[$browser->UserId]))
                {
                    if($_fullList && count($browser->History)==0)
                        continue;

                    $tvisitors[$browser->UserId]->Browsers[count($tvisitors[$browser->UserId]->Browsers)] = $browser;
                }
            }

            foreach($tvisitors as $vid => $visitor)
                if(count($visitor->Browsers)>0)
                    Server::$Visitors[$vid] = $visitor;

            return count($tvisitors);
        }
        return 0;
    }

    static function GetNoName($_basename)
    {
        $mod = 111;
        for ($i = 0; $i < strlen($_basename); $i++)
        {
            $digit = substr($_basename,$i,1);

            if(is_numeric($digit))
            {
                $mod = ($mod + ($mod * (16 + $digit)) % 1000);
                if ($mod % 10 == 0)
                    $mod += 1;
            }
        }
        return substr($mod,strlen($mod)-4,4);
    }

    static function CloseAllOverlays($_visitorId)
    {
    }
}

class VisitorBrowser
{
    public $UserId;
    public $SystemId;
    public $DesiredChatGroup;
    public $DesiredChatPartner;
	public $BrowserId;
	public $History;
	public $ChatRequests = array();
	public $OverlayBox;
	public $Alert;
	public $Type = BROWSER_TYPE_BROWSER;
	public $Query;
    public $Code = "";
	public $VisitId;
    public $FirstActive;
    public $LastActive;
	public $Overlay;
	public $OverlayContainer;
    public $UpdateParams = array();
    public $Created;
    public $Closed;

    private $FirstCall = true;

    function __construct($_browserid,$_userid,$_history=true)
    {
        $this->BrowserId = $_browserid;
        $this->UserId = $_userid;
        $this->SystemId = $this->UserId . "~" . $this->BrowserId;

        if($_history)
        {
            $this->FirstCall = $this->LoadHistory();
        }
    }

	function GetFirstCall()
	{
		return $this->FirstCall;
	}

    function SetFirstCall($_value)
    {
        $this->FirstCall = $_value;
    }

	function LoadHistory()
	{
        $firstCall = true;
        $this->History = array();
        if($result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_BROWSER_URLS . "` WHERE `" . DB_PREFIX . DATABASE_VISITOR_BROWSER_URLS . "`.`browser_id`='" . DBManager::RealEscape($this->BrowserId) . "' ORDER BY `" . DB_PREFIX . DATABASE_VISITOR_BROWSER_URLS . "`.`entrance` ASC;"))
            while($row = DBManager::FetchArray($result))
            {
                $hu = new HistoryURL($row, true);
    			$this->History[] = $hu;
                /*
                if($row["closed"]==0)*/
                    $firstCall = false;

            }
        return $firstCall;
	}

	function SetQuery($_referrer,$issearchengine=false,$parammatch=false,$encoding="")
	{
		$parts = parse_url(strtolower($_referrer));
		$uparts = explode("&", $parts["query"] ?? "");
		foreach(HistoryUrl::$SearchEngines as $sparam => $engines)
			foreach($uparts as $param)
			{
				$kv = explode("=",$param);
				$parammatch = ($kv[0] == $sparam && !empty($kv[1]));
				
				foreach($engines as $engine)
				{
					if(isset($parts["host"]) && Is::WildcardMatch($engine,$parts["host"]))
						$issearchengine = true;

					if($issearchengine && $parammatch)
					{
						if(empty($encoding))
							foreach(HistoryUrl::$SearchEngineEncodings as $enc => $eengines)
								foreach($eengines as $eengine)
									if($eengine==$engine)
										$encoding = $enc;
						$this->Query = (empty($encoding)) ? urldecode(trim($kv[1])) : html_entity_decode(@iconv($encoding,"UTF-8",urldecode(trim($kv[1]))), ENT_QUOTES, 'UTF-8');
						
						if(!empty($this->Query) && Is::Null(Cookie::Get("sp")))
							Cookie::Set("sp",$this->Query);

                        $this->UpdateParams["query"] = $this->GetQueryId($this->Query,$_referrer);

						DBManager::Execute(true, $d = "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_BROWSERS . "` SET `query`='" . DBManager::RealEscape($this->UpdateParams["query"]) . "' WHERE `id`='" . DBManager::RealEscape($this->BrowserId) . "' LIMIT 1;");
                        return true;
					}
				}
			}
		return $issearchengine;
	}
	
	function GetQueryId($_query,$_referrer,$_maxlength=255,$_self=false)
	{
		if(empty($_query))
			$_query = "";
	
		if(!$_self && $_maxlength != null && strlen($_query) > $_maxlength)
			$_query = substr($_query,0,$_maxlength);
		
		$result = DBManager::Execute(false, "INSERT INTO `" . DB_PREFIX . DATABASE_VISITOR_DATA_QUERIES . "` (`id`, `query`) VALUES (NULL, '" . DBManager::RealEscape($_query) . "');");
		if(!$_self && !empty($_query) && !$result && !Is::Null(DBManager::GetErrorCode()) && DBManager::GetErrorCode() != 1062)
			$this->GetQueryId(utf8_encode(urldecode($_query)),$_referrer,$_maxlength,true);

		$row = DBManager::FetchArray(DBManager::Execute(true, "SELECT `id` FROM `" . DB_PREFIX . DATABASE_VISITOR_DATA_QUERIES . "` WHERE `query`='" . DBManager::RealEscape($_query) . "';"));
		return $row["id"];
	}

    function Load()
    {

        $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_BROWSERS . "` WHERE `id`='" . DBManager::RealEscape($this->BrowserId) . "';");
        if(DBManager::GetRowCount($result) >= 1)
            $this->SetValues(DBManager::FetchArray($result),true);
    }

	function Save()
	{
        if($this->Type==1 || strpos($this->BrowserId,'_OVL') !== false)
            return false;

		if($this->FirstCall && $res = DBManager::Execute(true, "INSERT IGNORE INTO `" . DB_PREFIX . DATABASE_VISITOR_BROWSERS . "` (`id`, `visitor_id`, `visit_id`, `created`, `last_active`, `is_chat`,`pre_message`,`overlay`,`overlay_container`) VALUES ('" . DBManager::RealEscape($this->BrowserId) . "','" . DBManager::RealEscape($this->UserId) . "','" . DBManager::RealEscape($this->VisitId) . "','" . DBManager::RealEscape(time()) . "','" . DBManager::RealEscape(time()) . "','" . DBManager::RealEscape($this->Type) . "',''," . ($this->Overlay ? 1 : 0) . "," . ($this->OverlayContainer ? 1 : 0) . ");"))
        {
            $rowsInserted = DBManager::GetAffectedRowCount();
            if($rowsInserted == 0)
            {
                //Logging::ErrorLog("Can't save browser: " . $this->BrowserId . " / " . $this->UserId . " / " . @$_GET["ue"]);
                return false;
            }
        }
        else if(!$this->FirstCall)
        {
            $fields = "";
            DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_BROWSERS . "` SET `last_active`=" . time() . $fields . " WHERE `closed`=0 AND `id`='" . DBManager::RealEscape($this->BrowserId) . "' AND `visitor_id`='" . DBManager::RealEscape($this->UserId) . "' LIMIT 1;");
            if(DBManager::GetAffectedRowCount()==0)
            {
                $this->Load();
                if($this->Closed>0)
                {
                    //Logging::ErrorLog("Browser is closed 27: " . $this->BrowserId . " / " . $this->UserId . " / " . @$_GET["ue"]);
                    return false;
                }
            }
        }
        return true;
    }
	
	function Destroy()
	{
        DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_BROWSERS . "` SET `last_active`=`last_active`-" . DBManager::RealEscape(Server::$Configuration->File["timeout_track"]) . " WHERE `id`='" . DBManager::RealEscape($this->BrowserId) . "' LIMIT 1;");
	}

    function Close()
    {
        DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_BROWSERS . "` SET `closed`=" . time() . " WHERE `id`='" . DBManager::RealEscape($this->BrowserId) . "' LIMIT 1;");
    }
	
    function IsActivity()
    {
        if(count($this->History)==0)
            $this->LoadHistory();
        if(count($this->History) > 0 && $this->History[count($this->History)-1]->Entrance >= (time()-((Server::$Configuration->File["gl_inti"]*60))))
            return true;
        return false;
    }

    function TextReplace($_text)
    {
        $_text = str_replace("%searchstring%",$this->Query,$_text);
        if(count($this->History) > 0)
        {
            $_text = str_replace("%page_title%",$this->History[count($this->History)-1]->Url->PageTitle,$_text);
            $_text = str_replace("%url%",$this->History[count($this->History)-1]->Url->GetAbsoluteUrl(),$_text);
        }
        return $_text;
    }

    function SetValues($_row)
    {
        $this->BrowserId = $_row["id"];
        $this->VisitId = $_row["visit_id"];
        $this->Created = $_row["created"];
        $this->LastActive = $_row["last_active"];
        $this->Closed = $_row["closed"];
    }

    function GetXMLV2($_visitorId)
    {
        return "<b v=\"".base64_encode($_visitorId)."\" vi=\"".base64_encode($this->VisitId)."\" id=\"".base64_encode($this->BrowserId)."\" c=\"".base64_encode($this->Closed)."\" e=\"".base64_encode($this->Created)."\" l=\"".base64_encode($this->LastActive)."\" />";
    }

    function ReplaceLoginDetails($_user,$_reset=false,$_fileUploads=false,$values="",$keys="",$comma="",$files="")
    {
        Server::InitDataBlock(array("INPUTS"));
        foreach(Server::$Inputs as $index => $input)
        {
            $data = $input->GetValue($_user);
            $data = (!empty($data)) ? $data : $input->GetServerInput();

            //if($index == 114)
              //  $data = "";

            $values .= $comma . $input->GetJavascript($data);
            $keys .= $comma . "'".$index."'";

            if($_fileUploads)
            {
                $iffiles = "";
                $ifcomma = "";
                if($input->Type == "File" && $input->Active)
                {
                    $tfiles = KnowledgeBase::GetTemporaryTicketFiles($_user->UserId,$index);
                    foreach($tfiles as $tfile)
                    {
                        $iffiles .= $ifcomma . "['" . md5($tfile["id"]) . "','" . base64_encode($tfile["title"]) . "']";
                        $ifcomma = ",";
                    }
                }
                $files .= $comma . "[".$iffiles."]";
            }
            $comma = ",";
        }

        if($_reset)
            $_user->AddFunctionCall("LiveZillaData.InputFieldValues=null;LiveZillaData.InputFieldIndices=null;",false);

        $_user->AddFunctionCall("if(LiveZillaData.InputFieldIndices==null)LiveZillaData.InputFieldIndices = [".$keys."];",false);
        $_user->AddFunctionCall("if(LiveZillaData.InputFieldValues==null)LiveZillaData.InputFieldValues = [".$values."];",false);

        if($_fileUploads && !empty($files))
            $_user->AddFunctionCall("LiveZillaData.InputFieldFiles = [".$files."];",false);

        return $_user;
    }

    static function FromCache($_uid,$_bid)
    {
        $browser = new VisitorBrowser($_bid,$_uid,true);
        return $browser;
    }
}


class VisitorChat extends VisitorBrowser
{
	public $Waiting;
	public $Chat;
	public $Type = BROWSER_TYPE_CHAT;
	public $ConnectingMessageDisplayed = null;
	public $Members;
	public $TranscriptEmail;
	public $ChatId;
	public $ResponseTime;
	public $ArchiveCreated = 0;
	public $Activated;
	public $Closed;
    public $GroupId;
	public $Declined = 0;
	public $InternalActivation;
	public $ExternalClosed;
	public $InternalClosed;
	public $OperatorId;
	public $LastActive = 0;
	public $Priority = 2;
	public $AllocatedTime = 0;
	public $ChatTicketId = "";
	public $CallMeBack = false;
	public $InitChatWith;
    public $TranslationSettings;
    public $FirstCall = true;
    public $Typing;
    public $Status;
    public $HistoryCounts;
    public $GroupChat;
    public $Subject;
    public $Exit;
    public $Company = "";
    public $Phone = "";

    public static $DynamicGroup;
    public static $Router;

	function __construct()
   	{
        $loadParentHistory=true;
		if(func_num_args() == 2)
		{
			$this->UserId = func_get_arg(0);
			$this->BrowserId = func_get_arg(1);
			$this->FirstCall = true;
		}
        else if(func_num_args() == 3)
        {
            $this->UserId = func_get_arg(0);
            $this->BrowserId = func_get_arg(1);
            $this->FirstCall = false;
            $loadParentHistory = false;
        }
		else if(func_num_args() == 4)
		{
			$this->UserId = func_get_arg(0);
			$this->BrowserId = func_get_arg(1);
			$this->DesiredChatGroup = func_get_arg(2);
			$this->SetDesiredChatPartner(func_get_arg(3));
			$this->FirstCall = true;
		}
		else if(func_num_args() == 1)
		{
			$this->SetValues(func_get_arg(0));
		}
		parent::__construct($this->BrowserId,$this->UserId,$loadParentHistory);
   	}

    function GetChatXML($_visitorId)
    {
        $atts = array();
        $atts["v"] = $_visitorId;
        $atts["a"] = $this->AllocatedTime;
        $atts["i"] = $this->ChatId;
		$atts["f"] = $this->FirstActive;
        $atts["l"] = $this->LastActive;
        $atts["p"] = $this->Priority;
        $atts["s"] = $this->Subject;
        $atts["b"] = $this->BrowserId;
        $atts["t"] = $this->Typing;
        $atts["e"] = $this->Exit;
        $atts["c"] = $this->Closed;
        $atts["w"] = $this->Waiting;
        $atts["tid"] = $this->ChatTicketId;
		$atts["dcg"] = $this->DesiredChatGroup;
		$atts["dcp"] = $this->DesiredChatPartner;
        $atts["cmb"] = $this->CallMeBack;

        $atts["ai"] = $this->InternalActivation;
        $atts["ae"] = true;

        $atts["ce"] = $this->ExternalClosed;
        $atts["ci"] = $this->InternalClosed;
        $atts["di"] = $this->Declined;

        $innerXML = "";

        $this->LoadMembers();
        if(is_array($this->Members))
            foreach($this->Members as $member)
                $innerXML .= $member->GetXML();

        return To::XMLTag("c",$innerXML,$atts);
    }
	
    function SetTranslation($_value)
    {
        DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `translation`='" . DBManager::RealEscape($_value) . "' WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' LIMIT 1;");
    }
	
	function SetCookieGroup()
	{
		if(!empty($this->DesiredChatGroup))
			Cookie::Set("login_group",$this->DesiredChatGroup);
	}

	function SetCallMeBackStatus($_cmb)
	{
		DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_CHAT_ARCHIVE . "` SET `call_me_back`=" . intval($_cmb) . " WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' LIMIT 1;");
		DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `call_me_back`=" . intval($_cmb) . " WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' LIMIT 1;");
	}
	
	function Load()
	{
		$this->Status = CHAT_STATUS_OPEN;
    	$this->LastActive = time();
		$this->Members = array();

        $historyEntry = false;

        $count = 0;
		$result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` WHERE `visitor_id`='" . DBManager::RealEscape($this->UserId) . "' AND `browser_id`='" . DBManager::RealEscape($this->BrowserId) . "' ORDER BY `first_active` DESC LIMIT 2;");
		if($result)
			while($row = DBManager::FetchArray($result))
			{
				if(empty($row["external_close"]))
				{
					$this->FirstCall = !empty($row["exit"]);
					$this->SetValues($row);
				}

                if($historyEntry && empty(Server::$Configuration->File["gl_save_op"]))
                    continue;

				if(!empty($row["request_operator"]) && empty($this->DesiredChatPartner))
					$this->SetDesiredChatPartner($row["request_operator"]);

				if(!empty($row["request_group"]) && empty($this->DesiredChatGroup))
					$this->DesiredChatGroup = $row["request_group"];

                $historyEntry = true;

                $count++;
			}
		$this->LoadMembers();
	}
	
	function LoadMembers()
	{
        if(empty($this->ChatId))
            return;

		Server::InitDataBlock(array("INTERNAL"));
		$result = DBManager::Execute(true,"SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` WHERE `status`<9 AND `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' ORDER BY `status` DESC, `dtime` DESC;");

        while($row = DBManager::FetchArray($result))
			if(isset(Server::$Operators[$row["user_id"]]))
			{
				$this->Members[$row["user_id"]] = new ChatMember($row["user_id"],$row["status"],!empty($row["declined"]),$row["jtime"],$row["ltime"]);
                if($row["status"] < 2)
					$this->OperatorId = $row["user_id"];
				$this->Declined = $row["dtime"];
			}
	}

    function GetHostOperator()
    {
        $fallback = "";
        if(empty($this->Members))
            $this->LoadMembers();
        if(is_array($this->Members))
        {
            foreach($this->Members as $member)
                if($member->Status == 0)
                    return $member->SystemId;
                else if($member->Status == 1 && empty($fallback))
                    $fallback = $member->SystemId;
        }
        return $fallback;
    }

    function GetTotalWaitingTime(&$_startMarker,&$_endMarker)
    {
        if(!empty($this->Declined))
        {
            $_startMarker = 2;
            $waitingTime = $this->Declined-$this->FirstActive;
        }
        else if($this->InternalActivation)
        {
            $_startMarker = 1;
            $waitingTime = $this->AllocatedTime-$this->FirstActive;
        }
        else
        {
            $_startMarker = 0;
            $waitingTime = $this->LastActive-$this->FirstActive;
        }

        if(($this->InternalActivation && $this->InternalClosed) || $this->Declined)
            $_endMarker = 1;

        $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` WHERE `visitor_id`='" . DBManager::RealEscape($this->UserId) . "' AND `browser_id`='" . DBManager::RealEscape($this->BrowserId) . "' AND `visit_id`='" . DBManager::RealEscape($this->VisitId) . "' AND `archive_created`=2 AND `first_active`<" . intval($this->FirstActive) . ";");
        while($row = DBManager::FetchArray($result))
            $waitingTime += $row["last_active"]-$row["first_active"];
        return max($waitingTime,0);
    }

    function GetTotalDuration()
    {
        return ($this->AllocatedTime>0 && $this->LastActive>$this->AllocatedTime) ? $this->LastActive-$this->AllocatedTime : max($this->LastActive-$this->FirstActive,10);
    }
	
	function SetValues($row)
	{
        $this->LastActive = $row["last_active"];
        $this->AllocatedTime = $row["allocated"];
        $this->Waiting = $row["waiting"];
        $this->FirstActive = $row["first_active"];
        $this->Subject = @$row["subject"];

        if(empty(Server::$Configuration->File["gl_cmsp"]))
            $this->Typing = (!empty($row["typing"]) && $row["last_active"] > (time()-30));
        else
            $this->Typing = $row["typing"];

        $this->ChatId = $row["chat_id"];
        $this->VisitId = $row["visit_id"];
        $this->SetDesiredChatPartner($row["request_operator"]);
        $this->DesiredChatGroup =
        $this->GroupId = $row["request_group"];
        $this->Priority = $row["priority"];
        $this->ChatTicketId = $row["chat_ticket_id"];
        $this->ArchiveCreated = $row["archive_created"];
        $this->InternalActivation = !empty($row["internal_active"]);
        $this->Declined = !empty($row["internal_declined"]);
        $this->Closed = !empty($row["exit"]);
        $this->Exit = $row["exit"];
        $this->CallMeBack = $row["call_me_back"];
        $this->ExternalClosed = !empty($row["external_close"]);
        $this->FirstActive = $row["first_active"];
        $this->InternalClosed = !empty($row["internal_closed"]);
        $this->LastActive = $row["last_active"];
        $this->InitChatWith = $row["init_chat_with"];
        $this->UserId = $row["visitor_id"];
        $this->BrowserId = $row["browser_id"];
        $this->Status = $row["status"];
        $this->HistoryCounts = $row["history"];
        $this->GroupChat = $row["dgc"];
        $this->Activated = (($this->InternalActivation) ? CHAT_STATUS_ACTIVE : CHAT_STATUS_OPEN);
        $this->TranslationSettings = (!empty($row["translation"])) ? explode(",",$row["translation"]) : null;
    }
	
	function SetChatId()
	{
		if(isset($_POST[POST_EXTERN_CHAT_ID]) && $this->Status != CHAT_STATUS_OPEN)
		{
			$this->ChatId = Encoding::Base64UrlDecode($_POST[POST_EXTERN_CHAT_ID]);
		}
		else
		{
			$result = DBManager::Execute(true, "SELECT `chat_id` FROM `" . DB_PREFIX . DATABASE_INFO . "`");
			$row = DBManager::FetchArray($result);
			$cid = $row["chat_id"]+1;
			DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_INFO . "` SET `chat_id`='" . DBManager::RealEscape($cid) . "' WHERE `chat_id`='" . DBManager::RealEscape($row["chat_id"]) . "'");
			if(DBManager::GetAffectedRowCount() == 0)
			{
				$this->ChatId = $this->SetChatId();
				return $this->ChatId;
			}
			else
			{
				$this->ChatId = $cid;
			}
		}

		$this->FirstActive = time();

		DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `last_active`='" . DBManager::RealEscape(time()) . "',`first_active`='" . DBManager::RealEscape(time()) . "',`chat_id`='" . DBManager::RealEscape($this->ChatId) . "' WHERE `exit`=0 AND `visitor_id`='" . DBManager::RealEscape($this->UserId) . "' AND `browser_id`='" . DBManager::RealEscape($this->BrowserId) . "' ORDER BY `first_active` DESC LIMIT 1;");
        if(DBManager::GetAffectedRowCount() == 0)
        {
            return false;
        }
        else
        {
            $this->SetTargetGroup($this->DesiredChatGroup);
        }
        return $this->ChatId;
	}
	
	function SetStatus($_status)
	{
		DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `last_active`='" . DBManager::RealEscape(time()) . "',`status`='" . DBManager::RealEscape($_status) . "' WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "';");
	}

    function SetPublicGroup($_groupId)
    {
        DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `dgc`='" . DBManager::RealEscape($_groupId) . "' WHERE `visitor_id`='" . DBManager::RealEscape($this->UserId) . "' AND `exit`=0 AND `browser_id`='" . DBManager::RealEscape($this->BrowserId) . "';");
    }
	
	function SetWaiting($_waiting)
	{
		$this->Waiting=$_waiting;
		DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `waiting`='" . DBManager::RealEscape((($_waiting) ? 1 : 0)) . "' WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "';");
	}
	
	function UpdateArchive($_tcemail)
	{
		DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_CHAT_ARCHIVE . "` SET `transcript_receiver`='" . DBManager::RealEscape($_tcemail) . "' WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "';");
	}

	function JoinChat($_internalUser,$_invisible=false,$_rePost=false)
	{
        if(!empty($this->ChatId))
        {
            DBManager::Execute(false, "INSERT IGNORE INTO `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` (`chat_id`,`user_id`,`jtime`,`status`,`alloc`) VALUES ('" . DBManager::RealEscape($this->ChatId) . "','" . DBManager::RealEscape($_internalUser) . "'," . (($_invisible) ? 0 : time()) . "," . (($_invisible) ? 2 : 1) . ",0);");
            if(DBManager::GetAffectedRowCount() != 1)
            {
                $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' AND `user_id`='" . DBManager::RealEscape($_internalUser) . "' LIMIT 1;");
                if($row = DBManager::FetchArray($result))
                {
                    $jtime = ($_invisible && ($row["status"] == 1 || $row["status"] == 0)) ? "`jtime`" : (($_invisible) ? 0 : time());
                    DBManager::Execute(false, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` SET `ltime`=" . (($_invisible) ? time() : 0) . ",`jtime`=" . $jtime . ",`dtime`=0,`declined`=0,`status`=" . (($_invisible) ? 2 : 1) . " WHERE `chat_id` = '" . DBManager::RealEscape($this->ChatId) . "' AND `user_id`='" . DBManager::RealEscape($_internalUser) . "' LIMIT 1;");
                }
            }
            if($_rePost)
            {
                $this->RepostChatHistory(1,$this->ChatId,$_internalUser,0,0,"",$this->ChatId);
                return;
            }
        }
	}
	
	function LeaveChat($_internalUser)
	{
		if(count($this->Members)>=2 && !empty($this->Members[$_internalUser]) && $this->Members[$_internalUser]->Status == 0)
			foreach($this->Members as $sysid => $member)
				if($member->Status == 1)
				{
					$this->SetHost($sysid);
					break;
				}

		DBManager::Execute(false, "DELETE FROM `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` WHERE `chat_id` = '" . DBManager::RealEscape($this->ChatId) . "' AND `user_id`='" . DBManager::RealEscape($_internalUser) . "' AND `status`=2 LIMIT 1;");
        if(!empty($this->ChatId))
            DBManager::Execute(false, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` SET `status`=9,`ltime`=" . time() . " WHERE `chat_id` = '" . DBManager::RealEscape($this->ChatId) . "' AND `user_id`='" . DBManager::RealEscape($_internalUser) . "' LIMIT 1;");
	}

    function ValidateOperator()
    {
        if(!empty($this->GroupChat) || !empty(VisitorChat::$DynamicGroup) || empty($this->OperatorId))
            return;

        if(count($this->Members) == 1 && Server::$Operators[$this->OperatorId]->Status == USER_STATUS_OFFLINE)
        {
            $this->CloseChat(4);
        }
        else
        {
            foreach($this->Members as $sid => $member)
                if(Server::$Operators[$sid]->Status == USER_STATUS_OFFLINE)
                    $this->LeaveChat($sid);

            if($this->OperatorId != $this->DesiredChatPartner)
                $this->SetDesiredChatPartner($this->OperatorId);
        }
    }
	
	function SetHost($_internalUser)
	{
        if(!empty($this->ChatId))
        {
		    DBManager::Execute(false, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` SET `status`=0 WHERE `chat_id` = '" . DBManager::RealEscape($this->ChatId) . "' AND `user_id`='" . DBManager::RealEscape($_internalUser) . "' LIMIT 1;");
		    if(DBManager::GetAffectedRowCount() != 1)
			    DBManager::Execute(false, "INSERT IGNORE INTO `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` (`chat_id`,`user_id`,`status`) VALUES ('" . DBManager::RealEscape($this->ChatId) . "','" . DBManager::RealEscape($_internalUser) . "',0);");
            DBManager::Execute(false, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` SET `status`=1 WHERE `status`=0 AND `chat_id` = '" . DBManager::RealEscape($this->ChatId) . "' AND `user_id`!='" . DBManager::RealEscape($_internalUser) . "';");
	    }
    }
	
	function SetPriority($_priority)
	{
		DBManager::Execute(false, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `priority`='" . DBManager::RealEscape($_priority) . "' WHERE `chat_id` = '" . DBManager::RealEscape($this->ChatId) . "' LIMIT 1;");
	}

    function SetDesiredChatPartner($_systemId)
    {
        $this->DesiredChatPartner = $_systemId;
    }
	
	function SetTargetOperator($_internalUser)
	{
        if(!empty($this->ChatId))
        {
		    DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `request_operator`='" . DBManager::RealEscape($_internalUser) . "' WHERE `chat_id`=" . DBManager::RealEscape($this->ChatId) . " LIMIT 1;");
	    }
    }
	
	function RequestInitChat($_internalUser)
	{
		DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `init_chat_with`='" . DBManager::RealEscape($_internalUser) . "' WHERE `browser_id` = '" . DBManager::RealEscape($this->BrowserId) . "' AND `visitor_id` = '" . DBManager::RealEscape($this->UserId) . "';");
	}
	
	function SetTargetGroup($_groupId)
	{
        $this->GroupId = $_groupId;
		DBManager::Execute(false, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `request_group`='" . DBManager::RealEscape($_groupId) . "' WHERE `chat_id` = '" . DBManager::RealEscape($this->ChatId) . "' LIMIT 1;");
	}
	
	function TakeChat($_internalUser,$_groupId,$_activate)
	{
		$this->SetHost($_internalUser);
		$_groupId = (!empty($_groupId)) ? ",`request_group`='".DBManager::RealEscape($_groupId)."'" : "";
		DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` SET `status`=0,`declined`=0,`dtime`=0,`ltime`=0,`jtime`=" . time() . " WHERE `chat_id` = '" . DBManager::RealEscape($this->ChatId) . "' AND `user_id`='" . DBManager::RealEscape($_internalUser) . "' LIMIT 1;");
        DBManager::Execute(true, "DELETE FROM `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` WHERE `chat_id` = '" . DBManager::RealEscape($this->ChatId) . "' AND `user_id`!='" . DBManager::RealEscape($_internalUser) . "';");
        DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `internal_active`=".($_activate==1 ? 1 : 0).",`status`=1,`waiting`=0,`request_operator`='" . DBManager::RealEscape($_internalUser) . "'" . $_groupId . " WHERE `chat_id` = '" . DBManager::RealEscape($this->ChatId) . "' LIMIT 1;");
    }
	
    function RemoteActivate($_internalUser)
    {
        if(!Is::Defined("CALLER_SYSTEM_ID"))
        {
            define("CALLER_SYSTEM_ID",$_internalUser->SystemId);
            $this->InternalActivate(null);

            if($_internalUser->IsBot)
                $this->ExternalActivate();
            $this->SetStatus(CHAT_STATUS_ACTIVE);
        }
    }

    function CreateSPAMFilter()
    {
        if(!empty(Server::$Configuration->File["gl_sfc"]) && Visitor::CreateSPAMFilter($this->UserId))
            return true;
        return false;
    }
	
	function CreateChat($_internalUser, $_visitor, $_host=false, $custom="", $etpl="", $_customsInTranscript=true, $_externalSelf=true, $pdm=null)
	{
        if(empty($this->ChatId))
            return false;

        if(!empty($_internalUser))
        {
		    $this->OperatorId = $_internalUser->SystemId;
            $_internalUser->SetLastChatAllocation();
        }
        else
            $this->OperatorId = "";

		$this->SetStatus(CHAT_STATUS_WAITING);
		Server::InitDataBlock(array("INPUTS"));
	    DBManager::Execute(true,"INSERT IGNORE INTO `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` (`chat_id`,`user_id`,`jtime`,`status`) VALUES ('" . DBManager::RealEscape($this->ChatId) . "','" . DBManager::RealEscape($this->OperatorId) . "'," . time() . "," . (($_host) ? 0 : 1) . ");");

        $this->CreateArchiveEntry($_internalUser, $_visitor, $custom, $etpl, $_customsInTranscript, $pdm);

		if($_internalUser->IsBot)
		{
			$this->RemoteActivate($_internalUser);
		}
		else if(!empty($_internalUser->AppDeviceId) && $_internalUser->AppBackgroundMode)
        {
            $name = (!empty($_visitor->VisitorData->Fullname)) ? $_visitor->VisitorData->Fullname : Visitor::GetNoName($this->UserId.Communication::GetIP());
            $_internalUser->AddPushMessage($this->ChatId, $this->SystemId, $name, 0);
        }
        return true;
	}

    function CreateArchiveEntry($_internalUser, $_visitor, $custom="", $etpl="", $_customsInTranscript=true, $pdm=null)
    {
        if(!empty($this->ArchiveCreated))
            return;

        if($_visitor != null)
            $_visitor->Load();

        if(Server::$Inputs[111]->Active)
            $custom .= strip_tags(Server::$Inputs[111]->Caption) . " %efullname%\r\n";
        if(Server::$Inputs[112]->Active)
            $custom .= strip_tags(Server::$Inputs[112]->Caption) . " %eemail%\r\n";
        if(!empty($this->Company) && Server::$Inputs[113]->Active)
            $custom .= strip_tags(Server::$Inputs[113]->Caption) . " " . trim($_visitor->VisitorData->Company) . "\r\n";
        if(!empty($this->Question) && Server::$Inputs[114]->Active)
            $custom .= strip_tags(Server::$Inputs[114]->Caption) . " " . trim($_visitor->VisitorData->Text) . "\r\n";
        if(!empty($this->Phone) && Server::$Inputs[116]->Active)
            $custom .= strip_tags(Server::$Inputs[116]->Caption) . " " . trim($_visitor->VisitorData->Phone) . "\r\n";

        $customs = array();

        if($_visitor != null)
        {
            $customs = $_visitor->VisitorData->FormatChatArchiveArray();

            if(is_array($_visitor->VisitorData->Customs))
                foreach($_visitor->VisitorData->Customs as $cind => $value)
                    if(Server::$Inputs[$cind]->Active && Server::$Inputs[$cind]->Custom)
                    {
                        if(!isset(Server::$Groups[$this->DesiredChatGroup]->ChatInputsHidden[$cind]) && $_customsInTranscript)
                        {
                            if(Server::$Inputs[$cind]->Type == "CheckBox")
                                $custom .= strip_tags(Server::$Inputs[$cind]->Caption). " " . ((!empty($value)) ? "<!--lang_client_yes-->" : "<!--lang_client_no-->") . "\r\n";
                            else if(Server::$Inputs[$cind]->Type == "ComboBox")
                                $custom .= strip_tags(Server::$Inputs[$cind]->Caption). " " . Server::$Inputs[$cind]->GetClientValue($value) . "\r\n";
                            else
                                $custom .= strip_tags(Server::$Inputs[$cind]->Caption). " " . $value . "\r\n";
                        }
                    }
        }

        $tsData = array("","");
        if(!empty(Server::$Groups[$this->DesiredChatGroup]->PredefinedMessages))
        {
            $pdm = PredefinedMessage::GetByLanguage(Server::$Groups[$this->DesiredChatGroup]->PredefinedMessages,$_visitor->Language);
            if(!empty($pdm->EmailChatTranscriptBodyPlaintext))
                $tsData[0] = $pdm->EmailChatTranscriptBodyPlaintext;
            if(!empty($pdm->EmailChatTranscriptBodyHTML))
                $tsData[1] = $pdm->EmailChatTranscriptBodyHTML;
        }

        for($i=0;$i<count($tsData);$i++)
        {
            $tsData[$i] = str_replace("%chat_id%",$this->ChatId,$tsData[$i]);
            $tsData[$i] = str_replace("%details%",$custom,$tsData[$i]);
            $tsData[$i] = str_replace("%external_name%","%efullname%",$tsData[$i]);
            $tsData[$i] = str_replace("%external_email%","%eemail%",$tsData[$i]);
            $tsData[$i] = str_replace("%external_company%",$_visitor->VisitorData->Company,$tsData[$i]);
            $tsData[$i] = str_replace("%external_phone%",$_visitor->VisitorData->Phone,$tsData[$i]);
            $tsData[$i] = str_replace("%question%",$_visitor->VisitorData->Text,$tsData[$i]);
            $tsData[$i] = str_replace("%feedback_link%",Feedback::GetLink("cid=" . Encoding::Base64UrlEncode($this->ChatId)),$tsData[$i]);

            if($_visitor != null)
                $tsData[$i] = $_visitor->TextReplace($tsData[$i]);
            $tsData[$i] = Configuration::Replace($tsData[$i]);
        }

        $code = "";

        if($_visitor != null && isset($_visitor->Browsers[1]) && isset($_visitor->Browsers[1]->History[0]))
            $code = $_visitor->Browsers[1]->History[0]->Url->AreaCode;

        $subject = ($pdm != null) ? $pdm->SubjectChatTranscript : "";

        if($_visitor != null)
            $subject = Mailbox::GetSubject($subject,$_visitor->VisitorData->Email,$_visitor->VisitorData->Fullname,$this->DesiredChatGroup,$this->ChatId,$_visitor->VisitorData->Company,$_visitor->VisitorData->Phone,Communication::GetIP(),$_visitor->VisitorData->Text,Server::$Groups[$this->DesiredChatGroup]->GetDescription($_visitor->Language),$_visitor->VisitorData->Customs);

        $internal = ($_internalUser != null && $_internalUser->IsBot) ? $_internalUser->SystemId : "";

        $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_CHAT_ARCHIVE . "` WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "';");
        if($result && DBManager::GetRowCount($result) == 0)
        {
            if(Server::$Configuration->File["gl_adct"] == 1 || (!empty(Server::$Configuration->File["gl_rm_chats_time"]) || empty(Server::$Configuration->File["gl_rm_chats"])))
            {
                $url = Visitor::GetLastURLFromVisitor($this->UserId,true);


                if(!(($result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_CHAT_ARCHIVE . "` WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "';")) && $row = DBManager::FetchArray($result)))
                    DBManager::Execute(true, "INSERT INTO `" . DB_PREFIX . DATABASE_CHAT_ARCHIVE . "` (`time`,`endtime`,`closed`,`chat_id`,`external_id`,`fullname`,`internal_id`,`group_id`,`area_code`,`html`,`plaintext`,`transcript_text`,`transcript_html`,`email`,`company`, `phone`, `call_me_back`, `iso_language`,`iso_country`,`host`,`ip`,`gzip`,`transcript_sent`,`transcript_receiver`,`question`,`customs`,`subject`,`ticket_id`,`wait`,`duration`,`accepted`,`ended`,`chat_type`,`ref_url`) VALUES ('" . DBManager::RealEscape($this->FirstActive) . "',0,0,'" . DBManager::RealEscape($this->ChatId) . "','" . DBManager::RealEscape($this->UserId) . "','','" . DBManager::RealEscape($internal) . "','','" . DBManager::RealEscape($code) . "','','','" . DBManager::RealEscape($tsData[0]) . "','" . DBManager::RealEscape($tsData[1]) . "','','','',0,'" . DBManager::RealEscape($_visitor->Language) . "','" . DBManager::RealEscape($_visitor->GeoCountryISO2) . "','" . DBManager::RealEscape(Str::Cut($_visitor->Host,63,false)) . "','" . DBManager::RealEscape($_visitor->IP) . "',0,0,'" . DBManager::RealEscape($_visitor->VisitorData->Email) . "','','" . DBManager::RealEscape(@serialize($customs)) . "','" . DBManager::RealEscape($subject) . "','" . DBManager::RealEscape($this->ChatTicketId) . "',0,0,0,0,1,'" . DBManager::RealEscape($url) . "');");
            }
            $this->ArchiveCreated = 1;
            ChatRequest::AcceptAll($this->UserId);
        }
    }

	function RepostChatHistory($_caller,$_chatId,$_internalSystemId,$_from=0,$_last=0,$_receiverGroup="",$_targetChatId="",$_targetReceiverGroup="",$_external=false,$_botonly=false,$_externalSelf=true)
	{

	}
	
	function PostsReceived($_sender)
	{
		$result = DBManager::Execute(true, "SELECT COUNT(*) as `pcount`,SUM(received) as `rcount` FROM `" . DB_PREFIX . DATABASE_POSTS . "` WHERE `sender`='" . DBManager::RealEscape($_sender) . "' AND `receiver`='" . DBManager::RealEscape($this->SystemId) . "' AND `repost`=0");
		if($result)
			while($row = DBManager::FetchArray($result))
				return $row["pcount"]+$row["rcount"];
		return true;
	}
	
	function GetLastInvitationSender()
	{
		$result = DBManager::Execute(true, "SELECT `sender_system_id` FROM `" . DB_PREFIX . DATABASE_CHAT_REQUESTS . "` WHERE `receiver_user_id`='" . DBManager::RealEscape($this->UserId) . "' ORDER BY `created` DESC LIMIT 1");
		if($result)
			while($row = DBManager::FetchArray($result))
				return $row["sender_system_id"];
		return null;
	}
	
	function CloseChat()
	{
		$this->ExternalClose();
		$this->Closed=true;
	}
	
	function CloseWindow()
	{
		$this->ExternalClose();
		$this->Destroy();
	}
	
	function Save()
	{
        if(empty($this->UserId) || empty($this->VisitId))
        {
            Logging::DebugLog("Invalid Visitor Data: " . $this->UserId . " / " . $this->VisitId);
            return;
        }

		$_new = (func_num_args() > 0) ? func_get_arg(0) : false;
		if($_new)
		{
			$this->FirstCall = true;
			$this->Status = CHAT_STATUS_OPEN;
		}

        if(empty($this->ChatId) && empty(Server::$Configuration->File["gl_save_op"]) && $this->BrowserId != "API")
            $this->SetDesiredChatPartner("");

		if(empty($this->FirstActive))
			$this->FirstActive = time();

		if($this->FirstCall)
        {
            $pdbc = 0;
            $pdbt = 0;
            $hc = $pdbc.";".$pdbt;
			DBManager::Execute(true, "INSERT IGNORE INTO `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` (`visitor_id`,`browser_id`,`visit_id`,`priority`,`call_me_back`, `typing` ,`first_active` ,`last_active` ,`request_operator` ,`request_group` , `chat_ticket_id`, `queue_posts`, `history`, `dgc`, `subject`) VALUES ('" . DBManager::RealEscape($this->UserId) . "','" . DBManager::RealEscape($this->BrowserId) . "','" . DBManager::RealEscape($this->VisitId) . "','" . DBManager::RealEscape($this->Priority) . "'," . DBManager::RealEscape(($this->CallMeBack) ? 1 : 0) . ",'','" . DBManager::RealEscape($this->FirstActive) . "','" . DBManager::RealEscape($this->LastActive) . "','" . DBManager::RealEscape($this->DesiredChatPartner) . "','" . DBManager::RealEscape($this->DesiredChatGroup) . "','" . DBManager::RealEscape($this->ChatTicketId) . "','','" . DBManager::RealEscape($hc) . "','" . DBManager::RealEscape(VisitorChat::$DynamicGroup) . "','" . DBManager::RealEscape($this->Subject) . "');");
        }
        else
        {
			DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `typing`='" . DBManager::RealEscape(Str::Cut($this->Typing,200,true)) . "',`archive_created`=" . intval($this->ArchiveCreated) . ",`request_operator`='" . DBManager::RealEscape($this->DesiredChatPartner) . "',`chat_ticket_id`='" . DBManager::RealEscape($this->ChatTicketId) . "',`subject`='" . DBManager::RealEscape($this->Subject) . "',`last_active`='" . DBManager::RealEscape(time()) . "' WHERE `browser_id`='" . DBManager::RealEscape($this->BrowserId) . "' AND `visitor_id`='" . DBManager::RealEscape($this->UserId) . "' AND `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' LIMIT 1;");
        }
        parent::Save();
	}

	function Destroy()
	{
		parent::Destroy();
	}
	
	function InternalDecline($_internal,$remopcount=0)
	{
		if(!isset($this->Members[$_internal]))
			return;
			
		foreach($this->Members as $member)
			if(empty($member->Left))
				$remopcount++;

        if($this->Activated)
            return;

		DBManager::Execute(false, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` SET `declined`=1,`dtime`=" . time() . ",`ltime`=" . time() . " WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' AND `user_id`='" . DBManager::RealEscape($_internal) . "' LIMIT 1;");
		if($remopcount==1 || !isset($this->Members[$_internal]))
			$this->UpdateUserStatus(false, false, true, false);
		else if(count($this->Members)>1 && isset($this->Members[$_internal]) && $this->Members[$_internal]->Status==0)
			foreach($this->Members as $sysid => $member)
				if($_internal != $sysid)
				{
					$this->SetHost($sysid);
					break;
				}
	}
	
	function InternalClose()
	{
		$this->UpdateUserStatus(false, true, false, false);
	}
	
	function InternalActivate($_owner)
	{
        $activate = false;
        if($_owner != null)
        {
            $this->LoadMembers();
            if(isset($this->Members[$_owner]))
                $activate = true;
        }
        else
            $activate = true;

        if($activate)
        {
            DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_CHAT_ARCHIVE . "` SET `time`='" . DBManager::RealEscape(time()) . "' WHERE `closed`=0 AND `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' LIMIT 1;");
            $this->UpdateUserStatus(true, false, false, false);
        }
	}
	
	function ExternalActivate()
	{
		$this->UpdateUserStatus(false, false, false, false);
	}
		
	function ExternalClose()
	{
		$this->UpdateUserStatus(false, false, false, true);
	}

    function SetExternalClosed()
    {
        DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `external_close`='1' WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' LIMIT 1;");
    }
	
	function UpdateUserStatus($_internalActivated, $_internalClosed, $_internalDeclined=false, $_externalClose=false)
	{
		if(!empty($this->ChatId))
		{
			$this->Status = ($_externalClose || $_internalDeclined || $_internalClosed) ? CHAT_CLOSED : $this->Status;
			if($_internalActivated)
			{
				DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET `internal_active`='1',`allocated`='" . DBManager::RealEscape(time()) . "' WHERE `internal_active`=0 AND `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' LIMIT 1;");
				if(DBManager::GetAffectedRowCount() == 1)
				{
					DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` SET `status`=0 WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' AND `user_id`='" . DBManager::RealEscape(CALLER_SYSTEM_ID) . "';");
					DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHAT_OPERATORS . "` SET `status`=9,`ltime`=" . time() . ",`jtime`=0 WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' AND `user_id`!='" . DBManager::RealEscape(CALLER_SYSTEM_ID) . "' AND `status`<=1;");
				}
			}
			else
			{
                $where = "`chat_id`='" . DBManager::RealEscape($this->ChatId) . "'";

				if($_externalClose && empty($this->InternalClosed))
					$update = "`external_close`=1,`status`=2,`waiting`=0,`exit`=".intval(time()+1);
				else if($_externalClose && !empty($this->InternalClosed))
					$update = "`external_close`='1'";
				else if($_internalClosed && empty($this->InternalClosed))
					$update = "`internal_closed`='1',`exit`=".intval(time()+1);
				else if($_internalDeclined && empty($this->InternalDeclined))
                {
					$update = "`internal_declined`='1'";
                    $where .= " AND `internal_active`=0";
                }
				else
					$update = "`external_active`='1'";


				if(($_internalClosed || $_externalClose) && !empty($this->AllocatedTime))
				{
                    UserGroup::RemoveNonPersistantMember($this->SystemId);
					$params = $this->CalculateChatResponseTime();
					$update .= ",`response_time`=" . abs($params[0]) . ",`chat_posts`=" . abs($params[1]);
				}
				DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` SET " . $update . " WHERE " . $where . " LIMIT 1;");
			}
			DBManager::Execute(true, "UPDATE `" . DB_PREFIX . DATABASE_CHAT_ARCHIVE . "` SET `endtime`=" . $this->LastActive . ((!empty($this->AllocatedTime)) ? (",`time`=" . $this->AllocatedTime) : "") . " WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' LIMIT 1;");
		}
	}
	
	function CalculateChatResponseTime($start=0,$postcount=0)
	{
		$durations = array();
		$result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_POSTS . "` WHERE `chat_id`='" . DBManager::RealEscape($this->ChatId) . "' ORDER BY `time` ASC;");
		if($result)
			while($row = DBManager::FetchArray($result))
			{
				if(empty($start) && strpos($row["sender"],"~") !== false)
					$start = max($this->AllocatedTime,$row["time"]);
				else if(strpos($row["sender"],"~") === false)
				{
					$postcount++;
					if(!empty($start))
					{
						$durations[] = $row["time"]-$start;
						$start = 0;
					}
				}
			}
		if(count($durations) > 0)
			return array(0=>floor(array_sum($durations) / count($durations)),1=>$postcount);
		else
			return array(0=>0,1=>$postcount);
	}

    function IsMaxWaitingTime($_queue=false)
    {
        if(!$_queue)
        {
            if(!empty(Server::$Configuration->File["gl_mcwt"]) && $this->Status == CHAT_STATUS_WAITING && is_array($this->Members) && empty($this->InternalActivation))
                foreach($this->Members as $member)
                    if((time()-$member->Joined) > Server::$Configuration->File["gl_mcwt"])
                    {
                        return true;
                    }
        }
        else
        {
            if(!empty(Server::$Configuration->File["gl_mqwt"]) && (time()-$this->FirstActive) > (Server::$Configuration->File["gl_mqwt"]*60))
            {
                return true;
            }
        }
        return false;
    }

    function GetForwards()
    {
        $list = array();
        $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_CHAT_FORWARDS . "` WHERE `target_group_id`='" . DBManager::RealEscape($this->DesiredChatGroup) . "' AND `visitor_id`='" . DBManager::RealEscape($this->UserId) . "' AND `browser_id`='" . DBManager::RealEscape($this->BrowserId) . "' ORDER BY `created` ASC;");
        while($row = DBManager::FetchArray($result))
            $list[] = new Forward($row);
        return $list;
    }

    function GetMaxWaitingTimeAction($_queue=false)
    {
        if($this->IsMaxWaitingTime($_queue))
        {
            if(Server::$Configuration->File["gl_alloc_mode"] != ALLOCATION_MODE_ALL && !empty(Server::$Configuration->File["gl_mcwf"]))
                return "FORWARD";
            else if(Server::$Configuration->File["gl_mcwf"] != 1)
                return "MESSAGE";
        }
        return false;
    }

    function GetLastActiveChatId()
    {
        $result = DBManager::Execute(true, "SELECT `chat_id` FROM `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` WHERE `chat_id`>0 AND `status`>1 AND `browser_id`='" . DBManager::RealEscape($this->BrowserId) . "' AND `visitor_id`='" . DBManager::RealEscape($this->UserId) . "' ORDER BY `chat_id` DESC LIMIT 1;");
        if($result)
            if($row = DBManager::FetchArray($result))
                return $row["chat_id"];
        return 0;
    }

    function FindOperator($_router,$_user,$_allowBots=false,$_requireBot=false,$_exclude=null,$_closeOnError=true)
    {
        $_router->TargetGroupId = $this->DesiredChatGroup;
        $_router->PreviousOperatorSystemId = $this->DesiredChatPartner;
        $result = $_router->Find($_user,$_allowBots,$_requireBot,$_exclude,false);
        $this->SetDesiredChatPartner($_router->TargetOperatorSystemId);

        if(!$result && $_closeOnError)
            $this->CloseChat(3);

        if($_router->IsPredefined)
            $this->SetTargetOperator($this->DesiredChatPartner);

        return $result;
    }

    function CorrectInvalidTarget($_botmode)
    {
        if(!empty($this->DesiredChatPartner) && Server::$Operators[$this->DesiredChatPartner]->IsBot && !$_botmode)
            $this->SetDesiredChatPartner("");
    }

    function SetTargetOperatorId($_router,$_user,$_allowBots=false,$_requireBot=false,$_exclude=null,$_closeOnError=true,$_isForward=false,$_botmode=false)
    {
        $this->CorrectInvalidTarget($_botmode);
        $this->FindOperator($_router,$_user,$_allowBots,$_requireBot,$_exclude,$_closeOnError,false);

        if($this->Status > CHAT_STATUS_OPEN && !$this->Closed)
        {
            $this->SetDesiredChatPartner($this->OperatorId);
        }
        // init chat
        else if(!empty($this->InitChatWith) && Server::$Operators[$this->InitChatWith]->Status < USER_STATUS_OFFLINE)
        {
            $_user->AddFunctionCall("OverlayChatWidgetV2.__ModeBot = false;",false);
            $_user->AddFunctionCall("OverlayChatWidgetV2.InputState(false);",false);
            $this->SetDesiredChatPartner($this->InitChatWith);
        }
        // chat invite
        else if(!empty($_user->Browsers[1]->ChatRequests) && !$_user->Browsers[1]->ChatRequests[0]->IsAnswered(true))
        {
            $gs = "";
            $routToAutoInviteSender = false;
            if(!empty($_user->Browsers[1]->ChatRequests[0]->EventActionId))
            {
                Server::InitDataBlock(array("EVENTS"));
                $eva = Server::$Events->GetActionById($_user->Browsers[1]->ChatRequests[0]->EventActionId);
                $gs = $eva->Invitation->GetGroupSender();
                $routToAutoInviteSender = empty(Server::$Configuration->File["gl_iada"]);
            }

            if(empty($gs) || $routToAutoInviteSender)
            {
                $routToManualInviteSender = (empty(Server::$Configuration->File["gl_imda"]) && empty($_user->Browsers[1]->ChatRequests[0]->EventActionId));
                if($routToManualInviteSender || $routToAutoInviteSender)
                {
                    if(!Server::$Operators[$_user->Browsers[1]->ChatRequests[0]->SenderSystemId]->IsBot)
                        $this->SetDesiredChatPartner($_user->Browsers[1]->ChatRequests[0]->SenderSystemId);
                }
                else
                    $this->SetDesiredChatPartner("");
            }
        }
        // default allocation
        else
        {
            // just dont ovwr
        }
    }

    function CreateAutoForward($_user)
    {
        if(Server::$Configuration->File["gl_alloc_mode"] == ALLOCATION_MODE_ALL)
            return false;

        $currentOperator = '';
        $lastForward = null;
        $iaf = false;
        $allForwards = $this->GetForwards();
        foreach($allForwards as $forward)
        {
            if($forward->Created > (time()-(Server::$Configuration->File["gl_mcwt"]+10)))
                $iaf = true;
        }

        foreach($this->Members as $member)
            $currentOperator = $member->SystemId;

        if(!$iaf)
        {
            $targets = array();
            $this->FindOperator(VisitorChat::$Router,$_user,false,false,array($currentOperator=>$currentOperator),false,false);
            $isTargetAvailable = (count(VisitorChat::$Router->OperatorsAvailable)>=2 || (count(VisitorChat::$Router->OperatorsAvailable)==1 && !empty($currentOperator) && !isset(VisitorChat::$Router->OperatorsAvailable[$currentOperator])));

            if($isTargetAvailable)
            {
                $forwardedToCount = array();

                foreach(VisitorChat::$Router->OperatorsAvailable as $opsysId => $ccount)
                {
                    $lastForwardToTime = $ccount;
                    foreach($allForwards as $forward)
                    {
                        if($forward->TargetSessId == $opsysId)
                        {
                            $lastForwardToTime = max($forward->Created,$lastForwardToTime);
                            if(!isset($forwardedToCount[$opsysId]))
                                $forwardedToCount[$opsysId] = 0;
                            $forwardedToCount[$opsysId]++;
                        }
                    }
                    $targets[$opsysId] = $lastForwardToTime;
                }

                if(count($forwardedToCount)>0)
                    $forwardedToCount = min($forwardedToCount);
                else
                    $forwardedToCount = 0;

                if(!empty(Server::$Configuration->File["gl_mcfc"]) && is_numeric(Server::$Configuration->File["gl_mcfc"]))
                    if(Server::$Configuration->File["gl_mcfc"] <= $forwardedToCount)
                        return false;

                if(!empty($targets))
                {
                    asort($targets);

                    foreach($targets as $targetsysid => $time)
                    {
                        if($targetsysid != $currentOperator)
                        {
                            $forward = new Forward($this->ChatId,$this->DesiredChatPartner);
                            $forward->InitiatorSystemId = $currentOperator;
                            $forward->ReceiverUserId = $this->UserId;
                            $forward->ReceiverBrowserId = $this->BrowserId;
                            $forward->TargetSessId = $targetsysid;
                            $forward->TargetGroupId = $this->DesiredChatGroup;

                            if($lastForward != null && $lastForward->TargetSessId == $forward->TargetSessId)
                                return true;

                            $forward->Save();
                            $post = new Post(getId(32),"",$currentOperator,$targetsysid."[__[auto_forward:".$this->ChatId."]__]",time(),"","");
                            $post->ReceiverGroup = $this->SystemId;
                            $post->Save();
                            return true;
                        }
                    }
                }
            }
        }
        return true;
    }

    function ShowQueueInformation($_visitor,$_chatPosition,$_chatWaitingTime,$_html)
    {
        $_visitor->AddFunctionCall("OverlayChatWidgetV2.ShowQueueMessage(".$_chatPosition.",".min($_chatWaitingTime,30).",'".base64_encode($_html)."');",false);
    }

    function ShowConnecting($_visitor,$_resume=false)
    {
        if($_resume)
            $_visitor->AddFunctionCall("lz_chat_show_connected(false,true);",false);
        else
            $_visitor->AddFunctionCall("lz_chat_show_connected(".To::BoolString(empty(VisitorChat::$DynamicGroup)).",false);",false);
    }

    function TextReplace($_text)
    {
        $_text = parent::TextReplace($_text);
        $_text = str_replace(array("%chat_id%","%CHATID%"),$this->ChatId,$_text);
        return $_text;
    }

    static function FromCache($_uid,$_bid)
    {
        $br = new VisitorChat($_uid,$_bid);
        $br->Load();
        return $br;
    }

    static function GetByChatId($_chatId)
    {
        if(!empty($_chatId))
        {
            $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` WHERE `chat_id`='" . DBManager::RealEscape($_chatId) . "';");
            if($result)
                if($row = DBManager::FetchArray($result))
                    return new VisitorChat($row);
        }
        return null;
    }

    static function GetBySystemId($_systemId)
    {
        $parts = explode("~",$_systemId);
        if(count($parts)==2)
        {
            $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` WHERE `visitor_id`='" . DBManager::RealEscape($parts[0]) . "' AND `browser_id`='" . DBManager::RealEscape($parts[1]) . "' ORDER BY `chat_id` DESC;");
            if($result)
                if($row = DBManager::FetchArray($result))
                    return new VisitorChat($row);
        }
        return null;
    }

    static function GetByUserId($_userId)
    {
        $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_CHATS . "` WHERE `visitor_id`='" . DBManager::RealEscape($_userId) . "' ORDER BY `chat_id` DESC;");
        if($result)
            if($row = DBManager::FetchArray($result))
                return new VisitorChat($row);
        return null;
    }

    static function IsChatBrowserIdAvailable($_browserId, $_allowExisting=true)
    {
        if(empty($_browserId))
            return false;

        $result = DBManager::Execute(true, "SELECT * FROM `" . DB_PREFIX . DATABASE_VISITOR_BROWSERS . "` WHERE `id`='" . DBManager::RealEscape($_browserId) . "';");
        if($result)
            if($row = DBManager::FetchArray($result))
            {
                if(!$_allowExisting)
                    return false;

                if(!empty($row["overlay_container"]) || empty($row["is_chat"]))
                    return false;
            }

        return true;
    }

    static function ApplyDynamicGroup()
    {
        if(!empty($_GET[GET_EXTERN_PUBLIC_CHAT_GROUP]))
        {
            $tgroup = Communication::ReadParameter(GET_EXTERN_PUBLIC_CHAT_GROUP,"");
            if(isset(Server::$Groups[$tgroup]) && Server::$Groups[$tgroup]->IsDynamic)
                VisitorChat::$DynamicGroup = $tgroup;
        }
    }
}
?>