mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge branch 'notification/improve' into 'master'
Improve notifications See merge request Chill-Projet/chill-bundles!294
This commit is contained in:
commit
2b47868d88
@ -12,6 +12,12 @@ and this project adheres to
|
|||||||
|
|
||||||
<!-- write down unreleased development here -->
|
<!-- write down unreleased development here -->
|
||||||
* [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377)
|
* [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377)
|
||||||
|
* [notification: formulaire création] descend la box avec la description dans le bas du formulaire
|
||||||
|
* [notification for activity]: fix link to activity
|
||||||
|
* [notification] add "URGENT" before accompanying course with emergency = true
|
||||||
|
* [notification] add a "read more" button on system notification
|
||||||
|
* [notification] add `[Chill]` in the subject of each notification, automatically
|
||||||
|
* [notification] add a counter for notification in activity list and accompanying period list, and search results
|
||||||
* [parcours] bugfix if deathdate is not defined (eg. for a thirdparty) parcours is still displayed. Gave error before.
|
* [parcours] bugfix if deathdate is not defined (eg. for a thirdparty) parcours is still displayed. Gave error before.
|
||||||
|
|
||||||
## Test releases
|
## Test releases
|
||||||
|
@ -31,6 +31,7 @@ use DateTime;
|
|||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use RuntimeException;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
@ -38,8 +39,8 @@ use Symfony\Component\Form\FormInterface;
|
|||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Security\Core\Role\Role;
|
use Symfony\Component\Security\Core\Role\Role;
|
||||||
use Symfony\Component\Serializer\SerializerInterface;
|
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
use function array_key_exists;
|
use function array_key_exists;
|
||||||
|
|
||||||
final class ActivityController extends AbstractController
|
final class ActivityController extends AbstractController
|
||||||
@ -471,20 +472,21 @@ final class ActivityController extends AbstractController
|
|||||||
|
|
||||||
public function showAction(Request $request, int $id): Response
|
public function showAction(Request $request, int $id): Response
|
||||||
{
|
{
|
||||||
$view = null;
|
$entity = $this->activityRepository->find($id);
|
||||||
|
|
||||||
[$person, $accompanyingPeriod] = $this->getEntity($request);
|
if (null === $entity) {
|
||||||
|
throw $this->createNotFoundException('Unable to find Activity entity.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$accompanyingPeriod = $entity->getAccompanyingPeriod();
|
||||||
|
$person = $entity->getPerson();
|
||||||
|
|
||||||
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
|
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
|
||||||
$view = 'ChillActivityBundle:Activity:showAccompanyingCourse.html.twig';
|
$view = 'ChillActivityBundle:Activity:showAccompanyingCourse.html.twig';
|
||||||
} elseif ($person instanceof Person) {
|
} elseif ($person instanceof Person) {
|
||||||
$view = 'ChillActivityBundle:Activity:showPerson.html.twig';
|
$view = 'ChillActivityBundle:Activity:showPerson.html.twig';
|
||||||
}
|
} else {
|
||||||
|
throw new RuntimeException('the activity should be linked with a period or person');
|
||||||
$entity = $this->activityRepository->find($id);
|
|
||||||
|
|
||||||
if (null === $entity) {
|
|
||||||
throw $this->createNotFoundException('Unable to find Activity entity.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $accompanyingPeriod) {
|
if (null !== $accompanyingPeriod) {
|
||||||
@ -493,8 +495,7 @@ final class ActivityController extends AbstractController
|
|||||||
$entity->personsNotAssociated = $entity->getPersonsNotAssociated();
|
$entity->personsNotAssociated = $entity->getPersonsNotAssociated();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO revoir le Voter de Activity pour tenir compte qu'une activité peut appartenir a une période
|
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $entity);
|
||||||
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_SEE', $entity);
|
|
||||||
|
|
||||||
$deleteForm = $this->createDeleteForm($entity->getId(), $person, $accompanyingPeriod);
|
$deleteForm = $this->createDeleteForm($entity->getId(), $person, $accompanyingPeriod);
|
||||||
|
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
{% macro recordAction(activity, context = null, person_id = null, accompanying_course_id = null) %}
|
{% macro recordAction(activity, context = null, person_id = null, accompanying_course_id = null) %}
|
||||||
|
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
|
||||||
{% if no_action is not defined or no_action == false %}
|
{% if no_action is not defined or no_action == false %}
|
||||||
|
{% set notif_counter = chill_count_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) %}
|
||||||
|
{% if notif_counter.total > 0 %}
|
||||||
|
<li>{{ chill_counter_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) }}</li>
|
||||||
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', {
|
<a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', {
|
||||||
'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity',
|
'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity',
|
||||||
@ -54,6 +59,7 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
<div class="context-{{ context }}">
|
<div class="context-{{ context }}">
|
||||||
|
@ -168,6 +168,7 @@ class ChillMainExtension extends Extension implements
|
|||||||
$loader->load('services/timeline.yaml');
|
$loader->load('services/timeline.yaml');
|
||||||
$loader->load('services/search.yaml');
|
$loader->load('services/search.yaml');
|
||||||
$loader->load('services/serializer.yaml');
|
$loader->load('services/serializer.yaml');
|
||||||
|
$loader->load('services/mailer.yaml');
|
||||||
|
|
||||||
$this->configureCruds($container, $config['cruds'], $config['apis'], $loader);
|
$this->configureCruds($container, $config['cruds'], $config['apis'], $loader);
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,9 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
* @ORM\Entity
|
* @ORM\Entity
|
||||||
* @ORM\Table(
|
* @ORM\Table(
|
||||||
* name="chill_main_notification",
|
* name="chill_main_notification",
|
||||||
|
* indexes={
|
||||||
|
* @ORM\Index(name="chill_main_notification_related_entity_idx", columns={"relatedentityclass", "relatedentityid"})
|
||||||
|
* }
|
||||||
* )
|
* )
|
||||||
* @ORM\HasLifecycleCallbacks
|
* @ORM\HasLifecycleCallbacks
|
||||||
*/
|
*/
|
||||||
|
@ -50,7 +50,7 @@ class NotificationMailer
|
|||||||
$email = new TemplatedEmail();
|
$email = new TemplatedEmail();
|
||||||
$email
|
$email
|
||||||
->to($dest->getEmail())
|
->to($dest->getEmail())
|
||||||
->subject('Re: [Chill] ' . $comment->getNotification()->getTitle())
|
->subject('Re: ' . $comment->getNotification()->getTitle())
|
||||||
->textTemplate('@ChillMain/Notification/email_notification_comment_persist.fr.md.twig')
|
->textTemplate('@ChillMain/Notification/email_notification_comment_persist.fr.md.twig')
|
||||||
->context([
|
->context([
|
||||||
'comment' => $comment,
|
'comment' => $comment,
|
||||||
@ -79,11 +79,13 @@ class NotificationMailer
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($notification->isSystem()) {
|
|
||||||
$email = new Email();
|
$email = new Email();
|
||||||
$email
|
$email
|
||||||
->text($notification->getMessage())
|
->subject($notification->getTitle());
|
||||||
->subject('[Chill] ' . $notification->getTitle());
|
|
||||||
|
if ($notification->isSystem()) {
|
||||||
|
$email
|
||||||
|
->text($notification->getMessage());
|
||||||
} else {
|
} else {
|
||||||
$email = new TemplatedEmail();
|
$email = new TemplatedEmail();
|
||||||
$email
|
$email
|
||||||
|
@ -15,12 +15,15 @@ use Chill\MainBundle\Entity\Notification;
|
|||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Repository\NotificationRepository;
|
use Chill\MainBundle\Repository\NotificationRepository;
|
||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use function array_key_exists;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helps to find if a notification exist for a given entity.
|
* Helps to find if a notification exist for a given entity.
|
||||||
*/
|
*/
|
||||||
class NotificationPresence
|
class NotificationPresence
|
||||||
{
|
{
|
||||||
|
private array $cache = [];
|
||||||
|
|
||||||
private NotificationRepository $notificationRepository;
|
private NotificationRepository $notificationRepository;
|
||||||
|
|
||||||
private Security $security;
|
private Security $security;
|
||||||
@ -31,6 +34,29 @@ class NotificationPresence
|
|||||||
$this->notificationRepository = $notificationRepository;
|
$this->notificationRepository = $notificationRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function countNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId): array
|
||||||
|
{
|
||||||
|
if (array_key_exists($relatedEntityClass, $this->cache) && array_key_exists($relatedEntityId, $this->cache[$relatedEntityClass])) {
|
||||||
|
return $this->cache[$relatedEntityClass][$relatedEntityId];
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
|
if ($user instanceof User) {
|
||||||
|
$counter = $this->notificationRepository->countNotificationByRelatedEntityAndUserAssociated(
|
||||||
|
$relatedEntityClass,
|
||||||
|
$relatedEntityId,
|
||||||
|
$user
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->cache[$relatedEntityClass][$relatedEntityId] = $counter;
|
||||||
|
|
||||||
|
return $counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['unread' => 0, 'read' => 0];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array|Notification[]
|
* @return array|Notification[]
|
||||||
*/
|
*/
|
||||||
|
@ -23,6 +23,13 @@ class NotificationTwigExtension extends AbstractExtension
|
|||||||
'needs_environment' => true,
|
'needs_environment' => true,
|
||||||
'is_safe' => ['html'],
|
'is_safe' => ['html'],
|
||||||
]),
|
]),
|
||||||
|
new TwigFunction('chill_count_notifications', [NotificationTwigExtensionRuntime::class, 'countNotificationsFor'], [
|
||||||
|
'is_safe' => [],
|
||||||
|
]),
|
||||||
|
new TwigFunction('chill_counter_notifications', [NotificationTwigExtensionRuntime::class, 'counterNotificationFor'], [
|
||||||
|
'needs_environment' => true,
|
||||||
|
'is_safe' => ['html'],
|
||||||
|
]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,21 @@ class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface
|
|||||||
$this->notificationPresence = $notificationPresence;
|
$this->notificationPresence = $notificationPresence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function counterNotificationFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
|
||||||
|
{
|
||||||
|
return $environment->render(
|
||||||
|
'@ChillMain/Notification/extension_counter_notifications_for.html.twig',
|
||||||
|
[
|
||||||
|
'counter' => $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countNotificationsFor(string $relatedEntityClass, int $relatedEntityId, array $options = []): array
|
||||||
|
{
|
||||||
|
return $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId);
|
||||||
|
}
|
||||||
|
|
||||||
public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
|
public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
|
||||||
{
|
{
|
||||||
$notifications = $this->notificationPresence->getNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId);
|
$notifications = $this->notificationPresence->getNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId);
|
||||||
|
@ -13,6 +13,7 @@ namespace Chill\MainBundle\Repository;
|
|||||||
|
|
||||||
use Chill\MainBundle\Entity\Notification;
|
use Chill\MainBundle\Entity\Notification;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Doctrine\DBAL\Statement;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
@ -24,6 +25,8 @@ final class NotificationRepository implements ObjectRepository
|
|||||||
{
|
{
|
||||||
private EntityManagerInterface $em;
|
private EntityManagerInterface $em;
|
||||||
|
|
||||||
|
private ?Statement $notificationByRelatedEntityAndUserAssociatedStatement = null;
|
||||||
|
|
||||||
private EntityRepository $repository;
|
private EntityRepository $repository;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager)
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
@ -48,6 +51,30 @@ final class NotificationRepository implements ObjectRepository
|
|||||||
->getSingleScalarResult();
|
->getSingleScalarResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function countNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array
|
||||||
|
{
|
||||||
|
if (null === $this->notificationByRelatedEntityAndUserAssociatedStatement) {
|
||||||
|
$sql =
|
||||||
|
'SELECT
|
||||||
|
SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau WHERE user_id = 1812 and cmnau.notification_id = cmn.id))::int) AS unread,
|
||||||
|
SUM((cmn.sender_id = 1812)::int) AS sent,
|
||||||
|
COUNT(cmn.*) AS total
|
||||||
|
FROM chill_main_notification cmn
|
||||||
|
WHERE relatedentityclass = :relatedEntityClass AND relatedentityid = :relatedEntityId AND sender_id IS NOT NULL';
|
||||||
|
$this->notificationByRelatedEntityAndUserAssociatedStatement =
|
||||||
|
$this->em->getConnection()->prepare($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = $this->notificationByRelatedEntityAndUserAssociatedStatement
|
||||||
|
->executeQuery(['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId]);
|
||||||
|
|
||||||
|
$result = $results->fetchAssociative();
|
||||||
|
|
||||||
|
$results->free();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
public function countUnreadByUser(User $user): int
|
public function countUnreadByUser(User $user): int
|
||||||
{
|
{
|
||||||
$sql = 'SELECT count(*) AS c FROM chill_main_notification_addresses_unread WHERE user_id = :userId';
|
$sql = 'SELECT count(*) AS c FROM chill_main_notification_addresses_unread WHERE user_id = :userId';
|
||||||
@ -153,11 +180,29 @@ final class NotificationRepository implements ObjectRepository
|
|||||||
* @return array|Notification[]
|
* @return array|Notification[]
|
||||||
*/
|
*/
|
||||||
public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array
|
public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array
|
||||||
|
{
|
||||||
|
return
|
||||||
|
$this->buildQueryNotificationByRelatedEntityAndUserAssociated($relatedEntityClass, $relatedEntityId, $user)
|
||||||
|
->select('n')
|
||||||
|
->getQuery()
|
||||||
|
->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria, ?array $orderBy = null): ?Notification
|
||||||
|
{
|
||||||
|
return $this->repository->findOneBy($criteria, $orderBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassName()
|
||||||
|
{
|
||||||
|
return Notification::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildQueryNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): QueryBuilder
|
||||||
{
|
{
|
||||||
$qb = $this->repository->createQueryBuilder('n');
|
$qb = $this->repository->createQueryBuilder('n');
|
||||||
|
|
||||||
$qb
|
$qb
|
||||||
->select('n')
|
|
||||||
->where($qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'))
|
->where($qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'))
|
||||||
->andWhere($qb->expr()->eq('n.relatedEntityId', ':relatedEntityId'))
|
->andWhere($qb->expr()->eq('n.relatedEntityId', ':relatedEntityId'))
|
||||||
->andWhere($qb->expr()->isNotNull('n.sender'))
|
->andWhere($qb->expr()->isNotNull('n.sender'))
|
||||||
@ -171,17 +216,7 @@ final class NotificationRepository implements ObjectRepository
|
|||||||
->setParameter('relatedEntityId', $relatedEntityId)
|
->setParameter('relatedEntityId', $relatedEntityId)
|
||||||
->setParameter('user', $user);
|
->setParameter('user', $user);
|
||||||
|
|
||||||
return $qb->getQuery()->getResult();
|
return $qb;
|
||||||
}
|
|
||||||
|
|
||||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?Notification
|
|
||||||
{
|
|
||||||
return $this->repository->findOneBy($criteria, $orderBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getClassName()
|
|
||||||
{
|
|
||||||
return Notification::class;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function queryByAddressee(User $addressee, bool $countQuery = false): QueryBuilder
|
private function queryByAddressee(User $addressee, bool $countQuery = false): QueryBuilder
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
{{ c.notification.message|chill_markdown_to_html }}
|
{{ c.notification.message|chill_markdown_to_html }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
|
{{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
|
||||||
|
<p class="read-more"><a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">{{ 'Read more'|trans }}</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -85,7 +86,13 @@
|
|||||||
{% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', c.notification) %}
|
{% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', c.notification) %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}"
|
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}"
|
||||||
class="btn btn-show change-icon" title="{{ 'notification.see_comments_thread'|trans }}"><i class="fa fa-comment"></i></a>
|
class="btn {% if not c.notification.isSystem %}btn-show change-icon{% else %}btn-misc{% endif %}" title="{{ 'notification.see_comments_thread'|trans }}">
|
||||||
|
{% if not c.notification.isSystem() %}
|
||||||
|
<i class="fa fa-comment"></i>
|
||||||
|
{% else %}
|
||||||
|
{{ 'Read more'|trans }}
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -21,8 +21,6 @@
|
|||||||
{{ form_row(form.title, { 'label': 'notification.subject'|trans }) }}
|
{{ form_row(form.title, { 'label': 'notification.subject'|trans }) }}
|
||||||
{{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }}
|
{{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }}
|
||||||
|
|
||||||
{% include handler.template(notification) with handler.templateData(notification) %}
|
|
||||||
|
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<label class="col-form-label col-sm-4" for="notification_message">{{ form_label(form.message) }}</label>
|
<label class="col-form-label col-sm-4" for="notification_message">{{ form_label(form.message) }}</label>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@ -30,6 +28,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% include handler.template(notification) with handler.templateData(notification) %}
|
||||||
|
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
{% if counter.total > 0 %}<span class="badge rounded-pill bg-primary">{{ 'notification.counter total notifications'|trans({'total': counter.total }) }}</span>{% endif %}
|
||||||
|
{% if counter.unread > 0 %}<span class="badge rounded-pill bg-danger">{{ 'notification.counter unread notifications'|trans({'unread': counter.unread })}}</span>{% endif %}
|
@ -18,7 +18,7 @@
|
|||||||
data-notification-id="{{ notification.id }}"
|
data-notification-id="{{ notification.id }}"
|
||||||
data-notification-current-is-read="{{ notification.isReadBy(app.user) }}"
|
data-notification-current-is-read="{{ notification.isReadBy(app.user) }}"
|
||||||
data-container="notification-status"
|
data-container="notification-status"
|
||||||
data-show-button-url="{{ chill_path_add_return_path('chill_main_notification_show', {'id': notification.id}) }}"
|
data-show-button-url="{{ chill_path_add_return_path('chill_main_notification_show', {'id': notification.id}, false) }}"
|
||||||
data-button-class="btn-outline-primary"
|
data-button-class="btn-outline-primary"
|
||||||
data-button-text="false"
|
data-button-text="false"
|
||||||
></span>
|
></span>
|
||||||
|
50
src/Bundle/ChillMainBundle/Service/Mailer/ChillMailer.php
Normal file
50
src/Bundle/ChillMainBundle/Service/Mailer/ChillMailer.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Service\Mailer;
|
||||||
|
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\Mailer\Envelope;
|
||||||
|
use Symfony\Component\Mailer\MailerInterface;
|
||||||
|
use Symfony\Component\Mime\Address;
|
||||||
|
use Symfony\Component\Mime\Email;
|
||||||
|
use Symfony\Component\Mime\RawMessage;
|
||||||
|
|
||||||
|
class ChillMailer implements MailerInterface
|
||||||
|
{
|
||||||
|
private LoggerInterface $chillLogger;
|
||||||
|
|
||||||
|
private MailerInterface $initial;
|
||||||
|
|
||||||
|
private string $prefix = '[Chill] ';
|
||||||
|
|
||||||
|
public function __construct(MailerInterface $initial, LoggerInterface $chillLogger)
|
||||||
|
{
|
||||||
|
$this->initial = $initial;
|
||||||
|
$this->chillLogger = $chillLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(RawMessage $message, ?Envelope $envelope = null): void
|
||||||
|
{
|
||||||
|
if ($message instanceof Email) {
|
||||||
|
$message->subject($this->prefix . $message->getSubject());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->chillLogger->info('chill email sent', [
|
||||||
|
'to' => array_map(static function (Address $address) {
|
||||||
|
return $address->getAddress();
|
||||||
|
}, $message->getTo()),
|
||||||
|
'subject' => $message->getSubject(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->initial->send($message, $envelope);
|
||||||
|
}
|
||||||
|
}
|
10
src/Bundle/ChillMainBundle/config/services/mailer.yaml
Normal file
10
src/Bundle/ChillMainBundle/config/services/mailer.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
services:
|
||||||
|
_defaults:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
|
||||||
|
Chill\MainBundle\Service\Mailer\:
|
||||||
|
resource: '../../Service/Mailer'
|
||||||
|
|
||||||
|
Chill\MainBundle\Service\Mailer\ChillMailer:
|
||||||
|
decorates: Symfony\Component\Mailer\MailerInterface
|
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\Migrations\Main;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20220120155303 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP INDEX chill_main_notification_related_entity_idx');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Create index for counting notifications';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE INDEX chill_main_notification_related_entity_idx ON chill_main_notification (relatedentityclass, relatedentityid DESC)');
|
||||||
|
}
|
||||||
|
}
|
@ -13,3 +13,19 @@ notification:
|
|||||||
few {# notifications}
|
few {# notifications}
|
||||||
other {# notifications}
|
other {# notifications}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
counter total notifications: >-
|
||||||
|
{total, plural,
|
||||||
|
=0 {Aucune notification}
|
||||||
|
one {# notification}
|
||||||
|
few {# notifications}
|
||||||
|
other {# notifications}
|
||||||
|
}
|
||||||
|
|
||||||
|
counter unread notifications: >-
|
||||||
|
{unread, plural,
|
||||||
|
=0 {Aucune non-lue}
|
||||||
|
one {# non-lue}
|
||||||
|
few {# non-lues}
|
||||||
|
other {# non-lues}
|
||||||
|
}
|
||||||
|
@ -58,6 +58,7 @@ comment: commentaire
|
|||||||
Comment: Commentaire
|
Comment: Commentaire
|
||||||
Pinned comment: Commentaire épinglé
|
Pinned comment: Commentaire épinglé
|
||||||
Any comment: Aucun commentaire
|
Any comment: Aucun commentaire
|
||||||
|
Read more: Lire la suite
|
||||||
|
|
||||||
# comment embeddable
|
# comment embeddable
|
||||||
No comment associated: Aucun commentaire
|
No comment associated: Aucun commentaire
|
||||||
|
@ -9,19 +9,20 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Chill\PersonBundle\AccompanyingPeriod\Workflow;
|
namespace Chill\PersonBundle\AccompanyingPeriod\Events;
|
||||||
|
|
||||||
use Chill\MainBundle\Entity\Notification;
|
use Chill\MainBundle\Entity\Notification;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Component\Templating\EngineInterface;
|
use Symfony\Component\Templating\EngineInterface;
|
||||||
use Symfony\Component\Workflow\Event\EnteredEvent;
|
use Symfony\Component\Workflow\Event\EnteredEvent;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
class WorkflowEventSubscriber implements EventSubscriberInterface
|
class UserRefEventSubscriber implements EventSubscriberInterface
|
||||||
{
|
{
|
||||||
private EntityManagerInterface $em;
|
private EntityManagerInterface $em;
|
||||||
|
|
||||||
@ -55,15 +56,30 @@ class WorkflowEventSubscriber implements EventSubscriberInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function onPeriodConfirmed(AccompanyingPeriod $period)
|
public function postUpdate(AccompanyingPeriod $period, LifecycleEventArgs $args): void
|
||||||
|
{
|
||||||
|
if ($period->hasPreviousUser()
|
||||||
|
&& $period->getUser() !== $this->security->getUser()
|
||||||
|
&& $period->getStep() !== AccompanyingPeriod::STEP_DRAFT
|
||||||
|
) {
|
||||||
|
$this->generateNotificationToUser($period);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we are just out of a flush operation. Launch a new one
|
||||||
|
$this->em->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateNotificationToUser(AccompanyingPeriod $period)
|
||||||
{
|
{
|
||||||
if ($period->getUser() instanceof User
|
|
||||||
&& $period->getUser() !== $this->security->getUser()) {
|
|
||||||
$notification = new Notification();
|
$notification = new Notification();
|
||||||
|
|
||||||
|
$urgentStatement =
|
||||||
|
$period->isEmergency() ? strtoupper($this->translator->trans('accompanying_period.emergency')) . ' ' : '';
|
||||||
|
|
||||||
$notification
|
$notification
|
||||||
->setRelatedEntityId($period->getId())
|
->setRelatedEntityId($period->getId())
|
||||||
->setRelatedEntityClass(AccompanyingPeriod::class)
|
->setRelatedEntityClass(AccompanyingPeriod::class)
|
||||||
->setTitle($this->translator->trans('period_notification.period_designated_subject'))
|
->setTitle($urgentStatement . $this->translator->trans('period_notification.period_designated_subject'))
|
||||||
->setMessage($this->engine->render(
|
->setMessage($this->engine->render(
|
||||||
'@ChillPerson/Notification/accompanying_course_designation.md.twig',
|
'@ChillPerson/Notification/accompanying_course_designation.md.twig',
|
||||||
[
|
[
|
||||||
@ -71,7 +87,15 @@ class WorkflowEventSubscriber implements EventSubscriberInterface
|
|||||||
]
|
]
|
||||||
))
|
))
|
||||||
->addAddressee($period->getUser());
|
->addAddressee($period->getUser());
|
||||||
|
|
||||||
$this->em->persist($notification);
|
$this->em->persist($notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function onPeriodConfirmed(AccompanyingPeriod $period)
|
||||||
|
{
|
||||||
|
if ($period->getUser() instanceof User
|
||||||
|
&& $period->getUser() !== $this->security->getUser()) {
|
||||||
|
$this->generateNotificationToUser($period);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -327,6 +327,13 @@ class AccompanyingPeriod implements
|
|||||||
*/
|
*/
|
||||||
private ?User $user = null;
|
private ?User $user = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary field, which is filled when the user is changed.
|
||||||
|
*
|
||||||
|
* Used internally for listener when user change
|
||||||
|
*/
|
||||||
|
private ?User $userPrevious = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\OneToMany(
|
* @ORM\OneToMany(
|
||||||
* targetEntity=AccompanyingPeriodWork::class,
|
* targetEntity=AccompanyingPeriodWork::class,
|
||||||
@ -755,6 +762,11 @@ class AccompanyingPeriod implements
|
|||||||
return $this->pinnedComment;
|
return $this->pinnedComment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPreviousUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->userPrevious;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection|SocialAction[] All the descendant social actions of all
|
* @return Collection|SocialAction[] All the descendant social actions of all
|
||||||
* the descendants of the entity
|
* the descendants of the entity
|
||||||
@ -868,6 +880,11 @@ class AccompanyingPeriod implements
|
|||||||
return $this->works;
|
return $this->works;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hasPreviousUser(): bool
|
||||||
|
{
|
||||||
|
return null !== $this->userPrevious;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the closing date is after the opening date.
|
* Returns true if the closing date is after the opening date.
|
||||||
*/
|
*/
|
||||||
@ -1172,6 +1189,10 @@ class AccompanyingPeriod implements
|
|||||||
|
|
||||||
public function setUser(User $user): self
|
public function setUser(User $user): self
|
||||||
{
|
{
|
||||||
|
if ($this->user !== $user) {
|
||||||
|
$this->userPrevious = $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -61,13 +61,13 @@ final class CreationPersonType extends AbstractType
|
|||||||
'required' => false,
|
'required' => false,
|
||||||
])
|
])
|
||||||
->add('phonenumber', TelType::class, [
|
->add('phonenumber', TelType::class, [
|
||||||
'required' => false
|
'required' => false,
|
||||||
])
|
])
|
||||||
->add('mobilenumber', TelType::class, [
|
->add('mobilenumber', TelType::class, [
|
||||||
'required' => false
|
'required' => false,
|
||||||
])
|
])
|
||||||
->add('email', EmailType::class, [
|
->add('email', EmailType::class, [
|
||||||
'required' => false
|
'required' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($this->askCenters) {
|
if ($this->askCenters) {
|
||||||
|
@ -131,6 +131,10 @@
|
|||||||
{{ 'le ' ~ w.updatedAt|format_datetime('long', 'short') }}
|
{{ 'le ' ~ w.updatedAt|format_datetime('long', 'short') }}
|
||||||
</div>
|
</div>
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
|
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) %}
|
||||||
|
{% if notif_counter.total > 0 %}
|
||||||
|
<li>{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) }}</li>
|
||||||
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
|
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
|
||||||
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
|
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
|
|
||||||
{% macro recordAction(period, contextEntity) %}
|
{% macro recordAction(period, contextEntity) %}
|
||||||
{# TODO if enable_accompanying_course_with_multiple_persons is true ... #}
|
{# TODO if enable_accompanying_course_with_multiple_persons is true ... #}
|
||||||
|
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %}
|
||||||
|
{% if notif_counter.total > 0 %}
|
||||||
|
<li>{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) }}</li>
|
||||||
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': period.id }) }}"
|
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': period.id }) }}"
|
||||||
class="btn btn-show" title="{{ 'See accompanying period'|trans }}">{# {{ 'See this period'|trans }} #}</a>
|
class="btn btn-show" title="{{ 'See accompanying period'|trans }}">{# {{ 'See this period'|trans }} #}</a>
|
||||||
|
@ -118,6 +118,10 @@
|
|||||||
{{ 'File number'|trans }} {{ acp.id }}
|
{{ 'File number'|trans }} {{ acp.id }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', acp.id) %}
|
||||||
|
{% if notif_counter.total > 0 %}
|
||||||
|
<div class="counter">{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', acp.id) }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="wl-col list">
|
<div class="wl-col list">
|
||||||
|
@ -20,9 +20,19 @@ services:
|
|||||||
|
|
||||||
Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestionInterface: '@Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestion'
|
Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestionInterface: '@Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestion'
|
||||||
|
|
||||||
Chill\PersonBundle\AccompanyingPeriod\Workflow\:
|
Chill\PersonBundle\AccompanyingPeriod\Events\:
|
||||||
resource: './../../AccompanyingPeriod/Workflow'
|
resource: './../../AccompanyingPeriod/Events'
|
||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
|
|
||||||
|
Chill\PersonBundle\AccompanyingPeriod\Events\UserRefEventSubscriber:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
tags:
|
||||||
|
- # these are the options required to define the entity listener
|
||||||
|
name: 'doctrine.orm.entity_listener'
|
||||||
|
event: 'postUpdate'
|
||||||
|
entity: 'Chill\PersonBundle\Entity\AccompanyingPeriod'
|
||||||
|
|
||||||
|
# set the 'lazy' option to TRUE to only instantiate listeners when they are used
|
||||||
|
lazy: true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user