diff --git a/backend/composer.json b/backend/composer.json index 409914d..261dbfa 100644 --- a/backend/composer.json +++ b/backend/composer.json @@ -28,6 +28,7 @@ "symfony/dotenv": "^8.0", "symfony/flex": "^2", "symfony/framework-bundle": "^8.0", + "symfony/html-sanitizer": "8.0.*", "symfony/http-client": "8.0.*", "symfony/lock": "8.0.*", "symfony/mailer": "8.0.*", diff --git a/backend/composer.lock b/backend/composer.lock index fb21f74..57b067c 100644 --- a/backend/composer.lock +++ b/backend/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cf5f7c77977031afccfa7da74ed52205", + "content-hash": "92b9472c96a59c314d96372c4094f185", "packages": [ { "name": "api-platform/core", @@ -1785,6 +1785,188 @@ ], "time": "2025-10-17T11:30:53+00:00" }, + { + "name": "league/uri", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.8.1", + "php": "^8.1", + "psr/http-factory": "^1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-15T20:22:25+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-08T20:05:35+00:00" + }, { "name": "lexik/jwt-authentication-bundle", "version": "v3.2.0", @@ -4841,6 +5023,78 @@ ], "time": "2026-01-27T09:06:10+00:00" }, + { + "name": "symfony/html-sanitizer", + "version": "v8.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/html-sanitizer.git", + "reference": "555b37caeee3d07af33471e02377d5ff561f8ac2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/555b37caeee3d07af33471e02377d5ff561f8ac2", + "reference": "555b37caeee3d07af33471e02377d5ff561f8ac2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "league/uri": "^6.5|^7.0", + "php": ">=8.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HtmlSanitizer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Titouan Galopin", + "email": "galopintitouan@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to sanitize untrusted HTML input for safe insertion into a document's DOM.", + "homepage": "https://symfony.com", + "keywords": [ + "Purifier", + "html", + "sanitizer" + ], + "support": { + "source": "https://github.com/symfony/html-sanitizer/tree/v8.0.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-06T13:17:40+00:00" + }, { "name": "symfony/http-client", "version": "v8.0.5", diff --git a/backend/config/packages/html_sanitizer.yaml b/backend/config/packages/html_sanitizer.yaml new file mode 100644 index 0000000..7b1efb3 --- /dev/null +++ b/backend/config/packages/html_sanitizer.yaml @@ -0,0 +1,13 @@ +framework: + html_sanitizer: + sanitizers: + homework_sanitizer: + allow_elements: + p: [] + br: [] + strong: [] + em: [] + ul: [] + ol: [] + li: [] + a: ['href', 'target', 'rel'] diff --git a/backend/config/services.yaml b/backend/config/services.yaml index 5a6c87b..b316339 100644 --- a/backend/config/services.yaml +++ b/backend/config/services.yaml @@ -222,6 +222,13 @@ services: App\Scolarite\Domain\Service\HomeworkDuplicator: autowire: true + App\Scolarite\Application\Port\HtmlSanitizer: + alias: App\Scolarite\Infrastructure\Service\HomeworkHtmlSanitizer + + App\Scolarite\Infrastructure\Service\HomeworkHtmlSanitizer: + arguments: + $homeworkSanitizer: '@html_sanitizer.sanitizer.homework_sanitizer' + App\Scolarite\Application\Port\FileStorage: alias: App\Scolarite\Infrastructure\Storage\LocalFileStorage diff --git a/backend/src/Scolarite/Application/Command/CreateHomework/CreateHomeworkHandler.php b/backend/src/Scolarite/Application/Command/CreateHomework/CreateHomeworkHandler.php index ae9d29c..11875bf 100644 --- a/backend/src/Scolarite/Application/Command/CreateHomework/CreateHomeworkHandler.php +++ b/backend/src/Scolarite/Application/Command/CreateHomework/CreateHomeworkHandler.php @@ -10,6 +10,7 @@ use App\Administration\Domain\Model\User\UserId; use App\Scolarite\Application\Port\CurrentCalendarProvider; use App\Scolarite\Application\Port\EnseignantAffectationChecker; use App\Scolarite\Application\Port\HomeworkRulesChecker; +use App\Scolarite\Application\Port\HtmlSanitizer; use App\Scolarite\Domain\Exception\EnseignantNonAffecteException; use App\Scolarite\Domain\Exception\ReglesDevoirsNonRespecteesException; use App\Scolarite\Domain\Model\Homework\Homework; @@ -29,6 +30,7 @@ final readonly class CreateHomeworkHandler private CurrentCalendarProvider $calendarProvider, private DueDateValidator $dueDateValidator, private HomeworkRulesChecker $rulesChecker, + private HtmlSanitizer $htmlSanitizer, private Clock $clock, ) { } @@ -63,13 +65,17 @@ final readonly class CreateHomeworkHandler throw new ReglesDevoirsNonRespecteesException($rulesResult->toArray()); } + $description = $command->description !== null + ? $this->htmlSanitizer->sanitize($command->description) + : null; + $homework = Homework::creer( tenantId: $tenantId, classId: $classId, subjectId: $subjectId, teacherId: $teacherId, title: $command->title, - description: $command->description, + description: $description, dueDate: $dueDate, now: $now, ); diff --git a/backend/src/Scolarite/Application/Command/UpdateHomework/UpdateHomeworkHandler.php b/backend/src/Scolarite/Application/Command/UpdateHomework/UpdateHomeworkHandler.php index 02bb5e2..3f9ab7e 100644 --- a/backend/src/Scolarite/Application/Command/UpdateHomework/UpdateHomeworkHandler.php +++ b/backend/src/Scolarite/Application/Command/UpdateHomework/UpdateHomeworkHandler.php @@ -6,6 +6,7 @@ namespace App\Scolarite\Application\Command\UpdateHomework; use App\Administration\Domain\Model\User\UserId; use App\Scolarite\Application\Port\CurrentCalendarProvider; +use App\Scolarite\Application\Port\HtmlSanitizer; use App\Scolarite\Domain\Exception\NonProprietaireDuDevoirException; use App\Scolarite\Domain\Model\Homework\Homework; use App\Scolarite\Domain\Model\Homework\HomeworkId; @@ -23,6 +24,7 @@ final readonly class UpdateHomeworkHandler private HomeworkRepository $homeworkRepository, private CurrentCalendarProvider $calendarProvider, private DueDateValidator $dueDateValidator, + private HtmlSanitizer $htmlSanitizer, private Clock $clock, ) { } @@ -44,9 +46,13 @@ final readonly class UpdateHomeworkHandler $dueDate = new DateTimeImmutable($command->dueDate); $this->dueDateValidator->valider($dueDate, $now, $calendar); + $description = $command->description !== null + ? $this->htmlSanitizer->sanitize($command->description) + : null; + $homework->modifier( title: $command->title, - description: $command->description, + description: $description, dueDate: $dueDate, now: $now, ); diff --git a/backend/src/Scolarite/Application/Command/UploadHomeworkAttachment/UploadHomeworkAttachmentHandler.php b/backend/src/Scolarite/Application/Command/UploadHomeworkAttachment/UploadHomeworkAttachmentHandler.php index ba0b3a9..8548185 100644 --- a/backend/src/Scolarite/Application/Command/UploadHomeworkAttachment/UploadHomeworkAttachmentHandler.php +++ b/backend/src/Scolarite/Application/Command/UploadHomeworkAttachment/UploadHomeworkAttachmentHandler.php @@ -37,7 +37,7 @@ final readonly class UploadHomeworkAttachmentHandler $this->homeworkRepository->get($homeworkId, $tenantId); $attachmentId = HomeworkAttachmentId::generate(); - $storagePath = sprintf('homework/%s/%s/%s', $command->tenantId, $command->homeworkId, $command->filename); + $storagePath = sprintf('homework/%s/%s/%s/%s', $command->tenantId, $command->homeworkId, (string) $attachmentId, $command->filename); $content = file_get_contents($command->tempFilePath); diff --git a/backend/src/Scolarite/Application/Port/HtmlSanitizer.php b/backend/src/Scolarite/Application/Port/HtmlSanitizer.php new file mode 100644 index 0000000..cdf6ad2 --- /dev/null +++ b/backend/src/Scolarite/Application/Port/HtmlSanitizer.php @@ -0,0 +1,10 @@ +getSecurityUser(); + $tenantId = TenantId::fromString($user->tenantId()); + + $homework = $this->homeworkRepository->findById(HomeworkId::fromString($id), $tenantId); + + if ($homework === null) { + throw new NotFoundHttpException('Devoir non trouvé.'); + } + + if ((string) $homework->teacherId !== $user->userId()) { + throw new AccessDeniedHttpException('Accès non autorisé.'); + } + + $attachments = $this->attachmentRepository->findByHomeworkId($homework->id); + + return new JsonResponse(array_map( + static fn (HomeworkAttachment $a): array => [ + 'id' => (string) $a->id, + 'filename' => $a->filename, + 'fileSize' => $a->fileSize, + 'mimeType' => $a->mimeType, + ], + $attachments, + )); + } + + #[Route('/api/homework/{id}/attachments', name: 'api_homework_attachment_upload', methods: ['POST'])] + public function upload(string $id, Request $request): JsonResponse + { + $user = $this->getSecurityUser(); + $tenantId = TenantId::fromString($user->tenantId()); + + $homework = $this->homeworkRepository->findById(HomeworkId::fromString($id), $tenantId); + + if ($homework === null) { + throw new NotFoundHttpException('Devoir non trouvé.'); + } + + if ((string) $homework->teacherId !== $user->userId()) { + throw new AccessDeniedHttpException('Seul le propriétaire peut ajouter des pièces jointes.'); + } + + $file = $request->files->get('file'); + + if ($file === null) { + throw new BadRequestHttpException('Aucun fichier envoyé.'); + } + + /** @var \Symfony\Component\HttpFoundation\File\UploadedFile $file */ + $originalName = $file->getClientOriginalName(); + $mimeType = $file->getMimeType() ?? $file->getClientMimeType() ?? ''; + $fileSize = $file->getSize(); + + try { + $attachment = ($this->uploadHandler)(new UploadHomeworkAttachmentCommand( + tenantId: $user->tenantId(), + homeworkId: $id, + filename: $originalName, + mimeType: $mimeType, + fileSize: (int) $fileSize, + tempFilePath: $file->getPathname(), + )); + + $this->attachmentRepository->save($homework->id, $attachment); + } catch (PieceJointeInvalideException $e) { + throw new BadRequestHttpException($e->getMessage()); + } + + return new JsonResponse([ + 'id' => (string) $attachment->id, + 'filename' => $attachment->filename, + 'fileSize' => $attachment->fileSize, + 'mimeType' => $attachment->mimeType, + ], Response::HTTP_CREATED); + } + + #[Route('/api/homework/{id}/attachments/{attachmentId}', name: 'api_homework_attachment_download', methods: ['GET'])] + public function download(string $id, string $attachmentId): BinaryFileResponse + { + $user = $this->getSecurityUser(); + $tenantId = TenantId::fromString($user->tenantId()); + + $homework = $this->homeworkRepository->findById(HomeworkId::fromString($id), $tenantId); + + if ($homework === null) { + throw new NotFoundHttpException('Devoir non trouvé.'); + } + + if ((string) $homework->teacherId !== $user->userId()) { + throw new AccessDeniedHttpException('Accès non autorisé.'); + } + + $attachments = $this->attachmentRepository->findByHomeworkId($homework->id); + + foreach ($attachments as $attachment) { + if ((string) $attachment->id === $attachmentId) { + $fullPath = $this->storageDir . '/' . $attachment->filePath; + $realPath = realpath($fullPath); + $realStorageDir = realpath($this->storageDir); + + if ($realPath === false || $realStorageDir === false || !str_starts_with($realPath, $realStorageDir)) { + throw new NotFoundHttpException('Pièce jointe non trouvée.'); + } + + $response = new BinaryFileResponse($realPath); + $response->setContentDisposition( + ResponseHeaderBag::DISPOSITION_INLINE, + $attachment->filename, + ); + + return $response; + } + } + + throw new NotFoundHttpException('Pièce jointe non trouvée.'); + } + + #[Route('/api/homework/{id}/attachments/{attachmentId}', name: 'api_homework_attachment_delete', methods: ['DELETE'])] + public function delete(string $id, string $attachmentId): Response + { + $user = $this->getSecurityUser(); + $tenantId = TenantId::fromString($user->tenantId()); + + $homework = $this->homeworkRepository->findById(HomeworkId::fromString($id), $tenantId); + + if ($homework === null) { + throw new NotFoundHttpException('Devoir non trouvé.'); + } + + if ((string) $homework->teacherId !== $user->userId()) { + throw new AccessDeniedHttpException('Seul le propriétaire peut supprimer des pièces jointes.'); + } + + $attachments = $this->attachmentRepository->findByHomeworkId($homework->id); + + foreach ($attachments as $attachment) { + if ((string) $attachment->id === $attachmentId) { + $this->fileStorage->delete($attachment->filePath); + $this->attachmentRepository->delete($homework->id, $attachment); + + return new Response(status: Response::HTTP_NO_CONTENT); + } + } + + throw new NotFoundHttpException('Pièce jointe non trouvée.'); + } + + private function getSecurityUser(): SecurityUser + { + $user = $this->security->getUser(); + + if (!$user instanceof SecurityUser) { + throw new AccessDeniedHttpException('Authentification requise.'); + } + + return $user; + } +} diff --git a/backend/src/Scolarite/Infrastructure/Api/Controller/ParentHomeworkController.php b/backend/src/Scolarite/Infrastructure/Api/Controller/ParentHomeworkController.php index 0226288..c00bce2 100644 --- a/backend/src/Scolarite/Infrastructure/Api/Controller/ParentHomeworkController.php +++ b/backend/src/Scolarite/Infrastructure/Api/Controller/ParentHomeworkController.php @@ -45,7 +45,7 @@ final readonly class ParentHomeworkController private GetChildrenHomeworkDetailHandler $detailHandler, private HomeworkRepository $homeworkRepository, private HomeworkAttachmentRepository $attachmentRepository, - #[Autowire('%kernel.project_dir%/var/uploads')] + #[Autowire('%kernel.project_dir%/var/storage')] private string $uploadsDir, ) { } @@ -138,7 +138,8 @@ final readonly class ParentHomeworkController foreach ($attachments as $attachment) { if ((string) $attachment->id === $attachmentId) { - $realPath = realpath($attachment->filePath); + $fullPath = $this->uploadsDir . '/' . $attachment->filePath; + $realPath = realpath($fullPath); $realUploadsDir = realpath($this->uploadsDir); if ($realPath === false || $realUploadsDir === false || !str_starts_with($realPath, $realUploadsDir)) { diff --git a/backend/src/Scolarite/Infrastructure/Api/Controller/StudentHomeworkController.php b/backend/src/Scolarite/Infrastructure/Api/Controller/StudentHomeworkController.php index 2bbd470..8ccc3ac 100644 --- a/backend/src/Scolarite/Infrastructure/Api/Controller/StudentHomeworkController.php +++ b/backend/src/Scolarite/Infrastructure/Api/Controller/StudentHomeworkController.php @@ -44,7 +44,7 @@ final readonly class StudentHomeworkController private HomeworkAttachmentRepository $attachmentRepository, private ScheduleDisplayReader $displayReader, private StudentClassReader $studentClassReader, - #[Autowire('%kernel.project_dir%/var/uploads')] + #[Autowire('%kernel.project_dir%/var/storage')] private string $uploadsDir, ) { } @@ -115,7 +115,8 @@ final readonly class StudentHomeworkController foreach ($attachments as $attachment) { if ((string) $attachment->id === $attachmentId) { - $realPath = realpath($attachment->filePath); + $fullPath = $this->uploadsDir . '/' . $attachment->filePath; + $realPath = realpath($fullPath); $realUploadsDir = realpath($this->uploadsDir); if ($realPath === false || $realUploadsDir === false || !str_starts_with($realPath, $realUploadsDir)) { diff --git a/backend/src/Scolarite/Infrastructure/Persistence/Doctrine/DoctrineHomeworkAttachmentRepository.php b/backend/src/Scolarite/Infrastructure/Persistence/Doctrine/DoctrineHomeworkAttachmentRepository.php index a552c71..fbee12d 100644 --- a/backend/src/Scolarite/Infrastructure/Persistence/Doctrine/DoctrineHomeworkAttachmentRepository.php +++ b/backend/src/Scolarite/Infrastructure/Persistence/Doctrine/DoctrineHomeworkAttachmentRepository.php @@ -80,6 +80,18 @@ final readonly class DoctrineHomeworkAttachmentRepository implements HomeworkAtt ); } + #[Override] + public function delete(HomeworkId $homeworkId, HomeworkAttachment $attachment): void + { + $this->connection->executeStatement( + 'DELETE FROM homework_attachments WHERE id = :id AND homework_id = :homework_id', + [ + 'id' => (string) $attachment->id, + 'homework_id' => (string) $homeworkId, + ], + ); + } + /** @param array $row */ private function hydrate(array $row): HomeworkAttachment { diff --git a/backend/src/Scolarite/Infrastructure/Persistence/InMemory/InMemoryHomeworkAttachmentRepository.php b/backend/src/Scolarite/Infrastructure/Persistence/InMemory/InMemoryHomeworkAttachmentRepository.php index 00f7aea..b5bd9ea 100644 --- a/backend/src/Scolarite/Infrastructure/Persistence/InMemory/InMemoryHomeworkAttachmentRepository.php +++ b/backend/src/Scolarite/Infrastructure/Persistence/InMemory/InMemoryHomeworkAttachmentRepository.php @@ -9,7 +9,9 @@ use App\Scolarite\Domain\Model\Homework\HomeworkId; use App\Scolarite\Domain\Repository\HomeworkAttachmentRepository; use function array_fill_keys; +use function array_filter; use function array_map; +use function array_values; use Override; @@ -42,4 +44,19 @@ final class InMemoryHomeworkAttachmentRepository implements HomeworkAttachmentRe { $this->byHomeworkId[(string) $homeworkId][] = $attachment; } + + #[Override] + public function delete(HomeworkId $homeworkId, HomeworkAttachment $attachment): void + { + $key = (string) $homeworkId; + + if (!isset($this->byHomeworkId[$key])) { + return; + } + + $this->byHomeworkId[$key] = array_values(array_filter( + $this->byHomeworkId[$key], + static fn (HomeworkAttachment $a): bool => (string) $a->id !== (string) $attachment->id, + )); + } } diff --git a/backend/src/Scolarite/Infrastructure/Service/HomeworkHtmlSanitizer.php b/backend/src/Scolarite/Infrastructure/Service/HomeworkHtmlSanitizer.php new file mode 100644 index 0000000..eb3a5b8 --- /dev/null +++ b/backend/src/Scolarite/Infrastructure/Service/HomeworkHtmlSanitizer.php @@ -0,0 +1,23 @@ +homeworkSanitizer->sanitize($html); + } +} diff --git a/backend/tests/Unit/Scolarite/Application/Command/CreateHomework/CreateHomeworkHandlerTest.php b/backend/tests/Unit/Scolarite/Application/Command/CreateHomework/CreateHomeworkHandlerTest.php index 98b6e63..8c7ae33 100644 --- a/backend/tests/Unit/Scolarite/Application/Command/CreateHomework/CreateHomeworkHandlerTest.php +++ b/backend/tests/Unit/Scolarite/Application/Command/CreateHomework/CreateHomeworkHandlerTest.php @@ -15,6 +15,7 @@ use App\Scolarite\Application\Port\CurrentCalendarProvider; use App\Scolarite\Application\Port\EnseignantAffectationChecker; use App\Scolarite\Application\Port\HomeworkRulesChecker; use App\Scolarite\Application\Port\HomeworkRulesCheckResult; +use App\Scolarite\Application\Port\HtmlSanitizer; use App\Scolarite\Application\Port\RuleWarning; use App\Scolarite\Domain\Exception\DateEcheanceInvalideException; use App\Scolarite\Domain\Exception\EnseignantNonAffecteException; @@ -110,6 +111,46 @@ final class CreateHomeworkHandlerTest extends TestCase self::assertNull($homework->description); } + #[Test] + public function itSanitizesHtmlDescription(): void + { + $sanitizer = new class implements HtmlSanitizer { + public function sanitize(string $html): string + { + return strip_tags($html, '