Блог разработчика 1С-Битрикс

Модификация штатного шаблона bitrix:search.page. Вывод дополнительных параметров без потери производительности

Статья предназначена для разработчиков «1С-Битрикс: Управление сайтом», которые хотят обогатить поисковую выдачу картинками, описанием, хлебными крошками и датой публикации, не создавая лишней нагрузки на БД.

Модификация штатного шаблона bitrix:search.page

1. Зачем дорабатывать поиск

  • Повышаем CTR — мини-карточка с изображением и ценой притягивает внимание.
  • Улучшаем UX — пользователь раньше понимает, что найдёт внутри.
  • SEO-сниппет — более информативный контент улучшает поведенческие факторы.
  • Производительность — один запрос к БД + кеш CPHPCache.

2. Структура файлов


local/
└── templates/ваш_шаблон/
    └── components/
        └── bitrix/
            └── search.page/
                └── .default/
                    ├── template.php
                    └── result_modifier.php
        

Скопируйте файлы (замените существующий template.php, добавьте result_modifier.php).


3. Полный код

template.php


<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
/** @var array $arParams */
/** @var array $arResult */
/** @global CMain $APPLICATION */
/** @var CBitrixComponentTemplate $this */
?>
<div class="search-page">
    <!-- Форма поиска -->
    <form action="<?= POST_FORM_ACTION_URI ?>" method="get">
        <?php
        /* Поле с подсказками ----------------------------------------- */
        if ($arParams['USE_SUGGEST'] === 'Y') {
            if (
                mb_strlen($arResult['REQUEST']['~QUERY']) &&
                is_object($arResult['NAV_RESULT'])
            ) {
                $arResult['FILTER_MD5'] = $arResult['NAV_RESULT']->GetFilterMD5();
                $obSearchSuggest = new CSearchSuggest(
                    $arResult['FILTER_MD5'],
                    $arResult['REQUEST']['~QUERY']
                );
                $obSearchSuggest->SetResultCount(
                    $arResult['NAV_RESULT']->NavRecordCount
                );
            }
            $APPLICATION->IncludeComponent(
                'bitrix:search.suggest.input',
                '',
                [
                    'NAME'          => 'q',
                    'VALUE'         => $arResult['REQUEST']['~QUERY'],
                    'INPUT_SIZE'    => 40,
                    'DROPDOWN_SIZE' => 10,
                    'FILTER_MD5'    => $arResult['FILTER_MD5'],
                ],
                $component,
                ['HIDE_ICONS' => 'Y']
            );
        } else { ?>
            <input type="text"
                   name="q"
                   value="<?= htmlspecialcharsbx($arResult['REQUEST']['QUERY']) ?>"
                   size="40"
                   placeholder="<?= GetMessage('SEARCH_PLACEHOLDER') ?>"
            />
        <?php } ?>
        <input type="submit" value="<?= GetMessage('SEARCH_GO') ?>" />
        <input type="hidden" name="how"
               value="<?= $arResult['REQUEST']['HOW'] === 'd' ? 'd' : 'r' ?>" />
    </form>
    <br>
    <?php /* Подсказка о раскладке ------------------------------------ */ ?>
    <?php if (isset($arResult['REQUEST']['ORIGINAL_QUERY'])): ?>
        <div class="search-language-guess">
            <?=
            GetMessage(
                'CT_BSP_KEYBOARD_WARNING',
                [
                    '#query#' =>
                        '<a href="' . $arResult['ORIGINAL_QUERY_URL'] . '">'
                        . $arResult['REQUEST']['ORIGINAL_QUERY']
                        . '</a>'
                ]
            )
            ?>
        </div>
        <br>
    <?php endif; ?>
    <?php
    /* ---------------------- Вывод результатов ----------------------- */
    if ($arResult['ERROR_CODE']) {
        ShowError($arResult['ERROR_TEXT']);
    } elseif (!count($arResult['SEARCH'])) {
        ShowNote(GetMessage('SEARCH_NOTHING_TO_FOUND'));
    } else {
        if ($arParams['DISPLAY_TOP_PAGER'] !== 'N') {
            echo $arResult['NAV_STRING'];
        }
        ?><hr><?php
        foreach ($arResult['SEARCH'] as $arItem): ?>
            <article class="search-item" style="margin-bottom:1.5em;">
                <?php if ($arItem['PICTURE']): ?>
                    <a href="<?= $arItem['URL'] ?>"
                       aria-label="<?= htmlspecialcharsbx($arItem['NAME']) ?>">
                        <img src="<?= $arItem['PICTURE'] ?>"
                             alt="<?= htmlspecialcharsbx($arItem['NAME']) ?>"
                             loading="lazy"
                             style="max-width:120px;height:auto;margin-right:10px;float:left">
                    </a>
                <?php endif; ?>
                <!-- Заголовок -->
                <h3 style="margin-top:0">
                    <a href="<?= $arItem['URL'] ?>">
                        <?= htmlspecialcharsbx($arItem['NAME']) ?>
                    </a>
                </h3>
                <!-- Дата публикации -->
                <?php if ($arItem['DATE_ACTIVE_FROM']): ?>
                    <time datetime="<?= $arItem['DATE_ACTIVE_FROM'] ?>"
                          style="font-size:0.85em;color:#777;">
                        <?= $arItem['DATE_ACTIVE_FROM'] ?>
                    </time><br>
                <?php endif; ?>
                <!-- Описание -->
                <?php if ($arItem['DESCRIPTION']): ?>
                    <p><?= $arItem['DESCRIPTION'] ?></p>
                <?php endif; ?>
                <!-- Хлебные крошки -->
                <?php if ($arItem['CHAIN_PATH']): ?>
                    <div style="font-size:0.85em;color:#666;">
                        <?= GetMessage('SEARCH_PATH') ?>&nbsp;<?= $arItem['CHAIN_PATH'] ?>
                    </div>
                <?php endif; ?>
                <div style="clear:both;"></div>
            </article>
            <hr>
        <?php endforeach;
        if ($arParams['DISPLAY_BOTTOM_PAGER'] !== 'N') {
            echo $arResult['NAV_STRING'];
        }
    } ?>
</div>
        

result_modifier.php


<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
use Bitrix\Main\Loader;
if (!Loader::includeModule('iblock')) {
    return;
}
/*
 * Для каждого найденного элемента ИБ выводим:
 *  - NAME              — заголовок
 *  - DESCRIPTION       — короткое описание
 *  - PICTURE           — превью-картинку
 *  - CHAIN_PATH        — хлебные крошки
 *  - DATE_ACTIVE_FROM  — дата публикации
 */
$elementIds = [];
foreach ($arResult['SEARCH'] as $item) {
    if ($item['MODULE_ID'] === 'iblock' && (int)$item['ITEM_ID'] > 0) {
        $elementIds[] = (int)$item['ITEM_ID'];
    }
}
$elementIds = array_unique($elementIds);
if (!$elementIds) {
    return;
}
/* -------------------- Кеш дополнительной выборки ------------------- */
$cacheTime = 3600; // 1 час
$cacheId   = 'search_page_ext_' . md5(implode('|', $elementIds));
$cacheDir  = '/bitrix/search_page_ext';
$cache = new CPHPCache();
if ($cache->InitCache($cacheTime, $cacheId, $cacheDir)) {
    $extraData = $cache->GetVars();
} else {
    $extraData = [];
    $res = CIBlockElement::GetList(
        [],
        ['ID' => $elementIds],
        false,
        false,
        [
            'ID', 'IBLOCK_ID', 'IBLOCK_SECTION_ID', 'NAME',
            'PREVIEW_PICTURE', 'DETAIL_PICTURE',
            'PREVIEW_TEXT', 'ACTIVE_FROM'
        ]
    );
    while ($row = $res->GetNext()) {
        // Картинка
        $pictureId = $row['PREVIEW_PICTURE'] ?: $row['DETAIL_PICTURE'];
        $picture   = $pictureId ? CFile::GetPath($pictureId) : '';
        // Хлебные крошки
        $chainPath = '';
        if ((int)$row['IBLOCK_SECTION_ID'] > 0) {
            $sections = CIBlockSection::GetNavChain(
                $row['IBLOCK_ID'],
                $row['IBLOCK_SECTION_ID'],
                ['NAME', 'SECTION_PAGE_URL']
            );
            $chain = [];
            while ($sec = $sections->GetNext()) {
                $chain[] = '<a href="' . $sec['SECTION_PAGE_URL'] . '>'
                         . htmlspecialcharsbx($sec['NAME'])
                         . '</a>';
            }
            $chainPath = implode(' / ', $chain);
        }
        $extraData[$row['ID']] = [
            'NAME'            => $row['NAME'],
            'DESCRIPTION'     => $row['~PREVIEW_TEXT'],
            'PICTURE'         => $picture,
            'CHAIN_PATH'      => $chainPath,
            'DATE_ACTIVE_FROM'=> $row['ACTIVE_FROM']
                ? CIBlockFormatProperties::DateFormat(
                    'd F Y',
                    MakeTimeStamp($row['ACTIVE_FROM'])
                  )
                : '',
        ];
    }
    if ($cache->StartDataCache()) {
        $cache->EndDataCache($extraData);
    }
}
/* ------------------- Подмешиваем данные в результат ---------------- */
foreach ($arResult['SEARCH'] as &$item) {
    $id = (int)$item['ITEM_ID'];
    if (isset($extraData[$id])) {
        $item = array_merge($item, $extraData[$id]);
    }
}
unset($item);
        

4. Расширяем функционал: живые примеры

4.1 Вывод цены из свойства PRICE


<?php
// result_modifier.php — добавьте в выборку
'PROPERTY_PRICE_VALUE',
// …а при сохранении:
'PRICE' => (float)$row['PROPERTY_PRICE_VALUE'],
// template.php
?>
<?php if ($arItem['PRICE']): ?>
    <div class="price">
        <?= number_format($arItem['PRICE'], 0, ',', ' ') ?>&nbsp;₽
    </div>
<?php endif; ?>
        

4.2 Рейтинг

  1. Создайте числовое свойство RATING.
  2. Выведите в шаблоне пять ★, закрашивая их в зависимости от значения.

4.3 Фильтр «Только статьи»

Добавьте чекбокс в форму и отправляйте свой параметр where=blog.


5. Производительность и best-practice

  1. Один SQL-запрос по списку ID.
  2. Кеш-ключ = md5(IDшников) — переключение страниц не создаёт новый кеш, пока набор ID совпадает.
  3. Лёгкий откат — всё лежит в каталоге шаблона, не требует модификации ядра.
  4. Поддержка старых и новых версий — используется только публично задокументированный API.

6. Итог

  • Богатая выдача за счёт картинок, даты и описания.
  • Нулевая лишняя нагрузка на БД благодаря кешу.
  • Лёгкая масштабируемость — добавьте цену, бренд, рейтинг или любое другое свойство в одном месте.

Копируйте файлы, адаптируйте под свои свойства — и ваш поиск станет полезнее и привлекательнее для посетителей уже сегодня!

Теги:  поисковая выдача, CTR, UX, SEO, производительность, шаблон поиска, PHP, кеширование, bitrix:search.page


Стоимость услуг по разработке и сопровождению сайтов на 1C-Битрикс

Разработка корпоративного сайта

от 7 дней

от 40 000 рублей

Разработка сайта без системы оплаты заказов через корзину

* стоимость зависит от наличия верстки, использования готового решения и т.д.

Аутсорсинг

готов помочь, если нет времени

договорная

Могу взять на себя работы по full-stack на основе готовой верстки

* если нет верстки, то возможность верстать с Figma в режиме редактора

Модули и компоненты для «1С-Битрикс»

оценка производится на основе предоставленного Технического Задания

от 20 000 рублей
Разработка дополнительных модулей для 1С-Битрикс, расширение функционала, внедрение любых решений, требующихся для выполнения ваших бизнес-задач.

* стоимость зависит от конкретной задачи, ее объема и сложности выполняемых работ.