update
This commit is contained in:
335
modules/crosssellpro/crosssellpro.php
Normal file
335
modules/crosssellpro/crosssellpro.php
Normal file
@@ -0,0 +1,335 @@
|
||||
<?php
|
||||
/**
|
||||
* Cross Sell PRO module for cart page upsell.
|
||||
*
|
||||
* @author Pyziak Jacek
|
||||
* @copyright project-pro.pl
|
||||
* @link https://www.project-pro.pl
|
||||
*/
|
||||
|
||||
use PrestaShop\PrestaShop\Adapter\Image\ImageRetriever;
|
||||
use PrestaShop\PrestaShop\Adapter\Product\PriceFormatter;
|
||||
use PrestaShop\PrestaShop\Adapter\Product\ProductColorsRetriever;
|
||||
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Crosssellpro extends Module
|
||||
{
|
||||
const DEFAULT_LIMIT = 12;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->name = 'crosssellpro';
|
||||
$this->tab = 'pricing_promotion';
|
||||
$this->version = '1.1.6';
|
||||
$this->author = 'Pyziak Jacek';
|
||||
$this->need_instance = 0;
|
||||
$this->bootstrap = true;
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this->displayName = $this->l('Cross Sell PRO');
|
||||
$this->description = $this->l('Displays related products carousel in cart based on product associations.');
|
||||
}
|
||||
|
||||
public function install()
|
||||
{
|
||||
return parent::install()
|
||||
&& $this->registerHook('displayShoppingCartFooter')
|
||||
&& $this->registerHook('displayCheckoutSummaryTop')
|
||||
&& $this->registerHook('displayHeader')
|
||||
&& $this->registerHook('actionFrontControllerSetMedia');
|
||||
}
|
||||
|
||||
public function uninstall()
|
||||
{
|
||||
return parent::uninstall();
|
||||
}
|
||||
|
||||
public function hookActionFrontControllerSetMedia()
|
||||
{
|
||||
if (!$this->context || !$this->context->controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->isRegisteredInHook('displayCheckoutSummaryTop')) {
|
||||
$this->registerHook('displayCheckoutSummaryTop');
|
||||
}
|
||||
if (!$this->isRegisteredInHook('displayHeader')) {
|
||||
$this->registerHook('displayHeader');
|
||||
}
|
||||
|
||||
$controllerName = (string) $this->context->controller->php_self;
|
||||
if (!in_array($controllerName, ['cart', 'order'], true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->registerAssets();
|
||||
}
|
||||
|
||||
public function hookDisplayHeader()
|
||||
{
|
||||
if (!$this->context || !$this->context->controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
$controllerName = (string) $this->context->controller->php_self;
|
||||
if (!in_array($controllerName, ['cart', 'order'], true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->registerAssets();
|
||||
}
|
||||
|
||||
public function hookDisplayShoppingCartFooter($params)
|
||||
{
|
||||
if (!$this->context || !$this->context->cart || !$this->context->cart->id) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$products = $this->buildCrossSellProducts();
|
||||
if (empty($products)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->context->smarty->assign([
|
||||
'crosssellpro_products' => $products,
|
||||
'crosssellpro_cart_url' => $this->context->link->getPageLink('cart', true),
|
||||
'crosssellpro_return_url' => $this->context->link->getPageLink('cart', true, null, 'action=show'),
|
||||
'crosssellpro_mode' => 'cart',
|
||||
]);
|
||||
|
||||
return $this->fetch('module:' . $this->name . '/views/templates/hook/cartCrossSell.tpl');
|
||||
}
|
||||
|
||||
public function hookDisplayCheckoutSummaryTop($params)
|
||||
{
|
||||
if (!$this->context || !$this->context->cart || !$this->context->cart->id) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$products = $this->buildCrossSellProducts();
|
||||
if (empty($products)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->context->smarty->assign([
|
||||
'crosssellpro_products' => $products,
|
||||
'crosssellpro_cart_url' => $this->context->link->getPageLink('cart', true),
|
||||
'crosssellpro_return_url' => $this->context->link->getPageLink('order', true),
|
||||
'crosssellpro_mode' => 'checkout',
|
||||
]);
|
||||
|
||||
return $this->fetch('module:' . $this->name . '/views/templates/hook/checkoutCrossSell.tpl');
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds presented products list from accessories of products currently in cart.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function buildCrossSellProducts()
|
||||
{
|
||||
$cartProducts = $this->context->cart->getProducts(true);
|
||||
if (empty($cartProducts)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$inCartProductIds = [];
|
||||
foreach ($cartProducts as $cartProduct) {
|
||||
$inCartProductIds[(int) $cartProduct['id_product']] = true;
|
||||
}
|
||||
|
||||
$relatedIds = $this->collectAccessoryIds(array_keys($inCartProductIds));
|
||||
if (empty($relatedIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$relatedIds = array_values(array_diff($relatedIds, array_keys($inCartProductIds)));
|
||||
if (empty($relatedIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$limitedIds = array_slice($relatedIds, 0, static::DEFAULT_LIMIT);
|
||||
$products = $this->presentProducts($limitedIds);
|
||||
if (empty($products)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$combinationFlags = $this->getCombinationFlags($limitedIds);
|
||||
foreach ($products as &$product) {
|
||||
$productId = (int) $product['id_product'];
|
||||
$requiresSelection = !empty($combinationFlags[$productId]) || empty($product['add_to_cart_url']);
|
||||
|
||||
$product['crosssellpro_requires_selection'] = $requiresSelection;
|
||||
if ($requiresSelection) {
|
||||
$product['crosssellpro_cta_url'] = $product['url'];
|
||||
$product['crosssellpro_cta_label'] = $this->l('Wybierz wariant');
|
||||
} else {
|
||||
$qty = 1;
|
||||
if (isset($product['minimal_quantity']) && (int) $product['minimal_quantity'] > 0) {
|
||||
$qty = (int) $product['minimal_quantity'];
|
||||
}
|
||||
|
||||
$product['crosssellpro_post_add_url'] = $this->context->link->getPageLink(
|
||||
'cart',
|
||||
true,
|
||||
null,
|
||||
'add=1&id_product=' . $productId . '&qty=' . $qty . '&token=' . Tools::getToken(false)
|
||||
);
|
||||
$product['crosssellpro_cta_url'] = $this->context->link->getPageLink('cart', true, null, 'action=show');
|
||||
$product['crosssellpro_cta_label'] = $this->l('Dodaj do koszyka');
|
||||
}
|
||||
}
|
||||
unset($product);
|
||||
|
||||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $productIds
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
protected function collectAccessoryIds(array $productIds)
|
||||
{
|
||||
$ids = [];
|
||||
|
||||
foreach ($productIds as $productId) {
|
||||
$accessories = Product::getAccessoriesLight(
|
||||
(int) $this->context->language->id,
|
||||
(int) $productId,
|
||||
(Context::getContext() === null ? $this->context : Context::getContext())
|
||||
);
|
||||
|
||||
if (empty($accessories)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($accessories as $accessory) {
|
||||
if (!isset($accessory['id_product'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$accessoryId = (int) $accessory['id_product'];
|
||||
if ($accessoryId > 0) {
|
||||
$ids[$accessoryId] = $accessoryId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $productIds
|
||||
*
|
||||
* @return array<int, bool>
|
||||
*/
|
||||
protected function getCombinationFlags(array $productIds)
|
||||
{
|
||||
if (empty($productIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = Db::getInstance((bool) _PS_USE_SQL_SLAVE_)->executeS(
|
||||
'SELECT p.id_product, COUNT(pa.id_product_attribute) AS combinations
|
||||
FROM `' . _DB_PREFIX_ . 'product` p
|
||||
LEFT JOIN `' . _DB_PREFIX_ . 'product_attribute` pa ON (pa.id_product = p.id_product)
|
||||
WHERE p.id_product IN (' . implode(',', array_map('intval', $productIds)) . ')
|
||||
GROUP BY p.id_product'
|
||||
);
|
||||
|
||||
$flags = [];
|
||||
foreach ($rows as $row) {
|
||||
$flags[(int) $row['id_product']] = ((int) $row['combinations'] > 0);
|
||||
}
|
||||
|
||||
return $flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $productIds
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function presentProducts(array $productIds)
|
||||
{
|
||||
if (empty($productIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rawProducts = Product::getProductsProperties(
|
||||
(int) $this->context->language->id,
|
||||
Db::getInstance((bool) _PS_USE_SQL_SLAVE_)->executeS(
|
||||
'SELECT p.id_product
|
||||
FROM `' . _DB_PREFIX_ . 'product` p
|
||||
' . Shop::addSqlAssociation('product', 'p') . '
|
||||
WHERE p.id_product IN (' . implode(',', array_map('intval', $productIds)) . ')
|
||||
AND p.active = 1
|
||||
AND product_shop.visibility IN ("both", "catalog")
|
||||
ORDER BY FIELD(p.id_product,' . implode(',', array_map('intval', $productIds)) . ')'
|
||||
)
|
||||
);
|
||||
|
||||
if (empty($rawProducts)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$assembler = new ProductAssembler($this->context);
|
||||
$presenterFactory = new ProductPresenterFactory($this->context);
|
||||
$presentationSettings = $presenterFactory->getPresentationSettings();
|
||||
$presentationSettings->showPrices = true;
|
||||
|
||||
if (version_compare(_PS_VERSION_, '1.7.5', '>=')) {
|
||||
$presenter = new \PrestaShop\PrestaShop\Adapter\Presenter\Product\ProductListingPresenter(
|
||||
new ImageRetriever($this->context->link),
|
||||
$this->context->link,
|
||||
new PriceFormatter(),
|
||||
new ProductColorsRetriever(),
|
||||
$this->context->getTranslator()
|
||||
);
|
||||
} else {
|
||||
$presenter = new \PrestaShop\PrestaShop\Core\Product\ProductListingPresenter(
|
||||
new ImageRetriever($this->context->link),
|
||||
$this->context->link,
|
||||
new PriceFormatter(),
|
||||
new ProductColorsRetriever(),
|
||||
$this->context->getTranslator()
|
||||
);
|
||||
}
|
||||
|
||||
$productsForTemplate = [];
|
||||
foreach ($rawProducts as $rawProduct) {
|
||||
$presented = $presenter->present(
|
||||
$presentationSettings,
|
||||
$assembler->assembleProduct($rawProduct),
|
||||
$this->context->language
|
||||
);
|
||||
|
||||
if (!empty($presented['add_to_cart_url']) || !empty($presented['url'])) {
|
||||
$productsForTemplate[] = $presented;
|
||||
}
|
||||
}
|
||||
|
||||
return $productsForTemplate;
|
||||
}
|
||||
|
||||
protected function registerAssets()
|
||||
{
|
||||
$this->context->controller->registerStylesheet(
|
||||
'module-crosssellpro-cart',
|
||||
'modules/' . $this->name . '/views/css/cartCrossSell.css',
|
||||
['media' => 'all', 'priority' => 150]
|
||||
);
|
||||
|
||||
$this->context->controller->registerJavascript(
|
||||
'module-crosssellpro-cart',
|
||||
'modules/' . $this->name . '/views/js/cartCrossSell.js',
|
||||
['position' => 'bottom', 'priority' => 150]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
158
modules/crosssellpro/views/css/cartCrossSell.css
Normal file
158
modules/crosssellpro/views/css/cartCrossSell.css
Normal file
@@ -0,0 +1,158 @@
|
||||
.crosssellpro-block {
|
||||
margin: 1.25rem 0;
|
||||
}
|
||||
|
||||
.crosssellpro-block[data-crosssellpro-mode="checkout"] {
|
||||
margin: 0 0 1rem;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.crosssellpro-block[data-crosssellpro-mode="checkout"] .card-block {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.crosssellpro-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.crosssellpro-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.crosssellpro-nav {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.crosssellpro-nav-btn {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border: 1px solid #c8c8c8;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
color: #222;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.crosssellpro-viewport {
|
||||
overflow-x: auto;
|
||||
scrollbar-width: thin;
|
||||
scroll-snap-type: x mandatory;
|
||||
}
|
||||
|
||||
.crosssellpro-track {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.crosssellpro-item {
|
||||
flex: 0 0 calc((100% - 2rem) / 3);
|
||||
min-width: 220px;
|
||||
border: 1px solid #efefef;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
background: #fff;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
.crosssellpro-block[data-crosssellpro-mode="checkout"] .crosssellpro-item {
|
||||
flex: 0 0 100%;
|
||||
min-width: 100%;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.crosssellpro-block[data-crosssellpro-mode="checkout"] .crosssellpro-viewport {
|
||||
overflow-x: auto !important;
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
|
||||
.crosssellpro-block[data-crosssellpro-mode="checkout"] .crosssellpro-track {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: stretch !important;
|
||||
gap: 0 !important;
|
||||
}
|
||||
|
||||
.crosssellpro-block[data-crosssellpro-mode="checkout"] .crosssellpro-item {
|
||||
flex: 0 0 100% !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.crosssellpro-image-link {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.crosssellpro-image-link img {
|
||||
width: 100%;
|
||||
max-height: 140px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.crosssellpro-image-placeholder {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.crosssellpro-name {
|
||||
font-size: 0.95rem;
|
||||
margin: 0;
|
||||
min-height: 2.6em;
|
||||
}
|
||||
|
||||
.crosssellpro-name a {
|
||||
color: #222;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.crosssellpro-price {
|
||||
font-weight: 700;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.crosssellpro-cta {
|
||||
width: 100%;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.crosssellpro-item {
|
||||
flex-basis: calc((100% - 1rem) / 2);
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.crosssellpro-block[data-crosssellpro-mode="checkout"] .crosssellpro-item {
|
||||
flex-basis: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.crosssellpro-item {
|
||||
flex-basis: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.crosssellpro-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
98
modules/crosssellpro/views/js/cartCrossSell.js
Normal file
98
modules/crosssellpro/views/js/cartCrossSell.js
Normal file
@@ -0,0 +1,98 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var positionCartBlocks = function () {
|
||||
var blocks = document.querySelectorAll('[data-crosssellpro-block="1"][data-crosssellpro-mode="cart"]');
|
||||
Array.prototype.forEach.call(blocks, function (block) {
|
||||
var cartContainer = document.querySelector('.card.cart-container');
|
||||
if (cartContainer && cartContainer.parentNode) {
|
||||
cartContainer.insertAdjacentElement('afterend', block);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var getStep = function (block) {
|
||||
var track = block.querySelector('.js-crosssellpro-track');
|
||||
if (!track) {
|
||||
return 280;
|
||||
}
|
||||
var firstItem = track.querySelector('.crosssellpro-item');
|
||||
if (!firstItem) {
|
||||
return 280;
|
||||
}
|
||||
var style = window.getComputedStyle(track);
|
||||
var gap = parseFloat(style.columnGap || style.gap || '0');
|
||||
return firstItem.offsetWidth + gap;
|
||||
};
|
||||
|
||||
positionCartBlocks();
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
var prevBtn = event.target.closest('.js-crosssellpro-prev');
|
||||
if (prevBtn) {
|
||||
var prevBlock = prevBtn.closest('[data-crosssellpro-block="1"]');
|
||||
var prevViewport = prevBlock ? prevBlock.querySelector('.js-crosssellpro-viewport') : null;
|
||||
if (prevViewport && prevBlock) {
|
||||
event.preventDefault();
|
||||
prevViewport.scrollBy({ left: -getStep(prevBlock), behavior: 'smooth' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var nextBtn = event.target.closest('.js-crosssellpro-next');
|
||||
if (nextBtn) {
|
||||
var nextBlock = nextBtn.closest('[data-crosssellpro-block="1"]');
|
||||
var nextViewport = nextBlock ? nextBlock.querySelector('.js-crosssellpro-viewport') : null;
|
||||
if (nextViewport && nextBlock) {
|
||||
event.preventDefault();
|
||||
nextViewport.scrollBy({ left: getStep(nextBlock), behavior: 'smooth' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var addBtn = event.target.closest('[data-crosssellpro-add="1"]');
|
||||
if (!addBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
var block = addBtn.closest('[data-crosssellpro-block="1"]');
|
||||
if (!block) {
|
||||
window.location.href = addBtn.getAttribute('href') || '/';
|
||||
return;
|
||||
}
|
||||
|
||||
var cartUrl = block.getAttribute('data-cart-url');
|
||||
var returnUrl = block.getAttribute('data-return-url');
|
||||
var token = block.getAttribute('data-token');
|
||||
var productId = addBtn.getAttribute('data-id-product');
|
||||
var qty = addBtn.getAttribute('data-qty') || '1';
|
||||
var postAddUrl = addBtn.getAttribute('data-post-add-url');
|
||||
|
||||
if (!cartUrl || !token || !productId) {
|
||||
window.location.href = returnUrl || cartUrl || '/';
|
||||
return;
|
||||
}
|
||||
|
||||
var body = new URLSearchParams();
|
||||
body.set('token', token);
|
||||
body.set('id_product', productId);
|
||||
body.set('qty', qty);
|
||||
body.set('add', '1');
|
||||
body.set('action', 'update');
|
||||
|
||||
fetch(postAddUrl || cartUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
},
|
||||
body: body.toString(),
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(function () {
|
||||
window.location.href = returnUrl || cartUrl;
|
||||
})
|
||||
.catch(function () {
|
||||
window.location.href = returnUrl || cartUrl || '/';
|
||||
});
|
||||
});
|
||||
});
|
||||
70
modules/crosssellpro/views/templates/hook/cartCrossSell.tpl
Normal file
70
modules/crosssellpro/views/templates/hook/cartCrossSell.tpl
Normal file
@@ -0,0 +1,70 @@
|
||||
{if !empty($crosssellpro_products)}
|
||||
{assign var='crosssellpro_is_checkout' value=(isset($crosssellpro_mode) && $crosssellpro_mode == 'checkout')}
|
||||
<section
|
||||
class="crosssellpro-block{if isset($crosssellpro_mode) && $crosssellpro_mode == 'cart'} card{/if}"
|
||||
data-crosssellpro-block="1"
|
||||
data-crosssellpro-mode="{if isset($crosssellpro_mode)}{$crosssellpro_mode|escape:'htmlall':'UTF-8'}{else}cart{/if}"
|
||||
data-cart-url="{$crosssellpro_cart_url}"
|
||||
data-return-url="{$crosssellpro_return_url}"
|
||||
data-token="{$static_token}"
|
||||
>
|
||||
<div class="card-block">
|
||||
<div class="crosssellpro-header">
|
||||
<h2 class="h4 crosssellpro-title">{l s='Produkty, ktore moga Ci sie przydac' mod='crosssellpro'}</h2>
|
||||
<div class="crosssellpro-nav" aria-hidden="true">
|
||||
<button type="button" class="crosssellpro-nav-btn js-crosssellpro-prev" aria-label="{l s='Poprzednie produkty' mod='crosssellpro'}">‹</button>
|
||||
<button type="button" class="crosssellpro-nav-btn js-crosssellpro-next" aria-label="{l s='Nastepne produkty' mod='crosssellpro'}">›</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="crosssellpro-viewport js-crosssellpro-viewport"{if $crosssellpro_is_checkout} style="overflow-x:auto;overflow-y:hidden;"{/if}>
|
||||
<div class="crosssellpro-track js-crosssellpro-track"{if $crosssellpro_is_checkout} style="display:flex;flex-direction:row;flex-wrap:nowrap;gap:0;align-items:stretch;"{/if}>
|
||||
{foreach from=$crosssellpro_products item=product}
|
||||
<article class="crosssellpro-item"{if $crosssellpro_is_checkout} style="flex:0 0 100%;width:100%;max-width:100%;padding:0;border:0;background:transparent;border-radius:0;"{/if}>
|
||||
<a href="{$product.url}" class="crosssellpro-image-link" title="{$product.name|escape:'htmlall':'UTF-8'}">
|
||||
{if !empty($product.cover.bySize.home_default.url)}
|
||||
<img
|
||||
class="img-fluid"
|
||||
src="{$product.cover.bySize.home_default.url}"
|
||||
alt="{$product.name|escape:'htmlall':'UTF-8'}"
|
||||
loading="lazy"
|
||||
>
|
||||
{else}
|
||||
<span class="crosssellpro-image-placeholder"></span>
|
||||
{/if}
|
||||
</a>
|
||||
|
||||
<h3 class="crosssellpro-name">
|
||||
<a href="{$product.url}">{$product.name}</a>
|
||||
</h3>
|
||||
|
||||
<div class="crosssellpro-price">{$product.price}</div>
|
||||
|
||||
{if $product.crosssellpro_requires_selection}
|
||||
<a
|
||||
href="{$product.crosssellpro_cta_url}"
|
||||
class="btn btn-primary crosssellpro-cta"
|
||||
rel="nofollow"
|
||||
>
|
||||
{$product.crosssellpro_cta_label}
|
||||
</a>
|
||||
{else}
|
||||
<a
|
||||
href="{$crosssellpro_return_url}"
|
||||
class="btn btn-primary crosssellpro-cta"
|
||||
data-crosssellpro-add="1"
|
||||
data-id-product="{$product.id_product|intval}"
|
||||
data-qty="{if isset($product.minimal_quantity) && $product.minimal_quantity > 0}{$product.minimal_quantity|intval}{else}1{/if}"
|
||||
data-post-add-url="{$product.crosssellpro_post_add_url}"
|
||||
rel="nofollow"
|
||||
>
|
||||
{$product.crosssellpro_cta_label}
|
||||
</a>
|
||||
{/if}
|
||||
</article>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
@@ -0,0 +1 @@
|
||||
{include file='module:crosssellpro/views/templates/hook/cartCrossSell.tpl'}
|
||||
Reference in New Issue
Block a user