diff --git a/migrations/Version20250120141313.php b/migrations/Version20250120141313.php new file mode 100644 index 000000000..df4bc3fc2 --- /dev/null +++ b/migrations/Version20250120141313.php @@ -0,0 +1,26 @@ +addSql('ALTER TABLE suivi ADD is_sanitized TINYINT(1) NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE suivi DROP is_sanitized'); + } +} diff --git a/src/Command/SanitizeSuivisCommand.php b/src/Command/SanitizeSuivisCommand.php new file mode 100644 index 000000000..16a722f09 --- /dev/null +++ b/src/Command/SanitizeSuivisCommand.php @@ -0,0 +1,61 @@ +historyEntryManager->removeEntityListeners(); + $io = new SymfonyStyle($input, $output); + $countAll = $this->suiviRepository->count(['isSanitized' => false]); + $io->info(sprintf('Found %s suivis to sanitize', $countAll)); + $suivis = $this->suiviRepository->findBy(['isSanitized' => false], ['createdAt' => 'DESC'], 50000); + $i = 0; + $progressBar = new ProgressBar($output, \count($suivis)); + $progressBar->start(); + foreach ($suivis as $suivi) { + $suivi->setDescription($this->htmlSanitizer->sanitize($suivi->getDescription(false, false))); + $suivi->setIsSanitized(true); + ++$i; + $progressBar->advance(); + if (0 === $i % self::BATCH_SIZE) { + $this->entityManager->flush(); + } + } + $this->entityManager->flush(); + $progressBar->finish(); + $io->newLine(); + $io->success(sprintf('Sanitized %s/%s suivis', $i, $countAll)); + + return Command::SUCCESS; + } +} diff --git a/src/Entity/Suivi.php b/src/Entity/Suivi.php index 91fb10977..52f4b6136 100644 --- a/src/Entity/Suivi.php +++ b/src/Entity/Suivi.php @@ -73,10 +73,14 @@ class Suivi implements EntityHistoryInterface #[ORM\Column(nullable: true)] private ?array $originalData = null; + #[ORM\Column] + private ?bool $isSanitized = null; + public function __construct() { $this->createdAt = new \DateTimeImmutable(); $this->isPublic = false; + $this->isSanitized = true; } public function getId(): ?int @@ -142,8 +146,11 @@ public function getCreatedByLabel(): ?string return 'OCCUPANT : '.strtoupper($this->getSignalement()->getNomOccupant()).' '.ucfirst($this->getSignalement()->getPrenomOccupant()); } - public function getDescription($transformHtml = true): ?string + public function getDescription($transformHtml = true, $originalData = false): ?string { + if ($originalData) { + return $this->description; + } if (null !== $this->deletedAt) { return self::DESCRIPTION_DELETED.' '.$this->deletedAt->format('d/m/Y'); } @@ -262,4 +269,16 @@ public function getHistoryRegisteredEvent(): array { return [HistoryEntryEvent::CREATE, HistoryEntryEvent::UPDATE, HistoryEntryEvent::DELETE]; } + + public function getIsSanitized(): ?bool + { + return $this->isSanitized; + } + + public function setIsSanitized(bool $isSanitized): static + { + $this->isSanitized = $isSanitized; + + return $this; + } } diff --git a/src/Manager/SuiviManager.php b/src/Manager/SuiviManager.php index b04265c3f..e3c5c4b4a 100644 --- a/src/Manager/SuiviManager.php +++ b/src/Manager/SuiviManager.php @@ -14,6 +14,8 @@ use App\Service\Sanitizer; use Doctrine\Persistence\ManagerRegistry; use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; class SuiviManager extends Manager @@ -24,6 +26,8 @@ public function __construct( private readonly SignalementUpdatedListener $signalementUpdatedListener, private readonly Security $security, private readonly DesordreCritereRepository $desordreCritereRepository, + #[Autowire(service: 'html_sanitizer.sanitizer.app.message_sanitizer')] + private readonly HtmlSanitizerInterface $htmlSanitizer, string $entityName = Suivi::class, ) { parent::__construct($managerRegistry, $entityName); @@ -42,7 +46,7 @@ public function createSuivi( $suivi = (new Suivi()) ->setCreatedBy($user) ->setSignalement($signalement) - ->setDescription($description) + ->setDescription($this->htmlSanitizer->sanitize($description)) ->setType($type) ->setIsPublic($isPublic) ->setContext($context) diff --git a/templates/back/notifications/index.html.twig b/templates/back/notifications/index.html.twig index f917206e1..e65ca2720 100755 --- a/templates/back/notifications/index.html.twig +++ b/templates/back/notifications/index.html.twig @@ -69,10 +69,10 @@ {{ notification.suivi.createdAt|format_datetime(locale='fr', timezone=territory_timezone, pattern='d MMMM yyyy à HH:mm:ss') }} {{ notification.suivi.description - |replace({'&t=___TOKEN___':'/'~notification.signalement.uuid}) - |replace({'?t=___TOKEN___':'/'~notification.signalement.uuid}) - |replace({'?folder=_up':'/'~notification.signalement.uuid~'?variant=resize'}) - |sanitize_html('app.message_sanitizer') }} + |replace({'&t=___TOKEN___':'/'~notification.signalement.uuid}) + |replace({'?t=___TOKEN___':'/'~notification.signalement.uuid}) + |replace({'?folder=_up':'/'~notification.signalement.uuid~'?variant=resize'}) + |raw }} {{ notification.suivi.createdBy ? notification.suivi.createdBy.nomComplet : notification.signalement.nomOccupant|upper~' '~notification.signalement.prenomOccupant|capitalize }} diff --git a/templates/back/signalement/view/suivis.html.twig b/templates/back/signalement/view/suivis.html.twig index db3622015..005c880bf 100755 --- a/templates/back/signalement/view/suivis.html.twig +++ b/templates/back/signalement/view/suivis.html.twig @@ -61,17 +61,11 @@ {% else %}
{% endif %} - {#{{ dump(suivi.description - |replace({'&t=___TOKEN___':'/'~signalement.uuid}) - |replace({'?t=___TOKEN___':'/'~signalement.uuid}) - |replace({'?folder=_up':'/'~signalement.uuid~'?variant=resize'}) - |sanitize_html('app.message_sanitizer')) - }}#} {{ suivi.description - |replace({'&t=___TOKEN___':'/'~signalement.uuid}) - |replace({'?t=___TOKEN___':'/'~signalement.uuid}) - |replace({'?folder=_up':'/'~signalement.uuid~'?variant=resize'}) - |sanitize_html('app.message_sanitizer') + |replace({'&t=___TOKEN___':'/'~signalement.uuid}) + |replace({'?t=___TOKEN___':'/'~signalement.uuid}) + |replace({'?foldert=_up':'/'~signalement.uuid~'?variant=resize'}) + |raw }}
diff --git a/templates/front/_partials/_suivi_signalement_tab_suivi.html.twig b/templates/front/_partials/_suivi_signalement_tab_suivi.html.twig index 3b660782e..ac9858fc0 100755 --- a/templates/front/_partials/_suivi_signalement_tab_suivi.html.twig +++ b/templates/front/_partials/_suivi_signalement_tab_suivi.html.twig @@ -24,7 +24,7 @@
- {{ suivi.description|replace({'___TOKEN___':csrf_token('suivi_signalement_ext_file_view')})|sanitize_html('app.message_sanitizer') }} + {{ suivi.description|replace({'___TOKEN___':csrf_token('suivi_signalement_ext_file_view')})|raw }}
{% endfor %} diff --git a/templates/pdf/signalement.html.twig b/templates/pdf/signalement.html.twig index c8aefe5c1..8dbdfd1d7 100755 --- a/templates/pdf/signalement.html.twig +++ b/templates/pdf/signalement.html.twig @@ -625,7 +625,7 @@ {% endif %} {{ suivi.createdAt|date('d/m/Y') }} - {{ suivi.description|sanitize_html('app.message_sanitizer') }} + {{ suivi.description|raw }} {% endfor %} diff --git a/tests/Functional/Controller/Back/SignalementActionControllerTest.php b/tests/Functional/Controller/Back/SignalementActionControllerTest.php index f2cd22400..0bdbb4636 100644 --- a/tests/Functional/Controller/Back/SignalementActionControllerTest.php +++ b/tests/Functional/Controller/Back/SignalementActionControllerTest.php @@ -124,7 +124,7 @@ public function testDeleteSuivi(): void $route = $this->router->generate('back_signalement_delete_suivi', ['uuid' => $signalement->getUuid()]); $this->client->request('GET', $route); - $description = 'Un petit message de rappel afin d\'y revenir plus tard'; + $description = 'Un petit message de rappel afin d'y revenir plus tard'; $suivi = $this->suiviRepository->findOneBy(['description' => $description]); $this->client->request( diff --git a/tests/Functional/Manager/SuiviManagerTest.php b/tests/Functional/Manager/SuiviManagerTest.php index 937d79a8c..8d25ca046 100644 --- a/tests/Functional/Manager/SuiviManagerTest.php +++ b/tests/Functional/Manager/SuiviManagerTest.php @@ -13,6 +13,7 @@ use Doctrine\Persistence\ManagerRegistry; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -24,6 +25,7 @@ class SuiviManagerTest extends KernelTestCase private Security $security; private UrlGeneratorInterface $urlGenerator; private DesordreCritereRepository $desordreCritereRepository; + private HtmlSanitizerInterface $htmlSanitizerInterface; protected function setUp(): void { @@ -33,6 +35,7 @@ protected function setUp(): void $this->security = static::getContainer()->get(Security::class); $this->urlGenerator = static::getContainer()->get(UrlGeneratorInterface::class); $this->desordreCritereRepository = static::getContainer()->get(DesordreCritereRepository::class); + $this->htmlSanitizerInterface = static::getContainer()->get(HtmlSanitizerInterface::class); } public function testCreateSuivi(): void @@ -43,6 +46,7 @@ public function testCreateSuivi(): void $this->signalementUpdatedListener, $this->security, $this->desordreCritereRepository, + $this->htmlSanitizerInterface, Suivi::class, ); @@ -74,9 +78,10 @@ public function testCreateSuivi(): void $this->assertEquals(Suivi::TYPE_PARTNER, $suivi->getType()); $this->assertNotEquals($countSuivisBeforeCreate, $countSuivisAfterCreate); $this->assertInstanceOf(Suivi::class, $suivi); - $desc = 'Le signalement a été cloturé pour test avec le motif suivant
Non décence
Desc. : Lorem ipsum suivi sit amet, consectetur adipiscing elit.'; + $desc = 'Le signalement a été cloturé pour test avec le motif suivant
Non décence
Desc. : Lorem ipsum suivi sit amet, consectetur adipiscing elit.'; $this->assertEquals($desc, $suivi->getDescription()); $this->assertTrue($suivi->getIsPublic()); + $this->assertTrue($suivi->getIsSanitized()); $this->assertInstanceOf(UserInterface::class, $suivi->getCreatedBy()); } }