<?php 
 
/* 
 * This file is part of EC-CUBE 
 * 
 * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved. 
 * 
 * http://www.ec-cube.co.jp/ 
 * 
 * For the full copyright and license information, please view the LICENSE 
 * file that was distributed with this source code. 
 */ 
 
namespace Eccube\EventListener; 
 
use Detection\MobileDetect; 
use Doctrine\ORM\NoResultException; 
use Eccube\Common\EccubeConfig; 
use Eccube\Entity\AuthorityRole; 
use Eccube\Entity\Layout; 
use Eccube\Entity\Master\DeviceType; 
use Eccube\Entity\Member; 
use Eccube\Entity\Page; 
use Eccube\Entity\PageLayout; 
use Eccube\Repository\AuthorityRoleRepository; 
use Eccube\Repository\BaseInfoRepository; 
use Eccube\Repository\BlockPositionRepository; 
use Eccube\Repository\LayoutRepository; 
use Eccube\Repository\Master\DeviceTypeRepository; 
use Eccube\Repository\PageLayoutRepository; 
use Eccube\Repository\PageRepository; 
use Eccube\Request\Context; 
use Eccube\Service\SystemService; 
use Symfony\Component\EventDispatcher\EventSubscriberInterface; 
use Symfony\Component\HttpKernel\Event\RequestEvent; 
use Symfony\Component\HttpKernel\KernelEvents; 
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; 
use Twig\Environment; 
 
class TwigInitializeListener implements EventSubscriberInterface 
{ 
    /** 
     * @var bool 初期化済かどうか. 
     */ 
    protected $initialized = false; 
 
    /** 
     * @var Environment 
     */ 
    protected $twig; 
 
    /** 
     * @var BaseInfoRepository 
     */ 
    protected $baseInfoRepository; 
 
    /** 
     * @var DeviceTypeRepository 
     */ 
    protected $deviceTypeRepository; 
 
    /** 
     * @var PageRepository 
     */ 
    protected $pageRepository; 
 
    /** 
     * @var PageLayoutRepository 
     */ 
    protected $pageLayoutRepository; 
 
    /** 
     * @var BlockPositionRepository 
     */ 
    protected $blockPositionRepository; 
 
    /** 
     * @var Context 
     */ 
    protected $requestContext; 
 
    /** 
     * @var AuthorityRoleRepository 
     */ 
    private $authorityRoleRepository; 
 
    /** 
     * @var EccubeConfig 
     */ 
    private $eccubeConfig; 
 
    /** 
     * @var MobileDetect 
     */ 
    private $mobileDetector; 
 
    /** 
     * @var UrlGeneratorInterface 
     */ 
    private $router; 
 
    /** 
     * @var LayoutRepository 
     */ 
    private $layoutRepository; 
 
    /** 
     * @var SystemService 
     */ 
    protected $systemService; 
 
    /** 
     * TwigInitializeListener constructor. 
     */ 
    public function __construct( 
        Environment $twig, 
        BaseInfoRepository $baseInfoRepository, 
        PageRepository $pageRepository, 
        PageLayoutRepository $pageLayoutRepository, 
        BlockPositionRepository $blockPositionRepository, 
        DeviceTypeRepository $deviceTypeRepository, 
        AuthorityRoleRepository $authorityRoleRepository, 
        EccubeConfig $eccubeConfig, 
        Context $context, 
        MobileDetect $mobileDetector, 
        UrlGeneratorInterface $router, 
        LayoutRepository $layoutRepository, 
        SystemService $systemService 
    ) { 
        $this->twig = $twig; 
        $this->baseInfoRepository = $baseInfoRepository; 
        $this->pageRepository = $pageRepository; 
        $this->pageLayoutRepository = $pageLayoutRepository; 
        $this->blockPositionRepository = $blockPositionRepository; 
        $this->deviceTypeRepository = $deviceTypeRepository; 
        $this->authorityRoleRepository = $authorityRoleRepository; 
        $this->eccubeConfig = $eccubeConfig; 
        $this->requestContext = $context; 
        $this->mobileDetector = $mobileDetector; 
        $this->router = $router; 
        $this->layoutRepository = $layoutRepository; 
        $this->systemService = $systemService; 
    } 
 
    /** 
     * @throws NoResultException 
     * @throws \Doctrine\ORM\NonUniqueResultException 
     */ 
    public function onKernelRequest(RequestEvent $event) 
    { 
        if ($this->initialized) { 
            return; 
        } 
 
        $this->twig->addGlobal('BaseInfo', $this->baseInfoRepository->get()); 
 
        if ($this->requestContext->isAdmin()) { 
            $this->setAdminGlobals($event); 
        } else { 
            $this->setFrontVariables($event); 
        } 
 
        $this->initialized = true; 
    } 
 
    /** 
     * @throws \Doctrine\ORM\NonUniqueResultException 
     */ 
    public function setFrontVariables(RequestEvent $event) 
    { 
        $request = $event->getRequest(); 
        /** @var \Symfony\Component\HttpFoundation\ParameterBag $attributes */ 
        $attributes = $request->attributes; 
        $route = $attributes->get('_route'); 
        if ($route == 'user_data') { 
            $routeParams = $attributes->get('_route_params', []); 
            $route = isset($routeParams['route']) ? $routeParams['route'] : $attributes->get('route', ''); 
        } 
 
        $type = DeviceType::DEVICE_TYPE_PC; 
        if ($this->mobileDetector->isMobile()) { 
            $type = DeviceType::DEVICE_TYPE_MB; 
        } 
 
        // URLからPageを取得 
        /** @var Page $Page */ 
        $Page = $this->pageRepository->getPageByRoute($route); 
 
        /** @var PageLayout[] $PageLayouts */ 
        $PageLayouts = $Page->getPageLayouts(); 
 
        // Pageに紐づくLayoutからDeviceTypeが一致するLayoutを探す 
        $Layout = null; 
        foreach ($PageLayouts as $PageLayout) { 
            if ($PageLayout->getDeviceTypeId() == $type) { 
                $Layout = $PageLayout->getLayout(); 
                break; 
            } 
        } 
 
        // Pageに紐づくLayoutにDeviceTypeが一致するLayoutがない場合はPCのレイアウトを探す 
        if (!$Layout) { 
            log_info('fallback to PC layout'); 
            foreach ($PageLayouts as $PageLayout) { 
                if ($PageLayout->getDeviceTypeId() == DeviceType::DEVICE_TYPE_PC) { 
                    $Layout = $PageLayout->getLayout(); 
                    break; 
                } 
            } 
        } 
 
        // 管理者ログインしている場合にページレイアウトのプレビューが可能 
        if ($request->get('preview')) { 
            $is_admin = $request->getSession()->has('_security_admin'); 
            if ($is_admin) { 
                $Layout = $this->layoutRepository->get(Layout::DEFAULT_LAYOUT_PREVIEW_PAGE); 
 
                $this->twig->addGlobal('Layout', $Layout); 
                $this->twig->addGlobal('Page', $Page); 
                $this->twig->addGlobal('title', $Page->getName()); 
 
                return; 
            } 
        } 
 
        if ($Layout) { 
            // lazy loadを制御するため, Layoutを取得しなおす. 
            $Layout = $this->layoutRepository->get($Layout->getId()); 
        } else { 
            // Layoutのデータがない場合は空のLayoutをセット 
            $Layout = new Layout(); 
        } 
 
        $this->twig->addGlobal('Layout', $Layout); 
        $this->twig->addGlobal('Page', $Page); 
        $this->twig->addGlobal('title', $Page->getName()); 
        $this->twig->addGlobal('isMaintenance', $this->systemService->isMaintenanceMode()); 
    } 
 
    public function setAdminGlobals(RequestEvent $event) 
    { 
        // メニュー表示用配列. 
        $menus = []; 
        $this->twig->addGlobal('menus', $menus); 
 
        // メニューの権限制御. 
        $eccubeNav = $this->eccubeConfig['eccube_nav']; 
 
        $Member = $this->requestContext->getCurrentUser(); 
        if ($Member instanceof Member) { 
            $AuthorityRoles = $this->authorityRoleRepository->findBy(['Authority' => $Member->getAuthority()]); 
            $baseUrl = $event->getRequest()->getBaseUrl().'/'.$this->eccubeConfig['eccube_admin_route']; 
            $eccubeNav = $this->getDisplayEccubeNav($eccubeNav, $AuthorityRoles, $baseUrl); 
        } 
        $this->twig->addGlobal('eccubeNav', $eccubeNav); 
    } 
 
    /** 
     * URLに対する権限有無チェックして表示するNavを返す 
     * 
     * @param array $parentNav 
     * @param AuthorityRole[] $AuthorityRoles 
     * @param string $baseUrl 
     * 
     * @return array 
     */ 
    private function getDisplayEccubeNav($parentNav, $AuthorityRoles, $baseUrl) 
    { 
        $restrictUrls = $this->eccubeConfig['eccube_restrict_file_upload_urls']; 
 
        foreach ($parentNav as $key => $childNav) { 
            if (array_key_exists('children', $childNav) && count($childNav['children']) > 0) { 
                // 子のメニューがある場合は子の権限チェック 
                $parentNav[$key]['children'] = $this->getDisplayEccubeNav($childNav['children'], $AuthorityRoles, $baseUrl); 
 
                if (count($parentNav[$key]['children']) <= 0) { 
                    // 子が存在しない場合は配列から削除 
                    unset($parentNav[$key]); 
                } 
            } elseif (array_key_exists('url', $childNav)) { 
                // 子のメニューがなく、URLが設定されている場合は権限があるURLか確認 
                $param = array_key_exists('param', $childNav) ? $childNav['param'] : []; 
                $url = $this->router->generate($childNav['url'], $param); 
                foreach ($AuthorityRoles as $AuthorityRole) { 
                    $denyUrl = str_replace('/', '\/', $baseUrl.$AuthorityRole->getDenyUrl()); 
                    if (preg_match("/^({$denyUrl})/i", $url)) { 
                        // 権限がないURLの場合は配列から削除 
                        unset($parentNav[$key]); 
                        break; 
                    } 
                } 
 
                if ($this->eccubeConfig['eccube_restrict_file_upload'] === '1' && in_array($childNav['url'], $restrictUrls)) { 
                    unset($parentNav[$key]); 
                } 
            } 
        } 
 
        return $parentNav; 
    } 
 
    /** 
     * {@inheritdoc} 
     */ 
    public static function getSubscribedEvents() 
    { 
        return [ 
            KernelEvents::REQUEST => [ 
                // SecurityServiceProviderで、認証処理が完了した後に実行. 
                ['onKernelRequest', 6], 
            ], 
        ]; 
    } 
}