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

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 Customize\Entity\ProductDetailImage;
  14. use Customize\Entity\ProductGallery;
  15. use Customize\Entity\ProductNaireImage;
  16. use Customize\Repository\ProductDetailImageRepository;
  17. use Customize\Repository\ProductGalleryRepository;
  18. use Customize\Repository\ProductNaireImageRepository;
  19. use Doctrine\Common\Collections\ArrayCollection;
  20. use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
  21. use Eccube\Common\Constant;
  22. use Eccube\Controller\AbstractController;
  23. use Eccube\Entity\BaseInfo;
  24. use Eccube\Entity\ExportCsvRow;
  25. use Eccube\Entity\Master\CsvType;
  26. use Eccube\Entity\Master\ProductStatus;
  27. use Eccube\Entity\Product;
  28. use Eccube\Entity\ProductCategory;
  29. use Eccube\Entity\ProductClass;
  30. use Eccube\Entity\ProductImage;
  31. use Eccube\Entity\ProductStock;
  32. use Eccube\Entity\ProductTag;
  33. use Eccube\Event\EccubeEvents;
  34. use Eccube\Event\EventArgs;
  35. use Eccube\Form\Type\Admin\ProductType;
  36. use Eccube\Form\Type\Admin\SearchProductType;
  37. use Eccube\Repository\BaseInfoRepository;
  38. use Eccube\Repository\CategoryRepository;
  39. use Eccube\Repository\Master\PageMaxRepository;
  40. use Eccube\Repository\Master\ProductStatusRepository;
  41. use Eccube\Repository\ProductClassRepository;
  42. use Eccube\Repository\ProductImageRepository;
  43. use Eccube\Repository\ProductRepository;
  44. use Eccube\Repository\TagRepository;
  45. use Eccube\Repository\TaxRuleRepository;
  46. use Eccube\Service\CsvExportService;
  47. use Eccube\Util\CacheUtil;
  48. use Eccube\Util\FormUtil;
  49. use Knp\Component\Pager\PaginatorInterface;
  50. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  51. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
  52. use Symfony\Component\Filesystem\Filesystem;
  53. use Symfony\Component\HttpFoundation\File\File;
  54. use Symfony\Component\HttpFoundation\RedirectResponse;
  55. use Symfony\Component\HttpFoundation\Request;
  56. use Symfony\Component\HttpFoundation\Response;
  57. use Symfony\Component\HttpFoundation\StreamedResponse;
  58. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  59. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  60. use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
  61. use Symfony\Component\Routing\Annotation\Route;
  62. use Symfony\Component\Routing\RouterInterface;
  63. class ProductController extends AbstractController
  64. {
  65.     /**
  66.      * @var CsvExportService
  67.      */
  68.     protected $csvExportService;
  69.     /**
  70.      * @var ProductClassRepository
  71.      */
  72.     protected $productClassRepository;
  73.     /**
  74.      * @var ProductImageRepository
  75.      */
  76.     protected $productImageRepository;
  77.     /**
  78.      * @var TaxRuleRepository
  79.      */
  80.     protected $taxRuleRepository;
  81.     /**
  82.      * @var CategoryRepository
  83.      */
  84.     protected $categoryRepository;
  85.     /**
  86.      * @var ProductRepository
  87.      */
  88.     protected $productRepository;
  89.     /**
  90.      * @var BaseInfo
  91.      */
  92.     protected $BaseInfo;
  93.     /**
  94.      * @var PageMaxRepository
  95.      */
  96.     protected $pageMaxRepository;
  97.     /**
  98.      * @var ProductStatusRepository
  99.      */
  100.     protected $productStatusRepository;
  101.     /**
  102.      * @var TagRepository
  103.      */
  104.     protected $tagRepository;
  105.     /**
  106.      * @var ProductGalleryRepository
  107.      */
  108.     protected $productGalleryRepository;
  109.     /**
  110.      * @var ProductDetailImageRepository
  111.      */
  112.     protected $productDetailImageRepository;
  113.     /**
  114.      * @var ProductNaireImageRepository
  115.      */
  116.     protected $productNaireImageRepository;
  117.     /**
  118.      * ProductController constructor.
  119.      */
  120.     public function __construct(
  121.         CsvExportService $csvExportService,
  122.         ProductClassRepository $productClassRepository,
  123.         ProductImageRepository $productImageRepository,
  124.         TaxRuleRepository $taxRuleRepository,
  125.         CategoryRepository $categoryRepository,
  126.         ProductRepository $productRepository,
  127.         BaseInfoRepository $baseInfoRepository,
  128.         PageMaxRepository $pageMaxRepository,
  129.         ProductStatusRepository $productStatusRepository,
  130.         TagRepository $tagRepository,
  131.         ProductGalleryRepository $productGalleryRepository,
  132.         ProductDetailImageRepository $productDetailImageRepository,
  133.         ProductNaireImageRepository $productNaireImageRepository
  134.     ) {
  135.         $this->csvExportService $csvExportService;
  136.         $this->productClassRepository $productClassRepository;
  137.         $this->productImageRepository $productImageRepository;
  138.         $this->taxRuleRepository $taxRuleRepository;
  139.         $this->categoryRepository $categoryRepository;
  140.         $this->productRepository $productRepository;
  141.         $this->BaseInfo $baseInfoRepository->get();
  142.         $this->pageMaxRepository $pageMaxRepository;
  143.         $this->productStatusRepository $productStatusRepository;
  144.         $this->tagRepository $tagRepository;
  145.         $this->productGalleryRepository $productGalleryRepository;
  146.         $this->productDetailImageRepository $productDetailImageRepository;
  147.         $this->productNaireImageRepository $productNaireImageRepository;
  148.     }
  149.     /**
  150.      * @Route("/%eccube_admin_route%/product", name="admin_product", methods={"GET", "POST"})
  151.      * @Route("/%eccube_admin_route%/product/page/{page_no}", requirements={"page_no" = "\d+"}, name="admin_product_page", methods={"GET", "POST"})
  152.      * @Template("@admin/Product/index.twig")
  153.      */
  154.     public function index(Request $requestPaginatorInterface $paginator$page_no null)
  155.     {
  156.         $builder $this->formFactory
  157.             ->createBuilder(SearchProductType::class);
  158.         $event = new EventArgs(
  159.             [
  160.                 'builder' => $builder,
  161.             ],
  162.             $request
  163.         );
  164.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_PRODUCT_INDEX_INITIALIZE);
  165.         $searchForm $builder->getForm();
  166.         /**
  167.          * ページの表示件数は, 以下の順に優先される.
  168.          * - リクエストパラメータ
  169.          * - セッション
  170.          * - デフォルト値
  171.          * また, セッションに保存する際は mtb_page_maxと照合し, 一致した場合のみ保存する.
  172.          **/
  173.         $page_count $this->session->get('eccube.admin.product.search.page_count',
  174.             $this->eccubeConfig->get('eccube_default_page_count'));
  175.         $page_count_param = (int) $request->get('page_count');
  176.         $pageMaxis $this->pageMaxRepository->findAll();
  177.         if ($page_count_param) {
  178.             foreach ($pageMaxis as $pageMax) {
  179.                 if ($page_count_param == $pageMax->getName()) {
  180.                     $page_count $pageMax->getName();
  181.                     $this->session->set('eccube.admin.product.search.page_count'$page_count);
  182.                     break;
  183.                 }
  184.             }
  185.         }
  186.         if ('POST' === $request->getMethod()) {
  187.             $searchForm->handleRequest($request);
  188.             if ($searchForm->isValid()) {
  189.                 /**
  190.                  * 検索が実行された場合は, セッションに検索条件を保存する.
  191.                  * ページ番号は最初のページ番号に初期化する.
  192.                  */
  193.                 $page_no 1;
  194.                 $searchData $searchForm->getData();
  195.                 // 検索条件, ページ番号をセッションに保持.
  196.                 $this->session->set('eccube.admin.product.search'FormUtil::getViewData($searchForm));
  197.                 $this->session->set('eccube.admin.product.search.page_no'$page_no);
  198.             } else {
  199.                 // 検索エラーの際は, 詳細検索枠を開いてエラー表示する.
  200.                 return [
  201.                     'searchForm' => $searchForm->createView(),
  202.                     'pagination' => [],
  203.                     'pageMaxis' => $pageMaxis,
  204.                     'page_no' => $page_no,
  205.                     'page_count' => $page_count,
  206.                     'has_errors' => true,
  207.                 ];
  208.             }
  209.         } else {
  210.             if (null !== $page_no || $request->get('resume')) {
  211.                 /*
  212.                  * ページ送りの場合または、他画面から戻ってきた場合は, セッションから検索条件を復旧する.
  213.                  */
  214.                 if ($page_no) {
  215.                     // ページ送りで遷移した場合.
  216.                     $this->session->set('eccube.admin.product.search.page_no', (int) $page_no);
  217.                 } else {
  218.                     // 他画面から遷移した場合.
  219.                     $page_no $this->session->get('eccube.admin.product.search.page_no'1);
  220.                 }
  221.                 $viewData $this->session->get('eccube.admin.product.search', []);
  222.                 $searchData FormUtil::submitAndGetData($searchForm$viewData);
  223.             } else {
  224.                 /**
  225.                  * 初期表示の場合.
  226.                  */
  227.                 $page_no 1;
  228.                 // submit default value
  229.                 $viewData FormUtil::getViewData($searchForm);
  230.                 $searchData FormUtil::submitAndGetData($searchForm$viewData);
  231.                 // セッション中の検索条件, ページ番号を初期化.
  232.                 $this->session->set('eccube.admin.product.search'$viewData);
  233.                 $this->session->set('eccube.admin.product.search.page_no'$page_no);
  234.             }
  235.         }
  236.         $qb $this->productRepository->getQueryBuilderBySearchDataForAdmin($searchData);
  237.         $event = new EventArgs(
  238.             [
  239.                 'qb' => $qb,
  240.                 'searchData' => $searchData,
  241.             ],
  242.             $request
  243.         );
  244.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_PRODUCT_INDEX_SEARCH);
  245.         $sortKey $searchData['sortkey'];
  246.         if (empty($this->productRepository::COLUMNS[$sortKey]) || $sortKey == 'code' || $sortKey == 'status') {
  247.             $pagination $paginator->paginate(
  248.                 $qb,
  249.                 $page_no,
  250.                 $page_count
  251.             );
  252.         } else {
  253.             $pagination $paginator->paginate(
  254.                 $qb,
  255.                 $page_no,
  256.                 $page_count,
  257.                 ['wrap-queries' => true]
  258.             );
  259.         }
  260.         return [
  261.             'searchForm' => $searchForm->createView(),
  262.             'pagination' => $pagination,
  263.             'pageMaxis' => $pageMaxis,
  264.             'page_no' => $page_no,
  265.             'page_count' => $page_count,
  266.             'has_errors' => false,
  267.         ];
  268.     }
  269.     /**
  270.      * @Route("/%eccube_admin_route%/product/classes/{id}/load", name="admin_product_classes_load", methods={"GET"}, requirements={"id" = "\d+"}, methods={"GET"})
  271.      * @Template("@admin/Product/product_class_popup.twig")
  272.      * @ParamConverter("Product", options={"repository_method":"findWithSortedClassCategories"})
  273.      */
  274.     public function loadProductClasses(Request $requestProduct $Product)
  275.     {
  276.         if (!$request->isXmlHttpRequest() && $this->isTokenValid()) {
  277.             throw new BadRequestHttpException();
  278.         }
  279.         $data = [];
  280.         /** @var $Product ProductRepository */
  281.         if (!$Product) {
  282.             throw new NotFoundHttpException();
  283.         }
  284.         if ($Product->hasProductClass()) {
  285.             $class $Product->getProductClasses();
  286.             foreach ($class as $item) {
  287.                 $data[] = $item;
  288.             }
  289.         }
  290.         return [
  291.             'data' => $data,
  292.         ];
  293.     }
  294.     /**
  295.      * 画像アップロード時にリクエストされるメソッド.
  296.      *
  297.      * @see https://pqina.nl/filepond/docs/api/server/#process
  298.      * @Route("/%eccube_admin_route%/product/product/image/process", name="admin_product_image_process", methods={"POST"})
  299.      * @Route("/%eccube_admin_route%/product/product/image/process/{key_name}", name="admin_product_image_process_key", defaults={"entity_name": null, "key_name": null}, methods={"POST"})
  300.      * @Route("/%eccube_admin_route%/product/image/process/{entity_name}/{key_name}", name="admin_product_image_process_entity_key", methods={"POST"})
  301.      */
  302.     public function imageProcess(Request $request$entity_name null$key_name null)
  303.     {
  304.         if (!$request->isXmlHttpRequest() && $this->isTokenValid()) {
  305.             throw new BadRequestHttpException();
  306.         }
  307.         $images $request->files->get('admin_product');
  308.         $allowExtensions = ['gif''jpg''jpeg''png'];
  309.         $files = [];
  310.         if ($key_name) {
  311.             if ($entity_name) {
  312.                 if (isset($images[$entity_name])) {
  313.                     foreach ($images[$entity_name] as $key => $img) {
  314.                         $image $img[$key_name];
  315.                         // ファイルフォーマット検証
  316.                         $mimeType $image->getMimeType();
  317.                         if (!== strpos($mimeType'image')) {
  318.                             throw new UnsupportedMediaTypeHttpException();
  319.                         }
  320.                         // 拡張子
  321.                         $extension $image->getClientOriginalExtension();
  322.                         if (!in_array(strtolower($extension), $allowExtensions)) {
  323.                             throw new UnsupportedMediaTypeHttpException();
  324.                         }
  325.                         $filename date('mdHis').uniqid('_').'.'.$extension;
  326.                         $image->move($this->getParameter('eccube_temp_image_dir'), $filename);
  327.                         $files[] = $filename;
  328.                     }
  329.                 }
  330.             } else {
  331.                 if (isset($images[$key_name])) {
  332.                     $image $images[$key_name];
  333.                     // ファイルフォーマット検証
  334.                     $mimeType $image->getMimeType();
  335.                     if (!== strpos($mimeType'image')) {
  336.                         throw new UnsupportedMediaTypeHttpException();
  337.                     }
  338.                     // 拡張子
  339.                     $extension $image->getClientOriginalExtension();
  340.                     if (!in_array(strtolower($extension), $allowExtensions)) {
  341.                         throw new UnsupportedMediaTypeHttpException();
  342.                     }
  343.                     $filename date('mdHis').uniqid('_').'.'.$extension;
  344.                     $image->move($this->getParameter('eccube_temp_image_dir'), $filename);
  345.                     $files[] = $filename;
  346.                 }
  347.             }
  348.         } else {
  349.             if (count($images) > 0) {
  350.                 foreach ($images as $img) {
  351.                     foreach ($img as $image) {
  352.                         // ファイルフォーマット検証
  353.                         $mimeType $image->getMimeType();
  354.                         if (!== strpos($mimeType'image')) {
  355.                             throw new UnsupportedMediaTypeHttpException();
  356.                         }
  357.                         // 拡張子
  358.                         $extension $image->getClientOriginalExtension();
  359.                         if (!in_array(strtolower($extension), $allowExtensions)) {
  360.                             throw new UnsupportedMediaTypeHttpException();
  361.                         }
  362.                         $filename date('mdHis').uniqid('_').'.'.$extension;
  363.                         $image->move($this->eccubeConfig['eccube_temp_image_dir'], $filename);
  364.                         $files[] = $filename;
  365.                     }
  366.                 }
  367.             }
  368.         }
  369.         $event = new EventArgs(
  370.             [
  371.                 'images' => $images,
  372.                 'files' => $files,
  373.             ],
  374.             $request
  375.         );
  376.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_PRODUCT_ADD_IMAGE_COMPLETE);
  377.         $files $event->getArgument('files');
  378.         return new Response(array_shift($files));
  379.     }
  380.     /**
  381.      * アップロード画像を取得する際にコールされるメソッド.
  382.      *
  383.      * @see https://pqina.nl/filepond/docs/api/server/#load
  384.      * @Route("/%eccube_admin_route%/product/product/image/load", name="admin_product_image_load", methods={"GET"})
  385.      */
  386.     public function imageLoad(Request $request)
  387.     {
  388.         if (!$request->isXmlHttpRequest()) {
  389.             throw new BadRequestHttpException();
  390.         }
  391.         $dirs = [
  392.             $this->eccubeConfig['eccube_save_image_dir'],
  393.             $this->eccubeConfig['eccube_temp_image_dir'],
  394.         ];
  395.         foreach ($dirs as $dir) {
  396.             if (strpos($request->query->get('source'), '..') !== false) {
  397.                 throw new NotFoundHttpException();
  398.             }
  399.             $image \realpath($dir.'/'.$request->query->get('source'));
  400.             $dir \realpath($dir);
  401.             if (\is_file($image) && \str_starts_with($image$dir)) {
  402.                 $file = new \SplFileObject($image);
  403.                 return $this->file($file$file->getBasename());
  404.             }
  405.         }
  406.         throw new NotFoundHttpException();
  407.     }
  408.     /**
  409.      * アップロード画像をすぐ削除する際にコールされるメソッド.
  410.      *
  411.      * @see https://pqina.nl/filepond/docs/api/server/#revert
  412.      * @Route("/%eccube_admin_route%/product/product/image/revert", name="admin_product_image_revert", methods={"DELETE"})
  413.      */
  414.     public function imageRevert(Request $request)
  415.     {
  416.         if (!$request->isXmlHttpRequest() && $this->isTokenValid()) {
  417.             throw new BadRequestHttpException();
  418.         }
  419.         $tempFile $this->eccubeConfig['eccube_temp_image_dir'].'/'.$request->getContent();
  420.         if (is_file($tempFile) && stripos(realpath($tempFile), $this->eccubeConfig['eccube_temp_image_dir']) === 0) {
  421.             $fs = new Filesystem();
  422.             $fs->remove($tempFile);
  423.             return new Response(nullResponse::HTTP_NO_CONTENT);
  424.         }
  425.         throw new NotFoundHttpException();
  426.     }
  427.     /**
  428.      * @Route("/%eccube_admin_route%/product/product/new", name="admin_product_product_new", methods={"GET", "POST"})
  429.      * @Route("/%eccube_admin_route%/product/product/{id}/edit", requirements={"id" = "\d+"}, name="admin_product_product_edit", methods={"GET", "POST"})
  430.      * @Template("@admin/Product/product.twig")
  431.      */
  432.     public function edit(Request $requestRouterInterface $routerCacheUtil $cacheUtil$id null)
  433.     {
  434.         $has_class false;
  435.         /** @var Product|null */
  436.         $Product null;
  437.         if (is_null($id)) {
  438.             $Product = new Product();
  439.             $ProductClass = new ProductClass();
  440.             $ProductStatus $this->productStatusRepository->find(ProductStatus::DISPLAY_HIDE);
  441.             $Product
  442.                 ->addProductClass($ProductClass)
  443.                 ->setStatus($ProductStatus);
  444.             $ProductClass
  445.                 ->setVisible(true)
  446.                 ->setStockUnlimited(true)
  447.                 ->setProduct($Product);
  448.             $ProductStock = new ProductStock();
  449.             $ProductClass->setProductStock($ProductStock);
  450.             $ProductStock->setProductClass($ProductClass);
  451.         } else {
  452.             $Product $this->productRepository->findWithSortedClassCategories($id);
  453.             $ProductClass null;
  454.             $ProductStock null;
  455.             if (!$Product) {
  456.                 throw new NotFoundHttpException();
  457.             }
  458.             // 規格無しの商品の場合は、デフォルト規格を表示用に取得する
  459.             $has_class $Product->hasProductClass();
  460.             if (!$has_class) {
  461.                 $ProductClasses $Product->getProductClasses();
  462.                 foreach ($ProductClasses as $pc) {
  463.                     if (!is_null($pc->getClassCategory1())) {
  464.                         continue;
  465.                     }
  466.                     if ($pc->isVisible()) {
  467.                         $ProductClass $pc;
  468.                         break;
  469.                     }
  470.                 }
  471.                 if ($this->BaseInfo->isOptionProductTaxRule() && $ProductClass->getTaxRule()) {
  472.                     $ProductClass->setTaxRate($ProductClass->getTaxRule()->getTaxRate());
  473.                 }
  474.                 $ProductStock $ProductClass->getProductStock();
  475.             }
  476.         }
  477.         // 編集前の特徴情報を保持
  478.         $OriginProductFeatures = new ArrayCollection();
  479.         foreach ($Product->getProductFeatures() as $ProductFeature) {
  480.             $OriginProductFeatures->add($ProductFeature);
  481.         }
  482.         // 編集前のよくある質問の情報を保持
  483.         $OriginProductFaqs = new ArrayCollection();
  484.         foreach ($Product->getProductFaqs() as $ProductFaq) {
  485.             $OriginProductFaqs->add($ProductFaq);
  486.         }
  487.         // 編集前の関連特集の情報を保持
  488.         $OriginProductRelateFeatures = new ArrayCollection();
  489.         foreach ($Product->getProductRelateFeatures() as $ProductRelateFeature) {
  490.             $OriginProductRelateFeatures->add($ProductRelateFeature);
  491.         }
  492.         // 編集前のムービーアンドギャラリーの動画URLの情報を保持
  493.         $OriginProductMovies = new ArrayCollection();
  494.         foreach ($Product->getProductMovies() as $ProductMovie) {
  495.             $OriginProductMovies->add($ProductMovie);
  496.         }
  497.          // 編集前の商品詳細のスペック項目とスペック詳細の情報を保持
  498.          $OriginProductSpecs = new ArrayCollection();
  499.          foreach ($Product->getProductSpecs() as $ProductSpec) {
  500.              $OriginProductSpecs->add($ProductSpec);
  501.          }
  502.          // 編集前のハッシュタグの情報を保持
  503.          $OriginProductHashTags = new ArrayCollection();
  504.          foreach ($Product->getProductHashTags() as $ProductHashTag) {
  505.              $OriginProductHashTags->add($ProductHashTag);
  506.          }
  507.         // 編集前のムービー&ギャラリーの情報を保持
  508.         $OriginProductGalleries = new ArrayCollection();
  509.         foreach ($Product->getProductGalleries() as $ProductGallery) {
  510.             $OriginProductGalleries->add($ProductGallery);
  511.         }
  512.         // 編集前の商品仕様(詳細)の画像情報を保持
  513.         $OriginProductDetailImages = new ArrayCollection();
  514.         foreach ($Product->getProductDetailImages() as $ProductDetailImage) {
  515.             $OriginProductDetailImages->add($ProductDetailImage);
  516.         }
  517.         // 編集前の商品仕様(名入れ)の画像情報を保持
  518.         $OriginProductNaireImages = new ArrayCollection();
  519.         foreach ($Product->getProductNaireImages() as $ProductNaireImage) {
  520.             $OriginProductNaireImages->add($ProductNaireImage);
  521.         }
  522.         $builder $this->formFactory
  523.             ->createBuilder(ProductType::class, $Product);
  524.         // 規格あり商品の場合、規格関連情報をFormから除外
  525.         if ($has_class) {
  526.             $builder->remove('class');
  527.         }
  528.         $event = new EventArgs(
  529.             [
  530.                 'builder' => $builder,
  531.                 'Product' => $Product,
  532.             ],
  533.             $request
  534.         );
  535.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_PRODUCT_EDIT_INITIALIZE);
  536.         $form $builder->getForm();
  537.         if (!$has_class) {
  538.             $ProductClass->setStockUnlimited($ProductClass->isStockUnlimited());
  539.             $form['class']->setData($ProductClass);
  540.         }
  541.         // ファイルの登録
  542.         $images = [];
  543.         $ProductImages $Product->getProductImage();
  544.         foreach ($ProductImages as $ProductImage) {
  545.             $images[] = $ProductImage->getFileName();
  546.         }
  547.         $form['images']->setData($images);
  548.         $gallery_images = [];
  549.         $ProductGalleries $Product->getProductGalleries();
  550.         foreach ($ProductGalleries as $ProductGallery) {
  551.             $gallery_images[] = $ProductGallery->getFileName();
  552.         }
  553.         $form['gallery_images']->setData($gallery_images);
  554.         $detail_images = [];
  555.         $ProductDetailImages $Product->getProductDetailImages();
  556.         foreach ($ProductDetailImages as $ProductDetailImage) {
  557.             $detail_images[] = $ProductDetailImage->getFileName();
  558.         }
  559.         $form['detail_images']->setData($detail_images);
  560.         $naire_images = [];
  561.         $ProductNaireImages $Product->getProductNaireImages();
  562.         foreach ($ProductNaireImages as $ProductNaireImage) {
  563.             $naire_images[] = $ProductNaireImage->getFileName();
  564.         }
  565.         $form['naire_images']->setData($naire_images);
  566.         $categories = [];
  567.         $ProductCategories $Product->getProductCategories();
  568.         foreach ($ProductCategories as $ProductCategory) {
  569.             /* @var $ProductCategory \Eccube\Entity\ProductCategory */
  570.             $categories[] = $ProductCategory->getCategory();
  571.         }
  572.         $form['Category']->setData($categories);
  573.         $Tags $Product->getTags();
  574.         $form['Tag']->setData($Tags);
  575.         if ('POST' === $request->getMethod()) {
  576.             $form->handleRequest($request);
  577.             if ($form->isValid()) {
  578.                 log_info('商品登録開始', [$id]);
  579.                 $Product $form->getData();
  580.                 if (!$has_class) {
  581.                     $ProductClass $form['class']->getData();
  582.                     // 個別消費税
  583.                     if ($this->BaseInfo->isOptionProductTaxRule()) {
  584.                         if ($ProductClass->getTaxRate() !== null) {
  585.                             if ($ProductClass->getTaxRule()) {
  586.                                 $ProductClass->getTaxRule()->setTaxRate($ProductClass->getTaxRate());
  587.                             } else {
  588.                                 $taxrule $this->taxRuleRepository->newTaxRule();
  589.                                 $taxrule->setTaxRate($ProductClass->getTaxRate());
  590.                                 $taxrule->setApplyDate(new \DateTime());
  591.                                 $taxrule->setProduct($Product);
  592.                                 $taxrule->setProductClass($ProductClass);
  593.                                 $ProductClass->setTaxRule($taxrule);
  594.                             }
  595.                             $ProductClass->getTaxRule()->setTaxRate($ProductClass->getTaxRate());
  596.                         } else {
  597.                             if ($ProductClass->getTaxRule()) {
  598.                                 $this->taxRuleRepository->delete($ProductClass->getTaxRule());
  599.                                 $ProductClass->setTaxRule(null);
  600.                             }
  601.                         }
  602.                     }
  603.                     $this->entityManager->persist($ProductClass);
  604.                     // 在庫情報を作成
  605.                     if (!$ProductClass->isStockUnlimited()) {
  606.                         $ProductStock->setStock($ProductClass->getStock());
  607.                     } else {
  608.                         // 在庫無制限時はnullを設定
  609.                         $ProductStock->setStock(null);
  610.                     }
  611.                     $this->entityManager->persist($ProductStock);
  612.                 }
  613.                 // カテゴリの登録
  614.                 // 一度クリア
  615.                 /* @var $Product \Eccube\Entity\Product */
  616.                 foreach ($Product->getProductCategories() as $ProductCategory) {
  617.                     $Product->removeProductCategory($ProductCategory);
  618.                     $this->entityManager->remove($ProductCategory);
  619.                 }
  620.                 $this->entityManager->persist($Product);
  621.                 $this->entityManager->flush();
  622.                 $count 1;
  623.                 $Categories $form->get('Category')->getData();
  624.                 $categoriesIdList = [];
  625.                 foreach ($Categories as $Category) {
  626.                     foreach ($Category->getPath() as $ParentCategory) {
  627.                         if (!isset($categoriesIdList[$ParentCategory->getId()])) {
  628.                             $ProductCategory $this->createProductCategory($Product$ParentCategory$count);
  629.                             $this->entityManager->persist($ProductCategory);
  630.                             $count++;
  631.                             /* @var $Product \Eccube\Entity\Product */
  632.                             $Product->addProductCategory($ProductCategory);
  633.                             $categoriesIdList[$ParentCategory->getId()] = true;
  634.                         }
  635.                     }
  636.                     if (!isset($categoriesIdList[$Category->getId()])) {
  637.                         $ProductCategory $this->createProductCategory($Product$Category$count);
  638.                         $this->entityManager->persist($ProductCategory);
  639.                         $count++;
  640.                         /* @var $Product \Eccube\Entity\Product */
  641.                         $Product->addProductCategory($ProductCategory);
  642.                         $categoriesIdList[$Category->getId()] = true;
  643.                     }
  644.                 }
  645.                 // 画像の登録
  646.                 $add_images $form->get('add_images')->getData();
  647.                 foreach ($add_images as $add_image) {
  648.                     $ProductImage = new \Eccube\Entity\ProductImage();
  649.                     $ProductImage
  650.                         ->setFileName($add_image)
  651.                         ->setProduct($Product)
  652.                         ->setSortNo(1);
  653.                     $Product->addProductImage($ProductImage);
  654.                     $this->entityManager->persist($ProductImage);
  655.                     // 移動
  656.                     $file = new File($this->eccubeConfig['eccube_temp_image_dir'].'/'.$add_image);
  657.                     $file->move($this->eccubeConfig['eccube_save_image_dir']);
  658.                 }
  659.                 // 画像の削除
  660.                 $delete_images $form->get('delete_images')->getData();
  661.                 $fs = new Filesystem();
  662.                 foreach ($delete_images as $delete_image) {
  663.                     $ProductImage $this->productImageRepository->findOneBy([
  664.                         'Product' => $Product,
  665.                         'file_name' => $delete_image,
  666.                     ]);
  667.                     if ($ProductImage instanceof ProductImage) {
  668.                         $Product->removeProductImage($ProductImage);
  669.                         $this->entityManager->remove($ProductImage);
  670.                         $this->entityManager->flush();
  671.                         // 他に同じ画像を参照する商品がなければ画像ファイルを削除
  672.                         if (!$this->productImageRepository->findOneBy(['file_name' => $delete_image])) {
  673.                             $fs->remove($this->eccubeConfig['eccube_save_image_dir'].'/'.$delete_image);
  674.                         }
  675.                     } else {
  676.                         // 追加してすぐに削除した画像は、Entityに追加されない
  677.                         $fs->remove($this->eccubeConfig['eccube_temp_image_dir'].'/'.$delete_image);
  678.                     }
  679.                 }
  680.                 $this->entityManager->flush();
  681.                 if (array_key_exists('product_image'$request->request->get('admin_product'))) {
  682.                     $product_image $request->request->get('admin_product')['product_image'];
  683.                     foreach ($product_image as $sortNo => $filename) {
  684.                         $ProductImage $this->productImageRepository
  685.                             ->findOneBy([
  686.                                 'file_name' => pathinfo($filenamePATHINFO_BASENAME),
  687.                                 'Product' => $Product,
  688.                             ]);
  689.                         if ($ProductImage !== null) {
  690.                             $ProductImage->setSortNo($sortNo);
  691.                             $this->entityManager->persist($ProductImage);
  692.                         }
  693.                     }
  694.                     $this->entityManager->flush();
  695.                 }
  696.                 // コンセプト画像アップロード
  697.                 $concept_files = [
  698.                     $form->get('concept_file_name_pc')->getData(),
  699.                     $form->get('concept_file_name_sp')->getData(),
  700.                 ];
  701.                 foreach ($concept_files as $concept_file) {
  702.                     if (!$concept_file) {
  703.                         continue;
  704.                     }
  705.                     $fs = new Filesystem();
  706.                     if ($concept_files && strpos($concept_file'..') === false && $fs->exists($this->getParameter('eccube_temp_image_dir').'/'.$concept_file)) {
  707.                         $fs->rename(
  708.                             $this->getParameter('eccube_temp_image_dir').'/'.$concept_file,
  709.                             $this->getParameter('eccube_save_image_dir').'/'.$concept_file
  710.                         );
  711.                     }
  712.                 }
  713.                 // 特徴画像アップロード
  714.                 $ProductFeatures $form->get('ProductFeatures')->getData();
  715.                 foreach ($ProductFeatures as $ProductFeature) {
  716.                     $fs = new Filesystem();
  717.                     if ($ProductFeature->getFileName() && strpos($ProductFeature->getFileName(), '..') === false && $fs->exists($this->getParameter('eccube_temp_image_dir').'/'.$ProductFeature->getFileName())) {
  718.                         $fs->rename(
  719.                             $this->getParameter('eccube_temp_image_dir').'/'.$ProductFeature->getFileName(),
  720.                             $this->getParameter('eccube_save_image_dir').'/'.$ProductFeature->getFileName()
  721.                         );
  722.                     }
  723.                 }
  724.                 // 商品タグの登録
  725.                 // 商品タグを一度クリア
  726.                 $ProductTags $Product->getProductTag();
  727.                 foreach ($ProductTags as $ProductTag) {
  728.                     $Product->removeProductTag($ProductTag);
  729.                     $this->entityManager->remove($ProductTag);
  730.                 }
  731.                 // 商品タグの登録
  732.                 $Tags $form->get('Tag')->getData();
  733.                 foreach ($Tags as $Tag) {
  734.                     $ProductTag = new ProductTag();
  735.                     $ProductTag
  736.                         ->setProduct($Product)
  737.                         ->setTag($Tag);
  738.                     $Product->addProductTag($ProductTag);
  739.                     $this->entityManager->persist($ProductTag);
  740.                 }
  741.                 $this->entityManager->flush();
  742.                 // 特徴の登録
  743.                 $ProductFeatures $form->get('ProductFeatures')->getData();
  744.                 foreach ($ProductFeatures as $ProductFeature) {
  745.                     $ProductFeature->setProduct($Product);
  746.                     $Product->addProductFeature($ProductFeature);
  747.                     $this->entityManager->persist($ProductFeature);
  748.                 }
  749.                 foreach ($OriginProductFeatures as $ProductFeature) {
  750.                     if (!$ProductFeatures->contains($ProductFeature)) {
  751.                         $Product->removeProductFeature($ProductFeature);
  752.                         $this->entityManager->remove($ProductFeature);
  753.                     }
  754.                 }
  755.                 // よくあるご質問の登録
  756.                 $ProductFaqs $form->get('ProductFaqs')->getData();
  757.                 foreach ($ProductFaqs as $ProductFaq) {
  758.                     $ProductFaq->setProduct($Product);
  759.                     $Product->addProductFaq($ProductFaq);
  760.                     $this->entityManager->persist($ProductFaq);
  761.                 }
  762.                 foreach ($OriginProductFaqs as $ProductFaq) {
  763.                     if (!$ProductFaqs->contains($ProductFaq)) {
  764.                         $Product->removeProductFaq($ProductFaq);
  765.                         $this->entityManager->remove($ProductFaq);
  766.                     }
  767.                 }
  768.                 // 関連特集の登録
  769.                 $ProductRelateFeatures $form->get('ProductRelateFeatures')->getData();
  770.                 foreach ($ProductRelateFeatures as $ProductRelateFeature) {
  771.                     $ProductRelateFeature->setProduct($Product);
  772.                     $Product->addProductRelateFeature($ProductRelateFeature);
  773.                     $this->entityManager->persist($ProductRelateFeature);
  774.                 }
  775.                 foreach ($OriginProductRelateFeatures as $ProductRelateFeature) {
  776.                     if (!$ProductRelateFeatures->contains($ProductRelateFeature)) {
  777.                         $Product->removeProductRelateFeature($ProductRelateFeature);
  778.                         $this->entityManager->remove($ProductRelateFeature);
  779.                     }
  780.                 }
  781.                 // ムービーアンドギャラリーの画像の登録
  782.                 $gallery_add_images $form->get('gallery_add_images')->getData();
  783.                 foreach ($gallery_add_images as $gallery_add_image) {
  784.                     $ProductGallery = new ProductGallery();
  785.                     $ProductGallery
  786.                         ->setFileName($gallery_add_image)
  787.                         ->setProduct($Product)
  788.                         ->setSortNo(1);
  789.                     $Product->addProductGallery($ProductGallery);
  790.                     $this->entityManager->persist($ProductGallery);
  791.                     // 移動
  792.                     $file = new File($this->eccubeConfig['eccube_temp_image_dir'].'/'.$gallery_add_image);
  793.                     $file->move($this->eccubeConfig['eccube_save_image_dir']);
  794.                 }
  795.                 // ムービーアンドギャラリーの画像の削除
  796.                 $gallery_delete_images $form->get('gallery_delete_images')->getData();
  797.                 foreach ($gallery_delete_images as $gallery_delete_image) {
  798.                     $ProductGallery $this->productGalleryRepository->findOneBy([
  799.                         'Product' => $Product,
  800.                         'file_name' => $gallery_delete_image,
  801.                     ]);
  802.                     if ($ProductGallery instanceof ProductGallery) {
  803.                         $Product->removeProductGallery($ProductGallery);
  804.                         $this->entityManager->remove($ProductGallery);
  805.                         $this->entityManager->flush();
  806.                         // 他に同じ画像を参照する商品がなければ画像ファイルを削除
  807.                         if (!$this->productGalleryRepository->findOneBy(['file_name' => $gallery_delete_image])) {
  808.                             $fs->remove($this->eccubeConfig['eccube_save_image_dir'].'/'.$gallery_delete_image);
  809.                         }
  810.                     } else {
  811.                         // 追加してすぐに削除した画像は、Entityに追加されない
  812.                         $fs->remove($this->eccubeConfig['eccube_temp_image_dir'].'/'.$gallery_delete_image);
  813.                     }
  814.                 }
  815.                 $this->entityManager->flush();
  816.                 if (array_key_exists('product_gallery'$request->request->get('admin_product'))) {
  817.                     $product_gallery $request->request->get('admin_product')['product_gallery'];
  818.                     foreach ($product_gallery as $sortNo => $filename) {
  819.                         $ProductGallery $this->productGalleryRepository
  820.                             ->findOneBy([
  821.                                 'file_name' => pathinfo($filenamePATHINFO_BASENAME),
  822.                                 'Product' => $Product,
  823.                             ]);
  824.                         if ($ProductGallery !== null) {
  825.                             $ProductGallery->setSortNo($sortNo);
  826.                             $this->entityManager->persist($ProductGallery);
  827.                         }
  828.                     }
  829.                     $this->entityManager->flush();
  830.                 }
  831.                 // ムービーアンドギャラリーの動画URLの登録
  832.                 $ProductMovies $form->get('ProductMovies')->getData();
  833.                 foreach ($ProductMovies as $ProductMovie) {
  834.                     $ProductMovie->setProduct($Product);
  835.                     $Product->addProductMovie($ProductMovie);
  836.                     $this->entityManager->persist($ProductMovie);
  837.                 }
  838.                 foreach ($OriginProductMovies as $ProductMovie) {
  839.                     if (!$ProductMovies->contains($ProductMovie)) {
  840.                         $Product->removeProductMovie($ProductMovie);
  841.                         $this->entityManager->remove($ProductMovie);
  842.                     }
  843.                 }
  844.                 // ムービー&ギャラリーの画像alt_textの登録
  845.                 $ProductGalleries $form->get('ProductGalleries')->getData();
  846.                 foreach ($ProductGalleries as $ProductGallery) {
  847.                     $ProductGallery->setProduct($Product);
  848.                     $Product->addProductGallery($ProductGallery);
  849.                     $this->entityManager->persist($ProductGallery);
  850.                 }
  851.                 foreach ($OriginProductGalleries as $ProductGallery) {
  852.                     if (!$ProductGalleries->contains($ProductGallery)) {
  853.                         $Product->removeProductGallery($ProductGallery);
  854.                         $this->entityManager->remove($ProductGallery);
  855.                     }
  856.                 }
  857.                 // 商品仕様詳細のスペック項目とスペック詳細の登録
  858.                 $ProductSpecs $form->get('ProductSpecs')->getData();
  859.                 foreach ($ProductSpecs as $ProductSpec) {
  860.                     $ProductSpec->setProduct($Product);
  861.                     $Product->addProductSpec($ProductSpec);
  862.                     $this->entityManager->persist($ProductSpec);
  863.                 }
  864.                 foreach ($OriginProductSpecs as $ProductSpec) {
  865.                     if (!$ProductSpecs->contains($ProductSpec)) {
  866.                         $Product->removeProductSpec($ProductSpec);
  867.                         $this->entityManager->remove($ProductSpec);
  868.                     }
  869.                 }
  870.                 // 商品仕様詳細の画像alt_textの登録
  871.                 $ProductDetailImages $form->get('ProductDetailImages')->getData();
  872.                 foreach ($ProductDetailImages as $ProductDetailImage) {
  873.                     $ProductDetailImage->setProduct($Product);
  874.                     $Product->addProductDetailImage($ProductDetailImage);
  875.                     $this->entityManager->persist($ProductDetailImage);
  876.                 }
  877.                 foreach ($OriginProductDetailImages as $ProductDetailImage) {
  878.                     if (!$ProductDetailImages->contains($ProductDetailImage)) {
  879.                         $Product->removeProductDetailImage($ProductDetailImage);
  880.                         $this->entityManager->remove($ProductDetailImage);
  881.                     }
  882.                 }
  883.                 // 商品仕様(詳細)の画像の登録
  884.                 $detail_add_images $form->get('detail_add_images')->getData();
  885.                 foreach ($detail_add_images as $detail_add_image) {
  886.                     $ProductDetailImage = new ProductDetailImage();
  887.                     $ProductDetailImage
  888.                         ->setFileName($detail_add_image)
  889.                         ->setProduct($Product)
  890.                         ->setSortNo(1);
  891.                     $Product->addProductDetailImage($ProductDetailImage);
  892.                     $this->entityManager->persist($ProductDetailImage);
  893.                     // 移動
  894.                     $file = new File($this->eccubeConfig['eccube_temp_image_dir'].'/'.$detail_add_image);
  895.                     $file->move($this->eccubeConfig['eccube_save_image_dir']);
  896.                 }
  897.                 // 商品仕様(詳細)の画像の削除
  898.                 $detail_delete_images $form->get('detail_delete_images')->getData();
  899.                 foreach ($detail_delete_images as $detail_delete_image) {
  900.                     $ProductDetailImage $this->productDetailImageRepository->findOneBy([
  901.                         'Product' => $Product,
  902.                         'file_name' => $detail_delete_image,
  903.                     ]);
  904.                     if ($ProductDetailImage instanceof ProductDetailImage) {
  905.                         $Product->removeProductDetailImage($ProductDetailImage);
  906.                         $this->entityManager->remove($ProductDetailImage);
  907.                         $this->entityManager->flush();
  908.                         // 他に同じ画像を参照する商品がなければ画像ファイルを削除
  909.                         if (!$this->productDetailImageRepository->findOneBy(['file_name' => $detail_delete_image])) {
  910.                             $fs->remove($this->eccubeConfig['eccube_save_image_dir'].'/'.$detail_delete_image);
  911.                         }
  912.                     } else {
  913.                         // 追加してすぐに削除した画像は、Entityに追加されない
  914.                         $fs->remove($this->eccubeConfig['eccube_temp_image_dir'].'/'.$detail_delete_image);
  915.                     }
  916.                 }
  917.                 $this->entityManager->flush();
  918.                 if (array_key_exists('product_detail_image'$request->request->get('admin_product'))) {
  919.                     $product_detail_image $request->request->get('admin_product')['product_detail_image'];
  920.                     foreach ($product_detail_image as $sortNo => $filename) {
  921.                         $ProductDetailImage $this->productDetailImageRepository
  922.                             ->findOneBy([
  923.                                 'file_name' => pathinfo($filenamePATHINFO_BASENAME),
  924.                                 'Product' => $Product,
  925.                             ]);
  926.                         if ($ProductDetailImage !== null) {
  927.                             $ProductDetailImage->setSortNo($sortNo);
  928.                             $this->entityManager->persist($ProductDetailImage);
  929.                         }
  930.                     }
  931.                     $this->entityManager->flush();
  932.                 }
  933.                 // 商品仕様(名入れ)の画像の登録
  934.                 $naire_add_images $form->get('naire_add_images')->getData();
  935.                 foreach ($naire_add_images as $naire_add_image) {
  936.                     $ProductNaireImage = new ProductNaireImage();
  937.                     $ProductNaireImage
  938.                         ->setFileName($naire_add_image)
  939.                         ->setProduct($Product)
  940.                         ->setSortNo(1);
  941.                     $Product->addProductNaireImage($ProductNaireImage);
  942.                     $this->entityManager->persist($ProductNaireImage);
  943.                     // 移動
  944.                     $file = new File($this->eccubeConfig['eccube_temp_image_dir'].'/'.$naire_add_image);
  945.                     $file->move($this->eccubeConfig['eccube_save_image_dir']);
  946.                 }
  947.                 // 商品仕様(名入れ)の画像の削除
  948.                 $naire_delete_images $form->get('naire_delete_images')->getData();
  949.                 foreach ($naire_delete_images as $naire_delete_image) {
  950.                     $ProductNaireImage $this->productNaireImageRepository->findOneBy([
  951.                         'Product' => $Product,
  952.                         'file_name' => $naire_delete_image,
  953.                     ]);
  954.                     if ($ProductNaireImage instanceof ProductNaireImage) {
  955.                         $Product->removeProductNaireImage($ProductNaireImage);
  956.                         $this->entityManager->remove($ProductNaireImage);
  957.                         $this->entityManager->flush();
  958.                         // 他に同じ画像を参照する商品がなければ画像ファイルを削除
  959.                         if (!$this->productNaireImageRepository->findOneBy(['file_name' => $naire_delete_image])) {
  960.                             $fs->remove($this->eccubeConfig['eccube_save_image_dir'].'/'.$naire_delete_image);
  961.                         }
  962.                     } else {
  963.                         // 追加してすぐに削除した画像は、Entityに追加されない
  964.                         $fs->remove($this->eccubeConfig['eccube_temp_image_dir'].'/'.$naire_delete_image);
  965.                     }
  966.                 }
  967.                 // 商品仕様(名入れ)の画像alt_textの登録
  968.                 $ProductNaireImages $form->get('ProductNaireImages')->getData();
  969.                 foreach ($ProductNaireImages as $ProductNaireImage) {
  970.                     $ProductNaireImage->setProduct($Product);
  971.                     $Product->addProductNaireImage($ProductNaireImage);
  972.                     $this->entityManager->persist($ProductNaireImage);
  973.                 }
  974.                 foreach ($OriginProductNaireImages as $ProductNaireImage) {
  975.                     if (!$ProductNaireImages->contains($ProductNaireImage)) {
  976.                         $Product->removeProductNaireImage($ProductNaireImage);
  977.                         $this->entityManager->remove($ProductNaireImage);
  978.                     }
  979.                 }
  980.                 $this->entityManager->flush();
  981.                 if (array_key_exists('product_naire_image'$request->request->get('admin_product'))) {
  982.                     $product_naire_image $request->request->get('admin_product')['product_naire_image'];
  983.                     foreach ($product_naire_image as $sortNo => $filename) {
  984.                         $ProductNaireImage $this->productNaireImageRepository
  985.                             ->findOneBy([
  986.                                 'file_name' => pathinfo($filenamePATHINFO_BASENAME),
  987.                                 'Product' => $Product,
  988.                             ]);
  989.                         if ($ProductNaireImage !== null) {
  990.                             $ProductNaireImage->setSortNo($sortNo);
  991.                             $this->entityManager->persist($ProductNaireImage);
  992.                         }
  993.                     }
  994.                     $this->entityManager->flush();
  995.                 }
  996.                 // 製作者紹介画像アップロード
  997.                 $producer_file $form->get('producer_file')->getData();
  998.                 if ($producer_file && strpos($producer_file'..') === false && $fs->exists($this->getParameter('eccube_temp_image_dir').'/'.$producer_file)) {
  999.                     $fs->rename(
  1000.                         $this->getParameter('eccube_temp_image_dir').'/'.$producer_file,
  1001.                         $this->getParameter('eccube_save_image_dir').'/'.$producer_file
  1002.                     );
  1003.                 }
  1004.                 // ハッシュタグの登録
  1005.                 $ProductHashTags $form->get('ProductHashTags')->getData();
  1006.                 foreach ($ProductHashTags as $ProductHashTag) {
  1007.                     $ProductHashTag->setProduct($Product);
  1008.                     $Product->addProductHashTag($ProductHashTag);
  1009.                     $this->entityManager->persist($ProductHashTag);
  1010.                 }
  1011.                 foreach ($OriginProductHashTags as $ProductHashTag) {
  1012.                     if (!$ProductHashTags->contains($ProductHashTag)) {
  1013.                         $Product->removeProductHashTag($ProductHashTag);
  1014.                         $this->entityManager->remove($ProductHashTag);
  1015.                     }
  1016.                 }
  1017.                 // 関連特集の登録
  1018.                 $ProductRelateFeatures $form->get('ProductRelateFeatures')->getData();
  1019.                 foreach ($ProductRelateFeatures as $ProductRelateFeature) {
  1020.                     $fs = new Filesystem();
  1021.                     if ($ProductRelateFeature->getFileName() && strpos($ProductRelateFeature->getFileName(), '..') === false && $fs->exists($this->getParameter('eccube_temp_image_dir').'/'.$ProductRelateFeature->getFileName())) {
  1022.                         $fs->rename(
  1023.                             $this->getParameter('eccube_temp_image_dir').'/'.$ProductRelateFeature->getFileName(),
  1024.                             $this->getParameter('eccube_save_image_dir').'/'.$ProductRelateFeature->getFileName()
  1025.                         );
  1026.                     }
  1027.                 }
  1028.                 $Product->setUpdateDate(new \DateTime());
  1029.                 $this->entityManager->flush();
  1030.                 log_info('商品登録完了', [$id]);
  1031.                 $event = new EventArgs(
  1032.                     [
  1033.                         'form' => $form,
  1034.                         'Product' => $Product,
  1035.                     ],
  1036.                     $request
  1037.                 );
  1038.                 $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_PRODUCT_EDIT_COMPLETE);
  1039.                 $this->addSuccess('admin.common.save_complete''admin');
  1040.                 if ($returnLink $form->get('return_link')->getData()) {
  1041.                     try {
  1042.                         // $returnLinkはpathの形式で渡される. pathが存在するかをルータでチェックする.
  1043.                         $pattern '/^'.preg_quote($request->getBasePath(), '/').'/';
  1044.                         $returnLink preg_replace($pattern''$returnLink);
  1045.                         $result $router->match($returnLink);
  1046.                         // パラメータのみ抽出
  1047.                         $params array_filter($result, function ($key) {
  1048.                             return !== \strpos($key'_');
  1049.                         }, ARRAY_FILTER_USE_KEY);
  1050.                         // pathからurlを再構築してリダイレクト.
  1051.                         return $this->redirectToRoute($result['_route'], $params);
  1052.                     } catch (\Exception $e) {
  1053.                         // マッチしない場合はログ出力してスキップ.
  1054.                         log_warning('URLの形式が不正です。');
  1055.                     }
  1056.                 }
  1057.                 $cacheUtil->clearDoctrineCache();
  1058.                 return $this->redirectToRoute('admin_product_product_edit', ['id' => $Product->getId()]);
  1059.             }
  1060.         }
  1061.         // 検索結果の保持
  1062.         $builder $this->formFactory
  1063.             ->createBuilder(SearchProductType::class);
  1064.         $event = new EventArgs(
  1065.             [
  1066.                 'builder' => $builder,
  1067.                 'Product' => $Product,
  1068.             ],
  1069.             $request
  1070.         );
  1071.         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_PRODUCT_EDIT_SEARCH);
  1072.         $searchForm $builder->getForm();
  1073.         if ('POST' === $request->getMethod()) {
  1074.             $searchForm->handleRequest($request);
  1075.         }
  1076.         // Get Tags
  1077.         $TagsList $this->tagRepository->getList();
  1078.         // ツリー表示のため、ルートからのカテゴリを取得
  1079.         $TopCategories $this->categoryRepository->getList(null);
  1080.         $ChoicedCategoryIds array_map(function ($Category) {
  1081.             return $Category->getId();
  1082.         }, $form->get('Category')->getData());
  1083.         return [
  1084.             'Product' => $Product,
  1085.             'Tags' => $Tags,
  1086.             'TagsList' => $TagsList,
  1087.             'form' => $form->createView(),
  1088.             'searchForm' => $searchForm->createView(),
  1089.             'has_class' => $has_class,
  1090.             'id' => $id,
  1091.             'TopCategories' => $TopCategories,
  1092.             'ChoicedCategoryIds' => $ChoicedCategoryIds,
  1093.         ];
  1094.     }
  1095.     /**
  1096.      * @Route("/%eccube_admin_route%/product/product/{id}/delete", requirements={"id" = "\d+"}, name="admin_product_product_delete", methods={"DELETE"})
  1097.      */
  1098.     public function delete(Request $requestCacheUtil $cacheUtil$id null)
  1099.     {
  1100.         $this->isTokenValid();
  1101.         $session $request->getSession();
  1102.         $page_no intval($session->get('eccube.admin.product.search.page_no'));
  1103.         $page_no $page_no $page_no Constant::ENABLED;
  1104.         $success false;
  1105.         if (!is_null($id)) {
  1106.             /* @var $Product \Eccube\Entity\Product */
  1107.             $Product $this->productRepository->find($id);
  1108.             if (!$Product) {
  1109.                 if ($request->isXmlHttpRequest()) {
  1110.                     $message trans('admin.common.delete_error_already_deleted');
  1111.                     return $this->json(['success' => $success'message' => $message]);
  1112.                 } else {
  1113.                     $this->deleteMessage();
  1114.                     $rUrl $this->generateUrl('admin_product_page', ['page_no' => $page_no]).'?resume='.Constant::ENABLED;
  1115.                     return $this->redirect($rUrl);
  1116.                 }
  1117.             }
  1118.             if ($Product instanceof Product) {
  1119.                 log_info('商品削除開始', [$id]);
  1120.                 $deleteImages $Product->getProductImage();
  1121.                 $ProductClasses $Product->getProductClasses();
  1122.                 try {
  1123.                     $this->productRepository->delete($Product);
  1124.                     $this->entityManager->flush();
  1125.                     $event = new EventArgs(
  1126.                         [
  1127.                             'Product' => $Product,
  1128.                             'ProductClass' => $ProductClasses,
  1129.                             'deleteImages' => $deleteImages,
  1130.                         ],
  1131.                         $request
  1132.                     );
  1133.                     $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_PRODUCT_DELETE_COMPLETE);
  1134.                     $deleteImages $event->getArgument('deleteImages');
  1135.                     // 画像ファイルの削除(commit後に削除させる)
  1136.                     /** @var ProductImage $deleteImage */
  1137.                     foreach ($deleteImages as $deleteImage) {
  1138.                         if ($this->productImageRepository->findOneBy(['file_name' => $deleteImage->getFileName()])) {
  1139.                             continue;
  1140.                         }
  1141.                         try {
  1142.                             $fs = new Filesystem();
  1143.                             $fs->remove($this->eccubeConfig['eccube_save_image_dir'].'/'.$deleteImage);
  1144.                         } catch (\Exception $e) {
  1145.                             // エラーが発生しても無視する
  1146.                         }
  1147.                     }
  1148.                     log_info('商品削除完了', [$id]);
  1149.                     $success true;
  1150.                     $message trans('admin.common.delete_complete');
  1151.                     $cacheUtil->clearDoctrineCache();
  1152.                 } catch (ForeignKeyConstraintViolationException $e) {
  1153.                     log_info('商品削除エラー', [$id]);
  1154.                     $message trans('admin.common.delete_error_foreign_key', ['%name%' => $Product->getName()]);
  1155.                 }
  1156.             } else {
  1157.                 log_info('商品削除エラー', [$id]);
  1158.                 $message trans('admin.common.delete_error');
  1159.             }
  1160.         } else {
  1161.             log_info('商品削除エラー', [$id]);
  1162.             $message trans('admin.common.delete_error');
  1163.         }
  1164.         if ($request->isXmlHttpRequest()) {
  1165.             return $this->json(['success' => $success'message' => $message]);
  1166.         } else {
  1167.             if ($success) {
  1168.                 $this->addSuccess($message'admin');
  1169.             } else {
  1170.                 $this->addError($message'admin');
  1171.             }
  1172.             $rUrl $this->generateUrl('admin_product_page', ['page_no' => $page_no]).'?resume='.Constant::ENABLED;
  1173.             return $this->redirect($rUrl);
  1174.         }
  1175.     }
  1176.     /**
  1177.      * @Route("/%eccube_admin_route%/product/product/{id}/copy", requirements={"id" = "\d+"}, name="admin_product_product_copy", methods={"POST"})
  1178.      */
  1179.     public function copy(Request $request$id null)
  1180.     {
  1181.         $this->isTokenValid();
  1182.         if (!is_null($id)) {
  1183.             $Product $this->productRepository->find($id);
  1184.             if ($Product instanceof Product) {
  1185.                 $CopyProduct = clone $Product;
  1186.                 $CopyProduct->copy();
  1187.                 $ProductStatus $this->productStatusRepository->find(ProductStatus::DISPLAY_HIDE);
  1188.                 $CopyProduct->setStatus($ProductStatus);
  1189.                 $CopyProductCategories $CopyProduct->getProductCategories();
  1190.                 foreach ($CopyProductCategories as $Category) {
  1191.                     $this->entityManager->persist($Category);
  1192.                 }
  1193.                 // 規格あり商品の場合は, デフォルトの商品規格を取得し登録する.
  1194.                 if ($CopyProduct->hasProductClass()) {
  1195.                     $dummyClass $this->productClassRepository->findOneBy([
  1196.                         'visible' => false,
  1197.                         'ClassCategory1' => null,
  1198.                         'ClassCategory2' => null,
  1199.                         'Product' => $Product,
  1200.                     ]);
  1201.                     $dummyClass = clone $dummyClass;
  1202.                     $dummyClass->setProduct($CopyProduct);
  1203.                     $CopyProduct->addProductClass($dummyClass);
  1204.                 }
  1205.                 $CopyProductClasses $CopyProduct->getProductClasses();
  1206.                 foreach ($CopyProductClasses as $Class) {
  1207.                     $Stock $Class->getProductStock();
  1208.                     $CopyStock = clone $Stock;
  1209.                     $CopyStock->setProductClass($Class);
  1210.                     $this->entityManager->persist($CopyStock);
  1211.                     $TaxRule $Class->getTaxRule();
  1212.                     if ($TaxRule) {
  1213.                         $CopyTaxRule = clone $TaxRule;
  1214.                         $CopyTaxRule->setProductClass($Class);
  1215.                         $CopyTaxRule->setProduct($CopyProduct);
  1216.                         $this->entityManager->persist($CopyTaxRule);
  1217.                     }
  1218.                     $this->entityManager->persist($Class);
  1219.                 }
  1220.                 $Images $CopyProduct->getProductImage();
  1221.                 foreach ($Images as $Image) {
  1222.                     // 画像ファイルを新規作成
  1223.                     $extension pathinfo($Image->getFileName(), PATHINFO_EXTENSION);
  1224.                     $filename date('mdHis').uniqid('_').'.'.$extension;
  1225.                     try {
  1226.                         $fs = new Filesystem();
  1227.                         $fs->copy($this->eccubeConfig['eccube_save_image_dir'].'/'.$Image->getFileName(), $this->eccubeConfig['eccube_save_image_dir'].'/'.$filename);
  1228.                     } catch (\Exception $e) {
  1229.                         // エラーが発生しても無視する
  1230.                     }
  1231.                     $Image->setFileName($filename);
  1232.                     $this->entityManager->persist($Image);
  1233.                 }
  1234.                 $Tags $CopyProduct->getProductTag();
  1235.                 foreach ($Tags as $Tag) {
  1236.                     $this->entityManager->persist($Tag);
  1237.                 }
  1238.                 $this->entityManager->persist($CopyProduct);
  1239.                 $this->entityManager->flush();
  1240.                 $event = new EventArgs(
  1241.                     [
  1242.                         'Product' => $Product,
  1243.                         'CopyProduct' => $CopyProduct,
  1244.                         'CopyProductCategories' => $CopyProductCategories,
  1245.                         'CopyProductClasses' => $CopyProductClasses,
  1246.                         'images' => $Images,
  1247.                         'Tags' => $Tags,
  1248.                     ],
  1249.                     $request
  1250.                 );
  1251.                 $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_PRODUCT_COPY_COMPLETE);
  1252.                 $this->addSuccess('admin.product.copy_complete''admin');
  1253.                 return $this->redirectToRoute('admin_product_product_edit', ['id' => $CopyProduct->getId()]);
  1254.             } else {
  1255.                 $this->addError('admin.product.copy_error''admin');
  1256.             }
  1257.         } else {
  1258.             $msg trans('admin.product.copy_error');
  1259.             $this->addError($msg'admin');
  1260.         }
  1261.         return $this->redirectToRoute('admin_product');
  1262.     }
  1263.     /**
  1264.      * 商品CSVの出力.
  1265.      *
  1266.      * @Route("/%eccube_admin_route%/product/export", name="admin_product_export", methods={"GET"})
  1267.      *
  1268.      * @param Request $request
  1269.      *
  1270.      * @return StreamedResponse
  1271.      */
  1272.     public function export(Request $request)
  1273.     {
  1274.         // タイムアウトを無効にする.
  1275.         set_time_limit(0);
  1276.         // sql loggerを無効にする.
  1277.         $em $this->entityManager;
  1278.         $em->getConfiguration()->setSQLLogger(null);
  1279.         $response = new StreamedResponse();
  1280.         $response->setCallback(function () use ($request) {
  1281.             // CSV種別を元に初期化.
  1282.             $this->csvExportService->initCsvType(CsvType::CSV_TYPE_PRODUCT);
  1283.             // 商品データ検索用のクエリビルダを取得.
  1284.             $qb $this->csvExportService
  1285.                 ->getProductQueryBuilder($request);
  1286.             // ヘッダ行の出力.
  1287.             $this->csvExportService->exportHeader();
  1288.             // Get stock status
  1289.             $isOutOfStock 0;
  1290.             $session $request->getSession();
  1291.             if ($session->has('eccube.admin.product.search')) {
  1292.                 $searchData $session->get('eccube.admin.product.search', []);
  1293.                 if (isset($searchData['stock_status']) && $searchData['stock_status'] === 0) {
  1294.                     $isOutOfStock 1;
  1295.                 }
  1296.             }
  1297.             // joinする場合はiterateが使えないため, select句をdistinctする.
  1298.             // http://qiita.com/suin/items/2b1e98105fa3ef89beb7
  1299.             // distinctのmysqlとpgsqlの挙動をあわせる.
  1300.             // http://uedatakeshi.blogspot.jp/2010/04/distinct-oeder-by-postgresmysql.html
  1301.             $qb->resetDQLPart('select');
  1302.             if ($isOutOfStock) {
  1303.                 $qb->select('p, pc')
  1304.                     ->distinct();
  1305.             } else {
  1306.                 $qb->select('p')
  1307.                     ->distinct();
  1308.             }
  1309.             // データ行の出力.
  1310.             $this->csvExportService->setExportQueryBuilder($qb);
  1311.             $this->csvExportService->exportData(function ($entityCsvExportService $csvService) use ($request) {
  1312.                 $Csvs $csvService->getCsvs();
  1313.                 /** @var $Product \Eccube\Entity\Product */
  1314.                 $Product $entity;
  1315.                 /** @var $ProductClasses \Eccube\Entity\ProductClass[] */
  1316.                 $ProductClasses $Product->getProductClasses();
  1317.                 foreach ($ProductClasses as $ProductClass) {
  1318.                     $ExportCsvRow = new ExportCsvRow();
  1319.                     // CSV出力項目と合致するデータを取得.
  1320.                     foreach ($Csvs as $Csv) {
  1321.                         // 商品データを検索.
  1322.                         $ExportCsvRow->setData($csvService->getData($Csv$Product));
  1323.                         if ($ExportCsvRow->isDataNull()) {
  1324.                             // 商品規格情報を検索.
  1325.                             $ExportCsvRow->setData($csvService->getData($Csv$ProductClass));
  1326.                         }
  1327.                         $event = new EventArgs(
  1328.                             [
  1329.                                 'csvService' => $csvService,
  1330.                                 'Csv' => $Csv,
  1331.                                 'ProductClass' => $ProductClass,
  1332.                                 'ExportCsvRow' => $ExportCsvRow,
  1333.                             ],
  1334.                             $request
  1335.                         );
  1336.                         $this->eventDispatcher->dispatch($eventEccubeEvents::ADMIN_PRODUCT_CSV_EXPORT);
  1337.                         $ExportCsvRow->pushData();
  1338.                     }
  1339.                     // $row[] = number_format(memory_get_usage(true));
  1340.                     // 出力.
  1341.                     $csvService->fputcsv($ExportCsvRow->getRow());
  1342.                 }
  1343.             });
  1344.         });
  1345.         $now = new \DateTime();
  1346.         $filename 'product_'.$now->format('YmdHis').'.csv';
  1347.         $response->headers->set('Content-Type''application/octet-stream');
  1348.         $response->headers->set('Content-Disposition''attachment; filename='.$filename);
  1349.         log_info('商品CSV出力ファイル名', [$filename]);
  1350.         return $response;
  1351.     }
  1352.     /**
  1353.      * ProductCategory作成
  1354.      *
  1355.      * @param \Eccube\Entity\Product $Product
  1356.      * @param \Eccube\Entity\Category $Category
  1357.      * @param integer $count
  1358.      *
  1359.      * @return \Eccube\Entity\ProductCategory
  1360.      */
  1361.     private function createProductCategory($Product$Category$count)
  1362.     {
  1363.         $ProductCategory = new ProductCategory();
  1364.         $ProductCategory->setProduct($Product);
  1365.         $ProductCategory->setProductId($Product->getId());
  1366.         $ProductCategory->setCategory($Category);
  1367.         $ProductCategory->setCategoryId($Category->getId());
  1368.         return $ProductCategory;
  1369.     }
  1370.     /**
  1371.      * Bulk public action
  1372.      *
  1373.      * @Route("/%eccube_admin_route%/product/bulk/product-status/{id}", requirements={"id" = "\d+"}, name="admin_product_bulk_product_status", methods={"POST"})
  1374.      *
  1375.      * @param Request $request
  1376.      * @param ProductStatus $ProductStatus
  1377.      *
  1378.      * @return RedirectResponse
  1379.      */
  1380.     public function bulkProductStatus(Request $requestProductStatus $ProductStatusCacheUtil $cacheUtil)
  1381.     {
  1382.         $this->isTokenValid();
  1383.         /** @var Product[] $Products */
  1384.         $Products $this->productRepository->findBy(['id' => $request->get('ids')]);
  1385.         $count 0;
  1386.         foreach ($Products as $Product) {
  1387.             try {
  1388.                 $Product->setStatus($ProductStatus);
  1389.                 $this->productRepository->save($Product);
  1390.                 $count++;
  1391.             } catch (\Exception $e) {
  1392.                 $this->addError($e->getMessage(), 'admin');
  1393.             }
  1394.         }
  1395.         try {
  1396.             if ($count) {
  1397.                 $this->entityManager->flush();
  1398.                 $msg $this->translator->trans('admin.product.bulk_change_status_complete', [
  1399.                     '%count%' => $count,
  1400.                     '%status%' => $ProductStatus->getName(),
  1401.                 ]);
  1402.                 $this->addSuccess($msg'admin');
  1403.                 $cacheUtil->clearDoctrineCache();
  1404.             }
  1405.         } catch (\Exception $e) {
  1406.             $this->addError($e->getMessage(), 'admin');
  1407.         }
  1408.         return $this->redirectToRoute('admin_product', ['resume' => Constant::ENABLED]);
  1409.     }
  1410.     /**
  1411.      * 音声ファイルアップロード時に呼び出されるメソッド.
  1412.      *
  1413.      * @Route("/%eccube_admin_route%/product/product/audio", name="admin_product_audio_file", methods={"POST"})
  1414.      */
  1415.     public function addAudio(Request $request)
  1416.     {
  1417.         if (!$request->isXmlHttpRequest() && $this->isTokenValid()) {
  1418.             throw new BadRequestHttpException();
  1419.         }
  1420.         $audio $request->files->get('audio_file');
  1421.         $allowExtensions = ['mpeg''mp3''wav','ogg''midi'];
  1422.         //ファイルフォーマット検証
  1423.         $mimeType $audio->getMimeType();
  1424.         if (!== strpos($mimeType'audio')) {
  1425.             throw new UnsupportedMediaTypeHttpException();
  1426.         }
  1427.         // 拡張子
  1428.         $extension $audio->getClientOriginalExtension();
  1429.         if (!in_array(strtolower($extension), $allowExtensions)) {
  1430.             throw new UnsupportedMediaTypeHttpException();
  1431.         }
  1432.         $fileName date('mdHis').uniqid('_').'.'.$extension;
  1433.         $audio->move($this->getParameter('eccube_temp_image_dir'), $fileName);
  1434.         return new Response($fileName);
  1435.     }
  1436.     /**
  1437.      * 音声ファイル削除ボタンを押したときに呼び出されるメソッド.
  1438.      *
  1439.      * @Route("/%eccube_admin_route%/product/product/audio/delete/{id}", requirements={"id" = "\d+"}, name="admin_product_audio_delete", methods={"DELETE"})
  1440.      */
  1441.     public function deleteAudio(Request $request$id null)
  1442.     {
  1443.         if (!$request->isXmlHttpRequest() && $this->isTokenValid()) {
  1444.             throw new BadRequestHttpException();
  1445.         }
  1446.         /** @var Product|null */
  1447.         $Product null;
  1448.         $Product $this->productRepository->findWithSortedClassCategories($id);
  1449.         $fileName $Product->getAudioFileName();
  1450.         return new Response($fileName);
  1451.     }
  1452. }