diff --git a/src/Controller/CapsuleController.php b/src/Controller/CapsuleController.php index 6cc1a15a128f578d3a569eba2781d2f2219b1fc8..b169c9f4fc4eaedd9d4035c34513e45f1a36d189 100644 --- a/src/Controller/CapsuleController.php +++ b/src/Controller/CapsuleController.php @@ -5,12 +5,15 @@ namespace App\Controller; use App\Entity\Capsule; use App\Entity\User; use App\Form\DeleteCapsuleFormType; +use App\Form\DuplicateCapsuleFormType; use App\Helper\StringHelper; use App\Repository\CapsuleRepository; use App\Builder\CapsuleBuilder; use App\Form\CreateCapsuleFormType; use Knp\Component\Pager\PaginatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -24,7 +27,7 @@ class CapsuleController extends AbstractController * @Route("/", name="home") */ public function index( - CapsuleRepository $capsuleRepository, + CapsuleRepository $capsule_repository, PaginatorInterface $paginator, Request $request ): Response { @@ -34,7 +37,7 @@ class CapsuleController extends AbstractController return $this->redirectToRoute('app_logout'); } - $all_capsules = $capsuleRepository->findBy(['creation_author' => $current_user]); + $all_capsules = $capsule_repository->findBy(['creation_author' => $current_user]); $capsules = $paginator->paginate( $all_capsules, @@ -62,25 +65,9 @@ class CapsuleController extends AbstractController } if ($form->isSubmitted() && $form->isValid()) { - $new_date_time = new \DateTime(); - $capsule_name = $form->get('name')->getData(); $video_url = htmlspecialchars($form->get('video_url')->getData()); - $password = StringHelper::generateRandomHashedString(); - $preview_link = Uuid::v4(); - - $entityManager = $this->getDoctrine()->getManager(); - $capsule_builder = new CapsuleBuilder(); - $capsule = $capsule_builder - ->withName($capsule_name) - ->withCreationAuthor($current_user) - ->withCreationDate($new_date_time) - ->withLinkPath($preview_link) - ->withUpdateDate($new_date_time) - ->withPassword($password) - ->createCapsule(); - - $entityManager->persist($capsule); - $entityManager->flush(); + + $capsule = $this->createCapsuleInDB($form, $current_user); return $this->forward('App\Controller\ProjectController::create', [ 'capsule' => $capsule, @@ -147,7 +134,8 @@ class CapsuleController extends AbstractController public function delete( int $id, Request $request, - TranslatorInterface $translator + TranslatorInterface $translator, + CapsuleRepository $capsule_repository ): Response { $form = $this->createForm(DeleteCapsuleFormType::class); $form->handleRequest($request); @@ -167,12 +155,28 @@ class CapsuleController extends AbstractController } $capsule_name = $capsule->getName(); + $do_capsule_belongs_to_user = $capsule_repository->doCapsuleBelongsToUser($capsule, $current_user); + + if (! $do_capsule_belongs_to_user) { + $this->addFlash( + 'warning', + $translator->trans( + 'capsule.delete.error', + [ + 'capsule_name' => $capsule_name + ] + ) + ); + + return $this->redirectToRoute('capsule_list'); + } + if ($form->isSubmitted() && $form->isValid()) { $entityManager->remove($capsule); $entityManager->flush(); $this->addFlash( - 'capsule_deleted_success', + 'success', $translator->trans( 'capsule.delete.success', [ @@ -189,4 +193,99 @@ class CapsuleController extends AbstractController 'capsule_name' => $capsule_name ]); } + + /** + * @Route("/capsule/duplicate/{id}", name="duplicate_capsule") + */ + public function duplicate( + int $id, + Request $request, + Filesystem $file_system, + TranslatorInterface $translator, + CapsuleRepository $capsule_repository + ): Response { + $form = $this->createForm(DuplicateCapsuleFormType::class); + $form->handleRequest($request); + $current_user = $this->getUser(); + + if (! $current_user instanceof User) { + return $this->redirectToRoute('app_logout'); + } + + $entityManager = $this->getDoctrine()->getManager(); + $parent_capsule = $entityManager->getRepository(Capsule::class)->find($id); + + if (! $parent_capsule instanceof Capsule) { + throw new \Exception('The retrieved capsule is not an instance of Capsule.'); + } + + + if (! $capsule_repository->doCapsuleBelongsToUser($parent_capsule, $current_user)) { + $this->addFlash( + 'warning', + $translator->trans( + 'capsule.duplicate.error', + [ + 'capsule_name' => $parent_capsule->getName() + ] + ) + ); + + return $this->redirectToRoute('capsule_list'); + } + + $parent_directory_name = $parent_capsule->getLinkPath(); + $parent_directory_exists = $file_system->exists('../legacy/' . $parent_directory_name); + if (! $parent_directory_exists) { + $this->addFlash( + 'warning', + $translator->trans( + 'project.not_exist', + [ + 'capsule_name' => $parent_capsule->getName() + ] + ) + ); + + return $this->redirectToRoute('capsule_list'); + } + + if ($form->isSubmitted() && $form->isValid()) { + $capsule = $this->createCapsuleInDB($form, $current_user); + + return $this->forward('App\Controller\ProjectController::duplicate', [ + 'capsule' => $capsule, + 'parent_directory' => $parent_directory_name + ]); + } + + return $this->render('capsule/duplicate.html.twig', [ + 'duplicateCapsuleForm' => $form->createView(), + 'capsule_name' => $parent_capsule->getName() + ]); + } + + private function createCapsuleInDB(FormInterface $form, User $current_user): Capsule + { + $new_date_time = new \DateTime(); + $capsule_name = $form->get('name')->getData(); + $password = StringHelper::generateRandomHashedString(); + $preview_link = Uuid::v4(); + + $entityManager = $this->getDoctrine()->getManager(); + $capsule_builder = new CapsuleBuilder(); + $capsule = $capsule_builder + ->withName($capsule_name) + ->withCreationAuthor($current_user) + ->withCreationDate($new_date_time) + ->withLinkPath($preview_link) + ->withUpdateDate($new_date_time) + ->withPassword($password) + ->createCapsule(); + + $entityManager->persist($capsule); + $entityManager->flush(); + + return $capsule; + } } diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index e386f3936f420add02bc1d06041eeaaf0f27a6d3..6fded99a76649dbe77e4ccf3e7c2883a39228478 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -7,6 +7,7 @@ use App\Exception\ZipArchiveNotOpeningException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 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\Response; use Symfony\Component\Routing\Annotation\Route; @@ -50,7 +51,7 @@ class ProjectController extends AbstractController $this->createOrUpdatePasswordFile($capsule_directory, $capsule->getPassword()); $this->addFlash( - 'capsule_created_success', + 'success', $translator->trans( 'capsule.created_success', [ @@ -62,6 +63,52 @@ class ProjectController extends AbstractController return $this->redirectToRoute('capsule_list'); } + /** + * @Route("/project/duplicate", name="duplicate_project", methods={"POST"}) + * @throws ZipArchiveNotOpeningException + */ + public function duplicate( + TranslatorInterface $translator, + Capsule $capsule, + string $parent_directory, + Filesystem $file_system + ): Response { + chdir('../legacy/'); + + $capsule_name = $capsule->getName(); + $capsule_directory = $capsule->getLinkPath(); + + if (file_exists($capsule_directory)) { + $this->addFlash( + 'warning', + $translator->trans( + 'project.already_exists', + [ + 'capsule_name' => $capsule_name + ] + ) + ); + + return $this->redirectToRoute('capsule_list'); + } + + $this->extractZipArchiveInNewCapsuleDirectory($capsule_directory); + $this->replaceTheWholeFileDirectoryWithParentOne($file_system, $parent_directory, $capsule_directory); + $this->createOrUpdatePasswordFile($capsule_directory, $capsule->getPassword()); + + $this->addFlash( + 'success', + $translator->trans( + 'capsule.duplicate.success', + [ + 'capsule_name' => $capsule_name + ] + ) + ); + + return $this->redirectToRoute('capsule_list'); + } + /** * @throws ZipArchiveNotOpeningException The archive file doesn't exist. */ @@ -101,4 +148,18 @@ class ProjectController extends AbstractController $project_password_file = $capsule_directory . "/file/projectPassword.txt"; file_put_contents($project_password_file, $password); } + + private function replaceTheWholeFileDirectoryWithParentOne( + FileSystem $file_system, + string $parent_directory, + string $child_directory + ): void { + $file_directory_path = '/file/'; + $file_system->mirror( + $parent_directory . $file_directory_path, + $child_directory . $file_directory_path, + null, + ['override' => true] + ); + } } diff --git a/src/Form/DuplicateCapsuleFormType.php b/src/Form/DuplicateCapsuleFormType.php new file mode 100644 index 0000000000000000000000000000000000000000..7dc94cfe27e13b8a9a12dfffd17be38394b791b5 --- /dev/null +++ b/src/Form/DuplicateCapsuleFormType.php @@ -0,0 +1,35 @@ +<?php + +namespace App\Form; + +use App\Entity\Capsule; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class DuplicateCapsuleFormType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add( + 'name', + TextType::class, + ['label' => 'capsule.duplicate.new_name'] + ) + ->add( + 'validate', + SubmitType::class, + ['label' => 'general.validate'] + ); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Capsule::class, + ]); + } +} diff --git a/src/Repository/CapsuleRepository.php b/src/Repository/CapsuleRepository.php index 245b86803d8301c7165a8a445a6ec42551f44e23..7e5cd9f9e9820c23e9b5eda5685c5c3772130af8 100644 --- a/src/Repository/CapsuleRepository.php +++ b/src/Repository/CapsuleRepository.php @@ -3,6 +3,7 @@ namespace App\Repository; use App\Entity\Capsule; +use App\Entity\User; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -20,4 +21,11 @@ class CapsuleRepository extends ServiceEntityRepository { parent::__construct($registry, Capsule::class); } + + public function doCapsuleBelongsToUser(Capsule $capsule, User $user): bool + { + return + $capsule->getCreationAuthor() === $user || + $capsule->getUpdateAuthor() === $user; + } } diff --git a/templates/capsule/duplicate.html.twig b/templates/capsule/duplicate.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..af7f795b7d63fa3eeb4cb28b1b358eb2ad941450 --- /dev/null +++ b/templates/capsule/duplicate.html.twig @@ -0,0 +1,27 @@ +{% extends 'layout.html.twig' %} + +{% block title %} + {{ 'capsule.duplicate.title'|trans }} + - + {{ parent() }} +{% endblock %} + +{% block body %} + + <div> + <div class="row w-100 gx-0"> + <div class="row-title-box"> + <h3 class="row-title"> + {{ 'capsule.duplicate.title_name'|trans({'%capsule_name%': capsule_name}) }} + </h3> + </div> + </div> + + {{ form_start(duplicateCapsuleForm, {'attr': {novalidate: 'novalidate', 'class': 'd-flex flex-column justify-content-center'}}) }} + {{ form_row(duplicateCapsuleForm.name, {'row_attr': {'class' : 'm-auto mb-4 col-6'}}) }} + {{ form_row(duplicateCapsuleForm.validate, {'row_attr': {'class' : 'm-auto mb-5 col-2'}}) }} + {{ form_end(duplicateCapsuleForm) }} + + </div> + +{% endblock %} \ No newline at end of file diff --git a/templates/capsule/index.html.twig b/templates/capsule/index.html.twig index e99a2dacc7dc0aa74482fd24276964f5187e3387..c18fa5a17306da3c14718cc9a186e3e0704b011e 100644 --- a/templates/capsule/index.html.twig +++ b/templates/capsule/index.html.twig @@ -29,17 +29,12 @@ </div> {% endfor %} - {% for flashSuccess in app.flashes('capsule_created_success') %} + {% for flashSuccess in app.flashes('success') %} <div class="text-center alert alert-success col-5 mx-auto my-5" role="alert"> {{ flashSuccess }} </div> {% endfor %} - {% for flashSuccess in app.flashes('capsule_deleted_success') %} - <div class="text-center alert alert-success col-5 mx-auto my-5" role="alert"> - {{ flashSuccess }} - </div> - {% endfor %} </div> <div class="capsules-list d-flex flex-column m-6"> @@ -75,7 +70,7 @@ </a> </div> <div class="list-item text-nowrap"> - <a href="" class="links text-decoration-none"> + <a href="/capsule/duplicate/{{ capsule.getId() }}" class="links text-decoration-none"> <i class="far fa-clone m-2"></i> {{ 'capsule.duplicate.link'|trans }} </a> diff --git a/tests/files_for_tests/image_for_test.jpeg b/tests/files_for_tests/image_for_test.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..639529067e09340f8af5eaf6d4da7bd4b8d85679 Binary files /dev/null and b/tests/files_for_tests/image_for_test.jpeg differ diff --git a/tests/files_for_tests/projext.xml b/tests/files_for_tests/projext.xml new file mode 100644 index 0000000000000000000000000000000000000000..115a167f035c43af6f85aa0152ea7305d2b17226 --- /dev/null +++ b/tests/files_for_tests/projext.xml @@ -0,0 +1,78 @@ +<?xml version="1.0"?> +<!DOCTYPE rekall> +<project> + <video url="https://youtu.be/dQw4w9WgXcQ"/> + <document key="marker-1f10323d5f4a1c119e777e59c2f00dcc650e750d"> + <meta ctg="Rekall->Author" cnt=""/> + <meta ctg="Rekall->Date/Time" cnt="2015:08:31 18:06:16"/> + <meta ctg="Rekall->Import Date" cnt="2015:08:31 18:06:16"/> + <meta ctg="Rekall User Infos->User Name" cnt=""/> + <meta ctg="Rekall->Location Name" cnt="48.867359621322265, 2.3618561655447228"/> + <meta ctg="Rekall->Location GPS" cnt="48.867359621322265, 2.3618561655447228"/> + <meta ctg="Rekall User Infos->User IP" cnt="127.0.0.1"/> + <meta ctg="Rekall->Comments" cnt=""/> + <meta ctg="Rekall->Keywords" cnt=""/> + <meta ctg="Rekall->Group" cnt=""/> + <meta ctg="Rekall->Visibility" cnt=""/> + <meta ctg="Rekall->Type" cnt="rekall/marker"/> + <meta ctg="Rekall->Flag" cnt="Marker"/> + <meta ctg="Rekall->Name" cnt="New note"/> + </document> + <tag key="marker-1f10323d5f4a1c119e777e59c2f00dcc650e750d" timeStart="0" timeEnd="10" version="0"/> + <edition key="marker-1f10323d5f4a1c119e777e59c2f00dcc650e750d" metadataKey="Rekall->Name" metadataValue="First note" version="0"/> + <edition key="marker-1f10323d5f4a1c119e777e59c2f00dcc650e750d" metadataKey="Rekall->Comments" metadataValue="This is a note for tests purpose." version="0"/> + <edition key="marker-1f10323d5f4a1c119e777e59c2f00dcc650e750d" metadataKey="Rekall->Link" metadataValue="https://youtu.be/dQw4w9WgXcQ" version="0"/> + <edition key="marker-1f10323d5f4a1c119e777e59c2f00dcc650e750d" metadataKey="Rekall->Author" metadataValue="Rick Astley" version="0"/> + <document key="marker-45f08bfa88924f466539f2ae41b509f7823929db"> + <meta ctg="Rekall->Author" cnt=""/> + <meta ctg="Rekall->Date/Time" cnt="2022:01:21 14:09:09"/> + <meta ctg="Rekall->Import Date" cnt="2022:01:21 14:09:09"/> + <meta ctg="Rekall->Location Name" cnt=""/> + <meta ctg="Rekall->Location GPS" cnt=""/> + <meta ctg="Rekall User Infos->User Name" cnt=""/> + <meta ctg="Rekall User Infos->User IP" cnt="192.168.176.3"/> + <meta ctg="Rekall->Comments" cnt=""/> + <meta ctg="Rekall->Type" cnt="rekall/marker"/> + <meta ctg="Rekall->Flag" cnt="Marker"/> + <meta ctg="Rekall->Name" cnt="New note"/> + <meta ctg="key" cnt="marker-45f08bfa88924f466539f2ae41b509f7823929db"/> + </document> + <tag key="marker-45f08bfa88924f466539f2ae41b509f7823929db" timeStart="0" timeEnd="15" version="0"/> + <edition key="marker-45f08bfa88924f466539f2ae41b509f7823929db" metadataKey="Rekall->Name" metadataValue="Second note" version="0"/> + <edition key="marker-45f08bfa88924f466539f2ae41b509f7823929db" metadataKey="Rekall->Author" metadataValue="Bob" version="0"/> + <edition key="marker-45f08bfa88924f466539f2ae41b509f7823929db" metadataKey="Rekall->Comments" metadataValue="Empty" version="0"/> + <tag key="marker-45f08bfa88924f466539f2ae41b509f7823929db" timeStart="30" timeEnd="75" version="0"/> + <edition key="marker-45f08bfa88924f466539f2ae41b509f7823929db" metadataKey="Rekall->Link" metadataValue="http://undefined" version="0"/> + <tag key="marker-45f08bfa88924f466539f2ae41b509f7823929db" timeStart="30" timeEnd="75" version="0"/> + <edition key="marker-45f08bfa88924f466539f2ae41b509f7823929db" metadataKey="Rekall->Highlight" metadataValue="true" version="0"/> + <document> + <meta ctg="Rekall->Author" cnt=""/> + <meta ctg="File->File Access Date/Time" cnt="2022:01:21 15:10:38"/> + <meta ctg="File->File Creation Date/Time" cnt="2022:01:21 15:10:38"/> + <meta ctg="File->File Modification Date/Time" cnt="2022:01:21 15:10:38"/> + <meta ctg="Rekall->Date/Time" cnt="2022:01:21 15:10:38"/> + <meta ctg="Rekall->Import Date" cnt="2022:01:21 14:10:39"/> + <meta ctg="Rekall->Location Name" cnt=""/> + <meta ctg="Rekall->Location GPS" cnt=""/> + <meta ctg="Rekall User Infos->User Name" cnt=""/> + <meta ctg="Rekall User Infos->User IP" cnt="192.168.176.3"/> + <meta ctg="Rekall->Comments" cnt=""/> + <meta ctg="File->Hash" cnt="2B0F9FA5055C514A9464616416A1EA63A72FD88B"/> + <meta ctg="Rekall->Flag" cnt="File"/> + <meta ctg="File->Thumbnail" cnt=""/> + <meta ctg="File->Owner" cnt="www-data"/> + <meta ctg="File->MIME Type" cnt="image/jpeg"/> + <meta ctg="File->File Type" cnt="image/jpeg"/> + <meta ctg="Rekall->Type" cnt="image/jpeg"/> + <meta ctg="File->File Name" cnt="image_for_test.jpeg"/> + <meta ctg="File->Extension" cnt="jpeg"/> + <meta ctg="File->Basename" cnt="image_for_test"/> + <meta ctg="Rekall->Name" cnt="image_for_test"/> + <meta ctg="Rekall->Extension" cnt="JPEG"/> + <meta ctg="Rekall->Folder" cnt=""/> + <meta ctg="Rekall->File Size" cnt="5591"/> + <meta ctg="Rekall->File Size (MB)" cnt="0.0053319931030273"/> + <meta ctg="key" cnt="/image_for_test.jpeg"/> + </document> + <tag key="/image_for_test.jpeg" timeStart="0" timeEnd="15" version="0"/> +</project> diff --git a/tests/functional/ProjectControllerTest.php b/tests/functional/ProjectControllerTest.php index 45d2830bc0ce78bd636c70bdfb674763c642cc7e..5b2ef487374db5b2bb54319d74e955c60e03f8f3 100644 --- a/tests/functional/ProjectControllerTest.php +++ b/tests/functional/ProjectControllerTest.php @@ -17,6 +17,7 @@ class ProjectControllerTest extends WebTestCase private Form $form; private const TEST_DIR_PATH = __DIR__ . '/../../legacy/'; private const CAPSULE_NAME = 'TestCapsuleName'; + private const TEST_FILES_DIRECTORY = "../tests/files_for_tests/"; protected function setUp(): void { @@ -39,32 +40,20 @@ class ProjectControllerTest extends WebTestCase } $this->client->loginUser($verified_user); - - $crawler = $this->client->request('GET', '/create'); - $this->assertResponseIsSuccessful(); - - $this->client->enableProfiler(); - - $submit_button = $crawler->selectButton('Create a capsule'); - $this->form = $submit_button->form(); } protected function tearDown(): void { parent::tearDown(); - $file_system = new Filesystem(); - $file_system->remove(self::TEST_DIR_PATH . self::CAPSULE_NAME); $capsule_repository = $this->object_manager->getRepository(Capsule::class); - $detached_last_capsule = $capsule_repository->findOneBy(['name' => self::CAPSULE_NAME]); + $capsule = $capsule_repository->findOneBy(['name' => self::CAPSULE_NAME]); - if (! $detached_last_capsule instanceof Capsule) { + if (! $capsule instanceof Capsule) { throw new \Exception("Capsule does not exist."); } - $last_capsule = $this->object_manager->merge($detached_last_capsule); - $this->object_manager->remove($last_capsule); - $this->object_manager->flush(); + $this->deleteCapsuleDirectoryAndInDatabase($capsule); } /** @phpstan-ignore-next-line */ @@ -94,6 +83,14 @@ class ProjectControllerTest extends WebTestCase public function testProjectDirectoryWithCorrespondingXMLFileIsCreatedWhenCapsuleCreationIsSuccessful(): void { + $crawler = $this->client->request('GET', '/create'); + $this->assertResponseIsSuccessful(); + + $this->client->enableProfiler(); + + $submit_button = $crawler->selectButton('Create a capsule'); + $this->form = $submit_button->form(); + $video_url = "https://TestUrl"; $this->form['create_capsule_form[name]'] = self::CAPSULE_NAME; $this->form['create_capsule_form[video_url]'] = $video_url; @@ -137,11 +134,7 @@ class ProjectControllerTest extends WebTestCase public function testProjectDirectoryWithCorrespondingPasswordFileIsCreatedWhenCapsuleCreationIsSuccessful(): void { - $video_url = "https://TestUrl"; - $this->form['create_capsule_form[name]'] = self::CAPSULE_NAME; - $this->form['create_capsule_form[video_url]'] = $video_url; - - $this->client->submit($this->form); + $this->createCapsule(); $this->assertResponseRedirects( '/my_capsules', @@ -159,7 +152,6 @@ class ProjectControllerTest extends WebTestCase throw new \Exception("Capsule does not exist."); } - $capsule_name_in_db = $capsule_in_db->getName(); $capsule_directory = $capsule_in_db->getLinkPath(); $this->assertDirectoryExists($capsule_directory); @@ -175,4 +167,135 @@ class ProjectControllerTest extends WebTestCase "The password should be saved in projectPassword.txt file" ); } + + public function testDuplicatedDirectoryDuplicatedFilesFromParentWhenCapsuleDuplicationIsSuccessful(): void + { + $duplicated_capsule_name = 'duplicated ' . self::CAPSULE_NAME; + $this->createCapsule(); + + $capsule_repository = $this->object_manager->getRepository(Capsule::class); + $parent_capsule = $capsule_repository->findOneBy(['name' => self::CAPSULE_NAME]); + + if (! $parent_capsule instanceof Capsule) { + throw new \Exception("Capsule does not exist."); + } + + $parent_capsule_directory = $parent_capsule->getLinkPath(); + + $crawler = $this->client->request('GET', '/capsule/duplicate/' . $parent_capsule->getId()); + $this->assertResponseIsSuccessful(); + + $this->client->enableProfiler(); + + $submit_button = $crawler->selectButton('Validate'); + $this->form = $submit_button->form(); + $this->form['duplicate_capsule_form[name]'] = $duplicated_capsule_name; + $this->client->submit($this->form); + + $this->assertResponseRedirects( + '/my_capsules', + 302, + 'Once the capsule is duplicated, the user should be redirected to its capsules lists' + ); + + $this->client->followRedirect(); + $this->assertResponseIsSuccessful('/my_capsules'); + + $capsule_repository = $this->object_manager->getRepository(Capsule::class); + $duplicated_capsule_in_db = $capsule_repository->findOneBy(['name' => $duplicated_capsule_name]); + + if (! $duplicated_capsule_in_db instanceof Capsule) { + throw new \Exception("Capsule does not exist."); + } + + $duplicated_capsule_directory = $duplicated_capsule_in_db->getLinkPath(); + + $this->assertDirectoryExists($duplicated_capsule_directory); + $this->assertDirectoryIsReadable($duplicated_capsule_directory); + $this->assertXmlFileEqualsXmlFile( + $this->getXmlFilePath($duplicated_capsule_directory), + $this->getXmlFilePath($duplicated_capsule_directory) + ); + $this->assertAllFilesOfFileDirectoryAreSameExceptPasswordOne( + $parent_capsule_directory, + $duplicated_capsule_directory + ); + + $password_file_path = $this->getPasswordFilePath($duplicated_capsule_directory); + $password = file_get_contents($password_file_path, true); + + $this->assertTrue(false !== $password, "The projectPassword.txt should be readable"); + $this->assertSame( + $duplicated_capsule_in_db->getPassword(), + $password, + "The password should be saved in projectPassword.txt file" + ); + + $this->deleteCapsuleDirectoryAndInDatabase($duplicated_capsule_in_db); + } + + private function assertAllFilesOfFileDirectoryAreSameExceptPasswordOne( + string $parent_capsule_directory, + string $duplicated_capsule_directory + ): void { + $parent_capsule_files = scandir($parent_capsule_directory . "/file/"); + + if (! is_array($parent_capsule_files)) { + return; + } + + foreach ($parent_capsule_files as $key => $file) { + if ($file === "projectPassword.txt" || is_dir($file)) { + return; + } + + $this->assertFileExists($duplicated_capsule_directory . "/file/" . $file); + $this->assertFileEquals($file, $duplicated_capsule_directory . "/file/" . $file); + } + } + + private function createCapsule(): void + { + $crawler = $this->client->request('GET', '/create'); + $this->assertResponseIsSuccessful(); + + $this->client->enableProfiler(); + + $submit_button = $crawler->selectButton('Create a capsule'); + $this->form = $submit_button->form(); + + $video_url = "https://TestUrl"; + $this->form['create_capsule_form[name]'] = self::CAPSULE_NAME; + $this->form['create_capsule_form[video_url]'] = $video_url; + + $this->client->submit($this->form); + + $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_directory_path = $capsule->getLinkPath(); + $file_system = new Filesystem(); + $file_system->mirror( + self::TEST_FILES_DIRECTORY, + $capsule_directory_path . '/file/', + null, + ['override' => true] + ); + } + + private function deleteCapsuleDirectoryAndInDatabase(Capsule $capsule): void + { + $file_system = new Filesystem(); + + $capsule_directory_path = $capsule->getLinkPath(); + $file_system->remove(self::TEST_DIR_PATH . $capsule_directory_path); + + $capsule_to_delete = $this->object_manager->merge($capsule); + $this->object_manager->remove($capsule_to_delete); + $this->object_manager->flush(); + } } diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index 54613c726b86293981af8c8fd55550921ed8ac97..b6e3029b3d13c41d03059eed90d7f7ca774c477f 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -7,6 +7,7 @@ general: cancel_button: Cancel link_expire: This link will expire in %expirationDuration% greeting: Cheers! + validate: Validate login: account_disabled_feedback: Your user account is disabled. Please click on the link your receive by email to validate your registration. @@ -70,15 +71,22 @@ capsule: link: Edit capsule duplicate: link: Duplicate capsule + title: Duplicate capsule + title_name: Duplicate capsule %capsule_name% + new_name: Enter the name of the new capsule + success: The capsule has been successfully duplicated into capsule_name. You can see it at the end of your list + error: You don't have the permission to duplicate this capsule delete: link: Delete capsule button: Delete title: Delete capsule text: Do you really want to delete the capsule %capsule_name%? success: Capsule capsule_name was deleted successfully + error: You don't have the permission to delete this 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 preview: loading: Project loading diff --git a/translations/messages.fr.yaml b/translations/messages.fr.yaml index 527684ea05abe05593a5f057d58f6a5cd536513a..9fff9516c6726f3625dc8882cd317b063f54b269 100644 --- a/translations/messages.fr.yaml +++ b/translations/messages.fr.yaml @@ -7,6 +7,7 @@ general: greeting: Salutation ! go_back_to_home_page: Page d'accueil cancel_button: Annuler + validate: Valider 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 duplicate: link: Dupliquer la capsule + title: Dupliquer la capsule + title_name: Dupliquer la capsule %capsule_name% + new_name: Saisissez le nom de la nouvelle capsule + success: La capsule a bien été dupliquée en capsule_name. Vous la retrouverez à la suite des capsules + error: Vous n'avez pas les droits nécessaires pour dupliquer cette capsule delete: link: Supprimer la capsule not_found: Le projet n'existe pas @@ -74,9 +80,11 @@ capsule: title: Supprimer la capsule text: Souhaitez-vous vraiment supprimer la capsule %capsule_name% ? success: La capsule capsule_name a bien été supprimée + error: Vous n'avez pas les droits nécessaires pour supprimer cette 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 preview: loading: Projet en cours de chargement diff --git a/translations/validators.en.yaml b/translations/validators.en.yaml index 6b1f331119ea7c9406e35ae28f9294b10a490b5f..2a4c7e79c671ce31e50ed4099f7d3074a7fafabb 100644 --- a/translations/validators.en.yaml +++ b/translations/validators.en.yaml @@ -19,6 +19,6 @@ agreeTerms: You must agree with our terms capsule: name: not_blank: Please enter a capsule name - unique: There is already a project with this name + unique: There is already a capsule with this name video_url: not_blank: Please enter a video URL \ No newline at end of file diff --git a/translations/validators.fr.yaml b/translations/validators.fr.yaml index 899af067c83d99a6b2d4bb519d918c6898f8edd6..e6aa6050801f11f164ce603c6e01fd58177644a6 100644 --- a/translations/validators.fr.yaml +++ b/translations/validators.fr.yaml @@ -14,4 +14,11 @@ password: match: Les champs mots de passe doivent être identiques invalid: Mot de passe incorrect -agreeTerms: Veuillez accepter les termes et conditions \ No newline at end of file +agreeTerms: Veuillez accepter les termes et conditions + +capsule: + name: + not_blank: Veuillez saisir le nom de la capsule + unique: Il existe déjà une capsule avec ce nom + video_url: + not_blank: Veuillez saisir l'URL de votre vidéo \ No newline at end of file