src/Service/RequestService.php line 238

Open in your IDE?
  1. <?php
  2. namespace App\Service;
  3. use App\Entity\Permission;
  4. use App\Entity\Role;
  5. use Symfony\Component\HttpFoundation\RequestStack;
  6. use Doctrine\Common\Annotations\Reader;
  7. use App\Entity\Log;
  8. use App\Exception\CacheHitResponse;
  9. use App\Exception\AccessDeniedException;
  10. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  11. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  12. use Gedmo\Translatable\TranslatableListener;
  13. /**
  14.  * Class RequestService
  15.  * @package App\Service
  16.  */
  17. class RequestService extends BaseService
  18. {
  19.     public $exception = null;
  20.     public $exceptionCode = 0;
  21.     public $methodName = null;
  22.     public $logging = false;
  23.     public $log = null;
  24.     public $useCache = false;
  25.     public $cacheResponseKey = null;
  26.     public $isBackend = false;
  27.     public $language = 'fr';
  28.     public $deletedEntities = [];
  29.     public $timezone = null;
  30.     /**
  31.      * RequestService constructor.
  32.      * @param BaseService $baseService
  33.      * @param Reader $reader
  34.      * @param UserAgentService $userAgentService
  35.      */
  36.     public function __construct(
  37.         BaseService $baseService,
  38.         private Reader $reader,
  39.         private RequestStack $requestStack,
  40.         private TranslatableListener $translatableListener
  41.     ) {
  42.         $this->reflectFromParent($baseService);
  43.     }
  44.     public function checkPermission($methodName, $controller)
  45.     {
  46.         if ($methodName == 'showAction') return;
  47.         $this->isBackend = strpos(get_class($controller), 'Backend') !== false;
  48.         $token = $this->getToken();
  49.         if ((!$token || !($token->getUser() instanceof \App\Entity\User)) && (!$this->isBackend || $methodName === 'authenticate')) {
  50.             return;
  51.         }
  52.         // except public route
  53.         $isPublicRoute = $this->checkAnnotation(
  54.             'App\Annotation\PermissionPublic',
  55.             $methodName,
  56.             $controller
  57.         );
  58.         if ($isPublicRoute) return;
  59.         // check user route
  60.         $isUserRoute = $this->checkAnnotation(
  61.             'App\Annotation\PermissionUser',
  62.             $methodName,
  63.             $controller
  64.         );
  65.         if ($isUserRoute && !empty($token)) return;
  66.         // check admin route
  67.         $isAdminRoute = $this->checkAnnotation(
  68.             'App\Annotation\PermissionAdmin',
  69.             $methodName,
  70.             $controller
  71.         );
  72.         if ($isAdminRoute) {
  73.             // If super admin -> permit allow all
  74.             if (!empty($token) && $this->isSuperAdmin($this->getUser())) {
  75.                 return;
  76.             }
  77.             if (empty($token) || !$this->isAdmin($this->getUser())) {
  78.                 throw new AccessDeniedException(AccessDeniedException::NOT_PERMITTED, $methodName);
  79.             }
  80.             $controllerClassName = get_class($controller);
  81.             if (!$this->userService->isGranted($this->getUser(), $controllerClassName, $methodName)) {
  82.                 throw new AccessDeniedException(AccessDeniedException::NOT_PERMITTED, $methodName);
  83.             }
  84.             return;
  85.         }
  86.     }
  87.     public function startLogging($methodName, $controller)
  88.     {
  89.         $this->methodName = $methodName;
  90.         $this->logging = $this->checkAnnotation('App\Annotation\Log', $methodName, $controller);
  91.     }
  92.     public function startCache($methodName, $controller)
  93.     {
  94.         $request = $this->requestStack->getCurrentRequest();
  95.         $this->language = $request->headers->get('X-Language') ?? 'fr';
  96.         $this->useCache = $this->checkAnnotation('App\Annotation\Cache', $methodName, $controller);
  97.         if ($this->useCache === true) {
  98.             $route = $request->attributes->get('_route');
  99.             $routeParams = $request->attributes->get('_route_params');
  100.             $allInputs = array_merge($this->stripRequest($request), $routeParams);
  101.             $allInputs['lang'] = $this->language;
  102.             $hashedInputs = md5(json_encode($allInputs, 1));
  103.             $this->cacheResponseKey = 'Response.' . $route . '.' . $hashedInputs;
  104.             $cacheResponse = $this->appCache->getItem($this->cacheResponseKey);
  105.             if ($cacheResponse->isHit()) {
  106.                 $payload = json_decode($cacheResponse->get(), 1);
  107.                 $data = json_encode($payload['data'], 1);
  108.                 throw new CacheHitResponse($data, $payload['code']);
  109.             }
  110.         }
  111.     }
  112.     public function checkAnnotation($nameAnnotation, $methodName, $controller)
  113.     {
  114.         $controllerName = $this->commonService->getClassName($controller);
  115.         $annotationShortName = $this->commonService->getClassName($nameAnnotation);
  116.         $cacheKey = 'Annotation.' . $annotationShortName . '.' . $controllerName . '.' . $methodName;
  117.         $cachedCheckAnnotation = $this->appCache->getItem($cacheKey);
  118.         if ($cachedCheckAnnotation->isHit()) {
  119.             return $cachedCheckAnnotation->get() == 'yes';
  120.         }
  121.         $reflectionClass = new \ReflectionClass($controller);
  122.         $reflectionObject = new \ReflectionObject($controller);
  123.         $reflectionMethod = $reflectionObject->getMethod($methodName);
  124.         $hasAnnotation = false;
  125.         if (!is_null($reflectionClass) && !is_null($reflectionMethod)) {
  126.             $classAnnotation = $this->reader
  127.                 ->getClassAnnotation($reflectionClass, $nameAnnotation);
  128.             $methodAnnotation = $this->reader
  129.                 ->getMethodAnnotation($reflectionMethod, $nameAnnotation);
  130.             if ($classAnnotation || $methodAnnotation) {
  131.                 $hasAnnotation = true;
  132.             }
  133.         }
  134.         $cachedCheckAnnotation->set($hasAnnotation === true ? 'yes' : 'no');
  135.         $this->appCache->save($cachedCheckAnnotation);
  136.         return $hasAnnotation;
  137.     }
  138.     public function saveLog($code, $content, $methodName = null, $force = false)
  139.     {
  140.         if ($this->logging || $force) {
  141.             $this->logging = false;
  142.             $request = $this->requestStack->getCurrentRequest();
  143.             $log = new Log();
  144.             $log->setIp($request->getClientIp());
  145.             $log->setMethod($request->getMethod());
  146.             $methodName = $this->methodName ? $methodName : $methodName;
  147.             if ($methodName) {
  148.                 $log->setPermission($this->permissionRepo->findOneBy(['action' => $methodName]));
  149.             }
  150.             if (!is_null($this->getUser())) {
  151.                 $log->setUserId($this->getUser()->getId());
  152.             }
  153.             $allInputs = $this->stripRequest($request);
  154.             $log->setRequest($allInputs);
  155.             $log->setUrl($request->getRequestUri());
  156.             $log->setResult($code);
  157.             if ($this->exception) {
  158.                 $log->setResult($this->exceptionCode);
  159.                 $log->setException($this->exception->getLine() . " of " . $this->exception->getFile() . ' ' . $this->exception->getTraceAsString());
  160.             } else {
  161.                 $log->setResult($code);
  162.             }
  163.             $response = json_decode($content, 1);
  164.             if (is_string($response)) {
  165.                 $response = [$response];
  166.             }
  167.             $log->setResponse($response, 1);
  168.             $log->setSize(memory_get_usage() / 1000);
  169.             $log->setResponseTime((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000);
  170.             $logRepo = $this->logRepo;
  171.             $userAgentHeader = $request->headers->get('user-agent');
  172.             $callback = function () use ($logRepo, $log, $userAgentHeader) {
  173.                 if ($userAgentHeader) {
  174.                     $log->setUserAgent($this->getUserAgent($userAgentHeader));
  175.                 }
  176.                 $logRepo->save($log, true);
  177.             };
  178.             $logRepo->getEntityManager()->transactional($callback);
  179.         }
  180.     }
  181.     public function stripRequest($request)
  182.     {
  183.         $allInputs = $request->request->all() + $request->query->all();
  184.         if (count($allInputs) > 0) {
  185.             if (isset($allInputs['result']['websiteLogo'])) {
  186.                 unset($allInputs['result']['websiteLogo']);
  187.             }
  188.             if (isset($allInputs['result']['websiteBanner'])) {
  189.                 unset($allInputs['result']['websiteBanner']);
  190.             }
  191.             if (isset($allInputs['result']['facebookLogo'])) {
  192.                 unset($allInputs['result']['facebookLogo']);
  193.             }
  194.             if (isset($allInputs['result']['facebookBanner'])) {
  195.                 unset($allInputs['result']['facebookBanner']);
  196.             }
  197.         }
  198.         return $allInputs;
  199.     }
  200.     public function archiveDeletedEntities()
  201.     {
  202.         foreach ($this->deletedEntities as $entity) {
  203.             $this->archiveService->add($entity, null, null, null, true);
  204.         }
  205.         $this->getEntityManager()->flush();
  206.     }
  207.     public function getTimezone($controller)
  208.     {
  209.         $request = $this->requestStack->getCurrentRequest();
  210.         $session = $this->requestStack->getSession();
  211.         if ($request->headers->has('X-Ozone-Spot')) {
  212.             $this->timezone = $this->normalizeTimezone($request->headers->get('X-Ozone-Spot'));
  213.         } else if ($session->has('X-Ozone-Spot')) {
  214.             $this->timezone = $this->normalizeTimezone($session->get('X-Ozone-Spot'));
  215.         }
  216.         if (!$this->timezone) {
  217.             $this->timezone = $this->params->get('fallback_timezone');
  218.             $session->set('X-Ozone-Spot', $this->timezone);
  219.         }
  220.         //$this->logger->error('timezone', [$this->timezone]);
  221.         return $this->timezone;
  222.         //todo: set timezone on response
  223.     }
  224.     private function normalizeTimezone(?string $tz): ?string
  225.     {
  226.         if (!$tz) return null;
  227.         // Map deprecated IANA aliases to canonical names
  228.         $aliases = [
  229.             'Asia/Saigon'       => 'Asia/Ho_Chi_Minh',
  230.             'Asia/Calcutta'     => 'Asia/Kolkata',
  231.             'Asia/Katmandu'     => 'Asia/Kathmandu',
  232.             'Asia/Rangoon'      => 'Asia/Yangon',
  233.             'Asia/Ulaanbaatar'  => 'Asia/Ulaanbaatar',
  234.             'US/Eastern'        => 'America/New_York',
  235.             'US/Central'        => 'America/Chicago',
  236.             'US/Mountain'       => 'America/Denver',
  237.             'US/Pacific'        => 'America/Los_Angeles',
  238.         ];
  239.         $tz = $aliases[$tz] ?? $tz;
  240.         // Validate — return null if unknown so fallback kicks in
  241.         try {
  242.             new \DateTimeZone($tz);
  243.             return $tz;
  244.         } catch (\Exception $e) {
  245.             return null;
  246.         }
  247.     }
  248.     public function getUserAgent($name)
  249.     {
  250.         $userAgent = $this->userAgentRepo->findOneBy(['name' => $name]);
  251.         if (!$userAgent) {
  252.             $userAgent = $this->userAgentService->newEntity();
  253.             $userAgent->setName($name);
  254.             $this->userAgentRepo->save($userAgent, true);
  255.         }
  256.         return $userAgent;
  257.     }
  258.     public function setLocale($methodName, $controller)
  259.     {
  260.         $request = $this->requestStack->getCurrentRequest();
  261.         if ($request->attributes->has('_locale')) {
  262.             $this->translatableListener->setTranslatableLocale($request->attributes->get('_locale'));
  263.         } else {
  264.             $this->translatableListener->setTranslatableLocale($request->headers->get('X-Language') ?? 'fr');
  265.         }
  266.     }
  267.     public function setTrackingCode()
  268.     {
  269.         if ($this->requestStack->getSession()->has('trackingCode')) return;
  270.         $request = $this->requestStack->getCurrentRequest();
  271.         if ($request->query->has('utm_source')) {
  272.             $this->requestStack->getSession()->set('trackingCode', $request->query->get('utm_source'));
  273.         }
  274.     }
  275. }