src/FlexApp/Controller/ChatBController.php line 204

Open in your IDE?
  1. <?php
  2. /** @noinspection PhpDocSignatureInspection */
  3. /**
  4.  * Created by Pavel Popov.
  5.  */
  6. namespace FlexApp\Controller;
  7. use DateTime;
  8. use DateTimeZone;
  9. use Doctrine\ORM\EntityManagerInterface;
  10. use Doctrine\ORM\NonUniqueResultException;
  11. use Exception;
  12. use FlexApp\Classes\FileTypeUtil;
  13. use FlexApp\Constant\ChatBBrowserToWsTypes;
  14. use FlexApp\Constant\ChatBWsMessageCategories;
  15. use FlexApp\Entity\ChatB;
  16. use FlexApp\Entity\FileEntity;
  17. use FlexApp\Entity\InterviewEntity;
  18. use FlexApp\Entity\SubscriptionsEntity;
  19. use FlexApp\Events\Style43\GeneralErrorEvent;
  20. use FlexApp\Exceptions\RegistrationException;
  21. use FlexApp\Exceptions\WebsocketMessageHandlerException;
  22. use FlexApp\Form\ChatBEmailFormType;
  23. use FlexApp\Repository\BoolDBSettingRepository;
  24. use FlexApp\Repository\ChatBMessageRepository;
  25. use FlexApp\Repository\ChatBRepository;
  26. use FlexApp\Repository\SubscriptionsEntityRepository;
  27. use FlexApp\Security\LoginFormAuthenticator;
  28. use FlexApp\Service\AjaxRegistrationByEmailService;
  29. use FlexApp\Service\ChangedUnreadCountHandler;
  30. use FlexApp\Service\ChatArrayGenerator;
  31. use FlexApp\Service\ChatOnMessageHandler;
  32. use FlexApp\Service\ChatStateRefresher;
  33. use FlexApp\Service\ConsDefiner;
  34. use FlexApp\Service\CurrentUserProvider;
  35. use FlexApp\Service\EntitySender;
  36. use FlexApp\Service\ManualReg\SetIsReadLogger;
  37. use FlexApp\Service\MoveFilesToAdmManager;
  38. use FlexApp\Service\ParametersProvider;
  39. use FlexApp\Service\PreviewGenerator;
  40. use FlexApp\Service\TelegramNotifier;
  41. use FlexApp\Service\TranslatorWrapper;
  42. use FlexApp\Service\WelcomeInfoDataGenerator;
  43. use Psr\Cache\InvalidArgumentException;
  44. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
  45. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  46. use Symfony\Component\Form\Form;
  47. use Symfony\Component\Form\FormFactoryInterface;
  48. use Symfony\Component\HttpFoundation\File\Exception\FileException;
  49. use Symfony\Component\HttpFoundation\File\UploadedFile;
  50. use Symfony\Component\HttpFoundation\JsonResponse;
  51. use Symfony\Component\HttpFoundation\RequestStack;
  52. use Symfony\Component\HttpFoundation\Response;
  53. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  54. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  55. use Symfony\Component\Lock\LockFactory;
  56. use Symfony\Component\Lock\Store\DoctrineDbalPostgreSqlStore;
  57. use Symfony\Component\Lock\Store\SemaphoreStore;
  58. use Symfony\Component\Routing\Annotation\Route;
  59. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  60. use Symfony\Component\Routing\RouterInterface;
  61. use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
  62. use Symfony\Contracts\Translation\TranslatorInterface;
  63. use Throwable;
  64. use Twig\Environment as Twig;
  65. use Twig\Error\Error;
  66. use Twig\Error\LoaderError;
  67. use Twig\Error\RuntimeError;
  68. use Twig\Error\SyntaxError;
  69. use WebBundle\Controller\ExtendedController;
  70. use WebBundle\Entity\Calendar;
  71. use WebBundle\Entity\User;
  72. use WebBundle\Helper\App;
  73. use WebBundle\Helper\Mailer;
  74. use WebBundle\Helper\PortalHelper;
  75. use WebBundle\Helper\UserHelper;
  76. use WebBundle\Repository\CalendarRepository;
  77. use WebBundle\Repository\UserRepository;
  78. /**
  79.  * Документация ✏ ✏ ✏ ✏ ✏ ✏ ✏
  80.  * https://docs.google.com/document/d/1nn7gwvNXZ6fx9tZnKoxE0cbh7DYhCR_pyUUXnZBlvKA/edit?usp=sharing
  81.  * Class ChatBController.
  82.  */
  83. class ChatBController extends ExtendedController
  84. {
  85.     const LOCK_KEY 'ajax-set-is-read';
  86.     /** @required */
  87.     public ChatOnMessageHandler $chatOnMessageHandler;
  88.     /** @required */
  89.     public UserRepository $userRepository;
  90.     /** @required */
  91.     public UserAuthenticatorInterface $userAuthenticator;
  92.     /** @required */
  93.     public LoginFormAuthenticator $loginFormAuthenticator;
  94.     private Twig $twig;
  95.     private ParametersProvider $parametersProvider;
  96.     private FormFactoryInterface $formFactory;
  97.     private RequestStack $requestStack;
  98.     private AjaxRegistrationByEmailService $ajaxRegistrationByEmailService;
  99.     private EventDispatcherInterface $eventDispatcher;
  100.     private SessionInterface $session;
  101.     private CurrentUserProvider $currentUserProvider;
  102.     private TranslatorWrapper $translatorWrapper;
  103.     private ChatBRepository $chatBRepository;
  104.     private ConsDefiner $consDefiner;
  105.     private EntityManagerInterface $entityManager;
  106.     private ChatBMessageRepository $chatBMessageRepository;
  107.     private ChatArrayGenerator $chatArrayGenerator;
  108.     private RouterInterface $router;
  109.     private ChangedUnreadCountHandler $changedUnreadCountHandler;
  110.     private MoveFilesToAdmManager $moveFilesToAdmManager;
  111.     private EntitySender $entitySender;
  112.     private ChatStateRefresher $chatStateRefresher;
  113.     /** @noinspection PhpMissingParentConstructorInspection */
  114.     public function __construct(
  115.         Twig $twig,
  116.         ParametersProvider $parametersProvider,
  117.         FormFactoryInterface $formFactory,
  118.         RequestStack $requestStack,
  119.         AjaxRegistrationByEmailService $ajaxRegistrationByEmailService,
  120.         EventDispatcherInterface $eventDispatcher,
  121.         SessionInterface $session,
  122.         CurrentUserProvider $currentUserProvider,
  123.         TranslatorWrapper $translatorWrapper,
  124.         ChatBRepository $chatBRepository,
  125.         ConsDefiner $consDefiner,
  126.         EntityManagerInterface $entityManager,
  127.         ChatBMessageRepository $chatBMessageRepository,
  128.         ChatArrayGenerator $chatArrayGenerator,
  129.         RouterInterface $router,
  130.         ChangedUnreadCountHandler $changedUnreadCountHandler,
  131.         MoveFilesToAdmManager $moveFilesToAdmManager,
  132.         EntitySender $entitySender,
  133.         ChatStateRefresher $chatStateRefresher
  134.     ) {
  135.         $this->twig $twig;
  136.         $this->parametersProvider $parametersProvider;
  137.         $this->formFactory $formFactory;
  138.         $this->requestStack $requestStack;
  139.         $this->ajaxRegistrationByEmailService $ajaxRegistrationByEmailService;
  140.         $this->eventDispatcher $eventDispatcher;
  141.         $this->session $session;
  142.         $this->currentUserProvider $currentUserProvider;
  143.         $this->translatorWrapper $translatorWrapper;
  144.         $this->chatBRepository $chatBRepository;
  145.         $this->consDefiner $consDefiner;
  146.         $this->entityManager $entityManager;
  147.         $this->chatBMessageRepository $chatBMessageRepository;
  148.         $this->chatArrayGenerator $chatArrayGenerator;
  149.         $this->router $router;
  150.         $this->changedUnreadCountHandler $changedUnreadCountHandler;
  151.         $this->moveFilesToAdmManager $moveFilesToAdmManager;
  152.         $this->entitySender $entitySender;
  153.         $this->chatStateRefresher $chatStateRefresher;
  154.     }
  155.     /**
  156.      * @Route("/{_locale}/chatb", methods={"GET"}, name="app_chatb_chatbpage", requirements={"_locale"="%lang_country%"})
  157.      *
  158.      * @return Response
  159.      *
  160.      * @throws LoaderError
  161.      * @throws RuntimeError
  162.      * @throws SyntaxError
  163.      * @throws Exception
  164.      * @throws InvalidArgumentException
  165.      */
  166.     public function chatBPage(WelcomeInfoDataGenerator $welcomeInfoDataGeneratorTranslatorInterface $translatorBoolDBSettingRepository $boolDBSettingRepository)
  167.     {
  168.         if ($currentUser $this->currentUserProvider->getUser()) {
  169.             if (UserHelper::isEmployee($currentUser->getEmail())) {
  170.                 $html $this->twig->render('ChatB/deny_for_employees.html.twig');
  171.                 return new Response($html);
  172.             }
  173.         }
  174.         //Проверяем еще юзера по токену, т.к. для поддоменов используется тот же самый токен, что и для tile.expert
  175.         /** @var User|null $tokenUser */
  176.         $tokenUser $this->userRepository->findOneBy(['token' => UserHelper::getInstance()->getToken()]);
  177.         if ($tokenUser) {
  178.             if (UserHelper::isEmployee($tokenUser->getEmail())) {
  179.                 $html $this->twig->render('ChatB/deny_for_employees.html.twig');
  180.                 return new Response($html);
  181.             }
  182.         }
  183.         $countryIsoCodeLower App::getCurCountry(); //TODO в будущем переделать без использование App (через service injection)
  184.         $countryByIp App::getCountryByIp(); //TODO в будущем переделать без использование App (через service injection)
  185.         if ('ru' === $countryIsoCodeLower && !$this->isInterview()) {
  186.             throw new NotFoundHttpException();
  187.         }
  188.         if (!($boolDBSettingRepository->findOneByName('old_chat') && $boolDBSettingRepository->findOneByName('old_chat')->getVal())
  189.             && !$this->isInterview()
  190.         ) {
  191.             throw $this->createNotFoundException('old chat page has been disabled in admin panel');
  192.         }
  193.         /** @var ChatB|null $chat */
  194.         $chat $this->chatBRepository->findOneBy(['token' => UserHelper::getInstance()->getToken()]);
  195.         $chatArray null;
  196.         if ($chat) {
  197.             $chat->setCurrentLocale(App::getCurLocale());
  198.             $chat->setCurrentCountry($countryIsoCodeLower);
  199.             $chat->setCurrentCountryByIp($countryByIp);
  200.             $this->entityManager->flush();
  201.             $chatArray $chat->toArray(false);
  202.         } else {
  203.             $chat = new ChatB(
  204.                 WelcomeInfoDataGenerator::DEFAULT_CONS_PORTAL_LOGIN,
  205.                 $countryIsoCodeLower,
  206.                 $countryByIp,
  207.                 UserHelper::getInstance()->getToken(),
  208.                 App::getCurLocale()
  209.             );
  210.         }
  211.         $cons $this->consDefiner->getCons(
  212.             $chat->getCurrentLocale(),
  213.             $chat->getCurrentCountry(),
  214.             $currentUser,
  215.             UserHelper::getInstance()->getToken()
  216.         );
  217.         $chat->setCurrentWelcomeCons($cons['login']);
  218.         $chatBlockData $this->getChatBlockData($chat$countryIsoCodeLower$countryByIpApp::getCurLocale());
  219.         $appUser null;
  220.         if ($user $this->currentUserProvider->getUser()) {
  221.             $appUser = [
  222.                 'id' => $user->getId(),
  223.                 'token' => $user->getToken(),
  224.                 'emailCanonical' => $user->getEmail(),
  225.             ];
  226.         }
  227.         $jobSeekerEmail null;
  228.         $jobSeekerName null;
  229.         if ($this->isInterview()) {
  230.             $jobSeekerEmail $this->getInterview()->getJobSeekerEmail();
  231.             $jobSeekerName $this->getInterview()->getJobSeekerName();
  232.         }
  233.         $welcomeInfoDataGenerator->addConsWorkHoursAndWorkDaysAndUTCOffset($cons$chat);
  234.         $cons['timezone'] = $translator->trans($cons['timezone'], [], null$chat->getCurrentLocale());
  235.         $signUpUrl $this->router->generate(
  236.             'app_reg',
  237.             ['_locale' => App::getCurLocale(true)],
  238.             UrlGeneratorInterface::ABSOLUTE_URL
  239.         );
  240.         $scheduleParameter "{$cons['work_hours']} ({$cons['timezone']}{$cons['work_days']}";
  241.         $trans = [
  242.             'offlineBlockText' => $this->translatorWrapper->translate(
  243.                 'chat_cons_offline_text',
  244.                 null,
  245.                 ['%schedule%' => $scheduleParameter'%link%' => $signUpUrl]
  246.             ),
  247.             'chatPageHeading' => $this->translatorWrapper->translate('contacts.live_chat'),
  248.             'poweredBy' => $this->translatorWrapper->translate('powered_by'),
  249.             // когда ру локаль будет убрана с сайта, убрать этот перевод ("from"), он нужен только для ру локали
  250.             'poweredByRu' => $this->translatorWrapper->translate('from'),
  251.             'description' => $this->translatorWrapper->translate('discussion.description'),
  252.             'onlyEnglishSupport' => $this->translatorWrapper->translate('only_english_lang_support'),
  253.             'onlySpanishSupport' => $this->translatorWrapper->translate('only_spanish_lang_support'),
  254.             'signUpLink' => $this->translatorWrapper->translate('discussion.sign_up'),
  255.             'signUpText' => $this->translatorWrapper->translate('discussion.or_take_a_email'),
  256.             'emailFieldPlaceholder' => $this->translatorWrapper->translate('request_email'),
  257.             'msgFieldPlaceholder' => $this->translatorWrapper->translate('discussion.type_message_here'),
  258.             'interviewLine' => $this->translatorWrapper->translate('interview'),
  259.             'interviewDescription' => $this->translatorWrapper->translate('you_are_on_the_interview_chat_page'),
  260.             'unreadMessages' => $this->translatorWrapper->translate('chat_unread_messages'),
  261.             'robot' => $this->translatorWrapper->translate('robot'),
  262.             'visitor' => $this->translatorWrapper->translate('visitor'),
  263.             'consTyping' => $this->translatorWrapper->translate('chat_consultant_typing_notification'),
  264.             'status' => [
  265.                 'online' => $this->translatorWrapper->translate('online'),
  266.                 'offline' => $this->translatorWrapper->translate('offline'),
  267.             ],
  268.             'errors' => [
  269.                 'cookiesDisabled' => $this->translatorWrapper->translate('chat_cookies_disabled'),
  270.                 'accessForEmployeesDenied' => 'Доступ на страницу чата из-под аккаунта сотрудника запрещен.<br />Подробнее <a target="_blank" href="https://te.remote.team/#/discus/662C9F46-C935-534C-D41E-7C6C1A0E6D23/">здесь</a>.<br />Войдите под другим аккаунтом (не являющимся аккаунтом сотрудника), или перейдите на страницу чата под анонимным пользователем.',
  271.                 'noServerConnection' => 'No connection to the server',
  272.                 'other' => "There's been an error. Can not connect to the websocket server.",
  273.                 'connectionClosed' => 'Connection is closed.',
  274.                 'chatConnClosed' => 'The chat connection is closed. Please wait or come back at a later time',
  275.                 'extraErrorMessage' => $this->translatorWrapper->translate('extra_error_message'),
  276.             ],
  277.         ];
  278.         $token UserHelper::getInstance()->getToken();
  279.         /** @var ChatB $chatB */
  280.         $chatB $this->chatBRepository->findOneBy(['token' => $token]);
  281.         /** @noinspection PhpRouteMissingInspection */
  282.         $chatPageData = [
  283.             'isEmailReceived' => is_object($this->currentUserProvider->getUser()),
  284.             'token' => UserHelper::getInstance()->getToken(),
  285.             'trans' => $trans,
  286.             'wshost' => $this->parametersProvider->getParameter('web_socket_server_host'),
  287.             'wsport' => $this->parametersProvider->getParameter('web_socket_server_external_port'),
  288.             'wsprotocol' => $this->parametersProvider->getParameter('web_socket_server_protocol'),
  289.             'clientIp' => $this->requestStack->getCurrentRequest()->getClientIp(),
  290.             'countryIsoCodeLower' => $countryIsoCodeLower,
  291.             'countryName' => $countryIsoCodeLower,
  292.             'countryByIp' => $countryByIp,
  293.             'BROWSER_TO_WS' => ChatBWsMessageCategories::BROWSER_TO_WS,
  294.             'INIT_TE' => ChatBBrowserToWsTypes::INIT_TE,
  295.             'TYPING_SYMBOL' => ChatBBrowserToWsTypes::TYPING_SYMBOL,
  296.             'appUser' => $appUser,
  297.             'chat' => $chatArray,
  298.             'isInterview' => $this->isInterview(),
  299.             'isInterviewConfirmed' => $chatB && $chatB->getInterview() && $chatB->getInterview()->getIsConfirmed(),
  300.             'jobSeekerEmail' => $jobSeekerEmail,
  301.             'jobSeekerName' => $jobSeekerName,
  302.             'urls' => [
  303.                 'ws' => $this->parametersProvider->getParameter('web_socket_server_protocol') . '://' .
  304.                     $this->parametersProvider->getParameter('web_socket_server_host') . ':' .
  305.                     $this->parametersProvider->getParameter('web_socket_server_external_port'),
  306.                 'interviewOver' => $this->router->generate(
  307.                     'interview_is_over',
  308.                     ['_locale' => App::getCurLocale(true)],
  309.                     RouterInterface::ABSOLUTE_URL
  310.                 ),
  311.                 'emailFilled' => $this->router->generate(
  312.                     'app_chatb_fillemailajax',
  313.                     ['_locale' => App::getCurLocale(true)],
  314.                     RouterInterface::ABSOLUTE_URL
  315.                 ),
  316.                 'registration' => $this->router->generate(
  317.                     'app_reg',
  318.                     ['_locale' => App::getCurLocale(true)],
  319.                     RouterInterface::ABSOLUTE_URL
  320.                 ),
  321.                 'fileUploadAjax' => $this->router->generate(
  322.                     'chat_file_upload_ajax',
  323.                     ['_locale' => App::getCurLocale(true)],
  324.                     RouterInterface::ABSOLUTE_URL
  325.                 ),
  326.                 'ajaxResendFailedMessage' => $this->router->generate(
  327.                     'ajax-resend-failed-message',
  328.                     ['_locale' => App::getCurLocale(true)],
  329.                     RouterInterface::ABSOLUTE_URL
  330.                 ),
  331.                 'ajaxNewChatMessage' => $this->router->generate(
  332.                     'ajax-new-chat-message',
  333.                     ['_locale' => App::getCurLocale(true)],
  334.                     RouterInterface::ABSOLUTE_URL
  335.                 ),
  336.                 'ajaxGetChatState' => $this->router->generate(
  337.                     'ajax-get-chat-state',
  338.                     ['_locale' => App::getCurLocale(true)],
  339.                     RouterInterface::ABSOLUTE_URL
  340.                 ),
  341.                 'ajaxConnectionFail' => $this->router->generate(
  342.                     'ajax-connection-fail',
  343.                     ['_locale' => App::getCurLocale(true)],
  344.                     RouterInterface::ABSOLUTE_URL
  345.                 ),
  346.                 'ajaxSetIsRead' => $this->router->generate('ajax-set-is-read', [], RouterInterface::ABSOLUTE_URL),
  347.                 'defaultUserPic' => 'https://img.tile.expert/img/author.png',
  348.             ],
  349.         ];
  350.         $parameters = [
  351.             'initialState' => [
  352.                 'chatPageData' => $chatPageData,
  353.                 'chatBlockData' => $chatBlockData,
  354.             ],
  355.             'isInterview' => $this->isInterview(),
  356.             'isChatPage' => true,
  357.         ];
  358.         if ($this->isInterview()) {
  359.             $parameters['metaData']['title'] = 'Interview';
  360.         }
  361.         return $this->renderReact('ChatB/chatb.html.twig'$parameters);
  362.     }
  363.     /**
  364.      * Как только юзер ввел email - это уже не анонимный контакт, поэтому регистрируем его, чтобы перепривязать все сообщения анонимного к неанонимному.
  365.      *
  366.      * @Route("/{_locale}/chatb_email_ajax", methods={"POST"}, name="app_chatb_fillemailajax", requirements={"_locale"="%lang_country%"})
  367.      *
  368.      * @return JsonResponse
  369.      */
  370.     public function fillEmailAjax()
  371.     {
  372.         $form $this->formFactory->create(ChatBEmailFormType::class);
  373.         $form->handleRequest($this->requestStack->getCurrentRequest());
  374.         if ($form->isSubmitted() && $form->isValid()) {
  375.             try {
  376.                 $user $this->ajaxRegistrationByEmailService->register($form->getData()['email']);
  377.                 $this->userAuthenticator->authenticateUser(
  378.                     $user,
  379.                     $this->loginFormAuthenticator,
  380.                     $this->requestStack->getCurrentRequest()
  381.                 );
  382.                 $this->session->getFlashBag()->add(
  383.                     'success',
  384.                     $this->translatorWrapper->translate(
  385.                         'chat_email_form_success',
  386.                         null,
  387.                         ['extra@tile.expert' => $form->getData()['email']]
  388.                     )
  389.                 );
  390.                 return new JsonResponse(['success' => true]);
  391.             } catch (Throwable $t) {
  392.                 if ($t instanceof RegistrationException) {
  393.                     return $this->getErrorJsonResponse($t->getMessage());
  394.                 } else {
  395.                     $this->eventDispatcher->dispatch(new GeneralErrorEvent($t));
  396.                     return $this->getErrorJsonResponse(
  397.                         'Internal Server Error (technical error). Error code: ubIKyPXwbA. Contact support. ' $t->getMessage(
  398.                         )
  399.                     );
  400.                 }
  401.             }
  402.         }
  403.         $errors = [];
  404.         foreach ($form->getErrors() as $error) {
  405.             $errors[] = $error->getMessage();
  406.         }
  407.         if ($form->count()) {
  408.             /** @var Form $child */
  409.             foreach ($form as $child) {
  410.                 if ($child->isSubmitted() && !$child->isValid()) {
  411.                     $childErrors $child->getErrors();
  412.                     foreach ($childErrors as $error) {
  413.                         $errors[] = $error->getMessage();
  414.                     }
  415.                 }
  416.             }
  417.         }
  418.         $errorsString implode(';'$errors);
  419.         return $this->getErrorJsonResponse($errorsString);
  420.     }
  421.     /**
  422.      * @Route("/get_current_visitor_data", methods={"GET"})
  423.      *
  424.      * @return JsonResponse
  425.      *
  426.      * @throws Exception
  427.      */
  428.     public function getCurrentVisitorData()
  429.     {
  430.         $data = [
  431.             'shortLocale' => App::getCurLocale(),
  432.             'countryMain' => App::getCurCountry(),
  433.             'countryByIp' => App::getCountryByIp(),
  434.             'unid' => $this->currentUserProvider->getUser() ? $this->currentUserProvider->getUser()->getUnid() : null,
  435.         ];
  436.         return new JsonResponse($data);
  437.     }
  438.     /**
  439.      * @Route("/{_locale}/get_cons_online_attribute", name="get_cons_online_attribute", methods={"GET"}, requirements={"_locale"="%lang_country%"})
  440.      */
  441.     public function getOnlineAttribute()
  442.     {
  443.         if ($this->parametersProvider->getParameter('disable_get_cons_online_attr_request')) {
  444.             return new JsonResponse(['online' => false'login' => null]);
  445.         }
  446.         $consData $this->consDefiner->getCons(
  447.             App::getCurLocale(),
  448.             App::getCurCountry(),
  449.             $this->currentUserProvider->getUser(),
  450.             UserHelper::getInstance()->getToken()
  451.         );
  452.         $isOnline = (bool)($consData['workTime'] ?? null);
  453.         return new JsonResponse(['online' => $isOnline'login' => (string)($consData['login'] ?? null)]);
  454.     }
  455.     /**
  456.      * @Route("/{_locale}/get_is_holiday_attribute", methods={"GET"}, requirements={"_locale"="%lang_country%"})
  457.      */
  458.     public function isHoliday()
  459.     {
  460.         $isHoliday $this->isHolidayBool();
  461.         return new JsonResponse(['isHoliday' => $isHoliday]);
  462.     }
  463.     /**
  464.      * @Route("/{_locale}/get_is_call_available", methods={"GET"}, requirements={"_locale"="%lang_country%"})
  465.      */
  466.     public function isCallAvailable()
  467.     {
  468.         $isCallAvailable $this->isConsOnlineBool() && !$this->isHolidayBool();
  469.         return new JsonResponse(['is_call_available' => $isCallAvailable]);
  470.     }
  471.     /**
  472.      * @Route("/te-token", methods={"GET"})
  473.      */
  474.     public function getToken(): JsonResponse
  475.     {
  476.         return new JsonResponse(['token' => UserHelper::getInstance()->getToken()]);
  477.     }
  478.     /**
  479.      * @Route("/chat/messages/set_is_read", name="ajax-set-is-read", methods={"POST"})
  480.      */
  481.     public function setIsRead(
  482.         PortalHelper $portalHelper,
  483.         SetIsReadLogger $setIsReadLogger,
  484.         Mailer $mailer
  485.     ): JsonResponse {
  486.         if ('te' === $this->parametersProvider->getParameter('kernel.environment')) {
  487.             $store = new DoctrineDbalPostgreSqlStore($this->parametersProvider->getParameter('pg_dsn'));
  488.         } else {
  489.             $store = new SemaphoreStore();
  490.         }
  491.         $factory = new LockFactory($store);
  492.         $lock $factory->createLock(self::LOCK_KEY);
  493.         $jsonString $this->requestStack->getCurrentRequest()->getContent();
  494.         $requestId md5(uniqid());
  495.         $setIsReadLogger->log("$requestId $jsonString");
  496.         $array json_decode($jsonStringtrue);
  497.         if (!is_array($array)) {
  498.             $setIsReadLogger->log("$requestId invalid json or null");
  499.             return $this->getErrorJsonResponse('Невалидный json или null');
  500.         }
  501.         if (empty($array)) {
  502.             $setIsReadLogger->log("$requestId array is empty");
  503.             return $this->getErrorJsonResponse('Массив не должен быть пустым');
  504.         }
  505.         $token UserHelper::getInstance()->getToken();
  506.         $setIsReadLogger->log("$requestId token from user helper: $token");
  507.         $tokenFromCookie $_COOKIE['token'];
  508.         $setIsReadLogger->log("$requestId token from cookie: $tokenFromCookie");
  509.         $unids = [];
  510.         $ids = [];
  511.         foreach ($array as $val) {
  512.             if (!isset($val['id'])) {
  513.                 $setIsReadLogger->log("$requestId subarray must contain id");
  514.                 return $this->getErrorJsonResponse('Подмассив должен содержать id');
  515.             }
  516.             if (!isset($val['readMoment'])) {
  517.                 $setIsReadLogger->log("$requestId subarray must contain readMoment");
  518.                 return $this->getErrorJsonResponse('Подмассив должен содержать readMoment');
  519.             }
  520.             $message $this->chatBMessageRepository->find($val['id']);
  521.             if ($message) {
  522.                 $setIsReadLogger->log("$requestId chat message has been found");
  523.                 $setIsReadLogger->log("$requestId token from message: {$message->getChatB()->getToken()}");
  524.                 if ($message->getChatB()->getToken() !== $token) {
  525.                     $setIsReadLogger->log(
  526.                         "Токен из сообщения {$message->getChatB()->getToken()} не совпадает с токеном из юзер хелпера $token"
  527.                     );
  528.                     continue;
  529.                 }
  530.                 $message->setIsRead(true);
  531.                 $message->setReadMoment(
  532.                     (new DateTime("@{$val['readMoment']}"))->setTimezone(new DateTimeZone(date_default_timezone_get()))
  533.                 );
  534.                 if ($message->getUnid()) {
  535.                     $unids[] = $message->getUnid();
  536.                 }
  537.                 if ($message->getId()) {
  538.                     $ids[] = $message->getId();
  539.                 }
  540.             } else {
  541.                 $setIsReadLogger->log("$requestId chat message has not been found");
  542.             }
  543.         }
  544.         if (sizeof($ids) === 0) {
  545.             return $this->getErrorJsonResponse(
  546.                 "Не передано ни одного сообщения, относящегося к текущему token: $token"
  547.             );
  548.         }
  549. //        try {
  550. //            $lock->acquire(true);
  551. //        } catch (LockAcquiringException $exception) {
  552. //            //Если не удалось получить блокировку до таймаута - продолжаем без блокировки, чтоб не было 500, но при этом уведомляем по email
  553. //            $mailer->sendEmail(
  554. //                'Уведомление (не 500) -  LockAcquiringException',
  555. //                $this->parametersProvider->getParameter('critical_errors_from'),
  556. //                $this->parametersProvider->getParameter('critical_errors_email'),
  557. //                $exception->getMessage()
  558. //            );
  559. //        }
  560.         $this->entityManager->flush();
  561. //        $lock->release();
  562.         $setIsReadLogger->log("$requestId handling changed unread count...");
  563.         $this->changedUnreadCountHandler->handle($token);
  564.         $setIsReadLogger->log("$requestId done");
  565.         $setIsReadLogger->log("$requestId refreshing chat state...");
  566.         $refreshResult = (int)$this->refreshChatStateByToken($token);
  567.         $setIsReadLogger->log("$requestId done. Refresh result = $refreshResult");
  568.         $unids array_unique($unids);
  569.         foreach ($unids as $unid) {
  570.             $setIsReadLogger->log("$requestId notifying portal about is read for unid $unid ...");
  571.             $portalHelper->setChatMessageIsRead($unid);
  572.             $setIsReadLogger->log("$requestId done");
  573.         }
  574.         $setIsReadLogger->log("$requestId success");
  575.         return new JsonResponse(['success' => true]);
  576.     }
  577.     /**
  578.      * @Route("/{_locale}/file-upload-ajax", name="chat_file_upload_ajax", methods={"POST"})
  579.      *
  580.      * @noinspection DuplicatedCode - код внутри метода будет меняться, поэтому в будущем дублирования скорей всего не будет
  581.      */
  582.     public function ajaxFileUpload(PreviewGenerator $previewGenerator)
  583.     {
  584.         /** @var UploadedFile $file */
  585.         $file $this->requestStack->getCurrentRequest()->files->get('file');
  586.         if (!$file) {
  587.             return new JsonResponse(
  588.                 json_encode(['success' => false'message' => 'There is no file'], JSON_UNESCAPED_UNICODE),
  589.                 Response::HTTP_BAD_REQUEST,
  590.                 [],
  591.                 true
  592.             );
  593.         }
  594.         if ($file->getSize() > 10 1024 1024) {
  595.             return new JsonResponse(
  596.                 json_encode(['success' => false'message' => 'Max file size is 10 MB'], JSON_UNESCAPED_UNICODE),
  597.                 Response::HTTP_BAD_REQUEST,
  598.                 [],
  599.                 true
  600.             );
  601.         }
  602.         $originalName $file->getClientOriginalName();
  603.         $name $originalName '_' md5(uniqid(''true)) . ".{$file->getClientOriginalExtension()}";
  604.         $mimeType $file->getMimeType();
  605.         $isImage FileTypeUtil::isImage($mimeType);
  606.         $fileEntity = new FileEntity($name$originalName$mimeType);
  607.         $dir $this->parametersProvider->getParameter('kernel.project_dir') . '/tmp_for_uploads';
  608.         try {
  609.             $file->move($dir$name);
  610.         } catch (FileException $fileException) {
  611.             return new JsonResponse(['success' => false'messageForAdmin' => $fileException->getMessage()]);
  612.         }
  613.         $sourcePath "$dir/$name";
  614.         $targetPath '/var/www/tile.expert/web/img_lb/_uploads/chat';
  615.         try {
  616.             $this->moveFilesToAdmManager->move($sourcePath$targetPath);
  617.         } catch (Throwable $throwable) {
  618.             return new JsonResponse(['success' => false'messageForAdmin' => $throwable->getMessage()]);
  619.         }
  620.         $this->entityManager->persist($fileEntity);
  621.         if ($isImage) {
  622.             $previewPath $previewGenerator->generatePreview("https://img.tile.expert/img_lb/_uploads/chat/$name");
  623.             $originalPreviewName basename($previewPath);
  624.             $previewFileEntity = new FileEntity($originalPreviewName$originalPreviewName);
  625.             $sourcePath "$dir/$originalPreviewName";
  626.             try {
  627.                 $this->moveFilesToAdmManager->move($sourcePath$targetPath);
  628.             } catch (Throwable $throwable) {
  629.                 return new JsonResponse(['success' => false'messageForAdmin' => $throwable->getMessage()]);
  630.             }
  631.             $this->entityManager->persist($previewFileEntity);
  632.             $fileEntity->setPreviewUrl("https://img.tile.expert/img_lb/_uploads/chat/$originalPreviewName");
  633.         }
  634.         $this->entityManager->flush();
  635.         return new JsonResponse(
  636.             json_encode(
  637.                 [
  638.                     'success' => true,
  639.                     'stringIdentifier' => $fileEntity->getStringIdentifier(),
  640.                     'url' => "https://img.tile.expert/img_lb/_uploads/chat/$name",
  641.                     'mimeType' => $fileEntity->getMimeType(),
  642.                     'isImage' => FileTypeUtil::isImage($fileEntity->getMimeType()),
  643.                     'preview' => $fileEntity->getPreviewUrl(),
  644.                     'originalName' => $fileEntity->getOriginalName(),
  645.                 ],
  646.                 JSON_UNESCAPED_SLASHES JSON_UNESCAPED_UNICODE
  647.             ),
  648.             Response::HTTP_OK,
  649.             [],
  650.             true
  651.         );
  652.     }
  653.     /**
  654.      * @Route("/{_locale}/chatb/ajax-resend-failed-message", name="ajax-resend-failed-message", methods={"POST"})
  655.      */
  656.     public function ajaxResendMessage()
  657.     {
  658.         $jsonString $this->requestStack->getCurrentRequest()->getContent();
  659.         $array json_decode($jsonStringtrue);
  660.         if (!is_array($array)) {
  661.             return $this->getErrorJsonResponse('Невалидный json или null');
  662.         }
  663.         if (empty($array)) {
  664.             return $this->getErrorJsonResponse('Массив не должен быть пустым');
  665.         }
  666.         if (!isset($array['id'])) {
  667.             return $this->getErrorJsonResponse('В массиве должен содержаться id сообщения');
  668.         }
  669.         $id $array['id'];
  670.         $chatBMessage $this->chatBMessageRepository->find($id);
  671.         if (!$chatBMessage) {
  672.             return $this->getErrorJsonResponse("Не найдено сообщение id $id в базе");
  673.         }
  674.         $this->entitySender->sendChatBMessage($chatBMessage);
  675.         $success = (bool)$chatBMessage->getIsDeliveryToPortalSuccess();
  676.         if (!$success) {
  677.             return $this->getErrorJsonResponse("Не удалось отправить сообщение на портал"Response::HTTP_INTERNAL_SERVER_ERROR);
  678.         }
  679.         $result $this->refreshChatStateByToken($chatBMessage->getChatB()->getToken());
  680.         if (!$result) {
  681.             return $this->getErrorJsonResponse("Не удалось обновить состояние чата");
  682.         }
  683.         return new JsonResponse(['success' => true]);
  684.     }
  685.     /**
  686.      * @Route("/{_locale}/chatb/ajax-new-chat-message", name="ajax-new-chat-message", methods={"POST"})
  687.      */
  688.     public function ajaxNewChatMessage(): JsonResponse
  689.     {
  690.         $cookies $this->requestStack->getCurrentRequest()->cookies;
  691.         if (!$cookies->has('token')) {
  692.             return $this->getErrorJsonResponse('Your cookies do not contain token');
  693.         }
  694.         $token $cookies->get('token');
  695.         $chat $this->chatBRepository->findOneBy(['token' => $token]);
  696.         if (!$chat) {
  697.             $this->createNewChat($token);
  698.         }
  699.         $jsonString $this->requestStack->getCurrentRequest()->getContent();
  700.         $handleResult $this->chatOnMessageHandler->handle($jsonString);
  701.         $handleResultArray json_decode($handleResulttrue);
  702.         $refreshResult $this->refreshChatStateByToken(UserHelper::getInstance()->getToken());
  703.         if (!$refreshResult) {
  704.             return $this->getErrorJsonResponse($this->getCouldNotUpdateChatStateMessage());
  705.         }
  706.         if (isset($handleResultArray['success']) && false === $handleResultArray['success']) {
  707.             return $this->getErrorJsonResponse($handleResultArray['message'], Response::HTTP_INTERNAL_SERVER_ERROR);
  708.         }
  709.         return new JsonResponse(['success' => true], Response::HTTP_OK);
  710.     }
  711.     /**
  712.      * @Route("{_locale}/chatb/ajax-connection-fail", name="ajax-connection-fail", methods={"POST"})
  713.      */
  714.     public function notifyAboutConnectionFail(TelegramNotifier $telegramNotifier): JsonResponse
  715.     {
  716. //        $string = (string) $this->requestStack->getCurrentRequest()->getContent();
  717. //        $string = 'Ошибка соединения с ws-сервером. ' . $string;
  718. //        $fullLocale = App::getCurLocale(true);
  719. //        $token = UserHelper::getInstance()->getToken();
  720. //        $user = $this->currentUserProvider->getUser();
  721. //
  722. //        $ips = $this->requestStack->getCurrentRequest()->getClientIps();
  723. //        $ipsString = implode(', ', $ips);
  724. //
  725. //        $string .= " Full locale: $fullLocale. Token: $token. IP: $ipsString. ";
  726. //
  727. //        if ($user) {
  728. //            $emailCanonical = $user->getEmailCanonical();
  729. //        } else {
  730. //            $emailCanonical = 'null';
  731. //        }
  732. //
  733. //        $string .= "emailCanonical: $emailCanonical";
  734. //
  735. //        $telegramNotifier->sendMessage($string);
  736.         return new JsonResponse(['success' => true]);
  737.     }
  738.     /**
  739.      * @Route("/{_locale}/chatb/ajax-get-chat-state", name="ajax-get-chat-state", methods={"GET"})
  740.      * @throws Exception
  741.      */
  742.     public function ajaxGetChatState(): JsonResponse
  743.     {
  744.         $token UserHelper::getInstance()->getToken();
  745.         $chat $this->chatBRepository->findOneBy(['token' => $token]);
  746.         if (!$chat) {
  747.             //Если чат не найден, значит он пустой, а пустые чаты не сохраняем в базу, иначе она очень сильно разрастаться будет.
  748.             $currentUser $this->currentUserProvider->getUser();
  749.             $cons $this->consDefiner->getCons(
  750.                 App::getCurLocale(),
  751.                 App::getCurCountry(),
  752.                 $currentUser,
  753.                 UserHelper::getInstance()->getToken()
  754.             );
  755.             $chat = new ChatB(
  756.                 $cons['login'] ?? WelcomeInfoDataGenerator::DEFAULT_CONS_PORTAL_LOGIN,
  757.                 App::getCurCountry(),
  758.                 App::getCountryByIp(),
  759.                 $token,
  760.                 App::getCurLocale()
  761.             );
  762.         }
  763.         $refreshResult $this->refreshChatStateByChatObject($chat);
  764.         if (!$refreshResult) {
  765.             return $this->getErrorJsonResponse($this->getCouldNotUpdateChatStateMessage());
  766.         }
  767.         return new JsonResponse(['success' => true], Response::HTTP_OK);
  768.     }
  769.     private function isInterview()
  770.     {
  771.         $token UserHelper::getInstance()->getToken();
  772.         /** @var ChatB $chatB */
  773.         $chatB $this->chatBRepository->findOneBy(['token' => $token]);
  774.         if (!$chatB) {
  775.             return false;
  776.         }
  777.         return $chatB->isInterview();
  778.     }
  779.     private function getInterview(): ?InterviewEntity
  780.     {
  781.         $token UserHelper::getInstance()->getToken();
  782.         /** @var ChatB $chatB */
  783.         $chatB $this->chatBRepository->findOneBy(['token' => $token]);
  784.         if (!$chatB) {
  785.             return null;
  786.         }
  787.         return $chatB->getInterview();
  788.     }
  789.     private function isHolidayBool(): bool
  790.     {
  791.         $curCountryEntity App::getCurCountryEntity();
  792.         if (null === $curCountryEntity) {
  793.             return false;
  794.         }
  795.         /** @var CalendarRepository $calendarRepository */
  796.         $calendarRepository $this->entityManager->getRepository(Calendar::class);
  797.         return !empty($calendarRepository->getTodayHolidays($curCountryEntity));
  798.     }
  799.     private function isConsOnlineBool(): bool
  800.     {
  801.         $consData $this->consDefiner->getCons(
  802.             App::getCurLocale(),
  803.             App::getCurCountry(),
  804.             $this->currentUserProvider->getUser(),
  805.             UserHelper::getInstance()->getToken()
  806.         );
  807.         return (bool)($consData['workTime'] ?? null);
  808.     }
  809.     protected function getErrorJsonResponse(string $errorMessageint $code 400): JsonResponse
  810.     {
  811.         return new JsonResponse(
  812.             json_encode(['success' => false'message' => $errorMessage], JSON_UNESCAPED_UNICODE),
  813.             $code,
  814.             [],
  815.             true
  816.         );
  817.     }
  818.     /**
  819.      * @throws Error
  820.      * @throws LoaderError
  821.      * @throws RuntimeError
  822.      * @throws SyntaxError
  823.      * @throws WebsocketMessageHandlerException
  824.      * @throws NonUniqueResultException
  825.      * @throws InvalidArgumentException
  826.      */
  827.     private function getChatBlockData(?ChatB $chat$countryIsoCodeLower$countryByIp$locale): array
  828.     {
  829.         if (!$chat) {
  830.             $chat = new ChatB(
  831.                 WelcomeInfoDataGenerator::DEFAULT_CONS_PORTAL_LOGIN,
  832.                 $countryIsoCodeLower,
  833.                 $countryByIp,
  834.                 UserHelper::getInstance()->getToken(),
  835.                 $locale
  836.             );
  837.         }
  838.         return $this->chatArrayGenerator->generateByChatObject($chat);
  839.     }
  840.     private function refreshChatStateByToken(string $token)
  841.     {
  842.         return $this->chatStateRefresher->refreshStateByToken($token);
  843.     }
  844.     private function createNewChat(string $token): void
  845.     {
  846.         $currentUser $this->currentUserProvider->getUser();
  847.         $cons $this->consDefiner->getCons(
  848.             App::getCurLocale(),
  849.             App::getCurCountry(),
  850.             $currentUser,
  851.             UserHelper::getInstance()->getToken()
  852.         );
  853.         $chat = new ChatB(
  854.             $cons['login'] ?? WelcomeInfoDataGenerator::DEFAULT_CONS_PORTAL_LOGIN,
  855.             App::getCurCountry(),
  856.             App::getCountryByIp(),
  857.             $token,
  858.             App::getCurLocale()
  859.         );
  860.         $this->entityManager->persist($chat);
  861.         $this->entityManager->flush();
  862.     }
  863.     private function refreshChatStateByChatObject(ChatB $chat)
  864.     {
  865.         return $this->chatStateRefresher->refreshStateByChatObject($chat);
  866.     }
  867.     /**
  868.      * @return string
  869.      */
  870.     private function getCouldNotUpdateChatStateMessage(): string
  871.     {
  872.         return "Could not update chat state. Try to reload the page or use chat later.";
  873.     }
  874.     /**
  875.      * Временная мера - отписываем через GET. Позже этот экшн будет удален, когда будет готов раздел с подписками.
  876.      * @Route("/{_locale}/unsub_chat", name="unsub_chat", methods={"GET"})
  877.      * @Security("is_granted('ROLE_USER')")
  878.      */
  879.     public function unsubscribe(SubscriptionsEntityRepository $subscriptionsEntityRepository): Response {
  880.         $user $this->currentUserProvider->getUser();
  881.         $subs $subscriptionsEntityRepository->findOneBy(['user' => $user]);
  882.         if (!$subs) {
  883.             $subs = new SubscriptionsEntity();
  884.             $subs->setUser($user);
  885.             $this->entityManager->persist($subs);
  886.             $this->entityManager->flush();
  887.         }
  888.         $subs->setChatSub(false);
  889.         $this->entityManager->flush();
  890.         return $this->render('ChatB/unsubscribe_success.html.twig');
  891.     }
  892. }