Skip to content

Instantly share code, notes, and snippets.

@cawa87
Created June 17, 2020 09:35
Show Gist options
  • Save cawa87/b219571c88490e02714bd779f7fed269 to your computer and use it in GitHub Desktop.
Save cawa87/b219571c88490e02714bd779f7fed269 to your computer and use it in GitHub Desktop.
<?php
namespace Ronte\TicketBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\SoftDeleteable\Traits\SoftDeleteable;
use Ronte\UserBundle\Entity\User;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Ticket
*
* @ORM\Table(name="tickets")
* @ORM\Entity(repositoryClass="Ronte\TicketBundle\Repository\TicketRepository")
* @Gedmo\SoftDeleteable(fieldName="deletedAt")
*/
class Ticket
{
use SoftDeleteable;
public const TYPE_JURIDICAL = 1;
public const TYPE_PHYSICAL = 2;
public const TYPES = [
self::TYPE_JURIDICAL => 'JURIDICAL',
self::TYPE_PHYSICAL => 'PHYSICAL',
];
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*
* @Serializer\Groups({"Ticket", "TicketChat", "post_chats_out", "get_chats_out", "put_chats_out"})
*/
private $id;
/**
* @var \DateTime
*
* @ORM\Column(type="datetime")
*
* @Gedmo\Timestampable(on="create")
*
* @Serializer\Groups({"Ticket"})
*/
protected $createdAt;
/**
* @var \DateTime
*
* @ORM\Column(type="datetime")
*
* @Gedmo\Timestampable(on="update")
*
* @Serializer\Groups({"Ticket"})
*/
protected $updatedAt;
/**
* @var \DateTime
*
* @ORM\Column(type="datetime", nullable=true)
*
* @Serializer\Groups({"Ticket"})
*/
protected $closedAt;
/**
* @var \DateTime
*
* @ORM\Column(name="deleted_at", type="datetime", nullable=true)
*/
protected $deletedAt;
/**
* @var string
*
* @ORM\Column(name="subject", type="string", length=255)
*
* @Serializer\Groups({"Ticket", "post_ticket", "put_ticket", "post_chats_out", "get_chats_out", "put_chats_out"})
*
* @Assert\NotBlank
* @Assert\Length(max = 255)
*/
private $subject;
/**
* @var string
*
* @ORM\Column(name="body", type="text", nullable=true)
*
* @Serializer\Groups({"Ticket", "post_ticket", "put_ticket"})
*/
private $body;
/**
* @var TicketStatus
*
* @ORM\ManyToOne(targetEntity="TicketStatus")
* @ORM\JoinColumn(name="status_id", referencedColumnName="id", nullable=false)
*
* @Serializer\Groups({"Ticket", "post_ticket", "put_ticket", "post_chats_out", "get_chats_out", "put_chats_out"})
* @Assert\Type("Ronte\TicketBundle\Entity\TicketStatus")
* @Assert\Valid
* @Assert\NotNull
*/
private $status;
/**
* @var User
*
* @ORM\ManyToOne(targetEntity="Ronte\UserBundle\Entity\User")
* @ORM\JoinColumn(name="assign_id", referencedColumnName="id", nullable=false)
*
* @Serializer\Groups({"ChatShort", "post_ticket", "put_ticket"})
* @Assert\Type("Ronte\UserBundle\Entity\User")
* @Assert\Valid
* @Assert\NotNull
*/
private $assign;
/**
* @var User
*
* @ORM\ManyToOne(targetEntity="Ronte\UserBundle\Entity\User")
* @ORM\JoinColumn(name="client_user_id", referencedColumnName="id", nullable=false)
*
* @Serializer\Groups({"ChatShort", "post_ticket", "put_ticket"})
* @Assert\Type("Ronte\UserBundle\Entity\User")
* @Assert\Valid
* @Assert\NotNull
*/
private $clientUser;
/**
* @var User|null
*
* @ORM\ManyToOne(targetEntity="Ronte\UserBundle\Entity\User")
* @ORM\JoinColumn(name="founder_id", referencedColumnName="id", nullable=true)
*
* @Serializer\Groups({"ChatShort", "post_ticket"})
* @Assert\Type("Ronte\UserBundle\Entity\User")
* @Assert\Valid
*/
private $founder;
/**
* @var TicketCategory
*
* @ORM\ManyToOne(targetEntity="TicketCategory")
* @ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=false)
*
* @Serializer\Groups({"Ticket", "post_ticket", "put_ticket"})
*
* @Assert\Type("Ronte\TicketBundle\Entity\TicketCategory")
* @Assert\Valid
* @Assert\NotNull
*/
private $category;
/**
* @var int
*
* @ORM\Column(name="type", type="integer")
*
* @Serializer\Groups({"Ticket", "post_ticket", "put_ticket"})
*
* @Assert\Choice({Ticket::TYPE_PHYSICAL, Ticket::TYPE_JURIDICAL}, strict=true)
*/
private $type = self::TYPE_PHYSICAL;
/**
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="Ronte\TicketBundle\Entity\TicketChat", mappedBy="ticket")
*/
protected $ticketChats;
/**
* Constructor
*/
public function __construct()
{
$this->ticketChats = new ArrayCollection();
}
/**
* Get id.
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return \DateTime
*/
public function getCreatedAt(): \DateTime
{
return $this->createdAt;
}
/**
* @param \DateTime $createdAt
* @return Ticket
*/
public function setCreatedAt(\DateTime $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
/**
* @return \DateTime
*/
public function getUpdatedAt(): \DateTime
{
return $this->updatedAt;
}
/**
* @param \DateTime $updatedAt
* @return Ticket
*/
public function setUpdatedAt(\DateTime $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* @return \DateTime|null
*/
public function getClosedAt(): ?\DateTime
{
return $this->closedAt;
}
/**
* @param \DateTime|null $closedAt
* @return Ticket
*/
public function setClosedAt(\DateTime $closedAt = null): self
{
$this->closedAt = $closedAt;
return $this;
}
/**
* @param string $subject
* @return $this
*/
public function setSubject(string $subject): self
{
$this->subject = $subject;
return $this;
}
/**
* @return string
*/
public function getSubject(): string
{
return $this->subject;
}
/**
* Set body.
*
* @param string|null $body
*
* @return Ticket
*/
public function setBody($body = null): self
{
$this->body = $body;
return $this;
}
/**
* Get body.
*
* @return string|null
*/
public function getBody(): ?string
{
return $this->body;
}
/**
* Set status.
*
* @param TicketStatus $status
*
* @return Ticket
*/
public function setStatus(TicketStatus $status): self
{
$this->status = $status;
return $this;
}
/**
* Get status.
*
* @return TicketStatus
*/
public function getStatus(): TicketStatus
{
return $this->status;
}
/**
* Set assign.
*
* @param User $assign
*
* @return Ticket
*/
public function setAssign(User $assign): self
{
$this->assign = $assign;
return $this;
}
/**
* Get assign.
*
* @return User
*/
public function getAssign(): User
{
return $this->assign;
}
/**
* Set clientUser.
*
* @param User $clientUser
*
* @return Ticket
*/
public function setClientUser(User $clientUser): self
{
$this->clientUser = $clientUser;
return $this;
}
/**
* Get clientUser.
*
* @return User
*/
public function getClientUser(): User
{
return $this->clientUser;
}
/**
* Set founder.
*
* @param User|null $founder
*
* @return Ticket
*/
public function setFounder(User $founder = null): self
{
$this->founder = $founder;
return $this;
}
/**
* Get founder.
*
* @return User|null
*/
public function getFounder(): ?User
{
return $this->founder;
}
/**
* Set category.
*
* @param TicketCategory $category
*
* @return Ticket
*/
public function setCategory(TicketCategory $category): self
{
$this->category = $category;
return $this;
}
/**
* Get category.
*
* @return TicketCategory
*/
public function getCategory(): TicketCategory
{
return $this->category;
}
/**
* Set type.
*
* @param int $type
*
* @return Ticket
*/
public function setType(int $type): self
{
$this->type = $type;
return $this;
}
/**
* Get type.
*
* @return int
*/
public function getType(): int
{
return $this->type;
}
public function getTicketChats(): Collection
{
return $this->ticketChats;
}
}
<?php
namespace Ronte\ChatsBundle\Controller;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Request\ParamFetcherInterface;
use Ronte\ChatsBundle\Entity\Chat;
use Ronte\ChatsBundle\Entity\Message;
use Ronte\ChatsBundle\Event\MessageDeliveredEvent;
use Ronte\ChatsBundle\Event\MessagesReadEvent;
use Ronte\ChatsBundle\Exception\AccessDeniedException;
use Ronte\ChatsBundle\Exception\BadRequestHttpException;
use Ronte\ChatsBundle\Exception\ConflictViolationException;
use Ronte\ChatsBundle\Exception\ConstraintViolationException;
use Ronte\ChatsBundle\Manager\MessageManager;
use Ronte\UserBundle\Entity\Session;
use Ronte\UserBundle\Entity\User;
use Ronte\UserBundle\Manager\UserManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration as Extra;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\Encoder\CsvEncoder;
use Symfony\Component\Serializer\Serializer;
/**
* MessagesController
*/
class MessageController extends FOSRestController
{
/**
* Update messages read
*
* @Rest\Put("/messages/read", name="put_read", options={"method_prefix"=false})
* @Rest\View(serializerGroups={"put_read_out", "get_files_out"})
*
* @param Request $request
*
* @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
* @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
* @throws AccessDeniedException
* @throws BadRequestHttpException
* @throws ConstraintViolationException
* @throws \InvalidArgumentException
* @throws \LogicException
*
* @return Message[]
*/
public function putMessagesReadAction(Request $request): array
{
$user = $this->getUser();
$manager = $this->getDoctrine()->getManager();
$repository = $this->getDoctrine()
->getRepository(Message::class);
$body = \json_decode($request->getContent(), true);
if (empty($body['ids']) || $body['ids'] !== array_filter($body['ids'], '\is_int')) {
throw new BadRequestHttpException('Invalid parameter: ids.');
}
if (JSON_ERROR_NONE !== json_last_error()) {
throw new BadRequestHttpException('Invalid parameter: json.' . json_last_error());
}
$messages = $repository->findBy(['id' => $body['ids']]);
if (empty($messages)) {
throw new BadRequestHttpException('Wrong parameter: message ids.');
}
foreach ($messages as $key => $message) {
/** @var Chat $chat */
$chat = $message->getChat();
if (null !== $chat && !$chat->isFounder($user) && !$chat->isResponsible($user)) {
throw new AccessDeniedException();
}
if ($message->isSender($user)) {
unset($messages[$key]);
continue;
}
$message->setReadAt(new \DateTime());
$manager->persist($message);
}
if (\count($messages)) {
$messages = array_values($messages);
$manager->flush();
$this->get('event_dispatcher')->dispatch(MessagesReadEvent::NAME, new MessagesReadEvent($messages));
}
return $messages;
}
/**
* Update message read
*
* @Rest\Put("/messages/{id}/read", name="put_message_read", requirements={"id":"\d+"})
* @Rest\View(serializerGroups={"put_read_out", "get_files_out"})
*
* @param Message $message
*
* @throws ConflictViolationException
* @throws \LogicException
*
* @return Message
*/
public function putMessageReadAction(Message $message): Message
{
/** @var User $user */
$user = $this->getUser();
$userRole = $user->getRole(true);
$chat = $message->getChat();
if (null !== $chat && $userRole === 'ROLE_CLIENT' && !$chat->isFounder($user)) {
throw new AccessDeniedException();
}
if (null !== $chat && $userRole !== 'ROLE_CLIENT' && !$chat->isResponsible($user)) {
throw new AccessDeniedException();
}
if (null === $chat && $userRole !== 'ROLE_CLIENT') {
throw new AccessDeniedException();
}
if ($message->isSender($this->getUser())) {
throw new ConflictViolationException('Can`t read own message', 'supportMessageRead');
}
$manager = $this->getDoctrine()->getManager();
$message->setReadAt(new \DateTime());
$manager->persist($message);
$manager->flush();
$this->get('event_dispatcher')->dispatch(MessagesReadEvent::NAME, new MessagesReadEvent([$message]));
return $message;
}
/**
* Get messages
*
* @Rest\Get("/messages", name="get_messages_2", options={"method_prefix"=false})
* @Rest\QueryParam(name="sessionId", requirements="\d+", nullable=true, description="Required for clients")
* @Rest\QueryParam(name="chatId", requirements="\d+", nullable=true)
* @Rest\QueryParam(name="offset", requirements="\d+", nullable=true, default="0")
* @Rest\QueryParam(name="limit", requirements="\d+", nullable=true, default="1000")
* @Rest\QueryParam(name="id_gt", requirements="\d+", nullable=true, description="Greater then given id")
* @Rest\QueryParam(name="id_lt", requirements="\d+", nullable=true, description="Less then given id")
* @Rest\QueryParam(name="sort", nullable=true, description="Allownd alues: 'id' sort by id ASC, '-id' sort by id DESC")
*
* @Rest\View(serializerGroups={"get_messages_out", "get_files_out"})
*
* @Extra\Security("has_role('ROLE_CLIENT') or has_role('ROLE_OPERATOR1')")
*
* @param ParamFetcherInterface $paramFetcher
*
* @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
* @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
* @throws \LogicException
* @throws \Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException
* @throws BadRequestHttpException
* @throws AccessDeniedException
*
* @return Message[]
*/
public function getMessagesListAction(ParamFetcherInterface $paramFetcher): array
{
$user = $this->getUser();
$authChecker = $this->get('security.authorization_checker');
$sessionId = $paramFetcher->get('sessionId');
if ($authChecker->isGranted('ROLE_CLIENT')) {
if (!$sessionId) {
throw new BadRequestHttpException('Parameter: sessionId can not be empty');
}
if (!$session = $this->getDoctrine()->getRepository(Session::class)->find($sessionId)) {
throw new BadRequestHttpException('Parameter: sessionId is invalid.');
}
if ($user !== $session->getUser()) {
throw new AccessDeniedException('');
}
return $this->getDoctrine()
->getRepository(Message::class)
->findByClientSession($session, $paramFetcher->all())
;
}
if ($authChecker->isGranted('ROLE_OPERATOR1')) {
$operators = $this->get(UserManager::class)->getAvailOperators($user);
$operators[] = $user;
if ($authChecker->isGranted('ROLE_OPERATOR2')) {
$operators[] = null;
}
return $this->getDoctrine()
->getRepository(Message::class)
->findForOperators($operators, $paramFetcher->all());
}
throw new AccessDeniedException('');
}
/**
* Get messages
*
* @Rest\Get("/messages/archive", name="get_messages_archive", options={"method_prefix"=false})
* @Rest\QueryParam(name="username", strict=true)
* @Rest\QueryParam(name="app", strict=false)
* @Rest\QueryParam(name="product", strict=false)
* @Rest\QueryParam(name="format", strict=false)
* @Rest\QueryParam(name="from_date", strict=false, requirements="\d+")
* @Rest\QueryParam(name="to_date", strict=false, requirements="\d+")
*
* @Rest\View(serializerGroups={"get_messages_out", "get_files_out"})
*
* @Extra\Security("has_role('ROLE_ADMIN') or has_role('ROLE_OPERATOR3')")
*
* @param ParamFetcherInterface $paramFetcher
*
* @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
* @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
* @throws \LogicException
* @throws \Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException
* @throws BadRequestHttpException
* @throws AccessDeniedException
*
* @return Message[]|BinaryFileResponse
*/
public function getMessagesArchiveAction(ParamFetcherInterface $paramFetcher)
{
$messages = $this->get(MessageManager::class)->getArchive($paramFetcher->all());
if ($paramFetcher->get('format') === 'csv') {
/** @var Serializer $serializer */
$serializer = $this->get('serializer');
$data = $serializer->serialize($messages, 'csv', [
'groups' => ['get_messages_csv'],
CsvEncoder::DELIMITER_KEY => ';'
]);
$data = mb_convert_encoding($data, 'Windows-1251');
$date = (new \DateTime())->format('Y-m-d_H:i:s.u');
$filename = '/tmp/archive(' . $date . ').csv';
file_put_contents($filename, $data);
return $this->file($filename);
}
return $messages;
}
/**
* Create message
*
* @Rest\Post("/messages", name="post_message")
* @Rest\RequestParam(name="body", strict=false, description="Message body")
* @Rest\RequestParam(name="messageType", requirements="^(raw|file)$", strict=false, default="raw", description="Message type")
* @Rest\RequestParam(name="sessionId", strict=false, requirements="\d+", description="Required for clients")
* @Rest\RequestParam(name="chatId", strict=false, requirements="\d+", description="Required for operators")
* @Rest\RequestParam(name="msgId", strict=false, description="Client's UUID")
* @Rest\RequestParam(name="files", strict=false, description="User files. Required if messageType=file")
* @Rest\View(serializerGroups={"post_messages_out", "get_files_out"})
*
* @param ParamFetcherInterface $paramFetcher
*
* @throws AccessDeniedException
* @throws BadRequestHttpException
* @throws ConflictViolationException
* @throws \Doctrine\ORM\ORMInvalidArgumentException
* @throws \LogicException
* @throws \Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException
* @throws ConstraintViolationException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\NonUniqueResultException
*
* @return Message
*/
public function postMessageAction(ParamFetcherInterface $paramFetcher): Message
{
$authChecker = $this->get('security.authorization_checker');
if ($authChecker->isGranted('ROLE_CLIENT')) {
$message = $this->get(MessageManager::class)->createByClient($this->getUser(), $paramFetcher->all());
} elseif ($authChecker->isGranted('ROLE_OPERATOR1')) {
$message = $this->get(MessageManager::class)->createByOperator($this->getUser(), $paramFetcher->all());
} elseif ($authChecker->isGranted('ROLE_SERVICE')) {
$message = $this->get(MessageManager::class)->createByService($this->getUser(), $paramFetcher->all());
} else {
throw new AccessDeniedException();
}
return $message;
}
/**
* Get unread messages count
*
* @Rest\Get("/messages/unreadCount", name="get_unread_messages_count")
* @Rest\QueryParam(name="sessionId", strict=false, description="Required for clients")
* @Rest\QueryParam(name="chatId", strict=false, description="Required for operators")
*
* @param ParamFetcherInterface $paramFetcher
* @throws BadRequestHttpException
* @throws AccessDeniedException
* @throws \LogicException
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
* @return array
*/
public function getUnreadMessagesCountAction(ParamFetcherInterface $paramFetcher): array
{
$count = $this->get(MessageManager::class)->countUnreadMessages($this->getUser(), $paramFetcher->all());
return [
'count' => $count,
];
}
/**
* Update message read
*
* @Rest\Put("/messages/{id}/delivered", name="put_message_delivered", requirements={"id":"\d+"})
* @Rest\View(serializerGroups={"put_read_out", "get_files_out"})
*
* @param Message $message
*
* @throws ConflictViolationException
* @throws \LogicException
*
* @return Message
*/
public function putMessageDeliveredAction(Message $message): Message
{
/** @var User $user */
$user = $this->getUser();
$userRole = $user->getRole(true);
$chat = $message->getChat();
if (null !== $chat && $userRole === 'ROLE_CLIENT' && !$chat->isFounder($user)) {
throw new AccessDeniedException();
}
if (null === $chat && $userRole !== 'ROLE_CLIENT') {
throw new AccessDeniedException();
}
if ($message->isSender($this->getUser())) {
throw new ConflictViolationException('Can`t check as delivered own message', 'supportMessageDelivered');
}
$manager = $this->getDoctrine()->getManager();
$message->setDeliveredAt(new \DateTime());
$manager->persist($message);
$manager->flush();
$this->get('event_dispatcher')->dispatch(MessageDeliveredEvent::NAME, new MessageDeliveredEvent($message));
return $message;
}
}
<?php
namespace Ronte\ChatsBundle\Manager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Ronte\ChatsBundle\Entity\ChatStatus;
use Ronte\ChatsBundle\Entity\File;
use Ronte\ChatsBundle\Event\ChatReopenEvent;
use Ronte\ChatsBundle\Event\ChatsAvailabilityUpdateEvent;
use Ronte\ChatsBundle\Event\MessagePostByCommandEvent;
use Ronte\ChatsBundle\Event\MessagePostEvent;
use Ronte\ChatsBundle\Event\MetricsEvent;
use Ronte\ChatsBundle\Event\RmqCheckEvent;
use Ronte\ChatsBundle\Exception\AccessDeniedException;
use Ronte\ChatsBundle\Exception\BadRequestHttpException;
use Ronte\ChatsBundle\Exception\ConflictViolationException;
use Ronte\ChatsBundle\Repository\ChatRepository;
use Ronte\UserBundle\Entity\Application;
use Ronte\UserBundle\Entity\Product;
use Ronte\UserBundle\Manager\SessionManager;
use Ronte\UserBundle\Manager\UserManager;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Ronte\ChatsBundle\Entity\Chat;
use Ronte\ChatsBundle\Entity\Message;
use Ronte\ChatsBundle\Event\ChatsPostEvent;
use Ronte\ChatsBundle\Exception\ConstraintViolationException;
use Ronte\UserBundle\Entity\Session;
use Ronte\UserBundle\Entity\User;
use UAParser\Parser;
/**
* Class MessageManager
* @package Ronte\ChatsBundle\Manager
*/
class MessageManager
{
/** @var ValidatorInterface */
private $validator;
/** @var EntityManager */
private $entityManager;
/** @var ChatManager */
private $chatManager;
/** @var UserManager */
private $userManager;
/** @var SessionManager */
private $sessionManager;
/** @var AuthorizationCheckerInterface */
private $authChecker;
/** @var EventDispatcherInterface */
private $eventDispatcher;
/** @var SerializerInterface*/
protected $serializer;
/** @var ChatRepository */
private $chatRepository;
/** @var EntityRepository */
private $sessionRepository;
/** @var EntityRepository */
private $chatStatusRepository;
private $chatReopened = false;
/**
* MessageManager constructor.
* @param ValidatorInterface $validator
* @param EntityManager $entityManager
* @param ChatManager $chatManager
* @param UserManager $userManager
* @param SessionManager $sessionManager
* @param AuthorizationCheckerInterface $authChecker
* @param EventDispatcherInterface $eventDispatcher
* @param SerializerInterface $serializer
*/
public function __construct(
ValidatorInterface $validator,
EntityManager $entityManager,
ChatManager $chatManager,
UserManager $userManager,
SessionManager $sessionManager,
AuthorizationCheckerInterface $authChecker,
EventDispatcherInterface $eventDispatcher,
SerializerInterface $serializer
) {
$this->validator = $validator;
$this->entityManager = $entityManager;
$this->chatManager = $chatManager;
$this->userManager = $userManager;
$this->sessionManager = $sessionManager;
$this->authChecker = $authChecker;
$this->eventDispatcher = $eventDispatcher;
$this->serializer = $serializer;
$this->chatRepository = $entityManager->getRepository(Chat::class);
$this->sessionRepository = $this->entityManager->getRepository(Session::class);
$this->chatStatusRepository = $entityManager->getRepository(ChatStatus::class);
}
/**
* @return string
*/
public static function uuid(): string
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
random_int(0, 0xffff),
random_int(0, 0xffff),
// 16 bits for "time_mid"
random_int(0, 0xffff),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
random_int(0, 0x0fff) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
random_int(0, 0x3fff) | 0x8000,
// 48 bits for "node"
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0xffff)
);
}
/**
* @param User $user
* @param array $params
* @return Message
* @throws \Doctrine\ORM\OptimisticLockException
* @throws BadRequestHttpException
*/
public function createByService(User $user, array $params): Message
{
$this->eventDispatcher->dispatch(RmqCheckEvent::NAME, new RmqCheckEvent());
if (empty($params['sessionId'])) {
throw new BadRequestHttpException('Parameter: sessionId should not be null.');
}
if (empty($params['body'])) {
throw new BadRequestHttpException('Parameter: body should not be null.');
}
/** @var Session $session */
$session = $this->entityManager
->getRepository(Session::class)
->find($params['sessionId']);
if (!$session) {
throw new BadRequestHttpException('Parameter: sessionId is invalid.');
}
$message = $this->create([
'body' => $params['body'],
'session' => $session,
'sender' => $user,
]);
$this->eventDispatcher->dispatch(MessagePostByCommandEvent::NAME, new MessagePostByCommandEvent($message));
return $message;
}
/**
* @param User $user
* @param array $params
* @return Message
* @throws ConflictViolationException
* @throws BadRequestHttpException
* @throws ConstraintViolationException
* @throws \Doctrine\ORM\ORMInvalidArgumentException
* @throws AccessDeniedException
* @throws BadRequestHttpException
* @throws \Doctrine\ORM\NonUniqueResultException
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function createByClient(User $user, array $params): Message
{
$this->eventDispatcher->dispatch(RmqCheckEvent::NAME, new RmqCheckEvent());
if (empty($params['sessionId'])) {
throw new BadRequestHttpException('Parameter: sessionId should not be null.');
}
/** @var Session $session */
$session = $this->entityManager
->getRepository(Session::class)
->find($params['sessionId']);
if (!$session) {
throw new BadRequestHttpException('Parameter: sessionId is invalid.');
}
if ($user !== $session->getUser()) {
throw new AccessDeniedException();
}
$chat = $this->getChatBySession($session);
if (null === $chat) {
$chat = $this->chatManager->create([
'session' => $session,
'founder' => $user,
'status' => ChatStatus::STATUS_UNASSIGNED
], false);
$uaParsed = (Parser::create())->parse($session->getUa());
$this->create([
'chat' => $chat,
'body' => sprintf('Chat started by %s in "%s/%s"',
$user->getName(), $uaParsed->toString(), $uaParsed->device->toString()
),
'session' => $session,
'sender' => $user,
'type' => Message::TYPE_SYSTEM,
]);
$this->eventDispatcher->dispatch(ChatsPostEvent::NAME_BY_CLIENT, new ChatsPostEvent($chat, $session));
}
$message = $this->create([
'chat' => $chat,
'body' => $params['body'] ?? '',
'session' => $session,
'sender' => $user,
'msgId' => $params['msgId'],
'type' => $params['messageType'] ?? Message::TYPE_RAW,
'files' => $params['files'] ?? []
]);
if ($chat->getStatus()->getName() === ChatStatus::STATUS_HOLD) {
$greetingMessage = $this->create([
'chat' => $chat,
'type' => Message::TYPE_GREETING
]);
$this->modifiedHoldChat($chat);
}
$this->eventDispatcher->dispatch(MessagePostEvent::NAME, new MessagePostEvent([$message]));
if ($this->chatReopened) {
$this->eventDispatcher->dispatch(MetricsEvent::NAME_REOPEN_CHAT, new MetricsEvent($chat));
}
if (isset($greetingMessage)) {
$this->eventDispatcher->dispatch(MessagePostEvent::NAME, new MessagePostEvent([$greetingMessage]));
}
$metricEvent = new MetricsEvent($chat);
$metricEvent->setMessage($message);
$this->eventDispatcher->dispatch( MetricsEvent::NAME_POST_CLIENT_MESSAGE, $metricEvent);
return $message;
}
private function getChatBySession(Session $session): ?Chat
{
$chat = $this->chatRepository->findOpenedBySession($session->getId());
if (null !== $chat) {
return $chat;
}
$chat = $this->chatRepository->findLastClosedBySession($session->getId(), new \DateTime('-7 days'));
$operator = $chat ? $chat->getResponsible() : null;
if (null !== $operator && $operator->isOnlineOperator() && $operator->getActive()) {
$activeChatsCount = $this->chatRepository->countActiveByOperator($operator);
if ($activeChatsCount < 5) {
$activeStatus = $this->chatStatusRepository->findOneBy(['name' => ChatStatus::STATUS_ACTIVE]);
$chat->setStatus($activeStatus);
$this->entityManager->flush($chat);
$opSession = $this->sessionManager->getOperatorsSession($operator);
$this->eventDispatcher->dispatch(ChatReopenEvent::NAME_CLIENT, new ChatReopenEvent($chat, $session, $opSession));
$this->chatReopened = true;
return $chat;
}
}
return null;
}
/**
* @param Chat $chat
* @throws \Doctrine\ORM\ORMInvalidArgumentException
* @throws \Doctrine\ORM\OptimisticLockException
*/
private function modifiedHoldChat(Chat $chat): void
{
$statusActive = $this->chatStatusRepository->findOneBy(['name' => ChatStatus::STATUS_ACTIVE]);
$chat->setStatus($statusActive);
$chat->setFrozenTill(null);
$this->entityManager->persist($chat);
$this->entityManager->flush($chat);
$this->eventDispatcher->dispatch(ChatsAvailabilityUpdateEvent::NAME, new ChatsAvailabilityUpdateEvent($chat));
$this->eventDispatcher->dispatch(MetricsEvent::NAME_UNHOLD_CHAT, new MetricsEvent($chat));
}
/**
* @param User $user
* @param array $params
* @return Message
* @throws \Ronte\ChatsBundle\Exception\ConstraintViolationException
* @throws \Doctrine\ORM\ORMInvalidArgumentException
* @throws \Ronte\ChatsBundle\Exception\ConflictViolationException
* @throws \Ronte\ChatsBundle\Exception\AccessDeniedException
* @throws \Ronte\ChatsBundle\Exception\BadRequestHttpException
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function createByOperator(User $user, array $params): Message
{
$chatId = $params['chatId'] ?? null;
if (!$chatId) {
throw new BadRequestHttpException('Parameter: chatId can not be null');
}
/** @var Chat $chat */
$chat = $this->chatRepository->find($chatId);
if (null === $chat) {
throw new BadRequestHttpException('Parameter: chatId is invalid');
}
if ($chat->getResponsible() !== $user) {
throw new AccessDeniedException();
}
if ($chat->getStatus()->getName() === ChatStatus::STATUS_CLOSED) {
throw new ConflictViolationException(
'Chat closed!',
'supportChat',
null,
Response::HTTP_LENGTH_REQUIRED
);
}
if ($chat->getStatus()->getName() === ChatStatus::STATUS_HOLD) {
$this->modifiedHoldChat($chat);
}
$message = $this->create([
'chat' => $chat,
'body' => $params['body'] ?? '',
'session' => $chat->getSession(),
'sender' => $user,
'msgId' => $params['msgId'],
'type' => $params['messageType'] ?? Message::TYPE_RAW,
'files' => $params['files'] ?? []
]);
$this->eventDispatcher->dispatch(MessagePostEvent::NAME, new MessagePostEvent([$message]));
$metricEvent = new MetricsEvent($chat);
$metricEvent->setMessage($message);
$this->eventDispatcher->dispatch( MetricsEvent::NAME_POST_OPERATOR_MESSAGE, $metricEvent);
return $message;
}
/**
* @param array $params
* @param bool $flush
* @return Message
* @throws \Ronte\ChatsBundle\Exception\ConstraintViolationException
* @throws \Doctrine\ORM\ORMInvalidArgumentException
* @throws \Ronte\ChatsBundle\Exception\ConflictViolationException
* @throws \Ronte\ChatsBundle\Exception\BadRequestHttpException
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function create(array $params, $flush = true): ?Message
{
$this->eventDispatcher->dispatch(RmqCheckEvent::NAME, new RmqCheckEvent());
$params['body'] = isset($params['body']) ? trim($params['body']) : '';
$params['type'] = $params['type'] ?? Message::TYPE_RAW;
$chat = $params['chat'] ?? null;
//validate messageType.raw
if (($params['type'] == Message::TYPE_RAW) && $params['body'] === '') {
throw new BadRequestHttpException('Parameter: body should not be empty.');
}
if (($params['type'] == Message::TYPE_GREETING)) {
if (!$greeting = $this->chatManager->getGreeting($chat)) {
return null;
}
$params['body'] = $greeting->getMessage();
$params['session'] = $chat->getSession();
$params['sender'] = $chat->getResponsible() ?? $this->userManager->getBotUser();
if (null === $params['sender']) {
throw new ConflictViolationException('Cant find chats responsible or bot user!');
}
}
/** @var Message $message */
$message = $this->serializer
->deserialize(
json_encode($params),
Message::class,
'json',
[
'enable_max_depth' => true,
'groups' => ['post_messages_in'],
]
);
$message->setChat($chat);
$message->setType($params['type']);
$message->setSession($params['session']);
$message->setSender($params['sender']);
//validate messageType.file
if ($params['type'] == Message::TYPE_FILE) {
if (empty($params['files'])) {
throw new BadRequestHttpException('Parameter: files can not be empty');
}
$fileRepository = $this->entityManager->getRepository(File::class);
foreach ($params['files'] as $fileId) {
$file = $fileRepository->find($fileId);
if ($file === null) {
throw new ConflictViolationException(
sprintf('Parameter: files %d incorrect', $fileId),
'supportMessage',
null,
404,
['files' => $fileId]
);
}
if (!$file->isOwner($params['sender'])) {
throw new ConflictViolationException(
sprintf('Parameter: files no access to file %d', $fileId),
'supportMessage',
null,
410,
['files' => $fileId]
);
}
$message->addFile($file);
}
}
if (!empty($params['msgId'])) {
$message->setMsgId($params['msgId']);
}
$errors = $this->validator
->validate($message, null, ['post_messages_in']);
if (\count($errors)) {
throw new ConstraintViolationException($errors);
}
$this->entityManager->persist($message);
if ($flush) {
$this->entityManager->flush();
}
return $message;
}
/**
* @param User $user
* @param array $params
* @return int
* @throws \Ronte\ChatsBundle\Exception\AccessDeniedException
* @throws \Ronte\ChatsBundle\Exception\BadRequestHttpException
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function countUnreadMessages(User $user, array $params): int
{
$sessionId = !empty($params['sessionId']) ? $params['sessionId'] : null;
$chatId = !empty($params['chatId']) ? $params['chatId'] : null;
if ($this->authChecker->isGranted('ROLE_CLIENT')) {
if (!$sessionId) {
throw new BadRequestHttpException('Parameter: sessionId can not be empty');
}
if (!$session = $this->sessionRepository->find($sessionId)) {
throw new BadRequestHttpException('Parameter: sessionId is invalid.');
}
if ($user !== $session->getUser()) {
throw new AccessDeniedException();
}
} elseif ($this->authChecker->isGranted('ROLE_OPERATOR1')) {
if (!$chatId) {
throw new BadRequestHttpException('Parameter: chatId can not be empty');
}
if (!$chat = $this->chatRepository->find($chatId)) {
throw new BadRequestHttpException('Parameter: chatId is invalid.');
}
}
$count = $this->entityManager
->getRepository(Message::class)
->getCountUnreadMessages($user, $sessionId, $chatId)
;
return (int)$count;
}
public function getArchive(array $params)
{
$em = $this->entityManager;
$username = $params['username'] ?? null;
$filters = [];
$user = $this->userManager->findClientByUsername($username);
if (null === $user) {
throw new BadRequestHttpException('Client not found.');
}
if (!empty($params['app'])) {
$filters['app'] = $em->getRepository(Application::class)->findOneBy(['code' => $params['app']]);
if (null === $filters['app']) {
throw new BadRequestHttpException('Parameter: app is invalid.');
}
}
if (!empty($params['product'])) {
$filters['product'] = $em->getRepository(Product::class)->findOneBy(['code' => $params['product']]);
if (null === $filters['product']) {
throw new BadRequestHttpException('Parameter: product is invalid.');
}
}
if (!empty($params['from_date'])) {
$filters['from_date'] = (new \DateTime())->setTimestamp($params['from_date']);
}
if (!empty($params['to_date'])) {
$filters['to_date'] = (new \DateTime())->setTimestamp($params['to_date']);
}
return $this->entityManager->getRepository(Message::class)->findClientArchive($user, $filters);
}
}
<?php
namespace Ronte\TicketBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\SoftDeleteable\Traits\SoftDeleteable;
use Ronte\UserBundle\Entity\User;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Ticket
*
* @ORM\Table(name="tickets")
* @ORM\Entity(repositoryClass="Ronte\TicketBundle\Repository\TicketRepository")
* @Gedmo\SoftDeleteable(fieldName="deletedAt")
*/
class Ticket
{
use SoftDeleteable;
public const TYPE_JURIDICAL = 1;
public const TYPE_PHYSICAL = 2;
public const TYPES = [
self::TYPE_JURIDICAL => 'JURIDICAL',
self::TYPE_PHYSICAL => 'PHYSICAL',
];
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*
* @Serializer\Groups({"Ticket", "TicketChat", "post_chats_out", "get_chats_out", "put_chats_out"})
*/
private $id;
/**
* @var \DateTime
*
* @ORM\Column(type="datetime")
*
* @Gedmo\Timestampable(on="create")
*
* @Serializer\Groups({"Ticket"})
*/
protected $createdAt;
/**
* @var \DateTime
*
* @ORM\Column(type="datetime")
*
* @Gedmo\Timestampable(on="update")
*
* @Serializer\Groups({"Ticket"})
*/
protected $updatedAt;
/**
* @var \DateTime
*
* @ORM\Column(type="datetime", nullable=true)
*
* @Serializer\Groups({"Ticket"})
*/
protected $closedAt;
/**
* @var \DateTime
*
* @ORM\Column(name="deleted_at", type="datetime", nullable=true)
*/
protected $deletedAt;
/**
* @var string
*
* @ORM\Column(name="subject", type="string", length=255)
*
* @Serializer\Groups({"Ticket", "post_ticket", "put_ticket", "post_chats_out", "get_chats_out", "put_chats_out"})
*
* @Assert\NotBlank
* @Assert\Length(max = 255)
*/
private $subject;
/**
* @var string
*
* @ORM\Column(name="body", type="text", nullable=true)
*
* @Serializer\Groups({"Ticket", "post_ticket", "put_ticket"})
*/
private $body;
/**
* @var TicketStatus
*
* @ORM\ManyToOne(targetEntity="TicketStatus")
* @ORM\JoinColumn(name="status_id", referencedColumnName="id", nullable=false)
*
* @Serializer\Groups({"Ticket", "post_ticket", "put_ticket", "post_chats_out", "get_chats_out", "put_chats_out"})
* @Assert\Type("Ronte\TicketBundle\Entity\TicketStatus")
* @Assert\Valid
* @Assert\NotNull
*/
private $status;
/**
* @var User
*
* @ORM\ManyToOne(targetEntity="Ronte\UserBundle\Entity\User")
* @ORM\JoinColumn(name="assign_id", referencedColumnName="id", nullable=false)
*
* @Serializer\Groups({"ChatShort", "post_ticket", "put_ticket"})
* @Assert\Type("Ronte\UserBundle\Entity\User")
* @Assert\Valid
* @Assert\NotNull
*/
private $assign;
/**
* @var User
*
* @ORM\ManyToOne(targetEntity="Ronte\UserBundle\Entity\User")
* @ORM\JoinColumn(name="client_user_id", referencedColumnName="id", nullable=false)
*
* @Serializer\Groups({"ChatShort", "post_ticket", "put_ticket"})
* @Assert\Type("Ronte\UserBundle\Entity\User")
* @Assert\Valid
* @Assert\NotNull
*/
private $clientUser;
/**
* @var User|null
*
* @ORM\ManyToOne(targetEntity="Ronte\UserBundle\Entity\User")
* @ORM\JoinColumn(name="founder_id", referencedColumnName="id", nullable=true)
*
* @Serializer\Groups({"ChatShort", "post_ticket"})
* @Assert\Type("Ronte\UserBundle\Entity\User")
* @Assert\Valid
*/
private $founder;
/**
* @var TicketCategory
*
* @ORM\ManyToOne(targetEntity="TicketCategory")
* @ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=false)
*
* @Serializer\Groups({"Ticket", "post_ticket", "put_ticket"})
*
* @Assert\Type("Ronte\TicketBundle\Entity\TicketCategory")
* @Assert\Valid
* @Assert\NotNull
*/
private $category;
/**
* @var int
*
* @ORM\Column(name="type", type="integer")
*
* @Serializer\Groups({"Ticket", "post_ticket", "put_ticket"})
*
* @Assert\Choice({Ticket::TYPE_PHYSICAL, Ticket::TYPE_JURIDICAL}, strict=true)
*/
private $type = self::TYPE_PHYSICAL;
/**
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="Ronte\TicketBundle\Entity\TicketChat", mappedBy="ticket")
*/
protected $ticketChats;
/**
* Constructor
*/
public function __construct()
{
$this->ticketChats = new ArrayCollection();
}
/**
* Get id.
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return \DateTime
*/
public function getCreatedAt(): \DateTime
{
return $this->createdAt;
}
/**
* @param \DateTime $createdAt
* @return Ticket
*/
public function setCreatedAt(\DateTime $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
/**
* @return \DateTime
*/
public function getUpdatedAt(): \DateTime
{
return $this->updatedAt;
}
/**
* @param \DateTime $updatedAt
* @return Ticket
*/
public function setUpdatedAt(\DateTime $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* @return \DateTime|null
*/
public function getClosedAt(): ?\DateTime
{
return $this->closedAt;
}
/**
* @param \DateTime|null $closedAt
* @return Ticket
*/
public function setClosedAt(\DateTime $closedAt = null): self
{
$this->closedAt = $closedAt;
return $this;
}
/**
* @param string $subject
* @return $this
*/
public function setSubject(string $subject): self
{
$this->subject = $subject;
return $this;
}
/**
* @return string
*/
public function getSubject(): string
{
return $this->subject;
}
/**
* Set body.
*
* @param string|null $body
*
* @return Ticket
*/
public function setBody($body = null): self
{
$this->body = $body;
return $this;
}
/**
* Get body.
*
* @return string|null
*/
public function getBody(): ?string
{
return $this->body;
}
/**
* Set status.
*
* @param TicketStatus $status
*
* @return Ticket
*/
public function setStatus(TicketStatus $status): self
{
$this->status = $status;
return $this;
}
/**
* Get status.
*
* @return TicketStatus
*/
public function getStatus(): TicketStatus
{
return $this->status;
}
/**
* Set assign.
*
* @param User $assign
*
* @return Ticket
*/
public function setAssign(User $assign): self
{
$this->assign = $assign;
return $this;
}
/**
* Get assign.
*
* @return User
*/
public function getAssign(): User
{
return $this->assign;
}
/**
* Set clientUser.
*
* @param User $clientUser
*
* @return Ticket
*/
public function setClientUser(User $clientUser): self
{
$this->clientUser = $clientUser;
return $this;
}
/**
* Get clientUser.
*
* @return User
*/
public function getClientUser(): User
{
return $this->clientUser;
}
/**
* Set founder.
*
* @param User|null $founder
*
* @return Ticket
*/
public function setFounder(User $founder = null): self
{
$this->founder = $founder;
return $this;
}
/**
* Get founder.
*
* @return User|null
*/
public function getFounder(): ?User
{
return $this->founder;
}
/**
* Set category.
*
* @param TicketCategory $category
*
* @return Ticket
*/
public function setCategory(TicketCategory $category): self
{
$this->category = $category;
return $this;
}
/**
* Get category.
*
* @return TicketCategory
*/
public function getCategory(): TicketCategory
{
return $this->category;
}
/**
* Set type.
*
* @param int $type
*
* @return Ticket
*/
public function setType(int $type): self
{
$this->type = $type;
return $this;
}
/**
* Get type.
*
* @return int
*/
public function getType(): int
{
return $this->type;
}
public function getTicketChats(): Collection
{
return $this->ticketChats;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment