Merge remote-tracking branch 'origin/master' into issue541_change_moving_date

This commit is contained in:
2022-04-13 23:30:51 +02:00
67 changed files with 1649 additions and 347 deletions

View File

@@ -33,11 +33,11 @@ class ApiController extends AbstractCRUDController
* Base method for handling api action.
*
* @param mixed $id
* @param mixed $_format
* @param string $_format
*
* @return void
*/
public function entityApi(Request $request, $id, $_format): Response
public function entityApi(Request $request, $id, ?string $_format = 'json'): Response
{
switch ($request->getMethod()) {
case Request::METHOD_GET:

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\CRUDController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request;
class LocationController extends CRUDController
@@ -29,4 +30,9 @@ class LocationController extends CRUDController
{
$query->where('e.availableForUsers = true'); //TODO not working
}
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
{
return $query->addOrderBy('e.name', 'DESC');
}
}

View File

@@ -22,6 +22,7 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\NotificationRepository;
use Chill\MainBundle\Security\Authorization\NotificationVoter;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -31,14 +32,19 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array;
/**
* @Route("/{_locale}/notification")
*/
class NotificationController extends AbstractController
{
private LoggerInterface $chillLogger;
private EntityManagerInterface $em;
private LoggerInterface $logger;
private NotificationHandlerManager $notificationHandlerManager;
private NotificationRepository $notificationRepository;
@@ -51,6 +57,8 @@ class NotificationController extends AbstractController
public function __construct(
EntityManagerInterface $em,
LoggerInterface $chillLogger,
LoggerInterface $logger,
Security $security,
NotificationRepository $notificationRepository,
NotificationHandlerManager $notificationHandlerManager,
@@ -58,6 +66,8 @@ class NotificationController extends AbstractController
TranslatorInterface $translator
) {
$this->em = $em;
$this->logger = $logger;
$this->chillLogger = $chillLogger;
$this->security = $security;
$this->notificationRepository = $notificationRepository;
$this->notificationHandlerManager = $notificationHandlerManager;
@@ -150,6 +160,49 @@ class NotificationController extends AbstractController
]);
}
/**
* @Route("/{id}/access_key", name="chill_main_notification_grant_access_by_access_key")
*/
public function getAccessByAccessKey(Notification $notification, Request $request): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
if (!$this->security->getUser() instanceof User) {
throw new AccessDeniedHttpException('You must be authenticated and a user to create a notification');
}
foreach (['accessKey', 'email'] as $param) {
if (!$request->query->has($param)) {
throw new BadRequestHttpException("Missing {$param} parameter");
}
}
if ($notification->getAccessKey() !== $request->query->getAlnum('accessKey')) {
throw new AccessDeniedHttpException('access key is invalid');
}
if (!in_array($request->query->get('email'), $notification->getAddressesEmails(), true)) {
return (new Response('The email address is no more associated with this notification'))
->setStatusCode(Response::HTTP_FORBIDDEN);
}
$notification->addAddressee($this->security->getUser());
$this->getDoctrine()->getManager()->flush();
$logMsg = '[Notification] a user is granted access to notification trough an access key';
$context = [
'notificationId' => $notification->getId(),
'email' => $request->query->get('email'),
'user' => $this->security->getUser()->getId(),
];
$this->logger->info($logMsg, $context);
$this->chillLogger->info($logMsg, $context);
return $this->redirectToRoute('chill_main_notification_show', ['id' => $notification->getId()]);
}
/**
* @Route("/inbox", name="chill_main_notification_my")
*/

View File

@@ -122,7 +122,7 @@ class SearchController extends AbstractController
public function searchAction(Request $request, $_format)
{
$pattern = $request->query->get('q', '');
$pattern = trim($request->query->get('q', ''));
if ('' === $pattern) {
switch ($_format) {

View File

@@ -18,6 +18,9 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function count;
use function in_array;
/**
* @ORM\Entity
@@ -31,15 +34,34 @@ use Symfony\Component\Validator\Constraints as Assert;
*/
class Notification implements TrackUpdateInterface
{
/**
* @ORM\Column(type="text", nullable=false)
*/
private string $accessKey;
private array $addedAddresses = [];
/**
* @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_main_notification_addresses_user")
* @Assert\Count(min="1", minMessage="notification.At least one addressee")
*/
private Collection $addressees;
/**
* a list of destinee which will receive notifications.
*
* @var array|string[]
* @ORM\Column(type="json")
*/
private array $addressesEmails = [];
/**
* a list of emails adresses which were added to the notification.
*
* @var array|string[]
*/
private array $addressesEmailsAdded = [];
private ?ArrayCollection $addressesOnLoad = null;
/**
@@ -111,6 +133,7 @@ class Notification implements TrackUpdateInterface
$this->unreadBy = new ArrayCollection();
$this->comments = new ArrayCollection();
$this->setDate(new DateTimeImmutable());
$this->accessKey = bin2hex(openssl_random_pseudo_bytes(24));
}
public function addAddressee(User $addressee): self
@@ -123,6 +146,14 @@ class Notification implements TrackUpdateInterface
return $this;
}
public function addAddressesEmail(string $email)
{
if (!in_array($email, $this->addressesEmails, true)) {
$this->addressesEmails[] = $email;
$this->addressesEmailsAdded[] = $email;
}
}
public function addComment(NotificationComment $comment): self
{
if (!$this->comments->contains($comment)) {
@@ -142,6 +173,30 @@ class Notification implements TrackUpdateInterface
return $this;
}
/**
* @Assert\Callback
*
* @param array $payload
*/
public function assertCountAddresses(ExecutionContextInterface $context, $payload): void
{
if (0 === (count($this->getAddressesEmails()) + count($this->getAddressees()))) {
$context->buildViolation('notification.At least one addressee')
->atPath('addressees')
->addViolation();
}
}
public function getAccessKey(): string
{
return $this->accessKey;
}
public function getAddedAddresses(): array
{
return $this->addedAddresses;
}
/**
* @return Collection|User[]
*/
@@ -155,6 +210,22 @@ class Notification implements TrackUpdateInterface
return $this->addressees;
}
/**
* @return array|string[]
*/
public function getAddressesEmails(): array
{
return $this->addressesEmails;
}
/**
* @return array|string[]
*/
public function getAddressesEmailsAdded(): array
{
return $this->addressesEmailsAdded;
}
public function getComments(): Collection
{
return $this->comments;
@@ -271,6 +342,14 @@ class Notification implements TrackUpdateInterface
return $this;
}
public function removeAddressesEmail(string $email)
{
if (in_array($email, $this->addressesEmails, true)) {
$this->addressesEmails = array_filter($this->addressesEmails, static fn ($e) => $e !== $email);
$this->addressesEmailsAdded = array_filter($this->addressesEmailsAdded, static fn ($e) => $e !== $email);
}
}
public function removeComment(NotificationComment $comment): self
{
$this->comments->removeElement($comment);

View File

@@ -12,12 +12,17 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Form\Type\ChillCollectionType;
use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
class NotificationType extends AbstractType
{
@@ -30,9 +35,27 @@ class NotificationType extends AbstractType
])
->add('addressees', PickUserDynamicType::class, [
'multiple' => true,
'required' => false,
])
->add('message', ChillTextareaType::class, [
'required' => false,
])
->add('addressesEmails', ChillCollectionType::class, [
'label' => 'notification.dest by email',
'help' => 'notification.dest by email help',
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true,
'entry_type' => EmailType::class,
'button_add_label' => 'notification.Add an email',
'button_remove_label' => 'notification.Remove an email',
'empty_collection_explain' => 'notification.Any email',
'entry_options' => [
'constraints' => [
new NotNull(), new NotBlank(), new Email(['checkMX' => true]),
],
'label' => 'Email',
],
]);
}

View File

@@ -73,6 +73,17 @@ class NotificationMailer
* Send a email after a notification is persisted.
*/
public function postPersistNotification(Notification $notification, LifecycleEventArgs $eventArgs): void
{
$this->sendNotificationEmailsToAddresses($notification);
$this->sendNotificationEmailsToAddressesEmails($notification);
}
public function postUpdateNotification(Notification $notification, LifecycleEventArgs $eventArgs): void
{
$this->sendNotificationEmailsToAddressesEmails($notification);
}
private function sendNotificationEmailsToAddresses(Notification $notification): void
{
foreach ($notification->getAddressees() as $addressee) {
if (null === $addressee->getEmail()) {
@@ -108,4 +119,31 @@ class NotificationMailer
}
}
}
private function sendNotificationEmailsToAddressesEmails(Notification $notification): void
{
foreach ($notification->getAddressesEmailsAdded() as $emailAddress) {
$email = new TemplatedEmail();
$email
->textTemplate('@ChillMain/Notification/email_non_system_notification_content_to_email.fr.md.twig')
->context([
'notification' => $notification,
'dest' => $emailAddress,
]);
$email
->subject($notification->getTitle())
->to($emailAddress);
try {
$this->mailer->send($email);
} catch (TransportExceptionInterface $e) {
$this->logger->warning('[NotificationMailer] could not send an email notification', [
'to' => $emailAddress,
'error_message' => $e->getMessage(),
'error_trace' => $e->getTraceAsString(),
]);
}
}
}
}

View File

@@ -56,17 +56,18 @@ final class NotificationRepository implements ObjectRepository
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,
SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau JOIN chill_main_notification cmn ON cmnau.notification_id = cmn.id WHERE user_id = :userid and cmnau.notification_id = cmn.id and cmn.sender_id IS NOT NULL))::int) AS unread,
SUM((cmn.sender_id = :userid)::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]);
->executeQuery(['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId, 'userid' => $user->getId()]);
$result = $results->fetchAssociative();

View File

@@ -8,30 +8,31 @@ window.addEventListener('DOMContentLoaded', function (e) {
document.querySelectorAll('.notification_toggle_read_status')
.forEach(function (el, i) {
createApp({
template: '<notification-read-toggle ' +
':notificationId="notificationId" ' +
':buttonClass="buttonClass" ' +
':buttonNoText="buttonNoText" ' +
':showUrl="showUrl" ' +
':isRead="isRead"' +
'@markRead="onMarkRead" @markUnread="onMarkUnread"' +
'></notification-read-toggle>',
template: `<notification-read-toggle
:notificationId="notificationId"
:buttonClass="buttonClass"
:buttonNoText="buttonNoText"
:showUrl="showUrl"
:isRead="isRead"
@markRead="onMarkRead"
@markUnread="onMarkUnread">
</notification-read-toggle>`,
components: {
NotificationReadToggle,
},
data() {
return {
notificationId: +el.dataset.notificationId,
notificationId: el.dataset.notificationId,
buttonClass: el.dataset.buttonClass,
buttonNoText: 'false' === el.dataset.buttonText,
showUrl: el.dataset.showButtonUrl,
isRead: 1 === +el.dataset.notificationCurrentIsRead,
isRead: 1 === Number.parseInt(el.dataset.notificationCurrentIsRead),
container: el.dataset.container
}
},
computed: {
getContainer() {
return document.querySelectorAll('div.' + this.container);
return document.querySelectorAll(`div.${this.container}`);
}
},
methods: {

View File

@@ -81,7 +81,7 @@ export default {
/// [Option] showUrl is href for show page second button.
// When passed, the component return a button-group with 2 buttons.
isButtonGroup() {
return !!this.showUrl
return this.showUrl;
}
},
methods: {

View File

@@ -47,6 +47,8 @@
</tbody>
</table>
{{ chill_pagination(paginator) }}
<ul class="record_actions">
<li>
<a href="{{ path('chill_crud_main_location_new') }}" class="btn btn-create">

View File

@@ -40,6 +40,11 @@
{{ a|chill_entity_render_string }}
</span>
{% endfor %}
{% for a in c.notification.addressesEmails %}
<span class="badge-user" title="{{ 'notification.Email with access link'|trans|e('html_attr') }}">
{{ a }}
</span>
{% endfor %}
</li>
{% endif %}
</ul>
@@ -61,17 +66,28 @@
<div class="item-row">
<div class="notification-content">
{% if c.full_content is defined and c.full_content == true %}
{{ c.notification.message|chill_markdown_to_html }}
{% if c.notification.message is not empty %}
{{ c.notification.message|chill_markdown_to_html }}
{% else %}
<p class="chill-no-data-statement">{{ 'Any comment'|trans }}</p>
{% endif %}
{% else %}
{{ 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>
{% if c.notification.message is not empty %}
{{ 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>
{% else %}
<p class="chill-no-data-statement">{{ 'Any comment'|trans }}</p>
{% endif %}
{% endif %}
</div>
</div>
{% endmacro %}
{% macro actions(c) %}
{% if c.action_button is not defined or c.action_button != false %}
<div class="item-row separator">
<div class="item-col item-meta">
{% if c.notification.comments|length > 0 %}
<div class="comment-counter">
<span class="counter">
@@ -79,16 +95,16 @@
</span>
</div>
{% endif %}
</div>
<div class="item-col">
<ul class="record_actions">
<li>
{# Vue component #}
<span class="notification_toggle_read_status"
data-notification-id="{{ c.notification.id }}"
data-notification-current-is-read="{{ c.notification.isReadBy(app.user) }}"
data-container="notification-status"
data-notification-id="{{ c.notification.id }}"
data-notification-current-is-read="{{ c.notification.isReadBy(app.user) }}"
data-container="notification-status"
></span>
</li>
{% if is_granted('CHILL_MAIN_NOTIFICATION_UPDATE', c.notification) %}
@@ -122,24 +138,25 @@
<button type="button" class="accordion-button collapsed"
data-bs-toggle="collapse" data-bs-target="#flush-collapse-{{ notification.id }}"
aria-expanded="false" aria-controls="flush-collapse-{{ notification.id }}">
{{ _self.title(_context) }}
</button>
{{ _self.header(_context) }}
</div>
<div id="flush-collapse-{{ notification.id }}"
class="accordion-collapse collapse"
aria-labelledby="flush-heading-{{ notification.id }}"
data-bs-parent="#notification-fold">
{{ _self.content(_context) }}
</div>
{{ _self.actions(_context) }}
{% else %}
{{ _self.title(_context) }}
{{ _self.header(_context) }}
{{ _self.content(_context) }}
{{ _self.actions(_context) }}
{% endif %}
</div>

View File

@@ -20,7 +20,9 @@
{{ form_row(form.title, { 'label': 'notification.subject'|trans }) }}
{{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }}
{{ form_row(form.addressesEmails) }}
{% include handler.template(notification) with handler.templateData(notification) %}
<div class="mb-3 row">

View File

@@ -21,6 +21,8 @@
{{ form_row(form.title, { 'label': 'notification.subject'|trans }) }}
{{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }}
{{ form_row(form.addressesEmails) }}
{% include handler.template(notification) with handler.templateData(notification) %}
<div class="mb-3 row">

View File

@@ -0,0 +1,20 @@
{{ dest }},
{{ notification.sender.label }} a créé une notification pour vous:
> {{ notification.title }}
>
>
{%- for line in notification.message|split("\n") %}
> {{ line }}
{%- if not loop.last %}
>
{%- endif %}
{%- endfor %}
Vous pouvez cliquer sur ce lien pour obtenir un accès permanent à la notification:
{{ absolute_url(path('chill_main_notification_grant_access_by_access_key', {'_locale': 'fr', 'id': notification.id, 'accessKey': notification.accessKey, 'email': dest})) }}
--
Le logiciel Chill

View File

@@ -3,6 +3,7 @@
{% block title 'workflow.Delete workflow ?'|trans %}
{% block display_content %}
<div class="col-10 workflow">
<h2>
{{ handler.entityTitle(entityWorkflow) }}
</h2>
@@ -10,6 +11,7 @@
{% include handler.template(entityWorkflow) with handler.templateData(entityWorkflow)|merge({
'display_action': false
}) %}
{% endblock %}
{% block content %}
@@ -25,5 +27,5 @@
'form' : delete_form
} ) }}
</div>
</div>
{% endblock %}

View File

@@ -49,11 +49,12 @@ class SearchUserApiProvider implements SearchApiInterface
SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical))', [$pattern, $pattern])
->setFromClause('users AS u')
->setWhereClauses('
u.enabled IS TRUE and (
SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15
OR u.usernamecanonical LIKE \'%\' || LOWER(UNACCENT(?)) || \'%\'
OR SIMILARITY(LOWER(UNACCENT(?)), LOWER(UNACCENT(u.label))) > 0.15
OR u.label LIKE \'%\' || LOWER(UNACCENT(?)) || \'%\'
', [$pattern, $pattern, $pattern, $pattern]);
)', [$pattern, $pattern, $pattern, $pattern]);
return $query;
}

View File

@@ -59,7 +59,23 @@ class EntityWorkflowVoter extends Voter
return true;
}
return $this->security->isGranted($entityAttribute, $handler->getRelatedEntity($subject));
$relatedEntity = $handler->getRelatedEntity($subject);
if (null === $relatedEntity) {
return true;
}
if ($this->security->isGranted($entityAttribute, $relatedEntity)) {
return true;
}
foreach ($subject->getSteps() as $step) {
if ($step->getAllDestUser()->contains($token->getUser())) {
return true;
}
}
return false;
case self::DELETE:
return $subject->getStep() === 'initial';

View File

@@ -44,14 +44,25 @@ class DateNormalizer implements ContextAwareNormalizerInterface, DenormalizerInt
switch ($type) {
case DateTime::class:
return DateTime::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']);
$result = DateTime::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']);
break;
case DateTimeInterface::class:
case DateTimeImmutable::class:
return DateTimeImmutable::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']);
$result = DateTimeImmutable::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']);
break;
default:
throw new UnexpectedValueException();
}
throw new UnexpectedValueException();
if (false === $result) {
return null;
}
return $result;
}
public function normalize($date, $format = null, array $context = [])

View File

@@ -32,7 +32,7 @@ class PhonenumberNormalizer implements ContextAwareNormalizerInterface, Denormal
}
/**
* @param mixed $data
* @param string|null $data
* @param mixed $type
* @param null|mixed $format
*
@@ -40,7 +40,7 @@ class PhonenumberNormalizer implements ContextAwareNormalizerInterface, Denormal
*/
public function denormalize($data, $type, $format = null, array $context = [])
{
if ('' === trim($data)) {
if ('' === trim((string) $data)) {
return null;
}

View File

@@ -88,6 +88,24 @@ final class NotificationTest extends KernelTestCase
$this->assertNotContains($user1, $notification->getUnreadBy()->toArray());
}
public function testAddressesEmail(): void
{
$notification = new Notification();
$notification->addAddressesEmail('test');
$notification->addAddressesEmail('other');
$this->assertContains('test', $notification->getAddressesEmails());
$this->assertContains('other', $notification->getAddressesEmails());
$this->assertContains('test', $notification->getAddressesEmailsAdded());
$this->assertContains('other', $notification->getAddressesEmailsAdded());
$notification->removeAddressesEmail('other');
$this->assertNotContains('other', $notification->getAddressesEmails());
$this->assertNotContains('other', $notification->getAddressesEmailsAdded());
}
/**
* @dataProvider generateNotificationData
*/

View File

@@ -0,0 +1,54 @@
<?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 Serializer\Normalizer;
use Chill\MainBundle\Serializer\Normalizer\PhonenumberNormalizer;
use libphonenumber\PhoneNumber;
use libphonenumber\PhoneNumberUtil;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
/**
* @internal
* @coversNothing
*/
final class PhonenumberNormalizerTest extends TestCase
{
use ProphecyTrait;
public function dataProviderNormalizePhonenumber()
{
$phonenumberUtil = PhoneNumberUtil::getInstance();
yield [$phonenumberUtil->parse('+32486123465'), 'docgen', ['docgen:expects' => PhoneNumber::class], '0486 12 34 65'];
yield [null, 'docgen', ['docgen:expects' => PhoneNumber::class], ''];
}
/**
* @dataProvider dataProviderNormalizePhonenumber
*
* @param mixed $format
* @param mixed $context
* @param mixed $expected
*/
public function testNormalize(?Phonenumber $phonenumber, $format, $context, $expected)
{
$parameterBag = $this->prophesize(ParameterBagInterface::class);
$parameterBag->get(Argument::exact('chill_main'))->willReturn(['phone_helper' => ['default_carrier_code' => 'BE']]);
$normalizer = new PhonenumberNormalizer($parameterBag->reveal());
$this->assertEquals($expected, $normalizer->normalize($phonenumber, $format, $context));
}
}

View File

@@ -61,6 +61,15 @@ services:
# set the 'lazy' option to TRUE to only instantiate listeners when they are used
lazy: true
method: 'postPersistNotification'
-
name: 'doctrine.orm.entity_listener'
event: 'postUpdate'
entity: 'Chill\MainBundle\Entity\Notification'
# set the 'lazy' option to TRUE to only instantiate listeners when they are used
lazy: true
method: 'postUpdateNotification'
-
name: 'doctrine.orm.entity_listener'
event: 'postPersist'

View File

@@ -0,0 +1,46 @@
<?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 Version20220413154743 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_notification DROP adressesEmails');
$this->addSql('ALTER TABLE chill_main_notification DROP accessKey');
}
public function getDescription(): string
{
return 'add access keys and emails dest to notifications';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_notification ADD addressesEmails JSON NOT NULL DEFAULT \'[]\';');
$this->addSql('ALTER TABLE chill_main_notification ADD accessKey TEXT DEFAULT NULL');
$this->addSql('WITH randoms AS (select
n.id,
string_agg(substr(characters, (random() * length(characters) + 0.5)::integer, 1), \'\') as random_word
from (values(\'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\')) as symbols(characters)
-- length of word
join generate_series(1, 16) on 1 = 1
JOIN chill_main_notification n ON true
GROUP BY n.id)
UPDATE chill_main_notification SET accessKey = randoms.random_word FROM randoms WHERE chill_main_notification.id = randoms.id');
$this->addSql('ALTER TABLE chill_main_notification ALTER accessKey DROP DEFAULT');
$this->addSql('ALTER TABLE chill_main_notification ALTER accessKey SET NOT NULL');
}
}

View File

@@ -450,4 +450,10 @@ notification:
subject: Objet
see_comments_thread: Voir le fil de commentaires associé
object_prefix: "[CHILL] notification - "
dest by email: Lien d'accès par email
Any email: Aucun email
Add an email: Ajouter un email
dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Un compte utilisateur sera toujours nécessaire.
Remove an email: Supprimer l'adresse email
Email with access link: Adresse email ayant reçu un lien d'accès