Completed
Push — 4.0 ( 268f2c...88f012 )
by Hideki
05:48 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) EC-CUBE CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.ec-cube.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(
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(strtolower($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;
0 ignored issues
show
The variable $ParentCategory seems to be defined by a foreach iteration on line 500. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
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