Главная/Статьи/Битрикс: кнопка «Открыть на сайте» в админке

Битрикс: кнопка «Открыть на сайте» в админке

Добавление ссылки на публичную страницу элемента в административной панели. Событие OnAdminContextMenuShow.

ДМ
Дмитрий Мещеряков
📅 19 апреля 2020 г.📖 5 мин чтения

Просьба, которую я слышал от контент-менеджеров на каждом втором проекте: «сделай кнопку, чтобы из карточки товара сразу открыть его на сайте». Звучит как мелочь, но при работе с каталогом на несколько тысяч позиций копирование символьного кода и сборка URL руками съедают ощутимую часть рабочего дня.

Битрикс закрывает это одним событием — OnAdminContextMenuShow. Ниже минимальный рабочий вариант и версия, которую не стыдно оставить в проекте.

Задача

При редактировании элемента инфоблока в админке добавить кнопку для быстрого перехода на страницу элемента на сайте.

Решение

Событие OnAdminContextMenuShow вызывается при отрисовке контекстного меню любой админской страницы и получает массив пунктов по ссылке. Отсюда два следствия, которые определяют весь код ниже: обработчик срабатывает на всех страницах админки (поэтому первым делом фильтруем по текущему URL) и модифицировать нужно именно $items по ссылке, ничего не возвращая.

php
<?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
<?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
<?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
<?php
// Событие для списка элементов
EventManager::getInstance()->addEventHandler(
    'iblock',
    'OnAfterIBlockElementAdd',
    function ($arFields) {
        // Событие вызывается после добавления
        // Здесь можно добавить логику
    }
);

// Или переопределение result_modifier.php компонента admin

QR-код на страницу

Бонус — добавляем QR-код для мобильного просмотра:

php
<?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 с нужным хостом — относительная ссылка из админки уведёт менеджера на несуществующую страницу админского домена.

🚀

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

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

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

Комментарии

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