Completed
Pull Request — 4.0 (#4903)
by kazunori
05:17
created

ProductController::loadProductClasses()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.1158

Importance

Changes 0
Metric Value
cc 5
nc 4
nop 2
dl 0
loc 23
ccs 10
cts 12
cp 0.8333
crap 5.1158
rs 9.2408
c 0
b 0
f 0
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(
0 ignored issues
show
Duplication introduced by
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.product.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.product.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", options={"repository_method":"findWithSortedClassCategories"})
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()) {
0 ignored issues
show
Documentation Bug introduced by
The method hasProductClass does not exist on object<Eccube\Repository\ProductRepository>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
292 1
            $class = $Product->getProductClasses();
0 ignored issues
show
Documentation Bug introduced by
The method getProductClasses does not exist on object<Eccube\Repository\ProductRepository>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
293 1
            foreach ($class as $item) {
294 1
                $data[] = $item;
295 1
            }
296
        }
297
298
        return [
299
            'data' => $data,
300
        ];
301 1
    }
302
303
    /**
304
     * @Route("/%eccube_admin_route%/product/product/image/add", name="admin_product_image_add", methods={"POST"})
305
     */
306
    public function addImage(Request $request)
307
    {
308
        if (!$request->isXmlHttpRequest()) {
309
            throw new BadRequestHttpException();
310
        }
311
312
        $images = $request->files->get('admin_product');
313
314
        $allowExtensions = ['gif', 'jpg', 'jpeg', 'png'];
315
        $files = [];
316
        if (count($images) > 0) {
317
            foreach ($images as $img) {
318
                foreach ($img as $image) {
319
                    //ファイルフォーマット検証
320
                    $mimeType = $image->getMimeType();
321
                    if (0 !== strpos($mimeType, 'image')) {
322
                        throw new UnsupportedMediaTypeHttpException();
323
                    }
324
325
                    // 拡張子
326
                    $extension = $image->getClientOriginalExtension();
327
                    if (!in_array(strtolower($extension), $allowExtensions)) {
328
                        throw new UnsupportedMediaTypeHttpException();
329
                    }
330
331
                    $filename = date('mdHis').uniqid('_').'.'.$extension;
332
                    $image->move($this->eccubeConfig['eccube_temp_image_dir'], $filename);
333
                    $files[] = $filename;
334
                }
335
            }
336
        }
337
338
        $event = new EventArgs(
339
            [
340
                'images' => $images,
341
                'files' => $files,
342
            ],
343
            $request
344
        );
345
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_ADD_IMAGE_COMPLETE, $event);
346
        $files = $event->getArgument('files');
347
348
        return $this->json(['files' => $files], 200);
349
    }
350
351
    /**
352
     * @Route("/%eccube_admin_route%/product/product/new", name="admin_product_product_new")
353 18
     * @Route("/%eccube_admin_route%/product/product/{id}/edit", requirements={"id" = "\d+"}, name="admin_product_product_edit")
354
     * @Template("@admin/Product/product.twig")
355 18
     */
356 18
    public function edit(Request $request, $id = null, RouterInterface $router, CacheUtil $cacheUtil)
357 5
    {
358 5
        $has_class = false;
359 5
        if (is_null($id)) {
360
            $Product = new Product();
361 5
            $ProductClass = new ProductClass();
362 5
            $ProductStatus = $this->productStatusRepository->find(ProductStatus::DISPLAY_HIDE);
363
            $Product
364 5
                ->addProductClass($ProductClass)
365 5
                ->setStatus($ProductStatus);
0 ignored issues
show
Bug introduced by
It seems like $ProductStatus defined by $this->productStatusRepo...ctStatus::DISPLAY_HIDE) on line 362 can also be of type object; however, Eccube\Entity\Product::setStatus() does only seem to accept null|object<Eccube\Entity\Master\ProductStatus>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
366 5
            $ProductClass
367 5
                ->setVisible(true)
368 5
                ->setStockUnlimited(true)
369 5
                ->setProduct($Product);
370
            $ProductStock = new ProductStock();
371 13
            $ProductClass->setProductStock($ProductStock);
372 13
            $ProductStock->setProductClass($ProductClass);
373
        } else {
374
            $Product = $this->productRepository->findWithSortedClassCategories($id);
375
            $ProductClass = null;
376 13
            $ProductStock = null;
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[$Category->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
                    $this->entityManager->flush();
549 13
550
                    if (!$this->productImageRepository->findOneBy(['file_name' => $delete_image])) {
551 13
                        // 削除
552 13
                        $fs = new Filesystem();
553
                        $fs->remove($this->eccubeConfig['eccube_save_image_dir'] . '/' . $delete_image);
554
                    }
555
                }
556
                $this->entityManager->persist($Product);
557
                $this->entityManager->flush();
558
559
                $sortNos = $request->get('sort_no_images');
560
                if ($sortNos) {
561
                    foreach ($sortNos as $sortNo) {
562
                        list($filename, $sortNo_val) = explode('//', $sortNo);
563
                        $ProductImage = $this->productImageRepository
564 13
                            ->findOneBy([
565
                                'file_name' => $filename,
566
                                'Product' => $Product,
567
                            ]);
568 13
                        $ProductImage->setSortNo($sortNo_val);
569 13
                        $this->entityManager->persist($ProductImage);
570 1
                    }
571 1
                }
572
                $this->entityManager->flush();
573
574
                // 商品タグの登録
575 13
                // 商品タグを一度クリア
576 13
                $ProductTags = $Product->getProductTag();
577 13
                foreach ($ProductTags as $ProductTag) {
578
                    $Product->removeProductTag($ProductTag);
579 13
                    $this->entityManager->remove($ProductTag);
580 13
                }
581 13
582 13
                // 商品タグの登録
583
                $Tags = $form->get('Tag')->getData();
584
                foreach ($Tags as $Tag) {
585 13
                    $ProductTag = new ProductTag();
586 13
                    $ProductTag
587
                        ->setProduct($Product)
588 13
                        ->setTag($Tag);
589
                    $Product->addProductTag($ProductTag);
590 13
                    $this->entityManager->persist($ProductTag);
591
                }
592 13
593 13
                $Product->setUpdateDate(new \DateTime());
594
                $this->entityManager->flush();
595 13
596
                log_info('商品登録完了', [$id]);
597 13
598
                $event = new EventArgs(
599 13
                    [
600
                        'form' => $form,
601 13
                        'Product' => $Product,
602 1
                    ],
603
                    $request
604
                );
605 13
                $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_EDIT_COMPLETE, $event);
606
607
                $this->addSuccess('admin.common.save_complete', 'admin');
608
609 View Code Duplication
                if ($returnLink = $form->get('return_link')->getData()) {
610 5
                    try {
611 5
                        // $returnLinkはpathの形式で渡される. pathが存在するかをルータでチェックする.
612
                        $pattern = '/^'.preg_quote($request->getBasePath(), '/').'/';
613 5
                        $returnLink = preg_replace($pattern, '', $returnLink);
614
                        $result = $router->match($returnLink);
0 ignored issues
show
Bug introduced by
It seems like $returnLink defined by preg_replace($pattern, '', $returnLink) on line 613 can also be of type array<integer,string>; however, Symfony\Component\Routin...tcherInterface::match() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
615 5
                        // パラメータのみ抽出
616 5
                        $params = array_filter($result, function ($key) {
617
                            return 0 !== \strpos($key, '_');
618 5
                        }, ARRAY_FILTER_USE_KEY);
619
620 5
                        // pathからurlを再構築してリダイレクト.
621
                        return $this->redirectToRoute($result['_route'], $params);
622 5
                    } catch (\Exception $e) {
623
                        // マッチしない場合はログ出力してスキップ.
624 5
                        log_warning('URLの形式が不正です。');
625
                    }
626
                }
627
628
                $cacheUtil->clearDoctrineCache();
629 5
630
                return $this->redirectToRoute('admin_product_product_edit', ['id' => $Product->getId()]);
631
            }
632 5
        }
633 5
634 3
        // 検索結果の保持
635 5
        $builder = $this->formFactory
636
            ->createBuilder(SearchProductType::class);
637
638 5
        $event = new EventArgs(
639 5
            [
640 5
                'builder' => $builder,
641 5
                'Product' => $Product,
642 5
            ],
643 5
            $request
644 5
        );
645 5
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_EDIT_SEARCH, $event);
646 5
647
        $searchForm = $builder->getForm();
648
649
        if ('POST' === $request->getMethod()) {
650
            $searchForm->handleRequest($request);
651
        }
652
653
        // Get Tags
654 1
        $TagsList = $this->tagRepository->getList();
655
656 1
        // ツリー表示のため、ルートからのカテゴリを取得
657 1
        $TopCategories = $this->categoryRepository->getList(null);
658 1
        $ChoicedCategoryIds = array_map(function ($Category) {
659 1
            return $Category->getId();
660 1
        }, $form->get('Category')->getData());
661 1
662
        return [
663 1
            'Product' => $Product,
664
            'Tags' => $Tags,
665 1
            'TagsList' => $TagsList,
666 1
            'form' => $form->createView(),
667
            'searchForm' => $searchForm->createView(),
668
            'has_class' => $has_class,
669
            'id' => $id,
670
            'TopCategories' => $TopCategories,
671
            'ChoicedCategoryIds' => $ChoicedCategoryIds,
672
        ];
673
    }
674
675
    /**
676
     * @Route("/%eccube_admin_route%/product/product/{id}/delete", requirements={"id" = "\d+"}, name="admin_product_product_delete", methods={"DELETE"})
677
     */
678
    public function delete(Request $request, $id = null, CacheUtil $cacheUtil)
679 1
    {
680 1
        $this->isTokenValid();
681
        $session = $request->getSession();
682 1
        $page_no = intval($session->get('eccube.admin.product.search.page_no'));
683 1
        $page_no = $page_no ? $page_no : Constant::ENABLED;
684
        $message = null;
685
        $success = false;
686 1
687 1
        if (!is_null($id)) {
688
            /* @var $Product \Eccube\Entity\Product */
689 1
            $Product = $this->productRepository->find($id);
690 View Code Duplication
            if (!$Product) {
691 1
                if ($request->isXmlHttpRequest()) {
692 1
                    $message = trans('admin.common.delete_error_already_deleted');
693 1
694
                    return $this->json(['success' => $success, 'message' => $message]);
695 1
                } else {
696
                    $this->deleteMessage();
697 1
                    $rUrl = $this->generateUrl('admin_product_page', ['page_no' => $page_no]).'?resume='.Constant::ENABLED;
698 1
699
                    return $this->redirect($rUrl);
700
                }
701 1
            }
702
703 1
            if ($Product instanceof Product) {
704 1
                log_info('商品削除開始', [$id]);
705 1
706
                $deleteImages = $Product->getProductImage();
707
                $ProductClasses = $Product->getProductClasses();
708
709
                try {
710 1
                    $this->productRepository->delete($Product);
711
                    $this->entityManager->flush();
712 1
713 1
                    $event = new EventArgs(
714
                        [
715
                            'Product' => $Product,
716 1
                            'ProductClass' => $ProductClasses,
717
                            'deleteImages' => $deleteImages,
718
                        ],
719
                        $request
720 1
                    );
721
                    $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_DELETE_COMPLETE, $event);
722
                    $deleteImages = $event->getArgument('deleteImages');
723
724
                    // 画像ファイルの削除(commit後に削除させる)
725
                    /** @var ProductImage $deleteImage */
726 View Code Duplication
                    foreach ($deleteImages as $deleteImage) {
727 1
                        if ($this->productImageRepository->findOneBy(['file_name' => $deleteImage->getFileName()])) {
728
                            continue;
729
                        }
730 1
                        try {
731 1
                            $fs = new Filesystem();
732
                            $fs->remove($this->eccubeConfig['eccube_save_image_dir'].'/'.$deleteImage);
733
                        } catch (\Exception $e) {
734
                            // エラーが発生しても無視する
735
                        }
736 1
                    }
737
738 1
                    log_info('商品削除完了', [$id]);
739
740
                    $success = true;
741
                    $message = trans('admin.common.delete_complete');
742
743
                    $cacheUtil->clearDoctrineCache();
744
                } catch (ForeignKeyConstraintViolationException $e) {
745
                    log_info('商品削除エラー', [$id]);
746 1
                    $message = trans('admin.common.delete_error_foreign_key', ['%name%' => $Product->getName()]);
747
                }
748 1
            } else {
749
                log_info('商品削除エラー', [$id]);
750 1
                $message = trans('admin.common.delete_error');
751 1
            }
752 1
        } else {
753 1
            log_info('商品削除エラー', [$id]);
754 1
            $message = trans('admin.common.delete_error');
755 1
        }
756 1
757 View Code Duplication
        if ($request->isXmlHttpRequest()) {
758 1
            return $this->json(['success' => $success, 'message' => $message]);
759 1
        } else {
760 1
            if ($success) {
761
                $this->addSuccess($message, 'admin');
762
            } else {
763
                $this->addError($message, 'admin');
764 1
            }
765 1
766 1
            $rUrl = $this->generateUrl('admin_product_page', ['page_no' => $page_no]).'?resume='.Constant::ENABLED;
767
768
            return $this->redirect($rUrl);
769 1
        }
770
    }
771 1
772 1
    /**
773 1
     * @Route("/%eccube_admin_route%/product/product/{id}/copy", requirements={"id" = "\d+"}, name="admin_product_product_copy", methods={"POST"})
774
     */
775
    public function copy(Request $request, $id = null)
776 1
    {
777 1
        $this->isTokenValid();
778 1
779 1
        if (!is_null($id)) {
780 1
            $Product = $this->productRepository->find($id);
781 1
            if ($Product instanceof Product) {
782
                $CopyProduct = clone $Product;
783 1
                $CopyProduct->copy();
784 1
                $ProductStatus = $this->productStatusRepository->find(ProductStatus::DISPLAY_HIDE);
785
                $CopyProduct->setStatus($ProductStatus);
0 ignored issues
show
Bug introduced by
It seems like $ProductStatus defined by $this->productStatusRepo...ctStatus::DISPLAY_HIDE) on line 784 can also be of type object; however, Eccube\Entity\Product::setStatus() does only seem to accept null|object<Eccube\Entity\Master\ProductStatus>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
786
787
                $CopyProductCategories = $CopyProduct->getProductCategories();
788
                foreach ($CopyProductCategories as $Category) {
789
                    $this->entityManager->persist($Category);
790 1
                }
791
792 1
                // 規格あり商品の場合は, デフォルトの商品規格を取得し登録する.
793 1
                if ($CopyProduct->hasProductClass()) {
794
                    $dummyClass = $this->productClassRepository->findOneBy([
795 1
                        'visible' => false,
796 1
                        'ClassCategory1' => null,
797
                        'ClassCategory2' => null,
798 1
                        'Product' => $Product,
799 1
                    ]);
800 1
                    $dummyClass = clone $dummyClass;
801
                    $dummyClass->setProduct($CopyProduct);
802
                    $CopyProduct->addProductClass($dummyClass);
803 1
                }
804
805 1
                $CopyProductClasses = $CopyProduct->getProductClasses();
806
                foreach ($CopyProductClasses as $Class) {
807 1
                    $Stock = $Class->getProductStock();
808 1
                    $CopyStock = clone $Stock;
809
                    $CopyStock->setProductClass($Class);
810
                    $this->entityManager->persist($CopyStock);
811
812 1
                    $TaxRule = $Class->getTaxRule();
813
                    if ($TaxRule) {
814 1
                        $CopyTaxRule = clone $TaxRule;
815
                        $CopyTaxRule->setProductClass($Class);
816 1
                        $CopyTaxRule->setProduct($CopyProduct);
817
                        $this->entityManager->persist($CopyTaxRule);
818 1
                    }
819 1
                    $this->entityManager->persist($Class);
820 1
                }
821 1
                $Images = $CopyProduct->getProductImage();
822 1
                foreach ($Images as $Image) {
823 1
                    // 画像ファイルを新規作成
824
                    $extension = pathinfo($Image->getFileName(), PATHINFO_EXTENSION);
825 1
                    $filename = date('mdHis').uniqid('_').'.'.$extension;
826
                    try {
827 1
                        $fs = new Filesystem();
828
                        $fs->copy($this->eccubeConfig['eccube_save_image_dir'].'/'.$Image->getFileName(), $this->eccubeConfig['eccube_save_image_dir'].'/'.$filename);
829 1
                    } catch (\Exception $e) {
830
                        // エラーが発生しても無視する
831 1
                    }
832
                    $Image->setFileName($filename);
833
834
                    $this->entityManager->persist($Image);
835
                }
836
                $Tags = $CopyProduct->getProductTag();
837
                foreach ($Tags as $Tag) {
838
                    $this->entityManager->persist($Tag);
839
                }
840
841
                $this->entityManager->persist($CopyProduct);
842
843
                $this->entityManager->flush();
844
845
                $event = new EventArgs(
846
                    [
847
                        'Product' => $Product,
848
                        'CopyProduct' => $CopyProduct,
849
                        'CopyProductCategories' => $CopyProductCategories,
850
                        'CopyProductClasses' => $CopyProductClasses,
851
                        'images' => $Images,
852
                        'Tags' => $Tags,
853
                    ],
854
                    $request
855
                );
856
                $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_COPY_COMPLETE, $event);
857
858
                $this->addSuccess('admin.product.copy_complete', 'admin');
859
860
                return $this->redirectToRoute('admin_product_product_edit', ['id' => $CopyProduct->getId()]);
861
            } else {
862
                $this->addError('admin.product.copy_error', 'admin');
863
            }
864
        } else {
865
            $msg = trans('admin.product.copy_error');
866
            $this->addError($msg, 'admin');
867
        }
868
869
        return $this->redirectToRoute('admin_product');
870
    }
871
872
    /**
873
     * @Route("/%eccube_admin_route%/product/product/{id}/display", requirements={"id" = "\d+"}, name="admin_product_product_display")
874
     */
875
    public function display(Request $request, $id = null)
876
    {
877
        $event = new EventArgs(
878
            [],
879
            $request
880
        );
881
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_DISPLAY_COMPLETE, $event);
882
883
        if (!is_null($id)) {
884
            return $this->redirectToRoute('product_detail', ['id' => $id, 'admin' => '1']);
885
        }
886
887
        return $this->redirectToRoute('admin_product');
888
    }
889
890
    /**
891
     * 商品CSVの出力.
892
     *
893
     * @Route("/%eccube_admin_route%/product/export", name="admin_product_export")
894
     *
895
     * @param Request $request
896
     *
897
     * @return StreamedResponse
898
     */
899
    public function export(Request $request)
900
    {
901
        // タイムアウトを無効にする.
902
        set_time_limit(0);
903
904
        // sql loggerを無効にする.
905
        $em = $this->entityManager;
906
        $em->getConfiguration()->setSQLLogger(null);
907
908
        $response = new StreamedResponse();
909
        $response->setCallback(function () use ($request) {
910
            // CSV種別を元に初期化.
911
            $this->csvExportService->initCsvType(CsvType::CSV_TYPE_PRODUCT);
912
913
            // ヘッダ行の出力.
914
            $this->csvExportService->exportHeader();
915
916
            // 商品データ検索用のクエリビルダを取得.
917
            $qb = $this->csvExportService
918
                ->getProductQueryBuilder($request);
919
920
            // Get stock status
921
            $isOutOfStock = 0;
922
            $session = $request->getSession();
923
            if ($session->has('eccube.admin.product.search')) {
924
                $searchData = $session->get('eccube.admin.product.search', []);
925
                if (isset($searchData['stock_status']) && $searchData['stock_status'] === 0) {
926
                    $isOutOfStock = 1;
927
                }
928
            }
929
930
            // joinする場合はiterateが使えないため, select句をdistinctする.
931
            // http://qiita.com/suin/items/2b1e98105fa3ef89beb7
932
            // distinctのmysqlとpgsqlの挙動をあわせる.
933
            // http://uedatakeshi.blogspot.jp/2010/04/distinct-oeder-by-postgresmysql.html
934
            $qb->resetDQLPart('select')
935
                ->resetDQLPart('orderBy')
936
                ->orderBy('p.update_date', 'DESC');
937
938
            if ($isOutOfStock) {
939
                $qb->select('p, pc')
940
                    ->distinct();
941
            } else {
942
                $qb->select('p')
943
                    ->distinct();
944
            }
945
            // データ行の出力.
946
            $this->csvExportService->setExportQueryBuilder($qb);
947
948
            $this->csvExportService->exportData(function ($entity, CsvExportService $csvService) use ($request) {
949
                $Csvs = $csvService->getCsvs();
950
951
                /** @var $Product \Eccube\Entity\Product */
952
                $Product = $entity;
953
954
                /** @var $ProductClasses \Eccube\Entity\ProductClass[] */
955
                $ProductClasses = $Product->getProductClasses();
956
957
                foreach ($ProductClasses as $ProductClass) {
958
                    $ExportCsvRow = new ExportCsvRow();
959
960
                    // CSV出力項目と合致するデータを取得.
961
                    foreach ($Csvs as $Csv) {
962
                        // 商品データを検索.
963
                        $ExportCsvRow->setData($csvService->getData($Csv, $Product));
964
                        if ($ExportCsvRow->isDataNull()) {
965
                            // 商品規格情報を検索.
966
                            $ExportCsvRow->setData($csvService->getData($Csv, $ProductClass));
967
                        }
968
969
                        $event = new EventArgs(
970
                            [
971
                                'csvService' => $csvService,
972
                                'Csv' => $Csv,
973
                                'ProductClass' => $ProductClass,
974
                                'ExportCsvRow' => $ExportCsvRow,
975
                            ],
976
                            $request
977
                        );
978
                        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_CSV_EXPORT, $event);
979
980
                        $ExportCsvRow->pushData();
981
                    }
982
983
                    // $row[] = number_format(memory_get_usage(true));
984
                    // 出力.
985
                    $csvService->fputcsv($ExportCsvRow->getRow());
986
                }
987
            });
988
        });
989
990
        $now = new \DateTime();
991
        $filename = 'product_'.$now->format('YmdHis').'.csv';
992
        $response->headers->set('Content-Type', 'application/octet-stream');
993
        $response->headers->set('Content-Disposition', 'attachment; filename='.$filename);
994
        $response->send();
995
996
        log_info('商品CSV出力ファイル名', [$filename]);
997
998
        return $response;
999
    }
1000
1001
    /**
1002
     * ProductCategory作成
1003
     *
1004 1
     * @param \Eccube\Entity\Product $Product
1005
     * @param \Eccube\Entity\Category $Category
1006 1
     * @param integer $count
1007
     *
1008
     * @return \Eccube\Entity\ProductCategory
1009 1
     */
1010 1 View Code Duplication
    private function createProductCategory($Product, $Category, $count)
0 ignored issues
show
Unused Code introduced by
The parameter $count is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
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...
1011 1
    {
1012
        $ProductCategory = new ProductCategory();
1013 1
        $ProductCategory->setProduct($Product);
1014 1
        $ProductCategory->setProductId($Product->getId());
1015 1
        $ProductCategory->setCategory($Category);
1016
        $ProductCategory->setCategoryId($Category->getId());
1017 1
1018
        return $ProductCategory;
1019
    }
1020
1021 1
    /**
1022 1
     * Bulk public action
1023 1
     *
1024 1
     * @Route("/%eccube_admin_route%/product/bulk/product-status/{id}", requirements={"id" = "\d+"}, name="admin_product_bulk_product_status", methods={"POST"})
1025 1
     *
1026
     * @param Request $request
1027 1
     * @param ProductStatus $ProductStatus
1028
     *
1029
     * @return RedirectResponse
1030
     */
1031
    public function bulkProductStatus(Request $request, ProductStatus $ProductStatus, CacheUtil $cacheUtil)
1032
    {
1033 1
        $this->isTokenValid();
1034
1035
        /** @var Product[] $Products */
1036
        $Products = $this->productRepository->findBy(['id' => $request->get('ids')]);
1037
        $count = 0;
1038
        foreach ($Products as $Product) {
1039
            try {
1040
                $Product->setStatus($ProductStatus);
1041
                $this->productRepository->save($Product);
1042
                $count++;
1043
            } catch (\Exception $e) {
1044
                $this->addError($e->getMessage(), 'admin');
1045
            }
1046
        }
1047
        try {
1048
            if ($count) {
1049
                $this->entityManager->flush();
1050
                $msg = $this->translator->trans('admin.product.bulk_change_status_complete', [
1051
                    '%count%' => $count,
1052
                    '%status%' => $ProductStatus->getName(),
1053
                ]);
1054
                $this->addSuccess($msg, 'admin');
1055
                $cacheUtil->clearDoctrineCache();
1056
            }
1057
        } catch (\Exception $e) {
1058
            $this->addError($e->getMessage(), 'admin');
1059
        }
1060
1061
        return $this->redirectToRoute('admin_product', ['resume' => Constant::ENABLED]);
1062
    }
1063
}
1064