<?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\School;
use Customize\Form\Type\MatomeCartType;
use Eccube\Controller\AbstractController;
use Eccube\Entity\BaseInfo;
use Eccube\Entity\CartItem;
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 Eccube\Repository\CustomerFavoriteProductRepository;
use Eccube\Repository\Master\ProductListMaxRepository;
use Eccube\Repository\OrderRepository;
use Customize\Repository\ProductRepository;
use Customize\Repository\SetProductRepository;
use Customize\Repository\RecommendXRepository;
use Customize\Repository\RecommendYRepository;
use Customize\Repository\RecommendSizeRepository;
use Customize\Repository\ProductSchoolRepository;
use Customize\Repository\SchoolRepository;
use Eccube\Repository\ProductClassRepository;
use Customize\Service\CartService;
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 Customize\Repository\BrotherRepository;
use Customize\Service\UserService;
class ProductController extends AbstractController
{
/**
* @var PurchaseFlow
*/
protected $purchaseFlow;
/**
* @var CustomerFavoriteProductRepository
*/
protected $customerFavoriteProductRepository;
/**
* @var CartService
*/
protected $cartService;
/**
* @var ProductRepository
*/
protected $productRepository;
/**
* @var SetProductRepository
*/
protected $setProductRepository;
/**
* @var BaseInfo
*/
protected $BaseInfo;
/**
* @var AuthenticationUtils
*/
protected $helper;
/**
* @var ProductListMaxRepository
*/
protected $productListMaxRepository;
private $title = '';
/**
* @var RecommendXRepository
*/
protected $recommendXRepository;
/**
* @var RecommendYRepository
*/
protected $recommendYRepository;
/**
* @var RecommendSizeRepository
*/
protected $recommendSizeRepository;
/**
* @var ProductSchoolRepository
*/
protected $productSchoolRepository;
/**
* @var SchoolRepository
*/
protected $schoolRepository;
/**
* @var ProductClassRepository
*/
protected $productClassRepository;
/**
* @var BrotherRepository
*/
protected $brotherRepository;
/**
* @var UserService
*/
protected $userService;
/**
* @var OrderRepository
*/
protected $orderRepository;
/**
* ProductController constructor.
*
* @param PurchaseFlow $cartPurchaseFlow
* @param CustomerFavoriteProductRepository $customerFavoriteProductRepository
* @param CartService $cartService
* @param ProductRepository $productRepository
* @param SetProductRepository $setProductRepository
* @param BaseInfoRepository $baseInfoRepository
* @param AuthenticationUtils $helper
* @param ProductListMaxRepository $productListMaxRepository
* @param RecommendXRepository $recommendXRepository
* @param RecommendYRepository $recommendYRepository
* @param RecommendSizeRepository $recommendSizeRepository
* @param ProductSchoolRepository $productSchoolRepository
* @param SchoolRepository $schoolRepository
* @param ProductClassRepository $productClassRepository
* @param UserService $userService
*/
public function __construct(
PurchaseFlow $cartPurchaseFlow,
CustomerFavoriteProductRepository $customerFavoriteProductRepository,
CartService $cartService,
ProductRepository $productRepository,
SetProductRepository $setProductRepository,
BaseInfoRepository $baseInfoRepository,
AuthenticationUtils $helper,
ProductListMaxRepository $productListMaxRepository,
RecommendXRepository $recommendXRepository,
RecommendYRepository $recommendYRepository,
RecommendSizeRepository $recommendSizeRepository,
ProductSchoolRepository $productSchoolRepository,
SchoolRepository $schoolRepository,
ProductClassRepository $productClassRepository,
BrotherRepository $brotherRepository,
UserService $userService,
OrderRepository $orderRepository
) {
$this->purchaseFlow = $cartPurchaseFlow;
$this->customerFavoriteProductRepository = $customerFavoriteProductRepository;
$this->cartService = $cartService;
$this->productRepository = $productRepository;
$this->setProductRepository = $setProductRepository;
$this->BaseInfo = $baseInfoRepository->get();
$this->helper = $helper;
$this->productListMaxRepository = $productListMaxRepository;
$this->recommendXRepository = $recommendXRepository;
$this->recommendYRepository = $recommendYRepository;
$this->recommendSizeRepository = $recommendSizeRepository;
$this->productSchoolRepository = $productSchoolRepository;
$this->schoolRepository = $schoolRepository;
$this->productClassRepository = $productClassRepository;
$this->brotherRepository = $brotherRepository;
$this->userService = $userService;
$this->orderRepository = $orderRepository;
}
/**
* 商品一覧画面.
*
* @Route("/products/list", name="product_list", methods={"GET", "POST"})
* @Template("Product/list.twig")
*/
public function index(Request $request, PaginatorInterface $paginator)
{
// 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();
$Customer = $this->userService->getCurrentUser();
$brother_id = $request->get('brother_id', '');
if (!empty($brother_id)) {
$Brother = $this->brotherRepository->find($brother_id);
if ($Brother) {
$Customer = $Brother->getCustomer();
}
}
// default sex filtering: only apply when customer's sex id is 1 or 2
$School = $Customer->getSchool();
// 学校がNULLの場合はログイン画面に遷移
if ($School === null) {
$this->addWarning('学校情報が設定されていません。ログインしてください。');
return $this->redirectToRoute('mypage_login');
}
$temporaryOrder = false;
if (is_object($Customer) && !$this->session->get('update_temporary_order_id')) {
if ($School) {
$this->session->set('storeMeasuringFlag', $School->getStoreMeasuringFlag());
$this->session->set('schoolMeasuringFlag', $School->getSchoolMeasuringFlag());
$this->session->set('onlineFlag', $School->getOnlineFlag());
if ($School->getOnlineFlag() == 0) {
$temporaryOrder = true;
} else {
$temporaryOrder = false;
}
}
}
$searchData['school_id'] = $request->get('s');
// 絞り込み条件のパラメータを取得
$genderType = $request->get('gender_type');
$commodityType = $request->get('commodity_type');
$setCommodityType = $request->get('set_commodity_type');
$setGenderType = $request->get('set_gender_type');
// すべての絞り込み条件が未選択かどうかをチェック
$noFilterSelected = empty($genderType) && empty($commodityType) &&
empty($setCommodityType) && empty($setGenderType);
// デフォルト性別絞り込みの適用(すべての絞り込み条件が未選択の場合のみ)
if ($noFilterSelected && $School && $School->getDefaultSexProductsFilteringFlag() == 1 && ($sexObj = $Customer->getSex())) {
$sexId = $sexObj->getId();
// 性別が男子(1)または女子(2)の場合のみ適用(なし(0)と共通(3)は無視)
if ($sexId === 1 || $sexId === 2) {
// デフォルト値を設定(単品・セットの男女別と入学時の必要品すべて)
$genderType = [$sexId];
$setGenderType = [$sexId];
$commodityType = [$sexId];
$setCommodityType = [$sexId];
}
}
// gender_type の処理(単一値または配列)
if (is_array($genderType)) {
$searchData['gender_type'] = $genderType;
} elseif ($genderType) {
$searchData['gender_type'] = [$genderType];
}
// commodity_type の処理(単一値または配列)
if (is_array($commodityType)) {
$searchData['commodity_type'] = $commodityType;
} elseif ($commodityType) {
$searchData['commodity_type'] = [$commodityType];
}
// セット商品用の commodity_type の処理
if (is_array($setCommodityType)) {
$searchData['set_commodity_type'] = $setCommodityType;
} elseif ($setCommodityType) {
$searchData['set_commodity_type'] = [$setCommodityType];
}
// セット商品用の gender_type の処理
if (is_array($setGenderType)) {
$searchData['set_gender_type'] = $setGenderType;
} elseif ($setGenderType) {
$searchData['set_gender_type'] = [$setGenderType];
}
$qb = $this->productRepository->getQueryBuilderBySearchData($searchData);
$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 */
if ($School && $School->getItemVisibleType() == School::ITEM_VISIBLE_TYPE_MATOME) {
$limit = 1000000;
} else {
$limit = !empty($searchData['disp_number']) ? $searchData['disp_number']->getId() : $this->productListMaxRepository->findOneBy([], ['sort_no' => 'ASC'])->getId();
}
$pagination = $paginator->paginate(
$query,
!empty($searchData['pageno']) ? $searchData['pageno'] : 1,
$limit,
array('wrap-queries' => true)
);
// 絞り込み条件なしの状態でセット商品が存在するかチェック
$hasSetProductsTotal = false;
$qbTotal = $this->productRepository->getQueryBuilderBySearchData(['school_id' => $searchData['school_id']]);
$qbTotal->select('COUNT(DISTINCT p.id)')
->andWhere("p.product_type = 'set'")
->setMaxResults(1);
$setProductCountTotal = $qbTotal->getQuery()->getSingleScalarResult();
if ($setProductCountTotal > 0) {
$hasSetProductsTotal = true;
}
// 検索結果全体でセット商品が存在するかチェック(絞り込み後)
$hasSetProducts = false;
$qbCheck = clone $qb;
$qbCheck->select('COUNT(DISTINCT p.id)')
->andWhere("p.product_type = 'set'")
->setMaxResults(1);
$setProductCount = $qbCheck->getQuery()->getSingleScalarResult();
if ($setProductCount > 0) {
$hasSetProducts = true;
}
$ids = [];
foreach ($pagination as $Product) {
$ids[] = $Product->getId();
}
$ProductsAndClassCategories = $this->productRepository->findProductsWithSortedClassCategories($ids, 'p.id');
// addCart form
$forms = [];
$ProductInSets = [];
foreach ($pagination as $index => $Product) {
/* @var $builder \Symfony\Component\Form\FormBuilderInterface */
$builder = $this->formFactory->createNamedBuilder(
'',
AddCartType::class,
null,
[
'product' => $ProductsAndClassCategories[$Product->getId()],
'allow_extra_fields' => true,
]
);
$addCartForm = $builder->getForm();
$forms[$Product->getId()] = $addCartForm->createView();
$product_ids[$index] = $Product->getId();
if ($Product->getProductType() == 'set') {
$ProductInSets[$Product->getId()] = [];
$setProduct = $Product->getSetProduct();
if ($setProduct) {
$pis_all = $setProduct->getSetProductProduct();
foreach ($pis_all as $pis) {
if (!in_array($pis->getProduct(), $ProductInSets[$Product->getId()])) {
array_push($ProductInSets[$Product->getId()], $pis->getProduct());
}
}
}
}
}
// 表示件数
$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();
//まとめて購入処理 >>>
$builder = $this->formFactory->createNamedBuilder(
'',
MatomeCartType::class,
null,
[
'products' => $pagination,
'ProductInSets' => $ProductInSets,
'allow_extra_fields' => true,
]
);
/* @var $searchForm \Symfony\Component\Form\FormInterface */
$matomeForm = $builder->getForm();
$matomeForm->handleRequest($request);
$back = $request->get('back', false);
$refresh = $this->session->get('refresh', true);
if ($back && $request->getMethod() == "GET" && !$refresh) {
$old_matomete_items = $this->session->get('matomeItems');
foreach ($old_matomete_items as $product_class_id => $omi) {
$old_product_class = $this->productClassRepository->find($product_class_id);
if (isset($omi['set_product_class']['class_list'])) {
// 兄弟情報を復元(IDからBrotherエンティティを取得)
$brother = null;
if (isset($omi['brother']) && $omi['brother']) {
$brother = $this->brotherRepository->find($omi['brother']);
}
$this->cartService->addProduct($old_product_class, -abs($omi['quantity']), $omi['set_product_class']['class_list'], null, $brother);
}
}
$Carts = $this->cartService->getCarts();
foreach ($Carts as $Cart) {
$result = $this->purchaseFlow->validate($Cart, new PurchaseContext($Cart, $this->userService->getCurrentUser()));
// 復旧不可のエラーが発生した場合は追加した明細を削除.
if ($result->hasError()) {
foreach ($old_matomete_items as $product_class_id => $omi) {
if (!empty($omi['quantity'])) {
$this->cartService->removeProduct($omi['product_class_id']);
}
}
foreach ($result->getErrors() as $error) {
$errorMessages[] = $error->getMessage();
}
}
foreach ($result->getWarning() as $warning) {
$errorMessages[] = $warning->getMessage();
}
}
$this->cartService->save();
$refresh = $this->session->set('refresh', true);
}
if ($matomeForm->isSubmitted() && $matomeForm->isValid()) {
$data = $matomeForm->getData();
// デバッグ: データ構造を確認
log_info('MatomeForm データ構造', [
'data_keys' => array_keys($data),
'first_item_type' => count($data) > 0 ? gettype(reset($data)) : 'empty',
]);
if (count($data) > 0) {
$firstItem = reset($data);
if (is_array($firstItem) && count($firstItem) > 0) {
$firstCartItem = reset($firstItem);
log_info('First CartItem', [
'is_object' => is_object($firstCartItem),
'type' => gettype($firstCartItem),
'class' => is_object($firstCartItem) ? get_class($firstCartItem) : 'not_object',
'has_brother' => is_object($firstCartItem) && method_exists($firstCartItem, 'getBrother'),
'brother_value' => is_object($firstCartItem) && method_exists($firstCartItem, 'getBrother')
? ($firstCartItem->getBrother() ? get_class($firstCartItem->getBrother()) : 'null')
: 'not_accessible',
]);
}
}
$isValid = false;
foreach ($data as $item) {
foreach ($item as $cart_item) {
// CartItemオブジェクトの場合
if (is_object($cart_item) && method_exists($cart_item, 'getQuantity')) {
if (!empty($cart_item->getQuantity())) {
$isValid = true;
}
} elseif (!empty($cart_item['quantity'])) {
$isValid = true;
}
}
}
if ($isValid) {
$mData = [];
//Go Shopping
foreach ($data as $itemKey => $item) {
foreach ($item as $cart_index => $cart_item) {
$quantity = is_object($cart_item) && method_exists($cart_item, 'getQuantity')
? $cart_item->getQuantity()
: ($cart_item['quantity'] ?? 0);
if (!empty($quantity)) {
$cartProduct = $request->get($itemKey)[$cart_index];
$setProductClass = [];
foreach ($cartProduct as $attrKey => $attr) {
if (!empty($attr['ProductClass'])) {
$setProductClass[] = $attr['ProductClass'];
}
}
// 兄弟情報を取得(CartItemオブジェクトから)
$brother = null;
if (is_object($cart_item) && method_exists($cart_item, 'getBrother')) {
$brother = $cart_item->getBrother();
} elseif (isset($cart_item['brother'])) {
$brother = $cart_item['brother'];
}
// ProductClassIDを取得
$productClassId = is_object($cart_item) && method_exists($cart_item, 'getProductClass')
? ($cart_item->getProductClass() ? $cart_item->getProductClass()->getId() : null)
: ($cart_item['product_class_id'] ?? null);
// 備考を取得
$remarks = is_object($cart_item) && method_exists($cart_item, 'getRemarks')
? $cart_item->getRemarks()
: ($cart_item['remarks'] ?? '');
$this->cartService->addProduct($productClassId, $quantity, $setProductClass, $remarks, $brother);
$mItem = [];
$mItem['quantity'] = $quantity;
$mItem['set_product_class'] = [];
foreach ($setProductClass as $spc) {
$spc_class = $this->productClassRepository->find($spc);
$productInSet = $spc_class->getProduct();
if ($spc_class && $productInSet) {
$spc_class_category1 = $spc_class->getClassCategory1();
$class_cat_tmp = [];
if ($spc_class_category1) {
$class_cat_tmp['class_category1_id'] = $spc_class_category1->getId();
}
$spc_class_category2 = $spc_class->getClassCategory2();
if ($spc_class_category2) {
$class_cat_tmp['class_category2_id'] = $spc_class_category2->getId();
}
$mItem['set_product_class'][$productInSet->getId()] = $class_cat_tmp;
$class_cat_tmp['product_class_id'] = $spc;
$mItem['set_product_class'][$productInSet->getId()] = $class_cat_tmp;
}
}
$mItem['set_product_class']['class_list'] = $setProductClass;
$mItem['remarks'] = $remarks;
// 兄弟情報を保存(Brotherオブジェクトの場合はIDを保存)
if ($brother) {
$mItem['brother'] = is_object($brother) ? $brother->getId() : $brother;
} else {
$mItem['brother'] = null;
}
$productClass = $this->productClassRepository->find($productClassId);
if ($productClass) {
$product_class_category1 = $productClass->getClassCategory1();
if ($product_class_category1) {
$mItem['class_category1_id'] = $product_class_category1->getId();
}
$product_class_category2 = $productClass->getClassCategory2();
if ($product_class_category2) {
$mItem['class_category2_id'] = $product_class_category2->getId();
}
}
$mData[$productClassId] = $mItem;
}
}
}
// 明細の正規化
$Carts = $this->cartService->getCarts();
foreach ($Carts as $Cart) {
$result = $this->purchaseFlow->validate($Cart, new PurchaseContext($Cart, $this->userService->getCurrentUser()));
// 復旧不可のエラーが発生した場合は追加した明細を削除.
if ($result->hasError()) {
foreach ($data as $item) {
foreach ($item as $cart_item) {
$productClassId = is_object($cart_item) && method_exists($cart_item, 'getProductClass')
? ($cart_item->getProductClass() ? $cart_item->getProductClass()->getId() : null)
: ($cart_item['product_class_id'] ?? null);
$quantity = is_object($cart_item) && method_exists($cart_item, 'getQuantity')
? $cart_item->getQuantity()
: ($cart_item['quantity'] ?? 0);
if (!empty($quantity) && $productClassId) {
$this->cartService->removeProduct($productClassId);
}
}
}
foreach ($result->getErrors() as $error) {
$errorMessages[] = $error->getMessage();
}
}
foreach ($result->getWarning() as $warning) {
$errorMessages[] = $warning->getMessage();
}
}
$this->cartService->save();
if (empty($errorMessages)) {
if ($temporaryOrder) {
$this->session->set('matomeItems', $mData);
$this->session->set('refresh', false);
return $this->redirectToRoute('shopping_temporary');
} else
return $this->redirectToRoute('shopping');
}
}
}
//<<<
return [
'matomeForm' => $matomeForm->createView(), //まとめ購入用
'ProductInSets' => $ProductInSets, //まとめ購入用
'subtitle' => $this->getPageTitle($searchData),
'pagination' => $pagination,
'search_form' => $searchForm->createView(),
'disp_number_form' => $dispNumberForm->createView(),
'order_by_form' => $orderByForm->createView(),
'forms' => $forms,
'Category' => $Category,
'School' => $School,
'product_type' => isset($searchData['product_type']) ? $searchData['product_type'] : null,
'matomeItems' => $this->session->get('matomeItems'),
'back' => $back,
'FilterSchool' => $request->get('s') ? $this->schoolRepository->findActiveSchool($request->get('s')) : null,
'hasSetProducts' => $hasSetProducts,
'hasSetProductsTotal' => $hasSetProductsTotal,
'searchData' => $searchData
];
}
/**
* 商品詳細画面.
*
* @Route("/products/detail/{id}", name="product_detail", methods={"GET"}, requirements={"id" = "\d+"})
* @Template("Product/detail.twig")
*
* @param Request $request
* @param Product $Product
*
* @return array
*/
public function detail(Request $request, $id)
{
$Product = $this->productRepository->findWithSortedClassCategories($id);
if (!$this->checkVisibility($Product)) {
throw new NotFoundHttpException();
}
$builder = $this->formFactory->createNamedBuilder(
'',
AddCartType::class,
null,
[
'product' => $Product,
'id_add_product_id' => false,
]
);
$event = new EventArgs(
[
'builder' => $builder,
'Product' => $Product,
],
$request
);
$this->eventDispatcher->dispatch($event, EccubeEvents::FRONT_PRODUCT_DETAIL_INITIALIZE);
$Customer = $this->userService->getCurrentUser();
$School = $Customer->getSchool();
// 学校がNULLの場合はログイン画面に遷移
if ($School === null) {
$this->addWarning('学校情報が設定されていません。ログインしてください。');
return $this->redirectToRoute('mypage_login');
}
$is_favorite = false;
if ($this->isGranted('ROLE_USER')) {
$is_favorite = $this->customerFavoriteProductRepository->isFavorite($Customer, $Product);
}
$Place = [
1 => '肩幅',
2 => '袖丈',
3 => '裄丈',
4 => '総丈',
5 => '首回り',
11 => 'バスト',
12 => 'ウェスト',
13 => 'ヒップ',
21 => '股下',
22 => 'スカート丈',
31 => '身長',
32 => '体重',
];
$RecommendSize = [];
$xValue = 0;
$yValue = 0;
$Recommend = $Product->getRecommend();
if (!$Recommend) {
//学校ID->オススメサイズを取得する
$Recommend = $this->productSchoolRepository->getRecommend($Product);
}
if ($Recommend) {
$Recommend->getRecommendXplace();
$xKey = 'getDr' . sprintf('%02d', $Recommend->getRecommendXplace());
$yKey = 'getDr' . sprintf('%02d', $Recommend->getRecommendYplace());
$Customer = $this->userService->getCurrentUser();
$xValue = 0;
$yValue = 0;
if (method_exists($Customer, $xKey)) $xValue = $Customer->$xKey();
if (method_exists($Customer, $yKey)) $yValue = $Customer->$yKey();
$RecommendX = $this->recommendXRepository->searchOne($Recommend, $xValue);
$RecommendY = $this->recommendYRepository->searchOne($Recommend, $yValue);
if ($RecommendX && $RecommendY)
$RecommendSize = $this->recommendSizeRepository->searchOne($Recommend, $RecommendX[0], $RecommendY[0]);
}
$ProductInSet = [];
if ($Product->isSetProduct()) {
$pis_all = $this->setProductRepository->findOneBy(['set_product_id' => $Product->getSetProductId()])->getSetProductProduct();
foreach ($pis_all as $pis) {
array_push($ProductInSet, $this->productRepository->findWithSortedClassCategories($pis->getProduct()->getId()));
}
}
// 販売期間チェック
$salesPeriodStatus = $this->checkSalesPeriod($Product);
return [
'title' => $this->title,
'subtitle' => $Product->getName(),
'form' => $builder->getForm()->createView(),
'Product' => $Product,
'is_favorite' => $is_favorite,
'RecommendSize' => $RecommendSize,
'Place' => $Place,
'x' => $xValue,
'y' => $yValue,
'ProductInSet' => $ProductInSet,
'SetProduct' => $Product->isSetProduct() ? $this->setProductRepository->findOneBy(['set_product_id' => $Product->getSetProductId()]) : null,
'sales_period_status' => $salesPeriodStatus
];
}
/**
* お気に入り追加.
*
* @Route("/products/add_favorite/{id}", name="product_add_favorite", requirements={"id" = "\d+"}, methods={"GET", "POST"})
*/
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);
if ($this->isGranted('ROLE_USER')) {
$Customer = $this->userService->getCurrentUser();
$this->customerFavoriteProductRepository->addFavorite($Customer, $Product);
$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('product_detail', ['id' => $Product->getId()]);
} 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');
}
}
/**
* カートに追加.
*
* @Route("/products/add_cart/{id}", name="product_add_cart", methods={"POST"}, requirements={"id" = "\d+"})
*/
public function addCart(Request $request, Product $Product)
{
// エラーメッセージの配列
$errorMessages = [];
if (!$this->checkVisibility($Product)) {
throw new NotFoundHttpException();
}
$builder = $this->formFactory->createNamedBuilder(
'',
AddCartType::class,
null,
[
'product' => $Product,
'id_add_product_id' => false,
]
);
$event = new EventArgs(
[
'builder' => $builder,
'Product' => $Product,
],
$request
);
$this->eventDispatcher->dispatch($event, EccubeEvents::FRONT_PRODUCT_CART_ADD_INITIALIZE);
// 販売期間チェック
$salesPeriodStatus = $this->checkSalesPeriod($Product);
if ($salesPeriodStatus['is_sales_period_enabled'] && !$salesPeriodStatus['is_within_period']) {
if ($request->isXmlHttpRequest()) {
return $this->json(['done' => false, 'messages' => [$salesPeriodStatus['message']]]);
} else {
$this->addRequestError($salesPeriodStatus['message']);
return $this->redirectToRoute('product_detail', ['id' => $Product->getId()]);
}
}
/* @var $form \Symfony\Component\Form\FormInterface */
$form = $builder->getForm();
$form->handleRequest($request);
// Brother情報を取得
$brother = null;
// セット商品の場合はフォームバリデーションをスキップ
if ($Product->getProductType() == 'set') {
// セット商品の場合は特別な処理
if ($form->isSubmitted()) {
// フォームデータからbrother情報を取得
$cartItem = $form->getData();
if ($cartItem && $cartItem->getBrother()) {
$brother = $cartItem->getBrother();
}
// セット商品の場合、フォームバリデーションはスキップして直接データを作成
// POSTデータは add_cart という名前ではなく、直接送られてくる
$addCartData = [
'product_class_id' => $request->get('ProductClass') ?? $Product->getProductClasses()->first()->getId(),
'quantity' => (int)$request->get('quantity', 1),
];
} else {
throw new NotFoundHttpException();
}
} else {
// 通常商品の場合はフォームバリデーションを実行
if (!$form->isValid()) {
throw new NotFoundHttpException();
}
if ($form->isSubmitted() && !$form->isValid()) {
throw new \Exception('Invalid form data');
}
$addCartData = $form->getData();
// 通常商品のbrother情報を取得
if ($addCartData instanceof CartItem && $addCartData->getBrother()) {
$brother = $addCartData->getBrother();
}
}
// 購入点数制限の事前チェック
$purchaseLimit = null;
$purchaseLimitChecked = false;
$requestQuantity = $addCartData['quantity'];
$allowedQuantity = $requestQuantity;
$partialAddMessage = null; // 一部追加時のメッセージ
// セット商品の購入制限チェック
if ($Product->isSetProduct()) {
$SetProduct = $this->setProductRepository->findOneBy(['set_product_id' => $Product->getSetProductId()]);
$purchaseLimit = $SetProduct ? $SetProduct->getPurchaseLimit() : null;
$purchaseLimitChecked = true;
} else {
// 単品商品の購入点数制限のチェック
$purchaseLimit = $Product->getPurchaseLimit();
$purchaseLimitChecked = true;
}
if ($purchaseLimit && $purchaseLimit > 0) {
// ログインユーザーを取得
$Customer = $this->userService->getCurrentUser();
// 過去の購入数量を取得(ログインユーザーのみ)
$purchasedQuantity = 0;
if ($Customer) {
$purchasedQuantity = $this->orderRepository->getPurchasedQuantityByProduct(
$Customer,
$Product->getId()
);
}
// カート内の既存数量を取得(商品単位で集計)
$cartQuantity = 0;
$Carts = $this->cartService->getCarts();
foreach ($Carts as $Cart) {
foreach ($Cart->getCartItems() as $CartItem) {
$ProductClass = $CartItem->getProductClass();
if ($ProductClass && $ProductClass->getProduct() &&
$ProductClass->getProduct()->getId() === $Product->getId()) {
$cartQuantity += $CartItem->getQuantity();
}
}
}
// 追加可能な数量を計算
$currentTotal = $purchasedQuantity + $cartQuantity;
$allowedQuantity = max(0, $purchaseLimit - $currentTotal);
if ($allowedQuantity <= 0) {
// 全く追加できない場合
$errorMessages[] = sprintf(
'この商品は1人%d点までの購入制限があります。すでに購入制限に達しているため、カートに追加できません。(カート内:%d点、過去購入:%d点)',
$purchaseLimit,
$cartQuantity,
$purchasedQuantity
);
// カートに追加しない
$allowedQuantity = 0;
} elseif ($allowedQuantity < $requestQuantity) {
// 一部のみ追加できる場合
$partialAddMessage = sprintf(
'この商品は1人%d点までの購入制限があります。%d点のご希望でしたが、%d点のみカートに追加しました。(カート内:%d点、過去購入:%d点)',
$purchaseLimit,
$requestQuantity,
$allowedQuantity,
$cartQuantity,
$purchasedQuantity
);
}
}
// カートへ追加(許可された数量のみ)
$actuallyAdded = false;
if ($allowedQuantity > 0) {
$this->cartService->addProduct($addCartData['product_class_id'], $allowedQuantity, $request->get('SetProductClass', []), null, $brother);
$actuallyAdded = true;
}
// 明細の正規化
$Carts = $this->cartService->getCarts();
foreach ($Carts as $Cart) {
$result = $this->purchaseFlow->validate($Cart, new PurchaseContext($Cart, $this->userService->getCurrentUser()));
// 復旧不可のエラーが発生した場合は追加した明細を削除(実際に追加した場合のみ)
if ($result->hasError()) {
// 実際にカートに追加した場合のみ、追加分を削除する
if ($actuallyAdded) {
$this->cartService->removeProduct($addCartData['product_class_id']);
}
foreach ($result->getErrors() as $error) {
$errorMessages[] = $error->getMessage();
}
}
foreach ($result->getWarning() as $warning) {
$errorMessages[] = $warning->getMessage();
}
}
$this->cartService->save();
$event = new EventArgs(
[
'form' => $form,
'Product' => $Product,
],
$request
);
$this->eventDispatcher->dispatch($event, EccubeEvents::FRONT_PRODUCT_CART_ADD_COMPLETE);
if ($event->getResponse() !== null) {
return $event->getResponse();
}
if ($request->isXmlHttpRequest()) {
// ajaxでのリクエストの場合は結果をjson形式で返す。
// 初期化
$done = null;
$messages = [];
if (empty($errorMessages)) {
// エラーが発生していない場合
$done = true;
// 一部追加のメッセージがある場合はそれを表示、なければ通常の成功メッセージ
if ($partialAddMessage) {
array_push($messages, $partialAddMessage);
} else {
array_push($messages, trans('front.product.add_cart_complete'));
}
} else {
// エラーが発生している場合
$done = false;
$messages = $errorMessages;
}
return $this->json(['done' => $done, 'messages' => $messages]);
} else {
// ajax以外でのリクエストの場合はカート画面へリダイレクト
foreach ($errorMessages as $errorMessage) {
$this->addRequestError($errorMessage);
}
// 一部追加のメッセージがある場合は警告として表示
if ($partialAddMessage) {
$this->addWarning($partialAddMessage, 'front');
}
return $this->redirectToRoute('cart');
}
}
/**
* ページタイトルの設定
*
* @param array|null $searchData
*
* @return str
*/
protected function getPageTitle($searchData)
{
if (isset($searchData['name']) && !empty($searchData['name'])) {
return trans('front.product.search_result');
} elseif (isset($searchData['category_id']) && $searchData['category_id']) {
return $searchData['category_id']->getName();
} else {
return trans('front.product.all_products');
}
}
/**
* 閲覧可能な商品かどうかを判定
*
* @param Product $Product
*
* @return boolean 閲覧可能な場合はtrue
*/
protected function checkVisibility(Product $Product)
{
$is_admin = $this->session->has('_security_admin');
// 管理ユーザの場合はステータスやオプションにかかわらず閲覧可能.
if (!$is_admin) {
// 在庫なし商品の非表示オプションが有効な場合.
// if ($this->BaseInfo->isOptionNostockHidden()) {
// if (!$Product->getStockFind()) {
// return false;
// }
// }
// 公開ステータスでない商品は表示しない.
if ($Product->getStatus()->getId() !== ProductStatus::DISPLAY_SHOW) {
return false;
}
}
return true;
}
/**
* 商品の販売期間状態をチェック
*
* @param Product $Product
*
* @return array 販売期間の状態情報
*/
protected function checkSalesPeriod(Product $Product)
{
$currentDate = new \DateTime();
$status = [
'is_sales_period_enabled' => $Product->getSalesPeriodFlg(),
'is_within_period' => true,
'message' => '',
'start_date' => $Product->getSalesStartDate(),
'end_date' => $Product->getSalesEndDate()
];
// 販売期間制限が有効な場合のみチェック
if ($Product->getSalesPeriodFlg()) {
$isWithinPeriod = true;
$message = '';
// 公開開始日のチェック
if ($Product->getSalesStartDate() && $Product->getSalesStartDate() > $currentDate) {
$isWithinPeriod = false;
$message = '公開開始日前のため、購入できません。公開開始日: ' . $Product->getSalesStartDate()->format('Y年m月d日 H:i');
}
// 公開終了日のチェック
elseif ($Product->getSalesEndDate() && $Product->getSalesEndDate() < $currentDate) {
$isWithinPeriod = false;
$message = '公開期間が終了しているため、購入できません。公開終了日: ' . $Product->getSalesEndDate()->format('Y年m月d日 H:i');
}
$status['is_within_period'] = $isWithinPeriod;
$status['message'] = $message;
}
return $status;
}
/**
* 価格取得.
*
* @Route("/products/get_product_price", name="get_product_price", methods={"POST"})
*/
public function getPriceByProductClass(Request $request)
{
$product_class_id = $request->get('product_class_id', null);
$done = false;
if ($product_class_id) {
$done = true;
$ProductClass = $this->productClassRepository->find($product_class_id);
$Customer = $this->userService->getCurrentUser();
$school = $Customer ? $Customer->getSchool() : null;
if ($school && $school->isOnSale() && !empty($ProductClass->getPrice03IncTax())) {
return $this->json(['done' => $done, 'price' => $ProductClass->getPrice03IncTax()]);
} else {
return $this->json(['done' => $done, 'price' => $ProductClass->getPrice02IncTax()]);
}
}
return $this->json(['done' => false, 'price' => null]);
}
/**
* @Route("/products/groupBySeller", name="get_product_group_by_seller", methods={"GET"})
*/
public function getProductsGroupedBySeller()
{
return $this->json($this->productRepository->getProductsGroupedBySeller());
}
}