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

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') ?> <?= $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, ',', ' ') ?> ₽
</div>
<?php endif; ?>
4.2 Рейтинг
- Создайте числовое свойство
RATING
. - Выведите в шаблоне пять ★, закрашивая их в зависимости от значения.
4.3 Фильтр «Только статьи»
Добавьте чекбокс в форму и отправляйте свой параметр where=blog
.
5. Производительность и best-practice
- Один SQL-запрос по списку ID.
- Кеш-ключ = md5(IDшников) — переключение страниц не создаёт новый кеш, пока набор ID совпадает.
- Лёгкий откат — всё лежит в каталоге шаблона, не требует модификации ядра.
- Поддержка старых и новых версий — используется только публично задокументированный API.
6. Итог
- Богатая выдача за счёт картинок, даты и описания.
- Нулевая лишняя нагрузка на БД благодаря кешу.
- Лёгкая масштабируемость — добавьте цену, бренд, рейтинг или любое другое свойство в одном месте.
Копируйте файлы, адаптируйте под свои свойства — и ваш поиск станет полезнее и привлекательнее для посетителей уже сегодня!