src/Controller/DefaultController.php line 50

Open in your IDE?
  1. <?php
  2. namespace MedBrief\MSR\Controller;
  3. use DateTime;
  4. use Doctrine\ORM\EntityManagerInterface;
  5. use Exception;
  6. use MedBrief\MSR\Controller\BaseController as Controller;
  7. use MedBrief\MSR\Entity\Analytics\AnalyticsReport;
  8. use MedBrief\MSR\Entity\Invitation;
  9. use MedBrief\MSR\Entity\Project;
  10. use MedBrief\MSR\Entity\RoleInvitation;
  11. use MedBrief\MSR\Entity\User;
  12. use MedBrief\MSR\Form\ChangePasswordFormType;
  13. use MedBrief\MSR\Form\Filter\MatterFilterType;
  14. use MedBrief\MSR\Form\Order\MatterOrderType;
  15. use MedBrief\MSR\Repository\ProjectRepository;
  16. use MedBrief\MSR\Repository\RoleInvitationRepository;
  17. use MedBrief\MSR\Repository\SystemNotificationRepository;
  18. use MedBrief\MSR\Service\Analytics\AnalyticsService;
  19. use MedBrief\MSR\Service\Analytics\Model\AnalyticsEmbeddedReport;
  20. use MedBrief\MSR\Service\DeviceDetectorService;
  21. use MedBrief\MSR\Service\EntityHelper\UserHelper;
  22. use MedBrief\MSR\Service\LicenceRenewal\RenewalHelperService;
  23. use MedBrief\MSR\Service\Role\RoleParserService;
  24. use MedBrief\MSR\Traits\PaginatorAwareTrait;
  25. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
  26. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
  27. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  28. use Symfony\Component\HttpFoundation\Cookie;
  29. use Symfony\Component\HttpFoundation\JsonResponse;
  30. use Symfony\Component\HttpFoundation\RedirectResponse;
  31. use Symfony\Component\HttpFoundation\Request;
  32. use Symfony\Component\HttpFoundation\Response;
  33. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  34. use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
  35. use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
  36. use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
  37. class DefaultController extends Controller
  38. {
  39. use PaginatorAwareTrait;
  40. /**
  41. * When the user navigates to the MedBrief home page, if they are logged in, then we
  42. * log them out. We then redirect to login. This is per Steve England's
  43. * request that for security purposes, when the landing page is hit, we log
  44. * users out.
  45. */
  46. public function indexAction(): JsonResponse|RedirectResponse
  47. {
  48. // if the user is currently logged in
  49. if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
  50. // Forward to the dashboard. We used to log the user out but this caused more problems than it solved.
  51. return $this->redirectWithAjaxSupport($this->generateUrl('infology_medbrief_dashboard'));
  52. }
  53. // if we have a redirect page specified in the parameters
  54. $redirectUrl = $this->getParameter('root_page_redirect_url');
  55. if ($redirectUrl) {
  56. // redirect the user to it
  57. return $this->redirectWithAjaxSupport($redirectUrl);
  58. }
  59. // otherwise just sent them to login
  60. return $this->redirectWithAjaxSupport($this->generateUrl('login'));
  61. }
  62. /**
  63. * @param EntityManagerInterface $entityManager
  64. * @param AnalyticsService $analyticsService
  65. * @param UserHelper $userHelper
  66. * @param ProjectRepository $projectRepository
  67. * @param RoleInvitationRepository $roleInvitationRepository
  68. * @param SystemNotificationRepository $systemNotificationRepository
  69. * @param RoleParserService $roleParser
  70. * @param RenewalHelperService $renewalHelperService
  71. * @param Request $request
  72. *
  73. * @throws Exception
  74. *
  75. */
  76. public function dashboardAction(
  77. AnalyticsService $analyticsService,
  78. UserHelper $userHelper,
  79. ProjectRepository $projectRepository,
  80. RoleInvitationRepository $roleInvitationRepository,
  81. SystemNotificationRepository $systemNotificationRepository,
  82. RoleParserService $roleParser,
  83. RenewalHelperService $renewalHelperService,
  84. Request $request
  85. ): JsonResponse|RedirectResponse|Response {
  86. // Redirect to pending invitations page if User has pending role invitations.
  87. if ($this->userHasPendingInvitations()) {
  88. return $this->redirectWithAjaxSupport($this->pendingInvitationsUrlWithFlash());
  89. }
  90. /** @var User $user */
  91. $user = $this->getUser();
  92. $userHelper->setUser($user);
  93. // If the user has 2FA enabled and is rate limited, redirect them to the login page.
  94. if ($user->hasMultiMfaEnabled() && $request->getSession()->has('rateLimited')) {
  95. $this->addFlash('danger', 'You have entered the incorrect authentication code too many times. Please try again in fifteen minutes.');
  96. return $this->redirectToRoute('logout');
  97. }
  98. // If the user has 2FA enabled and their IP Address has been blacklisted, redirect them to the login page.
  99. if ($user->hasMultiMfaEnabled() && $request->getSession()->has('ipBlacklisted')) {
  100. $this->addFlash('danger', 'You have entered the incorrect authentication code too many times from this IP Address. Please try again in fifteen minutes.');
  101. return $this->redirectToRoute('logout');
  102. }
  103. $analyticsEmbeddedReport = $analyticsService->generateAnalyticsEmbeddedReport($user, AnalyticsReport::REPORT_TYPE_DASHBOARD);
  104. // We use this to determine if we should display the 'Explore Analytics' button.
  105. $analyticsEmbeddedReportMain = $analyticsService->generateAnalyticsEmbeddedReport($user, AnalyticsReport::REPORT_TYPE_MAIN);
  106. $recentlyViewedProjects = $this->getRecentlyViewedProjects($projectRepository, $userHelper);
  107. $favouriteProjects = $this->getFavouriteProjects();
  108. $recentlyAcceptedProjects = $this->getRecentlyAcceptedProjects($roleInvitationRepository, $roleParser);
  109. $recentlyViewedProjectCount = count($recentlyViewedProjects);
  110. $favouritesCount = count($favouriteProjects);
  111. $recentlyAcceptedCount = count($recentlyAcceptedProjects);
  112. $renewalsCount = $projectRepository->countRenewalsDueWithin30DaysForUser($userHelper);
  113. // Create a combined array of all unique Projects fetched in this controller action.
  114. $uniqueProjects = array_unique(array_merge(
  115. $recentlyViewedProjects,
  116. $favouriteProjects,
  117. $recentlyAcceptedProjects,
  118. ));
  119. $grossTotalProjectsCount = count($uniqueProjects);
  120. // All Projects that have a renewal date
  121. $eligibleProjects = $this->getEligibleRenewalProjects($uniqueProjects, $renewalHelperService);
  122. // Get the latest active system notification
  123. $systemNotification = $systemNotificationRepository->findLatestSystemNotification();
  124. /**
  125. * Create a form we will use to allow the user to Filter this list
  126. */
  127. $filterForm = $this->createForm(MatterFilterType::class, null, [
  128. 'userHelper' => $userHelper,
  129. 'em' => $this->getDoctrine()->getManager(),
  130. ]);
  131. // Create and user a form to order this list
  132. $orderForm = $this->createForm(MatterOrderType::class);
  133. $isExpert = $user->isExpertOnly() || $user->isExpertViewerOnly();
  134. // Order A applies to all user roles, excluding the two expert user roles
  135. // Order B applies to the two expert user roles only
  136. $tabOrder = $isExpert ? 'order-b' : 'order-a';
  137. return $this->renderTurbo('Default/dashboard.html.twig', [
  138. 'eligibleProjectsToDisplayRenewalDate' => $eligibleProjects,
  139. 'filterForm' => $filterForm->createView(),
  140. 'orderForm' => $orderForm->createView(),
  141. 'systemNotification' => $systemNotification,
  142. 'analyticsEmbeddedReport' => $analyticsEmbeddedReport,
  143. 'analyticsEmbeddedReportMain' => $analyticsEmbeddedReportMain,
  144. 'recentlyViewedCount' => $recentlyViewedProjectCount,
  145. 'recentlyAcceptedCount' => $recentlyAcceptedCount,
  146. 'favouritesCount' => $favouritesCount,
  147. 'renewalsCount' => $renewalsCount,
  148. 'grossTotalProjectsCount' => $grossTotalProjectsCount,
  149. 'tabOrder' => $tabOrder,
  150. 'isExpert' => $isExpert,
  151. ]);
  152. }
  153. /**
  154. * List Matters most recently viewed
  155. *
  156. * @Template("Default/Partials/viewedTab.html.twig")
  157. *
  158. * @param RoleInvitationRepository $roleInvitationRepository
  159. * @param RoleParserService $roleParser
  160. * @param UserHelper $userHelper
  161. * @param ProjectRepository $projectRepository
  162. * @param RenewalHelperService $renewalHelperService
  163. *
  164. * @throws Exception
  165. *
  166. */
  167. public function dashboardViewedAjaxAction(
  168. UserHelper $userHelper,
  169. ProjectRepository $projectRepository,
  170. RenewalHelperService $renewalHelperService
  171. ): JsonResponse|RedirectResponse|array {
  172. // Redirect to pending invitations page if User has pending role invitations.
  173. if ($this->userHasPendingInvitations()) {
  174. return $this->redirectWithAjaxSupport($this->pendingInvitationsUrlWithFlash());
  175. }
  176. $userHelper->setUser($this->getUser());
  177. $recentlyViewedProjects = $this->getRecentlyViewedProjects($projectRepository, $userHelper);
  178. // Return if no qualifying Projects
  179. if ($recentlyViewedProjects === []) {
  180. return [
  181. 'blurb' => 'You do not have any recently viewed matters.',
  182. ];
  183. }
  184. // All Projects that have a renewal date
  185. $eligibleProjects = $this->getEligibleRenewalProjects($recentlyViewedProjects, $renewalHelperService);
  186. return [
  187. 'projects' => $recentlyViewedProjects,
  188. 'eligibleProjectsToDisplayRenewalDate' => $eligibleProjects,
  189. 'blurb' => 'Most recently viewed matters (up to 20).',
  190. ];
  191. }
  192. /**
  193. * List the User's favourite Matters
  194. *
  195. * @Template("Default/Partials/favouritesTab.html.twig")
  196. *
  197. * @param Request $request
  198. * @param RenewalHelperService $renewalHelperService
  199. *
  200. * @throws Exception
  201. */
  202. public function dashboardFavouritesAjaxAction(Request $request, RenewalHelperService $renewalHelperService): JsonResponse|RedirectResponse|array
  203. {
  204. // Redirect to pending invitations page if User has pending role invitations.
  205. if ($this->userHasPendingInvitations()) {
  206. return $this->redirectWithAjaxSupport($this->pendingInvitationsUrlWithFlash());
  207. }
  208. $favouriteProjects = $this->getFavouriteProjects();
  209. // Return if no qualifying Projects
  210. if ($favouriteProjects === []) {
  211. return [
  212. 'blurb' => 'You do not have any matters added as a favourite.',
  213. ];
  214. }
  215. // All favourite Projects that have a renewal date
  216. $eligibleProjects = $this->getEligibleRenewalProjects($favouriteProjects, $renewalHelperService);
  217. $projectCount = count($favouriteProjects);
  218. $blurbText = $projectCount === 1 ? '1 Matter added as a favourite.' : sprintf('%d %s', $projectCount, 'Matters added as a favourite.');
  219. // Paginate with a specific page and limit
  220. $pagination = $this->paginator->paginate(
  221. $favouriteProjects, // the array to paginate
  222. $request->query->getInt('page', 1), // current page number, default is 1
  223. 10 // items per page
  224. );
  225. return [
  226. 'pagination' => $pagination,
  227. 'totalProjectCount' => $projectCount,
  228. 'eligibleProjectsToDisplayRenewalDate' => $eligibleProjects,
  229. 'blurb' => $blurbText,
  230. ];
  231. }
  232. /**
  233. * List Matters accepted in the past 30 days
  234. *
  235. * @Template("Default/Partials/invitationsTab.html.twig")
  236. *
  237. * @param Request $request
  238. * @param RoleInvitationRepository $roleInvitationRepository
  239. * @param RoleParserService $roleParser
  240. * @param RenewalHelperService $renewalHelperService
  241. *
  242. * @throws Exception
  243. */
  244. public function dashboardInvitationsAjaxAction(
  245. Request $request,
  246. RoleInvitationRepository $roleInvitationRepository,
  247. RoleParserService $roleParser,
  248. RenewalHelperService $renewalHelperService
  249. ): JsonResponse|RedirectResponse|array {
  250. // Redirect to pending invitations page if User has pending role invitations.
  251. if ($this->userHasPendingInvitations()) {
  252. return $this->redirectWithAjaxSupport($this->pendingInvitationsUrlWithFlash());
  253. }
  254. $recentlyAccepted = $this->getRecentlyAcceptedProjects($roleInvitationRepository, $roleParser);
  255. // Return if no qualifying Projects
  256. if ($recentlyAccepted === []) {
  257. return [
  258. 'blurb' => 'You have not accepted any invitations in the last 30 days.',
  259. ];
  260. }
  261. // All recently accepted Projects that have a renewal date
  262. $eligibleProjects = $this->getEligibleRenewalProjects($recentlyAccepted, $renewalHelperService);
  263. $projectCount = count($recentlyAccepted);
  264. $blurbText = $projectCount === 1 ? '1 Invitation accepted in the last 30 days.' : sprintf('%d %s', $projectCount, 'Invitations accepted in the last 30 days.');
  265. // Paginate with a specific page and limit
  266. $pagination = $this->paginator->paginate(
  267. $recentlyAccepted, // the array to paginate
  268. $request->query->getInt('page', 1), // current page number, default is 1
  269. 10 // items per page
  270. );
  271. return [
  272. 'pagination' => $pagination,
  273. 'totalProjectCount' => $projectCount,
  274. 'eligibleProjectsToDisplayRenewalDate' => $eligibleProjects,
  275. 'blurb' => $blurbText,
  276. ];
  277. }
  278. /**
  279. * List Matters due for renewal
  280. *
  281. * @Template("Default/Partials/renewalsTab.html.twig")
  282. *
  283. * @param Request $request
  284. * @param ProjectRepository $projectRepository
  285. * @param UserHelper $userHelper
  286. *
  287. * @return array
  288. */
  289. public function dashboardRenewalsAjaxAction(Request $request, ProjectRepository $projectRepository, UserHelper $userHelper): RedirectResponse|array
  290. {
  291. // Redirect to pending invitations page if User has pending role invitations.
  292. if ($this->userHasPendingInvitations()) {
  293. return $this->redirect($this->pendingInvitationsUrlWithFlash());
  294. }
  295. $userHelper->setUser($this->getUser());
  296. // All Projects that are due for renewal within the next 30 days
  297. $renewalProjects = $projectRepository->findRenewalsDueWithin30DaysForUser($userHelper);
  298. $projectCount = count($renewalProjects);
  299. $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.');
  300. // Return if no qualifying Projects
  301. if (empty($renewalProjects)) {
  302. return [
  303. 'blurb' => 'There are no matters due for renewal in the next 30 days.',
  304. ];
  305. }
  306. // Paginate with a specific page and limit
  307. $pagination = $this->paginator->paginate(
  308. $renewalProjects, // the array to paginate
  309. $request->query->getInt('page', 1), // current page number, default is 1
  310. 10 // items per page
  311. );
  312. return [
  313. 'pagination' => $pagination,
  314. 'totalProjectCount' => $projectCount,
  315. 'eligibleProjectsToDisplayRenewalDate' => $renewalProjects,
  316. 'blurb' => $blurbText,
  317. ];
  318. }
  319. /**
  320. * @Template("Default/analytics.html.twig")
  321. *
  322. * @Security("is_granted('IS_AUTHENTICATED_REMEMBERED')")
  323. *
  324. * @param AnalyticsService $analyticsService
  325. */
  326. public function analyticsAction(AnalyticsService $analyticsService): RedirectResponse|array
  327. {
  328. $analyticsEmbeddedReport = $analyticsService->generateAnalyticsEmbeddedReport($this->getUser(), AnalyticsReport::REPORT_TYPE_MAIN);
  329. if (!$analyticsEmbeddedReport instanceof AnalyticsEmbeddedReport) {
  330. $this->addFlash('warning', 'No Analytics Report for this case');
  331. return $this->redirectToRoute('infology_medbrief_dashboard');
  332. }
  333. return [
  334. 'analyticsEmbeddedReport' => $analyticsEmbeddedReport,
  335. ];
  336. }
  337. /**
  338. * This action is the click through from the email that is sent to a user asking
  339. * them to finalise their registration in order to enable their account
  340. *
  341. * @Template("Default/enableAccount.html.twig")
  342. *
  343. * @param Request $request
  344. * @param TokenStorageInterface $tokenStorage
  345. * @param EventDispatcherInterface $eventDispatcher
  346. * @param EntityManagerInterface $entityManager
  347. * @param UserPasswordEncoderInterface $userPasswordEncoder
  348. * @param UserHelper $userHelper
  349. *
  350. * @throws Exception
  351. */
  352. public function enableAccountAction(
  353. Request $request,
  354. TokenStorageInterface $tokenStorage,
  355. EventDispatcherInterface $eventDispatcher,
  356. EntityManagerInterface $entityManager,
  357. UserPasswordEncoderInterface $userPasswordEncoder,
  358. UserHelper $userHelper
  359. ): RedirectResponse|array {
  360. // if the user is logged in
  361. if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
  362. // log them out
  363. $tokenStorage->setToken();
  364. $request->getSession()->invalidate();
  365. // redirect them back here
  366. return $this->redirect($request->getRequestUri());
  367. }
  368. // grab the invitation code from the URL
  369. $invitationCode = $request->query->get('i');
  370. // if none was provided, we have a problem
  371. if (empty($invitationCode)) {
  372. throw $this->createNotFoundException('Invitation could not be found.');
  373. }
  374. // try to find a matching invitation
  375. $invitation = $this->getDoctrine()->getRepository(Invitation::class)->findOneByCode($invitationCode);
  376. // if we cant find one, we have a problem
  377. if (!$invitation) {
  378. throw $this->createNotFoundException('Unable to find Invitation.');
  379. }
  380. // try to find a user who matches this invitation
  381. $matchingUser = $entityManager->getRepository(User::class)
  382. ->findOneByEmail($invitation->getEmail())
  383. ;
  384. if (!$matchingUser) {
  385. throw $this->createNotFoundException('Unable to find User.');
  386. }
  387. // if the user is already enabled, then we assume this invitation has been processed already
  388. if ($matchingUser->isEnabled()) {
  389. return $this->redirectToRoute('infology_medbrief_invitation_already_processed');
  390. }
  391. // if we get here then we have found a user who is not yet enabled and may now complete their registration
  392. $form = $this->createForm(ChangePasswordFormType::class, $matchingUser, [
  393. 'action' => $this->generateUrl('infology_medbrief_user_complete_registration', ['id' => $matchingUser->getId()]),
  394. ]);
  395. $form->handleRequest($request);
  396. if ($form->isSubmitted() && $form->isValid()) {
  397. // Accept the Medbrief policies
  398. $userHelper->acceptDeclineMedbriefPolicies($matchingUser);
  399. $encodedPassword = $userPasswordEncoder->encodePassword(
  400. $matchingUser,
  401. $form->get('plainPassword')->getData()
  402. );
  403. // Set the password
  404. $matchingUser->setPassword($encodedPassword);
  405. // set the user enabled now
  406. $matchingUser->setEnabled(true);
  407. $entityManager->flush();
  408. // log the user in
  409. $token = new PostAuthenticationGuardToken($matchingUser, 'main', $matchingUser->getRoles());
  410. $tokenStorage->setToken($token); //now the user is logged in
  411. //now dispatch the login event
  412. $eventDispatcher->dispatch(
  413. new InteractiveLoginEvent($request, $token),
  414. 'security.interactive_login'
  415. );
  416. // Determine if th user requires 2FA to be enabled (if they don't already) and store it in the session
  417. // to prevent navigating to any part of the site, except the 2FA enable page, or logout page.
  418. // @see MedBrief\MSR\EventSubscriber\Enforce2FASubscriber
  419. /** @var User $user */
  420. $user = $this->getUser();
  421. $userHelper->setUser($user);
  422. if ($userHelper->twoFactoryAuthEnabledUser() && !$user->hasMultiMfaEnabled()) {
  423. $request->getSession()->set('enforce-two-factor-authentication-detail', [
  424. '2faRequired' => true,
  425. 'targetPath' => null,
  426. ]);
  427. return $this->redirectToRoute('mfa_enable_force');
  428. }
  429. // If the user has 2FA enabled and is rate limited, redirect them to the login page.
  430. if ($user->hasMultiMfaEnabled() && $request->getSession()->has('rateLimited')) {
  431. $this->addFlash('danger', 'You have entered the incorrect authentication code too many times. Please try again in five minutes.');
  432. return $this->redirectToRoute('logout');
  433. }
  434. // If the user has 2FA enabled and their IP Address has been blacklisted, redirect them to the login page.
  435. if ($user->hasMultiMfaEnabled() && $request->getSession()->has('ipBlacklisted')) {
  436. $this->addFlash('danger', 'You have entered the incorrect authentication code too many times from this IP address. Please try again in fifteen minutes.');
  437. return $this->redirectToRoute('logout');
  438. }
  439. return $this->redirectToRoute('infology_medbrief_dashboard');
  440. }
  441. return [
  442. 'user' => $matchingUser,
  443. 'form' => $form->createView(),
  444. ];
  445. }
  446. /**
  447. * Is the user logged in?
  448. *
  449. *
  450. *
  451. * @param Request $request
  452. *
  453. * @return JsonResponse 1/0
  454. */
  455. public function isUserLoggedInAction(Request $request)
  456. {
  457. // if the user is currently logged in
  458. if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
  459. return new JsonResponse(['status' => 1], 200);
  460. }
  461. return new JsonResponse(['status' => 0], 200);
  462. }
  463. /**
  464. * Refresh the session upon user wishing to remain logged in.
  465. *
  466. *
  467. *
  468. * @param Request $request
  469. *
  470. * @return JsonResponse
  471. */
  472. public function refreshSessionAction(Request $request)
  473. {
  474. // if the user is currently logged in
  475. if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
  476. // Extend the session
  477. try {
  478. $cookie_lifetime = ini_get('session.cookie_lifetime');
  479. if ($cookie_lifetime === false || $cookie_lifetime === '') {
  480. throw new Exception('session.cookie_lifetime not found!');
  481. }
  482. $lifeTime = (int) $cookie_lifetime;
  483. } catch (Exception) {
  484. $lifeTime = $request->getSession()->getMetadataBag()->getLifetime();
  485. }
  486. $request->getSession()->getMetadataBag()->stampNew($lifeTime);
  487. $now = new DateTime();
  488. $expire = $request->getSession()->getMetadataBag()->getCreated() + $request->getSession()->getMetadataBag()->getLifetime();
  489. $response = new JsonResponse([
  490. 'sessionExpire' => $expire,
  491. ]);
  492. $sessionName = $this->getParameter('session.name');
  493. $sessionCookieValue = $request->cookies->get($sessionName);
  494. $sessionCookie = Cookie::create($sessionName, $sessionCookieValue, $expire);
  495. $response->headers->setCookie($sessionCookie);
  496. return $response;
  497. }
  498. return new JsonResponse([], 404);
  499. }
  500. /**
  501. * Refresh the session upon user wishing to remain logged in.
  502. *
  503. *
  504. *
  505. * @param Request $request
  506. *
  507. * @return JsonResponse
  508. */
  509. public function refreshUserSessionAction(Request $request)
  510. {
  511. // if the user is currently logged in
  512. if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
  513. // Expiration time
  514. $expire = time() + (20 * 60 * 1000);
  515. return new JsonResponse([
  516. 'sessionExpire' => $expire,
  517. ]);
  518. }
  519. return new JsonResponse(['status' => 0], Response::HTTP_FORBIDDEN);
  520. }
  521. /**
  522. * Redirect the user to the login page with a message
  523. *
  524. * @param Request $request
  525. */
  526. public function userInactivityLoginRedirectAction(Request $request): RedirectResponse
  527. {
  528. $this->addFlash('warning', 'You have been automatically logged out due to inactivity');
  529. return $this->redirectToRoute('fos_user_security_logout');
  530. }
  531. /**
  532. * Helper function which determines if the user is using a deprecated browser.
  533. *
  534. *
  535. *
  536. * @param Request $request
  537. * @param DeviceDetectorService $deviceDetector
  538. *
  539. * @return bool
  540. */
  541. protected function isDeprecatedBrowser(Request $request, DeviceDetectorService $deviceDetector)
  542. {
  543. $dci = $deviceDetector->getClientInfoFromRequest($request);
  544. if (is_array($dci)) {
  545. // Check if the browser matches IE10 or lower (which we won't support in future).
  546. return $dci['type'] === 'browser' && $dci['short_name'] === 'IE' && floor($dci['version']) <= 10;
  547. }
  548. // If we didn't get an array of results, this is probably an automated action
  549. return true;
  550. }
  551. /**
  552. * Helper function to determine whether the User has any pending Role Invitations
  553. */
  554. private function userHasPendingInvitations(): bool
  555. {
  556. return $this->getUser()->hasUnsuppressedRoleInvitationsPendingApproval();
  557. }
  558. /**
  559. * Generate the Url to the role invitation listing page and
  560. * add a flash message to current session
  561. */
  562. private function pendingInvitationsUrlWithFlash(): string
  563. {
  564. $this->addFlash('info', 'You have pending invitations. Before you continue, please accept or decline all of your invitations.');
  565. return $this->generateUrl('infology_medbrief_role_invitation_list_pending');
  566. }
  567. /**
  568. * Get all Projects the User has recently viewed
  569. *
  570. * @param ProjectRepository $projectRepository
  571. * @param UserHelper $userHelper
  572. * @param int $limit
  573. */
  574. private function getRecentlyViewedProjects(
  575. ProjectRepository $projectRepository,
  576. UserHelper $userHelper,
  577. int $limit = 20
  578. ): array {
  579. return $projectRepository->findRecentlyAccessedByUser($userHelper, $limit);
  580. }
  581. /**
  582. * Get all Projects the User has recently accepted
  583. *
  584. * @param RoleInvitationRepository $roleInvitationRepository
  585. * @param RoleParserService $roleParser
  586. */
  587. private function getRecentlyAcceptedProjects(RoleInvitationRepository $roleInvitationRepository, RoleParserService $roleParser): array
  588. {
  589. /**
  590. * Not Accessed
  591. */
  592. $recentlyAccepted = $roleInvitationRepository->findLatestProjectRoleInvitationsByUser($this->getUser());
  593. // Map Role Invitations to obtain Projects via the RoleParserService
  594. $recentlyAccepted = array_map(fn (RoleInvitation $roleInvitation)
  595. => $roleParser->parseRole($roleInvitation)->getSubject(), $recentlyAccepted);
  596. // Filter out Projects with any type of deleted or archived status
  597. $recentlyAccepted = array_filter($recentlyAccepted, fn ($project): bool => !$project->isCloseInProgressOrComplete());
  598. return $recentlyAccepted;
  599. }
  600. /**
  601. * Get all the User's favourite Projects
  602. */
  603. private function getFavouriteProjects(): array
  604. {
  605. $favouriteProjects = $this->getUser()->getFavouriteProjects()->toArray();
  606. // Filter out deleted and archived Projects
  607. $favouriteProjects = array_filter($favouriteProjects, fn ($project): bool => !$project->isDeleteStatusComplete() && !$project->isArchiveStatusComplete());
  608. // Converts favourite projects to project_id's and removes ones where access is no longer granted
  609. foreach ($favouriteProjects as $key => $project) {
  610. if (!$this->isGranted('READ', $project)) {
  611. unset($favouriteProjects[$key]);
  612. }
  613. }
  614. // TODO: create a UserProject Entity to store the date when the Project was favourited
  615. return $favouriteProjects;
  616. }
  617. /**
  618. * @param Project[] $projects
  619. * @param RenewalHelperService $renewalHelperService
  620. *
  621. * @return Projects[]|[]
  622. */
  623. private function getEligibleRenewalProjects(array $projects, RenewalHelperService $renewalHelperService): array
  624. {
  625. $eligibleProjectsToDisplayRenewalDate = array_filter($projects, fn ($project): ?bool => $renewalHelperService->isMatterEligibleForRenewal($project));
  626. return $this->sortProjectsByDateOrder($eligibleProjectsToDisplayRenewalDate, 'descending');
  627. }
  628. /**
  629. * Sort an array of Projects by their Updated date in specified order.
  630. *
  631. *
  632. *
  633. * @param array $projects
  634. * @param string $order
  635. *
  636. * @return array $projects
  637. */
  638. private function sortProjectsByDateOrder(array $projects, string $order = 'descending'): array
  639. {
  640. // Use usort to sort the array using the custom comparison function
  641. if ($order === 'descending') {
  642. usort($projects, self::compareByDateDescending(...));
  643. } else {
  644. usort($projects, self::compareByDateAscending(...));
  645. }
  646. return $projects;
  647. }
  648. /**
  649. * Callback function for the comparison of two Projects by Updated date Descending.
  650. *
  651. * @param Project $project1
  652. * @param Project $project2
  653. */
  654. private function compareByDateDescending($project1, $project2): bool
  655. {
  656. $date1 = $project1->getUpdated();
  657. $date2 = $project2->getUpdated();
  658. return $date2 <=> $date1;
  659. }
  660. /**
  661. * Callback function for the comparison of two Projects by Updated date Ascending.
  662. *
  663. * @param Project $project1
  664. * @param Project $project2
  665. */
  666. private function compareByDateAscending($project1, $project2): bool
  667. {
  668. $date1 = $project1->getUpdated();
  669. $date2 = $project2->getUpdated();
  670. return $date1 <=> $date2;
  671. }
  672. }