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');
0 ignored issues
show
Documentation Bug introduced by
It seems like $event->getArgument('excludes') of type * is incompatible with the declared type array of property $excludes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
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
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