diff --git a/assets/styles/app.scss b/assets/styles/app.scss index cf6890562c4369911c7a20c0dba3c2043128204c..764d94d9a4e76b0fc06b03a39b21b2c672dc5c43 100644 --- a/assets/styles/app.scss +++ b/assets/styles/app.scss @@ -95,16 +95,15 @@ button[type=submit]{ .standard-button { line-height: 40px; - margin: 30px 0 0 0; padding: 0.5em 2em; - font-size: 14px; - text-transform: uppercase; + font-size: 16px; font-weight: bold; border: none; color: #FFF !important; border-radius: 3px; background: #49AD9A; box-shadow: 0 -2px 0 #3B8C7E inset; + flex-basis: auto; } .standard-button:hover { diff --git a/migrations/Version20220208145209.php b/migrations/Version20220208145209.php deleted file mode 100644 index e99fc5d43b23e6ac877eafa57e40568f1fb654c2..0000000000000000000000000000000000000000 --- a/migrations/Version20220208145209.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace DoctrineMigrations; - -use Doctrine\DBAL\Schema\Schema; -use Doctrine\Migrations\AbstractMigration; - -/** - * Auto-generated Migration: Please modify to your needs! - */ -final class Version20220208145209 extends AbstractMigration -{ - public function getDescription(): string - { - return ''; - } - - public function up(Schema $schema): void - { - // this up() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE TABLE `group` (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); - $this->addSql('CREATE TABLE group_capsule (group_id INT NOT NULL, capsule_id INT NOT NULL, INDEX IDX_4468F58FFE54D947 (group_id), INDEX IDX_4468F58F714704E9 (capsule_id), PRIMARY KEY(group_id, capsule_id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); - $this->addSql('ALTER TABLE group_capsule ADD CONSTRAINT FK_4468F58FFE54D947 FOREIGN KEY (group_id) REFERENCES `group` (id) ON DELETE CASCADE'); - $this->addSql('ALTER TABLE group_capsule ADD CONSTRAINT FK_4468F58F714704E9 FOREIGN KEY (capsule_id) REFERENCES capsule (id) ON DELETE CASCADE'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE group_capsule DROP FOREIGN KEY FK_4468F58FFE54D947'); - $this->addSql('DROP TABLE `group`'); - $this->addSql('DROP TABLE group_capsule'); - } -} diff --git a/migrations/Version20220216153510.php b/migrations/Version20220216153510.php new file mode 100644 index 0000000000000000000000000000000000000000..7792eafe7ab5fd86d4650cd6464a4cf164e67798 --- /dev/null +++ b/migrations/Version20220216153510.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20220216153510 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE `group` (id INT AUTO_INCREMENT NOT NULL, author INT NOT NULL, name VARCHAR(255) NOT NULL, INDEX IDX_6DC044C5BDAFD8C8 (author), UNIQUE INDEX UNIQ_6DC044C55E237E06BDAFD8C8 (name, author), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE group_capsule (group_id INT NOT NULL, capsule_id INT NOT NULL, INDEX IDX_4468F58FFE54D947 (group_id), INDEX IDX_4468F58F714704E9 (capsule_id), PRIMARY KEY(group_id, capsule_id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE `group` ADD CONSTRAINT FK_6DC044C5BDAFD8C8 FOREIGN KEY (author) REFERENCES `user` (id)'); + $this->addSql('ALTER TABLE group_capsule ADD CONSTRAINT FK_4468F58FFE54D947 FOREIGN KEY (group_id) REFERENCES `group` (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE group_capsule ADD CONSTRAINT FK_4468F58F714704E9 FOREIGN KEY (capsule_id) REFERENCES capsule (id) ON DELETE CASCADE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE group_capsule DROP FOREIGN KEY FK_4468F58FFE54D947'); + $this->addSql('DROP TABLE `group`'); + $this->addSql('DROP TABLE group_capsule'); + $this->addSql('ALTER TABLE capsule CHANGE nom nom VARCHAR(255) NOT NULL COLLATE `utf8_unicode_ci`, CHANGE link link VARCHAR(255) NOT NULL COLLATE `utf8_unicode_ci`, CHANGE edition_link edition_link VARCHAR(255) NOT NULL COLLATE `utf8_unicode_ci`, CHANGE password password VARCHAR(50) DEFAULT \'\' NOT NULL COLLATE `utf8_unicode_ci`'); + $this->addSql('ALTER TABLE invitation_editeur_capsule CHANGE email email VARCHAR(255) NOT NULL COLLATE `utf8_unicode_ci`'); + $this->addSql('ALTER TABLE reset_password_request CHANGE selector selector VARCHAR(20) NOT NULL COLLATE `utf8_unicode_ci`, CHANGE hashed_token hashed_token VARCHAR(100) NOT NULL COLLATE `utf8_unicode_ci`'); + $this->addSql('ALTER TABLE `user` CHANGE email email VARCHAR(255) NOT NULL COLLATE `utf8_unicode_ci`, CHANGE email_canonical email_canonical VARCHAR(255) NOT NULL COLLATE `utf8_unicode_ci`, CHANGE name name VARCHAR(255) NOT NULL COLLATE `utf8_unicode_ci`, CHANGE firstname firstname VARCHAR(255) NOT NULL COLLATE `utf8_unicode_ci`, CHANGE username username VARCHAR(255) NOT NULL COLLATE `utf8_unicode_ci`, CHANGE username_canonical username_canonical VARCHAR(255) NOT NULL COLLATE `utf8_unicode_ci`, CHANGE roles roles LONGTEXT NOT NULL COLLATE `utf8_unicode_ci` COMMENT \'(DC2Type:json)\', CHANGE password password VARCHAR(255) NOT NULL COLLATE `utf8_unicode_ci`, CHANGE salt salt VARCHAR(255) NOT NULL COLLATE `utf8_unicode_ci`'); + } +} diff --git a/src/Controller/CapsuleController.php b/src/Controller/CapsuleController.php index 15fa5a497ef95b2de0768f4ae360dc0c5051197c..fa48abb70049e09252bf1897822f1be21ab0baeb 100755 --- a/src/Controller/CapsuleController.php +++ b/src/Controller/CapsuleController.php @@ -12,8 +12,6 @@ use App\Helper\StringHelper; use App\Repository\CapsuleRepository; use App\Builder\CapsuleBuilder; use App\Form\CreateCapsuleFormType; -use App\Repository\GroupRepository; -use ArrayObject; use Doctrine\ORM\EntityManagerInterface; use Knp\Component\Pager\PaginatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -57,7 +55,9 @@ class CapsuleController extends AbstractController // get all capsules without any filter $all_capsules = $current_user->getCapsules()->toArray(); - $form = $this->createGroupFilterForm($current_user); + $groups = $this->prependGroupAllToUserGroups($current_user); + + $form = $this->createForm(FilterByGroupFormType::class, ['groups' => $groups]); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { @@ -67,8 +67,8 @@ class CapsuleController extends AbstractController throw new \Exception('Group not found'); } - if ($group->getId() !== Group::$ALL_GROUP_ID) { - $all_capsules = $group->getCapsulesIntersection($all_capsules); + if ($group->getId() !== Group::$GROUP_ALL_ID) { + $all_capsules = $group->getCapsules()->toArray(); } } @@ -323,24 +323,27 @@ class CapsuleController extends AbstractController return $capsule; } - private function getDefaultGroup(): Group + private function createGroupAll(User $current_user): Group { - $noGroup = new Group(); - - // TODO : to be translated - $noGroup->setName($this->translator->trans('groups.filter.no_filter')); - $noGroup->setId(Group::$ALL_GROUP_ID); + $group_all = new Group(); + $group_all->setName($this->translator->trans('groups.filter.no_filter')); + $group_all->setId(Group::$GROUP_ALL_ID); + $group_all->setAuthor($current_user); - return $noGroup; + return $group_all; } - private function createGroupFilterForm(User $current_user): FormInterface + /** + * @return array<Group> + */ + private function prependGroupAllToUserGroups(User $current_user): array { - $groups = new ArrayObject($current_user->getGroups()); - $groups = $groups->getArrayCopy(); - array_unshift($groups, self::getDefaultGroup()); + $group_all = $this->createGroupAll($current_user); + $author_groups_without_group_all = $current_user->getGroups()->toArray(); + $groups[] = $group_all; + $groups = array_merge($groups, $author_groups_without_group_all); - return $this->createForm(FilterByGroupFormType::class, ['groups' => $groups]); + return $groups; } } diff --git a/src/Controller/CapsuleGroupController.php b/src/Controller/CapsuleGroupController.php index 6374a92b387d9c23b0eaa63a94429ea36627a617..6c672ae9ee43f94e86574bcc504078ad343b460f 100755 --- a/src/Controller/CapsuleGroupController.php +++ b/src/Controller/CapsuleGroupController.php @@ -6,11 +6,11 @@ use App\Entity\Capsule; use App\Entity\Group; use App\Entity\User; use App\Form\CreateCapsuleGroupsFormType; +use App\Form\DeleteGroupFormType; use App\Form\RemoveGroupFormType; use App\Form\SelectCapsuleGroupsFormType; use App\Repository\CapsuleRepository; use App\Repository\GroupRepository; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormInterface; @@ -39,9 +39,9 @@ class CapsuleGroupController extends AbstractController } /** - * @Route("/capsule/{capsule_id}/groups/edit", name="edit_capsule_groups") + * @Route("/my_groups", name="edit_user_groups") */ - public function edit(int $capsule_id, Request $request): Response + public function editUserGroups(): Response { $current_user = $this->getUser(); @@ -49,55 +49,105 @@ class CapsuleGroupController extends AbstractController return $this->redirectToRoute('app_logout'); } - $capsule = $this->capsule_repository->findOneBy(['id' => $capsule_id]); + $groups = $current_user->getGroups()->toArray(); - if (! $capsule instanceof Capsule) { - throw new \Exception("Capsule does not exist"); + return $this->render('capsule/groups/user_groups.html.twig', [ + 'groups' => $groups + ]); + } + + /** + * @Route("my_groups/create", name="create_group") + */ + public function create(Request $request): Response + { + $current_user = $this->getUser(); + + if (! $current_user instanceof User) { + return $this->redirectToRoute('app_logout'); } - $groups = $capsule->getGroups(); - $editor_groups_not_added = $this->getEditorGroupsNotAdded($current_user, $groups); + $group = new Group(); + $group->setAuthor($current_user); - $form = $this->createForm(SelectCapsuleGroupsFormType::class, ['groups' => $editor_groups_not_added]); + $form = $this->createForm(CreateCapsuleGroupsFormType::class, $group); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $groups = $this->addGroups($form, $capsule); + $group = $this->createAGroup($form, $group); - $groups_names = []; + $this->addFlash( + 'success', + $this->translator->trans( + 'groups.create.success', + [ + 'group_name' => $group->getName() + ] + ) + ); - foreach ($groups as $group) { - $groups_names[] = $group->getName(); + return $this->redirectToRoute('edit_user_groups'); + } + + return $this->render('capsule/groups/create.html.twig', [ + 'createCapsuleGroupsForm' => $form->createView() + ]); + } + + /** + * @Route("/my_groups/{group_id}/delete", name="delete_group") + */ + public function deleteGroup(int $group_id, Request $request): Response + { + $current_user = $this->getUser(); + if (! $current_user instanceof User) { + return $this->redirectToRoute('app_logout'); + } + + $group = $this->group_repository->findOneBy(['id' => $group_id]); + if (! $group instanceof Group) { + throw new \Exception('Group not found'); + } + + $form = $this->createForm(DeleteGroupFormType::class); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $capsules = $group->getCapsules()->toArray(); + foreach ($capsules as $capsule) { + $capsule->removeGroup($group); + $this->entity_manager->persist($capsule); } + $current_user->removeGroup($group); + $this->entity_manager->persist($current_user); + + $this->entity_manager->remove($group); + $this->entity_manager->flush(); + $this->addFlash( 'success', $this->translator->trans( - 'groups.add.success', + 'groups.general.delete.success', [ - 'groups_names' => implode(",", $groups_names), - 'capsule_name' => $capsule->getName() + 'group_name' => $group->getName() ] ) ); - return $this->redirectToRoute('edit_capsule_groups', [ - 'capsule_id' => $capsule_id - ]); + return $this->redirectToRoute('edit_user_groups'); } - return $this->render('capsule/groups/edit.html.twig', [ - 'selectCapsuleGroupsForm' => $form->createView(), - 'capsule' => $capsule, - 'groups' => $groups, - 'existing_groups' => $editor_groups_not_added + return $this->render('capsule/groups/delete.html.twig', [ + 'deleteGroupForm' => $form->createView(), + 'group_name' => $group->getName() ]); } /** - * @Route("capsule/{capsule_id}/groups/create", name="create_group") + * @Route("/capsule/{capsule_id}/groups/edit", name="edit_capsule_groups") */ - public function create(int $capsule_id, Request $request): Response + public function edit(int $capsule_id, Request $request): Response { $current_user = $this->getUser(); @@ -111,18 +161,31 @@ class CapsuleGroupController extends AbstractController throw new \Exception("Capsule does not exist"); } - $form = $this->createForm(CreateCapsuleGroupsFormType::class); + $capsule_groups_by_author = $current_user->getGroupsByCapsule($capsule); + $author_capsule_groups_not_added = $capsule->getNotAddedGroupsByAuthor($current_user); + + $form = $this->createForm( + SelectCapsuleGroupsFormType::class, + ['groups' => $author_capsule_groups_not_added] + ); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $group = $this->createAGroup($form, $capsule); + $capsule_groups = $this->addGroups($form, $capsule); + + $groups_names = []; + + foreach ($capsule_groups as $group) { + $groups_names[] = $group->getName(); + } $this->addFlash( 'success', $this->translator->trans( - 'groups.create.success', + 'groups.add.success', [ - 'group_name' => $group->getName() + 'groups_names' => implode(",", $groups_names), + 'capsule_name' => $capsule->getName() ] ) ); @@ -132,9 +195,11 @@ class CapsuleGroupController extends AbstractController ]); } - return $this->render('capsule/groups/create.html.twig', [ - 'createCapsuleGroupsForm' => $form->createView(), - 'capsule' => $capsule + return $this->render('capsule/groups/capsule_groups.html.twig', [ + 'selectCapsuleGroupsForm' => $form->createView(), + 'capsule' => $capsule, + 'groups' => $capsule_groups_by_author, + 'groups_not_added' => $author_capsule_groups_not_added ]); } @@ -167,6 +232,10 @@ class CapsuleGroupController extends AbstractController if ($form->isSubmitted() && $form->isValid()) { $capsule->removeGroup($group); $this->entity_manager->persist($capsule); + $current_user->removeGroup($group); + $this->entity_manager->persist($current_user); + $group->removeCapsule($capsule); + $this->entity_manager->persist($group); $this->entity_manager->flush(); $this->addFlash( @@ -217,34 +286,15 @@ class CapsuleGroupController extends AbstractController return $groups; } - private function createAGroup(FormInterface $form, Capsule $capsule): Group - { + private function createAGroup( + FormInterface $form, + Group $group + ): Group { $group_name = $form->get('name')->getData(); - - $group = new Group(); $group->setName($group_name); $this->entity_manager->persist($group); - - $capsule->addGroup($group); - $this->entity_manager->persist($capsule); - $this->entity_manager->flush(); return $group; } - - /** - * @param Collection<Group> $groups - * @return array<Group> - */ - private function getEditorGroupsNotAdded(User $current_user, Collection $groups): array - { - return array_udiff( - $current_user->getGroups(), - $groups->toArray(), - function ($obj_a, $obj_b) { - return $obj_a->getId() - $obj_b->getId(); - } - ); - } } diff --git a/src/DataFixtures/GroupFixtures.php b/src/DataFixtures/GroupFixtures.php index 82cb6670ce1bb648173cccdbe8d83f4ebed27235..a6f02c4d850d3658aadb14dab3259ecfb24b1c17 100644 --- a/src/DataFixtures/GroupFixtures.php +++ b/src/DataFixtures/GroupFixtures.php @@ -3,24 +3,51 @@ namespace App\DataFixtures; use App\Entity\Group; +use App\Entity\User; use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Persistence\ObjectManager; -class GroupFixtures extends Fixture +class GroupFixtures extends Fixture implements DependentFixtureInterface { public function load(ObjectManager $manager): void { - $group_all = new Group(); - $group_all->setName("All"); - $manager->persist($group_all); + $user_repository = $manager->getRepository(User::class); + $group_author_1 = $user_repository->findOneBy(['email' => "defaultUser@localhost.com"]); + if (! $group_author_1 instanceof User) { + throw new \Exception("User does not exist."); + } + + $group_author_2 = $user_repository->findOneBy(['email' => "defaultUser2@localhost.com"]); + if (! $group_author_2 instanceof User) { + throw new \Exception("User does not exist."); + } + + $group_pop_1 = $this->createGroup($group_author_1, "Pop", $manager); + $group_rock = $this->createGroup($group_author_1, "Rock", $manager); + + $group_pop_2 = $this->createGroup($group_author_2, "Pop", $manager); + + $author_1_capsules = $group_author_1->getCapsules(); + $author_1_capsules->toArray()[0]->addGroup($group_pop_1); + $manager->flush(); } - /** @phpstan-ignore-next-line */ + private function createGroup(User $author, string $group_name, ObjectManager $manager): Group + { + $group = new Group(); + $group->setName($group_name); + $group->setAuthor($author); + $manager->persist($group); + + return $group; + } + public function getDependencies(): array { return [ - CapsuleFixtures::class, + UserFixtures::class, ]; } } diff --git a/src/Entity/Capsule.php b/src/Entity/Capsule.php index f3e6f715c893e385361331ebfdc6b072d19fb9ac..814801223262c0b268f04f88f72749c38c9df374 100755 --- a/src/Entity/Capsule.php +++ b/src/Entity/Capsule.php @@ -264,4 +264,18 @@ class Capsule return $this; } + + /** + * @return array<Group> + */ + public function getNotAddedGroupsByAuthor(User $author): array + { + return array_udiff( + $author->getGroups()->toArray(), + $this->getGroups()->toArray(), + function ($obj1, $obj2) { + return $obj1->getId() - $obj2->getId(); + } + ); + } } diff --git a/src/Entity/Group.php b/src/Entity/Group.php index af8ad780d8377e498ee1a03db3115e2636657e6e..64b3e07e5b33131d1623d4ff6a0186a3c8629da6 100755 --- a/src/Entity/Group.php +++ b/src/Entity/Group.php @@ -10,12 +10,17 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * @ORM\Entity(repositoryClass=GroupRepository::class) - * @ORM\Table(name="`group`") - * @UniqueEntity(fields={"name"}, message="group.name.unique") + * @ORM\Table(name="`group`", uniqueConstraints={@ORM\UniqueConstraint(columns={"name", "author"})}) + * @UniqueEntity( + * fields={"name", "author"}, + * errorPath="name", + * message="group.name.unique" + * ) */ class Group { - public static int $ALL_GROUP_ID = -1; + public static int $GROUP_ALL_ID = -1; + /** * @ORM\Id * @ORM\GeneratedValue @@ -26,7 +31,15 @@ class Group /** * @ORM\Column(type="string", length=255) */ - private string $name; + protected string $name; + + /** + * + * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="groups") + * @ORM\JoinColumn(name="author", referencedColumnName="id", nullable=false) + * + */ + protected User $author; /** * @var Collection<Capsule> @@ -61,6 +74,18 @@ class Group return $this; } + public function getAuthor(): User + { + return $this->author; + } + + public function setAuthor(User $author): self + { + $this->author = $author; + + return $this; + } + /** * @return Collection|Capsule[] */ @@ -97,19 +122,4 @@ class Group return $this; } - - /** - * @param array<Capsule> $capsules - * @return array<Capsule> - */ - public function getCapsulesIntersection(array $capsules): array - { - return array_uintersect( - $capsules, - $this->getCapsules()->toArray(), - function ($obj_a, $obj_b) { - return $obj_a->getId() - $obj_b->getId(); - } - ); - } } diff --git a/src/Entity/User.php b/src/Entity/User.php index 21bcad23af3a2c2aeb5e0e49c129a8bb663deeca..500b9ec0cbfac0e276210206e54e061daad9da58 100755 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -99,6 +99,12 @@ class User implements UserInterface, LegacyPasswordAuthenticatedUserInterface */ private Collection $capsules; + /** + * @var Collection<Group> + * @ORM\OneToMany(targetEntity="App\Entity\Group", mappedBy="author") + */ + private Collection $groups; + /** * @var string $plainPassword plain password to store before hashing it */ @@ -107,6 +113,7 @@ class User implements UserInterface, LegacyPasswordAuthenticatedUserInterface public function __construct() { $this->capsules = new ArrayCollection(); + $this->groups = new ArrayCollection(); $this->acceptGeneralConditions = false; $this->isVerified = false; $this->credentialExpired = false; @@ -301,19 +308,42 @@ class User implements UserInterface, LegacyPasswordAuthenticatedUserInterface } /** + * @return Collection<Group> + */ + public function getGroups(): Collection + { + return $this->groups; + } + + public function setGroupAll(Group $group_all): void + { + $groups = $this->getGroups()->toArray(); + array_unshift($groups, $group_all); + } + + public function addGroup(Group $group): void + { + $this->groups[] = $group; + } + + public function removeGroup(Group $group): User + { + $this->groups->removeElement($group); + return $this; + } + + /** + * @param Capsule $capsule * @return array<Group> */ - public function getGroups(): array - { - $editor_capsules = $this->getCapsules(); - $existing_groups_for_current_editor = []; - foreach ($editor_capsules as $editor_capsule) { - $existing_groups_for_current_editor = array_merge( - $existing_groups_for_current_editor, - $editor_capsule->getGroups()->toArray() - ); - } - - return $existing_groups_for_current_editor; + public function getGroupsByCapsule(Capsule $capsule): array + { + return array_uintersect( + $this->getGroups()->toArray(), + $capsule->getGroups()->toArray(), + function ($obj1, $obj2) { + return $obj1->getId() - $obj2->getId(); + } + ); } } diff --git a/src/Form/DeleteGroupFormType.php b/src/Form/DeleteGroupFormType.php new file mode 100644 index 0000000000000000000000000000000000000000..20b0ef372d02cc00e24477f29832139afed217b4 --- /dev/null +++ b/src/Form/DeleteGroupFormType.php @@ -0,0 +1,37 @@ +<?php + +namespace App\Form; + +use App\Entity\Group; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ButtonType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class DeleteGroupFormType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add( + 'cancel', + ButtonType::class, + ['label' => 'general.cancel_button', + 'attr' => ['class' => 'button-cancel'] + ] + ) + ->add( + 'delete', + SubmitType::class, + ['label' => 'general.delete'] + ); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Group::class, + ]); + } +} diff --git a/templates/capsule/groups/edit.html.twig b/templates/capsule/groups/capsule_groups.html.twig similarity index 74% rename from templates/capsule/groups/edit.html.twig rename to templates/capsule/groups/capsule_groups.html.twig index 6248ac5e488e16f7b67378628dea051165909003..5129485dccce24ff3c8181cdfa6f98efc7461908 100644 --- a/templates/capsule/groups/edit.html.twig +++ b/templates/capsule/groups/capsule_groups.html.twig @@ -38,13 +38,15 @@ </h5> <ul class="ps-0"> {% for group in groups %} - {% if group.getName() != 'All' %} - <li class="text-capitalize text-secondary list-unstyled p-1"> - {{ group.getName() }} + {% if group.getName() != 'groups.filter.no_filter'|trans %} + <li class="text-secondary list-unstyled p-1"> + <span class="text-capitalize"> + {{ group.getName() }} + </span> - - <a href="/capsule/{{ capsule.getId() }}/groups/{{ group.getId() }}/remove" class="remove-link"> - {{ 'groups.remove.link'|trans }} - </a> + <a href="/capsule/{{ capsule.getId() }}/groups/{{ group.getId() }}/remove" class="remove-link"> + {{ 'groups.remove.link'|trans }} + </a> </li> {% endif %} {% endfor %} @@ -53,13 +55,7 @@ </div> <div class="d-flex flex-column justify-content-center order-md-2 mb-4 col-sm-8 col-md-6 col-lg-5 col-xl-4"> - <h5 class="mb-5"> - <a href="/capsule/{{ capsule.getId() }}/groups/create" class="list-unstyled"> - {{ 'groups.create.title'|trans }} - </a> - </h5> - - {% if existing_groups is not empty %} + {% if groups_not_added is not empty %} <div class="d-flex flex-column justify-content-center mb-4"> {{ form_start(selectCapsuleGroupsForm, {'attr': {novalidate: 'novalidate'}}) }} {{ form_row(selectCapsuleGroupsForm.name, {'row_attr': {'class' : 'm-auto mb-4'}}) }} diff --git a/templates/capsule/groups/create.html.twig b/templates/capsule/groups/create.html.twig index c590d3a25987673defbb2b59eae384c421d504d9..aa0da53e162d2c444d521ab75e5ecf514f5f6bb9 100644 --- a/templates/capsule/groups/create.html.twig +++ b/templates/capsule/groups/create.html.twig @@ -23,7 +23,7 @@ <div class="d-flex flex-column flex-sm-row justify-content-center align-items-center align-items-sm-start"> {{ form_row(createCapsuleGroupsForm.validate, {'row_attr': {'class' : 'me-sm-3 mb-3'}}) }} - <a href="/capsule/{{ capsule.getId() }}/groups/edit"> + <a href="/my_groups"> {{ form_row(createCapsuleGroupsForm.go_back) }} </a> </div> diff --git a/templates/capsule/groups/delete.html.twig b/templates/capsule/groups/delete.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..80dcb3e8135c58a1ae22da7886572c75900a6334 --- /dev/null +++ b/templates/capsule/groups/delete.html.twig @@ -0,0 +1,34 @@ +{% extends 'layout.html.twig' %} + +{% block title %} + {{ 'groups.general.delete.title'|trans }} +{% endblock %} + +{% block body %} + + <div> + <div class="row w-100 gx-0"> + <div class="row-title-box"> + <h3 class="row-title"> + {{ 'groups.general.delete.title'|trans }} + </h3> + </div> + </div> + + <div class="d-flex flex-column justify-content-center align-items-center"> + + <p class="text-secondary fs-5 mb-5"> + {{ 'groups.general.delete.text'|trans({'%group_name%': group_name}) }} + </p> + + {{ form_start(deleteGroupForm, {'attr': {novalidate: 'novalidate', 'class': 'd-flex flex-row justify-content-center'}}) }} + {{ form_row(deleteGroupForm.delete, {'row_attr': {'class' : 'm-auto mb-2 me-3'}}) }} + <a href="/my_groups"> + {{ form_row(deleteGroupForm.cancel, {'row_attr': {'class' : 'm-auto mb-2 bg-secondary rounded ms-3'}}) }} + </a> + {{ form_end(deleteGroupForm) }} + </div> + + </div> + +{% endblock %} \ No newline at end of file diff --git a/templates/capsule/groups/user_groups.html.twig b/templates/capsule/groups/user_groups.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..db6848f778fa5f23c1cdedbd07b43efab5721fbf --- /dev/null +++ b/templates/capsule/groups/user_groups.html.twig @@ -0,0 +1,66 @@ +{% extends 'layout.html.twig' %} + +{% block title %} + {{ 'groups.general.title'|trans }} + - + {{ parent() }} +{% endblock %} + +{% block body %} + <div> + <div class="row w-100 gx-0"> + <div class="row-title-box"> + <h3 class="row-title"> + {{ 'groups.general.title'|trans }} + </h3> + </div> + </div> + + {% for flashWarning in app.flashes('warning') %} + <div class="text-center alert alert-warning col-5 mx-auto my-5 mt-2" role="alert"> + {{ flashWarning }} + </div> + {% endfor %} + + {% for flashSuccess in app.flashes('success') %} + <div class="text-center alert alert-success col-5 mx-auto my-5 mt-2" role="alert"> + {{ flashSuccess }} + </div> + {% endfor %} + + <div class="d-flex flex-md-row flex-column justify-content-center"> + + <div class="d-flex flex-row pe-md-5 fw-normal me-0 me-md-5"> + <div class="pe-3 pe-md-4 text-nowrap"> + <h5> + {{ 'groups.general.all'|trans }} + </h5> + <ul class="ps-0"> + {% for group in groups %} + {% if group.getName() != 'groups.filter.no_filter'|trans %} + <li class="text-secondary list-unstyled p-1"> + <span class="text-capitalize"> + {{ group.getName() }} + </span> + - + <a href="/my_groups/{{ group.getId() }}/delete" class="remove-link"> + {{ 'general.delete'|trans }} + </a> + </li> + {% endif %} + {% endfor %} + </ul> + </div> + </div> + + <div class="d-flex align-items-center mt-3 mt-md-0"> + <button type="submit" class="p-2"> + <a href="/my_groups/create" class="list-unstyled text-decoration-none text-white"> + {{ 'groups.create.title'|trans }} + </a> + </button> + </div> + + </div> + </div> +{% endblock %} \ No newline at end of file diff --git a/templates/capsule/index.html.twig b/templates/capsule/index.html.twig index b5413a1606ac2ddfe3760a07bc9cebd8b2ddb280..c70d9a25ea9ec8205c7d395a329df0f8df0a564e 100644 --- a/templates/capsule/index.html.twig +++ b/templates/capsule/index.html.twig @@ -14,12 +14,14 @@ </h2> {% if app.request.query.get('page')|default(1) == 1 %} - <div class="mb-3 mb-sm-0"> - {{ form_start(filterByGroupForm, {'attr': {novalidate: 'novalidate', 'class': 'd-flex flex-column flex-sm-row mb-0 align-items-center pt-3'}}) }} - {{ form_row(filterByGroupForm.name, {'attr': {'class': ''}}) }} - {{ form_row(filterByGroupForm.filter, {'attr': {'class': 'ms-2'}}) }} - {{ form_end(filterByGroupForm) }} - </div> + {% if current_user.getGroups()|length > 1 %} + <div class="mb-3 mb-sm-0"> + {{ form_start(filterByGroupForm, {'attr': {novalidate: 'novalidate', 'class': 'd-flex flex-column flex-sm-row mb-0 align-items-center pt-3'}}) }} + {{ form_row(filterByGroupForm.name, {'attr': {'class': ''}}) }} + {{ form_row(filterByGroupForm.filter, {'attr': {'class': 'ms-2'}}) }} + {{ form_end(filterByGroupForm) }} + </div> + {% endif %} {% endif %} <form class="d-flex mb-4 mb-lg-0"> diff --git a/templates/layout.html.twig b/templates/layout.html.twig index deb0ae75ea6f813f39bca214619befa203c58f9c..a202620327414838f6dfb1fedc728a540a0221c8 100644 --- a/templates/layout.html.twig +++ b/templates/layout.html.twig @@ -25,9 +25,14 @@ </a> {% if is_granted("IS_AUTHENTICATED_REMEMBERED") %} - <div id="user-block"> + <div id="user-block" class="d-flex flex-column"> <div id="user-block-nom">{{ app.user.firstName }} {{ app.user.lastName }}</div> - <a href="{{ path('show_profile') }}" class="text-decoration-none fs-5">My profile</a> + <a href="{{ path('show_profile') }}" class="text-decoration-none fs-5"> + {{ 'user.profile.title'|trans }} + </a> + <a href="{{ path('edit_user_groups') }}" class="text-decoration-none fs-5"> + {{ 'groups.general.title'|trans }} + </a> <form> <button class="btn btn-primary" formaction="/logout"> {{ 'general.log_out'|trans }} diff --git a/tests/functional/CapsuleEditorControllerTest.php b/tests/functional/CapsuleEditorControllerTest.php index 2be2f65302c2b9a781fe24ccd080fe31dbc97f08..b843c959b01bad2f5dfb185151ae14d236f3eab4 100644 --- a/tests/functional/CapsuleEditorControllerTest.php +++ b/tests/functional/CapsuleEditorControllerTest.php @@ -30,7 +30,6 @@ class CapsuleEditorControllerTest extends WebTestCase ->get('doctrine') ->getManager(); - $this->user_repository = $this->object_manager->getRepository(User::class); $this->capsule_repository = $this->object_manager->getRepository(Capsule::class); diff --git a/tests/functional/CapsuleGroupControllerTest.php b/tests/functional/CapsuleGroupControllerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ac32fb8a6671c163a987464f1106e30afa93507d --- /dev/null +++ b/tests/functional/CapsuleGroupControllerTest.php @@ -0,0 +1,130 @@ +<?php + +namespace App\Tests\functional; + +use App\Entity\Capsule; +use App\Entity\Group; +use App\Entity\User; +use App\Repository\CapsuleRepository; +use App\Repository\GroupRepository; +use App\Repository\UserRepository; +use Doctrine\Persistence\ObjectManager; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +class CapsuleGroupControllerTest extends WebTestCase +{ + private KernelBrowser $client; + private ObjectManager $object_manager; + private UserRepository $user_repository; /** @phpstan-ignore-line */ + private CapsuleRepository $capsule_repository; /** @phpstan-ignore-line */ + private GroupRepository $group_repository; + private User $user_1; + private User $user_2; + private Group $group_pop_own_by_user_1; + private Group $group_rock_own_by_user_1; + + protected function setUp(): void + { + self::ensureKernelShutdown(); + + $this->client = static::createClient(); + + $this->object_manager = $this->client->getContainer() + ->get('doctrine') + ->getManager(); + + $this->user_repository = $this->object_manager->getRepository(User::class); + $this->capsule_repository = $this->object_manager->getRepository(Capsule::class); + $this->group_repository = $this->object_manager->getRepository(Group::class); + + $this->setUsers(); + $this->setCapsuleGroupPop(); + $this->setCapsuleGroupRock(); + } + + protected function tearDown(): void + { + parent::tearDown(); + self::ensureKernelShutdown(); + } + + public function testUserShouldNotBeAbleToCreateACapsuleGroupWithExistingGroupName(): void + { + $this->client->loginUser($this->user_1); + $uri = '/my_groups/create'; + $crawler = $this->client->request('GET', $uri); + $this->assertResponseIsSuccessful(); + + $this->client->enableProfiler(); + $submit_button = $crawler->selectButton('Create a new group'); + $form = $submit_button->form(); + $this->assertContains($this->group_pop_own_by_user_1, $this->user_1->getGroups()->toArray()); + + $form['create_capsule_groups_form[name]'] = $this->group_pop_own_by_user_1->getName(); + $this->client->submit($form); + + $this->assertFalse($this->client->getResponse()->isRedirect()); + } + + public function testUserShouldBeAbleToCreateACapsuleGroupWithTheSameGroupNameUsedByAnotherUser(): void + { + $this->client->loginUser($this->user_2); + $uri = '/my_groups/create'; + $crawler = $this->client->request('GET', $uri); + $this->assertResponseIsSuccessful(); + + $this->client->enableProfiler(); + $submit_button = $crawler->selectButton('Create a new group'); + $form = $submit_button->form(); + $this->assertContains($this->group_pop_own_by_user_1, $this->user_1->getGroups()->toArray()); + + $form['create_capsule_groups_form[name]'] = $this->group_rock_own_by_user_1->getName(); + $this->client->submit($form); + + $this->assertTrue($this->client->getResponse()->isRedirect()); + } + + private function setUsers(): void + { + $verified_user_1 = $this->user_repository + ->findOneBy(['email' => 'defaultUser@localhost.com']); + + if (! $verified_user_1 instanceof User) { + throw new \Exception("User does not exist."); + } + + $this->user_1 = $verified_user_1; + + $user_2 = $this->user_repository + ->findOneBy(['email' => 'defaultUser2@localhost.com']); + + if (! $user_2 instanceof User) { + throw new \Exception("User does not exist."); + } + + $this->user_2 = $user_2; + } + + private function setCapsuleGroupPop(): void + { + $group_pop = $this->group_repository->findOneBy(['name' => 'Pop', 'author' => $this->user_1]); + + if (! $group_pop instanceof Group) { + throw new \Exception("Group does not exist."); + } + + $this->group_pop_own_by_user_1 = $group_pop; + } + + private function setCapsuleGroupRock(): void + { + $group_rock = $this->group_repository->findOneBy(['name' => 'Rock', 'author' => $this->user_1]); + + if (! $group_rock instanceof Group) { + throw new \Exception("Group does not exist."); + } + + $this->group_rock_own_by_user_1 = $group_rock; + } +} diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index 0ca87ae5989f26eb7d394eef75ec64335afba1e0..637a19cd59faa190490b420e1c72922b25e60509 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -9,6 +9,7 @@ general: greeting: Cheers! validate: Validate save: Save + delete: Delete login: account_disabled_feedback: Your user account is disabled. Please click on the link your receive by email to validate your registration. @@ -159,6 +160,13 @@ editors: If you want to remove the capsule from your list, go to the capsule list page and click on "delete capsule" groups: + general: + title: My groups + all: All groups + delete: + title: Delete + text: Do you really want to delete group %group_name%? + success: Group group_name deleted successfully list: title: Capsule groups edit: @@ -169,7 +177,7 @@ groups: success: Group group_name has been created and added to the capsule successfully name: Choose a new group name validate: Create a new group - go_back: Go back to capsule groups + go_back: Go back to my groups add: success: Group(s) groups_names has/have been added successfully to capsule capsule_name validate: Add theses groups to the capsule diff --git a/translations/messages.fr.yaml b/translations/messages.fr.yaml index 258d7cec2ef3119f597bbb3e46dd80276a28b0d5..b4fef15df56a4272c595c676680e464e941fc0f1 100644 --- a/translations/messages.fr.yaml +++ b/translations/messages.fr.yaml @@ -9,6 +9,7 @@ general: greeting: Salutation ! validate: Valider save: Enregistrer + delete: Supprimer login: account_disabled_feedback: Le compte utilisateur a été désactivé. Veuillez cliquer sur le lien pour recevoir un courriel de validation @@ -158,6 +159,13 @@ editors: Si vous voulez enlever la capsule de votre liste, allez sur la page qui affiche la liste de vos capsules et cliquer sur "supprimer la capsule" groups: + general: + title: Mes groupes + all: Tous les groupes + delete: + title: Supprimer le groupe + text: Souhaitez-vous vraiment supprimer le groupe %group_name% ? + success: Le groupe group_name a bien été supprimé list: title: Groupes de la capsule edit: