Привет, это Дима, бэкенд-разработчик из Hawking Bros. Сегодня я расскажу о решении задачи, с которой мы столкнулись при реализации нестандартной логики инфоблоков на проекте с битриксовой многосайтовостью.

Эта статья будет полезна разработчикам, которые занимаются настройкой Битрикс и хотят узнать больше о его возможностях. Если вы хотите пропустить вводную часть про базовую настройку, смело листайте вниз к разделу Личный опыт.
Создание пользовательских типов свойств
Информационный блок (инфоблок) — это некая сущность для хранения информации какого-либо блока на сайте. Он используется для хранения стандартной информации блоков сайта: название, id, текст для превью, картинка для превью и т.д. Инфоблоки содержат стандартные поля для записи, которые охватывают большую часть случаев применения. Для остальных случаев вам пригодится эта статья :)
Новое свойство можно создать в несколько простых шагов:
Придумать название и символьный код для свойства.
Выбрать необходимый тип свойства — они бывают базовыми и пользовательскими. Пользовательские помогают в решении специфических задач: сделать привязку к Яндекс Карте, или блоков на сайте к типам пользователей.
Выбрать, множественное это свойство или нет. Например, добавление нескольких изображений для карточки товара реализуется через этот параметр.
Задать дополнительные параметры в расширенном редакторе при необходимости.

Бывают ситуации, когда базовых типов недостаточно для реализации определенной бизнес-логики: расписание занятий у тренера, время приема у врачей. Тогда на помощь приходят пользовательские типы свойств. Сложность в том, что простого инструмента для такой настройки нет, и реализуется это через код.
Для создания нового тип свойства необходимо в файле init.php добавить слушатель события OnIBlockPropertyBuildList из модуля iblock и написать обработчик для данного события в виде класса с функцией GetUserTypeDescription, которая возвращает массив с информацией о типе свойств. Правилом хорошего тона считается вынесение логики слушателей в отдельный файл events.php, который потом подключается в файле init.php, чтобы не загромождать один файл огромным количеством логики.
Необходимыми элементами являются:
USER_TYPE_ID– уникальный идентификатор типа свойства.USER_TYPE– cимвольный код типа свойства.CLASS_NAME– название класса, который реализует логику типа свойства (чаще всего данный метод и логика свойства располагаются в одном классе, но можно разделить их на разные).DESCRIPTION– название типа свойства, которое будет отображаться в административной панели.PROPERTY_TYPE– тип базового свойства, от которого наследуется стандартная логика работа со свойством.
Пример такой функции:
public function GetUserTypeDescription(): array
{
return [
'USER_TYPE_ID' => 'schedule', //Уникальный идентификатор типа свойств
'USER_TYPE' => 'SCHEDULE', // Символьный код типа свойств
'CLASS_NAME' => __CLASS__, // Название класса, который реализует логику типа свойств
'DESCRIPTION' => 'Расписание', // Название свойства, которое будет отображаться в административной панели
'PROPERTY_TYPE' => Bitrix\Iblock\PropertyTable::TYPE_STRING, // Тип базового свойства, от которого будет браться стандартная логика работы с данным свойством
];
}
Созданный тип свойства появится в административной панели, но будет отображаться пустым — так как нет реализованного метода отображения свойства с пользовательским типом.
Переходим к созданию метода:
Реализуем метод
GetPropertyFieldHtmlв классе, название которого указанно в полеCLASS_NAME.Он должен находиться в массиве, который возвращается методомGetUserTypeDescription.Добавим в массив поле вида:
'GetPropertyFieldHtml' => [CLASS, 'GetPropertyFieldHtml'].В созданном методе сформируем отображение свойств с описываемым типом свойства и вернем его результатом работы функции (в виде строки).
Дополнительные параметры для массива GetUserTypeDescription
Рассмотрим другие параметры для массива GetUserTypeDescription, которые могут пригодиться в работе:
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– Метод возвращает либо массив с дополнительными настройками свойства, либо весь набор настроек, включая стандартные.
Личный опыт настройки пользовательских типов свойств
На проекте для интернет-магазина строительных материалов задумана реализация многогородности с помощью битриксовой многосайтовости: один город – один физический сайт.
Перед нашей командой стояла задача — реализовать разделение контента между сайтами, чтобы создавать уникальный контент для разных городов, а также использовать на нескольких сайтах уже созданный.
Мы решили сделать это с помощью свойства в инфоблоках, которое позволило бы выбирать несколько сайтов для одной записи. При этом у нас было несколько ограничений: отсутствие свойства для вывода списка сайтов в инфоблоке, и запрет на добавление сайтов вручную через свойство "Список".
Так мы пришли к потребности создать свой пользовательский тип свойства, которое через API Bitrix будет подтягивать все сайты и выводить их в виде обычного списка.
Первая проблема возникла при сохранении выбранных результатов. В своём кастомном типе свойства я указал:
'PROPERTY_TYPE' => Iblock</span>PropertyTable::TYPE_LIST, так как свойство имеет вид стандартного списка, просто с автоматическими заполняемыми значениями. Это не сработало, и в базу данных выбранные значения свойства не приходили.
Поиск по открытым источникам и работа с ядром привели меня к тому, что для свойства типа список недостаточно просто вывести весь перечень значений. Также следовало записать их в базу данных в отдельную таблицу для всех Enum всех свойств типа список, что при обычном ручном заполнении происходит под капотом Битрикса. Оптимального способа сделать этого я не нашёл, поэтому прибегнул к альтернативе.
Я заменил PROPERTYTYPE на Iblock</span>PropertyTable::TYPESTRING, что позволило сохранять в базе просто названия сайтов, а не их ENUM_ID из таблицы значений полей свойства типа список.
Так как требовалось предусмотреть логику множественного выбора сайтов у 1 элемента, я выбрал «Множественное» свойство, но вместо компактного вывода моей таблицы с множественным выбором значений — как это обычно работает в стандартном свойстве типа Список — мне множество раз вывелась таблица с возможностью в каждой из них выбрать только одно значение.
После долгих поисков в Ядре я нашёл, что искал: проверку на существование в массиве описания типа свойства поля со ссылкой на метод для уникальной вёрстки при выбранном множественном свойстве.
Чтобы сделать уникальную вёрстку для отображения множественного свойства для пользовательского типа необходимо в результат метода GetUserTypeDescription добавить поле 'GetPropertyFieldHtmlMulty' => [CLASS, 'GetPropertyFieldHtmlMulty'], а также реализовать метод 'GetPropertyFieldHtmlMulty' с телом, в котором будет формироваться отображение для множественного свойства.
Делюсь итоговым вариантом на скриншоте:

Надеюсь, эта статья была вам полезна! Буду рад, если вы поделитесь своими историями в комментариях.