Completed
Pull Request — experimental/3.1 (#2484)
by Kentaro
57:08 queued 35:11
created

ProductController   F

Complexity

Total Complexity 84

Size/Duplication

Total Lines 905
Duplicated Lines 9.39 %

Coupling/Cohesion

Components 1
Dependencies 29

Test Coverage

Coverage 66.37%

Importance

Changes 0
Metric Value
dl 85
loc 905
ccs 302
cts 455
cp 0.6637
rs 1.0434
c 0
b 0
f 0
wmc 84
lcom 1
cbo 29

8 Methods

Rating   Name   Duplication   Size   Complexity  
D index() 7 141 13
B addImage() 0 38 6
F edit() 27 282 34
C delete() 0 89 11
C copy() 0 94 9
A display() 0 14 2
C export() 40 102 8
A createProductCategory() 11 11 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ProductController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ProductController, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * This file is part of EC-CUBE
4
 *
5
 * Copyright(c) 2000-2015 LOCKON CO.,LTD. All Rights Reserved.
6
 *
7
 * http://www.lockon.co.jp/
8
 *
9
 * This program is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU General Public License
11
 * as published by the Free Software Foundation; either version 2
12
 * of the License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22
 */
23
24
25
namespace Eccube\Controller\Admin\Product;
26
27
use Doctrine\ORM\EntityManager;
28
use Eccube\Annotation\Component;
29
use Eccube\Annotation\Inject;
30
use Eccube\Application;
31
use Eccube\Common\Constant;
32
use Eccube\Controller\AbstractController;
33
use Eccube\Entity\BaseInfo;
34
use Eccube\Entity\Master\CsvType;
35
use Eccube\Entity\ProductTag;
36
use Eccube\Event\EccubeEvents;
37
use Eccube\Event\EventArgs;
38
use Eccube\Form\Type\Admin\ProductType;
39
use Eccube\Form\Type\Admin\SearchProductType;
40
use Eccube\Repository\CategoryRepository;
41
use Eccube\Repository\Master\DispRepository;
42
use Eccube\Repository\Master\PageMaxRepository;
43
use Eccube\Repository\ProductClassRepository;
44
use Eccube\Repository\ProductImageRepository;
45
use Eccube\Repository\ProductRepository;
46
use Eccube\Repository\TaxRuleRepository;
47
use Eccube\Service\CsvExportService;
48
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
49
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
50
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
51
use Symfony\Component\EventDispatcher\EventDispatcher;
52
use Symfony\Component\Filesystem\Filesystem;
53
use Symfony\Component\Form\FormFactory;
54
use Symfony\Component\HttpFoundation\File\File;
55
use Symfony\Component\HttpFoundation\Request;
56
use Symfony\Component\HttpFoundation\Session\Session;
57
use Symfony\Component\HttpFoundation\StreamedResponse;
58
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
59
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
60
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
61
62
/**
63
 * @Component
64
 * @Route(service=ProductController::class)
65
 */
66
class ProductController extends AbstractController
67
{
68
    /**
69
     * @Inject(CsvExportService::class)
70
     * @var CsvExportService
71
     */
72
    protected $csvExportService;
73
74
    /**
75
     * @Inject(ProductClassRepository::class)
76
     * @var ProductClassRepository
77
     */
78
    protected $productClassRepository;
79
80
    /**
81
     * @Inject(ProductImageRepository::class)
82
     * @var ProductImageRepository
83
     */
84
    protected $productImageRepository;
85
86
    /**
87
     * @Inject("orm.em")
88
     * @var EntityManager
89
     */
90
    protected $entityManager;
91
92
    /**
93
     * @Inject(TaxRuleRepository::class)
94
     * @var TaxRuleRepository
95
     */
96
    protected $taxRuleRepository;
97
98
    /**
99
     * @Inject(CategoryRepository::class)
100
     * @var CategoryRepository
101
     */
102
    protected $categoryRepository;
103
104
    /**
105
     * @Inject(ProductRepository::class)
106
     * @var ProductRepository
107
     */
108
    protected $productRepository;
109
110
    /**
111
     * @Inject(BaseInfo::class)
112
     * @var BaseInfo
113
     */
114
    protected $BaseInfo;
115
116
    /**
117
     * @Inject("config")
118
     * @var array
119
     */
120
    protected $appConfig;
121
122
    /**
123
     * @Inject(PageMaxRepository::class)
124
     * @var PageMaxRepository
125
     */
126
    protected $pageMaxRepository;
127
128
    /**
129
     * @Inject(DispRepository::class)
130
     * @var DispRepository
131
     */
132
    protected $dispRepository;
133
134
    /**
135
     * @Inject("eccube.event.dispatcher")
136
     * @var EventDispatcher
137
     */
138
    protected $eventDispatcher;
139
140
    /**
141
     * @Inject("form.factory")
142
     * @var FormFactory
143
     */
144
    protected $formFactory;
145
146
    /**
147
     * @Inject("session")
148
     * @var Session
149
     */
150
    protected $session;
151
152
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$app" missing
Loading history...
introduced by
Doc comment for parameter "$request" missing
Loading history...
introduced by
Doc comment for parameter "$page_no" missing
Loading history...
153
     * @Route("/{_admin}/product", name="admin_product")
154
     * @Route("/{_admin}/product/page/{page_no}", requirements={"page_no" = "\d+"}, name="admin_product_page")
155
     * @Template("Product/index.twig")
156
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
157 7
    public function index(Application $app, Request $request, $page_no = null)
158
    {
159
160 7
        $session = $this->session;
161
162 7
        $builder = $this->formFactory
163 7
            ->createBuilder(SearchProductType::class);
164
165 7
        $event = new EventArgs(
166
            array(
167 7
                'builder' => $builder,
168
            ),
169 7
            $request
170
        );
171 7
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_INDEX_INITIALIZE, $event);
172
173 7
        $searchForm = $builder->getForm();
174
175 7
        $pagination = array();
176
177 7
        $disps = $this->dispRepository->findAll();
178 7
        $pageMaxis = $this->pageMaxRepository->findAll();
179 7
        $page_count = $this->appConfig['default_page_count'];
180 7
        $page_status = null;
181 7
        $active = false;
182
183 7
        if ('POST' === $request->getMethod()) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
184
185 5
            $searchForm->handleRequest($request);
186
187 5
            if ($searchForm->isValid()) {
188 5
                $searchData = $searchForm->getData();
189
190
                // paginator
191 5
                $qb = $this->productRepository->getQueryBuilderBySearchDataForAdmin($searchData);
192 5
                $page_no = 1;
193
194 5
                $event = new EventArgs(
195
                    array(
196 5
                        'qb' => $qb,
197 5
                        'searchData' => $searchData,
198
                    ),
199 5
                    $request
200
                );
201 5
                $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_INDEX_SEARCH, $event);
202 5
                $searchData = $event->getArgument('searchData');
203
204 5
                $pagination = $app['paginator']()->paginate(
205 5
                    $qb,
206 5
                    $page_no,
207 5
                    $page_count,
208 5
                    array('wrap-queries' => true)
209
                );
210
211
                // sessionのデータ保持
212 5
                $session->set('eccube.admin.product.search', $searchData);
213 5
                $session->set('eccube.admin.product.search.page_no', $page_no);
214
            }
215
        } else {
216 2
            if (is_null($page_no) && $request->get('resume') != Constant::ENABLED) {
217
                // sessionを削除
218 1
                $session->remove('eccube.admin.product.search');
219 1
                $session->remove('eccube.admin.product.search.page_no');
220
            } else {
221
                // pagingなどの処理
222 1
                $searchData = $session->get('eccube.admin.product.search');
223 1
                if (is_null($page_no)) {
224
                    $page_no = intval($session->get('eccube.admin.product.search.page_no'));
225
                } else {
226 1
                    $session->set('eccube.admin.product.search.page_no', $page_no);
227
                }
228 1
                if (!is_null($searchData)) {
229
                    // 公開ステータス
230
                    // 1:公開, 2:非公開, 3:在庫なし
231 1
                    $status = $request->get('status');
232 1
                    if (empty($status)) {
233
                        $searchData['link_status'] = null;
234
                        $searchData['stock_status'] = null;
235
                    } else {
236 1
                        $searchData['link_status'] = $this->dispRepository->find($status);
237 1
                        $searchData['stock_status'] = null;
238 1
                        if ($status == $this->appConfig['admin_product_stock_status']) {
239
                            // 在庫なし
240 1
                            $searchData['link_status'] = null;
241 1
                            $searchData['stock_status'] = Constant::DISABLED;
242
                        }
243 1
                        $page_status = $status;
244
                    }
245 1
                    $session->set('eccube.admin.product.search', $searchData);
246
247
                    // 表示件数
248 1
                    $page_count = $request->get('page_count', $page_count);
249
250 1
                    $qb = $this->productRepository->getQueryBuilderBySearchDataForAdmin($searchData);
251
252 1
                    $event = new EventArgs(
253
                        array(
254 1
                            'qb' => $qb,
255 1
                            'searchData' => $searchData,
256
                        ),
257 1
                        $request
258
                    );
259 1
                    $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_INDEX_SEARCH, $event);
260 1
                    $searchData = $event->getArgument('searchData');
261
262 1
                    $pagination = $app['paginator']()->paginate(
263 1
                        $qb,
264 1
                        $page_no,
265 1
                        $page_count,
266 1
                        array('wrap-queries' => true)
267
                    );
268
269
                    // セッションから検索条件を復元(カテゴリ)
270 1
                    if (!empty($searchData['category_id'])) {
271
                        $searchData['category_id'] = $this->categoryRepository->find($searchData['category_id']);
272
                    }
273
274
                    // セッションから検索条件を復元(スーテタス)
275 1 View Code Duplication
                    if (isset($searchData['status']) && count($searchData['status']) > 0) {
276
                        $status_ids = array();
277
                        foreach ($searchData['status'] as $Status) {
278
                            $status_ids[] = $Status->getId();
279
                        }
280
                        $searchData['status'] = $this->dispRepository->findBy(array('id' => $status_ids));
281
                    }
282 1
                    $searchForm->setData($searchData);
283
                }
284
            }
285
        }
286
287
        return [
288 7
            'searchForm' => $searchForm->createView(),
289 7
            'pagination' => $pagination,
290 7
            'disps' => $disps,
291 7
            'pageMaxis' => $pageMaxis,
292 7
            'page_no' => $page_no,
293 7
            'page_status' => $page_status,
294 7
            'page_count' => $page_count,
295 7
            'active' => $active,
296
        ];
297
    }
298
299
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$app" missing
Loading history...
introduced by
Doc comment for parameter "$request" missing
Loading history...
300
     * @Method("POST")
301
     * @Route("/{_admin}/product/product/image/add", name="admin_product_image_add")
302
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
303
    public function addImage(Application $app, Request $request)
304
    {
305
        if (!$request->isXmlHttpRequest()) {
306
            throw new BadRequestHttpException('リクエストが不正です');
307
        }
308
309
        $images = $request->files->get('admin_product');
310
311
        $files = array();
312
        if (count($images) > 0) {
313
            foreach ($images as $img) {
314
                foreach ($img as $image) {
315
                    //ファイルフォーマット検証
316
                    $mimeType = $image->getMimeType();
317
                    if (0 !== strpos($mimeType, 'image')) {
318
                        throw new UnsupportedMediaTypeHttpException('ファイル形式が不正です');
319
                    }
320
321
                    $extension = $image->getClientOriginalExtension();
322
                    $filename = date('mdHis') . uniqid('_') . '.' . $extension;
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
323
                    $image->move($this->appConfig['image_temp_realdir'], $filename);
324
                    $files[] = $filename;
325
                }
326
            }
327
        }
328
329
        $event = new EventArgs(
330
            array(
331
                'images' => $images,
332
                'files' => $files,
333
            ),
334
            $request
335
        );
336
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_ADD_IMAGE_COMPLETE, $event);
337
        $files = $event->getArgument('files');
338
339
        return $app->json(array('files' => $files), 200);
340
    }
341
342
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$app" missing
Loading history...
introduced by
Doc comment for parameter "$request" missing
Loading history...
introduced by
Doc comment for parameter "$id" missing
Loading history...
343
     * @Route("/{_admin}/product/product/new", name="admin_product_product_new")
344
     * @Route("/{_admin}/product/product/{id}/edit", requirements={"id" = "\d+"}, name="admin_product_product_edit")
345
     * @Template("Product/product.twig")
346
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
347 16
    public function edit(Application $app, Request $request, $id = null)
348
    {
349 16
        $has_class = false;
350 16
        if (is_null($id)) {
351 5
            $Product = new \Eccube\Entity\Product();
352 5
            $ProductClass = new \Eccube\Entity\ProductClass();
353 5
            $Disp = $this->dispRepository->find(\Eccube\Entity\Master\Disp::DISPLAY_HIDE);
354
            $Product
355 5
                ->setDelFlg(Constant::DISABLED)
356 5
                ->addProductClass($ProductClass)
357 5
                ->setStatus($Disp);
358
            $ProductClass
359 5
                ->setDelFlg(Constant::DISABLED)
360 5
                ->setStockUnlimited(true)
361 5
                ->setProduct($Product);
362 5
            $ProductStock = new \Eccube\Entity\ProductStock();
363 5
            $ProductClass->setProductStock($ProductStock);
364 5
            $ProductStock->setProductClass($ProductClass);
365
        } else {
366 11
            $Product = $this->productRepository->find($id);
367 11
            if (!$Product) {
368
                throw new NotFoundHttpException();
369
            }
370
            // 規格あり商品か
371 11
            $has_class = $Product->hasProductClass();
372 11
            if (!$has_class) {
373 10
                $ProductClasses = $Product->getProductClasses();
374 10
                $ProductClass = $ProductClasses[0];
375 10 View Code Duplication
                if ($this->BaseInfo->getOptionProductTaxRule() == Constant::ENABLED && $ProductClass->getTaxRule() && !$ProductClass->getTaxRule()->getDelFlg()) {
376 6
                    $ProductClass->setTaxRate($ProductClass->getTaxRule()->getTaxRate());
377
                }
378 10
                $ProductStock = $ProductClasses[0]->getProductStock();
379
            }
380
        }
381
382 16
        $builder = $this->formFactory
383 16
            ->createBuilder(ProductType::class, $Product);
384
385
        // 規格あり商品の場合、規格関連情報をFormから除外
386 16
        if ($has_class) {
387 1
            $builder->remove('class');
388
        }
389
390 16
        $event = new EventArgs(
391
            array(
392 16
                'builder' => $builder,
393 16
                'Product' => $Product,
394
            ),
395 16
            $request
396
        );
397 16
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_EDIT_INITIALIZE, $event);
398
399 16
        $form = $builder->getForm();
400
401 16
        if (!$has_class) {
402 15
            $ProductClass->setStockUnlimited((boolean)$ProductClass->getStockUnlimited());
0 ignored issues
show
Bug introduced by
The variable $ProductClass does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Coding Style introduced by
As per coding-style, a cast statement should be followed by a single space.
Loading history...
403 15
            $form['class']->setData($ProductClass);
404
        }
405
406
        // ファイルの登録
407 16
        $images = array();
408 16
        $ProductImages = $Product->getProductImage();
409 16
        foreach ($ProductImages as $ProductImage) {
410 11
            $images[] = $ProductImage->getFileName();
411
        }
412 16
        $form['images']->setData($images);
413
414 16
        $categories = array();
415 16
        $ProductCategories = $Product->getProductCategories();
416 16
        foreach ($ProductCategories as $ProductCategory) {
417
            /* @var $ProductCategory \Eccube\Entity\ProductCategory */
418 11
            $categories[] = $ProductCategory->getCategory();
419
        }
420 16
        $form['Category']->setData($categories);
421
422 16
        $Tags = array();
423 16
        $ProductTags = $Product->getProductTag();
424 16
        foreach ($ProductTags as $ProductTag) {
425
            $Tags[] = $ProductTag->getTag();
426
        }
427 16
        $form['Tag']->setData($Tags);
428
429 16
        if ('POST' === $request->getMethod()) {
430 13
            $form->handleRequest($request);
431 13
            if ($form->isValid()) {
432 13
                log_info('商品登録開始', array($id));
433 13
                $Product = $form->getData();
434
435 13
                if (!$has_class) {
436 13
                    $ProductClass = $form['class']->getData();
437
438
                    // 個別消費税
439 13
                    if ($this->BaseInfo->getOptionProductTaxRule() == Constant::ENABLED) {
440 12
                        if ($ProductClass->getTaxRate() !== null) {
441 8 View Code Duplication
                            if ($ProductClass->getTaxRule()) {
442 4
                                if ($ProductClass->getTaxRule()->getDelFlg() == Constant::ENABLED) {
443
                                    $ProductClass->getTaxRule()->setDelFlg(Constant::DISABLED);
444
                                }
445
446 4
                                $ProductClass->getTaxRule()->setTaxRate($ProductClass->getTaxRate());
447
                            } else {
448 4
                                $taxrule = $this->taxRuleRepository->newTaxRule();
449 4
                                $taxrule->setTaxRate($ProductClass->getTaxRate());
450 4
                                $taxrule->setApplyDate(new \DateTime());
451 4
                                $taxrule->setProduct($Product);
452 4
                                $taxrule->setProductClass($ProductClass);
453 8
                                $ProductClass->setTaxRule($taxrule);
454
                            }
455
                        } else {
456 4
                            if ($ProductClass->getTaxRule()) {
457 2
                                $ProductClass->getTaxRule()->setDelFlg(Constant::ENABLED);
458
                            }
459
                        }
460
                    }
461 13
                    $this->entityManager->persist($ProductClass);
462
463
                    // 在庫情報を作成
464 13
                    if (!$ProductClass->getStockUnlimited()) {
465
                        $ProductStock->setStock($ProductClass->getStock());
0 ignored issues
show
Bug introduced by
The variable $ProductStock does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
466
                    } else {
467
                        // 在庫無制限時はnullを設定
468 13
                        $ProductStock->setStock(null);
469
                    }
470 13
                    $this->entityManager->persist($ProductStock);
471
                }
472
473
                // カテゴリの登録
474
                // 一度クリア
475
                /* @var $Product \Eccube\Entity\Product */
476 13
                foreach ($Product->getProductCategories() as $ProductCategory) {
477 10
                    $Product->removeProductCategory($ProductCategory);
478 10
                    $this->entityManager->remove($ProductCategory);
479
                }
480 13
                $this->entityManager->persist($Product);
481 13
                $this->entityManager->flush();
482
483 13
                $count = 1;
484 13
                $Categories = $form->get('Category')->getData();
485 13
                $categoriesIdList = array();
486 13
                foreach ($Categories as $Category) {
487 View Code Duplication
                    foreach($Category->getPath() as $ParentCategory){
488
                        if (!isset($categoriesIdList[$ParentCategory->getId()])){
489
                            $ProductCategory = $this->createProductCategory($Product, $ParentCategory, $count);
490
                            $this->entityManager->persist($ProductCategory);
491
                            $count++;
492
                            /* @var $Product \Eccube\Entity\Product */
493
                            $Product->addProductCategory($ProductCategory);
494
                            $categoriesIdList[$ParentCategory->getId()] = true;
495
                        }
496
                    }
497
                    if (!isset($categoriesIdList[$Category->getId()])){
498
                        $ProductCategory = $this->createProductCategory($Product, $Category, $count);
499
                        $this->entityManager->persist($ProductCategory);
500
                        $count++;
501
                        /* @var $Product \Eccube\Entity\Product */
502
                        $Product->addProductCategory($ProductCategory);
503
                        $categoriesIdList[$Category->getId()] = true;
504
                    }
505
                }
506
507
                // 画像の登録
508 13
                $add_images = $form->get('add_images')->getData();
509 13
                foreach ($add_images as $add_image) {
510
                    $ProductImage = new \Eccube\Entity\ProductImage();
511
                    $ProductImage
512
                        ->setFileName($add_image)
513
                        ->setProduct($Product)
514
                        ->setRank(1);
515
                    $Product->addProductImage($ProductImage);
516
                    $this->entityManager->persist($ProductImage);
517
518
                    // 移動
519
                    $file = new File($this->appConfig['image_temp_realdir'] . '/' . $add_image);
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
520
                    $file->move($this->appConfig['image_save_realdir']);
521
                }
522
523
                // 画像の削除
524 13
                $delete_images = $form->get('delete_images')->getData();
525 13
                foreach ($delete_images as $delete_image) {
526
                    $ProductImage = $this->productImageRepository
527
                        ->findOneBy(array('file_name' => $delete_image));
528
529
                    // 追加してすぐに削除した画像は、Entityに追加されない
530
                    if ($ProductImage instanceof \Eccube\Entity\ProductImage) {
531
                        $Product->removeProductImage($ProductImage);
532
                        $this->entityManager->remove($ProductImage);
533
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
534
                    }
535
                    $this->entityManager->persist($Product);
536
537
                    // 削除
538
                    $fs = new Filesystem();
539
                    $fs->remove($this->appConfig['image_save_realdir'] . '/' . $delete_image);
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
540
                }
541 13
                $this->entityManager->persist($Product);
542 13
                $this->entityManager->flush();
543
544
545 13
                $ranks = $request->get('rank_images');
546 13
                if ($ranks) {
547
                    foreach ($ranks as $rank) {
548
                        list($filename, $rank_val) = explode('//', $rank);
549
                        $ProductImage = $this->productImageRepository
550
                            ->findOneBy(array(
551
                                'file_name' => $filename,
552
                                'Product' => $Product,
553
                            ));
554
                        $ProductImage->setRank($rank_val);
555
                        $this->entityManager->persist($ProductImage);
0 ignored issues
show
Bug introduced by
It seems like $ProductImage defined by $this->productImageRepos...'Product' => $Product)) on line 549 can also be of type null; however, Doctrine\ORM\EntityManager::persist() does only seem to accept object, 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...
556
                    }
557
                }
558 13
                $this->entityManager->flush();
559
560
                // 商品タグの登録
561
                // 商品タグを一度クリア
562 13
                $ProductTags = $Product->getProductTag();
563 13
                foreach ($ProductTags as $ProductTag) {
564
                    $Product->removeProductTag($ProductTag);
565
                    $this->entityManager->remove($ProductTag);
566
                }
567
568
                // 商品タグの登録
569 13
                $Tags = $form->get('Tag')->getData();
570 13
                foreach ($Tags as $Tag) {
571 13
                    $ProductTag = new ProductTag();
572
                    $ProductTag
573 13
                        ->setProduct($Product)
574 13
                        ->setTag($Tag);
575 13
                    $Product->addProductTag($ProductTag);
576 13
                    $this->entityManager->persist($ProductTag);
577
                }
578
579 13
                $Product->setUpdateDate(new \DateTime());
580 13
                $this->entityManager->flush();
581
582 13
                log_info('商品登録完了', array($id));
583
584 13
                $event = new EventArgs(
585
                    array(
586 13
                        'form' => $form,
587 13
                        'Product' => $Product,
588
                    ),
589 13
                    $request
590
                );
591 13
                $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_EDIT_COMPLETE, $event);
592
593 13
                $app->addSuccess('admin.register.complete', 'admin');
594
595 13
                return $app->redirect($app->url('admin_product_product_edit', array('id' => $Product->getId())));
596
            } else {
597
                log_info('商品登録チェックエラー', array($id));
598
                $app->addError('admin.register.failed', 'admin');
599
            }
600
        }
601
602
        // 検索結果の保持
603 3
        $builder = $this->formFactory
604 3
            ->createBuilder(SearchProductType::class);
605
606 3
        $event = new EventArgs(
607
            array(
608 3
                'builder' => $builder,
609 3
                'Product' => $Product,
610
            ),
611 3
            $request
612
        );
613 3
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_EDIT_SEARCH, $event);
614
615 3
        $searchForm = $builder->getForm();
616
617 3
        if ('POST' === $request->getMethod()) {
618
            $searchForm->handleRequest($request);
619
        }
620
621
        return [
622 3
            'Product' => $Product,
623 3
            'form' => $form->createView(),
624 3
            'searchForm' => $searchForm->createView(),
625 3
            'has_class' => $has_class,
626 3
            'id' => $id,
627
        ];
628
    }
629
630
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$app" missing
Loading history...
introduced by
Doc comment for parameter "$request" missing
Loading history...
introduced by
Doc comment for parameter "$id" missing
Loading history...
631
     * @Method("DELETE")
632
     * @Route("/{_admin}/product/product/{id}/delete", requirements={"id" = "\d+"}, name="admin_product_product_delete")
633
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
634 1
    public function delete(Application $app, Request $request, $id = null)
635
    {
636 1
        $this->isTokenValid($app);
637 1
        $session = $request->getSession();
638 1
        $page_no = intval($session->get('eccube.admin.product.search.page_no'));
639 1
        $page_no = $page_no ? $page_no : Constant::ENABLED;
640
641 1
        if (!is_null($id)) {
642
            /* @var $Product \Eccube\Entity\Product */
643 1
            $Product = $this->productRepository->find($id);
644 1
            if (!$Product) {
645
                $app->deleteMessage();
646
                return $app->redirect($app->url('admin_product_page', array('page_no' => $page_no)).'?resume='.Constant::ENABLED);
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
647
            }
648
649 1
            if ($Product instanceof \Eccube\Entity\Product) {
650 1
                log_info('商品削除開始', array($id));
651
652 1
                $Product->setDelFlg(Constant::ENABLED);
653
654 1
                $ProductClasses = $Product->getProductClasses();
655 1
                $deleteImages = array();
656 1
                foreach ($ProductClasses as $ProductClass) {
657 1
                    $ProductClass->setDelFlg(Constant::ENABLED);
658 1
                    $Product->removeProductClass($ProductClass);
659
660 1
                    $ProductClasses = $Product->getProductClasses();
661 1
                    foreach ($ProductClasses as $ProductClass) {
662 1
                        $ProductClass->setDelFlg(Constant::ENABLED);
663 1
                        $Product->removeProductClass($ProductClass);
664
665 1
                        $ProductStock = $ProductClass->getProductStock();
666 1
                        $this->entityManager->remove($ProductStock);
667
                    }
668
669 1
                    $ProductImages = $Product->getProductImage();
670 1
                    foreach ($ProductImages as $ProductImage) {
671 1
                        $Product->removeProductImage($ProductImage);
672 1
                        $deleteImages[] = $ProductImage->getFileName();
673 1
                        $this->entityManager->remove($ProductImage);
674
                    }
675
676 1
                    $ProductCategories = $Product->getProductCategories();
677 1
                    foreach ($ProductCategories as $ProductCategory) {
678 1
                        $Product->removeProductCategory($ProductCategory);
679 1
                        $this->entityManager->remove($ProductCategory);
680
                    }
681
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
682
                }
683
684 1
                $this->entityManager->persist($Product);
685
686 1
                $this->entityManager->flush();
687
688 1
                $event = new EventArgs(
689
                    array(
690 1
                        'Product' => $Product,
691 1
                        'ProductClass' => $ProductClasses,
692 1
                        'deleteImages' => $deleteImages,
693
                    ),
694 1
                    $request
695
                );
696 1
                $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_DELETE_COMPLETE, $event);
697 1
                $deleteImages = $event->getArgument('deleteImages');
698
699
                // 画像ファイルの削除(commit後に削除させる)
700 1
                foreach ($deleteImages as $deleteImage) {
701
                    try {
702 1
                        $fs = new Filesystem();
703 1
                        $fs->remove($this->appConfig['image_save_realdir'] . '/' . $deleteImage);
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
704 1
                    } catch (\Exception $e) {
705
                        // エラーが発生しても無視する
706
                    }
707
                }
708
709 1
                log_info('商品削除完了', array($id));
710
711 1
                $app->addSuccess('admin.delete.complete', 'admin');
712
            } else {
713
                log_info('商品削除エラー', array($id));
714 1
                $app->addError('admin.delete.failed', 'admin');
715
            }
716
        } else {
717
            log_info('商品削除エラー', array($id));
718
            $app->addError('admin.delete.failed', 'admin');
719
        }
720
721 1
        return $app->redirect($app->url('admin_product_page', array('page_no' => $page_no)).'?resume='.Constant::ENABLED);
722
    }
723
724
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$app" missing
Loading history...
introduced by
Doc comment for parameter "$request" missing
Loading history...
introduced by
Doc comment for parameter "$id" missing
Loading history...
725
     * @Method("POST")
726
     * @Route("/{_admin}/product/product/{id}/copy", requirements={"id" = "\d+"}, name="admin_product_product_copy")
727
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
728 1
    public function copy(Application $app, Request $request, $id = null)
729
    {
730 1
        $this->isTokenValid($app);
731
732 1
        if (!is_null($id)) {
733 1
            $Product = $this->productRepository->find($id);
734 1
            if ($Product instanceof \Eccube\Entity\Product) {
735 1
                $CopyProduct = clone $Product;
736 1
                $CopyProduct->copy();
737 1
                $Disp = $this->dispRepository->find(\Eccube\Entity\Master\Disp::DISPLAY_HIDE);
738 1
                $CopyProduct->setStatus($Disp);
739
740 1
                $CopyProductCategories = $CopyProduct->getProductCategories();
741 1
                foreach ($CopyProductCategories as $Category) {
742 1
                    $this->entityManager->persist($Category);
743
                }
744
745
                // 規格あり商品の場合は, デフォルトの商品規格を取得し登録する.
746 1
                if ($CopyProduct->hasProductClass()) {
747 1
                    $softDeleteFilter = $this->entityManager->getFilters()->getFilter('soft_delete');
748 1
                    $softDeleteFilter->setExcludes(array(
0 ignored issues
show
introduced by
Add a comma after each item in a multi-line array
Loading history...
749 1
                        'Eccube\Entity\ProductClass'
750
                    ));
751 1
                    $dummyClass = $this->productClassRepository->findOneBy(array(
752 1
                        'del_flg' => \Eccube\Common\Constant::ENABLED,
753
                        'ClassCategory1' => null,
754
                        'ClassCategory2' => null,
755 1
                        'Product' => $Product,
756
                    ));
757 1
                    $dummyClass = clone $dummyClass;
758 1
                    $dummyClass->setProduct($CopyProduct);
759 1
                    $CopyProduct->addProductClass($dummyClass);
760 1
                    $softDeleteFilter->setExcludes(array());
761
                }
762
763 1
                $CopyProductClasses = $CopyProduct->getProductClasses();
764 1
                foreach ($CopyProductClasses as $Class) {
765 1
                    $Stock = $Class->getProductStock();
766 1
                    $CopyStock = clone $Stock;
767 1
                    $CopyStock->setProductClass($Class);
768 1
                    $this->entityManager->persist($CopyStock);
769
770 1
                    $this->entityManager->persist($Class);
771
                }
772 1
                $Images = $CopyProduct->getProductImage();
773 1
                foreach ($Images as $Image) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
774
775
                    // 画像ファイルを新規作成
776 1
                    $extension = pathinfo($Image->getFileName(), PATHINFO_EXTENSION);
777 1
                    $filename = date('mdHis') . uniqid('_') . '.' . $extension;
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
778
                    try {
779 1
                        $fs = new Filesystem();
780 1
                        $fs->copy($this->appConfig['image_save_realdir'] . '/' . $Image->getFileName(), $this->appConfig['image_save_realdir'] . '/' . $filename);
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
781 1
                    } catch (\Exception $e) {
782
                        // エラーが発生しても無視する
783
                    }
784 1
                    $Image->setFileName($filename);
785
786 1
                    $this->entityManager->persist($Image);
787
                }
788 1
                $Tags = $CopyProduct->getProductTag();
789 1
                foreach ($Tags as $Tag) {
790
                    $this->entityManager->persist($Tag);
791
                }
792
793 1
                $this->entityManager->persist($CopyProduct);
794
795 1
                $this->entityManager->flush();
796
797 1
                $event = new EventArgs(
798
                    array(
799 1
                        'Product' => $Product,
800 1
                        'CopyProduct' => $CopyProduct,
801 1
                        'CopyProductCategories' => $CopyProductCategories,
802 1
                        'CopyProductClasses' => $CopyProductClasses,
803 1
                        'images' => $Images,
804 1
                        'Tags' => $Tags,
805
                    ),
806 1
                    $request
807
                );
808 1
                $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_COPY_COMPLETE, $event);
809
810 1
                $app->addSuccess('admin.product.copy.complete', 'admin');
811
812 1
                return $app->redirect($app->url('admin_product_product_edit', array('id' => $CopyProduct->getId())));
813
            } else {
814
                $app->addError('admin.product.copy.failed', 'admin');
815
            }
816
        } else {
817
            $app->addError('admin.product.copy.failed', 'admin');
818
        }
819
820
        return $app->redirect($app->url('admin_product'));
821
    }
822
823
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$app" missing
Loading history...
introduced by
Doc comment for parameter "$request" missing
Loading history...
introduced by
Doc comment for parameter "$id" missing
Loading history...
824
     * @Route("/{_admin}/product/product/{id}/display", requirements={"id" = "\d+"}, name="admin_product_product_display")
825
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
826
    public function display(Application $app, Request $request, $id = null)
827
    {
828
        $event = new EventArgs(
829
            array(),
830
            $request
831
        );
832
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_DISPLAY_COMPLETE, $event);
833
834
        if (!is_null($id)) {
835
            return $app->redirect($app->url('product_detail', array('id' => $id, 'admin' => '1')));
836
        }
837
838
        return $app->redirect($app->url('admin_product'));
839
    }
840
841
    /**
842
     * 商品CSVの出力.
843
     *
844
     * @Route("export", name="admin_product_export")
845
     *
846
     * @param Application $app
847
     * @param Request $request
0 ignored issues
show
introduced by
Expected 5 spaces after parameter type; 1 found
Loading history...
848
     * @return StreamedResponse
849
     */
850
    public function export(Application $app, Request $request)
851
    {
852
        // タイムアウトを無効にする.
853
        set_time_limit(0);
854
855
        // sql loggerを無効にする.
856
        $em = $this->entityManager;
857
        $em->getConfiguration()->setSQLLogger(null);
858
859
        $response = new StreamedResponse();
860
        $response->setCallback(function () use ($app, $request) {
861
862
            // CSV種別を元に初期化.
863
            $this->csvExportService->initCsvType(CsvType::CSV_TYPE_PRODUCT);
864
865
            // ヘッダ行の出力.
866
            $this->csvExportService->exportHeader();
867
868
            // 商品データ検索用のクエリビルダを取得.
869
            $qb = $this->csvExportService
870
                ->getProductQueryBuilder($request);
871
872
            // Get stock status
873
            $isOutOfStock = 0;
874
            $session = $request->getSession();
875
            if ($session->has('eccube.admin.product.search')) {
876
                $searchData = $session->get('eccube.admin.product.search', array());
877
                if (isset($searchData['stock_status']) && $searchData['stock_status'] === 0) {
878
                    $isOutOfStock = 1;
879
                }
880
            }
881
882
            // joinする場合はiterateが使えないため, select句をdistinctする.
883
            // http://qiita.com/suin/items/2b1e98105fa3ef89beb7
884
            // distinctのmysqlとpgsqlの挙動をあわせる.
885
            // http://uedatakeshi.blogspot.jp/2010/04/distinct-oeder-by-postgresmysql.html
886
            $qb->resetDQLPart('select')
887
                ->resetDQLPart('orderBy')
888
                ->orderBy('p.update_date', 'DESC');
889
890
            if ($isOutOfStock) {
891
                $qb->select('p, pc')
892
                    ->distinct();
893
            } else {
894
                $qb->select('p')
895
                    ->distinct();
896
            }
897
            // データ行の出力.
898
            $this->csvExportService->setExportQueryBuilder($qb);
899
900 View Code Duplication
            $this->csvExportService->exportData(function ($entity, CsvExportService $csvService) use ($app, $request) {
901
                $Csvs = $csvService->getCsvs();
902
903
                /** @var $Product \Eccube\Entity\Product */
904
                $Product = $entity;
905
906
                /** @var $ProductClassess \Eccube\Entity\ProductClass[] */
907
                $ProductClassess = $Product->getProductClasses();
908
909
                foreach ($ProductClassess as $ProductClass) {
910
                    $ExportCsvRow = new \Eccube\Entity\ExportCsvRow();
911
912
                    // CSV出力項目と合致するデータを取得.
913
                    foreach ($Csvs as $Csv) {
914
                        // 商品データを検索.
915
                        $ExportCsvRow->setData($csvService->getData($Csv, $Product));
916
                        if ($ExportCsvRow->isDataNull()) {
917
                            // 商品規格情報を検索.
918
                            $ExportCsvRow->setData($csvService->getData($Csv, $ProductClass));
919
                        }
920
921
                        $event = new EventArgs(
922
                            array(
923
                                'csvService' => $csvService,
924
                                'Csv' => $Csv,
925
                                'ProductClass' => $ProductClass,
926
                                'ExportCsvRow' => $ExportCsvRow,
927
                            ),
928
                            $request
929
                        );
930
                        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_PRODUCT_CSV_EXPORT, $event);
931
932
                        $ExportCsvRow->pushData();
933
                    }
934
935
                    // $row[] = number_format(memory_get_usage(true));
936
                    // 出力.
937
                    $csvService->fputcsv($ExportCsvRow->getRow());
938
                }
939
            });
940
        });
941
942
        $now = new \DateTime();
943
        $filename = 'product_' . $now->format('YmdHis') . '.csv';
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
944
        $response->headers->set('Content-Type', 'application/octet-stream');
945
        $response->headers->set('Content-Disposition', 'attachment; filename=' . $filename);
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
946
        $response->send();
947
948
        log_info('商品CSV出力ファイル名', array($filename));
949
950
        return $response;
951
    }
952
    
0 ignored issues
show
introduced by
Please trim any trailing whitespace
Loading history...
953
    /**
954
     * ProductCategory作成
955
     * @param \Eccube\Entity\Product $Product
956
     * @param \Eccube\Entity\Category $Category
957
     * @return \Eccube\Entity\ProductCategory
958
     */
959 View Code Duplication
    private function createProductCategory($Product, $Category, $count)
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...
960
    {
961
        $ProductCategory = new \Eccube\Entity\ProductCategory();
962
        $ProductCategory->setProduct($Product);
963
        $ProductCategory->setProductId($Product->getId());
964
        $ProductCategory->setCategory($Category);
965
        $ProductCategory->setCategoryId($Category->getId());
966
        $ProductCategory->setRank($count);
967
        
0 ignored issues
show
introduced by
Please trim any trailing whitespace
Loading history...
968
        return $ProductCategory;
969
    }
970
}
971