diff --git a/src/Bundle/ChillMainBundle/ChillMainBundle.php b/src/Bundle/ChillMainBundle/ChillMainBundle.php
index 0416edf76..7a4e563bb 100644
--- a/src/Bundle/ChillMainBundle/ChillMainBundle.php
+++ b/src/Bundle/ChillMainBundle/ChillMainBundle.php
@@ -30,6 +30,7 @@ use Chill\MainBundle\Security\Resolver\CenterResolverInterface;
use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
use Chill\MainBundle\Templating\Entity\CompilerPass as RenderEntityCompilerPass;
+use Chill\MainBundle\Templating\UI\NotificationCounterInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
@@ -53,6 +54,8 @@ class ChillMainBundle extends Bundle
->addTag('chill.search_api_provider');
$container->registerForAutoconfiguration(NotificationHandlerInterface::class)
->addTag('chill_main.notification_handler');
+ $container->registerForAutoconfiguration(NotificationCounterInterface::class)
+ ->addTag('chill.count_notification.user');
$container->addCompilerPass(new SearchableServicesCompilerPass());
$container->addCompilerPass(new ConfigConsistencyCompilerPass());
diff --git a/src/Bundle/ChillMainBundle/Controller/NotificationController.php b/src/Bundle/ChillMainBundle/Controller/NotificationController.php
index dddaf306f..52f58ed68 100644
--- a/src/Bundle/ChillMainBundle/Controller/NotificationController.php
+++ b/src/Bundle/ChillMainBundle/Controller/NotificationController.php
@@ -209,9 +209,6 @@ class NotificationController extends AbstractController
{
$this->denyAccessUnlessGranted(NotificationVoter::NOTIFICATION_SEE, $notification);
- $appendComment = new NotificationComment();
- $appendCommentForm = $this->createForm(NotificationCommentType::class, $appendComment);
-
if ($request->query->has('edit')) {
$commentId = $request->query->getInt('edit');
$editedComment = $notification->getComments()->filter(static function (NotificationComment $c) use ($commentId) {
@@ -222,6 +219,8 @@ class NotificationController extends AbstractController
throw $this->createNotFoundException("Comment with id {$commentId} does not exists nor belong to this notification");
}
+ $this->denyAccessUnlessGranted(NotificationVoter::COMMENT_EDIT, $editedComment);
+
$editedCommentForm = $this->createForm(NotificationCommentType::class, $editedComment);
if (Request::METHOD_POST === $request->getMethod() && 'edit' === $request->request->get('form')) {
@@ -240,26 +239,31 @@ class NotificationController extends AbstractController
}
}
- if (Request::METHOD_POST === $request->getMethod() && 'append' === $request->request->get('form')) {
- $appendCommentForm->handleRequest($request);
+ if ($this->isGranted(NotificationVoter::COMMENT_ADD, $notification)) {
+ $appendComment = new NotificationComment();
+ $appendCommentForm = $this->createForm(NotificationCommentType::class, $appendComment);
- if ($appendCommentForm->isSubmitted() && $appendCommentForm->isValid()) {
- $notification->addComment($appendComment);
- $this->em->persist($appendComment);
- $this->em->flush();
+ if (Request::METHOD_POST === $request->getMethod() && 'append' === $request->request->get('form')) {
+ $appendCommentForm->handleRequest($request);
- $this->addFlash('success', $this->translator->trans('notification.comment_appended'));
+ if ($appendCommentForm->isSubmitted() && $appendCommentForm->isValid()) {
+ $notification->addComment($appendComment);
+ $this->em->persist($appendComment);
+ $this->em->flush();
- return $this->redirectToRoute('chill_main_notification_show', [
- 'id' => $notification->getId(),
- ]);
+ $this->addFlash('success', $this->translator->trans('notification.comment_appended'));
+
+ return $this->redirectToRoute('chill_main_notification_show', [
+ 'id' => $notification->getId(),
+ ]);
+ }
}
}
$response = $this->render('@ChillMain/Notification/show.html.twig', [
'notification' => $notification,
'handler' => $this->notificationHandlerManager->getHandler($notification),
- 'appendCommentForm' => $appendCommentForm->createView(),
+ 'appendCommentForm' => isset($appendCommentForm) ? $appendCommentForm->createView() : null,
'editedCommentForm' => isset($editedCommentForm) ? $editedCommentForm->createView() : null,
'editedCommentId' => $commentId ?? null,
]);
diff --git a/src/Bundle/ChillMainBundle/Notification/Counter/NotificationByUserCounter.php b/src/Bundle/ChillMainBundle/Notification/Counter/NotificationByUserCounter.php
new file mode 100644
index 000000000..8dfd2df8a
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Notification/Counter/NotificationByUserCounter.php
@@ -0,0 +1,95 @@
+cacheItemPool = $cacheItemPool;
+ $this->notificationRepository = $notificationRepository;
+ }
+
+ public function addNotification(UserInterface $u): int
+ {
+ if (!$u instanceof User) {
+ return 0;
+ }
+
+ return $this->countUnreadByUser($u);
+ }
+
+ public function countUnreadByUser(User $user): int
+ {
+ $key = self::generateCacheKeyUnreadNotificationByUser($user);
+
+ $item = $this->cacheItemPool->getItem($key);
+
+ if ($item->isHit()) {
+ return $item->get();
+ }
+
+ $unreads = $this->notificationRepository->countUnreadByUser($user);
+
+ $item
+ ->set($unreads)
+ // keep in cache for 15 minutes
+ ->expiresAfter(60 * 15);
+ $this->cacheItemPool->save($item);
+
+ return $unreads;
+ }
+
+ public static function generateCacheKeyUnreadNotificationByUser(User $user): string
+ {
+ return 'chill_main_notif_unread_by_' . $user->getId();
+ }
+
+ public function onEditNotificationComment(NotificationComment $notificationComment, LifecycleEventArgs $eventArgs): void
+ {
+ $this->resetCacheForNotification($notificationComment->getNotification());
+ }
+
+ public function onPreFlushNotification(Notification $notification, PreFlushEventArgs $eventArgs): void
+ {
+ $this->resetCacheForNotification($notification);
+ }
+
+ private function resetCacheForNotification(Notification $notification): void
+ {
+ $keys = [];
+
+ if (null !== $notification->getSender()) {
+ $keys[] = self::generateCacheKeyUnreadNotificationByUser($notification->getSender());
+ }
+
+ foreach ($notification->getAddressees() as $addressee) {
+ $keys[] = self::generateCacheKeyUnreadNotificationByUser($addressee);
+ }
+
+ $this->cacheItemPool->deleteItems($keys);
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php b/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php
index 68c6b8be6..ce9e3b9e9 100644
--- a/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php
+++ b/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php
@@ -50,12 +50,13 @@ final class NotificationRepository implements ObjectRepository
public function countUnreadByUser(User $user): int
{
- $sql = 'SELECT count(*) AS c FROM chill_main_notification_addresses_unread WHERE user_id = ?';
+ $sql = 'SELECT count(*) AS c FROM chill_main_notification_addresses_unread WHERE user_id = :userId';
$rsm = new Query\ResultSetMapping();
$rsm->addScalarResult('c', 'c', Types::INTEGER);
- $nq = $this->em->createNativeQuery($sql, $rsm);
+ $nq = $this->em->createNativeQuery($sql, $rsm)
+ ->setParameter('userId', $user->getId());
return $nq->getSingleScalarResult();
}
diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/list.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/list.html.twig
index 836883846..0c0a75312 100644
--- a/src/Bundle/ChillMainBundle/Resources/views/Notification/list.html.twig
+++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/list.html.twig
@@ -60,15 +60,29 @@
{% if not notification.isReadBy(app.user) %}
{{ 'notification.is_unread'|trans }}
{% endif %}
+ {% if notification.isSystem %}
+ {{ 'notification.is_system'|trans }}
+ {% endif %}
+
- {% if step == 'inbox' %}
- {{ 'notification.from'|trans }}: {{ notification.sender|chill_entity_render_string }}
+ {% if step == 'inbox' and not notification.isSystem %}
+ {{ 'notification.from'|trans }}: {{ notification.sender|chill_entity_render_string }}
+ {% else %}
+
{% endif %}
-
{{ 'notification.adressees'|trans }}{% for a in notification.addressees %}{{ a|chill_entity_render_string }}{% if not loop.last %}, {% endif %}{% endfor %}
+
{{ 'notification.adressees'|trans }}
+
+ {% for a in notification.addressees %}
+ -
+ {{ a|chill_entity_render_string }}
+
+ {% endfor %}
+
+
{{ notification.date|format_datetime('long', 'short') }}
diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/show.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/show.html.twig
index 96357233a..271def828 100644
--- a/src/Bundle/ChillMainBundle/Resources/views/Notification/show.html.twig
+++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/show.html.twig
@@ -65,7 +65,7 @@
{% endfor %}
{% endif %}
- {% if appendCommentForm is defined %}
+ {% if appendCommentForm is not null %}
{{ form_start(appendCommentForm) }}
{{ form_widget(appendCommentForm) }}
diff --git a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php
index 74a8ff31d..ae2eb1f2c 100644
--- a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php
+++ b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php
@@ -12,18 +12,25 @@ declare(strict_types=1);
namespace Chill\MainBundle\Routing\MenuBuilder;
use Chill\MainBundle\Entity\User;
+use Chill\MainBundle\Notification\Counter\NotificationByUserCounter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
class UserMenuBuilder implements LocalMenuBuilderInterface
{
+ private NotificationByUserCounter $notificationByUserCounter;
+
private Security $security;
private TranslatorInterface $translator;
- public function __construct(Security $security, TranslatorInterface $translator)
- {
+ public function __construct(
+ NotificationByUserCounter $notificationByUserCounter,
+ Security $security,
+ TranslatorInterface $translator
+ ) {
+ $this->notificationByUserCounter = $notificationByUserCounter;
$this->security = $security;
$this->translator = $translator;
}
@@ -49,14 +56,17 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
'icon' => 'map-marker',
]);
+ $nbNotifications = $this->notificationByUserCounter->countUnreadByUser($user);
+
$menu
->addChild(
- $this->translator->trans('My notifications'),
+ $this->translator->trans('notification.My notifications with counter', ['nb' => $nbNotifications]),
['route' => 'chill_main_notification_my']
)
->setExtras([
'order' => 600,
'icon' => 'envelope',
+ 'counter' => $nbNotifications,
]);
$menu
diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/NotificationVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/NotificationVoter.php
index d0b283c24..0a6c239f4 100644
--- a/src/Bundle/ChillMainBundle/Security/Authorization/NotificationVoter.php
+++ b/src/Bundle/ChillMainBundle/Security/Authorization/NotificationVoter.php
@@ -20,6 +20,13 @@ use UnexpectedValueException;
final class NotificationVoter extends Voter
{
+ /**
+ * Allow to add a comment on a notification.
+ *
+ * May apply on both @see{NotificationComment::class} and @see{Notification::class}.
+ */
+ public const COMMENT_ADD = 'CHILL_MAIN_NOTIFICATION_COMMENT_ADD';
+
public const COMMENT_EDIT = 'CHILL_MAIN_NOTIFICATION_COMMENT_EDIT';
public const NOTIFICATION_SEE = 'CHILL_MAIN_NOTIFICATION_SEE';
@@ -47,20 +54,30 @@ final class NotificationVoter extends Voter
if ($subject instanceof Notification) {
switch ($attribute) {
+ case self::COMMENT_ADD:
+ return false === $subject->isSystem() && (
+ $subject->getAddressees()->contains($user) || $subject->getSender() === $user
+ );
+
case self::NOTIFICATION_SEE:
case self::NOTIFICATION_TOGGLE_READ_STATUS:
return $subject->getSender() === $user || $subject->getAddressees()->contains($user);
case self::NOTIFICATION_UPDATE:
- return $subject->getSender() === $user;
+ return $subject->getSender() === $user && false === $subject->isSystem();
default:
throw new UnexpectedValueException("this subject {$attribute} is not implemented");
}
} elseif ($subject instanceof NotificationComment) {
switch ($attribute) {
+ case self::COMMENT_ADD:
+ return false === $subject->getNotification()->isSystem() && (
+ $subject->getNotification()->getAddressees()->contains($user) || $subject->getNotification()->getSender() === $user
+ );
+
case self::COMMENT_EDIT:
- return $subject->getCreatedBy() === $user;
+ return $subject->getCreatedBy() === $user && false === $subject->getNotification()->isSystem();
default:
throw new UnexpectedValueException("this subject {$attribute} is not implemented");
diff --git a/src/Bundle/ChillMainBundle/config/services/notification.yaml b/src/Bundle/ChillMainBundle/config/services/notification.yaml
index 4ec90de41..f98561bd9 100644
--- a/src/Bundle/ChillMainBundle/config/services/notification.yaml
+++ b/src/Bundle/ChillMainBundle/config/services/notification.yaml
@@ -22,3 +22,30 @@ services:
Chill\MainBundle\Notification\Templating\NotificationTwigExtension: ~
Chill\MainBundle\Notification\Templating\NotificationTwigExtensionRuntime: ~
+
+ Chill\MainBundle\Notification\Counter\NotificationByUserCounter:
+ autoconfigure: true
+ autowire: true
+ tags:
+ -
+ name: 'doctrine.orm.entity_listener'
+ event: 'preFlush'
+ entity: 'Chill\MainBundle\Entity\Notification'
+ # set the 'lazy' option to TRUE to only instantiate listeners when they are used
+ lazy: true
+ method: 'onPreFlushNotification'
+
+ -
+ name: 'doctrine.orm.entity_listener'
+ event: 'postUpdate'
+ entity: 'Chill\MainBundle\Entity\NotificationComment'
+ # set the 'lazy' option to TRUE to only instantiate listeners when they are used
+ lazy: true
+ method: 'onEditNotificationComment'
+ -
+ name: 'doctrine.orm.entity_listener'
+ event: 'postPersist'
+ entity: 'Chill\MainBundle\Entity\NotificationComment'
+ # set the 'lazy' option to TRUE to only instantiate listeners when they are used
+ lazy: true
+ method: 'onEditNotificationComment'
diff --git a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml
index d5dedbb9f..4f25b46da 100644
--- a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml
+++ b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml
@@ -1,6 +1,15 @@
years_old: >-
- {age, plural,
+ {age, plural,
one {# an}
many {# ans}
other {# ans}
}
+
+notification:
+ My notifications with counter: >-
+ {nb, plural,
+ =0 {Mes notifications}
+ one {Une notification}
+ few {# notifications}
+ other {# notifications}
+ }
diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml
index 0bd15b74b..7efda04d1 100644
--- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml
@@ -360,6 +360,9 @@ notification:
Notifications received: Notifications reçues
Notifications sent: Notification envoyées
comment_appended: Commentaire ajouté
+ append_comment: Ajouter un commentaire
comment_updated: Commentaire mis à jour
is_unread: Non-lue
+ is_system: Notification automatique
+ list: Notifications
diff --git a/src/Bundle/ChillTaskBundle/config/services/templating.yaml b/src/Bundle/ChillTaskBundle/config/services/templating.yaml
index 401bef6d5..0fb98cc19 100644
--- a/src/Bundle/ChillTaskBundle/config/services/templating.yaml
+++ b/src/Bundle/ChillTaskBundle/config/services/templating.yaml
@@ -4,10 +4,9 @@ services:
$taskWorkflowManager: '@Chill\TaskBundle\Workflow\TaskWorkflowManager'
tags:
- { name: 'twig.extension' }
-
+
Chill\TaskBundle\Templating\UI\CountNotificationTask:
+ autoconfigure: true
arguments:
$singleTaskRepository: '@Chill\TaskBundle\Repository\SingleTaskRepository'
$cachePool: '@cache.user_data'
- tags:
- - { name: chill.count_notification.user }