Completed
Push — 4.0 ( b48f64...137622 )
by chihiro
20:21 queued 10s
created

Controller/Admin/Product/ProductController.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Controller\Admin\Product;
15
16
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
17
use Eccube\Common\Constant;
18
use Eccube\Controller\AbstractController;
19
use Eccube\Entity\BaseInfo;
20
use Eccube\Entity\ExportCsvRow;
21
use Eccube\Entity\Master\CsvType;
22
use Eccube\Entity\Master\ProductStatus;
23
use Eccube\Entity\Product;
24
use Eccube\Entity\ProductCategory;
25
use Eccube\Entity\ProductClass;
26
use Eccube\Entity\ProductImage;
27
use Eccube\Entity\ProductStock;
28
use Eccube\Entity\ProductTag;
29
use Eccube\Event\EccubeEvents;
30
use Eccube\Event\EventArgs;
31
use Eccube\Form\Type\Admin\ProductType;
32
use Eccube\Form\Type\Admin\SearchProductType;
33
use Eccube\Repository\BaseInfoRepository;
34
use Eccube\Repository\CategoryRepository;
35
use Eccube\Repository\Master\PageMaxRepository;
36
use Eccube\Repository\Master\ProductStatusRepository;
37
use Eccube\Repository\ProductClassRepository;
38
use Eccube\Repository\ProductImageRepository;
39
use Eccube\Repository\ProductRepository;
40
use Eccube\Repository\TagRepository;
41
use Eccube\Repository\TaxRuleRepository;
42
use Eccube\Service\CsvExportService;
43
use Eccube\Util\CacheUtil;
44
use Eccube\Util\FormUtil;
45
use Knp\Component\Pager\Paginator;
46
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
47
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
48
use Symfony\Component\Filesystem\Filesystem;
49
use Symfony\Component\HttpFoundation\File\File;
50
use Symfony\Component\HttpFoundation\RedirectResponse;
51
use Symfony\Component\HttpFoundation\Request;
52
use Symfony\Component\HttpFoundation\StreamedResponse;
53
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
54
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
55
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
56
use Symfony\Component\Routing\Annotation\Route;
57
use Symfony\Component\Routing\RouterInterface;
58
59
class ProductController extends AbstractController
60
{
61
    /**
62
     * @var CsvExportService
63
     */
64
    protected $csvExportService;
65
66
    /**
67
     * @var ProductClassRepository
68
     */
69
    protected $productClassRepository;
70
71
    /**
72
     * @var ProductImageRepository
73
     */
74
    protected $productImageRepository;
75
76
    /**
77
     * @var TaxRuleRepository
78
     */
79
    protected $taxRuleRepository;
80
81
    /**
82
     * @var CategoryRepository
83
     */
84
    protected $categoryRepository;
85
86
    /**
87
     * @var ProductRepository
88
     */
89
    protected $productRepository;
90
91
    /**
92
     * @var BaseInfo
93
     */
94
    protected $BaseInfo;
95
96
    /**
97
     * @var PageMaxRepository
98
     */
99
    protected $pageMaxRepository;
100
101
    /**
102
     * @var ProductStatusRepository
103
     */
104
    protected $productStatusRepository;
105
106
    /**
107
     * @var TagRepository
108
     */
109
    protected $tagRepository;
110
111
    /**
112
     * ProductController constructor.
113
     *
114
     * @param CsvExportService $csvExportService
115
     * @param ProductClassRepository $productClassRepository
116
     * @param ProductImageRepository $productImageRepository
117
     * @param TaxRuleRepository $taxRuleRepository
118
     * @param CategoryRepository $categoryRepository
119
     * @param ProductRepository $productRepository
120
     * @param BaseInfoRepository $baseInfoRepository
121
     * @param PageMaxRepository $pageMaxRepository
122
     * @param ProductStatusRepository $productStatusRepository
123
     * @param TagRepository $tagRepository
124 26
     */
125 View Code Duplication
    public function __construct(
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

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