Наверняка, многие знают, что в битриксе есть морфологический поиск. И вроде бы он даже худо-бедно работает. Но если мы торгуем товарами, и хотим, чтобы поиск искал и по части чего-то специфического, например, артикула товара (пример: артикул «Р6543», подстрокой будет «6543»), то ничего из этого не выйдет. Поиск тупо ничего не найдёт.

В этой заметке покажу способ, как можно эту проблему решить.

Суть

В момент индексации элемента будем добавлять в таблицу b_search_content_stem наши части слов. То есть, в нашем случае, для товара с артикулом «Р6543» мы добавим «6543» в поисковый индекс. Запросы «654» и «65» так же будут отдавать товар с артикулом «Р6543».

Обработчики — наше всё

Как обычно, решать мы будем обработчиками. Оффтоп: иногда вижу проекты вообще без файлов init.php и своих кастомных модулей. Ребята, как вам удаётся без этого сайты собирать?

Будем слушать 3 события: BeforeIndex, OnBeforeIndexUpdate и OnAfterIndexAdd.

  • BeforeIndex — вызывается перед индексацией элемента методом CSearch::Index()
  • OnBeforeIndexUpdate — вызывается перед обновлением поискового индекса
  • OnAfterIndexAdd — вызывается после добавления новых данных в поисковый индекс.

На событие BeforeIndex мы проверим, что в индекс летит нужная нам запись и сохраним в статическую переменную данные, которые нам понадобятся в последующих событиях. По другому их там не достать.

На события OnBeforeIndexUpdate и OnAfterIndexAdd запишем в таблицу b_search_content_stem то, что нам нужно. API для этого нет, так что будем писать чистые SQL-запросы. Проверял на MySQL с InnoDB таблицей. На остальных не факт, что будет корректно работать

Код обработчика

Сохраняем код в файле /local/php_interface/classes/handlers/search/stemming.php

В свойстве $allowedIblockId класса необходимо указать ID инфоблоков, для которых нужен функционал.

<?php
/**
 * Created by olegpro.ru
 * User: Oleg Maksimenko <oleg.39style@gmail.com>
 */
 
namespace Olegpro\Classes\Handlers\Search;
 
use Bitrix\Main\Loader;
use Bitrix\Main\Application;
use Bitrix\Main\DB\SqlQueryException;
 
class Stemming
{
 
    /**
     * @var array
     */
    protected static $allowedIblockId = array(
        1
    );
 
    /**
     * @var
     */
    private static $element;
 
    /**
     * @param array $arFields
     * @return mixed
     */
    public static function beforeIndex($arFields)
    {
        static $allowedIblockId = null;
 
        if ($allowedIblockId === null) {
            $allowedIblockId = array_flip(self::$allowedIblockId);
        }
 
        if (
            $arFields['MODULE_ID'] == 'iblock'
            && isset($allowedIblockId[$arFields['PARAM2']])
            && strlen($arFields['ITEM_ID']) > 0
            && substr($arFields['ITEM_ID'], 0, 1) != 'S'
            && Loader::includeModule('iblock')
        ) {
            if ($obElement = \CIBlockElement::GetList(
                array(),
                array('ID' => $arFields['ITEM_ID']),
                false,
                false,
                array()
            )->GetNextElement()
            ) {
 
                $element = $obElement->GetFields();
 
                $element['PROPERTIES'] = $obElement->GetProperties();
 
                self::$element = $element;
 
            }
 
        }
 
        return $arFields;
 
    }
 
 
    /**
     * Events: OnBeforeIndexUpdate and OnAfterIndexAdd
     * @param $indexId
     * @param $arFields
     */
    public static function beforeIndexUpdate($indexId, $arFields)
    {
 
        if (
            isset(self::$element) && is_array(self::$element)
            && isset(self::$element['PROPERTIES']['ARTICLE'])
            && strlen(trim(self::$element['PROPERTIES']['ARTICLE']['VALUE']))
            && preg_match('~^.*?([0-9.,]+).*?$~', trim(self::$element['PROPERTIES']['ARTICLE']['VALUE']), $m)
        ) {
 
            $word = ToUpper($m[1]);
 
            $stemId = \CSearch::RegisterStem($word);
 
            if ($stemId > 0) {
 
                $connection = Application::getConnection();
 
                $sqlHelper = $connection->getSqlHelper();
 
                try {
 
                    $thereIs = $connection->queryScalar(
                        sprintf(
                            "SELECT 1 FROM b_search_content_stem WHERE SEARCH_CONTENT_ID = '%s' AND STEM = '%s'",
                            $sqlHelper->forSql($indexId),
                            \CSearch::RegisterStem($word)
                        )
                    );
 
                    if ($thereIs === null) {
                        $connection->query(sprintf(
                            "INSERT IGNORE INTO `b_search_content_stem` (`SEARCH_CONTENT_ID`, `LANGUAGE_ID`, `STEM`, `TF`, `PS`) VALUES ('%s', '%s', '%s', '%s', '%s')",
                            $sqlHelper->forSql($indexId),
                            'ru',
                            \CSearch::RegisterStem($word),
                            number_format(0.2, 4, ".", ""),
                            number_format(1, 4, ".", "")
                        ));
                    }
 
                } catch (SqlQueryException $e) {
                    AddMessage2Log(sprintf("\\%s:\n%s", __METHOD__, $e->getMessage()));
                }
 
            }
 
        }
 
        self::$element = null;
 
    }
 
}

Регистрируем обработчики и добавляем класс в автозагрузку

Добавить в /local/php_interface/init.php

<?php
 
\Bitrix\Main\EventManager::getInstance()->addEventHandler('search', 'BeforeIndex',
    array('\Olegpro\Classes\Handlers\Search\Stemming', 'beforeIndex')
);
 
\Bitrix\Main\EventManager::getInstance()->addEventHandler('search', 'OnBeforeIndexUpdate',
    array('\Olegpro\Classes\Handlers\Search\Stemming', 'beforeIndexUpdate')
);
 
\Bitrix\Main\EventManager::getInstance()->addEventHandler('search', 'OnAfterIndexAdd',
    array('\Olegpro\Classes\Handlers\Search\Stemming', 'beforeIndexUpdate')
);
 
\Bitrix\Main\Loader::registerAutoLoadClasses(null, array(
    '\Olegpro\Classes\Handlers\Search\Stemming' => '/local/php_interface/classes/handlers/search/stemming.php',
));

На этом всё. Осталось только запустить полную переиндексацию и теперь можно не тратить жизнь на вписывание в поиск артикулов полностью.

P.S.

Для своих частных случаев код придётся, конечно, поправить под себя.