~5 минут, Опубликовано 18.02.2023

Делаем сложный фильтр для новостей при помощи Bitrix D7

Делаем сложный фильтр для новостей при помощи Bitrix D7

Введение

Статья написана после того, как в реальном проекте появилась необходимость использовать сложные фильтры с большим набором условий и возможность простого масштабирования фильтрации. Было принято решение использовать ядро D7 и класс запросов Query для построения фильтра.

Какая задача решалась

Была база новостей с большим количеством различных параметром, например, таких как языковая версия новости, тип новости(Анонс, новость, мероприятие, видео и другие), отображать на определенном сайте или нет и множество других условий. Новостей было более 100 тыс., так что и производительность при выполнении запросов нужна была оптимальная.

Процесс написания фильтра

Еще одним из важных моментов было то, что данный фильтр необходимо было использоваться в разных компонентах, которые выводят новости для того, чтобы в рамкам одного сайта на главной странице и странице со списком новостей были одни и те же новости. Чтобы не было, так сказать, рассинхрона между страницами, вынесли это в модуль.

Первое, что необходимо сделать при написании нового класса, это подключить модули, с которыми будем работать. В нашем случае достаточно было подключить модуль iblock и forumedia.common (собственная разработка для более удобного использования стандартного API).

Заранее необходимо продумать все возможные варианты фильтрации и учесть их при построении логики. А так все поля и свойства, которые вам необходимы.

Обрабатываем входные параметры и значения по умолчанию

Первым делом решил написать небольшой обработчик для входящий параметров и параметров по умолчанию, которые не зависят от контента, который фильтруется. За это отвечает функция initParams. В ней мы сразу узнаем админ ли текущий пользователь или нет, а также активные новости будем достать или любые (это необходимо, так как в проекте для админов свои кнопки для редактирования новостей, а создатели новости видят в добавок только свои новости).

Читайте также  Bitrix: Популярный инструмент для управления бизнесом

Далее мы создали “справочники” возможных значений для свойств Языковая версия и Тип ресурса, на котором новость отображается и справочник Типов новости.  Так же обработали входящие значения для более удобного использования в дальнейшем.

В функции initLanguageList использовали ORM Bitrix с использованием кэша на 10 дней, так как нет смысла каждый раз получать список языков, он меняется очень редко, а может и никогда:)

Обратите внимание на обработку $params[‘SECTION’].Добавлена проверка, что мы передали ID раздела или символьный код, мы не разделяли название параметра для указания раздела на SECTION_ID и SECTION_CODE во имя своего удобства.

Думаю, суть понятна, что мы хотели сделать. Переходим к не менее  важной части – построение самого фильтра. Функция getFilter().

Строим фильтр

Тут все просто, думаю особых объяснений не требуется, если что-то не понятно, можете оставить комментарий, подскажу и помогу.

Хотелось бы отметить, что у нас реализована возможность поиска И по тегам, и по разделу, что вы можеет увидеть в коде.

return \Bitrix\Main\Entity\Query::filter() ->logic('or') ->where($this->filter) ->where($this->filterTags);

Исходный код класса Filter

class Filter{
    const IBLOCK_ID = 400;
    const PROPERTY_ELEMENT_TYPE_ID = 2454;
    const LANGUAGE_LIST_CACHE_TIME = 864000;
    const SET_DEFAULT_LANGUAGE = 'ru';

    private $filter, $filterTags, $user, $data, $list;

    public function __construct(array $params){
        Loader::includeModule('iblock');
        Loader::includeModule('forumedia.common');

        self::initParams($params);
    }

    public function getFilter():Query\Filter\ConditionTree{
        $this->filter = Query::filter();

        if($this->data->isActive){
            $this->filter->where('ACTIVE', '=', $this->data->isActive);
        }

        if(!is_null($this->data->activeFrom)){
            $this->filter->where('ACTIVE_FROM', '>=', $this->data->activeFrom);        
        }

        if(!is_null($this->data->activeTo)){
            $this->filter->where('ACTIVE_FROM', '<=', $this->data->activeTo);
        }

        $this->filter->where('ELEMENT_TYPE.VALUE', '=', $this->data->elementType);

        $this->filter->whereNotIn('UNSET_RESOURCES.VALUE', $this->data->resources->id);

        if($this->data->language->code === 'ru'){
            $this->filter->where('LANGUAGE_VERSION.VALUE', '=', 
                Query::filter()
                    ->logic('or')
                    ->whereNull('LANGUAGE_VERSION.VALUE')
                    ->where('LANGUAGE_VERSION.VALUE', $this->data->language->id)
            );
        }else{
            $this->filter->where('LANGUAGE_VERSION.VALUE', '=', $this->data->language->id);
        }

        // Фильтр только по тегам
        if($this->data->tags && empty($this->data->section)){
            $this->filter = Query::filter()->where('IBLOCK_ID', '=', self::IBLOCK_ID);
            $this->filter->whereIn('ID', \forumedia\common\iblock::searchByTags($this->data->tags, self::IBLOCK_ID));

            return $this->filter;
        }

        // Фильтра по тегу или по разделу
        if($this->data->tags && !empty($this->data->section)){			
            $this->filterTags = $this->filter->getConditions();

            $this->filterTags = Query::filter()->where('IBLOCK_ID', '=', self::IBLOCK_ID);
            $this->filterTags->whereIn('ID', \forumedia\common\iblock::searchByTags($this->data->tags, self::IBLOCK_ID));
            
            $this->filter->where($this->data->sectionType, '=', $this->data->section);

            return Query::filter()
                ->logic('or')
                ->where($this->filter)
                ->where($this->filterTags);
        }
        
        // Фильтр только по разделу
        $this->filter->where($this->data->sectionType, '=', $this->data->section);
        return $this->filter;
    }

    protected function initParams(array $params = []){
        $this->user->isAdmin = $GLOBALS['USER']->IsAdmin();
        $this->user->isGlobalModerator = \forumedia\common\subdomain::isGlobalModerator();
        $this->user->isResourcesModerator = \forumedia\common\subdomain::isModerator();

        $this->data->isActive	= (!$this->user->isGlobalModerator || ($params['ONLY_ACTIVE'] === 'Y')) ?: null;

        $this->data->activeFrom	= $params['ACTIVE_FROM'] ? \Bitrix\Main\Type\DateTime::createFromTimestamp($params['ACTIVE_FROM']) : null;
        $this->data->activeTo	= $params['ACTIVE_TO']   ? \Bitrix\Main\Type\DateTime::createFromTimestamp($params['ACTIVE_TO'])   : null;

        self::initElementTypeList();
        $this->data->elementType    = $this->list->elementType->{$params['ELEMENT_TYPE']}  ?: $this->list->elementType->news; //default is element type - news

        if($params['SECTION']){
            if(!is_numeric($params['SECTION'])){
                $this->data->section = $params['SECTION'];
                $this->data->sectionType = 'IBLOCK_SECTION.CODE';
            }else{
                $this->data->section = intval($params['SECTION']);
                $this->data->sectionType = 'IBLOCK_SECTION.ID';
            }
        }

        self::initLanguageList();
        $langCode = ($params['LANGUAGE'] ?: \LANGUAGE_ID) ?: self::SET_DEFAULT_LANGUAGE;
        $this->data->language->id = $this->list->languages->{$langCode};
        $this->data->language->code = $langCode;
        
        $this->data->resources->id = self::initResources($params['RESOURCES']);
        
        if($params['FILTER_TAGS'] && is_array($params['FILTER_TAGS']))
           $this->data->tags = $params['FILTER_TAGS'];
    }

    protected function initElementTypeList(){
        if(empty($this->list->elementType)){
            $collection = \Bitrix\Iblock\PropertyEnumerationTable::getList(array(
                'filter' => array('PROPERTY_ID' => self::PROPERTY_ELEMENT_TYPE_ID)
            ))->fetchCollection();
    
            foreach ($collection as $key => $enum) {
                $this->list->elementType->{$enum->getXmlId()} = $enum->getId();
            }
        }
    }

    protected function initLanguageList(){
        if(empty($this->list->languages)){
            $collection = \Bitrix\Iblock\Elements\ElementLanguageTable::getList([
                'select' => ['ID', 'CODE'],
                'filter' => ['=ACTIVE' => true],
                'cache' => ['ttl' => self::LANGUAGE_LIST_CACHE_TIME] //10 дней
            ])->fetchCollection();
    
            foreach ($collection as $language) {
                $this->list->languages->{$language->getCode()} = $language->getId();
            }
        }
    }

    protected function initResources(string $params = null):string{
        return (\forumedia\common\subdomain::getCurrentResources($params))['ID'];
    }

    public static function getDefaultSelect():array{
        return [
            'ID',
            'NAME',
            'CODE',
            'PREVIEW_PICTURE',
            'DETAIL_PICTURE',
            'DATE_CREATE',
            'ACTIVE_FROM',
            'ACTIVE',
            'SECTION_CODE' => 'IBLOCK_SECTION.CODE',
            'TYPE' => 'ELEMENT_TYPE.VALUE',
            'DATE_ANONCE_START_' => 'DATE_ANONCE_START.VALUE',
            'UNSET_RESOURCES_' => 'UNSET_RESOURCES.VALUE',
            'LANGUAGE_VERSION_' => 'LANGUAGE_VERSION.VALUE',
            'IBLOCK_SECTION_ID_' => 'IBLOCK_SECTION.ID',
            'IBLOCK_ID',
            'PREVIEW_TEXT',
            'DETAIL_TEXT',
            'TAGS'
        ];
    }

    public static function getDefaultCache():array{
        return [
            'ttl' => 600,
            'cache_joins' => true,
        ];
    }
}

 

Скачать этот класс вы можете внизу, файл в архиве.

Категории: Bitrix Bitrix D7

Прикрепленные файлы