Как добавить собственный тип пользовательского поля в Битрикс 24

Задача:

Добавить сортировку по ID, NAME, SORT для поля «Связь с элементом инфоблока» для Компании в CRM Битрикс 24.

Решение:

Для этого нужно изменить поле «Связь с элементом инфоблока». Нужно на основе этого поля добавить собственный тип поля, назовем его «Связь с элементом инфоблока с сортировкой».

1. Создадим (если еще не имеется) папку local  в корне сайта и в ней — папку php_interface. В ней создадим файл (если его еще нет) init.php. В этом файле присоединим наш файл с кодом, назовем его handlers.php и сохраним в папке local\php_interface\include\:

<?php
if(!defined(«B_PROLOG_INCLUDED») || B_PROLOG_INCLUDED!==true) die();

if (file_exists($_SERVER[«DOCUMENT_ROOT»].»/local/php_interface/include/handlers.php»))
require_once($_SERVER[«DOCUMENT_ROOT»].»/local/php_interface/include/handlers.php»);

2. В handlers.php добавим наш класс, назовем его MyCurledType, в котором и создается собственный тип поля:

class MyCurledType extends CUserTypeIBlockElement

В созданном классе определим свой метод GetUserTypeDescription, собственный тип поля назовем c_ibel:

public function GetUserTypeDescription()
{
return array(
«USER_TYPE_ID» => «c_ibel»,
«CLASS_NAME» => «MyCurledType»,
«DESCRIPTION» => GetMessage(«USER_TYPE_IBEL_SORT_DESCRIPTION»),
«BASE_TYPE» => «int»,
);
}

Чтобы все было красиво, сообщения будем брать из языкового файла с таким же именем handlers.php  из папки local\php_interface\include\lang\ru\:

<?
$MESS[«USER_TYPE_IBEL_SORT_DESCRIPTION»] = «Привязка к элементам инф. блоков с сортировкой»;
$MESS[«USER_TYPE_IBEL_SORT_DISPLAY»] = «Поле для сортировки»;
$MESS[«USER_TYPE_IBEL_SORT_BY_NAME»] = «Упорядочить по NAME»;
$MESS[«USER_TYPE_IBEL_SORT_BY_ID»] = «Упорядочить по ID»;
$MESS[«USER_TYPE_IBEL_SORT_BY_SORT»] = «Упорядочить по SORT»;
$MESS[«USER_TYPE_IBEL_SORT_ANY»] = «(выберите порядок)»;
$MESS[«USER_TYPE_IBEL_SORT_CHECKBOX»] = «Флажки»;
$MESS[«USER_TYPE_IBEL_SORT_LIST»] = «Список»;
$MESS[«USER_TYPE_IBEL_SORT_LIST_HEIGHT»] = «Высота списка»;
$MESS[«USER_TYPE_IBEL_SORT_DEFAULT_VALUE»] = «Значение по умолчанию»;
$MESS[«USER_TYPE_IBEL_SORT_ACTIVE_FILTER»] = «Показывать только активные элементы»;
$MESS[«CRM_FIELDS_TYPE_IBLOCK_SORT»] = «Привязка к элементам инф. блоков с сортировкой»;
$MESS[«CRM_FIELDS_TYPE_IB_IBLOCK_SORT»] = «Порядок сортировки»;
$MESS[«CRM_FIELDS_TYPE_IB_IBLOCK_SORT_ID»] = «Сортировка по ID»;
$MESS[«CRM_FIELDS_TYPE_IB_IBLOCK_SORT_NAME»] = «Сортировка по NAME»;
$MESS[«CRM_FIELDS_TYPE_IB_IBLOCK_SORT_SORT»] = «Сортировка по SORT»;
?>

3. Определим обработчик нашего класса

AddEventHandler(«main», «OnUserTypeBuildList», array(«MyCurledType», «GetUserTypeDescription»));

4. Добавить новое свойство поля в создаваемом типе нужно в функции GetSettingsHTML. После вывода свойств для инфоблока, выведем наше новое свойство для задания порядка сортировки, назовем его IBLOCK_SORT:

if($bVarsFromForm)
$iblock_sort = $GLOBALS[$arHtmlControl[«NAME»]][«IBLOCK_SORT»];
elseif(is_array($arUserField))
$iblock_sort = $arUserField[«SETTINGS»][«IBLOCK_SORT»];
else
$iblock_sort = «»;

$result .= ‘
<tr>
<td>’.GetMessage(«USER_TYPE_IBEL_SORT_DISPLAY»).’:</td>
<td>’;
$result .= ‘<select class=»‘.$iblock_sort.’ ‘.$iblock_id.'» name=»‘.$arHtmlControl[«NAME»].'[IBLOCK_SORT]» class=»adm-detail-iblock-sort» id=»‘.$arHtmlControl[«NAME»].'[IBLOCK_SORT]»>’.»\n»;
$result .= ‘<option value=»0″>’.GetMessage(«USER_TYPE_IBEL_SORT_ANY»).'</option>’.»\n»;
$result .= ‘<option value=»ID»‘.($iblock_sort==»ID»? ‘ selected’: »).’>’.GetMessage(«USER_TYPE_IBEL_SORT_BY_ID»).'</option>’.»\n»;
$result .= ‘<option value=»NAME»‘.($iblock_sort==»NAME»? ‘ selected’: »).’>’.GetMessage(«USER_TYPE_IBEL_SORT_BY_NAME»).'</option>’.»\n»;
$result .= ‘<option value=»SORT»‘.($iblock_sort==»SORT»? ‘ selected’: »).’>’.GetMessage(«USER_TYPE_IBEL_SORT_BY_SORT»).'</option>’.»\n»;
$result .= «</select></td>
</tr>\n»;

И в вызове метода CIBlockElement::GetList изменим первый параметр

array(«NAME» => «ASC», «ID» => «ASC»)

на

$arSort = array(«ID» => «ASC»);
if($iblock_sort==»NAME») $arSort = array(«NAME» => «ASC»);
if($iblock_sort==»SORT») $arSort = array(«SORT» => «ASC»);

получилось

$rs = CIBlockElement::GetList(
$arSort,
$arFilter,
false,
false,
array(«ID», «NAME»)
);

5. Нужно еще переопределить метод  GetList в нашем классе:

function GetList($arUserField)
{
$rsElement = false;
if(CModule::IncludeModule(‘iblock’))
{
$obElement = new CIBlockElementEnumMy;
$rsElement = $obElement->GetTreeList($arUserField[«SETTINGS»][«IBLOCK_ID»], $arUserField[«SETTINGS»][«ACTIVE_FILTER»], $arUserField[«SETTINGS»][«IBLOCK_SORT»]);
}
return $rsElement;
}

И, соответственно, придется переопределить метод GetTreeList в классе CIBlockElementEnum:

class CIBlockElementEnumMy extends CIBlockElementEnum
{
function GetTreeList($IBLOCK_ID, $ACTIVE_FILTER=»N», $IBLOCK_SORT=»NAME»)
{
$rs = false;
if(CModule::IncludeModule(‘iblock’))
{
$arFilter = Array(«IBLOCK_ID»=>$IBLOCK_ID);
if($ACTIVE_FILTER === «Y»)
$arFilter[«ACTIVE»] = «Y»;

$arSort = array(«ID» => «ASC»);
if($IBLOCK_SORT === «NAME»)
$arSort = array(«NAME» => «ASC»);
if($IBLOCK_SORT === «SORT»)
$arSort = array(«SORT» => «ASC»);

$rs = CIBlockElement::GetList(
$arSort,
$arFilter,
false,
false,
array(«ID», «NAME»)
);
if($rs)
{
$rs = new CIBlockElementEnum($rs);
}
}
return $rs;
}

6. Еще один класс, в котором мы должны переопределить методы:  CCrmFields.

class CCrmFieldsMy extends CCrmFields
{
public static function GetFieldTypes()
{
//’Disk File’ is disabled due to GUI issues (see CCrmDocument::GetDocumentFieldTypes)
$arFieldType = Array(
‘string’ => array( ‘ID’ =>’string’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_S’)),
‘integer’ => array( ‘ID’ =>’integer’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_I’)),
‘double’ => array( ‘ID’ =>’double’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_D’)),
‘boolean’ => array( ‘ID’ =>’boolean’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_B’)),
‘datetime’ => array( ‘ID’ =>’datetime’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_DT’)),
‘date’ => array( ‘ID’ =>’date’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_DATE’)),
‘money’ => array( ‘ID’ =>’money’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_MONEY’)),
‘url’ => array( ‘ID’ =>’url’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_URL’)),
‘address’ => array( ‘ID’ =>’address’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_ADDRESS’)),
‘enumeration’ => array( ‘ID’ =>’enumeration’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_E’)),
‘file’ => array( ‘ID’ =>’file’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_F’)),
’employee’ => array( ‘ID’ =>’employee’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_EM’)),
‘crm_status’ => array( ‘ID’ =>’crm_status’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_CRM_STATUS’)),
‘iblock_section’=> array( ‘ID’ =>’iblock_section’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_IBLOCK_SECTION’)),
‘iblock_element’=> array( ‘ID’ =>’iblock_element’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_IBLOCK_ELEMENT’)),
‘c_ibel’ => array( ‘ID’ =>’c_ibel’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_IBLOCK_SORT’)),
‘crm’ => array( ‘ID’ =>’crm’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_CRM_ELEMENT’))
//’disk_file’ => array( ‘ID’ =>’disk_file’, ‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_DISK_FILE’)),
);
return $arFieldType;
}

И еще метод из этого класса GetAdditionalFields. Здесь мы в switch добавляем свой case, аналогично case для iblock_element:

case ‘c_ibel’:
$id = isset($fieldValue[‘IB_IBLOCK_ID’])? $fieldValue[‘IB_IBLOCK_ID’]: 0;
$bActiveFilter = isset($fieldValue[‘IB_ACTIVE_FILTER’]) && $fieldValue[‘IB_ACTIVE_FILTER’] == ‘Y’? ‘Y’: ‘N’;

$arFields[] = array(
‘id’ => ‘IB_IBLOCK_TYPE_ID’,
‘name’ => GetMessage(‘CRM_FIELDS_TYPE_IB_IBLOCK_TYPE_ID’),
‘type’ => ‘custom’,
‘value’ => GetIBlockDropDownList($id, ‘IB_IBLOCK_TYPE_ID’, ‘IB_IBLOCK_ID’)
);

$arFilter = Array(«IBLOCK_ID»=>$id);
if($bActiveFilter === «Y»)
$arFilter[«ACTIVE»] = «Y»;

$rs = CIBlockElement::GetList(
array(«SORT» => «DESC», «NAME»=>»ASC»),
$arFilter,
false,
false,
array(«ID», «NAME»)
);

$arDefault = Array(»=>GetMessage(‘CRM_FIELDS_TYPE_IB_DEFAULT_VALUE_ANY’));
while($ar = $rs->GetNext())
$arDefault[$ar[«ID»]] = $ar[«NAME»];

$arFields[] = array(
‘id’ => ‘IB_DEFAULT_VALUE’,
‘name’ => GetMessage(‘CRM_FIELDS_TYPE_IB_DEFAULT_VALUE’),
‘items’ => $arDefault,
‘type’ => ‘list’,
);

$arFields[] = array(
‘id’ => ‘IB_DISPLAY’,
‘name’ => GetMessage(‘CRM_FIELDS_TYPE_IB_DISPLAY’),
‘type’ => ‘list’,
‘items’ => array(
‘LIST’ => GetMessage(‘CRM_FIELDS_TYPE_IB_DISPLAY_LIST’),
‘CHECKBOX’ => GetMessage(‘CRM_FIELDS_TYPE_IB_DISPLAY_CHECKBOX’),
),
);

$arFields[] = array(
‘id’ => ‘IB_IBLOCK_SORT’,
‘name’ => GetMessage(‘CRM_FIELDS_TYPE_IB_IBLOCK_SORT’),
‘type’ => ‘list’,
‘items’ => array(
‘ID’ => GetMessage(‘CRM_FIELDS_TYPE_IB_IBLOCK_SORT_ID’),
‘NAME’ => GetMessage(‘CRM_FIELDS_TYPE_IB_IBLOCK_SORT_NAME’),
‘SORT’ => GetMessage(‘CRM_FIELDS_TYPE_IB_IBLOCK_SORT_SORT’),
),
);

$arFields[] = array(
‘id’ => ‘IB_LIST_HEIGHT’,
‘name’ => GetMessage(‘CRM_FIELDS_TYPE_IB_LIST_HEIGHT’),
‘type’ => ‘text’,
);
$arFields[] = array(
‘id’ => ‘IB_ACTIVE_FILTER’,
‘name’ => GetMessage(‘CRM_FIELDS_TYPE_IB_ACTIVE_FILTER’),
‘type’ => ‘checkbox’,
);

break;

Это все с файлом handlers.php

7. Еще одно изменение мы должны внести в файл компонента crm.config.fields.edit, для этого создадим его копию в папке local\components\bitrix\ и изменим файл компонента component.php в этой копии. В switch обработки типов полей добавим свой case аналогичный iblock_element:

case ‘c_ibel’:
$arField[‘SETTINGS’][‘IBLOCK_SORT’] = $_POST[‘IB_IBLOCK_SORT’];
$arField[‘SETTINGS’][‘IBLOCK_TYPE_ID’] = $_POST[‘IB_IBLOCK_TYPE_ID’];
$arField[‘SETTINGS’][‘IBLOCK_ID’] = $_POST[‘IB_IBLOCK_ID’];
$arField[‘SETTINGS’][‘DEFAULT_VALUE’] = $_POST[‘IB_DEFAULT_VALUE’];
$arField[‘SETTINGS’][‘DISPLAY’] = $_POST[‘IB_DISPLAY’];
$arField[‘SETTINGS’][‘LIST_HEIGHT’] = $_POST[‘IB_LIST_HEIGHT’];
$arField[‘SETTINGS’][‘ACTIVE_FILTER’] = isset($_POST[‘IB_ACTIVE_FILTER’]) && $_POST[‘IB_ACTIVE_FILTER’] == ‘Y’? ‘Y’: ‘N’;
break;

В другом switch ниже действуем точно так же (т.е. аналогично iblock_element):

case ‘c_ibel’:
$arResult[‘FIELD’][‘IB_IBLOCK_SORT’] = $arResult[‘FIELD’][‘SETTINGS’][‘IBLOCK_SORT’];
$arResult[‘FIELD’][‘IB_IBLOCK_TYPE_ID’] = $arResult[‘FIELD’][‘SETTINGS’][‘IBLOCK_TYPE_ID’];
$arResult[‘FIELD’][‘IB_IBLOCK_ID’] = $arResult[‘FIELD’][‘SETTINGS’][‘IBLOCK_ID’];
$arResult[‘FIELD’][‘IB_DEFAULT_VALUE’] = $arResult[‘FIELD’][‘SETTINGS’][‘DEFAULT_VALUE’];
$arResult[‘FIELD’][‘IB_DISPLAY’] = $arResult[‘FIELD’][‘SETTINGS’][‘DISPLAY’];
$arResult[‘FIELD’][‘IB_LIST_HEIGHT’] = $arResult[‘FIELD’][‘SETTINGS’][‘LIST_HEIGHT’];
$arResult[‘FIELD’][‘IB_ACTIVE_FILTER’] = $arResult[‘FIELD’][‘SETTINGS’][‘ACTIVE_FILTER’] == ‘Y’? ‘Y’: ‘N’;
break;

И изменим вызов стандартных классов методов на свои, вместо

$CCrmFields = new CCrmFields($USER_FIELD_MANAGER, $arResult[‘ENTITY_ID’]);

вставляем

$CCrmFields = new CCrmFieldsMy($USER_FIELD_MANAGER, $arResult[‘ENTITY_ID’]);

и вместо

$arResult[‘FIELD’][‘ADDITIONAL_FIELDS’] = CCrmFields::GetAdditionalFields($arResult[‘FIELD’][‘USER_TYPE_ID’], $arResult[‘FIELD’]);

вставляем

$arResult[‘FIELD’][‘ADDITIONAL_FIELDS’] = CCrmFieldsMy::GetAdditionalFields($arResult[‘FIELD’][‘USER_TYPE_ID’], $arResult[‘FIELD’]);

8. Осталось только скопировать стандартные компоненты и шаблоны в соответствующие папки в local для компонентов system.field.edit и system.field.view — local\components\bitrix\, для шаблонов  system.field.edit и system.field.view из папки bitrix\components\bitrix\system.field.edit\templates\iblock_element в папку local\templates\.default\components\bitrix\system.field.edit\c_ibel\ и из папки bitrix\components\bitrix\system.field.view\templates\iblock_element в папкуlocal\templates\.default\components\bitrix\system.field.view\c_ibel\.

Полностью код можно посмотреть на github