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 */ 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] ); } }