403Webshell
Server IP : 80.87.202.40  /  Your IP : 216.73.216.169
Web Server : Apache
System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64
User : bitrix ( 600)
PHP Version : 8.2.27
Disable Function : NONE
MySQL : OFF |  cURL : ON |  WGET : ON |  Perl : ON |  Python : OFF |  Sudo : ON |  Pkexec : ON
Directory :  /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/main/classes/general/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/main/classes/general/usertypemanager.php
<?php

use Bitrix\Main\Application;
use Bitrix\Main\DB\SqlExpression;
use Bitrix\Main\Entity;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Text\HtmlFilter;
use Bitrix\Main\UI\FileInputUtility;
use Bitrix\Main\UserField\Types\BaseType;
use Bitrix\Main\UserField\Types\DateTimeType;

IncludeModuleLangFile(__FILE__);

/**
* This class essentially serves as an interface layer between the values
* of user properties and the entity to which they are attached.
* @package usertype
* @subpackage classes
*/
class CUserTypeManager
{
	const BASE_TYPE_INT = "int";
	const BASE_TYPE_FILE = "file";
	const BASE_TYPE_ENUM = "enum";
	const BASE_TYPE_DOUBLE = "double";
	const BASE_TYPE_DATETIME = "datetime";
	const BASE_TYPE_STRING = "string";

	/** @var array */
	protected $arUserTypes = false;
	protected $arFieldsCache = [];
	protected $arRightsCache = [];
	/**
	 * @var null|array Stores relations of usertype ENTITY_ID to ORM entities. Aggregated by event main:onUserTypeEntityOrmMap.
	 * @see CUserTypeManager::getEntityList()
	 */
	protected $entityList = null;

	public function CleanCache()
	{
		$this->arFieldsCache = [];
		$this->arUserTypes = false;
	}

	/**
	* Function to return type metadata.
	*
	* <p>If this is the first call to the function, the system event OnUserTypeBuildList (main) is executed.
	* Registered handlers must return the type description data. In this case, the rule applies -
	* the last one is the boss. (in case one type is registered to be handled by "several" classes)</p>
	* <p>Without parameters, the function returns a complete list of types.<p>
	* <p>When a user_type_id is specified, it returns an array if such a type is registered and false if not.<p>
	* @param string|bool $user_type_id Optional. Property type identifier.
	* @return array|boolean
	*/
	public function GetUserType($user_type_id = false)
	{
		if (!is_array($this->arUserTypes))
		{
			$this->arUserTypes = [];
			foreach (GetModuleEvents("main", "OnUserTypeBuildList", true) as $arEvent)
			{
				$res = ExecuteModuleEventEx($arEvent);
				$this->arUserTypes[$res["USER_TYPE_ID"]] = $res;
			}
		}
		if ($user_type_id !== false)
		{
			if (array_key_exists($user_type_id, $this->arUserTypes))
			{
				return $this->arUserTypes[$user_type_id];
			}
			else
			{
				return false;
			}
		}
		else
		{
			return $this->arUserTypes;
		}
	}

	public function GetDBColumnType($arUserField)
	{
		if ($arType = $this->GetUserType($arUserField["USER_TYPE_ID"]))
		{
			if (is_callable([$arType["CLASS_NAME"], "getdbcolumntype"]))
			{
				return call_user_func_array([$arType["CLASS_NAME"], "getdbcolumntype"], [$arUserField]);
			}
		}
		return "";
	}

	public function getUtsDBColumnType($arUserField)
	{
		if ($arUserField['MULTIPLE'] == 'Y')
		{
			$sqlHelper = \Bitrix\Main\Application::getConnection()->getSqlHelper();
			return $sqlHelper->getColumnTypeByField(new Entity\TextField('TMP'));
		}
		else
		{
			return $this->GetDBColumnType($arUserField);
		}
	}

	public function getUtmDBColumnType($arUserField)
	{
		return $this->GetDBColumnType($arUserField);
	}

	public function PrepareSettings($ID, $arUserField, $bCheckUserType = true)
	{
		$user_type_id = $arUserField['USER_TYPE_ID'] ?? null;
		if ($ID > 0)
		{
			$rsUserType = CUserTypeEntity::GetList([], ["ID" => $ID]);
			$arUserType = $rsUserType->Fetch();
			if ($arUserType)
			{
				$user_type_id = $arUserType["USER_TYPE_ID"];
				$arUserField += $arUserType;
			}
		}

		if (!$bCheckUserType)
		{
			if (!isset($arUserField["SETTINGS"]))
			{
				return [];
			}

			if (!is_array($arUserField["SETTINGS"]))
			{
				return [];
			}

			if (empty($arUserField["SETTINGS"]))
			{
				return [];
			}
		}

		$userType = $this->GetUserType($user_type_id);

		if ($userType && is_callable([$userType['CLASS_NAME'], 'preparesettings']))
		{
			return call_user_func_array([$userType['CLASS_NAME'], 'preparesettings'], [$arUserField]);
		}

		if (!$userType)
		{
			return [];
		}

		return null;
	}

	public function getCustomData(array $userField, int $entityValueId): array
	{
		$hasCustomData =
			isset($userField['USER_TYPE']['CLASS_NAME'])
			&& is_callable([$userField['USER_TYPE']['CLASS_NAME'], 'getCustomData']);

		$customData = [];
		if ($hasCustomData)
		{
			$customData = call_user_func_array(
				[$userField['USER_TYPE']['CLASS_NAME'], 'getCustomData'],
				[$userField, $entityValueId]
			);
		}

		return is_array($customData) ? $customData : [];
	}

	public function OnEntityDelete($entity_id)
	{
		$obUserField = new CUserTypeEntity;
		return $obUserField->DropEntity($entity_id);
	}

	/**
	* Function to return metadata of fields defined for an entity.
	*
	* <p>Important! The $arUserField includes the field ENTITY_VALUE_ID - this is the identifier of the entity instance
	* that allows distinguishing new records from old ones and accordingly using default values.</p>
	*/
	public function GetUserFields($entity_id, $value_id = 0, $LANG = false, $user_id = false, $selectFields = null)
	{
		global $DB;

		$entity_id = static::normalizeId($entity_id);
		$value_id = intval($value_id);

		$result = $this->getEntities($entity_id, $LANG, $user_id);

		if (!empty($result) && $value_id > 0)
		{
			$valuesGottenByEvent = $this->getUserFieldValuesByEvent($result, $entity_id, $value_id);

			if ($selectFields !== null)
			{
				$selectFields = array_flip($selectFields);
			}

			$select = [];
			foreach ($result as $fieldName => $arUserField)
			{
				$result[$fieldName]["ENTITY_VALUE_ID"] = $value_id;

				if (is_array($valuesGottenByEvent))
				{
					$result[$fieldName]["VALUE"] = array_key_exists($fieldName, $valuesGottenByEvent) ? $valuesGottenByEvent[$fieldName] : $result[$fieldName]["VALUE"];
					$result[$fieldName]["CUSTOM_DATA"] = $this->getCustomData($result[$fieldName], $value_id);
				}
				elseif ($selectFields === null || isset($selectFields[$fieldName]))
				{
					if ($arUserField["MULTIPLE"] == "N"
						&& is_array($arUserField["USER_TYPE"])
						&& array_key_exists("CLASS_NAME", $arUserField["USER_TYPE"])
						&& is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "FormatField"]))
					{
						$select[] = call_user_func_array([$arUserField["USER_TYPE"]["CLASS_NAME"], "FormatField"], [$arUserField, $fieldName]) . " " . $fieldName;
					}
					else
					{
						$select[] = $fieldName;
					}
				}
			}

			if (is_array($valuesGottenByEvent))
			{
				return $result;
			}

			if (!empty($select))
			{
				$rs = $DB->Query("SELECT " . implode(', ', $select) . " FROM b_uts_" . strtolower($entity_id) . " WHERE VALUE_ID = " . $value_id);

				if ($ar = $rs->Fetch())
				{
					foreach ($ar as $key => $value)
					{
						$result[$key]["VALUE_EXISTS"] = true;
						$result[$key]["VALUE_RAW"] = $value;

						if ($result[$key]["MULTIPLE"] == "Y")
						{
							if (!str_starts_with($value, 'a') && $value > 0)
							{
								$value = $this->LoadMultipleValues($result[$key], $value);
							}
							else
							{
								$value = unserialize($value, ['allowed_classes' => false]);
							}
						}
						$result[$key]["VALUE"] = $this->OnAfterFetch($result[$key], $value);
					}
				}
			}

			foreach ($result as $fieldName => $userField)
			{
				$result[$fieldName]["CUSTOM_DATA"] = $this->getCustomData($userField, $value_id);
			}
		}

		return $result;
	}

	/**
	 * Replacement for getUserFields, if you are already have fetched old data
	 *
	 * @param      $entity_id
	 * @param      $readyData
	 * @param bool $LANG
	 * @param bool $user_id
	 * @param string $primaryIdName
	 *
	 * @return array
	 */
	public function getUserFieldsWithReadyData($entity_id, $readyData, $LANG = false, $user_id = false, $primaryIdName = 'VALUE_ID')
	{
		if ($readyData === null)
		{
			return $this->GetUserFields($entity_id, null, $LANG, $user_id);
		}

		$entity_id = static::normalizeId($entity_id);

		$result = $this->getEntities($entity_id, $LANG, $user_id);

		foreach ($readyData as $key => $value)
		{
			if (array_key_exists($key, $result))
			{
				if ($result[$key]["MULTIPLE"] == "Y" && !is_array($value))
				{
					$value = unserialize($value, ['allowed_classes' => false]);
				}

				$result[$key]["VALUE"] = $this->OnAfterFetch($result[$key], $value);
				$result[$key]["ENTITY_VALUE_ID"] = $readyData[$primaryIdName] ?? null;
			}
		}

		return $result;
	}

	public function GetUserFieldValue($entity_id, $field_id, $value_id, $LANG = false)
	{
		global $DB;

		$entity_id = static::normalizeId($entity_id);
		$field_id = static::normalizeId($field_id);
		$value_id = intval($value_id);
		$strTableName = "b_uts_" . strtolower($entity_id);
		$result = false;

		$arFilter = [
			"ENTITY_ID" => $entity_id,
			"FIELD_NAME" => $field_id,
		];
		if ($LANG)
		{
			$arFilter["LANG"] = $LANG;
		}
		$rs = CUserTypeEntity::GetList([], $arFilter);
		if ($arUserField = $rs->Fetch())
		{
			$values = $this->getUserFieldValuesByEvent([$arUserField['FIELD_NAME'] => $arUserField], $entity_id, $value_id);
			if (is_array($values))
			{
				return $values[$arUserField['FIELD_NAME']];
			}
			$arUserField["USER_TYPE"] = $this->GetUserType($arUserField["USER_TYPE_ID"]);
			$arTableFields = $DB->GetTableFields($strTableName);
			if (array_key_exists($field_id, $arTableFields))
			{
				$simpleFormat = true;
				$select = "";
				if ($arUserField["MULTIPLE"] == "N")
				{
					if ($arType = $arUserField["USER_TYPE"])
					{
						if (is_callable([$arType["CLASS_NAME"], "FormatField"]))
						{
							$select = call_user_func_array([$arType["CLASS_NAME"], "FormatField"], [$arUserField, $field_id]);
							$simpleFormat = false;
						}
					}
				}
				if ($simpleFormat)
				{
					$select = $field_id;
				}

				$rs = $DB->Query("SELECT " . $select . " " . $DB->quote('VALUE') . " FROM " . $strTableName . " WHERE VALUE_ID = " . $value_id);
				if ($ar = $rs->Fetch())
				{
					if ($arUserField["MULTIPLE"] == "Y")
					{
						$result = $this->OnAfterFetch($arUserField, unserialize($ar["VALUE"], ['allowed_classes' => false]));
					}
					else
					{
						$result = $this->OnAfterFetch($arUserField, $ar["VALUE"]);
					}
				}
			}
		}

		return $result;
	}

	/**
	 * Aggregates entity map by event.
	 * @return array [ENTITY_ID => 'SomeTable']
	 */
	public function getEntityList()
	{
		if ($this->entityList === null)
		{
			$event = new \Bitrix\Main\Event('main', 'onUserTypeEntityOrmMap');
			$event->send();

			foreach ($event->getResults() as $eventResult)
			{
				if ($eventResult->getType() == \Bitrix\Main\EventResult::SUCCESS)
				{
					$result = $eventResult->getParameters(); // [ENTITY_ID => 'SomeTable']
					foreach ($result as $entityId => $entityClass)
					{
						if (!str_starts_with($entityClass, '\\'))
						{
							$entityClass = '\\' . $entityClass;
						}

						$this->entityList[$entityId] = $entityClass;
					}
				}
			}
		}

		return $this->entityList;
	}

	public function OnAfterFetch($arUserField, $result)
	{
		if (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "onafterfetch"]))
		{
			if ($arUserField["MULTIPLE"] == "Y")
			{
				if (is_array($result))
				{
					$resultCopy = $result;
					$result = [];
					foreach ($resultCopy as $value)
					{
						$convertedValue = call_user_func_array(
							[$arUserField["USER_TYPE"]["CLASS_NAME"], "onafterfetch"],
							[
								$arUserField,
								[
									"VALUE" => $value,
								],
							]
						);
						if ($convertedValue !== null)
						{
							$result[] = $convertedValue;
						}
					}
				}
			}
			else
			{
				$result = call_user_func_array(
					[$arUserField["USER_TYPE"]["CLASS_NAME"], "onafterfetch"],
					[
						$arUserField,
						[
							"VALUE" => $result,
						],
					]
				);
			}
		}
		return $result;
	}

	public function LoadMultipleValues($arUserField, $valueId)
	{
		global $DB;
		$result = [];

		$rs = $DB->Query("
			SELECT *
			FROM b_utm_" . strtolower($arUserField["ENTITY_ID"]) . "
			WHERE VALUE_ID = " . intval($valueId) . "
			AND FIELD_ID = " . $arUserField["ID"] . "
		");
		while ($ar = $rs->Fetch())
		{
			if ($arUserField["USER_TYPE"]["USER_TYPE_ID"] == "date")
			{
				$result[] = substr($ar["VALUE_DATE"], 0, 10);
			}
			else
			{
				switch ($arUserField["USER_TYPE"]["BASE_TYPE"])
				{
					case "int":
					case "file":
					case "enum":
						$result[] = $ar["VALUE_INT"];
						break;
					case "double":
						$result[] = $ar["VALUE_DOUBLE"];
						break;
					case "datetime":
						$result[] = $ar["VALUE_DATE"];
						break;
					default:
						$result[] = $ar["VALUE"];
				}
			}
		}
		return $result;
	}

	public function EditFormTab($entity_id)
	{
		$entity_id = static::normalizeId($entity_id);

		return [
			"DIV" => "user_fields_tab_" . $entity_id,
			"TAB" => GetMessage("USER_TYPE_EDIT_TAB"),
			"ICON" => "none",
			"TITLE" => GetMessage("USER_TYPE_EDIT_TAB_TITLE"),
		];
	}

	public function EditFormShowTab($entity_id, $bVarsFromForm, $ID)
	{
		global $APPLICATION;

		if ($this->GetRights($entity_id) >= "W")
		{
			echo "<tr colspan=\"2\"><td align=\"left\"><a href=\"/bitrix/admin/userfield_edit.php?lang=" . LANG . "&ENTITY_ID=" . urlencode($entity_id) . "&back_url=" . urlencode($APPLICATION->GetCurPageParam("", ["bxpublic"]) . "&tabControl_active_tab=user_fields_tab") . "\">" . GetMessage("USER_TYPE_EDIT_TAB_HREF") . "</a></td></tr>";
		}

		$arUserFields = $this->GetUserFields($entity_id, $ID, LANGUAGE_ID);
		if (!empty($arUserFields))
		{
			foreach ($arUserFields as $FIELD_NAME => $arUserField)
			{
				$arUserField["VALUE_ID"] = intval($ID);
				echo $this->GetEditFormHTML($bVarsFromForm, $GLOBALS[$FIELD_NAME], $arUserField);
			}
		}
	}

	public function EditFormAddFields($entity_id, &$arFields, array $options = null)
	{
		if (!is_array($options))
		{
			$options = [];
		}

		if (!is_array($arFields))
		{
			$arFields = [];
		}

		$files = $options['FILES'] ?? $_FILES;
		$form = isset($options['FORM']) && is_array($options['FORM']) ? $options['FORM'] : $GLOBALS;

		$arUserFields = $this->GetUserFields($entity_id);
		foreach ($arUserFields as $arUserField)
		{
			$fieldName = $arUserField['FIELD_NAME'];
			if ($arUserField["EDIT_IN_LIST"] == "Y")
			{
				if ($arUserField["USER_TYPE"]["BASE_TYPE"] == "file")
				{
					if (isset($files[$fieldName]))
					{
						if (is_array($files[$fieldName]["name"]))
						{
							$arFields[$fieldName] = [];
							foreach ($files[$fieldName]["name"] as $key => $value)
							{
								$old_id = $form[$fieldName . "_old_id"][$key] ?? null;
								$arFields[$fieldName][$key] = [
									"name" => $files[$fieldName]["name"][$key],
									"type" => $files[$fieldName]["type"][$key],
									"tmp_name" => $files[$fieldName]["tmp_name"][$key],
									"error" => $files[$fieldName]["error"][$key],
									"size" => $files[$fieldName]["size"][$key],
									"del" =>
										isset($form[$fieldName . "_del"])
										&& is_array($form[$fieldName . "_del"]) &&
										(in_array($old_id, $form[$fieldName . "_del"]) ||
											(
												array_key_exists($key, $form[$fieldName . "_del"]) &&
												$form[$fieldName . "_del"][$key] == "Y"
											)
										),
									"old_id" => $old_id,
								];
							}
						}
						else
						{
							$arFields[$fieldName] = $files[$fieldName];
							$arFields[$fieldName]["del"] = $form[$fieldName . "_del"] ?? '';
							$arFields[$fieldName]["old_id"] = $form[$fieldName . "_old_id"] ?? '';
						}
					}
					else
					{
						if (isset($form[$fieldName]))
						{
							if (!is_array($form[$fieldName]))
							{
								if (intval($form[$fieldName]) > 0)
								{
									$arFields[$fieldName] = intval($form[$fieldName]);
								}
							}
							else
							{
								$fields = [];
								foreach ($form[$fieldName] as $val)
								{
									if (intval($val) > 0)
									{
										$fields[] = intval($val);
									}
								}
								$arFields[$fieldName] = $fields;
							}
						}
					}
				}
				else
				{
					if (isset($files[$fieldName]))
					{
						$arFile = [];
						CFile::ConvertFilesToPost($files[$fieldName], $arFile);

						if (isset($form[$fieldName]))
						{
							if ($arUserField["MULTIPLE"] == "Y")
							{
								foreach ($form[$fieldName] as $key => $value)
								{
									$arFields[$fieldName][$key] = array_merge($value, $arFile[$key]);
								}
							}
							else
							{
								$arFields[$fieldName] = array_merge($form[$fieldName], $arFile);
							}
						}
						else
						{
							$arFields[$fieldName] = $arFile;
						}
					}
					else
					{
						if (isset($form[$fieldName]))
						{
							$arFields[$fieldName] = $form[$fieldName];
						}
					}
				}
			}
		}
	}

	/**
	 * Add field for filter.
	 * @param string $entityId Entity id.
	 * @param array $arFilterFields Array for fill.
	 */
	public function AdminListAddFilterFields($entityId, &$arFilterFields)
	{
		$arUserFields = $this->GetUserFields($entityId);
		foreach ($arUserFields as $fieldName => $arUserField)
		{
			if ($arUserField['SHOW_FILTER'] != 'N' && $arUserField['USER_TYPE']['BASE_TYPE'] != 'file')
			{
				$arFilterFields[] = 'find_' . $fieldName;
				if ($arUserField['USER_TYPE']['BASE_TYPE'] == 'datetime')
				{
					$arFilterFields[] = 'find_' . $fieldName . '_from';
					$arFilterFields[] = 'find_' . $fieldName . '_to';
				}
			}
		}
	}

	public function AdminListAddFilterFieldsV2($entityId, &$arFilterFields)
	{
		$arUserFields = $this->GetUserFields($entityId, 0, $GLOBALS["lang"]);
		foreach ($arUserFields as $fieldName => $arUserField)
		{
			if ($arUserField['SHOW_FILTER'] != 'N' && $arUserField['USER_TYPE']['BASE_TYPE'] != 'file')
			{
				if (is_callable([$arUserField['USER_TYPE']['CLASS_NAME'], 'GetFilterData']))
				{
					$arFilterFields[] = call_user_func_array(
						[$arUserField['USER_TYPE']['CLASS_NAME'], 'GetFilterData'],
						[
							$arUserField,
							[
								'ID' => $fieldName,
								'NAME' => $arUserField['LIST_FILTER_LABEL'] ?: $arUserField['FIELD_NAME'],
							],
						]
					);
				}
			}
		}
	}

	public function IsNotEmpty($value)
	{
		if (is_array($value))
		{
			foreach ($value as $v)
			{
				if ((string)$v <> '')
				{
					return true;
				}
			}

			return false;
		}
		else
		{
			if ((string)$value <> '')
			{
				return true;
			}
			else
			{
				return false;
			}
		}
	}

	/**
	 * Add value for filter.
	 * @param string $entityId Entity id.
	 * @param array $arFilter Array for fill.
	 */
	public function AdminListAddFilter($entityId, &$arFilter)
	{
		$arUserFields = $this->GetUserFields($entityId);
		foreach ($arUserFields as $fieldName => $arUserField)
		{
			if (
				$arUserField['SHOW_FILTER'] != 'N' &&
				$arUserField['USER_TYPE']['BASE_TYPE'] == 'datetime'
			)
			{
				$value1 = $GLOBALS['find_' . $fieldName . '_from'] ?? null;
				$value2 = $GLOBALS['find_' . $fieldName . '_to'] ?? null;
				if ($this->IsNotEmpty($value1) && \Bitrix\Main\Type\Date::isCorrect($value1))
				{
					$date = new \Bitrix\Main\Type\Date($value1);
					$arFilter['>=' . $fieldName] = $date;
				}
				if ($this->IsNotEmpty($value2) && \Bitrix\Main\Type\Date::isCorrect($value2))
				{
					$date = new \Bitrix\Main\Type\Date($value2);
					if ($arUserField['USER_TYPE_ID'] != 'date')
					{
						$date->add('+1 day');
					}
					$arFilter['<=' . $fieldName] = $date;
				}
				continue;
			}
			else
			{
				$value = $GLOBALS['find_' . $fieldName] ?? null;
			}
			if (
				$arUserField['SHOW_FILTER'] != 'N'
				&& $arUserField['USER_TYPE']['BASE_TYPE'] != 'file'
				&& $this->IsNotEmpty($value)
			)
			{
				if ($arUserField['SHOW_FILTER'] == 'I')
				{
					$arFilter['=' . $fieldName] = $value;
				}
				elseif ($arUserField['SHOW_FILTER'] == 'S')
				{
					$arFilter['%' . $fieldName] = $value;
				}
				else
				{
					$arFilter[$fieldName] = $value;
				}
			}
		}
	}

	public function AdminListAddFilterV2($entityId, &$arFilter, $filterId, $filterFields)
	{
		$filterOption = new Bitrix\Main\UI\Filter\Options($filterId);
		$filterData = $filterOption->getFilter($filterFields);

		$arUserFields = $this->GetUserFields($entityId);
		foreach ($arUserFields as $fieldName => $arUserField)
		{
			if ($arUserField['SHOW_FILTER'] != 'N' && $arUserField['USER_TYPE']['BASE_TYPE'] == 'datetime')
			{
				$value1 = $filterData[$fieldName . '_from'] ?? '';
				$value2 = $filterData[$fieldName . '_to'] ?? '';
				if ($this->IsNotEmpty($value1) && \Bitrix\Main\Type\Date::isCorrect($value1))
				{
					$date = new \Bitrix\Main\Type\Date($value1);
					$arFilter['>=' . $fieldName] = $date;
				}
				if ($this->IsNotEmpty($value2) && \Bitrix\Main\Type\Date::isCorrect($value2))
				{
					$date = new \Bitrix\Main\Type\Date($value2);
					if ($arUserField['USER_TYPE_ID'] != 'date')
					{
						$date->add('+1 day');
					}
					$arFilter['<=' . $fieldName] = $date;
				}
				continue;
			}
			elseif ($arUserField['SHOW_FILTER'] != 'N' && $arUserField['USER_TYPE']['BASE_TYPE'] == 'int')
			{
				if ($arUserField['USER_TYPE_ID'] == 'boolean')
				{
					if (isset($filterData[$fieldName]) && $filterData[$fieldName] === 'Y')
					{
						$filterData[$fieldName] = 1;
					}
					if (isset($filterData[$fieldName]) && $filterData[$fieldName] === 'N')
					{
						$filterData[$fieldName] = 0;
					}
				}
				$value = $filterData[$fieldName] ?? null;
			}
			else
			{
				$value = $filterData[$fieldName] ?? null;
			}
			if (
				$arUserField['SHOW_FILTER'] != 'N'
				&& $arUserField['USER_TYPE']['BASE_TYPE'] != 'file'
				&& $this->IsNotEmpty($value)
			)
			{
				if ($arUserField['SHOW_FILTER'] == 'I')
				{
					unset($arFilter[$fieldName]);
					$arFilter['=' . $fieldName] = $value;
				}
				elseif ($arUserField['SHOW_FILTER'] == 'S')
				{
					unset($arFilter[$fieldName]);
					$arFilter['%' . $fieldName] = $value;
				}
				else
				{
					$arFilter[$fieldName] = $value;
				}
			}
		}
	}

	public function AdminListPrepareFields($entity_id, &$arFields)
	{
		$arUserFields = $this->GetUserFields($entity_id);
		foreach ($arUserFields as $FIELD_NAME => $arUserField)
		{
			if ($arUserField["EDIT_IN_LIST"] != "Y")
			{
				unset($arFields[$FIELD_NAME]);
			}
		}
	}

	public function AdminListAddHeaders($entity_id, &$arHeaders)
	{
		$arUserFields = $this->GetUserFields($entity_id, 0, $GLOBALS["lang"]);
		foreach ($arUserFields as $FIELD_NAME => $arUserField)
		{
			if ($arUserField["SHOW_IN_LIST"] == "Y")
			{
				$arHeaders[] = [
					"id" => $FIELD_NAME,
					"content" => htmlspecialcharsbx($arUserField["LIST_COLUMN_LABEL"] ?: $arUserField["FIELD_NAME"]),
					"sort" => $arUserField["MULTIPLE"] == "N" ? $FIELD_NAME : false,
				];
			}
		}
	}

	public function AddUserFields($entity_id, $arRes, $row)
	{
		$arUserFields = $this->GetUserFields($entity_id);
		foreach ($arUserFields as $FIELD_NAME => $arUserField)
		{
			if ($arUserField["SHOW_IN_LIST"] == "Y" && array_key_exists($FIELD_NAME, $arRes))
			{
				$this->AddUserField($arUserField, $arRes[$FIELD_NAME], $row);
			}
		}
	}

	public function AddFindFields($entity_id, &$arFindFields)
	{
		$arUserFields = $this->GetUserFields($entity_id, 0, $GLOBALS["lang"]);
		foreach ($arUserFields as $FIELD_NAME => $arUserField)
		{
			if ($arUserField["SHOW_FILTER"] != "N" && $arUserField["USER_TYPE"]["BASE_TYPE"] != "file")
			{
				if ($arUserField["USER_TYPE"] && is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "getfilterhtml"]))
				{
					if ($arUserField["LIST_FILTER_LABEL"])
					{
						$arFindFields[$FIELD_NAME] = htmlspecialcharsbx($arUserField["LIST_FILTER_LABEL"]);
					}
					else
					{
						$arFindFields[$FIELD_NAME] = $arUserField["FIELD_NAME"];
					}
				}
			}
		}
	}

	public function AdminListShowFilter($entity_id)
	{
		$arUserFields = $this->GetUserFields($entity_id, 0, $GLOBALS["lang"]);
		foreach ($arUserFields as $FIELD_NAME => $arUserField)
		{
			if ($arUserField["SHOW_FILTER"] != "N" && $arUserField["USER_TYPE"]["BASE_TYPE"] != "file")
			{
				echo $this->GetFilterHTML($arUserField, "find_" . $FIELD_NAME, $GLOBALS["find_" . $FIELD_NAME]);
			}
		}
	}

	public function ShowScript()
	{
		global $APPLICATION;

		$APPLICATION->AddHeadScript("/bitrix/js/main/usertype.js");

		return "";
	}

	public function GetEditFormHTML($bVarsFromForm, $form_value, $arUserField)
	{
		global $APPLICATION;
		global $adminPage, $adminSidePanelHelper;

		if ($arUserField["USER_TYPE"])
		{
			if ($this->GetRights($arUserField["ENTITY_ID"]) >= "W")
			{
				$selfFolderUrl = $adminPage->getSelfFolderUrl();
				$userFieldUrl = $selfFolderUrl . "userfield_edit.php?lang=" . LANGUAGE_ID . "&ID=" . $arUserField["ID"];
				$userFieldUrl = $adminSidePanelHelper->editUrlToPublicPage($userFieldUrl);
				$edit_link = ($arUserField["HELP_MESSAGE"] ? htmlspecialcharsex($arUserField["HELP_MESSAGE"]) . '<br>' : '') . '<a href="' . htmlspecialcharsbx($userFieldUrl . '&back_url=' . urlencode($APPLICATION->GetCurPageParam("", ["bxpublic"]) . '&tabControl_active_tab=user_fields_tab')) . '">' . htmlspecialcharsex(GetMessage("MAIN_EDIT")) . '</a>';
			}
			else
			{
				$edit_link = '';
			}

			$hintHTML = '<span id="hint_' . $arUserField["FIELD_NAME"] . '"></span><script>BX.hint_replace(BX(\'hint_' . $arUserField["FIELD_NAME"] . '\'), \'' . CUtil::JSEscape($edit_link) . '\');</script>&nbsp;';

			if ($arUserField["MANDATORY"] == "Y")
			{
				$strLabelHTML = $hintHTML . '<span class="adm-required-field">' . htmlspecialcharsbx($arUserField["EDIT_FORM_LABEL"] ?: $arUserField["FIELD_NAME"]) . '</span>' . ':';
			}
			else
			{
				$strLabelHTML = $hintHTML . htmlspecialcharsbx($arUserField["EDIT_FORM_LABEL"] ?: $arUserField["FIELD_NAME"]) . ':';
			}

			if (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "geteditformhtml"]))
			{
				$js = $this->ShowScript();

				if (!$bVarsFromForm)
				{
					$form_value = $arUserField["VALUE"];
				}
				elseif ($arUserField["USER_TYPE"]["BASE_TYPE"] == "file")
				{
					$form_value = $GLOBALS[$arUserField["FIELD_NAME"] . "_old_id"];
				}
				elseif ($arUserField["EDIT_IN_LIST"] == "N")
				{
					$form_value = $arUserField["VALUE"];
				}

				if (
					$arUserField["MULTIPLE"] === "N"
					||
					!empty($arUserField['USER_TYPE']['USE_FIELD_COMPONENT'])
				)
				{
					$valign = "";
					$rowClass = "";
					$html = call_user_func_array(
						[$arUserField["USER_TYPE"]["CLASS_NAME"], "geteditformhtml"],
						[
							$arUserField,
							[
								"NAME" => $arUserField["FIELD_NAME"],
								"VALUE" => (is_array($form_value) ? $form_value : htmlspecialcharsbx($form_value)),
								"VALIGN" => &$valign,
								"ROWCLASS" => &$rowClass,
							],
							$bVarsFromForm,
						]
					);
					return '<tr' . ($rowClass != '' ? ' class="' . $rowClass . '"' : '') . '><td' . ($valign <> 'middle' ? ' class="adm-detail-valign-top"' : '') . ' width="40%">' . $strLabelHTML . '</td><td width="60%">' . $html . '</td></tr>' . $js;
				}
				elseif (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "geteditformhtmlmulty"]))
				{
					if (!is_array($form_value))
					{
						$form_value = [];
					}
					foreach ($form_value as $key => $value)
					{
						if (!is_array($value))
						{
							$form_value[$key] = htmlspecialcharsbx($value);
						}
					}

					$rowClass = "";
					$html = call_user_func_array(
						[$arUserField["USER_TYPE"]["CLASS_NAME"], "geteditformhtmlmulty"],
						[
							$arUserField,
							[
								"NAME" => $arUserField["FIELD_NAME"] . "[]",
								"VALUE" => $form_value,
								"ROWCLASS" => &$rowClass,
							],
							$bVarsFromForm,
						]
					);
					return '<tr' . ($rowClass != '' ? ' class="' . $rowClass . '"' : '') . '><td class="adm-detail-valign-top">' . $strLabelHTML . '</td><td>' . $html . '</td></tr>' . $js;
				}
				else
				{
					if (!is_array($form_value))
					{
						$form_value = [];
					}
					$html = "";
					$i = -1;
					foreach ($form_value as $i => $value)
					{
						if (
							(is_array($value) && (implode("", $value) <> ''))
							|| ((!is_array($value)) && ((string)$value <> ''))
						)
						{
							$html .= '<tr><td>' . call_user_func_array(
									[$arUserField["USER_TYPE"]["CLASS_NAME"], "geteditformhtml"],
									[
										$arUserField,
										[
											"NAME" => $arUserField["FIELD_NAME"] . "[" . $i . "]",
											"VALUE" => (is_array($value) ? $value : htmlspecialcharsbx($value)),
										],
										$bVarsFromForm,
									]
								) . '</td></tr>';
						}
					}
					//Add multiple values support
					$rowClass = "";
					$FIELD_NAME_X = str_replace('_', 'x', $arUserField["FIELD_NAME"]);
					$fieldHtml = call_user_func_array(
						[$arUserField["USER_TYPE"]["CLASS_NAME"], "geteditformhtml"],
						[
							$arUserField,
							[
								"NAME" => $arUserField["FIELD_NAME"] . "[" . ($i + 1) . "]",
								"VALUE" => "",
								"ROWCLASS" => &$rowClass,
							],
							$bVarsFromForm,
						]
					);
					return '<tr' . ($rowClass != '' ? ' class="' . $rowClass . '"' : '') . '><td class="adm-detail-valign-top">' . $strLabelHTML . '</td><td>' .
						'<table id="table_' . $arUserField["FIELD_NAME"] . '">' . $html . '<tr><td>' . $fieldHtml . '</td></tr>' .
						'<tr><td style="padding-top: 6px;"><input type="button" value="' . GetMessage("USER_TYPE_PROP_ADD") . '" onClick="addNewRow(\'table_' . $arUserField["FIELD_NAME"] . '\', \'' . $FIELD_NAME_X . '|' . $arUserField["FIELD_NAME"] . '|' . $arUserField["FIELD_NAME"] . '_old_id\')"></td></tr>' .
						"<script>BX.addCustomEvent('onAutoSaveRestore', function(ob, data) {
							for (let i in data)
							{
								if (i.substring(0," . (mb_strlen($arUserField['FIELD_NAME']) + 1) . ")=='" . CUtil::JSEscape($arUserField['FIELD_NAME']) . "[')
								{
									addNewRow('table_" . $arUserField["FIELD_NAME"] . "', '" . $FIELD_NAME_X . '|' . $arUserField["FIELD_NAME"] . '|' . $arUserField["FIELD_NAME"] . "_old_id');
								}
							}
						})</script>" .
						'</table>' .
						'</td></tr>' . $js;
				}
			}
		}
		return '';
	}

	public function GetFilterHTML($arUserField, $filter_name, $filter_value)
	{
		if ($arUserField["USER_TYPE"])
		{
			if (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "getfilterhtml"]))
			{
				$useFieldComponent = ($arUserField['USER_TYPE']['USE_FIELD_COMPONENT'] ?? false) === true;
				$html = call_user_func_array(
						[$arUserField["USER_TYPE"]["CLASS_NAME"], "getfilterhtml"],
						[
							$arUserField,
							[
								'NAME' => $filter_name,
								'VALUE' => ($useFieldComponent ? $filter_value : htmlspecialcharsEx($filter_value)),
							],
						]
					) . CAdminCalendar::ShowScript();

				return '<tr><td>' . htmlspecialcharsbx($arUserField["LIST_FILTER_LABEL"] ?: $arUserField["FIELD_NAME"]) . ':</td><td>' . $html . '</td></tr>';
			}
		}

		return '';
	}

	/**
	 * @param $arUserField
	 * @param $value
	 * @param CAdminListRow $row
	 */
	public function AddUserField($arUserField, $value, $row)
	{
		if ($arUserField["USER_TYPE"])
		{
			$js = $this->ShowScript();
			$useFieldComponent = !empty($arUserField['USER_TYPE']['USE_FIELD_COMPONENT']);

			if (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtml"]))
			{
				if ($arUserField["MULTIPLE"] == "N")
				{
					$html = call_user_func_array(
						[$arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtml"],
						[
							$arUserField,
							[
								"NAME" => "FIELDS[" . $row->id . "][" . $arUserField["FIELD_NAME"] . "]",
								"VALUE" => ($useFieldComponent ? $value : htmlspecialcharsbx($value)),
							],
						]
					);
					if ($html === '')
					{
						$html = '&nbsp;';
					}
					$row->AddViewField($arUserField["FIELD_NAME"], $html . $js . CAdminCalendar::ShowScript());
				}
				elseif (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtmlmulty"]))
				{
					if (is_array($value))
					{
						$form_value = $value;
					}
					else
					{
						$form_value = unserialize($value, ['allowed_classes' => false]);
					}

					if (!is_array($form_value))
					{
						$form_value = [];
					}

					foreach ($form_value as $key => $val)
					{
						$form_value[$key] = ($useFieldComponent ? $val : htmlspecialcharsbx($val));
					}

					$html = call_user_func_array(
						[$arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtmlmulty"],
						[
							$arUserField,
							[
								"NAME" => "FIELDS[" . $row->id . "][" . $arUserField["FIELD_NAME"] . "]" . "[]",
								"VALUE" => $form_value,
							],
						]
					);
					if ($html == '')
					{
						$html = '&nbsp;';
					}
					$row->AddViewField($arUserField["FIELD_NAME"], $html . $js . CAdminCalendar::ShowScript());
				}
				else
				{
					$html = "";

					if (is_array($value))
					{
						$form_value = $value;
					}
					else
					{
						$form_value = $value <> '' ? unserialize($value, ['allowed_classes' => false]) : false;
					}

					if (!is_array($form_value))
					{
						$form_value = [];
					}

					foreach ($form_value as $i => $val)
					{
						if ($html != "")
						{
							$html .= " / ";
						}
						$html .= call_user_func_array(
							[$arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtml"],
							[
								$arUserField,
								[
									"NAME" => "FIELDS[" . $row->id . "][" . $arUserField["FIELD_NAME"] . "]" . "[" . $i . "]",
									"VALUE" => ($useFieldComponent ? $val : htmlspecialcharsbx($val)),
								],
							]
						);
					}
					if ($html == '')
					{
						$html = '&nbsp;';
					}
					$row->AddViewField($arUserField["FIELD_NAME"], $html . $js . CAdminCalendar::ShowScript());
				}
			}
			if ($arUserField["EDIT_IN_LIST"] == "Y" && is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistedithtml"]))
			{
				if (!$row->bEditMode)
				{
					// put dummy
					$row->AddEditField($arUserField["FIELD_NAME"], "&nbsp;");
				}
				elseif ($arUserField["MULTIPLE"] == "N")
				{
					$html = call_user_func_array(
						[$arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistedithtml"],
						[
							$arUserField,
							[
								"NAME" => "FIELDS[" . $row->id . "][" . $arUserField["FIELD_NAME"] . "]",
								"VALUE" => ($useFieldComponent ? $value : htmlspecialcharsbx($value)),
							],
						]
					);
					if ($html == '')
					{
						$html = '&nbsp;';
					}
					$row->AddEditField($arUserField["FIELD_NAME"], $html . $js . CAdminCalendar::ShowScript());
				}
				elseif (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistedithtmlmulty"]))
				{
					if (is_array($value))
					{
						$form_value = $value;
					}
					else
					{
						$form_value = $value <> '' ? unserialize($value, ['allowed_classes' => false]) : false;
					}

					if (!is_array($form_value))
					{
						$form_value = [];
					}

					foreach ($form_value as $key => $val)
					{
						$form_value[$key] = ($useFieldComponent ? $val : htmlspecialcharsbx($val));
					}

					$html = call_user_func_array(
						[$arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistedithtmlmulty"],
						[
							$arUserField,
							[
								"NAME" => "FIELDS[" . $row->id . "][" . $arUserField["FIELD_NAME"] . "][]",
								"VALUE" => $form_value,
							],
						]
					);
					if ($html == '')
					{
						$html = '&nbsp;';
					}
					$row->AddEditField($arUserField["FIELD_NAME"], $html . $js . CAdminCalendar::ShowScript());
				}
				else
				{
					$html = "<table id=\"table_" . $arUserField["FIELD_NAME"] . "_" . $row->id . "\">";
					if (is_array($value))
					{
						$form_value = $value;
					}
					else
					{
						$form_value = unserialize($value, ['allowed_classes' => false]);
					}

					if (!is_array($form_value))
					{
						$form_value = [];
					}

					$i = -1;
					foreach ($form_value as $i => $val)
					{
						$html .= '<tr><td>' . call_user_func_array(
								[$arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistedithtml"],
								[
									$arUserField,
									[
										"NAME" => "FIELDS[" . $row->id . "][" . $arUserField["FIELD_NAME"] . "]" . "[" . $i . "]",
										"VALUE" => ($useFieldComponent ? $val : htmlspecialcharsbx($val)),
									],
								]
							) . '</td></tr>';
					}
					$html .= '<tr><td>' . call_user_func_array(
							[$arUserField["USER_TYPE"]["CLASS_NAME"], "getadminlistedithtml"],
							[
								$arUserField,
								[
									"NAME" => "FIELDS[" . $row->id . "][" . $arUserField["FIELD_NAME"] . "]" . "[" . ($i + 1) . "]",
									"VALUE" => "",
								],
							]
						) . '</td></tr>';
					$html .= '<tr><td><input type="button" value="' . GetMessage("USER_TYPE_PROP_ADD") . '" onClick="addNewRow(\'table_' . $arUserField["FIELD_NAME"] . '_' . $row->id . '\', \'FIELDS\\\\[' . $row->id . '\\\\]\\\\[' . $arUserField["FIELD_NAME"] . '\\\\]\')"></td></tr>' .
						'</table>';
					$row->AddEditField($arUserField["FIELD_NAME"], $html . $js . CAdminCalendar::ShowScript());
				}
			}
		}
	}

	public function getListView($userfield, $value)
	{
		$html = '';

		if (is_callable([$userfield["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtml"]))
		{
			if ($userfield["MULTIPLE"] == "N")
			{
				$html = call_user_func_array(
					[$userfield["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtml"],
					[
						$userfield,
						[
							"VALUE" => htmlspecialcharsbx($value),
						],
					]
				);
			}
			elseif (is_callable([$userfield["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtmlmulty"]))
			{
				$form_value = is_array($value) ? $value : unserialize($value, ['allowed_classes' => false]);

				if (!is_array($form_value))
				{
					$form_value = [];
				}

				foreach ($form_value as $key => $val)
				{
					$form_value[$key] = htmlspecialcharsbx($val);
				}

				$html = call_user_func_array(
					[$userfield["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtmlmulty"],
					[
						$userfield,
						[
							"VALUE" => $form_value,
						],
					]
				);
			}
			else
			{
				if (is_array($value))
				{
					$form_value = $value;
				}
				else
				{
					$form_value = $value <> '' ? unserialize($value, ['allowed_classes' => false]) : false;
				}

				if (!is_array($form_value))
				{
					$form_value = [];
				}

				foreach ($form_value as $val)
				{
					if ($html != "")
					{
						$html .= " / ";
					}

					$html .= call_user_func_array(
						[$userfield["USER_TYPE"]["CLASS_NAME"], "getadminlistviewhtml"],
						[
							$userfield,
							[
								"VALUE" => htmlspecialcharsbx($val),
							],
						]
					);
				}
			}
		}

		return $html <> '' ? $html : '&nbsp;';
	}

	public function CallUserTypeComponent($componentName, $componentTemplate, $arUserField, $arAdditionalParameters = [])
	{
		global $APPLICATION;
		$arParams = $arAdditionalParameters;
		$arParams['arUserField'] = $arUserField;
		ob_start();
		$APPLICATION->IncludeComponent(
			$componentName,
			$componentTemplate,
			$arParams,
			null,
			["HIDE_ICONS" => "Y"]
		);
		return ob_get_clean();
	}

	/**
	 * @param array $userField
	 * @param array|null $additionalParameters
	 * @return string|null
	 */
	public function renderField(array $userField, ?array $additionalParameters = []): ?string
	{
		$userType = $this->getUserType($userField['USER_TYPE_ID']);
		if (!empty($userType['CLASS_NAME']) && is_callable([$userType['CLASS_NAME'], 'renderField']))
		{
			return call_user_func([$userType['CLASS_NAME'], 'renderField'], $userField, $additionalParameters);
		}
		return null;
	}

	public function GetPublicView($arUserField, $arAdditionalParameters = [])
	{
		$event = new \Bitrix\Main\Event("main", "onBeforeGetPublicView", [&$arUserField, &$arAdditionalParameters]);
		$event->send();

		$arType = $this->GetUserType($arUserField["USER_TYPE_ID"]);

		$html = null;
		$event = new \Bitrix\Main\Event("main", "onGetPublicView", [$arUserField, $arAdditionalParameters]);
		$event->send();
		foreach ($event->getResults() as $evenResult)
		{
			if ($evenResult->getType() == \Bitrix\Main\EventResult::SUCCESS)
			{
				$html = $evenResult->getParameters();
				break;
			}
		}

		if ($html !== null)
		{
			//All done
		}
		elseif (isset($arUserField["VIEW_CALLBACK"]) && is_callable($arUserField['VIEW_CALLBACK']))
		{
			$html = call_user_func_array($arUserField["VIEW_CALLBACK"], [
				$arUserField,
				$arAdditionalParameters,
			]);
		}
		elseif ($arType && isset($arType["VIEW_CALLBACK"]) && is_callable($arType['VIEW_CALLBACK']))
		{
			$html = call_user_func_array($arType["VIEW_CALLBACK"], [
				$arUserField,
				$arAdditionalParameters,
			]);
		}
		elseif (isset($arUserField["VIEW_COMPONENT_NAME"]))
		{
			$html = $this->CallUserTypeComponent(
				$arUserField["VIEW_COMPONENT_NAME"],
				$arUserField["VIEW_COMPONENT_TEMPLATE"],
				$arUserField,
				$arAdditionalParameters
			);
		}
		elseif ($arType && isset($arType["VIEW_COMPONENT_NAME"]))
		{
			$html = $this->CallUserTypeComponent(
				$arType["VIEW_COMPONENT_NAME"],
				$arType["VIEW_COMPONENT_TEMPLATE"],
				$arUserField,
				$arAdditionalParameters
			);
		}
		else
		{
			$html = $this->CallUserTypeComponent(
				"bitrix:system.field.view",
				$arUserField["USER_TYPE_ID"],
				$arUserField,
				$arAdditionalParameters
			);
		}

		$event = new \Bitrix\Main\Event("main", "onAfterGetPublicView", [$arUserField, $arAdditionalParameters, &$html]);
		$event->send();

		return $html;
	}

	public function getPublicText($userField)
	{
		$userType = $this->getUserType($userField['USER_TYPE_ID']);
		if (!empty($userType['CLASS_NAME']) && is_callable([$userType['CLASS_NAME'], 'getPublicText']))
		{
			return call_user_func_array([$userType['CLASS_NAME'], 'getPublicText'], [$userField]);
		}

		return join(', ', array_map(function ($v) {
			return is_null($v) || is_scalar($v) ? (string)$v : '';
		}, (array)$userField['VALUE']));
	}

	public function GetPublicEdit($arUserField, $arAdditionalParameters = [])
	{
		$event = new \Bitrix\Main\Event("main", "onBeforeGetPublicEdit", [&$arUserField, &$arAdditionalParameters]);
		$event->send();

		$arType = $this->GetUserType($arUserField["USER_TYPE_ID"]);

		$html = null;
		$event = new \Bitrix\Main\Event("main", "onGetPublicEdit", [$arUserField, $arAdditionalParameters]);
		$event->send();
		foreach ($event->getResults() as $evenResult)
		{
			if ($evenResult->getType() == \Bitrix\Main\EventResult::SUCCESS)
			{
				$html = $evenResult->getParameters();
				break;
			}
		}

		if ($html !== null)
		{
			//All done
		}
		elseif (isset($arUserField["EDIT_CALLBACK"]) && is_callable($arUserField['EDIT_CALLBACK']))
		{
			$html = call_user_func_array($arUserField["EDIT_CALLBACK"], [
				$arUserField,
				$arAdditionalParameters,
			]);
		}
		elseif ($arType && isset($arType["EDIT_CALLBACK"]) && is_callable($arType['EDIT_CALLBACK']))
		{
			$html = call_user_func_array($arType["EDIT_CALLBACK"], [
				$arUserField,
				$arAdditionalParameters,
			]);
		}
		elseif (isset($arUserField["EDIT_COMPONENT_NAME"]))
		{
			$html = $this->CallUserTypeComponent(
				$arUserField["EDIT_COMPONENT_NAME"],
				$arUserField["EDIT_COMPONENT_TEMPLATE"],
				$arUserField,
				$arAdditionalParameters
			);
		}
		elseif ($arType && isset($arType["EDIT_COMPONENT_NAME"]))
		{
			$html = $this->CallUserTypeComponent(
				$arType["EDIT_COMPONENT_NAME"],
				$arType["EDIT_COMPONENT_TEMPLATE"],
				$arUserField,
				$arAdditionalParameters
			);
		}
		else
		{
			$html = $this->CallUserTypeComponent(
				"bitrix:system.field.edit",
				$arUserField["USER_TYPE_ID"],
				$arUserField,
				$arAdditionalParameters
			);
		}

		$event = new \Bitrix\Main\Event("main", "onAfterGetPublicEdit", [$arUserField, $arAdditionalParameters, &$html]);
		$event->send();

		return $html;
	}

	public function GetSettingsHTML($arUserField, $bVarsFromForm = false)
	{
		if (!is_array($arUserField)) // New field
		{
			if ($arType = $this->GetUserType($arUserField))
			{
				if (is_callable([$arType["CLASS_NAME"], "getsettingshtml"]))
				{
					return call_user_func_array([$arType["CLASS_NAME"], "getsettingshtml"], [false, ["NAME" => "SETTINGS"], $bVarsFromForm]);
				}
			}
		}
		else
		{
			if (!is_array($arUserField["SETTINGS"]) || empty($arUserField["SETTINGS"]))
			{
				$arUserField["SETTINGS"] = $this->PrepareSettings(0, $arUserField);
			}

			if ($arType = $this->GetUserType($arUserField["USER_TYPE_ID"]))
			{
				if (is_callable([$arType["CLASS_NAME"], "getsettingshtml"]))
				{
					return call_user_func_array([$arType["CLASS_NAME"], "getsettingshtml"], [$arUserField, ["NAME" => "SETTINGS"], $bVarsFromForm]);
				}
			}
		}
		return null;
	}

	/**
	 * @param string $entity_id
	 * @param int | null $ID
	 * @param array $arFields
	 * @param bool | int $user_id False means current user id.
	 * @param bool $checkRequired Whether to check required fields.
	 * @param array|null $requiredFields Conditionally required fields.
	 * @param array|null $filteredFields Filtered fields.
	 * @return bool
	 */
	public function CheckFields($entity_id, $ID, $arFields, $user_id = false, $checkRequired = true, array $requiredFields = null, array $filteredFields = null)
	{
		global $APPLICATION;

		$requiredFieldMap = ($requiredFields !== null ? array_fill_keys($requiredFields, true) : null);
		$aMsg = [];

		//1 Get user typed fields list for entity
		$selectFields = null;
		if ($ID === null || $ID > 0 || !$checkRequired)
		{
			// need to select all fields only for a new record and check required
			$selectFields = array_keys($arFields);
			if ($requiredFields !== null)
			{
				$selectFields = $selectFields + $requiredFields;
			}
			if ($filteredFields !== null)
			{
				$selectFields = array_diff($selectFields, $filteredFields);
			}
		}
		$arUserFields = $this->GetUserFields($entity_id, $ID, LANGUAGE_ID, false, $selectFields);

		//2 Filter user typed fields
		if ($filteredFields !== null && $selectFields === null)
		{
			$arUserFields = array_filter(
				$arUserFields,
				static function ($fieldName) use ($filteredFields) {
					return in_array($fieldName, $filteredFields);
				},
				ARRAY_FILTER_USE_KEY
			);
		}

		//3 For each field
		foreach ($arUserFields as $FIELD_NAME => $arUserField)
		{
			$enableRequiredFieldCheck = $arUserField["MANDATORY"] === "Y"
				? $checkRequired : ($requiredFieldMap && isset($requiredFieldMap[$FIELD_NAME]));

			//common Check for all fields
			$isSingleValue = ($arUserField["MULTIPLE"] === "N");

			if (
				$enableRequiredFieldCheck
				&& (
					($ID !== null && $ID <= 0)
					|| array_key_exists($FIELD_NAME, $arFields)
				)
			)
			{
				$EDIT_FORM_LABEL = $arUserField["EDIT_FORM_LABEL"] <> '' ? $arUserField["EDIT_FORM_LABEL"] : $arUserField["FIELD_NAME"];

				if ($arUserField["USER_TYPE"]["BASE_TYPE"] === "file")
				{
					$isNewFilePresent = false;
					$files = [];
					if (is_array($arUserField["VALUE"]))
					{
						$files = array_flip($arUserField["VALUE"]);
					}
					elseif ($arUserField["VALUE"] > 0)
					{
						$files = [$arUserField["VALUE"] => 0];
					}
					elseif (is_numeric($arFields[$FIELD_NAME]))
					{
						if (!$this->isFileValueDeleted($arUserField, (int)$arFields[$FIELD_NAME]))
						{
							$files = [$arFields[$FIELD_NAME] => 0];
						}
					}

					if ($isSingleValue)
					{
						$value = $arFields[$FIELD_NAME];
						if (is_array($value) && array_key_exists("tmp_name", $value))
						{
							if (array_key_exists("del", $value) && $value["del"])
							{
								unset($files[$value["old_id"]]);
							}
							elseif (array_key_exists("size", $value) && $value["size"] > 0)
							{
								$isNewFilePresent = true;
							}
						}
						elseif ($value > 0)
						{
							if (!$this->isFileValueDeleted($arUserField, (int)$value))
							{
								$isNewFilePresent = true;
								$files[$value] = $value;
							}
						}
					}
					else
					{
						if (is_array($arFields[$FIELD_NAME]))
						{
							foreach ($arFields[$FIELD_NAME] as $value)
							{
								if (is_array($value) && array_key_exists("tmp_name", $value))
								{
									if (array_key_exists("del", $value) && $value["del"])
									{
										unset($files[$value["old_id"]]);
									}
									elseif (array_key_exists("size", $value) && $value["size"] > 0)
									{
										$isNewFilePresent = true;
									}
								}
								elseif ($value > 0)
								{
									if (!$this->isFileValueDeleted($arUserField, (int)$value))
									{
										$isNewFilePresent = true;
										$files[$value] = $value;
									}
								}
							}
						}
					}

					if (!$isNewFilePresent && empty($files))
					{
						$aMsg[] = ["id" => $FIELD_NAME, "text" => str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING"))];
					}
				}
				elseif ($isSingleValue)
				{
					if ($this->isValueEmpty($arUserField, $arFields[$FIELD_NAME]))
					{
						$aMsg[] = ["id" => $FIELD_NAME, "text" => str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING"))];
					}
				}
				else
				{
					if (!is_array($arFields[$FIELD_NAME]))
					{
						$aMsg[] = ["id" => $FIELD_NAME, "text" => str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING"))];
					}
					else
					{
						$bFound = false;
						foreach ($arFields[$FIELD_NAME] as $value)
						{
							if (
								(
									is_array($value)
									&& (implode("", $value) <> '')
								)
								||
								(
									!is_array($value)
									&& !$this->isValueEmpty($arUserField, $value)
								)
							)
							{
								$bFound = true;
								break;
							}
						}
						if (!$bFound)
						{
							$aMsg[] = ["id" => $FIELD_NAME, "text" => str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING"))];
						}
					}
				}
			}
			//identify user type
			if ($arUserField["USER_TYPE"])
			{
				$CLASS_NAME = $arUserField["USER_TYPE"]["CLASS_NAME"];
				if (array_key_exists($FIELD_NAME, $arFields) && is_callable([$CLASS_NAME, "checkfields"]))
				{
					if ($isSingleValue)
					{
						if (!($arFields[$FIELD_NAME] instanceof SqlExpression))
						{
							$canUseArrayValueForSingleField = false;
							if (
								is_callable([$CLASS_NAME, 'canUseArrayValueForSingleField'])
								&& $CLASS_NAME::canUseArrayValueForSingleField()
							)
							{
								$canUseArrayValueForSingleField = true;
							}

							if (is_array($arFields[$FIELD_NAME]) && !$canUseArrayValueForSingleField)
							{
								$fieldName = HtmlFilter::encode(
									empty($arUserField['EDIT_FORM_LABEL'])
										? $arUserField['FIELD_NAME']
										: $arUserField['EDIT_FORM_LABEL']
								);
								$messages = [
									[
										'id' => $arUserField['FIELD_NAME'],
										'text' => Loc::getMessage('USER_TYPE_FIELD_VALUE_IS_MULTIPLE', [
											'#FIELD_NAME#' => $fieldName,
										]),
									],
								];
							}
							else
							{
								//apply appropriate check function
								$messages = call_user_func_array(
									[
										$CLASS_NAME,
										'checkfields',
									],
									[
										$arUserField,
										$arFields[$FIELD_NAME],
										$user_id,
									]
								);
							}
							$aMsg = array_merge($aMsg, $messages);
						}
					}
					elseif (is_array($arFields[$FIELD_NAME]))
					{
						foreach ($arFields[$FIELD_NAME] as $value)
						{
							if (!empty($value))
							{
								if ($value instanceof SqlExpression)
								{
									$aMsg[] = [
										'id' => $FIELD_NAME,
										'text' => "Multiple field \"#FIELD_NAME#\" can't handle SqlExpression because of serialized uts cache",
									];
								}
								else
								{
									//apply appropriate check function
									$ar = call_user_func_array(
										[$CLASS_NAME, "checkfields"],
										[$arUserField, $value, $user_id]
									);
									$aMsg = array_merge($aMsg, $ar);
								}
							}
						}
					}
				}
			}
		}

		//3 Return succsess/fail flag
		if (!empty($aMsg))
		{
			$e = new CAdminException($aMsg);
			$APPLICATION->ThrowException($e);
			return false;
		}
		return true;
	}

	/**
	 * Replacement for CheckFields, if you are already have fetched old data
	 *
	 * @param $entity_id
	 * @param $oldData
	 * @param $arFields
	 *
	 * @return bool
	 */
	public function CheckFieldsWithOldData($entity_id, $oldData, $arFields)
	{
		global $APPLICATION;

		$aMsg = [];

		//1 Get user typed fields list for entity
		$arUserFields = $this->getUserFieldsWithReadyData($entity_id, $oldData, LANGUAGE_ID);

		//2 For each field
		foreach ($arUserFields as $FIELD_NAME => $arUserField)
		{
			//identify user type
			if ($arUserField["USER_TYPE"])
			{
				$CLASS_NAME = $arUserField["USER_TYPE"]["CLASS_NAME"];
				$EDIT_FORM_LABEL = $arUserField["EDIT_FORM_LABEL"] <> '' ? $arUserField["EDIT_FORM_LABEL"] : $arUserField["FIELD_NAME"];

				if (array_key_exists($FIELD_NAME, $arFields) && is_callable([$CLASS_NAME, "checkfields"]))
				{
					// check required values
					if ($arUserField["MANDATORY"] === "Y")
					{
						if ($arUserField["USER_TYPE"]["BASE_TYPE"] === "file")
						{
							$isNewFilePresent = false;
							$files = [];
							if (is_array($arUserField["VALUE"]))
							{
								$files = array_flip($arUserField["VALUE"]);
							}
							elseif ($arUserField["VALUE"] > 0)
							{
								$files = [$arUserField["VALUE"] => 0];
							}
							elseif (is_numeric($arFields[$FIELD_NAME]))
							{
								$files = [$arFields[$FIELD_NAME] => 0];
							}

							if ($arUserField["MULTIPLE"] === "N")
							{
								$value = $arFields[$FIELD_NAME];
								if (is_array($value) && array_key_exists("tmp_name", $value))
								{
									if (array_key_exists("del", $value) && $value["del"])
									{
										unset($files[$value["old_id"]]);
									}
									elseif (array_key_exists("size", $value) && $value["size"] > 0)
									{
										$isNewFilePresent = true;
									}
								}
								elseif ($value > 0)
								{
									$isNewFilePresent = true;
									$files[$value] = $value;
								}
							}
							else
							{
								if (is_array($arFields[$FIELD_NAME]))
								{
									foreach ($arFields[$FIELD_NAME] as $value)
									{
										if (is_array($value) && array_key_exists("tmp_name", $value))
										{
											if (array_key_exists("del", $value) && $value["del"])
											{
												unset($files[$value["old_id"]]);
											}
											elseif (array_key_exists("size", $value) && $value["size"] > 0)
											{
												$isNewFilePresent = true;
											}
										}
										elseif ($value > 0)
										{
											$isNewFilePresent = true;
											$files[$value] = $value;
										}
									}
								}
							}

							if (!$isNewFilePresent && empty($files))
							{
								$aMsg[] = ["id" => $FIELD_NAME, "text" => str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING"))];
							}
						}
						elseif ($arUserField["MULTIPLE"] == "N")
						{
							if ($this->isValueEmpty($arUserField, $arFields[$FIELD_NAME]))
							{
								$aMsg[] = ["id" => $FIELD_NAME, "text" => str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING"))];
							}
						}
						else
						{
							if (!is_array($arFields[$FIELD_NAME]))
							{
								$aMsg[] = ["id" => $FIELD_NAME, "text" => str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING"))];
							}
							else
							{
								$bFound = false;
								foreach ($arFields[$FIELD_NAME] as $value)
								{
									if (
										(is_array($value) && (implode("", $value) <> ''))
										|| ((!is_array($value)) && ((string)$value <> ''))
									)
									{
										$bFound = true;
										break;
									}
								}
								if (!$bFound)
								{
									$aMsg[] = ["id" => $FIELD_NAME, "text" => str_replace("#FIELD_NAME#", $EDIT_FORM_LABEL, GetMessage("USER_TYPE_FIELD_VALUE_IS_MISSING"))];
								}
							}
						}
					}

					// check regular values
					if ($arUserField["MULTIPLE"] == "N")
					{
						//apply appropriate check function
						$ar = call_user_func_array(
							[$CLASS_NAME, "checkfields"],
							[$arUserField, $arFields[$FIELD_NAME]]
						);
						$aMsg = array_merge($aMsg, $ar);
					}
					elseif (is_array($arFields[$FIELD_NAME]))
					{
						foreach ($arFields[$FIELD_NAME] as $value)
						{
							if (!empty($value))
							{
								//apply appropriate check function
								$ar = call_user_func_array(
									[$CLASS_NAME, "checkfields"],
									[$arUserField, $value]
								);
								$aMsg = array_merge($aMsg, $ar);
							}
						}
					}
				}
			}
		}

		//3 Return succsess/fail flag
		if (!empty($aMsg))
		{
			$e = new CAdminException($aMsg);
			$APPLICATION->ThrowException($e);
			return false;
		}

		return true;
	}

	protected function isValueEmpty(array $userField, $value): bool
	{
		$className = $userField['USER_TYPE']['CLASS_NAME'] ?? null;
		if (!is_a($className, BaseType::class, true))
		{
			$className = BaseType::class;
		}
		if (!$className::isMandatorySupported())
		{
			return false;
		}
		$isNumberType = (
			$userField['USER_TYPE_ID'] === \Bitrix\Main\UserField\Types\IntegerType::USER_TYPE_ID
			|| $userField['USER_TYPE_ID'] === \Bitrix\Main\UserField\Types\DoubleType::USER_TYPE_ID
		);
		if (
			$isNumberType
			&& (
				$value === 0 || $value === 0.0 || $value === "0" || $value === "0.0" || $value === "0,0"
			)
		)
		{
			return false;
		}

		return (string)$value === "";
	}

	public function Update($entity_id, $ID, $arFields, $user_id = false)
	{
		$entity_id = static::normalizeId($entity_id);
		$ID = (int)$ID;

		$result = $this->updateUserFieldValuesByEvent($entity_id, $ID, $arFields);
		if ($result !== null)
		{
			return $result;
		}

		$arUpdate = [];
		$arInsert = [];
		$arInsertType = [];
		$arDelete = [];

		$arUserFields = $this->GetUserFields($entity_id, $ID, false, $user_id, array_keys($arFields));

		foreach ($arUserFields as $FIELD_NAME => $arUserField)
		{
			if (array_key_exists($FIELD_NAME, $arFields))
			{
				$arUserField['VALUE_ID'] = $ID;

				if ($arUserField["MULTIPLE"] == "N")
				{
					if (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "onbeforesave"]))
					{
						$arFields[$FIELD_NAME] = call_user_func_array([$arUserField["USER_TYPE"]["CLASS_NAME"], "onbeforesave"], [$arUserField, $arFields[$FIELD_NAME], $user_id]);
					}

					$modified = (!array_key_exists('VALUE_RAW', $arUserField) || $arFields[$FIELD_NAME] !== $arUserField['VALUE_RAW']);
					if ($modified)
					{
						if ((string)$arFields[$FIELD_NAME] !== '')
						{
							$arUpdate[$FIELD_NAME] = $arFields[$FIELD_NAME];
						}
						elseif (!empty($arUserField['VALUE_EXISTS']))
						{
							$arUpdate[$FIELD_NAME] = null;
						}
					}
				}
				elseif (is_array($arFields[$FIELD_NAME]))
				{
					$arInsert[$arUserField["ID"]] = [];
					$arInsertType[$arUserField["ID"]] = $arUserField["USER_TYPE"];
					$arInsertType[$arUserField['ID']]['FIELD_NAME'] = $arUserField['FIELD_NAME'];

					if (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "onbeforesaveall"]))
					{
						$arInsert[$arUserField["ID"]] = call_user_func_array([$arUserField["USER_TYPE"]["CLASS_NAME"], "onbeforesaveall"], [$arUserField, $arFields[$FIELD_NAME], $user_id]);
					}
					else
					{
						foreach ($arFields[$FIELD_NAME] as $value)
						{
							if (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "onbeforesave"]))
							{
								$value = call_user_func_array([$arUserField["USER_TYPE"]["CLASS_NAME"], "onbeforesave"], [$arUserField, $value, $user_id]);
							}

							if ((string)$value <> '')
							{
								switch ($arInsertType[$arUserField["ID"]]["BASE_TYPE"])
								{
									case "int":
									case "file":
									case "enum":
										$value = intval($value);
										break;
									case "double":
										$value = doubleval($value);
										if (!is_finite($value))
										{
											$value = 0;
										}
										break;
									case "string":
										$value = (string)$value;
										break;
								}
								$arInsert[$arUserField["ID"]][] = $value;
							}
						}
					}

					if ($arUserField['USER_TYPE_ID'] == 'datetime')
					{
						$serialized = \Bitrix\Main\UserFieldTable::serializeMultipleDatetime($arInsert[$arUserField["ID"]]);
					}
					elseif ($arUserField['USER_TYPE_ID'] == 'date')
					{
						$serialized = \Bitrix\Main\UserFieldTable::serializeMultipleDate($arInsert[$arUserField["ID"]]);
					}
					else
					{
						$serialized = serialize($arInsert[$arUserField["ID"]]);
					}

					$modified = (!array_key_exists('VALUE_RAW', $arUserField) || $serialized !== $arUserField['VALUE_RAW']);
					if ($modified)
					{
						$arUpdate[$FIELD_NAME] = $serialized;

						if (!empty($arUserField['VALUE_EXISTS']))
						{
							$arDelete[$arUserField["ID"]] = true;
						}
					}
					else
					{
						$arInsert[$arUserField["ID"]] = [];
					}
				}
			}
		}

		$lower_entity_id = strtolower($entity_id);

		$connection = Application::getConnection();
		$helper = $connection->getSqlHelper();

		if (!empty($arUpdate))
		{
			$insertFields = $arUpdate;
			$insertFields['VALUE_ID'] = $ID;
			$strUpdate = $helper->prepareMerge("b_uts_" . $lower_entity_id, ['VALUE_ID'], $insertFields, $arUpdate)[0];
		}
		else
		{
			// nothing to insert/update
			return true;
		}

		$connection->startTransaction();

		$connection->query($strUpdate);

		foreach ($arDelete as $key => $value)
		{
			$connection->query("DELETE from b_utm_" . $lower_entity_id . " WHERE FIELD_ID = " . intval($key) . " AND VALUE_ID = " . $ID);
		}

		foreach ($arInsert as $FieldId => $arField)
		{
			switch ($arInsertType[$FieldId]["BASE_TYPE"])
			{
				case "int":
				case "file":
				case "enum":
					$COLUMN = "VALUE_INT";
					break;
				case "double":
					$COLUMN = "VALUE_DOUBLE";
					break;
				case "datetime":
					$COLUMN = "VALUE_DATE";
					break;
				default:
					$COLUMN = "VALUE";
			}
			foreach ($arField as $value)
			{
				if ($value instanceof \Bitrix\Main\Type\Date)
				{
					// little hack to avoid timezone vs 00:00:00 ambiguity. for utm only
					$value = new \Bitrix\Main\Type\DateTime($value->format('Y-m-d H:i:s'), 'Y-m-d H:i:s');
				}

				switch ($arInsertType[$FieldId]["BASE_TYPE"])
				{
					case "int":
					case "file":
					case "enum":
					case "double":
						break;
					case "datetime":
						$userFieldName = $arInsertType[$FieldId]['FIELD_NAME'];
						$value = DateTimeType::charToDate($arUserFields[$userFieldName], $value);
						break;
					default:
						$value = "'" . $helper->forSql($value) . "'";
				}
				$connection->query("INSERT INTO b_utm_" . $lower_entity_id . " (VALUE_ID, FIELD_ID, " . $COLUMN . ")
					VALUES (" . intval($ID) . ", '" . $FieldId . "', " . $value . ")");
			}
		}

		$connection->commitTransaction();

		return true;
	}

	public function copy($entity_id, $id, $copiedId, $entityObject, $userId = false, $ignoreList = [])
	{
		$userFields = $this->getUserFields($entity_id, $id);

		$fields = [];
		foreach ($userFields as $fieldName => $userField)
		{
			if (!in_array($fieldName, $ignoreList))
			{
				if (is_callable([$userField["USER_TYPE"]["CLASS_NAME"], "onBeforeCopy"]))
				{
					$fields[$fieldName] = call_user_func_array(
						[$userField["USER_TYPE"]["CLASS_NAME"], "onBeforeCopy"],
						[$userField, $copiedId, $userField["VALUE"], $entityObject, $userId]
					);
				}
				else
				{
					$fields[$fieldName] = $userField["VALUE"];
				}
			}
		}

		$this->update($entity_id, $copiedId, $fields, $userId);

		foreach ($userFields as $fieldName => $userField)
		{
			if (!in_array($fieldName, $ignoreList))
			{
				if (is_callable([$userField["USER_TYPE"]["CLASS_NAME"], "onAfterCopy"]))
				{
					$fields[$fieldName] = call_user_func_array(
						[$userField["USER_TYPE"]["CLASS_NAME"], "onAfterCopy"],
						[$userField, $copiedId, $fields[$fieldName], $entityObject, $userId]
					);
				}
			}
		}
	}

	public function Delete($entity_id, $ID)
	{
		global $DB;

		$ID = (int)$ID;

		$result = $this->deleteUserFieldValuesByEvent($entity_id, $ID);
		if ($result !== null)
		{
			return;
		}

		if ($arUserFields = $this->GetUserFields($entity_id, $ID, false, 0))
		{
			foreach ($arUserFields as $arUserField)
			{
				if (is_array($arUserField["VALUE"]))
				{
					foreach ($arUserField["VALUE"] as $value)
					{
						if (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "ondelete"]))
						{
							call_user_func_array([$arUserField["USER_TYPE"]["CLASS_NAME"], "ondelete"], [$arUserField, $value]);
						}

						if ($arUserField["USER_TYPE"]["BASE_TYPE"] == "file")
						{
							CFile::Delete($value);
						}
					}
				}
				else
				{
					if (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "ondelete"]))
					{
						call_user_func_array([$arUserField["USER_TYPE"]["CLASS_NAME"], "ondelete"], [$arUserField, $arUserField["VALUE"]]);
					}

					if ($arUserField["USER_TYPE"]["BASE_TYPE"] == "file")
					{
						CFile::Delete($arUserField["VALUE"]);
					}
				}
			}
			$DB->Query("DELETE FROM b_utm_" . strtolower($entity_id) . " WHERE VALUE_ID = " . $ID);
			$DB->Query("DELETE FROM b_uts_" . strtolower($entity_id) . " WHERE VALUE_ID = " . $ID);
		}
	}

	public function OnSearchIndex($entity_id, $ID)
	{
		$result = "";
		if ($arUserFields = $this->GetUserFields($entity_id, $ID, false, 0))
		{
			foreach ($arUserFields as $arUserField)
			{
				if ($arUserField["IS_SEARCHABLE"] == "Y")
				{
					if ($arUserField["USER_TYPE"])
					{
						if (is_callable([$arUserField["USER_TYPE"]["CLASS_NAME"], "onsearchindex"]))
						{
							$result .= "\r\n" . call_user_func_array([$arUserField["USER_TYPE"]["CLASS_NAME"], "onsearchindex"], [$arUserField]);
						}
					}
				}
			}
		}
		return $result;
	}

	public function GetRights($ENTITY_ID = false, $ID = false)
	{
		if (($ID !== false) && array_key_exists("ID:" . $ID, $this->arRightsCache))
		{
			return $this->arRightsCache["ID:" . $ID];
		}
		if (($ENTITY_ID !== false) && array_key_exists("ENTITY_ID:" . $ENTITY_ID, $this->arRightsCache))
		{
			return $this->arRightsCache["ENTITY_ID:" . $ENTITY_ID];
		}

		global $USER;
		if (is_object($USER) && $USER->CanDoOperation('edit_other_settings'))
		{
			$RIGHTS = "X";
		}
		else
		{
			$RIGHTS = "D";
			if ($ID !== false)
			{
				$ar = CUserTypeEntity::GetByID($ID);
				if ($ar)
				{
					$ENTITY_ID = $ar["ENTITY_ID"];
				}
			}

			foreach (GetModuleEvents("main", "OnUserTypeRightsCheck", true) as $arEvent)
			{
				$res = ExecuteModuleEventEx($arEvent, [$ENTITY_ID]);
				if ($res > $RIGHTS)
				{
					$RIGHTS = $res;
				}
			}
		}

		if ($ID !== false)
		{
			$this->arRightsCache["ID:" . $ID] = $RIGHTS;
		}
		if ($ENTITY_ID !== false)
		{
			$this->arRightsCache["ENTITY_ID:" . $ENTITY_ID] = $RIGHTS;
		}

		return $RIGHTS;
	}

	/**
	 * @param             $arUserField
	 * @param null|string $fieldName
	 * @param array $fieldParameters
	 *
	 * @return Entity\DatetimeField|Entity\FloatField|Entity\IntegerField|Entity\StringField|mixed
	 * @throws Bitrix\Main\ArgumentException
	 */
	public function getEntityField($arUserField, $fieldName = null, $fieldParameters = [])
	{
		if (empty($fieldName))
		{
			$fieldName = $arUserField['FIELD_NAME'];
		}

		if (is_callable([$arUserField['USER_TYPE']['CLASS_NAME'], 'getEntityField']))
		{
			$field = call_user_func([$arUserField['USER_TYPE']['CLASS_NAME'], 'getEntityField'], $fieldName, $fieldParameters);
		}
		elseif ($arUserField['USER_TYPE']['USER_TYPE_ID'] == 'date')
		{
			$field = new Entity\DateField($fieldName, $fieldParameters);
		}
		else
		{
			switch ($arUserField['USER_TYPE']['BASE_TYPE'])
			{
				case 'int':
				case 'enum':
				case 'file':
					$field = (new Entity\IntegerField($fieldName, $fieldParameters))
						->configureNullable()
					;
					break;
				case 'double':
					$field = (new Entity\FloatField($fieldName, $fieldParameters))
						->configureNullable()
					;
					break;
				case 'string':
					$field = (new Entity\StringField($fieldName, $fieldParameters))
						->configureNullable()
					;
					break;
				case 'datetime':
					$field = (new Entity\DatetimeField($fieldName, $fieldParameters))
						->configureNullable()
						->configureUseTimezone(isset($arUserField['SETTINGS']['USE_TIMEZONE']) && $arUserField['SETTINGS']['USE_TIMEZONE'] == 'Y')
					;
					break;
				default:
					throw new \Bitrix\Main\ArgumentException(sprintf(
						'Unknown userfield base type `%s`', $arUserField["USER_TYPE"]['BASE_TYPE']
					));
			}
		}

		$ufHandlerClass = $arUserField['USER_TYPE']['CLASS_NAME'];

		if (is_subclass_of($ufHandlerClass, BaseType::class))
		{
			$defaultValue = $ufHandlerClass::getDefaultValue($arUserField);
			$field->configureDefaultValue($defaultValue);
		}

		return $field;
	}

	/**
	 * @param                    $arUserField
	 * @param Entity\ScalarField $entityField
	 *
	 * @return Entity\ReferenceField[]
	 */
	public function getEntityReferences($arUserField, Entity\ScalarField $entityField)
	{
		if (is_callable([$arUserField['USER_TYPE']['CLASS_NAME'], 'getEntityReferences']))
		{
			return call_user_func([$arUserField['USER_TYPE']['CLASS_NAME'], 'getEntityReferences'], $arUserField, $entityField);
		}

		return [];
	}

	protected function getUserFieldValuesByEvent(array $userFields, string $entityId, int $value): ?array
	{
		$result = [];
		if ($value === 0)
		{
			return null;
		}
		$isGotByEvent = false;
		$event = new \Bitrix\Main\Event('main', 'onGetUserFieldValues', ['userFields' => $userFields, 'entityId' => $entityId, 'value' => $value]);
		$event->send();
		foreach ($event->getResults() as $eventResult)
		{
			if ($eventResult->getType() === \Bitrix\Main\EventResult::SUCCESS)
			{
				$parameters = $eventResult->getParameters();
				if (isset($parameters['values']) && is_array($parameters['values']))
				{
					$isGotByEvent = true;
					foreach ($userFields as $fieldName => $userField)
					{
						if (isset($parameters['values'][$fieldName]))
						{
							$result[$fieldName] = $parameters['values'][$fieldName];
						}
					}
				}
			}
		}
		if ($isGotByEvent)
		{
			return $result;
		}

		return null;
	}

	protected function updateUserFieldValuesByEvent(string $entityId, int $id, array $fields): ?bool
	{
		$result = null;

		$event = new \Bitrix\Main\Event('main', 'onUpdateUserFieldValues', ['entityId' => $entityId, 'id' => $id, 'fields' => $fields]);
		$event->send();
		foreach ($event->getResults() as $eventResult)
		{
			if ($eventResult->getType() === \Bitrix\Main\EventResult::SUCCESS)
			{
				$result = true;
			}
			elseif ($eventResult->getType() === \Bitrix\Main\EventResult::ERROR)
			{
				$result = false;
			}
		}

		return $result;
	}

	protected function deleteUserFieldValuesByEvent(string $entityId, int $id): ?bool
	{
		$result = null;

		$event = new \Bitrix\Main\Event('main', 'onDeleteUserFieldValues', ['entityId' => $entityId, 'id' => $id]);
		$event->send();
		foreach ($event->getResults() as $eventResult)
		{
			if ($eventResult->getType() === \Bitrix\Main\EventResult::SUCCESS)
			{
				$result = true;
			}
			elseif ($eventResult->getType() === \Bitrix\Main\EventResult::ERROR)
			{
				$result = false;
			}
		}

		return $result;
	}

	protected function getEntities($entity_id, $LANG, $user_id)
	{
		$cacheId = $entity_id . "." . $LANG . '.' . (int)$user_id;

		$result = [];
		if (!array_key_exists($cacheId, $this->arFieldsCache))
		{
			$arFilter = ["ENTITY_ID" => $entity_id];
			if ($LANG)
			{
				$arFilter["LANG"] = $LANG;
			}
			$rs = CUserTypeEntity::GetList([], $arFilter);
			while ($arUserField = $rs->Fetch())
			{
				if ($arType = $this->GetUserType($arUserField["USER_TYPE_ID"]))
				{
					if ($user_id !== 0 && is_callable([$arType["CLASS_NAME"], "checkpermission"]))
					{
						if (!call_user_func_array([$arType["CLASS_NAME"], "checkpermission"], [$arUserField, $user_id]))
						{
							continue;
						}
					}
					$arUserField["USER_TYPE"] = $arType;
					$arUserField["VALUE"] = false;
					if (!is_array($arUserField["SETTINGS"]) || empty($arUserField["SETTINGS"]))
					{
						$arUserField["SETTINGS"] = $this->PrepareSettings(0, $arUserField);
					}
					$result[$arUserField["FIELD_NAME"]] = $arUserField;
				}
			}
			$this->arFieldsCache[$cacheId] = $result;
		}
		else
		{
			$result = $this->arFieldsCache[$cacheId];
		}

		return $result;
	}

	protected static function normalizeId(string $id): string
	{
		return preg_replace("/[^0-9A-Z_]+/", "", $id);
	}

	protected function isFileValueDeleted(array $userField, int $fileId): bool
	{
		$fieldName = $userField['FIELD_NAME'] ?? '';

		if (is_string($fieldName) && $fieldName !== '')
		{
			$deletedValues = Application::getInstance()->getContext()->getRequest()->get($fieldName . '_del');

			if (!is_array($deletedValues))
			{
				$deletedValues = [$deletedValues];
			}

			foreach ($deletedValues as $deletedValue)
			{
				if ($fileId === (int)$deletedValue)
				{
					return true;
				}
			}

			$fileInputUtility = FileInputUtility::instance();
			$delResult = $fileInputUtility->checkDeletedFiles($fileInputUtility->getUserFieldCid($userField));
			if (in_array($fileId, $delResult))
			{
				return true;
			}
		}

		return false;
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit