Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/controllers/CartController.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public function behaviors(): array
return array_merge(parent::behaviors(), [
'rateLimiter' => [
'class' => RateLimiter::class,
'only' => ['get-cart', 'update-cart', 'load-cart', 'complete'],
'only' => ['get-cart', 'get-static-cart', 'update-cart', 'load-cart', 'complete'],
'enableRateLimitHeaders' => false,
'user' => function() {
// Only apply rate limiting when a cart number is explicitly passed
Expand Down Expand Up @@ -119,6 +119,21 @@ public function actionGetCart(): Response
]);
}

/**
* Returns the existing cart for this session without creating one, setting cookies, or touching the session.
* Returns null (as empty cart data) if no cart exists for the current session.
*/
public function actionGetStaticCart(): Response
{
$this->requireAcceptsJson();

$cart = Plugin::getInstance()->getCarts()->getStaticCart();

return $this->asSuccess(data: [
$this->_cartVariable => $cart ? $this->cartArray($cart) : null,
]);
}

/**
* Updates the cart by adding purchasables to the cart, updating line items, or updating various cart attributes.
*
Expand Down
40 changes: 36 additions & 4 deletions src/services/Carts.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,35 @@ public function getCart(bool $forceSave = false): Order
return $this->_cart;
}

/**
* Returns the existing cart for this session without creating one, setting cookies, or touching the session.
* Returns null if no cart cookie is present or no matching cart exists.
*/
public function getStaticCart(): ?Order
{
if (isset($this->_cart)) {
return $this->_cart;
}

if ($this->_cartNumber === false) {
return null;
}

if (!$this->_cartNumber) {
$this->_cartNumber = Craft::$app->getRequest()->getCookies()->getValue($this->cartCookie['name'], false) ?: null;
}

if (!$this->_cartNumber) {
return null;
}

return Order::find()->number($this->_cartNumber)->isCompleted(false)->one();
}

/**
* Get the current cart for this session.
*/
private function _getCart(): ?Order
private function _getCart(bool $forgetInvalidCart = true, bool $checkAnonymousCartSession = true): ?Order
{
$number = $this->getSessionCartNumber();
/** @var Order|null $cart */
Expand All @@ -227,7 +252,9 @@ private function _getCart(): ?Order

// If the cart is already completed or trashed, forget the cart and start again.
if ($cart && ($cart->isCompleted || $cart->trashed)) {
$this->forgetCart();
if ($forgetInvalidCart) {
$this->forgetCart();
}
return null;
}

Expand All @@ -237,7 +264,10 @@ private function _getCart(): ?Order

// Did an anonymous user provide an email that belonged to a credentialed user?
// See CartController::actionUpdate()
$anonymousCartWithCredentialedCustomer = $cart && Craft::$app->getSession()->get('commerce:anonymousCartWithCredentialedCustomer:' . $cart->number, false);
$anonymousCartWithCredentialedCustomer = false;
if ($checkAnonymousCartSession && $cart) {
$anonymousCartWithCredentialedCustomer = Craft::$app->getSession()->get('commerce:anonymousCartWithCredentialedCustomer:' . $cart->number, false);
}

if ($cart && $cartCustomer && $cartCustomer->getIsCredentialed() &&
(
Expand All @@ -248,7 +278,9 @@ private function _getCart(): ?Order
($currentUser && $currentUser->id != $cartCustomer->id)
)
) {
$this->forgetCart();
if ($forgetInvalidCart) {
$this->forgetCart();
}
return null;
}

Expand Down
62 changes: 62 additions & 0 deletions tests/unit/services/CartsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,66 @@ public function testGetCartSwitchCustomer(): void
Craft::$app->getUser()->setIdentity($originalIdentity);
Craft::$app->getElements()->deleteElement($cart, true);
}

public function testGetStaticCartDoesNotStartCartSession(): void
{
$originalCarts = Plugin::getInstance()->getCarts();
$cartNumber = $originalCarts->generateCartNumber();
$cookieName = $originalCarts->cartCookie['name'];

$order = new Order();
$order->number = $cartNumber;
Craft::$app->getElements()->saveElement($order, false);

$carts = $this->make(Carts::class, [
'setSessionCartNumber' => function() {
self::fail('Static cart retrieval should not update the cart session.');
},
]);
$carts->cartCookie = ['name' => $cookieName];
Plugin::getInstance()->set('carts', $carts);

$requestCookies = new \yii\web\CookieCollection();
$requestCookies->add(new \yii\web\Cookie([
'name' => $cookieName,
'value' => $cartNumber,
]));
$originalRequest = Craft::$app->getRequest();
$requestMock = $this->make(Request::class, [
'getCookies' => $requestCookies,
]);
Craft::$app->set('request', $requestMock);

try {
$cart = Plugin::getInstance()->getCarts()->getStaticCart();

self::assertNotNull($cart);
self::assertSame($cartNumber, $cart->number);
} finally {
Craft::$app->set('request', $originalRequest);
Craft::$app->getElements()->deleteElement($order, true);
}
}

public function testGetStaticCartReturnsNullWithNoCookie(): void
{
$cookieName = Plugin::getInstance()->getCarts()->cartCookie['name'];

$carts = $this->make(Carts::class);
$carts->cartCookie = ['name' => $cookieName];
Plugin::getInstance()->set('carts', $carts);

$originalRequest = Craft::$app->getRequest();
$requestMock = $this->make(Request::class, [
'getCookies' => new \yii\web\CookieCollection(),
]);
Craft::$app->set('request', $requestMock);

try {
$cart = Plugin::getInstance()->getCarts()->getStaticCart();
self::assertNull($cart);
} finally {
Craft::$app->set('request', $originalRequest);
}
}
}
Loading