<?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 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 数量 
     * 
     * @return bool 商品を追加できた場合はtrue 
     */ 
    public function addProduct($ProductClass, $quantity = 1) 
    { 
        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->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; 
    } 
}