Оптовые скидки в списке товаров

Описание

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

Платформа CS-Cart имеет очень полезный функционал — «Оптовые скидки» (см. Оптовые скидки).

Вы можете задать особые цены на ваши товары при оптовой покупке или для определённых групп покупателей.

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

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

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

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

Мы сделаем так, чтобы оптовые скидки отображались и на странице списка товаров.

Было Стало

Анализ

В CS-Cart обработка и отображении информации разделены. Любая информация которую мы видим в браузере была:

  1. Обработана, подготовлена и передана на отображение с помощью контроллера — специального PHP скрипта.
  2. Отображена с помощью шаблона.

Соответственно нам нужно найти:

  1. PHP функцию которая подготовила информацию об оптовых скидках для товара.
  2. Шаблон который отобразил информацию.

С чего начать?

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

Не интересен процесс? Переходите к финалу: Получим информацию о скидках для страницы списка товаров

Найдём контроллер страницы товара

Всё начинается с контроллера.

  1. Откройте страницу товара на витрине.

  2. Смотрим на URL в адресной строке браузера.

    URL: ваш_домен/index.php?dispatch=products.view&product_id=12

    Примечание

    Включен модуль SEO? Выключайте! Живой магазин? Делайте копию для экспериментов на поддомене.

    Нас интересует параметр dispatch=products.view в URL.

    В данном случае:

    products — это название контроллера который обрабатывает данную страницу.

  3. Все контроллеры витрины находятся в папке:

    /app/controllers/frontend

  4. Мы определили по URL, что нам нужен контроллер products.

    Открываем файл /app/controllers/frontend/products.php

  5. Вставляем fn_print_r('Test'); где нибудь в начале файла контроллера.

    1
    2
    3
    4
    5
    6
    use Tygh\Registry;
    use Tygh\BlockManager\ProductTabs;
    
    fn_print_r('Test');
    
    if (!defined('BOOTSTRAP')) { die('Access denied'); }
    

    Перезагружаем страницу товара в браузере. Должно отобразится слово Test в левом верхнем углу.

  6. Отлично, контроллер найден. Однако тут много кода, какой нам нужен?

    Снова смотрим на параметр dispatch=products.view в URL.

    Нас интересует часть после точки — view .

    Так обозначается режим (mode) контроллера — секция кода, которая отвечает за данную страницу. (см. О контроллерах)

    Значит нам нужно найти блок с условием — $mode == 'view'

  7. Находим с помощью поиска по файлу блок с нужным условием и вставляем fn_print_r('Test mode'); в него для проверки.

    1
    2
    3
    4
    5
    6
    7
    8
    //
    // View product details
    //
    } elseif ($mode == 'view' || $mode == 'quick_view') {
    
        fn_print_r('Test');
    
        $_REQUEST['product_id'] = empty($_REQUEST['product_id']) ? 0 : $_REQUEST['product_id'];
    

    Проверяем в браузере. Должны появится слова Test mode

  8. Изучаем код. Практически сразу видим предположительное появление информации о товаре.

    Добавляем fn_print_r($product);` чтобы распечатать на экран переменную ``$product которая появляется в результате работы функции fn_get_product_data

    1
    2
    3
        $product = fn_get_product_data($_REQUEST['product_id'], $auth, CART_LANGUAGE, '', true, true, true, true, fn_is_preview_action($auth, $_REQUEST));
    
    fn_print_r($product);
    

    Смотрим результат в браузере и видим массив с данными о товаре и об оптовых скидках в ячейке ['prices']:

  9. Если мы проследим за дальнейшими действиями над массивом $product в контроллере, то увидим:

    1
        Registry::get('view')->assign('product', $product);
    

    Данная строка передаёт массив с данными о товаре $product на отображение в шаблоны.

Находим нужную функцию

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

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

Скорее всего внутри функции fn_get_product_data есть небольшая функция для получения только оптовых скидок. 1. Все основные функции контроллеров платформы расположены в папке:

/app/functions

Файлы с функциями имеют название соответствующее области работы.

Мы работаем с каталогом, значит нам нужен файл: fn.catalog.php .

  1. Открываем /app/functions/fn.catalog.php и ищем функцию fn_get_product_data.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
     * Gets full product data by its id
     *
     * @param int $product_id Product ID
     * @param mixed $auth Array with authorization data
     * @param string $lang_code 2 letters language code
     * @param string $field_list List of fields for retrieving
     * @param boolean $get_add_pairs Get additional images
     * @param boolean $get_main_pair Get main images
     * @param boolean $get_taxes Get taxes
     * @param boolean $get_qty_discounts Get quantity discounts
     * @param boolean $preview Is product previewed by admin
     * @param boolean $features Get product features
     * @param boolean $skip_company_condition Skip company condition and retrieve product data for displayin on other store page. (Works only in ULT)
     * @return mixed Array with product data
     */
    function fn_get_product_data($product_id, &$auth, $lang_code = CART_LANGUAGE, $field_list = '', $get_add_pairs = true, $get_main_pair = true, $get_taxes = true, $get_qty_discounts = false, $preview = false, $features = true, $skip_company_condition = false, $feature_variants_selected_only = false)
    
  2. Смотрим параметры передаваемые в функцию.

    Очень интересен параметр $get_qty_discounts . По смыслу — то что нужно, он должен включать или выключать получение оптовых цен для товара.

  3. Находим код, который выполняется для параметра $get_qty_discounts.

    1
    2
    3
    4
            // Get qty discounts
            if ($get_qty_discounts == true) {
                fn_get_product_prices($product_id, $product_data, $auth);
            }
    
  4. Мы нашли конечную функцию fn_get_product_prices, которая получает данные об оптовых скидках для товара. Проверим:

    1
    2
    3
    4
    5
    6
            // Get qty discounts
            if ($get_qty_discounts == true) {
    fn_print_r($product_data['prices']);
                fn_get_product_prices($product_id, $product_data, $auth);
    fn_print_r($product_data['prices']);
            }
    

    Первый fn_print_r пустой, а второй уже содержит информацию о скидках.

    Результат:

Переходим к странице списка товаров

Мы нашли функцию которая нам нужна. Наша цель отобразить оптовые скидки на странице списка товаров. Найдём и изучим контроллер целевой страницы:

  1. Открываем страницу списка товаров (страницу категории).

  2. Смотрим URL в браузере, чтобы определить контроллер.

    Оптовые скидки

    Нам по прежнему важен параметр dispatch=categories.view , где:

    • Контроллер — categories
    • Режим (mode) — view
  3. Открываем файл:

    app/controllers/frontend/categories.php

  4. Находим раздел с $mode == 'view' и вставим функцию fn_print_r('Test Cat'); для проверки.

    1
    2
    3
    } elseif ($mode == 'view') {
    
        fn_print_r('Test Cat');
    

    Перезагрузим страницу в браузере, чтобы проверить. Если увидите Test Cat , то вы там где нужно.

    Оптовые скидки
  5. Ищем по смыслу функцию, которая получает информацию о товарах. Можно просто перебирать переменные с помощью fn_print_r().

    После недолгого поиска, находится fn_get_products, которая создаёт массив с товарами $products:

    1
    2
    3
            list($products, $search) = fn_get_products($params, Registry::get('settings.Appearance.products_per_page'), CART_LANGUAGE);
    
    fn_print_r($products);
    

    Распечатаем $products.

    Оптовые скидки
  6. Изучаем массив данных и не находим информации об оптовых скидках. Идём дальше по коду контроллера categories.php.

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

    1
    2
    3
            list($products, $search) = fn_get_products($params, Registry::get('settings.Appearance.products_per_page'), CART_LANGUAGE);
    
    fn_print_r($products);
    

    Распечатываем $products после функции и изучаем массив с данными.

    К сожалению, информации о оптовых скидках не появилось. У функции есть параметр 'get_discounts' => true, , судя по названию, он связан со скидками. Зайдём и посмотрим.

  7. Зайдём и изучим функцию fn_gather_additional_products_data.

    Функция находится в файле /app/functions/fn.catalog.php.

    Находим код отрабатывающий по параметру: 'get_discounts' => true,

    1
    2
            // Get product discounts
            if ($params['get_discounts'] && !isset($product['exclude_from_calculate'])) {
    

    К сожалению, функции в данном условии отвечают за получение скидок по «Промо-акциям» для каталога.

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

    Однако в данной функции есть много хуков и возможно нам они понадобятся.

    Хуки в php выглядят так.

    1
    fn_set_hook('gather_additional_product_data_before_discounts', $product, $auth, $params);
    

    Хуки позволяют подключить и выполнить свой php код, используя данные переданные в хук.

  8. Возвращаемся в контроллер categories.php и идём дальше по коду.

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

    Распечатаем переменную $products перед передачей её на отображение

    1
    2
    fn_print_r($products);
    Registry::get('view')->assign('products', $products);
    

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

  9. Будем добавлять.

Получим информацию о скидках для страницы списка товаров

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

Значит нам нужно её добавить.

Мы нашли функцию fn_get_product_prices которая по id товара сможет получить информацию о скидках.

Мы нашли хуки в одной из функций которые выполняет контроллер categories.php.

Подключимся к хуку и расширим данные товаров информацией о скидках.

Расширять будем с помощью модуля «Мои изменения».

  1. Рассмотрим хук из функции fn_gather_additional_products_data.

    1
    fn_set_hook('gather_additional_product_data_before_discounts', $product, $auth, $params);
    

    Первый аргумент функции fn_set_hook соответствует названию хука.

    Последующие аргументы $product, $auth, $params соответствуют данным которые будут доступны для модификации в хуке.

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

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

    /app/addons/my_changes/init.php

    Вставьте код для инициализации хука:

    1
    2
    3
    4
    5
    <?php
    
    fn_register_hooks(
        'gather_additional_product_data_before_discounts'
    );
    

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

  3. Чтобы выполнить свой php код в хуке, создайте файл:

    /app/addons/my_changes/func.php

    Создайте в данном файле новую функцию с названием вида:

    fn_[идентификатор_модуля]_[название_хука]

    В качестве аргументов функции используйте переменные передаваемые в хук.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <?php
    
    if (!defined('BOOTSTRAP')) { die('Access denied'); }
    
    function fn_my_changes_gather_additional_product_data_before_discounts(&$product, &$auth, &$params)
    {
    
        fn_print_r('ID товара: ' . $product['product_id']);
        
        return true;
    }
    
  4. Включите модуль «Мои изменения» и проверьте подключение к хуку.

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

  5. У нас есть всё что бы получить нужные данные.

    Выполним функцию fn_get_product_prices и получим данные о скидках.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <?php
    
    if (!defined('BOOTSTRAP')) { die('Access denied'); }
    
    function fn_my_changes_gather_additional_product_data_before_discounts(&$product, &$auth, &$params)
    {
    
        fn_get_product_prices($product['product_id'], $product, $auth);
        
        fn_print_r($product['prices']);
    
        return true;
    }
    

    Результат:

    Мы используем стандартную функцию из детальной страницы товара. Как видите функция расширила данные о товаре информацией о скидками. Информация о скидках записалась в ячейку ['prices'] так же как и в контроллере детальной страницы товара. Товары у которых нет оптовых цен не имеют ячейки ['prices'].

    Обратите внимание, что аргументы (&$product, &$auth, &$params) переданы в хук ссылками, поэтому изменения в хуке выходят наружу и влияют на работу всего скрипта.

  6. Убираем fn_print_r из хука и переносим его в контроллер categories.php, в то место где переменная $products передаётся на отображение. Делаем это, чтобы проверить появились ли данные о скидках вне модуля.

    1
    2
    fn_print_r('Проверяем работу хука',$products[0]['product'],$products[0]['prices']);
    Registry::get('view')->assign('products', $products);
    

    Я передал на распечатку название и оптовые скидки первого по порядку товара в массиве $products, потому что оптовые скидки в примере настроенны именно для этого товара.

Отлично, данные о скидках получены.

Убираем все fn_print_r в функциях и контроллерах и переходим к шаблонам.

Оптовые скидки уже появились на странице списка товаров

Было Стало

Почему скидки появились сразу?

Всё дело в шаблонах, в шаблоне отвечающего за выбор количеств товара было условие: «Если есть информация об оптовых скидках, то отображаем».

  1. Откроем детальную страницу товара и посмотрим код блока оптовых скидок в браузере:

  2. Все шаблоны находятся в папке:

    /design/themes/[название_темы]/templates/

    Сделаем поиск по файлам в папке c шаблонами, будем искать файл в котором встречается класс ty-qty-discount__table, который использует таблица скидок.

    Поиск нашёл всего один такой файл:

    /design/themes/[название_темы]/templates/views/products/components/products_qty_discounts.tpl

    Откроем файл и добавим <p>Test</p> в любое место.

    Контрольная фраза появилась. Отлично.

  3. Найдём где подключается данный шаблон уровнем выше.

    Делаем поиск по файлам, ищем products/components/products_qty_discounts.tpl в папке с шаблонами.

    Поиск находит всего один файл:

    /design/themes/[название_темы]/templates/common/product_data.tpl

    Открываем данный файл и находим подключение нужного нам шаблона и условие:

    1
    2
    3
                {if $product.prices}
                    {include file="views/products/components/products_qty_discounts.tpl"}
                {/if}
    

    Добавим <p>Test 1</p>.

    Шаблоны найдены, убираем все тесты.

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

Cкидки не появились сразу?

Попробуйте выключить параметры тестового товара.

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

Вы можете воспользоваться уроком Отображение опций в списке.

Спасибо! Если возникнут вопросы, пишите в комментариях.