Свой тип свойств
Готовый код можно скачать в моем репозитории на GitFlic.
Свойство можно применять для того, чтобы связать запись HL блока с пользователем создавшим её или внёсшим в неё изменения, ставить ответственного за обработку записи, например если хранить в HL блоке какие-то не стандартные заявки от посетителей и многое другое.
Бывают ситуации, когда базовых типов недостаточно для реализации определенной бизнес-логики, тогда на помощь приходят пользовательские типы свойств. Сложность в том, что простого инструмента для такой настройки нет, и реализуется это через код. Полный боевой пример можно скачать ниже после статьи.
Все классы пользовательских полей хранятся в главном модуле в папке /bitrix/modules/main/classes/general/
файлы классов имеют префикс usertype
, по названию фалов можно догадаться к какому типу они относятся:
- usertypebool.php
- usertypedate.php
- usertypedbl.php
- usertypeelement.php
- usertypeenum.php
- usertypefile.php
- usertypeint.php
- usertypesection.php
- usertypestr.php
- usertypestrfmt.php
- usertypetime.php
- usertypeurl.php
Хранить значения будем USER_ID
, следовательно нам потребуется скопировать и кастомизировать классы usertypeenum
(список).
Подготовка
Структура проекта:
-
Папка local
-
Папка php_interface
-
Папка lib
- Папка usertype
- Файл CUserTypeUserId.php
-
Папка lib
- Файл autoload.php
- Файл constants.php
- Файл event_handler.php
- Файл init.php
-
Папка php_interface
Реализация
Файл init.php
Подключаем файлы:
local/php_interface/init.php<?
//Константы
require_once dirname(__FILE__) . '/constants.php';
//Автозагрузка классов
require_once dirname(__FILE__) . '/autoload.php';
//Обработка событий
require_once dirname(__FILE__) . '/event_handler.php';
Файл constants.php
Храним константу с путем к ключевой папке:
local/php_interface/constants.php<?
// константа APP_CLASS_FOLDER, путь к папке с пользовательскими классами
define('APP_CLASS_FOLDER', '/local/php_interface/lib/');
Файл autoload.php
Подгружаем один единственный класс, у меня он будет реализован в файле CUserTypeTimesheet.php
:
local/php_interface/autoload.php<?
// автозагрузка класса
CModule::AddAutoloadClasses(
'', // не указываем имя модуля
array(
// ключ - имя класса с простанством имен, значение - путь относительно корня сайта к файлу
'lib\UserType\CUserTypeUserId' => APP_CLASS_FOLDER . 'UserType/CUserTypeUserId.php',
)
);
Файл event_handler.php
Вешаем обработчик на событие построения списка доступных свойств в инфоблоке OnUserTypeBuildList
:
local/php_interface/event_handler.php<?
// вешаем обработчик на событие создания списка пользовательских свойств OnUserTypeBuildList
addEventHandler('main', 'OnUserTypeBuildList', ['lib\UserType\CUserTypeUserId', 'GetUserTypeDescription']);
Файл CUserTypeUserId.php
GetUserTypeDescription()
метод возвращает массив описания собственного типа свойствGetDBColumnType()
обязательный метод для определения типа поля таблицы в БД при создании свойстваGetList()
получаем список значенийGetEditFormHTML()
получить HTML формы для редактирования свойстваGetEditFormHTMLMulty()
получить HTML формы для редактирования МНОЖЕСТВЕННОГО свойстваGetAdminListViewHTML()
получаем HTML для списка элементов в админкеgetEmptyCaption()
получаем текст для пустого значения свойстваGetAdminListEditHTML()
получить HTML для редактирования свойства в списке админ-панелиGetAdminListEditHTMLMulty()
получить HTML для редактирования МНОЖЕСТВЕННОГО свойства в списке админ-панелиGetFilterHTML()
получаем HTML блок для фильтрации списка элементов по этому свойству
local/php_interface/lib/usertype/CUserTypeUserId.php<?
namespace lib\usertype;
class CUserTypeUserId
{
/**
* Метод возвращает массив описания собственного типа свойств
* @return array
*/
public function GetUserTypeDescription()
{
return array(
"USER_TYPE_ID" => 'userid', //Уникальный идентификатор типа свойств
"CLASS_NAME" => __CLASS__,
"DESCRIPTION" => 'Привязка к пользователю',
"BASE_TYPE" => \CUserTypeManager::BASE_TYPE_INT,
);
}
/**
* Обязательный метод для определения типа поля таблицы в БД при создании свойства
* @param $arUserField
* @return string
*/
function GetDBColumnType($arUserField)
{
global $DB;
switch (strtolower($DB->type)) {
case "mysql":
return "int(18)";
case "oracle":
return "number(18)";
case "mssql":
return "int";
}
return "int";
}
/**
* Получаем список значений
* @param $arUserField
* @return array|bool|\CDBResult
*/
public function GetList($arUserField)
{
$rsEnum = [];
//GROUPS_ID - Администраторы, контент редакторы
$dbResultList = \CUser::GetList(($by = 'id'), ($order = 'asc'), ['GROUPS_ID' => [1, 5]]);
while ($arResult = $dbResultList->Fetch()) {
$rsEnum[] = [
'ID' => $arResult['ID'],
//Формат отображения значений
'VALUE' => $arResult['NAME'] . ' ' . $arResult['LAST_NAME'] . ' (' . $arResult['EMAIL'] . ')'
];
}
return $rsEnum;
}
/**
* Получить HTML формы для редактирования свойства
* @param $arUserField
* @param $arHtmlControl
* @return string
*/
public function GetEditFormHTML($arUserField, $arHtmlControl)
{
if (($arUserField['ENTITY_VALUE_ID'] < 1) && strlen($arUserField['SETTINGS']['DEFAULT_VALUE']) > 0)
$arHtmlControl['VALUE'] = intval($arUserField['SETTINGS']['DEFAULT_VALUE']);
$result = '';
$rsEnum = call_user_func_array(
array($arUserField['USER_TYPE']['CLASS_NAME'], 'GetList'),
array(
$arUserField,
)
);
if (!$rsEnum)
return '';
$bWasSelect = false;
$result2 = '';
foreach ($rsEnum as $arEnum) {
$bSelected = (
($arHtmlControl['VALUE'] == $arEnum['ID']) ||
($arUserField['ENTITY_VALUE_ID'] <= 0 && $arEnum['DEF'] == 'Y') //Можно сделать логику для дефолтного значения
);
$bWasSelect = $bWasSelect || $bSelected;
$result2 .= '<option value="' . $arEnum['ID'] . '"' . ($bSelected ? ' selected' : '') . '>' . $arEnum['VALUE'] . '</option>';
}
if ($arUserField["SETTINGS"]["LIST_HEIGHT"] > 1) {
$size = ' size="' . $arUserField['SETTINGS']['LIST_HEIGHT'] . '"';
} else {
$arHtmlControl['VALIGN'] = 'middle';
$size = '';
}
$result = '<select name="' . $arHtmlControl['NAME'] . '"' . $size . ($arUserField['EDIT_IN_LIST'] != "Y" ? ' disabled="disabled" ' : '') . '>';
if ($arUserField["MANDATORY"] != "Y") {
$result .= '<option value=""' . (!$bWasSelect ? ' selected' : '') . '>' . htmlspecialcharsbx(self::getEmptyCaption($arUserField)) . '</option>';
}
$result .= $result2;
$result .= '</select>';
return $result;
}
/**
* Получить HTML формы для редактирования МНОЖЕСТВЕННОГО свойства
* @param $arUserField
* @param $arHtmlControl
* @return string
*/
public function GetEditFormHTMLMulty($arUserField, $arHtmlControl)
{
$rsEnum = call_user_func_array([$arUserField['USER_TYPE']['CLASS_NAME'], 'GetList'], [$arUserField,]);
if (!$rsEnum) {
return '';
}
$result = '<select multiple name="' . $arHtmlControl["NAME"] . '" size="' . $arUserField["SETTINGS"]["LIST_HEIGHT"] . '"' . ($arUserField["EDIT_IN_LIST"] != "Y" ? ' disabled="disabled" ' : '') . '>';
if ($arUserField["MANDATORY"] <> "Y") {
$result .= '<option value=""' . (!$arHtmlControl["VALUE"] ? ' selected' : '') . '>' . htmlspecialcharsbx(self::getEmptyCaption($arUserField)) . '</option>';
}
foreach ($rsEnum as $arEnum) {
$bSelected = (
(in_array($arEnum['ID'], $arHtmlControl["VALUE"])) ||
($arUserField['ENTITY_VALUE_ID'] <= 0 && $arEnum['DEF'] == 'Y') //Можно сделать логику для дефолтного значения
);
$result .= '<option value="' . $arEnum["ID"] . '"' . ($bSelected ? ' selected' : '') . '>' . $arEnum["VALUE"] . '</option>';
}
$result .= '</select>';
return $result;
}
/**
* Получаем HTML для списка элементов в админке
* @param $arUserField
* @param $arHtmlControl
* @return mixed|string
*/
function GetAdminListViewHTML($arUserField, $arHtmlControl)
{
static $cache = array();
$empty_caption = ' ';
$rsEnum = '';
if (!array_key_exists($arHtmlControl['VALUE'], $cache)) {
$rsEnum = call_user_func_array([$arUserField['USER_TYPE']['CLASS_NAME'], 'GetList'], [$arUserField]);
if (!$rsEnum)
return $empty_caption;
foreach ($rsEnum as $arEnum) {
$cache[$arEnum["ID"]] = $arEnum["VALUE"];
}
}
if (!array_key_exists($arHtmlControl["VALUE"], $cache))
$cache[$arHtmlControl["VALUE"]] = $empty_caption;
return $cache[$arHtmlControl["VALUE"]];
}
/**
* Получаем текст для пустого значения свойства
* @param $arUserField
* @return mixed|string|string[]
*/
protected static function getEmptyCaption($arUserField)
{
return $arUserField["SETTINGS"]["CAPTION_NO_VALUE"] <> ''
? $arUserField["SETTINGS"]["CAPTION_NO_VALUE"]
: 'Пользователь не выбран';
}
/**
* Получить HTML для редактирования свойства в списке админ-панели
* @param $arUserField
* @param $arHtmlControl
* @return string
*/
function GetAdminListEditHTML($arUserField, $arHtmlControl)
{
$rsEnum = call_user_func_array(
array($arUserField["USER_TYPE"]["CLASS_NAME"], "getList"),
array(
$arUserField,
)
);
if (!$rsEnum)
return '';
if ($arUserField["SETTINGS"]["LIST_HEIGHT"] > 1)
$size = ' size="' . $arUserField["SETTINGS"]["LIST_HEIGHT"] . '"';
else
$size = '';
$result = '<select name="' . $arHtmlControl["NAME"] . '"' . $size . ($arUserField["EDIT_IN_LIST"] != "Y" ? ' disabled="disabled" ' : '') . '>';
if ($arUserField["MANDATORY"] != "Y") {
$result .= '<option value=""' . (!$arHtmlControl["VALUE"] ? ' selected' : '') . '>' . htmlspecialcharsbx(self::getEmptyCaption($arUserField)) . '</option>';
}
foreach ($rsEnum as $key => $arEnum) {
$result .= '<option value="' . $arEnum["ID"] . '"' . ($arHtmlControl["VALUE"] == $arEnum["ID"] ? ' selected' : '') . '>' . $arEnum["VALUE"] . '</option>';
}
$result .= '</select>';
return $result;
}
/**
* Получить HTML для редактирования множественного свойства в списке админ-панели
* @param $arUserField
* @param $arHtmlControl
* @return string
*/
function GetAdminListEditHTMLMulty($arUserField, $arHtmlControl)
{
if (!is_array($arHtmlControl["VALUE"]))
$arHtmlControl["VALUE"] = array();
$rsEnum = call_user_func_array(
array($arUserField["USER_TYPE"]["CLASS_NAME"], "getlist"),
array(
$arUserField,
)
);
if (!$rsEnum)
return '';
$result = '<select multiple name="' . $arHtmlControl["NAME"] . '" size="' . $arUserField["SETTINGS"]["LIST_HEIGHT"] . '"' . ($arUserField["EDIT_IN_LIST"] != "Y" ? ' disabled="disabled" ' : '') . '>';
if ($arUserField["MANDATORY"] != "Y") {
$result .= '<option value=""' . (!$arHtmlControl["VALUE"] ? ' selected' : '') . '>' . htmlspecialcharsbx(self::getEmptyCaption($arUserField)) . '</option>';
}
foreach ($rsEnum as $arEnum) {
$result .= '<option value="' . $arEnum["ID"] . '"' . (in_array($arEnum["ID"], $arHtmlControl["VALUE"]) ? ' selected' : '') . '>' . $arEnum["VALUE"] . '</option>';
}
$result .= '</select>';
return $result;
}
/**
* Получаем HTML блок для фильтрации списка эдементов по этому свойству
* @param $arUserField
* @param $arHtmlControl
* @return string
*/
function GetFilterHTML($arUserField, $arHtmlControl)
{
if (!is_array($arHtmlControl["VALUE"]))
$arHtmlControl["VALUE"] = array();
$rsEnum = call_user_func_array(
array($arUserField["USER_TYPE"]["CLASS_NAME"], "getList"),
array(
$arUserField,
)
);
if (!$rsEnum)
return '';
if ($arUserField["SETTINGS"]["LIST_HEIGHT"] < 5)
$size = ' size="5"';
else
$size = ' size="' . $arUserField["SETTINGS"]["LIST_HEIGHT"] . '"';
$result = '<select multiple name="' . $arHtmlControl["NAME"] . '[]"' . $size . '>';
$result .= '<option value=""' . (!$arHtmlControl["VALUE"] ? ' selected' : '') . '>' . GetMessage("MAIN_ALL") . '</option>';
foreach ($rsEnum as $key => $arEnum) {
$result .= '<option value="' . $arEnum["ID"] . '"' . (in_array($arEnum["ID"], $arHtmlControl["VALUE"]) ? ' selected' : '') . '>' . $arEnum["VALUE"] . '</option>';
}
$result .= '</select>';
return $result;
}
}
Параметры для массива GetUserTypeDescription
USER_TYPE_ID
уникальный идентификатор типа свойстваUSER_TYPE
cимвольный код типа свойстваCLASS_NAME
название класса, который реализует логику типа свойства (чаще всего данный метод и логика свойства располагаются в одном классе, но можно разделить их на разные)DESCRIPTION
название типа свойства, которое будет отображаться в административной панелиPROPERTY_TYPE
тип базового свойства, от которого наследуется стандартная логика работа со свойствомCheckFields
метод должен проверить корректность значения свойства и вернуть массив. Пустой, если ошибок нет, и с сообщениями об ошибках, если естьGetUIFilterProperty
метод описывает вид поля фильтрации в компонентеmain.ui.filter
на административных страницах инфоблоковGetLength
метод должен вернуть фактическую длину значения свойства. Он нужен только для свойств, значения которых представляют собой сложные структуры (например, массив)ConvertToDB
метод должен преобразовать значение свойства в формат, пригодный для сохранения в базе данных. И вернуть массив видаarray("VALUE" => "...", "DESCRIPTION" => "...")
. Если значение свойства массив, то разумным будет использование функцииserialize
. А вот Дата/время преобразуется в ODBC формат"YYYY-MM-DD HH:MI:SS"
. Это определит возможности сортировки и фильтрации по значениям данного свойстваConvertFromDB
Метод должен преобразовать значение свойства из формата пригодного для сохранения в базе данных в формат обработки. И вернуть массив видаarray("VALUE" => "...", "DESCRIPTION" => "...")
. ДополняетConvertToDB
GetPropertyFieldHtmlMulty
Вывод формы редактирования множественного свойства. Если отсутствует, то используетсяGetPropertyFieldHtml
для каждого значения отдельно (у множественных свойств)GetAdminListViewHTML
метод возвращает безопасный HTML отображения значения свойства в списке элементов административной частиGetPublicViewHTML
метод должен вернуть безопасный HTML отображения значения свойства в публичной части сайта. Если она вернет пустое значение, то значение отображаться не будетGetPublicEditHTML
метод должен вернуть HTML отображения элемента управления для редактирования значений свойства в публичной части сайтаGetSettingsHTML
метод возвращает безопасный HTML отображения настроек свойства для формы редактирования инфоблокаPrepareSettings
метод возвращает либо массив с дополнительными настройками свойства, либо весь набор настроек, включая стандартные
Результат
Подключив полноценный класс мы получаем вот такое свойство:
За вывод этого списка отвечает метод GetEditFormHTML()
. В настройках свойства так же можно включить множественный вариант его работы, который выглядит так:
В данном случае работает метод GetEditFormHTMLMulty()
. Если у класса корректно определены методы:
GetAdminListViewHTML()
GetAdminListEditHTML()
GetAdminListEditHTMLMulty()
Значение свойств будут так же корректно отображаться и редактироваться в списке элементов:
На скриншоте виден так же блок фильтрации, он определяется методом GetFilterHTML()
. Чтобы вывести собственное свойство в фильтр его можно добавить в настройках формы фильтра так: