vendor/se7enxweb/admin-ui/src/bundle/Controller/NotificationController.php line 233

Open in your IDE?
  1. <?php
  2. /**
  3. * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4. * @license For full copyright and license information view LICENSE file distributed with this source code.
  5. */
  6. declare(strict_types=1);
  7. namespace Ibexa\Bundle\AdminUi\Controller;
  8. use DateTimeInterface;
  9. use Ibexa\AdminUi\Form\Data\Notification\NotificationSelectionData;
  10. use Ibexa\AdminUi\Form\Factory\FormFactory;
  11. use Ibexa\AdminUi\Form\SubmitHandler;
  12. use Ibexa\AdminUi\Form\Type\Notification\SearchType;
  13. use Ibexa\AdminUi\Pagination\Pagerfanta\NotificationAdapter;
  14. use Ibexa\Bundle\AdminUi\Form\Data\SearchQueryData;
  15. use Ibexa\Contracts\AdminUi\Controller\Controller;
  16. use Ibexa\Contracts\AdminUi\Notification\TranslatableNotificationHandlerInterface;
  17. use Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException;
  18. use Ibexa\Contracts\Core\Repository\NotificationService;
  19. use Ibexa\Contracts\Core\Repository\Values\Notification\Query\Criterion;
  20. use Ibexa\Contracts\Core\Repository\Values\Notification\Query\NotificationQuery;
  21. use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
  22. use Ibexa\Core\Notification\Renderer\Registry;
  23. use InvalidArgumentException;
  24. use JMS\TranslationBundle\Annotation\Desc;
  25. use Pagerfanta\Pagerfanta;
  26. use Symfony\Component\Form\FormInterface;
  27. use Symfony\Component\HttpFoundation\JsonResponse;
  28. use Symfony\Component\HttpFoundation\RedirectResponse;
  29. use Symfony\Component\HttpFoundation\Request;
  30. use Symfony\Component\HttpFoundation\Response;
  31. use Symfony\Contracts\Translation\TranslatorInterface;
  32. use Throwable;
  33. final class NotificationController extends Controller
  34. {
  35. protected NotificationService $notificationService;
  36. protected Registry $registry;
  37. protected TranslatorInterface $translator;
  38. private ConfigResolverInterface $configResolver;
  39. private FormFactory $formFactory;
  40. private SubmitHandler $submitHandler;
  41. private TranslatableNotificationHandlerInterface $notificationHandler;
  42. public function __construct(
  43. NotificationService $notificationService,
  44. Registry $registry,
  45. TranslatorInterface $translator,
  46. ConfigResolverInterface $configResolver,
  47. FormFactory $formFactory,
  48. SubmitHandler $submitHandler,
  49. TranslatableNotificationHandlerInterface $notificationHandler
  50. ) {
  51. $this->notificationService = $notificationService;
  52. $this->registry = $registry;
  53. $this->translator = $translator;
  54. $this->configResolver = $configResolver;
  55. $this->formFactory = $formFactory;
  56. $this->submitHandler = $submitHandler;
  57. $this->notificationHandler = $notificationHandler;
  58. }
  59. /**
  60. * @param callable(): JsonResponse $callback
  61. */
  62. private function handleJsonErrors(callable $callback): JsonResponse
  63. {
  64. try {
  65. return $callback();
  66. } catch (NotFoundException $exception) {
  67. return new JsonResponse([
  68. 'status' => 'failed',
  69. 'error' => $exception->getMessage(),
  70. ], 404);
  71. } catch (Throwable $exception) {
  72. return new JsonResponse([
  73. 'status' => 'failed',
  74. 'error' => 'Unexpected error occurred.',
  75. ], 500);
  76. }
  77. }
  78. public function getNotificationsAction(Request $request, int $offset, int $limit): JsonResponse
  79. {
  80. return $this->handleJsonErrors(function () use ($offset, $limit) {
  81. $notificationList = $this->notificationService->loadNotifications($offset, $limit);
  82. return new JsonResponse([
  83. 'pending' => $this->notificationService->getPendingNotificationCount(),
  84. 'total' => $notificationList->totalCount,
  85. 'notifications' => $notificationList->items,
  86. ]);
  87. });
  88. }
  89. public function renderNotificationsPageAction(Request $request, int $page): Response
  90. {
  91. $searchForm = $this->createForm(SearchType::class);
  92. $searchForm->handleRequest($request);
  93. $query = $this->getNotificationQuery($request, $searchForm);
  94. $pagerfanta = new Pagerfanta(
  95. new NotificationAdapter($this->notificationService, $query)
  96. );
  97. $limit = $this->configResolver->getParameter('pagination.notification_limit');
  98. $pagerfanta->setMaxPerPage($limit);
  99. $pagerfanta->setCurrentPage(min($page, $pagerfanta->getNbPages()));
  100. $notifications = $this->renderNotificationsPage($pagerfanta);
  101. $deleteForm = $this->formFactory->deleteNotification($this->createNotificationSelectionData($pagerfanta));
  102. $template = $request->attributes->get('template', '@ibexadesign/account/notifications/list.html.twig');
  103. return $this->render($template, [
  104. 'notifications' => $notifications,
  105. 'sidebarNotifications' => $this->renderNotifications($this->notificationService->loadNotifications(0, $limit)->items),
  106. 'notifications_count_interval' => $this->configResolver->getParameter('notification_count.interval'),
  107. 'pager' => $pagerfanta,
  108. 'search_form' => $searchForm->createView(),
  109. 'delete_form' => $deleteForm->createView(),
  110. ]);
  111. }
  112. /**
  113. * @param \Symfony\Component\Form\FormInterface<SearchQueryData|null> $searchForm
  114. */
  115. private function getNotificationQuery(Request $request, FormInterface $searchForm): NotificationQuery
  116. {
  117. $session = $request->getSession();
  118. if ($searchForm->isSubmitted() && $searchForm->isValid()) {
  119. $data = $searchForm->getData();
  120. $session->set('notifications_filters', $data);
  121. return $this->buildQuery($data);
  122. }
  123. $data = $session->get('notifications_filters');
  124. if ($data !== null) {
  125. $searchForm->setData($data);
  126. return $this->buildQuery($data);
  127. }
  128. return new NotificationQuery();
  129. }
  130. /**
  131. * Renders notifications from any iterable source or Pagerfanta page.
  132. *
  133. * @param iterable<\Ibexa\Contracts\Core\Repository\Values\Notification\Notification> $notifications
  134. *
  135. * @return string[]
  136. */
  137. private function renderNotifications(iterable $notifications): array
  138. {
  139. $result = [];
  140. foreach ($notifications as $notification) {
  141. if ($this->registry->hasRenderer($notification->type)) {
  142. $result[] = $this->registry->getRenderer($notification->type)->render($notification);
  143. }
  144. }
  145. return $result;
  146. }
  147. /**
  148. * Renders current page of Pagerfanta notifications.
  149. *
  150. * @param \Pagerfanta\Pagerfanta<\Ibexa\Contracts\Core\Repository\Values\Notification\Notification> $pagerfanta
  151. *
  152. * @return string[]
  153. */
  154. private function renderNotificationsPage(Pagerfanta $pagerfanta): array
  155. {
  156. return $this->renderNotifications($pagerfanta->getCurrentPageResults());
  157. }
  158. private function buildQuery(?SearchQueryData $data): NotificationQuery
  159. {
  160. if ($data === null) {
  161. return new NotificationQuery([]);
  162. }
  163. $criteria = [];
  164. if ($data->getType()) {
  165. $criteria[] = new Criterion\Type($data->getType());
  166. }
  167. if (!empty($data->getStatuses())) {
  168. $criteria[] = new Criterion\Status($data->getStatuses());
  169. }
  170. $range = $data->getCreatedRange();
  171. if ($range !== null) {
  172. $min = $range->getMin() instanceof DateTimeInterface ? $range->getMin() : null;
  173. $max = $range->getMax() instanceof DateTimeInterface ? $range->getMax() : null;
  174. if ($min !== null || $max !== null) {
  175. $criteria[] = new Criterion\DateCreated($min, $max);
  176. }
  177. }
  178. return new NotificationQuery($criteria);
  179. }
  180. /**
  181. * @param \Pagerfanta\Pagerfanta<\Ibexa\Contracts\Core\Repository\Values\Notification\Notification> $pagerfanta
  182. */
  183. private function createNotificationSelectionData(Pagerfanta $pagerfanta): NotificationSelectionData
  184. {
  185. $notifications = [];
  186. foreach ($pagerfanta->getCurrentPageResults() as $notification) {
  187. $notifications[$notification->id] = false;
  188. }
  189. return new NotificationSelectionData($notifications);
  190. }
  191. public function countNotificationsAction(): JsonResponse
  192. {
  193. return $this->handleJsonErrors(fn () => new JsonResponse([
  194. 'pending' => $this->notificationService->getPendingNotificationCount(),
  195. 'total' => $this->notificationService->getNotificationCount(),
  196. ]));
  197. }
  198. /**
  199. * We're not able to establish two-way stream (it requires additional
  200. * server service for websocket connection), so * we need a way to mark notification
  201. * as read. AJAX call is fine.
  202. */
  203. public function markNotificationAsReadAction(Request $request, int $notificationId): JsonResponse
  204. {
  205. return $this->handleJsonErrors(function () use ($notificationId) {
  206. $notification = $this->notificationService->getNotification($notificationId);
  207. $this->notificationService->markNotificationAsRead($notification);
  208. $data = ['status' => 'success'];
  209. if ($this->registry->hasRenderer($notification->type)) {
  210. $url = $this->registry->getRenderer($notification->type)->generateUrl($notification);
  211. if ($url) {
  212. $data['redirect'] = $url;
  213. }
  214. }
  215. return new JsonResponse($data);
  216. });
  217. }
  218. public function markNotificationsAsReadAction(Request $request): JsonResponse
  219. {
  220. return $this->handleJsonErrors(function () use ($request) {
  221. $ids = $request->toArray()['ids'] ?? [];
  222. if (empty($ids)) {
  223. throw new InvalidArgumentException('Missing or invalid "ids" parameter.');
  224. }
  225. $this->notificationService->markUserNotificationsAsRead($ids);
  226. return new JsonResponse([
  227. 'status' => 'success',
  228. 'redirect' => $this->generateUrl('ibexa.notifications.render.all'),
  229. ]);
  230. });
  231. }
  232. public function markAllNotificationsAsReadAction(Request $request): JsonResponse
  233. {
  234. return $this->handleJsonErrors(function () {
  235. $this->notificationService->markUserNotificationsAsRead();
  236. return new JsonResponse(['status' => 'success']);
  237. });
  238. }
  239. public function markNotificationAsUnreadAction(Request $request, int $notificationId): JsonResponse
  240. {
  241. return $this->handleJsonErrors(function () use ($notificationId) {
  242. $notification = $this->notificationService->getNotification($notificationId);
  243. $this->notificationService->markNotificationAsUnread($notification);
  244. return new JsonResponse(['status' => 'success']);
  245. });
  246. }
  247. public function deleteNotificationAction(Request $request, int $notificationId): JsonResponse
  248. {
  249. return $this->handleJsonErrors(function () use ($notificationId) {
  250. $notification = $this->notificationService->getNotification($notificationId);
  251. $this->notificationService->deleteNotification($notification);
  252. return new JsonResponse(['status' => 'success']);
  253. });
  254. }
  255. public function deleteNotificationsAction(Request $request): Response
  256. {
  257. $form = $this->formFactory->deleteNotification();
  258. $form->handleRequest($request);
  259. if (!$form->isSubmitted()) {
  260. return $this->redirectToRoute('ibexa.notifications.render.all');
  261. }
  262. if ($form->isValid()) {
  263. $result = $this->submitHandler->handle(
  264. $form,
  265. function (NotificationSelectionData $data): Response {
  266. return $this->processDeleteNotifications($data);
  267. }
  268. );
  269. return $result ?? $this->redirectToRoute('ibexa.notifications.render.all');
  270. }
  271. $this->notificationHandler->error(
  272. /** @Desc("An unexpected error occurred while deleting notifications.") */
  273. 'error.unexpected_delete_notifications',
  274. [],
  275. 'ibexa_notifications'
  276. );
  277. return $this->redirectToRoute('ibexa.notifications.render.all');
  278. }
  279. private function processDeleteNotifications(NotificationSelectionData $data): RedirectResponse
  280. {
  281. foreach (array_keys($data->getNotifications()) as $id) {
  282. $notification = $this->notificationService->getNotification((int)$id);
  283. $this->notificationService->deleteNotification($notification);
  284. }
  285. return $this->redirectToRoute('ibexa.notifications.render.all');
  286. }
  287. }
  288. class_alias(NotificationController::class, 'EzSystems\EzPlatformAdminUiBundle\Controller\NotificationController');