<?php
/** @noinspection PhpDocSignatureInspection */
/**
* Created by Pavel Popov.
*/
namespace FlexApp\Controller;
use DateTime;
use DateTimeZone;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Exception;
use FlexApp\Classes\FileTypeUtil;
use FlexApp\Constant\ChatBBrowserToWsTypes;
use FlexApp\Constant\ChatBWsMessageCategories;
use FlexApp\Entity\ChatB;
use FlexApp\Entity\FileEntity;
use FlexApp\Entity\InterviewEntity;
use FlexApp\Entity\SubscriptionsEntity;
use FlexApp\Events\Style43\GeneralErrorEvent;
use FlexApp\Exceptions\RegistrationException;
use FlexApp\Exceptions\WebsocketMessageHandlerException;
use FlexApp\Form\ChatBEmailFormType;
use FlexApp\Repository\BoolDBSettingRepository;
use FlexApp\Repository\ChatBMessageRepository;
use FlexApp\Repository\ChatBRepository;
use FlexApp\Repository\SubscriptionsEntityRepository;
use FlexApp\Security\LoginFormAuthenticator;
use FlexApp\Service\AjaxRegistrationByEmailService;
use FlexApp\Service\ChangedUnreadCountHandler;
use FlexApp\Service\ChatArrayGenerator;
use FlexApp\Service\ChatOnMessageHandler;
use FlexApp\Service\ChatStateRefresher;
use FlexApp\Service\ConsDefiner;
use FlexApp\Service\CurrentUserProvider;
use FlexApp\Service\EntitySender;
use FlexApp\Service\ManualReg\SetIsReadLogger;
use FlexApp\Service\MoveFilesToAdmManager;
use FlexApp\Service\ParametersProvider;
use FlexApp\Service\PreviewGenerator;
use FlexApp\Service\TelegramNotifier;
use FlexApp\Service\TranslatorWrapper;
use FlexApp\Service\WelcomeInfoDataGenerator;
use Psr\Cache\InvalidArgumentException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\DoctrineDbalPostgreSqlStore;
use Symfony\Component\Lock\Store\SemaphoreStore;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Throwable;
use Twig\Environment as Twig;
use Twig\Error\Error;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
use WebBundle\Controller\ExtendedController;
use WebBundle\Entity\Calendar;
use WebBundle\Entity\User;
use WebBundle\Helper\App;
use WebBundle\Helper\Mailer;
use WebBundle\Helper\PortalHelper;
use WebBundle\Helper\UserHelper;
use WebBundle\Repository\CalendarRepository;
use WebBundle\Repository\UserRepository;
/**
* Документация ✏ ✏ ✏ ✏ ✏ ✏ ✏
* https://docs.google.com/document/d/1nn7gwvNXZ6fx9tZnKoxE0cbh7DYhCR_pyUUXnZBlvKA/edit?usp=sharing
* Class ChatBController.
*/
class ChatBController extends ExtendedController
{
const LOCK_KEY = 'ajax-set-is-read';
/** @required */
public ChatOnMessageHandler $chatOnMessageHandler;
/** @required */
public UserRepository $userRepository;
/** @required */
public UserAuthenticatorInterface $userAuthenticator;
/** @required */
public LoginFormAuthenticator $loginFormAuthenticator;
private Twig $twig;
private ParametersProvider $parametersProvider;
private FormFactoryInterface $formFactory;
private RequestStack $requestStack;
private AjaxRegistrationByEmailService $ajaxRegistrationByEmailService;
private EventDispatcherInterface $eventDispatcher;
private SessionInterface $session;
private CurrentUserProvider $currentUserProvider;
private TranslatorWrapper $translatorWrapper;
private ChatBRepository $chatBRepository;
private ConsDefiner $consDefiner;
private EntityManagerInterface $entityManager;
private ChatBMessageRepository $chatBMessageRepository;
private ChatArrayGenerator $chatArrayGenerator;
private RouterInterface $router;
private ChangedUnreadCountHandler $changedUnreadCountHandler;
private MoveFilesToAdmManager $moveFilesToAdmManager;
private EntitySender $entitySender;
private ChatStateRefresher $chatStateRefresher;
/** @noinspection PhpMissingParentConstructorInspection */
public function __construct(
Twig $twig,
ParametersProvider $parametersProvider,
FormFactoryInterface $formFactory,
RequestStack $requestStack,
AjaxRegistrationByEmailService $ajaxRegistrationByEmailService,
EventDispatcherInterface $eventDispatcher,
SessionInterface $session,
CurrentUserProvider $currentUserProvider,
TranslatorWrapper $translatorWrapper,
ChatBRepository $chatBRepository,
ConsDefiner $consDefiner,
EntityManagerInterface $entityManager,
ChatBMessageRepository $chatBMessageRepository,
ChatArrayGenerator $chatArrayGenerator,
RouterInterface $router,
ChangedUnreadCountHandler $changedUnreadCountHandler,
MoveFilesToAdmManager $moveFilesToAdmManager,
EntitySender $entitySender,
ChatStateRefresher $chatStateRefresher
) {
$this->twig = $twig;
$this->parametersProvider = $parametersProvider;
$this->formFactory = $formFactory;
$this->requestStack = $requestStack;
$this->ajaxRegistrationByEmailService = $ajaxRegistrationByEmailService;
$this->eventDispatcher = $eventDispatcher;
$this->session = $session;
$this->currentUserProvider = $currentUserProvider;
$this->translatorWrapper = $translatorWrapper;
$this->chatBRepository = $chatBRepository;
$this->consDefiner = $consDefiner;
$this->entityManager = $entityManager;
$this->chatBMessageRepository = $chatBMessageRepository;
$this->chatArrayGenerator = $chatArrayGenerator;
$this->router = $router;
$this->changedUnreadCountHandler = $changedUnreadCountHandler;
$this->moveFilesToAdmManager = $moveFilesToAdmManager;
$this->entitySender = $entitySender;
$this->chatStateRefresher = $chatStateRefresher;
}
/**
* @Route("/{_locale}/chatb", methods={"GET"}, name="app_chatb_chatbpage", requirements={"_locale"="%lang_country%"})
*
* @return Response
*
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
* @throws Exception
* @throws InvalidArgumentException
*/
public function chatBPage(WelcomeInfoDataGenerator $welcomeInfoDataGenerator, TranslatorInterface $translator, BoolDBSettingRepository $boolDBSettingRepository)
{
if ($currentUser = $this->currentUserProvider->getUser()) {
if (UserHelper::isEmployee($currentUser->getEmail())) {
$html = $this->twig->render('ChatB/deny_for_employees.html.twig');
return new Response($html);
}
}
//Проверяем еще юзера по токену, т.к. для поддоменов используется тот же самый токен, что и для tile.expert
/** @var User|null $tokenUser */
$tokenUser = $this->userRepository->findOneBy(['token' => UserHelper::getInstance()->getToken()]);
if ($tokenUser) {
if (UserHelper::isEmployee($tokenUser->getEmail())) {
$html = $this->twig->render('ChatB/deny_for_employees.html.twig');
return new Response($html);
}
}
$countryIsoCodeLower = App::getCurCountry(); //TODO в будущем переделать без использование App (через service injection)
$countryByIp = App::getCountryByIp(); //TODO в будущем переделать без использование App (через service injection)
if ('ru' === $countryIsoCodeLower && !$this->isInterview()) {
throw new NotFoundHttpException();
}
if (!($boolDBSettingRepository->findOneByName('old_chat') && $boolDBSettingRepository->findOneByName('old_chat')->getVal())
&& !$this->isInterview()
) {
throw $this->createNotFoundException('old chat page has been disabled in admin panel');
}
/** @var ChatB|null $chat */
$chat = $this->chatBRepository->findOneBy(['token' => UserHelper::getInstance()->getToken()]);
$chatArray = null;
if ($chat) {
$chat->setCurrentLocale(App::getCurLocale());
$chat->setCurrentCountry($countryIsoCodeLower);
$chat->setCurrentCountryByIp($countryByIp);
$this->entityManager->flush();
$chatArray = $chat->toArray(false);
} else {
$chat = new ChatB(
WelcomeInfoDataGenerator::DEFAULT_CONS_PORTAL_LOGIN,
$countryIsoCodeLower,
$countryByIp,
UserHelper::getInstance()->getToken(),
App::getCurLocale()
);
}
$cons = $this->consDefiner->getCons(
$chat->getCurrentLocale(),
$chat->getCurrentCountry(),
$currentUser,
UserHelper::getInstance()->getToken()
);
$chat->setCurrentWelcomeCons($cons['login']);
$chatBlockData = $this->getChatBlockData($chat, $countryIsoCodeLower, $countryByIp, App::getCurLocale());
$appUser = null;
if ($user = $this->currentUserProvider->getUser()) {
$appUser = [
'id' => $user->getId(),
'token' => $user->getToken(),
'emailCanonical' => $user->getEmail(),
];
}
$jobSeekerEmail = null;
$jobSeekerName = null;
if ($this->isInterview()) {
$jobSeekerEmail = $this->getInterview()->getJobSeekerEmail();
$jobSeekerName = $this->getInterview()->getJobSeekerName();
}
$welcomeInfoDataGenerator->addConsWorkHoursAndWorkDaysAndUTCOffset($cons, $chat);
$cons['timezone'] = $translator->trans($cons['timezone'], [], null, $chat->getCurrentLocale());
$signUpUrl = $this->router->generate(
'app_reg',
['_locale' => App::getCurLocale(true)],
UrlGeneratorInterface::ABSOLUTE_URL
);
$scheduleParameter = "{$cons['work_hours']} ({$cons['timezone']}) {$cons['work_days']}";
$trans = [
'offlineBlockText' => $this->translatorWrapper->translate(
'chat_cons_offline_text',
null,
['%schedule%' => $scheduleParameter, '%link%' => $signUpUrl]
),
'chatPageHeading' => $this->translatorWrapper->translate('contacts.live_chat'),
'poweredBy' => $this->translatorWrapper->translate('powered_by'),
// когда ру локаль будет убрана с сайта, убрать этот перевод ("from"), он нужен только для ру локали
'poweredByRu' => $this->translatorWrapper->translate('from'),
'description' => $this->translatorWrapper->translate('discussion.description'),
'onlyEnglishSupport' => $this->translatorWrapper->translate('only_english_lang_support'),
'onlySpanishSupport' => $this->translatorWrapper->translate('only_spanish_lang_support'),
'signUpLink' => $this->translatorWrapper->translate('discussion.sign_up'),
'signUpText' => $this->translatorWrapper->translate('discussion.or_take_a_email'),
'emailFieldPlaceholder' => $this->translatorWrapper->translate('request_email'),
'msgFieldPlaceholder' => $this->translatorWrapper->translate('discussion.type_message_here'),
'interviewLine' => $this->translatorWrapper->translate('interview'),
'interviewDescription' => $this->translatorWrapper->translate('you_are_on_the_interview_chat_page'),
'unreadMessages' => $this->translatorWrapper->translate('chat_unread_messages'),
'robot' => $this->translatorWrapper->translate('robot'),
'visitor' => $this->translatorWrapper->translate('visitor'),
'consTyping' => $this->translatorWrapper->translate('chat_consultant_typing_notification'),
'status' => [
'online' => $this->translatorWrapper->translate('online'),
'offline' => $this->translatorWrapper->translate('offline'),
],
'errors' => [
'cookiesDisabled' => $this->translatorWrapper->translate('chat_cookies_disabled'),
'accessForEmployeesDenied' => 'Доступ на страницу чата из-под аккаунта сотрудника запрещен.<br />Подробнее <a target="_blank" href="https://te.remote.team/#/discus/662C9F46-C935-534C-D41E-7C6C1A0E6D23/">здесь</a>.<br />Войдите под другим аккаунтом (не являющимся аккаунтом сотрудника), или перейдите на страницу чата под анонимным пользователем.',
'noServerConnection' => 'No connection to the server',
'other' => "There's been an error. Can not connect to the websocket server.",
'connectionClosed' => 'Connection is closed.',
'chatConnClosed' => 'The chat connection is closed. Please wait or come back at a later time',
'extraErrorMessage' => $this->translatorWrapper->translate('extra_error_message'),
],
];
$token = UserHelper::getInstance()->getToken();
/** @var ChatB $chatB */
$chatB = $this->chatBRepository->findOneBy(['token' => $token]);
/** @noinspection PhpRouteMissingInspection */
$chatPageData = [
'isEmailReceived' => is_object($this->currentUserProvider->getUser()),
'token' => UserHelper::getInstance()->getToken(),
'trans' => $trans,
'wshost' => $this->parametersProvider->getParameter('web_socket_server_host'),
'wsport' => $this->parametersProvider->getParameter('web_socket_server_external_port'),
'wsprotocol' => $this->parametersProvider->getParameter('web_socket_server_protocol'),
'clientIp' => $this->requestStack->getCurrentRequest()->getClientIp(),
'countryIsoCodeLower' => $countryIsoCodeLower,
'countryName' => $countryIsoCodeLower,
'countryByIp' => $countryByIp,
'BROWSER_TO_WS' => ChatBWsMessageCategories::BROWSER_TO_WS,
'INIT_TE' => ChatBBrowserToWsTypes::INIT_TE,
'TYPING_SYMBOL' => ChatBBrowserToWsTypes::TYPING_SYMBOL,
'appUser' => $appUser,
'chat' => $chatArray,
'isInterview' => $this->isInterview(),
'isInterviewConfirmed' => $chatB && $chatB->getInterview() && $chatB->getInterview()->getIsConfirmed(),
'jobSeekerEmail' => $jobSeekerEmail,
'jobSeekerName' => $jobSeekerName,
'urls' => [
'ws' => $this->parametersProvider->getParameter('web_socket_server_protocol') . '://' .
$this->parametersProvider->getParameter('web_socket_server_host') . ':' .
$this->parametersProvider->getParameter('web_socket_server_external_port'),
'interviewOver' => $this->router->generate(
'interview_is_over',
['_locale' => App::getCurLocale(true)],
RouterInterface::ABSOLUTE_URL
),
'emailFilled' => $this->router->generate(
'app_chatb_fillemailajax',
['_locale' => App::getCurLocale(true)],
RouterInterface::ABSOLUTE_URL
),
'registration' => $this->router->generate(
'app_reg',
['_locale' => App::getCurLocale(true)],
RouterInterface::ABSOLUTE_URL
),
'fileUploadAjax' => $this->router->generate(
'chat_file_upload_ajax',
['_locale' => App::getCurLocale(true)],
RouterInterface::ABSOLUTE_URL
),
'ajaxResendFailedMessage' => $this->router->generate(
'ajax-resend-failed-message',
['_locale' => App::getCurLocale(true)],
RouterInterface::ABSOLUTE_URL
),
'ajaxNewChatMessage' => $this->router->generate(
'ajax-new-chat-message',
['_locale' => App::getCurLocale(true)],
RouterInterface::ABSOLUTE_URL
),
'ajaxGetChatState' => $this->router->generate(
'ajax-get-chat-state',
['_locale' => App::getCurLocale(true)],
RouterInterface::ABSOLUTE_URL
),
'ajaxConnectionFail' => $this->router->generate(
'ajax-connection-fail',
['_locale' => App::getCurLocale(true)],
RouterInterface::ABSOLUTE_URL
),
'ajaxSetIsRead' => $this->router->generate('ajax-set-is-read', [], RouterInterface::ABSOLUTE_URL),
'defaultUserPic' => 'https://img.tile.expert/img/author.png',
],
];
$parameters = [
'initialState' => [
'chatPageData' => $chatPageData,
'chatBlockData' => $chatBlockData,
],
'isInterview' => $this->isInterview(),
'isChatPage' => true,
];
if ($this->isInterview()) {
$parameters['metaData']['title'] = 'Interview';
}
return $this->renderReact('ChatB/chatb.html.twig', $parameters);
}
/**
* Как только юзер ввел email - это уже не анонимный контакт, поэтому регистрируем его, чтобы перепривязать все сообщения анонимного к неанонимному.
*
* @Route("/{_locale}/chatb_email_ajax", methods={"POST"}, name="app_chatb_fillemailajax", requirements={"_locale"="%lang_country%"})
*
* @return JsonResponse
*/
public function fillEmailAjax()
{
$form = $this->formFactory->create(ChatBEmailFormType::class);
$form->handleRequest($this->requestStack->getCurrentRequest());
if ($form->isSubmitted() && $form->isValid()) {
try {
$user = $this->ajaxRegistrationByEmailService->register($form->getData()['email']);
$this->userAuthenticator->authenticateUser(
$user,
$this->loginFormAuthenticator,
$this->requestStack->getCurrentRequest()
);
$this->session->getFlashBag()->add(
'success',
$this->translatorWrapper->translate(
'chat_email_form_success',
null,
['extra@tile.expert' => $form->getData()['email']]
)
);
return new JsonResponse(['success' => true]);
} catch (Throwable $t) {
if ($t instanceof RegistrationException) {
return $this->getErrorJsonResponse($t->getMessage());
} else {
$this->eventDispatcher->dispatch(new GeneralErrorEvent($t));
return $this->getErrorJsonResponse(
'Internal Server Error (technical error). Error code: ubIKyPXwbA. Contact support. ' . $t->getMessage(
)
);
}
}
}
$errors = [];
foreach ($form->getErrors() as $error) {
$errors[] = $error->getMessage();
}
if ($form->count()) {
/** @var Form $child */
foreach ($form as $child) {
if ($child->isSubmitted() && !$child->isValid()) {
$childErrors = $child->getErrors();
foreach ($childErrors as $error) {
$errors[] = $error->getMessage();
}
}
}
}
$errorsString = implode(';', $errors);
return $this->getErrorJsonResponse($errorsString);
}
/**
* @Route("/get_current_visitor_data", methods={"GET"})
*
* @return JsonResponse
*
* @throws Exception
*/
public function getCurrentVisitorData()
{
$data = [
'shortLocale' => App::getCurLocale(),
'countryMain' => App::getCurCountry(),
'countryByIp' => App::getCountryByIp(),
'unid' => $this->currentUserProvider->getUser() ? $this->currentUserProvider->getUser()->getUnid() : null,
];
return new JsonResponse($data);
}
/**
* @Route("/{_locale}/get_cons_online_attribute", name="get_cons_online_attribute", methods={"GET"}, requirements={"_locale"="%lang_country%"})
*/
public function getOnlineAttribute()
{
if ($this->parametersProvider->getParameter('disable_get_cons_online_attr_request')) {
return new JsonResponse(['online' => false, 'login' => null]);
}
$consData = $this->consDefiner->getCons(
App::getCurLocale(),
App::getCurCountry(),
$this->currentUserProvider->getUser(),
UserHelper::getInstance()->getToken()
);
$isOnline = (bool)($consData['workTime'] ?? null);
return new JsonResponse(['online' => $isOnline, 'login' => (string)($consData['login'] ?? null)]);
}
/**
* @Route("/{_locale}/get_is_holiday_attribute", methods={"GET"}, requirements={"_locale"="%lang_country%"})
*/
public function isHoliday()
{
$isHoliday = $this->isHolidayBool();
return new JsonResponse(['isHoliday' => $isHoliday]);
}
/**
* @Route("/{_locale}/get_is_call_available", methods={"GET"}, requirements={"_locale"="%lang_country%"})
*/
public function isCallAvailable()
{
$isCallAvailable = $this->isConsOnlineBool() && !$this->isHolidayBool();
return new JsonResponse(['is_call_available' => $isCallAvailable]);
}
/**
* @Route("/te-token", methods={"GET"})
*/
public function getToken(): JsonResponse
{
return new JsonResponse(['token' => UserHelper::getInstance()->getToken()]);
}
/**
* @Route("/chat/messages/set_is_read", name="ajax-set-is-read", methods={"POST"})
*/
public function setIsRead(
PortalHelper $portalHelper,
SetIsReadLogger $setIsReadLogger,
Mailer $mailer
): JsonResponse {
if ('te' === $this->parametersProvider->getParameter('kernel.environment')) {
$store = new DoctrineDbalPostgreSqlStore($this->parametersProvider->getParameter('pg_dsn'));
} else {
$store = new SemaphoreStore();
}
$factory = new LockFactory($store);
$lock = $factory->createLock(self::LOCK_KEY);
$jsonString = $this->requestStack->getCurrentRequest()->getContent();
$requestId = md5(uniqid());
$setIsReadLogger->log("$requestId $jsonString");
$array = json_decode($jsonString, true);
if (!is_array($array)) {
$setIsReadLogger->log("$requestId invalid json or null");
return $this->getErrorJsonResponse('Невалидный json или null');
}
if (empty($array)) {
$setIsReadLogger->log("$requestId array is empty");
return $this->getErrorJsonResponse('Массив не должен быть пустым');
}
$token = UserHelper::getInstance()->getToken();
$setIsReadLogger->log("$requestId token from user helper: $token");
$tokenFromCookie = $_COOKIE['token'];
$setIsReadLogger->log("$requestId token from cookie: $tokenFromCookie");
$unids = [];
$ids = [];
foreach ($array as $val) {
if (!isset($val['id'])) {
$setIsReadLogger->log("$requestId subarray must contain id");
return $this->getErrorJsonResponse('Подмассив должен содержать id');
}
if (!isset($val['readMoment'])) {
$setIsReadLogger->log("$requestId subarray must contain readMoment");
return $this->getErrorJsonResponse('Подмассив должен содержать readMoment');
}
$message = $this->chatBMessageRepository->find($val['id']);
if ($message) {
$setIsReadLogger->log("$requestId chat message has been found");
$setIsReadLogger->log("$requestId token from message: {$message->getChatB()->getToken()}");
if ($message->getChatB()->getToken() !== $token) {
$setIsReadLogger->log(
"Токен из сообщения {$message->getChatB()->getToken()} не совпадает с токеном из юзер хелпера $token"
);
continue;
}
$message->setIsRead(true);
$message->setReadMoment(
(new DateTime("@{$val['readMoment']}"))->setTimezone(new DateTimeZone(date_default_timezone_get()))
);
if ($message->getUnid()) {
$unids[] = $message->getUnid();
}
if ($message->getId()) {
$ids[] = $message->getId();
}
} else {
$setIsReadLogger->log("$requestId chat message has not been found");
}
}
if (sizeof($ids) === 0) {
return $this->getErrorJsonResponse(
"Не передано ни одного сообщения, относящегося к текущему token: $token"
);
}
// try {
// $lock->acquire(true);
// } catch (LockAcquiringException $exception) {
// //Если не удалось получить блокировку до таймаута - продолжаем без блокировки, чтоб не было 500, но при этом уведомляем по email
// $mailer->sendEmail(
// 'Уведомление (не 500) - LockAcquiringException',
// $this->parametersProvider->getParameter('critical_errors_from'),
// $this->parametersProvider->getParameter('critical_errors_email'),
// $exception->getMessage()
// );
// }
$this->entityManager->flush();
// $lock->release();
$setIsReadLogger->log("$requestId handling changed unread count...");
$this->changedUnreadCountHandler->handle($token);
$setIsReadLogger->log("$requestId done");
$setIsReadLogger->log("$requestId refreshing chat state...");
$refreshResult = (int)$this->refreshChatStateByToken($token);
$setIsReadLogger->log("$requestId done. Refresh result = $refreshResult");
$unids = array_unique($unids);
foreach ($unids as $unid) {
$setIsReadLogger->log("$requestId notifying portal about is read for unid $unid ...");
$portalHelper->setChatMessageIsRead($unid);
$setIsReadLogger->log("$requestId done");
}
$setIsReadLogger->log("$requestId success");
return new JsonResponse(['success' => true]);
}
/**
* @Route("/{_locale}/file-upload-ajax", name="chat_file_upload_ajax", methods={"POST"})
*
* @noinspection DuplicatedCode - код внутри метода будет меняться, поэтому в будущем дублирования скорей всего не будет
*/
public function ajaxFileUpload(PreviewGenerator $previewGenerator)
{
/** @var UploadedFile $file */
$file = $this->requestStack->getCurrentRequest()->files->get('file');
if (!$file) {
return new JsonResponse(
json_encode(['success' => false, 'message' => 'There is no file'], JSON_UNESCAPED_UNICODE),
Response::HTTP_BAD_REQUEST,
[],
true
);
}
if ($file->getSize() > 10 * 1024 * 1024) {
return new JsonResponse(
json_encode(['success' => false, 'message' => 'Max file size is 10 MB'], JSON_UNESCAPED_UNICODE),
Response::HTTP_BAD_REQUEST,
[],
true
);
}
$originalName = $file->getClientOriginalName();
$name = $originalName . '_' . md5(uniqid('', true)) . ".{$file->getClientOriginalExtension()}";
$mimeType = $file->getMimeType();
$isImage = FileTypeUtil::isImage($mimeType);
$fileEntity = new FileEntity($name, $originalName, $mimeType);
$dir = $this->parametersProvider->getParameter('kernel.project_dir') . '/tmp_for_uploads';
try {
$file->move($dir, $name);
} catch (FileException $fileException) {
return new JsonResponse(['success' => false, 'messageForAdmin' => $fileException->getMessage()]);
}
$sourcePath = "$dir/$name";
$targetPath = '/var/www/tile.expert/web/img_lb/_uploads/chat';
try {
$this->moveFilesToAdmManager->move($sourcePath, $targetPath);
} catch (Throwable $throwable) {
return new JsonResponse(['success' => false, 'messageForAdmin' => $throwable->getMessage()]);
}
$this->entityManager->persist($fileEntity);
if ($isImage) {
$previewPath = $previewGenerator->generatePreview("https://img.tile.expert/img_lb/_uploads/chat/$name");
$originalPreviewName = basename($previewPath);
$previewFileEntity = new FileEntity($originalPreviewName, $originalPreviewName);
$sourcePath = "$dir/$originalPreviewName";
try {
$this->moveFilesToAdmManager->move($sourcePath, $targetPath);
} catch (Throwable $throwable) {
return new JsonResponse(['success' => false, 'messageForAdmin' => $throwable->getMessage()]);
}
$this->entityManager->persist($previewFileEntity);
$fileEntity->setPreviewUrl("https://img.tile.expert/img_lb/_uploads/chat/$originalPreviewName");
}
$this->entityManager->flush();
return new JsonResponse(
json_encode(
[
'success' => true,
'stringIdentifier' => $fileEntity->getStringIdentifier(),
'url' => "https://img.tile.expert/img_lb/_uploads/chat/$name",
'mimeType' => $fileEntity->getMimeType(),
'isImage' => FileTypeUtil::isImage($fileEntity->getMimeType()),
'preview' => $fileEntity->getPreviewUrl(),
'originalName' => $fileEntity->getOriginalName(),
],
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
),
Response::HTTP_OK,
[],
true
);
}
/**
* @Route("/{_locale}/chatb/ajax-resend-failed-message", name="ajax-resend-failed-message", methods={"POST"})
*/
public function ajaxResendMessage()
{
$jsonString = $this->requestStack->getCurrentRequest()->getContent();
$array = json_decode($jsonString, true);
if (!is_array($array)) {
return $this->getErrorJsonResponse('Невалидный json или null');
}
if (empty($array)) {
return $this->getErrorJsonResponse('Массив не должен быть пустым');
}
if (!isset($array['id'])) {
return $this->getErrorJsonResponse('В массиве должен содержаться id сообщения');
}
$id = $array['id'];
$chatBMessage = $this->chatBMessageRepository->find($id);
if (!$chatBMessage) {
return $this->getErrorJsonResponse("Не найдено сообщение id $id в базе");
}
$this->entitySender->sendChatBMessage($chatBMessage);
$success = (bool)$chatBMessage->getIsDeliveryToPortalSuccess();
if (!$success) {
return $this->getErrorJsonResponse("Не удалось отправить сообщение на портал", Response::HTTP_INTERNAL_SERVER_ERROR);
}
$result = $this->refreshChatStateByToken($chatBMessage->getChatB()->getToken());
if (!$result) {
return $this->getErrorJsonResponse("Не удалось обновить состояние чата");
}
return new JsonResponse(['success' => true]);
}
/**
* @Route("/{_locale}/chatb/ajax-new-chat-message", name="ajax-new-chat-message", methods={"POST"})
*/
public function ajaxNewChatMessage(): JsonResponse
{
$cookies = $this->requestStack->getCurrentRequest()->cookies;
if (!$cookies->has('token')) {
return $this->getErrorJsonResponse('Your cookies do not contain token');
}
$token = $cookies->get('token');
$chat = $this->chatBRepository->findOneBy(['token' => $token]);
if (!$chat) {
$this->createNewChat($token);
}
$jsonString = $this->requestStack->getCurrentRequest()->getContent();
$handleResult = $this->chatOnMessageHandler->handle($jsonString);
$handleResultArray = json_decode($handleResult, true);
$refreshResult = $this->refreshChatStateByToken(UserHelper::getInstance()->getToken());
if (!$refreshResult) {
return $this->getErrorJsonResponse($this->getCouldNotUpdateChatStateMessage());
}
if (isset($handleResultArray['success']) && false === $handleResultArray['success']) {
return $this->getErrorJsonResponse($handleResultArray['message'], Response::HTTP_INTERNAL_SERVER_ERROR);
}
return new JsonResponse(['success' => true], Response::HTTP_OK);
}
/**
* @Route("{_locale}/chatb/ajax-connection-fail", name="ajax-connection-fail", methods={"POST"})
*/
public function notifyAboutConnectionFail(TelegramNotifier $telegramNotifier): JsonResponse
{
// $string = (string) $this->requestStack->getCurrentRequest()->getContent();
// $string = 'Ошибка соединения с ws-сервером. ' . $string;
// $fullLocale = App::getCurLocale(true);
// $token = UserHelper::getInstance()->getToken();
// $user = $this->currentUserProvider->getUser();
//
// $ips = $this->requestStack->getCurrentRequest()->getClientIps();
// $ipsString = implode(', ', $ips);
//
// $string .= " Full locale: $fullLocale. Token: $token. IP: $ipsString. ";
//
// if ($user) {
// $emailCanonical = $user->getEmailCanonical();
// } else {
// $emailCanonical = 'null';
// }
//
// $string .= "emailCanonical: $emailCanonical";
//
// $telegramNotifier->sendMessage($string);
return new JsonResponse(['success' => true]);
}
/**
* @Route("/{_locale}/chatb/ajax-get-chat-state", name="ajax-get-chat-state", methods={"GET"})
* @throws Exception
*/
public function ajaxGetChatState(): JsonResponse
{
$token = UserHelper::getInstance()->getToken();
$chat = $this->chatBRepository->findOneBy(['token' => $token]);
if (!$chat) {
//Если чат не найден, значит он пустой, а пустые чаты не сохраняем в базу, иначе она очень сильно разрастаться будет.
$currentUser = $this->currentUserProvider->getUser();
$cons = $this->consDefiner->getCons(
App::getCurLocale(),
App::getCurCountry(),
$currentUser,
UserHelper::getInstance()->getToken()
);
$chat = new ChatB(
$cons['login'] ?? WelcomeInfoDataGenerator::DEFAULT_CONS_PORTAL_LOGIN,
App::getCurCountry(),
App::getCountryByIp(),
$token,
App::getCurLocale()
);
}
$refreshResult = $this->refreshChatStateByChatObject($chat);
if (!$refreshResult) {
return $this->getErrorJsonResponse($this->getCouldNotUpdateChatStateMessage());
}
return new JsonResponse(['success' => true], Response::HTTP_OK);
}
private function isInterview()
{
$token = UserHelper::getInstance()->getToken();
/** @var ChatB $chatB */
$chatB = $this->chatBRepository->findOneBy(['token' => $token]);
if (!$chatB) {
return false;
}
return $chatB->isInterview();
}
private function getInterview(): ?InterviewEntity
{
$token = UserHelper::getInstance()->getToken();
/** @var ChatB $chatB */
$chatB = $this->chatBRepository->findOneBy(['token' => $token]);
if (!$chatB) {
return null;
}
return $chatB->getInterview();
}
private function isHolidayBool(): bool
{
$curCountryEntity = App::getCurCountryEntity();
if (null === $curCountryEntity) {
return false;
}
/** @var CalendarRepository $calendarRepository */
$calendarRepository = $this->entityManager->getRepository(Calendar::class);
return !empty($calendarRepository->getTodayHolidays($curCountryEntity));
}
private function isConsOnlineBool(): bool
{
$consData = $this->consDefiner->getCons(
App::getCurLocale(),
App::getCurCountry(),
$this->currentUserProvider->getUser(),
UserHelper::getInstance()->getToken()
);
return (bool)($consData['workTime'] ?? null);
}
protected function getErrorJsonResponse(string $errorMessage, int $code = 400): JsonResponse
{
return new JsonResponse(
json_encode(['success' => false, 'message' => $errorMessage], JSON_UNESCAPED_UNICODE),
$code,
[],
true
);
}
/**
* @throws Error
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
* @throws WebsocketMessageHandlerException
* @throws NonUniqueResultException
* @throws InvalidArgumentException
*/
private function getChatBlockData(?ChatB $chat, $countryIsoCodeLower, $countryByIp, $locale): array
{
if (!$chat) {
$chat = new ChatB(
WelcomeInfoDataGenerator::DEFAULT_CONS_PORTAL_LOGIN,
$countryIsoCodeLower,
$countryByIp,
UserHelper::getInstance()->getToken(),
$locale
);
}
return $this->chatArrayGenerator->generateByChatObject($chat);
}
private function refreshChatStateByToken(string $token)
{
return $this->chatStateRefresher->refreshStateByToken($token);
}
private function createNewChat(string $token): void
{
$currentUser = $this->currentUserProvider->getUser();
$cons = $this->consDefiner->getCons(
App::getCurLocale(),
App::getCurCountry(),
$currentUser,
UserHelper::getInstance()->getToken()
);
$chat = new ChatB(
$cons['login'] ?? WelcomeInfoDataGenerator::DEFAULT_CONS_PORTAL_LOGIN,
App::getCurCountry(),
App::getCountryByIp(),
$token,
App::getCurLocale()
);
$this->entityManager->persist($chat);
$this->entityManager->flush();
}
private function refreshChatStateByChatObject(ChatB $chat)
{
return $this->chatStateRefresher->refreshStateByChatObject($chat);
}
/**
* @return string
*/
private function getCouldNotUpdateChatStateMessage(): string
{
return "Could not update chat state. Try to reload the page or use chat later.";
}
/**
* Временная мера - отписываем через GET. Позже этот экшн будет удален, когда будет готов раздел с подписками.
* @Route("/{_locale}/unsub_chat", name="unsub_chat", methods={"GET"})
* @Security("is_granted('ROLE_USER')")
*/
public function unsubscribe(SubscriptionsEntityRepository $subscriptionsEntityRepository): Response {
$user = $this->currentUserProvider->getUser();
$subs = $subscriptionsEntityRepository->findOneBy(['user' => $user]);
if (!$subs) {
$subs = new SubscriptionsEntity();
$subs->setUser($user);
$this->entityManager->persist($subs);
$this->entityManager->flush();
}
$subs->setChatSub(false);
$this->entityManager->flush();
return $this->render('ChatB/unsubscribe_success.html.twig');
}
}