Основные методы работы

Получение списка полей

Контакт это комплексная структура состоящая из нескольких составных частей: основные данные, множественные поля, пользовательские поля.

Получить большую часть полей, можно через php код:

use \Bitrix\Main;

global $USER_FIELD_MANAGER;

if ( \Bitrix\Main\Loader::IncludeModule('crm') )
{
    $fieldsInfo = \CCrmContact::GetFieldsInfo();

    $typesID = array_keys( \CCrmFieldMulti::GetEntityTypeInfos() );
    foreach($typesID as $typeID)
    {
        $fieldsInfo[$typeID] = [
            'TYPE' => 'crm_multifield',
            'ATTRIBUTES' => [\CCrmFieldInfoAttr::Multiple]
        ];
    }

    foreach ($fieldsInfo as $code => &$field)
    {
        $field['CAPTION'] = \CCrmContact::GetFieldCaption($code);
    }

    $userType = new \CCrmUserType(
        $USER_FIELD_MANAGER,
        \CCrmContact::$sUFEntityID
    );
    $userType->PrepareFieldsInfo($fieldsInfo);

    /**
     * Lead fields
     * @array
     */
    var_dump($fieldsInfo);
}

Однако следует учитывать некоторые разночтения после начала ввода новой архитектуры D7, в систему были добавлены DataManager’ы, которые в свою очередь имеют свой допустимый перечень полей. Можно посмотреть их в \Bitrix\Crm\ContactTable::getMap()

Получение списка контактов

Существует несколько способов получить перечень контактов из системы: старое API и DataManager. Каждый способ имеет свои преимущества и не достатки, что в конечном счете использовать должен выбрать разработчик.

Сравнение Старое API DataManager
Возможность получить данные из БД v v
Учет прав при получении v x
Простая фильтрация данных v v
Использование подзапросов для сложной фильтрации x v

Резюмируя, в случае DataManager как преимущество можно выделить скорость работы и гибкость за счет трансляции кода в SQL запрос, в то время как использование старого api дает возможность использовать проверку прав.

Используя старое API

Для получение контактов используя старое API используется метод ```CCrmContact::GetListEx``.

Сигнатура метода:

$contactResult = CCrmContact::GetListEx(
    $arOrder  = array(),
    $arFilter = array(),
    $arGroupBy = false,
    $arNavStartParams = false,
    $arSelectFields = array(),
    $arOptions = array()
);

Механизм получения данных аналогичен выборке из инфоблоков. По-умолчанию при выборке проверяются права текущего пользователя, поэтому чтобы получить все значения без проверки прав необходимо передать в фильтре флаг CHECK_PERMISSIONS с значением N.

Пример: необходимо получить ФИО и ID контактов, которые созданные 1 января 2021 года без учета прав пользователя, отсортированные по источнику (убыванию)

$contactResult = CCrmContact::GetListEx(
    [
        'SOURCE_ID' => 'DESC'
    ],
    [
        '><DATE_CREATE' => [
            '01.01.2021 00:00:00',
            '01.01.2021 23:59:59'
        ],
        'CHECK_PERMISSIONS' => 'N'
    ],
    false,
    false,
    [
        'ID',
        'FULL_NAME'
    ]
);

while( $contact = $contactResult->fetch() )
{
    /**
     * [ 'ID' => ..., 'TITLE' => ... ]
     * @var array
     */
    var_dump($contact);
    var_dump($contact['ID']);
}

Отдельно стоит упомянуть наличие дополнительных возможностей фильтра и опций при работе с методом.

Прямое указание LIMIT и OFFSET

При помощи параметра QUERY_OPTIONS в $arOptions имеется возможность задания таких параметров. Это очень удобно для двухступенчатой постаничной навигации, когда сначала выбираются ID элементов, а потом уже данные для полученных ID.

Пример запроса:

$contactResult = CCrmContact::GetListEx(
    ['ID' => 'DESC'],
    [],
    false,
    false,
    [
        'ID',
        'TITLE'
    ],
    [
        'QUERY_OPTIONS' => [
            'LIMIT' => 100,
            'OFFSET' => 200
        ]
    ]
);

Запросы с участием внешних таблиц в фильтре

Иногда бывает необходимость достать только те контакты, для которых есть запись в сторонней таблице. Для этого можно воспользоваться __JOINS ключем.


/**
 * Entity alias. C - contact (default)
 * @var string
 */
$entityAlias = 'L';

$contactResult = CCrmContact::GetListEx(
    ['ID' => 'DESC'],
    [
        '__JOINS' => [
            [
                'TYPE' => 'INNER',
                'SQL' => 'INNER JOIN SOME_TABLE ST ON ST.ENTITY_ID = '.$entityAlias.'.ID'
            ]
        ],
    ],
    false,
    false,
    [
        'ID',
        'NAME'
    ]
);

/**
 * Query:
 * SELECT
 *  C.ID as ID, C.NAME as NAME
 * FROM b_crm_contact C
 * INNER JOIN SOME_TABLE ST ON ST.ENTITY_ID = L.ID
 * ORDER BY C.ID DESC
 */

Но порой возникает необходимость не просто JOIN к таблице, а полноценные сложные запросы. Для реализации таких запросов в фильтрации существует ключ __CONDITIONS который добавляет любую строку к SQL запросу.

$contactResult = CCrmContact::GetListEx(
    ['ID' => 'DESC'],
    [
        '__CONDITIONS' => [
            [
                'SQL' => 'SOME_DUMP = 132'
            ]
        ],
    ],
    false,
    false,
    [
        'ID',
        'NAME'
    ]
);

/**
 * SQL:
 * SELECT
 *  C.ID as ID, C.NAME as NAME
 * FROM b_crm_lead C
 * WHERE SOME_DUMP = 132
 * ORDER BY C.ID DESC
 */

Помните что это опасные операции из-за непроверяемых параметров, они открыты для SQL Injection.

Используя DataManager

Для получения контактов через DataManager используется класс \Bitrix\Crm\ContactTable который является представлением для таблицы b_crm_contact. Для получения данных используется универсальный метод, который хорошо описан в документации.

Примечание: реализация DataManager архитектурно отличается от старого API, поэтому перечень полей для контакта старого и нового API может разниться. Актуальный список уже доступных полей можно найти получить из метода getMap(), а так же используя класс CCrmUserType (см. “Получение списка полей”).

Рассмотрим пример выше в реализации DataManager.

use \Bitrix\Crm;

$contactResult = Crm\ContactTable::getList([
    'select' => [
        'ID',
        'NAME'
    ],
    'filter' => [
        '><DATE_CREATE' => [
            new \Bitrix\Main\Type\DateTime('01.01.2021 00:00:00'),
            new \Bitrix\Main\Type\DateTime('01.01.2021 23:59:59')
        ]
    ],
    'order' => [
        'SOURCE_ID' => 'DESC'
    ]
]);

foreach ($contactResult as $contact)
{
    /**
     * [ 'ID' => ..., 'TITLE' => ... ]
     * @var array
     */
    var_dump($contact);
    var_dump($contact['ID']);
}

Создание контакта

Добавление контакта производится нестатическим методом Add класса CCrmContact. Сигнатура метода:

public function Add(array &$arFields, $bUpdateSearch = true, $options = array())

$arFields - передающийся по ссылке массив полей добавляемого контакта. $bUpdateSearch - флаг необходимости перерасчета поискового индекса. $options - дополнительные опции при добавлении контакта.

Общий процесс добавления контакта:

  1. Проверка прав на добавление
  2. Проверка обязательных полей
  3. Подготовка технических полей
  4. Вызов события OnBeforeCrmContactAdd.
  5. Запись в таблицу
  6. Расчет прав
  7. Сохранение связанных сущностей
  8. Создание сообщения в ленту (по-умолчанию - да)
  9. Вызов события OnAfterCrmContactAdd
  10. При наличии ORIGIN_ID вызов события OnAfterExternalCrmContactAdd

Технически, контакт не является простой сущностью и состоит из нескольких структур:

  • Общие данные
  • Множественные поля
  • Привязки к компаниям/реквизитам/faceid/адресам
  • Пользовательских полей

Однако на практике, не имеет смысла разделять их между собой. Ниже представлен код иллюстрирующий добавление контакта.

/**
 * true, если нужно проверять права текущего пользователя.
 * Текущий пользователь определяется ID в ключе CURRENT_USER
 * $arOptions
 * @var boolean
 */
$bCheckRight = true;

/**
 * Поля добавляемого контакта
 * @var array
 */
$contactFields = [

    // Основные поля
    'LAST_NAME'   => 'Иванов',
    'NAME'        => 'Иван',
    'SECOND_NAME' => 'Иванович',
    'BIRTHDATE'   => '10.10.1990',
    "FM"          => [
        "PHONE" => [
            "n0" => [
                "VALUE"      => '+78889996644',
                "VALUE_TYPE" => "WORK",
            ],
            "n1" => [
                "VALUE"      => '+7 (495) 888-66-44',
                "VALUE_TYPE" => "HOME",
            ]
        ],
        "EMAIL" => [
            "n0" => [
                "VALUE"      => 'some@email.com',
                "VALUE_TYPE" => "WORK",
            ],
        ],
    ],

    'COMPANY_IDS' => [
        123, // Связан с компанией ID:123
    ],

    // Технические поля
    "OPENED" => "Y", // "Доступен для всех" = Да
    "ASSIGNED_BY_ID" => 123, // По-умолчанию ответственным будет пользователь с ID:123

    // Поля для маркетинга
    'SOURCE_ID' => 'WEB',
    'SOURCE_DESCRIPTION' => 'Пришел с dev.1c-bitrix.ru', 
];


$contactEntity = new \CCrmContact( $bCheckRight );

$contactId = $contactEntity->Add(
    $contactFields,
    $bUpdateSearch = true,
    $arOptions = [
        /**
         * ID пользователя, от лица которого выполняется действие
         * в том числе проверка прав
         * @var integer
         */
        'CURRENT_USER' => \CCrmSecurityHelper::GetCurrentUserID(),

        /**
         * Устанавливайте флаг, только если сущность проходит
         * процедуру восстановления. В случае если флаг есть
         * можно заполнять технические поля DATE_CREATE, DATE_MODIFY
         * @var boolean
         */
        // 'IS_RESTORATION' => true,
    ]
);

if ( !$contactId )
{
    /**
     * Произошла ошибка при добавлении контакта, посмотреть ее можно
     * через любой из способов ниже:
     * 1. $contactFields['RESULT_MESSAGE']
     * 2. $contactEntity->LAST_ERROR
     */
}

Структура FM описывает коммуникационные поля. Если она вам незнакома, то рекомендуется изучить соответствуюую статью

Обновление контакта

Обновление (изменение) контакта производится нестатическим методом Update класса CCrmContact.

Сигнатура метода:

public function Update($ID, array &$arFields, $bCompare = true, $bUpdateSearch = true, $options = array())

$ID - идентификатор изменяемого контакта $arFields - передающийся по ссылке массив полей изменяемого контакта. $bCompare - флаг необходимости проведения сравнения с предыдущими значениями для фиксации событий изменения. bUpdateSearch - флаг необходимости расчета старого поиска. $options - дополнительные опции при обновлении контакта.

Общий процесс обновления контакта:

  1. Получение текущих данных по контакта
  2. Проверка обязательных полей
  3. Проверка прав на изменение
  4. Вызов события OnBeforeCrmContactUpdate.
  5. Подготовка технических полей (доступен для всех, загрузка фотографии, реляции).
  6. Проведения сравнения с предыдущими значениями (при необходимости)
  7. Запись изменений в таблицу
  8. Расчет прав
  9. Запись в контроль дубликатов
  10. Изменение привязок компании
  11. Сохранение адреса (если используется)
  12. Обновление пользовательских полей
  13. Запись в статистические таблицы и историю
  14. Сброс кеша
  15. Обновление поискового индекса
  16. Запись сообщения в ленту контакта/чат
  17. Вызов события OnAfterCrmContactUpdate

Ниже представлен код иллюстрирующий изменение контакта.

/**
 * true, если нужно проверять права текущего пользователя.
 * Текущий пользователь определяется ID в ключе CURRENT_USER
 * $arOptions
 * @var boolean
 */
$bCheckRight = true;

/**
 * Идентификатор изменяемого контакта
 * @var integer
 */
$contactId = 123;

/**
 * Поля изменяемого контакта
 * @var array
 */
$contactFields = [
    // Основные поля
    'LAST_NAME'   => 'Иванов',
    'NAME'        => 'Иван',
    'SECOND_NAME' => 'Иванович',
    "FM"       => [
        "PHONE" => [
            // Телефон с ID 3567 будет изменен
            "3567" => [
                "VALUE"      => '+78889996644',
                "VALUE_TYPE" => "WORK",
            ],
            // Телефон в ID 1234 будет удален
            "1234" => [
                "VALUE"      => '',
                "VALUE_TYPE" => "HOME",
            ]
        ],
    ],

    "ASSIGNED_BY_ID" => 123,
];

$contactEntity = new \CCrmContact( $bCheckRight );

$isUpdateSuccess = $contactEntity->Update(
    $contactId,
    $contactFields,
    $bCompare = true,
    $arOptions = [
        /**
         * ID пользователя, от лица которого выполняется действие
         * в том числе проверка прав
         * @var integer
         */
        'CURRENT_USER' => \CCrmSecurityHelper::GetCurrentUserID(),

        /**
         * Флаг системного действия. В случае true у контакта не будут
         * занесены данные о пользователе который производит действие
         * и дата изменения контакта не изменится.
         * @var boolean
         */
        'IS_SYSTEM_ACTION' => false,

        /**
         * Отмечать кеш менеджера дубликатов для этого контакта.
         * @var boolean
         */
        `ENABLE_DUP_INDEX_INVALIDATION` => true,

        /**
         * В случае true, битрикс создаст сообщение в ленту о изменении
         * @var boolean
         */
        'REGISTER_SONET_EVENT' => true,

        /**
         * В случае если флаг true не будут проверяться:
         * - Пользовательские обязательные поля
         * - Валидация пользовательских полей
         * @var boolean
         */
        'DISABLE_USER_FIELD_CHECK' => false,

        /**
         * В случае если флаг true, не будет проверки
         * обязательности заполнения пользовательских полей
         * 
         * Если флаг `DISABLE_USER_FIELD_CHECK` установлен в true,
         * данный флаг игнорируется - проверок не будет
         * 
         * Флаг не отменяет необходимость корректного заполнения
         * переданных полей.
         * @var boolean
         */
        'DISABLE_REQUIRED_USER_FIELD_CHECK' => false,
    ]
);

if ( !$isUpdateSuccess )
{
    /**
     * Произошла ошибка при обновлении контакта, посмотреть ее можно
     * через любой из способов ниже:
     * 1. $contactFields['RESULT_MESSAGE']
     * 2. $contactEntity->LAST_ERROR
     */
}

Структура FM описывает коммуникационные поля. Если она вам незнакома, то рекомендуется изучить соответствуюую статью

Удаление контакта

Удаление контакта в системе осуществляется исключительно по ID.

Общий процесс удаления контакта:

  1. Проверка существования контакта и прав на него
  2. Вызов события OnBeforeCrmContactDelete.
  3. Удаление контакта
  4. Очистка связанных данных
  5. Вызов события OnAfterCrmContactDelete

За удаление отвечает нестатический метод Delete класса CCrmContact, который имеет следующую сигнатуру:

public function Delete($ID, $arOptions = array()): bool

Полное описание метода удаления на примере контакта с идентификатором 123:


/**
 * Идентификатор контакта для удаления
 * @var integer
 */
$entityId = 123;

/**
 * true, если нужно проверять права текущего пользователя.
 * Текущий пользователь определяется ID в ключе CURRENT_USER
 * $arOptions
 * @var boolean
 */
$bCheckRight = true;

$contactEntity = new \CCrmContact( $bCheckRight );

$deleteResult = $contactEntity->Delete(
    $entityId,
    [
        /**
         * ID пользователя, от лица которого выполняется операция
         * Значение по-умолчанию: ID текущего пользователя
         * @var int
         */
        'CURRENT_USER' => \CCrmSecurityHelper::GetCurrentUserID(),

        /**
         * Удалить связанные бизнес-процессы
         * Значение по-умолчанию: true
         * @var boolean
         */
        'PROCESS_BIZPROC' => true,

        /**
         * Включить отложенное удаление 
         * Значение по-умолчанию хранится в методе \Bitrix\Crm\Settings\ContactSettings::getCurrent()->isDeferredCleaningEnabled()
         * @var boolean
         */
        'ENABLE_DEFERRED_MODE' => \Bitrix\Crm\Settings\ContactSettings::getCurrent()->isDeferredCleaningEnabled()

        /**
         * Пометить кеш дубликатов не актуальным
         * Значение по-умолчанию: true
         * @var boolean
         */
        'ENABLE_DUP_INDEX_INVALIDATION' => true,
    ]
);

if ( !$deleteResult )
{
    // Операция не удалась
    var_dump($contactEntity->LAST_ERROR);
}

Это излишнее описание с указанием значений по-умолчанию. В большинстве случае достаточно будет использовать упрощенную форму:

/**
 * Идентификатор контакта для удаления
 * @var integer
 */
$entityId = 123;

/**
 * Идентификатор пользователя, чье имя нужно занести в историю
 * @var integer
 */
$logHistoryUserId = \CCrmSecurityHelper::GetCurrentUserID();

$contactEntity = new \CCrmContact( false );

$deleteResult = $contactEntity->Delete(
    $entityId,
    [
        'CURRENT_USER' => $logHistoryUserId,
    ]
);

if ( !$deleteResult )
{
    // Операция не удалась
    var_dump($contactEntity->LAST_ERROR);
}