Главная/Статьи/Битрикс: транслитерация символьного кода элементов

Битрикс: транслитерация символьного кода элементов

Автоматическая генерация CODE из NAME при добавлении элементов в инфоблок. CUtil::translit и альтернативные методы.

ДМ
Дмитрий Мещеряков
📅 27 июля 2020 г.📖 6 мин чтения

Символьный код — то, из чего складывается адрес страницы, а значит вещь долгоживущая: сгенерировали неудачно, а исправлять придётся вместе с настройкой редиректов. Разберём, как генерировать коды правильно с первого раза, и почему автогенерация «на лету» опаснее, чем кажется.

Зачем нужен символьный код

Символьный код (CODE) элемента используется в ЧПУ (человекопонятных URL):

  • /catalog/smartfony/ вместо /catalog/?SECTION_ID=15
  • /product/iphone-15-pro/ вместо /product/?ID=12345

Код должен быть уникальным, содержать только латиницу, цифры и дефисы.

Метод 1: CUtil::translit (рекомендуемый)

Встроенная функция Битрикса для транслитерации:

php
<?php
use Bitrix\Main\Loader;

Loader::includeModule('iblock');

$name = 'iPhone 15 Pro Max 256GB Синий титан';

$params = [
    'replace_space' => '-',    // Замена пробелов
    'replace_other' => '-',    // Замена прочих символов
    'delete_repeat_replace' => true, // Удалять повторы дефисов
    'use_google' => false,     // Не использовать Google Translate
];

$code = \CUtil::translit($name, 'ru', $params);
// Результат: iphone-15-pro-max-256gb-sinii-titan

// Или краткая версия
$code = \CUtil::translit($name, 'ru', ['replace_space' => '-', 'replace_other' => '-']);
💡 Совет

use_google => false — правильное значение, и менять его не стоит. Параметр включает перевод названия через внешний сервис вместо транслитерации. Это означает сетевой запрос в момент сохранения элемента (то есть замедление админки и обмена с 1С), зависимость от доступности стороннего API и непредсказуемый результат: одно и то же название сегодня и завтра может дать разные коды, а значит — разные URL.

Добавление элемента с автогенерацией кода

php
<?php
use Bitrix\Main\Loader;

Loader::includeModule('iblock');

$name = 'Смартфон Samsung Galaxy S24 Ultra';

// Генерируем символьный код
$code = \CUtil::translit(
    mb_strtolower($name), 
    'ru',
    [
        'replace_space' => '-',
        'replace_other' => '-',
        'delete_repeat_replace' => true,
    ]
);

$el = new \CIBlockElement();

$elementId = $el->Add([
    'IBLOCK_ID' => 2,
    'NAME' => $name,
    'CODE' => $code,
    'ACTIVE' => 'Y',
    'PREVIEW_TEXT' => 'Флагманский смартфон Samsung',
    'PROPERTY_VALUES' => [
        'PRICE' => 129990,
        'VENDOR' => 'Samsung',
    ],
]);

if ($elementId) {
    echo "Добавлен элемент ID: {$elementId}, CODE: {$code}";
} else {
    echo "Ошибка: " . $el->LAST_ERROR;
}

Класс для генерации уникального кода

php
<?php

namespace Local\Iblock;

class CodeGenerator
{
    private array $translitParams = [
        'replace_space' => '-',
        'replace_other' => '-',
        'delete_repeat_replace' => true,
    ];

    /**
     * Генерация кода из строки
     */
    public function generate(string $name): string
    {
        $code = \CUtil::translit(
            mb_strtolower(trim($name)),
            'ru',
            $this->translitParams
        );

        // Удаляем дефисы в начале и конце
        return trim($code, '-');
    }

    /**
     * Генерация уникального кода для инфоблока
     */
    public function generateUnique(string $name, int $iblockId, ?int $excludeId = null): string
    {
        $baseCode = $this->generate($name);
        $code = $baseCode;
        $suffix = 1;

        while ($this->codeExists($code, $iblockId, $excludeId)) {
            $code = $baseCode . '-' . $suffix;
            $suffix++;
            
            // Защита от бесконечного цикла
            if ($suffix > 1000) {
                $code = $baseCode . '-' . uniqid();
                break;
            }
        }

        return $code;
    }

    /**
     * Проверка существования кода
     */
    private function codeExists(string $code, int $iblockId, ?int $excludeId): bool
    {
        $filter = [
            'IBLOCK_ID' => $iblockId,
            '=CODE' => $code,
        ];

        if ($excludeId) {
            $filter['!ID'] = $excludeId;
        }

        $result = \CIBlockElement::GetList(
            [],
            $filter,
            false,
            ['nTopCount' => 1],
            ['ID']
        );

        return (bool) $result->Fetch();
    }

    /**
     * Генерация кода для раздела
     */
    public function generateForSection(string $name, int $iblockId, ?int $excludeId = null): string
    {
        $baseCode = $this->generate($name);
        $code = $baseCode;
        $suffix = 1;

        while ($this->sectionCodeExists($code, $iblockId, $excludeId)) {
            $code = $baseCode . '-' . $suffix;
            $suffix++;
        }

        return $code;
    }

    private function sectionCodeExists(string $code, int $iblockId, ?int $excludeId): bool
    {
        $filter = [
            'IBLOCK_ID' => $iblockId,
            '=CODE' => $code,
        ];

        if ($excludeId) {
            $filter['!ID'] = $excludeId;
        }

        $result = \CIBlockSection::GetList(
            [],
            $filter,
            false,
            ['ID']
        );

        return (bool) $result->Fetch();
    }
}

// Использование
$generator = new CodeGenerator();

// Простая генерация
$code = $generator->generate('iPhone 15 Pro');
// iphone-15-pro

// Уникальный код
$code = $generator->generateUnique('iPhone 15 Pro', 2);
// iphone-15-pro или iphone-15-pro-1 если уже занят

Автоматическая генерация через событие

php
<?php
// /local/php_interface/init.php

use Bitrix\Main\EventManager;
use Local\Iblock\CodeGenerator;

// Автогенерация CODE при добавлении
EventManager::getInstance()->addEventHandler(
    'iblock',
    'OnBeforeIBlockElementAdd',
    function (&$arFields) {
        // Только если код не указан
        if (!empty($arFields['CODE'])) {
            return;
        }

        // Только для определённых инфоблоков
        $autoCodeIblocks = [2, 3, 5]; // ID инфоблоков
        if (!in_array($arFields['IBLOCK_ID'], $autoCodeIblocks)) {
            return;
        }

        $generator = new CodeGenerator();
        $arFields['CODE'] = $generator->generateUnique(
            $arFields['NAME'],
            $arFields['IBLOCK_ID']
        );
    }
);

// Обновление CODE при изменении NAME
EventManager::getInstance()->addEventHandler(
    'iblock',
    'OnBeforeIBlockElementUpdate',
    function (&$arFields) {
        // Пропускаем если CODE явно передан
        if (isset($arFields['CODE'])) {
            return;
        }

        // Проверяем изменилось ли имя
        $element = \CIBlockElement::GetByID($arFields['ID'])->Fetch();
        if (!$element || $element['NAME'] === $arFields['NAME']) {
            return;
        }

        // Генерируем новый код
        $generator = new CodeGenerator();
        $arFields['CODE'] = $generator->generateUnique(
            $arFields['NAME'],
            $arFields['IBLOCK_ID'],
            $arFields['ID']
        );
    }
);

Настройки транслитерации в админке

Битрикс позволяет настроить автогенерацию кода в настройках инфоблока:

  1. Перейдите в Контент → Инфоблоки → Типы инфоблоков
  2. Выберите нужный инфоблок
  3. Вкладка ПоляСимвольный код
  4. Установите флаг «Транслитерировать из названия при добавлении»
  5. Настройте параметры транслитерации

Массовая генерация кодов

Скрипт для генерации кодов у существующих элементов:

php
<?php
$_SERVER['DOCUMENT_ROOT'] = realpath(__DIR__ . '/../../');
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';

use Bitrix\Main\Loader;
use Local\Iblock\CodeGenerator;

Loader::includeModule('iblock');

$iblockId = 2;
$generator = new CodeGenerator();

$result = \CIBlockElement::GetList(
    ['ID' => 'ASC'],
    [
        'IBLOCK_ID' => $iblockId,
        ['LOGIC' => 'OR', '=CODE' => '', '=CODE' => false],
    ],
    false,
    false,
    ['ID', 'NAME', 'CODE']
);

$updated = 0;

while ($element = $result->Fetch()) {
    if (!empty($element['CODE'])) {
        continue;
    }

    $code = $generator->generateUnique($element['NAME'], $iblockId, $element['ID']);

    $el = new \CIBlockElement();
    if ($el->Update($element['ID'], ['CODE' => $code])) {
        echo "ID: {$element['ID']} => {$code}\n";
        $updated++;
    }
}

echo "\nОбновлено элементов: {$updated}\n";
⚠️ Важно

Массовая генерация кодов меняет адреса всех страниц каталога. Это не техническая операция, а SEO-событие: старые URL начинают отдавать 404, позиции сбрасываются, внешние ссылки ведут в никуда, а реклама с прямыми ссылками на карточки перестаёт работать.

Если запускаете скрипт из этой статьи на живом проекте:

  1. Сохраните соответствие старых и новых кодов до обновления — потом восстановить его будет не из чего.
  2. Настройте 301-редиректы со старых адресов на новые.
  3. Обновите sitemap и отправьте его на переобход.
  4. Делайте это разом, а не порциями в течение недели: поисковик должен увидеть единовременное изменение структуры, а не постоянно меняющийся сайт.

И самое главное — не генерируйте коды заново для элементов, у которых они уже есть. В скрипте для этого есть проверка; убирать её ради «единообразия» не стоит ни при каких обстоятельствах.

Итоги

Код должен быть неизменяемым после публикации. Автогенерация уместна на OnBeforeIBlockElementAdd, но не на Update: переименование товара не должно менять его адрес. Иначе каждая правка названия контент-менеджером создаёт новую страницу и убивает старую.

Проверка уникальности обязательна, но помните о её стоимости: это запрос к базе на каждое сохранение, и на массовом импорте он множится на объём каталога.

Короткий код лучше длинного/iphone-15-pro/ вместо /smartfon-apple-iphone-15-pro-max-256gb-sinii-titan/. Но не любой ценой: код обязан оставаться уникальным, а на каталоге с похожими названиями агрессивное сокращение приводит к суффиксам -2, -3, которые выглядят хуже длинного, но осмысленного адреса.

Транслитерация — только встроенная. Внешние переводчики дают недетерминированный результат, а недетерминированность в том, что становится URL, — это гарантированные проблемы.

💡 Совет

Про уже существующие «плохие» коды. Соблазн переписать их одним скриптом велик, а выгода почти всегда меньше издержек: адреса вида /element-1234/ некрасивы, но работают и проиндексированы. Менять массово стоит, только если коды реально ломают работу (дубли, кириллица, недопустимые символы), — и тогда по процедуре с редиректами выше.

🚀

Хотите такое же решение?

Настрою окружение под ваш проект, учту специфику инфраструктуры и обучу команду.

Обсудить проект →
Бесплатная консультация · Ответ в течение дня

Комментарии

Система комментариев скоро будет подключена. А пока вы можете написать мне в Telegram или на email.