<?php
namespace App\Service;
use App\Entity\Permission;
use App\Entity\Role;
use Symfony\Component\HttpFoundation\RequestStack;
use Doctrine\Common\Annotations\Reader;
use App\Entity\Log;
use App\Exception\CacheHitResponse;
use App\Exception\AccessDeniedException;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Gedmo\Translatable\TranslatableListener;
/**
* Class RequestService
* @package App\Service
*/
class RequestService extends BaseService
{
public $exception = null;
public $exceptionCode = 0;
public $methodName = null;
public $logging = false;
public $log = null;
public $useCache = false;
public $cacheResponseKey = null;
public $isBackend = false;
public $language = 'fr';
public $deletedEntities = [];
public $timezone = null;
private SessionInterface $sessionInterface;
/**
* RequestService constructor.
* @param BaseService $baseService
* @param Reader $reader
* @param UserAgentService $userAgentService
*/
public function __construct(
BaseService $baseService,
Reader $reader,
RequestStack $requestStack,
SessionInterface $sessionInterface,
TranslatableListener $translatableListener
) {
$this->reflectFromParent($baseService);
$this->reader = $reader;
$this->requestStack = $requestStack;
$this->sessionInterface = $sessionInterface;
$this->translatableListener = $translatableListener;
}
public function checkPermission($methodName, $controller)
{
if ($methodName == 'showAction') return;
$this->isBackend = strpos(get_class($controller), 'Backend') !== false;
$token = $this->getToken();
if ((!$token || !($token->getUser() instanceof \App\Entity\User)) && (!$this->isBackend || $methodName === 'authenticate')) {
return;
}
// except public route
$isPublicRoute = $this->checkAnnotation(
'App\Annotation\PermissionPublic',
$methodName,
$controller
);
if ($isPublicRoute) return;
// check user route
$isUserRoute = $this->checkAnnotation(
'App\Annotation\PermissionUser',
$methodName,
$controller
);
if ($isUserRoute && !empty($token)) return;
// check admin route
$isAdminRoute = $this->checkAnnotation(
'App\Annotation\PermissionAdmin',
$methodName,
$controller
);
if ($isAdminRoute) {
// If super admin -> permit allow all
if (!empty($token) && $this->isSuperAdmin($this->getUser())) {
return;
}
if (empty($token) || !$this->isAdmin($this->getUser())) {
throw new AccessDeniedException(AccessDeniedException::NOT_PERMITTED, $methodName);
}
$controllerClassName = get_class($controller);
if (!$this->userService->isGranted($this->getUser(), $controllerClassName, $methodName)) {
throw new AccessDeniedException(AccessDeniedException::NOT_PERMITTED, $methodName);
}
return;
}
}
public function startLogging($methodName, $controller)
{
$this->methodName = $methodName;
$this->logging = $this->checkAnnotation('App\Annotation\Log', $methodName, $controller);
}
public function startCache($methodName, $controller)
{
$request = $this->requestStack->getCurrentRequest();
$this->language = $request->headers->get('X-Language') ?? 'fr';
$this->useCache = $this->checkAnnotation('App\Annotation\Cache', $methodName, $controller);
if ($this->useCache === true) {
$route = $request->attributes->get('_route');
$routeParams = $request->attributes->get('_route_params');
$allInputs = array_merge($this->stripRequest($request), $routeParams);
$allInputs['lang'] = $this->language;
$hashedInputs = md5(json_encode($allInputs, 1));
$this->cacheResponseKey = 'Response.' . $route . '.' . $hashedInputs;
$cacheResponse = $this->cache->getItem($this->cacheResponseKey);
if ($cacheResponse->isHit()) {
$payload = json_decode($cacheResponse->get(), 1);
$data = json_encode($payload['data'], 1);
throw new CacheHitResponse($data, $payload['code']);
}
}
}
public function checkAnnotation($nameAnnotation, $methodName, $controller)
{
$controllerName = $this->commonService->getClassName($controller);
$annotationShortName = $this->commonService->getClassName($nameAnnotation);
$cacheKey = 'Annotation.' . $annotationShortName . '.' . $controllerName . '.' . $methodName;
$cachedCheckAnnotation = $this->cache->getItem($cacheKey);
if ($cachedCheckAnnotation->isHit()) {
return $cachedCheckAnnotation->get() == 'yes';
}
$reflectionClass = new \ReflectionClass($controller);
$reflectionObject = new \ReflectionObject($controller);
$reflectionMethod = $reflectionObject->getMethod($methodName);
$hasAnnotation = false;
if (!is_null($reflectionClass) && !is_null($reflectionMethod)) {
$classAnnotation = $this->reader
->getClassAnnotation($reflectionClass, $nameAnnotation);
$methodAnnotation = $this->reader
->getMethodAnnotation($reflectionMethod, $nameAnnotation);
if ($classAnnotation || $methodAnnotation) {
$hasAnnotation = true;
}
}
$cachedCheckAnnotation->set($hasAnnotation === true ? 'yes' : 'no');
$this->cache->save($cachedCheckAnnotation);
return $hasAnnotation;
}
public function saveLog($code, $content, $methodName = null, $force = false)
{
if ($this->logging || $force) {
$this->logging = false;
$request = $this->requestStack->getCurrentRequest();
$log = new Log();
$log->setIp($request->getClientIp());
$log->setMethod($request->getMethod());
$methodName = $this->methodName ? $methodName : $methodName;
if ($methodName) {
$log->setPermission($this->permissionRepo->findOneBy(['action' => $methodName]));
}
if (!is_null($this->getUser())) {
$log->setUserId($this->getUser()->getId());
}
$allInputs = $this->stripRequest($request);
$log->setRequest($allInputs);
$log->setUrl($request->getRequestUri());
$log->setResult($code);
if ($this->exception) {
$log->setResult($this->exceptionCode);
$log->setException($this->exception->getLine() . " of " . $this->exception->getFile() . ' ' . $this->exception->getTraceAsString());
} else {
$log->setResult($code);
}
$response = json_decode($content, 1);
if (is_string($response)) {
$response = [$response];
}
$log->setResponse($response, 1);
$log->setSize(memory_get_usage() / 1000);
$log->setResponseTime((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000);
$logRepo = $this->logRepo;
$userAgentHeader = $request->headers->get('user-agent');
$callback = function () use ($logRepo, $log, $userAgentHeader) {
if ($userAgentHeader) {
$log->setUserAgent($this->getUserAgent($userAgentHeader));
}
$logRepo->save($log, true);
};
$logRepo->getEntityManager()->transactional($callback);
}
}
public function stripRequest($request)
{
$allInputs = $request->request->all() + $request->query->all();
if (count($allInputs) > 0) {
if (isset($allInputs['result']['websiteLogo'])) {
unset($allInputs['result']['websiteLogo']);
}
if (isset($allInputs['result']['websiteBanner'])) {
unset($allInputs['result']['websiteBanner']);
}
if (isset($allInputs['result']['facebookLogo'])) {
unset($allInputs['result']['facebookLogo']);
}
if (isset($allInputs['result']['facebookBanner'])) {
unset($allInputs['result']['facebookBanner']);
}
}
return $allInputs;
}
public function archiveDeletedEntities()
{
foreach ($this->deletedEntities as $entity) {
$this->archiveService->add($entity, null, null, null, true);
}
$this->getEntityManager()->flush();
}
public function getTimezone($controller)
{
$request = $this->requestStack->getCurrentRequest();
$session = $this->sessionInterface;
if ($request->headers->has('X-Ozone-Spot')) {
$this->timezone = $request->headers->get('X-Ozone-Spot');
//$this->logger->error('found timezone in header', [$this->timezone]);
} else if ($session->has('X-Ozone-Spot')) {
$this->timezone = $session->get('X-Ozone-Spot');
//$this->logger->error('found timezone in session', [$this->timezone]);
} else {
$ipdata = @file_get_contents('http://ip-api.com/json/' . $_SERVER['REMOTE_ADDR']);
if ($ipdata) {
$ipDataArr = json_decode($ipdata, 1);
if (is_array($ipDataArr) && isset($ipDataArr['timezone'])) {
$this->timezone = $ipDataArr['timezone'];
//$this->logger->error('found timezone in external api', [$this->timezone]);
$session->set('X-Ozone-Spot', $this->timezone);
return $this->timezone;
}
}
}
if (!$this->timezone) {
$this->timezone = $this->params->get('fallback_timezone');
//$this->logger->error('use fall back timezone', [$this->timezone]);
$session->set('X-Ozone-Spot', $this->timezone);
}
//$this->logger->error('timezone', [$this->timezone]);
return $this->timezone;
//todo: set timezone on response
}
public function getUserAgent($name)
{
$userAgent = $this->userAgentRepo->findOneBy(['name' => $name]);
if (!$userAgent) {
$userAgent = $this->userAgentService->newEntity();
$userAgent->setName($name);
$this->userAgentRepo->save($userAgent, true);
}
return $userAgent;
}
public function setLocale($methodName, $controller)
{
$request = $this->requestStack->getCurrentRequest();
if ($request->attributes->has('_locale')) {
$this->translatableListener->setTranslatableLocale($request->attributes->get('_locale'));
} else {
$this->translatableListener->setTranslatableLocale($request->headers->get('X-Language') ?? 'fr');
}
}
public function setTrackingCode()
{
if ($this->sessionInterface->has('trackingCode')) return;
$request = $this->requestStack->getCurrentRequest();
if ($request->query->has('utm_source')) {
$this->sessionInterface->set('trackingCode', $request->query->get('utm_source'));
}
}
}