<?php
namespace MedBrief\MSR\Controller;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use MedBrief\MSR\Controller\BaseController as Controller;
use MedBrief\MSR\Entity\Analytics\AnalyticsReport;
use MedBrief\MSR\Entity\Invitation;
use MedBrief\MSR\Entity\Project;
use MedBrief\MSR\Entity\RoleInvitation;
use MedBrief\MSR\Entity\User;
use MedBrief\MSR\Form\ChangePasswordFormType;
use MedBrief\MSR\Form\Filter\MatterFilterType;
use MedBrief\MSR\Form\Order\MatterOrderType;
use MedBrief\MSR\Repository\ProjectRepository;
use MedBrief\MSR\Repository\RoleInvitationRepository;
use MedBrief\MSR\Repository\SystemNotificationRepository;
use MedBrief\MSR\Service\Analytics\AnalyticsService;
use MedBrief\MSR\Service\Analytics\Model\AnalyticsEmbeddedReport;
use MedBrief\MSR\Service\DeviceDetectorService;
use MedBrief\MSR\Service\EntityHelper\UserHelper;
use MedBrief\MSR\Service\LicenceRenewal\RenewalHelperService;
use MedBrief\MSR\Service\Role\RoleParserService;
use MedBrief\MSR\Traits\PaginatorAwareTrait;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
class DefaultController extends Controller
{
use PaginatorAwareTrait;
/**
* When the user navigates to the MedBrief home page, if they are logged in, then we
* log them out. We then redirect to login. This is per Steve England's
* request that for security purposes, when the landing page is hit, we log
* users out.
*/
public function indexAction(): JsonResponse|RedirectResponse
{
// if the user is currently logged in
if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
// Forward to the dashboard. We used to log the user out but this caused more problems than it solved.
return $this->redirectWithAjaxSupport($this->generateUrl('infology_medbrief_dashboard'));
}
// if we have a redirect page specified in the parameters
$redirectUrl = $this->getParameter('root_page_redirect_url');
if ($redirectUrl) {
// redirect the user to it
return $this->redirectWithAjaxSupport($redirectUrl);
}
// otherwise just sent them to login
return $this->redirectWithAjaxSupport($this->generateUrl('login'));
}
/**
* @param EntityManagerInterface $entityManager
* @param AnalyticsService $analyticsService
* @param UserHelper $userHelper
* @param ProjectRepository $projectRepository
* @param RoleInvitationRepository $roleInvitationRepository
* @param SystemNotificationRepository $systemNotificationRepository
* @param RoleParserService $roleParser
* @param RenewalHelperService $renewalHelperService
* @param Request $request
*
* @throws Exception
*
*/
public function dashboardAction(
AnalyticsService $analyticsService,
UserHelper $userHelper,
ProjectRepository $projectRepository,
RoleInvitationRepository $roleInvitationRepository,
SystemNotificationRepository $systemNotificationRepository,
RoleParserService $roleParser,
RenewalHelperService $renewalHelperService,
Request $request
): JsonResponse|RedirectResponse|Response {
// Redirect to pending invitations page if User has pending role invitations.
if ($this->userHasPendingInvitations()) {
return $this->redirectWithAjaxSupport($this->pendingInvitationsUrlWithFlash());
}
/** @var User $user */
$user = $this->getUser();
$userHelper->setUser($user);
// If the user has 2FA enabled and is rate limited, redirect them to the login page.
if ($user->hasMultiMfaEnabled() && $request->getSession()->has('rateLimited')) {
$this->addFlash('danger', 'You have entered the incorrect authentication code too many times. Please try again in fifteen minutes.');
return $this->redirectToRoute('logout');
}
// If the user has 2FA enabled and their IP Address has been blacklisted, redirect them to the login page.
if ($user->hasMultiMfaEnabled() && $request->getSession()->has('ipBlacklisted')) {
$this->addFlash('danger', 'You have entered the incorrect authentication code too many times from this IP Address. Please try again in fifteen minutes.');
return $this->redirectToRoute('logout');
}
$analyticsEmbeddedReport = $analyticsService->generateAnalyticsEmbeddedReport($user, AnalyticsReport::REPORT_TYPE_DASHBOARD);
// We use this to determine if we should display the 'Explore Analytics' button.
$analyticsEmbeddedReportMain = $analyticsService->generateAnalyticsEmbeddedReport($user, AnalyticsReport::REPORT_TYPE_MAIN);
$recentlyViewedProjects = $this->getRecentlyViewedProjects($projectRepository, $userHelper);
$favouriteProjects = $this->getFavouriteProjects();
$recentlyAcceptedProjects = $this->getRecentlyAcceptedProjects($roleInvitationRepository, $roleParser);
$recentlyViewedProjectCount = count($recentlyViewedProjects);
$favouritesCount = count($favouriteProjects);
$recentlyAcceptedCount = count($recentlyAcceptedProjects);
$renewalsCount = $projectRepository->countRenewalsDueWithin30DaysForUser($userHelper);
// Create a combined array of all unique Projects fetched in this controller action.
$uniqueProjects = array_unique(array_merge(
$recentlyViewedProjects,
$favouriteProjects,
$recentlyAcceptedProjects,
));
$grossTotalProjectsCount = count($uniqueProjects);
// All Projects that have a renewal date
$eligibleProjects = $this->getEligibleRenewalProjects($uniqueProjects, $renewalHelperService);
// Get the latest active system notification
$systemNotification = $systemNotificationRepository->findLatestSystemNotification();
/**
* Create a form we will use to allow the user to Filter this list
*/
$filterForm = $this->createForm(MatterFilterType::class, null, [
'userHelper' => $userHelper,
'em' => $this->getDoctrine()->getManager(),
]);
// Create and user a form to order this list
$orderForm = $this->createForm(MatterOrderType::class);
$isExpert = $user->isExpertOnly() || $user->isExpertViewerOnly();
// Order A applies to all user roles, excluding the two expert user roles
// Order B applies to the two expert user roles only
$tabOrder = $isExpert ? 'order-b' : 'order-a';
return $this->renderTurbo('Default/dashboard.html.twig', [
'eligibleProjectsToDisplayRenewalDate' => $eligibleProjects,
'filterForm' => $filterForm->createView(),
'orderForm' => $orderForm->createView(),
'systemNotification' => $systemNotification,
'analyticsEmbeddedReport' => $analyticsEmbeddedReport,
'analyticsEmbeddedReportMain' => $analyticsEmbeddedReportMain,
'recentlyViewedCount' => $recentlyViewedProjectCount,
'recentlyAcceptedCount' => $recentlyAcceptedCount,
'favouritesCount' => $favouritesCount,
'renewalsCount' => $renewalsCount,
'grossTotalProjectsCount' => $grossTotalProjectsCount,
'tabOrder' => $tabOrder,
'isExpert' => $isExpert,
]);
}
/**
* List Matters most recently viewed
*
* @Template("Default/Partials/viewedTab.html.twig")
*
* @param RoleInvitationRepository $roleInvitationRepository
* @param RoleParserService $roleParser
* @param UserHelper $userHelper
* @param ProjectRepository $projectRepository
* @param RenewalHelperService $renewalHelperService
*
* @throws Exception
*
*/
public function dashboardViewedAjaxAction(
UserHelper $userHelper,
ProjectRepository $projectRepository,
RenewalHelperService $renewalHelperService
): JsonResponse|RedirectResponse|array {
// Redirect to pending invitations page if User has pending role invitations.
if ($this->userHasPendingInvitations()) {
return $this->redirectWithAjaxSupport($this->pendingInvitationsUrlWithFlash());
}
$userHelper->setUser($this->getUser());
$recentlyViewedProjects = $this->getRecentlyViewedProjects($projectRepository, $userHelper);
// Return if no qualifying Projects
if ($recentlyViewedProjects === []) {
return [
'blurb' => 'You do not have any recently viewed matters.',
];
}
// All Projects that have a renewal date
$eligibleProjects = $this->getEligibleRenewalProjects($recentlyViewedProjects, $renewalHelperService);
return [
'projects' => $recentlyViewedProjects,
'eligibleProjectsToDisplayRenewalDate' => $eligibleProjects,
'blurb' => 'Most recently viewed matters (up to 20).',
];
}
/**
* List the User's favourite Matters
*
* @Template("Default/Partials/favouritesTab.html.twig")
*
* @param Request $request
* @param RenewalHelperService $renewalHelperService
*
* @throws Exception
*/
public function dashboardFavouritesAjaxAction(Request $request, RenewalHelperService $renewalHelperService): JsonResponse|RedirectResponse|array
{
// Redirect to pending invitations page if User has pending role invitations.
if ($this->userHasPendingInvitations()) {
return $this->redirectWithAjaxSupport($this->pendingInvitationsUrlWithFlash());
}
$favouriteProjects = $this->getFavouriteProjects();
// Return if no qualifying Projects
if ($favouriteProjects === []) {
return [
'blurb' => 'You do not have any matters added as a favourite.',
];
}
// All favourite Projects that have a renewal date
$eligibleProjects = $this->getEligibleRenewalProjects($favouriteProjects, $renewalHelperService);
$projectCount = count($favouriteProjects);
$blurbText = $projectCount === 1 ? '1 Matter added as a favourite.' : sprintf('%d %s', $projectCount, 'Matters added as a favourite.');
// Paginate with a specific page and limit
$pagination = $this->paginator->paginate(
$favouriteProjects, // the array to paginate
$request->query->getInt('page', 1), // current page number, default is 1
10 // items per page
);
return [
'pagination' => $pagination,
'totalProjectCount' => $projectCount,
'eligibleProjectsToDisplayRenewalDate' => $eligibleProjects,
'blurb' => $blurbText,
];
}
/**
* List Matters accepted in the past 30 days
*
* @Template("Default/Partials/invitationsTab.html.twig")
*
* @param Request $request
* @param RoleInvitationRepository $roleInvitationRepository
* @param RoleParserService $roleParser
* @param RenewalHelperService $renewalHelperService
*
* @throws Exception
*/
public function dashboardInvitationsAjaxAction(
Request $request,
RoleInvitationRepository $roleInvitationRepository,
RoleParserService $roleParser,
RenewalHelperService $renewalHelperService
): JsonResponse|RedirectResponse|array {
// Redirect to pending invitations page if User has pending role invitations.
if ($this->userHasPendingInvitations()) {
return $this->redirectWithAjaxSupport($this->pendingInvitationsUrlWithFlash());
}
$recentlyAccepted = $this->getRecentlyAcceptedProjects($roleInvitationRepository, $roleParser);
// Return if no qualifying Projects
if ($recentlyAccepted === []) {
return [
'blurb' => 'You have not accepted any invitations in the last 30 days.',
];
}
// All recently accepted Projects that have a renewal date
$eligibleProjects = $this->getEligibleRenewalProjects($recentlyAccepted, $renewalHelperService);
$projectCount = count($recentlyAccepted);
$blurbText = $projectCount === 1 ? '1 Invitation accepted in the last 30 days.' : sprintf('%d %s', $projectCount, 'Invitations accepted in the last 30 days.');
// Paginate with a specific page and limit
$pagination = $this->paginator->paginate(
$recentlyAccepted, // the array to paginate
$request->query->getInt('page', 1), // current page number, default is 1
10 // items per page
);
return [
'pagination' => $pagination,
'totalProjectCount' => $projectCount,
'eligibleProjectsToDisplayRenewalDate' => $eligibleProjects,
'blurb' => $blurbText,
];
}
/**
* List Matters due for renewal
*
* @Template("Default/Partials/renewalsTab.html.twig")
*
* @param Request $request
* @param ProjectRepository $projectRepository
* @param UserHelper $userHelper
*
* @return array
*/
public function dashboardRenewalsAjaxAction(Request $request, ProjectRepository $projectRepository, UserHelper $userHelper): RedirectResponse|array
{
// Redirect to pending invitations page if User has pending role invitations.
if ($this->userHasPendingInvitations()) {
return $this->redirect($this->pendingInvitationsUrlWithFlash());
}
$userHelper->setUser($this->getUser());
// All Projects that are due for renewal within the next 30 days
$renewalProjects = $projectRepository->findRenewalsDueWithin30DaysForUser($userHelper);
$projectCount = count($renewalProjects);
$blurbText = $projectCount === 1 ? '1 Matter due for renewal within the next 30 days.' : sprintf('%d %s', $projectCount, 'Matters due for renewal within the next 30 days.');
// Return if no qualifying Projects
if (empty($renewalProjects)) {
return [
'blurb' => 'There are no matters due for renewal in the next 30 days.',
];
}
// Paginate with a specific page and limit
$pagination = $this->paginator->paginate(
$renewalProjects, // the array to paginate
$request->query->getInt('page', 1), // current page number, default is 1
10 // items per page
);
return [
'pagination' => $pagination,
'totalProjectCount' => $projectCount,
'eligibleProjectsToDisplayRenewalDate' => $renewalProjects,
'blurb' => $blurbText,
];
}
/**
* @Template("Default/analytics.html.twig")
*
* @Security("is_granted('IS_AUTHENTICATED_REMEMBERED')")
*
* @param AnalyticsService $analyticsService
*/
public function analyticsAction(AnalyticsService $analyticsService): RedirectResponse|array
{
$analyticsEmbeddedReport = $analyticsService->generateAnalyticsEmbeddedReport($this->getUser(), AnalyticsReport::REPORT_TYPE_MAIN);
if (!$analyticsEmbeddedReport instanceof AnalyticsEmbeddedReport) {
$this->addFlash('warning', 'No Analytics Report for this case');
return $this->redirectToRoute('infology_medbrief_dashboard');
}
return [
'analyticsEmbeddedReport' => $analyticsEmbeddedReport,
];
}
/**
* This action is the click through from the email that is sent to a user asking
* them to finalise their registration in order to enable their account
*
* @Template("Default/enableAccount.html.twig")
*
* @param Request $request
* @param TokenStorageInterface $tokenStorage
* @param EventDispatcherInterface $eventDispatcher
* @param EntityManagerInterface $entityManager
* @param UserPasswordEncoderInterface $userPasswordEncoder
* @param UserHelper $userHelper
*
* @throws Exception
*/
public function enableAccountAction(
Request $request,
TokenStorageInterface $tokenStorage,
EventDispatcherInterface $eventDispatcher,
EntityManagerInterface $entityManager,
UserPasswordEncoderInterface $userPasswordEncoder,
UserHelper $userHelper
): RedirectResponse|array {
// if the user is logged in
if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
// log them out
$tokenStorage->setToken();
$request->getSession()->invalidate();
// redirect them back here
return $this->redirect($request->getRequestUri());
}
// grab the invitation code from the URL
$invitationCode = $request->query->get('i');
// if none was provided, we have a problem
if (empty($invitationCode)) {
throw $this->createNotFoundException('Invitation could not be found.');
}
// try to find a matching invitation
$invitation = $this->getDoctrine()->getRepository(Invitation::class)->findOneByCode($invitationCode);
// if we cant find one, we have a problem
if (!$invitation) {
throw $this->createNotFoundException('Unable to find Invitation.');
}
// try to find a user who matches this invitation
$matchingUser = $entityManager->getRepository(User::class)
->findOneByEmail($invitation->getEmail())
;
if (!$matchingUser) {
throw $this->createNotFoundException('Unable to find User.');
}
// if the user is already enabled, then we assume this invitation has been processed already
if ($matchingUser->isEnabled()) {
return $this->redirectToRoute('infology_medbrief_invitation_already_processed');
}
// if we get here then we have found a user who is not yet enabled and may now complete their registration
$form = $this->createForm(ChangePasswordFormType::class, $matchingUser, [
'action' => $this->generateUrl('infology_medbrief_user_complete_registration', ['id' => $matchingUser->getId()]),
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Accept the Medbrief policies
$userHelper->acceptDeclineMedbriefPolicies($matchingUser);
$encodedPassword = $userPasswordEncoder->encodePassword(
$matchingUser,
$form->get('plainPassword')->getData()
);
// Set the password
$matchingUser->setPassword($encodedPassword);
// set the user enabled now
$matchingUser->setEnabled(true);
$entityManager->flush();
// log the user in
$token = new PostAuthenticationGuardToken($matchingUser, 'main', $matchingUser->getRoles());
$tokenStorage->setToken($token); //now the user is logged in
//now dispatch the login event
$eventDispatcher->dispatch(
new InteractiveLoginEvent($request, $token),
'security.interactive_login'
);
// Determine if th user requires 2FA to be enabled (if they don't already) and store it in the session
// to prevent navigating to any part of the site, except the 2FA enable page, or logout page.
// @see MedBrief\MSR\EventSubscriber\Enforce2FASubscriber
/** @var User $user */
$user = $this->getUser();
$userHelper->setUser($user);
if ($userHelper->twoFactoryAuthEnabledUser() && !$user->hasMultiMfaEnabled()) {
$request->getSession()->set('enforce-two-factor-authentication-detail', [
'2faRequired' => true,
'targetPath' => null,
]);
return $this->redirectToRoute('mfa_enable_force');
}
// If the user has 2FA enabled and is rate limited, redirect them to the login page.
if ($user->hasMultiMfaEnabled() && $request->getSession()->has('rateLimited')) {
$this->addFlash('danger', 'You have entered the incorrect authentication code too many times. Please try again in five minutes.');
return $this->redirectToRoute('logout');
}
// If the user has 2FA enabled and their IP Address has been blacklisted, redirect them to the login page.
if ($user->hasMultiMfaEnabled() && $request->getSession()->has('ipBlacklisted')) {
$this->addFlash('danger', 'You have entered the incorrect authentication code too many times from this IP address. Please try again in fifteen minutes.');
return $this->redirectToRoute('logout');
}
return $this->redirectToRoute('infology_medbrief_dashboard');
}
return [
'user' => $matchingUser,
'form' => $form->createView(),
];
}
/**
* Is the user logged in?
*
*
*
* @param Request $request
*
* @return JsonResponse 1/0
*/
public function isUserLoggedInAction(Request $request)
{
// if the user is currently logged in
if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
return new JsonResponse(['status' => 1], 200);
}
return new JsonResponse(['status' => 0], 200);
}
/**
* Refresh the session upon user wishing to remain logged in.
*
*
*
* @param Request $request
*
* @return JsonResponse
*/
public function refreshSessionAction(Request $request)
{
// if the user is currently logged in
if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
// Extend the session
try {
$cookie_lifetime = ini_get('session.cookie_lifetime');
if ($cookie_lifetime === false || $cookie_lifetime === '') {
throw new Exception('session.cookie_lifetime not found!');
}
$lifeTime = (int) $cookie_lifetime;
} catch (Exception) {
$lifeTime = $request->getSession()->getMetadataBag()->getLifetime();
}
$request->getSession()->getMetadataBag()->stampNew($lifeTime);
$now = new DateTime();
$expire = $request->getSession()->getMetadataBag()->getCreated() + $request->getSession()->getMetadataBag()->getLifetime();
$response = new JsonResponse([
'sessionExpire' => $expire,
]);
$sessionName = $this->getParameter('session.name');
$sessionCookieValue = $request->cookies->get($sessionName);
$sessionCookie = Cookie::create($sessionName, $sessionCookieValue, $expire);
$response->headers->setCookie($sessionCookie);
return $response;
}
return new JsonResponse([], 404);
}
/**
* Refresh the session upon user wishing to remain logged in.
*
*
*
* @param Request $request
*
* @return JsonResponse
*/
public function refreshUserSessionAction(Request $request)
{
// if the user is currently logged in
if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
// Expiration time
$expire = time() + (20 * 60 * 1000);
return new JsonResponse([
'sessionExpire' => $expire,
]);
}
return new JsonResponse(['status' => 0], Response::HTTP_FORBIDDEN);
}
/**
* Redirect the user to the login page with a message
*
* @param Request $request
*/
public function userInactivityLoginRedirectAction(Request $request): RedirectResponse
{
$this->addFlash('warning', 'You have been automatically logged out due to inactivity');
return $this->redirectToRoute('fos_user_security_logout');
}
/**
* Helper function which determines if the user is using a deprecated browser.
*
*
*
* @param Request $request
* @param DeviceDetectorService $deviceDetector
*
* @return bool
*/
protected function isDeprecatedBrowser(Request $request, DeviceDetectorService $deviceDetector)
{
$dci = $deviceDetector->getClientInfoFromRequest($request);
if (is_array($dci)) {
// Check if the browser matches IE10 or lower (which we won't support in future).
return $dci['type'] === 'browser' && $dci['short_name'] === 'IE' && floor($dci['version']) <= 10;
}
// If we didn't get an array of results, this is probably an automated action
return true;
}
/**
* Helper function to determine whether the User has any pending Role Invitations
*/
private function userHasPendingInvitations(): bool
{
return $this->getUser()->hasUnsuppressedRoleInvitationsPendingApproval();
}
/**
* Generate the Url to the role invitation listing page and
* add a flash message to current session
*/
private function pendingInvitationsUrlWithFlash(): string
{
$this->addFlash('info', 'You have pending invitations. Before you continue, please accept or decline all of your invitations.');
return $this->generateUrl('infology_medbrief_role_invitation_list_pending');
}
/**
* Get all Projects the User has recently viewed
*
* @param ProjectRepository $projectRepository
* @param UserHelper $userHelper
* @param int $limit
*/
private function getRecentlyViewedProjects(
ProjectRepository $projectRepository,
UserHelper $userHelper,
int $limit = 20
): array {
return $projectRepository->findRecentlyAccessedByUser($userHelper, $limit);
}
/**
* Get all Projects the User has recently accepted
*
* @param RoleInvitationRepository $roleInvitationRepository
* @param RoleParserService $roleParser
*/
private function getRecentlyAcceptedProjects(RoleInvitationRepository $roleInvitationRepository, RoleParserService $roleParser): array
{
/**
* Not Accessed
*/
$recentlyAccepted = $roleInvitationRepository->findLatestProjectRoleInvitationsByUser($this->getUser());
// Map Role Invitations to obtain Projects via the RoleParserService
$recentlyAccepted = array_map(fn (RoleInvitation $roleInvitation)
=> $roleParser->parseRole($roleInvitation)->getSubject(), $recentlyAccepted);
// Filter out Projects with any type of deleted or archived status
$recentlyAccepted = array_filter($recentlyAccepted, fn ($project): bool => !$project->isCloseInProgressOrComplete());
return $recentlyAccepted;
}
/**
* Get all the User's favourite Projects
*/
private function getFavouriteProjects(): array
{
$favouriteProjects = $this->getUser()->getFavouriteProjects()->toArray();
// Filter out deleted and archived Projects
$favouriteProjects = array_filter($favouriteProjects, fn ($project): bool => !$project->isDeleteStatusComplete() && !$project->isArchiveStatusComplete());
// Converts favourite projects to project_id's and removes ones where access is no longer granted
foreach ($favouriteProjects as $key => $project) {
if (!$this->isGranted('READ', $project)) {
unset($favouriteProjects[$key]);
}
}
// TODO: create a UserProject Entity to store the date when the Project was favourited
return $favouriteProjects;
}
/**
* @param Project[] $projects
* @param RenewalHelperService $renewalHelperService
*
* @return Projects[]|[]
*/
private function getEligibleRenewalProjects(array $projects, RenewalHelperService $renewalHelperService): array
{
$eligibleProjectsToDisplayRenewalDate = array_filter($projects, fn ($project): ?bool => $renewalHelperService->isMatterEligibleForRenewal($project));
return $this->sortProjectsByDateOrder($eligibleProjectsToDisplayRenewalDate, 'descending');
}
/**
* Sort an array of Projects by their Updated date in specified order.
*
*
*
* @param array $projects
* @param string $order
*
* @return array $projects
*/
private function sortProjectsByDateOrder(array $projects, string $order = 'descending'): array
{
// Use usort to sort the array using the custom comparison function
if ($order === 'descending') {
usort($projects, self::compareByDateDescending(...));
} else {
usort($projects, self::compareByDateAscending(...));
}
return $projects;
}
/**
* Callback function for the comparison of two Projects by Updated date Descending.
*
* @param Project $project1
* @param Project $project2
*/
private function compareByDateDescending($project1, $project2): bool
{
$date1 = $project1->getUpdated();
$date2 = $project2->getUpdated();
return $date2 <=> $date1;
}
/**
* Callback function for the comparison of two Projects by Updated date Ascending.
*
* @param Project $project1
* @param Project $project2
*/
private function compareByDateAscending($project1, $project2): bool
{
$date1 = $project1->getUpdated();
$date2 = $project2->getUpdated();
return $date1 <=> $date2;
}
}