<?php
namespace App\Controller\Back;
use App\Form\User\UserFirstConnexionType;
use App\Repository\AbonnementRepository;
use App\Repository\FactureRepository;
use App\Repository\GarageRepository;
use App\Repository\LogsGarageRepository;
use App\Repository\OccupationGaragesRepository;
use App\Repository\OffreRepository;
use App\Repository\ParameterRepository;
use App\Repository\ZChartRevenuesRepository;
use App\Repository\ZChartSubscribersRepository;
use App\Repository\ZChartUsageRepository;
use App\Repository\ZChartUsersRepository;
use App\Repository\ZShelterMonthlyOccupationRepository;
use App\Security\JwtAuthenticator;
use App\Service\ShelterService;
use App\Services\ParameterService;
use App\Utils\DateCalculator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\GoneHttpException;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class AccueilController extends AbstractController
{
public function __construct(
private readonly ParameterRepository $parameterRepository,
private readonly GarageRepository $shelterRepository,
private readonly FactureRepository $invoiceRepository,
private readonly AbonnementRepository $subscriptionRepository,
private readonly OffreRepository $offerRepository,
private readonly LogsGarageRepository $logsGarageRepository,
private readonly OccupationGaragesRepository $occupationGaragesRepository,
private readonly ZShelterMonthlyOccupationRepository $shelterMonthlyOccupationRepository,
private readonly ParameterService $parameterService,
private readonly EntityManagerInterface $em,
private readonly DateCalculator $dateCalculator,
private readonly ZChartRevenuesRepository $chartRevenuesRepository,
private readonly ZChartUsageRepository $chartUsageRepository,
private readonly ZChartUsersRepository $chartUsersRepository,
private readonly ZChartSubscribersRepository $chartSubscribersRepository,
private readonly KernelInterface $kernelInterface,
) {}
/**
* @Route("/connexion", name="connexion")
*/
public function connexion(AuthenticationUtils $authenticationUtils, Request $request)
{
if ($this->getUser() && $this->isGranted('IS_REMEMBERED')) {
return $this->redirectToRoute('home');
}
// Parameters management
$parameters = $this->parameterRepository->findAll();
$request->getSession()->set('params', $parameters);
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
if ($request->query->has('error')) {
$error = new BadCredentialsException($request->query->get('error'));
}
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
if ($request->query->has('lastUsername')) {
$lastUsername = $request->query->get('lastUsername');
}
return $this->render('login/index.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
/**
* @Route("/first-connexion/{jwt}", name="first_connexion")
*/
public function firstConnexion(
string $jwt,
JwtAuthenticator $jwtAuthenticator,
UserPasswordHasherInterface $passwordHasher,
Request $request
) {
try {
$user = $jwtAuthenticator->checkJwt($jwt);
} catch (\Exception $e) {
throw new GoneHttpException('token invalid :: ' . $e->getMessage());
}
$form = $this->createForm(UserFirstConnexionType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$hashedPassword = $passwordHasher->hashPassword(
$user,
$user->getPassword()
);
$user->setPassword($hashedPassword);
$user->setIsActive(true);
$user->deleteEmailAuthCode();
$this->addFlash('success', 'success.firstConnexionSuccess');
$this->em->persist($user);
$this->em->flush();
return $this->redirectToRoute('connexion');
}
return $this->render('login/first-connexion.html.twig', [
'form' => $form->createView(),
'user' => $user,
]);
}
/**
* @Route("/accueil", name="home")
*/
public function home()
{
if ('1' === $this->parameterService->get('sensorModeOnly')) {
return $this->redirectToRoute('homeSensorModeOnly');
}
try {
if (0 === count($this->chartRevenuesRepository->findAll())) {
$this->preloadChartsCommand(
$this->kernelInterface,
'app:preloadDashboardChartsCommand'
);
}
} catch (\Exception $e) {
$this->redirectToRoute('liste_abris');
}
$occupiedPlaces = 0;
$totalAbundance = 0;
$nbSubMax = 0;
$nbSheltersLimited = 0;
$subscriptionLimited = false;
$abundance = 0;
$garages = $this->shelterRepository->getAllGarages();
foreach ($garages as $garage) {
if (-1 !== $garage->getNbTheoriqueAbo()) {
$subscriptionLimited = true;
$nbSubMax += $garage->getNbTheoriqueAbo();
$abundance = ($garage->getNbTheoriqueAbo() / $garage->getCapacite()) * 100;
$totalAbundance += $abundance;
++$nbSheltersLimited;
}
}
if (0 < $nbSheltersLimited) {
$abundance = round($totalAbundance / $nbSheltersLimited, 1);
}
// capacity graph
$totalCapacity = $this->shelterRepository->getTotalCapacity();
// Occupation graph
$occupations = $this->occupationGaragesRepository->findRecentOccupation();
foreach ($occupations as $occupation) {
$occupiedPlaces += $occupation->getOccupation();
}
$freePlaces = $totalCapacity - $occupiedPlaces;
return $this->render('home/home.html.twig', [
'user' => $this->getUser(),
'activeShelters' => count($this->shelterRepository->getConnectedShelters()),
'totalShelters' => count($this->shelterRepository->getAllGarages()),
'totalCapacity' => $totalCapacity,
'nbAbo' => count($this->subscriptionRepository->getCurrentSubscriptions()),
'nbSubMax' => $nbSubMax,
'abundance' => $abundance,
'occupiedPlaces' => $occupiedPlaces,
'freePlaces' => $freePlaces,
'sensor' => (bool) $this->parameterService->get('capteur'),
'subscriptionLimited' => $subscriptionLimited,
]);
}
/**
* @Route("/accueil-captage", name="homeSensorModeOnly")
*/
public function homeSensorModeOnly(ShelterService $shelterService)
{
try {
if (0 === count($this->shelterMonthlyOccupationRepository->findAll())) {
$this->preloadChartsCommand(
$this->kernelInterface,
'app:preloadDataChartsCommand'
);
}
} catch (\Exception $e) {
$this->redirectToRoute('liste_abris');
}
$shelters = $this->shelterRepository->findAll();
$occupiedPlaces = 0;
$totalCapacity = (int) $this->shelterRepository->getTotalCapacity();
$occupations = $this->occupationGaragesRepository->findRecentOccupation();
foreach ($occupations as $occupation) {
$occupiedPlaces += $occupation->getOccupation();
}
$freePlaces = $totalCapacity - $occupiedPlaces;
$sheltersOccupation = $shelterService->getSheltersOccupationData($shelters);
return $this->render('home/sensor_mode_only.html.twig', [
'occupiedPlaces' => $occupiedPlaces,
'freePlaces' => $freePlaces,
'totalCapacity' => $totalCapacity,
'shelters' => $sheltersOccupation,
]);
}
/**
* @Route("/accueil-captage/getSheltersOccupation",
* name="homeGetSheltersOccupation",
* options={"expose"=true},
* methods={"GET"}
* )
*/
public function getSheltersOccupation(ShelterService $shelterService)
{
$shelters = $this->shelterRepository->findAll();
$sheltersOccupation = $shelterService->getSheltersOccupationData($shelters);
return new JsonResponse($sheltersOccupation);
}
/**
* @Route("/accueil-captage/last24hOccupation",
* name="homeLast24hOccupation",
* options={"expose"=true},
* methods={"GET"}
* )
*/
public function getLast24hOccupation()
{
$data = $this->occupationGaragesRepository->getOccupationData(
'%Y-%m-%d %H:00:00',
'-24 hours',
'hour'
);
$maxCapacity = (int) $this->shelterRepository->getTotalCapacity();
$mappedData = $this->mapOccupationData($data, 'H', 'hour', $maxCapacity, 'h00');
return new JsonResponse($mappedData);
}
/**
* @Route("/accueil-captage/last8DaysOccupation",
* name="homeLast8DaysOccupation",
* options={"expose"=true},
* methods={"GET"}
* )
*/
public function getLast8DaysOccupation()
{
$data = $this->occupationGaragesRepository->getOccupationData(
'%Y-%m-%d',
'-8 days',
'day'
);
$maxCapacity = (int) $this->shelterRepository->getTotalCapacity();
$mappedData = $this->mapOccupationData($data, 'EEE', 'day', $maxCapacity);
return new JsonResponse($mappedData);
}
/**
* @Route("/accueil-captage/last12MonthsOccupation",
* name="homeLast12MonthsOccupation",
* options={"expose"=true},
* methods={"GET"}
* )
*/
public function getLast12MonthsOccupation()
{
$data = $this->mergeCurrentAndAnnualOccupationData();
return new JsonResponse($data);
}
/**
* @Route(
* "/accueil/getRevenues",
* name="homeRevenues",
* options={"expose"=true},
* methods={"GET"})
*
* @return JsonResponse
*/
public function getRevenues()
{
$now = (new \DateTime());
$firstDayOfThisMonth = $this->dateCalculator->fromNow('first day of this month');
$monthRevenues = $this->invoiceRepository->getRevenuesByDateRange($firstDayOfThisMonth, $now);
$lastMonth = $this->chartRevenuesRepository->getLastMonth();
$year = $this->chartRevenuesRepository->getRevenuesFromStartOfYear() + $monthRevenues;
return new JsonResponse([
'month' => $monthRevenues,
'lastMonth' => $lastMonth ? $lastMonth : 0,
'year' => $year ? $year : 0,
]);
}
/**
* @Route(
* "/accueil/getOffersDistribution",
* name="homeOffersDistribution",
* options={"expose"=true},
* methods={"GET"}
* )
*/
public function getOffersDistribution()
{
$offerArray = [];
$offers = $this->offerRepository->getOffresVisible();
foreach ($offers as $offer) {
$abos = $this->subscriptionRepository->findByOffer($offer);
if (array_key_exists($offer->getDescription(), $offerArray)) {
$offerArray[$offer->getDescription()] += count($abos);
continue;
}
$offerArray[$offer->getDescription()] = count($abos);
}
arsort($offerArray);
return new JsonResponse($offerArray);
}
/**
* @Route(
* "/accueil/getConnectedShelters",
* name="homeConnectedShelters",
* options={"expose"=true},
* methods={"GET"}
* )
*/
public function getConnectedShelters()
{
$connectedShelters = count($this->shelterRepository->getConnectedShelters());
$totalShelters = count($this->shelterRepository->getAllGarages());
$res = ($connectedShelters / $totalShelters) * 100;
return new JsonResponse($res);
}
/**
* @Route(
* "/accueil/getUsage",
* name="homeGetUsage",
* options={"expose"=true},
* methods={"GET"}
* )
*/
public function getUsage()
{
$days = [];
for ($i = 0; $i <= 10; ++$i) {
$days[] = $this->dateCalculator->getDatePart($i, 'days', 'E');
}
$logs = $this->chartUsageRepository->getUsage();
$arrayLogs = [];
$arrayUsers = [];
foreach ($logs as $log) {
$arrayLogs[] = $log->getNbAccess();
$arrayUsers[] = $log->getNbUsers();
}
// get data for the current day
$today = $this->dateCalculator->fromNow('today')->format('Y-m-d');
$logsLastDay = $this->logsGarageRepository->getAccessHistoryByDay($today);
$usersLastDay = $this->logsGarageRepository->getUsersByPeriod($today, 'day');
$arrayLogs[count($arrayLogs) - 1] = count($logsLastDay);
$arrayUsers[count($arrayUsers) - 1] = count($usersLastDay);
$dataUsage = [
$arrayLogs,
$arrayUsers,
array_reverse($days),
];
return new JsonResponse($dataUsage);
}
/**
* @Route(
* "/accueil/getSubscription",
* name="homeGetSubscription",
* options={"expose"=true},
* methods={"GET"}
* )
*/
public function getSubscription()
{
$currentSubscriptions = $this->subscriptionRepository->getCurrentSubscriptions();
return new JsonResponse(count($currentSubscriptions));
}
/**
* @Route(
* "/accueil/getOccupation",
* name="homeGetOccupation",
* options={"expose"=true},
* methods={"GET"}
* )
*/
public function getOccupation()
{
$occupiedPlaces = 0;
$occupations = $this->occupationGaragesRepository->findRecentOccupation();
foreach ($occupations as $occupation) {
$occupiedPlaces += $occupation->getOccupation();
}
return new JsonResponse($occupiedPlaces);
}
/**
* @Route(
* "/accueil/getActiveSubscriptions",
* name="homeGetActiveSubscriptions",
* options={"expose"=true},
* methods={"GET"}
* )
*/
public function getActiveSubscriptions()
{
$subscriptionsArray = $offerNames = $months = $dataActiveSubscription = [];
$users = $this->chartUsersRepository->getLast12Months();
$offers = $this->offerRepository->getOffresVisible();
$usersArray = array_map(function ($user) {
return $user->getValue();
}, $users);
foreach ($offers as $offer) {
$offerNames[] = $offer->getDescription();
$logs = $this->chartSubscribersRepository->getLast12Months($offer);
$subscriptionsArray[] = array_map(function ($log) {
return $log->getValue();
}, $logs);
}
for ($i = 0; $i <= 11; ++$i) {
array_push($months, $this->dateCalculator->getDatePart($i, 'month', 'MMM'));
}
$months = array_reverse($months);
// Get last month's data
$today = $this->dateCalculator->fromNow('today')->format('Y-m-d');
$monthlySubsArray = array_map(function ($offer) use ($today) {
return count($this->subscriptionRepository->findByMonthAndOffer($offer, $today));
}, $offers);
foreach ($subscriptionsArray as $index => &$sous_tableau) {
$sous_tableau[count($sous_tableau) - 1] = $monthlySubsArray[$index];
}
$monthlyUsers = $this->logsGarageRepository->getUsersByPeriod($today, 'month');
$usersArray[count($usersArray) - 1] = count($monthlyUsers);
array_push($dataActiveSubscription, $offerNames, $subscriptionsArray, $usersArray, $months);
return new JsonResponse($dataActiveSubscription);
}
public function two_fa(RequestStack $requestStack)
{
$authenticationException = $this->getLastAuthenticationException($requestStack);
$attempts2Fa = $requestStack->getSession()->get('attempts2Fa');
if (3 === $attempts2Fa) {
return $this->redirectToRoute('logout');
}
return $this->render('security/2fa.html.twig', [
'authenticationError' => $authenticationException ? $authenticationException->getMessageKey() : null,
'authenticationErrorData' => $authenticationException ? $authenticationException->getMessageData() : null,
'checkPathUrl' => '/2fa_check',
'authCodeParameterName' => '_auth_code',
'displayTrustedOption' => false,
'isCsrfProtectionEnabled' => false,
]);
}
/**
* @Route("/mentionslegales", name="legalNotices", methods={"GET"})
*/
public function legalNotices()
{
return $this->render('notice/index.html.twig');
}
protected function getLastAuthenticationException(RequestStack $requestStack): ?AuthenticationException
{
$authException = $requestStack->getSession()->get(Security::AUTHENTICATION_ERROR);
if ($authException instanceof AuthenticationException) {
$requestStack->getSession()->remove(Security::AUTHENTICATION_ERROR);
return $authException;
}
return null; // The value does not come from the security component.
}
private function mapOccupationData(
array $occupations,
string $dateFormat,
string $dateKey,
int $maxCapacity,
string $datetimeTermination = ''
): array {
$timezone = $this->dateCalculator->getDateTimeZone();
$formatter = $this->dateCalculator->newIntlDateFormatter($dateFormat);
return [
'labels' => array_map(function ($occupation) use ($formatter, $dateKey, $datetimeTermination, $timezone) {
$date = new \DateTime($occupation[$dateKey], $timezone);
return $formatter->format($date) . $datetimeTermination;
}, $occupations),
'avgOccupation' => array_map(function ($occupation) {
return floor((float) $occupation['avgOccupation']);
}, $occupations),
'minOccupation' => array_map(function ($occupation) {
return floor((int) $occupation['minOccupation']);
}, $occupations),
'maxOccupation' => array_map(function ($occupation) {
return floor((int) $occupation['maxOccupation']);
}, $occupations),
'maxCapacity' => $maxCapacity,
];
}
private function mapShelterMonthlyOccupationData(
array $occupations,
string $dateFormat,
int $maxCapacity
): array {
$formatter = $this->dateCalculator->newIntlDateFormatter($dateFormat);
$monthlyData = [];
foreach ($occupations as $occupation) {
$formattedMonth = $formatter->format($occupation->getMonth());
if (!isset($monthlyData[$formattedMonth])) {
$monthlyData[$formattedMonth] = [
'avgOccupation' => 0,
'minOccupation' => 0,
'maxOccupation' => 0,
];
}
$monthlyData[$formattedMonth]['avgOccupation'] += $occupation->getAvgOccupation();
$monthlyData[$formattedMonth]['minOccupation'] += $occupation->getMinOccupation();
$monthlyData[$formattedMonth]['maxOccupation'] += $occupation->getMaxOccupation();
}
$result = [
'labels' => [],
'avgOccupation' => [],
'minOccupation' => [],
'maxOccupation' => [],
'maxCapacity' => $maxCapacity,
];
foreach ($monthlyData as $month => $data) {
$result['labels'][] = $month;
$result['avgOccupation'][] = $data['avgOccupation'];
$result['minOccupation'][] = $data['minOccupation'];
$result['maxOccupation'][] = $data['maxOccupation'];
}
return $result;
}
private function mergeCurrentAndAnnualOccupationData(): array
{
$last12MonthsData = $this->shelterMonthlyOccupationRepository->getLast12FullMonthsData();
$last12MonthsOccupationArray = $this->mapShelterMonthlyOccupationData(
$last12MonthsData,
'MMM',
(int) $this->shelterRepository->getTotalCapacity()
);
$currentMonthData = $this->occupationGaragesRepository->getOccupationData(
'%Y-%m',
'first day of this month',
'month'
);
$currentMonthOccupationArray = [
'labels' => [],
'avgOccupation' => [],
'minOccupation' => [],
'maxOccupation' => [],
];
if (!empty($currentMonthData)) {
$data = $currentMonthData[0];
$formattedMonth = $this->dateCalculator->newIntlDateFormatter('MMM')
->format(new \DateTime($data['month'] . '-01'))
;
$currentMonthOccupationArray = [
'labels' => [$formattedMonth],
'avgOccupation' => [floor((float) $data['avgOccupation'])],
'minOccupation' => [(int) $data['minOccupation']],
'maxOccupation' => [(int) $data['maxOccupation']],
];
}
return array_merge_recursive($last12MonthsOccupationArray, $currentMonthOccupationArray);
}
private function preloadChartsCommand(
KernelInterface $kernel,
string $command
): void {
$application = new Application($kernel);
$application->setAutoExit(false);
$input = new ArrayInput([
'command' => $command,
]);
$application->run($input);
}
}