Найти в Дзене

При выгрузке товаров из 1С УТ на сайт товары не привязываются к брендам. Как сделать привязку

Важно. Эта инструкция подходит только для специалистов с уровнем знания PHP не ниже среднего. Если вы не уверены в своих навыках, такую доработку лучше не внедрять на рабочем сайте самостоятельно. Ошибка в init.php может привести к некорректной работе обмена с 1С или к 500 ошибке. Одна из самых частых проблем на проектах с 1С-Битрикс и обменом из 1С УТ — бренд у товара формально приходит, но на сайте нормально не работает. В 1С он хранится как строка или список, а на сайте для каталога, фильтрации, страниц брендов и SEO обычно нужна привязка к отдельному инфоблоку брендов. В итоге менеджеры и контент-специалисты после каждой выгрузки получают одну и ту же ручную задачу: создать новый бренд, заполнить его, а потом еще и привязать его к товарам. Пока это не сделано, часть логики каталога работает неполноценно: фильтр может быть пустым, страницы брендов не формируются как нужно, а сам каталог становится менее управляемым. Что дает такая доработка бизнесу: новые бренды создаются автоматиче
Оглавление

Важно. Эта инструкция подходит только для специалистов с уровнем знания PHP не ниже среднего. Если вы не уверены в своих навыках, такую доработку лучше не внедрять на рабочем сайте самостоятельно. Ошибка в init.php может привести к некорректной работе обмена с 1С или к 500 ошибке.

Одна из самых частых проблем на проектах с 1С-Битрикс и обменом из 1С УТ — бренд у товара формально приходит, но на сайте нормально не работает. В 1С он хранится как строка или список, а на сайте для каталога, фильтрации, страниц брендов и SEO обычно нужна привязка к отдельному инфоблоку брендов.

В итоге менеджеры и контент-специалисты после каждой выгрузки получают одну и ту же ручную задачу: создать новый бренд, заполнить его, а потом еще и привязать его к товарам. Пока это не сделано, часть логики каталога работает неполноценно: фильтр может быть пустым, страницы брендов не формируются как нужно, а сам каталог становится менее управляемым.

Что дает такая доработка бизнесу: новые бренды создаются автоматически, товары сразу получают правильную привязку, а каталог меньше зависит от ручной рутины после обмена с 1С. Экономится часы времени при большом каталоге. Содержание:

  • В чем проблема со свойством «Бренд»
  • Почему это влияет не только на удобство, но и на работу сайта
  • Как работает автоматическая привязка бренда
  • Когда такая доработка особенно полезна
  • Что нужно подготовить перед внедрением
  • Готовый код для init.php
  • Что проверить после внедрения
  • FAQ

В чем проблема со свойством «Бренд»

На большинстве сайтов бренды сделаны отдельным инфоблоком. Это нормальная и удобная архитектура: у бренда можно сделать собственную страницу, добавить логотип, описание, баннер, SEO-текст, связать его с каталогом и использовать в фильтрах.

Но в 1С все обычно устроено проще. Там бренд хранится как строка или список. При обмене это значение действительно приходит в товар, но не связывается автоматически со свойством типа «Привязка к элементам», которое используется на сайте.

Из-за этого появляется типовая ситуация:

  1. в 1С бренд есть;
  2. в товар на сайте название бренда приехало;
  3. в инфоблоке брендов нужный элемент либо отсутствует, либо уже есть;
  4. но товар с этим брендом не связан так, как нужно для сайта.

То есть визуально кажется, что обмен работает, но фактически каталог остается недособранным.

Почему это влияет не только на удобство, но и на работу сайта

На первый взгляд проблема выглядит технической и не самой критичной. Но на практике она затрагивает сразу несколько рабочих процессов:

  • усложняется сопровождение каталога;
  • менеджеры тратят время на рутину после каждой выгрузки;
  • новые бренды могут не попадать в фильтры и подборки;
  • страницы брендов либо не наполняются, либо наполняются с задержкой;
  • часть SEO-структуры каталога работает не так, как задумано.

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

Как работает автоматическая привязка бренда

Логика решения достаточно простая и надежная:

  1. В момент сохранения свойств товара мы читаем значение бренда, которое пришло из 1С.
  2. Проверяем, существует ли в инфоблоке брендов элемент с таким названием.
  3. Если бренд уже есть — просто записываем привязку к нему в товар.
  4. Если бренда нет — создаем новый элемент бренда и сразу же привязываем товар к нему.

В результате не нужно отдельно создавать бренд руками и потом возвращаться к товару, чтобы настроить связь. Каталог сам достраивает нужную структуру в момент обмена.

По сути правило одно: если бренд найден — использовать его; если не найден — создать автоматически и сразу связать с товаром.

Когда такая доработка особенно полезна

Чаще всего она нужна в следующих случаях:

  • на сайте большой каталог с регулярным обменом из 1С;
  • бренды уже вынесены в отдельный инфоблок;
  • по брендам есть фильтрация, подборки или SEO-страницы;
  • новые бренды появляются постоянно;
  • у команды нет желания после каждой выгрузки вручную дорабатывать каталог.

Особенно хорошо эта доработка работает там, где сайт используется не просто как витрина, а как рабочий инструмент продаж и лидогенерации. Чем меньше в каталоге ручных операций, тем стабильнее вся система.

Что нужно подготовить перед внедрением

Перед тем как вставлять код в init.php, нужно определить параметры именно вашего проекта:

  • ID инфоблока товаров — где хранятся карточки товара;
  • ID инфоблока брендов — куда будут создаваться новые элементы;
  • код свойства из 1С — откуда приходит название бренда;
  • код свойства привязки — куда должна записываться ссылка на бренд.

Часто схема выглядит так: из 1С бренд приходит в свойство вроде CML2_MANUFACTURER, а на сайте используется отдельное свойство BRAND с типом «Привязка к элементам».

Перед внедрением обязательно:

  • сделайте резервную копию файла init.php;
  • проверьте код на тестовом сайте;
  • убедитесь, что обмен с 1С после внедрения проходит корректно.

Готовый код для init.php

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

Готовый код для init.php Скопировать весь код // Конфигурация обработчика define('PRODUCT_IBLOCK_ID', 40); // ID инфоблока товаров define('BRAND_IBLOCK_ID', 12); // ID инфоблока брендов define('MANUFACTURER_PROP_CODE', 'CML2_MANUFACTURER'); // код свойства производитель (список) define('BRAND_PROP_CODE', 'BRAND'); // код свойства бренд (привязка к элементам) // Регистрируем обработчики на добавление и обновление элемента AddEventHandler('iblock', 'OnAfterIBlockElementAdd', 'CopyManufacturerToBrandHandler'); AddEventHandler('iblock', 'OnAfterIBlockElementUpdate', 'CopyManufacturerToBrandHandler'); /** * Основной обработчик события * Копирует значение из поля "Производитель" (список) в поле "Бренд" * При необходимости создает новый элемент бренда * * @param array &$arFields Поля элемента * @return void */ function CopyManufacturerToBrandHandler(&$arFields): void { // Проверяем, что событие сработало в инфоблоке товаров if (!isProductIBlock($arFields['IBLOCK_ID'])) { return; } $elementId = (int)$arFields['ID']; // Получаем значение производителя (текстовое название) $manufacturerName = getManufacturerName($elementId, $arFields); if (empty($manufacturerName)) { logMessage("Производитель не указан для элемента ID: {$elementId}"); return; } // Очищаем название от HTML сущностей и лишних символов $manufacturerName = cleanManufacturerName($manufacturerName); if (empty($manufacturerName)) { logMessage("После очистки название производителя пустое для элемента ID: {$elementId}"); return; } // ... Показать полный код // Конфигурация обработчика define('PRODUCT_IBLOCK_ID', 40); // ID инфоблока товаров define('BRAND_IBLOCK_ID', 12); // ID инфоблока брендов define('MANUFACTURER_PROP_CODE', 'CML2_MANUFACTURER'); // код свойства производитель (список) define('BRAND_PROP_CODE', 'BRAND'); // код свойства бренд (привязка к элементам) // Регистрируем обработчики на добавление и обновление элемента AddEventHandler('iblock', 'OnAfterIBlockElementAdd', 'CopyManufacturerToBrandHandler'); AddEventHandler('iblock', 'OnAfterIBlockElementUpdate', 'CopyManufacturerToBrandHandler'); /** * Основной обработчик события * Копирует значение из поля "Производитель" (список) в поле "Бренд" * При необходимости создает новый элемент бренда * * @param array &$arFields Поля элемента * @return void */ function CopyManufacturerToBrandHandler(&$arFields): void { // Проверяем, что событие сработало в инфоблоке товаров if (!isProductIBlock($arFields['IBLOCK_ID'])) { return; } $elementId = (int)$arFields['ID']; // Получаем значение производителя (текстовое название) $manufacturerName = getManufacturerName($elementId, $arFields); if (empty($manufacturerName)) { logMessage("Производитель не указан для элемента ID: {$elementId}"); return; } // Очищаем название от HTML сущностей и лишних символов $manufacturerName = cleanManufacturerName($manufacturerName); if (empty($manufacturerName)) { logMessage("После очистки название производителя пустое для элемента ID: {$elementId}"); return; } // Получаем ID бренда (существующего или нового) $brandId = getOrCreateBrand($manufacturerName); if (!$brandId) { logError("Не удалось получить или создать бренд '{$manufacturerName}'"); return; } // Привязываем бренд к товару attachBrandToProduct($elementId, $brandId); } /** * Проверяет, является ли инфоблок инфоблоком товаров * * @param int $iblockId ID инфоблока * @return bool */ function isProductIBlock($iblockId): bool { return (int)$iblockId === PRODUCT_IBLOCK_ID; } /** * Очищает название производителя от HTML сущностей и лишних символов * * @param string $name Исходное название * @return string */ function cleanManufacturerName(string $name): string { // Декодируем HTML сущности (например, & -> &) $name = html_entity_decode($name, ENT_QUOTES | ENT_HTML5, 'UTF-8'); // Удаляем лишние пробелы $name = trim($name); // Удаляем множественные пробелы $name = preg_replace('/\s+/', ' ', $name); return $name; } /** * Получает название производителя из свойства-списка * * @param int $elementId ID элемента * @param array $arFields Поля из события * @return string */ function getManufacturerName(int $elementId, array $arFields): string { $propCode = MANUFACTURER_PROP_CODE; $manufacturerValue = ''; // Пытаемся получить ID значения из переданных полей if (isset($arFields['PROPERTY_VALUES'][$propCode])) { $propData = $arFields['PROPERTY_VALUES'][$propCode]; $enumId = extractEnumId($propData); if ($enumId) { $manufacturerValue = getEnumValueName($enumId); } } // Если не нашли, получаем из базы данных if (empty($manufacturerValue)) { $manufacturerValue = getManufacturerFromDatabase($elementId, $propCode); } return $manufacturerValue; } /** * Извлекает ID значения из списка * * @param mixed $propData Данные свойства * @return int|null ID значения списка или null */ function extractEnumId($propData): ?int { if (is_array($propData)) { $firstValue = reset($propData); // Для множественных свойств if (is_array($firstValue)) { if (isset($firstValue['VALUE_ID'])) { return (int)$firstValue['VALUE_ID']; } if (isset($firstValue['VALUE'])) { return (int)$firstValue['VALUE']; } } // Для одиночных свойств if (is_numeric($firstValue)) { return (int)$firstValue; } if (is_string($firstValue) && is_numeric($firstValue)) { return (int)$firstValue; } } // Если передано просто число if (is_numeric($propData)) { return (int)$propData; } return null; } /** * Получает название значения из списка по ID * * @param int $enumId ID значения списка * @return string */ function getEnumValueName(int $enumId): string { $dbEnum = CUserFieldEnum::GetList([], ['ID' => $enumId]); if ($enum = $dbEnum->Fetch()) { // Получаем значение и декодируем его $value = trim($enum['VALUE']); return html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } return ''; } /** * Получает название производителя из базы данных * * @param int $elementId ID элемента * @param string $propCode Код свойства * @return string */ function getManufacturerFromDatabase(int $elementId, string $propCode): string { $dbProps = CIBlockElement::GetProperty( PRODUCT_IBLOCK_ID, $elementId, [], ['CODE' => $propCode] ); if ($prop = $dbProps->Fetch()) { // Если это свойство-список, значение хранится в VALUE_ENUM if (!empty($prop['VALUE_ENUM'])) { $value = trim($prop['VALUE_ENUM']); return html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } // Если получили ID, пытаемся получить название if (!empty($prop['VALUE']) && is_numeric($prop['VALUE'])) { return getEnumValueName((int)$prop['VALUE']); } // Если получили строковое значение if (!empty($prop['VALUE'])) { $value = trim($prop['VALUE']); return html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } } return ''; } /** * Получает существующий бренд или создает новый * * @param string $brandName Название бренда * @return int|null ID бренда или null в случае ошибки */ function getOrCreateBrand(string $brandName): ?int { // Ищем существующий бренд $existingBrandId = findExistingBrand($brandName); if ($existingBrandId) { logMessage("Найден существующий бренд '{$brandName}' ID: {$existingBrandId}"); return $existingBrandId; } // Создаем новый бренд return createNewBrand($brandName); } /** * Ищет существующий бренд по названию * * @param string $brandName Название бренда * @return int|null ID бренда или null если не найден */ function findExistingBrand(string $brandName): ?int { $dbBrands = CIBlockElement::GetList( ['SORT' => 'ASC'], [ 'IBLOCK_ID' => BRAND_IBLOCK_ID, 'NAME' => $brandName, 'ACTIVE' => 'Y' ], false, ['nTopCount' => 1], ['ID', 'NAME'] ); while ($brand = $dbBrands->Fetch()) { $brandNameDb = html_entity_decode(trim($brand['NAME']), ENT_QUOTES | ENT_HTML5, 'UTF-8'); if ($brandNameDb === $brandName) { return (int)$brand['ID']; } } $dbBrands = CIBlockElement::GetList( ['SORT' => 'ASC'], [ 'IBLOCK_ID' => BRAND_IBLOCK_ID, '%NAME' => str_replace(['&', '&'], ['&', '&'], $brandName), 'ACTIVE' => 'Y' ], false, ['nTopCount' => 5], ['ID', 'NAME'] ); while ($brand = $dbBrands->Fetch()) { $brandNameDb = html_entity_decode(trim($brand['NAME']), ENT_QUOTES | ENT_HTML5, 'UTF-8'); if ($brandNameDb === $brandName) { return (int)$brand['ID']; } } return null; } /** * Создает новый элемент бренда * * @param string $brandName Название бренда * @return int|null ID созданного бренда или null в случае ошибки */ function createNewBrand(string $brandName): ?int { $el = new CIBlockElement(); $cleanName = cleanManufacturerName($brandName); $brandFields = [ 'IBLOCK_ID' => BRAND_IBLOCK_ID, 'NAME' => $brandName, 'CODE' => generateTranslitCode($cleanName), 'ACTIVE' => 'Y', ]; $brandId = $el->Add($brandFields); if ($brandId) { logMessage("Создан новый бренд ID: {$brandId} с названием '{$brandName}'"); return (int)$brandId; } logError("Ошибка создания бренда '{$brandName}': " . $el->LAST_ERROR); return null; } /** * Генерирует символьный код из названия * * @param string $name Название для транслитерации * @return string */ function generateTranslitCode(string $name): string { $name = html_entity_decode($name, ENT_QUOTES | ENT_HTML5, 'UTF-8'); return CUtil::translit($name, 'ru', [ 'replace_space' => '-', 'replace_other' => '-', 'max_len' => 100, 'change_case' => 'L', 'safe_chars' => '&' ]); } /** * Привязывает бренд к товару * * @param int $productId ID товара * @param int $brandId ID бренда * @return void */ function attachBrandToProduct(int $productId, int $brandId): void { $result = CIBlockElement::SetPropertyValuesEx( $productId, PRODUCT_IBLOCK_ID, [BRAND_PROP_CODE => $brandId] ); if ($result) { logMessage("Бренд ID: {$brandId} успешно привязан к товару ID: {$productId}"); } else { logError("Ошибка привязки бренда к товару ID: {$productId}"); } } /** * Логирует информационное сообщение * * @param string $message Сообщение * @return void */ function logMessage(string $message): void { AddMessage2Log($message, 'aspro-brand-handler'); } /** * Логирует сообщение об ошибке * * @param string $message Сообщение об ошибке * @return void */ function logError(string $message): void { AddMessage2Log('[ОШИБКА] ' . $message, 'aspro-brand-handler'); } Что нужно поменять в коде под ваш проект

  • CATALOG_IBLOCK_ID — ID инфоблока каталога;
  • BRANDS_IBLOCK_ID — ID инфоблока брендов;
  • SOURCE_BRAND_PROPERTY_CODE — код свойства, которое приходит из 1С;
  • TARGET_BRAND_LINK_PROPERTY_CODE — код свойства привязки в товаре.

Если у вас у брендов нужно автоматически заполнять еще и картинку, описание, SEO-поля или пользовательские свойства, это можно расширить в методе createBrand().

Что проверить после внедрения

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

  1. Создается ли новый бренд, если его еще нет в инфоблоке.
  2. Не создается ли дубль, если бренд уже существует.
  3. Привязывается ли товар к найденному или созданному бренду.
  4. Корректно ли работают фильтры, страницы брендов и вывод товаров по производителю.
  5. Нет ли дублей из-за разного написания брендов в 1С, например с лишними пробелами или разными вариантами названия.

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

FAQ

Подойдет ли этот вариант, если бренд из 1С приходит списком, а не строкой?

Да, подойдет. В примере предусмотрено чтение как строкового, так и списочного значения. Если свойство списочное, берется его текстовое значение и уже по нему выполняется поиск или создание бренда.

Будет ли код автоматически создавать новые бренды?

Да. Если в инфоблоке брендов не найден элемент с нужным названием, код создаст его автоматически и сразу же привяжет к товару.

Можно ли дополнительно заполнять у нового бренда описание, картинку и SEO-поля?

Да, это можно сделать. В базовом варианте создается минимально необходимый элемент бренда, но логику легко расширить и добавить заполнение дополнительных полей и свойств.

Что делать с уже существующими товарами, где бренд ранее не был привязан?

Для них лучше отдельно запустить разовую массовую обработку каталога. Этот обработчик нужен для автоматической работы в момент следующих обменов и сохранений.