Covivo/mobicoop

View on GitHub
api/src/User/Service/UserManager.php

Summary

Maintainability
D
3 days
Test Coverage
<?php

/**
 * Copyright (c) 2020, 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\User\Service;

use App\Action\Event\ActionEvent;
use App\Action\Repository\ActionRepository;
use App\App\Entity\App;
use App\Auth\Entity\AuthItem;
use App\Auth\Entity\UserAuthAssignment;
use App\Auth\Repository\AuthItemRepository;
use App\Carpool\Entity\Ask;
use App\Carpool\Repository\AskHistoryRepository;
use App\Carpool\Repository\AskRepository;
use App\Carpool\Service\ProofManager;
use App\CarpoolStandard\Service\BookingManager;
use App\Communication\Entity\Medium;
use App\Communication\Entity\Message;
use App\Communication\Repository\MessageRepository;
use App\Communication\Repository\NotificationRepository;
use App\Communication\Service\InternalMessageManager;
use App\Community\Entity\CommunityUser;
use App\Community\Repository\CommunityRepository;
use App\Community\Repository\CommunityUserRepository;
use App\Gamification\Service\GamificationManager;
use App\Geography\Service\GeoTools;
use App\I18n\Repository\LanguageRepository;
use App\Image\Service\ImageManager;
use App\Payment\Repository\PaymentProfileRepository;
use App\Payment\Ressource\BankAccount;
use App\Payment\Service\PaymentDataProvider;
use App\Scammer\Repository\ScammerRepository;
use App\Service\DrivingLicenceService;
use App\Solidary\Entity\Operate;
use App\Solidary\Entity\SolidaryAsk;
use App\Solidary\Entity\SolidaryUser;
use App\Solidary\Entity\Structure;
use App\Solidary\Exception\SolidaryException;
use App\Solidary\Repository\SolidaryAskHistoryRepository;
use App\Solidary\Repository\SolidaryAskRepository;
use App\Solidary\Repository\SolidaryRepository;
use App\Solidary\Repository\StructureRepository;
use App\User\Entity\AuthenticationDelegation;
use App\User\Entity\SsoAccount;
use App\User\Entity\SsoUser;
use App\User\Entity\User;
use App\User\Entity\UserNotification;
use App\User\Event\AskParentalConsentEvent;
use App\User\Event\SsoAssociationEvent;
use App\User\Event\SsoAuthenticationEvent;
use App\User\Event\UserDelegateRegisteredEvent;
use App\User\Event\UserDelegateRegisteredPasswordSendEvent;
use App\User\Event\UserDeleteAccountWasDriverEvent;
use App\User\Event\UserDeleteAccountWasPassengerEvent;
use App\User\Event\UserDrivingLicenceNumberUpdateEvent;
use App\User\Event\UserGeneratePhoneTokenAskedEvent;
use App\User\Event\UserHomeAddressUpdateEvent;
use App\User\Event\UserPasswordChangeAskedEvent;
use App\User\Event\UserPasswordChangedEvent;
use App\User\Event\UserPhoneUpdateEvent;
use App\User\Event\UserRegisteredEvent;
use App\User\Event\UserSendValidationEmailEvent;
use App\User\Event\UserUpdatedSelfEvent;
use App\User\Exception\UserException;
use App\User\Exception\UserNotFoundException;
use App\User\Exception\UserUnderAgeException;
use App\User\Repository\SsoAccountRepository;
use App\User\Repository\UserNotificationRepository;
use App\User\Repository\UserRepository;
use App\User\Ressource\ProfileSummary;
use App\User\Ressource\PublicProfile;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * User manager service.
 *
 * @author Sylvain Briat <sylvain.briat@mobicoop.org>
 * @author Remi Wortemann <remi.wortemann@mobicoop.org>
 */
class UserManager
{
    private $entityManager;
    private $imageManager;
    private $authItemRepository;
    private $communityRepository;
    private $communityUserRepository;
    private $messageRepository;
    private $askRepository;
    private $askHistoryRepository;
    private $notificationRepository;
    private $userNotificationRepository;
    private $userRepository;
    private $proofManager;
    private $solidaryRepository;
    private $solidaryAskRepository;
    private $solidaryAskHistoryRepository;
    private $structureRepository;
    private $logger;
    private $eventDispatcher;
    private $encoder;
    private $translator;
    private $security;
    private $paymentProvider;
    private $blockManager;
    private $internalMessageManager;
    private $reviewManager;
    private $paymentActive;
    private $languageRepository;
    private $actionRepository;
    private $gamificationManager;
    private $scammerRepository;
    private $userMinAge;
    private $paymentProfileRepository;
    private $bookingManager;
    private $carpoolStandardEnabled;
    private $ssoAccountRepository;

    // Default carpool settings
    private $chat;
    private $music;
    private $smoke;

    private $fakeFirstMail;
    private $fakeFirstToken;
    private $domains;
    private $profile;
    private $passwordTokenValidity;

    private $geoTools;

    private $authorizedReferrals;

    /**
     * @var PseudonymizationManager
     */
    private $_pseudonymizationManager;

    /**
     * Constructor.
     *
     * @param mixed $chat
     * @param mixed $smoke
     * @param mixed $music
     * @param mixed $passwordTokenValidity
     * @param mixed $userMinAge
     */
    public function __construct(
        EntityManagerInterface $entityManager,
        ImageManager $imageManager,
        LoggerInterface $logger,
        EventDispatcherInterface $dispatcher,
        AuthItemRepository $authItemRepository,
        CommunityRepository $communityRepository,
        MessageRepository $messageRepository,
        UserPasswordEncoderInterface $encoder,
        NotificationRepository $notificationRepository,
        UserNotificationRepository $userNotificationRepository,
        AskHistoryRepository $askHistoryRepository,
        AskRepository $askRepository,
        UserRepository $userRepository,
        ProofManager $proofManager,
        $chat,
        $smoke,
        $music,
        CommunityUserRepository $communityUserRepository,
        TranslatorInterface $translator,
        Security $security,
        SolidaryRepository $solidaryRepository,
        SolidaryAskRepository $solidaryAskRepository,
        SolidaryAskHistoryRepository $solidaryAskHistoryRepository,
        StructureRepository $structureRepository,
        string $fakeFirstMail,
        string $fakeFirstToken,
        PaymentDataProvider $paymentProvider,
        BlockManager $blockManager,
        InternalMessageManager $internalMessageManager,
        ReviewManager $reviewManager,
        array $domains,
        array $profile,
        $passwordTokenValidity,
        string $paymentActive,
        PaymentProfileRepository $paymentProfileRepository,
        GeoTools $geoTools,
        LanguageRepository $languageRepository,
        ActionRepository $actionRepository,
        GamificationManager $gamificationManager,
        ScammerRepository $scammerRepository,
        PseudonymizationManager $pseudonymizationManager,
        $userMinAge,
        BookingManager $bookingManager,
        bool $carpoolStandardEnabled,
        SsoAccountRepository $ssoAccountRepository,
        array $authorizedReferrals
    ) {
        $this->entityManager = $entityManager;
        $this->imageManager = $imageManager;
        $this->logger = $logger;
        $this->authItemRepository = $authItemRepository;
        $this->communityRepository = $communityRepository;
        $this->communityUserRepository = $communityUserRepository;
        $this->messageRepository = $messageRepository;
        $this->askRepository = $askRepository;
        $this->askHistoryRepository = $askHistoryRepository;
        $this->eventDispatcher = $dispatcher;
        $this->encoder = $encoder;
        $this->translator = $translator;
        $this->security = $security;
        $this->notificationRepository = $notificationRepository;
        $this->userNotificationRepository = $userNotificationRepository;
        $this->userRepository = $userRepository;
        $this->proofManager = $proofManager;
        $this->solidaryRepository = $solidaryRepository;
        $this->solidaryAskRepository = $solidaryAskRepository;
        $this->solidaryAskHistoryRepository = $solidaryAskHistoryRepository;
        $this->structureRepository = $structureRepository;
        $this->chat = $chat;
        $this->music = $music;
        $this->smoke = $smoke;
        $this->fakeFirstMail = $fakeFirstMail;
        $this->fakeFirstToken = $fakeFirstToken;
        $this->domains = $domains;
        $this->paymentProvider = $paymentProvider;
        $this->blockManager = $blockManager;
        $this->internalMessageManager = $internalMessageManager;
        $this->reviewManager = $reviewManager;
        $this->profile = $profile;
        $this->passwordTokenValidity = $passwordTokenValidity;
        $this->paymentProfileRepository = $paymentProfileRepository;
        $this->paymentActive = $paymentActive;
        $this->geoTools = $geoTools;
        $this->languageRepository = $languageRepository;
        $this->actionRepository = $actionRepository;
        $this->gamificationManager = $gamificationManager;
        $this->scammerRepository = $scammerRepository;
        $this->_pseudonymizationManager = $pseudonymizationManager;
        $this->userMinAge = $userMinAge;
        $this->bookingManager = $bookingManager;
        $this->carpoolStandardEnabled = $carpoolStandardEnabled;
        $this->ssoAccountRepository = $ssoAccountRepository;
        $this->authorizedReferrals = $authorizedReferrals;
    }

    /**
     * Get a user by its id.
     *
     * @return null|User
     */
    public function getUser(int $id)
    {
        $user = $this->userRepository->find($id);
        if ($user) {
            $user = $this->getUnreadMessageNumber($user);
        }

        return $user;
    }

    /**
     * Get a user by its email.
     *
     * @param string $email The email to find
     *
     * @return null|User The user found
     */
    public function getUserByEmail(string $email)
    {
        return $this->userRepository->findOneBy(['email' => $email]);
    }

    /**
     * Check if an email is already used by someone; returns a code.
     *
     * @param string $email The email to check
     *
     * @return string The code
     */
    public function checkEmail(string $email)
    {
        // Email already exist in db
        if ($this->userRepository->findOneBy(['email' => $email])) {
            return 'email-exist';
        }

        foreach ($this->domains as $name => $domain) {
            if (explode('@', $email)[1] == $domain) {
                return 'authorized';
            }
        }

        return implode(', ', $this->domains);
    }

    /**
     * Check if a password token and password token date exist.
     *
     * @param string $pwdToken The password token to check
     *
     * @return null|string The checked token or null if token invalid
     */
    public function checkPasswordToken(string $pwdToken)
    {
        if ($user = $this->userRepository->findOneBy(['pwdToken' => $pwdToken])) {
            if ((time() - (int) $user->getPwdTokenDate()->getTimestamp()) > $this->passwordTokenValidity) {
                return null;
            }

            return $pwdToken;
        }

        return null;
    }

    /**
     * Get a user by security token.
     *
     * @return null|User
     */
    public function getMe()
    {
        $user = $this->userRepository->findOneBy(['email' => $this->security->getUser()->getUsername()]);

        return $this->getUnreadMessageNumber($user);
    }

    /**
     * Registers a user.
     *
     * @param User $user           The user to register
     * @param bool $encodePassword True to encode password
     *
     * @return User The user created
     */
    public function registerUser(User $user, bool $encodePassword = true, bool $isSolidary = false, ?SsoUser $ssoUser = null)
    {
        if (!is_null($user->getReferral()) && '' !== trim($user->getReferral())) {
            if (!in_array($user->getReferral(), $this->authorizedReferrals)) {
                $user->setReferral(null);
            }
        }

        // we check if the user is on the scammer list
        $this->checkIfScammer($user);
        //  we check if the user is not underaged
        $this->checkBirthDate($user);
        // we check if the user has an email
        if (is_null($user->getEmail())) {
            throw new UserException(UserException::EMAIL_IS_MANDATORY);
        }

        $user = $this->prepareUser($user, $encodePassword);

        // persist the user
        $this->entityManager->persist($user);
        $this->entityManager->flush();

        if (!is_null($ssoUser)) {
            $this->updateUserSsoProperties($user, $ssoUser);
        }

        // creation of the alert preferences
        $user = $this->createAlerts($user);

        if (!$isSolidary) {
            // dispatch en event
            if (is_null($user->getUserDelegate())) {
                // registration by the user itself
                $event = new UserRegisteredEvent($user);
                $this->eventDispatcher->dispatch(UserRegisteredEvent::NAME, $event);
            } else {
                // delegate registration
                $event = new UserDelegateRegisteredEvent($user);
                $this->eventDispatcher->dispatch(UserDelegateRegisteredEvent::NAME, $event);
                // send password ?
                if (User::PWD_SEND_TYPE_SMS == $user->getPasswordSendType()) {
                    $event = new UserDelegateRegisteredPasswordSendEvent($user);
                    $this->eventDispatcher->dispatch(UserDelegateRegisteredPasswordSendEvent::NAME, $event);
                }
            }
        }

        if (!is_null($user->getLegalGuardianEmail())) {
            $event = new AskParentalConsentEvent($user);
            $this->eventDispatcher->dispatch(AskParentalConsentEvent::NAME, $event);
        }

        //  we dispatch the gamification event associated
        if ($user->getHomeAddress()) {
            $action = $this->actionRepository->findOneBy(['name' => 'user_home_address_updated']);
            $actionEvent = new ActionEvent($action, $user);
            $this->eventDispatcher->dispatch($actionEvent, ActionEvent::NAME);
        }

        if (!is_null($user->getCommunityId())) {
            $communityUser = new CommunityUser();
            $communityUser->setUser($user);
            $communityUser->setCommunity($this->communityRepository->find($user->getCommunityId()));
            $communityUser->setStatus(CommunityUser::STATUS_ACCEPTED_AS_MEMBER);
            $this->entityManager->persist($communityUser);
            $this->entityManager->flush();
        }

        // return the user
        return $user;
    }

    /**
     * Set the default availabilities of a SolidaryUser
     * If no availabilitie already given, we take the structure default
     * For the days check, if there is no indication, we consider the user available.
     *
     * @param SolidaryUser $solidaryUser The SolidaryUser
     * @param Structure    $structure    The Structure (if there is no Structure, we take the admin's one)
     */
    public function setDefaultSolidaryUserAvailabilities(SolidaryUser $solidaryUser, ?Structure $structure = null): SolidaryUser
    {
        $solidaryUserstructure = null;
        if (!is_null($structure)) {
            // A structure is given. We're looking for the solidaryUserStructure between this structure and the SolidaryUser
            $solidaryUserstructures = $solidaryUser->getSolidaryUserStructures();
            foreach ($solidaryUserstructures as $currentSolidaryUserstructure) {
                if ($currentSolidaryUserstructure->getStructure()->getId() == $structure->getId()) {
                    $solidaryUserstructure = $currentSolidaryUserstructure;

                    break;
                }
            }
        } else {
            // No structure given. We take the admin's one
            $structures = $this->security->getUser()->getSolidaryStructures();
            if (is_array($structures) && isset($structures[0])) {
                $solidaryUserstructure = $structures[0];
            }
        }

        if (is_null($solidaryUserstructure)) {
            throw new SolidaryException(SolidaryException::NO_STRUCTURE);
        }

        // Times
        if ('' == $solidaryUser->getMMinTime()) {
            $solidaryUser->setMMinTime($solidaryUserstructure->getStructure()->getMMinTime());
        }
        if ('' == $solidaryUser->getMMaxTime()) {
            $solidaryUser->setMMaxTime($solidaryUserstructure->getStructure()->getMMaxTime());
        }
        if ('' == $solidaryUser->getAMinTime()) {
            $solidaryUser->setAMinTime($solidaryUserstructure->getStructure()->getAMinTime());
        }
        if ('' == $solidaryUser->getAMaxTime()) {
            $solidaryUser->setAMaxTime($solidaryUserstructure->getStructure()->getAMaxTime());
        }
        if ('' == $solidaryUser->getEMinTime()) {
            $solidaryUser->setEMinTime($solidaryUserstructure->getStructure()->getEMinTime());
        }
        if ('' == $solidaryUser->getEMaxTime()) {
            $solidaryUser->setEMaxTime($solidaryUserstructure->getStructure()->getEMaxTime());
        }

        // Days
        if (false !== $solidaryUser->hasMMon()) {
            $solidaryUser->setMMon(true);
        }
        if (false !== $solidaryUser->hasAMon()) {
            $solidaryUser->setAMon(true);
        }
        if (false !== $solidaryUser->hasEMon()) {
            $solidaryUser->setEMon(true);
        }
        if (false !== $solidaryUser->hasMTue()) {
            $solidaryUser->setMTue(true);
        }
        if (false !== $solidaryUser->hasATue()) {
            $solidaryUser->setATue(true);
        }
        if (false !== $solidaryUser->hasETue()) {
            $solidaryUser->setETue(true);
        }
        if (false !== $solidaryUser->hasMWed()) {
            $solidaryUser->setMWed(true);
        }
        if (false !== $solidaryUser->hasAWed()) {
            $solidaryUser->setAWed(true);
        }
        if (false !== $solidaryUser->hasEWed()) {
            $solidaryUser->setEWed(true);
        }
        if (false !== $solidaryUser->hasMThu()) {
            $solidaryUser->setMThu(true);
        }
        if (false !== $solidaryUser->hasAThu()) {
            $solidaryUser->setAThu(true);
        }
        if (false !== $solidaryUser->hasEThu()) {
            $solidaryUser->setEThu(true);
        }
        if (false !== $solidaryUser->hasMFri()) {
            $solidaryUser->setMFri(true);
        }
        if (false !== $solidaryUser->hasAFri()) {
            $solidaryUser->setAFri(true);
        }
        if (false !== $solidaryUser->hasEFri()) {
            $solidaryUser->setEFri(true);
        }
        if (false !== $solidaryUser->hasMSat()) {
            $solidaryUser->setMSat(true);
        }
        if (false !== $solidaryUser->hasASat()) {
            $solidaryUser->setASat(true);
        }
        if (false !== $solidaryUser->hasESat()) {
            $solidaryUser->setESat(true);
        }
        if (false !== $solidaryUser->hasMSun()) {
            $solidaryUser->setMSun(true);
        }
        if (false !== $solidaryUser->hasASun()) {
            $solidaryUser->setASun(true);
        }
        if (false !== $solidaryUser->hasESun()) {
            $solidaryUser->setESun(true);
        }

        return $solidaryUser;
    }

    /**
     * Prepare a user for registration : set default values.
     *
     * @param User $user           The user to prepare
     * @param bool $encodePassword True to encode password
     *
     * @return User The prepared user
     */
    public function prepareUser(User $user, bool $encodePassword = false)
    {
        // We add the default roles we set in User Entity
        $authItem = $this->authItemRepository->find(User::ROLE_DEFAULT);
        $userAuthAssignment = new UserAuthAssignment();
        $userAuthAssignment->setAuthItem($authItem);
        $user->addUserAuthAssignment($userAuthAssignment);

        // No password given, we generate one
        if (is_null($user->getPassword())) {
            $user->setPassword($this->randomPassword());
        }

        if ($encodePassword) {
            $user->setClearPassword($user->getPassword());
            $user->setPassword($this->encoder->encodePassword($user, $user->getPassword()));
        }

        // default phone display : restricted
        $user->setPhoneDisplay(User::PHONE_DISPLAY_RESTRICTED);

        // Default carpool settings
        $user->setChat($this->chat);
        $user->setMusic($this->music);
        $user->setSmoke($this->smoke);

        // Create token to validate inscription
        $user->setEmailToken($this->createShortToken());

        // Create token to unscubscribe from the instance news
        $user->setUnsubscribeToken($this->createToken($user));

        if (!is_null($user->getLegalGuardianEmail())) {
            $user->setParentalConsentToken($this->createShortToken());
            $user->setParentalConsentUuid($this->_generateUuid());
        }

        // return the user
        return $user;
    }

    /**
     * Add an auth item to a user.
     *
     * @param User $user       The user
     * @param int  $authItemId The auth item id
     *
     * @return User The user with the new auth item added
     */
    public function addAuthItem(User $user, int $authItemId)
    {
        if ($authItem = $this->authItemRepository->find($authItemId)) {
            $userAuthAssignment = new UserAuthAssignment();
            $userAuthAssignment->setAuthItem($authItem);
            $user->addUserAuthAssignment($userAuthAssignment);
        }

        return $user;
    }

    /**
     * Update a user.
     *
     * @param User $user The user to update
     *
     * @return User The user updated
     */
    public function updateUser(User $user)
    {
        // activate sms notification if phone validated
        if ($user->getPhoneValidatedDate()) {
            $user = $this->activateSmsNotification($user);
        }

        $phoneUpdate = false;
        // check if the phone is updated and if so reset phoneToken and validatedDate
        if ($user->getTelephone() != $user->getOldTelephone()) {
            $user->setPhoneToken(null);
            $user->setPhoneValidatedDate(null);
            // deactivate sms notification since the phone is new
            $user = $this->deActivateSmsNotification($user);
            $phoneUpdate = true;
        }

        $emailUpdate = false;
        // check if the email is updated and if so set a new Token and reset the validatedDate
        if ($user->getEmail() != $user->getOldEmail()) {
            $user->setEmailToken($this->createShortToken());
            $user->setValidatedDate(null);
            $emailUpdate = true;
        }

        $drivingLicenceNumberUpdate = $user->getDrivingLicenceNumber() != $user->getOldDrivingLicenceNumber();
        $homeAddressUpdate = $user->getHomeAddress() != $user->getOldHomeAddress();

        // we add/remove structures associated to user
        if (!is_null($user->getSolidaryStructures())) {
            // We initialize an arry with the ids of the user's structures
            $structuresIds = [];
            // we initialise the bool that indicate that we update structures at false
            $updateStructures = false;
            foreach ($user->getOperates() as $operate) {
                // we put in array the ids of the user's structures
                $structuresIds[] = $operate->getStructure()->getId();
            }
            // We initialize an arry with the ids of the user's new structures
            $newStructuresIds = [];
            foreach ($user->getSolidaryStructures() as $solidaryStructure) {
                if (!is_array($solidaryStructure)) {
                    continue;
                }
                // we set the boolean at true
                $updateStructures = true;
                // we put in array the ids of the user's new structures
                $newStructuresIds[] = $solidaryStructure['id'];
                // we add the new structures not present in the array of structures to the user
                if (!in_array($solidaryStructure['id'], $structuresIds)) {
                    $structure = $this->structureRepository->find($solidaryStructure['id']);
                    $operate = new Operate();
                    $operate->setStructure($structure);
                    $operate->setCreatedDate(new \DateTime());
                    $operate->setUpdatedDate(new \DateTime());
                    $user->addOperate($operate);
                }
            }
            // if we delete all structures we pass an empty array with the user so we set the boolean at true
            if (empty($user->getSolidaryStructures())) {
                $updateStructures = true;
            }
            // we execute only if we have updated the structures
            if ($updateStructures) {
                // we remove the structures not present in  the new array of structures
                foreach ($user->getOperates() as $operate) {
                    if (!in_array($operate->getStructure()->getId(), $newStructuresIds)) {
                        $user->removeOperate($operate);
                        $this->entityManager->remove($operate);
                    }
                }
            }
        }

        // check if the user is also a solidary user
        if ($user->getHomeAddress() && $user->getSolidaryUser() && $user->getSolidaryUser()->isVolunteer()) {
            $user->getSolidaryUser()->setAddress($user->getHomeAddress());
        }

        // we check if the user is not underaged
        $this->checkBirthDate($user);

        $this->checkDriverLicenceNumber($user);

        // persist the user
        $this->entityManager->persist($user);
        $this->entityManager->flush();

        // dispatch an event
        $event = new UserUpdatedSelfEvent($user);
        $this->eventDispatcher->dispatch(UserUpdatedSelfEvent::NAME, $event);

        // if the email has changed we send a validation email
        if ($emailUpdate) {
            $event = new UserSendValidationEmailEvent($user);
            $this->eventDispatcher->dispatch(UserSendValidationEmailEvent::NAME, $event);
        }

        if ($phoneUpdate) {
            //  we dispatch the gamification event associated
            $action = $this->actionRepository->findOneBy(['name' => 'user_phone_updated']);
            $actionEvent = new ActionEvent($action, $user);
            $this->eventDispatcher->dispatch($actionEvent, ActionEvent::NAME);

            $eecEvent = new UserPhoneUpdateEvent($user);
            $this->eventDispatcher->dispatch(UserPhoneUpdateEvent::NAME, $event);
        }

        //  we dispatch the gamification event associated
        if ($user->getHomeAddress()) {
            $action = $this->actionRepository->findOneBy(['name' => 'user_home_address_updated']);
            $actionEvent = new ActionEvent($action, $user);
            $this->eventDispatcher->dispatch($actionEvent, ActionEvent::NAME);
        }

        if ($homeAddressUpdate) {
            $eecEvent = new UserHomeAddressUpdateEvent($user);
            $this->eventDispatcher->dispatch(UserHomeAddressUpdateEvent::NAME, $eecEvent);
        }

        if ($drivingLicenceNumberUpdate) {
            $eecEvent = new UserDrivingLicenceNumberUpdateEvent($user);
            $this->eventDispatcher->dispatch(UserDrivingLicenceNumberUpdateEvent::NAME, $eecEvent);
        }

        // return the user
        return $user;
    }

    /**
     * Update the user infos on the payment provider platform.
     *
     * @return User
     */
    public function updatePaymentProviderUser(User $user)
    {
        // We check if the user have a payment profile
        if ($this->paymentActive) {
            $paymentProfiles = $this->paymentProfileRepository->findBy(['user' => $user]);
            if (is_null($paymentProfiles) || 0 == count($paymentProfiles) || is_null($paymentProfiles[0]->getIdentifier())) {
                return $user;
            }
            $this->paymentProvider->updateUser($user);
        }

        return $user;
    }

    /**
     * Encode a password for a user.
     *
     * @param User   $user     The user
     * @param string $password The password to encode
     *
     * @return string The encoded password
     */
    public function encodePassword(User $user, string $password)
    {
        return $this->encoder->encodePassword($user, $password);
    }

    /**
     * Check a password for a user.
     *
     * @param User   $user     The user
     * @param string $password The password to check
     *
     * @return bool The password matches or not
     */
    public function isValidPassword(User $user, string $password)
    {
        return $this->encoder->isPasswordValid($user, $password);
    }

    /**
     * Treat a user : set default parameters.
     * Used for example for imports.
     *
     * @param User $user The user to treat
     *
     * @return User The user treated
     */
    public function treatUser(User $user)
    {
        // we treat the role
        if (0 == count($user->getUserAuthAssignments())) {
            // default role : user registered full
            $authItem = $this->authItemRepository->find(AuthItem::ROLE_USER_REGISTERED_FULL);
            $userAuthAssignment = new UserAuthAssignment();
            $userAuthAssignment->setAuthItem($authItem);
            $user->addUserAuthAssignment($userAuthAssignment);
        }

        // we treat the notifications
        if (0 == count($user->getUserNotifications())) {
            // we have to create the default user notifications, we don't persist immediately
            $user = $this->createAlerts($user, false);
        }

        $this->entityManager->persist($user);

        return $user;
    }

    /**
     * Get the private communities of the given user.
     */
    public function getPrivateCommunities(?User $user): array
    {
        if (is_null($user)) {
            return [];
        }
        if ($communities = $this->communityRepository->findByUser($user, true, null, [CommunityUser::STATUS_ACCEPTED_AS_MODERATOR, CommunityUser::STATUS_ACCEPTED_AS_MEMBER])) {
            return $communities;
        }

        return [];
    }

    public function getThreads(User $user): array
    {
        if ($threads = $this->messageRepository->findThreads($user)) {
            return $threads;
        }

        return [];
    }

    /**
     * Build messages threads considering the type (Direct or Carpool).
     *
     * @param User   $user The User involved
     * @param string $type Type of messages Direct or Carpool
     */
    public function getThreadsMessages(User $user, $type = Message::TYPE_DIRECT): array
    {
        $threads = [];
        if (Message::TYPE_DIRECT == $type) {
            $threads = $this->messageRepository->findThreadsDirectMessages($user);
        } elseif (Message::TYPE_CARPOOL == $type) {
            $threads = $this->askRepository->findAskByUser($user, Ask::ASKS_WITHOUT_SOLIDARY);
        } elseif (Message::TYPE_SOLIDARY == $type) {
            $threads = $this->solidaryAskRepository->findSolidaryAsksForDriver($user);
        } else {
            return [];
        }

        if (!$threads && !$this->carpoolStandardEnabled) {
            return [];
        }

        switch ($type) {
            case Message::TYPE_DIRECT:
                $messages = $this->parseThreadsDirectMessages($user, $threads);

                break;

            case Message::TYPE_SOLIDARY:
                $messages = $this->parseThreadsSolidaryMessages($user, $threads);

                break;

            case Message::TYPE_CARPOOL:
                $messages = $this->parseThreadsCarpoolMessages($user, $threads);

                break;
        }

        return $messages;
    }

    /**
     * Parse the given threads into direct messages.
     *
     * @param User  $user    The mailbox owner
     * @param array $threads The threads (an array of Message objects)
     *
     * @return array The parsed messages
     */
    public function parseThreadsDirectMessages(User $user, array $threads)
    {
        // $threads is a Message[]

        $messages = [];
        foreach ($threads as $message) {
            // To Do : We support only one recipient at this time...
            $currentMessage = [
                'idMessage' => $message->getId(),
                'idRecipient' => ($user->getId() === $message->getUser('user')->getId()) ? $message->getRecipients()[0]->getUser('user')->getId() : $message->getUser('user')->getId(),
                'avatarsRecipient' => ($user->getId() === $message->getUser('user')->getId()) ? $message->getRecipients()[0]->getUser('user')->getAvatars()[0] : $message->getUser('user')->getAvatars()[0],
                'givenName' => ($user->getId() === $message->getUser('user')->getId()) ? $message->getRecipients()[0]->getUser('user')->getGivenName() : $message->getUser('user')->getGivenName(),
                'shortFamilyName' => ($user->getId() === $message->getUser('user')->getId()) ? $message->getRecipients()[0]->getUser('user')->getShortFamilyName() : $message->getUser('user')->getShortFamilyName(),
                'date' => (null === $message->getLastMessage()) ? $message->getCreatedDate() : $message->getLastMessage()->getCreatedDate(),
                'selected' => false,
                'unreadMessages' => 0,
            ];

            // For each message, we check the all chain to determined the unread messages
            $completeThreadMessages = $this->internalMessageManager->getCompleteThread($message->getId());
            foreach ($completeThreadMessages as $message) {
                foreach ($message->getRecipients() as $recipient) {
                    if ($user->getId() == $recipient->getUser()->getId() && is_null($recipient->getReadDate())) {
                        ++$currentMessage['unreadMessages'];
                    }
                }
            }

            // We check if the user and it's carpooler are involved in a block
            $user2 = ($user->getId() === $message->getRecipients()[0]->getUser()->getId() ? $message->getUser() : $message->getRecipients()[0]->getUser());
            $blocks = $this->blockManager->getInvolvedInABlock($user, $user2);
            $currentMessage['blockerId'] = null;
            if (is_array($blocks) && count($blocks) > 0) {
                foreach ($blocks as $block) {
                    if ($block->getUser()->getId() == $user->getId()) {
                        // The blocker is the current User
                        $currentMessage['blockerId'] = $user->getId();

                        break;
                    }
                    $currentMessage['blockerId'] = $user2->getId();
                }
            }
            $messages[] = $currentMessage;
        }
        // Sort with the last message received first
        usort($messages, [$this, 'sortThread']);

        return $messages;
    }

    /**
     * Parse the given threads into solidary messages.
     *
     * @param User  $user    The mailbox owner
     * @param array $threads The threads (an array of SolidaryAsk objects)
     *
     * @return array The parsed messages
     */
    public function parseThreadsSolidaryMessages(User $user, array $threads)
    {
        $messages = [];

        foreach ($threads as $solidaryAsk) {
            /**
             * @var SolidaryAsk $solidaryAsk
             */
            // we check if the solidary ask has at least on message thru the solidary ask histories
            if ($lastSolidaryAskHistory = $this->solidaryAskHistoryRepository->findLastSolidaryAskHistory($solidaryAsk)) {
                $message = $lastSolidaryAskHistory->getMessage();

                $beneficiary = $solidaryAsk->getSolidarySolution()->getSolidaryMatching()->getSolidary()->getSolidaryUserStructure()->getSolidaryUser()->getUser();

                // find the driver : can be either a carpooler or a volunteer
                if ($solidaryAsk->getSolidarySolution()->getSolidaryMatching()->getMatching()) {
                    // carpooler
                    $driver = $solidaryAsk->getSolidarySolution()->getSolidaryMatching()->getMatching()->getProposalOffer()->getUser();
                    // the waypoints are the waypoints of the matching
                    $waypoints = $solidaryAsk->getSolidarySolution()->getSolidaryMatching()->getMatching()->getWaypoints();
                } elseif ($solidaryAsk->getSolidarySolution()->getSolidaryMatching()->getSolidaryUser()) {
                    // volunteer
                    $driver = $solidaryAsk->getSolidarySolution()->getSolidaryMatching()->getSolidaryUser()->getUser();
                    // the waypoints are the waypoints of the solidary proposal
                    $waypoints = $solidaryAsk->getSolidarySolution()->getSolidaryMatching()->getSolidary()->getProposal()->getWaypoints();
                } else {
                    // whaaaaat ?
                    return [];
                }

                $recipient = $user->getId() === $driver->getId() ? $beneficiary : $driver;

                $currentThread = [
                    'idSolidaryAskHistory' => $lastSolidaryAskHistory->getId(),
                    'idSolidaryAsk' => $solidaryAsk->getId(),
                    'idRecipient' => $recipient->getId(),
                    'avatarsRecipient' => $recipient->getAvatars()[0],
                    'givenName' => $recipient->getGivenName(),
                    'shortFamilyName' => $recipient->getShortFamilyName(),
                    'date' => (null === $message) ? $lastSolidaryAskHistory->getCreatedDate() : $message->getCreatedDate(),
                    'selected' => false,
                    'unreadMessages' => 0,
                ];

                // The message id : the one linked to the last solidary ask history or we try to find the last existing one
                $idMessage = -99;
                if (null !== $message) {
                    (null !== $message->getMessage()) ? $idMessage = $message->getMessage()->getId() : $idMessage = $message->getId();
                } else {
                    if ($formerSolidaryAskHistory = $this->solidaryAskHistoryRepository->findLastSolidaryAskHistoryWithMessage($solidaryAsk)) {
                        if ($formerSolidaryAskHistory->getMessage()->getMessage()) {
                            $idMessage = $formerSolidaryAskHistory->getMessage()->getMessage()->getId();
                        } else {
                            $idMessage = $formerSolidaryAskHistory->getMessage()->getId();
                        }
                    }
                }

                // For each message, we check the all chain to determined the unread messages
                if (-99 !== $idMessage) {
                    $completeThreadMessages = $this->internalMessageManager->getCompleteThread($idMessage);
                    foreach ($completeThreadMessages as $currentMessage) {
                        foreach ($currentMessage->getRecipients() as $recipient) {
                            if ($user->getId() == $recipient->getUser()->getId() && is_null($recipient->getReadDate())) {
                                ++$currentThread['unreadMessages'];
                            }
                        }
                    }
                }

                $currentThread['idMessage'] = $idMessage;
                // for now, the criteria is the one of the beneficiary proposal
                $criteria = $solidaryAsk->getSolidarySolution()->getSolidaryMatching()->getSolidary()->getProposal()->getCriteria();
                $currentThread['carpoolInfos'] = [
                    'solidaryAskHistoryId' => $lastSolidaryAskHistory->getId(),
                    'origin' => $waypoints[0]->getAddress()->getAddressLocality(),
                    'destination' => $waypoints[count($waypoints) - 1]->getAddress()->getAddressLocality(),
                    'criteria' => [
                        'frequency' => $criteria->getFrequency(),
                        'fromDate' => $criteria->getFromDate(),
                        'fromTime' => $criteria->getFromTime(),
                        'monCheck' => $criteria->isMonCheck(),
                        'tueCheck' => $criteria->isTueCheck(),
                        'wedCheck' => $criteria->isWedCheck(),
                        'thuCheck' => $criteria->isThuCheck(),
                        'friCheck' => $criteria->isFriCheck(),
                        'satCheck' => $criteria->isSatCheck(),
                        'sunCheck' => $criteria->isSunCheck(),
                    ],
                ];

                // We check if the user and it's carpooler are involved in a block
                // $user2 = ($user->getId() === $ask->getUserRelated()->getId() ? $ask->getUser() : $ask->getUserRelated());
                // $blocks = $this->blockManager->getInvolvedInABlock($user, $user2);
                // $currentThread['blockerId'] = null;
                // if (is_array($blocks) && count($blocks)>0) {
                //     foreach ($blocks as $block) {
                //         if ($block->getUser()->getId() == $user->getId()) {
                //             // The blocker is the current User
                //             $currentThread['blockerId'] = $user->getId();
                //             break;
                //         }
                //         $currentThread['blockerId'] = $user2->getId();
                //     }
                // }

                $messages[] = $currentThread;
            }
        }

        // Sort with the last message received first
        usort($messages, [$this, 'sortThread']);

        return $messages;
    }

    /**
     * Parse the given threads into carpool messages.
     *
     * @param User  $user    The mailbox owner
     * @param array $threads The threads (an array of Ask objects)
     *
     * @return array The parsed messages
     */
    public function parseThreadsCarpoolMessages(User $user, array $threads)
    {
        $messages = [];

        foreach ($threads as $ask) {
            $askHistories = $this->askHistoryRepository->findLastAskHistory($ask);

            // Only the Ask with at least one AskHistory
            // Only one-way or outward of a round trip.
            if (count($askHistories) > 0 && (1 == $ask->getType() || 2 == $ask->getType())) {
                $askHistory = $askHistories[0];

                $message = $askHistory->getMessage();

                $currentThread = [
                    'idAskHistory' => $askHistory->getId(),
                    'idAsk' => $ask->getId(),
                    'idRecipient' => ($user->getId() === $ask->getUser('user')->getId()) ? $ask->getUserRelated()->getId() : $ask->getUser('user')->getId(),
                    'avatarsRecipient' => ($user->getId() === $ask->getUser('user')->getId()) ? $ask->getUserRelated()->getAvatars()[0] : $ask->getUser('user')->getAvatars()[0],
                    'givenName' => ($user->getId() === $ask->getUser('user')->getId()) ? $ask->getUserRelated()->getGivenName() : $ask->getUser('user')->getGivenName(),
                    'shortFamilyName' => ($user->getId() === $ask->getUser('user')->getId()) ? $ask->getUserRelated()->getShortFamilyName() : $ask->getUser('user')->getShortFamilyName(),
                    'date' => (null === $message) ? $askHistory->getCreatedDate() : $message->getCreatedDate(),
                    'selected' => false,
                    'unreadMessages' => 0,
                ];

                // The message id : the one linked to the current askHistory or we try to find the last existing one
                $idMessage = -99;
                if (null !== $message) {
                    (null !== $message->getMessage()) ? $idMessage = $message->getMessage()->getId() : $idMessage = $message->getId();
                } else {
                    if ($formerAskHistory = $this->askHistoryRepository->findLastAskHistoryWithMessage($ask)) {
                        if ($formerAskHistory->getMessage()->getMessage()) {
                            $idMessage = $formerAskHistory->getMessage()->getMessage()->getId();
                        } else {
                            $idMessage = $formerAskHistory->getMessage()->getId();
                        }
                    }
                }

                // For each message, we check the all chain to determined the unread messages
                if (-99 !== $idMessage) {
                    $completeThreadMessages = $this->internalMessageManager->getCompleteThread($idMessage);
                    foreach ($completeThreadMessages as $currentMessage) {
                        foreach ($currentMessage->getRecipients() as $recipient) {
                            if ($user->getId() == $recipient->getUser()->getId() && is_null($recipient->getReadDate())) {
                                ++$currentThread['unreadMessages'];
                            }
                        }
                    }
                }

                $currentThread['idMessage'] = $idMessage;
                $waypoints = $ask->getMatching()->getWaypoints();
                $criteria = $ask->getCriteria();
                $currentThread['carpoolInfos'] = [
                    'askHistoryId' => $askHistory->getId(),
                    'origin' => $waypoints[0]->getAddress()->getAddressLocality(),
                    'destination' => $waypoints[count($waypoints) - 1]->getAddress()->getAddressLocality(),
                    'criteria' => [
                        'frequency' => $criteria->getFrequency(),
                        'fromDate' => $criteria->getFromDate(),
                        'fromTime' => $criteria->getFromTime(),
                        'monCheck' => $criteria->isMonCheck(),
                        'tueCheck' => $criteria->isTueCheck(),
                        'wedCheck' => $criteria->isWedCheck(),
                        'thuCheck' => $criteria->isThuCheck(),
                        'friCheck' => $criteria->isFriCheck(),
                        'satCheck' => $criteria->isSatCheck(),
                        'sunCheck' => $criteria->isSunCheck(),
                    ],
                ];

                // We check if the user and it's carpooler are involved in a block
                $user2 = ($user->getId() === $ask->getUserRelated()->getId() ? $ask->getUser() : $ask->getUserRelated());
                $blocks = $this->blockManager->getInvolvedInABlock($user, $user2);
                $currentThread['blockerId'] = null;
                if (is_array($blocks) && count($blocks) > 0) {
                    foreach ($blocks as $block) {
                        if ($block->getUser()->getId() == $user->getId()) {
                            // The blocker is the current User
                            $currentThread['blockerId'] = $user->getId();

                            break;
                        }
                        $currentThread['blockerId'] = $user2->getId();
                    }
                }

                $messages[] = $currentThread;
            }
        }

        // We get carpoolStandard bookings if enabled
        if ($this->carpoolStandardEnabled) {
            $bookings = $this->bookingManager->getBookings($user->getId());
            foreach ($bookings as $booking) {
                $currentThread = [
                    'idRecipient' => $booking->getDriver()->getId(),
                    'givenName' => $booking->getDriver()->getAlias(),
                    'date' => '',
                    'selected' => false,
                    'unreadMessages' => 0,
                    'idBooking' => $booking->getId(),
                    'carpoolInfos' => [
                        'origin' => $booking->getPassengerPickupAddress(),
                        'destination' => $booking->getPassengerDropAddress(),
                        'criteria' => [
                            'frequency' => 1,
                            'fromDate' => date('Y-m-d', $booking->passengerPickupDate()),
                            'fromTime' => date('H:i:s', $booking->passengerPickupDate()),
                        ],
                    ],
                ];
                $messages[] = $currentThread;
            }
        }

        // Sort with the last message received first
        usort($messages, [$this, 'sortThread']);

        return $messages;
    }

    public function getThreadsDirectMessages(User $user): array
    {
        return $this->getThreadsMessages($user, Message::TYPE_DIRECT);
    }

    public function getThreadsCarpoolMessages(User $user): array
    {
        return $this->getThreadsMessages($user, Message::TYPE_CARPOOL);
    }

    public function getThreadsSolidaryMessages(User $user): array
    {
        return $this->getThreadsMessages($user, Message::TYPE_SOLIDARY);
    }

    /**
     * User password change request.
     *
     * @return Response
     */
    public function updateUserPasswordRequest(User $data)
    {
        // We get the way the password update was asked (app, android, ios)
        $mobileRegistration = $data->getMobileRegistration();
        // Get the user
        $user = $this->userRepository->findOneBy(['email' => $data->getEmail()]);
        if (!is_null($user)) {
            // Create a password token
            $user->setPwdToken($this->createToken($user));
            // persist the user
            $this->entityManager->persist($user);
            $this->entityManager->flush();
            // dispatch en event
            // we set the way the password was asked before to send the event
            $user->setMobileRegistration($mobileRegistration);
            $event = new UserPasswordChangeAskedEvent($user);
            $this->eventDispatcher->dispatch($event, UserPasswordChangeAskedEvent::NAME);

            $user->setPwdToken(null);

            return $user;
        }
        // send response with the sender information in all case
        $user = new User();
        $user->setEmail($data->getEmail());

        return $user;
    }

    /**
     * User password change confirmation.
     *
     * @return Response
     */
    public function updateUserPassword(User $data)
    {
        $user = $this->userRepository->findOneBy(['pwdToken' => $data->getPwdToken()]);
        if (!is_null($user)) {
            $user->setPassword($this->encoder->encodePassword($user, $data->getPassword()));
            // we reset tokens
            $user->setPwdTokenDate(null);
            $user->setPwdToken(null);
            // persist the user
            $this->entityManager->persist($user);
            $this->entityManager->flush();
            // dispatch en event
            $event = new UserPasswordChangedEvent($user);
            $this->eventDispatcher->dispatch($event, UserPasswordChangedEvent::NAME);

            // return the user
            return $user;
        }

        return new JsonResponse();
    }

    /**
     * Get user alert preferences.
     *
     * @return User
     */
    public function getAlerts(User $user)
    {
        // if no alerts are detected we create them
        if (0 == count($user->getUserNotifications())) {
            $user = $this->createAlerts($user);
        }
        $alerts = [];
        $actions = [];
        // first pass to get the actions
        foreach ($user->getUserNotifications() as $userNotification) {
            if (Medium::MEDIUM_SMS == $userNotification->getNotification()->getMedium()->getId() && is_null($user->getPhoneValidatedDate())) {
                // check telephone for sms
                continue;
            }
            if (Medium::MEDIUM_PUSH == $userNotification->getNotification()->getMedium()->getId() && !$user->hasMobile()) {
                // check apps for push
                continue;
            }
            if (!in_array($userNotification->getNotification()->getAction()->getName(), $alerts)) {
                $alerts[$userNotification->getNotification()->getAction()->getId()] = [
                    'action' => $userNotification->getNotification()->getAction()->getName(),
                    'alert' => [],
                ];
                $actions[$userNotification->getNotification()->getAction()->getId()] = $userNotification->getNotification()->getAction()->getId();
            }
        }
        ksort($alerts);
        // second pass to get the media
        $media = [];
        foreach ($user->getUserNotifications() as $userNotification) {
            if (Medium::MEDIUM_SMS == $userNotification->getNotification()->getMedium()->getId() && is_null($user->getPhoneValidatedDate())) {
                // check telephone for sms
                continue;
            }
            if (Medium::MEDIUM_PUSH == $userNotification->getNotification()->getMedium()->getId() && !$user->hasMobile()) {
                // check apps for push
                continue;
            }
            $media[$userNotification->getNotification()->getAction()->getId()][$userNotification->getNotification()->getPosition()] = [
                'medium' => $userNotification->getNotification()->getMedium()->getId(),
                'id' => $userNotification->getId(),
                'active' => $userNotification->isActive(),
            ];
        }

        // third pass to order media
        $mediaOrdered = [];
        foreach ($media as $actionID => $unorderedMedia) {
            $copy = $unorderedMedia;
            ksort($copy);
            $mediaOrdered[$actionID] = $copy;
        }
        // fourth pass to link media to actions
        foreach ($mediaOrdered as $actionID => $orderedMedia) {
            $alerts[$actions[$actionID]]['alert'] = $orderedMedia;
        }
        $user->setAlerts($alerts);

        return $user;
    }

    /**
     * Create user alerts.
     *
     * @param User  $user    The user to treat
     * @param mixed $persist
     *
     * @return User
     */
    public function createAlerts(User $user, $persist = true)
    {
        $notifications = $this->notificationRepository->findUserEditable();
        foreach ($notifications as $notification) {
            $userNotification = new UserNotification();
            $userNotification->setNotification($notification);
            $userNotification->setActive($notification->isUserActiveDefault());
            if (Medium::MEDIUM_SMS == $userNotification->getNotification()->getMedium()->getId() && is_null($user->getPhoneValidatedDate())) {
                // check telephone for sms
                $userNotification->setActive(false);
            } elseif (Medium::MEDIUM_PUSH == $userNotification->getNotification()->getMedium()->getId() && !$user->hasMobile()) {
                // check apps for push
                $userNotification->setActive(false);
            }
            $user->addUserNotification($userNotification);
        }
        $this->entityManager->persist($user);
        if ($persist) {
            $this->entityManager->flush();
        }

        return $user;
    }

    /**
     * Update user alerts.
     */
    public function updateAlerts(User $user)
    {
        if (!is_null($user->getAlerts())) {
            foreach ($user->getAlerts() as $id => $active) {
                if ($userNotification = $this->userNotificationRepository->find($id)) {
                    $userNotification->setActive($active);
                    $this->entityManager->persist($userNotification);
                }
            }
            $this->entityManager->flush();
        }

        return $this->getAlerts($user);
    }

    /**
     * set sms notification to active when phone is validated.
     */
    public function activateSmsNotification(User $user)
    {
        $userNotifications = $this->userNotificationRepository->findUserNotifications($user->getId());
        foreach ($userNotifications as $userNotification) {
            if (Medium::MEDIUM_SMS == $userNotification->getNotification()->getMedium()->getId()) {
                // check telephone for sms
                $userNotification->setActive(true);
                $userNotification->setUser($user);
                $this->entityManager->persist($userNotification);
            }
        }
        $this->entityManager->flush();

        return $user;
    }

    /**
     * set sms notification to non active when phone change or is removed.
     */
    public function deActivateSmsNotification(User $user)
    {
        $userNotifications = $this->userNotificationRepository->findUserNotifications($user->getId());
        foreach ($userNotifications as $userNotification) {
            if (Medium::MEDIUM_SMS == $userNotification->getNotification()->getMedium()->getId()) {
                $userNotification->setActive(false);
                $userNotification->setUser($user);
                $this->entityManager->persist($userNotification);
            }
        }
        $this->entityManager->flush();

        return $user;
    }

    /**
     * Generate a phone token.
     */
    public function generatePhoneToken(User $user)
    {
        // Generate the token
        $phoneToken = strval(mt_rand(1000, 9999));
        $user->setPhoneToken($phoneToken);
        // dispatch the event
        $event = new UserGeneratePhoneTokenAskedEvent($user);
        $this->eventDispatcher->dispatch(UserGeneratePhoneTokenAskedEvent::NAME, $event);
        // Persist user
        $this->entityManager->persist($user);
        $this->entityManager->flush();

        return $user;
    }

    /**
     * Delete the user.
     *
     * @param mixed $isScammer
     */
    public function deleteUser(User $user, $isScammer = false)
    {
        if (!$isScammer) {
            // We check if the user have ads.
            // If he have ads we check if a carpool is initiated if yes we send an email to the carpooler
            foreach ($user->getProposals() as $proposal) {
                if ($proposal->isPrivate()) {
                    continue;
                }
                foreach ($proposal->getMatchingRequests() as $matching) {
                    foreach ($matching->getAsks() as $ask) {
                        // todo : find why class of $ask can be a proxy of Ask class
                        if (Ask::class !== get_class($ask)) {
                            continue;
                        }
                        $event = new UserDeleteAccountWasDriverEvent($ask, $user->getId());
                        $this->eventDispatcher->dispatch(UserDeleteAccountWasDriverEvent::NAME, $event);
                    }
                }
                foreach ($proposal->getMatchingOffers() as $matching) {
                    foreach ($matching->getAsks() as $ask) {
                        // todo : find why class of $ask can be a proxy of Ask class
                        if (Ask::class !== get_class($ask)) {
                            continue;
                        }
                        $event = new UserDeleteAccountWasPassengerEvent($ask, $user->getId());
                        $this->eventDispatcher->dispatch(UserDeleteAccountWasPassengerEvent::NAME, $event);
                    }
                }
            }
        }

        $this->deleteUserImages($user);

        $user = $this->_pseudonymizationManager->pseudonymizedUser($user);

        $this->entityManager->flush();
    }

    // Get asks for an user -> use for check if a ask is already done on a proposal
    public function getAsks(User $user): array
    {
        if ($asks = $this->askRepository->findAskByAsker($user)) {
            $arrayAsks = [];
            foreach ($asks as $ask) {
                $arrayAsks['offers'][] = $ask->getMatching()->getProposalOffer()->getId();
                $arrayAsks['request'][] = $ask->getMatching()->getProposalRequest()->getId();
            }

            return $arrayAsks;
        }

        return [];
    }

    public function checkPhoneToken($data)
    {
        $userFound = $this->userRepository->findOneBy(['phoneToken' => $data->getPhoneToken(), 'telephone' => $data->getTelephone()]);
        if (!is_null($userFound)) {
            // User found by token match with the given Telephone. We update de validated date, persist, then return the user found
            $userFound->setPhoneValidatedDate(new \DateTime());
            $this->entityManager->persist($userFound);
            $this->entityManager->flush();

            //  we dispatch the gamification event associated
            $action = $this->actionRepository->findOneBy(['name' => 'user_phone_validation']);
            $actionEvent = new ActionEvent($action, $userFound);
            $this->eventDispatcher->dispatch($actionEvent, ActionEvent::NAME);

            return $userFound;
        }

        // No user found. We return nothing.
        return new JsonResponse();
    }

    public function unsubscribeFromEmail(User $user, $lang = 'fr_FR')
    {
        if (!is_null($user->getLanguage())) {
            $lang = $user->getLanguage();
            $this->translator->setLocale($lang->getCode());
        } else {
            $this->translator->setLocale($lang);
        }

        $messageUnsubscribe = $this->translator->trans('unsubscribeEmailAlertFront', ['instanceName' => $_ENV['EMAILS_PLATFORM_NAME']]);

        $user->setNewsSubscription(0);
        $user->setUnsubscribeDate(new \DateTime());

        $user->setUnsubscribeMessage($messageUnsubscribe);

        $this->entityManager->persist($user);
        $this->entityManager->flush();

        return $user;
    }

    /**
     * Update the activity of an user.
     *
     * @param User $user The user to update
     */
    public function updateActivity(User $user)
    {
        $user->setLastActivityDate(new \DateTime());

        $this->entityManager->persist($user);
        $this->entityManager->flush();
    }

    public function createAuthenticationDelegation(User $userByDelegation, User $user): void
    {
        $authenticationDelegation = new AuthenticationDelegation($userByDelegation, $user);
        $this->entityManager->persist($authenticationDelegation);
        $this->entityManager->flush();
    }

    /**
     * Get the solidaries of a user.
     *
     * @param int $userId The user id we want to get the solidaries
     *
     * @return null|User
     */
    public function getSolidaries(int $userId)
    {
        $user = $this->userRepository->find($userId);
        if (empty($user)) {
            throw new SolidaryException(SolidaryException::UNKNOWN_USER);
        }

        $solidaries = $this->solidaryRepository->findByUser($user);
        if (!empty($solidaries)) {
            $user->setSolidaries($solidaries);
        }

        return $user;
    }

    /**
     * Get the structures of a user.
     *
     * @param int $userId The user id we want to get the structures
     *
     * @return null|User
     */
    public function getStructures(int $userId)
    {
        $user = $this->userRepository->find($userId);
        if (empty($user)) {
            throw new SolidaryException(SolidaryException::UNKNOWN_USER);
        }

        $structures = $this->structureRepository->findByUser($user);
        if (!empty($structures)) {
            $user->setStructures($structures);
        }

        return $user;
    }

    /**
     * Create a random token for a user.
     *
     * @param User $user The user
     *
     * @return string The token generated
     */
    public function createToken(User $user)
    {
        $datetime = new \DateTime();
        $time = $datetime->getTimestamp();
        // note : we replace the '/' by an arbitrary 'a' as the token could be used in a url

        if ($user->getEmail() == $this->fakeFirstMail) {
            return $this->fakeFirstToken;
        }

        return $this->sanitizeString(hash('sha256', $user->getEmail().rand().$time.rand().$user->getSalt()));
    }

    /**
     * Create a random token for an email validation.
     */
    public function createShortToken(): string
    {
        return strval(rand(100000, 999999));
    }

    /**
     * Check if an user $user have a specific AuthItem $authItem.
     *
     * @param User $user The user to check
     *
     * @return bool True if user have item
     */
    public function checkUserHaveAuthItem(User $user, AuthItem $authItem)
    {
        foreach ($user->getUserAuthAssignments() as $oneItem) {
            if ($oneItem->getAuthItem() == $authItem) {
                return true;
            }
        }

        return false;
    }

    /**
     * Generate a random string.
     *
     * @param int $length The length of the string to generate
     *
     * @return string The generated string
     */
    public function randomString(int $length = 10)
    {
        $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
        $string = []; // remember to declare $string as an array
        $alphaLength = strlen($alphabet) - 1; // put the length -1 in cache
        for ($i = 0; $i < $length; ++$i) {
            $n = rand(0, $alphaLength);
            $string[] = $alphabet[$n];
        }

        return implode($string); // turn the array into a string
    }

    /**
     * Generate a sub email address.
     *
     * @param string $email  The base email
     * @param int    $length The length of the generated random string
     * @param string $glue   The string to add before the random string
     *
     * @return string The generated sub email address
     */
    public function generateSubEmail(string $email, int $length = 10, string $glue = '+')
    {
        $exploded = explode('@', $email);

        return $exploded[0].$glue.$this->randomString($length).'@'.$exploded[1];
    }

    /**
     * Get the payment profile (bankaccounts, wallets...) of the User.
     *
     * @return null|User
     */
    public function getPaymentProfile(?User $user = null)
    {
        if (is_null($user)) {
            $user = $this->userRepository->findOneBy(['email' => $this->security->getUser()->getUsername()]);
        }
        $paymentProfiles = $this->paymentProvider->getPaymentProfiles($user);
        $bankAccounts = $wallets = [];
        foreach ($paymentProfiles as $paymentProfile) {
            if (!is_null($paymentProfile->getBankAccounts())) {
                foreach ($paymentProfile->getBankAccounts() as $bankaccount) {
                    /**
                     * @var BankAccount $bankaccount
                     */

                    // We replace some characters in Iban and Bic by *
                    $iban = $bankaccount->getIban();
                    for ($i = 4; $i < strlen($iban) - 4; ++$i) {
                        $iban[$i] = '*';
                    }
                    $bic = $bankaccount->getBic();
                    for ($i = 2; $i < strlen($bic) - 2; ++$i) {
                        $bic[$i] = '*';
                    }

                    $bankaccount->setIban($iban);
                    $bankaccount->setBic($bic);

                    $bankaccount->setRefusalReason($paymentProfile->getRefusalReason());

                    $bankAccounts[] = $bankaccount;
                }
            }
            if (!is_null($paymentProfile->getWallets())) {
                foreach ($paymentProfile->getWallets() as $wallet) {
                    $wallets[] = $wallet;
                }
            }
        }
        $user->setBankAccounts($bankAccounts);
        $user->setWallets($wallets);

        return $user;
    }

    /**
     * Generate a randomPassword.
     *
     * @return string
     */
    public function randomPassword()
    {
        $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
        $pass = []; // remember to declare $pass as an array
        $alphaLength = strlen($alphabet) - 1; // put the length -1 in cache
        for ($i = 0; $i < 10; ++$i) {
            $n = rand(0, $alphaLength);
            $pass[] = $alphabet[$n];
        }

        return implode($pass); // turn the array into a string
    }

    public function updateUserSsoProperties(User $user, SsoUser $ssoUser, bool $createdBySso = false): User
    {
        foreach ($user->getSsoAccounts() as $ssoAccount) {
            /**
             * @var SsoAccount $ssoAccount
             */
            if ($ssoAccount->getSsoProvider() == $ssoUser->getProvider()) {
                $ssoAccount->setSsoId($ssoUser->getSub());
                $ssoAccount->setCreatedBySso($createdBySso);
                if (is_null($ssoAccount->getCreatedDate())) {
                    $ssoAccount->setCreatedDate(new \DateTime('now'));
                }
                $this->entityManager->persist($ssoAccount);
                $this->entityManager->flush();

                $event = new SsoAssociationEvent($user, $ssoUser);
                $this->eventDispatcher->dispatch(SsoAssociationEvent::NAME, $event);

                return $user;
            }
        }

        $newSsoAccount = new SsoAccount();
        $newSsoAccount->setSsoId($ssoUser->getSub());
        $newSsoAccount->setSsoProvider($ssoUser->getProvider());
        $newSsoAccount->setCreatedBySso($createdBySso);
        $user->addSsoAccount($newSsoAccount);

        $this->entityManager->persist($user);
        $this->entityManager->flush();

        $event = new SsoAssociationEvent($user, $ssoUser);
        $this->eventDispatcher->dispatch(SsoAssociationEvent::NAME, $event);

        return $user;
    }

    /**
     * Return a User from a SsoUser
     * Existing user or a new one.
     */
    public function getUserFromSso(SsoUser $ssoUser): ?User
    {
        $user = $this->userRepository->findUserBySsoIdAndProvider($ssoUser->getSub(), $ssoUser->getProvider());

        if (is_null($user)) {
            // check if a user with this email already exists
            $user = $this->userRepository->findOneBy(['email' => $ssoUser->getEmail()]);
            if (!is_null($user)) {
                return $this->_attachUserBySso($user, $ssoUser);
            }

            if (!$ssoUser->hasAutoCreateAccount()) {
                throw new \LogicException('Autocreate/Autoattach account disable');
            }

            $user = $this->_createNewUserBySso($ssoUser);
        }

        $this->updateUserSsoProperties($user, $ssoUser);

        return $user;
    }

    /**
     * Get a User by it's SsoId.
     */
    public function getUserBySsoIdAndAppDelegate(string $ssoId, App $appDelegate): ?User
    {
        if ($user = $this->userRepository->findUserBySsoIdAndAppDelegate($ssoId, $appDelegate)) {
            return $user;
        }

        return null;
    }

    /**
     * Get the profile summary of a User.
     *
     * @param User $user The User
     */
    public function getProfileSummary(User $user): ProfileSummary
    {
        $profileSummary = new ProfileSummary($user->getId());
        $profileSummary->setGivenName($user->getGivenName());
        $profileSummary->setShortFamilyName($user->getShortFamilyName());
        $profileSummary->setCreatedDate($user->getCreatedDate());
        $profileSummary->setLastActivityDate($user->getLastActivityDate());

        $profileSummary->setAge($user->getBirthDate() ? $user->getBirthDate()->diff(new \DateTime())->y : null);

        $profileSummary->setVerifiedIdentity($user->hasVerifiedIdentity());

        $profileSummary->setPhoneDisplay($user->getPhoneDisplay());
        if (User::PHONE_DISPLAY_ALL == $user->getPhoneDisplay()) {
            $profileSummary->setTelephone($user->getTelephone());
        }
        if (is_array($user->getAvatars()) && count($user->getAvatars()) > 0) {
            $profileSummary->setAvatar($user->getAvatars()[count($user->getAvatars()) - 1]);
        }

        // Number of realized carpool (number of accepted Aks as driver or passenger)
        $asks = $this->askRepository->findAcceptedAsksForUser($user);
        // We count only one way and outward of a round trip
        $nbAsks = 0;
        $totalSavedCo2 = 0;
        foreach ($asks as $ask) {
            if (Ask::TYPE_ONE_WAY == $ask->getType() || Ask::TYPE_OUTWARD_ROUNDTRIP == $ask->getType()) {
                // Compute the saved Co2 for this Ask
                $totalSavedCo2 += $this->computeSavedCo2($ask, $user->getId());
                ++$nbAsks;
            }
        }
        $profileSummary->setCarpoolRealized($nbAsks);
        $profileSummary->setSavedCo2($totalSavedCo2);

        // Get the first messages of every threads the user is involved in
        $threads = $this->messageRepository->findThreads($user);
        $nbMessageConsidered = 0;
        $nbMessagesTotal = 0;
        $nbMessagesAnswered = 0;
        foreach ($threads as $firstMessage) {
            // We keep only the XX last messages (.env variable)
            if ($nbMessageConsidered >= $this->profile['maxMessagesForAnswerRate']) {
                break;
            }

            // We keep only the messages where the user was user
            if ($firstMessage->getRecipients()[0]->getUser()->getId() == $user->getId()) {
                ++$nbMessagesTotal;
                // We check if the User sent an anwser to this message
                $completeThread = $this->internalMessageManager->getCompleteThread($firstMessage->getId());
                foreach ($completeThread as $message) {
                    if ($message->getUser()->getid() == $user->getId()) {
                        ++$nbMessagesAnswered;

                        break;
                    }
                }
                ++$nbMessageConsidered;
            }
        }
        $profileSummary->setAnswerPct((0 == $nbMessagesTotal) ? $this->profile['experiencedTagMinAnswerPctDefault'] : round(($nbMessagesAnswered / $nbMessagesTotal) * 100));

        // Experienced user
        // To be experienced :
        // The User has to have a number of realized carpools >= experiencedTagMinCarpools(.env)
        // The User has to have a answer percentage >= experiencedTagMinAnswerPct(.env)
        $profileSummary->setExperienced(false);
        if ($this->profile['experiencedTag']) {
            if (
                $profileSummary->getCarpoolRealized() >= $this->profile['experiencedTagMinCarpools']
                && $profileSummary->getAnswerPct() >= $this->profile['experiencedTagMinAnswerPct']
            ) {
                $profileSummary->setExperienced(true);
            }
        }

        // Set number of earned badges
        $profileSummary->setNumberOfBadges(count($this->gamificationManager->getBadgesEarned($user)));

        return $profileSummary;
    }

    /**
     * Get the public profile of a User.
     *
     * @param User $user The User
     */
    public function getPublicProfile(User $user): PublicProfile
    {
        $publicProfile = new PublicProfile($user->getId());
        // Get the profile summary
        $publicProfile->setProfileSummary($this->getProfileSummary($user));

        // Preferences
        $publicProfile->setSmoke($user->getSmoke());
        $publicProfile->setMusic($user->hasMusic());
        $publicProfile->setMusicFavorites($user->getMusicFavorites());
        $publicProfile->setChat($user->hasChat());
        $publicProfile->setChatFavorites($user->getChatFavorites());

        // Get the reviews about this user
        $publicProfile->setReviewActive($this->profile['userReview']);
        $publicProfile->setReviews($this->reviewManager->getSpecificReviews(null, $user));

        // Get the user's badges
        $publicProfile->setBadges($this->gamificationManager->getBadgesEarned($user));

        return $publicProfile;
    }

    /**
     * Send a verification email.
     *
     * @param string $email The email to verify
     *
     * @return null|User The user found
     */
    public function sendValidationEmail(string $email)
    {
        if ($user = $this->userRepository->findOneBy(['email' => $email])) {
            $event = new UserSendValidationEmailEvent($user);
            $this->eventDispatcher->dispatch(UserSendValidationEmailEvent::NAME, $event);

            return $user;
        }

        throw new UserNotFoundException('Unknow email', 1);
    }

    /**
     * Compute the saved Co2 on a Ask by a user.
     *
     * @param Ask   $ask    The Ask
     * @param int   $userId The User id
     * @param mixed $export
     */
    public function computeSavedCo2(Ask $ask, int $userId, $export = false): int
    {
        $driver = ($ask->getMatching()->getProposalOffer()->getUser()->getId() == $userId);

        // Original driver distance
        $originalDriverDistance = $ask->getMatching()->getOriginalDistance();

        // Original passenger distance
        $originalPassengerDistance = $ask->getMatching()->getProposalRequest()->getCriteria()->getDirectionPassenger()->getDistance();

        // Get the carpooled distance and the detour distance
        $newDistance = $ask->getMatching()->getNewDistance();
        $detourDistance = $ask->getMatching()->getDetourDistance();

        // The saved distance depends if you're the driver or the passenger

        if ($driver) {
            // ********* If you are the driver :
            // Without carpool you would've traveled $originalDriverDistance
            // Without carpool your passenger would've traveled $originalPassengerDistance
            // By taking a carpooler you made did $newDistance
            // So, the economie was : $originalDriverDistance + $originalPassengerDistance - $newDistance
            $savedDistance = $originalDriverDistance + $originalPassengerDistance - $newDistance;
        } else {
            // ********* If you are the passenger :
            // Without carpool you would've traveled $originalPassengerDistance
            // By asking for a driver you cost extra $detourDistance in addition of the driver's original trip
            // So for you, the economie is $originalPassengerDistance - $detour
            $savedDistance = $originalPassengerDistance - $detourDistance;
        }

        // If the is a Ask linked, it's twice the economy (round trip)
        if (!is_null($ask->getAskLinked()) && !$export) {
            $savedDistance *= 2;
        }

        return $this->geoTools->getCO2($savedDistance);
    }

    /**
     * Update user's language.
     */
    public function updateLanguage(User $user, string $code): User
    {
        $language = $this->languageRepository->findOneBy(['code' => $code]);
        $user->setLanguage($language);

        $this->entityManager->persist($user);
        $this->entityManager->flush();

        return $user;
    }

    /**
     * Get the communities where the User is accepted or pending.
     *
     * @return Community[]
     */
    public function getUserCommunities(User $user)
    {
        $communityUsers = $this->communityUserRepository->findBy(['user' => $user, 'status' => [CommunityUser::STATUS_ACCEPTED_AS_MEMBER, CommunityUser::STATUS_ACCEPTED_AS_MODERATOR]]);
        $communities = [];
        if (!is_null($communityUsers) && count($communityUsers) > 0) {
            foreach ($communityUsers as $communityUser) {
                $communities[] = $communityUser->getCommunity();
            }
        }

        return $communities;
    }

    public function checkIfScammer(User $user)
    {
        if ($this->scammerRepository->findOneBy(['email' => $user->getEmail()]) || $this->scammerRepository->findOneBy(['telephone' => $user->getTelephone()])) {
            throw new \Exception('', 1);
        }
    }

    public function getUnreadMessageNumberForResponseInsertion(User $user): User
    {
        return $this->getUnreadMessageNumber($user);
    }

    public function _generateUuid()
    {
        // Generate a random string of bytes
        $bytes = openssl_random_pseudo_bytes(16);

        // Convert the bytes to a hexadecimal string
        $hex = bin2hex($bytes);

        // Format the hexadecimal string as a UUID
        return sprintf(
            '%s-%s-%s-%s-%s',
            substr($hex, 0, 8),
            substr($hex, 8, 4),
            substr($hex, 12, 4),
            substr($hex, 16, 4),
            substr($hex, 20, 12)
        );
    }

    private function _attachUserBySso(User $user, SsoUser $ssoUser): User
    {
        // AutoCreate Autoattach disable. If the User isn't already attached to this SSO provider we reject it
        if (!$ssoUser->hasAutoCreateAccount()) {
            $providers = $this->ssoAccountRepository->getListOfSsoAccountOfAUser($user);
            if (!in_array($ssoUser->getProvider(), $providers)) {
                if (!$ssoUser->hasAutoCreateAccount()) {
                    throw new \LogicException('Autocreate/Autoattach account disable');
                }
            } else {
                $event = new SsoAuthenticationEvent($user, $ssoUser);
                $this->eventDispatcher->dispatch(SsoAuthenticationEvent::NAME, $event);

                return $user;
            }
        }

        // We update the user with ssoId and ssoProvider and return it
        return $this->updateUserSsoProperties($user, $ssoUser);
    }

    private function _createNewUserBySso(SsoUser $ssoUser): User
    {
        // Create a new one
        $user = new User();
        $user->setGivenName($ssoUser->getFirstname());
        $user->setFamilyName($ssoUser->getLastname());
        $user->setEmail($ssoUser->getEmail());

        $ssoAccount = new SsoAccount();
        $ssoAccount->setSsoId($ssoUser->getSub());
        $ssoAccount->setSsoProvider($ssoUser->getProvider());
        $ssoAccount->setCreatedBySso(true);

        $user->addSsoAccount($ssoAccount);

        // Gender
        switch ($ssoUser->getGender()) {
            case SsoUser::GENDER_MALE:$user->setGender(User::GENDER_MALE);

                break;

            case SsoUser::GENDER_FEMALE:$user->setGender(User::GENDER_FEMALE);

                break;

            default: $user->setGender(User::GENDER_OTHER);
        }

        if ('' != trim($ssoUser->getBirthdate())) {
            $user->setBirthDate(\DateTime::createFromFormat('Y-m-d', $ssoUser->getBirthdate()));
        }

        return $this->registerUser($user, true, false, $ssoUser);
    }

    /**
     * Sort function.
     *
     * @param mixed $a
     * @param mixed $b
     */
    private static function sortThread($a, $b)
    {
        if ($a['date'] == $b['date']) {
            return 0;
        }

        return ($a['date'] < $b['date']) ? 1 : -1;
    }

    // Delete images associated to the user
    // deleteBase -> delete the base image and remove the entry
    private function deleteUserImages(User $user)
    {
        foreach ($user->getImages() as $image) {
            $this->imageManager->deleteVersions($image);
            $this->imageManager->deleteBase($image);
        }
    }

    // Delete link between the delete account and his communities
    private function deleteCommunityUsers(User $user)
    {
        $myCommunityUsers = $this->communityUserRepository->findBy(['user' => $user]);

        foreach ($myCommunityUsers as $myCommunityUser) {
            $this->entityManager->remove($myCommunityUser);
        }
        $this->entityManager->flush();
    }

    /**
     * Sanitize a string by replacing non-letter or digits by letters or digits.
     *
     * @param string $string The string to sanitize
     *
     * @return string The sanitized string
     */
    private function sanitizeString(string $string)
    {
        return preg_replace('/[^\w]/', $this->getRandomChar(), $string);
    }

    /**
     * Get a random letter or digit.
     *
     * @return string A letter or digit
     */
    private function getRandomChar()
    {
        $seed = str_split('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');

        return $seed[array_rand($seed)];
    }

    /**
     * Get the number of unread message of a User.
     */
    private function getUnreadMessageNumber(User $user): User
    {
        $user->setUnreadCarpoolMessageNumber(0);
        $user->setUnreadDirectMessageNumber(0);
        $user->setUnreadSolidaryMessageNumber(0);

        $recipients = $this->messageRepository->findUnreadMessages($user);
        foreach ($recipients as $recipient) {
            if (!is_null($recipient->getMessage()->getSolidaryAskHistory())) {
                $user->setUnreadSolidaryMessageNumber($user->getUnreadSolidaryMessageNumber() + 1);
            } elseif (!is_null($recipient->getMessage()->getAskHistory())) {
                $user->setUnreadCarpoolMessageNumber($user->getUnreadCarpoolMessageNumber() + 1);
            } else {
                $user->setUnreadDirectMessageNumber($user->getUnreadDirectMessageNumber() + 1);
            }
        }

        return $user;
    }

    /**
     * Check if the user is not underaged.
     */
    private function checkBirthDate(User $user): User
    {
        if (is_null($user->getBirthDate()) || '' === $user->getBirthDate()) {
            return $user;
        }

        $today = new \DateTime('now');
        if ($user->getBirthDate()->format('Y-m-d') == $today->format('Y-m-d')) {
            return $user;
        }

        if ($user->getBirthDate()->diff($today)->y >= $this->userMinAge) {
            return $user;
        }

        throw new UserUnderAgeException(UserUnderAgeException::USER_UNDER_AGED);
    }

    private function checkDriverLicenceNumber(User $user)
    {
        $drivingLicenceService = new DrivingLicenceService($user->getDrivingLicenceNumber());

        if (!$drivingLicenceService->isDrivingLicenceNumberValid()) {
            $user->setDrivingLicenceNumber(null);
        }
    }
}