diff --git a/vendor/magento/module-quote/Model/CartLockedException.php b/vendor/magento/module-quote/Model/CartLockedException.php
new file mode 100644
index 0000000..7f2aa87
--- /dev/null
+++ b/vendor/magento/module-quote/Model/CartLockedException.php
@@ -0,0 +1,18 @@
+lockManager = $lockManager;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function execute(int $id, callable $callable, array $args = [])
+ {
+ $lockName = 'cart_lock_' . $id;
+
+ if (!$this->lockManager->lock($lockName, 0)) {
+ $this->logger->critical(
+ 'The cart is locked for processing, the request has been aborted. Quote ID: ' . $id
+ );
+ throw new CartLockedException(
+ __('The cart is locked for processing. Please try again later.')
+ );
+ }
+
+ try {
+ $result = $callable(...$args);
+ } finally {
+ $this->lockManager->unlock($lockName);
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/magento/module-quote/Model/CartMutexInterface.php b/vendor/magento/module-quote/Model/CartMutexInterface.php
new file mode 100644
index 0000000..6681c63
--- /dev/null
+++ b/vendor/magento/module-quote/Model/CartMutexInterface.php
@@ -0,0 +1,25 @@
+eventManager = $eventManager;
$this->submitQuoteValidator = $submitQuoteValidator;
@@ -269,8 +268,8 @@ class QuoteManagement implements CartManagementInterface
->get(RequestInterface::class);
$this->remoteAddress = $remoteAddress ?: ObjectManager::getInstance()
->get(RemoteAddress::class);
- $this->lockManager = $lockManager ?: ObjectManager::getInstance()
- ->get(LockManagerInterface::class);
+ $this->cartMutex = $cartMutex
+ ?? ObjectManager::getInstance()->get(CartMutexInterface::class);
}
/**
@@ -396,10 +395,28 @@ class QuoteManagement implements CartManagementInterface
/**
* @inheritdoc
+ */
+ public function placeOrder($cartId, PaymentInterface $paymentMethod = null)
+ {
+ return $this->cartMutex->execute(
+ (int)$cartId,
+ \Closure::fromCallable([$this, 'placeOrderRun']),
+ [$cartId, $paymentMethod]
+ );
+ }
+
+ /**
+ * Places an order for a specified cart.
+ *
+ * @param int $cartId The cart ID.
+ * @param PaymentInterface|null $paymentMethod
+ * @throws CouldNotSaveException
+ * @return int Order ID.
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
*/
- public function placeOrder($cartId, PaymentInterface $paymentMethod = null)
+ private function placeOrderRun($cartId, PaymentInterface $paymentMethod = null)
{
$quote = $this->quoteRepository->getActive($cartId);
$customer = $quote->getCustomer();
@@ -613,14 +630,7 @@ class QuoteManagement implements CartManagementInterface
]
);
- $lockedName = self::LOCK_PREFIX . $quote->getId();
- if ($this->lockManager->isLocked($lockedName)) {
- throw new LocalizedException(__(
- 'A server error stopped your order from being placed. Please try to place your order again.'
- ));
- }
try {
- $this->lockManager->lock($lockedName, self::LOCK_TIMEOUT);
$order = $this->orderManagement->place($order);
$quote->setIsActive(false);
$this->eventManager->dispatch(
@@ -631,9 +641,7 @@ class QuoteManagement implements CartManagementInterface
]
);
$this->quoteRepository->save($quote);
- $this->lockManager->unlock($lockedName);
} catch (\Exception $e) {
- $this->lockManager->unlock($lockedName);
$this->rollbackAddresses($quote, $order, $e);
throw $e;
}
diff --git a/vendor/magento/module-quote/Model/QuoteRepository.php b/vendor/magento/module-quote/Model/QuoteRepository.php
index b1bef83..f4694d6 100644
--- a/vendor/magento/module-quote/Model/QuoteRepository.php
+++ b/vendor/magento/module-quote/Model/QuoteRepository.php
@@ -12,6 +12,7 @@ use Magento\Framework\Api\SearchCriteria\CollectionProcessor;
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\App\RequestSafetyInterface;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Quote\Api\CartRepositoryInterface;
@@ -94,6 +95,11 @@ class QuoteRepository implements CartRepositoryInterface
private $cartFactory;
/**
+ * @var RequestSafetyInterface
+ */
+ private $requestSafety;
+
+ /**
* Constructor
*
* @param QuoteFactory $quoteFactory
@@ -104,6 +110,7 @@ class QuoteRepository implements CartRepositoryInterface
* @param CollectionProcessorInterface|null $collectionProcessor
* @param QuoteCollectionFactory|null $quoteCollectionFactory
* @param CartInterfaceFactory|null $cartFactory
+ * @param RequestSafetyInterface|null $requestSafety
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
@@ -114,7 +121,8 @@ class QuoteRepository implements CartRepositoryInterface
JoinProcessorInterface $extensionAttributesJoinProcessor,
CollectionProcessorInterface $collectionProcessor = null,
QuoteCollectionFactory $quoteCollectionFactory = null,
- CartInterfaceFactory $cartFactory = null
+ CartInterfaceFactory $cartFactory = null,
+ RequestSafetyInterface $requestSafety = null
) {
$this->quoteFactory = $quoteFactory;
$this->storeManager = $storeManager;
@@ -125,6 +133,7 @@ class QuoteRepository implements CartRepositoryInterface
$this->quoteCollectionFactory = $quoteCollectionFactory ?: ObjectManager::getInstance()
->get(QuoteCollectionFactory::class);
$this->cartFactory = $cartFactory ?: ObjectManager::getInstance()->get(CartInterfaceFactory::class);
+ $this->requestSafety = $requestSafety ?: ObjectManager::getInstance()->get(RequestSafetyInterface::class);
}
/**
@@ -165,6 +174,7 @@ class QuoteRepository implements CartRepositoryInterface
*/
public function getActive($cartId, array $sharedStoreIds = [])
{
+ $this->validateCachedActiveQuote((int)$cartId);
$quote = $this->get($cartId, $sharedStoreIds);
if (!$quote->getIsActive()) {
throw NoSuchEntityException::singleField('cartId', $cartId);
@@ -173,10 +183,32 @@ class QuoteRepository implements CartRepositoryInterface
}
/**
+ * Validates if cached quote is still active.
+ *
+ * @param int $cartId
+ * @return void
+ * @throws NoSuchEntityException
+ */
+ private function validateCachedActiveQuote(int $cartId): void
+ {
+ if (isset($this->quotesById[$cartId]) && !$this->requestSafety->isSafeMethod()) {
+ $quote = $this->cartFactory->create();
+ if (is_callable([$quote, 'setSharedStoreIds'])) {
+ $quote->setSharedStoreIds(['*']);
+ }
+ $quote->loadActive($cartId);
+ if (!$quote->getIsActive()) {
+ throw NoSuchEntityException::singleField('cartId', $cartId);
+ }
+ }
+ }
+
+ /**
* @inheritdoc
*/
public function getActiveForCustomer($customerId, array $sharedStoreIds = [])
{
+ $this->validateCachedCustomerActiveQuote((int)$customerId);
$quote = $this->getForCustomer($customerId, $sharedStoreIds);
if (!$quote->getIsActive()) {
throw NoSuchEntityException::singleField('customerId', $customerId);
@@ -185,6 +217,28 @@ class QuoteRepository implements CartRepositoryInterface
}
/**
+ * Validates if cached customer quote is still active.
+ *
+ * @param int $customerId
+ * @return void
+ * @throws NoSuchEntityException
+ */
+ private function validateCachedCustomerActiveQuote(int $customerId): void
+ {
+ if (isset($this->quotesByCustomerId[$customerId]) && !$this->requestSafety->isSafeMethod()) {
+ $quoteId = $this->quotesByCustomerId[$customerId]->getId();
+ $quote = $this->cartFactory->create();
+ if (is_callable([$quote, 'setSharedStoreIds'])) {
+ $quote->setSharedStoreIds(['*']);
+ }
+ $quote->loadActive($quoteId);
+ if (!$quote->getIsActive()) {
+ throw NoSuchEntityException::singleField('customerId', $customerId);
+ }
+ }
+ }
+
+ /**
* @inheritdoc
*/
public function save(CartInterface $quote)
diff --git a/vendor/magento/module-quote/etc/di.xml b/vendor/magento/module-quote/etc/di.xml
index 5ffc82d..cef8eee 100644
--- a/vendor/magento/module-quote/etc/di.xml
+++ b/vendor/magento/module-quote/etc/di.xml
@@ -45,6 +45,7 @@
+
diff --git a/vendor/magento/module-quote/i18n/en_US.csv b/vendor/magento/module-quote/i18n/en_US.csv
index d96c88b..5d3fe79 100644
--- a/vendor/magento/module-quote/i18n/en_US.csv
+++ b/vendor/magento/module-quote/i18n/en_US.csv
@@ -69,3 +69,4 @@ Carts,Carts
"Validated Country Code","Validated Country Code"
"Validated Vat Number","Validated Vat Number"
"Invalid Quote Item id %1","Invalid Quote Item id %1"
+"The cart is locked for processing. Please try again later.","The cart is locked for processing. Please try again later."
diff --git a/vendor/magento/module-quote-graph-ql/Model/Cart/PlaceOrderMutex.php b/vendor/magento/module-quote-graph-ql/Model/Cart/PlaceOrderMutex.php
deleted file mode 100644
index 2b13086..0000000
--- a/vendor/magento/module-quote-graph-ql/Model/Cart/PlaceOrderMutex.php
+++ /dev/null
@@ -1,72 +0,0 @@
-lockManager = $lockManager;
- $this->lockWaitTimeout = $lockWaitTimeout;
- }
-
- /**
- * @inheritDoc
- */
- public function execute(string $maskedId, callable $callable, array $args = [])
- {
- if (empty($maskedId)) {
- throw new \InvalidArgumentException('Quote masked id must be provided');
- }
-
- if ($this->lockManager->isLocked(self::LOCK_PREFIX . $maskedId)) {
- throw new GraphQlAlreadyExistsException(
- __('The order has already been placed and is currently processing.')
- );
- }
-
- if ($this->lockManager->lock(self::LOCK_PREFIX . $maskedId, $this->lockWaitTimeout)) {
- try {
- return $callable(...$args);
- } finally {
- $this->lockManager->unlock(self::LOCK_PREFIX . $maskedId);
- }
- } else {
- throw new LocalizedException(
- __('Could not acquire lock for the quote id: %1', $maskedId)
- );
- }
- }
-}
diff --git a/vendor/magento/module-quote-graph-ql/Model/Cart/PlaceOrderMutexInterface.php b/vendor/magento/module-quote-graph-ql/Model/Cart/PlaceOrderMutexInterface.php
deleted file mode 100644
index 6e4c85d..0000000
--- a/vendor/magento/module-quote-graph-ql/Model/Cart/PlaceOrderMutexInterface.php
+++ /dev/null
@@ -1,27 +0,0 @@
-getCartForCheckout = $getCartForCheckout;
$this->placeOrder = $placeOrder;
$this->orderRepository = $orderRepository;
$this->errorMessageFormatter = $errorMessageFormatter;
- $this->placeOrderMutex = $placeOrderMutex ?: ObjectManager::getInstance()->get(PlaceOrderMutexInterface::class);
}
/**
@@ -80,25 +70,6 @@ class PlaceOrder implements ResolverInterface
throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
}
- return $this->placeOrderMutex->execute(
- $args['input']['cart_id'],
- \Closure::fromCallable([$this, 'run']),
- [$field, $context, $info, $args]
- );
- }
-
- /**
- * Run the resolver.
- *
- * @param Field $field
- * @param ContextInterface $context
- * @param ResolveInfo $info
- * @param array|null $args
- * @return array[]
- * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
- */
- private function run(Field $field, ContextInterface $context, ResolveInfo $info, ?array $args): array
- {
$maskedCartId = $args['input']['cart_id'];
$userId = (int)$context->getUserId();
$storeId = (int)$context->getExtensionAttributes()->getStore()->getId();
diff --git a/vendor/magento/module-quote-graph-ql/etc/di.xml b/vendor/magento/module-quote-graph-ql/etc/di.xml
index 63eb001..4b55681 100644
--- a/vendor/magento/module-quote-graph-ql/etc/di.xml
+++ b/vendor/magento/module-quote-graph-ql/etc/di.xml
@@ -9,7 +9,6 @@
-