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/usertype.php
<?php

/**
 * Bitrix Framework
 * @package bitrix
 * @subpackage main
 * @copyright 2001-2024 Bitrix
 */

use Bitrix\Main\UserFieldTable;
use Bitrix\Main\UserFieldLangTable;

IncludeModuleLangFile(__FILE__);

/**
 * This class is used to manage metadata of user properties.
 *
 * <p>Selections, Deletion, Addition, and Update of metadata in the b_user_field table.</p>
 * create table b_user_field (
 * ID    int(11) not null auto_increment,
 * ENTITY_ID  varchar(50),
 * FIELD_NAME  varchar(50),
 * USER_TYPE_ID  varchar(50),
 * XML_ID    varchar(255),
 * SORT    int,
 * MULTIPLE  char(1) not null default 'N',
 * MANDATORY  char(1) not null default 'N',
 * SHOW_FILTER  char(1) not null default 'N',
 * SHOW_IN_LIST  char(1) not null default 'Y',
 * EDIT_IN_LIST  char(1) not null default 'Y',
 * IS_SEARCHABLE  char(1) not null default 'N',
 * SETTINGS  text,
 * PRIMARY KEY (ID),
 * UNIQUE ux_user_type_entity(ENTITY_ID, FIELD_NAME)
 * )
 * ------------------
 * ID
 * ENTITY_ID (example: IBLOCK_SECTION, USER ....)
 * FIELD_NAME (example: UF_EMAIL, UF_SOME_COUNTER ....)
 * SORT -- used to do check in the specified order
 * BASE_TYPE - String, Number, Integer, Enumeration, File, DateTime
 * USER_TYPE_ID
 * SETTINGS (blob) -- to store some settings which may be useful for an field instance
 * [some base settings comon to all types: mandatory or no, etc.]
 * <p>b_user_field</p>
 * <ul>
 * <li><b>ID</b> int(11) not null auto_increment
 * <li>ENTITY_ID varchar(50)
 * <li>FIELD_NAME varchar(20)
 * <li>USER_TYPE_ID varchar(50)
 * <li>XML_ID varchar(255)
 * <li>SORT int
 * <li>MULTIPLE char(1) not null default 'N'
 * <li>MANDATORY char(1) not null default 'N'
 * <li>SHOW_FILTER char(1) not null default 'N'
 * <li>SHOW_IN_LIST char(1) not null default 'Y'
 * <li>EDIT_IN_LIST char(1) not null default 'Y'
 * <li>IS_SEARCHABLE char(1) not null default 'N'
 * <li>SETTINGS text
 * <li>PRIMARY KEY (ID),
 * <li>UNIQUE ux_user_type_entity(ENTITY_ID, FIELD_NAME)
 * </ul>
 * create table b_user_field_lang (
 * USER_FIELD_ID int(11) REFERENCES b_user_field(ID),
 * LANGUAGE_ID char(2),
 * EDIT_FORM_LABEL varchar(255),
 * LIST_COLUMN_LABEL varchar(255),
 * LIST_FILTER_LABEL varchar(255),
 * ERROR_MESSAGE varchar(255),
 * HELP_MESSAGE varchar(255),
 * PRIMARY KEY (USER_FIELD_ID, LANGUAGE_ID)
 * )
 * <p>b_user_field_lang</p>
 * <ul>
 * <li><b>USER_FIELD_ID</b> int(11) REFERENCES b_user_field(ID)
 * <li><b>LANGUAGE_ID</b> char(2)
 * <li>EDIT_FORM_LABEL varchar(255)
 * <li>LIST_COLUMN_LABEL varchar(255)
 * <li>LIST_FILTER_LABEL varchar(255)
 * <li>ERROR_MESSAGE varchar(255)
 * <li>HELP_MESSAGE varchar(255)
 * <li>PRIMARY KEY (USER_FIELD_ID, LANGUAGE_ID)
 * </ul>
 * @package usertype
 * @subpackage classes
 */
class CAllUserTypeEntity extends CDBResult
{
	function CreatePropertyTables($entity_id)
	{
		$connection = \Bitrix\Main\Application::getConnection();

		if (!$connection->isTableExists("b_utm_" . strtolower($entity_id)))
		{
			$connection->createTable("b_utm_" . strtolower($entity_id), [
				'ID' => new \Bitrix\Main\ORM\Fields\IntegerField('ID'),
				'VALUE_ID' => new \Bitrix\Main\ORM\Fields\IntegerField('VALUE_ID'),
				'FIELD_ID' => new \Bitrix\Main\ORM\Fields\IntegerField('FIELD_ID'),
				'VALUE' => new \Bitrix\Main\ORM\Fields\TextField('VALUE', ['nullable' => true]),
				'VALUE_INT' => new \Bitrix\Main\ORM\Fields\IntegerField('VALUE_INT', ['nullable' => true]),
				'VALUE_DOUBLE' => new \Bitrix\Main\ORM\Fields\FloatField('VALUE_DOUBLE', ['nullable' => true]),
				'VALUE_DATE' => new \Bitrix\Main\ORM\Fields\DatetimeField('VALUE_DATE', ['nullable' => true]),
			], ['ID'], ['ID']);

			$connection->createIndex("b_utm_" . strtolower($entity_id), "ix_utm_" . $entity_id . "_2", ["VALUE_ID"]);
			$connection->createIndex("b_utm_" . strtolower($entity_id), "ix_utm_" . $entity_id . "_4", ["FIELD_ID", "VALUE_ID", "VALUE_INT"]);
		}

		if (!$connection->isTableExists("b_uts_" . strtolower($entity_id)))
		{
			$connection->createTable("b_uts_" . strtolower($entity_id), [
				'VALUE_ID' => new \Bitrix\Main\ORM\Fields\IntegerField('VALUE_ID'),
			], ['VALUE_ID']);
		}

		return true;
	}

	/**
	 * Function to fetch metadata of a user property.
	 *
	 * <p>Returns an associative array of metadata that can be passed to Update.</p>
	 * @param integer $ID Property identifier
	 * @return array If the property is not found, false is returned
	 * @static
	 */
	public static function GetByID($ID)
	{
		global $DB;
		static $arLabels = ["EDIT_FORM_LABEL", "LIST_COLUMN_LABEL", "LIST_FILTER_LABEL", "ERROR_MESSAGE", "HELP_MESSAGE"];
		static $cache = [];

		if (!array_key_exists($ID, $cache))
		{
			$rsUserField = CUserTypeEntity::GetList([], ["ID" => intval($ID)]);
			if ($arUserField = $rsUserField->Fetch())
			{
				$rs = $DB->Query("SELECT * FROM b_user_field_lang WHERE USER_FIELD_ID = " . intval($ID));
				while ($ar = $rs->Fetch())
				{
					foreach ($arLabels as $label)
					{
						$arUserField[$label][$ar["LANGUAGE_ID"]] = $ar[$label];
					}
				}
				$cache[$ID] = $arUserField;
			}
			else
			{
				$cache[$ID] = false;
			}
		}
		return $cache[$ID];
	}

	/**
	 * Function to fetch metadata of user properties.
	 *
	 * <p>Returns CDBResult - a selection based on filter and sorting.</p>
	 * <p>The aSort parameter defaults to array("SORT"=>"ASC", "ID"=>"ASC").</p>
	 * <p>If LANG is passed in aFilter, language messages are additionally selected.</p>
	 * @param array $aSort Associative array for sorting (ID, ENTITY_ID, FIELD_NAME, SORT, USER_TYPE_ID)
	 * @param array $aFilter Associative array for filtering with strict matching (<b>equals</b>) (ID, ENTITY_ID, FIELD_NAME, USER_TYPE_ID, SORT, MULTIPLE, MANDATORY, SHOW_FILTER)
	 * @return CDBResult
	 * @static
	 */
	public static function GetList($aSort = [], $aFilter = [])
	{
		global $DB, $CACHE_MANAGER;

		if (CACHED_b_user_field !== false)
		{
			$cacheId = "b_user_type" . md5(serialize($aSort) . "." . serialize($aFilter));
			if ($CACHE_MANAGER->Read(CACHED_b_user_field, $cacheId, "b_user_field"))
			{
				$arResult = $CACHE_MANAGER->Get($cacheId);
				$res = new CDBResult;
				$res->InitFromArray($arResult);
				$res = new CUserTypeEntity($res);
				return $res;
			}
		}

		$bLangJoin = false;
		$arFilter = [];
		foreach ($aFilter as $key => $val)
		{
			if (is_array($val) || (string)$val == '')
			{
				continue;
			}

			$key = strtoupper($key);
			$val = $DB->ForSql($val);

			switch ($key)
			{
				case "ID":
				case "ENTITY_ID":
				case "FIELD_NAME":
				case "USER_TYPE_ID":
				case "XML_ID":
				case "SORT":
				case "MULTIPLE":
				case "MANDATORY":
				case "SHOW_FILTER":
				case "SHOW_IN_LIST":
				case "EDIT_IN_LIST":
				case "IS_SEARCHABLE":
					$arFilter[] = "UF." . $key . " = '" . $val . "'";
					break;
				case "LANG":
					$bLangJoin = $val;
					break;
			}
		}

		$arOrder = [];
		foreach ($aSort as $key => $val)
		{
			$key = strtoupper($key);
			$ord = (strtoupper($val) <> "ASC" ? "DESC" : "ASC");
			switch ($key)
			{
				case "ID":
				case "ENTITY_ID":
				case "FIELD_NAME":
				case "USER_TYPE_ID":
				case "XML_ID":
				case "SORT":
					$arOrder[] = "UF." . $key . " " . $ord;
					break;
			}
		}
		if (empty($arOrder))
		{
			$arOrder[] = "UF.SORT asc";
			$arOrder[] = "UF.ID asc";
		}
		DelDuplicateSort($arOrder);
		$sOrder = "\nORDER BY " . implode(", ", $arOrder);

		if (empty($arFilter))
		{
			$sFilter = "";
		}
		else
		{
			$sFilter = "\nWHERE " . implode("\nAND ", $arFilter);
		}

		$strSql = "
			SELECT
				UF.ID
				,UF.ENTITY_ID
				,UF.FIELD_NAME
				,UF.USER_TYPE_ID
				,UF.XML_ID
				,UF.SORT
				,UF.MULTIPLE
				,UF.MANDATORY
				,UF.SHOW_FILTER
				,UF.SHOW_IN_LIST
				,UF.EDIT_IN_LIST
				,UF.IS_SEARCHABLE
				,UF.SETTINGS
				" . ($bLangJoin ? "
					,UFL.EDIT_FORM_LABEL
					,UFL.LIST_COLUMN_LABEL
					,UFL.LIST_FILTER_LABEL
					,UFL.ERROR_MESSAGE
					,UFL.HELP_MESSAGE
				" : "") . "
			FROM
				b_user_field UF
				" . ($bLangJoin ? "LEFT JOIN b_user_field_lang UFL on UFL.LANGUAGE_ID = '" . $bLangJoin . "' AND UFL.USER_FIELD_ID = UF.ID" : "") . "
			" . $sFilter . $sOrder;

		if (CACHED_b_user_field === false)
		{
			$res = $DB->Query($strSql);
		}
		else
		{
			$arResult = [];
			$res = $DB->Query($strSql);
			while ($ar = $res->Fetch())
			{
				$arResult[] = $ar;
			}

			/** @noinspection PhpUndefinedVariableInspection */
			$CACHE_MANAGER->Set($cacheId, $arResult);

			$res = new CDBResult;
			$res->InitFromArray($arResult);
		}

		return new CUserTypeEntity($res);
	}

	/**
	 * Function to validate metadata values of user properties.
	 *
	 * <p>Called in Add and Update methods to check the correctness of the entered values.</p>
	 * <p>Validations:</p>
	 * <ul>
	 * <li>ENTITY_ID - required
	 * <li>ENTITY_ID - no more than 50 characters
	 * <li>ENTITY_ID - must not contain any characters other than 0-9, A-Z, and _
	 * <li>FIELD_NAME - required
	 * <li>FIELD_NAME - at least 4 characters
	 * <li>FIELD_NAME - no more than 50 characters
	 * <li>FIELD_NAME - must not contain any characters other than 0-9, A-Z, and _
	 * <li>FIELD_NAME - must start with UF_
	 * <li>USER_TYPE_ID - required
	 * <li>USER_TYPE_ID - must be registered
	 * </ul>
	 * <p>In case of an error, catch the application exception!</p>
	 * @param integer $ID - property identifier. 0 - for new.
	 * @param array $arFields Property metadata
	 * @param bool $bCheckUserType
	 * @return boolean false - if any validation fails.
	 */
	public function CheckFields($ID, $arFields, $bCheckUserType = true)
	{
		/** @global CUserTypeManager $USER_FIELD_MANAGER */
		global $APPLICATION, $USER_FIELD_MANAGER;
		$aMsg = [];
		$ID = intval($ID);

		if (($ID <= 0 || array_key_exists("ENTITY_ID", $arFields)) && $arFields["ENTITY_ID"] == '')
		{
			$aMsg[] = ["id" => "ENTITY_ID", "text" => GetMessage("USER_TYPE_ENTITY_ID_MISSING")];
		}
		if (array_key_exists("ENTITY_ID", $arFields))
		{
			if (mb_strlen($arFields["ENTITY_ID"]) > 50)
			{
				$aMsg[] = ["id" => "ENTITY_ID", "text" => GetMessage("USER_TYPE_ENTITY_ID_TOO_LONG1")];
			}
			if (!preg_match('/^[0-9A-Z_]+$/', $arFields["ENTITY_ID"]))
			{
				$aMsg[] = ["id" => "ENTITY_ID", "text" => GetMessage("USER_TYPE_ENTITY_ID_INVALID")];
			}
		}

		if (($ID <= 0 || array_key_exists("FIELD_NAME", $arFields)) && $arFields["FIELD_NAME"] == '')
		{
			$aMsg[] = ["id" => "FIELD_NAME", "text" => GetMessage("USER_TYPE_FIELD_NAME_MISSING")];
		}
		if (array_key_exists("FIELD_NAME", $arFields))
		{
			if (mb_strlen($arFields["FIELD_NAME"]) < 4)
			{
				$aMsg[] = ["id" => "FIELD_NAME", "text" => GetMessage("USER_TYPE_FIELD_NAME_TOO_SHORT")];
			}
			if (mb_strlen($arFields["FIELD_NAME"]) > 50)
			{
				$aMsg[] = ["id" => "FIELD_NAME", "text" => GetMessage("USER_TYPE_FIELD_NAME_TOO_LONG1")];
			}
			if (strncmp($arFields["FIELD_NAME"], "UF_", 3) !== 0)
			{
				$aMsg[] = ["id" => "FIELD_NAME", "text" => GetMessage("USER_TYPE_FIELD_NAME_NOT_UF")];
			}
			if (!preg_match('/^[0-9A-Z_]+$/', $arFields["FIELD_NAME"]))
			{
				$aMsg[] = ["id" => "FIELD_NAME", "text" => GetMessage("USER_TYPE_FIELD_NAME_INVALID")];
			}
		}

		if (($ID <= 0 || array_key_exists("USER_TYPE_ID", $arFields)) && $arFields["USER_TYPE_ID"] == '')
		{
			$aMsg[] = ["id" => "USER_TYPE_ID", "text" => GetMessage("USER_TYPE_USER_TYPE_ID_MISSING")];
		}
		if (
			$bCheckUserType
			&& array_key_exists("USER_TYPE_ID", $arFields)
			&& !$USER_FIELD_MANAGER->GetUserType($arFields["USER_TYPE_ID"])
		)
		{
			$aMsg[] = ["id" => "USER_TYPE_ID", "text" => GetMessage("USER_TYPE_USER_TYPE_ID_INVALID")];
		}

		if (!empty($aMsg))
		{
			$e = new CAdminException($aMsg);
			$APPLICATION->ThrowException($e);
			return false;
		}
		return true;
	}

	/**
	 * Function to add a user property.
	 *
	 * <p>First, the instance method CheckFields is called (i.e., $this->CheckFields($arFields) ).</p>
	 * <p>If the validation is successful, a check is performed to see if such a field already exists for the given entity.</p>
	 * <p>Then, if necessary, tables of the form <b>b_uts_[ENTITY_ID]</b> and <b>b_utm_[ENTITY_ID]</b> are created.</p>
	 * <p>After that, the metadata is saved in the database.</p>
	 * <p>Only after this, <b>the structure of the table b_uts_[ENTITY_ID] is modified</b>.</p>
	 * <p>Array arFields:</p>
	 * <ul>
	 * <li>ENTITY_ID - entity
	 * <li>FIELD_NAME - the actual column name in the database where the property values will be stored.
	 * <li>USER_TYPE_ID - property type
	 * <li>XML_ID - identifier for use in import/export
	 * <li>SORT - sort order (default 100)
	 * <li>MULTIPLE - multiplicity flag Y/N (default N)
	 * <li>MANDATORY - mandatory value input flag Y/N (default N)
	 * <li>SHOW_FILTER - whether to show in the admin list filter and what type to use. see below.
	 * <li>SHOW_IN_LIST - whether to show in the admin list (default Y)
	 * <li>EDIT_IN_LIST - allow editing in forms, but not in API! (default Y)
	 * <li>IS_SEARCHABLE - field participates in search (default N)
	 * <li>SETTINGS - array with property settings dependent on the property type. They are "cleaned" through the type handler PrepareSettings.
	 * <li>EDIT_FORM_LABEL - array of language messages in the form array("ru"=>"привет", "en"=>"hello")
	 * <li>LIST_COLUMN_LABEL
	 * <li>LIST_FILTER_LABEL
	 * <li>ERROR_MESSAGE
	 * <li>HELP_MESSAGE
	 * </ul>
	 * <p>In case of an error, catch the application exception!</p>
	 * <p>Values for SHOW_FILTER:</p>
	 * <ul>
	 * <li>N - do not show
	 * <li>I - exact match
	 * <li>E - mask
	 * <li>S - substring
	 * </ul>
	 * @param array $arFields Metadata of the new property
	 * @param bool $bCheckUserType
	 * @return integer - identifier of the added property, false - if the property was not added.
	 */
	public function Add($arFields, $bCheckUserType = true)
	{
		global $DB, $APPLICATION, $USER_FIELD_MANAGER;

		if (!$this->CheckFields(0, $arFields, $bCheckUserType))
		{
			return false;
		}

		$rs = CUserTypeEntity::GetList([], [
			"ENTITY_ID" => $arFields["ENTITY_ID"],
			"FIELD_NAME" => $arFields["FIELD_NAME"],
		]);

		if ($rs->Fetch())
		{
			$aMsg = [];
			$aMsg[] = [
				"id" => "FIELD_NAME",
				"text" => GetMessage("USER_TYPE_ADD_ALREADY_ERROR", [
					"#FIELD_NAME#" => htmlspecialcharsbx($arFields["FIELD_NAME"]),
					"#ENTITY_ID#" => htmlspecialcharsbx($arFields["ENTITY_ID"]),
				]),
			];
			$e = new CAdminException($aMsg);
			$APPLICATION->ThrowException($e);
			return false;
		}

		unset($arFields["ID"]);
		if (!isset($arFields["SORT"]) || intval($arFields["SORT"]) <= 0)
		{
			$arFields["SORT"] = 100;
		}
		if (!isset($arFields["MULTIPLE"]) || $arFields["MULTIPLE"] !== "Y")
		{
			$arFields["MULTIPLE"] = "N";
		}
		if (!isset($arFields["MANDATORY"]) || $arFields["MANDATORY"] !== "Y")
		{
			$arFields["MANDATORY"] = "N";
		}
		$arFields["SHOW_FILTER"] = mb_substr($arFields["SHOW_FILTER"] ?? '', 0, 1);
		if ($arFields["SHOW_FILTER"] == '' || mb_strpos("NIES", $arFields["SHOW_FILTER"]) === false)
		{
			$arFields["SHOW_FILTER"] = "N";
		}
		if (!isset($arFields["SHOW_IN_LIST"]) || $arFields["SHOW_IN_LIST"] !== "N")
		{
			$arFields["SHOW_IN_LIST"] = "Y";
		}
		if (!isset($arFields["EDIT_IN_LIST"]) || $arFields["EDIT_IN_LIST"] !== "N")
		{
			$arFields["EDIT_IN_LIST"] = "Y";
		}
		if (!isset($arFields["IS_SEARCHABLE"]) || $arFields["IS_SEARCHABLE"] !== "Y")
		{
			$arFields["IS_SEARCHABLE"] = "N";
		}

		if (!array_key_exists("SETTINGS", $arFields))
		{
			$arFields["SETTINGS"] = [];
		}
		$arFields["SETTINGS"] = serialize($USER_FIELD_MANAGER->PrepareSettings(0, $arFields, $bCheckUserType));

		/**
		 * events
		 * PROVIDE_STORAGE - use own uf subsystem to store data (uts/utm tables)
		 */
		$commonEventResult = ['PROVIDE_STORAGE' => true];

		foreach (GetModuleEvents("main", "OnBeforeUserTypeAdd", true) as $arEvent)
		{
			$eventResult = ExecuteModuleEventEx($arEvent, [&$arFields]);

			if ($eventResult === false)
			{
				if ($APPLICATION->GetException())
				{
					return false;
				}

				$aMsg = [];
				$aMsg[] = [
					"id" => "FIELD_NAME",
					"text" => GetMessage("USER_TYPE_ADD_ERROR", [
						"#FIELD_NAME#" => htmlspecialcharsbx($arFields["FIELD_NAME"]),
						"#ENTITY_ID#" => htmlspecialcharsbx($arFields["ENTITY_ID"]),
					]),
				];

				$e = new CAdminException($aMsg);
				$APPLICATION->ThrowException($e);

				return false;
			}
			elseif (is_array($eventResult))
			{
				$commonEventResult = array_merge($commonEventResult, $eventResult);
			}
		}

		if (is_object($USER_FIELD_MANAGER))
		{
			$USER_FIELD_MANAGER->CleanCache();
		}

		if ($commonEventResult['PROVIDE_STORAGE'])
		{
			if (!$this->CreatePropertyTables($arFields["ENTITY_ID"]))
			{
				return false;
			}

			$strType = $USER_FIELD_MANAGER->getUtsDBColumnType($arFields);

			if (!$strType)
			{
				$aMsg = [];
				$aMsg[] = [
					"id" => "FIELD_NAME",
					"text" => GetMessage("USER_TYPE_ADD_ERROR", [
						"#FIELD_NAME#" => htmlspecialcharsbx($arFields["FIELD_NAME"]),
						"#ENTITY_ID#" => htmlspecialcharsbx($arFields["ENTITY_ID"]),
					]),
				];
				$e = new CAdminException($aMsg);
				$APPLICATION->ThrowException($e);
				return false;
			}

			$tableName = 'b_uts_' . mb_strtolower($arFields['ENTITY_ID']);
			$tableFields = $DB->GetTableFields($tableName);
			if (!array_key_exists($arFields['FIELD_NAME'], $tableFields))
			{
				$ddl = 'ALTER TABLE ' . $tableName . ' ADD ' . $arFields['FIELD_NAME'] . ' ' . $strType;
				if (!$DB->DDL($ddl, true))
				{
					$aMsg = [];
					$aMsg[] = [
						"id" => "FIELD_NAME",
						"text" => GetMessage("USER_TYPE_ADD_ERROR", [
							"#FIELD_NAME#" => htmlspecialcharsbx($arFields["FIELD_NAME"]),
							"#ENTITY_ID#" => htmlspecialcharsbx($arFields["ENTITY_ID"]),
						]),
					];
					$e = new CAdminException($aMsg);
					$APPLICATION->ThrowException($e);
					return false;
				}
			}
		}

		if ($ID = $DB->Add("b_user_field", $arFields, ["SETTINGS"], '', true))
		{
			$arLabels = ["EDIT_FORM_LABEL", "LIST_COLUMN_LABEL", "LIST_FILTER_LABEL", "ERROR_MESSAGE", "HELP_MESSAGE"];
			$arLangs = [];
			foreach ($arLabels as $label)
			{
				if (isset($arFields[$label]) && is_array($arFields[$label]))
				{
					foreach ($arFields[$label] as $lang => $value)
					{
						$arLangs[$lang][$label] = $value;
					}
				}
			}

			foreach ($arLangs as $lang => $arLangFields)
			{
				$arLangFields["USER_FIELD_ID"] = $ID;
				$arLangFields["LANGUAGE_ID"] = $lang;
				$arInsert = $DB->PrepareInsert("b_user_field_lang", $arLangFields);
				$DB->Query("INSERT INTO b_user_field_lang (" . $arInsert[0] . ") VALUES (" . $arInsert[1] . ")");
			}

			static::cleanCache();
		}
		else
		{
			return false;
		}

		// post event
		$arFields['ID'] = $ID;

		foreach (GetModuleEvents("main", "OnAfterUserTypeAdd", true) as $arEvent)
		{
			ExecuteModuleEventEx($arEvent, [$arFields]);
		}

		return $ID;
	}

	/**
	 * Function to modify metadata of a user property.
	 *
	 * <p>It should be noted that for the sake of faster development, it was decided not to implement
	 * the same flexibility as in infoblocks (we will do without alters and other things for now).</p>
	 * <p>First, the instance method CheckFields is called (i.e., $this->CheckFields($arFields) ).</p>
	 * <p>After that, the metadata is saved in the database.</p>
	 * <p>Array arFields (only what can be changed):</p>
	 * <ul>
	 * <li>SORT - sort order
	 * <li>MANDATORY - mandatory value input flag Y/N
	 * <li>SHOW_FILTER - flag to show in the list filter Y/N
	 * <li>SHOW_IN_LIST - flag to show in the list Y/N
	 * <li>EDIT_IN_LIST - allow editing the field in admin forms or not Y/N
	 * <li>IS_SEARCHABLE - search flag Y/N
	 * <li>SETTINGS - array with property settings dependent on the property type. They are "cleaned" through the type handler PrepareSettings.
	 * <li>EDIT_FORM_LABEL - array of language messages in the form array("ru"=>"привет", "en"=>"hello")
	 * <li>LIST_COLUMN_LABEL
	 * <li>LIST_FILTER_LABEL
	 * <li>ERROR_MESSAGE
	 * <li>HELP_MESSAGE
	 * </ul>
	 * <p>In case of an error, catch the application exception!</p>
	 * @param integer $ID Property identifier
	 * @param array $arFields New property metadata
	 * @return boolean - true if the update is successful, false otherwise.
	 */
	public function Update($ID, $arFields)
	{
		global $DB, $USER_FIELD_MANAGER, $APPLICATION;
		$ID = intval($ID);

		unset($arFields["ENTITY_ID"]);
		unset($arFields["FIELD_NAME"]);
		unset($arFields["USER_TYPE_ID"]);
		unset($arFields["MULTIPLE"]);

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

		if (array_key_exists("SETTINGS", $arFields))
		{
			$arFields["SETTINGS"] = serialize($USER_FIELD_MANAGER->PrepareSettings($ID, $arFields));
		}
		if (array_key_exists("MANDATORY", $arFields) && $arFields["MANDATORY"] !== "Y")
		{
			$arFields["MANDATORY"] = "N";
		}
		if (array_key_exists("SHOW_FILTER", $arFields))
		{
			$arFields["SHOW_FILTER"] = mb_substr($arFields["SHOW_FILTER"], 0, 1);
			if (mb_strpos("NIES", $arFields["SHOW_FILTER"]) === false)
			{
				$arFields["SHOW_FILTER"] = "N";
			}
		}
		if (array_key_exists("SHOW_IN_LIST", $arFields) && $arFields["SHOW_IN_LIST"] !== "N")
		{
			$arFields["SHOW_IN_LIST"] = "Y";
		}
		if (array_key_exists("EDIT_IN_LIST", $arFields) && $arFields["EDIT_IN_LIST"] !== "N")
		{
			$arFields["EDIT_IN_LIST"] = "Y";
		}
		if (array_key_exists("IS_SEARCHABLE", $arFields) && $arFields["IS_SEARCHABLE"] !== "Y")
		{
			$arFields["IS_SEARCHABLE"] = "N";
		}

		// events
		foreach (GetModuleEvents("main", "OnBeforeUserTypeUpdate", true) as $arEvent)
		{
			if (ExecuteModuleEventEx($arEvent, [&$arFields]) === false)
			{
				if ($APPLICATION->GetException())
				{
					return false;
				}

				$aMsg = [];
				$aMsg[] = [
					"id" => "FIELD_NAME",
					"text" => GetMessage("USER_TYPE_UPDATE_ERROR", [
						"#FIELD_NAME#" => htmlspecialcharsbx($arFields["FIELD_NAME"]),
						"#ENTITY_ID#" => htmlspecialcharsbx($arFields["ENTITY_ID"]),
					]),
				];

				$e = new CAdminException($aMsg);
				$APPLICATION->ThrowException($e);

				return false;
			}
		}

		if (is_object($USER_FIELD_MANAGER))
		{
			$USER_FIELD_MANAGER->CleanCache();
		}

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

		static $arLabels = ["EDIT_FORM_LABEL", "LIST_COLUMN_LABEL", "LIST_FILTER_LABEL", "ERROR_MESSAGE", "HELP_MESSAGE"];
		$arLangs = [];
		foreach ($arLabels as $label)
		{
			if (isset($arFields[$label]) && is_array($arFields[$label]))
			{
				foreach ($arFields[$label] as $lang => $value)
				{
					$arLangs[$lang][$label] = $value;
				}
			}
		}

		if ($strUpdate <> "" || !empty($arLangs))
		{
			if ($strUpdate <> "")
			{
				$strSql = "UPDATE b_user_field SET " . $strUpdate . " WHERE ID = " . $ID;
				if (array_key_exists("SETTINGS", $arFields))
				{
					$arBinds = ["SETTINGS" => $arFields["SETTINGS"]];
				}
				else
				{
					$arBinds = [];
				}
				$DB->QueryBind($strSql, $arBinds);
			}

			if (!empty($arLangs))
			{
				$DB->StartTransaction();

				$DB->Query("DELETE FROM b_user_field_lang WHERE USER_FIELD_ID = " . $ID);

				foreach ($arLangs as $lang => $arLangFields)
				{
					$arLangFields["USER_FIELD_ID"] = $ID;
					$arLangFields["LANGUAGE_ID"] = $lang;
					$arInsert = $DB->PrepareInsert("b_user_field_lang", $arLangFields);
					$DB->Query("INSERT INTO b_user_field_lang (" . $arInsert[0] . ") VALUES (" . $arInsert[1] . ")");
				}

				$DB->Commit();
			}

			static::cleanCache();

			foreach (GetModuleEvents("main", "OnAfterUserTypeUpdate", true) as $arEvent)
			{
				ExecuteModuleEventEx($arEvent, [$arFields, $ID]);
			}
		}

		return true;
	}

	/**
	 * Function to delete a user property and all its values.
	 *
	 * <p>First, the property metadata is deleted.</p>
	 * <p>Then, all values of multiple properties are deleted from the table of the form <b>b_utm_[ENTITY_ID]</b>.</p>
	 * <p>After that, the column is dropped from the table of the form <b>b_uts_[ENTITY_ID]</b>.</p>
	 * <p>And if this was the "last" property for the entity, the tables storing the values are dropped themselves.</p>
	 * @param integer $ID Property identifier
	 * @return CDBResult | false - result of the last query executed by the function.
	 */
	public function Delete($ID)
	{
		global $DB, $USER_FIELD_MANAGER, $APPLICATION;
		$ID = intval($ID);

		$rs = $this->GetList([], ["ID" => $ID]);
		if ($arField = $rs->Fetch())
		{
			/**
			 * events
			 * PROVIDE_STORAGE - use own uf subsystem to store data (uts/utm tables)
			 */
			$commonEventResult = ['PROVIDE_STORAGE' => true];

			foreach (GetModuleEvents("main", "OnBeforeUserTypeDelete", true) as $arEvent)
			{
				$eventResult = ExecuteModuleEventEx($arEvent, [&$arField]);

				if ($eventResult === false)
				{
					if ($APPLICATION->GetException())
					{
						return false;
					}

					$aMsg = [];
					$aMsg[] = [
						"id" => "FIELD_NAME",
						"text" => GetMessage("USER_TYPE_DELETE_ERROR", [
							"#FIELD_NAME#" => htmlspecialcharsbx($arField["FIELD_NAME"]),
							"#ENTITY_ID#" => htmlspecialcharsbx($arField["ENTITY_ID"]),
						]),
					];

					$e = new CAdminException($aMsg);
					$APPLICATION->ThrowException($e);

					return false;
				}
				elseif (is_array($eventResult))
				{
					$commonEventResult = array_merge($commonEventResult, $eventResult);
				}
			}

			$entityId = strtolower($arField["ENTITY_ID"]);

			$arType = $USER_FIELD_MANAGER->GetUserType($arField["USER_TYPE_ID"]);
			//We need special handling of file type properties
			if ($arType)
			{
				if ($arType["BASE_TYPE"] == "file" && $commonEventResult['PROVIDE_STORAGE'])
				{
					// only if we store values
					if ($arField["MULTIPLE"] == "Y")
					{
						$strSql = "SELECT VALUE_INT AS VALUE FROM b_utm_" . $entityId . " WHERE FIELD_ID=" . $arField["ID"];
					}
					else
					{
						$strSql = "SELECT " . $arField["FIELD_NAME"] . " AS VALUE FROM b_uts_" . $entityId;
					}
					$rsFile = $DB->Query($strSql);
					while ($arFile = $rsFile->Fetch())
					{
						CFile::Delete($arFile["VALUE"]);
					}
				}
				elseif ($arType["BASE_TYPE"] == "enum")
				{
					$obEnum = new CUserFieldEnum;
					$obEnum->DeleteFieldEnum($arField["ID"]);
				}
			}

			$rs = $DB->Query("DELETE FROM b_user_field_lang WHERE USER_FIELD_ID = " . $ID);
			if ($rs)
			{
				$rs = $DB->Query("DELETE FROM b_user_field WHERE ID = " . $ID);
			}

			if ($rs && $commonEventResult['PROVIDE_STORAGE'])
			{
				// only if we store values
				$rs = $this->GetList([], ["ENTITY_ID" => $arField["ENTITY_ID"]]);
				if ($rs->Fetch()) // more than one
				{
					$DB->Query("ALTER TABLE b_uts_" . $entityId . " DROP " . $arField["FIELD_NAME"], true);
					$rs = $DB->Query("DELETE FROM b_utm_" . $entityId . " WHERE FIELD_ID = '" . $ID . "'");
				}
				else
				{
					$DB->Query("DROP TABLE IF EXISTS b_uts_" . $entityId);
					$rs = $DB->Query("DROP TABLE IF EXISTS b_utm_" . $entityId);
				}
			}

			static::cleanCache();

			foreach (GetModuleEvents("main", "OnAfterUserTypeDelete", true) as $arEvent)
			{
				ExecuteModuleEventEx($arEvent, [$arField, $ID]);
			}
		}
		return $rs;
	}

	/**
	 * Function to delete ALL user properties of an entity.
	 *
	 * <p>First, the property metadata is deleted.</p>
	 * <p>Can be called, for example, when deleting an infoblock.</p>
	 * <p>Then, the tables of the form <b>b_utm_[ENTITY_ID]</b> and <b>b_uts_[ENTITY_ID]</b> are dropped.</p>
	 * @param string $entity_id Entity identifier
	 * @return CDBResult - result of the last query executed by the function.
	 */
	public function DropEntity($entity_id)
	{
		global $DB, $USER_FIELD_MANAGER;

		$entity_id = preg_replace("/[^0-9A-Z_]+/", "", $entity_id);

		$rs = true;
		$rsFields = $this->GetList([], ["ENTITY_ID" => $entity_id]);
		//We need special handling of file and enum type properties
		while ($arField = $rsFields->Fetch())
		{
			$arType = $USER_FIELD_MANAGER->GetUserType($arField["USER_TYPE_ID"]);
			if ($arType && ($arType["BASE_TYPE"] == "file" || $arType["BASE_TYPE"] == "enum"))
			{
				$this->Delete($arField["ID"]);
			}
		}

		$bDropTable = false;
		$rsFields = $this->GetList([], ["ENTITY_ID" => $entity_id]);
		while ($arField = $rsFields->Fetch())
		{
			$bDropTable = true;
			$DB->Query("DELETE FROM b_user_field_lang WHERE USER_FIELD_ID = " . $arField["ID"]);
			$rs = $DB->Query("DELETE FROM b_user_field WHERE ID = " . $arField["ID"]);

			foreach (GetModuleEvents("main", "OnAfterUserTypeDelete", true) as $arEvent)
			{
				ExecuteModuleEventEx($arEvent, [$arField, $arField["ID"]]);
			}
		}

		if ($bDropTable)
		{
			$DB->Query("DROP TABLE IF EXISTS b_uts_" . strtolower($entity_id), true);
			$rs = $DB->Query("DROP TABLE IF EXISTS b_utm_" . strtolower($entity_id), true);
		}

		static::cleanCache();

		return $rs;
	}

	protected static function cleanCache(): void
	{
		global $CACHE_MANAGER, $USER_FIELD_MANAGER;

		if (CACHED_b_user_field !== false)
		{
			$CACHE_MANAGER->CleanDir("b_user_field");
		}
		UserFieldTable::cleanCache();
		UserFieldLangTable::cleanCache();
		$USER_FIELD_MANAGER->CleanCache();
	}

	/**
	 * Fetch function.
	 *
	 * <p>Deserializes the SETTINGS field.</p>
	 * @return array Returns false in case of the last record in the selection.
	 */
	function Fetch()
	{
		$res = parent::Fetch();
		if ($res && $res["SETTINGS"] <> '')
		{
			$res["SETTINGS"] = unserialize($res["SETTINGS"], ['allowed_classes' => false]);
		}
		return $res;
	}
}

class CUserTypeEntity extends CAllUserTypeEntity
{
}

Youez - 2016 - github.com/yon3zu
LinuXploit