Полный цикл в digital

Свой тип свойств

Готовый код можно скачать в моем репозитории на 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
    • Файл autoload.php
    • Файл constants.php
    • Файл event_handler.php
    • Файл init.php

Реализация

Файл 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

  1. USER_TYPE_ID уникальный идентификатор типа свойства
  2. USER_TYPE cимвольный код типа свойства
  3. CLASS_NAME название класса, который реализует логику типа свойства (чаще всего данный метод и логика свойства располагаются в одном классе, но можно разделить их на разные)
  4. DESCRIPTION название типа свойства, которое будет отображаться в административной панели
  5. PROPERTY_TYPE тип базового свойства, от которого наследуется стандартная логика работа со свойством
  6. CheckFields метод должен проверить корректность значения свойства и вернуть массив. Пустой, если ошибок нет, и с сообщениями об ошибках, если есть
  7. GetUIFilterProperty метод описывает вид поля фильтрации в компоненте main.ui.filter на административных страницах инфоблоков
  8. GetLength метод должен вернуть фактическую длину значения свойства. Он нужен только для свойств, значения которых представляют собой сложные структуры (например, массив)
  9. ConvertToDB метод должен преобразовать значение свойства в формат, пригодный для сохранения в базе данных. И вернуть массив вида array("VALUE" => "...", "DESCRIPTION" => "..."). Если значение свойства массив, то разумным будет использование функции serialize. А вот Дата/время преобразуется в ODBC формат "YYYY-MM-DD HH:MI:SS". Это определит возможности сортировки и фильтрации по значениям данного свойства
  10. ConvertFromDB Метод должен преобразовать значение свойства из формата пригодного для сохранения в базе данных в формат обработки. И вернуть массив вида array("VALUE" => "...", "DESCRIPTION" => "..."). Дополняет ConvertToDB
  11. GetPropertyFieldHtmlMulty Вывод формы редактирования множественного свойства. Если отсутствует, то используется GetPropertyFieldHtml для каждого значения отдельно (у множественных свойств)
  12. GetAdminListViewHTML метод возвращает безопасный HTML отображения значения свойства в списке элементов административной части
  13. GetPublicViewHTML метод должен вернуть безопасный HTML отображения значения свойства в публичной части сайта. Если она вернет пустое значение, то значение отображаться не будет
  14. GetPublicEditHTML метод должен вернуть HTML отображения элемента управления для редактирования значений свойства в публичной части сайта
  15. GetSettingsHTML метод возвращает безопасный HTML отображения настроек свойства для формы редактирования инфоблока
  16. PrepareSettings метод возвращает либо массив с дополнительными настройками свойства, либо весь набор настроек, включая стандартные

Результат

Подключив полноценный класс мы получаем вот такое свойство:

За вывод этого списка отвечает метод GetEditFormHTML(). В настройках свойства так же можно включить множественный вариант его работы, который выглядит так:

В данном случае работает метод GetEditFormHTMLMulty(). Если у класса корректно определены методы:

  • GetAdminListViewHTML()
  • GetAdminListEditHTML()
  • GetAdminListEditHTMLMulty()

Значение свойств будут так же корректно отображаться и редактироваться в списке элементов:

На скриншоте виден так же блок фильтрации, он определяется методом GetFilterHTML(). Чтобы вывести собственное свойство в фильтр его можно добавить в настройках формы фильтра так:

Заполните форму уже сегодня!
Для начала сотрудничества необходимо заполнить заявку или заказать обратный звонок. В ответ получите коммерческое предложение, которое будет содержать индивидуальную стратегию с учетом требований и поставленных задач
Работаем по будням с 9:00 до 18:00. Заявки, отправленные в выходные, обрабатываем в первый рабочий день до 12:00.
Спасибо, ваш запрос принят и будет обработан!
Эйч Маркетинг