Files
2026-09-15 10:35:56 +08:00

165 lines
6.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Esun\Cart\Controller\Mix;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product;
use Magento\Checkout\Model\Cart;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\DataObject;
use Magento\Framework\Data\Form\FormKey\Validator as FormKeyValidator;
use Magento\Framework\Exception\LocalizedException;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface;
class Add extends Action implements HttpPostActionInterface
{
public function __construct(
Context $context,
private readonly FormKeyValidator $formKeyValidator,
private readonly ProductRepositoryInterface $productRepository,
private readonly StoreManagerInterface $storeManager,
private readonly Cart $cart,
private readonly LoggerInterface $logger
) {
parent::__construct($context);
}
public function execute(): Redirect
{
$redirect = $this->resultRedirectFactory->create();
$backUrl = (string)$this->_redirect->getRefererUrl();
if (!$this->formKeyValidator->validate($this->getRequest())) {
$this->messageManager->addErrorMessage(__('Your session has expired. Please try again.'));
return $redirect->setUrl($backUrl);
}
try {
$parentProduct = $this->getParentProduct();
$items = $this->getRequestedItems();
$this->validateChildren($parentProduct, array_keys($items));
$configurableAttributes = $parentProduct->getTypeInstance()
->getConfigurableAttributes($parentProduct);
$itemsToAdd = $items;
$isSyncMode = (string)$this->getRequest()->getParam('mix_mode') === 'sync';
if ($isSyncMode) {
$updates = [];
foreach ($this->cart->getQuote()->getAllVisibleItems() as $quoteItem) {
if ((int)$quoteItem->getProductId() !== (int)$parentProduct->getId()) {
continue;
}
$simpleProductOption = $quoteItem->getOptionByCode('simple_product');
$childProductId = (int)($simpleProductOption?->getProduct()?->getId() ?: 0);
if ($childProductId > 0 && isset($items[$childProductId])) {
$updates[(int)$quoteItem->getId()] = ['qty' => $items[$childProductId]];
unset($itemsToAdd[$childProductId]);
} else {
$updates[(int)$quoteItem->getId()] = ['remove' => true];
}
}
if ($updates) {
$this->cart->updateItems($updates);
}
}
foreach ($itemsToAdd as $childProductId => $quantity) {
$childProduct = $this->productRepository->getById(
$childProductId,
false,
(int)$this->storeManager->getStore()->getId()
);
if (!$childProduct->isSaleable()) {
throw new LocalizedException(
__('%1 is currently unavailable.', $childProduct->getName())
);
}
$superAttributes = [];
foreach ($configurableAttributes as $configurableAttribute) {
$attribute = $configurableAttribute->getProductAttribute();
$superAttributes[(int)$attribute->getId()] = $childProduct->getData(
$attribute->getAttributeCode()
);
}
$this->cart->addProduct(clone $parentProduct, new DataObject([
'qty' => $quantity,
'super_attribute' => $superAttributes,
]));
}
$this->cart->save();
$totalQuantity = array_sum($items);
$this->messageManager->addSuccessMessage($isSyncMode
? __('You updated %1 variants (%2 items) in your shopping cart.', count($items), $totalQuantity)
: __('You added %1 variants (%2 items) to your shopping cart.', count($items), $totalQuantity));
} catch (LocalizedException $exception) {
$this->messageManager->addErrorMessage($exception->getMessage());
} catch (\Throwable $exception) {
$this->logger->critical($exception);
$this->messageManager->addErrorMessage(
__('We can\'t add these items to your shopping cart right now.')
);
}
return $redirect->setUrl($backUrl);
}
private function getParentProduct(): Product
{
$productId = (int)$this->getRequest()->getParam('product');
$product = $this->productRepository->getById(
$productId,
false,
(int)$this->storeManager->getStore()->getId()
);
if ($product->getTypeId() !== Configurable::TYPE_CODE || !$product->isSaleable()) {
throw new LocalizedException(__('This product is not available for mix purchasing.'));
}
return $product;
}
/** @return array<int, int> */
private function getRequestedItems(): array
{
$requestItems = $this->getRequest()->getParam('mix_items', []);
$items = [];
if (is_array($requestItems)) {
foreach ($requestItems as $childProductId => $quantity) {
$childProductId = (int)$childProductId;
$quantity = (int)$quantity;
if ($childProductId > 0 && $quantity > 0) {
$items[$childProductId] = $quantity;
}
}
}
if (!$items) {
throw new LocalizedException(__('Please choose at least one variant and quantity.'));
}
return $items;
}
/** @param int[] $childProductIds */
private function validateChildren(Product $parentProduct, array $childProductIds): void
{
$allowedIds = array_map(
'intval',
$parentProduct->getTypeInstance()->getUsedProductIds($parentProduct)
);
if (array_diff($childProductIds, $allowedIds)) {
throw new LocalizedException(__('One or more selected variants do not belong to this product.'));
}
}
}