<?php
/*
* This file is part of EC-CUBE
*
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
*
* http://www.ec-cube.co.jp/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Eccube\Service;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\UnitOfWork;
use Eccube\Entity\Cart;
use Eccube\Entity\CartItem;
use Customize\Entity\CartItemTrait;
use Eccube\Entity\Customer;
use Eccube\Entity\ItemHolderInterface;
use Eccube\Entity\ProductClass;
use Eccube\Repository\CartRepository;
use Eccube\Repository\OrderRepository;
use Eccube\Repository\ProductClassRepository;
use Eccube\Service\Cart\CartItemAllocator;
use Eccube\Service\Cart\CartItemComparator;
use Eccube\Util\StringUtil;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class CartService
{
/**
* @var Cart[]
*/
protected $carts;
/**
* @var SessionInterface
*/
protected $session;
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
protected $entityManager;
/**
* @var ItemHolderInterface
*
* @deprecated
*/
protected $cart;
/**
* @var ProductClassRepository
*/
protected $productClassRepository;
/**
* @var CartRepository
*/
protected $cartRepository;
/**
* @var CartItemComparator
*/
protected $cartItemComparator;
/**
* @var CartItemAllocator
*/
protected $cartItemAllocator;
/**
* @var OrderRepository
*/
protected $orderRepository;
/**
* @var TokenStorageInterface
*/
protected $tokenStorage;
/**
* @var AuthorizationCheckerInterface
*/
protected $authorizationChecker;
/**
* CartService constructor.
*/
public function __construct(
SessionInterface $session,
EntityManagerInterface $entityManager,
ProductClassRepository $productClassRepository,
CartRepository $cartRepository,
CartItemComparator $cartItemComparator,
CartItemAllocator $cartItemAllocator,
OrderRepository $orderRepository,
TokenStorageInterface $tokenStorage,
AuthorizationCheckerInterface $authorizationChecker
) {
$this->session = $session;
$this->entityManager = $entityManager;
$this->productClassRepository = $productClassRepository;
$this->cartRepository = $cartRepository;
$this->cartItemComparator = $cartItemComparator;
$this->cartItemAllocator = $cartItemAllocator;
$this->orderRepository = $orderRepository;
$this->tokenStorage = $tokenStorage;
$this->authorizationChecker = $authorizationChecker;
}
/**
* 現在のカートの配列を取得する.
*
* 本サービスのインスタンスのメンバーが空の場合は、DBまたはセッションからカートを取得する
*
* @param bool $empty_delete true の場合、商品明細が空のカートが存在した場合は削除する
*
* @return Cart[]
*/
public function getCarts($empty_delete = false)
{
if (null !== $this->carts) {
if ($empty_delete) {
$cartKeys = [];
foreach (array_keys($this->carts) as $index) {
$Cart = $this->carts[$index];
if ($Cart->getItems()->count() > 0) {
$cartKeys[] = $Cart->getCartKey();
} else {
$this->entityManager->remove($this->carts[$index]);
$this->entityManager->flush();
unset($this->carts[$index]);
}
}
$this->session->set('cart_keys', $cartKeys);
}
return $this->carts;
}
if ($this->getUser()) {
$this->carts = $this->getPersistedCarts();
} else {
$this->carts = $this->getSessionCarts();
}
return $this->carts;
}
/**
* 永続化されたカートを返す
*
* @return Cart[]
*/
public function getPersistedCarts()
{
return $this->cartRepository->findBy(['Customer' => $this->getUser()]);
}
/**
* セッションにあるカートを返す
*
* @return Cart[]
*/
public function getSessionCarts()
{
$cartKeys = $this->session->get('cart_keys', []);
if (empty($cartKeys)) {
return [];
}
return $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'ASC']);
}
/**
* 会員が保持する永続化されたカートと、非会員時のカートをマージする.
*/
public function mergeFromPersistedCart()
{
$persistedCarts = $this->getPersistedCarts();
$sessionCarts = $this->getSessionCarts();
$CartItems = [];
// 永続化されたカートとセッションのカートが同一の場合はマージしない #4574
$cartKeys = $this->session->get('cart_keys', []);
if ((count($persistedCarts) > 0) && !in_array($persistedCarts[0]->getCartKey(), $cartKeys, true)) {
foreach ($persistedCarts as $Cart) {
$CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
}
}
// セッションにある非会員カートとDBから取得した会員カートをマージする.
foreach ($sessionCarts as $Cart) {
$CartItems = $this->mergeCartItems($Cart->getCartItems(), $CartItems);
}
$this->restoreCarts($CartItems);
}
/**
* @return Cart|null
*/
public function getCart()
{
$Carts = $this->getCarts();
if (empty($Carts)) {
return null;
}
$cartKeys = $this->session->get('cart_keys', []);
$Cart = null;
if (count($cartKeys) > 0) {
foreach ($Carts as $cart) {
if ($cart->getCartKey() === current($cartKeys)) {
$Cart = $cart;
break;
}
}
} else {
$Cart = $Carts[0];
}
return $Cart;
}
/**
* @param CartItem[] $cartItems
*
* @return CartItem[]
*/
protected function mergeAllCartItems($cartItems = [])
{
/** @var CartItem[] $allCartItems */
$allCartItems = [];
foreach ($this->getCarts() as $Cart) {
$allCartItems = $this->mergeCartItems($Cart->getCartItems(), $allCartItems);
}
return $this->mergeCartItems($cartItems, $allCartItems);
}
/**
* @param $cartItems
* @param $allCartItems
*
* @return array
*/
protected function mergeCartItems($cartItems, $allCartItems)
{
foreach ($cartItems as $item) {
$itemExists = false;
foreach ($allCartItems as $itemInArray) {
// 同じ明細があればマージする
if ($this->cartItemComparator->compare($item, $itemInArray)) {
$itemInArray->setQuantity($itemInArray->getQuantity() + $item->getQuantity());
$itemExists = true;
break;
}
}
if (!$itemExists) {
$allCartItems[] = $item;
}
}
return $allCartItems;
}
protected function restoreCarts($cartItems)
{
foreach ($this->getCarts() as $Cart) {
foreach ($Cart->getCartItems() as $i) {
$this->entityManager->remove($i);
$this->entityManager->flush();
}
$this->entityManager->remove($Cart);
$this->entityManager->flush();
}
$this->carts = [];
/** @var Cart[] $Carts */
$Carts = [];
foreach ($cartItems as $item) {
$allocatedId = $this->cartItemAllocator->allocate($item);
$cartKey = $this->createCartKey($allocatedId, $this->getUser());
if (isset($Carts[$cartKey])) {
$Cart = $Carts[$cartKey];
$Cart->addCartItem($item);
$item->setCart($Cart);
} else {
/** @var Cart $Cart */
$Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
if ($Cart) {
foreach ($Cart->getCartItems() as $i) {
$this->entityManager->remove($i);
$this->entityManager->flush();
}
$this->entityManager->remove($Cart);
$this->entityManager->flush();
}
$Cart = new Cart();
$Cart->setCartKey($cartKey);
$Cart->addCartItem($item);
$item->setCart($Cart);
$Carts[$cartKey] = $Cart;
}
}
$this->carts = array_values($Carts);
}
/**
* カートに商品を追加します.
*
* @param $ProductClass ProductClass 商品規格
* @param $quantity int 数量
* @param $maker_id int メーカーID
*
* @return bool 商品を追加できた場合はtrue
*/
public function addProduct($ProductClass, $quantity = 1, $maker_id)
{
if (!$ProductClass instanceof ProductClass) {
$ProductClassId = $ProductClass;
$ProductClass = $this->entityManager
->getRepository(ProductClass::class)
->find($ProductClassId);
if (is_null($ProductClass)) {
return false;
}
}
$ClassCategory1 = $ProductClass->getClassCategory1();
if ($ClassCategory1 && !$ClassCategory1->isVisible()) {
return false;
}
$ClassCategory2 = $ProductClass->getClassCategory2();
if ($ClassCategory2 && !$ClassCategory2->isVisible()) {
return false;
}
$newItem = new CartItem();
$newItem->setQuantity($quantity);
$newItem->setMakerId($maker_id);
$newItem->setPrice($ProductClass->getPrice02IncTax());
$newItem->setProductClass($ProductClass);
$allCartItems = $this->mergeAllCartItems([$newItem]);
$this->restoreCarts($allCartItems);
return true;
}
public function removeProduct($ProductClass)
{
if (!$ProductClass instanceof ProductClass) {
$ProductClassId = $ProductClass;
$ProductClass = $this->entityManager
->getRepository(ProductClass::class)
->find($ProductClassId);
if (is_null($ProductClass)) {
return false;
}
}
$removeItem = new CartItem();
$removeItem->setPrice($ProductClass->getPrice02IncTax());
$removeItem->setProductClass($ProductClass);
$allCartItems = $this->mergeAllCartItems();
$foundIndex = -1;
foreach ($allCartItems as $index => $itemInCart) {
if ($this->cartItemComparator->compare($itemInCart, $removeItem)) {
$foundIndex = $index;
break;
}
}
array_splice($allCartItems, $foundIndex, 1);
$this->restoreCarts($allCartItems);
return true;
}
public function save()
{
$cartKeys = [];
foreach ($this->carts as $Cart) {
$Cart->setCustomer($this->getUser());
$this->entityManager->persist($Cart);
foreach ($Cart->getCartItems() as $item) {
$this->entityManager->persist($item);
}
$this->entityManager->flush();
$cartKeys[] = $Cart->getCartKey();
}
$this->session->set('cart_keys', $cartKeys);
return;
}
/**
* @param string $pre_order_id
*
* @return \Eccube\Service\CartService
*/
public function setPreOrderId($pre_order_id)
{
$this->getCart()->setPreOrderId($pre_order_id);
return $this;
}
/**
* @return string|null
*/
public function getPreOrderId()
{
$Cart = $this->getCart();
if (!empty($Cart)) {
return $Cart->getPreOrderId();
}
return null;
}
/**
* @return \Eccube\Service\CartService
*/
public function clear()
{
$Carts = $this->getCarts();
if (!empty($Carts)) {
$removed = $this->getCart();
if ($removed && UnitOfWork::STATE_MANAGED === $this->entityManager->getUnitOfWork()->getEntityState($removed)) {
$this->entityManager->remove($removed);
$this->entityManager->flush();
$cartKeys = [];
foreach ($Carts as $key => $Cart) {
// テーブルから削除されたカートを除外する
if ($Cart == $removed) {
unset($Carts[$key]);
}
$cartKeys[] = $Cart->getCartKey();
}
$this->session->set('cart_keys', $cartKeys);
// 注文完了のカートキーをセッションから削除する
$this->session->remove('cart_key');
$this->carts = $this->cartRepository->findBy(['cart_key' => $cartKeys], ['id' => 'ASC']);
}
}
return $this;
}
/**
* @param CartItemComparator $cartItemComparator
*/
public function setCartItemComparator($cartItemComparator)
{
$this->cartItemComparator = $cartItemComparator;
}
/**
* カートキーで指定したインデックスにあるカートを優先にする
*
* @param string $cartKey カートキー
*/
public function setPrimary($cartKey)
{
$Carts = $this->getCarts();
$primary = $Carts[0];
$index = 0;
foreach ($Carts as $key => $Cart) {
if ($Cart->getCartKey() === $cartKey) {
$index = $key;
$primary = $Carts[$index];
break;
}
}
$prev = $Carts[0];
array_splice($Carts, 0, 1, [$primary]);
array_splice($Carts, $index, 1, [$prev]);
$this->carts = $Carts;
$this->save();
}
protected function getUser()
{
if (null === $token = $this->tokenStorage->getToken()) {
return;
}
if (!is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return;
}
return $user;
}
/**
* @param string $allocatedId
*/
protected function createCartKey($allocatedId, Customer $Customer = null)
{
if ($Customer instanceof Customer) {
return $Customer->getId().'_'.$allocatedId;
}
if ($this->session->has('cart_key_prefix')) {
return $this->session->get('cart_key_prefix').'_'.$allocatedId;
}
do {
$random = StringUtil::random(32);
$cartKey = $random.'_'.$allocatedId;
$Cart = $this->cartRepository->findOneBy(['cart_key' => $cartKey]);
} while ($Cart);
$this->session->set('cart_key_prefix', $random);
return $cartKey;
}
}