src/FlexApp/Controller/ChatBController.php line 486

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