В Битриксе существует достаточно интересная система ранжирования результатов поиска, однако на одном из проектов был замечена не очень эффективная сортировка.
Чтобы воспроизвести данную проблему необходимо выполнить следующие действия:
В результате открывается страница с компонентом 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; }