Модуль для PrestaShop

Просмотров: 30874Комментарии: 12
CMS

В предыдущей статье мы коротко познакомились с архитектурой системы электронной коммерции (интернет-магазина) PrestaShop, сегодня поговорим про ее модульную систему. Модули являются частью PrestaShop, позволяя расширять функциональность системы, правда, чтобы сделать нечто более-менее серьезное, зачастую вам придется расширять или переопределять методы классов, иначе сплошные костыли (см. ниже). А иногда хочется сесть и переписать магазин с нуля на Kohana. В оригинальном дистрибутиве значительная часть функций реализуется именно модулями, их там больше сотни (платежные системы, способы доставки, информационные блоки, статистика и многое другое).


Какова архитектура и принципы работы модуля системы электронной коммерции PrestaShop? Давайте рассмотрим на примере, только я немного схитрю: не буду создавать новый модуль, а воспользуюсь готовым, к примеру, Shop by Price 1.2.1 by David StClair. Он выводит блок со списком диапазонов цен, так что кликнув по диапазону, можно получить все товары магазина с ценами из диапазона. Однако на версии 1.4.0.х модуль сбоит, вот мы его и починим, заодно добавив кое-что полезное.

Распакуем папку модуля в WEBROOT/modules/ и посмотрим на составляющие его файлы:

1) logo.gif

2) blockshopbyprice.php

3) blockshopbyprice.tpl

4) shopbyprice.php

5) shopbyprice.tpl


С первым файлом все понятно -- это иконка модуля для отображения в админке.

Файл blockshopbyprice.php является сердцем модуля, он отвечает за инсталляцию модуля, настройку (в случае необходимости) и привязку к системе (посредством хуков). Сущность модуля в PrestaShop реализуется классом, наследованным от системного класса Module (в нашем случае это class Blockshopbyprice). Имя каталога модуля и главного файла модуля, содержащего его класс, а также внутреннее наименование модуля (см. ниже) должны являться названием класса модуля в нижнем регистре (blockshopbyprice).


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

function __construct()
{
 	$this->name = 'blockshopbyprice'; // внутреннее наименование модуля
 	$this->tab = 'Blocks'; // вкладка, где будет модуль
	$this->displayName = $this->l('Shop by price block'); // название блока в списке
	$this->description = $this->l('Adds a block listing products by price'); // описание функционала блока
	$this->confirmUninstall = $this->l('Uninstalling this will delete any price ranges you set up in this module. It might be worth making a note of them before uninstalling. Pressing OK will uninstall the module or pressing Cancel will leave it installed'); // сообщение при попытке деинсталляции модуля
	$this->version = '1.2.1mod'; // номер версии модуля, я его подправил
		
    parent::__construct();
}

Заметьте -- все строки, предназначенные для прочтения человеком, оборачиваются в функцию Module::l(), что делает возможным их перевод на любой язык (в "Инструменты-Переводы-Модули"). Кроме конструктора обязательными являются метод инсталляции и деинсталляции, а также хуки, организующие взаимодействие с системой.


Метод инсталляции осуществляет создание таблиц в базе данных (если нужно), а также регистрацию хуков. Все это по традиции (?) делается в условном выражении оператора ветвления.

public function install()
{
	if (!parent::install()
		OR !$this->createtbl()
		OR !$this->registerHook('leftColumn'))
		return false;
	return true;
}

Смысл использованных методов прост: parent::install() прописывает модуль в таблице модулей системы, $this->createtbl() создает таблицу (или несколько -- как прикажете) в БД, $this->registerHook() прописывает хук в таблице связей между хуками и модулями с указанием порядкового номера модуля, чтобы при вызове хука сформировать некий порядок работы модулей (и его можно изменять). Для более сложных модулей могут понадобиться дополнительные настройки системы, но нам достаточно этого.


Метод деинсталляции модуля удаляет его из таблицы модулей, а его хуки -- из таблицы связей между хуками и модулями (за это отвечает parent::uninstall()), а также удаляет собственные таблицы модуля (а вот это на вашей совести!):

public function uninstall()
{
    if (!parent::uninstall()
        OR !Db::getInstance()->Execute('DROP TABLE `'._DB_PREFIX_.'shopbyprice`;'))
        return false;
    return true;
}

Модуль blockshopbyprice ставит хук на появление в левой колонке (но на всякий случай предусмотрена и правая). Как было сказано в предыдущей статье, хук в PrestaShop это метод класса модуля, выполняемый при вызове ядром системы метода Module::hookExec(). Чтобы быть выполненным, хук должен называться hookНазваниеХука(). Вот как это реализовано в рассматриваемом случае (да, да, в PrestaShop используются глобальные переменные) :

function hookLeftColumn($params)
{
	return $this->hookRightColumn($params);
}
function hookRightColumn($params)
{
	$db = Db::getInstance(); // create and object to represent the database
    $result = $db->Execute("SELECT `minprice`, `maxprice` FROM `"._DB_PREFIX_."shopbyprice` ORDER BY `displayorder`"); 
    while ($row = mysql_fetch_assoc($result))
        $results[] = $row;
    if (empty($results)){$results = 0;} ; 
    global $cookie;
	global $smarty;
    $currency = Currency::getCurrency(intval($cookie->id_currency));
    $smarty->assign(array(
        'currencysign' => $currency["sign"],
        'pricerange' => $results
        ));
    return $this->display(__FILE__, 'blockshopbyprice.tpl');
}

Сначала загружаем из БД список диапазонов цен, получаем текущую валюту (чтобы вывести красивый значок) и грузим все в лежащий в папке модуля шаблон Smarty blockshopbyprice.tpl (тут название может быть любым, но обычно главный шаблон именуют как и файл класса модуля). В этом шаблоне нет ничего загадочного, если хоть немного владеть Смарти. Стоит отметить только вызов функции интернационализации {l s='Shop by Price' mod='blockshopbyprice'}, которая предоставит возможность перевода строки 'Shop by Price' на странице перевода интерфейсов модулей в блоке модуля 'blockshopbyprice'.


Еще один предопределенный метод модуля getContent() отвечает за настройку модуля в админке (если таковая нужна). То есть, если метод с таким названием присутствует, на странице управления модулями автоматически создается ссылка "Настроить" для данного модуля. Метод должен как обеспечить вывод страницы настроек модуля в админке, так и обработать пришедшие от админа данные настроек, сохранив их в базе (или где там они должны храниться).

public function getContent()
{
    if (isset($_POST['Delete']) OR isset($_POST['Add']))
    {
        $this->_postProcess();
    }
		 
    $this->_html = $this->_html.'<h2>'.$this->displayName.' v.'.$this->version.'</h2><h3><a style="color:red;margin:5px;" target="_blank" href="http://www.techietips.net/prestashop-shop-by-price-module.html">'.$this->l('>> Read Documentation on this module <<').'</a></h3>';
		  
	$this->_Priceranges();
    return $this->_html;
}

В нашем случае метод разбит на два логических блока, первый из которых отвечает за обработку переданных в форму данных (private function _postProcess()), а второй за вывод самой формы и уже имеющихся диапазонов цен на страницу настройки в админке (public function _Priceranges()). Как видите, верстка для блока настроек не отделена от управляющего кода, это проблема PrestaShop. Не вижу смысла рассматривать остальные методы класса модуля подробно -- там все достаточно понятно. Пойдем дальше.


Для вывода списка товаров в диапазоне цен в шаблоне блока (blockshopbyprice.tpl) формируются ссылки вида {$base_dir}modules/blockshopbyprice/shopbyprice.php?minprice=***&maxprice=***. При клике по такой ссылке запрашиавется еще один скрипт из папки модуля -- shopbyprice.php (названия дополнительных скриптов и шаблонов уже никак не регламентируются). Структура этого скрипта проста, однако немного запутана. Сначала определяются вспомогательные функции, обеспечивающие получение и обработку границ дипазона, если с нижней границе все в порядке, то определяется функция для получения списка товаров в диапазоне из базы данных. Затем эта функция вызывается, полученные товары отправляются в еще один шаблон Smarty shopbyprice.tpl, подключающий шаблоны сортировки и списка товаров из текущей темы. Поскольку скрипт отвечает за вывод страницы целиком, то в нем предусмотрена предварительная инициализация

include(dirname(__FILE__).'/../../config/config.inc.php');

а также пагинация, "шапка" и "подвал".


Итак, со структурой более-менее разобрались. Инсталлируем модуль, зайдем в его настройки и создадим несколько диапазонов цен (присутствует дополнительный параметр, определяющий порядок вывода диапазонов в блоке). Теперь заглянем на фронтэнд -- блок появился. Кликнем по какому-нибудь диапазону -- вместо красивого списка товаров появляется лишь шапка + левое меню. Будем чинить.

Для начала включим показ ошибок в WEBROOT/config/config.inc.php (не забыть в продакшне выключить!):

@ini_set('display_errors', 'on');

Ругается на ошибку в шаблоне Смарти. Заключив значение для атрибута file в двойные кавычки и избавившись от конкатенации, исправляем ошибку

{include file="$tpl_dir/breadcrumb.tpl"}

и аналогично далее по коду. Заработало, правда в любом диапазоне выводит "No Products in that price range." Значит, в Смарти продукты не приходят. Где же они теряются? Настало время включить показ ошибок MySQL в WEBROOT/config/config.inc.php:

define('_PS_DEBUG_SQL_', true);

Опа:

Unknown column 'p.id_tax' in 'on clause'
    SELECT p.*, pl.`description`, pl.`description_short`, pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`, p.`ean13`,
        i.`id_image`, il.`legend`, t.`rate`, m.`name` AS manufacturer_name
    FROM `ps_product` p
    LEFT JOIN `ps_product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.`id_lang` = 4)
    LEFT JOIN `ps_image` i ON (i.`id_product` = p.`id_product` AND i.`cover` = 1)
    LEFT JOIN `ps_image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = 4)
    LEFT JOIN `ps_tax` t ON (t.`id_tax` = p.`id_tax`)
    LEFT JOIN `ps_manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
    WHERE p.`active` = 1
        AND p.`price` BETWEEN 1 and 39
        AND p.`id_product` IN (
                SELECT cp.`id_product`
                FROM `ps_category_group` cg
                LEFT JOIN `ps_category_product` cp ON (cp.`id_category` = cg.`id_category`)
                WHERE cg.`id_group` = 1
        )
    ORDER BY p.`date_add` DESC
    LIMIT 0, 10

Ошибка происходит в функции выборки товаров диапазона productsbyPrice(). Движок БД не находит столбца id_tax в таблице продуктов. И вправду -- его там уже нет. В версии PrestaShop 1.4.0.х механизм вычисления налогов усложнен -- это делается через налоговые правила, плюс локализация.

В итоге вместо

LEFT JOIN `ps_tax` t ON (t.`id_tax` = p.`id_tax`)

придется добавить кучу джойнов типа

LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (p.`id_tax_rules_group` = tr.`id_tax_rules_group`
        AND tr.`id_country` = '.intval(Country::getDefaultCountryId()).'
	AND tr.`id_state` = 0)
LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`)
LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (t.`id_tax` = tl.`id_tax` AND tl.`id_lang` = '.intval($id_lang).')

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


Прежде чем продолжить операцию по выпиливанию ошибок, немного "причешу" код:

- вынесу функцию productsbyPrice() из условного ветвления

- вынесу require_once(dirname(__FILE__).'/../../footer.php') за ветвление -- футер нам нужен в любом случае

- в условии ветвления использую стандартную функцию получения переменных из GET- и POST-запросов Tools::getvalue() и уберу из него return, чтобы корректно выводить правый столбец и футер даже если условие истинно. Одинокая фраза "No Min Price Set" хотя и предназначается хакеру, но в учебных целях приукрасить ее можно. Создадим шаблон shopbyprice-nomin.tpl, в котором и напишем все, что думаем

{include file="$tpl_dir/breadcrumb.tpl"}
<p class="warning">{l s='No Min Price Set.' mod='blockshopbyprice'}

Итак, возвращаемся к глобальным проблемам модуля. Начнем с кнопки "Добавить в корзину". Поскольку для отображения списка товаров используется шаблон текущей темы product-list.tpl, то различия отображения кнопки следует искать в получаемых им данных. Оказывается, не выводятся товары, у которых есть модификации (тот же товар, но с измененными характеристиками, например, другой объем памяти). В админке есть опция, позволяющая скрывать кнопку "Добавить в корзину" у тех товаров, у которых есть модификация. По-видимому, это делается для того, чтобы вынудить покупателя просмотреть все варианты модификаций -- ведь в списке отображается только "оригинальный" вариант товара. Так вот, значение данной опции в шаблон никак не передается, поэтому он и не выводит кнопку для товаров с модификациями (поведение по умолчанию). Чтобы исправить ситуацию, передадим в шаблон shopbyprice.tpl переменную

'add_prod_display' => Configuration::get('PS_ATTRIBUTE_CATEGORY_DISPLAY')

Теперь показ кнопки подчиняется общим настройкам.


Функция получения списка товаров из диапазона цен

function productsbyPrice($id_lang, $pageNumber = 0, $nbProducts = 10, $count = false, $orderBy = NULL, $orderWay = NULL)

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

$nbProducts = intval(productsbyPrice(intval($cookie->id_lang), isset($p) ? intval($p) - 1 : NULL, isset($n) ? intval($n) : NULL, true));

второй раз (в присвоении шаблона Смарти) для получения списка товаров на странице

'products' => productsbyPrice(intval($cookie->id_lang), intval($p) - 1, intval($n), false, $orderBy, $orderWay)

Так вот, несмотря на корректную работу управляющего элемента, сортировки не происходит. Метод выборки товаров в присваивании шаблона принимает переменные $orderBy и $orderWay, а скрипт product-sort.php, который мы подключили в начале shopbyprice.php заполняет свойства

public $orderBy;
public $orderWay;

объекта $controller класса FrontController. Таким образом, в метод productsbyPrice() подаются неинициализированные переменные, а инициализированные никуда не идут! Исправить это просто, достаточно подать в функцию получения списка товаров в качестве параметров выборки $controller->orderBy и $controller->orderWay -- и все должно стать на свои места. Однако ж не становится! Проблема в том, что непосредственно перед вызовом функции выборки товаров подключается скрипт пагинации, который переопределяет объект $controller

include(dirname(__FILE__).'/../../pagination.php');

То есть предварительно заполненные свойства контроллера делают нам ручкой. Возможный корректный способ устранения проблемы - сохранить в служебной переменной либо сам объект $controller, либо только его свойства orderBy и orderWay. Однако мне видится более элегантное решение: зачем нам заново инициализировать хороший, годный объект $controller, если при этой инициализации всего только выполняется метод пагинации (который сам по себе лишь заполняет свойства контроллера - номер страницы и число товаров на странице)? Более того, при подключении контроллера с пагинацией в метод pagination() не подается общее количество товаров в выборке, поэтому скрипт вынужден брать значение по умолчанию 10. Поэтому с чистым сердцем выбросим подключение скрипта pagination.php, а пагинацию сделаем вызовом метода уже имеющегося объекта

$controller->pagination($nbProducts);

Обратите внимание еще на одну штуку: в метод выборки списка товаров не подаются параметры пагинации $pageNumber и $nbProducts. Как это не подаются? А что такое $p и $n? Возможно, в предыдущей версии движка эти переменные корретно инициализировались, но в 1.4 это не так. Метод пагинации заполняет свойства

public $p;
public $n;

объекта $controller класса FrontController, которые и нужно подать в функцию получения списка товаров ценового диапазона. По ходу дела еще выяснилось, что на странице не показываются элементы управления постраничной навигацией. Ну, это решается просто: подключим в shopbyprice.tpl шаблон пагинации

{include file="$tpl_dir./pagination.tpl"}

Казалось бы, все. Но посмотрите на определение функции productsbyPrice() в части получения количества товаров. Где в формировании запроса к БД фигурируют границы ценового диапазона? Нигде. Оно считает все подряд. Однако вместо того, чтобы, ругаясь, вписать требуемое, давайте подумаем: а нужно ли отдельно вычислять число товаров? Ведь само по себе оно нигде не задействовано, только в связке с выборкой списка. Так давайте насладимся возможностями MySQL, чтобы немного сэкономить: выбросим ветвление, в запросе используем параметр SQL_CALC_FOUND_ROWS, а после получения списка товаров вызовем SELECT FOUND_ROWS(). Результат выполнения функции сделаем ассоциативным массивом

function productsbyPrice($id_lang, $pageNumber = 0, $nbProducts = 10, $orderBy = NULL, $orderWay = NULL)
{
    global $link, $cookie;
        if ($pageNumber < 0) $pageNumber = 0;
        if ($nbProducts < 1) $nbProducts = 10;
        if (empty($orderBy) || $orderBy == 'position') $orderBy = 'date_add';
        if (empty($orderWay)) $orderWay = 'DESC';
        if ($orderBy == 'id_product' OR $orderBy == 'price' OR $orderBy == 'date_add')
                $orderByPrefix = 'p';
        elseif ($orderBy == 'name')
                $orderByPrefix = 'pl';
        if (!Validate::isOrderBy($orderBy) OR !Validate::isOrderWay($orderWay))
                die(Tools::displayError());
        $result = Db::getInstance()->ExecuteS('
        SELECT SQL_CALC_FOUND_ROWS 
            p.*, pl.`description`, pl.`description_short`, pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`, p.`ean13`,
                i.`id_image`, il.`legend`, t.`rate`, m.`name` AS manufacturer_name
        FROM `'._DB_PREFIX_.'product` p
        LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.`id_lang` = '.intval($id_lang).')
        LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = p.`id_product` AND i.`cover` = 1)
        LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.intval($id_lang).')
        LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (p.`id_tax_rules_group` = tr.`id_tax_rules_group`
            AND tr.`id_country` = '.intval(Country::getDefaultCountryId()).'
	    AND tr.`id_state` = 0)
	LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`)
	LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (t.`id_tax` = tl.`id_tax` AND tl.`id_lang` = '.intval($id_lang).')
        LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
        WHERE p.`active` = 1
        '.getQuery().'
        AND p.`id_product` IN (
                SELECT cp.`id_product`
                FROM `'._DB_PREFIX_.'category_group` cg
                LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
                WHERE cg.`id_group` '.(!$cookie->id_customer ?  '= 1' : 'IN (SELECT id_group FROM '._DB_PREFIX_.'customer_group WHERE id_customer = '.intval($cookie->id_customer).')').'
        )
        ORDER BY '.(isset($orderByPrefix) ? pSQL($orderByPrefix).'.' : '').'`'.pSQL($orderBy).'` '.pSQL($orderWay).'
        LIMIT '.intval($pageNumber * $nbProducts).', '.intval($nbProducts));
        $numProd = Db::getInstance()->getValue('SELECT FOUND_ROWS()');
        if ($orderBy == 'price')
                Tools::orderbyPrice($result, $orderWay);
        if (!$result)
                return false;
        return array(
            'list'  => Product::getProductsProperties(intval($id_lang), $result),
            'num'   => intval($numProd)
            );
}

Разумеется, можно придумать полезняшки, требующие отдельного подсчета количества товаров (например вывести его около ценового диапазона). Если такое понадобится, думаю, вы без труда сделаете самостоятельно smile


И вот, после этого упрощения нас поджидает очередная ловушка: метод FrontController::pagination() рассчитывает вспомогательные величины (границы диапазона и т.п.) и самостоятельно передает их в шаблон. Однако ему нужно в качестве параметра передать общее количество товаров выборки, считаемое методом productsbyPrice(), которому, в свою очередь, нужно знать номер страницы и число товаров на странице. Предлагаю разорвать этот замкнутый круг, два раза вызвав метод пагинации:

$controller->pagination();
$result = productsbyPrice(intval($cookie->id_lang), intval($controller->p) - 1, intval($controller->n), $controller->orderBy, $controller->orderWay, $id_category);
$controller->pagination($result['num']);

При первом вызове (с параметром по умолчанию) корректно считаются параметры пагинации $p и $n, необходимые для корректной выборки списка товаров, и уже полученное общее количество товаров выборки подается в метод pagination() для окончательного расчета и передачи в шаблон. Да, выглядит и является грязным хаком, однако позволяет обойтись без дублирования функционала.


Следующая проблема функциональности модуля -- игнорирование валюты. То есть диапазоны цен "работают" только в валюте по умолчанию. При установке посетителем другой валюты, несмотря на то, что значок меняется, вывод фильтруется по "оригинальной" цене, которая хранится в таблице товаров. Подразумевается, что она выражена в валюте по умолчанию. То есть если вы смените валюту магазина по умолчанию, никакого пересчета не будет, все цены останутся численно прежними, но номинированными в новой валюте.


Следовательно, для корректного учета текущей валюты нам необходимо производить пересчет границ диапазона в валюту по умолчанию, и только полученный результат использовать в запросе к БД. Такая конвертация осуществляется системной функцией Tools::convertPrice($price, $currency = NULL, $to_currency = true). Поскольку нам нужно преобразовать в валюту по умолчанию, параметр $to_currency = false. Необходимо использовать эту функцию во вспомогательных getminPrice() и getmaxPrice(). Однако прежде обратим внимание на ничем не прикрытую передачу переменных из $_GET в запрос. Исправить сие можно, как минимум, двумя штатными способами:

1) Tools::getValue() (ну или даже сырой $_GET) с принудительным приведением к числовому типу

2) DB::pSQL() непосредственно при формировании запроса

Я для простоты приведу к числу. Кроме того, не нравится обработка максимальной цены, зачем там регулярное выражение? Зачем сравнение с минимальной ценой? Майсикловский BETWEEN корректно отработает даже неправильные границы диапазона. Кроме того, существует возможность покинуть функцию без возврата какого-либо значения. С учетом сказанного функции и были изменены, мультивалютность получила реальное подкрепление.


Еще одна серьезная проблема поджидает нас в случае включения человекопонятных урлов (ЧПУ). При этом методы генерации ссылок для пагинации и сортировки переделывают URL, убирая из него путь к папке модулей. Результат -- страница не найдена. Проще всего было бы исправить переопределением нескольких методов класса Link, однако это вне "компетенции" модуля. Поэтому пришлось лепить "костыли", переопределять шаблоны product-sort.tpl pagination.tpl. Не очень красиво получилось, поэтому не буду приводить решение тут. Кому интересно, посмотрите в код.


Наконец, основные баги исправлены, модуль штатно работает. Увы, у него остался, как минимум, один приличный "скелет в шкафу", который сейчас я убирать не буду. Дело в том, что в таблице базы данных фигурирует исходная цена, безо всяких скидок и модификаций. Поэтому при выборке товар будет отображаться в ценовом диапазоне, куда попадает именно исходная цена. К сожалению, подсчет цены продажи происходит динамически при выводе списка с учетом налогов, повышающих и понижающих коэффициентов и прочих прибамбасов, что делает "пакетную" работу с ней несколько затруднительной, нужно считать для каждого товара в отдельности. Штука поправимая, но не в рамках данной статьи.


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

Первое делается добавлением переменной 'id_category' в GET-запрос. Идем в наш хук, который выводит блок (файл blockshopbyprice.php), и ваяем извлечение номера категории

$id_category = (int)(Tools::getValue('id_category', 1));

По умолчанию он будет 1, что соответствует корню каталога магазина, заодно постановив переменной 'id_category' GET-запроса быть обязательной.


Теперь обработка запроса. Наверное, нет особого смысла проверять существование категории по ее номеру -- если ее нет, то и товаров не будет. Для извлечения номера воспользуемся той же конструкцией, что и в хуке, она обеспечит корректную работу как в корне, так и в категориях. А вот в функцию выборки товаров productsbyPrice() придется добавить параметр -- ID категории.

Предусмотреть дополнительную фильтрацию можно, например, таким образом:

$category_filter = '';
if ($id_category > 1)
{
    $category_filter = ' AND cg.`id_category`='.$id_category;
}

после чего в запросе выборки товара добавим $category_filter в условие WHERE от SELECT cp.`id_product`.

И второе пожелание, совсем скромное: в случае, когда максимальной границы диапазона нет, в блоке выводится только минимальная граница, что, мягко говоря, неочевидно. Чтобы "озвучить" диапазон, немного подкорректируем шаблон

<a href="{$base_dir}modules/blockshopbyprice/shopbyprice.php?id_category={$id_category}&minprice={$pricerange[nr].minprice}"/>{l s='more' mod='blockshopbyprice'} {$currencysign}{$pricerange[nr].minprice}</a>

Разумеется, дорабатывать можно долго -- сделать возможность выбора товаров для сравнения, дополнительно показать диапазон, чтобы посетители не забыли, в каком ищут, однако, я здесь остановлюсь. Надеюсь, читатели нашли в этом достаточно сумбурном посте что-то для себя полезное и составили некое впечатление о модульной архитектуре PrestaShop.


Download zipped / Скачать архив с модулем ShopByPrice 103 (тестировалось на PrestaShop 1.4.0.15)

Комментариев: 12 RSS

1 Alesss 23-04-2011 04:26

И еще одна шикарная статья по разборке модуля.

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

Отличная работа, огромное спасибо Александру Купрееву

5 Алексей 08-06-2011 16:25

Очень поучительно, и от меня спасибо.

6 Аноним 22-06-2011 19:01

Учитывая что документации вообще мало то это шикарния статья

7 Комментатор 26 02-07-2011 12:53

Подойдет ли prestashop для реализации вот такого сайта

www.tetriskostenlos.com

Хочу взять за основу именно этот движок но не знаю есть ли смысл ???

Движок мне нравится !!!

8 Дмитрий 17-07-2011 01:55

Спасибо за работу и готовый модуль для 1.4. респект!

Вопрос, как сортировать диапазон цен на сайте?

А то скрипт мне выстроил такую не очень логичную схему:

1500 3000 999999

1 1500 999999

3000 NoMax 999999

А хотелось бы ессно так:

1 1500 999999

1500 3000 999999

3000 NoMax 999999

И ещё выводится на сайте в виде:

р.1 - 1500

Где изменить, чтобы было так:

1 - 1500 руб.

9 Александр Купреев 19-07-2011 20:10

Прошу простить, что долго не отвечал, занятость.

Порядок следования конкретного диапазона определяется его порядковым номером (задается в поле Enter Display Order)

Возможность редактирования диапазонов в модуле отсутствует, так что если вы что-то не так создали, то удалите и пересоздайте заново, уже в нужном порядке (сортируется по возрастанию номера порядка, то есть диапазон с order 10 будет показан выше, чем диапазон с order 11)

Чтобы убрать значок валюты у диапазона, найдите в папке модуля шаблон blockshopbyprice.tpl и удалите из него переменную {$currencysign}

10 Дмитрий 05-10-2011 16:30

Вы не подскажите каким образом блоки по бокам приобретают вид текущей темы??

11 Антон 04-08-2012 23:22

Просто шикарная статья и изложения материала... СПАСИБО от всей души.

Дмитрий, скорее всего, блоки по бокам приобретают вид текущей темы, посредством CSS или в Параметрах Позиции Модулей.

12 Иван 06-06-2017 01:33

Спасибо огромное!! Если б не Вы еще 3 часа бы сидел и не вдуплял как вывести обложку.

У меня модуль вывоил второе изображение не хватало:

AND '._DB_PREFIX_.'image.cover = 1

Спасибо!!

Оставьте комментарий!


Используйте нормальные имена.

     

  

Если вы уже зарегистрированы как комментатор или хотите зарегистрироваться, укажите пароль и свой действующий email. При регистрации на указанный адрес придет письмо с кодом активации и ссылкой на ваш персональный аккаунт, где вы сможете изменить свои данные, включая адрес сайта, ник, описание, контакты и т.д., а также подписку на новые комментарии.

MaxSiteAuth. Войти через loginza

(обязательно)