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

src/Eccube/Controller/Admin/AdminController.php (1 issue)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Controller\Admin;
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\Exception\PluginApiException;
28
use Eccube\Form\Type\Admin\ChangePasswordType;
29
use Eccube\Form\Type\Admin\LoginType;
30
use Eccube\Repository\CustomerRepository;
31
use Eccube\Repository\Master\OrderStatusRepository;
32
use Eccube\Repository\MemberRepository;
33
use Eccube\Repository\OrderRepository;
34
use Eccube\Repository\ProductRepository;
35
use Eccube\Service\PluginApiService;
36
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
37
use Symfony\Component\HttpFoundation\Request;
38
use Symfony\Component\Routing\Annotation\Route;
39
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
40
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
41
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
42
43
class AdminController extends AbstractController
44
{
45
    /**
46
     * @var AuthorizationCheckerInterface
47
     */
48
    protected $authorizationChecker;
49
50
    /**
51
     * @var AuthenticationUtils
52
     */
53
    protected $helper;
54
55
    /**
56
     * @var MemberRepository
57
     */
58
    protected $memberRepository;
59
60
    /**
61
     * @var EncoderFactoryInterface
62
     */
63
    protected $encoderFactory;
64
65
    /**
66
     * @var OrderRepository
67
     */
68
    protected $orderRepository;
69
70
    /**
71
     * @var OrderStatusRepository
72
     */
73
    protected $orderStatusRepository;
74
75
    /**
76
     * @var CustomerRepository
77
     */
78
    protected $customerRepository;
79
80
    /**
81
     * @var ProductRepository
82
     */
83
    protected $productRepository;
84
85
    /** @var PluginApiService */
86
    protected $pluginApiService;
87
88
    /**
89
     * @var array 売り上げ状況用受注状況
90
     */
91
    private $excludes = [OrderStatus::CANCEL, OrderStatus::PENDING, OrderStatus::PROCESSING, OrderStatus::RETURNED];
92
93
    /**
94
     * AdminController constructor.
95
     *
96
     * @param AuthorizationCheckerInterface $authorizationChecker
97
     * @param AuthenticationUtils $helper
98
     * @param MemberRepository $memberRepository
99
     * @param EncoderFactoryInterface $encoderFactory
100 10
     * @param OrderRepository $orderRepository
101
     * @param OrderStatusRepository $orderStatusRepository
102
     * @param CustomerRepository $custmerRepository
103
     * @param ProductRepository $productRepository
104
     * @param PluginApiService $pluginApiService
105
     */
106
    public function __construct(
107
        AuthorizationCheckerInterface $authorizationChecker,
108
        AuthenticationUtils $helper,
109
        MemberRepository $memberRepository,
110 10
        EncoderFactoryInterface $encoderFactory,
111 10
        OrderRepository $orderRepository,
112 10
        OrderStatusRepository $orderStatusRepository,
113 10
        CustomerRepository $custmerRepository,
114 10
        ProductRepository $productRepository,
115 10
        PluginApiService $pluginApiService
116 10
    ) {
117 10
        $this->authorizationChecker = $authorizationChecker;
118
        $this->helper = $helper;
119
        $this->memberRepository = $memberRepository;
120
        $this->encoderFactory = $encoderFactory;
121
        $this->orderRepository = $orderRepository;
122
        $this->orderStatusRepository = $orderStatusRepository;
123
        $this->customerRepository = $custmerRepository;
124 2
        $this->productRepository = $productRepository;
125
        $this->pluginApiService = $pluginApiService;
126 2
    }
127
128
    /**
129
     * @Route("/%eccube_admin_route%/login", name="admin_login")
130
     * @Template("@admin/login.twig")
131 2
     */
132
    public function login(Request $request)
133 2
    {
134
        if ($this->authorizationChecker->isGranted('ROLE_ADMIN')) {
135 2
            return $this->redirectToRoute('admin_homepage');
136
        }
137 2
138
        /* @var $form \Symfony\Component\Form\FormInterface */
139 2
        $builder = $this->formFactory->createNamedBuilder('', LoginType::class);
140
141 2
        $event = new EventArgs(
142
            [
143
                'builder' => $builder,
144 2
            ],
145 2
            $request
146
        );
147
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_ADMIM_LOGIN_INITIALIZE, $event);
148
149
        $form = $builder->getForm();
150
151
        return [
152
            'error' => $this->helper->getLastAuthenticationError(),
153
            'form' => $form->createView(),
154
        ];
155
    }
156
157
    /**
158
     * 管理画面ホーム
159
     *
160
     * @param Request $request
161
     *
162 4
     * @return array
163
     *
164
     * @throws NoResultException
165
     * @throws \Doctrine\ORM\NonUniqueResultException
166
     *
167 4
     * @Route("/%eccube_admin_route%/", name="admin_homepage")
168 4
     * @Template("@admin/index.twig")
169 4
     */
170 4
    public function index(Request $request)
171 4
    {
172
        $adminRoute = $this->eccubeConfig['eccube_admin_route'];
173 4
        $is_danger_admin_url = false;
174
        if ($adminRoute === 'admin') {
175 4
            $is_danger_admin_url = true;
176
        }
177 4
        /**
178
         * 受注状況.
179 4
         */
180 4
        $excludes = [];
181
        $excludes[] = OrderStatus::CANCEL;
182
        $excludes[] = OrderStatus::DELIVERED;
183 4
        $excludes[] = OrderStatus::PENDING;
184
        $excludes[] = OrderStatus::PROCESSING;
185
        $excludes[] = OrderStatus::RETURNED;
186 4
187
        $event = new EventArgs(
188 4
            [
189 4
                'excludes' => $excludes,
190 4
            ],
191
            $request
192
        );
193
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_ADMIM_INDEX_ORDER, $event);
194
        $excludes = $event->getArgument('excludes');
195 4
196
        // 受注ステータスごとの受注件数.
197 4
        $Orders = $this->getOrderEachStatus($excludes);
198
199 4
        // 受注ステータスの一覧.
200
        $Criteria = new Criteria();
201 4
        $Criteria
202 4
            ->where($Criteria::expr()->notIn('id', $excludes))
203
            ->orderBy(['sort_no' => 'ASC']);
204
        $OrderStatuses = $this->orderStatusRepository->matching($Criteria);
205 4
206
        /**
207 4
         * 売り上げ状況
208
         */
209 4
        $event = new EventArgs(
210
            [
211
                'excludes' => $this->excludes,
212
            ],
213
            $request
214
        );
215 4
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_ADMIM_INDEX_SALES, $event);
216
        $this->excludes = $event->getArgument('excludes');
217
218 4
        // 今日の売上/件数
219
        $salesToday = $this->getSalesByDay(new \DateTime());
220
        // 昨日の売上/件数
221 4
        $salesYesterday = $this->getSalesByDay(new \DateTime('-1 day'));
222
        // 今月の売上/件数
223 4
        $salesThisMonth = $this->getSalesByMonth(new \DateTime());
224
225 4
        /**
226 4
         * ショップ状況
227 4
         */
228 4
        // 在庫切れ商品数
229 4
        $countNonStockProducts = $this->countNonStockProducts();
230 4
231 4
        // 取り扱い商品数
232 4
        $countProducts = $this->countProducts();
233
234 4
        // 本会員数
235
        $countCustomers = $this->countCustomers();
236 4
237
        $event = new EventArgs(
238
            [
239 4
                'Orders' => $Orders,
240 4
                'OrderStatuses' => $OrderStatuses,
241 4
                'salesThisMonth' => $salesThisMonth,
242 4
                'salesToday' => $salesToday,
243 4
                'salesYesterday' => $salesYesterday,
244 4
                'countNonStockProducts' => $countNonStockProducts,
245 4
                'countProducts' => $countProducts,
246 4
                'countCustomers' => $countCustomers,
247
            ],
248
            $request
249
        );
250
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_ADMIM_INDEX_COMPLETE, $event);
251
252
        // 推奨プラグイン
253
        $recommendedPlugins = [];
254
        try {
255
            $recommendedPlugins = $this->pluginApiService->getRecommended();
256
        } catch (PluginApiException $ignore) {
257
        }
258
259
        return [
260
            'Orders' => $Orders,
261
            'OrderStatuses' => $OrderStatuses,
262
            'salesThisMonth' => $salesThisMonth,
263
            'salesToday' => $salesToday,
264
            'salesYesterday' => $salesYesterday,
265
            'countNonStockProducts' => $countNonStockProducts,
266
            'countProducts' => $countProducts,
267
            'countCustomers' => $countCustomers,
268
            'recommendedPlugins' => $recommendedPlugins,
269
            'is_danger_admin_url' => $is_danger_admin_url,
270
        ];
271
    }
272
273
    /**
274
     * 売上状況の取得
275
     *
276
     * @param Request $request
277
     *
278
     * @Route("/%eccube_admin_route%/sale_chart", name="admin_homepage_sale")
279
     *
280
     * @return \Symfony\Component\HttpFoundation\JsonResponse
281
     */
282
    public function sale(Request $request)
283
    {
284
        if (!($request->isXmlHttpRequest() && $this->isTokenValid())) {
285
            return $this->json(['status' => 'NG'], 400);
286
        }
287
288
        // 週間の売上金額
289
        $toDate = Carbon::now();
290
        $fromDate = Carbon::today()->subWeek();
291
        $rawWeekly = $this->getData($fromDate, $toDate, 'Y/m/d');
292
293 3
        // 月間の売上金額
294
        $fromDate = Carbon::now()->startOfMonth();
295 3
        $rawMonthly = $this->getData($fromDate, $toDate, 'Y/m/d');
296 3
297
        // 年間の売上金額
298 3
        $fromDate = Carbon::now()->subYear()->startOfMonth();
299
        $rawYear = $this->getData($fromDate, $toDate, 'Y/m');
300 3
301
        $datas = [$rawWeekly, $rawMonthly, $rawYear];
302 3
303
        return $this->json($datas);
304 3
    }
305
306 3
    /**
307 3
     * パスワード変更画面
308
     *
309 3
     * @Route("/%eccube_admin_route%/change_password", name="admin_change_password")
310 1
     * @Template("@admin/change_password.twig")
311 1
     *
312 1
     * @param Request $request
313
     *
314 1
     * @return \Symfony\Component\HttpFoundation\RedirectResponse|array
315
     */
316
    public function changePassword(Request $request)
317 1
    {
318
        $builder = $this->formFactory
319
            ->createBuilder(ChangePasswordType::class);
320
321 1
        $event = new EventArgs(
322
            [
323
                'builder' => $builder,
324 1
            ],
325 1
            $request
326
        );
327 1
        $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_ADMIM_CHANGE_PASSWORD_INITIALIZE, $event);
328
329 1
        $form = $builder->getForm();
330
        $form->handleRequest($request);
331 1
332 1
        if ($form->isSubmitted() && $form->isValid()) {
333
            $Member = $this->getUser();
334 1
            $salt = $Member->getSalt();
335
            $password = $form->get('change_password')->getData();
336 1
337
            $encoder = $this->encoderFactory->getEncoder($Member);
338 1
339
            // 2系からのデータ移行でsaltがセットされていない場合はsaltを生成.
340 1
            if (empty($salt)) {
341
                $salt = $encoder->createSalt();
342
            }
343
344 2
            $password = $encoder->encodePassword($password, $salt);
345
346
            $Member
347
                ->setPassword($password)
348
                ->setSalt($salt);
349
350
            $this->memberRepository->save($Member);
351
352
            $event = new EventArgs(
353
                [
354
                    'form' => $form,
355
                    'Member' => $Member,
356
                ],
357 2
                $request
358
            );
359
            $this->eventDispatcher->dispatch(EccubeEvents::ADMIN_ADMIN_CHANGE_PASSWORD_COMPLETE, $event);
360 2
361 2
            $this->addSuccess('admin.change_password.password_changed', 'admin');
362 2
363 2
            return $this->redirectToRoute('admin_change_password');
364
        }
365 2
366 2
        return [
367 2
            'form' => $form->createView(),
368
        ];
369
    }
370
371
    /**
372
     * 在庫なし商品の検索結果を表示する.
373
     *
374
     * @Route("/%eccube_admin_route%/search_nonstock", name="admin_homepage_nonstock")
375
     *
376
     * @param Request $request
377
     *
378
     * @return \Symfony\Component\HttpFoundation\Response
379
     */
380
    public function searchNonStockProducts(Request $request)
381
    {
382
        // 在庫なし商品の検索条件をセッションに付与し, 商品マスタへリダイレクトする.
383
        $searchData = [];
384
        $searchData['stock'] = [ProductStock::OUT_OF_STOCK];
385
        $session = $request->getSession();
386
        $session->set('eccube.admin.product.search', $searchData);
387
388
        return $this->redirectToRoute('admin_product_page', [
389
            'page_no' => 1,
390
        ]);
391
    }
392
393
    /**
394
     * 本会員の検索結果を表示する.
395
     *
396
     * @Route("/%eccube_admin_route%/search_customer", name="admin_homepage_customer")
397 4
     *
398
     * @param Request $request
399 4
     *
400
     * @return \Symfony\Component\HttpFoundation\Response
401
     */
402
    public function searchCustomer(Request $request)
403
    {
404
        $searchData = [];
405
        $searchData['customer_status'] = [CustomerStatus::REGULAR];
406
        $session = $request->getSession();
407
        $session->set('eccube.admin.customer.search', $searchData);
408
409
        return $this->redirectToRoute('admin_customer_page', [
410 4
            'page_no' => 1,
411 4
        ]);
412 4
    }
413 4
414 4
    /**
415 4
     * @param \Doctrine\ORM\EntityManagerInterface $em
0 ignored issues
show
There is no parameter named $em. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
416 4
     * @param array $excludes
417 4
     *
418 1
     * @return null|Request
419
     */
420
    private function getOrderEachStatus(array $excludes)
421 4
    {
422
        $sql = 'SELECT
423
                    t1.order_status_id as status,
424
                    COUNT(t1.id) as count
425
                FROM
426
                    dtb_order t1
427
                WHERE
428
                    t1.order_status_id NOT IN (:excludes)
429 4
                GROUP BY
430
                    t1.order_status_id
431
                ORDER BY
432
                    t1.order_status_id';
433 4
        $rsm = new ResultSetMapping();
434
        $rsm->addScalarResult('status', 'status');
435
        $rsm->addScalarResult('count', 'count');
436
        $query = $this->entityManager->createNativeQuery($sql, $rsm);
437
        $query->setParameters([':excludes' => $excludes]);
438
        $result = $query->getResult();
439
        $orderArray = [];
440
        foreach ($result as $row) {
441
            $orderArray[$row['status']] = $row['count'];
442
        }
443
444
        return $orderArray;
445 4
    }
446 4
447 4
    /**
448 4
     * @param $dateTime
449
     *
450 4
     * @return array|mixed
451
     *
452 4
     * @throws \Doctrine\ORM\NonUniqueResultException
453 3
     */
454 View Code Duplication
    private function getSalesByDay($dateTime)
455
    {
456
        // concat... for pgsql
457 4
        // http://stackoverflow.com/questions/1091924/substr-does-not-work-with-datatype-timestamp-in-postgres-8-3
458
        $dql = 'SELECT
459
                  SUBSTRING(CONCAT(o.order_date, \'\'), 1, 10) AS order_day,
460
                  SUM(o.payment_total) AS order_amount,
461
                  COUNT(o) AS order_count
462
                FROM
463
                  Eccube\Entity\Order o
464
                WHERE
465 4
                    o.OrderStatus NOT IN (:excludes)
466
                    AND SUBSTRING(CONCAT(o.order_date, \'\'), 1, 10) = SUBSTRING(:targetDate, 1, 10)
467
                GROUP BY
468
                  order_day';
469 4
470
        $q = $this->entityManager
471
            ->createQuery($dql)
472
            ->setParameter(':excludes', $this->excludes)
473
            ->setParameter(':targetDate', $dateTime);
474
475
        $result = [];
476
        try {
477
            $result = $q->getSingleResult();
478
        } catch (NoResultException $e) {
479
            // 結果がない場合は空の配列を返す.
480
        }
481 4
482 4
        return $result;
483 4
    }
484 4
485
    /**
486 4
     * @param $dateTime
487
     *
488 4
     * @return array|mixed
489 3
     *
490
     * @throws \Doctrine\ORM\NonUniqueResultException
491
     */
492 View Code Duplication
    private function getSalesByMonth($dateTime)
493 4
    {
494
        // concat... for pgsql
495
        // http://stackoverflow.com/questions/1091924/substr-does-not-work-with-datatype-timestamp-in-postgres-8-3
496
        $dql = 'SELECT
497
                  SUBSTRING(CONCAT(o.order_date, \'\'), 1, 7) AS order_month,
498
                  SUM(o.payment_total) AS order_amount,
499
                  COUNT(o) AS order_count
500
                FROM
501
                  Eccube\Entity\Order o
502
                WHERE
503 4
                    o.OrderStatus NOT IN (:excludes)
504
                    AND SUBSTRING(CONCAT(o.order_date, \'\'), 1, 7) = SUBSTRING(:targetDate, 1, 7)
505 4
                GROUP BY
506 4
                  order_month';
507 4
508 4
        $q = $this->entityManager
509 4
            ->createQuery($dql)
510
            ->setParameter(':excludes', $this->excludes)
511 4
            ->setParameter(':targetDate', $dateTime);
512
513
        $result = [];
514
        try {
515
            $result = $q->getSingleResult();
516
        } catch (NoResultException $e) {
517
            // 結果がない場合は空の配列を返す.
518
        }
519
520
        return $result;
521 4
    }
522
523 4
    /**
524 4
     * 在庫切れ商品数を取得
525 4
     *
526 4
     * @return mixed
527
     *
528 4
     * @throws \Doctrine\ORM\NonUniqueResultException
529
     */
530
    private function countNonStockProducts()
531
    {
532
        $qb = $this->productRepository->createQueryBuilder('p')
533
            ->select('count(DISTINCT p.id)')
534
            ->innerJoin('p.ProductClasses', 'pc')
535
            ->where('pc.stock_unlimited = :StockUnlimited AND pc.stock = 0')
536
            ->setParameter('StockUnlimited', false);
537
538 4
        return $qb->getQuery()->getSingleScalarResult();
539
    }
540 4
541 4
    /**
542 4
     * 商品数を取得
543 4
     *
544
     * @return mixed
545 4
     *
546
     * @throws \Doctrine\ORM\NonUniqueResultException
547
     */
548
    private function countProducts()
549
    {
550
        $qb = $this->productRepository->createQueryBuilder('p')
551
            ->select('count(p.id)')
552
            ->where('p.Status in (:Status)')
553
            ->setParameter('Status', [ProductStatus::DISPLAY_SHOW, ProductStatus::DISPLAY_HIDE]);
554
555
        return $qb->getQuery()->getSingleScalarResult();
556
    }
557
558
    /**
559
     * 本会員数を取得
560
     *
561
     * @return mixed
562
     *
563
     * @throws \Doctrine\ORM\NonUniqueResultException
564
     */
565
    private function countCustomers()
566
    {
567
        $qb = $this->customerRepository->createQueryBuilder('c')
568
            ->select('count(c.id)')
569
            ->where('c.Status = :Status')
570
            ->setParameter('Status', CustomerStatus::REGULAR);
571
572
        return $qb->getQuery()->getSingleScalarResult();
573
    }
574
575
    /**
576
     * 期間指定のデータを取得
577
     *
578
     * @param Carbon $fromDate
579
     * @param Carbon $toDate
580
     * @param $format
581
     *
582
     * @return array
583
     */
584
    private function getData(Carbon $fromDate, Carbon $toDate, $format)
585
    {
586
        $qb = $this->orderRepository->createQueryBuilder('o')
587
            ->andWhere('o.order_date >= :fromDate')
588
            ->andWhere('o.order_date <= :toDate')
589
            ->andWhere('o.OrderStatus NOT IN (:excludes)')
590
            ->setParameter(':excludes', $this->excludes)
591
            ->setParameter(':fromDate', $fromDate->copy())
592
            ->setParameter(':toDate', $toDate->copy())
593
            ->orderBy('o.order_date');
594
595
        $result = $qb->getQuery()->getResult();
596
597
        return $this->convert($result, $fromDate, $toDate, $format);
598
    }
599
600
    /**
601
     * 期間毎にデータをまとめる
602
     *
603
     * @param $result
604
     * @param Carbon $fromDate
605
     * @param Carbon $toDate
606
     * @param $format
607
     *
608
     * @return array
609
     */
610
    private function convert($result, Carbon $fromDate, Carbon $toDate, $format)
611
    {
612
        $raw = [];
613
        for ($date = $fromDate; $date <= $toDate; $date = $date->addDay()) {
614
            $raw[$date->format($format)]['price'] = 0;
615
            $raw[$date->format($format)]['count'] = 0;
616
        }
617
618
        foreach ($result as $Order) {
619
            $raw[$Order->getOrderDate()->format($format)]['price'] += $Order->getPaymentTotal();
620
            ++$raw[$Order->getOrderDate()->format($format)]['count'];
621
        }
622
623
        return $raw;
624
    }
625
}
626