<?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 Eccube\Common\EccubeConfig; 
use Eccube\Entity\Customer; 
use Eccube\Entity\Member; 
use Eccube\Request\Context; 
use Psr\Container\ContainerInterface; 
use Symfony\Component\EventDispatcher\EventSubscriberInterface; 
use Symfony\Component\HttpKernel\Event\ControllerEvent; 
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; 
use Symfony\Component\HttpKernel\KernelEvents; 
use Symfony\Component\RateLimiter\RateLimiterFactory; 
use Symfony\Component\Security\Core\User\UserInterface; 
 
class RateLimiterListener implements EventSubscriberInterface 
{ 
    private ContainerInterface $locator; 
    private EccubeConfig $eccubeConfig; 
    private Context $requestContext; 
 
    public function __construct(ContainerInterface $locator, EccubeConfig $eccubeConfig, Context $requestContext) 
    { 
        $this->locator = $locator; 
        $this->eccubeConfig = $eccubeConfig; 
        $this->requestContext = $requestContext; 
    } 
 
    public function onController(ControllerEvent $event) 
    { 
        if (!$event->isMainRequest()) { 
            return; 
        } 
 
        $request = $event->getRequest(); 
        $route = $request->attributes->get('_route'); 
        $limiterConfigs = $this->eccubeConfig['eccube_rate_limiter_configs']; 
 
        if (!isset($limiterConfigs[$route])) { 
            return; 
        } 
        $method = $request->getMethod(); 
 
        foreach ($limiterConfigs[$route] as $id => $config) { 
            $methods = array_filter($config['method'], fn ($m) => $m === $method); 
            if (empty($methods)) { 
                // http methodが不一致であればスキップ 
                continue; 
            } 
 
            if (!empty($config['params'])) { 
                $matchParams = array_filter($config['params'], function ($value, $key) use ($request) { 
                    return $request->get($key) === $value; 
                }, ARRAY_FILTER_USE_BOTH); 
 
                if (count($config['params']) !== count($matchParams)) { 
                    // パラメータが不一致であればスキップ 
                    continue; 
                } 
            } 
 
            $limiterId = 'limiter.'.$id; 
            if (!$this->locator->has($limiterId)) { 
                continue; 
            } 
            /** @var RateLimiterFactory $factory */ 
            $factory = $this->locator->get($limiterId); 
            if (in_array('customer', $config['type']) || in_array('user', $config['type'])) { 
                $User = $this->requestContext->getCurrentUser(); 
                if ($User instanceof UserInterface) { 
                    $limiter = $factory->create($User->getId()); 
                    if (!$limiter->consume()->isAccepted()) { 
                        throw new TooManyRequestsHttpException(); 
                    } 
                } 
            } 
            if (in_array('ip', $config['type'])) { 
                $limiter = $factory->create($request->getClientIp()); 
                if (!$limiter->consume()->isAccepted()) { 
                    throw new TooManyRequestsHttpException(); 
                } 
            } 
        } 
    } 
 
    /** 
     * {@inheritdoc} 
     */ 
    public static function getSubscribedEvents() 
    { 
        return [ 
            KernelEvents::CONTROLLER => ['onController', 0], 
        ]; 
    } 
}