an only present instance of Cart'); } if ($shouldSeparateGifts) { $rawProducts = $cart->getProductsWithSeparatedGifts(); } else { $rawProducts = $cart->getProducts(true); } $products = array_map([$this, 'presentProduct'], $rawProducts); $products = $this->addCustomizedData($products, $cart); $subtotals = []; $productsTotalExcludingTax = $cart->getOrderTotal(false, Cart::ONLY_PRODUCTS); $total_excluding_tax = $cart->getOrderTotal(false); $total_including_tax = $cart->getOrderTotal(true); $total_discount = $cart->getDiscountSubtotalWithoutGifts($this->includeTaxes()); $totalCartAmount = $cart->getOrderTotal($this->includeTaxes(), Cart::ONLY_PRODUCTS); $subtotals['products'] = [ 'type' => 'products', 'label' => $this->translator->trans('Subtotal', [], 'Shop.Theme.Checkout'), 'amount' => $totalCartAmount, 'value' => $this->priceFormatter->format($totalCartAmount), ]; if ($total_discount) { $subtotals['discounts'] = [ 'type' => 'discount', 'label' => $this->translator->trans('Discount(s)', [], 'Shop.Theme.Checkout'), 'amount' => $total_discount, 'value' => $this->priceFormatter->format($total_discount), ]; } else { $subtotals['discounts'] = null; } if ($cart->gift) { $giftWrappingPrice = ($cart->getGiftWrappingPrice($this->includeTaxes()) != 0) ? $cart->getGiftWrappingPrice($this->includeTaxes()) : 0; $subtotals['gift_wrapping'] = [ 'type' => 'gift_wrapping', 'label' => $this->translator->trans('Gift wrapping', [], 'Shop.Theme.Checkout'), 'amount' => $giftWrappingPrice, 'value' => ($giftWrappingPrice > 0) ? $this->priceFormatter->convertAndFormat($giftWrappingPrice) : $this->translator->trans('Free', [], 'Shop.Theme.Checkout'), ]; } if (!$cart->isVirtualCart()) { $shippingCost = $cart->getTotalShippingCost(null, $this->includeTaxes()); } else { $shippingCost = 0; } $subtotals['shipping'] = [ 'type' => 'shipping', 'label' => $this->translator->trans('Shipping', [], 'Shop.Theme.Checkout'), 'amount' => $shippingCost, 'value' => $this->getShippingDisplayValue($cart, $shippingCost), ]; $subtotals['tax'] = null; if (Configuration::get('PS_TAX_DISPLAY')) { $taxAmount = $total_including_tax - $total_excluding_tax; $subtotals['tax'] = [ 'type' => 'tax', 'label' => ($this->includeTaxes()) ? $this->translator->trans('Included taxes', [], 'Shop.Theme.Checkout') : $this->translator->trans('Taxes', [], 'Shop.Theme.Checkout'), 'amount' => $taxAmount, 'value' => $this->priceFormatter->format($taxAmount), ]; } $totals = [ 'total' => [ 'type' => 'total', 'label' => $this->translator->trans('Total', [], 'Shop.Theme.Checkout'), 'amount' => $this->includeTaxes() ? $total_including_tax : $total_excluding_tax, 'value' => $this->priceFormatter->format( $this->includeTaxes() ? $total_including_tax : $total_excluding_tax ), ], 'total_including_tax' => [ 'type' => 'total', 'label' => $this->translator->trans('Total (tax incl.)', [], 'Shop.Theme.Checkout'), 'amount' => $total_including_tax, 'value' => $this->priceFormatter->format($total_including_tax), ], 'total_excluding_tax' => [ 'type' => 'total', 'label' => $this->translator->trans('Total (tax excl.)', [], 'Shop.Theme.Checkout'), 'amount' => $total_excluding_tax, 'value' => $this->priceFormatter->format($total_excluding_tax), ], ]; $products_count = array_reduce($products, function ($count, $product) { return $count + $product['quantity']; }, 0); $summary_string = $products_count === 1 ? $this->translator->trans('1 item', [], 'Shop.Theme.Checkout') : $this->translator->trans('%count% items', ['%count%' => $products_count], 'Shop.Theme.Checkout'); $minimalPurchase = $this->priceFormatter->convertAmount((float) Configuration::get('PS_PURCHASE_MINIMUM')); Hook::exec('overrideMinimalPurchasePrice', [ 'minimalPurchase' => &$minimalPurchase, ]); // TODO: move it to a common parent, since it's copied in OrderPresenter and ProductPresenter $labels = [ 'tax_short' => ($this->includeTaxes()) ? $this->translator->trans('(tax incl.)', [], 'Shop.Theme.Global') : $this->translator->trans('(tax excl.)', [], 'Shop.Theme.Global'), 'tax_long' => ($this->includeTaxes()) ? $this->translator->trans('(tax included)', [], 'Shop.Theme.Global') : $this->translator->trans('(tax excluded)', [], 'Shop.Theme.Global'), ]; $discounts = $cart->getDiscounts(); $vouchers = $this->getTemplateVarVouchers($cart); $cartRulesIds = array_flip(array_map( function ($voucher) { return $voucher['id_cart_rule']; }, $vouchers['added'] )); $discounts = array_filter($discounts, function ($discount) use ($cartRulesIds, $cart) { $voucherCustomerId = (int) $discount['id_customer']; $voucherIsRestrictedToASingleCustomer = ($voucherCustomerId !== 0); $voucherIsEmptyCode = empty($discount['code']); if ($voucherIsRestrictedToASingleCustomer && $cart->id_customer !== $voucherCustomerId && $voucherIsEmptyCode) { return false; } return !array_key_exists($discount['id_cart_rule'], $cartRulesIds); }); $result = [ 'products' => $products, 'totals' => $totals, 'subtotals' => $subtotals, 'products_count' => $products_count, 'summary_string' => $summary_string, 'labels' => $labels, 'id_address_delivery' => $cart->id_address_delivery, 'id_address_invoice' => $cart->id_address_invoice, 'is_virtual' => $cart->isVirtualCart(), 'vouchers' => $vouchers, 'discounts' => $discounts, 'minimalPurchase' => $minimalPurchase, 'minimalPurchaseRequired' => ($productsTotalExcludingTax < $minimalPurchase) ? $this->translator->trans( 'A minimum shopping cart total of %amount% (tax excl.) is required to validate your order. Current cart total is %total% (tax excl.).', [ '%amount%' => $this->priceFormatter->format($minimalPurchase), '%total%' => $this->priceFormatter->format($productsTotalExcludingTax), ], 'Shop.Theme.Checkout' ) : '', ]; Hook::exec('actionPresentCart', ['presentedCart' => &$result] ); return $result; } /** * Accepts a cart object with the shipping cost amount and formats the shipping cost display value accordingly. * If the shipping cost is 0, then we must check if this is because of a free carrier and thus display 'Free' or * simply because the system was unable to determine shipping cost at this point and thus send an empty string to hide the shipping line. * * @param Cart $cart * @param float $shippingCost * * @return string */ private function getShippingDisplayValue($cart, $shippingCost) { $shippingDisplayValue = ''; // if one of the applied cart rules have free shipping, then the shipping display value is 'Free' foreach ($cart->getCartRules() as $rule) { if ($rule['free_shipping'] && !$rule['carrier_restriction']) { return $this->translator->trans('Free', [], 'Shop.Theme.Checkout'); } } if ($shippingCost != 0) { $shippingDisplayValue = $this->priceFormatter->format($shippingCost); } else { $defaultCountry = null; if (isset(Context::getContext()->cookie->id_country)) { $defaultCountry = new Country(Context::getContext()->cookie->id_country); } $deliveryOptionList = $cart->getDeliveryOptionList($defaultCountry); if (count($deliveryOptionList) > 0) { foreach ($deliveryOptionList as $option) { foreach ($option as $currentCarrier) { if (isset($currentCarrier['is_free']) && $currentCarrier['is_free'] > 0) { $shippingDisplayValue = $this->translator->trans('Free', [], 'Shop.Theme.Checkout'); break 2; } } } } } return $shippingDisplayValue; } private function getTemplateVarVouchers(Cart $cart) { $cartVouchers = $cart->getCartRules(); $vouchers = []; $cartHasTax = null === $cart->id ? false : $cart::getTaxesAverageUsed($cart); $freeShippingAlreadySet = false; foreach ($cartVouchers as $cartVoucher) { $vouchers[$cartVoucher['id_cart_rule']]['id_cart_rule'] = $cartVoucher['id_cart_rule']; $vouchers[$cartVoucher['id_cart_rule']]['name'] = $cartVoucher['name']; $vouchers[$cartVoucher['id_cart_rule']]['code'] = $cartVoucher['code']; $vouchers[$cartVoucher['id_cart_rule']]['reduction_percent'] = $cartVoucher['reduction_percent']; $vouchers[$cartVoucher['id_cart_rule']]['reduction_currency'] = $cartVoucher['reduction_currency']; $vouchers[$cartVoucher['id_cart_rule']]['free_shipping'] = (bool) $cartVoucher['free_shipping']; // Voucher reduction depending of the cart tax rule // if $cartHasTax & voucher is tax excluded, set amount voucher to tax included if ($cartHasTax && $cartVoucher['reduction_tax'] == '0') { $cartVoucher['reduction_amount'] = $cartVoucher['reduction_amount'] * (1 + $cartHasTax / 100); } $vouchers[$cartVoucher['id_cart_rule']]['reduction_amount'] = $cartVoucher['reduction_amount']; if ($this->cartVoucherHasGiftProductReduction($cartVoucher)) { $cartVoucher['reduction_amount'] = $cartVoucher['value_real']; } $totalCartVoucherReduction = 0; if (!$this->cartVoucherHasPercentReduction($cartVoucher) && !$this->cartVoucherHasAmountReduction($cartVoucher) && !$this->cartVoucherHasGiftProductReduction($cartVoucher)) { $freeShippingOnly = true; if ($freeShippingAlreadySet) { unset($vouchers[$cartVoucher['id_cart_rule']]); continue; } else { $freeShippingAlreadySet = true; } } else { $freeShippingOnly = false; $totalCartVoucherReduction = $this->includeTaxes() ? $cartVoucher['value_real'] : $cartVoucher['value_tax_exc']; } // when a voucher has only a shipping reduction, the value displayed must be "Free Shipping" if ($freeShippingOnly) { $cartVoucher['reduction_formatted'] = $this->translator->trans( 'Free shipping', [], 'Admin.Shipping.Feature' ); } else { $cartVoucher['reduction_formatted'] = '-' . $this->priceFormatter->format($totalCartVoucherReduction); } $vouchers[$cartVoucher['id_cart_rule']]['reduction_formatted'] = $cartVoucher['reduction_formatted']; $vouchers[$cartVoucher['id_cart_rule']]['delete_url'] = $this->link->getPageLink( 'cart', true, null, [ 'deleteDiscount' => $cartVoucher['id_cart_rule'], 'token' => Tools::getToken(false), ] ); } return [ 'allowed' => (int) CartRule::isFeatureActive(), 'added' => $vouchers, ]; } /** * @param array $cartVoucher * * @return bool */ private function cartVoucherHasFreeShipping($cartVoucher) { return !empty($cartVoucher['free_shipping']); } /** * @param array $cartVoucher * * @return bool */ private function cartVoucherHasPercentReduction($cartVoucher) { return isset($cartVoucher['reduction_percent']) && $cartVoucher['reduction_percent'] > 0 && $cartVoucher['reduction_amount'] == '0.00'; } /** * @param array $cartVoucher * * @return bool */ private function cartVoucherHasAmountReduction($cartVoucher) { return isset($cartVoucher['reduction_amount']) && $cartVoucher['reduction_amount'] > 0; } /** * @param array $cartVoucher * * @return bool */ private function cartVoucherHasGiftProductReduction($cartVoucher) { return !empty($cartVoucher['gift_product']); } /** * Receives a string containing a list of attributes affected to the product and returns them as an array. * * @param string $attributes * * @return array Converted attributes in an array */ protected function getAttributesArrayFromString($attributes) { $separator = Configuration::get('PS_ATTRIBUTE_ANCHOR_SEPARATOR'); $pattern = '/(?>(?P[^:]+:[^:]+)' . $separator . '+(?!' . $separator . '([^:' . $separator . '])+:))/'; $attributesArray = []; $matches = []; if (!preg_match_all($pattern, $attributes . $separator, $matches)) { return $attributesArray; } foreach ($matches['attribute'] as $attribute) { list($key, $value) = explode(':', $attribute); $attributesArray[trim($key)] = ltrim($value); } return $attributesArray; } protected function getSettings(): ProductPresentationSettings { if ($this->settings === null) { $this->settings = new ProductPresentationSettings(); $this->settings->catalog_mode = Configuration::isCatalogMode(); $this->settings->catalog_mode_with_prices = (int) Configuration::get('PS_CATALOG_MODE_WITH_PRICES'); $this->settings->include_taxes = $this->includeTaxes(); $this->settings->allow_add_variant_to_cart_from_listing = (int) Configuration::get('PS_ATTRIBUTE_CATEGORY_DISPLAY'); $this->settings->stock_management_enabled = Configuration::get('PS_STOCK_MANAGEMENT'); $this->settings->showPrices = Configuration::showPrices(); $this->settings->showLabelOOSListingPages = (bool) Configuration::get('PS_SHOW_LABEL_OOS_LISTING_PAGES'); } return $this->settings; } protected function getProductAssembler(): ProductAssembler { if ($this->productAssembler === null) { $this->productAssembler = new ProductAssembler(Context::getContext()); } return $this->productAssembler; } }