Начнем с того, что все кастомные обработчики находятся в папке /local/php_interface/[название папки с обработчиками], можно назвать include или custom-init. В папке /local/php_interface/ должен быть файл init.php, если его там нет, создайте. В этом файле нужно написать следующий код.
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
foreach (glob("{$_SERVER["DOCUMENT_ROOT"]}/local/php_interface/custom-init/*.php") as $customInit) { // путь до ваших обработчиков
require_once $customInit;
}
/*.php — этим значением мы говорим, что будут запускаться все обработчики в папке custom-init. Итого мы создали универсальный init и теперь нам нужно просто писать обработчики и складывать их в папку custom-init для их запуска.
Теперь давайте напишем код обработчика который будет запрещать редактировать конкретное поле всем пользователям, кроме указанных, как админы и так же администраторам портала. Также обработчик будет писать логи и сохранять их в папку /upload. Это позволит нам отследить все изменения, кто и когда менял или пытался поменять это поле. Ниже будет представлен полный код, можете его скопировать себе в коробку, отредактировать Константы класса и пользоваться.
<?php
// Подключение необходимых классов Bitrix
use Bitrix\Main\EventManager; // Для работы с событиями
use Bitrix\Main\Localization\Loc; // Для локализации сообщений
use Bitrix\Main\IO\Directory; // Для работы с директориями
use Bitrix\Main\IO\File; // Для работы с файлами
use Bitrix\Crm\CompanyTable; // Для работы с таблицей компаний CRM
use Bitrix\Main\Loader; // Для подключения модулей
use Bitrix\Main\UserTable; // Для работы с таблицей пользователей
// Загрузка языковых сообщений из текущего файла
Loc::loadMessages(__FILE__);
class CompanyFieldEditRestriction
{
// Константы класса
const ALLOWED_USER_IDS = [1, 5]; // ID пользователей с правами редактирования
const PROTECTED_FIELD = 'COMPANY_TYPE'; // Защищаемое поле
const LOG_DIR = '/upload/'; // Директория для логов
const LOG_FILE = 'restriction.log'; // Файл логов
const ERROR_MESSAGE = 'Изменение поля "%s" запрещено. Для изменения обратитесь к администратору.'; // Шаблон сообщения об ошибке
const ERROR_TITLE = 'Ограничение прав'; // Заголовок ошибки
/**
* Инициализация обработчиков событий
*/
public static function init()
{
$eventManager = EventManager::getInstance();
// Обработчик перед обновлением компании
$eventManager->addEventHandlerCompatible(
'crm', // Модуль CRM
'OnBeforeCrmCompanyUpdate', // Событие перед обновлением
[__CLASS__, 'onBeforeCompanyUpdate'] // Метод-обработчик
);
// Обработчик после добавления кнопок формы
$eventManager->addEventHandlerCompatible(
'crm', // Модуль CRM
'OnAfterCrmCompanyFormButtonsAdd', // Событие после добавления кнопок
[__CLASS__, 'onAfterFormButtonsAdd'] // Метод-обработчик
);
}
/**
* Логирование сообщений в файл
* @param string $message - сообщение для записи в лог
*/
protected static function log($message)
{
try {
$logPath = $_SERVER['DOCUMENT_ROOT'].self::LOG_DIR;
$logFile = $logPath.self::LOG_FILE;
// Создаем директорию, если не существует
if (!Directory::isDirectoryExists($logPath)) {
Directory::createDirectory($logPath);
}
// Записываем сообщение с временной меткой
$timestamp = date('Y-m-d H:i:s');
File::putFileContents(
$logFile,
"[{$timestamp}] {$message}\n",
FILE_APPEND
);
} catch (Exception $e) {
// В случае ошибки пишем в системный лог Bitrix
AddMessage2Log($e->getMessage(), __CLASS__);
}
}
/**
* Получение читаемого названия поля
* @param string $fieldName - код поля
* @return string - название поля
*/
protected static function getFieldTitle($fieldName)
{
static $fieldTitles = null;
if ($fieldTitles === null) {
Loader::includeModule('crm'); // Подключаем модуль CRM
// Массив соответствий кодов полей и их названий
$fieldTitles = [
'COMPANY_TYPE' => Loc::getMessage('COMPANY_TYPE_FIELD_TITLE') ?: 'Тип компании',
];
}
return $fieldTitles[$fieldName] ?? $fieldName; // Возвращаем название или код, если название не найдено
}
/**
* Получение информации о пользователе (ФИО или логин)
* @param int $userId - ID пользователя
* @return string - информация о пользователе
*/
protected static function getUserInfo($userId)
{
static $usersCache = []; // Кеш пользователей
if (!isset($usersCache[$userId])) {
// Получаем данные пользователя из БД
$user = UserTable::getById($userId)->fetch();
if ($user) {
// Формируем ФИО или используем логин
$name = trim($user['NAME'] . ' ' . $user['LAST_NAME']);
$usersCache[$userId] = $name ?: $user['LOGIN'];
} else {
$usersCache[$userId] = "ID{$userId}";
}
}
return $usersCache[$userId];
}
/**
* Обработчик перед обновлением компании
* @param array &$fields - массив полей компании
* @return bool - разрешить (true) или запретить (false) обновление
*/
public static function onBeforeCompanyUpdate(&$fields)
{
global $USER, $APPLICATION;
// Получаем ID компании из разных возможных источников
$id = $fields['ID'] ?? ($GLOBALS['ID'] ?? 0);
if ($id <= 0) {
return true; // Если ID невалидный, пропускаем
}
$userId = (int)$USER->GetID();
$userInfo = self::getUserInfo($userId);
$fieldTitle = self::getFieldTitle(self::PROTECTED_FIELD);
// Логируем попытку изменения
self::log("Попытка обновления компании ID {$id} пользователем {$userInfo}");
// Проверяем права пользователя
if (in_array($userId, self::ALLOWED_USER_IDS)) {
return true; // Разрешаем для определенных пользователей
}
// Если защищаемое поле не изменяется
if (!isset($fields[self::PROTECTED_FIELD])) {
return true;
}
// Получаем текущие данные компании
$company = CompanyTable::getById($id)->fetch();
if (!$company || !isset($company[self::PROTECTED_FIELD])) {
self::log("Ошибка: компания ID {$id} не найдена или поле '{$fieldTitle}' отсутствует");
return true;
}
// Сравниваем старые и новые значения
$oldValue = $company[self::PROTECTED_FIELD];
$newValue = $fields[self::PROTECTED_FIELD];
if ($oldValue == $newValue) {
return true; // Если значение не изменилось
}
// Логируем попытку изменения
self::log("Попытка изменения поля '{$fieldTitle}' в компании {$id}: '{$oldValue}' → '{$newValue}' (пользователь: {$userInfo})");
// Формируем и выводим сообщение об ошибке
$errorMessage = sprintf(self::ERROR_MESSAGE, $fieldTitle);
$fields['RESULT_MESSAGE'] = $errorMessage; // Сообщение для формы
$APPLICATION->ThrowException($errorMessage, self::ERROR_TITLE); // Исключение
return false; // Запрещаем сохранение
}
/**
* Обработчик формы редактирования компании
* Делает защищаемое поле недоступным для редактирования
*/
public static function onAfterFormButtonsAdd($guid, &$buttons, &$form, &$card)
{
global $USER;
$userId = (int)$USER->GetID();
$userInfo = self::getUserInfo($userId);
// Проверяем права пользователя
if (!in_array($userId, self::ALLOWED_USER_IDS)) {
if (isset($form->arFields[self::PROTECTED_FIELD])) {
$fieldTitle = self::getFieldTitle(self::PROTECTED_FIELD);
$errorMessage = sprintf(self::ERROR_MESSAGE, $fieldTitle);
// Модифицируем поле формы:
$form->arFields[self::PROTECTED_FIELD]['SETTINGS']['READONLY'] = true; // Только чтение
$form->arFields[self::PROTECTED_FIELD]['SETTINGS']['DISABLED'] = true; // Отключено
$form->arFields[self::PROTECTED_FIELD]['ATTRIBUTES']['title'] = $errorMessage; // Подсказка
$form->arFields[self::PROTECTED_FIELD]['ATTRIBUTES']['style'] = 'background-color:#f9f9f9; color:#888; cursor:not-allowed;'; // Стили
$form->arFields[self::PROTECTED_FIELD]['ATTRIBUTES']['data-restricted'] = 'true'; // Маркер
// Логируем открытие формы
self::log("Пользователь {$userInfo} открыл форму с ограниченными правами (поле '{$fieldTitle}')");
}
}
}
}
// Языковые сообщения (можно вынести в отдельный файл)
Loc::loadMessages(__FILE__);
$MESS['COMPANY_TYPE_FIELD_TITLE'] = 'Тип компании';
// Инициализация обработчиков событий
CompanyFieldEditRestriction::init();Основные блоки и их функции:
- Константы класса:
- Хранят настройки: ID разрешенных пользователей, защищаемое поле, пути для логов, тексты сообщений
- Метод init():
- Регистрирует обработчики событий Bitrix
- Связывает события с методами класса
- Метод log():
- Записывает сообщения в лог-файл
- Автоматически создает директорию для логов при необходимости
- Обрабатывает ошибки записи
- Метод getFieldTitle():
- Преобразует код поля в читаемое название
- Использует локализацию через Loc::getMessage()
- Кеширует результаты для производительности
- Метод getUserInfo():
- Получает ФИО пользователя по ID
- Использует кеширование для минимизации запросов к БД
- Возвращает логин, если ФИО не указано
- Метод onBeforeCompanyUpdate():
- Проверяет попытки изменения защищаемого поля
- Сравнивает старые и новые значения
- Логирует все попытки изменений
- Блокирует сохранение для неавторизованных пользователей
- Выводит сообщение об ошибке
- Метод onAfterFormButtonsAdd():
- Модифицирует форму редактирования компании
- Делает защищаемое поле недоступным для редактирования
- Добавляет подсказку при наведении
- Применяет CSS-стили для визуального выделения
- Языковые сообщения:
- Определяют читаемые названия полей
- Могут быть вынесены в отдельный файл локализации
- Инициализация:
- Запускает весь механизм через вызов CompanyFieldEditRestriction::init()
Теперь давайте посмотрим, как это работает…

При попытке сменить значение в поле «Тип компании» система вывела мне сообщение «Изменение поля «Тип компании» запрещено. Для изменения обратитесь к администратору.«