src/Eccube/Controller/Admin/Product/ProductController.php line 154

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of EC-CUBE
  4.  *
  5.  * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
  6.  *
  7.  * http://www.ec-cube.co.jp/
  8.  *
  9.  * For the full copyright and license information, please view the LICENSE
  10.  * file that was distributed with this source code.
  11.  */
  12. namespace Eccube\Controller\Admin\Product;
  13. use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
  14. use Eccube\Common\Constant;
  15. use Eccube\Controller\AbstractController;
  16. use Eccube\Entity\BaseInfo;
  17. use Eccube\Entity\ExportCsvRow;
  18. use Eccube\Entity\Master\CsvType;
  19. use Eccube\Entity\Master\ProductStatus;
  20. use Eccube\Entity\Product;
  21. use Eccube\Entity\ProductCategory;
  22. use Eccube\Entity\ProductClass;
  23. use Eccube\Entity\ProductImage;
  24. use Eccube\Entity\ProductStock;
  25. use Eccube\Entity\ProductTag;
  26. use Eccube\Event\EccubeEvents;
  27. use Eccube\Event\EventArgs;
  28. use Eccube\Form\Type\Admin\ProductType;
  29. use Eccube\Form\Type\Admin\SearchProductType;
  30. use Eccube\Repository\BaseInfoRepository;
  31. use Eccube\Repository\CategoryRepository;
  32. use Eccube\Repository\Master\PageMaxRepository;
  33. use Eccube\Repository\Master\ProductStatusRepository;
  34. use Eccube\Repository\ProductClassRepository;
  35. use Eccube\Repository\ProductImageRepository;
  36. use Eccube\Repository\ProductRepository;
  37. use Eccube\Repository\TagRepository;
  38. use Eccube\Repository\TaxRuleRepository;
  39. use Eccube\Service\CsvExportService;
  40. use Eccube\Util\CacheUtil;
  41. use Eccube\Util\FormUtil;
  42. use Knp\Component\Pager\Paginator;
  43. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  44. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
  45. use Symfony\Component\Filesystem\Filesystem;
  46. use Symfony\Component\HttpFoundation\File\File;
  47. use Symfony\Component\HttpFoundation\RedirectResponse;
  48. use Symfony\Component\HttpFoundation\Request;
  49. use Symfony\Component\HttpFoundation\StreamedResponse;
  50. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  51. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  52. use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
  53. use Symfony\Component\Routing\Annotation\Route;
  54. use Symfony\Component\Routing\RouterInterface;
  55. class ProductController extends AbstractController
  56. {
  57.     /**
  58.      * @var CsvExportService
  59.      */
  60.     protected $csvExportService;
  61.     /**
  62.      * @var ProductClassRepository
  63.      */
  64.     protected $productClassRepository;
  65.     /**
  66.      * @var ProductImageRepository
  67.      */
  68.     protected $productImageRepository;
  69.     /**
  70.      * @var TaxRuleRepository
  71.      */
  72.     protected $taxRuleRepository;
  73.     /**
  74.      * @var CategoryRepository
  75.      */
  76.     protected $categoryRepository;
  77.     /**
  78.      * @var ProductRepository
  79.      */
  80.     protected $productRepository;
  81.     /**
  82.      * @var BaseInfo
  83.      */
  84.     protected $BaseInfo;
  85.     /**
  86.      * @var PageMaxRepository
  87.      */
  88.     protected $pageMaxRepository;
  89.     /**
  90.      * @var ProductStatusRepository
  91.      */
  92.     protected $productStatusRepository;
  93.     /**
  94.      * @var TagRepository
  95.      */
  96.     protected $tagRepository;
  97.     /**
  98.      * ProductController constructor.
  99.      *
  100.      * @param CsvExportService $csvExportService
  101.      * @param ProductClassRepository $productClassRepository
  102.      * @param ProductImageRepository $productImageRepository
  103.      * @param TaxRuleRepository $taxRuleRepository
  104.      * @param CategoryRepository $categoryRepository
  105.      * @param ProductRepository $productRepository
  106.      * @param BaseInfoRepository $baseInfoRepository
  107.      * @param PageMaxRepository $pageMaxRepository
  108.      * @param ProductStatusRepository $productStatusRepository
  109.      * @param TagRepository $tagRepository
  110.      */
  111.     public function __construct(
  112.         CsvExportService $csvExportService,
  113.         ProductClassRepository $productClassRepository,
  114.         ProductImageRepository $productImageRepository,
  115.         TaxRuleRepository $taxRuleRepository,
  116.         CategoryRepository $categoryRepository,
  117.         ProductRepository $productRepository,
  118.         BaseInfoRepository $baseInfoRepository,
  119.         PageMaxRepository $pageMaxRepository,
  120.         ProductStatusRepository $productStatusRepository,
  121.         TagRepository $tagRepository
  122.     ) {
  123.         $this->csvExportService $csvExportService;
  124.         $this->productClassRepository $productClassRepository;
  125.         $this->productImageRepository $productImageRepository;
  126.         $this->taxRuleRepository $taxRuleRepository;
  127.         $this->categoryRepository $categoryRepository;
  128.         $this->productRepository $productRepository;
  129.         $this->BaseInfo $baseInfoRepository->get();
  130.         $this->pageMaxRepository $pageMaxRepository;
  131.         $this->productStatusRepository $productStatusRepository;
  132.         $this->tagRepository $tagRepository;
  133.     }
  134.     /**
  135.      * @Route("/%eccube_admin_route%/product", name="admin_product")
  136.      * @Route("/%eccube_admin_route%/product/page/{page_no}", requirements={"page_no" = "\d+"}, name="admin_product_page")
  137.      * @Template("@admin/Product/index.twig")
  138.      */
  139.     public function index(Request $request$page_no nullPaginator $paginator)
  140.     {
  141.         $builder $this->formFactory
  142.             ->createBuilder(SearchProductType::class);
  143.         $event = new EventArgs(
  144.             [
  145.                 'builder' => $builder,
  146.             ],
  147.             $request
  148.         );
  149.         $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_INDEX_INITIALIZE$event);
  150.         $searchForm $builder->getForm();
  151.         /**
  152.          * ページの表示件数は, 以下の順に優先される.
  153.          * - リクエストパラメータ
  154.          * - セッション
  155.          * - デフォルト値
  156.          * また, セッションに保存する際は mtb_page_maxと照合し, 一致した場合のみ保存する.
  157.          **/
  158.         $page_count $this->session->get('eccube.admin.order.search.page_count',
  159.             $this->eccubeConfig->get('eccube_default_page_count'));
  160.         $page_count_param = (int) $request->get('page_count');
  161.         $pageMaxis $this->pageMaxRepository->findAll();
  162.         if ($page_count_param) {
  163.             foreach ($pageMaxis as $pageMax) {
  164.                 if ($page_count_param == $pageMax->getName()) {
  165.                     $page_count $pageMax->getName();
  166.                     $this->session->set('eccube.admin.order.search.page_count'$page_count);
  167.                     break;
  168.                 }
  169.             }
  170.         }
  171.         if ('POST' === $request->getMethod()) {
  172.             $searchForm->handleRequest($request);
  173.             if ($searchForm->isValid()) {
  174.                 /**
  175.                  * 検索が実行された場合は, セッションに検索条件を保存する.
  176.                  * ページ番号は最初のページ番号に初期化する.
  177.                  */
  178.                 $page_no 1;
  179.                 $searchData $searchForm->getData();
  180.                 // 検索条件, ページ番号をセッションに保持.
  181.                 $this->session->set('eccube.admin.product.search'FormUtil::getViewData($searchForm));
  182.                 $this->session->set('eccube.admin.product.search.page_no'$page_no);
  183.             } else {
  184.                 // 検索エラーの際は, 詳細検索枠を開いてエラー表示する.
  185.                 return [
  186.                     'searchForm' => $searchForm->createView(),
  187.                     'pagination' => [],
  188.                     'pageMaxis' => $pageMaxis,
  189.                     'page_no' => $page_no,
  190.                     'page_count' => $page_count,
  191.                     'has_errors' => true,
  192.                 ];
  193.             }
  194.         } else {
  195.             if (null !== $page_no || $request->get('resume')) {
  196.                 /*
  197.                  * ページ送りの場合または、他画面から戻ってきた場合は, セッションから検索条件を復旧する.
  198.                  */
  199.                 if ($page_no) {
  200.                     // ページ送りで遷移した場合.
  201.                     $this->session->set('eccube.admin.product.search.page_no', (int) $page_no);
  202.                 } else {
  203.                     // 他画面から遷移した場合.
  204.                     $page_no $this->session->get('eccube.admin.product.search.page_no'1);
  205.                 }
  206.                 $viewData $this->session->get('eccube.admin.product.search', []);
  207.                 $searchData FormUtil::submitAndGetData($searchForm$viewData);
  208.             } else {
  209.                 /**
  210.                  * 初期表示の場合.
  211.                  */
  212.                 $page_no 1;
  213.                 // submit default value
  214.                 $viewData FormUtil::getViewData($searchForm);
  215.                 $searchData FormUtil::submitAndGetData($searchForm$viewData);
  216.                 // セッション中の検索条件, ページ番号を初期化.
  217.                 $this->session->set('eccube.admin.product.search'$viewData);
  218.                 $this->session->set('eccube.admin.product.search.page_no'$page_no);
  219.             }
  220.         }
  221.         $qb $this->productRepository->getQueryBuilderBySearchDataForAdmin($searchData);
  222.         $event = new EventArgs(
  223.             [
  224.                 'qb' => $qb,
  225.                 'searchData' => $searchData,
  226.             ],
  227.             $request
  228.         );
  229.         $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_INDEX_SEARCH$event);
  230.         $pagination $paginator->paginate(
  231.             $qb,
  232.             $page_no,
  233.             $page_count
  234.         );
  235.         return [
  236.             'searchForm' => $searchForm->createView(),
  237.             'pagination' => $pagination,
  238.             'pageMaxis' => $pageMaxis,
  239.             'page_no' => $page_no,
  240.             'page_count' => $page_count,
  241.             'has_errors' => false,
  242.         ];
  243.     }
  244.     /**
  245.      * @Route("/%eccube_admin_route%/product/classes/{id}/load", name="admin_product_classes_load", methods={"GET"}, requirements={"id" = "\d+"})
  246.      * @Template("@admin/Product/product_class_popup.twig")
  247.      * @ParamConverter("Product")
  248.      */
  249.     public function loadProductClasses(Request $requestProduct $Product)
  250.     {
  251.         if (!$request->isXmlHttpRequest()) {
  252.             throw new BadRequestHttpException();
  253.         }
  254.         $data = [];
  255.         /** @var $Product ProductRepository */
  256.         if (!$Product) {
  257.             throw new NotFoundHttpException();
  258.         }
  259.         if ($Product->hasProductClass()) {
  260.             $class $Product->getProductClasses();
  261.             foreach ($class as $item) {
  262.                 if ($item['visible']) {
  263.                     $data[] = $item;
  264.                 }
  265.             }
  266.         }
  267.         return [
  268.             'data' => $data,
  269.         ];
  270.     }
  271.     /**
  272.      * @Route("/%eccube_admin_route%/product/product/image/add", name="admin_product_image_add", methods={"POST"})
  273.      */
  274.     public function addImage(Request $request)
  275.     {
  276.         if (!$request->isXmlHttpRequest()) {
  277.             throw new BadRequestHttpException();
  278.         }
  279.         $images $request->files->get('admin_product');
  280.         $allowExtensions = ['gif''jpg''jpeg''png'];
  281.         $files = [];
  282.         if (count($images) > 0) {
  283.             foreach ($images as $img) {
  284.                 foreach ($img as $image) {
  285.                     //ファイルフォーマット検証
  286.                     $mimeType $image->getMimeType();
  287.                     if (!== strpos($mimeType'image')) {
  288.                         throw new UnsupportedMediaTypeHttpException();
  289.                     }
  290.                     // 拡張子
  291.                     $extension $image->getClientOriginalExtension();
  292.                     if (!in_array(strtolower($extension), $allowExtensions)) {
  293.                         throw new UnsupportedMediaTypeHttpException();
  294.                     }
  295.                     $filename date('mdHis').uniqid('_').'.'.$extension;
  296.                     $image->move($this->eccubeConfig['eccube_temp_image_dir'], $filename);
  297.                     $files[] = $filename;
  298.                 }
  299.             }
  300.         }
  301.         $event = new EventArgs(
  302.             [
  303.                 'images' => $images,
  304.                 'files' => $files,
  305.             ],
  306.             $request
  307.         );
  308.         $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_ADD_IMAGE_COMPLETE$event);
  309.         $files $event->getArgument('files');
  310.         return $this->json(['files' => $files], 200);
  311.     }
  312.     /**
  313.      * @Route("/%eccube_admin_route%/product/product/new", name="admin_product_product_new")
  314.      * @Route("/%eccube_admin_route%/product/product/{id}/edit", requirements={"id" = "\d+"}, name="admin_product_product_edit")
  315.      * @Template("@admin/Product/product.twig")
  316.      */
  317.     public function edit(Request $request$id nullRouterInterface $routerCacheUtil $cacheUtil)
  318.     {
  319.         $has_class false;
  320.         if (is_null($id)) {
  321.             $Product = new Product();
  322.             $ProductClass = new ProductClass();
  323.             $ProductStatus $this->productStatusRepository->find(ProductStatus::DISPLAY_HIDE);
  324.             $Product
  325.                 ->addProductClass($ProductClass)
  326.                 ->setStatus($ProductStatus);
  327.             $ProductClass
  328.                 ->setVisible(true)
  329.                 ->setStockUnlimited(true)
  330.                 ->setProduct($Product);
  331.             $ProductStock = new ProductStock();
  332.             $ProductClass->setProductStock($ProductStock);
  333.             $ProductStock->setProductClass($ProductClass);
  334.         } else {
  335.             $Product $this->productRepository->find($id);
  336.             if (!$Product) {
  337.                 throw new NotFoundHttpException();
  338.             }
  339.             // 規格無しの商品の場合は、デフォルト規格を表示用に取得する
  340.             $has_class $Product->hasProductClass();
  341.             if (!$has_class) {
  342.                 $ProductClasses $Product->getProductClasses();
  343.                 foreach ($ProductClasses as $pc) {
  344.                     if (!is_null($pc->getClassCategory1())) {
  345.                         continue;
  346.                     }
  347.                     if ($pc->isVisible()) {
  348.                         $ProductClass $pc;
  349.                         break;
  350.                     }
  351.                 }
  352.                 if ($this->BaseInfo->isOptionProductTaxRule() && $ProductClass->getTaxRule()) {
  353.                     $ProductClass->setTaxRate($ProductClass->getTaxRule()->getTaxRate());
  354.                 }
  355.                 $ProductStock $ProductClass->getProductStock();
  356.             }
  357.         }
  358.         $builder $this->formFactory
  359.             ->createBuilder(ProductType::class, $Product);
  360.         // 規格あり商品の場合、規格関連情報をFormから除外
  361.         if ($has_class) {
  362.             $builder->remove('class');
  363.         }
  364.         $event = new EventArgs(
  365.             [
  366.                 'builder' => $builder,
  367.                 'Product' => $Product,
  368.             ],
  369.             $request
  370.         );
  371.         $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_EDIT_INITIALIZE$event);
  372.         $form $builder->getForm();
  373.         if (!$has_class) {
  374.             $ProductClass->setStockUnlimited($ProductClass->isStockUnlimited());
  375.             $form['class']->setData($ProductClass);
  376.         }
  377.         // ファイルの登録
  378.         $images = [];
  379.         $ProductImages $Product->getProductImage();
  380.         foreach ($ProductImages as $ProductImage) {
  381.             $images[] = $ProductImage->getFileName();
  382.         }
  383.         $form['images']->setData($images);
  384.         $categories = [];
  385.         $ProductCategories $Product->getProductCategories();
  386.         foreach ($ProductCategories as $ProductCategory) {
  387.             /* @var $ProductCategory \Eccube\Entity\ProductCategory */
  388.             $categories[] = $ProductCategory->getCategory();
  389.         }
  390.         $form['Category']->setData($categories);
  391.         $Tags $Product->getTags();
  392.         $form['Tag']->setData($Tags);
  393.         if ('POST' === $request->getMethod()) {
  394.             $form->handleRequest($request);
  395.             if ($form->isValid()) {
  396.                 log_info('商品登録開始', [$id]);
  397.                 $Product $form->getData();
  398.                 if (!$has_class) {
  399.                     $ProductClass $form['class']->getData();
  400.                     // 個別消費税
  401.                     if ($this->BaseInfo->isOptionProductTaxRule()) {
  402.                         if ($ProductClass->getTaxRate() !== null) {
  403.                             if ($ProductClass->getTaxRule()) {
  404.                                 $ProductClass->getTaxRule()->setTaxRate($ProductClass->getTaxRate());
  405.                             } else {
  406.                                 $taxrule $this->taxRuleRepository->newTaxRule();
  407.                                 $taxrule->setTaxRate($ProductClass->getTaxRate());
  408.                                 $taxrule->setApplyDate(new \DateTime());
  409.                                 $taxrule->setProduct($Product);
  410.                                 $taxrule->setProductClass($ProductClass);
  411.                                 $ProductClass->setTaxRule($taxrule);
  412.                             }
  413.                             $ProductClass->getTaxRule()->setTaxRate($ProductClass->getTaxRate());
  414.                         } else {
  415.                             if ($ProductClass->getTaxRule()) {
  416.                                 $this->taxRuleRepository->delete($ProductClass->getTaxRule());
  417.                                 $ProductClass->setTaxRule(null);
  418.                             }
  419.                         }
  420.                     }
  421.                     $this->entityManager->persist($ProductClass);
  422.                     // 在庫情報を作成
  423.                     if (!$ProductClass->isStockUnlimited()) {
  424.                         $ProductStock->setStock($ProductClass->getStock());
  425.                     } else {
  426.                         // 在庫無制限時はnullを設定
  427.                         $ProductStock->setStock(null);
  428.                     }
  429.                     $this->entityManager->persist($ProductStock);
  430.                 }
  431.                 // カテゴリの登録
  432.                 // 一度クリア
  433.                 /* @var $Product \Eccube\Entity\Product */
  434.                 foreach ($Product->getProductCategories() as $ProductCategory) {
  435.                     $Product->removeProductCategory($ProductCategory);
  436.                     $this->entityManager->remove($ProductCategory);
  437.                 }
  438.                 $this->entityManager->persist($Product);
  439.                 $this->entityManager->flush();
  440.                 $count 1;
  441.                 $Categories $form->get('Category')->getData();
  442.                 $categoriesIdList = [];
  443.                 foreach ($Categories as $Category) {
  444.                     foreach ($Category->getPath() as $ParentCategory) {
  445.                         if (!isset($categoriesIdList[$ParentCategory->getId()])) {
  446.                             $ProductCategory $this->createProductCategory($Product$ParentCategory$count);
  447.                             $this->entityManager->persist($ProductCategory);
  448.                             $count++;
  449.                             /* @var $Product \Eccube\Entity\Product */
  450.                             $Product->addProductCategory($ProductCategory);
  451.                             $categoriesIdList[$ParentCategory->getId()] = true;
  452.                         }
  453.                     }
  454.                     if (!isset($categoriesIdList[$Category->getId()])) {
  455.                         $ProductCategory $this->createProductCategory($Product$Category$count);
  456.                         $this->entityManager->persist($ProductCategory);
  457.                         $count++;
  458.                         /* @var $Product \Eccube\Entity\Product */
  459.                         $Product->addProductCategory($ProductCategory);
  460.                         $categoriesIdList[$ParentCategory->getId()] = true;
  461.                     }
  462.                 }
  463.                 // 画像の登録
  464.                 $add_images $form->get('add_images')->getData();
  465.                 foreach ($add_images as $add_image) {
  466.                     $ProductImage = new \Eccube\Entity\ProductImage();
  467.                     $ProductImage
  468.                         ->setFileName($add_image)
  469.                         ->setProduct($Product)
  470.                         ->setSortNo(1);
  471.                     $Product->addProductImage($ProductImage);
  472.                     $this->entityManager->persist($ProductImage);
  473.                     // 移動
  474.                     $file = new File($this->eccubeConfig['eccube_temp_image_dir'].'/'.$add_image);
  475.                     $file->move($this->eccubeConfig['eccube_save_image_dir']);
  476.                 }
  477.                 // 画像の削除
  478.                 $delete_images $form->get('delete_images')->getData();
  479.                 foreach ($delete_images as $delete_image) {
  480.                     $ProductImage $this->productImageRepository
  481.                         ->findOneBy(['file_name' => $delete_image]);
  482.                     // 追加してすぐに削除した画像は、Entityに追加されない
  483.                     if ($ProductImage instanceof ProductImage) {
  484.                         $Product->removeProductImage($ProductImage);
  485.                         $this->entityManager->remove($ProductImage);
  486.                     }
  487.                     $this->entityManager->persist($Product);
  488.                     // 削除
  489.                     $fs = new Filesystem();
  490.                     $fs->remove($this->eccubeConfig['eccube_save_image_dir'].'/'.$delete_image);
  491.                 }
  492.                 $this->entityManager->persist($Product);
  493.                 $this->entityManager->flush();
  494.                 $sortNos $request->get('sort_no_images');
  495.                 if ($sortNos) {
  496.                     foreach ($sortNos as $sortNo) {
  497.                         list($filename$sortNo_val) = explode('//'$sortNo);
  498.                         $ProductImage $this->productImageRepository
  499.                             ->findOneBy([
  500.                                 'file_name' => $filename,
  501.                                 'Product' => $Product,
  502.                             ]);
  503.                         $ProductImage->setSortNo($sortNo_val);
  504.                         $this->entityManager->persist($ProductImage);
  505.                     }
  506.                 }
  507.                 $this->entityManager->flush();
  508.                 // 商品タグの登録
  509.                 // 商品タグを一度クリア
  510.                 $ProductTags $Product->getProductTag();
  511.                 foreach ($ProductTags as $ProductTag) {
  512.                     $Product->removeProductTag($ProductTag);
  513.                     $this->entityManager->remove($ProductTag);
  514.                 }
  515.                 // 商品タグの登録
  516.                 $Tags $form->get('Tag')->getData();
  517.                 foreach ($Tags as $Tag) {
  518.                     $ProductTag = new ProductTag();
  519.                     $ProductTag
  520.                         ->setProduct($Product)
  521.                         ->setTag($Tag);
  522.                     $Product->addProductTag($ProductTag);
  523.                     $this->entityManager->persist($ProductTag);
  524.                 }
  525.                 $Product->setUpdateDate(new \DateTime());
  526.                 $this->entityManager->flush();
  527.                 log_info('商品登録完了', [$id]);
  528.                 $event = new EventArgs(
  529.                     [
  530.                         'form' => $form,
  531.                         'Product' => $Product,
  532.                     ],
  533.                     $request
  534.                 );
  535.                 $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_EDIT_COMPLETE$event);
  536.                 $this->addSuccess('admin.common.save_complete''admin');
  537.                 if ($returnLink $form->get('return_link')->getData()) {
  538.                     try {
  539.                         // $returnLinkはpathの形式で渡される. pathが存在するかをルータでチェックする.
  540.                         $pattern '/^'.preg_quote($request->getBasePath(), '/').'/';
  541.                         $returnLink preg_replace($pattern''$returnLink);
  542.                         $result $router->match($returnLink);
  543.                         // パラメータのみ抽出
  544.                         $params array_filter($result, function ($key) {
  545.                             return !== \strpos($key'_');
  546.                         }, ARRAY_FILTER_USE_KEY);
  547.                         // pathからurlを再構築してリダイレクト.
  548.                         return $this->redirectToRoute($result['_route'], $params);
  549.                     } catch (\Exception $e) {
  550.                         // マッチしない場合はログ出力してスキップ.
  551.                         log_warning('URLの形式が不正です。');
  552.                     }
  553.                 }
  554.                 $cacheUtil->clearDoctrineCache();
  555.                 return $this->redirectToRoute('admin_product_product_edit', ['id' => $Product->getId()]);
  556.             }
  557.         }
  558.         // 検索結果の保持
  559.         $builder $this->formFactory
  560.             ->createBuilder(SearchProductType::class);
  561.         $event = new EventArgs(
  562.             [
  563.                 'builder' => $builder,
  564.                 'Product' => $Product,
  565.             ],
  566.             $request
  567.         );
  568.         $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_EDIT_SEARCH$event);
  569.         $searchForm $builder->getForm();
  570.         if ('POST' === $request->getMethod()) {
  571.             $searchForm->handleRequest($request);
  572.         }
  573.         // Get Tags
  574.         $TagsList $this->tagRepository->getList();
  575.         // ツリー表示のため、ルートからのカテゴリを取得
  576.         $TopCategories $this->categoryRepository->getList(null);
  577.         $ChoicedCategoryIds array_map(function ($Category) {
  578.             return $Category->getId();
  579.         }, $form->get('Category')->getData());
  580.         return [
  581.             'Product' => $Product,
  582.             'Tags' => $Tags,
  583.             'TagsList' => $TagsList,
  584.             'form' => $form->createView(),
  585.             'searchForm' => $searchForm->createView(),
  586.             'has_class' => $has_class,
  587.             'id' => $id,
  588.             'TopCategories' => $TopCategories,
  589.             'ChoicedCategoryIds' => $ChoicedCategoryIds,
  590.         ];
  591.     }
  592.     /**
  593.      * @Route("/%eccube_admin_route%/product/product/{id}/delete", requirements={"id" = "\d+"}, name="admin_product_product_delete", methods={"DELETE"})
  594.      */
  595.     public function delete(Request $request$id nullCacheUtil $cacheUtil)
  596.     {
  597.         $this->isTokenValid();
  598.         $session $request->getSession();
  599.         $page_no intval($session->get('eccube.admin.product.search.page_no'));
  600.         $page_no $page_no $page_no Constant::ENABLED;
  601.         $message null;
  602.         $success false;
  603.         if (!is_null($id)) {
  604.             /* @var $Product \Eccube\Entity\Product */
  605.             $Product $this->productRepository->find($id);
  606.             if (!$Product) {
  607.                 if ($request->isXmlHttpRequest()) {
  608.                     $message trans('admin.common.delete_error_already_deleted');
  609.                     return $this->json(['success' => $success'message' => $message]);
  610.                 } else {
  611.                     $this->deleteMessage();
  612.                     $rUrl $this->generateUrl('admin_product_page', ['page_no' => $page_no]).'?resume='.Constant::ENABLED;
  613.                     return $this->redirect($rUrl);
  614.                 }
  615.             }
  616.             if ($Product instanceof Product) {
  617.                 log_info('商品削除開始', [$id]);
  618.                 $deleteImages $Product->getProductImage();
  619.                 $ProductClasses $Product->getProductClasses();
  620.                 try {
  621.                     $this->productRepository->delete($Product);
  622.                     $this->entityManager->flush();
  623.                     $event = new EventArgs(
  624.                         [
  625.                             'Product' => $Product,
  626.                             'ProductClass' => $ProductClasses,
  627.                             'deleteImages' => $deleteImages,
  628.                         ],
  629.                         $request
  630.                     );
  631.                     $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_DELETE_COMPLETE$event);
  632.                     $deleteImages $event->getArgument('deleteImages');
  633.                     // 画像ファイルの削除(commit後に削除させる)
  634.                     foreach ($deleteImages as $deleteImage) {
  635.                         try {
  636.                             $fs = new Filesystem();
  637.                             $fs->remove($this->eccubeConfig['eccube_save_image_dir'].'/'.$deleteImage);
  638.                         } catch (\Exception $e) {
  639.                             // エラーが発生しても無視する
  640.                         }
  641.                     }
  642.                     log_info('商品削除完了', [$id]);
  643.                     $success true;
  644.                     $message trans('admin.common.delete_complete');
  645.                     $cacheUtil->clearDoctrineCache();
  646.                 } catch (ForeignKeyConstraintViolationException $e) {
  647.                     log_info('商品削除エラー', [$id]);
  648.                     $message trans('admin.common.delete_error_foreign_key', ['%name%' => $Product->getName()]);
  649.                 }
  650.             } else {
  651.                 log_info('商品削除エラー', [$id]);
  652.                 $message trans('admin.common.delete_error');
  653.             }
  654.         } else {
  655.             log_info('商品削除エラー', [$id]);
  656.             $message trans('admin.common.delete_error');
  657.         }
  658.         if ($request->isXmlHttpRequest()) {
  659.             return $this->json(['success' => $success'message' => $message]);
  660.         } else {
  661.             if ($success) {
  662.                 $this->addSuccess($message'admin');
  663.             } else {
  664.                 $this->addError($message'admin');
  665.             }
  666.             $rUrl $this->generateUrl('admin_product_page', ['page_no' => $page_no]).'?resume='.Constant::ENABLED;
  667.             return $this->redirect($rUrl);
  668.         }
  669.     }
  670.     /**
  671.      * @Route("/%eccube_admin_route%/product/product/{id}/copy", requirements={"id" = "\d+"}, name="admin_product_product_copy", methods={"POST"})
  672.      */
  673.     public function copy(Request $request$id null)
  674.     {
  675.         $this->isTokenValid();
  676.         if (!is_null($id)) {
  677.             $Product $this->productRepository->find($id);
  678.             if ($Product instanceof Product) {
  679.                 $CopyProduct = clone $Product;
  680.                 $CopyProduct->copy();
  681.                 $ProductStatus $this->productStatusRepository->find(ProductStatus::DISPLAY_HIDE);
  682.                 $CopyProduct->setStatus($ProductStatus);
  683.                 $CopyProductCategories $CopyProduct->getProductCategories();
  684.                 foreach ($CopyProductCategories as $Category) {
  685.                     $this->entityManager->persist($Category);
  686.                 }
  687.                 // 規格あり商品の場合は, デフォルトの商品規格を取得し登録する.
  688.                 if ($CopyProduct->hasProductClass()) {
  689.                     $dummyClass $this->productClassRepository->findOneBy([
  690.                         'visible' => false,
  691.                         'ClassCategory1' => null,
  692.                         'ClassCategory2' => null,
  693.                         'Product' => $Product,
  694.                     ]);
  695.                     $dummyClass = clone $dummyClass;
  696.                     $dummyClass->setProduct($CopyProduct);
  697.                     $CopyProduct->addProductClass($dummyClass);
  698.                 }
  699.                 $CopyProductClasses $CopyProduct->getProductClasses();
  700.                 foreach ($CopyProductClasses as $Class) {
  701.                     $Stock $Class->getProductStock();
  702.                     $CopyStock = clone $Stock;
  703.                     $CopyStock->setProductClass($Class);
  704.                     $this->entityManager->persist($CopyStock);
  705.                     $TaxRule $Class->getTaxRule();
  706.                     if ($TaxRule) {
  707.                         $CopyTaxRule = clone $TaxRule;
  708.                         $CopyTaxRule->setProductClass($Class);
  709.                         $CopyTaxRule->setProduct($CopyProduct);
  710.                         $this->entityManager->persist($CopyTaxRule);
  711.                     }
  712.                     $this->entityManager->persist($Class);
  713.                 }
  714.                 $Images $CopyProduct->getProductImage();
  715.                 foreach ($Images as $Image) {
  716.                     // 画像ファイルを新規作成
  717.                     $extension pathinfo($Image->getFileName(), PATHINFO_EXTENSION);
  718.                     $filename date('mdHis').uniqid('_').'.'.$extension;
  719.                     try {
  720.                         $fs = new Filesystem();
  721.                         $fs->copy($this->eccubeConfig['eccube_save_image_dir'].'/'.$Image->getFileName(), $this->eccubeConfig['eccube_save_image_dir'].'/'.$filename);
  722.                     } catch (\Exception $e) {
  723.                         // エラーが発生しても無視する
  724.                     }
  725.                     $Image->setFileName($filename);
  726.                     $this->entityManager->persist($Image);
  727.                 }
  728.                 $Tags $CopyProduct->getProductTag();
  729.                 foreach ($Tags as $Tag) {
  730.                     $this->entityManager->persist($Tag);
  731.                 }
  732.                 $this->entityManager->persist($CopyProduct);
  733.                 $this->entityManager->flush();
  734.                 $event = new EventArgs(
  735.                     [
  736.                         'Product' => $Product,
  737.                         'CopyProduct' => $CopyProduct,
  738.                         'CopyProductCategories' => $CopyProductCategories,
  739.                         'CopyProductClasses' => $CopyProductClasses,
  740.                         'images' => $Images,
  741.                         'Tags' => $Tags,
  742.                     ],
  743.                     $request
  744.                 );
  745.                 $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_COPY_COMPLETE$event);
  746.                 $this->addSuccess('admin.product.copy_complete''admin');
  747.                 return $this->redirectToRoute('admin_product_product_edit', ['id' => $CopyProduct->getId()]);
  748.             } else {
  749.                 $this->addError('admin.product.copy_error''admin');
  750.             }
  751.         } else {
  752.             $msg trans('admin.product.copy_error');
  753.             $this->addError($msg'admin');
  754.         }
  755.         return $this->redirectToRoute('admin_product');
  756.     }
  757.     /**
  758.      * @Route("/%eccube_admin_route%/product/product/{id}/display", requirements={"id" = "\d+"}, name="admin_product_product_display")
  759.      */
  760.     public function display(Request $request$id null)
  761.     {
  762.         $event = new EventArgs(
  763.             [],
  764.             $request
  765.         );
  766.         $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_DISPLAY_COMPLETE$event);
  767.         if (!is_null($id)) {
  768.             return $this->redirectToRoute('product_detail', ['id' => $id'admin' => '1']);
  769.         }
  770.         return $this->redirectToRoute('admin_product');
  771.     }
  772.     /**
  773.      * 商品CSVの出力.
  774.      *
  775.      * @Route("/%eccube_admin_route%/product/export", name="admin_product_export")
  776.      *
  777.      * @param Request $request
  778.      *
  779.      * @return StreamedResponse
  780.      */
  781.     public function export(Request $request)
  782.     {
  783.         // タイムアウトを無効にする.
  784.         set_time_limit(0);
  785.         // sql loggerを無効にする.
  786.         $em $this->entityManager;
  787.         $em->getConfiguration()->setSQLLogger(null);
  788.         $response = new StreamedResponse();
  789.         $response->setCallback(function () use ($request) {
  790.             // CSV種別を元に初期化.
  791.             $this->csvExportService->initCsvType(CsvType::CSV_TYPE_PRODUCT);
  792.             // ヘッダ行の出力.
  793.             $this->csvExportService->exportHeader();
  794.             // 商品データ検索用のクエリビルダを取得.
  795.             $qb $this->csvExportService
  796.                 ->getProductQueryBuilder($request);
  797.             // Get stock status
  798.             $isOutOfStock 0;
  799.             $session $request->getSession();
  800.             if ($session->has('eccube.admin.product.search')) {
  801.                 $searchData $session->get('eccube.admin.product.search', []);
  802.                 if (isset($searchData['stock_status']) && $searchData['stock_status'] === 0) {
  803.                     $isOutOfStock 1;
  804.                 }
  805.             }
  806.             // joinする場合はiterateが使えないため, select句をdistinctする.
  807.             // http://qiita.com/suin/items/2b1e98105fa3ef89beb7
  808.             // distinctのmysqlとpgsqlの挙動をあわせる.
  809.             // http://uedatakeshi.blogspot.jp/2010/04/distinct-oeder-by-postgresmysql.html
  810.             $qb->resetDQLPart('select')
  811.                 ->resetDQLPart('orderBy')
  812.                 ->orderBy('p.update_date''DESC');
  813.             if ($isOutOfStock) {
  814.                 $qb->select('p, pc')
  815.                     ->distinct();
  816.             } else {
  817.                 $qb->select('p')
  818.                     ->distinct();
  819.             }
  820.             // データ行の出力.
  821.             $this->csvExportService->setExportQueryBuilder($qb);
  822.             $this->csvExportService->exportData(function ($entityCsvExportService $csvService) use ($request) {
  823.                 $Csvs $csvService->getCsvs();
  824.                 /** @var $Product \Eccube\Entity\Product */
  825.                 $Product $entity;
  826.                 /** @var $ProductClasses \Eccube\Entity\ProductClass[] */
  827.                 $ProductClasses $Product->getProductClasses();
  828.                 foreach ($ProductClasses as $ProductClass) {
  829.                     $ExportCsvRow = new ExportCsvRow();
  830.                     // CSV出力項目と合致するデータを取得.
  831.                     foreach ($Csvs as $Csv) {
  832.                         // 商品データを検索.
  833.                         $ExportCsvRow->setData($csvService->getData($Csv$Product));
  834.                         if ($ExportCsvRow->isDataNull()) {
  835.                             // 商品規格情報を検索.
  836.                             $ExportCsvRow->setData($csvService->getData($Csv$ProductClass));
  837.                         }
  838.                         $event = new EventArgs(
  839.                             [
  840.                                 'csvService' => $csvService,
  841.                                 'Csv' => $Csv,
  842.                                 'ProductClass' => $ProductClass,
  843.                                 'ExportCsvRow' => $ExportCsvRow,
  844.                             ],
  845.                             $request
  846.                         );
  847.                         $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_CSV_EXPORT$event);
  848.                         $ExportCsvRow->pushData();
  849.                     }
  850.                     // $row[] = number_format(memory_get_usage(true));
  851.                     // 出力.
  852.                     $csvService->fputcsv($ExportCsvRow->getRow());
  853.                 }
  854.             });
  855.         });
  856.         $now = new \DateTime();
  857.         $filename 'product_'.$now->format('YmdHis').'.csv';
  858.         $response->headers->set('Content-Type''application/octet-stream');
  859.         $response->headers->set('Content-Disposition''attachment; filename='.$filename);
  860.         $response->send();
  861.         log_info('商品CSV出力ファイル名', [$filename]);
  862.         return $response;
  863.     }
  864.     /**
  865.      * ProductCategory作成
  866.      *
  867.      * @param \Eccube\Entity\Product $Product
  868.      * @param \Eccube\Entity\Category $Category
  869.      * @param integer $count
  870.      *
  871.      * @return \Eccube\Entity\ProductCategory
  872.      */
  873.     private function createProductCategory($Product$Category$count)
  874.     {
  875.         $ProductCategory = new ProductCategory();
  876.         $ProductCategory->setProduct($Product);
  877.         $ProductCategory->setProductId($Product->getId());
  878.         $ProductCategory->setCategory($Category);
  879.         $ProductCategory->setCategoryId($Category->getId());
  880.         return $ProductCategory;
  881.     }
  882.     /**
  883.      * Bulk public action
  884.      *
  885.      * @Route("/%eccube_admin_route%/product/bulk/product-status/{id}", requirements={"id" = "\d+"}, name="admin_product_bulk_product_status", methods={"POST"})
  886.      *
  887.      * @param Request $request
  888.      * @param ProductStatus $ProductStatus
  889.      *
  890.      * @return RedirectResponse
  891.      */
  892.     public function bulkProductStatus(Request $requestProductStatus $ProductStatusCacheUtil $cacheUtil)
  893.     {
  894.         $this->isTokenValid();
  895.         /** @var Product[] $Products */
  896.         $Products $this->productRepository->findBy(['id' => $request->get('ids')]);
  897.         $count 0;
  898.         foreach ($Products as $Product) {
  899.             try {
  900.                 $Product->setStatus($ProductStatus);
  901.                 $this->productRepository->save($Product);
  902.                 $count++;
  903.             } catch (\Exception $e) {
  904.                 $this->addError($e->getMessage(), 'admin');
  905.             }
  906.         }
  907.         try {
  908.             if ($count) {
  909.                 $this->entityManager->flush();
  910.                 $msg $this->translator->trans('admin.product.bulk_change_status_complete', [
  911.                     '%count%' => $count,
  912.                     '%status%' => $ProductStatus->getName(),
  913.                 ]);
  914.                 $this->addSuccess($msg'admin');
  915.                 $cacheUtil->clearDoctrineCache();
  916.             }
  917.         } catch (\Exception $e) {
  918.             $this->addError($e->getMessage(), 'admin');
  919.         }
  920.         return $this->redirectToRoute('admin_product', ['resume' => Constant::ENABLED]);
  921.     }
  922. }