<?php
namespace App\Controller;
use App\Entity\Company;
use App\Entity\Form\AnySearch;
use App\Entity\User;
use App\Entity\UserSettings;
use App\Enum\User\Permission;
use App\Enum\Theme\Theme;
use App\Enum\User\Role;
use App\Library\ModalMessageManager;
use App\Library\Route\RouteParamObjects;
use App\Library\Utils\Other\Other;
use App\Security\User\SecurityUser;
use App\Service\AccessToken\AccessTokenService;
use App\Service\CarWash\CarWashService;
use App\Service\ClientLegal\ClientLegalService;
use App\Service\Company\CompanyService;
use App\Service\Image\ImageService;
use App\Service\Notification\NotificationService;
use App\Service\RouteService;
use App\Service\ServiceRetriever;
use App\Service\ThemeService;
use App\Service\FileService;
use App\Service\User\UserService;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\HttpFoundation\Response;
use App\Security\Authenticator;
use Symfony\Component\Security\Core\Exception\AccessDeniedException as HttpAccessDeniedException;
abstract class BaseAbstractController extends AbstractController
{
const LOCATION_IF_ROUTE_NOT_AVAILABLE = "/admin/clients";
private $todayStart = null;
private $todayEnd = null;
/**
* @var RequestStack
*/
private $requestStack;
/**
* @var RouteService
*/
protected $routeService;
/**
* @var FileService
*/
private $fileService;
/**
* @var RouterInterface
*/
private $router;
/**
* @var CarWashService
*/
private $carWashService;
/**
* @var SessionInterface
*/
private $session;
/**
* @var EntityManagerInterface
*/
protected $em;
/**
* @var ClientLegalService
*/
private $clientLegalService;
/**
* @var NotificationService
*/
protected $notificationService;
/**
* @var ModalMessageManager
*/
protected $modalMessageManager;
/**
* @var ServiceRetriever
*/
protected $serviceRetriever;
/**
* @var Security
*/
protected $security;
/**
* @var ValidatorInterface
*/
protected $validator;
/**
* @var ImageService
*/
private $imageService;
private UserService $userService;
private AccessTokenService $accessTokenService;
private ?string $onAuthenticationFailUrl = null;
/**
* FrontendApiController constructor.
* @param RequestStack $requestStack
*
* auto unpack data
*/
public function __construct(Security $security, RequestStack $requestStack,
ValidatorInterface $validator, RouteService $routeService,
RouterInterface $router, SessionInterface $session, EntityManagerInterface $em,
FileService $fileService,
ServiceRetriever $serviceRetriever, ImageService $imageService = null,
UserService $userService, AccessTokenService $accessTokenService)
{
$this->security = $security;
$this->validator = $validator;
$this->routeService = $routeService;
$this->router = $router;
$this->requestStack = $requestStack;
$this->fileService = $fileService;
$this->session = $session;
$this->em = $em;
$this->userService = $userService;
$this->accessTokenService = $accessTokenService;
$this->serviceRetriever = $serviceRetriever;
$this->checkAuthentication();
$this->checkRouteAccess();
$routeName = $this->getRouteName();
// $routeWrapper = $routeName ? $this->routeService->getRouteWrapper($routeName) : null;
// if (!$userService->isRouteAvailable($routeName) || ($routeWrapper && (!$routeWrapper->isOn() || $routeWrapper->isInDevelopment()))) {
// // return $this->redirectToRoute($userService->getAvailableRoute()); //symfony bug - cant get container here
// if ($userService->isClientLegalUser()) {
// header('Location: /admin/profile');
// } else {
// header('Location: ' . self::LOCATION_IF_ROUTE_NOT_AVAILABLE);
// }
// exit;
// }
// $companyStatus = $user->getCompany() ? $user->getCompany()->getStatus() : '';
// if ($companyStatus == Status::STATUS_BLOCKED && strpos($_SERVER['REQUEST_URI'],'paymen')===false) {
// header('Location: /admin/payment');
// exit;
// }
// $this->modalMessageManager = $modalMessageManager;
$this->imageService = $imageService;
}
private function checkAuthentication(): void
{
$request = $this->getRequest();
$uri = $request->getRequestUri();
$isMessengerRoute = preg_match("/^\/1bc3f6fdaea8f6ae35($|.)/", $uri);
if (!$this->security->getUser()
&& !in_array($this->getRouteName(), ["zoom_webhook"])) {
if ($this->getRouteName() !== Authenticator::LOGIN_ROUTE
&& $this->getRouteName() !== "messenger_login" && !$isMessengerRoute && $uri != "/") {
$this->onAuthenticationFailure();
}
}
}
protected function onAuthenticationFailure()
{
header('Location: ' . $this->getOnAuthenticationFailUrl());
exit();
}
public function setOnAuthenticationFailUrl(string $url)
{
$this->onAuthenticationFailUrl = $url;
}
public function getOnAuthenticationFailUrl(): string
{
if (!$this->onAuthenticationFailUrl) {
return Authenticator::ON_AUTHENTICATION_FAIL_URL;
}
return $this->onAuthenticationFailUrl;
}
const ROUTES_DATA = [
'moderator_subscriber_edit' => [
'permission' => Permission::PERMISSION_CAN_EDIT_STUDENTS,
],
'moderator_subscriber_delete' => [
'permission' => Permission::PERMISSION_CAN_EDIT_STUDENTS,
],
'moderator_candidate_edit' => [
'permission' => Permission::PERMISSION_CAN_EDIT_CANDIDATES,
],
'moderator_candidate_delete' => [
'permission' => Permission::PERMISSION_CAN_EDIT_CANDIDATES,
],
'moderator__telegram_campaigns' => [
'permission' => Permission::PERMISSION_CAN_VIEW_CAMPAIGNS,
],
'moderator__payment_requisites' => [
'permission' => Permission::PERMISSION_CAN_VIEW_PAYMENT_REQUISITES,
],
];
private function checkRouteAccess(): void
{
/** @var User $user */
$user = $this->security->getUser();
if (!$user) {
return;
}
$routeName = $this->getRouteName();
if (!$this->isRouteAccessibleByPermission($routeName, $user)) {
throw new HttpAccessDeniedException();
}
// admins and developers have all rights regardless of user settings
if (in_array($routeName, ['moderator_calendar_events'])) {
if (!$user->getSettings()->isCanViewCalendarEvents()) {
throw new HttpAccessDeniedException();
}
} elseif (in_array($routeName, ['moderator_calendar_event_edit'])) {
if (!$user->getSettings()->isCanEditCalendarEvents()) {
throw new HttpAccessDeniedException();
}
} elseif (in_array($routeName, ['moderator_student_edit'])) {
if (!$user->getSettings()->isCanEditStudents()) {
throw new HttpAccessDeniedException();
}
} elseif (in_array($routeName, ['moderator_students'])) {
if (!$user->getSettings()->isCanViewStudents()) {
throw new HttpAccessDeniedException();
}
} elseif (in_array($routeName, ['moderator_payment_edit'])) {
if (!$user->getSettings()->isCanEditPayments()) {
throw new HttpAccessDeniedException();
}
} elseif (in_array($routeName, ['moderator_payments'])) {
if (!$user->getSettings()->isCanViewPayments()) {
throw new HttpAccessDeniedException();
}
} elseif (in_array($routeName, ['moderator_candidates'])) {
if (!$user->getSettings()->isCanViewCandidates()) {
throw new HttpAccessDeniedException();
}
} elseif (in_array($routeName, ['moderator_candidates_review'])) {
if (!$user->getSettings()->isCanReviewCandidates()) {
throw new HttpAccessDeniedException();
}
} elseif (in_array($routeName, ['moderator_lists_zoom'])) {
if (!$user->getSettings()->isCanViewZoomReport()) {
throw new HttpAccessDeniedException();
}
} elseif (in_array($routeName, ['moderator_payments_by_persons_report'])) {
if (!$user->getSettings()->isCanViewPaymentsReport()) {
throw new HttpAccessDeniedException();
}
} elseif (in_array($routeName, ['moderator_inner_payments'])) {
if (!$user->getSettings()->isCanViewInnerPayments()) {
throw new HttpAccessDeniedException();
}
} elseif (in_array($routeName, ['moderator_candidate_toggle_inner_payment'])) {
if (!$user->getSettings()->isCanEditInnerPayments()) {
throw new HttpAccessDeniedException();
}
}
}
private function isRouteAccessibleByPermission(string $routeName, User $user): bool
{
if (!isset(self::ROUTES_DATA[$routeName])) {
return true;
}
$permission = self::ROUTES_DATA[$routeName]['permission'] ?? null;
if ($permission) {
return $user->getSettings()->getPermission($permission) === true;
}
return true;
}
// public function isDemo(): bool
// {
// return $this->security->getUser()->isDemoRole();
// }
// public function isDesigner(): bool
// {
// return $this->security->getUser()->isDesignerRole();
// }
public function redirectOnPostInDemo(Request $request)
{
/** @var SecurityUser $user */
$user = $this->security->getUser();
if (($user->isDemoRole() || $user->isDesignerRole()) && $request->getMethod() == "POST") {
$redirectUrl = null;
try {
/** @var \Symfony\Component\HttpFoundation\Request $request */
$redirectUrl = $request->headers->get('referer');
} catch (\Exception $exception) {};
$this->addFlash('errors', "Невозможно изменить данные в демо-режиме");
header('Location: ' . ($redirectUrl ?? self::LOCATION_IF_ROUTE_NOT_AVAILABLE));
exit;
}
}
public function validate($item): bool
{
$errors = $this->validator->validate($item);
if ($errors->count()) {
foreach ($errors as $error) {
$this->addFlash('errors', $error->getMessage());
}
return false;
}
return true;
}
public function handleValidationResult($validationResult): bool
{
if (is_bool($validationResult)) {
return $validationResult;
} else {
foreach ($validationResult as $error) {
$this->addFlash('errors', $error);
}
return false;
}
}
// public function getCompany(): ?Company
// {
// return $this->companyService->get($this->security->getUser()->getCompany());
// }
// protected function isAdmin(): bool
// {
// $user = ($this->security->getUser() ? $this->security->getUser()->getAssociatedUser() : null);
// return $user instanceof User && $user->getRole() == Role::ROLE_ADMIN;
// }
// protected function getSecurityUser(): SecurityUser
// {
// return $this->security->getUser();
// }
public function getLoggedInUser(): ?User
{
return $this->userService->getLoggedInUser();
}
public function isAdminMainRoute(): bool
{
return preg_match("/^admin_main/", $this->requestStack->getCurrentRequest()->get('_route'));
}
public function setFile($form, array $requestData, $item, string $field, string $filesSubPath, bool $changeFileName = true)
{
$this->fileService->setEntityFile($item, $requestData, $field, $form, $filesSubPath, $changeFileName);
}
public function setImage($form, $item, array $requestData, string $field, string $imagesSubPath)
{
$this->setFile($form, $requestData, $item, $field, $imagesSubPath);
}
public function getRouteName(): ?string
{
return $this->requestStack->getCurrentRequest()->get('_route');
}
public function getRouteParamNames(string $route = null): array
{
if (!$route) {
$route = $this->getRouteName();
}
$routeCollection = $this->router->getRouteCollection();
$route = $routeCollection->get($route);
$parameterNames = $route->compile()->getVariables();
return $parameterNames;
}
public function getRequest(): Request
{
return $this->requestStack->getCurrentRequest();
}
/**
* @param Request $request
* @param $handleCallback
* Example: function (array $post, &$status, &$message, &$content) { }
* @return Response
*/
public function handleAjax(Request $request, $handleCallback): Response
{
throw new \Exception('');
// $status = 200;
// $content = null;
// $message = null;
// $error = null;
// try {
// $post = json_decode($request->getContent(), true);
// $handleCallback($post, $status, $message, $content);
// } catch (\Throwable $exception) {
// Other::appendLog('', \App\Controller\Admin\MainController::errorLogPath, [
// 'class' => __CLASS__,
// 'method' => __FUNCTION__,
// 'line' => __LINE__,
// 'error' => "AJAX error: ",
// $exception
// ]);
// $status = $status == 200 ? 500 : $status;
// $error = $exception->getMessage();
// } finally {
// $response = json_encode([
// "content" => $content,
// "message" => $message,
// "error" => $error,
// "status" => $status,
// "postedData" => $post,
// ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// return new Response($response, $status);
// }
}
public function addErrorFlashes(array $errors)
{
foreach ($errors as $error) {
$this->addFlash("errors", $error);
}
}
public function addSuccessFlashes(array $errors)
{
foreach ($errors as $error) {
$this->addFlash("success", $error);
}
}
// public function createSearch(): AnySearch
// {
// $request = $this->getRequest();
// return new AnySearch($request->get('page'));
// }
public function persistAll(array $entities)
{
foreach ($entities as $entity) {
$this->em->persist($entity);
}
}
public function getTodayStart(): \DateTime
{
if ($this->todayStart) {
return $this->todayStart;
}
$this->todayStart = (new \DateTime())->setTime(0, 0);
return $this->todayStart;
}
public function getTodayEnd(): \DateTime
{
if ($this->todayEnd) {
return $this->todayEnd;
}
$this->todayEnd = (new \DateTime())->setTime(23, 59, 59);
return $this->todayEnd;
}
public function getFormType(FormInterface $form): FormTypeInterface
{
return $form->getConfig()->getType()->getInnerType();
}
public function getRouteParamObjects(): RouteParamObjects
{
return $this->routeService->getRouteObjects();
}
// protected function render(string $view, array $parameters = [], Response $response = null): Response
// {
// //todo убрать этот метод в будущем после перехода на новый дизайн
// if ($view == "admin/base.html.twig") {
// return parent::render($view, $parameters, $response);
// }
// return parent::render($this->themeService->getTemplate($view, $this->getRouteName()), $parameters, $response);
// }
/**
* @param $callback
* @param null|callable $onSuccess
* @param array $handleExceptions
* @return void|null
* @throws \Throwable
*/
protected function tryWithFlashError($callback, ?callable $onSuccess, array $handleExceptions)
{
Other::tryDo($callback, null,
function (\Throwable $throwable) {
$this->addFlash("errors", $throwable->getMessage());
return null;
},
null, $onSuccess, $handleExceptions);
}
public function handleEntityCollectionField($entity, FormInterface $form, Request $request, string $fieldName, ArrayCollection $originalItemsCollection,
$afterCollectionItemPersistCallback = null, $beforeCollectionItemRemoveCallback = null)
{
if ($form->has($fieldName)) {
$collectionForm = $form->get($fieldName);
foreach ($collectionForm as $childForm) {
$collectionItem = $childForm->getData();
if ($collectionItem && null === $collectionItem->getId()) {
$this->em->persist($collectionItem);
}
if ($collectionItem && $afterCollectionItemPersistCallback) {
$afterCollectionItemPersistCallback($collectionItem, $childForm);
}
}
}
// Определяем удалённые сообщения: те, что были в originalMessages, но их нет в текущей коллекции
$method = "get" . ucfirst($fieldName);
foreach ($originalItemsCollection as $origCollectionItem) {
if (!$entity->$method()->contains($origCollectionItem)) {
// удалённый элемент
$beforeCollectionItemRemoveCallback($origCollectionItem);
// удалить сам объект сообщения
$this->em->remove($origCollectionItem);
}
}
}
public function isUserAdmin(): bool
{
$user = $this->getUser();
return $user && in_array(Role::ROLE_ADMIN, $user->getRoles());
}
protected function getUserSettings(): ?UserSettings
{
$user = $this->getLoggedInUser();
if (!$user) {
return null;
}
return $user->getSettings();
}
protected function handleRequestWithAccessToken($callback): Response
{
$token = $this->accessTokenService->getByCookies();
if (!$token || (!$token->isAlive() && !$token->isRefreshable())) {
$this->onAuthenticationFailure();
}
$response = $callback();
if (!$token->isAlive()) {
$token = $this->accessTokenService->refresh($token, true);
$this->accessTokenService->setResponseCookie($response, $token);
}
return $response;
}
public function uploadFile(Request $request): JsonResponse
{
/** @var UploadedFile|null $file */
$file = $request->files->get('file');
if (!$file) {
return $this->json([
'success' => false,
'message' => 'File is required.'
], 400);
}
unlink($file->getRealPath());
return new JsonResponse([]);
}
}