<?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 Customize\Controller;
use Customize\Entity\Floor;
use Customize\Repository\BrandRepository;
use Customize\Repository\MediaCoverageRepository;
use Customize\Service\ArticleService;
use Eccube\Controller\ProductController as ParentController;
use Customize\Repository\ProductRepository;
use Customize\Repository\ProductRelatedRepository;
use Customize\Entity\ProductRelated;
use Customize\Service\FloorDistributionService;
use Eccube\Entity\BaseInfo;
use Eccube\Entity\Master\ProductStatus;
use Eccube\Entity\Product;
use Eccube\Event\EccubeEvents;
use Eccube\Event\EventArgs;
use Eccube\Form\Type\AddCartType;
use Eccube\Form\Type\Master\ProductListMaxType;
use Eccube\Form\Type\Master\ProductListOrderByType;
use Eccube\Form\Type\SearchProductType;
use Eccube\Repository\BaseInfoRepository;
use Customize\Repository\CustomerFavoriteProductRepository;
use Eccube\Repository\Master\ProductListMaxRepository;
use Eccube\Service\CartService;
use Customize\Service\ProductService;
use Eccube\Entity\Master\SaleType;
use Eccube\Entity\ProductClass;
use Eccube\Service\PurchaseFlow\PurchaseContext;
use Eccube\Service\PurchaseFlow\PurchaseFlow;
use Knp\Bundle\PaginatorBundle\Pagination\SlidingPagination;
use Knp\Component\Pager\PaginatorInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use GuzzleHttp\Client;
use Eccube\Repository\Master\SaleTypeRepository;
class ProductController extends ParentController
{
/**
* @var PurchaseFlow
*/
protected $purchaseFlow;
/**
* @var CustomerFavoriteProductRepository
*/
protected $customerFavoriteProductRepository;
/**
* @var CartService
*/
protected $cartService;
/**
* @var ProductService
*/
protected $productService;
/**
* @var ProductRepository
*/
protected $productRepository;
/**
* @var BaseInfo
*/
protected $BaseInfo;
/**
* @var AuthenticationUtils
*/
protected $helper;
/**
* @var ProductListMaxRepository
*/
protected $productListMaxRepository;
private $title = '';
private $articleService;
/** @var mediaCoverageRepository */
private $mediaCoverageRepository;
/** @var FloorDistributionService */
private $floorDistributionService;
/** @var BrandRepository */
private $brandRepository;
/** @var ProductRelatedRepository */
private $productRelatedRepository;
/** @var SaleTypeRepository */
private $saleTypeRepository;
/**
* ProductController constructor.
*
* @param PurchaseFlow $cartPurchaseFlow
* @param CustomerFavoriteProductRepository $customerFavoriteProductRepository
* @param CartService $cartService
* @param ProductService $productService
* @param ProductRepository $productRepository
* @param BaseInfoRepository $baseInfoRepository
* @param AuthenticationUtils $helper
* @param ProductListMaxRepository $productListMaxRepository
* @param ArticleService $articleService
* @param mediaCoverageRepository $mediaCoverageRepository
* @param FloorDistributionService $floorDistributionService
* @param BrandRepository $brandRepository
* @param ProductRelatedRepository $productRelatedRepository
* @param SaleTypeRepository $saleTypeRepository
*/
public function __construct(
PurchaseFlow $cartPurchaseFlow,
CustomerFavoriteProductRepository $customerFavoriteProductRepository,
CartService $cartService,
ProductService $productService,
ProductRepository $productRepository,
BaseInfoRepository $baseInfoRepository,
AuthenticationUtils $helper,
ProductListMaxRepository $productListMaxRepository,
ArticleService $articleService,
MediaCoverageRepository $mediaCoverageRepository,
FloorDistributionService $floorDistributionService,
BrandRepository $brandRepository,
ProductRelatedRepository $productRelatedRepository,
SaleTypeRepository $saleTypeRepository
) {
$this->purchaseFlow = $cartPurchaseFlow;
$this->customerFavoriteProductRepository = $customerFavoriteProductRepository;
$this->cartService = $cartService;
$this->productService = $productService;
$this->productRepository = $productRepository;
$this->BaseInfo = $baseInfoRepository->get();
$this->helper = $helper;
$this->productListMaxRepository = $productListMaxRepository;
$this->articleService = $articleService;
$this->mediaCoverageRepository = $mediaCoverageRepository;
$this->floorDistributionService = $floorDistributionService;
$this->brandRepository = $brandRepository;
$this->productRelatedRepository = $productRelatedRepository;
$this->saleTypeRepository = $saleTypeRepository;
}
/**
* 商品一覧画面.
*
* @Route("/products/list", name="product_list")
* @Route("/{sale_type}/products/list", name="valuet_product_list")
* @Route("/{sale_type}/{floor_key}/{brand_identifier}/products/list", name="brand_product_list")
* @Template("Product/list.twig")
*/
public function index(Request $request, PaginatorInterface $paginator, $sale_type = null, $floor_key = null, $brand_identifier = null)
{
$Brand = null;
if ($floor_key && $brand_identifier) {
$floor_name = FloorDistributionService::floors[$floor_key] ?? null;
if (!$floor_name) {
throw new NotFoundHttpException();
}
/** @var \Customize\Entity\Brand $Brand */
$Brand = $this->brandRepository->findOneBy(['identifier' => $brand_identifier]);
if (!$Brand) {
throw new NotFoundHttpException();
}
if ($Brand->getFloor()->getName() !== $floor_name) {
throw new NotFoundHttpException();
}
}
// Doctrine SQLFilter
if ($this->BaseInfo->isOptionNostockHidden()) {
$this->entityManager->getFilters()->enable('option_nostock_hidden');
}
// handleRequestは空のqueryの場合は無視するため
if ($request->getMethod() === 'GET') {
$request->query->set('pageno', $request->query->get('pageno', ''));
}
// searchForm
/* @var $builder \Symfony\Component\Form\FormBuilderInterface */
$builder = $this->formFactory->createNamedBuilder('', SearchProductType::class);
if ($request->getMethod() === 'GET') {
$builder->setMethod('GET');
}
$event = new EventArgs(
[
'builder' => $builder,
],
$request
);
$this->eventDispatcher->dispatch($event, EccubeEvents::FRONT_PRODUCT_INDEX_INITIALIZE);
/* @var $searchForm \Symfony\Component\Form\FormInterface */
$searchForm = $builder->getForm();
$searchForm->handleRequest($request);
// paginator
$searchData = $searchForm->getData();
if ($Brand) {
$searchData['brand_id'] = $Brand;
}
// 銘店の一品の商品を含むかの判定
$templatePath = $this->floorDistributionService->getFloorTemplateCodeFromPath();
switch ($templatePath) {
case Floor::NAME_SPECIAL:
$qb = $this->productRepository->getQueryBuilderBySearchData($searchData, $sale_type, ProductClass::SHOW_AT_SPECIAL_ONLY);
break;
case null: // 百貨店にいる場合
$qb = $this->productRepository->getQueryBuilderBySearchData($searchData, $sale_type, ProductClass::SHOW_AT_BOTH);
break;
default: // 銘店の一品以外の各フロアにいる場合
$qb = $this->productRepository->getQueryBuilderBySearchData($searchData, $sale_type, ProductClass::SHOW_AT_FLOOR_ONLY);
break;
}
$event = new EventArgs(
[
'searchData' => $searchData,
'qb' => $qb,
],
$request
);
$this->eventDispatcher->dispatch($event, EccubeEvents::FRONT_PRODUCT_INDEX_SEARCH);
$searchData = $event->getArgument('searchData');
$query = $qb->getQuery()
->useResultCache(true, $this->eccubeConfig['eccube_result_cache_lifetime_short']);
/** @var SlidingPagination $pagination */
$pagination = $paginator->paginate(
$query,
!empty($searchData['pageno']) ? $searchData['pageno'] : 1,
!empty($searchData['disp_number']) ? $searchData['disp_number']->getId() : $this->productListMaxRepository->findOneBy([],
['sort_no' => 'ASC'])->getId(),
array('wrap-queries' => true)
);
// 品切れの商品を後ろに並び替える
$this->productService->sortProductsByOutOfStock($pagination);
$ids = [];
foreach ($pagination as $Product) {
$ids[] = $Product->getId();
}
$ProductsAndClassCategories = $this->productRepository->findProductsWithSortedClassCategories($ids, 'p.id');
// addCart form
$forms = [];
$Toppings = [];
foreach ($pagination as $Product) {
/* @var $builder \Symfony\Component\Form\FormBuilderInterface */
$builder = $this->formFactory->createNamedBuilder(
'',
AddCartType::class,
null,
[
'product' => $ProductsAndClassCategories[$Product->getId()],
'allow_extra_fields' => true,
'sale_type' => $sale_type,
]
);
$addCartForm = $builder->getForm();
$forms[$Product->getId()] = $addCartForm->createView();
$ToppingGroup = $Product->getToppingGroup();
if ($ToppingGroup) {
$ToppingGroupToppings = $ToppingGroup->getToppingGroupToppings();
foreach ($ToppingGroupToppings as $ToppingGroupTopping) {
$Topping = $ToppingGroupTopping->getProduct();
// 在庫がない、公開ではでない
if (!$Topping->getStockFind() || !$Topping->isEnable()) {
continue;
}
$Toppings[$Product->getId()][] = $Topping;
}
}
}
// 表示件数
$builder = $this->formFactory->createNamedBuilder(
'disp_number',
ProductListMaxType::class,
null,
[
'required' => false,
'allow_extra_fields' => true,
]
);
if ($request->getMethod() === 'GET') {
$builder->setMethod('GET');
}
$event = new EventArgs(
[
'builder' => $builder,
],
$request
);
$this->eventDispatcher->dispatch($event, EccubeEvents::FRONT_PRODUCT_INDEX_DISP);
$dispNumberForm = $builder->getForm();
$dispNumberForm->handleRequest($request);
// ソート順
$builder = $this->formFactory->createNamedBuilder(
'orderby',
ProductListOrderByType::class,
null,
[
'required' => false,
'allow_extra_fields' => true,
]
);
if ($request->getMethod() === 'GET') {
$builder->setMethod('GET');
}
$event = new EventArgs(
[
'builder' => $builder,
],
$request
);
$this->eventDispatcher->dispatch($event, EccubeEvents::FRONT_PRODUCT_INDEX_ORDER);
$orderByForm = $builder->getForm();
$orderByForm->handleRequest($request);
$Category = $searchForm->get('category_id')->getData();
// カテゴリ人気ランキング
$CategoryRankedProducts = $this->getCategoryRankedProducts($Category);
//Yazawaのカテゴリ記事機能
$name = $this->floorDistributionService->getFloorTemplateCodeFromPath();
$Articles = $Category? $this->articleService->getConditionFilteredArticles($Category->getId(), $name): null;
$Article = $Category? $this->articleService->getCategoryTopArticle($Category->getId(), $name): null;
return [
'subtitle' => $this->getPageTitle($searchData),
'pagination' => $pagination,
'search_form' => $searchForm->createView(),
'disp_number_form' => $dispNumberForm->createView(),
'order_by_form' => $orderByForm->createView(),
'forms' => $forms,
'Category' => $Category,
'CategoryRankedProducts' => $CategoryRankedProducts,
'Articles' => $Articles,
'Article' => $Article,
'Toppings' => $Toppings,
'sale_type' => $sale_type,
'floor_key' => $floor_key,
'brand_identifier' => $brand_identifier,
'Brand' => $Brand,
];
}
/**
* 商品詳細画面.
*
* @Route("/products/detail/{id}", name="product_detail", methods={"GET"}, requirements={"id" = "\d+"})
* @Route("/{sale_type}/products/detail/{id}", name="valuet_product_detail")
* @Template("Product/detail.twig")
* @ParamConverter("Product", options={"repository_method" = "findWithSortedClassCategories"})
*
* @param Request $request
* @param Product $Product
*
* @return array
*/
public function detail(Request $request, Product $Product, $sale_type = null)
{
// 銘店の一品にリダイレクトするかの判定を実施
$templatePath = $this->floorDistributionService->getFloorTemplateCodeFromPath();
$ProductClass = $Product->getProductClasses()[0];
switch ($templatePath) {
case Floor::NAME_SPECIAL:
if ($ProductClass->getShowAtSpecial() != ProductClass::SHOW_AT_SPECIAL_ONLY) {
return $this->redirectTo($request, array_flip(FloorDistributionService::floors)[$Product->getShopName()] );
}
break;
case null: // 百貨店にいる場合
if ($ProductClass->getShowAtSpecial() == ProductClass::SHOW_AT_SPECIAL_ONLY) {
return $this->redirectTo($request, Floor::NAME_SPECIAL);
} else {
// デリテイクはリダイレクトしない
if (!in_array($sale_type, array("delivery","takeout"))) {
return $this->redirectTo($request, array_flip(FloorDistributionService::floors)[$Product->getShopName()] );
}
}
break;
default: // 銘店の一品以外の各フロアにいる場合
if ($ProductClass->getShowAtSpecial() == ProductClass::SHOW_AT_SPECIAL_ONLY) {
return $this->redirectTo($request, Floor::NAME_SPECIAL);
}
break;
}
if (!$this->checkVisibility($Product)) {
throw new NotFoundHttpException();
}
if (!empty($sale_type)) {
$this->checkProductSaleTypeAndPathCombination($Product, $sale_type);
}
// トッピング商品は表示不可
$SaleType = $Product->getProductClasses()[0]->getSaleType();
if ($SaleType->getId() === SaleType::TOPPING) {
throw new NotFoundHttpException();
}
$builder = $this->formFactory->createNamedBuilder(
'',
AddCartType::class,
null,
[
'product' => $Product,
'id_add_product_id' => false,
'sale_type' => $sale_type,
]
);
$Toppings = [];
// if ($sale_type !== 'onlineshop') {
$ToppingGroup = $Product->getToppingGroup();
if ($ToppingGroup) {
$ToppingGroupToppings = $ToppingGroup->getToppingGroupToppings();
foreach ($ToppingGroupToppings as $ToppingGroupTopping) {
$Topping = $ToppingGroupTopping->getProduct();
// saleTypeがToppingのみ追加
if ($Topping->getProductClasses()[0]->getSaleType()->getId() === SaleType::TOPPING) {
// 在庫がない、公開ではでない
if (!$Topping->getStockFind() || !$Topping->isEnable()) {
continue;
}
$Toppings[$Product->getId()][] = $Topping;
}
}
}
// }
$event = new EventArgs(
[
'builder' => $builder,
'Product' => $Product,
],
$request
);
$this->eventDispatcher->dispatch($event, EccubeEvents::FRONT_PRODUCT_DETAIL_INITIALIZE);
$is_favorite = false;
if ($this->isGranted('ROLE_USER')) {
$Customer = $this->getUser();
$is_favorite = $this->customerFavoriteProductRepository->isFavorite($Customer, $Product);
}
// カテゴリナビ内に色をつけるためカテゴリの配列を作成
$ProductCategories = $this->productService->productCategories($Product);
$Brand = $Product->getBrand();
// 関連するメディア掲載履歴を取得
$MediaCoverages = $this->mediaCoverageRepository->getByProductId($Product);
$ProductCategoriesArray = $this->productRepository->getProductCategoriesArray($Product);
return [
'title' => $this->title,
'subtitle' => $Product->getName(),
'form' => $builder->getForm()->createView(),
'Product' => $Product,
'ProductCategories' => $ProductCategories,
'is_favorite' => $is_favorite,
'Toppings' => $Toppings,
'Brand' => $Brand,
'sale_type' => $sale_type,
'MediaCoverages' => $MediaCoverages,
'ProductCategoriesArray' => $ProductCategoriesArray,
];
}
private function redirectTo(Request $request, string $floorKey) {
$params = $request->attributes->get('_route_params');
$params = array_merge($params, $request->query->all());
unset($params['sale_type']);
$url = $this->generateUrl('product_detail', $params);
return $this->redirect('/' . $floorKey . $url, 301);
}
/**
* お気に入り追加.
*
* @Route("/products/add_favorite/{id}", name="product_add_favorite", requirements={"id" = "\d+"})
*/
public function addFavorite(Request $request, Product $Product)
{
$this->checkVisibility($Product);
$event = new EventArgs(
[
'Product' => $Product,
],
$request
);
$this->eventDispatcher->dispatch($event, EccubeEvents::FRONT_PRODUCT_FAVORITE_ADD_INITIALIZE);
$sale_type = $request->get('sale_type');
if ($this->isGranted('ROLE_USER')) {
$Customer = $this->getUser();
$saleTypes = [
'delivery' => SaleType::DELIVERY,
'takeout' => SaleType::TAKEOUT,
];
$SaleType = null;
if (array_key_exists($sale_type, $saleTypes)) {
$SaleType = $this->saleTypeRepository->find($saleTypes[$sale_type]);
} else {
foreach ($Product->getProductClasses() as $ProductClass) {
if ($ProductClass->getSaleType()) {
$SaleType = $ProductClass->getSaleType();
break;
}
}
}
$this->customerFavoriteProductRepository->addFavoriteWithSaleType($Customer, $Product, $SaleType);
$this->session->getFlashBag()->set('product_detail.just_added_favorite', $Product->getId());
$event = new EventArgs(
[
'Product' => $Product,
],
$request
);
$this->eventDispatcher->dispatch($event, EccubeEvents::FRONT_PRODUCT_FAVORITE_ADD_COMPLETE);
return $this->redirectToRoute('valuet_product_detail', ['id' => $Product->getId(), 'sale_type' => $sale_type]);
} else {
// 非会員の場合、ログイン画面を表示
// ログイン後の画面遷移先を設定
$this->setLoginTargetPath($this->generateUrl('product_add_favorite', ['id' => $Product->getId()], UrlGeneratorInterface::ABSOLUTE_URL));
$this->session->getFlashBag()->set('eccube.add.favorite', true);
$event = new EventArgs(
[
'Product' => $Product,
],
$request
);
$this->eventDispatcher->dispatch($event, EccubeEvents::FRONT_PRODUCT_FAVORITE_ADD_COMPLETE);
return $this->redirectToRoute('mypage_login');
}
}
/**
* sale_typeと商品のもつ販売種別が正しいかどうかをチェック
*
* @param Product $Product
* @param string|null $sale_type delivery / takeout / onlineshop
* @return void
*/
private function checkProductSaleTypeAndPathCombination(Product $Product, $sale_type = null)
{
/** @var ProductClass $ProductClass */
foreach ($Product->getProductClasses() as $ProductClass) {
$SaleType = $ProductClass->getSaleType();
switch ($sale_type) {
case 'onlineshop':
if (in_array($SaleType->getId(), [SaleType::DELIVERY, SaleType::TAKEOUT, SaleType::DELIVERY_TAKEOUT, SaleType::TOPPING])) {
// sale_typeがonlineshopで商品がデリバリー、テイクアウト、デリバリー / テイクアウト、トッピングであればException
throw new NotFoundHttpException();
}
break;
case 'delivery':
if (!in_array($SaleType->getId(), [SaleType::DELIVERY, SaleType::DELIVERY_TAKEOUT])) {
// sale_typeがdeliveryで商品がデリバリー、デリバリー / テイクアウトでなければException
throw new NotFoundHttpException();
}
break;
case 'takeout':
if (!in_array($SaleType->getId(), [SaleType::TAKEOUT, SaleType::DELIVERY_TAKEOUT])) {
// sale_typeがtakeoutで商品がテイクアウト、デリバリー / テイクアウトでなければException
throw new NotFoundHttpException();
}
break;
}
}
}
/**
* 百貨店用商品検索画面.
*
* @Route("/search", name="product_search")
* @Template("Product/search.twig")
*/
public function search(Request $request, PaginatorInterface $paginator)
{
// Doctrine SQLFilter
if ($this->BaseInfo->isOptionNostockHidden()) {
$this->entityManager->getFilters()->enable('option_nostock_hidden');
}
// searchForm
/* @var $builder \Symfony\Component\Form\FormBuilderInterface */
$builder = $this->formFactory->createNamedBuilder('', SearchProductType::class);
if ($request->getMethod() === 'GET') {
$builder->setMethod('GET');
}
/* @var $searchForm \Symfony\Component\Form\FormInterface */
$searchForm = $builder->getForm();
$searchForm->handleRequest($request);
// paginator
$searchData = $searchForm->getData();
$onlineShopPagination = $this->searchProductsBySaleType($paginator, $searchData, 'onlineshop');
$deliveryPagination = $this->searchProductsBySaleType($paginator, $searchData, 'delivery');
$takeoutPagination = $this->searchProductsBySaleType($paginator, $searchData, 'takeout');
// 画面上では参照しないが、Productに紐づくモデルのロードを行うために呼ぶ
// 他にやり方ありそうだけど思いつかないので暫定
list($onlineShopForms, $onlineShopToppings) = $this->getAddCartFormsAndToppings($onlineShopPagination, 'onlineshop');
list($deliveryForms, $deliveryToppings) = $this->getAddCartFormsAndToppings($deliveryPagination, 'delivery');
list($takeoutForms, $takeoutToppings) = $this->getAddCartFormsAndToppings($takeoutPagination, 'takeout');
return [
'subtitle' => $this->getPageTitle($searchData),
'onlineShopPagination' => $onlineShopPagination,
'deliveryPagination' => $deliveryPagination,
'deliveryForms' => $deliveryForms,
'deliveryToppings' => $deliveryToppings,
'takeoutPagination' => $takeoutPagination,
'takeoutForms' => $takeoutForms,
'takeoutToppings' => $takeoutToppings,
'search_form' => $searchForm->createView(),
];
}
public function searchProductsBySaleType($paginator, $searchData, $sale_type)
{
$qb = $this->productRepository->getQueryBuilderBySearchData($searchData, $sale_type, ProductClass::SHOW_AT_BOTH);
$query = $qb->getQuery()
->useResultCache(true, $this->eccubeConfig['eccube_result_cache_lifetime_short']);
/** @var SlidingPagination $pagination */
$pagination = $paginator->paginate(
$query,
1, // 1ページ目固定
15, // 15件固定で取る
array('wrap-queries' => true)
);
// 品切れの商品を後ろに並び替える
$this->productService->sortProductsByOutOfStock($pagination);
return $pagination;
}
public function getAddCartFormsAndToppings($pagination, $sale_type)
{
$ids = [];
foreach ($pagination as $Product) {
$ids[] = $Product->getId();
}
$ProductsAndClassCategories = $this->productRepository->findProductsWithSortedClassCategories($ids, 'p.id');
// addCart form
$forms = [];
$Toppings = [];
foreach ($pagination as $Product) {
/* @var $builder \Symfony\Component\Form\FormBuilderInterface */
$builder = $this->formFactory->createNamedBuilder(
'',
AddCartType::class,
null,
[
'product' => $ProductsAndClassCategories[$Product->getId()],
'allow_extra_fields' => true,
'sale_type' => $sale_type,
]
);
$addCartForm = $builder->getForm();
$forms[$Product->getId()] = $addCartForm->createView();
$ToppingGroup = $Product->getToppingGroup();
if ($ToppingGroup) {
$ToppingGroupToppings = $ToppingGroup->getToppingGroupToppings();
foreach ($ToppingGroupToppings as $ToppingGroupTopping) {
$Topping = $ToppingGroupTopping->getProduct();
// 在庫がない、公開ではでない
if (!$Topping->getStockFind() || !$Topping->isEnable()) {
continue;
}
$Toppings[$Product->getId()][] = $Topping;
}
}
}
return [$forms, $Toppings];
}
public function getCategoryRankedProducts($Category)
{
if (empty($Category)) {
return;
}
$rankingProductIds = [
$Category['category_ranking_first'],
$Category['category_ranking_second'],
$Category['category_ranking_third'],
$Category['category_ranking_fourth']
];
$CategoryRankingProducts = [];
foreach ($rankingProductIds as $rankingProductId) {
if (!empty($rankingProductId) && $this->productRepository->find($rankingProductId)) {
$CategoryRankingProducts[] = $this->productRepository->findWithSortedClassCategories($rankingProductId, 'p.id');
}
}
return $CategoryRankingProducts;
}
}