Если товара нет в наличии, покажем похожие товары

Задача

Проблема

Бывает так, что товар заканчивается на складе или отсутствует.

Покупатель заходит на страницу товара в надежде купить, а видит «Нет в наличии».

Покупатель может расстроиться.

Решение

Мы поможем ему не расстраиваться.

Предложим покупателю аналогичные товары из той же категории или товары за туже цену.

Стандартная функциональность

Платформа CS-Cart и Multi-Vendor позволяет создавать блоки с различным содержимым в любом месте интернет-магазина.

Один из таких блоков — «Товары».

Блок «Товары» имеет различные настройки, например:

  • Шаблоны — различные виды отображения товаров в блоке на витрине (сетка, список, мелкие элементы, скроллер и т.д.).
  • Содержимое — вы можете настроить заполнение блока. Другими словами, отфильтровать товары которые будут отображены в блоке:
    • Вручную — ручной выбор товаров для отображения
    • Новые поступления
    • Недавно просмотрено
    • Самый популярны
    • Хиты продаж

Вы можете создать блок «Товары» для детальной страницы товара (карточки товара), который будет отображать последние поступления товаров из той же категории.

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

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

Что будем делать?

  1. Добавим новую настройку «Отобразить блок, если основного товара нет на складе» для блока «Товары». Данная настройка будет работать, если блок размещён на странице (карточке) товара. Блок будет показан, если товара нет на складе.

  2. Добавим новый тип заполнения блока: Похожие товары

    Данный тип будет иметь две настройки:

    • Диапазон цены в % — можно создать блок с товарами близкими по стоимости.
    • Аналогичная категория — отобразить товары из той же категории.

Плюс

Можно использовать все возможности блока «Товары» и расположить блок в любом месте. Когда покупатель зайдёт на страницу товара, которого в настоящий момент нет на складе, он увидит похожие товары.

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

Подготовка

Отключим кэш блоков на время разработки

Все блоки кэшируются, кэшируются очень хорошо (шаблоны и php скрипты) и это помогает сильно ускорить загрузку страницы.

Отключим кэширование блоков на время разработки.

  1. Откройте файл config.local.php в основном каталоге интернет-магазина на веб-сервере.

  2. Найдите строчку:

    'disable_block_cache' => false, // used to disable block cache
    
  3. Замените на:

    'disable_block_cache' => true, // used to disable block cache
    
  4. Удалите папку var/cache, чтобы полностью удалить старый кэш.

Всё.

Анализ и планирование модификации

Мы будем модифицировать блок «Товары». Данный блок имеет:

  1. PHP Схему блока — описывает свойства и настройки блока.
  2. PHP функцию — которая получает товары для отображения.
  3. Smarty шаблон — отображает товары на витрине.

Нам нужно добавить новые настройки, значит нужна схема.

Нам нужно изменить выборку (получение) товаров, значит нужна Php функция, которая эти товары получает.

Нам не нужно изменять дизайн, поэтому мы не будем трогать Smarty шаблоны.

Найдём php схему блока «Товары»

Все схемы платформы расположены в папке:

/app/schemas

Cхемы связанные с блоками расположены в папке:

/app/schemas/block_manager

Главные схемы блоков хранятся в файле:

/app/schemas/block_manager/blocks.php

  1. Откройте файл /app/schemas/block_manager/blocks.php и найдите схему блока «Товары».

    — Как определить какая схема нужна?

    — Логически, исходя из типа блока.

    — Есть более точный способ?

    — Да. Откройте окно создания нового блока в панели администратора. В коде блока «Товары» указан его тип:

    <input type="hidden" name="block_data[type]" value="products">

  2. Рассмотрим схему которая отвечает за товарный блок:

        'products' => array (
            'content' => array (
                'items' => array (
                    'type' => 'enum',
                    'object' => 'products', 
                    'items_function' => 'fn_get_products',  
                    'remove_indent' => true,
                    'hide_label' => true,
                    'fillings' => array ( 
                        /*
                         ..... Список типов заполнения содержимого
                        */ 
                    ),
                ),
            ),
            'templates' => 'blocks/products',
            'settings' => array(
                'hide_add_to_cart_button' => array (
                    'type' => 'checkbox',
                    'default_value' => 'Y'
                )
            ),
            'wrappers' => 'blocks/wrappers',
            'cache' => array(
                // ..... Параметры кэширования блока
            )
        ),
    

    Значения которые нам понадобятся:

    Ключ Значение
    items_function Функция которая получает товары для отображения в блоке: fn_get_products .
    fillings

    Список видов заполнения (фильтров) товаров в блоке. Содержат параметры, которые передаются в функцию для получения товаров

    • manually — Вручную
    • newest — Новые поступления
    • recent_products — Недавно просмотрено
    • most_popular — Самый популярный
    settings

    Настройки блока.

    hide_add_to_cart_button — настройка “Спрятать кнопку добавления товара в корзину”

Схема найдена.

Найдём php функцию которая получает товары для блока

Функция указана ячейке items_function схемы блока. В нашем случае fn_get_products.

Все основные функции платформы находятся в папке:

app/functions/

Наша функция работает с товарами, значит лежит в файле:

app/functions/fn.catalog.php

  1. Открываем файл app/functions/fn.catalog.php и находим функцию fn_get_products.

    function fn_get_products($params, $items_per_page = 0, $lang_code = CART_LANGUAGE)
    {
        /**
         * Changes params for selecting products
         *
         * @param array  $params         Product search params
         * @param int    $items_per_page Items per page
         * @param string $lang_code      Two-letter language code (e.g. 'en', 'ru', etc.)
         */
        fn_set_hook('get_products_pre', $params, $items_per_page, $lang_code);
    
  2. Добавим в начале функции fn_print_r($params); для проверки и перезагрузим страницу товара.

    function fn_get_products($params, $items_per_page = 0, $lang_code = CART_LANGUAGE)
    {
        fn_print_r($params);
    

    Как видите в функции fn_get_products доступны все параметры и настройки продуктового блока.

  3. В самом начале функции находится хук:

        fn_set_hook('get_products_pre', $params, $items_per_page, $lang_code);
    

    Это значит, что мы можем внести изменения в работу функции с помощью модуля.

    Примечание

    Функция fn_get_products используется для получения товаров в различных блоках, странице категории, странице поиска и фильтра. Поэтому очень важно не вмешиваться в её работу на прямую.

Однако данная функция нам не поможет скрыть блок, если товар есть на складе. Другими словами, мы можем расширить её работу и повлиять на товары которые будут в блоке, но не сможем выключить блок полностью.

Будем искать глубже, найдём место где это функция выполняется, возможно там можно выключить блок.

Найдём место где подключаются блоки и функции блоков

Будем отталкиваться от цели. Найдём место, где включаются и выключаются блоки.

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

Искать будем в папках с функциями и классами ядра платформы:

  • app/functions — функции платформы
  • app/Tygh — классы платформы
  1. Сделаем поиск по содержимому файлов, с помощью текстового редактора или файлового менеджера.

    В папке app/functions не встречается items_function

    Поищем в папке app/Tygh.

    Найден один файл: app/Tygh/BlockManager/Block.php

  2. Открываем файл app/Tygh/BlockManager/Block.php.

    items_function используется в функции getItems класса Blocks.

    К сожалению, данная функция не имеет хуков, поэтому мы не сможем перехватить выполнение функции.

    Будем искать глубже.

  3. Найдем место где выполняется функция getItems. В этом же файле данная функция не выполняется, поэтому ищем в других.

    Искать начнём в той же папке: app/Tygh/BlockManager/ .

    Найден один файл app/Tygh/BlockManager/RenderManager.php .

  4. Открываем файл app/Tygh/BlockManager/RenderManager.php и находим место выполнения функции getItems.

    Это функция getValue класса RenderManager со следующим кодом:

            if ($field['type'] == 'enum') {
                $value = Block::instance()->getItems($template_variable, $block, $block_scheme);
            }
    

    Хуков опять нет. Ищем дальше. Искать начинаем с этого же файла.

  5. Делаем поиск по файлу RenderManager.php и находим.

    Находим хук прямо перед выполнением функции getValue.

    fn_set_hook('render_block_content_pre', $template_variable, $field, $block_scheme, $block);
    $value = self::getValue($template_variable, $field, $block_scheme, $block);tance()->getItems($template_variable, $block, $block_scheme);
    

    Отлично, есть хук, в котором есть параметры блока. Однако раз мы зашли далеко, углубимся ещё чуть чуть.

  6. Функция getValue выполняется в функции renderBlockContent , которая содержит ещё один хук:

    fn_set_hook('render_block_register_cache', $block, $cache_name, $block_scheme, $register_cache, $display_block);
    

    Судя по названию данный хук выполняется перед активацией кэша блока. Идём дальше.

  7. Найдём в этом же файле место где выполняется функция renderBlockContent.

    Находится функция renderBlock, которая не содержит хуков. Ищем где выполняется данная функция.

  8. Удача! Находится функция renderBlocks , которая выполняет renderBlock в цикле по всем блокам и перед выполнением содержит хук.

    fn_set_hook('render_blocks', $grid, $block, $this, $content);
    
    if ($this->_area == 'C' && $block['status'] == 'D') {
        // Do not render block in frontend if it disabled
        continue;
    }
    
    $content .= self::renderBlock($block, $grid, $this->_area);
    

    В этом маленьком кусочке кода, так много полезного:

    а. Хук с информацией о блоке.

    б. Условие «Если блок выключен, то пропускаем» после хука.

    Это значит, что можно модулем изменить $block['status'] и тем самым выбирать, когда показывать блок, а когда нет.

  9. Проверям, добавив fn_print_r($block); после хука 'render_blocks' .

    fn_set_hook('render_blocks', $grid, $block, $this, $content);
    
    fn_print_r($block);
    
    if ($this->_area == 'C' && $block['status'] == 'D') {
        // Do not render block in frontend if it disabled
        continue;
    }
    

    Смотрим результат на странице товара в браузере:

    Распечатаются все блоки, однако найти наш легко по названию.

  10. Отлично, нужный хук найден. Убираем fn_print_r($block);.

Итоги анализа

  1. Мы нашли схему которая отвечает за работу блока «Товары» и с помощью которой у блока появляются настройки.

    Нужная схема расположена в файле app/schemas/block_manager/blocks.php

    Мы будем расширять данную схему с помощью модуля, чтобы добавить новые настройки и тип заполнения.

  2. Мы нашли функцию fn_get_products которая получает товары для блока.

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

    fn_set_hook('get_products_pre', $params, $items_per_page, $lang_code);
    
  3. Мы нашли функцию которая подключает блоки и хук в ней

    fn_set_hook('render_blocks', $grid, $block, $this, $content);
    

    С помощью данного хука мы будем включать и выключать блок, в зависимости от наших условий.

Приступаем к расширению функционала.

Модификация

Всю разработку будем выполнять с помощью модуля «Мои изменения».

ID модуля — my_changes.

Расширим схему блока «Товары»

Добавим новую настройку «Отобразить блок, если основного товара нет на складе».

Мы выяснили, что схема расположена в файле app/schemas/block_manager/blocks.php.

Для расширения схем с помощью модуля нам нужно сделать аналогичную структуру в папке модуля:

Основная схема app/schemas/block_manager/blocks.php
Расширение app/addons/[ID_модуля]/schemas/block_manager/blocks.post.php

Всё очень логично.

  1. Откройте папку app/addons/my_changes.

    Создайте в ней папку schemas для расширения схем и пройдите в неё.

    Создайте папку block_manager для расширения схем менеджера блоков.

    Создайте файл blocks.post.php, где .post означает что файл будет запущен после основной схемы.

    В результате у нас получился файл:

    app/addons/my_changes/schemas/block_manager/blocks.post.php

  2. Наша цель подключится и проверить работу.

    Откройте только что созданный файл blocks.post.php и вставьте проверочный код.

    <?php
    
    fn_print_r('Проверяем подключение к схеме', $schema['products']);
    

    Установите и включите модуль «Мои изменения».

    Проверьте результат подключения к схеме в браузере на странице товара.

    На экране распечатается контрольная фраза и схема продуктового блока.

    Отлично!

  3. Расширим схему, добавив новую настройку.

    Настройки хранятся в ячейке settings схемы блока, в настоящий момент там только одна настройка hide_add_to_cart_button. Склонируем её изменив ключ.

    Добавим в схему новую настройку if_out_of_stock.

    <?php
    
    $schema['products']['settings']['if_out_of_stock'] = array (
            'type' => 'checkbox',
            'default_value' => 'Y'
        );
    
    fn_print_r('Проверяем подключение к схеме', $schema['products']);
    
    return $schema;
    

    Я просто скопировал hide_add_to_cart_button из основной схемы blocks.php

    Проверяем в браузере:

    Ячейка settings расширилась и появилась ещё одна настройка.

    Настройка должна появится в настройках блока в панели администратора.

  4. Проходим в настройки блока «Товары» в панели администратора. Обязательно уберите fn_print_r из модуля.

    Настройка появилась, однако:

    а. Нет языковой переменной и соответственно не отображается название.

    б. Она включена по умолчанию. Данная настройка будет скрывать блок, если товар есть в наличии, а это нужно не всегда.

  5. Сделаем так, чтобы настройка была выключена по умолчанию, установим 'default_value' => 'N' в расширении схемы.

    <?php
    
    $schema['products']['settings']['if_out_of_stock'] = array(
            'type' => 'checkbox',
            'default_value' => 'N'
        );
    
    return $schema;
    
  6. Добавим новую языковую переменную if_out_of_stock для новой настройки.

    Языковую переменную будем добавлять с помощью модуля который выполняет расширение.

    Откройте файл app/addons/my_changes/addon.xml

    Добавьте следующий код:

        <language_variables>
            <item lang="ru" id="if_out_of_stock">Отобразить блок, если основного товара нет на складе</item>
            <item lang="en" id="if_out_of_stock">Display block if the product is out of stock.</item>
        </language_variables>
    
    </addon>
    

    Примечание

    Переводы модулей устанавливаются при установке модуля. Для обновления, нужно переустановить модуль.

  7. Переустановите модуль и проверьте результат в настройках модуля.

    Отлично! Правильное название настройки появилось.

Заставим настройку работать.

Расширим функцию с помощью хука

Мы сделали новую настройку. Теперь нужно добавить функционал для настройки.

Наша цель выключить блок, до тех пор пока товар не закончится на складе.

Мы нашли функцию которая связана с обработкой блоков и имеет хук

fn_set_hook('render_blocks', $grid, $block, $this, $content);

Каждый хук в php имеет:

  • render_blocks — название хука.
  • $grid, $block, $this, $content — переменные доступные для использования и расширения.

Подключимся к данному хуку с помощью модуля.

  1. Необходимо активировать подключение к хуку в модуле.

    Создайте новый файл:

    app/addons/my_changes/init.php

    Вставьте код активации хука:

    <?php
    
    if (!defined('BOOTSTRAP')) { die('Access denied'); }
    
    fn_register_hooks(
        'render_blocks'
    );
    

    Как вы можете заметить, мы передали название хука в функцию fn_register_hooks . Можно активировать несколько хуков, если это требуется, перечислив их названия через запятую.

  2. Создадим функцию, которая подключится к хуку и позволит выполнить новый php код.

    Создайте новый файл:

    app/addons/my_changes/func.php

    Создайте в нём специальную функцию.

    Принцип создания функций для подключения к PHP хукам в таблице.

    Хук fn_set_hook('[название_хука]', [переменная_1], [переменная_2]);
    Шаблон fn_[id_модуля]_[название_хука] ([переменная_1], [переменная_2])

    Так как нам нужно подключится к хуку:

    fn_set_hook('render_blocks', $grid, $block, $this, $content);

    То нужно создать функцию:

    fn_my_changes_render_blocks($grid, &$block, $this, $content)

  3. Получаем такой код в файле app/addons/my_changes/func.php :

    <?php
    
    if (!defined('BOOTSTRAP')) { die('Access denied'); }
    
    function fn_my_changes_render_blocks($grid, &$block, $this, $content)
    {
        fn_print_r($block['name']);
    }
    

    Добавляем fn_print_r($block['name']); для проверки подключения и проверяем в браузере.

    На экране распечатались названия блоков. Хук работает!

  4. Добавим условие, чтобы отображать информацию только о нужном блоке и отобразим всё содержимое $block.

    <?php
    
    if (!defined('BOOTSTRAP')) { die('Access denied'); }
    
    function fn_my_changes_render_blocks($grid, &$block, $this, $content)
    {
        if ($block['name'] == 'Похожие товары') {
    
            fn_print_r($block);
    
        }
    }
    
  5. Включим новую настройку в панели администратора.

    Проверяем результат на витрине.

    В результате мы видим:

    • Новую настройку и её значение.
    • ID товара в настоящий момент.
    • Локацию (тип страницы) на витрине.
  6. Добавим логику в расширение хука.

    Для начала, сделаем так:

    • Если открыта витрина, а не панель администратора.
    • Если у блока есть наша настройка, то блок всегда скрыт.
    • Если это страница товара, то распечатаем количество товара.
    <?php
    
    use Tygh\Registry;
    
    if (!defined('BOOTSTRAP')) { die('Access denied'); }
    
    function fn_my_changes_render_blocks($grid, &$block, $this, $content)
    {
        // Если мы на витрине витрина.
        if (AREA == 'C') {
            // Если у блока есть нужная настройка
            if (!empty($block['properties']['if_out_of_stock']) && $block['properties']['if_out_of_stock'] == 'Y') {
    
                // Выключаем блок
                $block['status'] = 'D';
    
                // Если в настоящий момент открыта страница товара
                if ($block['object_type'] == 'products') {
                    // Получаем информацию о товаре. Так как блоки подключаются после контроллера, то контроллер уже передал информацию о товаре в Smarty.
                    $product = Registry::get('view')->getTemplateVars('product');
    
                    // Если информация о товаре существует
                    if (!empty($product['product_id'])) {
    
                        fn_print_r($product['amount']);
    
                    }
                }
            }
        }
    }
    

    Проверяем.

    Количесто товара на складе есть, блока нет. Отлично.

  7. Сделаем так, чтобы блок включался, если товара нет в наличии.

    Для этого добавим два условия:

    • Если количество товара меньше или равно нуля.
    • Если товар имеет статус «Скрытый»

    Если условия проходят, то ставим блоку статус «Активный».

    <?php
    
    use Tygh\Registry;
    
    if (!defined('BOOTSTRAP')) { die('Access denied'); }
    
    function fn_my_changes_render_blocks($grid, &$block, $this, $content)
    {
        // Если мы на витрине витрина.
        if (AREA == 'C') {
            // Если у блока есть нужная настройка
            if (!empty($block['properties']['if_out_of_stock']) && $block['properties']['if_out_of_stock'] == 'Y') {
    
                // Выключаем блок
                $block['status'] = 'D';
    
                // Если в настоящий момент открыта страница товара
                if ($block['object_type'] == 'products') {
                    // Получаем информацию о товаре. Так как блоки подключаются после контроллера, то контроллер уже передал информацию о товаре в Smarty.
                    $product = Registry::get('view')->getTemplateVars('product');
    
                    // Если информация о товаре существует
                    if (!empty($product['product_id'])) {
    
                        if ($product['amount'] <= 0 || $product['status'] == 'H') {
                            $block['status'] = 'A';
                        } 
    
                    }
                }
            }  
        }
    
    }
    
  8. Проверяем.

    Ставим для тестового товара 0 на складе и смотрим товар на витрине.

    Есть на складе Нет на складе

Успех! Новая настройка блока работает по нашим условиям и правилам.

Добавим новые типы заполнения в схему для блока «Товары»

Добавим новый тип заполнения: Похожие товары

Данный тип будет иметь четыре настройки:

  • Аналогичная категория
  • Учитывать подкатегории
  • Диапазон цены в %
  • Лимит — количество товаров в блоке
  1. Откройте файл расширяющий схему блока

    app/addons/my_changes/schemas/block_manager/blocks.post.php

    Распечатайте схему продуктового блока: fn_print_r($schema['products']);

    Типы заполнения описаны в ячейке fillings .

    Распечатайте только её

    fn_print_r($schema['products']['content']['items']['fillings']);
    

    Нам нужно добавить аналогичный тип со своим названием и условиями.

  2. Расширим схему, добавив новый тип similar_products

    <?php
    
    $schema['products']['settings']['if_out_of_stock'] = array (
        'type' => 'checkbox',
        'default_value' => 'N'
    );
    
    $schema['products']['content']['items']['fillings']['similar_products'] = array (
        'params' => array (
            'similar_products' => true,
            'request' => array (
                'main_product_id' => '%PRODUCT_ID%'
            )
        )
    );
    
    return $schema;
    

    Чтобы создать новый, был скопирован тип recent_products, и убрано всё лишнее.

    Для типа будет доступен один параметр main_product_id, значение которого будет соответствовать product_id товара на странице которого расположен блок. Возможно это пригодится, пока оставим.

    Проверим в настройках блока.

    Новый тип пока не содержит настроек, сейчас мы их добавим.

  3. Настройки типов заполнения хранятся в схеме app/schemas/block_manager/fillings.php

    Расширим её в нашем модуле аналогично расширению схемы blocks.php

    Создайте новый файл:

    app/addons/my_changes/schemas/block_manager/fillings.post.php

    Добавьте новые настройки для типа заполнения similar_products:

    <?php
    
    $schema['similar_products'] = array (
        'limit' => array (
            'type' => 'input',
            'default_value' => 3,
        ),
        'price_range' => array (
            'type' => 'input',
            'unset_empty' => true, // Параметр не используется, если пустой.
        ),
        'similar_category' => array (
            'type' => 'checkbox',
            'default_value' => 'Y'
        ),
        'similar_subcat' => array (
            'type' => 'checkbox',
            'default_value' => 'Y'
        ),
    );
    
    return $schema;
    

    Примечание

    Аналогичный принцип расширения реализован в модуле «Хиты продаж» (bestsellers)

  4. Проверяем результат в настройках блока

    Примечание

    Может понадобится грубая очистка кэша, удалением папки var/cache

  5. Создаём три новые языковые переменные для модуля «Мои изменения»

    Откройте файл app/addons/my_changes/addon.xml

    Добавьте следующий код:

        <language_variables>
            <item lang="ru" id="if_out_of_stock">Отобразить блок, если основного товара нет на складе</item>
            <item lang="en" id="if_out_of_stock">Display block if the product is out of stock.</item>
    
            <item lang="ru" id="similar_products">Похожие товары</item>
            <item lang="en" id="similar_products">Related Products</item>
    
            <item lang="ru" id="similar_category">Аналогичная категория</item>
            <item lang="en" id="similar_category">Similar category</item>
    
            <item lang="ru" id="similar_subcat">Учитывать подкатегории</item>
            <item lang="en" id="similar_subcat">Use subcategories</item>
    
            <item lang="ru" id="price_range">Диапазон цены (в % от стоимости основного товара)</item>
            <item lang="en" id="price_range">Price range (%)</item>
        </language_variables>
    

    Переустановите модуль и проверьте результат.

Отлично! Мы добавили новый тип заполнения.

Сохраняем настройки блока и переходим на витрину. Нужно заставить блок отображать товары в соответствии с новым типом заполнения.

Расширим функцию получения товаров для работы с новыми типами отображения

Мы добавили новые настройки блоку «Товары», добавим логику для их правильной работы.

Ранее мы нашли функцию fn_get_products которая получает товары для блока.

Данная функция имеет хуки, поэтому мы сможем расширить функцию с помощью модуля.

Мы подключимся к хуку в функции и добавим функционал для фильтрации «Похожих товаров» учитывая новые настройки.

Нужная нам функция (fn_get_products) расположена в файле app/functions/fn.catalog.php.

  1. Откроем файл app/functions/fn.catalog.php и найдём функцию fn_get_products.

    В самом начале функции есть хук:

        fn_set_hook('get_products_pre', $params, $items_per_page, $lang_code);
    

    Подключимся к данному хуку.

  2. Откройте файл app/addons/my_changes/init.php и добавьте название хука в функцию активации.

    <?php
    
    if (!defined('BOOTSTRAP')) { die('Access denied'); }
    
    fn_register_hooks(
        'render_blocks',
        'get_products_pre'
    );
    

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

  3. Откройте файл app/addons/my_changes/func.php и добавьте функцию для подключения к хуку:

    function fn_my_changes_get_products_pre(&$params, $items_per_page, $lang_code)
    {
        fn_print_r($params);
    }
    

    Также добавлена fn_print_r($params); для проверки и просмотра массива $params.

    Проверяем результат на странице товара.

    Отлично! Мы можем использовать в хуке новые настройки и изменить логику получения товаров для блока.

  4. В первую очередь добавим:

    • Так как хуки срабатывают всегда, когда выполняется функция fn_get_products , нужно добавить проверку, что это именно блок «Похожие товары»
    • Если блок отображается, значит текущего товара нет в наличии, поэтому мы исключим ID текущего товара из запроса.
    function fn_my_changes_get_products_pre(&$params, $items_per_page, $lang_code)
    {
        // Если это блока «Похожие товары»
        if (!empty($params['similar_products']) && $params['similar_products'] == 'Y') {
    
            // Получаем данные об основном товаре страницы.
            $product = Registry::get('view')->getTemplateVars('product');
    
            // Исключим основной товар из выборки.
            if (!empty($params['main_product_id'])) {
                $params['exclude_pid'] = $params['main_product_id'];
            }
    
        }
    }
    

    Если вы посмотрите на функцию fn_get_products, то заметите её гибкость.

    Функция по частичкам собирает запрос к базе данных исходя из переменной $params. Нам достаточно указать правильные параметры и на выходе мы получим нужные товары.

    Например, часть функции fn_get_products

    if (!empty($params['exclude_pid'])) {
        $condition .= db_quote(' AND products.product_id NOT IN (?n)', $params['exclude_pid']);
    }
    

    Если добавить ID товаров в параметр $params['exclude_pid'], то данные товары будут исключены.

  5. Добавим обработку для настройки «Аналогичная категория»

            // Обработка для настройки «Аналогичная категория»
            if (!empty($params['similar_category']) && $params['similar_category'] == 'Y') {
                $params['cid'] = $product['main_category'];
    
                // Обработка для настройки «Использовать подкатегории»
                if (!empty($params['similar_subcat']) && $params['similar_subcat'] == 'Y') {
                    $params['subcats'] = 'Y';
                }
            }
    

    Проверяем.

    Было Стало

    Настройка работает.

  6. Добавим логику для настройки «Диапазон цены».

            // Обработка для настройки «Диапазон цен»
            if (!empty($product['price']) && $product['price'] > 0 && !empty($params['price_range']) && $params['price_range'] > 0) {
    
                $range = $product['price'] / 100 * $params['price_range'];
    
                $params['price_from'] = $product['price'] - $range;
                $params['price_to'] = $product['price'] + $range;
            }
    

    Проверяем.

    Было Стало

    Как видите, отобразился только товар с такой же ценой.

    К сожалению, в моём примере используется всего несколько товаров и одна категория, однако это будет отлично работать везде.

  7. Добавим самое важное условие. Показывать в блоке только товары которые есть на складе.

    // Отображать товары с количеством больше 1
    $params['amount_from'] = 1 ;
    
  8. В результате у нас получилась такая функция:

    function fn_my_changes_get_products_pre(&$params, $items_per_page, $lang_code)
    {
        if (!empty($params['similar_products']) && $params['similar_products'] == 'Y') {
    
            // Получаем данные об основном товаре страницы.
            $product = Registry::get('view')->getTemplateVars('product');
    
            // Исключим основной товар из выборки.
            if (!empty($params['main_product_id'])) {
                $params['exclude_pid'] = $params['main_product_id'];
            }
    
            // Обработка для настройки «Аналогичная категория»
            if (!empty($params['similar_category']) && $params['similar_category'] == 'Y') {
                $params['cid'] = $product['main_category'];
    
                // Обработка для настройки «Использовать подкатегории»
                if (!empty($params['similar_subcat']) && $params['similar_subcat'] == 'Y') {
                    $params['subcats'] = 'Y';
                }
            }
    
            // Обработка для настройки «Диапазон цен»
            if (!empty($product['price']) && $product['price'] > 0 && !empty($params['price_range']) && $params['price_range'] > 0) {
    
                $range = $product['price'] / 100 * $params['price_range'];
    
                $params['price_from'] = $product['price'] - $range;
                $params['price_to'] = $product['price'] + $range;
            }
    
            // Отображать товары с количеством больше 1
            $params['amount_from'] = 1 ;
        }
    }
    

Всё! Модуль готов и настройки блока готовы.

Включите кэш блоков

  1. Откройте файл config.local.php в основном каталоге интернет-магазина на веб-сервере.

  2. Найдите строчку:

    'disable_block_cache' => true, // used to disable block cache
    
  3. Замените на:

    'disable_block_cache' => false, // used to disable block cache
    

Результат

Что сделали

Мы сделали с помощью модуля:

  1. Новую настройку для блока «Отобразить блок, если основного товара нет на складе»

  2. Новый тип заполнения содержимого блока — «Похожие товары» и три новые настройки для него.

    «Похожие товары» можно настраивать с помощью трёх настроек:

    • Диапазон цены в процентах.
    • Аналогичная категория.
    • Учитывать подкатегории.

Новые возможности

  1. Вы можете добавить блок «Похожие товары» на страницу товаров и настроить критерии схожести.
  2. Вы можете показывать любые товары, если основного товара нет в наличии.