Covivo/mobicoop

View on GitHub
api/src/Communication/Service/NotificationManager.php

Summary

Maintainability
C
1 day
Test Coverage
<?php

/**
 * Copyright (c) 2019, MOBICOOP. All rights reserved.
 * This project is dual licensed under AGPL and proprietary licence.
 ***************************
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as
 *    published by the Free Software Foundation, either version 3 of the
 *    License, or (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <gnu.org/licenses>.
 ***************************
 *    Licence MOBICOOP described in the file
 *    LICENSE
 */

namespace App\Communication\Service;

use App\Carpool\Entity\Ask;
use App\Carpool\Entity\AskHistory;
use App\Carpool\Entity\CarpoolProof;
use App\Carpool\Entity\Criteria;
use App\Carpool\Entity\Matching;
use App\Carpool\Entity\Proposal;
use App\Carpool\Entity\Waypoint;
use App\Carpool\Ressource\Ad;
use App\Carpool\Service\AdManager;
use App\Carpool\Service\ProposalManager;
use App\CarpoolStandard\Entity\Booking;
use App\Communication\Entity\Email;
use App\Communication\Entity\Medium;
use App\Communication\Entity\Message;
use App\Communication\Entity\Notification;
use App\Communication\Entity\Notified;
use App\Communication\Entity\Push;
use App\Communication\Entity\Recipient;
use App\Communication\Entity\Sms;
use App\Communication\Interfaces\MessagerInterface;
use App\Communication\Repository\NotificationRepository;
use App\Community\Entity\Community;
use App\Community\Entity\CommunityUser;
use App\DataProvider\Entity\Response;
use App\Event\Entity\Event;
use App\ExternalJourney\Ressource\ExternalConnection;
use App\Incentive\Entity\LongDistanceSubscription;
use App\Incentive\Entity\ShortDistanceSubscription;
use App\Incentive\Validator\SubscriptionValidator;
use App\Match\Entity\Mass;
use App\Match\Entity\MassPerson;
use App\Payment\Entity\CarpoolItem;
use App\Payment\Entity\PaymentProfile;
use App\Rdex\Entity\RdexConnection;
use App\Scammer\Entity\Scammer;
use App\Solidary\Entity\Solidary;
use App\Solidary\Entity\SolidaryContact;
use App\User\Entity\PushToken;
use App\User\Entity\Review;
use App\User\Entity\User;
use App\User\Event\TooLongInactivityFirstWarningEvent;
use App\User\Event\TooLongInactivityLastWarningEvent;
use App\User\Repository\UserNotificationRepository;
use App\User\Repository\UserRepository;
use App\User\Service\PseudonymizationManager;
use App\User\Service\UserManager;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment;

/**
 * Notification manager.
 *
 * @author Sylvain Briat <sylvain.briat@mobicoop.org>
 */
class NotificationManager
{
    public const LANG = 'fr';
    private const ADMIN_SOLIDARY_ITEM_URL = '/solidaryrecords/show/{SOLIDARY_ID}';

    private $entityManager;
    private $internalMessageManager;
    private $emailManager;
    private $pushManager;
    private $smsManager;
    private $templating;
    private $emailTemplatePath;
    private $emailTitleTemplatePath;
    private $pushTemplatePath;
    private $pushTitleTemplatePath;
    private $smsTemplatePath;
    private $logger;
    private $notificationRepository;
    private $userNotificationRepository;
    private $enabled;
    private $mailsEnabled;
    private $smsEnabled;
    private $pushEnabled;
    private $translator;
    private $userManager;
    private $adManager;
    private $proposalManager;
    private $communicationFolder;
    private $altCommunicationFolder;
    private $structureLogoUri;
    private $userRepository;

    public function __construct(
        EntityManagerInterface $entityManager,
        Environment $templating,
        InternalMessageManager $internalMessageManager,
        EmailManager $emailManager,
        PushManager $pushManager,
        SmsManager $smsManager,
        LoggerInterface $logger,
        NotificationRepository $notificationRepository,
        UserNotificationRepository $userNotificationRepository,
        string $emailTemplatePath,
        string $emailTitleTemplatePath,
        string $pushTemplatePath,
        string $pushTitleTemplatePath,
        string $smsTemplatePath,
        bool $enabled,
        bool $mailsEnabled,
        bool $smsEnabled,
        bool $pushEnabled,
        TranslatorInterface $translator,
        UserManager $userManager,
        AdManager $adManager,
        ProposalManager $proposalManager,
        string $communicationFolder,
        string $altCommunicationFolder,
        string $structureLogoUri,
        UserRepository $userRepository
    ) {
        $this->entityManager = $entityManager;
        $this->internalMessageManager = $internalMessageManager;
        $this->emailManager = $emailManager;
        $this->pushManager = $pushManager;
        $this->smsManager = $smsManager;
        $this->logger = $logger;
        $this->notificationRepository = $notificationRepository;
        $this->userNotificationRepository = $userNotificationRepository;
        $this->emailTemplatePath = $emailTemplatePath;
        $this->emailTitleTemplatePath = $emailTitleTemplatePath;
        $this->pushTemplatePath = $pushTemplatePath;
        $this->pushTitleTemplatePath = $pushTitleTemplatePath;
        $this->smsTemplatePath = $smsTemplatePath;
        $this->templating = $templating;
        $this->enabled = $enabled;
        $this->mailsEnabled = $mailsEnabled;
        $this->smsEnabled = $smsEnabled;
        $this->pushEnabled = $pushEnabled;
        $this->translator = $translator;
        $this->userManager = $userManager;
        $this->adManager = $adManager;
        $this->proposalManager = $proposalManager;
        $this->communicationFolder = $communicationFolder;
        $this->altCommunicationFolder = $altCommunicationFolder;
        $this->structureLogoUri = $structureLogoUri;
        $this->userRepository = $userRepository;
    }

    /**
     * Send a notification for the action/user.
     *
     * @param string $action    The action
     * @param User   $recipient The user to be notified
     * @param object $object    The object linked to the notification (if more information is needed to be joined in the notification)
     */
    public function notifies(string $action, User $recipient, ?object $object = null)
    {
        // check if notification system is enabled
        if (!$this->enabled) {
            return;
        }

        // Check if the user is anonymised if yes we don't send notifications
        if (USER::STATUS_ANONYMIZED == $recipient->getStatus()) {
            return;
        }

        // A pseudonymised user is not notified
        if (PseudonymizationManager::isUserPseudonymized($recipient)) {
            return;
        }

        // check if there's a notification associated with the given action
        $notifications = null;

        // we check the user notifications
        $userNotifications = $this->userNotificationRepository->findActiveByAction($action, $recipient->getId());

        if (count($userNotifications) > 0) {
            // the user should have notifications...
            $notifications = [];
            foreach ($userNotifications as $userNotification) {
                $notifications[] = $userNotification->getNotification();
            }
        } else {
            // if the user have no notifications, we use the default notifications
            $notifications = $this->notificationRepository->findActiveByAction($action);
        }
        if ($notifications && is_array($notifications)) {
            foreach ($notifications as $notification) {
                switch ($notification->getMedium()->getId()) {
                    case Medium::MEDIUM_MESSAGE:
                        if (!is_null($object)) {
                            $this->logger->info("Internal message notification for {$action} / ".get_class($object).' / '.$recipient->getEmail());
                            if ($object instanceof MessagerInterface && !is_null($object->getMessage())) {
                                $this->internalMessageManager->sendForObject([$recipient], $object);
                            }
                        }
                        $this->createNotified($notification, $recipient, $object);

                        break;

                    case Medium::MEDIUM_EMAIL:
                        if (!$this->mailsEnabled) {
                            break;
                        }
                        $this->notifyByEmail($notification, $recipient, $object);
                        $this->createNotified($notification, $recipient, $object);
                        $this->logger->info("Email notification for {$action} / ".$recipient->getEmail());

                        break;

                    case Medium::MEDIUM_SMS:
                        if (!$this->smsEnabled) {
                            break;
                        }
                        $this->notifyBySMS($notification, $recipient, $object);

                        break;

                    case Medium::MEDIUM_PUSH:
                        if (!$this->pushEnabled) {
                            break;
                        }
                        $this->notifyByPush($notification, $recipient, $object);
                        $this->createNotified($notification, $recipient, $object);
                        $this->logger->info("Push notification for {$action} / ".$recipient->getEmail());

                        break;
                }
            }
        }
    }

    /**
     * Create a notified object.
     *
     * @param Notification $notification The notification at the origin of the notified
     * @param User         $user         The recipient of the notification
     * @param null|object  $object       The object linked with the notification
     */
    public function createNotified(Notification $notification, User $user, ?object $object)
    {
        $notified = new Notified();
        $notified->setStatus(Notified::STATUS_SENT);
        $notified->setSentDate(new \DateTime());
        $notified->setNotification($notification);
        $notified->setUser($user);
        if ($object) {
            switch (get_class($object)) {
                case Proposal::class:
                    $notified->setProposal($object);

                    break;

                case Community::class:
                    $notified->setCommunity($object);

                    break;

                case Matching::class:
                    $notified->setMatching($object);

                    break;

                case AskHistory::class:
                    $notified->setAskHistory($object);

                    break;

                case Recipient::class:
                    $notified->setRecipient($object);

                    break;
            }
        }
        $this->entityManager->persist($notified);
        $this->entityManager->flush();
    }

    /**
     * Notify a user by email.
     * Different variables can be passed to the notification body and title depending on the object linked to the notification.
     */
    private function notifyByEmail(Notification $notification, User $recipient, ?object $object = null)
    {
        $email = new Email();
        $email->setRecipientEmail($recipient->getEmail());
        $signature = [];
        $titleContext = [];
        $bodyContext = [];
        if ($object) {
            switch (ClassUtils::getRealClass(get_class($object))) {
                case Proposal::class:
                    $origin = null;
                    $destination = null;
                    $departureTime = null;
                    $arrivalTime = null;
                    foreach ($object->getWaypoints() as $waypoint) {
                        if (0 == $waypoint->getPosition()) {
                            $origin = $waypoint;
                        }
                        if ($waypoint->isDestination()) {
                            $destination = $waypoint;
                        }
                    }
                    if (Criteria::FREQUENCY_PUNCTUAL == $object->getCriteria()->getFrequency()) {
                        $departureTime = $object->getCriteria()->getFromTime();
                        if ($object->getCriteria()->isPassenger()) {
                            $arrivalTime = clone $departureTime;
                            $arrivalTime->modify('+'.$object->getCriteria()->getDiractionPassenger()->getDuration().' second');
                        } else {
                            $arrivalTime = clone $departureTime;
                            $arrivalTime->modify('+'.$object->getCriteria()->getDiractionDriver()->getDuration().' second');
                        }
                    }
                    $titleContext = [];
                    $bodyContext = [
                        'user' => $recipient,
                        'notification' => $notification,
                        'proposal' => $object,
                        'origin' => $origin,
                        'destination' => $destination,
                        'departureTime' => $departureTime,
                        'arrivalTime' => $arrivalTime,
                    ];

                    break;

                case Matching::class:
                    $titleContext = [];
                    $bodyContext = ['user' => $recipient, 'notification' => $notification, 'matching' => $object];

                    break;

                case AskHistory::class:
                    $titleContext = [];
                    $bodyContext = ['user' => $recipient, 'askHistory' => $object];

                    break;

                case Ask::class:
                    $titleContext = [];
                    $outwardOrigin = null;
                    $outwardDestination = null;
                    $result = null;
                    $returnOrigin = null;
                    $returnDestination = null;
                    foreach ($object->getMatching()->getProposalRequest()->getWaypoints() as $waypoint) {
                        if (0 == $waypoint->getPosition()) {
                            $passengerOriginWaypoint = $waypoint;
                        } elseif (true == $waypoint->isDestination()) {
                            $passengerDestinationWaypoint = $waypoint;
                        }
                    }
                    if (!is_null($object->getAd())) {
                        if (null !== $object->getAd()->getResults()[0]->getResultPassenger()) {
                            $result = $object->getAd()->getResults()[0]->getResultPassenger();
                        } else {
                            $result = $object->getAd()->getResults()[0]->getResultDriver();
                        }
                        if (null !== $result->getOutward()) {
                            foreach ($result->getOutward()->getWaypoints() as $waypoint) {
                                if ('passenger' == $waypoint['role'] && 'origin' == $waypoint['type']) {
                                    $outwardOrigin = $waypoint;
                                } elseif ('passenger' == $waypoint['role'] && 'destination' == $waypoint['type']) {
                                    $outwardDestination = $waypoint;
                                }
                            }
                            // We check if there is really at least one day checked. Otherwide, we force the $result->outward at null to hide it in the mail
                            // It's the case when the user who made the ask only checked return days
                            if (Criteria::FREQUENCY_REGULAR == $object->getAd()->getFrequency()
                            && !$result->getOutward()->isMonCheck()
                            && !$result->getOutward()->isTueCheck()
                            && !$result->getOutward()->isWedCheck()
                            && !$result->getOutward()->isThuCheck()
                            && !$result->getOutward()->isFriCheck()
                            && !$result->getOutward()->isSatCheck()
                            && !$result->getOutward()->isSunCheck()
                            ) {
                                $result->setOutward(null);
                            }
                        }
                        if (null !== $result->getReturn()) {
                            foreach ($result->getReturn()->getWaypoints() as $waypoint) {
                                if ('passenger' == $waypoint['role'] && 'origin' == $waypoint['type']) {
                                    $returnOrigin = $waypoint;
                                } elseif ('passenger' == $waypoint['role'] && 'destination' == $waypoint['type']) {
                                    $returnDestination = $waypoint;
                                }
                            }
                        }
                    }
                    $bodyContext = [
                        'user' => $recipient,
                        'ask' => $object,
                        'origin' => $passengerOriginWaypoint,
                        'destination' => $passengerDestinationWaypoint,
                        'result' => $result,
                        'outwardOrigin' => $outwardOrigin,
                        'outwardDestination' => $outwardDestination,
                        'returnOrigin' => $returnOrigin,
                        'returnDestination' => $returnDestination,
                    ];

                    break;

                case Ad::class:
                    $titleContext = [];
                    $outwardOrigin = null;
                    $outwardDestination = null;
                    $returnOrigin = null;
                    $returnDestination = null;
                    $sender = $this->userManager->getUser($object->getUserId()) == $recipient ? $object->getResults()[0]->getCarpooler() : $this->userManager->getUser($object->getUserId());
                    if (null !== $object->getResults()[0]->getResultPassenger()) {
                        $result = $object->getResults()[0]->getResultPassenger();
                    } else {
                        $result = $object->getResults()[0]->getResultDriver();
                    }

                    if ($recipient->getId() !== $object->getUserId()) {
                        $recipientRole = $object->getRole();
                    } else {
                        $recipientRole = ad::ROLE_DRIVER == $object->getRole() ? ad::ROLE_PASSENGER : ad::ROLE_DRIVER;
                    }

                    if (null !== $result->getOutward()) {
                        foreach ($result->getOutward()->getWaypoints() as $waypoint) {
                            if ('passenger' == $waypoint['role'] && 'origin' == $waypoint['type']) {
                                $outwardOrigin = $waypoint;
                            } elseif ('passenger' == $waypoint['role'] && 'destination' == $waypoint['type']) {
                                $outwardDestination = $waypoint;
                            }
                        }
                        // We check if there is really at least one day checked. Otherwide, we force the $result->outward at null to hide it in the mail
                        // It's the case when the user who made the ask only checked return days
                        if (Criteria::FREQUENCY_REGULAR == $object->getFrequency()
                        && !$result->getOutward()->isMonCheck()
                        && !$result->getOutward()->isTueCheck()
                        && !$result->getOutward()->isWedCheck()
                        && !$result->getOutward()->isThuCheck()
                        && !$result->getOutward()->isFriCheck()
                        && !$result->getOutward()->isSatCheck()
                        && !$result->getOutward()->isSunCheck()
                        ) {
                            $result->setOutward(null);
                        }
                    }
                    if (null !== $result->getReturn()) {
                        foreach ($result->getReturn()->getWaypoints() as $waypoint) {
                            if ('passenger' == $waypoint['role'] && 'origin' == $waypoint['type']) {
                                $returnOrigin = $waypoint;
                            } elseif ('passenger' == $waypoint['role'] && 'destination' == $waypoint['type']) {
                                $returnDestination = $waypoint;
                            }
                        }
                    }
                    $bodyContext = [
                        'user' => $recipient,
                        'ad' => $object,
                        'sender' => $sender,
                        'result' => $result,
                        'outwardOrigin' => $outwardOrigin,
                        'outwardDestination' => $outwardDestination,
                        'returnOrigin' => $returnOrigin,
                        'returnDestination' => $returnDestination,
                        'recipientRole' => $recipientRole,
                    ];

                    break;

                case Recipient::class:
                    $titleContext = [];
                    $bodyContext = [];

                    break;

                case User::class:
                    if (!is_null($recipient->getLegalGuardianEmail())) {
                        $email->setRecipientEmail($recipient->getLegalGuardianEmail());
                    }
                    $titleContext = [];
                    $bodyContext = ['user' => $recipient];

                    break;

                case Event::class:
                    $titleContext = [];
                    $bodyContext = ['user' => $recipient, 'event' => $object];

                    break;

                case Community::class:
                    $sender = null;
                    $titleContext = [
                        'community' => $object,
                    ];
                    $bodyContext = [
                        'user' => $recipient,
                        'recipient' => $recipient,
                        'community' => $object,
                    ];

                    break;

                case CommunityUser::class:
                    $sender = null;
                    $bodyContext = [
                        'user' => $recipient,
                        'recipient' => $recipient,
                        'community' => $object->getCommunity(),
                        'senderGivenName' => $object->getUser()->getGivenName(),
                        'senderShortFamilyName' => $object->getUser()->getShortFamilyName(),
                    ];

                    break;

                case Message::class:
                    $titleContext = ['user' => $object->getUser()];

                    // ask history
                    if (is_null($object->getMessage())) {
                        $threadType = 'direct';
                    } elseif (
                        !is_null($object->getMessage()->getAskHistory())
                        && !is_null($object->getMessage()->getAskHistory()->getAsk())
                        && !is_null($object->getMessage()->getAskHistory()->getAsk()->getCriteria())
                        && $object->getMessage()->getAskHistory()->getAsk()->getCriteria()->isSolidaryExclusive()
                    ) {
                        $threadType = 'solidary';
                    } else {
                        $threadType = 'carpool';
                    }

                    $threadMessageId = is_null($object->getMessage()) ? $object->getId() : $object->getMessage()->getId();

                    $bodyContext = [
                        'sender' => $object->getUser(),
                        'sendingDate' => $object->getCreatedDate(),
                        'text' => $object->getText(),
                        'link' => "/{$threadMessageId}?type={$threadType}",
                        'user' => $recipient,
                    ];

                    break;

                case RdexConnection::class:
                    $titleContext = [];
                    $proposal = $this->proposalManager->get($object->getJourneysId());

                    $origin = $proposal->getWaypoints()[0]->getAddress()->getAddressLocality();
                    $destination = $proposal->getWaypoints()[count($proposal->getWaypoints()) - 1]->getAddress()->getAddressLocality();

                    $date = $time = '';
                    if (Criteria::FREQUENCY_PUNCTUAL == $proposal->getCriteria()->getFrequency()) {
                        if (!is_null($proposal->getCriteria()->getFromDate())) {
                            $date = $proposal->getCriteria()->getFromDate()->format('d/m/Y');
                        }
                        if (!is_null($proposal->getCriteria()->getFromTime())) {
                            $date = $proposal->getCriteria()->getFromDate();
                        }
                    }

                    $bodyContext = [
                        'text' => $object->getDetails(),
                        'user' => $recipient,
                        'operator' => $object->getOperator(),
                        'origin' => $object->getOrigin(),
                        'journeyOrigin' => $origin,
                        'journeyDestination' => $destination,
                        'journeyDate' => $date,
                        'journeyTime' => $time,
                    ];

                    break;

                case Solidary::class:
                    $titleContext = [];
                    $bodyContext = [
                        'adminUrl' => preg_replace('/\{SOLIDARY_ID\}/', $object->getId(), self::ADMIN_SOLIDARY_ITEM_URL),
                        'applicant' => $object->getSolidaryUserStructure()->getSolidaryUser()->getUser(),
                        'structure' => [
                            'logo' => !empty($object->getSolidaryUserStructure()->getStructure()->getImages()) ? $object->getSolidaryUserStructure()->getStructure()->getImages()[0] : null,
                            'name' => $object->getSolidaryUserStructure()->getStructure()->getName(),
                            'signature' => $object->getSolidaryUserStructure()->getStructure()->getSignature(),
                        ],
                        'journey' => $object->getProposal(),
                    ];

                    break;

                case SolidaryContact::class:
                    $structure = $recipient->getSolidaryUser()->getSolidaryUserStructures()[0]->getStructure();
                    $signature = [
                        'text' => $structure->getSignature(),
                        'logo' => count($structure->getImages()) > 0 ? $this->structureLogoUri.$structure->getImages()[0]->getFileName() : null,
                    ];
                    $titleContext = ['user' => $object->getSolidarySolution()->getSolidary()->getSolidaryUserStructure()->getSolidaryUser()->getUser()];
                    $bodyContext = ['text' => $object->getContent(), 'recipient' => $recipient, 'signature' => $signature];

                    break;

                case Mass::class:
                    $titleContext = ['massId' => $object->getId()];
                    $bodyContext = ['massId' => $object->getId(), 'errors' => $object->getErrors()];

                    break;

                case MassPerson::class:
                    $titleContext = [];
                    $bodyContext = ['user' => $recipient, 'clearPassword' => $object->getClearPassword()];

                    break;

                case CarpoolItem::class:
                    $titleContext = ['debtor' => $object->getDebtorUser()];
                    foreach ($object->getAsk()->getMatching()->getProposalRequest()->getWaypoints() as $waypoint) {
                        if (0 == $waypoint->getPosition()) {
                            $passengerOrigin = $waypoint->getAddress()->getAddressLocality();
                        } elseif (true == $waypoint->isDestination()) {
                            $passengerDestination = $waypoint->getAddress()->getAddressLocality();
                        }
                    }
                    // if regular we get the first day of the week
                    $firstDayOfWeek = null;
                    if (Criteria::FREQUENCY_REGULAR == $object->getAsk()->getCriteria()->getFrequency()) {
                        $day = new \DateTime($object->getItemDate()->format('d-m-Y'));
                        $day->setISODate((int) $day->format('o'), (int) $day->format('W'), 1);
                        $firstDayOfWeek = $day->format('l d F Y');
                    }

                    $date = null;
                    if (!is_null($object->getAsk()) && !is_null($object->getAsk()->getCriteria())) {
                        $date = \DateTime::createFromFormat('Y-m-d H:m', $object->getAsk()->getCriteria()->getFromDate()->format('Y-m-d').' '.is_null($object->getAsk()->getCriteria()->getFromTime()) ? '' : $object->getAsk()->getCriteria()->getFromTime()->format('H:m'));
                    }

                    $bodyContext = [
                        'debtor' => $object->getDebtorUser(),
                        'creditor' => $object->getCreditorUser(),
                        'amount' => $object->getAmount(),
                        'origin' => $passengerOrigin,
                        'destination' => $passengerDestination,
                        'week' => $firstDayOfWeek,
                        'date' => $date,
                    ];

                    break;

                case PaymentProfile::class:
                    $titleContext = [];
                    $bodyContext = ['paymentProfile' => $object];

                    break;

                case Review::class:
                    $titleContext = [];
                    $bodyContext = [
                        'givenName' => $object->getReviewer()->getGivenName(),
                        'shortFamilyName' => $object->getReviewer()->getShortFamilyName(),
                    ];

                    break;

                case Scammer::class:
                    $titleContext = [];
                    $bodyContext = ['scammer' => $object];

                    break;

                case Booking::class:
                    if ($recipient->getId() == $object->getPassenger()->getId()) {
                        $senderAlias = $object->getDriver()->getAlias();
                        $senderOperator = $object->getDriver()->getOperator();
                    } elseif ($recipient->getId() == $object->getDriver()->getId()) {
                        $senderAlias = $object->getPassenger()->getAlias();
                        $senderOperator = $object->getPassenger()->getOperator();
                    }
                    $titleContext = [];
                    $bodyContext = [
                        'booking' => $object,
                        'user' => $recipient,
                        'senderAlias' => $senderAlias,
                        'senderOperator' => $senderOperator,
                    ];

                    break;

                case TooLongInactivityLastWarningEvent::class:
                case TooLongInactivityFirstWarningEvent::class:
                    $titleContext = [];
                    $bodyContext = ['user' => $recipient, 'details' => $object, 'signature' => $signature];

                    break;

                case ExternalConnection::class:
                    $titleContext = [];
                    $bodyContext = ['user' => $recipient, 'externalConnection' => $object];

                    break;

                case LongDistanceSubscription::class:
                case ShortDistanceSubscription::class:
                    $bodyContext = [
                        'user' => $recipient,
                        'pre_verification_error' => [
                            'address' => !SubscriptionValidator::isAddressValid($object),
                            'drivingLicenceNumber' => !SubscriptionValidator::isDrivingLicenceNumberValid($object),
                            'phoneNumber' => !SubscriptionValidator::isPhoneNumberValid($object),
                        ],
                    ];

                    break;

                default:
                    if (isset($object->new, $object->old, $object->ask, $object->sender)) {
                        $outwardOrigin = null;
                        $outwardDestination = null;

                        /** @var Waypoint $waypoint */
                        foreach ($object->ask->getWaypoints() as $waypoint) {
                            if (0 == $waypoint->getPosition()) {
                                $outwardOrigin = $waypoint;
                            } elseif ($waypoint->isDestination()) {
                                $outwardDestination = $waypoint;
                            }
                        }
                        $bodyContext = [
                            'user' => $recipient,
                            'notification' => $notification,
                            'object' => $object,
                            'origin' => $outwardOrigin,
                            'destination' => $outwardDestination,
                        ];
                    }
            }
        } else {
            if (!is_null($recipient->getSolidaryUser())) {
                $structure = $recipient->getSolidaryUser()->getSolidaryUserStructures()[0]->getStructure();
                $signature = [
                    'text' => $structure->getSignature(),
                    'logo' => count($structure->getImages()) > 0 ? $this->structureLogoUri.$structure->getImages()[0]->getFileName() : null,
                ];
            }
            $bodyContext = ['user' => $recipient, 'notification' => $notification, 'signature' => $signature];
        }

        $lang = self::LANG;
        if (!is_null($recipient->getLanguage())) {
            $lang = $recipient->getLanguage();
            $this->translator->setLocale($lang->getCode());
            $templateLanguage = $lang->getCode();
        } else {
            $this->translator->setLocale($lang);
            $templateLanguage = $lang;
        }

        if ($notification->hasAlt()) {
            $titleTemplate = $this->altCommunicationFolder.$templateLanguage.$this->emailTitleTemplatePath.$notification->getAction()->getName().'.html.twig';
        } else {
            $titleTemplate = $this->communicationFolder.$templateLanguage.$this->emailTitleTemplatePath.$notification->getAction()->getName().'.html.twig';
        }
        $email->setObject($this->templating->render(
            $titleTemplate,
            [
                'context' => $titleContext,
            ]
        ));

        // if a template is associated with the action in the notification, we us it; otherwise we try the name of the action as template name
        if ($notification->hasAlt()) {
            $this->emailManager->send($email, $this->altCommunicationFolder.$templateLanguage.$this->emailTemplatePath.$notification->getAction()->getName(), $bodyContext, $lang);
        } else {
            $this->emailManager->send($email, $this->communicationFolder.$templateLanguage.$this->emailTemplatePath.$notification->getAction()->getName(), $bodyContext, $lang);
        }
    }

    /**
     * Notify a user by sms.
     * Different variables can be passed to the notification body and title depending on the object linked to the notification.
     */
    private function notifyBySms(Notification $notification, User $recipient, ?object $object = null)
    {
        if (is_null($recipient->getTelephone())) {
            return;
        }
        if (!$notification->isPermissive() && is_null($recipient->getPhoneValidatedDate()) && is_null($recipient->getPhoneToken())) {
            return;
        }
        $sms = new Sms();
        if (!is_null($recipient->getPhoneCode())) {
            $sms->setRecipientTelephone('+'.$recipient->getPhoneCode().ltrim($recipient->getTelephone(), 0));
        } else {
            $sms->setRecipientTelephone($recipient->getTelephone());
        }
        $bodyContext = [];
        if ($object) {
            switch (get_class($object)) {
                case Proposal::class:
                    $bodyContext = ['user' => $recipient, 'notification' => $notification, 'object' => $object];

                    break;

                case Matching::class:
                    $bodyContext = ['user' => $recipient, 'notification' => $notification, 'matching' => $object];

                    break;

                case AskHistory::class:
                    $bodyContext = ['user' => $recipient];

                    break;

                case Ask::class:
                    foreach ($object->getMatching()->getProposalRequest()->getWaypoints() as $waypoint) {
                        if (0 == $waypoint->getPosition()) {
                            $passengerOriginWaypoint = $waypoint;
                        } elseif (true == $waypoint->isDestination()) {
                            $passengerDestinationWaypoint = $waypoint;
                        }
                    }
                    $bodyContext = ['user' => $recipient, 'ask' => $object, 'origin' => $passengerOriginWaypoint, 'destination' => $passengerDestinationWaypoint];

                    break;

                case Ad::class:
                    $outwardOrigin = null;
                    $outwardDestination = null;
                    $returnOrigin = null;
                    $returnDestination = null;
                    $sender = $this->userManager->getUser($object->getUserId()) == $recipient ? $object->getResults()[0]->getCarpooler() : $this->userManager->getUser($object->getUserId());
                    if (null !== $object->getResults()[0]->getResultPassenger()) {
                        $result = $object->getResults()[0]->getResultPassenger();
                    } else {
                        $result = $object->getResults()[0]->getResultDriver();
                    }
                    if (null !== $result->getOutward()) {
                        foreach ($result->getOutward()->getWaypoints() as $waypoint) {
                            if ('passenger' == $waypoint['role'] && 'origin' == $waypoint['type']) {
                                $outwardOrigin = $waypoint;
                            } elseif ('passenger' == $waypoint['role'] && 'destination' == $waypoint['type']) {
                                $outwardDestination = $waypoint;
                            }
                        }
                    }
                    if (null !== $result->getReturn()) {
                        foreach ($result->getReturn()->getWaypoints() as $waypoint) {
                            if ('passenger' == $waypoint['role'] && 'origin' == $waypoint['type']) {
                                $returnOrigin = $waypoint;
                            } elseif ('passenger' == $waypoint['role'] && 'destination' == $waypoint['type']) {
                                $returnDestination = $waypoint;
                            }
                        }
                    }
                    $bodyContext = [
                        'user' => $recipient,
                        'ad' => $object,
                        'sender' => $sender,
                        'result' => $result,
                        'outwardOrigin' => $outwardOrigin,
                        'outwardDestination' => $outwardDestination,
                        'returnOrigin' => $returnOrigin,
                        'returnDestination' => $returnDestination,
                    ];

                    break;

                case Recipient::class:
                    $bodyContext = [];

                    break;

                case User::class:
                    $bodyContext = ['user' => $recipient];

                    break;

                case Message::class:
                    $bodyContext = ['text' => $object->getText(), 'user' => $recipient];

                    break;

                case SolidaryContact::class:
                    $bodyContext = ['text' => $object->getContent(), 'recipient' => $recipient];

                    break;

                case CarpoolItem::class:
                    foreach ($object->getAsk()->getMatching()->getProposalRequest()->getWaypoints() as $waypoint) {
                        if (0 == $waypoint->getPosition()) {
                            $passengerOrigin = $waypoint->getAddress()->getAddressLocality();
                        } elseif (true == $waypoint->isDestination()) {
                            $passengerDestination = $waypoint->getAddress()->getAddressLocality();
                        }
                    }
                    $firstDayOfWeek = null;
                    if (Criteria::FREQUENCY_REGULAR == $object->getAsk()->getCriteria()->getFrequency()) {
                        $day = new \DateTime($object->getItemDate()->format('d-m-Y'));
                        $day->setISODate((int) $day->format('o'), (int) $day->format('W'), 1);
                        $firstDayOfWeek = $day->format('l d F Y');
                    }
                    $bodyContext = [
                        'debtor' => $object->getDebtorUser(),
                        'creditor' => $object->getCreditorUser(),
                        'amount' => $object->getAmount(),
                        'origin' => $passengerOrigin,
                        'destination' => $passengerDestination,
                        'week' => $firstDayOfWeek,
                    ];

                    break;

                case PaymentProfile::class:
                    $bodyContext = ['paymentProfile' => $object];

                    break;

                case Review::class:
                    $bodyContext = [
                        'givenName' => $object->getReviewer()->getGivenName(),
                        'shortFamilyName' => $object->getReviewer()->getShortFamilyName(),
                    ];

                    break;

                case Booking::class:
                    if ($recipient->getId() == $object->getPassenger()->getId()) {
                        $senderAlias = $object->getDriver()->getAlias();
                        $senderOperator = $object->getDriver()->getOperator();
                    } elseif ($recipient->getId() == $object->getDriver()->getId()) {
                        $senderAlias = $object->getPassenger()->getAlias();
                        $senderOperator = $object->getPassenger()->getOperator();
                    }
                    $bodyContext = [
                        'booking' => $object,
                        'user' => $recipient,
                        'senderAlias' => $senderAlias,
                        'senderOperator' => $senderOperator,
                    ];

                    break;

                default:
                    if (isset($object->new, $object->old, $object->ask, $object->sender)) {
                        $outwardOrigin = null;
                        $outwardDestination = null;

                        /** @var Ask $ask */
                        $ask = $object->ask;
                        if ($recipient->getId() == $ask->getMatching()->getProposalOffer()->getUser()->getId()) {
                            // The recipient is the driver, we take the waypoints of the ProposalOffer
                            $waypoints = $ask->getMatching()->getProposalOffer()->getWaypoints();
                        } else {
                            // The recipient is the passenger, we take the waypoints of ProposalRequest
                            $waypoints = $ask->getMatching()->getProposalRequest()->getWaypoints();
                        }

                        foreach ($waypoints as $waypoint) {
                            if (0 == $waypoint->getPosition()) {
                                $outwardOrigin = $waypoint;
                            } elseif ($waypoint->isDestination()) {
                                $outwardDestination = $waypoint;
                            }
                        }

                        $bodyContext = [
                            'user' => $recipient,
                            'notification' => $notification,
                            'object' => $object,
                            'origin' => $outwardOrigin,
                            'destination' => $outwardDestination,
                        ];
                    }
            }
        } else {
            $bodyContext = ['user' => $recipient, 'notification' => $notification];
        }

        $lang = self::LANG;
        if (!is_null($recipient->getLanguage())) {
            $lang = $recipient->getLanguage();
            $this->translator->setLocale($lang->getCode());
            $templateLanguage = $lang->getCode();
        } else {
            $this->translator->setLocale($lang);
            $templateLanguage = $lang;
        }
        // if a template is associated with the action in the notification, we us it; otherwise we try the name of the action as template name
        if ($notification->hasAlt()) {
            $response = $this->smsManager->send($sms, $this->altCommunicationFolder.$templateLanguage.$this->smsTemplatePath.$notification->getAction()->getName(), $bodyContext, $lang);
        } else {
            $response = $this->smsManager->send($sms, $this->communicationFolder.$templateLanguage.$this->smsTemplatePath.$notification->getAction()->getName(), $bodyContext, $lang);
        }

        // ? #4705- Log creation when SMS is not send
        if (!$this->checkSmsSending($response)) {
            $this->logger->error('Sms notification to '.$recipient->getId().' for the '.$notification->getAction()->getName().' has failed');
        }

        $this->createNotified($notification, $recipient, $object);

        $this->logger->info('Sms notification for '.$notification->getAction()->getName().' / '.$recipient->getEmail());
    }

    private function checkSmsSending(Response $response): bool
    {
        return in_array($response->getCode(), [200, 201, 204]);
    }

    /**
     * Notify a user by push notification.
     * Different variables can be passed to the notification body and title depending on the object linked to the notification.
     *
     * @param Notification $notification The notification
     * @param User         $recipient    The recipient user
     * @param null|object  $object       The object to use
     */
    private function notifyByPush(Notification $notification, User $recipient, ?object $object = null)
    {
        $push = new Push();
        $recipientDeviceIds = [];
        foreach ($recipient->getPushTokens() as $pushToken) {
            // @var PushToken $pushToken
            $recipientDeviceIds[] = $pushToken->getToken();
        }
        // we check if the recipient has at least one push token id
        if (0 == count($recipientDeviceIds)) {
            return;
        }
        $push->setRecipientDeviceIds($recipientDeviceIds);
        $bodyContext = [];
        if ($object) {
            switch (get_class($object)) {
                case Proposal::class:
                    $titleContext = [];
                    $bodyContext = ['user' => $recipient, 'notification' => $notification, 'object' => $object];

                    break;

                case Matching::class:
                    $titleContext = [];
                    $bodyContext = ['user' => $recipient, 'notification' => $notification, 'matching' => $object];

                    break;

                case AskHistory::class:
                    $titleContext = [];
                    $bodyContext = ['user' => $recipient];

                    break;

                case Ask::class:
                    $titleContext = [];
                    foreach ($object->getMatching()->getProposalRequest()->getWaypoints() as $waypoint) {
                        if (0 == $waypoint->getPosition()) {
                            $passengerOriginWaypoint = $waypoint;
                        } elseif (true == $waypoint->isDestination()) {
                            $passengerDestinationWaypoint = $waypoint;
                        }
                    }
                    $bodyContext = ['user' => $recipient, 'ask' => $object, 'origin' => $passengerOriginWaypoint, 'destination' => $passengerDestinationWaypoint];

                    break;

                case Ad::class:
                    $titleContext = [];
                    $outwardOrigin = null;
                    $outwardDestination = null;
                    $returnOrigin = null;
                    $returnDestination = null;
                    $sender = $this->userManager->getUser($object->getUserId());
                    if (null !== $object->getResults()[0]->getResultPassenger()) {
                        $result = $object->getResults()[0]->getResultPassenger();
                    } else {
                        $result = $object->getResults()[0]->getResultDriver();
                    }
                    if (null !== $result->getOutward()) {
                        foreach ($result->getOutward()->getWaypoints() as $waypoint) {
                            if ('passenger' == $waypoint['role'] && 'origin' == $waypoint['type']) {
                                $outwardOrigin = $waypoint;
                            } elseif ('passenger' == $waypoint['role'] && 'destination' == $waypoint['type']) {
                                $outwardDestination = $waypoint;
                            }
                        }
                    }
                    if (null !== $result->getReturn()) {
                        foreach ($result->getReturn()->getWaypoints() as $waypoint) {
                            if ('passenger' == $waypoint['role'] && 'origin' == $waypoint['type']) {
                                $returnOrigin = $waypoint;
                            } elseif ('passenger' == $waypoint['role'] && 'destination' == $waypoint['type']) {
                                $returnDestination = $waypoint;
                            }
                        }
                    }
                    $bodyContext = [
                        'user' => $recipient,
                        'ad' => $object,
                        'sender' => $sender,
                        'result' => $result,
                        'outwardOrigin' => $outwardOrigin,
                        'outwardDestination' => $outwardDestination,
                        'returnOrigin' => $returnOrigin,
                        'returnDestination' => $returnDestination,
                    ];

                    break;

                case Recipient::class:
                    $titleContext = [];
                    $bodyContext = [];

                    break;

                case User::class:
                    $titleContext = [];
                    $bodyContext = ['user' => $recipient];

                    break;

                case Message::class:
                    $titleContext = ['user' => $object->getUser()];
                    $bodyContext = ['text' => $object->getText(), 'user' => $recipient];

                    break;

                case CarpoolItem::class:
                    $titleContext = ['debtor' => $object->getDebtorUser()];
                    foreach ($object->getAsk()->getMatching()->getProposalRequest()->getWaypoints() as $waypoint) {
                        if (0 == $waypoint->getPosition()) {
                            $passengerOrigin = $waypoint->getAddress()->getAddressLocality();
                        } elseif (true == $waypoint->isDestination()) {
                            $passengerDestination = $waypoint->getAddress()->getAddressLocality();
                        }
                    }
                    $firstDayOfWeek = null;
                    if (Criteria::FREQUENCY_REGULAR == $object->getAsk()->getCriteria()->getFrequency()) {
                        $day = new \DateTime($object->getItemDate()->format('d-m-Y'));
                        $day->setISODate((int) $day->format('o'), (int) $day->format('W'), 1);
                        $firstDayOfWeek = $day->format('l d F Y');
                    }
                    $bodyContext = [
                        'debtor' => $object->getDebtorUser(),
                        'creditor' => $object->getCreditorUser(),
                        'amount' => $object->getAmount(),
                        'origin' => $passengerOrigin,
                        'destination' => $passengerDestination,
                        'week' => $firstDayOfWeek,
                    ];

                    break;

                case PaymentProfile::class:
                    $titleContext = [];
                    $bodyContext = ['paymentProfile' => $object];

                    break;

                case Review::class:
                    $titleContext = [];
                    $bodyContext = [
                        'givenName' => $object->getReviewer()->getGivenName(),
                        'shortFamilyName' => $object->getReviewer()->getShortFamilyName(),
                    ];

                    break;

                case Booking::class:
                    if ($recipient->getId() == $object->getPassenger()->getId()) {
                        $senderAlias = $object->getDriver()->getAlias();
                        $senderOperator = $object->getDriver()->getOperator();
                    } elseif ($recipient->getId() == $object->getDriver()->getId()) {
                        $senderAlias = $object->getPassenger()->getAlias();
                        $senderOperator = $object->getPassenger()->getOperator();
                    }
                    $titleContext = [];
                    $bodyContext = [
                        'booking' => $object,
                        'user' => $recipient,
                        'senderAlias' => $senderAlias,
                        'senderOperator' => $senderOperator,
                    ];

                    break;

                case CarpoolProof::class:
                    $titleContext = [];
                    $bodyContext = ['user' => $recipient, 'notification' => $notification, 'object' => $object];

                    break;

                default:
                    $titleContext = [];
                    if (isset($object->new, $object->old, $object->ask, $object->sender)) {
                        $outwardOrigin = null;
                        $outwardDestination = null;

                        /** @var Waypoint $waypoint */
                        foreach ($object->ask->getWaypoints() as $waypoint) {
                            if (0 == $waypoint->getPosition()) {
                                $outwardOrigin = $waypoint;
                            } elseif ($waypoint->isDestination()) {
                                $outwardDestination = $waypoint;
                            }
                        }
                        $bodyContext = [
                            'user' => $recipient,
                            'notification' => $notification,
                            'object' => $object,
                            'origin' => $outwardOrigin,
                            'destination' => $outwardDestination,
                        ];
                    }
            }
        } else {
            $titleContext = [];
            $bodyContext = ['user' => $recipient, 'notification' => $notification];
        }

        $lang = self::LANG;
        if (!is_null($recipient->getLanguage())) {
            $lang = $recipient->getLanguage();
            $this->translator->setLocale($lang->getCode());
            $templateLanguage = $lang->getCode();
        } else {
            $this->translator->setLocale($lang);
            $templateLanguage = $lang;
        }

        if ($notification->hasAlt()) {
            $titleTemplate = $this->altCommunicationFolder.$templateLanguage.$this->pushTitleTemplatePath.$notification->getAction()->getName().'.html.twig';
        } else {
            $titleTemplate = $this->communicationFolder.$templateLanguage.$this->pushTitleTemplatePath.$notification->getAction()->getName().'.html.twig';
        }
        $push->setTitle($this->templating->render(
            $titleTemplate,
            [
                'context' => $titleContext,
            ]
        ));

        // if a template is associated with the action in the notification, we us it; otherwise we try the name of the action as template name
        if ($notification->hasAlt()) {
            $this->pushManager->send($push, $this->altCommunicationFolder.$templateLanguage.$this->pushTemplatePath.$notification->getAction()->getName(), $bodyContext, $lang);
        } else {
            $this->pushManager->send($push, $this->communicationFolder.$templateLanguage.$this->pushTemplatePath.$notification->getAction()->getName(), $bodyContext, $lang);
        }
    }
}