<?php
declare(strict_types=1);
namespace WPXToolsBundle\Services;
use Doctrine\ORM\EntityManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Validator\Validator;
use WPXToolsBundle\Entity\NotificationsPriority;
use WPXToolsBundle\Entity\NotificationsType;
use WPXToolsBundle\Entity\PlRequests;
use WPXToolsBundle\Entity\User;
class UserNotification
{
private $em;
private $container;
private $datetime;
private $datetimeString;
private $flashBag;
private $userId;
private $departmentId;
private $registerDate;
public $notifications = array();
public $notificationsUnseen = array();
public $notificationFeedbackChats = 0;
public $notificationFeedbackTickets = 0;
public $notificationAssignedIssues = 0;
public $notificationsWidget = array();
public $notificationsUnseenCount = 0;
/**
* UserNotification constructor.
* @param EntityManager $em
* @param TokenStorageInterface $token
* @param ContainerInterface $container
* @param FlashBag $flashBag
*/
public function __construct(EntityManager $em, TokenStorageInterface $token, ContainerInterface $container, FlashBag $flashBag){
$this->em = $em;
$this->container = $container;
$this->flashBag = $flashBag;
if($token->getToken() && $token->getToken()->getRoles()){
// Token needs to be checked if exists or not because of security firewall which prevents using token outside of container.
$this->userId = $token->getToken()->getUser()->getId();
$this->departmentId = ($token->getToken()->getUser()->getPosition()) ? $token->getToken()->getUser()->getPosition()->getDepartment()->getId() : null ;
$this->registerDate = ($token->getToken()->getUser()->getRegisterDate()) ? $token->getToken()->getUser()->getRegisterDate() : null ;
}
$this->datetime = new \DateTime();
$this->datetimeString = $this->datetime->format('Y-m-d H:i:s');
$this->getNotifications();
$this->getFeedbackChatsCount();
$this->getFeedbackTicketsCount();
$this->getAssignedIssuesCount();
}
/**
* @param int $id
* @return array
*/
private function getNotifications(int $id = 0){
$sql = "SELECT n.id, n.notificationSubject, n.notificationBody, n.notificationDate, n.isFromSystem, nt.typeName, np.priorityName, np.priorityLabel, np.isFlashable, u.realName as author,
IF((SELECT count(nv.id) FROM notifications_viewed as nv WHERE nv.notification_id = n.id AND nv.user_id = :userId) > 0, 1, 0) as viewed -- Check which is unread
FROM notifications as n
LEFT JOIN users as u ON u.id = n.author_id
LEFT JOIN notifications_type as nt ON nt.id = n.notification_type_id
LEFT JOIN notifications_priority as np ON np.id = n.notification_priority_id
WHERE ((n.notification_type_id = 3 AND FIND_IN_SET(:userId, n.targetId)) -- Get User (type id: 3) notifications
OR (n.notification_type_id = 2 AND FIND_IN_SET(:departmentId, n.targetId)) -- Get Department (type id: 2) notifications
OR (n.notification_type_id = 1)) -- And all System (type id: 1) notifications
AND ((n.startFrom <= :dateNow) AND (n.isEnabled = 1))
AND n.id LIKE :notificationId
ORDER BY n.id DESC, n.startFrom";
$query = $this->em->getConnection()->prepare($sql);
$query->execute([
'userId' => $this->userId,
'departmentId' => $this->departmentId,
'dateNow' => $this->datetimeString,
'notificationId' => ($id) ? $id : '%'
]);
$this->notifications = ($id) ? $query->fetch() : $query->fetchAll();
if($id === 0){
$i = 0;
foreach ($this->notifications as $notification){
$notificationDate = new \DateTime($notification['notificationDate']);
if((int)$notification['viewed'] === 0 && isset($this->registerDate) && $notificationDate >= $this->registerDate){
$this->notificationsUnseen[] = $notification;
$this->notificationsUnseenCount++;
}
if($i<10 && (int)$notification['isFromSystem'] === 0){
$this->notificationsWidget[] = $notification;
$i++;
}
}
}
return $this->notifications;
}
/**
* @param int $id
* @return array
*/
public function viewNotification(int $id){
$notification = $this->getNotifications($id);
if(!$notification){
$this->flashBag->add('warning', 'Unfortunately there is no such notification ID - '.$id.'.');
return [];
}elseif((int)$notification['viewed'] === 0){
$this->setSeenNotification($notification['id']);
}
return $notification;
}
/**
* @param $notificationId
* @return bool
*/
public function setSeenNotification($notificationId): bool{
$validator = new Validator([
'notificationId' => $notificationId
], false);
$validator->validateAll([
'notificationId' => [
'type' => 'int'
]
]);
if($validator->hasErrors()){
foreach ($validator->errors as $error){
$this->flashBag->add('warning', $error);
}
return false;
}
$sql = "INSERT INTO notifications_viewed (notification_id, user_id) VALUES (:notificationId, :userId)";
$query = $this->em->getConnection()->prepare($sql);
$result = $query->execute([
'notificationId' => $notificationId,
'userId' => $this->userId
]);
return ($result > 0) ? true : false;
}
/**
* @param array $data
* @return bool
*/
public function createNotification(array $data):bool {
$defaultPriority = $this->em->getRepository(NotificationsPriority::class)->findOneBy(['isDefault' => 1]);
$defaultType = $this->em->getRepository(NotificationsType::class)->findOneBy(['isDefault' => 1]);
$validator = new Validator($data, false);
$validator->validateAll([
'notificationSubject' => [
'filter' => 'trim'
],
'notificationDescription' => [
'filter' => 'trim'
],
'startFrom' => [
'required' => false,
'default' => $this->datetimeString,
'type' => 'datetime'
],
'notificationPriority' => [
'required' => false,
'default' => $defaultPriority->getId(),
'type' => 'int'
],
'notificationType' => [
'required' => false,
'default' => $defaultType->getId(),
'type' => 'int'
],
'target' => [
'required' => false,
'type' => 'json',
'multiple' => 1
],
'targetById' => [
'required' => false,
'filter' => 'trim'
],
'isFromSystem' => [
'type' => 'int',
'required' => false,
'default' => 0
],
'isEnabled' => [
'required' => false,
'match' => ['on', 'off']
]
]);
if($defaultType->getId() !== (int)$validator->valid['notificationType'] && empty($validator->valid['target']) && empty($validator->valid['targetById'])){
$validator->setInvalid('notificationType', 'When notification type isn\'t System, there should be at least one target selected.');
}
if($validator->hasErrors()){
foreach ($validator->errors as $error){
$this->flashBag->add('warning', $error);
}
return false;
}
$targets = false;
$type = $this->em->getRepository(NotificationsType::class)->findOneBy(['id' => $validator->valid['notificationType']]);
if($type->getTypeRepository() !== null && $validator->valid['target']){
$targets = json_decode($validator->valid['target']);
$getTarget = $this->em->getRepository('WPXToolsBundle:'.$type->getTypeRepository())->findByName($targets);
$targets = array();
foreach ($getTarget as $target){
$targets[] = $target->getId();
if($type->getTypeRepository() === 'User'){
$this->isUserInLeave((int)$target->getId());
}
}
$targets = implode(',', $targets);
}else if($validator->valid['targetById']){
$targets = $validator->valid['targetById'];
if($type->getTypeRepository() === 'User'){
foreach (explode(',', $targets) as $target){
$this->isUserInLeave((int)$target);
}
}
}
$sql = "INSERT INTO notifications (notificationSubject, notificationBody, notificationDate, notification_priority_id, notification_type_id, targetId, author_id, startFrom, isFromSystem, isEnabled)
VALUES (:notificationSubject, :notificationBody, :notificationDate, :notificationPriority, :notificationType, :targetId, :authorId, :startFrom, :isFromSystem, :isEnabled)";
$query = $this->em->getConnection()->prepare($sql);
$query->execute([
'notificationSubject' => $validator->valid['notificationSubject'],
'notificationBody' => $validator->valid['notificationDescription'],
'notificationDate' => $this->datetimeString,
'notificationPriority' => (int)$validator->valid['notificationPriority'],
'notificationType' => (int)$validator->valid['notificationType'],
'targetId' => ($targets) ? $targets : 0,
'authorId' => (int)$this->userId,
'startFrom' => $validator->valid['startFrom'],
'isFromSystem' => (int)$validator->valid['isFromSystem'],
'isEnabled' => ($validator->valid['isEnabled'] === 'on' || (int)$validator->valid['isFromSystem']) ? 1 : 0
]);
return true;
}
/**
* @param int $userId
* @return bool
*/
private function isUserInLeave(int $userId):bool {
$target = $this->em->getRepository(User::class)->findOneBy(['id' => $userId]);
$sql = "SELECT pr.dateFrom, pr.dateTo, plt.leaveName FROM pl_requests as pr
JOIN pl_leave_types as plt ON plt.id = pr.leave_type_id
WHERE user_requested_id = :userId AND isApproved = '1' AND now() BETWEEN dateFrom AND dateTo
ORDER BY pr.dateFrom DESC
LIMIT 1";
$query = $this->em->getConnection()->prepare($sql);
$query->execute([
'userId' => $userId
]);
$result = $query->fetch();
if($query->rowCount() > 0){
$this->flashBag->add('warning', 'Please note that '.$target->getRealName().' is currently '.$result['leaveName'].' from '.$result['dateFrom'].' to '.$result['dateTo'].' and will review their notification as soon as they\'re available.');
}
return (bool)$query->rowCount();
}
/**
* Writes directly a value to a public variable to provide the frontend with data.
*/
private function getFeedbackChatsCount(){
$sql = "SELECT count(qc.id) as unseenChats FROM qa_chats as qc
JOIN qa_chat_participants as qcp ON qcp.chatId = qc.chatId
WHERE qcp.agentId = :userId AND qcp.seenByAgent = '0' AND qc.toUserFeedback = '1'";
$query = $this->em->getConnection()->prepare($sql);
$query->execute([
'userId' => $this->userId
]);
$result = $query->fetch();
$this->notificationFeedbackChats = (int)$result['unseenChats'];
}
/**
* Writes directly a value to a public variable to provide the frontend with data.
*/
private function getAssignedIssuesCount(){
$sql = "SELECT count(qc.id) as unseenChats FROM qa_chats as qc
WHERE qc.assign_to_id = :userId AND qc.chatSeen = '0'";
$query = $this->em->getConnection()->prepare($sql);
$query->execute([
'userId' => $this->userId
]);
$countChats = $query->fetch();
$sql = "SELECT count(qt.id) as unseenTickets FROM qa_tickets as qt
WHERE qt.assign_to_id = :userId AND qt.ticketSeen = '0'";
$query = $this->em->getConnection()->prepare($sql);
$query->execute([
'userId' => $this->userId
]);
$countTickets = $query->fetch();
$this->notificationAssignedIssues = (int)$countChats['unseenChats']+$countTickets['unseenTickets'];
}
/**
* Writes directly a value to a public variable to provide the frontend with data.
*/
private function getFeedbackTicketsCount(){
$sql = "SELECT count(qttf.id) as unseenTickets FROM qa_tickets_to_feedbacks as qttf
WHERE qttf.user_id = :userId AND qttf.isSeen = 0";
$query = $this->em->getConnection()->prepare($sql);
$query->execute([
'userId' => $this->userId
]);
$result = $query->fetch();
$this->notificationFeedbackTickets = (int)$result['unseenTickets'];
}
/**
* @param PlRequests $request
* @param string $actionType Possible values: ['notify_approvers', 'notify_requester', 'changing_approver', 'editing_approved_request', 'send_request']
* @param int $response
* @param array $recipients If not empty, the method will send notification only to listed recipients in the variable.
* @return bool
*/
public function sendLeaveRequestNotification(PlRequests $request, string $actionType, int $response = null, array $recipients = []):bool {
if(!in_array($actionType, ['notify_approvers', 'notify_requester', 'changing_approver', 'editing_approved_request', 'send_request'])) return false;
$currentUser = $this->container->get('security.token_storage')->getToken()->getUser();
$answerString = ($response) ? 'APPROVED' : 'DENIED';
if($actionType === 'notify_requester'){
array_push($recipients, $request->getUserRequested()->getId());
}else{
if(empty($recipients)){
if($request->getUserApproved()) array_push($recipients, $request->getUserApproved()->getId());
if($request->getUserApprovedSecond()) array_push($recipients, $request->getUserApprovedSecond()->getId());
}
}
$data = array();
switch ($actionType){
case 'notify_approvers':
$data['subject'] = sprintf('[%s] %s is %s to have a %s (%s to %s)',
$answerString,
$request->getUserRequested()->getRealName(),
$answerString,
$request->getLeaveType()->getLeaveName(),
$request->getDateFrom()->format('d.m.Y'),
$request->getDateTo()->format('d.m.Y'));
$data['body'] = sprintf("Hello,\r\n\r\n%s is %s to have %s day(s) of %s\r\n\r\nFrom: %s\r\n\r\nTo: %s\r\n\r\nReason (optional): %s",
$request->getUserRequested()->getRealName(),
$answerString,
$request->getLeaveDays(),
$request->getLeaveType()->getLeaveName(),
$request->getDateFrom()->format('d.m.Y'),
$request->getDateTo()->format('d.m.Y'),
$request->getLeaveReason());
break;
case 'notify_requester':
$data['subject'] = sprintf('%s %s your leave request.', $currentUser->getRealName(), $answerString);
$data['body'] = sprintf("Requested leave: \r\n%s - %s days of %s from %s to %s\r\n Reason (optional): %s\r\nYour request has been %s. You can review your requests here - %s",
$request->getUserRequested()->getRealName(),
$request->getLeaveDays(),
$request->getLeaveType()->getLeaveName(),
$request->getDateFrom()->format('d.m.Y'),
$request->getDateTo()->format('d.m.Y'),
$request->getLeaveReason(),
$answerString,
$this->container->get('router')->getGenerator()->generate('leave_requests_my', [], UrlGeneratorInterface::ABSOLUTE_URL));
break;
case 'send_request':
case 'changing_approver':
$data['subject'] = sprintf('[REQUEST] %s has requested a %s',
$request->getUserRequested()->getRealName(),
$request->getLeaveType()->getLeaveName());
$data['body'] = sprintf("Hello,\r\n\r\n%s has requested a %s.\r\n\r\nYou are one of the assigned people to review the request.\r\n\r\nPlease review it as soon as possible in Alice:\r\n\r\n%s",
$request->getUserRequested()->getRealName(),
$request->getLeaveType()->getLeaveName(),
$this->container->get('router')->getGenerator()->generate('leave_requests_exact', [
'year' => $request->getDateFrom()->format('Y'),
'month' => $request->getDateFrom()->format('m'),
'department' => 0
], UrlGeneratorInterface::ABSOLUTE_URL));
break;
case 'editing_approved_request':
$data['subject'] = sprintf('[EDITED] %s\'s %s request has been edited',
$request->getUserRequested()->getRealName(),
$request->getLeaveType()->getLeaveName());
$data['body'] = sprintf("Hello,\r\n\r\n%s's %s request for %s day(s) [Dates: %s - %s] has been edited.\r\n\r\nPlease consider checking the changes to the request as soon as possible to avoid mistakes.\r\n\r\nYou check check it out in Alice here:\r\n\r\n%s",
$request->getUserRequested()->getRealName(),
$request->getLeaveType()->getLeaveName(),
$request->getLeaveDays(),
$request->getDateFrom()->format('d.m.Y'),
$request->getDateTo()->format('d.m.Y'),
$this->container->get('router')->getGenerator()->generate('leave_requests_exact', [
'year' => $request->getDateFrom()->format('Y'),
'month' => $request->getDateFrom()->format('m'),
'department' => 0
], UrlGeneratorInterface::ABSOLUTE_URL));
break;
}
return $this->createNotification([
'notificationSubject' => $data['subject'],
'notificationDescription' => $data['body'],
'notificationType' => 3, // User type
'targetById' => implode(',', $recipients),
'isFromSystem' => 1
]);
}
}