В Битриксе существует достаточно интересная система ранжирования результатов поиска, однако на одном из проектов был замечена не очень эффективная сортировка.
Чтобы воспроизвести данную проблему необходимо выполнить следующие действия:
В результате открывается страница с компонентом bitrix:search.page. В идеале элемент с названием совпадающим в точности с поисковой фразой должен быть первым в списке. Однако элементы отображаются, как правило, в порядке добавления.
К сожалению просто настройками модуля и компонента данную проблему решить не удалось. Привожу описание решения. При этом приведенный код является минимально необходимым. Например, в данной статье не демонстрируется кеширование, поиск осуществляется только среди элементов информационных блоков.
Для решения этой задачи необходимо отказаться от использования стандартного компонента bitrix:search.page. Можно создать свой модуль либо для вывода использовать bitix:catalog.section с дополнительным параметром.
Первым шагом находим искомые элементы ($word - поисковый запрос). На этом этапе добавлен важный момент: для каждого найденного элемента рассчитывается степень похожести поисковой фразы и заголовка. Для этого существуют различные алгоритмы в нашем случае используется функция similar_text http://php.net/manual/ru/function.similar-text.php. При этом важно, что производится выборка всех результатов поиска без разделения на страницы.
$arSearchResult = ['RANKS' => [], 'IDS' => [], 'PAGINATOR' => '','WORD'=>$word];
$arParams = [
'QUERY' => $word,
'SITE_ID' => LANG,
'MODULE_ID' => 'iblock'
];
$arSort = [
'CUSTOM_RANK' => 'DESC',
'TITLE_RANK' => 'DESC',
'RANK' => 'DESC',
'DATE_CHANGE' => 'DESC'
];
$obSearch = new CSearch;
$obSearch->Search($arParams,$arSort);
$arItems = [];
while ($arSearch = $obSearch->Fetch()) {
$arSearch['TITLERANK'] = 0;
similar_text($arSearch['TITLE'],$_REQUEST['q'],$arSearch['TITLERANK']);
$arItems[$arSearch['ITEM_ID']] = $arSearch;
}
if (count($arItems) == 0) {
/** если не найдено с включенной морфологией - пробуем найти без нее */
$obSearch->Search($arParams,$arSort,['STEMMING' => false]);
while ($arSearch = $obSearch->Fetch()) {
$arSearch['TITLERANK'] = 0;
similar_text($arSearch['TITLE'],$_REQUEST['q'],$arSearch['TITLERANK']);
$arItems[$arSearch['ITEM_ID']] = $arSearch;
}
}
$obSearch->Statistic = new CSearchStatistic($obSearch->strQueryText, $obSearch->strTagsText);
$obSearch->Statistic->PhraseStat($obSearch->NavRecordCount, $obSearch->NavPageNomer);
Далее массив необходимо отсортировать в необходимом нам порядке.
usort($arItems, "cmp");
public static function cmp($a, $b){
if ($a['TITLERANK'] == $b['TITLERANK']) {
return 0;
}
return ($a['TITLERANK'] > $b['TITLERANK']) ? -1 : 1;
}
Далее необходимо отобрать только те элементы, которые необходимо вывести на текущей странице.
use Bitrix\Main\Application;
$request = Application::getInstance()->getContext()->getRequest();
/** Текущая страница */
$pageNum = intval($request->get('PAGEN_1'));
if ($pageNum>0) --$pageNum;
/** Элементов на страницу */
$pageItems = 15;
$pageItemsTotal = count($arItems);
$pageCount = ceil($pageItemsTotal / $pageItems);
if ($pageNum > $pageCount) {
$pageNum = $pageCount;
}
$pageStart = $pageNum * $pageItems;
if ($pageStart > $pageItemsTotal) {
$pageStart = $pageItemsTotal - $pageItems;
}
$pageStop = $pageStart + $pageItems;
$arSearchResult['RANKS'] =[];
$arRanks = array_splice($arItemsFound,$pageStart,$pageStop);
foreach ($arRanks as $arItem){
$arSearchResult['IDS'][] = $arItem['ITEM_ID'];
$arSearchResult['RANKS'][$arItem['ITEM_ID']] = $arItem;
}
Формируем пагинацию
if ($pageCount > 1) {
$nav = new \CDBResult();
$nav->NavStart($pageItems);
$nav->NavPageCount = $pageCount;
$nav->NavRecordCount = $pageItemsTotal;
$nav->NavPageNomer = $pageNum + 1;
$navComponentObject = null;
$arSearchResult['PAGINATOR'] = $nav->GetPageNavStringEx($navComponentObject, '', 'arrows', 'N');
} else {
$arSearchResult['PAGINATOR'] = '';
}
На этом этапе собрано все необходимое для вывода результатов поиска. (Не забывайте про кеширование $arSearchResult). Следующим этапом вывод элементов.
В результате вышеприведенного кода имеем массив с элементами:
Передаем $arSearchResult['IDS'] в фильтр для CIBlockElement::GetList или в фильтр компонента bitrix:catalog.section. Кроме того, в случае использования компонента, необходимо передать ему в качестве параметра для дальнейшего использования полученный массив $arSearchResult
Далее необходимо отсортировать результаты выборки в нужном нам порядке. В случае использования компонента - в result_modifier.php используемого шаблона. Код ниже приводится на случай использования готового компонента.
if (count($arResult['ITEMS']) > 0){
$arItems = $arParams['SEARCH_ARRAY']['RANKS'];
foreach($arResult['ITEMS'] as $arItem){
$arItems[$arItem['ID']] = $arItem;
}
$arResult['ITEMS'] = $arItems;
}