Files

8 lines
21 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 349,
"slug": "editorial-2018-04-field-bitrix-slugs",
"title": "Bitrix: как доказать конфликт символьных кодов и исправить адрес товара",
"excerpt": "Карточка товара открывает соседний элемент или даёт нестабильный результат? Разбираем путь от URL до фильтра CIBlockElement, считаем совпадения и меняем CODE только после проверки маршрута.",
"contentHtml": "<p>Карточка товара открывает не тот элемент: в адресе стоит <code>classic-250-g</code>, а на странице показан другой товар. Иногда ошибка появляется только после импорта. Обе записи видны в админке, картинки на месте, а один адрес ведёт на соседнюю карточку. Очистка кеша в такой ситуации не доказывает причину.</p>\n<p>Цена ошибки — не только одна неверная страница. Клиент видит чужую цену или остаток. Поисковик получает дублирующиеся адреса. Ссылки из писем и рекламы ведут к непредсказуемой записи. Если менять поля наугад, можно потерять старый адрес и усложнить восстановление.</p>\n<p>Тезис простой: сначала нужно посчитать, сколько элементов подходит под фактический <code>CODE</code>, и проверить, какую переменную компонент получил из URL. Только после этого становится ясно, где ошибка: в маршруте, в фильтре или в данных инфоблока.</p>\n<p>Ниже используется процедурный API старых компонентов Bitrix: <code>CComponentEngine</code> и <code>CIBlockElement</code>. Он есть в официальной документации, но конкретные фильтры и поведение зависят от версии модулей; пример не переносит проект на D7 автоматически. Для каталога на версии 18.6.200 и новее отдельно сверяйте изменения товарных ключей в документации <code>GetList</code>.</p>\n<h2>Как возникает неправильная карточка</h2>\n<p>Браузер отправляет путь, например <code>/catalog/kofe/classic-250-g/</code>. Комплексный компонент разбирает этот путь по SEF-шаблону. SEF (Search Engine Friendly) — человекочитаемый адрес, который компонент сопоставляет с маркерами вроде <code>#SECTION_CODE#</code> и <code>#ELEMENT_CODE#</code>. Результат разбора попадает в массив переменных.</p>\n<p>Затем код компонента строит выборку по инфоблоку. Обычно в неё входят <code>IBLOCK_ID</code>, символьный код и признак активности. Проект может добавить раздел, права, витрину или свойство. Если элемент привязан к нескольким разделам, <code>IBLOCK_SECTION_ID</code> показывает основной раздел, а не все связи; для проверки принадлежности используют <code>SECTION_ID</code> с условиями проекта. URL сам по себе не выбирает строку базы. Между адресом и элементом стоит программный фильтр.</p>\n<figure><img src=\"/assets/editorial/2018/bitrix-slug-conflict-2018.svg\" alt=\"Диагностика неправильной карточки: URL, переменная ELEMENT_CODE, число совпадений GetList и действие\" /><figcaption>Разделяйте четыре шага: путь, переменная, выборка и выбранный элемент. Кеш проверяют после них.</figcaption></figure>\n<h2>Сначала зафиксируйте наблюдаемый симптом</h2>\n<p>Возьмите полный URL и ID товара, который должен открыться. Не переписывайте путь вручную. Запишите время запроса, инфоблок и фактический ID, который показала страница. Если ошибка плавающая, сохраните несколько ответов: порядок результатов может скрывать конфликт.</p>\n<p>Название товара не подходит для диагностики. Два товара могут называться «Classic 250 г». Разные названия могут получить один код после транслитерации и нормализации. Нужны значения, которыми реально пользуется компонент: <code>CODE</code>, <code>IBLOCK_ID</code>, раздел и дополнительные условия.</p>\n<h2>Контрольная выборка по CODE</h2>\n<p>Для первой проверки ограничьте запрос одним инфоблоком и активными элементами. Выведите ID, имя, код и раздел. <code>nTopCount</code> нужен здесь только для различения 0, 1 и 2+ результатов. Ограничение не выбирает «правильный» элемент и не заменяет полный аудит данных: если проект допускает одинаковый код у неактивных записей, проверьте их отдельной выборкой.</p>\n<pre><code>&lt;?php\n\nfunction findActiveElementsByCode($iblockId, $code)\n{\n $result = CIBlockElement::GetList(\n array(\"ID\" =&gt; \"ASC\"),\n array(\n \"IBLOCK_ID\" =&gt; (int)$iblockId,\n \"=CODE\" =&gt; $code,\n \"ACTIVE\" =&gt; \"Y\",\n ),\n false,\n array(\"nTopCount\" =&gt; 2),\n array(\"ID\", \"IBLOCK_ID\", \"NAME\", \"CODE\", \"IBLOCK_SECTION_ID\", \"DETAIL_PAGE_URL\")\n );\n\n $items = array();\n while ($item = $result-&gt;GetNext()) {\n $items[] = $item;\n }\n\n return $items;\n}\n\n$items = findActiveElementsByCode(12, \"classic-250-g\");\nif (count($items) !== 1) {\n throw new RuntimeException(\"Нужно разобрать не менее двух совпадений\");\n}</code></pre>\n<p>Сортировка по ID делает вывод повторяемым. Она не выбирает правильный товар. Если запрос вернул два элемента, меньший ID не становится владельцем адреса. Компонент не должен случайно решать спор порядком строк. В примере нет <code>CHECK_PERMISSIONS</code>: <code>GetList</code> по умолчанию не проверяет права, поэтому для честного сравнения с детальным компонентом добавьте те же условия доступа и активности.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class=\"table-scroll\"><table><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Возможная причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>0 совпадений</td><td>Неверный код, инфоблок, активность или фильтр</td><td>Вывести <code>ELEMENT_CODE</code> и поочерёдно убрать проектные условия</td><td>Исправить источник URL или условие выборки после сравнения с данными</td></tr><tr><td>1 совпадение, ID ожидаемый</td><td>Базовые данные верны</td><td>Сверить дополнительные фильтры компонента и кеширование</td><td>Не менять <code>CODE</code>; искать расхождение после базовой выборки</td></tr><tr><td>1 совпадение, ID другой</td><td>В URL попал не тот код или ожидаемый ID неверен</td><td>Сопоставить URL, массив переменных и ссылку-источник</td><td>Исправить генерацию ссылки или исходную запись</td></tr><tr><td>2 и более совпадений</td><td>Конфликт кодов или слишком широкий фильтр</td><td>Вывести все ID, разделы и полный фильтр детали</td><td>Выбрать уникальное правило кода или добавить доказанный контекст</td></tr><tr><td>Результат меняется между запросами</td><td>Нет явной сортировки, кеш или несколько слоёв маршрута</td><td>Повторить запрос с сортировкой и сравнить логи до кеша</td><td>Устранить неоднозначность, затем отдельно проверить кеш</td></tr></tbody></table></div>\n<h2>Проверьте, что пришло из URL</h2>\n<p>Если контрольная выборка выглядит правильно, проверьте разбор пути. Метод <code>CComponentEngine::ParseComponentPath</code> получает папку, шаблоны и текущий путь. Он возвращает имя найденного шаблона, а переменные записывает в переданный массив. В шаблоне путь задают без начального слеша: <code>#SECTION_CODE#/#ELEMENT_CODE#/</code>; папка передаётся отдельно как <code>SEF_FOLDER</code>. Это позволяет увидеть ошибку до обращения к инфоблоку.</p>\n<pre><code>&lt;?php\n\n$templates = array(\n \"detail\" =&gt; \"#SECTION_CODE#/#ELEMENT_CODE#/\",\n);\n$variables = array();\n$page = CComponentEngine::ParseComponentPath(\n \"/catalog/\",\n $templates,\n $variables,\n \"/catalog/kofe/classic-250-g/\"\n);\n\nif ($page !== \"detail\") {\n throw new RuntimeException(\"Не найден шаблон detail\");\n}\n\nif (empty($variables[\"ELEMENT_CODE\"])) {\n throw new RuntimeException(\"Пустой ELEMENT_CODE\");\n}\n\nerror_log(print_r($variables, true));</code></pre>\n<p>В учебном примере ожидается <code>SECTION_CODE=kofe</code> и <code>ELEMENT_CODE=classic-250-g</code>. Это пример для проверки на тестовой странице, а не утверждение о значениях в вашем проекте. В рабочем компоненте имена маркеров и папка могут отличаться.</p>\n<p>Если <code>$page</code> пуст, запрос не совпал с шаблоном. Не меняйте код элемента: сначала проверьте SEF-папку, начальный слеш и правила веб-сервера. Если шаблон найден, но <code>ELEMENT_CODE</code> пуст, сравните имя маркера с ключом, который читает компонент.</p>\n<p>Если переменная верна, а <code>GetList</code> возвращает несколько записей, проблема находится в данных или фильтре. Если запрос возвращает одну запись, но компонент показывает другую, добавьте к диагностике условия компонента: раздел, права, активность, дату публикации и свойства витрины.</p>\n<h2>Проверьте обратное направление</h2>\n<p>Ссылка и разбор должны использовать одну форму шаблона. Вызов <code>CComponentEngine::MakePathFromTemplate</code> собирает путь из тех же маркеров. Учебный круг «собрали → разобрали» показывает расхождение шаблонов, но не заменяет проверку настоящего компонента.</p>\n<pre><code>&lt;?php\n\n$url = CComponentEngine::MakePathFromTemplate(\n \"#SECTION_CODE#/#ELEMENT_CODE#/\",\n array(\n \"SECTION_CODE\" =&gt; \"kofe\",\n \"ELEMENT_CODE\" =&gt; \"classic-250-g\",\n )\n);\n\n// В примере $url равен \"kofe/classic-250-g/\".\n// Папку /catalog/ добавляет отдельный слой маршрута.</code></pre>\n<p>Если генератор ссылки использует <code>#CODE#</code>, а детальная страница ждёт <code>#ELEMENT_CODE#</code>, данные могут быть безупречны, но маршрут будет читать не то поле. Сверьте шаблон ссылки и шаблон компонента на конкретной странице.</p>\n<h2>Исправление подтверждённого конфликта</h2>\n<p>Не исправляйте несколько элементов одной массовой операцией до проверки одного случая. Сначала определите правило уникальности. Для каталога это может быть артикул, раздел, производитель или безопасный суффикс. Правило должно быть стабильным: название товара может измениться, а адрес не должен зависеть от случайного порядка импорта.</p>\n<p>Смена <code>CODE</code> меняет URL. До изменения сохраните старое значение, новый адрес, ID элемента и места, где старый URL формируется или хранится. Решите, нужен ли редирект со старого адреса. Проверьте ссылки из шаблонов, фидов, sitemap и интеграций, которые входят в область вашего проекта.</p>\n<pre><code>&lt;?php\n\n$duplicateId = 123; // ID элемента, который нужно отделить от конфликта\n$newCode = \"classic-250-g-2\";\n$element = new CIBlockElement();\n$updated = $element-&gt;Update($duplicateId, array(\n \"CODE\" =&gt; $newCode,\n));\n\nif (!$updated) {\n throw new RuntimeException($element-&gt;LAST_ERROR);\n}\n\n// После обработчиков снова выполняем выборку по новому CODE.</code></pre>\n<p>Значение <code>classic-250-g-2</code> — учебный пример. Подставьте правило своего каталога и заранее проверьте, что новый код свободен в нужном инфоблоке. Метод <code>Update</code> вызывает обработчики до и после изменения, поэтому успешный возврат означает только успешное выполнение операции API; затем отдельно проверьте маршрутизацию, ссылки и кеш. Нельзя считать суффикс защитой, если следующий импорт снова создаёт тот же код.</p>\n<h2>Порядок действий</h2>\n<ol><li>Сохраните проблемный URL, ожидаемый ID и фактический результат страницы.</li><li>Определите папку ЧПУ, шаблон детали и вызванный компонент.</li><li>На тестовой копии выведите результат <code>ParseComponentPath</code> и значение <code>ELEMENT_CODE</code>.</li><li>Выполните ограниченную выборку в том же <code>IBLOCK_ID</code> с теми же базовыми условиями и явной сортировкой.</li><li>Сравните все найденные ID с фильтром детального компонента и исключите вариант «ID выбирается первым».</li><li>Если конфликт доказан, выберите новое уникальное правило и проверьте его на данных элемента.</li><li>Измените один элемент через API Bitrix и сохраните ошибку <code>LAST_ERROR</code>, если операция не прошла.</li><li>Повторите выборку, разбор URL, открытие карточки и проверку старых ссылок.</li><li>Обновите кеш или индекс только после проверки данных и маршрута по принятому в проекте порядку.</li></ol>\n<h2>Ограничения и отрицательный путь</h2>\n<p>Одинаковый <code>CODE</code> не всегда ошибка. Некоторые проекты разделяют витрины или каталоги контекстом. Тогда уникальность задаёт не один код, а комбинация <code>IBLOCK_ID</code>, раздела и дополнительных условий. Не добавляйте раздел в фильтр автоматически. Сначала установите, какой набор полей проект считает идентичностью товара.</p>\n<p>Если совпадений нет, не делайте вывод о конфликте. Причиной может быть отключённый элемент, другой инфоблок, неверная транслитерация или фильтр по правам. Если URL не разбирается, исправление <code>CODE</code> не поможет: запрос не дошёл до выборки.</p>\n<p>Кеш может скрыть исправление, но он не объясняет два элемента в контрольной выборке. И наоборот, очистка кеша не исправит неверный SEF-шаблон. Диагностируйте слои в порядке прохождения запроса: URI, переменные, фильтр, данные, затем кеш.</p>\n<h2>Критерий готовности</h2>\n<p>Работу можно считать завершённой, когда один и тот же тестовый URL на чистом запросе разбирается в ожидаемый <code>ELEMENT_CODE</code>, контрольная выборка возвращает ровно один допустимый ID, детальный компонент показывает этот ID, а старый адрес либо ведёт на согласованный редирект, либо явно исключён из поддержки. Повторите проверку после прогрева кеша и с URL из реального источника. Результат должен оставаться тем же.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://dev.1c-bitrix.ru/api_help/main/reference/ccomponentengine/parsecomponentpath.php\" target=\"_blank\" rel=\"noopener\">Bitrix: CComponentEngine::ParseComponentPath</a> — разбор пути по SEF-шаблонам и восстановление переменных.</li><li><a href=\"https://dev.1c-bitrix.ru/api_help/iblock/classes/ciblockelement/getlist.php?print=Y\" target=\"_blank\" rel=\"noopener\">Bitrix: CIBlockElement::GetList</a> — выборка элементов с фильтром, сортировкой, ограничением и набором полей.</li><li><a href=\"https://dev.1c-bitrix.ru/api_help/iblock/classes/ciblockelement/update.php?print=Y\" target=\"_blank\" rel=\"noopener\">Bitrix: CIBlockElement::Update</a> — изменение полей элемента и получение результата операции.</li></ul>"
}