%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/bitrix/www/bitrix/modules/main/classes/general/
Upload File :
Create Path :
Current File : /home/bitrix/www/bitrix/modules/main/classes/general/user.php

<?php
/**
 * Bitrix Framework
 * @package bitrix
 * @subpackage main
 * @copyright 2001-2013 Bitrix
 */

use Bitrix\Main;
use Bitrix\Main\Authentication\ApplicationPasswordTable;

IncludeModuleLangFile(__FILE__);

global $BX_GROUP_POLICY;
$BX_GROUP_POLICY = array(
	"SESSION_TIMEOUT"	=>	0, //minutes
	"SESSION_IP_MASK"	=>	"0.0.0.0",
	"MAX_STORE_NUM"		=>	10,
	"STORE_IP_MASK"		=>	"0.0.0.0",
	"STORE_TIMEOUT"		=>	60*24*365, //minutes
	"CHECKWORD_TIMEOUT"	=>	60*24*365,  //minutes
	"PASSWORD_LENGTH"	=>	false,
	"PASSWORD_UPPERCASE"	=>	"N",
	"PASSWORD_LOWERCASE"	=>	"N",
	"PASSWORD_DIGITS"	=>	"N",
	"PASSWORD_PUNCTUATION"	=>	"N",
	"LOGIN_ATTEMPTS"	=>	0,
);

abstract class CAllUser extends CDBResult
{
	var $LAST_ERROR = "";
	var $bLoginByHash = false;
	protected $admin = null;
	protected static $CURRENT_USER = false;
	protected $justAuthorized = false;
	protected static $userGroupCache = array();

	const STATUS_ONLINE = 'online';
	const STATUS_OFFLINE = 'offline';

	abstract public function Add($arFields);

	public function GetParam($name)
	{
		if(isset($_SESSION["SESS_AUTH"][$name]))
			return $_SESSION["SESS_AUTH"][$name];
		else
			return null;
	}

	public function GetSecurityPolicy()
	{
		if(!is_set($_SESSION["SESS_AUTH"], "POLICY"))
			$_SESSION["SESS_AUTH"]["POLICY"] = CUser::GetGroupPolicy($_SESSION["SESS_AUTH"]["USER_ID"]);
		return $_SESSION["SESS_AUTH"]["POLICY"];
	}

	public function SetParam($name, $value)
	{
		$_SESSION["SESS_AUTH"][$name] = $value;
	}

	public function GetID()
	{
		if(isset($_SESSION["SESS_AUTH"]["USER_ID"]))
			return $_SESSION["SESS_AUTH"]["USER_ID"];
		else
			return null;
	}

	public function GetLogin()
	{
		return $_SESSION["SESS_AUTH"]["LOGIN"];
	}

	public function GetEmail()
	{
		return $_SESSION["SESS_AUTH"]["EMAIL"];
	}

	public function GetFullName()
	{
		return $_SESSION["SESS_AUTH"]["NAME"];
	}

	public function GetFirstName()
	{
		return $_SESSION["SESS_AUTH"]["FIRST_NAME"];
	}

	public function GetLastName()
	{
		return $_SESSION["SESS_AUTH"]["LAST_NAME"];
	}

	public function GetSecondName()
	{
		return $_SESSION["SESS_AUTH"]["SECOND_NAME"];
	}

	public function GetFormattedName($bUseBreaks = true, $bHTMLSpec = true)
	{
		return CUser::FormatName(CSite::GetNameFormat($bUseBreaks),
			array(
				"TITLE" => $this->GetParam("TITLE"),
				"NAME" => $this->GetFirstName(),
				"SECOND_NAME" => $this->GetSecondName(),
				"LAST_NAME" => $this->GetLastName(),
				"LOGIN" => $this->GetLogin(),
			),
			true,
			$bHTMLSpec
		);
	}

	public function GetUserGroupArray()
	{
		if(
			!isset($_SESSION["SESS_AUTH"]["GROUPS"])
			|| !is_array($_SESSION["SESS_AUTH"]["GROUPS"])
			|| empty($_SESSION["SESS_AUTH"]["GROUPS"])
		)
			return array(2);

		//always unique and sorted, containing group ID=2
		return $_SESSION["SESS_AUTH"]["GROUPS"];
	}

	public function SetUserGroupArray($arr)
	{
		$arr = array_map("intval", $arr);
		$arr = array_filter($arr);
		$arr[] = 2;
		$arr = array_values(array_unique($arr));
		sort($arr);
		$_SESSION["SESS_AUTH"]["GROUPS"] = $arr;
	}

	public function GetUserGroupString()
	{
		return $this->GetGroups();
	}

	public function GetGroups()
	{
		return implode(",", $this->GetUserGroupArray());
	}

	public function RequiredHTTPAuthBasic($Realm = "Bitrix")
	{
		header("WWW-Authenticate: Basic realm=\"{$Realm}\"");
		if(stristr(php_sapi_name(), "cgi") !== false)
			header("Status: 401 Unauthorized");
		else
			header($_SERVER["SERVER_PROTOCOL"]." 401 Unauthorized");

		return false;
	}

	public function LoginByCookies()
	{
		global $USER;

		if(COption::GetOptionString("main", "store_password", "Y") == "Y")
		{
			$bLogout = isset($_REQUEST["logout"]) && (strtolower($_REQUEST["logout"]) == "yes");

			$cookie_prefix = COption::GetOptionString('main', 'cookie_name', 'BITRIX_SM');
			$cookie_login = strval($_COOKIE[$cookie_prefix.'_UIDL']);
			if($cookie_login == '')
			{
				//compatibility reasons
				$cookie_login = strval($_COOKIE[$cookie_prefix.'_LOGIN']);
			}
			$cookie_md5pass = strval($_COOKIE[$cookie_prefix.'_UIDH']);

			if($cookie_login <> '' && $cookie_md5pass <> '' && !$bLogout)
			{
				if($_SESSION["SESS_PWD_HASH_TESTED"] != md5($cookie_login."|".$cookie_md5pass))
				{
					$USER->LoginByHash($cookie_login, $cookie_md5pass);
					$_SESSION["SESS_PWD_HASH_TESTED"] = md5($cookie_login."|".$cookie_md5pass);
				}
			}
		}
	}

	public function LoginByHash($login, $hash)
	{
		/** @global CMain $APPLICATION */
		global $DB, $APPLICATION;

		$result_message = true;
		$user_id = 0;
		$arParams = array(
			"LOGIN" => $login,
			"HASH" => $hash,
		);

		$APPLICATION->ResetException();
		$bOk = true;
		foreach(GetModuleEvents("main", "OnBeforeUserLoginByHash", true) as $arEvent)
		{
			if(ExecuteModuleEventEx($arEvent, array(&$arParams))===false)
			{
				if($err = $APPLICATION->GetException())
					$result_message = array("MESSAGE"=>$err->GetString()."<br>", "TYPE"=>"ERROR");
				else
				{
					$APPLICATION->ThrowException("Unknown error");
					$result_message = array("MESSAGE"=>"Unknown error"."<br>", "TYPE"=>"ERROR");
				}

				$bOk = false;
				break;
			}
		}

		if($bOk && $arParams['HASH'] <> '')
		{
			$strSql =
				"SELECT U.ID, U.ACTIVE, U.STORED_HASH, U.EXTERNAL_AUTH_ID ".
				"FROM b_user U ".
				"WHERE U.LOGIN='".$DB->ForSQL($arParams['LOGIN'], 50)."' ";
			$result = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);

			$bFound = false;
			$bHashFound = false;
			while(($arUser = $result->Fetch()))
			{
				$bFound = true;
				//there is no stored auth for external authorization, but domain spread auth should work
				$bExternal = ($arUser["EXTERNAL_AUTH_ID"] <> '');
				if(
					// if old method (STORED_HASH <> '') and exact match
					($arUser["STORED_HASH"] <> '' && $arUser["STORED_HASH"] == $arParams['HASH'])
					|| // or new method
					(CUser::CheckStoredHash($arUser["ID"], $arParams['HASH'], $bExternal))
				)
				{
					$bHashFound = true;
					if($arUser["ACTIVE"] == "Y")
					{
						$user_id = $arUser["ID"];
						$_SESSION["SESS_AUTH"]["SESSION_HASH"] = $arParams['HASH'];
						$this->bLoginByHash = true;
						$this->Authorize($arUser["ID"], !$bExternal);
					}
					else
					{
						$APPLICATION->ThrowException(GetMessage("LOGIN_BLOCK"));
						$result_message = array("MESSAGE"=>GetMessage("LOGIN_BLOCK")."<br>", "TYPE"=>"ERROR");
					}
					break;
				}
				else
				{
					//Delete invalid stored auth cookie
					$spread = (COption::GetOptionString("main", "auth_multisite", "N") == "Y"? (Main\Web\Cookie::SPREAD_SITES | Main\Web\Cookie::SPREAD_DOMAIN) : Main\Web\Cookie::SPREAD_DOMAIN);

					$cookie = new Main\Web\Cookie("UIDH", "", 0);
					$cookie->setSpread($spread);
					$cookie->setHttpOnly(true);
					Main\Context::getCurrent()->getResponse()->addCookie($cookie);
				}
			}
			if(!$bFound)
			{
				$APPLICATION->ThrowException(GetMessage("WRONG_LOGIN"));
				$result_message = array("MESSAGE"=>GetMessage("WRONG_LOGIN")."<br>", "TYPE"=>"ERROR");
			}
			elseif(!$bHashFound)
			{
				$APPLICATION->ThrowException(GetMessage("USER_WRONG_HASH"));
				$result_message = array("MESSAGE"=>GetMessage("USER_WRONG_HASH")."<br>", "TYPE"=>"ERROR");
			}
		}

		$arParams["USER_ID"] = $user_id;
		$arParams["RESULT_MESSAGE"] = $result_message;

		foreach (GetModuleEvents("main", "OnAfterUserLoginByHash", true) as $arEvent)
			ExecuteModuleEventEx($arEvent, array(&$arParams));

		if(($result_message !== true) && (COption::GetOptionString("main", "event_log_login_fail", "N") === "Y"))
			CEventLog::Log("SECURITY", "USER_LOGINBYHASH", "main", $login, $result_message["MESSAGE"]);

		return $arParams["RESULT_MESSAGE"];
	}

	public function LoginByHttpAuth()
	{
		$arAuth = CHTTP::ParseAuthRequest();

		foreach(GetModuleEvents("main", "onBeforeUserLoginByHttpAuth", true) as $arEvent)
		{
			$res = ExecuteModuleEventEx($arEvent, array(&$arAuth));
			if($res !== null)
			{
				return $res;
			}
		}

		if(isset($arAuth["basic"]) && $arAuth["basic"]["username"] <> '' && $arAuth["basic"]["password"] <> '')
		{
			// Authorize user, if it is http basic authorization, with no remembering
			if(!$this->IsAuthorized() || $this->GetLogin() <> $arAuth["basic"]["username"])
			{
				return $this->Login($arAuth["basic"]["username"], $arAuth["basic"]["password"], "N");
			}
		}
		elseif(isset($arAuth["digest"]) && $arAuth["digest"]["username"] <> '' && COption::GetOptionString('main', 'use_digest_auth', 'N') == 'Y')
		{
			// Authorize user by http digest authorization
			if(!$this->IsAuthorized() || $this->GetLogin() <> $arAuth["digest"]["username"])
			{
				return $this->LoginByDigest($arAuth["digest"]);
			}
		}

		return null;
	}

	public function LoginByDigest($arDigest)
	{
		//array("username"=>"", "nonce"=>"", "uri"=>"", "response"=>"")
		/** @global CMain $APPLICATION */
		global $DB, $APPLICATION;

		$APPLICATION->ResetException();

		$strSql =
			"SELECT U.ID, U.PASSWORD, UD.DIGEST_HA1, U.EXTERNAL_AUTH_ID ".
			"FROM b_user U LEFT JOIN b_user_digest UD ON UD.USER_ID=U.ID ".
			"WHERE U.LOGIN='".$DB->ForSQL($arDigest["username"])."' ";
		$res = $DB->Query($strSql);

		if($arUser = $res->Fetch())
		{
			$method = (isset($_SERVER['REDIRECT_REQUEST_METHOD']) ? $_SERVER['REDIRECT_REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']);
			$HA2 = md5($method.':'.$arDigest['uri']);

			if($arUser["EXTERNAL_AUTH_ID"] == '' && $arUser["DIGEST_HA1"] <> '')
			{
				//digest is for internal authentication only
				$_SESSION["BX_HTTP_DIGEST_ABSENT"] = false;

				$HA1 = $arUser["DIGEST_HA1"];
				$valid_response = md5($HA1.':'.$arDigest['nonce'].':'.$HA2);

				if($arDigest["response"] === $valid_response)
				{
					//regular user password
					return $this->Login($arDigest["username"], $arUser["PASSWORD"], "N", "N");
				}
			}

			//check for an application password, including external users
			if(($appPassword = \Bitrix\Main\Authentication\ApplicationPasswordTable::findDigestPassword($arUser["ID"], $arDigest)) !== false)
			{
				return $this->Login($arDigest["username"], $appPassword["PASSWORD"], "N", "N");
			}

			if($arUser["DIGEST_HA1"] == '')
			{
				//this indicates that we still have no user digest hash
				$_SESSION["BX_HTTP_DIGEST_ABSENT"] = true;
			}
		}

		$APPLICATION->ThrowException(GetMessage("USER_AUTH_DIGEST_ERR"));
		return array("MESSAGE"=>GetMessage("USER_AUTH_DIGEST_ERR")."<br>", "TYPE"=>"ERROR");
	}

	public static function UpdateDigest($ID, $pass)
	{
		global $DB;
		$ID = intval($ID);

		$res = $DB->Query("
			SELECT U.LOGIN, UD.DIGEST_HA1
			FROM b_user U LEFT JOIN b_user_digest UD on UD.USER_ID=U.ID
			WHERE U.ID=".$ID
		);
		if($arRes = $res->Fetch())
		{
			if(defined('BX_HTTP_AUTH_REALM'))
				$realm = BX_HTTP_AUTH_REALM;
			else
				$realm = "Bitrix Site Manager";

			$digest = md5($arRes["LOGIN"].':'.$realm.':'.$pass);

			if($arRes["DIGEST_HA1"] == '')
			{
				//new digest
				$DB->Query("insert into b_user_digest (user_id, digest_ha1) values('".$ID."', '".$DB->ForSQL($digest)."')");
			}
			else
			{
				//update digest (login, password or realm were changed)
				if($arRes["DIGEST_HA1"] !== $digest)
					$DB->Query("update b_user_digest set digest_ha1='".$DB->ForSQL($digest)."' where user_id=".$ID);
			}
		}
	}

	public function LoginHitByHash()
	{
		/** @global CMain $APPLICATION */
		global $DB, $APPLICATION;

		$hash = trim($_REQUEST["bx_hit_hash"]);
		if ($hash == '')
			return false;

		$APPLICATION->ResetException();

		$strSql =
			"SELECT UH.USER_ID AS USER_ID ".
			"FROM b_user_hit_auth UH ".
			"INNER JOIN b_user U ON U.ID = UH.USER_ID AND U.ACTIVE ='Y' ".
			"WHERE UH.HASH = '".$DB->ForSQL($hash, 32)."' ".
			"	AND '".$DB->ForSqlLike($APPLICATION->GetCurPageParam("", array(), true), 500)."' LIKE ".$DB->Concat("UH.URL", "'%'");

		if(!defined("ADMIN_SECTION") || ADMIN_SECTION !== true)
			$strSql .= " AND UH.SITE_ID = '".SITE_ID."'";

		$result = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		if($arUser = $result->Fetch())
		{
			setSessionExpired(true);
			$this->Authorize($arUser["USER_ID"], false);

			$DB->Query("UPDATE b_user_hit_auth SET TIMESTAMP_X = ".$DB->GetNowFunction()." WHERE HASH='".$DB->ForSQL($hash, 32)."'");
			return true;
		}
		else
			return false;
	}

	public static function AddHitAuthHash($url, $user_id = false, $site_id = false)
	{
		global $USER, $DB;

		if ($url == '')
			return false;

		if (!$user_id)
			$user_id = $USER->GetID();

		if (!$site_id && (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true))
			$site_id = SITE_ID;

		$hash = false;

		if ($user_id)
		{
			$hash = md5(uniqid(rand(), true));
			$arFields = array(
				'USER_ID' => $user_id,
				'URL' => $DB->ForSqlLike(trim($url), 500),
				'HASH' => $hash,
				'SITE_ID' => $DB->ForSQL(trim($site_id), 2),
				'~TIMESTAMP_X'=>$DB->CurrentTimeFunction()
			);
			$DB->Add("b_user_hit_auth", $arFields);
		}

		return $hash;
	}

	public static function GetHitAuthHash($url_mask, $userID = false)
	{
		global $USER, $DB;

		$url_mask = trim($url_mask);
		if ($url_mask == '')
			return false;

		if (!$userID)
		{
			if (!$USER->IsAuthorized())
				return false;
			else
				$userID = $USER->GetID();
		}

		$strSql =
			"SELECT ID, HASH ".
			"FROM b_user_hit_auth ".
			"WHERE URL = '".$DB->ForSqlLike($url_mask, 500)."' AND USER_ID = ".intval($userID);

		$result = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		if($arTmp = $result->Fetch())
			return $arTmp["HASH"];
		else
			return false;
	}

	public static function CleanUpHitAuthAgent()
	{
		global $DB;
		$cleanup_days = COption::GetOptionInt("main", "hit_auth_cleanup_days", 30);
		if($cleanup_days > 0)
		{
			$arDate = localtime(time());
			$date = mktime(0, 0, 0, $arDate[4]+1, $arDate[3]-$cleanup_days, 1900+$arDate[5]);
			$DB->Query("DELETE FROM b_user_hit_auth WHERE TIMESTAMP_X <= ".$DB->CharToDateFunction(ConvertTimeStamp($date, "FULL")));

		}
		return "CUser::CleanUpHitAuthAgent();";
	}

	protected function UpdateSessionData($id, $applicationId = null)
	{
		global $DB;

		unset($_SESSION["SESS_OPERATIONS"]);
		unset($_SESSION["MODULE_PERMISSIONS"]);
		$_SESSION["BX_LOGIN_NEED_CAPTCHA"] = false;

		$strSql =
			"SELECT U.* ".
			"FROM b_user U  ".
			"WHERE U.ID='".intval($id)."' ";
		$result = $DB->Query($strSql);

		if($arUser = $result->Fetch())
		{
			$_SESSION["SESS_AUTH"]["AUTHORIZED"] = "Y";
			$_SESSION["SESS_AUTH"]["USER_ID"] = $arUser["ID"];
			$_SESSION["SESS_AUTH"]["LOGIN"] = $arUser["LOGIN"];
			$_SESSION["SESS_AUTH"]["LOGIN_COOKIES"] = $arUser["LOGIN"];
			$_SESSION["SESS_AUTH"]["EMAIL"] = $arUser["EMAIL"];
			$_SESSION["SESS_AUTH"]["PASSWORD_HASH"] = $arUser["PASSWORD"];
			$_SESSION["SESS_AUTH"]["TITLE"] = $arUser["TITLE"];
			$_SESSION["SESS_AUTH"]["NAME"] = $arUser["NAME"].($arUser["NAME"] == '' || $arUser["LAST_NAME"] == ''? "":" ").$arUser["LAST_NAME"];
			$_SESSION["SESS_AUTH"]["FIRST_NAME"] = $arUser["NAME"];
			$_SESSION["SESS_AUTH"]["SECOND_NAME"] = $arUser["SECOND_NAME"];
			$_SESSION["SESS_AUTH"]["LAST_NAME"] = $arUser["LAST_NAME"];
			$_SESSION["SESS_AUTH"]["PERSONAL_PHOTO"] = $arUser["PERSONAL_PHOTO"];
			$_SESSION["SESS_AUTH"]["PERSONAL_GENDER"] = $arUser["PERSONAL_GENDER"];
			$_SESSION["SESS_AUTH"]["PERSONAL_WWW"] = $arUser["PERSONAL_WWW"];
			$_SESSION["SESS_AUTH"]["EXTERNAL_AUTH_ID"] = $arUser["EXTERNAL_AUTH_ID"];
			$_SESSION["SESS_AUTH"]["XML_ID"] = $arUser["XML_ID"];
			$_SESSION["SESS_AUTH"]["ADMIN"] = false;
			$_SESSION["SESS_AUTH"]["POLICY"] = CUser::GetGroupPolicy($arUser["ID"]);
			$_SESSION["SESS_AUTH"]["AUTO_TIME_ZONE"] = trim($arUser["AUTO_TIME_ZONE"]);
			$_SESSION["SESS_AUTH"]["TIME_ZONE"] = $arUser["TIME_ZONE"];
			$_SESSION["SESS_AUTH"]["TIME_ZONE_OFFSET"] = $arUser["TIME_ZONE_OFFSET"];
			$_SESSION["SESS_AUTH"]["APPLICATION_ID"] = $applicationId;
			$_SESSION["SESS_AUTH"]["BX_USER_ID"] = $arUser["BX_USER_ID"];

			// groups
			$_SESSION["SESS_AUTH"]["GROUPS"] = Main\UserTable::getUserGroupIds($arUser["ID"]);

			foreach ($_SESSION["SESS_AUTH"]["GROUPS"] as $groupId)
			{
				if ($groupId == 1)
				{
					$_SESSION["SESS_AUTH"]["ADMIN"] = true;
					break;
				}
			}
			return $arUser;
		}
		return false;
	}

	/**
	 * Performs the user authorization:
	 *    fills session parameters;
	 *    remembers auth;
	 *    spreads auth through sites.
	 * @param int $id An user ID.
	 * @param bool $bSave Save authorization in cookies.
	 * @param bool $bUpdate Update last login information in DB.
	 * @param string|null $applicationId An application password ID.
	 * @return bool
	 */
	public function Authorize($id, $bSave = false, $bUpdate = true, $applicationId = null)
	{
		/** @global CMain $APPLICATION */
		global $DB, $APPLICATION;

		$arUser = $this->UpdateSessionData($id, $applicationId);

		if($arUser !== false)
		{
			self::$CURRENT_USER = false;
			$this->justAuthorized = true;
			$this->SetControllerAdmin(false);

			//sometimes we don't need to update db (REST)
			if($bUpdate)
			{
				$tz = '';
				if(CTimeZone::Enabled())
				{
					if(!CTimeZone::IsAutoTimeZone(trim($arUser["AUTO_TIME_ZONE"])) || CTimeZone::GetCookieValue() !== null)
					{
						$offset = CTimeZone::GetOffset();
						$tz = ', TIME_ZONE_OFFSET = '.$offset;
						$_SESSION["SESS_AUTH"]["TIME_ZONE_OFFSET"] = $offset;
					}
				}

				$bxUid = '';
				if (!empty($_COOKIE['BX_USER_ID']) && preg_match('/^[0-9a-f]{32}$/', $_COOKIE['BX_USER_ID']))
				{
					if ($_COOKIE['BX_USER_ID'] != $arUser['BX_USER_ID'])
					{
						// save new bxuid value
						$bxUid = ", BX_USER_ID = '".$_COOKIE['BX_USER_ID']."'";

						$arUser['BX_USER_ID'] = $_COOKIE['BX_USER_ID'];
						$_SESSION["SESS_AUTH"]["BX_USER_ID"] = $_COOKIE['BX_USER_ID'];
					}
				}

				$DB->Query("
					UPDATE b_user SET
						STORED_HASH = NULL,
						LAST_LOGIN = ".$DB->GetNowFunction().",
						TIMESTAMP_X = TIMESTAMP_X,
						LOGIN_ATTEMPTS = 0
						".$tz."
						".$bxUid."
					WHERE
						ID=".$arUser["ID"]
				);

				if($applicationId === null && ($bSave || COption::GetOptionString("main", "auth_multisite", "N") == "Y"))
				{
					$response = Main\Context::getCurrent()->getResponse();

					$hash = $this->GetSessionHash();
					$secure = (COption::GetOptionString("main", "use_secure_password_cookies", "N")=="Y" && CMain::IsHTTPS());

					if($bSave)
					{
						$period = time()+60*60*24*30*60;
						$spread = Main\Web\Cookie::SPREAD_SITES | Main\Web\Cookie::SPREAD_DOMAIN;
					}
					else
					{
						$period = 0;
						$spread = Main\Web\Cookie::SPREAD_SITES;
					}

					$cookie = new Bitrix\Main\Web\Cookie("UIDH", $hash, $period);

					$cookie->setSecure($secure)
						->setSpread($spread)
						->setHttpOnly(true);

					$response->addCookie($cookie);

					$cookie = new Bitrix\Main\Web\Cookie("UIDL", $arUser["LOGIN"], $period);

					$cookie->setSecure($secure)
						->setSpread($spread)
						->setHttpOnly(true);

					$response->addCookie($cookie);

					$stored_id = CUser::CheckStoredHash($arUser["ID"], $hash);
					if($stored_id)
					{
						$DB->Query(
							"UPDATE b_user_stored_auth SET
								LAST_AUTH=".$DB->CurrentTimeFunction().",
								".($this->bLoginByHash?"":"TEMP_HASH='".($bSave?"N":"Y")."', ")."
								IP_ADDR='".sprintf("%u", ip2long($_SERVER["REMOTE_ADDR"]))."'
							WHERE ID=".$stored_id
						);
					}
					else
					{
						$arFields = array(
							'USER_ID'=>$arUser["ID"],
							'~DATE_REG'=>$DB->CurrentTimeFunction(),
							'~LAST_AUTH'=>$DB->CurrentTimeFunction(),
							'TEMP_HASH'=>($bSave?"N":"Y"),
							'~IP_ADDR'=>sprintf("%u", ip2long($_SERVER["REMOTE_ADDR"])),
							'STORED_HASH'=>$hash
						);
						$stored_id = $DB->Add("b_user_stored_auth", $arFields);
					}
					$_SESSION["SESS_AUTH"]["STORED_AUTH_ID"] = $stored_id;
				}

				if(COption::GetOptionString("main", "event_log_login_success", "N") === "Y")
					CEventLog::Log("SECURITY", "USER_AUTHORIZE", "main", $arUser["ID"], $applicationId);
			}

			$this->admin = null;

			$arParams = array(
				"user_fields" => $arUser,
				"save" => $bSave,
				"update" => $bUpdate,
				"applicationId" => $applicationId,
			);

			foreach (GetModuleEvents("main", "OnAfterUserAuthorize", true) as $arEvent)
				ExecuteModuleEventEx($arEvent, array($arParams));

			foreach (GetModuleEvents("main", "OnUserLogin", true) as $arEvent)
				ExecuteModuleEventEx($arEvent, array($_SESSION["SESS_AUTH"]["USER_ID"], $arParams));

			Main\Composite\Engine::onUserLogin();

			return true;
		}
		return false;
	}

	public function GetSessionHash()
	{
		if($_SESSION["SESS_AUTH"]["SESSION_HASH"] == '')
		{
			$_SESSION["SESS_AUTH"]["SESSION_HASH"] = md5(CMain::GetServerUniqID().uniqid("", true));
		}
		return $_SESSION["SESS_AUTH"]["SESSION_HASH"];
	}

	/** @deprecated */
	public function GetPasswordHash($PASSWORD_HASH)
	{
		$add = COption::GetOptionString("main", "pwdhashadd", "");
		if($add == '')
		{
			$add = md5(uniqid(rand(), true));
			COption::SetOptionString("main", "pwdhashadd", $add);
		}

		return md5($add.$PASSWORD_HASH);
	}

	/** @deprecated */
	public function SavePasswordHash()
	{
		/** @global CMain $APPLICATION */
		global $APPLICATION;

		$hash = $this->GetSessionHash();
		$time = time()+60*60*24*30*60;
		$secure = (COption::GetOptionString("main", "use_secure_password_cookies", "N")=="Y" && CMain::IsHTTPS());
		$spread = (COption::GetOptionString("main", "auth_multisite", "N") == "Y"? (Main\Web\Cookie::SPREAD_SITES | Main\Web\Cookie::SPREAD_DOMAIN) : Main\Web\Cookie::SPREAD_DOMAIN);

		$cookie = new Main\Web\Cookie("UIDH", $hash, $time);

		$cookie->setSpread($spread)
			->setSecure($secure)
			->setHttpOnly(true);

		Main\Context::getCurrent()->getResponse()->addCookie($cookie);
	}

	/**
	 * Authenticates the user and then authorizes him
	 * @param string $login
	 * @param string $password
	 * @param string $remember
	 * @param string $password_original
	 * @return array|bool
	 */
	public function Login($login, $password, $remember="N", $password_original="Y")
	{
		/** @global CMain $APPLICATION */
		global $DB, $APPLICATION;

		$result_message = true;
		$user_id = 0;
		$applicationId = null;
		$applicationPassId = null;

		$arParams = array(
			"LOGIN" => &$login,
			"PASSWORD" => &$password,
			"REMEMBER" => &$remember,
			"PASSWORD_ORIGINAL" => &$password_original,
		);

		unset($_SESSION["SESS_OPERATIONS"]);
		unset($_SESSION["MODULE_PERMISSIONS"]);
		$_SESSION["BX_LOGIN_NEED_CAPTCHA"] = false;

		$bOk = true;
		$APPLICATION->ResetException();
		foreach(GetModuleEvents("main", "OnBeforeUserLogin", true) as $arEvent)
		{
			if(ExecuteModuleEventEx($arEvent, array(&$arParams))===false)
			{
				if($err = $APPLICATION->GetException())
				{
					$result_message = array("MESSAGE"=>$err->GetString()."<br>", "TYPE"=>"ERROR");
				}
				else
				{
					$APPLICATION->ThrowException("Unknown login error");
					$result_message = array("MESSAGE"=>"Unknown login error"."<br>", "TYPE"=>"ERROR");
				}

				$bOk = false;
				break;
			}
		}

		if($bOk)
		{
			//external authentication
			foreach(GetModuleEvents("main", "OnUserLoginExternal", true) as $arEvent)
			{
				$user_id = ExecuteModuleEventEx($arEvent, array(&$arParams));

				if(isset($arParams["RESULT_MESSAGE"]))
				{
					$result_message = $arParams["RESULT_MESSAGE"];
				}
				if($user_id > 0)
				{
					break;
				}
			}

			if($user_id <= 0)
			{
				//internal authentication OR application password for external user

				$user_id = self::LoginInternal($arParams, $result_message, $applicationId, $applicationPassId);

				if($user_id <= 0)
				{
					//no user found by login - try to find an external user
					foreach(GetModuleEvents("main", "OnFindExternalUser", true) as $arEvent)
					{
						if(($external_user_id = intval(ExecuteModuleEventEx($arEvent, array($arParams["LOGIN"])))) > 0)
						{
							//external user authentication
							//let's try to find application password for the external user
							if(($appPassword = ApplicationPasswordTable::findPassword($external_user_id, $arParams["PASSWORD"], ($arParams["PASSWORD_ORIGINAL"] == "Y"))) !== false)
							{
								//bingo, the user has the application password
								$user_id = $external_user_id;
								$applicationId = $appPassword["APPLICATION_ID"];
								$applicationPassId = $appPassword["ID"];
							}
							break;
						}
					}
				}

				if($user_id <= 0 && $result_message === true)
				{
					$APPLICATION->ThrowException(GetMessage("WRONG_LOGIN"));
					$result_message = array("MESSAGE"=>GetMessage("WRONG_LOGIN")."<br>", "TYPE"=>"ERROR", "ERROR_TYPE" => "LOGIN");
				}
			}
		}

		// All except Admin
		if ($user_id > 1 && $arParams["CONTROLLER_ADMIN"] !== "Y")
		{
			if(!static::CheckUsersCount($user_id))
			{
				$user_id = 0;
				$APPLICATION->ThrowException(GetMessage("LIMIT_USERS_COUNT"));
				$result_message = array(
					"MESSAGE" => GetMessage("LIMIT_USERS_COUNT")."<br>",
					"TYPE" => "ERROR",
				);
			}
		}

		$arParams["USER_ID"] = $user_id;

		$doAuthorize = true;

		if($user_id > 0)
		{
			if($applicationId === null && CModule::IncludeModule("security"))
			{
				/*
				MFA can allow or disallow authorization.
				Allowed if:
				- OTP is not active for the user;
				- correct "OTP" in the $arParams (filled by the OnBeforeUserLogin event handler).
				Disallowed if:
				- OTP is not provided;
				- OTP is not correct.
				When authorization is disallowed the OTP form will be shown on the next hit.
				Note: there is no MFA check for an application password.
				*/

				$arParams["CAPTCHA_WORD"] = $_REQUEST["captcha_word"];
				$arParams["CAPTCHA_SID"] = $_REQUEST["captcha_sid"];

				$doAuthorize = \Bitrix\Security\Mfa\Otp::verifyUser($arParams);
			}

			if($doAuthorize)
			{
				$this->Authorize($user_id, ($arParams["REMEMBER"] == "Y"), true, $applicationId);

				if($applicationPassId !== null)
				{
					//update usage statistics for the application
					Main\Authentication\ApplicationPasswordTable::update($applicationPassId, array(
						'DATE_LOGIN' => new Main\Type\DateTime(),
						'LAST_IP' => $_SERVER["REMOTE_ADDR"],
					));
				}
			}
			else
			{
				$result_message = false;
			}

			if($applicationId === null && $arParams["LOGIN"] <> '')
			{
				//the cookie is for authentication forms mostly, does not make sense for applications
				$cookie = new Bitrix\Main\Web\Cookie("LOGIN", $arParams["LOGIN"], time()+60*60*24*30*60);
				Main\Context::getCurrent()->getResponse()->addCookie($cookie);
			}
		}

		$arParams["RESULT_MESSAGE"] = $result_message;

		$APPLICATION->ResetException();
		foreach(GetModuleEvents("main", "OnAfterUserLogin", true) as $arEvent)
			ExecuteModuleEventEx($arEvent, array(&$arParams));

		if($doAuthorize == true && $result_message !== true && (COption::GetOptionString("main", "event_log_login_fail", "N") === "Y"))
			CEventLog::Log("SECURITY", "USER_LOGIN", "main", $login, $result_message["MESSAGE"]);

		return $arParams["RESULT_MESSAGE"];
	}

	/**
	 * Internal authentication by login and password.
	 * @param array $arParams
	 * @param array|bool $result_message
	 * @param string|null $applicationId
	 * @param string|null $applicationPassId
	 * @return int User ID on success or 0 on failure. Additionally, $result_message will hold an error.
	 */
	public static function LoginInternal(&$arParams, &$result_message = true, &$applicationId = null, &$applicationPassId = null)
	{
		global $DB, $APPLICATION;

		$user_id = 0;

		$strSql =
			"SELECT U.ID, U.LOGIN, U.ACTIVE, U.PASSWORD, U.LOGIN_ATTEMPTS, U.CONFIRM_CODE, U.EMAIL ".
			"FROM b_user U  ".
			"WHERE U.LOGIN='".$DB->ForSQL($arParams["LOGIN"])."' ";

		if(isset($arParams["EXTERNAL_AUTH_ID"]) && $arParams["EXTERNAL_AUTH_ID"] <> '')
		{
			//external user
			$strSql .= " AND EXTERNAL_AUTH_ID='".$DB->ForSql($arParams["EXTERNAL_AUTH_ID"])."'";
		}
		else
		{
			//internal user (by default)
			$strSql .= " AND (EXTERNAL_AUTH_ID IS NULL OR EXTERNAL_AUTH_ID='') ";
		}

		$result = $DB->Query($strSql);

		if(($arUser = $result->Fetch()))
		{
			if(strlen($arUser["PASSWORD"]) > 32)
			{
				$salt = substr($arUser["PASSWORD"], 0, strlen($arUser["PASSWORD"]) - 32);
				$db_password = substr($arUser["PASSWORD"], -32);
			}
			else
			{
				$salt = "";
				$db_password = $arUser["PASSWORD"];
			}

			$user_password_no_otp = "";
			if($arParams["PASSWORD_ORIGINAL"] == "Y")
			{
				$user_password = md5($salt.$arParams["PASSWORD"]);
				if($arParams["OTP"] <> '')
				{
					$user_password_no_otp = md5($salt.substr($arParams["PASSWORD"], 0, -6));
				}
			}
			else
			{
				if(strlen($arParams["PASSWORD"]) > 32)
				{
					$user_password = substr($arParams["PASSWORD"], -32);
				}
				else
				{
					$user_password = $arParams["PASSWORD"];
				}
			}

			$passwordCorrect = ($db_password === $user_password || ($arParams["OTP"] <> '' && $db_password === $user_password_no_otp));

			if($db_password === $user_password)
			{
				//this password has no added otp for sure
				$arParams["OTP"] = '';
			}

			if(!$passwordCorrect)
			{
				//let's try to find application password
				if(($appPassword = ApplicationPasswordTable::findPassword($arUser["ID"], $arParams["PASSWORD"], ($arParams["PASSWORD_ORIGINAL"] == "Y"))) !== false)
				{
					$passwordCorrect = true;
					$applicationId = $appPassword["APPLICATION_ID"];
					$applicationPassId = $appPassword["ID"];
				}
			}

			$arPolicy = CUser::GetGroupPolicy($arUser["ID"]);
			$pol_login_attempts = intval($arPolicy["LOGIN_ATTEMPTS"]);
			$usr_login_attempts = intval($arUser["LOGIN_ATTEMPTS"]) + 1;
			if($pol_login_attempts > 0 && $usr_login_attempts > $pol_login_attempts)
			{
				$_SESSION["BX_LOGIN_NEED_CAPTCHA"] = true;
				if(!$APPLICATION->CaptchaCheckCode($_REQUEST["captcha_word"], $_REQUEST["captcha_sid"]))
				{
					$passwordCorrect = false;
				}
			}

			if($passwordCorrect)
			{
				if($salt == '' && $arParams["PASSWORD_ORIGINAL"] == "Y" && $applicationId === null)
				{
					$salt = randString(8, array(
						"abcdefghijklnmopqrstuvwxyz",
						"ABCDEFGHIJKLNMOPQRSTUVWXYZ",
						"0123456789",
						",.<>/?;:[]{}\\|~!@#\$%^&*()-_+=",
					));
					$new_password = $salt.md5($salt.$arParams["PASSWORD"]);
					$DB->Query("UPDATE b_user SET PASSWORD='".$DB->ForSQL($new_password)."', TIMESTAMP_X = TIMESTAMP_X WHERE ID = ".intval($arUser["ID"]));
				}

				if($arUser["ACTIVE"] == "Y")
				{
					$user_id = $arUser["ID"];

					//update digest hash for http digest authorization
					if($arParams["PASSWORD_ORIGINAL"] == "Y" && $applicationId === null && COption::GetOptionString('main', 'use_digest_auth', 'N') == 'Y')
					{
						CUser::UpdateDigest($arUser["ID"], $arParams["PASSWORD"]);
					}
				}
				elseif($arUser["CONFIRM_CODE"] <> '')
				{
					//unconfirmed registration
					$message = GetMessage("MAIN_LOGIN_EMAIL_CONFIRM", array("#EMAIL#" => $arUser["EMAIL"]));
					$APPLICATION->ThrowException($message);
					$result_message = array("MESSAGE" => $message."<br>", "TYPE" => "ERROR");
				}
				else
				{
					$APPLICATION->ThrowException(GetMessage("LOGIN_BLOCK"));
					$result_message = array("MESSAGE" => GetMessage("LOGIN_BLOCK")."<br>", "TYPE" => "ERROR");
				}
			}
			else
			{
				$DB->Query("UPDATE b_user SET LOGIN_ATTEMPTS = ".$usr_login_attempts.", TIMESTAMP_X = TIMESTAMP_X WHERE ID = ".intval($arUser["ID"]));
				$APPLICATION->ThrowException(GetMessage("WRONG_LOGIN"));
				$result_message = array("MESSAGE" => GetMessage("WRONG_LOGIN")."<br>", "TYPE" => "ERROR", "ERROR_TYPE" => "LOGIN");
			}
		}
		return $user_id;
	}

	protected static function CheckUsersCount($user_id)
	{
		$limitUsersCount = intval(COption::GetOptionInt("main", "PARAM_MAX_USERS", 0));
		if ($limitUsersCount > 0)
		{
			$by = "ID";
			$order = "ASC";
			$arFilter = array("LAST_LOGIN_1" => ConvertTimeStamp());

			//Intranet users only
			$intranet = IsModuleInstalled("intranet");
			if ($intranet)
			{
				$arFilter["!=UF_DEPARTMENT"] = false;
			}

			$rsUsers = CUser::GetList($by, $order, $arFilter, array("FIELDS" => array("ID")));

			while ($user = $rsUsers->fetch())
			{
				if ($user["ID"] == $user_id)
				{
					$limitUsersCount = 1;
					break;
				}
				$limitUsersCount--;
			}

			if ($limitUsersCount <= 0)
			{
				if($intranet)
				{
					//only intranet users are NOT allowed
					$currUserRs = CUser::GetByID($user_id);
					if($currUser = $currUserRs->Fetch())
					{
						if(!empty($currUser["UF_DEPARTMENT"]))
						{
							return false;
						}
					}
				}
				else
				{
					return false;
				}
			}
		}
		return true;
	}

	public function LoginByOtp($otp, $remember_otp = "N", $captcha_word = "", $captcha_sid = "")
	{
		if(!CModule::IncludeModule("security") || !\Bitrix\Security\Mfa\Otp::isOtpRequired())
		{
			return array("MESSAGE" => GetMessage("USER_LOGIN_OTP_ERROR")."<br>", "TYPE" => "ERROR");
		}

		$userParams = \Bitrix\Security\Mfa\Otp::getDeferredParams();

		$userParams["OTP"] = $otp;
		$userParams["OTP_REMEMBER"] = ($remember_otp === "Y");
		$userParams["CAPTCHA_WORD"] = $captcha_word;
		$userParams["CAPTCHA_SID"] = $captcha_sid;

		if(!\Bitrix\Security\Mfa\Otp::verifyUser($userParams))
		{
			return array("MESSAGE" => GetMessage("USER_LOGIN_OTP_INCORRECT")."<br>", "TYPE" => "ERROR");
		}

		$this->Authorize($userParams["USER_ID"], ($userParams["REMEMBER"] == "Y"));
		return true;
	}

	public function AuthorizeWithOtp($user_id)
	{
		$doAuthorize = true;

		if(CModule::IncludeModule("security"))
		{
			/*
			MFA can allow or disallow authorization.
			Allowed only if:
			- OTP is not active for the user;
			When authorization is disallowed the OTP form will be shown on the next hit.
			*/
			$doAuthorize = \Bitrix\Security\Mfa\Otp::verifyUser(array("USER_ID" => $user_id));
		}

		if($doAuthorize)
		{
			return $this->Authorize($user_id);
		}

		return false;
	}

	public function ChangePassword($LOGIN, $CHECKWORD, $PASSWORD, $CONFIRM_PASSWORD, $SITE_ID=false, $captcha_word = "", $captcha_sid = 0, $authActions = true)
	{
		/** @global CMain $APPLICATION */
		global $DB, $APPLICATION;

		$result_message = array("MESSAGE"=>GetMessage('PASSWORD_CHANGE_OK')."<br>", "TYPE"=>"OK");

		$arParams = array(
			"LOGIN"			=>	&$LOGIN,
			"CHECKWORD"			=>	&$CHECKWORD,
			"PASSWORD" 		=>	&$PASSWORD,
			"CONFIRM_PASSWORD" =>	&$CONFIRM_PASSWORD,
			"SITE_ID"		=>	&$SITE_ID
			);

		$APPLICATION->ResetException();
		$bOk = true;
		foreach(GetModuleEvents("main", "OnBeforeUserChangePassword", true) as $arEvent)
		{
			if(ExecuteModuleEventEx($arEvent, array(&$arParams))===false)
			{
				if($err = $APPLICATION->GetException())
					$result_message = array("MESSAGE"=>$err->GetString()."<br>", "TYPE"=>"ERROR");

				$bOk = false;
				break;
			}
		}

		if($bOk && COption::GetOptionString("main", "captcha_restoring_password", "N") == "Y")
		{
			if (!($APPLICATION->CaptchaCheckCode($captcha_word, $captcha_sid)))
			{
				$result_message = array("MESSAGE"=>GetMessage("main_user_captcha_error")."<br>", "TYPE"=>"ERROR");
				$bOk = false;
			}
		}

		if($bOk)
		{
			$strAuthError = "";
			if(strlen($arParams["LOGIN"])<3)
				$strAuthError .= GetMessage('MIN_LOGIN')."<br>";
			if($arParams["PASSWORD"]<>$arParams["CONFIRM_PASSWORD"])
				$strAuthError .= GetMessage('WRONG_CONFIRMATION')."<br>";

			if($strAuthError <> '')
				return array("MESSAGE"=>$strAuthError, "TYPE"=>"ERROR");

			CTimeZone::Disable();
			$db_check = $DB->Query(
				"SELECT ID, LID, CHECKWORD, ".$DB->DateToCharFunction("CHECKWORD_TIME", "FULL")." as CHECKWORD_TIME ".
				"FROM b_user ".
				"WHERE LOGIN='".$DB->ForSql($arParams["LOGIN"], 0)."' AND (EXTERNAL_AUTH_ID IS NULL OR EXTERNAL_AUTH_ID='')");
			CTimeZone::Enable();

			if(!($res = $db_check->Fetch()))
				return array("MESSAGE"=>preg_replace("/#LOGIN#/i", htmlspecialcharsbx($arParams["LOGIN"]), GetMessage('LOGIN_NOT_FOUND')), "TYPE"=>"ERROR", "FIELD" => "LOGIN");

			$salt = substr($res["CHECKWORD"], 0, 8);
			if($res["CHECKWORD"] == '' || $res["CHECKWORD"] != $salt.md5($salt.$arParams["CHECKWORD"]))
				return array("MESSAGE"=>preg_replace("/#LOGIN#/i", htmlspecialcharsbx($arParams["LOGIN"]), GetMessage("CHECKWORD_INCORRECT"))."<br>", "TYPE"=>"ERROR", "FIELD"=>"CHECKWORD");

			$arPolicy = CUser::GetGroupPolicy($res["ID"]);

			$passwordErrors = self::CheckPasswordAgainstPolicy($arParams["PASSWORD"], $arPolicy);
			if (!empty($passwordErrors))
			{
				return array(
					"MESSAGE" => implode("<br>", $passwordErrors)."<br>",
					"TYPE" => "ERROR"
				);
			}

			$site_format = CSite::GetDateFormat();
			if(time()-$arPolicy["CHECKWORD_TIMEOUT"]*60 > MakeTimeStamp($res["CHECKWORD_TIME"], $site_format))
				return array("MESSAGE"=>preg_replace("/#LOGIN#/i", htmlspecialcharsbx($arParams["LOGIN"]), GetMessage("CHECKWORD_EXPIRE"))."<br>", "TYPE"=>"ERROR", "FIELD"=>"CHECKWORD_EXPIRE");

			if($arParams["SITE_ID"] === false)
			{
				if(defined("ADMIN_SECTION") && ADMIN_SECTION===true)
					$arParams["SITE_ID"] = CSite::GetDefSite($res["LID"]);
				else
					$arParams["SITE_ID"] = SITE_ID;
			}

			// change the password
			$ID = $res["ID"];
			$obUser = new CUser;
			$res = $obUser->Update($ID, array("PASSWORD"=>$arParams["PASSWORD"]), $authActions);
			if(!$res && $obUser->LAST_ERROR <> '')
				return array("MESSAGE"=>$obUser->LAST_ERROR."<br>", "TYPE"=>"ERROR");
			CUser::SendUserInfo($ID, $arParams["SITE_ID"], GetMessage('CHANGE_PASS_SUCC'), true, 'USER_PASS_CHANGED');
		}

		return $result_message;
	}

	public static function CheckPasswordAgainstPolicy($password, $arPolicy)
	{
		$errors = array();

		$password_min_length = intval($arPolicy["PASSWORD_LENGTH"]);
		if($password_min_length <= 0)
			$password_min_length = 6;
		if(strlen($password) < $password_min_length)
			$errors[] = GetMessage("MAIN_FUNCTION_REGISTER_PASSWORD_LENGTH", array("#LENGTH#" => $arPolicy["PASSWORD_LENGTH"]));

		if(($arPolicy["PASSWORD_UPPERCASE"] === "Y") && !preg_match("/[A-Z]/", $password))
			$errors[] = GetMessage("MAIN_FUNCTION_REGISTER_PASSWORD_UPPERCASE");

		if(($arPolicy["PASSWORD_LOWERCASE"] === "Y") && !preg_match("/[a-z]/", $password))
			$errors[] = GetMessage("MAIN_FUNCTION_REGISTER_PASSWORD_LOWERCASE");

		if(($arPolicy["PASSWORD_DIGITS"] === "Y") && !preg_match("/[0-9]/", $password))
			$errors[] = GetMessage("MAIN_FUNCTION_REGISTER_PASSWORD_DIGITS");

		if(($arPolicy["PASSWORD_PUNCTUATION"] === "Y") && !preg_match("/[,.<>\\/?;:'\"[\\]\\{\\}\\\\|`~!@#\$%^&*()_+=-]/", $password))
			$errors[] = GetMessage("MAIN_FUNCTION_REGISTER_PASSWORD_PUNCTUATION");

		return $errors;
	}

	/**
	 * Sends a profile information to email
	 */
	public static function SendUserInfo($ID, $SITE_ID, $MSG, $bImmediate=false, $eventName="USER_INFO")
	{
		global $DB;

		// change CHECKWORD
		$ID = intval($ID);
		$salt = randString(8);
		$checkword = md5(CMain::GetServerUniqID().uniqid());
		$strSql = "UPDATE b_user SET ".
			"	CHECKWORD = '".$salt.md5($salt.$checkword)."', ".
			"	CHECKWORD_TIME = ".$DB->CurrentTimeFunction().", ".
			"	LID = '".$DB->ForSql($SITE_ID, 2)."', ".
			"   TIMESTAMP_X = TIMESTAMP_X ".
			"WHERE ID = '".$ID."'".
			"	AND (EXTERNAL_AUTH_ID IS NULL OR EXTERNAL_AUTH_ID='') ";

		$DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);

		$res = $DB->Query(
			"SELECT u.* ".
			"FROM b_user u ".
			"WHERE ID='".$ID."'".
			"	AND (EXTERNAL_AUTH_ID IS NULL OR EXTERNAL_AUTH_ID='') "
		);

		if($res_array = $res->Fetch())
		{
			$event = new CEvent;
			$arFields = array(
				"USER_ID"=>$res_array["ID"],
				"STATUS"=>($res_array["ACTIVE"]=="Y"?GetMessage("STATUS_ACTIVE"):GetMessage("STATUS_BLOCKED")),
				"MESSAGE"=>$MSG,
				"LOGIN"=>$res_array["LOGIN"],
				"URL_LOGIN"=>urlencode($res_array["LOGIN"]),
				"CHECKWORD"=>$checkword,
				"NAME"=>$res_array["NAME"],
				"LAST_NAME"=>$res_array["LAST_NAME"],
				"EMAIL"=>$res_array["EMAIL"]
			);

			$arParams = array(
				"FIELDS" => &$arFields,
				"USER_FIELDS" => $res_array,
				"SITE_ID" => &$SITE_ID,
				"EVENT_NAME" => &$eventName,
			);

			foreach (GetModuleEvents("main", "OnSendUserInfo", true) as $arEvent)
				ExecuteModuleEventEx($arEvent, array(&$arParams));

			if (!$bImmediate)
				$event->Send($eventName, $SITE_ID, $arFields, "Y", "", array(), $res_array["LANGUAGE_ID"]);
			else
				$event->SendImmediate($eventName, $SITE_ID, $arFields, "Y", "", array(), $res_array["LANGUAGE_ID"]);
		}
	}

	public static function SendPassword($LOGIN, $EMAIL, $SITE_ID = false, $captcha_word = "", $captcha_sid = 0)
	{
		/** @global CMain $APPLICATION */
		global $DB, $APPLICATION;

		$arParams = array(
			"LOGIN" => $LOGIN,
			"EMAIL" => $EMAIL,
			"SITE_ID" => $SITE_ID
		);

		$result_message = array("MESSAGE"=>GetMessage('ACCOUNT_INFO_SENT')."<br>", "TYPE"=>"OK");
		$APPLICATION->ResetException();
		$bOk = true;
		foreach(GetModuleEvents("main", "OnBeforeUserSendPassword", true) as $arEvent)
		{
			if(ExecuteModuleEventEx($arEvent, array(&$arParams))===false)
			{
				if($err = $APPLICATION->GetException())
					$result_message = array("MESSAGE"=>$err->GetString()."<br>", "TYPE"=>"ERROR");

				$bOk = false;
				break;
			}
		}

		if($bOk && COption::GetOptionString("main", "captcha_restoring_password", "N") == "Y")
		{
			if (!($APPLICATION->CaptchaCheckCode($captcha_word, $captcha_sid)))
			{
				$result_message = array("MESSAGE"=>GetMessage("main_user_captcha_error")."<br>", "TYPE"=>"ERROR");
				$bOk = false;
			}
		}

		if($bOk)
		{
			$f = false;
			if($arParams["LOGIN"] <> '' || $arParams["EMAIL"] <> '')
			{
				$confirmation = (COption::GetOptionString("main", "new_user_registration_email_confirmation", "N") == "Y");

				$strSql = "";
				if($arParams["LOGIN"] <> '')
				{
					$strSql =
						"SELECT ID, LID, ACTIVE, CONFIRM_CODE, LOGIN, EMAIL, NAME, LAST_NAME, LANGUAGE_ID ".
						"FROM b_user u ".
						"WHERE LOGIN='".$DB->ForSQL($arParams["LOGIN"])."' ".
						"	AND (ACTIVE='Y' OR NOT(CONFIRM_CODE IS NULL OR CONFIRM_CODE='')) ".
						"	AND (EXTERNAL_AUTH_ID IS NULL OR EXTERNAL_AUTH_ID='') ";
				}
				if($arParams["EMAIL"] <> '')
				{
					if($strSql <> '')
					{
						$strSql .= "\nUNION\n";
					}
					$strSql .=
						"SELECT ID, LID, ACTIVE, CONFIRM_CODE, LOGIN, EMAIL, NAME, LAST_NAME, LANGUAGE_ID ".
						"FROM b_user u ".
						"WHERE EMAIL='".$DB->ForSQL($arParams["EMAIL"])."' ".
						"	AND (ACTIVE='Y' OR NOT(CONFIRM_CODE IS NULL OR CONFIRM_CODE='')) ".
						"	AND (EXTERNAL_AUTH_ID IS NULL OR EXTERNAL_AUTH_ID='') ";
				}
				$res = $DB->Query($strSql);

				while($arUser = $res->Fetch())
				{
					if($arParams["SITE_ID"]===false)
					{
						if(defined("ADMIN_SECTION") && ADMIN_SECTION===true)
							$arParams["SITE_ID"] = CSite::GetDefSite($arUser["LID"]);
						else
							$arParams["SITE_ID"] = SITE_ID;
					}

					if($arUser["ACTIVE"] == "Y")
					{
						CUser::SendUserInfo($arUser["ID"], $arParams["SITE_ID"], GetMessage("INFO_REQ"), true, 'USER_PASS_REQUEST');
						$f = true;
					}
					elseif($confirmation)
					{
						//unconfirmed registration - resend confirmation email
						$arFields = array(
							"USER_ID" => $arUser["ID"],
							"LOGIN" => $arUser["LOGIN"],
							"EMAIL" => $arUser["EMAIL"],
							"NAME" => $arUser["NAME"],
							"LAST_NAME" => $arUser["LAST_NAME"],
							"CONFIRM_CODE" => $arUser["CONFIRM_CODE"],
							"USER_IP" => $_SERVER["REMOTE_ADDR"],
							"USER_HOST" => @gethostbyaddr($_SERVER["REMOTE_ADDR"]),
						);

						$event = new CEvent;
						$event->SendImmediate("NEW_USER_CONFIRM", $arParams["SITE_ID"], $arFields, "Y", "", array(), $arUser["LANGUAGE_ID"]);

						$result_message = array("MESSAGE"=>GetMessage("MAIN_SEND_PASS_CONFIRM")."<br>", "TYPE"=>"OK");
						$f = true;
					}

					if(COption::GetOptionString("main", "event_log_password_request", "N") === "Y")
					{
						CEventLog::Log("SECURITY", "USER_INFO", "main", $arUser["ID"]);
					}
				}
			}
			if(!$f)
			{
				return array("MESSAGE"=>GetMessage('DATA_NOT_FOUND')."<br>", "TYPE"=>"ERROR");
			}
		}
		return $result_message;
	}

	public function Register($USER_LOGIN, $USER_NAME, $USER_LAST_NAME, $USER_PASSWORD, $USER_CONFIRM_PASSWORD, $USER_EMAIL, $SITE_ID = false, $captcha_word = "", $captcha_sid = 0, $bSkipConfirm = false)
	{
		/**
		 * @global CMain $APPLICATION
		 * @global CUserTypeManager $USER_FIELD_MANAGER
		 */
		global $APPLICATION, $DB, $USER_FIELD_MANAGER;

		$APPLICATION->ResetException();
		if(defined("ADMIN_SECTION") && ADMIN_SECTION===true && $SITE_ID!==false)
		{
			$APPLICATION->ThrowException(GetMessage("MAIN_FUNCTION_REGISTER_NA_INADMIN"));
			return array("MESSAGE"=>GetMessage("MAIN_FUNCTION_REGISTER_NA_INADMIN"), "TYPE"=>"ERROR");
		}

		$strError = "";

		if (COption::GetOptionString("main", "captcha_registration", "N") == "Y")
		{
			if (!($APPLICATION->CaptchaCheckCode($captcha_word, $captcha_sid)))
			{
				$strError .= GetMessage("MAIN_FUNCTION_REGISTER_CAPTCHA")."<br>";
			}
		}

		if($strError)
		{
			if(COption::GetOptionString("main", "event_log_register_fail", "N") === "Y")
			{
				CEventLog::Log("SECURITY", "USER_REGISTER_FAIL", "main", false, $strError);
			}

			$APPLICATION->ThrowException($strError);
			return array("MESSAGE"=>$strError, "TYPE"=>"ERROR");
		}

		if($SITE_ID===false)
			$SITE_ID = SITE_ID;

		$checkword = md5(CMain::GetServerUniqID().uniqid());
		$bConfirmReq = !$bSkipConfirm && (COption::GetOptionString("main", "new_user_registration_email_confirmation", "N") == "Y" && COption::GetOptionString("main", "new_user_email_required", "Y") <> "N");
		$arFields = array(
			"LOGIN" => $USER_LOGIN,
			"NAME" => $USER_NAME,
			"LAST_NAME" => $USER_LAST_NAME,
			"PASSWORD" => $USER_PASSWORD,
			"CHECKWORD" => $checkword,
			"~CHECKWORD_TIME" => $DB->CurrentTimeFunction(),
			"CONFIRM_PASSWORD" => $USER_CONFIRM_PASSWORD,
			"EMAIL" => $USER_EMAIL,
			"ACTIVE" => $bConfirmReq? "N": "Y",
			"CONFIRM_CODE" => $bConfirmReq? randString(8): "",
			"SITE_ID" => $SITE_ID,
			"LANGUAGE_ID" => LANGUAGE_ID,
			"USER_IP" => $_SERVER["REMOTE_ADDR"],
			"USER_HOST" => @gethostbyaddr($_SERVER["REMOTE_ADDR"]),
		);
		$USER_FIELD_MANAGER->EditFormAddFields("USER", $arFields);

		$def_group = COption::GetOptionString("main", "new_user_registration_def_group", "");
		if($def_group!="")
			$arFields["GROUP_ID"] = explode(",", $def_group);

		$bOk = true;
		$result_message = true;
		foreach(GetModuleEvents("main", "OnBeforeUserRegister", true) as $arEvent)
		{
			if(ExecuteModuleEventEx($arEvent, array(&$arFields)) === false)
			{
				if($err = $APPLICATION->GetException())
					$result_message = array("MESSAGE"=>$err->GetString()."<br>", "TYPE"=>"ERROR");
				else
				{
					$APPLICATION->ThrowException("Unknown error");
					$result_message = array("MESSAGE"=>"Unknown error"."<br>", "TYPE"=>"ERROR");
				}

				$bOk = false;
				break;
			}
		}

		$ID = false;
		if($bOk)
		{
			$arFields["LID"] = $arFields["SITE_ID"];
			if($ID = $this->Add($arFields))
			{
				$arFields["USER_ID"] = $ID;

				$arEventFields = $arFields;
				unset($arEventFields["PASSWORD"]);
				unset($arEventFields["CONFIRM_PASSWORD"]);
				unset($arEventFields["~CHECKWORD_TIME"]);

				$event = new CEvent;
				$event->SendImmediate("NEW_USER", $arEventFields["SITE_ID"], $arEventFields);
				if($bConfirmReq)
					$event->SendImmediate("NEW_USER_CONFIRM", $arEventFields["SITE_ID"], $arEventFields);
				$result_message = array("MESSAGE"=>GetMessage("USER_REGISTER_OK"), "TYPE"=>"OK", "ID"=>$ID);
			}
			else
			{
				$APPLICATION->ThrowException($this->LAST_ERROR);
				$result_message = array("MESSAGE"=>$this->LAST_ERROR, "TYPE"=>"ERROR");
			}
		}

		if(is_array($result_message))
		{
			if($result_message["TYPE"] == "OK")
			{
				if(COption::GetOptionString("main", "event_log_register", "N") === "Y")
				{
					$res_log["user"] = ($USER_NAME != "" || $USER_LAST_NAME != "") ? trim($USER_NAME." ".$USER_LAST_NAME) : $USER_LOGIN;
					CEventLog::Log("SECURITY", "USER_REGISTER", "main", $ID, serialize($res_log));
				}
			}
			else
			{
				if(COption::GetOptionString("main", "event_log_register_fail", "N") === "Y")
				{
					CEventLog::Log("SECURITY", "USER_REGISTER_FAIL", "main", $ID, $result_message["MESSAGE"]);
				}
			}
		}

		//authorize succesfully registered user
		$isAuthorize = false;
		if($ID !== false && $arFields["ACTIVE"] === "Y")
			$isAuthorize = $this->Authorize($ID);

		$agreementId = intval(COption::getOptionString("main", "new_user_agreement", ""));
		if ($agreementId && $isAuthorize)
		{
			$agreementObject = new \Bitrix\Main\UserConsent\Agreement($agreementId);
			if ($agreementObject->isExist() && $agreementObject->isActive() && $_REQUEST["USER_AGREEMENT"] == "Y")
			{
				\Bitrix\Main\UserConsent\Consent::addByContext($agreementId, "main/reg", "register");
			}
		}

		$arFields["RESULT_MESSAGE"] = $result_message;
		foreach (GetModuleEvents("main", "OnAfterUserRegister", true) as $arEvent)
			ExecuteModuleEventEx($arEvent, array(&$arFields));

		return $arFields["RESULT_MESSAGE"];
	}

	public function SimpleRegister($USER_EMAIL, $SITE_ID = false)
	{
		/** @global CMain $APPLICATION */
		global $APPLICATION, $DB;

		$APPLICATION->ResetException();
		if(defined("ADMIN_SECTION") && ADMIN_SECTION===true && $SITE_ID===false)
		{
			$APPLICATION->ThrowException(GetMessage("MAIN_FUNCTION_SIMPLEREGISTER_NA_INADMIN"));
			return array("MESSAGE"=>GetMessage("MAIN_FUNCTION_SIMPLEREGISTER_NA_INADMIN"), "TYPE"=>"ERROR");
		}

		if($SITE_ID===false)
			$SITE_ID = SITE_ID;

		global $REMOTE_ADDR;

		$checkword = md5(CMain::GetServerUniqID().uniqid());
		$arFields = array(
			"CHECKWORD" => $checkword,
			"~CHECKWORD_TIME" => $DB->CurrentTimeFunction(),
			"EMAIL" => $USER_EMAIL,
			"ACTIVE" => "Y",
			"NAME"=>"",
			"LAST_NAME"=>"",
			"USER_IP"=>$REMOTE_ADDR,
			"USER_HOST"=>@gethostbyaddr($REMOTE_ADDR),
			"SITE_ID" => $SITE_ID,
			"LANGUAGE_ID" => LANGUAGE_ID,
		);

		$def_group = COption::GetOptionString("main", "new_user_registration_def_group", "");
		if($def_group!="")
		{
			$arFields["GROUP_ID"] = explode(",", $def_group);
			$arPolicy = $this->GetGroupPolicy($arFields["GROUP_ID"]);
		}
		else
		{
			$arPolicy = $this->GetGroupPolicy(array());
		}
		$password_min_length = intval($arPolicy["PASSWORD_LENGTH"]);
		if($password_min_length <= 0)
			$password_min_length = 6;
		$password_chars = array(
			"abcdefghijklnmopqrstuvwxyz",
			"ABCDEFGHIJKLNMOPQRSTUVWXYZ",
			"0123456789",
		);
		if($arPolicy["PASSWORD_PUNCTUATION"] === "Y")
			$password_chars[] = ",.<>/?;:'\"[]{}\\|`~!@#\$%^&*()-_+=";
		$arFields["PASSWORD"] = $arFields["CONFIRM_PASSWORD"] = randString($password_min_length, $password_chars);

		$bOk = true;
		$result_message = false;
		foreach(GetModuleEvents("main", "OnBeforeUserSimpleRegister", true) as $arEvent)
		{
			if(ExecuteModuleEventEx($arEvent, array(&$arFields)) === false)
			{
				if($err = $APPLICATION->GetException())
					$result_message = array("MESSAGE"=>$err->GetString()."<br>", "TYPE"=>"ERROR");
				else
				{
					$APPLICATION->ThrowException("Unknown error");
					$result_message = array("MESSAGE"=>"Unknown error"."<br>", "TYPE"=>"ERROR");
				}

				$bOk = false;
				break;
			}
		}

		$bRandLogin = false;
		if(!is_set($arFields, "LOGIN"))
		{
			$arFields["LOGIN"] = randString(50);
			$bRandLogin = true;
		}

		$ID = 0;
		if($bOk)
		{
			$arFields["LID"] = $arFields["SITE_ID"];
			$arFields["CHECKWORD"] = $checkword;
			if($ID = $this->Add($arFields))
			{
				if($bRandLogin)
				{
					$this->Update($ID, array("LOGIN"=>"user".$ID));
					$arFields["LOGIN"] = "user".$ID;
				}

				$this->Authorize($ID);

				$event = new CEvent;
				$arFields["USER_ID"] = $ID;

				$arEventFields = $arFields;
				unset($arEventFields["PASSWORD"]);
				unset($arEventFields["CONFIRM_PASSWORD"]);

				$event->SendImmediate("NEW_USER", $arEventFields["SITE_ID"], $arEventFields);
				CUser::SendUserInfo($ID, $arEventFields["SITE_ID"], GetMessage("USER_REGISTERED_SIMPLE"), true);
				$result_message = array("MESSAGE"=>GetMessage("USER_REGISTER_OK"), "TYPE"=>"OK");
			}
			else
				$result_message = array("MESSAGE"=>$this->LAST_ERROR, "TYPE"=>"ERROR");
		}

		if(is_array($result_message))
		{
			if($result_message["TYPE"] == "OK")
			{
				if(COption::GetOptionString("main", "event_log_register", "N") === "Y")
				{
					$res_log["user"] = $arFields["LOGIN"];
					CEventLog::Log("SECURITY", "USER_REGISTER", "main", $ID, serialize($res_log));
				}
			}
			else
			{
				if(COption::GetOptionString("main", "event_log_register_fail", "N") === "Y")
				{
					CEventLog::Log("SECURITY", "USER_REGISTER_FAIL", "main", $ID, $result_message["MESSAGE"]);
				}
			}
		}

		$arFields["RESULT_MESSAGE"] = $result_message;
		foreach(GetModuleEvents("main", "OnAfterUserSimpleRegister", true) as $arEvent)
			ExecuteModuleEventEx($arEvent, array(&$arFields));

		return $arFields["RESULT_MESSAGE"];
	}

	public function IsAuthorized()
	{
		return ($_SESSION["SESS_AUTH"]["AUTHORIZED"]=="Y");
	}

	public function HasNoAccess()
	{
		if (!$this->IsAuthorized())
		{
			return true;
		}

		$filePath = \Bitrix\Main\Context::getCurrent()->getRequest()->getScriptFile();

		return !$this->CanDoFileOperation('fm_view_file', [SITE_ID, $filePath]);
	}

	public function IsJustAuthorized()
	{
		return $this->justAuthorized;
	}

	public function IsJustBecameOnline()
	{
		if(!$_SESSION['SESS_AUTH']['PREV_LAST_ACTIVITY'])
		{
			return true;
		}
		else
		{
			return ($_SESSION['SESS_AUTH']['SET_LAST_ACTIVITY'] - $_SESSION['SESS_AUTH']['PREV_LAST_ACTIVITY']) > Main\UserTable::getSecondsForLimitOnline();
		}
	}

	public function IsAdmin()
	{
		if ($this->admin === null)
		{
			if(
				COption::GetOptionString("main", "controller_member", "N") == "Y"
				&& COption::GetOptionString("main", "~controller_limited_admin", "N") == "Y"
			)
			{
				if(
					isset($_SESSION["SESS_AUTH"])
					&& is_array($_SESSION["SESS_AUTH"])
					&& isset($_SESSION["SESS_AUTH"]["CONTROLLER_ADMIN"])
				)
					$this->admin = ($_SESSION["SESS_AUTH"]["CONTROLLER_ADMIN"] === true);
				else
					$this->admin = false;
			}
			else
			{
				if(
					isset($_SESSION["SESS_AUTH"])
					&& is_array($_SESSION["SESS_AUTH"])
					&& isset($_SESSION["SESS_AUTH"]["ADMIN"])
				)
					$this->admin = ($_SESSION["SESS_AUTH"]["ADMIN"] === true);
				else
					$this->admin = false;
			}
		}
		return $this->admin;
	}

	public function SetControllerAdmin($isAdmin=true)
	{
		$_SESSION["SESS_AUTH"]["CONTROLLER_ADMIN"] = $isAdmin;
	}

	public function Logout()
	{
		/** @global CMain $APPLICATION */
		global $APPLICATION, $DB;

		$USER_ID = $_SESSION["SESS_AUTH"]["USER_ID"];

		$arParams = array(
			"USER_ID" => &$USER_ID
		);

		$APPLICATION->ResetException();
		$bOk = true;
		foreach(GetModuleEvents("main", "OnBeforeUserLogout", true) as $arEvent)
		{
			if(ExecuteModuleEventEx($arEvent, array(&$arParams))===false)
			{
				if(!($APPLICATION->GetException()))
				{
					$APPLICATION->ThrowException("Unknown logout error");
				}

				$bOk = false;
				break;
			}
		}

		if($bOk)
		{
			foreach(GetModuleEvents("main", "OnUserLogout", true) as $arEvent)
				ExecuteModuleEventEx($arEvent, array($USER_ID));

			if($_SESSION["SESS_AUTH"]["STORED_AUTH_ID"]>0)
				$DB->Query("DELETE FROM b_user_stored_auth WHERE ID=".intval($_SESSION["SESS_AUTH"]["STORED_AUTH_ID"]));

			$this->justAuthorized = false;
			$this->admin = null;

			$_SESSION["SESS_AUTH"] = array();
			unset($_SESSION["SESS_AUTH"]);
			unset($_SESSION["SESS_OPERATIONS"]);
			unset($_SESSION["MODULE_PERMISSIONS"]);
			unset($_SESSION["SESS_PWD_HASH_TESTED"]);

			//change session id for security reason after logout
			if(COption::GetOptionString("security", "session", "N") === "Y" && CModule::IncludeModule("security"))
				CSecuritySession::UpdateSessID();
			else
				session_regenerate_id(true);

			$response = Main\Context::getCurrent()->getResponse();
			$spread = (COption::GetOptionString("main", "auth_multisite", "N") == "Y"? (Main\Web\Cookie::SPREAD_SITES | Main\Web\Cookie::SPREAD_DOMAIN) : Main\Web\Cookie::SPREAD_DOMAIN);

			$cookie = new Main\Web\Cookie("UIDH",  "", 0);
			$cookie->setSpread($spread);
			$cookie->setHttpOnly(true);
			$response->addCookie($cookie);

			$cookie = new Main\Web\Cookie("UIDL",  "", 0);
			$cookie->setSpread($spread);
			$cookie->setHttpOnly(true);
			$response->addCookie($cookie);

			Main\Composite\Engine::onUserLogout();
		}

		$arParams["SUCCESS"] = $bOk;
		foreach(GetModuleEvents("main", "OnAfterUserLogout", true) as $arEvent)
			ExecuteModuleEventEx($arEvent, array(&$arParams));

		if(COption::GetOptionString("main", "event_log_logout", "N") === "Y")
			CEventLog::Log("SECURITY", "USER_LOGOUT", "main", $USER_ID);
	}

	public static function GetUserGroup($ID)
	{
		$ID = (int)$ID;
		if (!isset(self::$userGroupCache[$ID]))
		{
			$arr = array();
			$res = static::GetUserGroupEx($ID);
			while ($r = $res->Fetch())
				$arr[] = $r["GROUP_ID"];

			self::$userGroupCache[$ID] = $arr;
		}

		return self::$userGroupCache[$ID];
	}

	public static function GetUserGroupEx($ID)
	{
		global $DB;

		$strSql = "
			SELECT UG.GROUP_ID, G.STRING_ID,
				".$DB->DateToCharFunction("UG.DATE_ACTIVE_FROM", "FULL")." as DATE_ACTIVE_FROM,
				".$DB->DateToCharFunction("UG.DATE_ACTIVE_TO", "FULL")." as DATE_ACTIVE_TO
			FROM b_user_group UG INNER JOIN b_group G ON G.ID=UG.GROUP_ID
			WHERE UG.USER_ID = ".intval($ID)."
				and ((UG.DATE_ACTIVE_FROM IS NULL) OR (UG.DATE_ACTIVE_FROM <= ".$DB->CurrentTimeFunction()."))
				and ((UG.DATE_ACTIVE_TO IS NULL) OR (UG.DATE_ACTIVE_TO >= ".$DB->CurrentTimeFunction()."))
				and G.ACTIVE = 'Y'
			UNION SELECT 2, 'everyone', NULL, NULL ".(strtoupper($DB->type) == "ORACLE"? " FROM dual " : "");

		$res = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);

		return $res;
	}

	public static function GetUserGroupList($ID)
	{
		global $DB;

		$strSql = "
			SELECT
				UG.GROUP_ID,
				".$DB->DateToCharFunction("UG.DATE_ACTIVE_FROM", "FULL")." as DATE_ACTIVE_FROM,
				".$DB->DateToCharFunction("UG.DATE_ACTIVE_TO", "FULL")." as DATE_ACTIVE_TO
			FROM
				b_user_group UG
			WHERE
				UG.USER_ID = ".intval($ID)."
			UNION SELECT 2, NULL, NULL ".(strtoupper($DB->type) == "ORACLE"? " FROM dual " : "");

		$res = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);

		return $res;
	}

	public function CheckFields(&$arFields, $ID=false)
	{
		/**
		 * @global CMain $APPLICATION
		 * @global CUserTypeManager $USER_FIELD_MANAGER
		 */
		global $DB, $APPLICATION, $USER_FIELD_MANAGER;

		$this->LAST_ERROR = "";

		$bInternal = true;
		if(is_set($arFields, "EXTERNAL_AUTH_ID"))
		{
			if(trim($arFields["EXTERNAL_AUTH_ID"]) <> '')
			{
				$bInternal = false;
			}
		}
		else
		{
			if($ID > 0)
			{
				$dbr = $DB->Query("SELECT EXTERNAL_AUTH_ID FROM b_user WHERE ID=".intval($ID));
				if(($ar = $dbr->Fetch()))
				{
					if($ar['EXTERNAL_AUTH_ID'] <> '')
					{
						$bInternal = false;
					}
				}
			}
		}

		if($bInternal)
		{
			$this->LAST_ERROR .= self::CheckInternalFields($arFields, $ID);
		}
		else
		{
			if(is_set($arFields, "EMAIL"))
			{
				if($arFields["EMAIL"] <> '' && !check_email($arFields["EMAIL"], true))
				{
					$this->LAST_ERROR .= GetMessage("WRONG_EMAIL")."<br>";
				}
			}
		}

		if(is_set($arFields, "PERSONAL_PHOTO") && $arFields["PERSONAL_PHOTO"]["name"] == '' && $arFields["PERSONAL_PHOTO"]["del"] == '')
			unset($arFields["PERSONAL_PHOTO"]);

		$maxWidth = COption::GetOptionInt("main", "profile_image_width", 0);
		$maxHeight = COption::GetOptionInt("main", "profile_image_height", 0);
		$maxSize = COption::GetOptionInt("main", "profile_image_size", 0);

		if(is_set($arFields, "PERSONAL_PHOTO"))
		{
			$res = CFile::CheckImageFile($arFields["PERSONAL_PHOTO"], $maxSize, $maxWidth, $maxHeight);
			if($res <> '')
				$this->LAST_ERROR .= $res."<br>";
		}

		if(is_set($arFields, "PERSONAL_BIRTHDAY") && $arFields["PERSONAL_BIRTHDAY"] <> '' && !CheckDateTime($arFields["PERSONAL_BIRTHDAY"]))
			$this->LAST_ERROR .= GetMessage("WRONG_PERSONAL_BIRTHDAY")."<br>";

		if(is_set($arFields, "WORK_LOGO") && $arFields["WORK_LOGO"]["name"] == '' && $arFields["WORK_LOGO"]["del"] == '')
			unset($arFields["WORK_LOGO"]);

		if(is_set($arFields, "WORK_LOGO"))
		{
			$res = CFile::CheckImageFile($arFields["WORK_LOGO"], $maxSize, $maxWidth, $maxHeight);
			if($res <> '')
				$this->LAST_ERROR .= $res."<br>";
		}

		if(is_set($arFields, "LOGIN"))
		{
			$res = $DB->Query(
				"SELECT 'x' ".
				"FROM b_user ".
				"WHERE LOGIN='".$DB->ForSql($arFields["LOGIN"], 50)."'	".
				"	".($ID===false ? "" : " AND ID<>".intval($ID)).
				"	".(!$bInternal ? "	AND EXTERNAL_AUTH_ID='".$DB->ForSql($arFields["EXTERNAL_AUTH_ID"])."' " : " AND (EXTERNAL_AUTH_ID IS NULL OR ".$DB->Length("EXTERNAL_AUTH_ID")."<=0)")
				);

			if($res->Fetch())
				$this->LAST_ERROR .= str_replace("#LOGIN#", htmlspecialcharsbx($arFields["LOGIN"]), GetMessage("USER_EXIST"))."<br>";
		}

		if(is_object($APPLICATION))
		{
			$APPLICATION->ResetException();

			if($ID===false)
				$events = GetModuleEvents("main", "OnBeforeUserAdd", true);
			else
			{
				$arFields["ID"] = $ID;
				$events = GetModuleEvents("main", "OnBeforeUserUpdate", true);
			}

			foreach($events as $arEvent)
			{
				$bEventRes = ExecuteModuleEventEx($arEvent, array(&$arFields));
				if($bEventRes===false)
				{
					if($err = $APPLICATION->GetException())
						$this->LAST_ERROR .= $err->GetString()." ";
					else
					{
						$APPLICATION->ThrowException("Unknown error");
						$this->LAST_ERROR .= "Unknown error. ";
					}
					break;
				}
			}
		}

		if(is_object($APPLICATION))
			$APPLICATION->ResetException();
		if (!$USER_FIELD_MANAGER->CheckFields("USER", $ID, $arFields))
		{
			if(is_object($APPLICATION) && $APPLICATION->GetException())
			{
				$e = $APPLICATION->GetException();
				$this->LAST_ERROR .= $e->GetString();
				$APPLICATION->ResetException();
			}
			else
			{
				$this->LAST_ERROR .= "Unknown error. ";
			}
		}

		if($this->LAST_ERROR <> '')
			return false;

		return true;
	}

	/**
	 * @param array $arFields
	 * @param int|bool $ID
	 * @return string
	 */
	public static function CheckInternalFields($arFields, $ID = false)
	{
		global $DB;

		$resultError = '';

		$emailRequired = (COption::GetOptionString("main", "new_user_email_required", "Y") <> "N");

		if($ID === false)
		{
			if(!isset($arFields["LOGIN"]))
			{
				$resultError .= GetMessage("user_login_not_set")."<br>";
			}

			if(!isset($arFields["PASSWORD"]))
			{
				$resultError .= GetMessage("user_pass_not_set")."<br>";
			}

			if($emailRequired && !isset($arFields["EMAIL"]))
			{
				$resultError .= GetMessage("user_email_not_set")."<br>";
			}
		}
		if(is_set($arFields, "LOGIN") && $arFields["LOGIN"] <> trim($arFields["LOGIN"]))
		{
			$resultError .= GetMessage("LOGIN_WHITESPACE")."<br>";
		}

		if(is_set($arFields, "LOGIN") && strlen($arFields["LOGIN"]) < 3)
		{
			$resultError .= GetMessage("MIN_LOGIN")."<br>";
		}

		if(is_set($arFields, "PASSWORD"))
		{
			if(array_key_exists("GROUP_ID", $arFields))
			{
				$arGroups = array();
				if(is_array($arFields["GROUP_ID"]))
				{
					foreach($arFields["GROUP_ID"] as $arGroup)
					{
						if(is_array($arGroup))
						{
							$arGroups[] = $arGroup["GROUP_ID"];
						}
						else
						{
							$arGroups[] = $arGroup;
						}
					}
				}
				$arPolicy = self::GetGroupPolicy($arGroups);
			}
			elseif($ID !== false)
			{
				$arPolicy = self::GetGroupPolicy($ID);
			}
			else
			{
				$arPolicy = self::GetGroupPolicy(array());
			}

			$passwordErrors = self::CheckPasswordAgainstPolicy($arFields["PASSWORD"], $arPolicy);
			if(!empty($passwordErrors))
			{
				$resultError .= implode("<br>", $passwordErrors)."<br>";
			}
		}

		if(is_set($arFields, "EMAIL"))
		{
			if(($emailRequired && strlen($arFields["EMAIL"]) < 3) || ($arFields["EMAIL"] <> '' && !check_email($arFields["EMAIL"], true)))
			{
				$resultError .= GetMessage("WRONG_EMAIL")."<br>";
			}
			elseif(COption::GetOptionString("main", "new_user_email_uniq_check", "N") === "Y")
			{
				if($arFields["EMAIL"] <> '')
				{
					$oldEmail = '';
					if($ID > 0)
					{
						//the option 'new_user_email_uniq_check' might have been switched on after the DB already contained identical emails
						//so we let a user to have the old email, but not the existing new one
						$dbr = $DB->Query("SELECT EMAIL FROM b_user WHERE ID=".intval($ID));
						if(($ar = $dbr->Fetch()))
						{
							$oldEmail = $ar['EMAIL'];
						}
					}
					if($ID == false || $arFields["EMAIL"] <> $oldEmail)
					{
						$b = $o = "";
						$res = CUser::GetList($b, $o,
							array(
								"=EMAIL" => $arFields["EMAIL"],
								"EXTERNAL_AUTH_ID" => $arFields["EXTERNAL_AUTH_ID"]
							),
							array(
								"FIELDS" => array("ID")
							)
						);
						while($ar = $res->Fetch())
						{
							if(intval($ar["ID"]) !== intval($ID))
							{
								$resultError .= GetMessage("USER_WITH_EMAIL_EXIST", array("#EMAIL#" => htmlspecialcharsbx($arFields["EMAIL"])))."<br>";
							}
						}
					}
				}
			}
		}

		if(is_set($arFields, "PASSWORD") && is_set($arFields, "CONFIRM_PASSWORD") && $arFields["PASSWORD"] !== $arFields["CONFIRM_PASSWORD"])
		{
			$resultError .= GetMessage("WRONG_CONFIRMATION")."<br>";
		}

		if(is_array($arFields["GROUP_ID"]) && count($arFields["GROUP_ID"]) > 0)
		{
			if(is_array($arFields["GROUP_ID"][0]) && count($arFields["GROUP_ID"][0]) > 0)
			{
				foreach($arFields["GROUP_ID"] as $arGroup)
				{
					if($arGroup["DATE_ACTIVE_FROM"] <> '' && !CheckDateTime($arGroup["DATE_ACTIVE_FROM"]))
					{
						$error = str_replace("#GROUP_ID#", $arGroup["GROUP_ID"], GetMessage("WRONG_DATE_ACTIVE_FROM"));
						$resultError .= $error."<br>";
					}

					if($arGroup["DATE_ACTIVE_TO"] <> '' && !CheckDateTime($arGroup["DATE_ACTIVE_TO"]))
					{
						$error = str_replace("#GROUP_ID#", $arGroup["GROUP_ID"], GetMessage("WRONG_DATE_ACTIVE_TO"));
						$resultError .= $error."<br>";
					}
				}
			}
		}

		return $resultError;
	}

	public static function GetByID($ID)
	{
		global $USER;

		$userID = (is_object($USER)? intval($USER->GetID()): 0);
		$ID = intval($ID);
		if($userID > 0 && $ID == $userID && is_array(self::$CURRENT_USER))
		{
			$rs = new CDBResult;
			$rs->InitFromArray(self::$CURRENT_USER);
		}
		else
		{
			$rs = CUser::GetList(($by="id"), ($order="asc"), array("ID_EQUAL_EXACT"=>intval($ID)), array("SELECT"=>array("UF_*")));
			if($userID > 0 && $ID == $userID)
			{
				self::$CURRENT_USER = array($rs->Fetch());
				$rs = new CDBResult;
				$rs->InitFromArray(self::$CURRENT_USER);
			}
		}
		return $rs;
	}

	public static function GetByLogin($LOGIN)
	{
		$rs = CUser::GetList(($by="id"), ($order="asc"), array("LOGIN_EQUAL_EXACT"=>$LOGIN), array("SELECT"=>array("UF_*")));
		return $rs;
	}

	public function Update($ID, $arFields, $authActions = true)
	{
		/** @global CUserTypeManager $USER_FIELD_MANAGER */
		global $DB, $USER_FIELD_MANAGER, $CACHE_MANAGER, $USER;

		$ID = intval($ID);

		if(!$this->CheckFields($arFields, $ID))
		{
			$result = false;
			$arFields["RESULT_MESSAGE"] = &$this->LAST_ERROR;
		}
		else
		{
			unset($arFields["ID"]);

			if(is_set($arFields, "ACTIVE") && $arFields["ACTIVE"]!="Y")
				$arFields["ACTIVE"]="N";

			if(is_set($arFields, "PERSONAL_GENDER") && ($arFields["PERSONAL_GENDER"]!="M" && $arFields["PERSONAL_GENDER"]!="F"))
				$arFields["PERSONAL_GENDER"] = "";

			$saveHistory = (Main\Config\Option::get("main", "user_profile_history") === "Y");

			//we need old values for some actions
			$arUser = null;
			if((isset($arFields["ACTIVE"]) && $arFields["ACTIVE"] == "N") || isset($arFields["PASSWORD"]) || $saveHistory)
			{
				$rUser = CUser::GetByID($ID);
				$arUser = $rUser->Fetch();
			}

			$newPassword = "";
			if(is_set($arFields, "PASSWORD"))
			{
				$original_pass = $arFields["PASSWORD"];
				$salt = randString(8, array(
					"abcdefghijklnmopqrstuvwxyz",
					"ABCDEFGHIJKLNMOPQRSTUVWXYZ",
					"0123456789",
					",.<>/?;:[]{}\\|~!@#\$%^&*()-_+=",
				));
				$arFields["PASSWORD"] = $salt.md5($salt.$arFields["PASSWORD"]);

				if($arUser)
				{
					$oldSalt = substr($arUser["PASSWORD"], 0, 8);
					$newPassword = $oldSalt.md5($oldSalt.$original_pass);

					if($newPassword <> $arUser["PASSWORD"])
					{
						$DB->Query("DELETE FROM b_user_stored_auth WHERE USER_ID=".$ID);
					}
				}
				if(COption::GetOptionString("main", "event_log_password_change", "N") === "Y")
					CEventLog::Log("SECURITY", "USER_PASSWORD_CHANGED", "main", $ID);
			}
			unset($arFields["STORED_HASH"]);

			$checkword = '';
			if(!is_set($arFields, "CHECKWORD"))
			{
				if(is_set($arFields, "PASSWORD") || is_set($arFields, "EMAIL") || is_set($arFields, "LOGIN")  || is_set($arFields, "ACTIVE"))
				{
					$salt =  randString(8);
					$checkword = md5(CMain::GetServerUniqID().uniqid());
					$arFields["CHECKWORD"] = $salt.md5($salt.$checkword);
				}
			}
			else
			{
				$salt =  randString(8);
				$checkword = $arFields["CHECKWORD"];
				$arFields["CHECKWORD"] = $salt.md5($salt.$checkword);
			}

			if(is_set($arFields, "CHECKWORD") && !is_set($arFields, "CHECKWORD_TIME"))
				$arFields["~CHECKWORD_TIME"] = $DB->CurrentTimeFunction();

			if(is_set($arFields, "WORK_COUNTRY"))
				$arFields["WORK_COUNTRY"] = intval($arFields["WORK_COUNTRY"]);

			if(is_set($arFields, "PERSONAL_COUNTRY"))
				$arFields["PERSONAL_COUNTRY"] = intval($arFields["PERSONAL_COUNTRY"]);

			if (
				array_key_exists("PERSONAL_PHOTO", $arFields)
				&& is_array($arFields["PERSONAL_PHOTO"])
				&& (
					!array_key_exists("MODULE_ID", $arFields["PERSONAL_PHOTO"])
					|| $arFields["PERSONAL_PHOTO"]["MODULE_ID"] == ''
				)
			)
			{
				$arFields["PERSONAL_PHOTO"]["MODULE_ID"] = "main";
			}

			CFile::SaveForDB($arFields, "PERSONAL_PHOTO", "main");

			if (
				array_key_exists("WORK_LOGO", $arFields)
				&& is_array($arFields["WORK_LOGO"])
				&& (
					!array_key_exists("MODULE_ID", $arFields["WORK_LOGO"])
					|| $arFields["WORK_LOGO"]["MODULE_ID"] == ''
				)
			)
			{
				$arFields["WORK_LOGO"]["MODULE_ID"] = "main";
			}

			CFile::SaveForDB($arFields, "WORK_LOGO", "main");

			$strUpdate = $DB->PrepareUpdate("b_user", $arFields);

			if(!is_set($arFields, "TIMESTAMP_X"))
				$strUpdate .= ($strUpdate <> ""? ",":"")." TIMESTAMP_X = ".$DB->GetNowFunction();

			$strSql = "UPDATE b_user SET ".$strUpdate." WHERE ID=".$ID;

			$DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);

			$USER_FIELD_MANAGER->Update("USER", $ID, $arFields);

			if(COption::GetOptionString("main", "event_log_user_edit", "N") === "Y")
			{
				$res_log["user"] = ($arFields["NAME"] != "" || $arFields["LAST_NAME"] != "") ? trim($arFields["NAME"]." ".$arFields["LAST_NAME"]) : $arFields["LOGIN"];
				CEventLog::Log("SECURITY", "USER_EDIT", "main", $ID, serialize($res_log));
			}

			if(is_set($arFields, "GROUP_ID"))
				CUser::SetUserGroup($ID, $arFields["GROUP_ID"]);

			//update digest hash for http digest authorization
			if(isset($arUser["ID"]) && is_set($arFields, "PASSWORD") && COption::GetOptionString('main', 'use_digest_auth', 'N') == 'Y')
			{
				/** @noinspection PhpUndefinedVariableInspection */
				CUser::UpdateDigest($arUser["ID"], $original_pass);
			}

			if($arUser && $authActions == true)
			{
				$authAction = false;
				if(isset($arFields["ACTIVE"]) && $arUser["ACTIVE"] == "Y" && $arFields["ACTIVE"] == "N")
				{
					$authAction = true;
				}

				$internalUser = true;
				if(isset($arFields["EXTERNAL_AUTH_ID"]))
				{
					if($arFields["EXTERNAL_AUTH_ID"] <> '')
					{
						$internalUser = false;
					}
				}
				elseif($arUser["EXTERNAL_AUTH_ID"] <> '')
				{
					$internalUser = false;
				}

				if($internalUser == true && isset($arFields["PASSWORD"]) && $newPassword <> $arUser["PASSWORD"])
				{
					$authAction = true;
					if(is_object($USER) && $USER->GetID() == $ID)
					{
						//changed password by himself
						$USER->SetParam("SELF_CHANGED_PASSWORD", true);
					}
				}

				if($authAction)
				{
					Main\UserAuthActionTable::add(array(
						'USER_ID' => $ID,
						'PRIORITY' => Main\UserAuthActionTable::PRIORITY_HIGH,
						'ACTION' => Main\UserAuthActionTable::ACTION_LOGOUT,
						'ACTION_DATE' => new Main\Type\DateTime(),
					));
				}
			}

			$result = true;
			$arFields["CHECKWORD"] = $checkword;

			//update session information and cache for current user
			if(is_object($USER) && $USER->GetID() == $ID)
			{
				static $arSessFields = array(
					'LOGIN'=>'LOGIN', 'EMAIL'=>'EMAIL', 'TITLE'=>'TITLE', 'FIRST_NAME'=>'NAME', 'SECOND_NAME'=>'SECOND_NAME', 'LAST_NAME'=>'LAST_NAME',
					'PERSONAL_PHOTO'=>'PERSONAL_PHOTO', 'PERSONAL_GENDER'=>'PERSONAL_GENDER', 'AUTO_TIME_ZONE'=>'AUTO_TIME_ZONE', 'TIME_ZONE'=>'TIME_ZONE');
				foreach($arSessFields as $key => $val)
					if(isset($arFields[$val]))
						$USER->SetParam($key, $arFields[$val]);
				$name = $USER->GetParam("FIRST_NAME");
				$last_name = $USER->GetParam("LAST_NAME");
				$USER->SetParam("NAME", $name.($name == '' || $last_name == ''? "":" ").$last_name);

				//cache for GetByID()
				self::$CURRENT_USER = false;
			}

			if($saveHistory && $arUser)
			{
				$rUser = CUser::GetByID($ID);
				$newUser = $rUser->Fetch();

				Main\UserProfileHistoryTable::addHistory($ID, Main\UserProfileHistoryTable::TYPE_UPDATE, $arUser, $newUser);
			}
		}

		$arFields["ID"] = $ID;
		$arFields["RESULT"] = &$result;

		foreach (GetModuleEvents("main", "OnAfterUserUpdate", true) as $arEvent)
			ExecuteModuleEventEx($arEvent, array(&$arFields));

		if($arFields["RESULT"] == true)
		{
			\Bitrix\Main\UserTable::indexRecord($ID);

			if(defined("BX_COMP_MANAGED_CACHE"))
			{
				$userData = \Bitrix\Main\UserTable::getById($ID)->fetch();
				$isRealUser = !$userData['EXTERNAL_AUTH_ID'] || !in_array($userData['EXTERNAL_AUTH_ID'], \Bitrix\Main\UserTable::getExternalUserTypes());

				$CACHE_MANAGER->ClearByTag("USER_CARD_".intval($ID / TAGGED_user_card_size));
				$CACHE_MANAGER->ClearByTag($isRealUser? "USER_CARD": "EXTERNAL_USER_CARD");

				static $arNameFields = array("NAME", "LAST_NAME", "SECOND_NAME", "LOGIN", "EMAIL", "PERSONAL_GENDER", "PERSONAL_PHOTO", "WORK_POSITION", "PERSONAL_PROFESSION", "PERSONAL_WWW", "PERSONAL_BIRTHDAY", "TITLE", "EXTERNAL_AUTH_ID", "UF_DEPARTMENT");
				$bClear = false;
				foreach($arNameFields as $val)
				{
					if(isset($arFields[$val]))
					{
						$bClear = true;
						break;
					}
				}
				if ($bClear)
				{
					$CACHE_MANAGER->ClearByTag("USER_NAME_".$ID);
					$CACHE_MANAGER->ClearByTag($isRealUser? "USER_NAME": "EXTERNAL_USER_NAME");
				}
			}
		}

		return $result;
	}

	public static function SetUserGroup($USER_ID, $arGroups, $newUser = false)
	{
		global $DB;

		$USER_ID = intval($USER_ID);

		if ($USER_ID === 0)
		{
			return false;
		}

		//remember previous groups of the user
		$aPrevGroups = array();
		$res = CUser::GetUserGroupList($USER_ID);
		while($res_arr = $res->Fetch())
			if($res_arr["GROUP_ID"] <> 2)
				$aPrevGroups[$res_arr["GROUP_ID"]] = $res_arr;

		$DB->Query("DELETE FROM b_user_group WHERE USER_ID=".$USER_ID, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);

		$inserted = array();
		if(is_array($arGroups))
		{
			foreach($arGroups as $group)
			{
				if(!is_array($group))
				{
					$group = array("GROUP_ID" => $group);
				}

				$group_id = intval($group["GROUP_ID"]);
				if($group_id > 0 && $group_id <> 2 && !isset($inserted[$group_id]))
				{
					$arInsert = $DB->PrepareInsert("b_user_group", $group);
					$strSql = "
						INSERT INTO b_user_group (
							USER_ID, ".$arInsert[0]."
						) VALUES (
							".$USER_ID.",
							".$arInsert[1]."
						)
					";
					$DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
					$inserted[$group_id] = $group;
				}
			}
		}
		self::clearUserGroupCache($USER_ID);

		foreach (GetModuleEvents("main", "OnAfterSetUserGroup", true) as $arEvent)
		{
			ExecuteModuleEventEx($arEvent, array("USER_ID"=>$USER_ID, "GROUPS"=>$inserted));
		}

		if($aPrevGroups <> $inserted)
		{
			if($newUser == false)
			{
				$authActionCommon = false;
				$now = new Main\Type\DateTime();
				foreach($inserted as $group)
				{
					foreach(array("DATE_ACTIVE_FROM", "DATE_ACTIVE_TO") as $field)
					{
						if($group[$field] <> '')
						{
							$date = Main\Type\DateTime::createFromUserTime($group[$field]);
							if($date > $now)
							{
								//group membership is in the future, we need separate records for each group
								Main\UserAuthActionTable::add(array(
									'USER_ID' => $USER_ID,
									'PRIORITY' => Main\UserAuthActionTable::PRIORITY_LOW,
									'ACTION' => Main\UserAuthActionTable::ACTION_UPDATE,
									'ACTION_DATE' => $date,
								));
							}
							else
							{
								$authActionCommon = true;
							}
						}
						else
						{
							$authActionCommon = true;
						}
					}
				}

				if($authActionCommon == true)
				{
					//one action for all groups without dates in the future
					Main\UserAuthActionTable::add(array(
						'USER_ID' => $USER_ID,
						'PRIORITY' => Main\UserAuthActionTable::PRIORITY_LOW,
						'ACTION' => Main\UserAuthActionTable::ACTION_UPDATE,
						'ACTION_DATE' => new Main\Type\DateTime(),
					));
				}
			}

			if(COption::GetOptionString("main", "event_log_user_groups", "N") === "Y")
			{
				$UserName = '';
				$rsUser = CUser::GetByID($USER_ID);
				if($arUser = $rsUser->GetNext())
					$UserName = ($arUser["NAME"] != "" || $arUser["LAST_NAME"] != "") ? trim($arUser["NAME"]." ".$arUser["LAST_NAME"]) : $arUser["LOGIN"];
				$res_log = array(
					"groups" => serialize($aPrevGroups)." => ".serialize($inserted),
					"user" => $UserName
				);
				CEventLog::Log("SECURITY", "USER_GROUP_CHANGED", "main", $USER_ID, serialize($res_log));
			}
		}
		return null;
	}

	/**
	 * Appends groups to the list of existing user's groups.
	 *
	 * @param int $user_id
	 * @param array|int $groups A single number, or an array of numbers, or an array of arrays("GROUP_ID"=>$val, "DATE_ACTIVE_FROM"=>$val, "DATE_ACTIVE_TO"=>$val)
	 */
	public static function AppendUserGroup($user_id, $groups)
	{
		$arGroups = array();
		$res = CUser::GetUserGroupList($user_id);
		while($res_arr = $res->Fetch())
		{
			$arGroups[] = array(
				"GROUP_ID" => $res_arr["GROUP_ID"],
				"DATE_ACTIVE_FROM" => $res_arr["DATE_ACTIVE_FROM"],
				"DATE_ACTIVE_TO" => $res_arr["DATE_ACTIVE_TO"],
			);
		}

		if(!is_array($groups))
		{
			$groups = array($groups);
		}

		foreach($groups as $group)
		{
			if(!is_array($group))
			{
				$group = array("GROUP_ID" => $group);
			}
			$arGroups[] = $group;
		}

		CUser::SetUserGroup($user_id, $arGroups);
	}

	public static function GetCount()
	{
		global $DB;
		$r = $DB->Query("SELECT COUNT('x') as C FROM b_user");
		$r = $r->Fetch();
		return Intval($r["C"]);
	}

	public static function Delete($ID)
	{
		/** @global CMain $APPLICATION */
		/** @global CUserTypeManager $USER_FIELD_MANAGER */
		global $DB, $APPLICATION, $USER_FIELD_MANAGER, $CACHE_MANAGER;

		$ID = intval($ID);

		@set_time_limit(600);

		$rsUser = $DB->Query("SELECT ID, LOGIN, NAME, LAST_NAME, EXTERNAL_AUTH_ID FROM b_user WHERE ID=".$ID." AND ID<>1");
		$arUser = $rsUser->Fetch();
		if(!$arUser)
			return false;

		foreach(GetModuleEvents("main", "OnBeforeUserDelete", true) as $arEvent)
		{
			if(ExecuteModuleEventEx($arEvent, array($ID))===false)
			{
				$err = GetMessage("MAIN_BEFORE_DEL_ERR").' '.$arEvent['TO_NAME'];
				if($ex = $APPLICATION->GetException())
					$err .= ': '.$ex->GetString();
				$APPLICATION->throwException($err);
				if(COption::GetOptionString("main", "event_log_user_delete", "N") === "Y")
				{
					$UserName = ($arUser["NAME"] != "" || $arUser["LAST_NAME"] != "") ? trim($arUser["NAME"]." ".$arUser["LAST_NAME"]) : $arUser["LOGIN"];
					$res_log = array(
						"user" => $UserName,
						"err" => $err
					);
					CEventLog::Log("SECURITY", "USER_DELETE", "main", $ID, serialize($res_log));
				}
				return false;
			}
		}

		foreach(GetModuleEvents("main", "OnUserDelete", true) as $arEvent)
		{
			if(ExecuteModuleEventEx($arEvent, array($ID))===false)
			{
				$err = GetMessage("MAIN_BEFORE_DEL_ERR").' '.$arEvent['TO_NAME'];
				if($ex = $APPLICATION->GetException())
					$err .= ': '.$ex->GetString();
				$APPLICATION->throwException($err);
				if(COption::GetOptionString("main", "event_log_user_delete", "N") === "Y")
				{
					$UserName = ($arUser["NAME"] != "" || $arUser["LAST_NAME"] != "") ? trim($arUser["NAME"]." ".$arUser["LAST_NAME"]) : $arUser["LOGIN"];
					$res_log = array(
						"user" => $UserName,
						"err" => $err
					);
					CEventLog::Log("SECURITY", "USER_DELETE", "main", $ID, serialize($res_log));
				}
				return false;
			}
		}

		$strSql = "SELECT F.ID FROM	b_user U, b_file F WHERE U.ID='$ID' and (F.ID=U.PERSONAL_PHOTO or F.ID=U.WORK_LOGO)";
		$z = $DB->Query($strSql, false, "FILE: ".__FILE__." LINE:".__LINE__);
		while ($zr = $z->Fetch())
			CFile::Delete($zr["ID"]);

		if(!$DB->Query("DELETE FROM b_user_group WHERE USER_ID=".$ID))
			return false;

		if(!$DB->Query("DELETE FROM b_user_digest WHERE USER_ID=".$ID))
			return false;

		if(!$DB->Query("DELETE FROM b_app_password WHERE USER_ID=".$ID))
			return false;

		$USER_FIELD_MANAGER->Delete("USER", $ID);

		if(COption::GetOptionString("main", "event_log_user_delete", "N") === "Y")
		{
			$res_log["user"] = ($arUser["NAME"] != "" || $arUser["LAST_NAME"] != "") ? trim($arUser["NAME"]." ".$arUser["LAST_NAME"]) : $arUser["LOGIN"];
			CEventLog::Log("SECURITY", "USER_DELETE", "main", $arUser["LOGIN"], serialize($res_log));
		}

		if(!$DB->Query("DELETE FROM b_user WHERE ID=".$ID." AND ID<>1"))
			return false;

		if(defined("BX_COMP_MANAGED_CACHE"))
		{
			$isRealUser = !$arUser['EXTERNAL_AUTH_ID'] || !in_array($arUser['EXTERNAL_AUTH_ID'], \Bitrix\Main\UserTable::getExternalUserTypes());

			$CACHE_MANAGER->ClearByTag("USER_CARD_".intval($ID / TAGGED_user_card_size));
			$CACHE_MANAGER->ClearByTag($isRealUser? "USER_CARD": "EXTERNAL_USER_CARD");

			$CACHE_MANAGER->ClearByTag("USER_NAME_".$ID);
			$CACHE_MANAGER->ClearByTag($isRealUser? "USER_NAME": "EXTERNAL_USER_CARD");
		}

		self::clearUserGroupCache($ID);

		Main\UserAuthActionTable::add(array(
			'USER_ID' => $ID,
			'PRIORITY' => Main\UserAuthActionTable::PRIORITY_HIGH,
			'ACTION' => Main\UserAuthActionTable::ACTION_LOGOUT,
			'ACTION_DATE' => new Main\Type\DateTime(),
		));

		if(Main\Config\Option::get("main", "user_profile_history") === "Y")
		{
			Main\UserProfileHistoryTable::deleteByUser($ID);
			Main\UserProfileHistoryTable::addHistory($ID, Main\UserProfileHistoryTable::TYPE_DELETE);
		}

		\Bitrix\Main\UserTable::deleteIndexRecord($ID);

		foreach(GetModuleEvents("main", "OnAfterUserDelete", true) as $arEvent)
		{
			ExecuteModuleEventEx($arEvent, array($ID));
		}

		return true;
	}

	public static function GetExternalAuthList()
	{
		$arAll = array();
		foreach(GetModuleEvents("main", "OnExternalAuthList", true) as $arEvent)
		{
			$arRes = ExecuteModuleEventEx($arEvent);
			if(is_array($arRes))
			{
				foreach($arRes as $v)
				{
					$arAll[] = $v;
				}
			}
		}

		$result = new CDBResult;
		$result->InitFromArray($arAll);
		return $result;
	}

	public static function GetGroupPolicy($iUserId)
	{
		global $DB;
		static $arPOLICY_CACHE;
		if(!is_array($arPOLICY_CACHE))
			$arPOLICY_CACHE = array();
		$CACHE_ID = md5(serialize($iUserId));
		if(array_key_exists($CACHE_ID, $arPOLICY_CACHE))
			return $arPOLICY_CACHE[$CACHE_ID];

		global $BX_GROUP_POLICY;
		$arPolicy = $BX_GROUP_POLICY;
		if($arPolicy["SESSION_TIMEOUT"]<=0)
			$arPolicy["SESSION_TIMEOUT"] = ini_get("session.gc_maxlifetime")/60;

		$arSql = array();
		$arSql[] =
			"SELECT G.SECURITY_POLICY ".
			"FROM b_group G ".
			"WHERE G.ID=2";

		if(is_array($iUserId))
		{
			$arGroups = array();
			foreach($iUserId as $value)
			{
				$value = intval($value);
				if($value > 0 && $value != 2)
					$arGroups[$value] = $value;
			}
			if(count($arGroups) > 0)
			{
				$arSql[] =
					"SELECT G.ID GROUP_ID, G.SECURITY_POLICY ".
					"FROM b_group G ".
					"WHERE G.ID in (".implode(", ", $arGroups).")";
			}
		}
		elseif(intval($iUserId) > 0)
		{
			$arSql[] =
				"SELECT UG.GROUP_ID, G.SECURITY_POLICY ".
				"FROM b_user_group UG, b_group G ".
				"WHERE UG.USER_ID = ".intval($iUserId)." ".
				"	AND UG.GROUP_ID = G.ID ".
				"	AND ((UG.DATE_ACTIVE_FROM IS NULL) OR (UG.DATE_ACTIVE_FROM <= ".$DB->CurrentTimeFunction().")) ".
				"	AND ((UG.DATE_ACTIVE_TO IS NULL) OR (UG.DATE_ACTIVE_TO >= ".$DB->CurrentTimeFunction().")) ";
		}

		foreach($arSql as $strSql)
		{
			$res = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
			while($ar = $res->Fetch())
			{
				if($ar["SECURITY_POLICY"])
					$arGroupPolicy = unserialize($ar["SECURITY_POLICY"]);
				else
					continue;

				if(!is_array($arGroupPolicy))
					continue;

				foreach($arGroupPolicy as $key=>$val)
				{
					switch($key)
					{
					case "STORE_IP_MASK":
					case "SESSION_IP_MASK":
						if($arPolicy[$key]<$val)
							$arPolicy[$key] = $val;
						break;
					case "SESSION_TIMEOUT":
						if($arPolicy[$key]<=0 || $arPolicy[$key]>$val)
							$arPolicy[$key] = $val;
						break;
					case "PASSWORD_LENGTH":
						if($arPolicy[$key]<=0 || $arPolicy[$key] < $val)
							$arPolicy[$key] = $val;
						break;
					case "PASSWORD_UPPERCASE":
					case "PASSWORD_LOWERCASE":
					case "PASSWORD_DIGITS":
					case "PASSWORD_PUNCTUATION":
						if($val === "Y")
							$arPolicy[$key] = "Y";
						break;
					case "LOGIN_ATTEMPTS":
						if($val > 0 && ($arPolicy[$key] <= 0 || $arPolicy[$key] > $val))
							$arPolicy[$key] = $val;
						break;
					default:
						if($arPolicy[$key]>$val)
							$arPolicy[$key] = $val;
					}
				}
			}
			if($arPolicy["PASSWORD_LENGTH"] === false)
				$arPolicy["PASSWORD_LENGTH"] = 6;
		}
		$ar = array(
			GetMessage("MAIN_GP_PASSWORD_LENGTH", array("#LENGTH#" => intval($arPolicy["PASSWORD_LENGTH"])))
		);
		if($arPolicy["PASSWORD_UPPERCASE"] === "Y")
			$ar[] = GetMessage("MAIN_GP_PASSWORD_UPPERCASE");
		if($arPolicy["PASSWORD_LOWERCASE"] === "Y")
			$ar[] = GetMessage("MAIN_GP_PASSWORD_LOWERCASE");
		if($arPolicy["PASSWORD_DIGITS"] === "Y")
			$ar[] = GetMessage("MAIN_GP_PASSWORD_DIGITS");
		if($arPolicy["PASSWORD_PUNCTUATION"] === "Y")
			$ar[] = GetMessage("MAIN_GP_PASSWORD_PUNCTUATION");
		$arPolicy["PASSWORD_REQUIREMENTS"] = implode(", ", $ar).".";

		if(count($arPOLICY_CACHE)<=10)
			$arPOLICY_CACHE[$CACHE_ID] = $arPolicy;

		return $arPolicy;
	}

	public static function CheckStoredHash($iUserId, $sHash, $bTempHashOnly=false)
	{
		global $DB;
		$arPolicy = CUser::GetGroupPolicy($iUserId);

		$cnt = 0;
		$auth_id = false;
		$site_format = CSite::GetDateFormat();

		CTimeZone::Disable();
		$strSql =
			"SELECT A.*, ".
			"	".$DB->DateToCharFunction("A.DATE_REG", "FULL")." as DATE_REG, ".
			"	".$DB->DateToCharFunction("A.LAST_AUTH", "FULL")." as LAST_AUTH ".
			"FROM b_user_stored_auth A ".
			"WHERE A.USER_ID = ".intval($iUserId)." ".
			"ORDER BY A.LAST_AUTH DESC";
		$res = $DB->Query($strSql);
		CTimeZone::Enable();

		while($ar = $res->Fetch())
		{
			if($ar["TEMP_HASH"]=="N")
				$cnt++;
			if($arPolicy["MAX_STORE_NUM"] < $cnt
				|| ($ar["TEMP_HASH"]=="N" && time()-$arPolicy["STORE_TIMEOUT"]*60 > MakeTimeStamp($ar["LAST_AUTH"], $site_format))
				|| ($ar["TEMP_HASH"]=="Y" && time()-$arPolicy["SESSION_TIMEOUT"]*60 > MakeTimeStamp($ar["LAST_AUTH"], $site_format))
			)
			{
				$DB->Query("DELETE FROM b_user_stored_auth WHERE ID=".$ar["ID"]);
			}
			elseif(!$auth_id)
			{
				//for domain spreaded external auth we should check only temporary hashes
				if($bTempHashOnly == false || $ar["TEMP_HASH"] == "Y")
				{
					$remote_net = ip2long($arPolicy["STORE_IP_MASK"]) & ip2long($_SERVER["REMOTE_ADDR"]);
					$stored_net = ip2long($arPolicy["STORE_IP_MASK"]) & (float)$ar["IP_ADDR"];
					if($sHash == $ar["STORED_HASH"] && $remote_net == $stored_net)
						$auth_id = $ar["ID"];
				}
			}
		}
		return $auth_id;
	}


	public function GetAllOperations($arGroups = false)
	{
		global $DB;

		if ($arGroups)
		{
			$userGroups = "2,".implode(",", array_map("intval", $arGroups));
		}
		else
		{
			$userGroups = $this->GetGroups();
		}

		$sql_str = "
			SELECT O.NAME OPERATION_NAME
			FROM b_group_task GT
				INNER JOIN b_task_operation T_O ON T_O.TASK_ID=GT.TASK_ID
				INNER JOIN b_operation O ON O.ID=T_O.OPERATION_ID
			WHERE GT.GROUP_ID IN(".$userGroups.")
			UNION
			SELECT O.NAME OPERATION_NAME
			FROM b_option OP
				INNER JOIN b_task_operation T_O ON T_O.TASK_ID=".$DB->ToChar("OP.VALUE", 18)."
				INNER JOIN b_operation O ON O.ID=T_O.OPERATION_ID
			WHERE OP.NAME='GROUP_DEFAULT_TASK'
			UNION
			SELECT O.NAME OPERATION_NAME
			FROM b_option OP
				INNER JOIN b_task T ON T.MODULE_ID=OP.MODULE_ID AND T.BINDING='module' AND T.LETTER=".$DB->ToChar("OP.VALUE", 1)." AND T.SYS='Y'
				INNER JOIN b_task_operation T_O ON T_O.TASK_ID=T.ID
				INNER JOIN b_operation O ON O.ID=T_O.OPERATION_ID
			WHERE OP.NAME='GROUP_DEFAULT_RIGHT'
		";

		$z = $DB->Query($sql_str, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		$arr = array();
		while($r = $z->Fetch())
			$arr[$r['OPERATION_NAME']] = $r['OPERATION_NAME'];

		return $arr;
	}

	public function CanDoOperation($op_name, $user_id = 0)
	{
		if ($user_id > 0)
		{
			$arGroups = array();
			$rsGroups = $this->GetUserGroupEx($user_id);
			while ($group = $rsGroups->Fetch())
			{
				$arGroups[] = $group["GROUP_ID"];
			}
			if (!$arGroups)
				return false;

			$op = $this->GetAllOperations($arGroups);
			return isset($op[$op_name]);
		}
		else
		{
			if ($this->IsAdmin())
				return true;

			if(!isset($_SESSION["SESS_OPERATIONS"]))
				$_SESSION["SESS_OPERATIONS"] = $this->GetAllOperations();

			return isset($_SESSION["SESS_OPERATIONS"][$op_name]);
		}
	}

	public static function GetFileOperations($arPath, $arGroups=false)
	{
		/** @global CMain $APPLICATION */
		global $APPLICATION;

		$ar = $APPLICATION->GetFileAccessPermission($arPath, $arGroups, true);
		$arFileOperations = array();

		for ($i = 0, $len = count($ar); $i < $len; $i++)
			$arFileOperations = array_merge($arFileOperations, CTask::GetOperations($ar[$i], true));
		$arFileOperations = array_values(array_unique($arFileOperations));

		return $arFileOperations;
	}


	public function CanDoFileOperation($op_name, $arPath)
	{
		global $APPLICATION, $USER;

		if ($this->IsAdmin())
			return true;

		if(!isset($APPLICATION->FILEMAN_OPERATION_CACHE))
			$APPLICATION->FILEMAN_OPERATION_CACHE = array();

		$k = addslashes($arPath[0].'|'.$arPath[1]);
		if(array_key_exists($k, $APPLICATION->FILEMAN_OPERATION_CACHE))
		{
			$arFileOperations = $APPLICATION->FILEMAN_OPERATION_CACHE[$k];
		}
		else
		{
			$arFileOperations = $this->GetFileOperations($arPath);
			$APPLICATION->FILEMAN_OPERATION_CACHE[$k] = $arFileOperations;
		}

		$arAlowedOperations = array('fm_delete_file','fm_rename_folder','fm_view_permission');
		if(substr($arPath[1], -10)=="/.htaccess" && !$USER->CanDoOperation('edit_php') && !in_array($op_name,$arAlowedOperations))
			return false;
		if(substr($arPath[1], -12)=="/.access.php")
			return false;

		return in_array($op_name, $arFileOperations);
	}

	public static function UserTypeRightsCheck($entity_id)
	{
		global $USER;

		if($entity_id == "USER" && $USER->CanDoOperation('edit_other_settings'))
		{
			return "W";
		}
		else
			return "D";
	}

	public function CanAccess($arCodes)
	{
		if(!is_array($arCodes) || empty($arCodes))
			return false;

		if(in_array('G2', $arCodes))
			return true;

		if($this->IsAuthorized() && in_array('AU', $arCodes))
			return true;

		$bEmpty = true;
		foreach($arCodes as $code)
		{
			if(trim($code) <> '')
			{
				$bEmpty = false;
				break;
			}
		}

		if($bEmpty)
			return false;

		$res = CAccess::GetUserCodes($this->GetID(), array("ACCESS_CODE"=>$arCodes));
		if($res->Fetch())
			return true;

		return false;
	}

	public function GetAccessCodes()
	{
		if(!$this->IsAuthorized())
			return array('G2');

		static $arCodes = array();

		$USER_ID = intval($this->GetID());

		if(!array_key_exists($USER_ID, $arCodes))
		{
			$arCodes[$USER_ID] = CAccess::GetUserCodesArray($USER_ID);

			if($this->IsAuthorized())
				$arCodes[$USER_ID][] = "AU";
		}

		return $arCodes[$USER_ID];
	}

	public static function CleanUpAgent()
	{
		$bTmpUser = false;
		if (!isset($GLOBALS["USER"]) || !is_object($GLOBALS["USER"]))
		{
			$bTmpUser = true;
			$GLOBALS["USER"] = new CUser;
		}

		$cleanup_days = COption::GetOptionInt("main", "new_user_registration_cleanup_days", 7);
		if($cleanup_days > 0 && COption::GetOptionString("main", "new_user_registration_email_confirmation", "N") === "Y")
		{
			$arDate = localtime(time());
			$date = mktime(0, 0, 0, $arDate[4]+1, $arDate[3]-$cleanup_days, 1900+$arDate[5]);
			$arFilter = array(
				"!CONFIRM_CODE" => false,
				"ACTIVE" => "N",
				"DATE_REGISTER_2" => ConvertTimeStamp($date),
			);
			$rsUsers = CUser::GetList(($by=""), ($order=""), $arFilter);
			while($arUser = $rsUsers->Fetch())
			{
				CUser::Delete($arUser["ID"]);
			}
		}
		if ($bTmpUser)
		{
			unset($GLOBALS["USER"]);
		}

		return "CUser::CleanUpAgent();";
	}

	public static function GetActiveUsersCount()
	{
		global $DB;

		$q = "SELECT COUNT(ID) as C FROM b_user WHERE ACTIVE = 'Y' AND LAST_LOGIN IS NOT NULL";
		if (IsModuleInstalled("intranet"))
			$q = "SELECT COUNT(U.ID) as C FROM b_user U WHERE U.ACTIVE = 'Y' AND U.LAST_LOGIN IS NOT NULL AND EXISTS(SELECT 'x' FROM b_utm_user UF, b_user_field F WHERE F.ENTITY_ID = 'USER' AND F.FIELD_NAME = 'UF_DEPARTMENT' AND UF.FIELD_ID = F.ID AND UF.VALUE_ID = U.ID AND UF.VALUE_INT IS NOT NULL AND UF.VALUE_INT <> 0)";

		$dbRes = $DB->Query($q, true);
		if ($dbRes && ($arRes = $dbRes->Fetch()))
			return $arRes["C"];
		else
			return 0;
	}

	public static function SetLastActivityDate($userId = null, $cache = false)
	{
		global $USER;

		if (is_null($userId))
		{
			$userId = $USER->GetId();
		}

		$userId = intval($userId);
		if ($userId <= 0)
		{
			return false;
		}

		if($userId == $USER->GetId())
		{
			$_SESSION['SESS_AUTH']['PREV_LAST_ACTIVITY'] = $_SESSION['SESS_AUTH']['SET_LAST_ACTIVITY'];

			if ($cache)
			{
				if (
					isset($_SESSION['SESS_AUTH']['SET_LAST_ACTIVITY'])
					&& intval($_SESSION['SESS_AUTH']['SET_LAST_ACTIVITY'])+60 > time()
				)
				{
					return false;
				}
			}
			$_SESSION['SESS_AUTH']['SET_LAST_ACTIVITY'] = time();
		}

		self::SetLastActivityDateByArray(array($userId), $_SERVER['REMOTE_ADDR']);

		return true;
	}

	public static function SetLastActivityDateByArray($arUsers, $ip = null)
	{
		global $DB;

		if (!is_array($arUsers) || count($arUsers) <= 0)
			return false;

		$strSqlPrefix = "UPDATE b_user SET ".
			"TIMESTAMP_X = ".(strtoupper($DB->type) == "ORACLE"? "NULL":"TIMESTAMP_X").", ".
			"LAST_ACTIVITY_DATE = ".$DB->CurrentTimeFunction()." WHERE ID IN (";
		$strSqlPostfix = ")";
		$maxValuesLen = 2048;
		$strSqlValues = "";

		$arUsers = array_map("intval", $arUsers);
		foreach($arUsers as $userId)
		{
			$strSqlValues .= ",$userId";
			if(strlen($strSqlValues) > $maxValuesLen)
			{
				$DB->Query($strSqlPrefix.substr($strSqlValues, 1).$strSqlPostfix, false, "", array("ignore_dml"=>true));
				$strSqlValues = "";
			}
		}

		if(strlen($strSqlValues) > 0)
		{
			$DB->Query($strSqlPrefix.substr($strSqlValues, 1).$strSqlPostfix, false, "", array("ignore_dml"=>true));
		}

		$event = new \Bitrix\Main\Event("main", "OnUserSetLastActivityDate", array($arUsers, $ip));
		$event->send();

		return true;
	}

	public static function GetSecondsForLimitOnline()
	{
		return \Bitrix\Main\UserTable::getSecondsForLimitOnline();
	}

	public static function GetExternalUserTypes()
	{
		return Main\UserTable::getExternalUserTypes();
	}

	public static function GetOnlineStatus($userId, $lastseen, $now = false)
	{
		$userId = intval($userId);

		if ($lastseen instanceof \Bitrix\Main\Type\DateTime)
		{
			$lastseen = $lastseen->getTimestamp();
		}
		else if (is_int($lastseen))
		{
			$lastseen = intval($lastseen);
		}
		else
		{
			$lastseen = 0;
		}

		if ($now === false)
		{
			$now = time();
		}
		else if ($now instanceof \Bitrix\Main\Type\DateTime)
		{
			$now = $now->getTimestamp();
		}
		else
		{
			$now = intval($now);
		}

		$result = Array(
			'IS_ONLINE' => false,
			'STATUS' => self::STATUS_OFFLINE,
			'STATUS_TEXT' =>  GetMessage('USER_STATUS_OFFLINE'),
			'LAST_SEEN' => $lastseen,
			'LAST_SEEN_TEXT' => "",
			'NOW' => $now,
		);

		if ($lastseen === false)
		{
			return $result;
		}

		$result['IS_ONLINE'] = $now - $lastseen <= self::GetSecondsForLimitOnline();
		$result['STATUS'] = $result['IS_ONLINE']? self::STATUS_ONLINE: self::STATUS_OFFLINE;
		$result['STATUS_TEXT'] = GetMessage('USER_STATUS_'.strtoupper($result['STATUS']));

		if ($lastseen && $now - $lastseen > 300)
		{
			$result['LAST_SEEN_TEXT'] = self::FormatLastActivityDate($lastseen, $now);
		}

		if ($userId > 0)
		{
			if ($result['IS_ONLINE'])
			{
				foreach(GetModuleEvents("main", "OnUserOnlineStatusGetCustomOnlineStatus", true) as $arEvent)
				{
					$customStatus = ExecuteModuleEventEx($arEvent, array($userId, $lastseen, $now, self::STATUS_ONLINE));
					if (is_array($customStatus))
					{
						if (!empty($customStatus['STATUS']) && !empty($customStatus['STATUS_TEXT']))
						{
							$result['STATUS'] = strtolower($customStatus['STATUS']);
							$result['STATUS_TEXT'] = $customStatus['STATUS_TEXT'];
						}
						if (isset($customStatus['LAST_SEEN']) && intval($customStatus['LAST_SEEN']) > 0)
						{
							$result['LAST_SEEN'] = intval($customStatus['LAST_SEEN']);
						}
						if (isset($customStatus['LAST_SEEN_TEXT']))
						{
							$result['LAST_SEEN_TEXT'] = $customStatus['LAST_SEEN_TEXT'];
						}
					}
				}
			}
			else
			{
				foreach(GetModuleEvents("main", "OnUserOnlineStatusGetCustomOfflineStatus", true) as $arEvent)
				{
					$customStatus = ExecuteModuleEventEx($arEvent, array($userId, $lastseen, $now, self::STATUS_OFFLINE));
					if (is_array($customStatus))
					{
						if (!empty($customStatus['STATUS']) && !empty($customStatus['STATUS_TEXT']))
						{
							$result['STATUS'] = strtolower($customStatus['STATUS']);
							$result['STATUS_TEXT'] = $customStatus['STATUS_TEXT'];
						}
						if (isset($customStatus['LAST_SEEN']) && intval($customStatus['LAST_SEEN']) > 0)
						{
							$result['LAST_SEEN'] = intval($customStatus['LAST_SEEN']);
						}
						if (isset($customStatus['LAST_SEEN_TEXT']))
						{
							$result['LAST_SEEN_TEXT'] = $customStatus['LAST_SEEN_TEXT'];
						}
					}
				}
			}
		}

		return $result;
	}

	/**
	 * @param int|bool|\Bitrix\Main\Type\DateTime $timestamp
	 * @param int|bool|\Bitrix\Main\Type\DateTime $now
	 *
	 * @return string
	 */
	public static function FormatLastActivityDate($timestamp, $now = false)
	{
		global $DB;

		if ($timestamp instanceof \Bitrix\Main\Type\DateTime)
		{
			$timestamp = $timestamp->getTimestamp();
		}
		else if (is_int($timestamp))
		{
			$timestamp = intval($timestamp);
		}
		else
		{
			return "";
		}

		if ($now === false)
		{
			$now = time();
		}
		else if ($now instanceof \Bitrix\Main\Type\DateTime)
		{
			$now = $now->getTimestamp();
		}
		else
		{
			$now = intval($now);
		}

		$ampm = IsAmPmMode(true);
		$timeFormat = ($ampm === AM_PM_LOWER? "g:i a" : ($ampm === AM_PM_UPPER? "g:i A" : "H:i"));

		$formattedDate = FormatDate(array(
			"tomorrow" => "#01#{$timeFormat}",
			"now" => "#02#",
			"todayFuture" => "#03#{$timeFormat}",
			"yesterday" => "#04#{$timeFormat}",
			"-" => preg_replace('/:s$/', '', $DB->DateFormatToPHP(CSite::GetDateFormat("FULL"))),
			"s60" => "sago",
			"i60" => "iago",
			"H5" => "Hago",
			"H24" => "#03#{$timeFormat}",
			"d31" => "dago",
			"m12>1" => "mago",
			"m12>0" => "dago",
			"" => "#05#",
		), $timestamp, $now);

		if (preg_match('/^#(\d+)#(.*)/', $formattedDate, $match))
		{
			switch($match[1])
			{
				case "01":
					$formattedDate = str_replace("#TIME#", $match[2], GetMessage('USER_LAST_SEEN_TOMORROW'));
				break;
				case "02":
					$formattedDate = GetMessage('USER_LAST_SEEN_NOW');
				break;
				case "03":
					$formattedDate = str_replace("#TIME#", $match[2], GetMessage('USER_LAST_SEEN_TODAY'));
				break;
				case "04":
					$formattedDate = str_replace("#TIME#", $match[2], GetMessage('USER_LAST_SEEN_YESTERDAY'));
				break;
				case "05":
					$formattedDate = GetMessage('USER_LAST_SEEN_MORE_YEAR');
				break;
				default:
					$formattedDate = $match[2];
				break;
			}
		}

		return $formattedDate;
	}

	public static function SearchUserByName($arName, $email = "", $bLoginMode = false)
	{
		global $DB;

		$arNameReady = array();
		foreach ($arName as $s)
		{
			$s = Trim($s);
			if (StrLen($s) > 0)
				$arNameReady[] = $s;
		}

		if (Count($arNameReady) <= 0)
			return false;

		$strSqlWhereEMail = ((StrLen($email) > 0) ? " AND upper(U.EMAIL) = upper('".$DB->ForSql($email)."') " : "");

		if ($bLoginMode)
		{
			if (count($arNameReady) > 3)
			{
				$strSql =
					"SELECT U.ID, U.NAME, U.LAST_NAME, U.SECOND_NAME, U.LOGIN, U.EMAIL ".
					"FROM b_user U ".
					"WHERE (";
				$bFirst = true;
				for ($i = 0; $i < 4; $i++)
				{
					for ($j = 0; $j < 4; $j++)
					{
						if ($i == $j)
							continue;

						for ($k = 0; $k < 4; $k++)
						{
							if ($i == $k || $j == $k)
								continue;

							for ($l = 0; $l < 4; $l++)
							{
								if ($i == $l || $j == $l || $k == $l)
									continue;

								if (!$bFirst)
									$strSql .= " OR ";

								$strSql .= "(U.NAME IS NOT NULL AND upper(U.NAME) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
									"AND U.LAST_NAME IS NOT NULL AND upper(U.LAST_NAME) LIKE upper('".$DB->ForSql($arNameReady[$j])."%') ".
									"AND U.LOGIN IS NOT NULL AND upper(U.LOGIN) LIKE upper('".$DB->ForSql($arNameReady[$k])."%') ".
									"AND U.EMAIL IS NOT NULL AND upper(U.EMAIL) LIKE upper('".$DB->ForSql($arNameReady[$l])."%'))";

								$bFirst = false;
							}
						}
					}
				}
				$strSql .= ")";
			}
			elseif (Count($arNameReady) == 3)
			{
				$strSql =
					"SELECT U.ID, U.NAME, U.LAST_NAME, U.SECOND_NAME, U.LOGIN, U.EMAIL ".
					"FROM b_user U ".
					"WHERE (";
				$bFirst = true;
				for ($i = 0; $i < 3; $i++)
				{
					for ($j = 0; $j < 3; $j++)
					{
						if ($i == $j)
							continue;

						for ($k = 0; $k < 3; $k++)
						{
							if ($i == $k || $j == $k)
								continue;

							if (!$bFirst)
								$strSql .= " OR ";

							$strSql .= "(";
							$strSql .= "(U.NAME IS NOT NULL AND upper(U.NAME) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
								"AND U.LAST_NAME IS NOT NULL AND upper(U.LAST_NAME) LIKE upper('".$DB->ForSql($arNameReady[$j])."%') ".
								"AND U.LOGIN IS NOT NULL AND upper(U.LOGIN) LIKE upper('".$DB->ForSql($arNameReady[$k])."%'))";
							$strSql .= " OR ";
							$strSql .= "(U.NAME IS NOT NULL AND upper(U.NAME) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
								"AND U.LAST_NAME IS NOT NULL AND upper(U.LAST_NAME) LIKE upper('".$DB->ForSql($arNameReady[$j])."%') ".
								"AND U.EMAIL IS NOT NULL AND upper(U.EMAIL) LIKE upper('".$DB->ForSql($arNameReady[$k])."%'))";
							$strSql .= " OR ";
							$strSql .= "(U.NAME IS NOT NULL AND upper(U.NAME) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
								"AND U.LOGIN IS NOT NULL AND upper(U.LOGIN) LIKE upper('".$DB->ForSql($arNameReady[$j])."%') ".
								"AND U.EMAIL IS NOT NULL AND upper(U.EMAIL) LIKE upper('".$DB->ForSql($arNameReady[$k])."%'))";
							$strSql .= " OR ";
							$strSql .= "(U.LAST_NAME IS NOT NULL AND upper(U.LAST_NAME) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
								"AND U.LOGIN IS NOT NULL AND upper(U.LOGIN) LIKE upper('".$DB->ForSql($arNameReady[$j])."%') ".
								"AND U.EMAIL IS NOT NULL AND upper(U.EMAIL) LIKE upper('".$DB->ForSql($arNameReady[$k])."%'))";
							$strSql .= ")";

							$bFirst = false;
						}
					}
				}
				$strSql .= ")";
			}
			elseif (Count($arNameReady) == 2)
			{
				$strSql =
					"SELECT U.ID, U.NAME, U.LAST_NAME, U.SECOND_NAME, U.LOGIN, U.EMAIL ".
					"FROM b_user U ".
					"WHERE (";
				$bFirst = true;
				for ($i = 0; $i < 2; $i++)
				{
					for ($j = 0; $j < 2; $j++)
					{
						if ($i == $j)
							continue;

						if (!$bFirst)
							$strSql .= " OR ";

						$strSql .= "(";
						$strSql .= "(U.NAME IS NOT NULL AND upper(U.NAME) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
							"AND U.LAST_NAME IS NOT NULL AND upper(U.LAST_NAME) LIKE upper('".$DB->ForSql($arNameReady[$j])."%'))";
						$strSql .= " OR ";
						$strSql .= "(U.NAME IS NOT NULL AND upper(U.NAME) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
							"AND U.LOGIN IS NOT NULL AND upper(U.LOGIN) LIKE upper('".$DB->ForSql($arNameReady[$j])."%'))";
						$strSql .= " OR ";
						$strSql .= "(U.LAST_NAME IS NOT NULL AND upper(U.LAST_NAME) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
							"AND U.LOGIN IS NOT NULL AND upper(U.LOGIN) LIKE upper('".$DB->ForSql($arNameReady[$j])."%'))";
						$strSql .= " OR ";
						$strSql .= "(U.LAST_NAME IS NOT NULL AND upper(U.LAST_NAME) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
							"AND U.EMAIL IS NOT NULL AND upper(U.EMAIL) LIKE upper('".$DB->ForSql($arNameReady[$j])."%'))";
						$strSql .= " OR ";
						$strSql .= "(U.NAME IS NOT NULL AND upper(U.NAME) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
							"AND U.EMAIL IS NOT NULL AND upper(U.EMAIL) LIKE upper('".$DB->ForSql($arNameReady[$j])."%'))";
						$strSql .= " OR ";
						$strSql .= "(U.LOGIN IS NOT NULL AND upper(U.LOGIN) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
							"AND U.EMAIL IS NOT NULL AND upper(U.EMAIL) LIKE upper('".$DB->ForSql($arNameReady[$j])."%'))";
						$strSql .= ")";
						$bFirst = false;
					}
				}
				$strSql .= ")";
			}
			else
			{
				$strSql =
					"SELECT U.ID, U.NAME, U.LAST_NAME, U.SECOND_NAME, U.LOGIN, U.EMAIL ".
					"FROM b_user U ".
					"WHERE (U.LAST_NAME IS NOT NULL AND upper(U.LAST_NAME) LIKE upper('".$DB->ForSql($arNameReady[0])."%') ".
					"	OR U.LOGIN IS NOT NULL AND upper(U.LOGIN) LIKE upper('".$DB->ForSql($arNameReady[0])."%') ".
					"	OR U.EMAIL IS NOT NULL AND upper(U.EMAIL) LIKE upper('".$DB->ForSql($arNameReady[0])."%') ".
					"	OR U.NAME IS NOT NULL AND upper(U.NAME) LIKE upper('".$DB->ForSql($arNameReady[0])."%')) ";
			}
			$strSql .= $strSqlWhereEMail;
		}
		else
		{
			if (Count($arNameReady) >= 3)
			{
				$strSql =
					"SELECT U.ID, U.NAME, U.LAST_NAME, U.SECOND_NAME, U.LOGIN, U.EMAIL ".
					"FROM b_user U ".
					"WHERE ";
				$bFirst = true;
				for ($i = 0; $i < 3; $i++)
				{
					for ($j = 0; $j < 3; $j++)
					{
						if ($i == $j)
							continue;

						for ($k = 0; $k < 3; $k++)
						{
							if ($i == $k || $j == $k)
								continue;

							if (!$bFirst)
								$strSql .= " OR ";

							$strSql .= "(U.NAME IS NOT NULL AND upper(U.NAME) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
								"AND U.LAST_NAME IS NOT NULL AND upper(U.LAST_NAME) LIKE upper('".$DB->ForSql($arNameReady[$j])."%') ".
								"AND U.SECOND_NAME IS NOT NULL AND upper(U.SECOND_NAME) LIKE upper('".$DB->ForSql($arNameReady[$k])."%')".$strSqlWhereEMail.")";

							$bFirst = false;
						}
					}
				}
			}
			elseif (Count($arNameReady) == 2)
			{
				$strSql =
					"SELECT U.ID, U.NAME, U.LAST_NAME, U.SECOND_NAME, U.LOGIN, U.EMAIL ".
					"FROM b_user U ".
					"WHERE ";
				$bFirst = true;
				for ($i = 0; $i < 2; $i++)
				{
					for ($j = 0; $j < 2; $j++)
					{
						if ($i == $j)
							continue;

						if (!$bFirst)
							$strSql .= " OR ";

						$strSql .= "(U.NAME IS NOT NULL AND upper(U.NAME) LIKE upper('".$DB->ForSql($arNameReady[$i])."%') ".
							"AND U.LAST_NAME IS NOT NULL AND upper(U.LAST_NAME) LIKE upper('".$DB->ForSql($arNameReady[$j])."%')".$strSqlWhereEMail.")";

						$bFirst = false;
					}
				}
			}
			else
			{
				$strSql =
					"SELECT U.ID, U.NAME, U.LAST_NAME, U.SECOND_NAME, U.LOGIN, U.EMAIL ".
					"FROM b_user U ".
					"WHERE U.LAST_NAME IS NOT NULL AND upper(U.LAST_NAME) LIKE upper('".$DB->ForSql($arNameReady[0])."%') ".
					$strSqlWhereEMail;
			}
		}

		$dbRes = $DB->Query($strSql);
		return $dbRes;
	}

	public static function FormatName($NAME_TEMPLATE, $arUser, $bUseLogin = false, $bHTMLSpec = true)
	{
		if (isset($arUser["ID"]))
			$ID = intval($arUser['ID']);
		else
			$ID = '';

		$NAME_SHORT = ($arUser['NAME'] <> ''? substr($arUser['NAME'], 0, 1).'.' : '');
		$LAST_NAME_SHORT = ($arUser['LAST_NAME'] <> ''? substr($arUser['LAST_NAME'], 0, 1).'.' : '');
		$SECOND_NAME_SHORT = ($arUser['SECOND_NAME'] <> ''? substr($arUser['SECOND_NAME'], 0, 1).'.' : '');

		$res = str_replace(
			array('#TITLE#', '#NAME#', '#LAST_NAME#', '#SECOND_NAME#', '#NAME_SHORT#', '#LAST_NAME_SHORT#', '#SECOND_NAME_SHORT#', '#EMAIL#', '#ID#'),
			array($arUser['TITLE'], $arUser['NAME'], $arUser['LAST_NAME'], $arUser['SECOND_NAME'], $NAME_SHORT, $LAST_NAME_SHORT, $SECOND_NAME_SHORT, $arUser['EMAIL'], $ID),
			$NAME_TEMPLATE
		);

		while(strpos($res, "  ") !== false)
		{
			$res = str_replace("  ", " ", $res);
		}
		$res = trim($res);

		$res_check = "";
		if (strpos($NAME_TEMPLATE, '#NAME#') !== false || strpos($NAME_TEMPLATE, '#NAME_SHORT#') !== false)
			$res_check .= $arUser['NAME'];
		if (strpos($NAME_TEMPLATE, '#LAST_NAME#') !== false || strpos($NAME_TEMPLATE, '#LAST_NAME_SHORT#') !== false)
			$res_check .= $arUser['LAST_NAME'];
		if (strpos($NAME_TEMPLATE, '#SECOND_NAME#') !== false || strpos($NAME_TEMPLATE, '#SECOND_NAME_SHORT#') !== false)
			$res_check .= $arUser['SECOND_NAME'];

		if (trim($res_check) == '')
		{
			if ($bUseLogin && $arUser['LOGIN'] <> '')
				$res = $arUser['LOGIN'];
			else
				$res = GetMessage('FORMATNAME_NONAME');

			if (strpos($NAME_TEMPLATE, '[#ID#]') !== false)
				$res .= " [".$ID."]";
		}

		if ($bHTMLSpec)
			$res = htmlspecialcharsbx($res);

		$res = str_replace(array('#NOBR#', '#/NOBR#'), '', $res);

		return $res;
	}

	public static function clearUserGroupCache($ID = false)
	{
		if ($ID === false)
		{
			self::$userGroupCache = array();
		}
		else
		{
			$ID = (int)$ID;
			if (isset(self::$userGroupCache[$ID]))
				unset(self::$userGroupCache[$ID]);
		}
	}

	public function CheckAuthActions()
	{
		if(!$this->IsAuthorized())
		{
			return;
		}

		if(!is_array($_SESSION["AUTH_ACTIONS_PERFORMED"]))
		{
			$_SESSION["AUTH_ACTIONS_PERFORMED"] = array();
		}

		$user_id = $this->GetID();

		//calculate a session lifetime
		$policy = $this->GetSecurityPolicy();
		$phpSessTimeout = ini_get("session.gc_maxlifetime");
		if($policy["SESSION_TIMEOUT"] > 0)
		{
			$interval = min($policy["SESSION_TIMEOUT"]*60, $phpSessTimeout);
		}
		else
		{
			$interval = $phpSessTimeout;
		}
		$now = new Main\Type\DateTime();
		$date = new Main\Type\DateTime();
		$date->add("-T".$interval."S");

		$actions = Main\UserAuthActionTable::getList(array(
			"filter" => array("=USER_ID" => $user_id),
			"order" => array("USER_ID" => "ASC", "PRIORITY" => "ASC", "ID" => "DESC"),
			"cache" => array("ttl" => 3600),
		));

		$deleted = false;
		while($action = $actions->fetch())
		{
			if($deleted == false)
			{
				//clear expired records for the user
				Main\UserAuthActionTable::deleteByFilter(array(
					"=USER_ID" => $user_id,
					"<ACTION_DATE" => $date,
				));
				$deleted = true;
			}

			if(isset($_SESSION["AUTH_ACTIONS_PERFORMED"][$action["ID"]]))
			{
				//already processed the action in this session
				continue;
			}

			/** @var Main\Type\DateTime() $actionDate */
			$actionDate = $action["ACTION_DATE"];

			if($actionDate >= $date && $actionDate <= $now)
			{
				//remember that we already did the action
				$_SESSION["AUTH_ACTIONS_PERFORMED"][$action["ID"]] = true;

				if($this->IsJustAuthorized())
				{
					//no need to update the session
					continue;
				}

				switch($action["ACTION"])
				{
					case Main\UserAuthActionTable::ACTION_LOGOUT:
						if($this->GetParam("SELF_CHANGED_PASSWORD") == true)
						{
							//user's changed password by himself, skip logout
							$this->SetParam("SELF_CHANGED_PASSWORD", false);
							break;
						}
						//redirect is possible
						$this->Logout();
						break;

					case Main\UserAuthActionTable::ACTION_UPDATE:
						$this->UpdateSessionData($user_id, $this->GetParam("APPLICATION_ID"));
						break;
				}

				//we need to process only the first action by proirity
				break;
			}
		}
	}

	public static function AuthActionsCleanUpAgent()
	{
		$date = new Main\Type\DateTime();
		$date->add("-1D");
		Main\UserAuthActionTable::deleteByFilter(array("<ACTION_DATE" => $date));
		return 'CUser::AuthActionsCleanUpAgent();';
	}
}

class CAllGroup
{
	var $LAST_ERROR;

	public static function err_mess()
	{
		return "<br>Class: CAllGroup<br>File: ".__FILE__;
	}

	public function CheckFields($arFields, $ID=false)
	{
		global $DB;
		$this->LAST_ERROR = "";

		if(is_set($arFields, "NAME") && $arFields["NAME"] == '')
			$this->LAST_ERROR .= GetMessage("BAD_GROUP_NAME")."<br>";

		if (is_array($arFields["USER_ID"]) && count($arFields["USER_ID"]) > 0)
		{
			if (is_array($arFields["USER_ID"][0]) && count($arFields["USER_ID"][0]) > 0)
			{
				foreach($arFields["USER_ID"] as $arUser)
				{
					if($arUser["DATE_ACTIVE_FROM"] <> '' && !CheckDateTime($arUser["DATE_ACTIVE_FROM"]))
					{
						$error = str_replace("#USER_ID#", $arUser["USER_ID"], GetMessage("WRONG_USER_DATE_ACTIVE_FROM"));
						$this->LAST_ERROR .= $error."<br>";
					}

					if($arUser["DATE_ACTIVE_TO"] <> '' && !CheckDateTime($arUser["DATE_ACTIVE_TO"]))
					{
						$error = str_replace("#USER_ID#", $arUser["USER_ID"], GetMessage("WRONG_USER_DATE_ACTIVE_TO"));
						$this->LAST_ERROR .= $error."<br>";
					}
				}
			}
		}
		if (isset($arFields['STRING_ID']) && $arFields['STRING_ID'] <> '')
		{
			$sql_str = "SELECT G.ID
					FROM b_group G
					WHERE G.STRING_ID='".$DB->ForSql($arFields['STRING_ID'])."'";
			$z = $DB->Query($sql_str, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
			if ($r = $z->Fetch())
			{
				if ($ID === false || $ID != $r['ID'])
					$this->LAST_ERROR .= GetMessage('MAIN_ERROR_STRING_ID')."<br>";
			}
		}
		if($this->LAST_ERROR <> '')
			return false;

		return true;
	}

	public function Update($ID, $arFields)
	{
		/** @global CMain $APPLICATION */
		global $DB, $APPLICATION;

		$ID = intval($ID);

		if(!$this->CheckFields($arFields, $ID))
			return false;

		foreach(GetModuleEvents("main", "OnBeforeGroupUpdate", true) as $arEvent)
		{
			$bEventRes = ExecuteModuleEventEx($arEvent, array($ID, &$arFields));
			if($bEventRes===false)
			{
				if($err = $APPLICATION->GetException())
					$this->LAST_ERROR .= $err->GetString()."<br>";
				else
					$this->LAST_ERROR .= "Unknown error in OnBeforeGroupUpdate handler."."<br>";
				return false;
			}
		}

		if($ID<=2)
			unset($arFields["ACTIVE"]);

		if(is_set($arFields, "ACTIVE") && $arFields["ACTIVE"]!="Y")
			$arFields["ACTIVE"]="N";

		$strUpdate = $DB->PrepareUpdate("b_group", $arFields);

		if(!is_set($arFields, "TIMESTAMP_X"))
			$strUpdate .= ", TIMESTAMP_X = ".$DB->GetNowFunction();


		$strSql = "UPDATE b_group SET $strUpdate WHERE ID=".$ID;
		if(is_set($arFields, "SECURITY_POLICY"))
		{
			if(COption::GetOptionString("main", "event_log_group_policy", "N") === "Y")
			{
				//get old security policy
				$aPrevPolicy = array();
				$res = $DB->Query("SELECT SECURITY_POLICY FROM b_group WHERE ID=".$ID);
				if(($res_arr = $res->Fetch()) && $res_arr["SECURITY_POLICY"] <> '')
					$aPrevPolicy = unserialize($res_arr["SECURITY_POLICY"]);
				//compare with new one
				$aNewPolicy = array();
				if($arFields["SECURITY_POLICY"] <> '')
					$aNewPolicy = unserialize($arFields["SECURITY_POLICY"]);
				$aDiff = array_diff_assoc($aNewPolicy, $aPrevPolicy);
				if(empty($aDiff))
					$aDiff = array_diff_assoc($aPrevPolicy, $aNewPolicy);
				if(!empty($aDiff))
					CEventLog::Log("SECURITY", "GROUP_POLICY_CHANGED", "main", $ID, print_r($aPrevPolicy, true)." => ".print_r($aNewPolicy, true));
			}
			$DB->QueryBind($strSql, array("SECURITY_POLICY"=>$arFields["SECURITY_POLICY"]), false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		}
		else
		{
			$DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		}

		if(is_set($arFields, "USER_ID") && is_array($arFields["USER_ID"]))
		{
			$log = (COption::GetOptionString("main", "event_log_user_groups", "N") === "Y");
			if($log)
			{
				//remember users in the group
				$aPrevUsers = array();
				$res = $DB->Query("SELECT USER_ID FROM b_user_group WHERE GROUP_ID=".$ID.($ID=="1"?" AND USER_ID<>1":""));
				while($res_arr = $res->Fetch())
					$aPrevUsers[] = $res_arr["USER_ID"];
			}

			$DB->Query("DELETE FROM b_user_group WHERE GROUP_ID=".$ID.($ID=="1"?" AND USER_ID<>1":""));

			$arUsers = $arFields["USER_ID"];
			$arTmp = array();
			foreach($arUsers as $user)
			{
				if(!is_array($user))
					$user = array("USER_ID" => $user);

				$user_id = intval($user["USER_ID"]);
				if(
					$user_id > 0
					&& !isset($arTmp[$user_id])
					&& ($ID != 1 || $user_id != 1)
				)
				{
					$arInsert = $DB->PrepareInsert("b_user_group", $user);
					$strSql = "
						INSERT INTO b_user_group (
							GROUP_ID, ".$arInsert[0]."
						) VALUES (
							".$ID.", ".$arInsert[1]."
						)
					";
					$DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
					$arTmp[$user_id] = true;
				}
			}
			$aNewUsers = array_keys($arTmp);
			CUser::clearUserGroupCache();

			if($log)
			{
				/** @noinspection PhpUndefinedVariableInspection */
				foreach($aPrevUsers as $user_id)
				{
					if(!in_array($user_id, $aNewUsers))
					{
						$UserName = '';
						$rsUser = CUser::GetByID($user_id);
						if($arUser = $rsUser->GetNext())
							$UserName = ($arUser["NAME"] != "" || $arUser["LAST_NAME"] != "") ? trim($arUser["NAME"]." ".$arUser["LAST_NAME"]) : $arUser["LOGIN"];
						$res_log = array(
							"groups" => "-(".$ID.")",
							"user" => $UserName
						);
						CEventLog::Log("SECURITY", "USER_GROUP_CHANGED", "main", $user_id, serialize($res_log));
					}
				}

				foreach($aNewUsers as $user_id)
				{
					if(!in_array($user_id, $aPrevUsers))
					{
						$UserName = '';
						$rsUser = CUser::GetByID($user_id);
						if($arUser = $rsUser->GetNext())
							$UserName = ($arUser["NAME"] != "" || $arUser["LAST_NAME"] != "") ? trim($arUser["NAME"]." ".$arUser["LAST_NAME"]) : $arUser["LOGIN"];
						$res_log = array(
							"groups" =>  "+(".$ID.")",
							"user" => $UserName
						);
						CEventLog::Log("SECURITY", "USER_GROUP_CHANGED", "main", $user_id, serialize($res_log));
					}
				}
			}
		}

		foreach (GetModuleEvents("main", "OnAfterGroupUpdate", true) as $arEvent)
			ExecuteModuleEventEx($arEvent, array($ID, &$arFields));

		return true;
	}

	public static function Delete($ID)
	{
		/** @global CMain $APPLICATION */
		global $APPLICATION, $DB;

		$ID = intval($ID);
		if($ID<=2)
			return false;

		@set_time_limit(600);

		foreach(GetModuleEvents("main", "OnBeforeGroupDelete", true) as $arEvent)
		{
			if(ExecuteModuleEventEx($arEvent, array($ID))===false)
			{
				$err = GetMessage("MAIN_BEFORE_DEL_ERR").' '.$arEvent['TO_NAME'];
				if($ex = $APPLICATION->GetException())
					$err .= ': '.$ex->GetString();
				$APPLICATION->throwException($err);
				return false;
			}
		}

		foreach(GetModuleEvents("main", "OnGroupDelete", true) as $arEvent)
			ExecuteModuleEventEx($arEvent, array($ID));

		CMain::DelGroupRight("",array($ID));

		if(!$DB->Query("DELETE FROM b_user_group WHERE GROUP_ID=".$ID." AND GROUP_ID>2", true))
			return false;
		CUser::clearUserGroupCache();

		return $DB->Query("DELETE FROM b_group WHERE ID=".$ID." AND ID>2", true);
	}

	public static function GetGroupUser($ID)
	{
		global $DB;
		$ID = intval($ID);

		if ($ID == 2)
		{
			$strSql = "SELECT U.ID as USER_ID FROM b_user U ";
		}
		else
		{
			$strSql =
				"SELECT UG.USER_ID ".
				"FROM b_user_group UG ".
				"WHERE UG.GROUP_ID = ".$ID." ".
				"	AND ((UG.DATE_ACTIVE_FROM IS NULL) OR (UG.DATE_ACTIVE_FROM <= ".$DB->CurrentTimeFunction().")) ".
				"	AND ((UG.DATE_ACTIVE_TO IS NULL) OR (UG.DATE_ACTIVE_TO >= ".$DB->CurrentTimeFunction().")) ";
		}

		$res = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		$arr = array();
		while($r = $res->Fetch())
			$arr[]=$r["USER_ID"];

		return $arr;
	}

	public static function GetGroupUserEx($ID)
	{
		global $DB;
		$ID = intval($ID);

		if ($ID == 2)
		{
			$strSql = "SELECT U.ID as USER_ID, NULL as DATE_ACTIVE_FROM, NULL as DATE_ACTIVE_TO FROM b_user U ";
		}
		else
		{
			$strSql =
				"SELECT UG.USER_ID, ".
				"	".$DB->DateToCharFunction("UG.DATE_ACTIVE_FROM", "FULL")." as DATE_ACTIVE_FROM, ".
				"	".$DB->DateToCharFunction("UG.DATE_ACTIVE_TO", "FULL")." as DATE_ACTIVE_TO ".
				"FROM b_user_group UG ".
				"WHERE UG.GROUP_ID = ".$ID." ".
				"	AND ((UG.DATE_ACTIVE_FROM IS NULL) OR (UG.DATE_ACTIVE_FROM <= ".$DB->CurrentTimeFunction().")) ".
				"	AND ((UG.DATE_ACTIVE_TO IS NULL) OR (UG.DATE_ACTIVE_TO >= ".$DB->CurrentTimeFunction().")) ";
		}
		$res = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);

		return $res;
	}

	public static function GetMaxSort()
	{
		global $DB;
		$err_mess = (CAllGroup::err_mess())."<br>Function: GetMaxSort<br>Line: ";
		$z = $DB->Query("SELECT max(C_SORT) M FROM b_group", false, $err_mess.__LINE__);
		$zr = $z->Fetch();
		return intval($zr["M"])+100;
	}

	public static function GetSubordinateGroups($grId)
	{
		global $DB, $CACHE_MANAGER;

		$groupFilter = array();
		if (is_array($grId))
		{
			foreach ($grId as $id)
			{
				$id = intval($id);
				if ($id > 0)
					$groupFilter[$id] = $id;
			}
		}
		else
		{
			$id = intval($grId);
			if ($id > 0)
				$groupFilter[$id] = $id;
		}

		$result = array(2);
		if (!empty($groupFilter))
		{
			if (CACHED_b_group_subordinate === false)
			{
				$z = $DB->Query("SELECT AR_SUBGROUP_ID FROM b_group_subordinate WHERE ID in (".implode(", ", $groupFilter).")");
				while ($zr = $z->Fetch())
				{
					$subordinateGroups = explode(",", $zr['AR_SUBGROUP_ID']);
					if (count($subordinateGroups) == 1 && !$subordinateGroups[0])
						continue;
					$result = array_merge($result, $subordinateGroups);
				}
			}
			else
			{
				if ($CACHE_MANAGER->Read(CACHED_b_group_subordinate, "b_group_subordinate"))
				{
					$cache = $CACHE_MANAGER->Get("b_group_subordinate");
				}
				else
				{
					$cache = array();
					$z = $DB->Query("SELECT ID, AR_SUBGROUP_ID FROM b_group_subordinate");
					while ($zr = $z->Fetch())
					{
						$subordinateGroups = explode(",", $zr['AR_SUBGROUP_ID']);
						if (count($subordinateGroups) == 1 && !$subordinateGroups[0])
							continue;
						$cache[$zr["ID"]] = $subordinateGroups;
					}
					$CACHE_MANAGER->Set("b_group_subordinate", $cache);
				}

				foreach ($cache as $groupId => $subordinateGroups)
				{
					if (isset($groupFilter[$groupId]))
					{
						$result = array_merge($result, $subordinateGroups);
					}
				}
			}
		}

		return array_unique($result);
	}

	public static function SetSubordinateGroups($grId, $arSubGroups=false)
	{
		global $DB, $CACHE_MANAGER;
		$grId = intval($grId);

		$DB->Query("DELETE FROM b_group_subordinate WHERE ID = ".$grId);
		if(is_array($arSubGroups))
		{
			$arInsert = $DB->PrepareInsert("b_group_subordinate", array(
				"ID" => $grId,
				"AR_SUBGROUP_ID" => implode(",", $arSubGroups),
			));
			$DB->Query("INSERT INTO b_group_subordinate(".$arInsert[0].") VALUES (".$arInsert[1].")");
		}
		$CACHE_MANAGER->Clean("b_group_subordinate");
	}


	public static function GetTasks($ID, $onlyMainTasks=true, $module_id=false)
	{
		global $DB;

		$sql_str = 'SELECT GT.TASK_ID,T.MODULE_ID,GT.EXTERNAL_ID
			FROM b_group_task GT
			INNER JOIN b_task T ON (T.ID=GT.TASK_ID)
			WHERE GT.GROUP_ID='.intval($ID);
		if ($module_id !== false)
			$sql_str .= ' AND T.MODULE_ID="'.$DB->ForSQL($module_id).'"';

		$z = $DB->Query($sql_str, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		$arr = array();
		$ex_arr = array();
		while($r = $z->Fetch())
		{
			if (!$r['EXTERNAL_ID'])
				$arr[$r['MODULE_ID']] = $r['TASK_ID'];
			else
				$ex_arr[] = $r;
		}
		if ($onlyMainTasks)
			return $arr;
		else
			return array($arr,$ex_arr);
	}


	public static function SetTasks($ID, $arr)
	{
		global $DB;
		$ID = intval($ID);

		if(COption::GetOptionString("main", "event_log_module_access", "N") === "Y")
		{
			//get old values
			$arOldTasks = array();
			$rsTask = $DB->Query("SELECT TASK_ID FROM b_group_task WHERE GROUP_ID=".$ID);
			while($arTask = $rsTask->Fetch())
				$arOldTasks[] = $arTask["TASK_ID"];
			//compare with new ones
			$aNewTasks = array();
			foreach($arr as $task_id)
				if($task_id > 0)
					$aNewTasks[] = $task_id;
			$aDiff = array_diff($arOldTasks, $aNewTasks);
			if(empty($aDiff))
				$aDiff = array_diff($aNewTasks, $arOldTasks);
			if(!empty($aDiff))
				CEventLog::Log("SECURITY", "MODULE_RIGHTS_CHANGED", "main", $ID, "(".implode(", ", $arOldTasks).") => (".implode(", ", $aNewTasks).")");
		}

		$sql_str = "DELETE FROM b_group_task WHERE GROUP_ID=".$ID.
				" AND (EXTERNAL_ID IS NULL OR EXTERNAL_ID = '')";
		$DB->Query($sql_str, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);

		$sID = "0";
		if(is_array($arr))
			foreach($arr as $task_id)
				$sID .= ",".intval($task_id);

		$DB->Query(
			"INSERT INTO b_group_task (GROUP_ID, TASK_ID, EXTERNAL_ID) ".
			"SELECT '".$ID."', ID, '' ".
			"FROM b_task ".
			"WHERE ID IN (".$sID.") "
			, false, "File: ".__FILE__."<br>Line: ".__LINE__
		);
	}


	public static function GetTasksForModule($module_id, $onlyMainTasks = true)
	{
		global $DB;

		$sql_str = "SELECT GT.TASK_ID,GT.GROUP_ID,GT.EXTERNAL_ID,T.NAME
			FROM b_group_task GT
			INNER JOIN b_task T ON (T.ID=GT.TASK_ID)
			WHERE T.MODULE_ID='".$DB->ForSQL($module_id)."'";

		$z = $DB->Query($sql_str, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);

		$main_arr = array();
		$ext_arr = array();
		while($r = $z->Fetch())
		{
			if (!$r['EXTERNAL_ID'])
			{
				$main_arr[$r['GROUP_ID']] = array('ID'=>$r['TASK_ID'],'NAME'=>$r['NAME']);
			}
			elseif(!$onlyMainTasks)
			{
				if (!isset($ext_arr[$r['GROUP_ID']]))
					$ext_arr[$r['GROUP_ID']] = array();
				$ext_arr[$r['GROUP_ID']][] = array('ID'=>$r['TASK_ID'],'NAME'=>$r['NAME'],'EXTERNAL_ID'=>$r['EXTERNAL_ID']);
			}
		}
		if ($onlyMainTasks)
			return $main_arr;
		else
			return array($main_arr,$ext_arr);
	}


	public static function SetTasksForModule($module_id, $arGroupTask)
	{
		global $DB;

		$module_id = $DB->ForSql($module_id);
		$sql_str = "SELECT T.ID
			FROM b_task T
			WHERE T.MODULE_ID='".$module_id."'";
		$r = $DB->Query($sql_str, false, "File: ".__FILE__."<br>Line: ".__LINE__);
		$arIds = array();
		while($arR = $r->Fetch())
			$arIds[] = $arR['ID'];

		if(COption::GetOptionString("main", "event_log_module_access", "N") === "Y")
		{
			//get old values
			$arOldTasks = array();
			if(!empty($arIds))
			{
				$rsTask = $DB->Query("SELECT GROUP_ID, TASK_ID FROM b_group_task WHERE TASK_ID IN (".implode(",", $arIds).")");
				while($arTask = $rsTask->Fetch())
					$arOldTasks[$arTask["GROUP_ID"]] = $arTask["TASK_ID"];
			}
			//compare with new ones
			foreach($arOldTasks as $gr_id=>$task_id)
				if($task_id <> $arGroupTask[$gr_id]['ID'])
					CEventLog::Log("SECURITY", "MODULE_RIGHTS_CHANGED", "main", $gr_id, $module_id.": (".$task_id.") => (".$arGroupTask[$gr_id]['ID'].")");
			foreach($arGroupTask as $gr_id => $oTask)
				if(intval($oTask['ID']) > 0 && !array_key_exists($gr_id, $arOldTasks))
					CEventLog::Log("SECURITY", "MODULE_RIGHTS_CHANGED", "main", $gr_id, $module_id.": () => (".$oTask['ID'].")");
		}

		if(!empty($arIds))
		{
			$sql_str = "DELETE FROM b_group_task WHERE TASK_ID IN (".implode(",", $arIds).")";
			$DB->Query($sql_str, false, "File: ".__FILE__."<br>Line: ".__LINE__);
		}

		foreach($arGroupTask as $gr_id => $oTask)
		{
			if(intval($oTask['ID']) > 0)
			{
				$DB->Query(
					"INSERT INTO b_group_task (GROUP_ID, TASK_ID, EXTERNAL_ID) ".
					"SELECT G.ID, T.ID, '' ".
					"FROM b_group G, b_task T ".
					"WHERE G.ID = ".intval($gr_id)." AND
					T.ID = ".intval($oTask['ID']),
					false, "File: ".__FILE__."<br>Line: ".__LINE__
				);
			}
		}
	}

	public static function GetModulePermission($group_id, $module_id)
	{
		/** @global CMain $APPLICATION */
		global $APPLICATION, $DB;

		// check module permissions mode
		$strSql = "SELECT T.ID, GT.TASK_ID FROM b_task T LEFT JOIN b_group_task GT ON T.ID=GT.TASK_ID AND GT.GROUP_ID=".intval($group_id)." WHERE T.MODULE_ID='".$DB->ForSql($module_id)."'";
		$dbr_tasks = $DB->Query($strSql, false, "File: ".__FILE__."<br>Line: ".__LINE__);
		if($ar_task = $dbr_tasks->Fetch())
		{
			do
			{
				if($ar_task["TASK_ID"]>0)
					return $ar_task["TASK_ID"];
			}
			while ($ar_task = $dbr_tasks->Fetch());

			return false;
		}

		return $APPLICATION->GetGroupRight($module_id, array($group_id), "N", "N");
	}

	public static function SetModulePermission($group_id, $module_id, $permission)
	{
		/** @global CMain $APPLICATION */
		global $DB, $APPLICATION;

		if(intval($permission)<=0 && $permission != false)
		{
			$strSql = "SELECT T.ID FROM b_task T WHERE T.MODULE_ID='".$DB->ForSql($module_id)."' AND NAME='".$DB->ForSql($permission)."'";
			$db_task = $DB->Query($strSql);
			if($ar_task=$db_task->Fetch())
				$permission = $ar_task['ID'];
		}

		$permission_letter = '';
		if(intval($permission)>0 || $permission === false)
		{
			$strSql = "SELECT T.ID FROM b_task T WHERE T.MODULE_ID='".$DB->ForSql($module_id)."'";
			$dbr_tasks = $DB->Query($strSql, false, "File: ".__FILE__."<br>Line: ".__LINE__);
			$arIds = array();
			while($arTask = $dbr_tasks->Fetch())
				$arIds[] = $arTask['ID'];

			if(!empty($arIds))
			{
				$strSql = "DELETE FROM b_group_task WHERE GROUP_ID=".intval($group_id)." AND TASK_ID IN (".implode(",", $arIds).")";
				$DB->Query($strSql, false, "File: ".__FILE__."<br>Line: ".__LINE__);
			}

			if(intval($permission)>0)
			{
				$DB->Query(
					"INSERT INTO b_group_task (GROUP_ID, TASK_ID, EXTERNAL_ID) ".
					"SELECT G.ID, T.ID, '' ".
					"FROM b_group G, b_task T ".
					"WHERE G.ID = ".intval($group_id)." AND T.ID = ".intval($permission),
					false,
					"File: ".__FILE__."<br>Line: ".__LINE__
				);

				$permission_letter = CTask::GetLetter($permission);
			}
		}
		else
		{
			$permission_letter = $permission;
		}

		if($permission_letter <> '')
			$APPLICATION->SetGroupRight($module_id, $group_id, $permission_letter);
		else
			$APPLICATION->DelGroupRight($module_id, array($group_id));
	}

	public static function GetIDByCode($code)
	{
		if(strval(intval($code)) == $code && $code > 0)
			return $code;

		if(strtolower($code) == 'administrators')
			return 1;

		if(strtolower($code) == 'everyone')
			return 2;

		global $DB;

		$strSql = "SELECT G.ID FROM b_group G WHERE G.STRING_ID='".$DB->ForSQL($code)."'";
		$db_res = $DB->Query($strSql);

		if($ar_res = $db_res->Fetch())
			return $ar_res["ID"];

		return false;
	}
}


class CAllTask
{
	public static function err_mess()
	{
		return "<br>Class: CAllTask<br>File: ".__FILE__;
	}

	public static function CheckFields(&$arFields, $ID = false)
	{
		/** @global CMain $APPLICATION */
		global $DB, $APPLICATION;

		if($ID>0)
			unset($arFields["ID"]);

		$arMsg = array();

		if(($ID===false || is_set($arFields, "NAME")) && $arFields["NAME"] == '')
			$arMsg[] = array("id"=>"NAME", "text"=> GetMessage('MAIN_ERROR_STRING_ID_EMPTY'));

		$sql_str = "SELECT T.ID
			FROM b_task T
			WHERE T.NAME='".$DB->ForSQL($arFields['NAME'])."'";
		if ($ID !== false)
			$sql_str .= " AND T.ID <> ".intval($ID);

		$z = $DB->Query($sql_str, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		if ($r = $z->Fetch())
			$arMsg[] = array("id"=>"NAME", "text"=> GetMessage('MAIN_ERROR_STRING_ID_DOUBLE'));

		if (isset($arFields['LETTER']))
		{
			if (preg_match("/[^A-Z]/i", $arFields['LETTER']) || strlen($arFields['LETTER']) > 1)
				$arMsg[] = array("id"=>"LETTER", "text"=> GetMessage('MAIN_TASK_WRONG_LETTER'));
			$arFields['LETTER'] = strtoupper($arFields['LETTER']);
		}
		else
		{
			$arFields['LETTER'] = '';
		}

		if(count($arMsg)>0)
		{
			$e = new CAdminException($arMsg);
			$APPLICATION->ThrowException($e);
			return false;
		}
		if (!isset($arFields['SYS']) || $arFields['SYS'] != "Y")
			$arFields['SYS'] = "N";
		if (!isset($arFields['BINDING']))
			$arFields['BINDING'] = 'module';

		return true;
	}

	public static function Add($arFields)
	{
		global $CACHE_MANAGER, $DB;

		if(!CTask::CheckFields($arFields))
			return false;

		if(CACHED_b_task !== false)
			$CACHE_MANAGER->CleanDir("b_task");

		$ID = $DB->Add("b_task", $arFields);
		return $ID;
	}

	public static function Update($arFields,$ID)
	{
		global $DB, $CACHE_MANAGER;

		if(!CTask::CheckFields($arFields,$ID))
			return false;

		$strUpdate = $DB->PrepareUpdate("b_task", $arFields);

		if($strUpdate)
		{
			if(CACHED_b_task !== false)
				$CACHE_MANAGER->CleanDir("b_task");
			$strSql =
				"UPDATE b_task SET ".
					$strUpdate.
				" WHERE ID=".intval($ID);
			$DB->Query($strSql, false, "File: ".__FILE__."<br>Line: ".__LINE__);
		}
		return true;
	}

	public static function UpdateModuleRights($id, $moduleId, $letter, $site_id = false)
	{
		global $DB;

		if (!isset($id, $moduleId))
			return false;

		$sql = "SELECT GT.GROUP_ID
				FROM b_group_task GT
				WHERE GT.TASK_ID=".intval($id);
		$z = $DB->Query($sql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);

		$arGroups = array();
		while($r = $z->Fetch())
		{
			$g = intval($r['GROUP_ID']);
			if ($g > 0)
				$arGroups[] = $g;
		}
		if (count($arGroups) == 0)
			return false;

		$str_groups = implode(',', $arGroups);
		$moduleId = $DB->ForSQL($moduleId);
		$DB->Query(
			"DELETE FROM b_module_group
			WHERE
				MODULE_ID = '".$moduleId."' AND
				SITE_ID ".($site_id ? "='".$site_id."'" : "IS NULL")." AND
				GROUP_ID IN (".$str_groups.")",
			false, "FILE: ".__FILE__."<br> LINE: ".__LINE__
		);

		if ($letter == '')
			return false;

		$letter = $DB->ForSQL($letter);
		$DB->Query(
			"INSERT INTO b_module_group (MODULE_ID, GROUP_ID, G_ACCESS, SITE_ID) ".
			"SELECT '".$moduleId."', G.ID, '".$letter."', ".($site_id ? "'".$site_id."'" : "NULL")." ".
			"FROM b_group G ".
			"WHERE G.ID IN (".$str_groups.")"
			, false, "File: ".__FILE__."<br>Line: ".__LINE__
		);
		return true;
	}

	public static function Delete($ID, $protect = true)
	{
		global $DB, $CACHE_MANAGER;

		$ID = intval($ID);

		if(CACHED_b_task !== false)
			$CACHE_MANAGER->CleanDir("b_task");

		$sql_str = "DELETE FROM b_task WHERE ID=".$ID;
		if ($protect)
			$sql_str .= " AND SYS='N'";
		$DB->Query($sql_str, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);

		if (!$protect)
		{
			if(CACHED_b_task_operation !== false)
				$CACHE_MANAGER->CleanDir("b_task_operation");

			$DB->Query("DELETE FROM b_task_operation WHERE TASK_ID=".$ID, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		}
	}

	public static function GetList($arOrder = array('MODULE_ID'=>'asc','LETTER'=>'asc'), $arFilter = array())
	{
		global $DB, $CACHE_MANAGER;;

		if(CACHED_b_task !== false)
		{
			$context = Main\Context::getCurrent();
			$cacheId = "b_task".md5(serialize($arOrder).".".serialize($arFilter).".".$context->getLanguage());
			if($CACHE_MANAGER->Read(CACHED_b_task, $cacheId, "b_task"))
			{
				$arResult = $CACHE_MANAGER->Get($cacheId);
				$res = new CDBResult;
				$res->InitFromArray($arResult);
				return $res;
			}
		}

		static $arFields = array(
			"ID" => array("FIELD_NAME" => "T.ID", "FIELD_TYPE" => "int"),
			"NAME" => array("FIELD_NAME" => "T.NAME", "FIELD_TYPE" => "string"),
			"LETTER" => array("FIELD_NAME" => "T.LETTER", "FIELD_TYPE" => "string"),
			"MODULE_ID" => array("FIELD_NAME" => "T.MODULE_ID", "FIELD_TYPE" => "string"),
			"SYS" => array("FIELD_NAME" => "T.SYS", "FIELD_TYPE" => "string"),
			"BINDING" => array("FIELD_NAME" => "T.BINDING", "FIELD_TYPE" => "string")
		);

		$err_mess = (CAllTask::err_mess())."<br>Function: GetList<br>Line: ";
		$arSqlSearch = array();
		if(is_array($arFilter))
		{
			foreach($arFilter as $n => $val)
			{
				$n = strtoupper($n);
				if(strlen($val) <= 0 || strval($val) == "NOT_REF")
					continue;

				if(isset($arFields[$n]))
				{
					$arSqlSearch[] = GetFilterQuery($arFields[$n]["FIELD_NAME"], $val, ($n == 'NAME'? "Y" : "N"));
				}
			}
		}

		$strOrderBy = '';
		foreach($arOrder as $by=>$order)
			if(isset($arFields[strtoupper($by)]))
				$strOrderBy .= $arFields[strtoupper($by)]["FIELD_NAME"].' '.(strtolower($order)=='desc'?'desc'.(strtoupper($DB->type)=="ORACLE"?" NULLS LAST":""):'asc'.(strtoupper($DB->type)=="ORACLE"?" NULLS FIRST":"")).',';

		if($strOrderBy <> '')
			$strOrderBy = "ORDER BY ".rtrim($strOrderBy, ",");

		$strSqlSearch = GetFilterSqlSearch($arSqlSearch);
		$strSql = "
			SELECT
				T.ID, T.NAME, T.DESCRIPTION, T.MODULE_ID, T.LETTER, T.SYS, T.BINDING
			FROM
				b_task T
			WHERE
				".$strSqlSearch."
			".$strOrderBy;

		$res = $DB->Query($strSql, false, $err_mess.__LINE__);

		$arResult = array();
		while($arRes = $res->Fetch())
		{
			$arRes['TITLE'] = CTask::GetLangTitle($arRes['NAME'], $arRes['MODULE_ID']);
			$arRes['DESC'] = CTask::GetLangDescription($arRes['NAME'], $arRes['DESCRIPTION'], $arRes['MODULE_ID']);
			$arResult[] = $arRes;
		}
		$res->InitFromArray($arResult);

		if(CACHED_b_task !== false)
		{
			/** @noinspection PhpUndefinedVariableInspection */
			$CACHE_MANAGER->Set($cacheId, $arResult);
		}

		return $res;
	}


	public static function GetOperations($ID, $return_names = false)
	{
		global $DB, $CACHE_MANAGER;
		static $TASK_OPERATIONS_CACHE = array();
		$ID = intval($ID);

		if (!isset($TASK_OPERATIONS_CACHE[$ID]))
		{
			if(CACHED_b_task_operation !== false)
			{
				$cacheId = "b_task_operation_".$ID;
				if($CACHE_MANAGER->Read(CACHED_b_task_operation, $cacheId, "b_task_operation"))
				{
					$TASK_OPERATIONS_CACHE[$ID] = $CACHE_MANAGER->Get($cacheId);
				}
			}
		}

		if (!isset($TASK_OPERATIONS_CACHE[$ID]))
		{
			$sql_str = '
				SELECT T_O.OPERATION_ID, O.NAME
				FROM b_task_operation T_O
				INNER JOIN b_operation O ON T_O.OPERATION_ID = O.ID
				WHERE T_O.TASK_ID = '.$ID.'
			';

			$TASK_OPERATIONS_CACHE[$ID] = array(
				'names' => array(),
				'ids' => array(),
			);
			$z = $DB->Query($sql_str, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
			while($r = $z->Fetch())
			{
				$TASK_OPERATIONS_CACHE[$ID]['names'][] = $r['NAME'];
				$TASK_OPERATIONS_CACHE[$ID]['ids'][] = $r['OPERATION_ID'];
			}

			if(CACHED_b_task_operation !== false)
			{
				/** @noinspection PhpUndefinedVariableInspection */
				$CACHE_MANAGER->Set($cacheId, $TASK_OPERATIONS_CACHE[$ID]);
			}
		}

		return $TASK_OPERATIONS_CACHE[$ID][$return_names ? 'names' : 'ids'];
	}

	public static function SetOperations($ID, $arr, $bOpNames=false)
	{
		global $DB, $CACHE_MANAGER;

		$ID = intval($ID);

		//get old operations
		$aPrevOp = array();
		$res = $DB->Query("
			SELECT O.NAME
			FROM b_operation O
			INNER JOIN b_task_operation T_OP ON O.ID = T_OP.OPERATION_ID
			WHERE T_OP.TASK_ID = ".$ID."
			ORDER BY O.ID
		");
		while(($res_arr = $res->Fetch()))
			$aPrevOp[] = $res_arr["NAME"];

		$sql_str = 'DELETE FROM b_task_operation WHERE TASK_ID='.$ID;
		$DB->Query($sql_str, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);

		if(is_array($arr) && count($arr)>0)
		{
			if($bOpNames)
			{
				$sID = "";
				foreach($arr as $op_id)
					$sID .= ",'".$DB->ForSQL($op_id)."'";
				$sID = LTrim($sID, ",");

				$DB->Query(
					"INSERT INTO b_task_operation (TASK_ID, OPERATION_ID) ".
					"SELECT '".$ID."', O.ID ".
					"FROM b_operation O, b_task T ".
					"WHERE O.NAME IN (".$sID.") AND T.MODULE_ID=O.MODULE_ID AND T.ID=".$ID." "
					, false, "File: ".__FILE__."<br>Line: ".__LINE__
				);
			}
			else
			{
				$sID = "0";
				foreach($arr as $op_id)
					$sID .= ",".intval($op_id);

				$DB->Query(
					"INSERT INTO b_task_operation (TASK_ID, OPERATION_ID) ".
					"SELECT '".$ID."', ID ".
					"FROM b_operation ".
					"WHERE ID IN (".$sID.") "
					, false, "File: ".__FILE__."<br>Line: ".__LINE__
				);
			}
		}

		if(CACHED_b_task_operation !== false)
			$CACHE_MANAGER->CleanDir("b_task_operation");

		//get new operations
		$aNewOp = array();
		$res = $DB->Query("
			SELECT O.NAME
			FROM b_operation O
			INNER JOIN b_task_operation T_OP ON O.ID = T_OP.OPERATION_ID
			WHERE T_OP.TASK_ID = ".$ID."
			ORDER BY O.ID
		");
		while(($res_arr = $res->Fetch()))
			$aNewOp[] = $res_arr["NAME"];

		//compare with old one
		$aDiff = array_diff($aNewOp, $aPrevOp);
		if(empty($aDiff))
			$aDiff = array_diff($aPrevOp, $aNewOp);
		if(!empty($aDiff))
		{
			if(COption::GetOptionString("main", "event_log_task", "N") === "Y")
				CEventLog::Log("SECURITY", "TASK_CHANGED", "main", $ID, "(".implode(", ", $aPrevOp).") => (".implode(", ", $aNewOp).")");
			foreach(GetModuleEvents("main", "OnTaskOperationsChanged", true) as $arEvent)
				ExecuteModuleEventEx($arEvent, array($ID, $aPrevOp, $aNewOp));
		}
	}

	public static function GetTasksInModules($mode=false, $module_id=false, $binding = false)
	{
		$arFilter = array();
		if ($module_id !== false)
			$arFilter["MODULE_ID"] = $module_id;
		if ($binding !== false)
			$arFilter["BINDING"] = $binding;

		$z = CTask::GetList(
			array(
				"MODULE_ID" => "asc",
				"LETTER" => "asc"
			),
			$arFilter
		);

		$arr = array();
		if ($mode)
		{
			while($r = $z->Fetch())
			{
				if (!is_array($arr[$r['MODULE_ID']]))
					$arr[$r['MODULE_ID']] = array('reference_id'=>array(),'reference'=>array());

				$arr[$r['MODULE_ID']]['reference_id'][] = $r['ID'];
				$arr[$r['MODULE_ID']]['reference'][] = '['.($r['LETTER'] ? $r['LETTER'] : '..').'] '.CTask::GetLangTitle($r['NAME'], $r['MODULE_ID']);
			}
		}
		else
		{
			while($r = $z->Fetch())
			{
				if (!is_array($arr[$r['MODULE_ID']]))
					$arr[$r['MODULE_ID']] = array();

				$arr[$r['MODULE_ID']][] = $r;
			}
		}
		return $arr;
	}

	public static function GetByID($ID)
	{
		return CTask::GetList(array(), array("ID" => intval($ID)));
	}

	protected static function GetDescriptions($module)
	{
		static $descriptions = array();

		if(preg_match("/[^a-z0-9._]/i", $module))
		{
			return array();
		}

		if(!isset($descriptions[$module]))
		{
			if(($path = getLocalPath("modules/".$module."/admin/task_description.php")) !== false)
			{
				$descriptions[$module] = include($_SERVER["DOCUMENT_ROOT"].$path);
			}
			else
			{
				$descriptions[$module] = array();
			}
		}

		return $descriptions[$module];
	}

	public static function GetLangTitle($name, $module = "main")
	{
		$descriptions = static::GetDescriptions($module);

		$nameUpper = strtoupper($name);

		if(isset($descriptions[$nameUpper]["title"]))
		{
			return $descriptions[$nameUpper]["title"];
		}

		return $name;
	}

	public static function GetLangDescription($name, $desc, $module = "main")
	{
		$descriptions = static::GetDescriptions($module);

		$nameUpper = strtoupper($name);

		if(isset($descriptions[$nameUpper]["description"]))
		{
			return $descriptions[$nameUpper]["description"];
		}

		return $desc;
	}

	public static function GetLetter($ID)
	{
		$z = CTask::GetById($ID);
		if ($r = $z->Fetch())
			if ($r['LETTER'])
				return $r['LETTER'];
		return false;
	}

	public static function GetIdByLetter($letter, $module, $binding='module')
	{
		static $TASK_LETTER_CACHE = array();
		if (!$letter)
			return false;

		if (!isset($TASK_LETTER_CACHE))
			$TASK_LETTER_CACHE = array();

		$k = strtoupper($letter.'_'.$module.'_'.$binding);
		if (isset($TASK_LETTER_CACHE[$k]))
			return $TASK_LETTER_CACHE[$k];

		$z = CTask::GetList(
			array(),
			array(
				"LETTER" => $letter,
				"MODULE_ID" => $module,
				"BINDING" => $binding,
				"SYS"=>"Y"
			)
		);

		if ($r = $z->Fetch())
		{
			$TASK_LETTER_CACHE[$k] = $r['ID'];
			if ($r['ID'])
				return $r['ID'];
		}

		return false;
	}
}

class CAllOperation
{
	public static function err_mess()
	{
		return "<br>Class: CAllOperation<br>File: ".__FILE__;
	}

	public static function GetList($arOrder = array('MODULE_ID'=>'asc'),$arFilter=array())
	{
		global $DB;

		static $arFields = array(
			"ID" => array("FIELD_NAME" => "O.ID", "FIELD_TYPE" => "int"),
			"NAME" => array("FIELD_NAME" => "O.NAME", "FIELD_TYPE" => "string"),
			"MODULE_ID" => array("FIELD_NAME" => "O.MODULE_ID", "FIELD_TYPE" => "string"),
			"BINDING" => array("FIELD_NAME" => "O.BINDING", "FIELD_TYPE" => "string")
		);

		$err_mess = (CAllOperation::err_mess())."<br>Function: GetList<br>Line: ";
		$arSqlSearch = array();
		if(is_array($arFilter))
		{
			foreach($arFilter as $n => $val)
			{
				$n = strtoupper($n);
				if($val == '' || strval($val)=="NOT_REF")
					continue;
				if ($n == 'ID' || $n == 'MODULE_ID' || $n == 'BINDING')
					$arSqlSearch[] = GetFilterQuery($arFields[$n]["FIELD_NAME"], $val, 'N');
				elseif(isset($arFields[$n]))
					$arSqlSearch[] = GetFilterQuery($arFields[$n]["FIELD_NAME"], $val);
			}
		}

		$strOrderBy = '';
		foreach($arOrder as $by=>$order)
			if(isset($arFields[strtoupper($by)]))
				$strOrderBy .= $arFields[strtoupper($by)]["FIELD_NAME"].' '.(strtolower($order)=='desc'?'desc'.(strtoupper($DB->type)=="ORACLE"?" NULLS LAST":""):'asc'.(strtoupper($DB->type)=="ORACLE"?" NULLS FIRST":"")).',';

		if($strOrderBy <> '')
			$strOrderBy = "ORDER BY ".rtrim($strOrderBy, ",");

		$strSqlSearch = GetFilterSqlSearch($arSqlSearch);
		$strSql = "
			SELECT *
			FROM
				b_operation O
			WHERE
				".$strSqlSearch."
			".$strOrderBy;

		$res = $DB->Query($strSql, false, $err_mess.__LINE__);
		return $res;
	}

	public static function GetAllowedModules()
	{
		global $DB;
		$sql_str = 'SELECT DISTINCT O.MODULE_ID FROM b_operation O';
		$z = $DB->Query($sql_str, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		$arr = array();
		while($r = $z->Fetch())
			$arr[] = $r['MODULE_ID'];
		return $arr;
	}

	public static function GetBindingList()
	{
		global $DB;
		$sql_str = 'SELECT DISTINCT O.MODULE_ID, O.BINDING FROM b_operation O';
		$z = $DB->Query($sql_str, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
		$arr = array();
		while($r = $z->Fetch())
			$arr[] = $r;
		return $arr;
	}

	public static function GetIDByName($name)
	{
		$z = COperation::GetList(array('MODULE_ID' => 'asc'), array("NAME" => $name));
		if ($r = $z->Fetch())
			return $r['ID'];
		return false;
	}

	protected static function GetDescriptions($module)
	{
		static $descriptions = array();

		if(preg_match("/[^a-z0-9._]/i", $module))
		{
			return array();
		}

		if(!isset($descriptions[$module]))
		{
			if(($path = getLocalPath("modules/".$module."/admin/operation_description.php")) !== false)
			{
				$descriptions[$module] = include($_SERVER["DOCUMENT_ROOT"].$path);
			}
			else
			{
				$descriptions[$module] = array();
			}
		}

		return $descriptions[$module];
	}

	public static function GetLangTitle($name, $module = "main")
	{
		$descriptions = static::GetDescriptions($module);

		$nameUpper = strtoupper($name);

		if(isset($descriptions[$nameUpper]["title"]))
		{
			return $descriptions[$nameUpper]["title"];
		}

		return $name;
	}

	public static function GetLangDescription($name, $desc, $module = "main")
	{
		$descriptions = static::GetDescriptions($module);

		$nameUpper = strtoupper($name);

		if(isset($descriptions[$nameUpper]["description"]))
		{
			return $descriptions[$nameUpper]["description"];
		}

		return $desc;
	}
}

Zerion Mini Shell 1.0