Задача: выводить вверху на всех страницах интернет-магазина надпись:
В корзине 5 товаров на сумму 157 евро
Обязательное условие: всё должно работать на стандартных компонентах (так как этот подход мне теперь очень нравится).
Как оказалось, стандратный Битриксовский компоннет bitrix:sale.basket.basket.line, который отображает количество товаров в корзине - не способен на это. Все что он может - это вывести текущее количество товаров в корзине, но без их цены, и без учета того, сколько раз был добавлен товар в корзину. [spoiler] Вариант решения, который вначале пришел в голову - использовать более "продвинутые" компоненты sale.basket.basket.small или sale.basket.basket, но, посмотрев их код, понял, что они каждый раз при подключении подрубают весь модуль интернет-магазина и лопатят запрос на выборку текущего количества товаров в корзине из базы данных на кажодом хите. А так как предполагается, что компонент корзины будет размещаться в шаблоне сайта на всех страницах, то этот способ решил не рассматривать, так как будет слишком напрягать сервер и пропадут все преимущества кеширования.
Единственным плюсом компонента bitrix:sale.basket.basket.line есть то, что он сохраняет количество товаров, которые сейчас у посетителя в корзине, в сессии. И в следующий раз, если в сессии есть значение количества товаров, то берет его не из базы, а из сессии. Сама сессионная переменная с количеством товаров в корзине - заблаговременно сбрасывается в ядре модуля интернет-магазина в момент изменения количества товаров.
Мне понравилась сама идея, что-то вроде кеширования в сессии, которое длится только на время сеанса работы с посетителем. Решил реализовать нечто подобное и для себя, благо, в модуль интернет-магазина еще в версии 8.5 добавили события для работы с корзиной.
Итак, чтобы все это провернуть, нужно добавить следующие обработчики в init.php:
#/bitrix/php_interface/init.php
AddEventHandler("sale", "OnBasketAdd", Array("MyBasketClass", "OnBasketAdd"));
AddEventHandler("sale", "OnBasketUpdate", Array("MyBasketClass", "OnBasketUpdate"));
AddEventHandler("sale", "OnBasketDelete", Array("MyBasketClass", "OnBasketDelete"));
AddEventHandler("main", "OnBeforeProlog", Array("MyBasketClass", "OnBeforeProlog"));
class MyBasketClass
{
function OnBasketAdd($ID, $arFields)
{
unset($_SESSION["YAM_CACHE"]["BASKET"]);
}
function OnBasketUpdate($ID, $arFields)
{
unset($_SESSION["YAM_CACHE"]["BASKET"]);
}
function OnBasketDelete($ID, $arFields)
{
unset($_SESSION["YAM_CACHE"]["BASKET"]);
}
function OnBeforeProlog()
{
// Не запускаем для админки
if (defined("ADMIN_SECTION"))
return false;
if (empty($_SESSION["YAM_CACHE"]["BASKET"]))
{
$_SESSION["YAM_CACHE"]["BASKET"]["ITEMS"] = array();
$productCount = $totalCount = $totalSum = $totalSumDiscount = 0;
if (CModule::IncludeModule("sale") && CModule::IncludeModule("currency"))
{
$dbBaket = CSaleBasket::GetList(
array("NAME" => "ASC"),
array("FUSER_ID" => CSaleBasket::GetBasketUserID(), "LID" => SITE_ID, "ORDER_ID" => "NULL")
);
$arItems = array();
while ($arBasket = $dbBaket->GetNext())
{
// Отложенные и прочие товары не учитываем
if ($arBasket["DELAY"]=="N" && $arBasket["CAN_BUY"]=="Y")
{
$arBasket["PRICE_FORMATED"] = SaleFormatCurrency($arBasket["PRICE"], $arBasket["CURRENCY"]);
$_SESSION["YAM_CACHE"]["BASKET"]["ITEMS"][$arBasket["ID"]] = $arBasket;
}
}
if (!empty($_SESSION["YAM_CACHE"]["BASKET"]["ITEMS"]) && CModule::IncludeModule("currency"))
{
$baseCurrency = CCurrency::GetBaseCurrency();
foreach ($_SESSION["YAM_CACHE"]["BASKET"]["ITEMS"] as $arBasketItem)
{
$productCount++; // Число товаров
$totalCount += $arBasketItem["QUANTITY"]; // Число товаров с учетом количества каждого товара
// Корвертация валюты всех товаров в корзине к базовой
if ($arBasketItem["CURRENCY"] != $baseCurrency)
$arBasket["PRICE"] = CCurrencyRates::ConvertCurrency($arBasketItem["PRICE"], $arBasketItem["CURRENCY"], $baseCurrency);
$totalSum += $arBasketItem["QUANTITY"] * $arBasketItem["PRICE"]; // Общая цена
// учет скидок
if ($arBasketItem["DISCOUNT_PRICE"] > 0)
$totalSumDiscount += $arBasketItem["QUANTITY"] * $arBasketItem["DISCOUNT_PRICE"];
else
$totalSumDiscount += $arBasketItem["QUANTITY"] * $arBasketItem["PRICE"];
}
}
$_SESSION["YAM_CACHE"]["BASKET"] = array(
"PRODUCT_COUNT" => $productCount, // Количество товаров в корзине
"ITEMS_COUNT" => $totalCount, // Количество товаров в козире (с учетом кол-ва каждого товара)
"TOTAL_SUM" => $totalSum, // Общая сумма всех товаров в корзине
"CURRENCY" => $baseCurrency, // Валюта всех товаров
"TOTAL_SUM_FORMATED" => SaleFormatCurrency($totalSum, $baseCurrency), // Общая сумма всех товаров в корзине (в формате валюты)
"TOTAL_DISCOUNT" => $totalSumDiscount, // Сумма всех товаров в корзине с учетом скидки
);
}
}
}
}
Как это работает:
В событии, которое наступает перед началом генерации страницы OnBeforeProlog() мы проверяем, есть ли у нас сессионная переменная-массив ($_SESSION["YAM_CACHE"]["BASKET"]). В неё должно попадать: количество товаров в корзине, их общая сумма, ну и, сами товары со ссылками на них, на всякий случай.
Если переменная не определена - то она заполняется (делается запрос к базе и проверка текущей корзины). Так как это сессионная переменная - то при следующем запросе страницы её значение для текущего посетителя сохранится.
При изменении записей в корзине (добавлении, изменении количества, или удалении) эта сессионная переменная, через обработчики событий, сбрасывается, и в момент начала создания страницы она будет вновь наполнена актуальными данными.
Теперь, в шаблоне компонента bitrix:sale.basket.basket.line в файле result_modifier.php добавляем код для определения текущих сведений о корзине, на основе сессионной переменной-массива:
<?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
//==============================================================//
// Дополнительный выбор общей суммы товаров в корзине из сессии //
//==============================================================//
// Данные попадают в сессию с обработчиков OnBasketAdd, OnBasketUpdate и OnBasketDelete
if (!empty($_SESSION["YAM_CACHE"]["BASKET"]))
{
$arResult["PRODUCT_COUNT"] = $_SESSION["YAM_CACHE"]["BASKET"]["PRODUCT_COUNT"]; // Аналог $arResult["NUM_PRODUCTS"]
$arResult["ITEMS_COUNT"] = $_SESSION["YAM_CACHE"]["BASKET"]["ITEMS_COUNT"];
$arResult["TOTAL_SUM"] = $_SESSION["YAM_CACHE"]["BASKET"]["TOTAL_SUM"];
$arResult["CURRENCY"] = $_SESSION["YAM_CACHE"]["BASKET"]["CURRENCY"];
$arResult["TOTAL_SUM_FORMATED"] = $_SESSION["YAM_CACHE"]["BASKET"]["TOTAL_SUM_FORMATED"];
$arResult["ITEMS"] = $_SESSION["YAM_CACHE"]["BASKET"]["ITEMS"];
}
?>
Так как стандартный компонент bitrix:sale.basket.basket.line выбирает только количество товаров, и не смотрит на то, сколько раз товар был добавлен в корзину (если мы хоитм купить три единицы одного товара, то он все время будет писать что в корзине один товар), то на помощью ему выбираются дополнительные данные (и записываются в $arResult) о общем достоверном количестве товаров в корзине и их суммарной стоимости.
Вывести всё это дело можно в template.php, например, таким образом:
В корзине <?=$arResult["ITEMS_COUNT"]?> товаров
на сумму <?=$arResult["TOTAL_SUM_FORMATED"]?>
P.S. поддержите идеи по стандартным компонентам, это позволит жить лучше и веселее, и делать много прикольных штук с помощью них:
Группы на сайте создаются не только сотрудниками «1С-Битрикс», но и партнерами компании. Поэтому мнения участников групп могут не совпадать с позицией компании «1С-Битрикс».