Your IP : 18.225.57.152


Current Path : /home/bitrix/ext_www/crm.klimatlend.ua/bitrix/modules/xmpp/classes/
Upload File :
Current File : /home/bitrix/ext_www/crm.klimatlend.ua/bitrix/modules/xmpp/classes/server.php

<?
class CXMPPServer
{
	private $sockServer;
	private $sockServerSSL;

	private $initialized;

	private $arClients = array();
	private $arClientsIndex = array();
	private $lastClientId;

	private $arSockets = array();
	private $socketsClientsStartIndex = 0;

	private $startPeriodTime;
	private $startClearPeriodTime;
	private $startLongPeriodTime;

	private $arOnlineOnSite = array();

	private $testMode = false;
	private $bitrix24Mode = false;

	public static function GetServer()
	{
		if (!array_key_exists("xmppServerObject", $GLOBALS) || !is_object($GLOBALS["xmppServerObject"]))
		{
			$GLOBALS["xmppServerObject"] = new CXMPPServer();
			$GLOBALS["xmppServerObject"]->Initialize();
		}
		return $GLOBALS["xmppServerObject"];
	}

	public static function IsServerStarted()
	{
		return (array_key_exists("xmppServerObject", $GLOBALS) && is_object($GLOBALS["xmppServerObject"]));
	}

	protected function Initialize()
	{
		if ($this->initialized)
			return;

		ini_set('max_execution_time', 0);
		set_time_limit(0);
		ob_implicit_flush(true);

		$level = ob_get_level();
		for ($i = 0; $i < $level; $i++)
			ob_end_clean();

		$this->testMode = ($GLOBALS["argc"] > 1 ? $GLOBALS["argv"][1] == "test_mode" : false);
		$this->bitrix24Mode = ($GLOBALS["argc"] > 1 ? $GLOBALS["argv"][1] == "bitrix24" : false);

		if ($this->testMode)
		{
			if ($f = @fsockopen(CXMPPServer::GetDomain(), 5222, $errNo, $errStr, 2))
				die(str_replace(array("#host#", "#port#"), array(CXMPPServer::GetDomain(), 5222), "#host#:#port# is already in use."));

			$curPhpVer = phpversion();
			$arCurPhpVer = explode(".", $curPhpVer);
			if (intval($arCurPhpVer[0]) < 5)
				die(str_replace("#ver#", $curPhpVer, "You are using PHP version #ver#, but the xmpp server requires version 5.0.0 or higher."));
		}

		$this->arClients = array();
		$this->arClientsIndex = array();
		$this->lastClientId = 0;

		$this->startPeriodTime = time();
		$this->startPeriodTimeTruncate = time();

		$this->arOnlineOnSite = array();

		$this->logLevel = intval(COption::GetOptionString("xmpp", "log_level", "4"));

		$this->initialized = true;
	}

	public function Run()
	{
		$server = CXMPPServer::GetServer();

		$startSSL = (strtoupper(COption::GetOptionString("xmpp", "start_ssl", "N")) == "Y");

		if ($server->Start() && (!$startSSL || $server->StartSSL()))
		{
			if ($server->testMode)
				$server->Stop();

			$server->Listen();
		}
	}

	protected function Start()
	{
		$listen = COption::GetOptionString("xmpp", "listen_domain", "0.0.0.0");
		$this->sockServer = stream_socket_server("tcp://".$listen.":5222", $errno = 0, $errstr = "");

		if (!$this->sockServer)
		{
			$this->WriteToLog("Create socket error: $errstr ($errno)", 10);
			return false;
		}

		$this->WriteToLog("Server started", 10);
		$this->socketsClientsStartIndex++;
		$this->arSockets[count($this->arSockets)] = $this->sockServer;
		return true;
	}

	protected function StartSSL()
	{
		$context = stream_context_create(
			array(
				'ssl' => array(
					'local_cert' => $_SERVER['DOCUMENT_ROOT'].'/cert/mycert.pem',
					'passphrase' => '',
					'allow_self_signed' => true,
					'verify_peer' => false,
				)
			)
		);

		$listen = COption::GetOptionString("xmpp", "listen_domain", "0.0.0.0");
		$this->sockServerSSL = stream_socket_server("ssl://".$listen.":5223", $errno = 0, $errstr = "", STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context);

		if (!$this->sockServerSSL)
		{
			$this->WriteToLog("Create socket error: $errstr ($errno)", 10);
			return false;
		}

		$this->WriteToLog("SSL Server started", 10);
		$this->socketsClientsStartIndex++;
		$this->arSockets[count($this->arSockets)] = $this->sockServerSSL;
		return true;
	}

	public function Stop()
	{
		if ($this->logFile)
			fclose($this->logFile);

		if ($this->sockServer)
			@fclose($this->sockServer);
		if ($this->sockServerSSL)
			@fclose($this->sockServerSSL);

		die();
	}

	protected function Listen()
	{
		$this->__ReportKernel();
		$this->startClearPeriodTime = time();

		if (!$this->sockServer && !$this->sockServerSSL)
			return;

		COption::SetOptionInt('xmpp', 'LastActivityDate', time());

		while (true)
		{
			$arReadSockets = $this->arSockets;

			$n = @stream_select($arReadSockets, $w = null, $e = null, 3);
			if ($n > 0)
			{
				while (list($k, $r) = each($arReadSockets))
				{
					if ($this->sockServer && ($r == $this->sockServer))
					{
						if (is_resource($sock = stream_socket_accept($this->sockServer, 0, $ip)))
						{
							$this->lastClientId++;
							$id = $this->lastClientId;

							stream_set_timeout($sock, 5);
							$this->arClients[$id] = new CXMPPClient($id, $sock);
							$this->arSockets[$id + $this->socketsClientsStartIndex] = $sock;

							$this->WriteToLog("Client connected (".$id.")", 5);
						}
					}
					elseif ($this->sockServerSSL && ($r == $this->sockServerSSL))
					{
						if (is_resource($sock = stream_socket_accept($this->sockServerSSL, 0, $ip)))
						{
							$this->lastClientId++;
							$id = $this->lastClientId;

							stream_set_timeout($sock, 5);
							$this->arClients[$id] = new CXMPPClient($id, $sock);
							$this->arSockets[$id + $this->socketsClientsStartIndex] = $sock;

							$this->WriteToLog("Client connected (".$id.")", 5);
						}
					}
					else
					{
						$id = array_search($r, $this->arSockets);
						if ($id !== false && $id > 1)
						{
							if (CXMPPUtility::SelectDatabase($this->arClients[$id - $this->socketsClientsStartIndex]->GetClientDomain()))
							{
								$this->arClients[$id - $this->socketsClientsStartIndex]->Receive();
							}
							else
							{
								$this->arClients[$id - $this->socketsClientsStartIndex]->Disconnect();
								$this->WriteToLog("Client is disconnected because it was not possible to select the database (".$this->arClients[$id - $this->socketsClientsStartIndex]->GetClientDomain().")", 10);
							}
						}
						else
						{
							$sn1 = @stream_socket_get_name($r, true);
							$sn2 = @stream_socket_get_name($r, false);
							$this->WriteToLog("Debug: Socket is not found (".$sn1."-".$sn2.")", 10);
						}
					}
				}
			}

			if (time() - $this->startClearPeriodTime > 30)
			{
				$this->__RefineClientsList();
				$this->WriteToLog("Number of clients connected = ".count($this->arClients), 10);
				$this->startClearPeriodTime = time();
			}

			if (time() - $this->startPeriodTime > 60)
			{
				$this->__ReportKernel();
				$this->startPeriodTime = time();
			}

			if (time() - $this->startLongPeriodTime > 600)
			{
				$this->__PingClients();
				$this->startLongPeriodTime = time();
				COption::SetOptionInt('xmpp', 'LastActivityDate', time());
			}
		}
	}

	private function __PingClients()
	{
		$bRefineClientsList = false;

		$arClientsKeys = array_keys($this->arClients);
		foreach ($arClientsKeys as $id)
		{
			$pingTime = $this->arClients[$id]->GetPingTime();
			if (($pingTime > 0) && (time() - $pingTime > 900))
			{
				$this->WriteToLog("Client disconnected from PING (".$id.",".time()."-".$pingTime.")", 5);
				$this->arClients[$id]->Disconnect();
				$bRefineClientsList = true;
			}
			else
			{
				$this->arClients[$id]->Send(
					array(
						"iq" => array(
							"." => array(
								"type" => "get",
								"from" => $this->arClients[$id]->GetClientDomain(),
								"to" => $this->arClients[$id]->GetJId(),
								"id" => "pg".rand(10, 99),
							),
							"ping" => array(
								"." => array("xmlns" => "urn:xmpp:ping"),
							),
						),
					)
				);
			}
		}

		if ($bRefineClientsList)
		{
			$this->__RefineClientsList();
			$this->WriteToLog("Number of clients connected = ".count($this->arClients), 10);
			$this->startClearPeriodTime = time();
		}
	}

	private function __RefineClientsList()
	{
		$arClientsKeys = array_keys($this->arClients);
		foreach ($arClientsKeys as $id)
		{
			if ($this->arClients[$id]->IsConnected() && !feof($this->arSockets[$id + $this->socketsClientsStartIndex]))
				continue;

			if ($this->arClients[$id]->IsConnected())
			{
				$this->WriteToLog("Client disconnected from REFINE (".$id.")", 5);
				$this->arClients[$id]->Disconnect();
			}

			$clientDomain = $this->arClients[$id]->GetClientDomain();
			$clientJId = $this->arClients[$id]->GetJId();
			$clientJIdWithResource = $this->arClients[$id]->GetJIdWithResource();

			if (is_array($this->arClientsIndex[$clientDomain][$clientJId]))
			{
				if (($i = array_search($id, $this->arClientsIndex[$clientDomain][$clientJId])) !== false)
					unset($this->arClientsIndex[$clientDomain][$clientJId][$i]);
			}

			if ($this->arClients[$id]->IsAuthenticated()
				&& (!array_key_exists($clientJId, $this->arClientsIndex[$clientDomain])
					|| count($this->arClientsIndex[$clientDomain][$clientJId]) <= 0))
			{
				foreach ($this->arClientsIndex[$clientDomain] as $jid1 => $arId1)
				{
					foreach ($arId1 as $id1)
					{
						if (($id1 != $id) && $this->arClients[$id1]->IsAuthenticated())
						{
							$this->Send(
								$jid1,
								array(
									"presence" => array(
										"." => array(
											"type" => "unavailable",
											"from" => $clientJIdWithResource,
											"to" => CXMPPUtility::GetJIdWithResource($jid1, $clientDomain),
										),
									),
								),
								$clientDomain
							);
						}
					}
				}
			}

			unset($this->arClients[$id]);
			unset($this->arSockets[$id + $this->socketsClientsStartIndex]);

			$this->WriteToLog("Client disconnected (".$id.")", 5);
		}
	}

	private function __ReportKernel()
	{
		foreach ($this->arClientsIndex as $clientDomain	=> $arDomainClientsIndex)
		{
			if (!CXMPPUtility::SelectDatabase($clientDomain))
				continue;
			if (count($arDomainClientsIndex) <= 0)
				continue;

			foreach ($arDomainClientsIndex as $jid => $arId)
			{
				foreach ($arId as $id)
				{
					if (array_key_exists($id, $this->arClients))
					{
						if ($this->arClients[$id]->IsConnected() && $this->arClients[$id]->IsAuthenticated())
						{
							$idTmp = $this->arClients[$id]->GetId();
							CUser::SetLastActivityDate($idTmp);
							CXMPPClient::SetLastActivityDate($idTmp);
						}
					}
				}
			}

			$arOnlineOnSiteTmp = array();

			$dbUsers = CUser::GetList(
				$b = "LOGIN",
				$o = "DESC",
				array(
					"ACTIVE" => "Y",
					"LAST_ACTIVITY" => 600,
					"!UF_DEPARTMENT" => false
				),
				array('FIELDS' => array('ID', 'LOGIN'))
			);
			while ($arUser = $dbUsers->Fetch())
				$arOnlineOnSiteTmp[] = CXMPPUtility::GetJId($arUser, $clientDomain);

			if (!is_array($this->arOnlineOnSite[$clientDomain]))
				$this->arOnlineOnSite[$clientDomain] = array();

			$arOffline = array_diff($this->arOnlineOnSite[$clientDomain], $arOnlineOnSiteTmp);

			foreach ($arOffline as $jid)
			{
				if (!array_key_exists($jid, $this->arClientsIndex[$clientDomain])
					|| array_key_exists($jid, $this->arClientsIndex[$clientDomain]) && count($this->arClientsIndex[$clientDomain][$jid]) <= 0)
				{
					$this->SendAll(
						array(
							"presence" => array(
								"." => array(
									"type" => "unavailable",
									"from" => $jid,
								),
							),
						)
					);
				}
			}

			$arOnline = array_diff($arOnlineOnSiteTmp, $this->arOnlineOnSite[$clientDomain]);

			foreach ($arOnline as $jid)
			{
				if (!array_key_exists($jid, $this->arClientsIndex[$clientDomain])
					|| array_key_exists($jid, $this->arClientsIndex[$clientDomain]) && count($this->arClientsIndex[$clientDomain][$jid]) <= 0)
					$this->SendAll(
						array(
							"presence" => array(
								"." => array(
									"from" => $jid,
								),
							),
						)
					);
			}

			$this->arOnlineOnSite[$clientDomain] = $arOnlineOnSiteTmp;
		}
	}

	public function SendPresenceMessages($receiverJId, $clientDomain = "")
	{
		if (empty($clientDomain))
			$clientDomain = CXMPPServer::GetDomain();

		$receiverJIdWithResource = CXMPPUtility::GetJIdWithResource($receiverJId, $clientDomain);

		if (array_key_exists($clientDomain, $this->arOnlineOnSite))
		{
			foreach ($this->arOnlineOnSite[$clientDomain] as $jid)
			{
				$this->Send(
					$receiverJId,
					array(
						"presence" => array(
							"." => array(
								"from" => CXMPPUtility::GetJIdWithResource($jid, $clientDomain),
								"to" => $receiverJIdWithResource,
							),
						),
					),
					$clientDomain
				);
			}
		}

		if (array_key_exists($clientDomain, $this->arClientsIndex))
		{
			foreach ($this->arClientsIndex[$clientDomain] as $jid => $arId)
			{
				if (count($arId) <= 0)
					continue;

				if (!is_array($this->arOnlineOnSite[$clientDomain]) || !in_array($jid, $this->arOnlineOnSite[$clientDomain]))
				{
					$this->Send(
						$receiverJId,
						array(
							"presence" => array(
								"." => array(
									"from" => CXMPPUtility::GetJIdWithResource($jid, $clientDomain),
									"to" => $receiverJIdWithResource,
								),
							),
						),
						$clientDomain
					);
				}
			}
		}
	}

	public function SendPresenceMessages2($receiverJId, $clientDomain = "")
	{
		if (empty($clientDomain))
			$clientDomain = CXMPPServer::GetDomain();

		$receiverJIdWithResource = CXMPPUtility::GetJIdWithResource($receiverJId, $clientDomain);

		if (array_key_exists($clientDomain, $this->arOnlineOnSite))
		{
			foreach ($this->arOnlineOnSite[$clientDomain] as $jid)
			{
				$this->Send(
					$jid,
					array(
						"presence" => array(
							"." => array(
								"from" => $receiverJIdWithResource,
								"to" => CXMPPUtility::GetJIdWithResource($jid, $clientDomain),
							),
						),
					),
					$clientDomain
				);
			}
		}

		if (array_key_exists($clientDomain, $this->arClientsIndex))
		{
			foreach ($this->arClientsIndex[$clientDomain] as $jid => $arId)
			{
				if (count($arId) <= 0)
					continue;

				if (!is_array($this->arOnlineOnSite[$clientDomain]) || !in_array($jid, $this->arOnlineOnSite[$clientDomain]))
				{
					$this->Send(
						$jid,
						array(
							"presence" => array(
								"." => array(
									"from" => $receiverJIdWithResource,
									"to" => CXMPPUtility::GetJIdWithResource($jid, $clientDomain),
								),
							),
						),
						$clientDomain
					);
				}
			}
		}
	}

	public static function GetDomain()
	{
		return COption::GetOptionString("xmpp", "domain_name", BX_XMPP_SERVER_DOMAIN);
	}

	public static function GetLanguage()
	{
		return COption::GetOptionString("xmpp", "domain_lang", "en");
	}

	public function Send($jid, $arMessage, $clientDomain = "")
	{
		$p = strpos($jid, "/");
		if ($p !== false)
			$jid = substr($jid, 0, $p);

		if (empty($clientDomain))
			$clientDomain = CXMPPServer::GetDomain();

		if (array_key_exists($clientDomain, $this->arClientsIndex))
		{
			if (array_key_exists($jid, $this->arClientsIndex[$clientDomain]))
			{
				$r = false;
				foreach ($this->arClientsIndex[$clientDomain][$jid] as $v)
					$r = $this->arClients[$v]->Send($arMessage);
				return $r;
			}
			else
			{
				CXMPPUtility::Show("Error jid=".$jid." - Not connected", 0);
			}
		}
		else
		{
			CXMPPUtility::Show("Error clientDomain=".$clientDomain." - Not connected", 0);
		}

		return false;
	}

	public function SendAll($arMessage, $clientDomain = "")
	{
		if (empty($clientDomain))
			$clientDomain = CXMPPServer::GetDomain();

		if (array_key_exists($clientDomain, $this->arClientsIndex))
		{
			foreach ($this->arClientsIndex[$clientDomain] as $jid => $arId)
			{
				foreach ($arId as $id)
				{
					if (array_key_exists($id, $this->arClients))
					{
						if ($this->arClients[$id]->IsAuthenticated())
							$this->arClients[$id]->Send($arMessage);
					}
				}
			}
		}
	}

	public function _IndexClient($jid, $id, $clientDomain = "")
	{
		$id = intval($id);

		if (empty($clientDomain))
			$clientDomain = CXMPPServer::GetDomain();

		if (!array_key_exists($clientDomain, $this->arClientsIndex))
			$this->arClientsIndex[$clientDomain] = array();

		if (!array_key_exists($jid, $this->arClientsIndex[$clientDomain]))
			$this->arClientsIndex[$clientDomain][$jid] = array();

		$this->arClientsIndex[$clientDomain][$jid][] = $id;
	}

	public function NumberOfOnlineUsers()
	{
		$this->__ReportKernel();

		$n = 0;
		foreach ($this->arOnlineOnSite as $key => $value)
			$n += count($value);

		return $n;
	}

	public function NumberOfConnectedUsers()
	{
		$this->__RefineClientsList();
		return Count($this->arClients);
	}

	public function ClearCaches()
	{
		$factory = CXMPPFactory::GetFactory();
		$factory->ClearCaches();
	}

	public function GetClient($jid, $domain = "")
	{
		if (empty($domain))
			$domain = CXMPPServer::GetDomain();

		if (array_key_exists($domain, $this->arClientsIndex))
		{
			if (array_key_exists($jid, $this->arClientsIndex[$domain]))
			{
				$r = array();
				foreach ($this->arClientsIndex[$domain][$jid] as $v)
					$r[] = $this->arClients[$v];
				if (count($r) > 0)
					return $r;
			}
		}

		return null;
	}

	var $logFile;
	var $logFileName = "/bitrix/modules/xmppd.log";
	var $logLevel = 0;
	var $logMaxSize = 2000000;
	var $startPeriodTimeTruncate;

	public function WriteToLog($txt, $level)
	{
		if ($this->logLevel > $level)
			return;

		if (time() - $this->startPeriodTimeTruncate > 600)
		{
			if ($this->logFile)
				fclose($this->logFile);

			$this->logFile = null;

			if (file_exists($_SERVER["DOCUMENT_ROOT"].$this->logFileName))
			{
				$logSize = @filesize($_SERVER["DOCUMENT_ROOT"].$this->logFileName);
				$logSize = intval($logSize);

				if ($logSize > $this->logMaxSize)
				{
					if (($fp = @fopen($_SERVER["DOCUMENT_ROOT"].$this->logFileName, "rb"))
						&& ($fp1 = @fopen($_SERVER["DOCUMENT_ROOT"].$this->logFileName."_", "wb")))
					{
						$iSeekLen = intval($logSize - $this->logMaxSize / 2.0);
						fseek($fp, $iSeekLen);

						@fwrite($fp1, "Truncated ".Date("Y-m-d H:i:s")."\n---------------------------------\n");
						do
						{
							$data = fread($fp, 8192);
							if (strlen($data) == 0)
								break;

							@fwrite($fp1, $data);
						}
						while (true);

						@fclose($fp);
						@fclose($fp1);

						@copy($_SERVER["DOCUMENT_ROOT"].$this->logFileName."_", $_SERVER["DOCUMENT_ROOT"].$this->logFileName);
						@unlink($_SERVER["DOCUMENT_ROOT"].$this->logFileName."_");
					}
				}
				ClearStatCache();
			}

			$this->startPeriodTimeTruncate = time();
		}

		if (!$this->logFile || $this->logFile == null)
			$this->logFile = fopen($_SERVER["DOCUMENT_ROOT"].$this->logFileName, "a");

		if (!$this->logFile)
		{
			echo "Can't write to log\n---------------------------------\n";
			return;
		}

		fwrite($this->logFile, date("Y-m-d H:i:s")."\n".$txt."\n---------------------------------\n");
		fflush($this->logFile);

		if ($level > 4)
			echo $txt."\n---------------------------------\n";
	}

	public function GetDumpData()
	{
		$result = "";

		$result .= "arClientsIndex:\n".print_r($this->arClientsIndex, true)."\n\n";
		$result .= "--------------------------------------------------------\n\n";

		$result .= "arClients:\n";
		foreach ($this->arClients as $key => $client)
			$result .= "> ".$key.":\n".$client->GetDumpData()."\n\n";
		$result .= "--------------------------------------------------------\n\n";

		$result .= "arOnlineOnSite:\n".print_r($this->arOnlineOnSite, true)."\n\n";
		$result .= "--------------------------------------------------------\n\n";

		return $result;
	}

	public function IsBitrix24Mode()
	{
		return $this->bitrix24Mode;
	}
}
?>