diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index 6fded99a76649dbe77e4ccf3e7c2883a39228478..62273a223632dec7c72a867afd04c81de0357614 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -3,12 +3,18 @@ namespace App\Controller; use App\Entity\Capsule; +use App\Entity\PendingEditorInvitation; use App\Exception\ZipArchiveNotOpeningException; +use App\Form\EditVideoUrlFormType; +use App\Form\RemoveEditorFormType; +use App\Repository\CapsuleRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\FrameworkBundle\Translation\Translator; use Symfony\Component\Config\Util\Exception\XmlParsingException; use Symfony\Component\Filesystem\Exception\FileNotFoundException; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Contracts\Translation\TranslatorInterface; @@ -16,12 +22,18 @@ use ZipArchive; class ProjectController extends AbstractController { + private TranslatorInterface $translator; + + public function __construct(TranslatorInterface $translator) + { + $this->translator = $translator; + } + /** * @Route("/project/create", name="create_project", methods={"POST"}) * @throws ZipArchiveNotOpeningException */ public function create( - TranslatorInterface $translator, Capsule $capsule, string $video_url ): Response { @@ -33,7 +45,7 @@ class ProjectController extends AbstractController if (file_exists($capsule_directory)) { $this->addFlash( 'warning', - $translator->trans( + $this->translator->trans( 'project.already_exists', [ 'capsule_name' => $capsule_name @@ -46,13 +58,13 @@ class ProjectController extends AbstractController $this->extractZipArchiveInNewCapsuleDirectory($capsule_directory); - $this->addProjectVideoUrlInXMLProjectFile($capsule_directory, $video_url); + $this->setVideoUrlNodeAttributeInXMLProjectFile($capsule_directory, $video_url); $this->createOrUpdatePasswordFile($capsule_directory, $capsule->getPassword()); $this->addFlash( 'success', - $translator->trans( + $this->translator->trans( 'capsule.created_success', [ 'capsule_name' => $capsule_name @@ -68,7 +80,6 @@ class ProjectController extends AbstractController * @throws ZipArchiveNotOpeningException */ public function duplicate( - TranslatorInterface $translator, Capsule $capsule, string $parent_directory, Filesystem $file_system @@ -81,7 +92,7 @@ class ProjectController extends AbstractController if (file_exists($capsule_directory)) { $this->addFlash( 'warning', - $translator->trans( + $this->translator->trans( 'project.already_exists', [ 'capsule_name' => $capsule_name @@ -98,7 +109,7 @@ class ProjectController extends AbstractController $this->addFlash( 'success', - $translator->trans( + $this->translator->trans( 'capsule.duplicate.success', [ 'capsule_name' => $capsule_name @@ -125,9 +136,9 @@ class ProjectController extends AbstractController $zip->close(); } - private function addProjectVideoUrlInXMLProjectFile(string $capsule_directory, string $video_url): void + private function setVideoUrlNodeAttributeInXMLProjectFile(string $project_directory, string $video_url): void { - $project_xml_file = $capsule_directory . "/file/project.xml"; + $project_xml_file = $project_directory . "/file/project.xml"; $xml_file_content = simplexml_load_file($project_xml_file); if (false === $xml_file_content) { @@ -162,4 +173,62 @@ class ProjectController extends AbstractController ['override' => true] ); } + + /** + * @Route("/capsule/{capsule_id}/edit_video_url", name="edit_video_url") + */ + public function editVideoUrl( + int $capsule_id, + Request $request, + CapsuleRepository $capsule_repository + ): Response { + $capsule = $capsule_repository->findOneBy(['id' => $capsule_id]); + if (! $capsule instanceof Capsule) { + throw new \Exception('The retrieved capsule is not an instance of Capsule.'); + } + + $file_system = new Filesystem(); + $parent_directory_name = $capsule->getLinkPath(); + $parent_directory_exists = $file_system->exists('../legacy/' . $parent_directory_name); + + if (! $parent_directory_exists) { + $this->addFlash( + 'warning', + $this->translator->trans( + 'project.not_exist', + [ + 'capsule_name' => $capsule->getName() + ] + ) + ); + + return $this->redirectToRoute('capsule_list'); + } + + $form = $this->createForm(EditVideoUrlFormType::class); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $new_video_url = $form->get('new_video_url')->getData(); + $project_directory = $capsule->getLinkPath(); + + chdir('../legacy/'); + $this->setVideoUrlNodeAttributeInXMLProjectFile($project_directory, $new_video_url); + + $this->addFlash( + 'success', + $this->translator->trans( + 'capsule.edit.video_url.success' + ) + ); + + return $this->redirectToRoute('capsule_list', [ + 'capsule_id' => $capsule_id + ]); + } + + return $this->render('project/edit_video_url.html.twig', [ + 'editVideoUrlForm' => $form->createView() + ]); + } } diff --git a/src/Form/EditVideoUrlFormType.php b/src/Form/EditVideoUrlFormType.php new file mode 100644 index 0000000000000000000000000000000000000000..2cd948f8ad634cd142ea68c0189b7587fbc56b26 --- /dev/null +++ b/src/Form/EditVideoUrlFormType.php @@ -0,0 +1,38 @@ +<?php + +namespace App\Form; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\EmailType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\UrlType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\NotBlank; + +class EditVideoUrlFormType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add( + 'new_video_url', + UrlType::class, + [ + 'constraints' => [new NotBlank(['message' => 'capsule.video_url.not_blank'])], + 'label' => 'capsule.edit.video_url.text', + 'empty_data' => '' + ] + ) + ->add( + 'save', + SubmitType::class, + ['label' => 'general.save'] + ); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([]); + } +} diff --git a/templates/capsule/create.html.twig b/templates/capsule/create.html.twig index 2c1cf8f7e7459d4aa090493d63acbfd08093236d..b6321afe04e1ba61c976f953832263ab69d5e2c7 100644 --- a/templates/capsule/create.html.twig +++ b/templates/capsule/create.html.twig @@ -9,7 +9,9 @@ <div> <div class="row w-100 gx-0"> <div class="row-title-box"> - <h3 class="row-title">Create a capsule</h3> + <h3 class="row-title"> + {{ 'capsule.create_capsule'|trans }} + </h3> </div> </div> diff --git a/templates/capsule/index.html.twig b/templates/capsule/index.html.twig index 64c31b6c823d34e31fbe30f5e34c7f138cd64267..f12717ee5238959b9032b5fd571041eb9a115c1d 100644 --- a/templates/capsule/index.html.twig +++ b/templates/capsule/index.html.twig @@ -72,6 +72,14 @@ {{ 'capsule.edit_permissions.link'|trans }} </a> </div> + + <div class="list-item text-nowrap"> + <a href="/capsule/{{ capsule.getId() }}/edit_video_url" class="links text-decoration-none"> + <i class="fas fa-link m-2"></i> + {{ 'capsule.edit.video_url.link'|trans }} + </a> + </div> + <div class="list-item text-nowrap"> <a href="/capsule/duplicate/{{ capsule.getId() }}" class="links text-decoration-none"> <i class="far fa-clone m-2"></i> diff --git a/templates/project/edit_video_url.html.twig b/templates/project/edit_video_url.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..7188f1ee43d0f1f34dafb2ad8114f01f54abd421 --- /dev/null +++ b/templates/project/edit_video_url.html.twig @@ -0,0 +1,25 @@ +{% extends 'layout.html.twig' %} + +{% block title %} + {{ 'capsule.edit.video_url.title'|trans }} +{% endblock %} + +{% block body %} + + <div> + <div class="row w-100 gx-0"> + <div class="row-title-box"> + <h3 class="row-title"> + {{ 'capsule.edit.video_url.title'|trans }} + </h3> + </div> + </div> + + {{ form_start(editVideoUrlForm, {'attr': {novalidate: 'novalidate', 'class': 'd-flex flex-column justify-content-center'}}) }} + {{ form_row(editVideoUrlForm.new_video_url, {'row_attr': {'class' : 'm-auto mb-4 col-6'}}) }} + {{ form_row(editVideoUrlForm.save, {'row_attr': {'class' : 'm-auto mb-5 col-2'}}) }} + {{ form_end(editVideoUrlForm) }} + + </div> + +{% endblock %} \ No newline at end of file diff --git a/tests/functional/ProjectControllerTest.php b/tests/functional/ProjectControllerTest.php index 5b2ef487374db5b2bb54319d74e955c60e03f8f3..04168284371b13473a79b8586e269c4c3c1bcf16 100644 --- a/tests/functional/ProjectControllerTest.php +++ b/tests/functional/ProjectControllerTest.php @@ -46,12 +46,7 @@ class ProjectControllerTest extends WebTestCase { parent::tearDown(); - $capsule_repository = $this->object_manager->getRepository(Capsule::class); - $capsule = $capsule_repository->findOneBy(['name' => self::CAPSULE_NAME]); - - if (! $capsule instanceof Capsule) { - throw new \Exception("Capsule does not exist."); - } + $capsule = $this->getLastCapsuleInDb(); $this->deleteCapsuleDirectoryAndInDatabase($capsule); } @@ -234,6 +229,40 @@ class ProjectControllerTest extends WebTestCase $this->deleteCapsuleDirectoryAndInDatabase($duplicated_capsule_in_db); } + public function testVideoUrlNodeAttributeInXMLFileShouldBeUpdatedWhenUserEditVideoUrl(): void + { + $capsule = $this->createCapsule(); + $crawler = $this->client->request('GET', '/capsule/' . $capsule->getId() . '/edit_video_url'); + $this->assertResponseIsSuccessful(); + + $this->client->enableProfiler(); + + $submit_button = $crawler->selectButton('Save'); + $this->form = $submit_button->form(); + $new_video_url = "https://youtu.be/tQwVKr8rCYw"; + $this->form['edit_video_url_form[new_video_url]'] = $new_video_url; + $this->client->submit($this->form); + + $this->client->followRedirect(); + $this->assertResponseIsSuccessful('/my_capsules'); + + $capsule_directory = $capsule->getLinkPath(); + + $this->assertDirectoryExists($capsule_directory); + $this->assertDirectoryIsReadable($capsule_directory); + + $dom_xml = self::getDomDocument($capsule_directory); + $video_node = $dom_xml->getElementsByTagName('video')->item(0); + + if (null === $video_node) { + throw new \Exception("Video node could not be found in XML project file"); + } + + $video_url_in_xml_file = $video_node->getAttribute('url'); + + $this->assertEquals($new_video_url, $video_url_in_xml_file); + } + private function assertAllFilesOfFileDirectoryAreSameExceptPasswordOne( string $parent_capsule_directory, string $duplicated_capsule_directory @@ -254,7 +283,7 @@ class ProjectControllerTest extends WebTestCase } } - private function createCapsule(): void + private function createCapsule(): Capsule { $crawler = $this->client->request('GET', '/create'); $this->assertResponseIsSuccessful(); @@ -285,6 +314,8 @@ class ProjectControllerTest extends WebTestCase null, ['override' => true] ); + + return $capsule; } private function deleteCapsuleDirectoryAndInDatabase(Capsule $capsule): void @@ -298,4 +329,16 @@ class ProjectControllerTest extends WebTestCase $this->object_manager->remove($capsule_to_delete); $this->object_manager->flush(); } + + private function getLastCapsuleInDb(): Capsule + { + $capsule_repository = $this->object_manager->getRepository(Capsule::class); + $capsule = $capsule_repository->findOneBy(['name' => self::CAPSULE_NAME]); + + if (! $capsule instanceof Capsule) { + throw new \Exception("Capsule does not exist."); + } + + return $capsule; + } } diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index 5c341f9ebaeda8d1dd704b2a6575a0d88952e46d..dca9f64e3b542dc44664919219c6ea725cb0c09b 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -8,6 +8,7 @@ general: link_expire: This link will expire in %expirationDuration% greeting: Cheers! validate: Validate + save: Save login: account_disabled_feedback: Your user account is disabled. Please click on the link your receive by email to validate your registration. @@ -70,6 +71,11 @@ capsule: link: Edit capsule not_allowed: You are not allowed to edit this capsule not_found: The capsule was not found + video_url: + link: Edit video URL + text: Enter a new video URL from Youtube or Vimeo + title: Edit video URL + success: The video URL of the project has been updated successfully duplicate: link: Duplicate capsule title: Duplicate capsule @@ -87,7 +93,7 @@ capsule: project: already_exists: Project capsule_name already exists so the capsule could not be created - not_exist: The project to duplicate does not exist + not_exist: The project does not exist preview: loading: Project loading diff --git a/translations/messages.fr.yaml b/translations/messages.fr.yaml index 6342e3e9b901aa7b811755b7f530523a3684cf93..a6502ad168de0a1998946982b5345b0e7fe95e2b 100644 --- a/translations/messages.fr.yaml +++ b/translations/messages.fr.yaml @@ -8,6 +8,7 @@ general: go_back_to_home_page: Page d'accueil cancel_button: Annuler validate: Valider + save: Enregistrer login: account_disabled_feedback: Le compte utilisateur a été désactivé. Veuillez cliquer sur le lien pour recevoir un courriel de validation @@ -67,6 +68,11 @@ capsule: link: Modifier la capsule not_allowed: Vous n'êtes pas autorisé a modifier cette capsule not_found: La capsule n'existe pas + video_url: + link: Modifier l'URL de la vidéo + text: Saisir l'URL de la nouvelle vidéo Youtube ou Vimeo + title: Modifier l'URL de la vidéo + success: L'URL de la vidéo du projet a bien été mise à jour duplicate: link: Dupliquer la capsule title: Dupliquer la capsule @@ -85,7 +91,7 @@ capsule: project: already_exists: Le projet capsule_name existe déjà. La capsule n'a pas pu être créée - not_exist: Le projet a dupliquer n'existe pas + not_exist: Le projet n'existe pas preview: loading: Projet en cours de chargement