Просьба, которую я слышал от контент-менеджеров на каждом втором проекте: «сделай кнопку, чтобы из карточки товара сразу открыть его на сайте». Звучит как мелочь, но при работе с каталогом на несколько тысяч позиций копирование символьного кода и сборка URL руками съедают ощутимую часть рабочего дня.
Битрикс закрывает это одним событием — OnAdminContextMenuShow. Ниже минимальный рабочий вариант и версия, которую не стыдно оставить в проекте.
Зачем нужна кнопка «Открыть на сайте»
При редактировании элемента инфоблока в админке добавить кнопку для быстрого перехода на страницу элемента на сайте.
Как добавить пункт в контекстное меню админки
Событие OnAdminContextMenuShow — это точка расширения, которая вызывается при отрисовке контекстного меню любой админской страницы и получает массив пунктов по ссылке. Отсюда два следствия, которые определяют весь код ниже: обработчик срабатывает на всех страницах админки (поэтому первым делом фильтруем по текущему URL) и модифицировать нужно именно $items по ссылке, ничего не возвращая.
<?php
// /local/php_interface/init.php
use Bitrix\Main\EventManager;
use Bitrix\Main\Loader;
EventManager::getInstance()->addEventHandler(
'main',
'OnAdminContextMenuShow',
'addElementPublicLink'
);
function addElementPublicLink(&$items): void
{
global $APPLICATION;
// Только на странице редактирования элемента
if ($APPLICATION->GetCurPage() !== '/bitrix/admin/iblock_element_edit.php') {
return;
}
// Только при редактировании существующего элемента
$elementId = (int) ($_REQUEST['ID'] ?? 0);
$iblockId = (int) ($_REQUEST['IBLOCK_ID'] ?? 0);
if ($elementId <= 0 || $iblockId <= 0) {
return;
}
Loader::includeModule('iblock');
// Получаем URL страницы элемента
$element = \CIBlockElement::GetList(
[],
[
'IBLOCK_ID' => $iblockId,
'ID' => $elementId,
],
false,
['nTopCount' => 1],
['ID', 'NAME', 'DETAIL_PAGE_URL']
)->GetNext(true, false);
if (!$element || empty($element['DETAIL_PAGE_URL'])) {
return;
}
// Добавляем кнопку в меню
$items[] = [
'TEXT' => 'Открыть на сайте',
'TITLE' => 'Открыть страницу элемента в новой вкладке',
'LINK' => $element['DETAIL_PAGE_URL'],
'LINK_PARAM' => 'target="_blank"',
'ICON' => 'btn_view',
];
}Не забудьте про фильтр по странице. Без проверки GetCurPage() пункт меню полезет во все списки и формы админки, включая настройки модулей, — а $_REQUEST['ID'] там означает совсем другую сущность. Ссылка при этом соберётся «успешно» и будет вести в никуда.
GetList здесь вызывается с nTopCount => 1 и минимальным набором полей — при отрисовке меню лишние выборки бьют по времени отклика админки, а DETAIL_PAGE_URL Битрикс подставляет сам, разворачивая шаблон URL инфоблока. Флаги GetNext(true, false) отключают HTML-экранирование: URL идёт в атрибут ссылки, экранированные амперсанды в нём только мешают.
Расширенная версия
<?php
namespace Local\Admin;
use Bitrix\Main\EventManager;
use Bitrix\Main\Loader;
class ElementLinkExtension
{
/**
* Регистрация расширения
*/
public static function register(): void
{
EventManager::getInstance()->addEventHandler(
'main',
'OnAdminContextMenuShow',
[self::class, 'addLinks']
);
}
/**
* Добавление ссылок
*/
public static function addLinks(&$items): void
{
global $APPLICATION;
$page = $APPLICATION->GetCurPage();
// Страница элемента
if ($page === '/bitrix/admin/iblock_element_edit.php') {
self::addElementLink($items);
}
// Страница раздела
if ($page === '/bitrix/admin/iblock_section_edit.php') {
self::addSectionLink($items);
}
}
/**
* Ссылка на элемент
*/
private static function addElementLink(array &$items): void
{
$elementId = (int) ($_REQUEST['ID'] ?? 0);
$iblockId = (int) ($_REQUEST['IBLOCK_ID'] ?? 0);
if ($elementId <= 0) {
return;
}
Loader::includeModule('iblock');
$element = \CIBlockElement::GetList(
[],
['IBLOCK_ID' => $iblockId, 'ID' => $elementId],
false,
['nTopCount' => 1],
['ID', 'NAME', 'CODE', 'DETAIL_PAGE_URL', 'IBLOCK_SECTION_ID']
)->GetNext(true, false);
if (!$element) {
return;
}
// Основная ссылка
if (!empty($element['DETAIL_PAGE_URL'])) {
$items[] = [
'TEXT' => 'Открыть на сайте',
'TITLE' => $element['NAME'],
'LINK' => $element['DETAIL_PAGE_URL'],
'LINK_PARAM' => 'target="_blank"',
'ICON' => 'btn_view',
];
}
// Ссылка на раздел элемента
if ($element['IBLOCK_SECTION_ID'] > 0) {
$section = \CIBlockSection::GetByID($element['IBLOCK_SECTION_ID'])->GetNext();
if ($section && !empty($section['SECTION_PAGE_URL'])) {
$items[] = [
'TEXT' => 'Раздел: ' . $section['NAME'],
'LINK' => $section['SECTION_PAGE_URL'],
'LINK_PARAM' => 'target="_blank"',
'ICON' => 'btn_list',
];
}
}
}
/**
* Ссылка на раздел
*/
private static function addSectionLink(array &$items): void
{
$sectionId = (int) ($_REQUEST['ID'] ?? 0);
$iblockId = (int) ($_REQUEST['IBLOCK_ID'] ?? 0);
if ($sectionId <= 0) {
return;
}
Loader::includeModule('iblock');
$section = \CIBlockSection::GetList(
[],
['IBLOCK_ID' => $iblockId, 'ID' => $sectionId],
false,
['ID', 'NAME', 'SECTION_PAGE_URL']
)->GetNext(true, false);
if ($section && !empty($section['SECTION_PAGE_URL'])) {
$items[] = [
'TEXT' => 'Открыть раздел на сайте',
'TITLE' => $section['NAME'],
'LINK' => $section['SECTION_PAGE_URL'],
'LINK_PARAM' => 'target="_blank"',
'ICON' => 'btn_view',
];
}
}
}Регистрация
<?php
// /local/php_interface/init.php
\Local\Admin\ElementLinkExtension::register();Версия в классе решает две практические задачи: обработчик один на все админские страницы (легче отлаживать и отключать), а ссылка на родительский раздел избавляет от второй частой просьбы — «а теперь покажи, как этот товар выглядит в категории».
Доступные иконки
| Значение ICON | Описание |
|---|---|
btn_view | Глаз (просмотр) |
btn_list | Список |
btn_copy | Копирование |
btn_delete | Удаление |
btn_edit | Редактирование |
btn_new | Добавление |
btn_lock | Блокировка |
Добавление в список элементов
Для добавления ссылки в контекстное меню списка элементов:
<?php
// Событие для списка элементов
EventManager::getInstance()->addEventHandler(
'iblock',
'OnAfterIBlockElementAdd',
function ($arFields) {
// Событие вызывается после добавления
// Здесь можно добавить логику
}
);
// Или переопределение result_modifier.php компонента adminQR-код на страницу
Бонус — добавляем QR-код для мобильного просмотра:
<?php
private static function addQrCode(array &$items, string $url): void
{
$qrUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data='
. urlencode($url);
$items[] = [
'TEXT' => 'QR-код страницы',
'TITLE' => 'Отсканируйте для просмотра на телефоне',
'LINK' => $qrUrl,
'LINK_PARAM' => 'target="_blank"',
'ICON' => 'btn_view',
];
}Итоги
Приём стоит десяти минут работы и заметно экономит время контент-менеджеров, поэтому я добавляю такую кнопку почти на каждом проекте с большим каталогом. Что важно помнить:
- обработчик глобальный — фильтрация по
GetCurPage()обязательна; - выборка должна быть минимальной, код выполняется на каждой отрисовке меню;
DETAIL_PAGE_URLвернёт корректный адрес только если у инфоблока заполнен шаблон URL детальной страницы — на инфоблоках без публичной части кнопки просто не будет, и это правильное поведение.
Совет: Если публичная часть живёт на другом домене (headless-фронтенд, поддомен), собирайте абсолютный URL с нужным хостом — относительная ссылка из админки уведёт менеджера на несуществующую страницу админского домена.
Частые вопросы
Как добавить свою кнопку в контекстное меню админки Битрикса?
Через событие OnAdminContextMenuShow. Оно вызывается при отрисовке контекстного меню любой административной страницы и получает массив пунктов по ссылке: обработчик добавляет в этот массив свой элемент с текстом, ссылкой и иконкой, ничего при этом не возвращая.
Почему обязателен фильтр по текущей странице?
Потому что обработчик срабатывает на всех страницах админки, включая настройки модулей и списки, не связанные с инфоблоками. Без проверки текущего адреса пункт меню появится везде, а параметр ID там означает совсем другую сущность — ссылка соберётся успешно и будет вести в никуда. Проверка адреса первой строкой обработчика решает это.
Как получить публичный адрес элемента инфоблока?
Полем DETAIL_PAGE_URL в выборке: Битрикс подставляет его сам, разворачивая шаблон URL инфоблока, поэтому собирать адрес вручную не нужно и не стоит — при изменении настроек инфоблока ручная сборка разъедется. Выборку делайте с ограничением в одну запись и минимальным набором полей.
Почему в GetNext передают два false?
Флаги отключают HTML-экранирование значений. Для адреса это важно: он идёт в атрибут ссылки, и экранированные амперсанды в нём только мешают — ссылка с ними ведёт не туда, куда нужно. Для полей, которые выводятся как текст, экранирование наоборот нужно, поэтому флаги ставят осознанно, а не копируют.
Какие иконки доступны для пунктов меню админки?
Стандартный набор: btn_view — глаз для просмотра, btn_list — список, btn_copy — копирование, btn_delete — удаление, btn_edit — редактирование, btn_new — добавление, btn_lock — блокировка. Для кнопки «Открыть на сайте» логичнее всего btn_view.
Можно ли добавить кнопку в список элементов, а не только в карточку?
Да, тем же событием: контекстное меню списка отрисовывается через него же, меняется только проверка текущей страницы и способ получения идентификатора. Практично сделать один обработчик в классе на все административные страницы — его легче отлаживать и отключать целиком.