Completed
Pull Request — experimental/sf (#3452)
by k-yamamura
61:32
created

AdminController::convert()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
nc 4
nop 4
dl 0
loc 15
ccs 0
cts 0
cp 0
crap 12
rs 9.7666
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Controller\Admin;
15
16
use Carbon\Carbon;
17
use Doctrine\Common\Collections\Criteria;
18
use Doctrine\ORM\NoResultException;
19
use Doctrine\ORM\Query\ResultSetMapping;
20
use Eccube\Controller\AbstractController;
21
use Eccube\Entity\Master\CustomerStatus;
22
use Eccube\Entity\Master\OrderStatus;
23
use Eccube\Entity\Master\ProductStatus;
24
use Eccube\Entity\ProductStock;
25
use Eccube\Event\EccubeEvents;
26
use Eccube\Event\EventArgs;
27
use Eccube\Form\Type\Admin\ChangePasswordType;
28
use Eccube\Form\Type\Admin\LoginType;
29
use Eccube\Repository\CustomerRepository;
30
use Eccube\Repository\Master\OrderStatusRepository;
31
use Eccube\Repository\MemberRepository;
32
use Eccube\Repository\OrderRepository;
33
use Eccube\Repository\ProductRepository;
34
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
35
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
36
use Symfony\Component\HttpFoundation\Request;
37
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
38
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
39
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
40
41
class AdminController extends AbstractController
42
{
43
    /**
44
     * @var AuthorizationCheckerInterface
45
     */
46
    protected $authorizationChecker;
47
48
    /**
49
     * @var AuthenticationUtils
50
     */
51
    protected $helper;
52
53
    /**
54
     * @var MemberRepository
55
     */
56
    protected $memberRepository;
57
58
    /**
59
     * @var EncoderFactoryInterface
60
     */
61
    protected $encoderFactory;
62
63
    /**
64
     * @var OrderRepository
65
     */
66
    protected $orderRepository;
67
68 10
    /**
69
     * @var OrderStatusRepository
70
     */
71
    protected $orderStatusRepository;
72
73
    /**
74 10
     * @var CustomerRepository
75 10
     */
76 10
    protected $customerRepository;
77 10
78
    /**
79
     * @var ProductRepository
80
     */
81
    protected $productRepository;
82
83
    /**
84 2
     * AdminController constructor.
85
     *
86 2
     * @param AuthorizationCheckerInterface $authorizationChecker
87
     * @param AuthenticationUtils $helper
88
     * @param MemberRepository $memberRepository
89
     * @param EncoderFactoryInterface $encoderFactory
90
     * @param OrderRepository $orderRepository
91 2
     * @param OrderStatusRepository $orderStatusRepository
92
     * @param CustomerRepository $custmerRepository
93 2
     * @param ProductRepository $productRepository
94
     */
95 2
    public function __construct(
96
        AuthorizationCheckerInterface $authorizationChecker,
97 2
        AuthenticationUtils $helper,
98
        MemberRepository $memberRepository,
99 2
        EncoderFactoryInterface $encoderFactory,
100
        OrderRepository $orderRepository,
101 2
        OrderStatusRepository $orderStatusRepository,
102
        CustomerRepository $custmerRepository,
103
        ProductRepository $productRepository
104 2
    ) {
105 2
        $this->authorizationChecker = $authorizationChecker;
106
        $this->helper = $helper;
107
        $this->memberRepository = $memberRepository;
108
        $this->encoderFactory = $encoderFactory;
109
        $this->orderRepository = $orderRepository;
110
        $this->orderStatusRepository = $orderStatusRepository;
111
        $this->customerRepository = $custmerRepository;
112
        $this->productRepository = $productRepository;
113 4
    }
114
115
    /**
116 4
     * @Route("/%eccube_admin_route%/login", name="admin_login")
117
     * @Template("@admin/login.twig")
118
     */
119
    public function login(Request $request)
120
    {
121
        if ($this->authorizationChecker->isGranted('ROLE_ADMIN')) {
122
            return $this->redirectToRoute('admin_homepage');
123
        }
124
125
        /* @var $form \Symfony\Component\Form\FormInterface */
126
        $builder = $this->formFactory->createNamedBuilder('', LoginType::class);
127
128
        $event = new EventArgs(
129
            [
130 4
                'builder' => $builder,
131
            ],
132 4
            $request
133
        );
134 4
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_ADMIM_LOGIN_INITIALIZE, $event);
135
136 4
        $form = $builder->getForm();
137
138 4
        return [
139 4
            'error' => $this->helper->getLastAuthenticationError(),
140 4
            'form' => $form->createView(),
141
        ];
142 4
    }
143
144 4
    /**
145
     * 管理画面ホーム
146
     *
147 4
     * @param Request $request
148
     *
149
     * @return array
150 4
     *
151
     * @throws NoResultException
152
     * @throws \Doctrine\ORM\NonUniqueResultException
153 4
     *
154
     * @Route("/%eccube_admin_route%/", name="admin_homepage")
155
     * @Template("@admin/index.twig")
156
     */
157
    public function index(Request $request)
158 4
    {
159 4
        /**
160 4
         * 受注状況.
161 4
         */
162 4
        $excludes = [];
163
        $excludes[] = OrderStatus::PENDING;
164 4
        $excludes[] = OrderStatus::PROCESSING;
165
        $excludes[] = OrderStatus::CANCEL;
166 4
        $excludes[] = OrderStatus::DELIVERED;
167
168 4
        $event = new EventArgs(
169
            [
170 4
                'excludes' => $excludes,
171 4
            ],
172
            $request
173
        );
174 4
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_ADMIM_INDEX_ORDER, $event);
175
        $excludes = $event->getArgument('excludes');
176 4
177
        // 受注ステータスごとの受注件数.
178
        $Orders = $this->getOrderEachStatus($this->entityManager, $excludes);
179
180
        // 受注ステータスの一覧.
181 4
        $Criteria = new Criteria();
182 4
        $Criteria->where($Criteria::expr()->notIn('id', $excludes));
183 4
        $OrderStatuses = $this->orderStatusRepository->matching($Criteria, $Criteria->orderBy(['sort_no' => 'ASC']));
0 ignored issues
show
Unused Code introduced by
The call to OrderStatusRepository::matching() has too many arguments starting with $Criteria->orderBy(array('sort_no' => 'ASC')).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
184 4
185
        /**
186 4
         * 売り上げ状況
187
         */
188 4
        $excludes = [];
189
        $excludes[] = OrderStatus::PROCESSING;
190 4
        $excludes[] = OrderStatus::CANCEL;
191
        $excludes[] = OrderStatus::PENDING;
192 4
193 4
        $event = new EventArgs(
194
            [
195
                'excludes' => $excludes,
196 4
            ],
197
            $request
198 4
        );
199
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_ADMIM_INDEX_SALES, $event);
200 4
        $excludes = $event->getArgument('excludes');
201
202
        // 今日の売上/件数
203
        $salesToday = $this->getSalesByDay($this->entityManager, new \DateTime(), $excludes);
204
        // 昨日の売上/件数
205
        $salesYesterday = $this->getSalesByDay($this->entityManager, new \DateTime('-1 day'), $excludes);
206 4
        // 今月の売上/件数
207
        $salesThisMonth = $this->getSalesByMonth($this->entityManager, new \DateTime(), $excludes);
208 4
209
        // 週間の売上金額
210 4
        $toDate = Carbon::now();
211
        $fromDate = Carbon::today()->subWeek();
212 4
        $rawWeekly = $this->getData($excludes, $fromDate, $toDate, 'Y/m/d');
213 4
214 4
        // 月間の売上金額
215 4
        $fromDate = Carbon::now()->startOfMonth();
216 4
        $rawMonthly = $this->getData($excludes, $fromDate, $toDate, 'Y/m/d');
217 4
218 4
        // 年間の売上金額
219
        $fromDate = Carbon::now()->subYear()->startOfMonth();
220 4
        $rawYear = $this->getData($excludes, $fromDate, $toDate, 'Y/m');
221
222 4
        $datas = [$rawWeekly, $rawMonthly, $rawYear];
223
224
        /**
225 4
         * ショップ状況
226 4
         */
227 4
        // 在庫切れ商品数
228 4
        $countNonStockProducts = $this->countNonStockProducts();
229 4
230 4
        // 取り扱い商品数
231 4
        $countProducts = $this->countProducts();
232 4
233 4
        // 本会員数
234 4
        $countCustomers = $this->countCustomers();
235
236
        $event = new EventArgs(
237
            [
238
                'Orders' => $Orders,
239
                'OrderStatuses' => $OrderStatuses,
240
                'salesThisMonth' => $salesThisMonth,
241
                'salesToday' => $salesToday,
242
                'salesYesterday' => $salesYesterday,
243
                'countNonStockProducts' => $countNonStockProducts,
244
                'countProducts' => $countProducts,
245
                'countCustomers' => $countCustomers,
246
                'datas' => $datas,
247
            ],
248 3
            $request
249
        );
250 3
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_ADMIM_INDEX_COMPLETE, $event);
251 3
252
        return [
253 3
            'Orders' => $Orders,
254
            'OrderStatuses' => $OrderStatuses,
255 3
            'salesThisMonth' => $salesThisMonth,
256
            'salesToday' => $salesToday,
257 3
            'salesYesterday' => $salesYesterday,
258
            'countNonStockProducts' => $countNonStockProducts,
259 3
            'countProducts' => $countProducts,
260
            'countCustomers' => $countCustomers,
261 3
            'datas' => $datas,
262 3
        ];
263
    }
264 3
265 1
    /**
266 1
     * パスワード変更画面
267 1
     *
268
     * @Route("/%eccube_admin_route%/change_password", name="admin_change_password")
269 1
     * @Template("@admin/change_password.twig")
270
     *
271
     * @param Request $request
272 1
     *
273
     * @return \Symfony\Component\HttpFoundation\RedirectResponse|array
274
     */
275
    public function changePassword(Request $request)
276 1
    {
277
        $builder = $this->formFactory
278
            ->createBuilder(ChangePasswordType::class);
279 1
280 1
        $event = new EventArgs(
281
            [
282 1
                'builder' => $builder,
283
            ],
284 1
            $request
285
        );
286 1
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_ADMIM_CHANGE_PASSWORD_INITIALIZE, $event);
287 1
288
        $form = $builder->getForm();
289 1
        $form->handleRequest($request);
290
291 1
        if ($form->isSubmitted() && $form->isValid()) {
292
            $Member = $this->getUser();
293 1
            $salt = $Member->getSalt();
294
            $password = $form->get('change_password')->getData();
295 1
296
            $encoder = $this->encoderFactory->getEncoder($Member);
0 ignored issues
show
Documentation introduced by
$Member is of type null|object, but the function expects a object<Symfony\Component...r\UserInterface>|string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
297
298
            // 2系からのデータ移行でsaltがセットされていない場合はsaltを生成.
299 2
            if (empty($salt)) {
300
                $salt = $encoder->createSalt();
301
            }
302
303
            $password = $encoder->encodePassword($password, $salt);
304
305
            $Member
306
                ->setPassword($password)
307
                ->setSalt($salt);
308
309
            $this->memberRepository->save($Member);
0 ignored issues
show
Documentation introduced by
$Member is of type null|object, but the function expects a object<Eccube\Entity\Member>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
310
311
            $event = new EventArgs(
312 2
                [
313
                    'form' => $form,
314
                    'Member' => $Member,
315
                ],
316 2
                $request
317 2
            );
318 2
            $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_ADMIN_CHANGE_PASSWORD_COMPLETE, $event);
319
320 2
            $this->addSuccess('admin.change_password.save.complete', 'admin');
321 2
322
            return $this->redirectToRoute('admin_change_password');
323 1
        }
324 1
325 1
        return [
326 1
            'form' => $form->createView(),
327
        ];
328 1
    }
329 1
330 1
    /**
331
     * 在庫なし商品の検索結果を表示する.
332
     *
333 1
     * @Route("/%eccube_admin_route%/nonstock", name="admin_homepage_nonstock")
334
     *
335
     * @param Request $request
336
     *
337
     * @return \Symfony\Component\HttpFoundation\Response
338
     */
339
    public function searchNonStockProducts(Request $request)
340
    {
341
        // 在庫なし商品の検索条件をセッションに付与し, 商品マスタへリダイレクトする.
342 4
        $searchData = [];
343
        $searchData['stock'] = [ProductStock::OUT_OF_STOCK];
344
        $session = $request->getSession();
345
        $session->set('eccube.admin.product.search', $searchData);
346 4
347 4
        return $this->redirectToRoute('admin_product_page', [
348
            'page_no' => 1,
349
            'status' => $this->eccubeConfig['eccube_admin_product_stock_status'], ]);
350 4
    }
351 4
352 4
    /**
353 4
     * 本会員の検索結果を表示する.
354
     *
355
     * @Route("/%eccube_admin_route%/customer", name="admin_homepage_customer")
356
     *
357
     * @param Request $request
358
     *
359
     * @return \Symfony\Component\HttpFoundation\Response
360
     */
361
    public function searchCustomer(Request $request)
362 4
    {
363
        $searchData = [];
364 4
        $searchData['customer_status'] = [CustomerStatus::REGULAR];
365
        $session = $request->getSession();
366
        $session->set('eccube.admin.customer.search', $searchData);
367
368
        return $this->redirectToRoute('admin_customer_page', [
369
            'page_no' => 1,
370
        ]);
371
    }
372
373
    /**
374
     * @param \Doctrine\ORM\EntityManagerInterface $em
375 4
     * @param array $excludes
376 4
     *
377 4
     * @return null|Request
378 4
     */
379 4
    private function getOrderEachStatus($em, array $excludes)
380 4
    {
381 4
        $sql = 'SELECT
382 4
                    t1.order_status_id as status,
383 1
                    COUNT(t1.id) as count
384
                FROM
385
                    dtb_order t1
386 4
                WHERE
387
                    t1.order_status_id NOT IN (:excludes)
388
                GROUP BY
389
                    t1.order_status_id
390
                ORDER BY
391
                    t1.order_status_id';
392
        $rsm = new ResultSetMapping();
393
        $rsm->addScalarResult('status', 'status');
394
        $rsm->addScalarResult('count', 'count');
395
        $query = $em->createNativeQuery($sql, $rsm);
396 4
        $query->setParameters([':excludes' => $excludes]);
397
        $result = $query->getResult();
398
        $orderArray = [];
399
        foreach ($result as $row) {
400 4
            $orderArray[$row['status']] = $row['count'];
401
        }
402
403
        return $orderArray;
404
    }
405
406
    /**
407
     * @param \Doctrine\ORM\EntityManagerInterface $em
408
     * @param \DateTime $dateTime
409
     * @param array $excludes
410
     *
411
     * @return null|Request
412
     */
413 4 View Code Duplication
    private function getSalesByMonth($em, $dateTime, array $excludes)
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...
414 4
    {
415 4
        // concat... for pgsql
416
        // http://stackoverflow.com/questions/1091924/substr-does-not-work-with-datatype-timestamp-in-postgres-8-3
417 4
        $dql = 'SELECT
418
                  SUBSTRING(CONCAT(o.order_date, \'\'), 1, 7) AS order_month,
419 4
                  SUM(o.payment_total) AS order_amount,
420 3
                  COUNT(o) AS order_count
421
                FROM
422
                  Eccube\Entity\Order o
423
                WHERE
424 4
                    o.OrderStatus NOT IN (:excludes)
425
                    AND SUBSTRING(CONCAT(o.order_date, \'\'), 1, 7) = SUBSTRING(:targetDate, 1, 7)
426
                GROUP BY
427
                  order_month';
428
429
        $q = $em
430
            ->createQuery($dql)
431
            ->setParameter(':excludes', $excludes)
432
            ->setParameter(':targetDate', $dateTime);
433
434 4
        $result = [];
435
        try {
436
            $result = $q->getSingleResult();
437
        } catch (NoResultException $e) {
438 4
            // 結果がない場合は空の配列を返す.
439
        }
440
441
        return $result;
442
    }
443
444
    /**
445
     * @param \Doctrine\ORM\EntityManagerInterface $em
446
     * @param \DateTime $dateTime
447
     * @param array $excludes
448
     *
449
     * @return null|Request
450
     */
451 4 View Code Duplication
    private function getSalesByDay($em, $dateTime, array $excludes)
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...
452 4
    {
453 4
        // concat... for pgsql
454
        // http://stackoverflow.com/questions/1091924/substr-does-not-work-with-datatype-timestamp-in-postgres-8-3
455 4
        $dql = 'SELECT
456
                  SUBSTRING(CONCAT(o.order_date, \'\'), 1, 10) AS order_day,
457 4
                  SUM(o.payment_total) AS order_amount,
458 3
                  COUNT(o) AS order_count
459
                FROM
460
                  Eccube\Entity\Order o
461
                WHERE
462 4
                    o.OrderStatus NOT IN (:excludes)
463
                    AND SUBSTRING(CONCAT(o.order_date, \'\'), 1, 10) = SUBSTRING(:targetDate, 1, 10)
464
                GROUP BY
465
                  order_day';
466
467
        $q = $em
468
            ->createQuery($dql)
469
            ->setParameter(':excludes', $excludes)
470
            ->setParameter(':targetDate', $dateTime);
471
472
        $result = [];
473 4
        try {
474
            $result = $q->getSingleResult();
475
        } catch (NoResultException $e) {
476 4
            // 結果がない場合は空の配列を返す.
477 4
        }
478 4
479 4
        return $result;
480 4
    }
481 4
482
    /**
483
     * 在庫切れ商品数を取得
484 4
     *
485 4
     * @return mixed
486
     *
487
     * @throws \Doctrine\ORM\NonUniqueResultException
488
     */
489
    private function countNonStockProducts()
490
    {
491
        $qb = $this->productRepository->createQueryBuilder('p')
492
            ->select('count(DISTINCT p.id)')
493
            ->innerJoin('p.ProductClasses', 'pc')
494
            ->where('pc.stock_unlimited = :StockUnlimited AND pc.stock = 0')
495
            ->setParameter('StockUnlimited', false);
496 4
497
        return $qb->getQuery()->getSingleScalarResult();
498
    }
499 4
500 4
    /**
501
     * 商品数を取得
502
     *
503 4
     * @return mixed
504 4
     *
505 4
     * @throws \Doctrine\ORM\NonUniqueResultException
506 4
     */
507 4
    private function countProducts()
508
    {
509
        $qb = $this->productRepository->createQueryBuilder('p')
510 4
            ->select('count(p.id)')
511 4
            ->where('p.Status in (:Status)')
512
            ->setParameter('Status', [ProductStatus::DISPLAY_SHOW, ProductStatus::DISPLAY_HIDE]);
513
514
        return $qb->getQuery()->getSingleScalarResult();
515
    }
516
517
    /**
518
     * 本会員数を取得
519
     *
520
     * @return mixed
521
     *
522
     * @throws \Doctrine\ORM\NonUniqueResultException
523
     */
524
    private function countCustomers()
525
    {
526
        $qb = $this->customerRepository->createQueryBuilder('c')
527
            ->select('count(c.id)')
528
            ->where('c.Status = :Status')
529
            ->setParameter('Status', CustomerStatus::REGULAR);
530
531
        return $qb->getQuery()->getSingleScalarResult();
532
    }
533
534
    /**
535
     * 期間指定のデータを取得
536
     *
537
     * @param array $excludes
538
     * @param Carbon $fromDate
539
     * @param Carbon $toDate
540
     * @param $format
541
     *
542
     * @return array
543
     */
544
    private function getData(array $excludes, Carbon $fromDate, Carbon $toDate, $format)
545
    {
546
        $qb = $this->orderRepository->createQueryBuilder('o')
547
            ->andWhere('o.order_date >= :fromDate')
548
            ->andWhere('o.order_date <= :toDate')
549
            ->andWhere('o.OrderStatus NOT IN (:excludes)')
550
            ->setParameter(':excludes', $excludes)
551
            ->setParameter(':fromDate', $fromDate->copy())
552
            ->setParameter(':toDate', $toDate->copy())
553
            ->orderBy('o.order_date');
554
555
        $result = $qb->getQuery()->getResult();
556
557
        return $this->convert($result, $fromDate, $toDate, $format);
558
    }
559
560
    /**
561
     * 期間毎にデータをまとめる
562
     *
563
     * @param $result
564
     * @param Carbon $fromDate
565
     * @param Carbon $toDate
566
     * @param $format
567
     *
568
     * @return array
569
     */
570
    private function convert($result, Carbon $fromDate, Carbon $toDate, $format)
571
    {
572
        $raw = [];
573
        for ($date = $fromDate; $date <= $toDate; $date = $date->addDay()) {
574
            $raw[$date->format($format)]['price'] = 0;
575
            $raw[$date->format($format)]['count'] = 0;
576
        }
577
578
        foreach ($result as $Order) {
579
            $raw[$Order->getOrderDate()->format($format)]['price'] += $Order->getPaymentTotal();
580
            ++$raw[$Order->getOrderDate()->format($format)]['count'];
581
        }
582
583
        return $raw;
584
    }
585
}
586