<?php 
 
/* 
 * This file is part of EC-CUBE 
 * 
 * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved. 
 * 
 * http://www.ec-cube.co.jp/ 
 * 
 * For the full copyright and license information, please view the LICENSE 
 * file that was distributed with this source code. 
 */ 
 
namespace Eccube\Controller\Admin\Product; 
 
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; 
use Eccube\Common\Constant; 
use Eccube\Controller\Admin\AbstractCsvImportController; 
use Eccube\Entity\BaseInfo; 
use Eccube\Entity\Category; 
use Eccube\Entity\Product; 
use Eccube\Entity\ProductCategory; 
use Eccube\Entity\ProductClass; 
use Eccube\Entity\ProductImage; 
use Eccube\Entity\ProductStock; 
use Eccube\Entity\ProductTag; 
use Eccube\Form\Type\Admin\CsvImportType; 
use Eccube\Repository\BaseInfoRepository; 
use Eccube\Repository\CategoryRepository; 
use Eccube\Repository\ClassCategoryRepository; 
use Eccube\Repository\DeliveryDurationRepository; 
use Eccube\Repository\Master\ProductStatusRepository; 
use Eccube\Repository\Master\SaleTypeRepository; 
use Eccube\Repository\ProductImageRepository; 
use Eccube\Repository\ProductRepository; 
use Eccube\Repository\TagRepository; 
use Eccube\Repository\TaxRuleRepository; 
use Eccube\Service\CsvImportService; 
use Eccube\Stream\Filter\ConvertLineFeedFilter; 
use Eccube\Stream\Filter\SjisToUtf8EncodingFilter; 
use Eccube\Util\CacheUtil; 
use Eccube\Util\StringUtil; 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; 
use Symfony\Component\Filesystem\Filesystem; 
use Symfony\Component\Finder\Finder; 
use Symfony\Component\Form\FormInterface; 
use Symfony\Component\HttpFoundation\File\UploadedFile; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\HttpFoundation\StreamedResponse; 
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; 
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 
use Symfony\Component\Routing\Annotation\Route; 
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; 
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual; 
use Symfony\Component\Validator\Validator\ValidatorInterface; 
 
class CsvImportController extends AbstractCsvImportController 
{ 
    /** 
     * @var DeliveryDurationRepository 
     */ 
    protected $deliveryDurationRepository; 
 
    /** 
     * @var SaleTypeRepository 
     */ 
    protected $saleTypeRepository; 
 
    /** 
     * @var TagRepository 
     */ 
    protected $tagRepository; 
 
    /** 
     * @var CategoryRepository 
     */ 
    protected $categoryRepository; 
 
    /** 
     * @var ClassCategoryRepository 
     */ 
    protected $classCategoryRepository; 
 
    /** 
     * @var ProductImageRepository 
     */ 
    protected $productImageRepository; 
 
    /** 
     * @var ProductStatusRepository 
     */ 
    protected $productStatusRepository; 
 
    /** 
     * @var ProductRepository 
     */ 
    protected $productRepository; 
 
    /** 
     * @var TaxRuleRepository 
     */ 
    private $taxRuleRepository; 
 
    /** 
     * @var BaseInfo 
     */ 
    protected $BaseInfo; 
 
    /** 
     * @var ValidatorInterface 
     */ 
    protected $validator; 
 
    private $errors = []; 
 
    protected $isSplitCsv = false; 
 
    protected $csvFileNo = 1; 
 
    protected $currentLineNo = 1; 
 
    /** 
     * CsvImportController constructor. 
     * 
     * @param DeliveryDurationRepository $deliveryDurationRepository 
     * @param SaleTypeRepository $saleTypeRepository 
     * @param TagRepository $tagRepository 
     * @param CategoryRepository $categoryRepository 
     * @param ClassCategoryRepository $classCategoryRepository 
     * @param ProductImageRepository $productImageRepository 
     * @param ProductStatusRepository $productStatusRepository 
     * @param ProductRepository $productRepository 
     * @param TaxRuleRepository $taxRuleRepository 
     * @param BaseInfoRepository $baseInfoRepository 
     * @param ValidatorInterface $validator 
     * 
     * @throws \Exception 
     */ 
    public function __construct( 
        DeliveryDurationRepository $deliveryDurationRepository, 
        SaleTypeRepository $saleTypeRepository, 
        TagRepository $tagRepository, 
        CategoryRepository $categoryRepository, 
        ClassCategoryRepository $classCategoryRepository, 
        ProductImageRepository $productImageRepository, 
        ProductStatusRepository $productStatusRepository, 
        ProductRepository $productRepository, 
        TaxRuleRepository $taxRuleRepository, 
        BaseInfoRepository $baseInfoRepository, 
        ValidatorInterface $validator 
    ) { 
        $this->deliveryDurationRepository = $deliveryDurationRepository; 
        $this->saleTypeRepository = $saleTypeRepository; 
        $this->tagRepository = $tagRepository; 
        $this->categoryRepository = $categoryRepository; 
        $this->classCategoryRepository = $classCategoryRepository; 
        $this->productImageRepository = $productImageRepository; 
        $this->productStatusRepository = $productStatusRepository; 
        $this->productRepository = $productRepository; 
        $this->taxRuleRepository = $taxRuleRepository; 
        $this->BaseInfo = $baseInfoRepository->get(); 
        $this->validator = $validator; 
    } 
 
    /** 
     * 商品登録CSVアップロード 
     * 
     * @Route("/%eccube_admin_route%/product/product_csv_upload", name="admin_product_csv_import", methods={"GET", "POST"}) 
     * @Template("@admin/Product/csv_product.twig") 
     * 
     * @return array 
     * 
     * @throws \Doctrine\DBAL\ConnectionException 
     * @throws \Doctrine\ORM\NoResultException 
     */ 
    public function csvProduct(Request $request, CacheUtil $cacheUtil) 
    { 
        $form = $this->formFactory->createBuilder(CsvImportType::class)->getForm(); 
        $headers = $this->getProductCsvHeader(); 
        if ('POST' === $request->getMethod()) { 
            $form->handleRequest($request); 
            if ($form->isValid()) { 
                $this->isSplitCsv = $form['is_split_csv']->getData(); 
                $this->csvFileNo = $form['csv_file_no']->getData(); 
 
                $formFile = $form['import_file']->getData(); 
                if (!empty($formFile)) { 
                    log_info('商品CSV登録開始'); 
                    $data = $this->getImportData($formFile); 
                    if ($data === false) { 
                        $this->addErrors(trans('admin.common.csv_invalid_format')); 
 
                        return $this->renderWithError($form, $headers, false); 
                    } 
                    $getId = function ($item) { 
                        return $item['id']; 
                    }; 
                    $requireHeader = array_keys(array_map($getId, array_filter($headers, function ($value) { 
                        return $value['required']; 
                    }))); 
 
                    $columnHeaders = $data->getColumnHeaders(); 
 
                    if (count(array_diff($requireHeader, $columnHeaders)) > 0) { 
                        $this->addErrors(trans('admin.common.csv_invalid_format')); 
 
                        return $this->renderWithError($form, $headers, false); 
                    } 
 
                    $size = count($data); 
 
                    if ($size < 1) { 
                        $this->addErrors(trans('admin.common.csv_invalid_no_data')); 
 
                        return $this->renderWithError($form, $headers, false); 
                    } 
 
                    $headerSize = count($columnHeaders); 
                    $headerByKey = array_flip(array_map($getId, $headers)); 
                    $deleteImages = []; 
 
                    $this->entityManager->getConfiguration()->setSQLLogger(null); 
                    $this->entityManager->getConnection()->beginTransaction(); 
                    // CSVファイルの登録処理 
                    foreach ($data as $row) { 
                        $line = $this->convertLineNo($data->key() + 1); 
                        $this->currentLineNo = $line; 
                        if ($headerSize != count($row)) { 
                            $message = trans('admin.common.csv_invalid_format_line', ['%line%' => $line]); 
                            $this->addErrors($message); 
 
                            return $this->renderWithError($form, $headers); 
                        } 
 
                        if (!isset($row[$headerByKey['id']]) || StringUtil::isBlank($row[$headerByKey['id']])) { 
                            $Product = new Product(); 
                            $this->entityManager->persist($Product); 
                        } else { 
                            if (preg_match('/^\d+$/', $row[$headerByKey['id']])) { 
                                $Product = $this->productRepository->find($row[$headerByKey['id']]); 
                                if (!$Product) { 
                                    $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['id']]); 
                                    $this->addErrors($message); 
 
                                    return $this->renderWithError($form, $headers); 
                                } 
                            } else { 
                                $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['id']]); 
                                $this->addErrors($message); 
 
                                return $this->renderWithError($form, $headers); 
                            } 
 
                            if (isset($row[$headerByKey['product_del_flg']])) { 
                                if (StringUtil::isNotBlank($row[$headerByKey['product_del_flg']]) && $row[$headerByKey['product_del_flg']] == (string) Constant::ENABLED) { 
                                    // 商品を物理削除 
                                    $deleteImages[] = $Product->getProductImage(); 
 
                                    try { 
                                        $this->productRepository->delete($Product); 
                                        $this->entityManager->flush(); 
 
                                        continue; 
                                    } catch (ForeignKeyConstraintViolationException $e) { 
                                        $message = trans('admin.common.csv_invalid_foreign_key', ['%line%' => $line, '%name%' => $Product->getName()]); 
                                        $this->addErrors($message); 
 
                                        return $this->renderWithError($form, $headers); 
                                    } 
                                } 
                            } 
                        } 
 
                        if (StringUtil::isBlank($row[$headerByKey['status']])) { 
                            $message = trans('admin.common.csv_invalid_required', ['%line%' => $line, '%name%' => $headerByKey['status']]); 
                            $this->addErrors($message); 
                        } else { 
                            if (preg_match('/^\d+$/', $row[$headerByKey['status']])) { 
                                $ProductStatus = $this->productStatusRepository->find($row[$headerByKey['status']]); 
                                if (!$ProductStatus) { 
                                    $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['status']]); 
                                    $this->addErrors($message); 
                                } else { 
                                    $Product->setStatus($ProductStatus); 
                                } 
                            } else { 
                                $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['status']]); 
                                $this->addErrors($message); 
                            } 
                        } 
 
                        if (StringUtil::isBlank($row[$headerByKey['name']])) { 
                            $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['name']]); 
                            $this->addErrors($message); 
 
                            return $this->renderWithError($form, $headers); 
                        } else { 
                            $Product->setName(StringUtil::trimAll($row[$headerByKey['name']])); 
                        } 
 
                        if (isset($row[$headerByKey['note']])) { 
                            if (StringUtil::isNotBlank($row[$headerByKey['note']])) { 
                                $Product->setNote(StringUtil::trimAll($row[$headerByKey['note']])); 
                            } else { 
                                $Product->setNote(null); 
                            } 
                        } 
 
                        if (isset($row[$headerByKey['description_list']])) { 
                            if (StringUtil::isNotBlank($row[$headerByKey['description_list']])) { 
                                $Product->setDescriptionList(StringUtil::trimAll($row[$headerByKey['description_list']])); 
                            } else { 
                                $Product->setDescriptionList(null); 
                            } 
                        } 
 
                        if (isset($row[$headerByKey['description_detail']])) { 
                            if (StringUtil::isNotBlank($row[$headerByKey['description_detail']])) { 
                                if (mb_strlen($row[$headerByKey['description_detail']]) > $this->eccubeConfig['eccube_ltext_len']) { 
                                    $message = trans('admin.common.csv_invalid_description_detail_upper_limit', [ 
                                        '%line%' => $line, 
                                        '%name%' => $headerByKey['description_detail'], 
                                        '%max%' => $this->eccubeConfig['eccube_ltext_len'], 
                                    ]); 
                                    $this->addErrors($message); 
 
                                    return $this->renderWithError($form, $headers); 
                                } else { 
                                    $Product->setDescriptionDetail(StringUtil::trimAll($row[$headerByKey['description_detail']])); 
                                } 
                            } else { 
                                $Product->setDescriptionDetail(null); 
                            } 
                        } 
 
                        if (isset($row[$headerByKey['search_word']])) { 
                            if (StringUtil::isNotBlank($row[$headerByKey['search_word']])) { 
                                $Product->setSearchWord(StringUtil::trimAll($row[$headerByKey['search_word']])); 
                            } else { 
                                $Product->setSearchWord(null); 
                            } 
                        } 
 
                        if (isset($row[$headerByKey['free_area']])) { 
                            if (StringUtil::isNotBlank($row[$headerByKey['free_area']])) { 
                                $Product->setFreeArea(StringUtil::trimAll($row[$headerByKey['free_area']])); 
                            } else { 
                                $Product->setFreeArea(null); 
                            } 
                        } 
 
                        // 商品画像登録 
                        $this->createProductImage($row, $Product, $data, $headerByKey); 
 
                        $this->entityManager->flush(); 
 
                        // 商品カテゴリ登録 
                        $this->createProductCategory($row, $Product, $data, $headerByKey); 
 
                        // タグ登録 
                        $this->createProductTag($row, $Product, $data, $headerByKey); 
 
                        // 商品規格が存在しなければ新規登録 
                        /** @var ProductClass[] $ProductClasses */ 
                        $ProductClasses = $Product->getProductClasses(); 
                        if ($ProductClasses->count() < 1) { 
                            // 規格分類1(ID)がセットされていると規格なし商品、規格あり商品を作成 
                            $ProductClassOrg = $this->createProductClass($row, $Product, $data, $headerByKey); 
                            if ($this->BaseInfo->isOptionProductDeliveryFee()) { 
                                if (isset($row[$headerByKey['delivery_fee']]) && StringUtil::isNotBlank($row[$headerByKey['delivery_fee']])) { 
                                    $deliveryFee = str_replace(',', '', $row[$headerByKey['delivery_fee']]); 
                                    $errors = $this->validator->validate($deliveryFee, new GreaterThanOrEqual(['value' => 0])); 
                                    if ($errors->count() === 0) { 
                                        $ProductClassOrg->setDeliveryFee($deliveryFee); 
                                    } else { 
                                        $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['delivery_fee']]); 
                                        $this->addErrors($message); 
                                    } 
                                } 
                            } 
 
                            // 商品別税率機能が有効の場合に税率を更新 
                            if ($this->BaseInfo->isOptionProductTaxRule()) { 
                                if (isset($row[$headerByKey['tax_rate']]) && StringUtil::isNotBlank($row[$headerByKey['tax_rate']])) { 
                                    $taxRate = $row[$headerByKey['tax_rate']]; 
                                    $errors = $this->validator->validate($taxRate, new GreaterThanOrEqual(['value' => 0])); 
                                    if ($errors->count() === 0) { 
                                        if ($ProductClassOrg->getTaxRule()) { 
                                            // 商品別税率の設定があれば税率を更新 
                                            $ProductClassOrg->getTaxRule()->setTaxRate($taxRate); 
                                        } else { 
                                            // 商品別税率の設定がなければ新規作成 
                                            $TaxRule = $this->taxRuleRepository->newTaxRule(); 
                                            $TaxRule->setTaxRate($taxRate); 
                                            $TaxRule->setApplyDate(new \DateTime()); 
                                            $TaxRule->setProduct($Product); 
                                            $TaxRule->setProductClass($ProductClassOrg); 
                                            $ProductClassOrg->setTaxRule($TaxRule); 
                                        } 
                                    } else { 
                                        $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['tax_rate']]); 
                                        $this->addErrors($message); 
                                    } 
                                } else { 
                                    // 税率の入力がなければ税率の設定を削除 
                                    if ($ProductClassOrg->getTaxRule()) { 
                                        $this->taxRuleRepository->delete($ProductClassOrg->getTaxRule()); 
                                        $ProductClassOrg->setTaxRule(null); 
                                    } 
                                } 
                            } 
 
                            if (isset($row[$headerByKey['class_category1']]) && StringUtil::isNotBlank($row[$headerByKey['class_category1']])) { 
                                if (isset($row[$headerByKey['class_category2']]) && $row[$headerByKey['class_category1']] == $row[$headerByKey['class_category2']]) { 
                                    $message = trans('admin.common.csv_invalid_not_same', [ 
                                        '%line%' => $line, 
                                        '%name1%' => $headerByKey['class_category1'], 
                                        '%name2%' => $headerByKey['class_category2'], 
                                    ]); 
                                    $this->addErrors($message); 
                                } else { 
                                    // 商品規格あり 
                                    // 規格分類あり商品を作成 
                                    $ProductClass = clone $ProductClassOrg; 
                                    $ProductStock = clone $ProductClassOrg->getProductStock(); 
 
                                    // 規格分類1、規格分類2がnullであるデータを非表示 
                                    $ProductClassOrg->setVisible(false); 
 
                                    // 規格分類1、2をそれぞれセットし作成 
                                    $ClassCategory1 = null; 
                                    if (preg_match('/^\d+$/', $row[$headerByKey['class_category1']])) { 
                                        $ClassCategory1 = $this->classCategoryRepository->find($row[$headerByKey['class_category1']]); 
                                        if (!$ClassCategory1) { 
                                            $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category1']]); 
                                            $this->addErrors($message); 
                                        } else { 
                                            $ProductClass->setClassCategory1($ClassCategory1); 
                                        } 
                                    } else { 
                                        $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category1']]); 
                                        $this->addErrors($message); 
                                    } 
 
                                    if (isset($row[$headerByKey['class_category2']]) && StringUtil::isNotBlank($row[$headerByKey['class_category2']])) { 
                                        if (preg_match('/^\d+$/', $row[$headerByKey['class_category2']])) { 
                                            $ClassCategory2 = $this->classCategoryRepository->find($row[$headerByKey['class_category2']]); 
                                            if (!$ClassCategory2) { 
                                                $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category2']]); 
                                                $this->addErrors($message); 
                                            } else { 
                                                if ($ClassCategory1 && 
                                                    ($ClassCategory1->getClassName()->getId() == $ClassCategory2->getClassName()->getId()) 
                                                ) { 
                                                    $message = trans('admin.common.csv_invalid_not_same', ['%line%' => $line, '%name1%' => $headerByKey['class_category1'], '%name2%' => $headerByKey['class_category2']]); 
                                                    $this->addErrors($message); 
                                                } else { 
                                                    $ProductClass->setClassCategory2($ClassCategory2); 
                                                } 
                                            } 
                                        } else { 
                                            $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category2']]); 
                                            $this->addErrors($message); 
                                        } 
                                    } 
                                    $ProductClass->setProductStock($ProductStock); 
                                    $ProductStock->setProductClass($ProductClass); 
 
                                    $this->entityManager->persist($ProductClass); 
                                    $this->entityManager->persist($ProductStock); 
                                } 
                            } else { 
                                if (isset($row[$headerByKey['class_category2']]) && StringUtil::isNotBlank($row[$headerByKey['class_category2']])) { 
                                    $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category2']]); 
                                    $this->addErrors($message); 
                                } 
                            } 
                        } else { 
                            // 商品規格の更新 
                            $flag = false; 
                            $classCategoryId1 = StringUtil::isBlank($row[$headerByKey['class_category1']]) ? null : $row[$headerByKey['class_category1']]; 
                            $classCategoryId2 = StringUtil::isBlank($row[$headerByKey['class_category2']]) ? null : $row[$headerByKey['class_category2']]; 
 
                            foreach ($ProductClasses as $pc) { 
                                $classCategory1 = is_null($pc->getClassCategory1()) ? null : $pc->getClassCategory1()->getId(); 
                                $classCategory2 = is_null($pc->getClassCategory2()) ? null : $pc->getClassCategory2()->getId(); 
 
                                // 登録されている商品規格を更新 
                                if ($classCategory1 == $classCategoryId1 && 
                                    $classCategory2 == $classCategoryId2 
                                ) { 
                                    $this->updateProductClass($row, $Product, $pc, $data, $headerByKey); 
 
                                    if ($this->BaseInfo->isOptionProductDeliveryFee()) { 
                                        if (isset($row[$headerByKey['delivery_fee']]) && StringUtil::isNotBlank($row[$headerByKey['delivery_fee']])) { 
                                            $deliveryFee = str_replace(',', '', $row[$headerByKey['delivery_fee']]); 
                                            $errors = $this->validator->validate($deliveryFee, new GreaterThanOrEqual(['value' => 0])); 
                                            if ($errors->count() === 0) { 
                                                $pc->setDeliveryFee($deliveryFee); 
                                            } else { 
                                                $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['delivery_fee']]); 
                                                $this->addErrors($message); 
                                            } 
                                        } 
                                    } 
 
                                    // 商品別税率機能が有効の場合に税率を更新 
                                    if ($this->BaseInfo->isOptionProductTaxRule()) { 
                                        if (isset($row[$headerByKey['tax_rate']]) && StringUtil::isNotBlank($row[$headerByKey['tax_rate']])) { 
                                            $taxRate = $row[$headerByKey['tax_rate']]; 
                                            $errors = $this->validator->validate($taxRate, new GreaterThanOrEqual(['value' => 0])); 
                                            if ($errors->count() === 0) { 
                                                if ($pc->getTaxRule()) { 
                                                    // 商品別税率の設定があれば税率を更新 
                                                    $pc->getTaxRule()->setTaxRate($taxRate); 
                                                } else { 
                                                    // 商品別税率の設定がなければ新規作成 
                                                    $TaxRule = $this->taxRuleRepository->newTaxRule(); 
                                                    $TaxRule->setTaxRate($taxRate); 
                                                    $TaxRule->setApplyDate(new \DateTime()); 
                                                    $TaxRule->setProduct($Product); 
                                                    $TaxRule->setProductClass($pc); 
                                                    $pc->setTaxRule($TaxRule); 
                                                } 
                                            } else { 
                                                $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['tax_rate']]); 
                                                $this->addErrors($message); 
                                            } 
                                        } else { 
                                            // 税率の入力がなければ税率の設定を削除 
                                            if ($pc->getTaxRule()) { 
                                                $this->taxRuleRepository->delete($pc->getTaxRule()); 
                                                $pc->setTaxRule(null); 
                                            } 
                                        } 
                                    } 
 
                                    $flag = true; 
                                    break; 
                                } 
                            } 
 
                            // 商品規格を登録 
                            if (!$flag) { 
                                $pc = $ProductClasses[0]; 
                                if ($pc->getClassCategory1() == null && 
                                    $pc->getClassCategory2() == null 
                                ) { 
                                    // 規格分類1、規格分類2がnullであるデータを非表示 
                                    $pc->setVisible(false); 
                                } 
 
                                if (isset($row[$headerByKey['class_category1']]) && isset($row[$headerByKey['class_category2']]) 
                                    && $row[$headerByKey['class_category1']] == $row[$headerByKey['class_category2']]) { 
                                    $message = trans('admin.common.csv_invalid_not_same', [ 
                                        '%line%' => $line, 
                                        '%name1%' => $headerByKey['class_category1'], 
                                        '%name2%' => $headerByKey['class_category2'], 
                                    ]); 
                                    $this->addErrors($message); 
                                } else { 
                                    // 必ず規格分類1がセットされている 
                                    // 規格分類1、2をそれぞれセットし作成 
                                    $ClassCategory1 = null; 
                                    if (preg_match('/^\d+$/', $classCategoryId1)) { 
                                        $ClassCategory1 = $this->classCategoryRepository->find($classCategoryId1); 
                                        if (!$ClassCategory1) { 
                                            $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category1']]); 
                                            $this->addErrors($message); 
                                        } 
                                    } else { 
                                        $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category1']]); 
                                        $this->addErrors($message); 
                                    } 
 
                                    $ClassCategory2 = null; 
                                    if (isset($row[$headerByKey['class_category2']]) && StringUtil::isNotBlank($row[$headerByKey['class_category2']])) { 
                                        if ($pc->getClassCategory1() != null && $pc->getClassCategory2() == null) { 
                                            $message = trans('admin.common.csv_invalid_can_not', ['%line%' => $line, '%name%' => $headerByKey['class_category2']]); 
                                            $this->addErrors($message); 
                                        } else { 
                                            if (preg_match('/^\d+$/', $classCategoryId2)) { 
                                                $ClassCategory2 = $this->classCategoryRepository->find($classCategoryId2); 
                                                if (!$ClassCategory2) { 
                                                    $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category2']]); 
                                                    $this->addErrors($message); 
                                                } else { 
                                                    if ($ClassCategory1 && 
                                                        ($ClassCategory1->getClassName()->getId() == $ClassCategory2->getClassName()->getId()) 
                                                    ) { 
                                                        $message = trans('admin.common.csv_invalid_not_same', [ 
                                                            '%line%' => $line, 
                                                            '%name1%' => $headerByKey['class_category1'], 
                                                            '%name2%' => $headerByKey['class_category2'], 
                                                        ]); 
                                                        $this->addErrors($message); 
                                                    } 
                                                } 
                                            } else { 
                                                $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category2']]); 
                                                $this->addErrors($message); 
                                            } 
                                        } 
                                    } else { 
                                        if ($pc->getClassCategory1() != null && $pc->getClassCategory2() != null) { 
                                            $message = trans('admin.common.csv_invalid_required', ['%line%' => $line, '%name%' => $headerByKey['class_category2']]); 
                                            $this->addErrors($message); 
                                        } 
                                    } 
                                    $ProductClass = $this->createProductClass($row, $Product, $data, $headerByKey, $ClassCategory1, $ClassCategory2); 
 
                                    if ($this->BaseInfo->isOptionProductDeliveryFee()) { 
                                        if (isset($row[$headerByKey['delivery_fee']]) && StringUtil::isNotBlank($row[$headerByKey['delivery_fee']])) { 
                                            $deliveryFee = str_replace(',', '', $row[$headerByKey['delivery_fee']]); 
                                            $errors = $this->validator->validate($deliveryFee, new GreaterThanOrEqual(['value' => 0])); 
                                            if ($errors->count() === 0) { 
                                                $ProductClass->setDeliveryFee($deliveryFee); 
                                            } else { 
                                                $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['delivery_fee']]); 
                                                $this->addErrors($message); 
                                            } 
                                        } 
                                    } 
 
                                    // 商品別税率機能が有効の場合に税率を更新 
                                    if ($this->BaseInfo->isOptionProductTaxRule()) { 
                                        if (isset($row[$headerByKey['tax_rate']]) && StringUtil::isNotBlank($row[$headerByKey['tax_rate']])) { 
                                            $taxRate = $row[$headerByKey['tax_rate']]; 
                                            $errors = $this->validator->validate($taxRate, new GreaterThanOrEqual(['value' => 0])); 
                                            if ($errors->count() === 0) { 
                                                $TaxRule = $this->taxRuleRepository->newTaxRule(); 
                                                $TaxRule->setTaxRate($taxRate); 
                                                $TaxRule->setApplyDate(new \DateTime()); 
                                                $TaxRule->setProduct($Product); 
                                                $TaxRule->setProductClass($ProductClass); 
                                                $ProductClass->setTaxRule($TaxRule); 
                                            } else { 
                                                $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['tax_rate']]); 
                                                $this->addErrors($message); 
                                            } 
                                        } 
                                    } 
 
                                    $Product->addProductClass($ProductClass); 
                                } 
                            } 
                        } 
                        if ($this->hasErrors()) { 
                            return $this->renderWithError($form, $headers); 
                        } 
                        $this->entityManager->persist($Product); 
                    } 
                    $this->entityManager->flush(); 
                    $this->entityManager->getConnection()->commit(); 
 
                    // 画像ファイルの削除(commit後に削除させる) 
                    foreach ($deleteImages as $images) { 
                        /** @var ProductImage $image */ 
                        foreach ($images as $image) { 
                            if ($this->productImageRepository->findOneBy(['file_name' => $image->getFileName()])) { 
                                continue; 
                            } 
                            try { 
                                $fs = new Filesystem(); 
                                $fs->remove($this->eccubeConfig['eccube_save_image_dir'].'/'.$image); 
                            } catch (\Exception $e) { 
                                // エラーが発生しても無視する 
                            } 
                        } 
                    } 
 
                    log_info('商品CSV登録完了'); 
                    if (!$this->isSplitCsv) { 
                        $message = 'admin.common.csv_upload_complete'; 
                        $this->session->getFlashBag()->add('eccube.admin.success', $message); 
                    } 
 
                    $cacheUtil->clearDoctrineCache(); 
                } 
            } 
        } 
 
        return $this->renderWithError($form, $headers); 
    } 
 
    /** 
     * カテゴリ登録CSVアップロード 
     * 
     * @Route("/%eccube_admin_route%/product/category_csv_upload", name="admin_product_category_csv_import", methods={"GET", "POST"}) 
     * @Template("@admin/Product/csv_category.twig") 
     */ 
    public function csvCategory(Request $request, CacheUtil $cacheUtil) 
    { 
        $form = $this->formFactory->createBuilder(CsvImportType::class)->getForm(); 
 
        $headers = $this->getCategoryCsvHeader(); 
        if ('POST' === $request->getMethod()) { 
            $form->handleRequest($request); 
            if ($form->isValid()) { 
                $formFile = $form['import_file']->getData(); 
                if (!empty($formFile)) { 
                    log_info('カテゴリCSV登録開始'); 
                    $data = $this->getImportData($formFile); 
                    if ($data === false) { 
                        $this->addErrors(trans('admin.common.csv_invalid_format')); 
 
                        return $this->renderWithError($form, $headers, false); 
                    } 
 
                    $getId = function ($item) { 
                        return $item['id']; 
                    }; 
                    $requireHeader = array_keys(array_map($getId, array_filter($headers, function ($value) { 
                        return $value['required']; 
                    }))); 
 
                    $headerByKey = array_flip(array_map($getId, $headers)); 
 
                    $columnHeaders = $data->getColumnHeaders(); 
                    if (count(array_diff($requireHeader, $columnHeaders)) > 0) { 
                        $this->addErrors(trans('admin.common.csv_invalid_format')); 
 
                        return $this->renderWithError($form, $headers, false); 
                    } 
 
                    $size = count($data); 
                    if ($size < 1) { 
                        $this->addErrors(trans('admin.common.csv_invalid_no_data')); 
 
                        return $this->renderWithError($form, $headers, false); 
                    } 
                    $this->entityManager->getConfiguration()->setSQLLogger(null); 
                    $this->entityManager->getConnection()->beginTransaction(); 
                    // CSVファイルの登録処理 
                    foreach ($data as $row) { 
                        /** @var $Category Category */ 
                        $Category = new Category(); 
                        if (isset($row[$headerByKey['id']]) && strlen($row[$headerByKey['id']]) > 0) { 
                            if (!preg_match('/^\d+$/', $row[$headerByKey['id']])) { 
                                $this->addErrors(($data->key() + 1).'行目のカテゴリIDが存在しません。'); 
 
                                return $this->renderWithError($form, $headers); 
                            } 
                            $Category = $this->categoryRepository->find($row[$headerByKey['id']]); 
                            if (!$Category) { 
                                $this->addErrors(($data->key() + 1).'行目の更新対象のカテゴリIDが存在しません。新規登録の場合は、カテゴリIDの値を空で登録してください。'); 
 
                                return $this->renderWithError($form, $headers); 
                            } 
                            if ($row[$headerByKey['id']] == $row[$headerByKey['parent_category_id']]) { 
                                $this->addErrors(($data->key() + 1).'行目のカテゴリIDと親カテゴリIDが同じです。'); 
 
                                return $this->renderWithError($form, $headers); 
                            } 
                        } 
 
                        if (isset($row[$headerByKey['category_del_flg']]) && StringUtil::isNotBlank($row[$headerByKey['category_del_flg']])) { 
                            if (StringUtil::trimAll($row[$headerByKey['category_del_flg']]) == 1) { 
                                if ($Category->getId()) { 
                                    log_info('カテゴリ削除開始', [$Category->getId()]); 
                                    try { 
                                        $this->categoryRepository->delete($Category); 
                                        log_info('カテゴリ削除完了', [$Category->getId()]); 
                                    } catch (ForeignKeyConstraintViolationException $e) { 
                                        log_info('カテゴリ削除エラー', [$Category->getId(), $e]); 
                                        $message = trans('admin.common.delete_error_foreign_key', ['%name%' => $Category->getName()]); 
                                        $this->addError($message, 'admin'); 
 
                                        return $this->renderWithError($form, $headers); 
                                    } 
                                } 
 
                                continue; 
                            } 
                        } 
 
                        if (!isset($row[$headerByKey['category_name']]) || StringUtil::isBlank($row[$headerByKey['category_name']])) { 
                            $this->addErrors(($data->key() + 1).'行目のカテゴリ名が設定されていません。'); 
 
                            return $this->renderWithError($form, $headers); 
                        } else { 
                            $Category->setName(StringUtil::trimAll($row[$headerByKey['category_name']])); 
                        } 
 
                        $ParentCategory = null; 
                        if (isset($row[$headerByKey['parent_category_id']]) && StringUtil::isNotBlank($row[$headerByKey['parent_category_id']])) { 
                            if (!preg_match('/^\d+$/', $row[$headerByKey['parent_category_id']])) { 
                                $this->addErrors(($data->key() + 1).'行目の親カテゴリIDが存在しません。'); 
 
                                return $this->renderWithError($form, $headers); 
                            } 
 
                            /** @var $ParentCategory Category */ 
                            $ParentCategory = $this->categoryRepository->find($row[$headerByKey['parent_category_id']]); 
                            if (!$ParentCategory) { 
                                $this->addErrors(($data->key() + 1).'行目の親カテゴリIDが存在しません。'); 
 
                                return $this->renderWithError($form, $headers); 
                            } 
                        } 
                        $Category->setParent($ParentCategory); 
 
                        // Level 
                        if (isset($row['階層']) && StringUtil::isNotBlank($row['階層'])) { 
                            if ($ParentCategory == null && $row['階層'] != 1) { 
                                $this->addErrors(($data->key() + 1).'行目の親カテゴリIDが存在しません。'); 
 
                                return $this->renderWithError($form, $headers); 
                            } 
                            $level = StringUtil::trimAll($row['階層']); 
                        } else { 
                            $level = 1; 
                            if ($ParentCategory) { 
                                $level = $ParentCategory->getHierarchy() + 1; 
                            } 
                        } 
 
                        $Category->setHierarchy($level); 
 
                        if ($this->eccubeConfig['eccube_category_nest_level'] < $Category->getHierarchy()) { 
                            $this->addErrors(($data->key() + 1).'行目のカテゴリが最大レベルを超えているため設定できません。'); 
 
                            return $this->renderWithError($form, $headers); 
                        } 
 
                        if ($this->hasErrors()) { 
                            return $this->renderWithError($form, $headers); 
                        } 
                        $this->entityManager->persist($Category); 
                        $this->categoryRepository->save($Category); 
                    } 
 
                    $this->entityManager->getConnection()->commit(); 
                    log_info('カテゴリCSV登録完了'); 
                    $message = 'admin.common.csv_upload_complete'; 
                    $this->session->getFlashBag()->add('eccube.admin.success', $message); 
 
                    $cacheUtil->clearDoctrineCache(); 
                } 
            } 
        } 
 
        return $this->renderWithError($form, $headers); 
    } 
 
    /** 
     * アップロード用CSV雛形ファイルダウンロード 
     * 
     * @Route("/%eccube_admin_route%/product/csv_template/{type}", requirements={"type" = "\w+"}, name="admin_product_csv_template", methods={"GET"}) 
     * 
     * @param $type 
     * 
     * @return StreamedResponse 
     */ 
    public function csvTemplate(Request $request, $type) 
    { 
        if ($type == 'product') { 
            $headers = $this->getProductCsvHeader(); 
            $filename = 'product.csv'; 
        } elseif ($type == 'category') { 
            $headers = $this->getCategoryCsvHeader(); 
            $filename = 'category.csv'; 
        } else { 
            throw new NotFoundHttpException(); 
        } 
 
        return $this->sendTemplateResponse($request, array_keys($headers), $filename); 
    } 
 
    /** 
     * 登録、更新時のエラー画面表示 
     * 
     * @param FormInterface $form 
     * @param array $headers 
     * @param bool $rollback 
     * 
     * @return array 
     * 
     * @throws \Doctrine\DBAL\ConnectionException 
     */ 
    protected function renderWithError($form, $headers, $rollback = true) 
    { 
        if ($this->hasErrors()) { 
            if ($rollback) { 
                $this->entityManager->getConnection()->rollback(); 
            } 
        } 
 
        $this->removeUploadedFile(); 
 
        if ($this->isSplitCsv) { 
            return $this->json([ 
                'success' => !$this->hasErrors(), 
                'success_message' => trans('admin.common.csv_upload_line_success', [ 
                    '%from%' => $this->convertLineNo(2), 
                    '%to%' => $this->currentLineNo, ]), 
                'errors' => $this->errors, 
                'error_message' => trans('admin.common.csv_upload_line_error', [ 
                    '%from%' => $this->convertLineNo(2), ]), 
            ]); 
        } 
 
        return [ 
            'form' => $form->createView(), 
            'headers' => $headers, 
            'errors' => $this->errors, 
        ]; 
    } 
 
    /** 
     * 商品画像の削除、登録 
     * 
     * @param $row 
     * @param Product $Product 
     * @param CsvImportService $data 
     * @param $headerByKey 
     */ 
    protected function createProductImage($row, Product $Product, $data, $headerByKey) 
    { 
        if (!isset($row[$headerByKey['product_image']])) { 
            return; 
        } 
        if (StringUtil::isNotBlank($row[$headerByKey['product_image']])) { 
            // 画像の削除 
            $ProductImages = $Product->getProductImage(); 
            foreach ($ProductImages as $ProductImage) { 
                $Product->removeProductImage($ProductImage); 
                $this->entityManager->remove($ProductImage); 
            } 
 
            // 画像の登録 
            $images = explode(',', $row[$headerByKey['product_image']]); 
 
            $sortNo = 1; 
 
            $pattern = "/\\$|^.*.\.\\\.*|\/$|^.*.\.\/\.*/"; 
            foreach ($images as $image) { 
                $fileName = StringUtil::trimAll($image); 
 
                // 商品画像名のフォーマットチェック 
                if (strlen($fileName) > 0 && preg_match($pattern, $fileName)) { 
                    $message = trans('admin.common.csv_invalid_image', ['%line%' => $data->key() + 1, '%name%' => $headerByKey['product_image']]); 
                    $this->addErrors($message); 
                } else { 
                    // 空文字は登録対象外 
                    if (!empty($fileName)) { 
                        $ProductImage = new ProductImage(); 
                        $ProductImage->setFileName($fileName); 
                        $ProductImage->setProduct($Product); 
                        $ProductImage->setSortNo($sortNo); 
 
                        $Product->addProductImage($ProductImage); 
                        $sortNo++; 
                        $this->entityManager->persist($ProductImage); 
                    } 
                } 
            } 
        } 
    } 
 
    /** 
     * 商品カテゴリの削除、登録 
     * 
     * @param $row 
     * @param Product $Product 
     * @param CsvImportService $data 
     * @param $headerByKey 
     */ 
    protected function createProductCategory($row, Product $Product, $data, $headerByKey) 
    { 
        if (!isset($row[$headerByKey['product_category']])) { 
            return; 
        } 
        // カテゴリの削除 
        $ProductCategories = $Product->getProductCategories(); 
        foreach ($ProductCategories as $ProductCategory) { 
            $Product->removeProductCategory($ProductCategory); 
            $this->entityManager->remove($ProductCategory); 
            $this->entityManager->flush(); 
        } 
 
        if (StringUtil::isNotBlank($row[$headerByKey['product_category']])) { 
            // カテゴリの登録 
            $categories = explode(',', $row[$headerByKey['product_category']]); 
            $sortNo = 1; 
            $categoriesIdList = []; 
            foreach ($categories as $category) { 
                $line = $data->key() + 1; 
                if (preg_match('/^\d+$/', $category)) { 
                    $Category = $this->categoryRepository->find($category); 
                    if (!$Category) { 
                        $message = trans('admin.common.csv_invalid_not_found_target', [ 
                            '%line%' => $line, 
                            '%name%' => $headerByKey['product_category'], 
                            '%target_name%' => $category, 
                        ]); 
                        $this->addErrors($message); 
                    } else { 
                        foreach ($Category->getPath() as $ParentCategory) { 
                            if (!isset($categoriesIdList[$ParentCategory->getId()])) { 
                                $ProductCategory = $this->makeProductCategory($Product, $ParentCategory, $sortNo); 
                                $this->entityManager->persist($ProductCategory); 
                                $sortNo++; 
 
                                $Product->addProductCategory($ProductCategory); 
                                $categoriesIdList[$ParentCategory->getId()] = true; 
                            } 
                        } 
                        if (!isset($categoriesIdList[$Category->getId()])) { 
                            $ProductCategory = $this->makeProductCategory($Product, $Category, $sortNo); 
                            $sortNo++; 
                            $this->entityManager->persist($ProductCategory); 
                            $Product->addProductCategory($ProductCategory); 
                            $categoriesIdList[$Category->getId()] = true; 
                        } 
                    } 
                } else { 
                    $message = trans('admin.common.csv_invalid_not_found_target', [ 
                        '%line%' => $line, 
                        '%name%' => $headerByKey['product_category'], 
                        '%target_name%' => $category, 
                    ]); 
                    $this->addErrors($message); 
                } 
            } 
        } 
    } 
 
    /** 
     * タグの登録 
     * 
     * @param array $row 
     * @param Product $Product 
     * @param CsvImportService $data 
     */ 
    protected function createProductTag($row, Product $Product, $data, $headerByKey) 
    { 
        if (!isset($row[$headerByKey['product_tag']])) { 
            return; 
        } 
        // タグの削除 
        $ProductTags = $Product->getProductTag(); 
        foreach ($ProductTags as $ProductTag) { 
            $Product->removeProductTag($ProductTag); 
            $this->entityManager->remove($ProductTag); 
        } 
 
        if (StringUtil::isNotBlank($row[$headerByKey['product_tag']])) { 
            // タグの登録 
            $tags = explode(',', $row[$headerByKey['product_tag']]); 
            foreach ($tags as $tag_id) { 
                $Tag = null; 
                if (preg_match('/^\d+$/', $tag_id)) { 
                    $Tag = $this->tagRepository->find($tag_id); 
 
                    if ($Tag) { 
                        $ProductTags = new ProductTag(); 
                        $ProductTags 
                            ->setProduct($Product) 
                            ->setTag($Tag); 
 
                        $Product->addProductTag($ProductTags); 
 
                        $this->entityManager->persist($ProductTags); 
                    } 
                } 
                if (!$Tag) { 
                    $message = trans('admin.common.csv_invalid_not_found_target', [ 
                        '%line%' => $data->key() + 1, 
                        '%name%' => $headerByKey['product_tag'], 
                        '%target_name%' => $tag_id, 
                    ]); 
                    $this->addErrors($message); 
                } 
            } 
        } 
    } 
 
    /** 
     * 商品規格分類1、商品規格分類2がnullとなる商品規格情報を作成 
     * 
     * @param $row 
     * @param Product $Product 
     * @param CsvImportService $data 
     * @param $headerByKey 
     * @param null $ClassCategory1 
     * @param null $ClassCategory2 
     * 
     * @return ProductClass 
     */ 
    protected function createProductClass($row, Product $Product, $data, $headerByKey, $ClassCategory1 = null, $ClassCategory2 = null) 
    { 
        // 規格分類1、規格分類2がnullとなる商品を作成 
        $ProductClass = new ProductClass(); 
        $ProductClass->setProduct($Product); 
        $ProductClass->setVisible(true); 
 
        $line = $data->key() + 1; 
        if (isset($row[$headerByKey['sale_type']]) && StringUtil::isNotBlank($row[$headerByKey['sale_type']])) { 
            if (preg_match('/^\d+$/', $row[$headerByKey['sale_type']])) { 
                $SaleType = $this->saleTypeRepository->find($row[$headerByKey['sale_type']]); 
                if (!$SaleType) { 
                    $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['sale_type']]); 
                    $this->addErrors($message); 
                } else { 
                    $ProductClass->setSaleType($SaleType); 
                } 
            } else { 
                $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['sale_type']]); 
                $this->addErrors($message); 
            } 
        } else { 
            $message = trans('admin.common.csv_invalid_required', ['%line%' => $line, '%name%' => $headerByKey['sale_type']]); 
            $this->addErrors($message); 
        } 
 
        $ProductClass->setClassCategory1($ClassCategory1); 
        $ProductClass->setClassCategory2($ClassCategory2); 
 
        if (isset($row[$headerByKey['delivery_date']]) && StringUtil::isNotBlank($row[$headerByKey['delivery_date']])) { 
            if (preg_match('/^\d+$/', $row[$headerByKey['delivery_date']])) { 
                $DeliveryDuration = $this->deliveryDurationRepository->find($row[$headerByKey['delivery_date']]); 
                if (!$DeliveryDuration) { 
                    $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['delivery_date']]); 
                    $this->addErrors($message); 
                } else { 
                    $ProductClass->setDeliveryDuration($DeliveryDuration); 
                } 
            } else { 
                $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['delivery_date']]); 
                $this->addErrors($message); 
            } 
        } 
 
        if (isset($row[$headerByKey['product_code']])) { 
            if (StringUtil::isNotBlank($row[$headerByKey['product_code']])) { 
                $ProductClass->setCode(StringUtil::trimAll($row[$headerByKey['product_code']])); 
            } else { 
                $ProductClass->setCode(null); 
            } 
        } 
 
        if (!isset($row[$headerByKey['stock_unlimited']]) 
            || StringUtil::isBlank($row[$headerByKey['stock_unlimited']]) 
            || $row[$headerByKey['stock_unlimited']] == (string) Constant::DISABLED 
        ) { 
            $ProductClass->setStockUnlimited(false); 
            // 在庫数が設定されていなければエラー 
            if (isset($row[$headerByKey['stock']]) && StringUtil::isNotBlank($row[$headerByKey['stock']])) { 
                $stock = str_replace(',', '', $row[$headerByKey['stock']]); 
                if (preg_match('/^\d+$/', $stock) && $stock >= 0) { 
                    $ProductClass->setStock($stock); 
                } else { 
                    $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['stock']]); 
                    $this->addErrors($message); 
                } 
            } else { 
                $message = trans('admin.common.csv_invalid_required', ['%line%' => $line, '%name%' => $headerByKey['stock']]); 
                $this->addErrors($message); 
            } 
        } elseif ($row[$headerByKey['stock_unlimited']] == (string) Constant::ENABLED) { 
            $ProductClass->setStockUnlimited(true); 
            $ProductClass->setStock(null); 
        } else { 
            $message = trans('admin.common.csv_invalid_required', ['%line%' => $line, '%name%' => $headerByKey['stock_unlimited']]); 
            $this->addErrors($message); 
        } 
 
        if (isset($row[$headerByKey['sale_limit']]) && StringUtil::isNotBlank($row[$headerByKey['sale_limit']])) { 
            $saleLimit = str_replace(',', '', $row[$headerByKey['sale_limit']]); 
            if (preg_match('/^\d+$/', $saleLimit) && $saleLimit >= 0) { 
                $ProductClass->setSaleLimit($saleLimit); 
            } else { 
                $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['sale_limit']]); 
                $this->addErrors($message); 
            } 
        } 
 
        if (isset($row[$headerByKey['price01']]) && StringUtil::isNotBlank($row[$headerByKey['price01']])) { 
            $price01 = str_replace(',', '', $row[$headerByKey['price01']]); 
            $errors = $this->validator->validate($price01, new GreaterThanOrEqual(['value' => 0])); 
            if ($errors->count() === 0) { 
                $ProductClass->setPrice01($price01); 
            } else { 
                $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['price01']]); 
                $this->addErrors($message); 
            } 
        } 
 
        if (isset($row[$headerByKey['price02']]) && StringUtil::isNotBlank($row[$headerByKey['price02']])) { 
            $price02 = str_replace(',', '', $row[$headerByKey['price02']]); 
            $errors = $this->validator->validate($price02, new GreaterThanOrEqual(['value' => 0])); 
            if ($errors->count() === 0) { 
                $ProductClass->setPrice02($price02); 
            } else { 
                $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['price02']]); 
                $this->addErrors($message); 
            } 
        } else { 
            $message = trans('admin.common.csv_invalid_required', ['%line%' => $line, '%name%' => $headerByKey['price02']]); 
            $this->addErrors($message); 
        } 
 
        if ($this->BaseInfo->isOptionProductDeliveryFee()) { 
            if (isset($row[$headerByKey['delivery_fee']]) && StringUtil::isNotBlank($row[$headerByKey['delivery_fee']])) { 
                $delivery_fee = str_replace(',', '', $row[$headerByKey['delivery_fee']]); 
                $errors = $this->validator->validate($delivery_fee, new GreaterThanOrEqual(['value' => 0])); 
                if ($errors->count() === 0) { 
                    $ProductClass->setDeliveryFee($delivery_fee); 
                } else { 
                    $message = trans('admin.common.csv_invalid_greater_than_zero', 
                        ['%line%' => $line, '%name%' => $headerByKey['delivery_fee']]); 
                    $this->addErrors($message); 
                } 
            } 
        } 
 
        $Product->addProductClass($ProductClass); 
        $ProductStock = new ProductStock(); 
        $ProductClass->setProductStock($ProductStock); 
        $ProductStock->setProductClass($ProductClass); 
 
        if (!$ProductClass->isStockUnlimited()) { 
            $ProductStock->setStock($ProductClass->getStock()); 
        } else { 
            // 在庫無制限時はnullを設定 
            $ProductStock->setStock(null); 
        } 
 
        $this->entityManager->persist($ProductClass); 
        $this->entityManager->persist($ProductStock); 
 
        return $ProductClass; 
    } 
 
    /** 
     * 商品規格情報を更新 
     * 
     * @param $row 
     * @param Product $Product 
     * @param ProductClass $ProductClass 
     * @param CsvImportService $data 
     * 
     * @return ProductClass 
     */ 
    protected function updateProductClass($row, Product $Product, ProductClass $ProductClass, $data, $headerByKey) 
    { 
        $ProductClass->setProduct($Product); 
 
        $line = $data->key() + 1; 
        if (!isset($row[$headerByKey['sale_type']]) || $row[$headerByKey['sale_type']] == '') { 
            $message = trans('admin.common.csv_invalid_required', ['%line%' => $line, '%name%' => $headerByKey['sale_type']]); 
            $this->addErrors($message); 
        } else { 
            if (preg_match('/^\d+$/', $row[$headerByKey['sale_type']])) { 
                $SaleType = $this->saleTypeRepository->find($row[$headerByKey['sale_type']]); 
                if (!$SaleType) { 
                    $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['sale_type']]); 
                    $this->addErrors($message); 
                } else { 
                    $ProductClass->setSaleType($SaleType); 
                } 
            } else { 
                $message = trans('admin.common.csv_invalid_required', ['%line%' => $line, '%name%' => $headerByKey['sale_type']]); 
                $this->addErrors($message); 
            } 
        } 
 
        // 規格分類1、2をそれぞれセットし作成 
        if (isset($row[$headerByKey['class_category1']]) && $row[$headerByKey['class_category1']] != '') { 
            if (preg_match('/^\d+$/', $row[$headerByKey['class_category1']])) { 
                $ClassCategory = $this->classCategoryRepository->find($row[$headerByKey['class_category1']]); 
                if (!$ClassCategory) { 
                    $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category1']]); 
                    $this->addErrors($message); 
                } else { 
                    $ProductClass->setClassCategory1($ClassCategory); 
                } 
            } else { 
                $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category1']]); 
                $this->addErrors($message); 
            } 
        } 
 
        if (isset($row[$headerByKey['class_category2']]) && $row[$headerByKey['class_category2']] != '') { 
            if (preg_match('/^\d+$/', $row[$headerByKey['class_category2']])) { 
                $ClassCategory = $this->classCategoryRepository->find($row[$headerByKey['class_category2']]); 
                if (!$ClassCategory) { 
                    $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category2']]); 
                    $this->addErrors($message); 
                } else { 
                    $ProductClass->setClassCategory2($ClassCategory); 
                } 
            } else { 
                $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['class_category2']]); 
                $this->addErrors($message); 
            } 
        } 
 
        if (isset($row[$headerByKey['delivery_date']]) && $row[$headerByKey['delivery_date']] != '') { 
            if (preg_match('/^\d+$/', $row[$headerByKey['delivery_date']])) { 
                $DeliveryDuration = $this->deliveryDurationRepository->find($row[$headerByKey['delivery_date']]); 
                if (!$DeliveryDuration) { 
                    $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['delivery_date']]); 
                    $this->addErrors($message); 
                } else { 
                    $ProductClass->setDeliveryDuration($DeliveryDuration); 
                } 
            } else { 
                $message = trans('admin.common.csv_invalid_not_found', ['%line%' => $line, '%name%' => $headerByKey['delivery_date']]); 
                $this->addErrors($message); 
            } 
        } 
 
        if (isset($row[$headerByKey['product_code']])) { 
            if (StringUtil::isNotBlank($row[$headerByKey['product_code']])) { 
                $ProductClass->setCode(StringUtil::trimAll($row[$headerByKey['product_code']])); 
            } else { 
                $ProductClass->setCode(null); 
            } 
        } 
 
        if (!isset($row[$headerByKey['stock_unlimited']]) 
            || StringUtil::isBlank($row[$headerByKey['stock_unlimited']]) 
            || $row[$headerByKey['stock_unlimited']] == (string) Constant::DISABLED 
        ) { 
            $ProductClass->setStockUnlimited(false); 
            // 在庫数が設定されていなければエラー 
            if ($row[$headerByKey['stock']] == '') { 
                $message = trans('admin.common.csv_invalid_required', ['%line%' => $line, '%name%' => $headerByKey['stock']]); 
                $this->addErrors($message); 
            } else { 
                $stock = str_replace(',', '', $row[$headerByKey['stock']]); 
                if (preg_match('/^\d+$/', $stock) && $stock >= 0) { 
                    $ProductClass->setStock($row[$headerByKey['stock']]); 
                } else { 
                    $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['stock']]); 
                    $this->addErrors($message); 
                } 
            } 
        } elseif ($row[$headerByKey['stock_unlimited']] == (string) Constant::ENABLED) { 
            $ProductClass->setStockUnlimited(true); 
            $ProductClass->setStock(null); 
        } else { 
            $message = trans('admin.common.csv_invalid_required', ['%line%' => $line, '%name%' => $headerByKey['stock_unlimited']]); 
            $this->addErrors($message); 
        } 
 
        if (isset($row[$headerByKey['sale_limit']])) { 
            if ($row[$headerByKey['sale_limit']] != '') { 
                $saleLimit = str_replace(',', '', $row[$headerByKey['sale_limit']]); 
                if (preg_match('/^\d+$/', $saleLimit) && $saleLimit >= 0) { 
                    $ProductClass->setSaleLimit($saleLimit); 
                } else { 
                    $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['sale_limit']]); 
                    $this->addErrors($message); 
                } 
            } else { 
                $ProductClass->setSaleLimit(null); 
            } 
        } 
 
        if (isset($row[$headerByKey['price01']])) { 
            if ($row[$headerByKey['price01']] != '') { 
                $price01 = str_replace(',', '', $row[$headerByKey['price01']]); 
                $errors = $this->validator->validate($price01, new GreaterThanOrEqual(['value' => 0])); 
                if ($errors->count() === 0) { 
                    $ProductClass->setPrice01($price01); 
                } else { 
                    $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['price01']]); 
                    $this->addErrors($message); 
                } 
            } else { 
                $ProductClass->setPrice01(null); 
            } 
        } 
 
        if (!isset($row[$headerByKey['price02']]) || $row[$headerByKey['price02']] == '') { 
            $message = trans('admin.common.csv_invalid_required', ['%line%' => $line, '%name%' => $headerByKey['price02']]); 
            $this->addErrors($message); 
        } else { 
            $price02 = str_replace(',', '', $row[$headerByKey['price02']]); 
            $errors = $this->validator->validate($price02, new GreaterThanOrEqual(['value' => 0])); 
            if ($errors->count() === 0) { 
                $ProductClass->setPrice02($price02); 
            } else { 
                $message = trans('admin.common.csv_invalid_greater_than_zero', ['%line%' => $line, '%name%' => $headerByKey['price02']]); 
                $this->addErrors($message); 
            } 
        } 
 
        $ProductStock = $ProductClass->getProductStock(); 
 
        // 在庫テーブルに存在しない場合、新規作成 
        if (!$ProductStock instanceof ProductStock) { 
            $ProductStock = new ProductStock(); 
            $ProductClass->setProductStock($ProductStock); 
            $ProductStock->setProductClass($ProductClass); 
        } 
 
        if (!$ProductClass->isStockUnlimited()) { 
            $ProductStock->setStock($ProductClass->getStock()); 
        } else { 
            // 在庫無制限時はnullを設定 
            $ProductStock->setStock(null); 
        } 
 
        if (isset($row[$headerByKey['product_class_visible_flg']]) 
            && StringUtil::isNotBlank($row[$headerByKey['product_class_visible_flg']])) { 
            $ProductClass->setVisible((bool) $row[$headerByKey['product_class_visible_flg']]); 
        } 
 
        return $ProductClass; 
    } 
 
    /** 
     * 登録、更新時のエラー画面表示 
     */ 
    protected function addErrors($message) 
    { 
        $this->errors[] = $message; 
    } 
 
    /** 
     * @return array 
     */ 
    protected function getErrors() 
    { 
        return $this->errors; 
    } 
 
    /** 
     * @return boolean 
     */ 
    protected function hasErrors() 
    { 
        return count($this->getErrors()) > 0; 
    } 
 
    /** 
     * 商品登録CSVヘッダー定義 
     * 
     * @return array 
     */ 
    protected function getProductCsvHeader() 
    { 
        return [ 
            trans('admin.product.product_csv.product_id_col') => [ 
                'id' => 'id', 
                'description' => 'admin.product.product_csv.product_id_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.display_status_col') => [ 
                'id' => 'status', 
                'description' => 'admin.product.product_csv.display_status_description', 
                'required' => true, 
            ], 
            trans('admin.product.product_csv.product_name_col') => [ 
                'id' => 'name', 
                'description' => 'admin.product.product_csv.product_name_description', 
                'required' => true, 
            ], 
            trans('admin.product.product_csv.shop_memo_col') => [ 
                'id' => 'note', 
                'description' => 'admin.product.product_csv.shop_memo_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.description_list_col') => [ 
                'id' => 'description_list', 
                'description' => 'admin.product.product_csv.description_list_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.description_detail_col') => [ 
                'id' => 'description_detail', 
                'description' => 'admin.product.product_csv.description_detail_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.keyword_col') => [ 
                'id' => 'search_word', 
                'description' => 'admin.product.product_csv.keyword_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.free_area_col') => [ 
                'id' => 'free_area', 
                'description' => 'admin.product.product_csv.free_area_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.delete_flag_col') => [ 
                'id' => 'product_del_flg', 
                'description' => 'admin.product.product_csv.delete_flag_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.product_image_col') => [ 
                'id' => 'product_image', 
                'description' => 'admin.product.product_csv.product_image_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.category_col') => [ 
                'id' => 'product_category', 
                'description' => 'admin.product.product_csv.category_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.tag_col') => [ 
                'id' => 'product_tag', 
                'description' => 'admin.product.product_csv.tag_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.sale_type_col') => [ 
                'id' => 'sale_type', 
                'description' => 'admin.product.product_csv.sale_type_description', 
                'required' => true, 
            ], 
            trans('admin.product.product_csv.class_category1_col') => [ 
                'id' => 'class_category1', 
                'description' => 'admin.product.product_csv.class_category1_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.class_category2_col') => [ 
                'id' => 'class_category2', 
                'description' => 'admin.product.product_csv.class_category2_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.delivery_duration_col') => [ 
                'id' => 'delivery_date', 
                'description' => 'admin.product.product_csv.delivery_duration_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.product_code_col') => [ 
                'id' => 'product_code', 
                'description' => 'admin.product.product_csv.product_code_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.stock_col') => [ 
                'id' => 'stock', 
                'description' => 'admin.product.product_csv.stock_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.stock_unlimited_col') => [ 
                'id' => 'stock_unlimited', 
                'description' => 'admin.product.product_csv.stock_unlimited_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.sale_limit_col') => [ 
                'id' => 'sale_limit', 
                'description' => 'admin.product.product_csv.sale_limit_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.normal_price_col') => [ 
                'id' => 'price01', 
                'description' => 'admin.product.product_csv.normal_price_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.sale_price_col') => [ 
                'id' => 'price02', 
                'description' => 'admin.product.product_csv.sale_price_description', 
                'required' => true, 
            ], 
            trans('admin.product.product_csv.delivery_fee_col') => [ 
                'id' => 'delivery_fee', 
                'description' => 'admin.product.product_csv.delivery_fee_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.tax_rate_col') => [ 
                'id' => 'tax_rate', 
                'description' => 'admin.product.product_csv.tax_rate_description', 
                'required' => false, 
            ], 
            trans('admin.product.product_csv.product_class_visible_flag_col') => [ 
                'id' => 'product_class_visible_flg', 
                'description' => 'admin.product.product_csv.product_class_visible_flag_description', 
                'required' => false, 
            ], 
        ]; 
    } 
 
    /** 
     * カテゴリCSVヘッダー定義 
     */ 
    protected function getCategoryCsvHeader() 
    { 
        return [ 
            trans('admin.product.category_csv.category_id_col') => [ 
                'id' => 'id', 
                'description' => 'admin.product.category_csv.category_id_description', 
                'required' => false, 
            ], 
            trans('admin.product.category_csv.category_name_col') => [ 
                'id' => 'category_name', 
                'description' => 'admin.product.category_csv.category_name_description', 
                'required' => true, 
            ], 
            trans('admin.product.category_csv.parent_category_id_col') => [ 
                'id' => 'parent_category_id', 
                'description' => 'admin.product.category_csv.parent_category_id_description', 
                'required' => false, 
            ], 
            trans('admin.product.category_csv.delete_flag_col') => [ 
                'id' => 'category_del_flg', 
                'description' => 'admin.product.category_csv.delete_flag_description', 
                'required' => false, 
            ], 
        ]; 
    } 
 
    /** 
     * ProductCategory作成 
     * 
     * @param \Eccube\Entity\Product $Product 
     * @param \Eccube\Entity\Category $Category 
     * @param int $sortNo 
     * 
     * @return ProductCategory 
     */ 
    private function makeProductCategory($Product, $Category, $sortNo) 
    { 
        $ProductCategory = new ProductCategory(); 
        $ProductCategory->setProduct($Product); 
        $ProductCategory->setProductId($Product->getId()); 
        $ProductCategory->setCategory($Category); 
        $ProductCategory->setCategoryId($Category->getId()); 
 
        return $ProductCategory; 
    } 
 
    /** 
     * @Route("/%eccube_admin_route%/product/csv_split", name="admin_product_csv_split", methods={"POST"}) 
     * 
     * @param Request $request 
     * 
     * @return \Symfony\Component\HttpFoundation\JsonResponse 
     */ 
    public function splitCsv(Request $request) 
    { 
        $this->isTokenValid(); 
 
        if (!$request->isXmlHttpRequest()) { 
            throw new BadRequestHttpException(); 
        } 
 
        $form = $this->formFactory->createBuilder(CsvImportType::class)->getForm(); 
        $form->handleRequest($request); 
 
        if ($form->isSubmitted() && $form->isValid()) { 
            $dir = $this->eccubeConfig['eccube_csv_temp_realdir']; 
            if (!file_exists($dir)) { 
                $fs = new Filesystem(); 
                $fs->mkdir($dir); 
            } 
 
            $data = $form['import_file']->getData(); 
            $file = new \SplFileObject($data->getRealPath()); 
 
            // stream filter を適用して文字エンコーディングと改行コードの変換を行う 
            // see https://github.com/EC-CUBE/ec-cube/issues/5252 
            $filters = [ 
                ConvertLineFeedFilter::class, 
            ]; 
 
            if (!\mb_check_encoding($file->current(), 'UTF-8')) { 
                // UTF-8 が検出できなかった場合は SJIS-win の stream filter を適用する 
                $filters[] = SjisToUtf8EncodingFilter::class; 
            } 
            $src = CsvImportService::applyStreamFilter($file, ...$filters); 
            $src->setFlags(\SplFileObject::READ_CSV | \SplFileObject::READ_AHEAD | \SplFileObject::SKIP_EMPTY); 
 
            $fileNo = 1; 
            $fileName = StringUtil::random(8); 
 
            $dist = new \SplFileObject($dir.'/'.$fileName.$fileNo.'.csv', 'w'); 
            $header = $src->current(); 
            $src->next(); 
            $dist->fputcsv($header); 
 
            $i = 0; 
            while ($row = $src->current()) { 
                $dist->fputcsv($row); 
                $src->next(); 
 
                if (!$src->eof() && ++$i % $this->eccubeConfig['eccube_csv_split_lines'] === 0) { 
                    $fileNo++; 
                    $dist = new \SplFileObject($dir.'/'.$fileName.$fileNo.'.csv', 'w'); 
                    $dist->fputcsv($header); 
                } 
            } 
 
            return $this->json(['success' => true, 'file_name' => $fileName, 'max_file_no' => $fileNo]); 
        } 
 
        return $this->json(['success' => false, 'message' => $form->getErrors(true, true)]); 
    } 
 
    /** 
     * @Route("/%eccube_admin_route%/product/csv_split_import", name="admin_product_csv_split_import", methods={"POST"}) 
     * 
     * @param Request $request 
     * 
     * @return \Symfony\Component\HttpFoundation\JsonResponse 
     */ 
    public function importCsv(Request $request, CsrfTokenManagerInterface $tokenManager) 
    { 
        $this->isTokenValid(); 
 
        if (!$request->isXmlHttpRequest()) { 
            throw new BadRequestHttpException(); 
        } 
 
        $choices = $this->getCsvTempFiles(); 
 
        $filename = $request->get('file_name'); 
        if (!isset($choices[$filename])) { 
            throw new BadRequestHttpException(); 
        } 
 
        $path = $this->eccubeConfig['eccube_csv_temp_realdir'].'/'.$filename; 
        $request->files->set('admin_csv_import', ['import_file' => new UploadedFile( 
            $path, 
            'import.csv', 
            'text/csv', 
            null, 
            true 
        )]); 
 
        $request->setMethod('POST'); 
        $request->request->set('admin_csv_import', [ 
            Constant::TOKEN_NAME => $tokenManager->getToken('admin_csv_import')->getValue(), 
            'is_split_csv' => true, 
            'csv_file_no' => $request->get('file_no'), 
        ]); 
 
        return $this->forwardToRoute('admin_product_csv_import'); 
    } 
 
    /** 
     * @Route("/%eccube_admin_route%/product/csv_split_cleanup", name="admin_product_csv_split_cleanup", methods={"POST"}) 
     * 
     * @param Request $request 
     * 
     * @return \Symfony\Component\HttpFoundation\JsonResponse 
     */ 
    public function cleanupSplitCsv(Request $request) 
    { 
        $this->isTokenValid(); 
 
        if (!$request->isXmlHttpRequest()) { 
            throw new BadRequestHttpException(); 
        } 
 
        $files = $request->get('files', []); 
        $choices = $this->getCsvTempFiles(); 
 
        foreach ($files as $filename) { 
            if (isset($choices[$filename])) { 
                unlink($choices[$filename]); 
            } else { 
                return $this->json(['success' => false]); 
            } 
        } 
 
        return $this->json(['success' => true]); 
    } 
 
    protected function getCsvTempFiles() 
    { 
        $files = Finder::create() 
            ->in($this->eccubeConfig['eccube_csv_temp_realdir']) 
            ->name('*.csv') 
            ->files(); 
 
        $choices = []; 
        foreach ($files as $file) { 
            $choices[$file->getBaseName()] = $file->getRealPath(); 
        } 
 
        return $choices; 
    } 
 
    protected function convertLineNo($currentLineNo) 
    { 
        if ($this->isSplitCsv) { 
            return ($this->eccubeConfig['eccube_csv_split_lines']) * ($this->csvFileNo - 1) + $currentLineNo; 
        } 
 
        return $currentLineNo; 
    } 
}