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..d713918e9c1983168fbbf57ab6d552cfa4831159 100755 --- a/src/Controller/CapsuleController.php +++ b/src/Controller/CapsuleController.php @@ -13,7 +13,6 @@ 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; @@ -30,15 +29,18 @@ class CapsuleController extends AbstractController private CapsuleRepository $capsule_repository; private TranslatorInterface $translator; private EntityManagerInterface $entity_manager; + private GroupRepository $group_repository; public function __construct( CapsuleRepository $capsule_repository, TranslatorInterface $translator, - EntityManagerInterface $entity_manager + EntityManagerInterface $entity_manager, + GroupRepository $group_repository ) { $this->capsule_repository = $capsule_repository; $this->translator = $translator; $this->entity_manager = $entity_manager; + $this->group_repository = $group_repository; } /** @@ -67,7 +69,7 @@ class CapsuleController extends AbstractController throw new \Exception('Group not found'); } - if ($group->getId() !== Group::$ALL_GROUP_ID) { + if ($group->getName() !== $this->translator->trans('groups.filter.no_filter')) { $all_capsules = $group->getCapsulesIntersection($all_capsules); } } @@ -323,24 +325,33 @@ class CapsuleController extends AbstractController return $capsule; } - private function getDefaultGroup(): Group + private function createGroupFilterForm(User $current_user): FormInterface { - $noGroup = new Group(); - - // TODO : to be translated - $noGroup->setName($this->translator->trans('groups.filter.no_filter')); - $noGroup->setId(Group::$ALL_GROUP_ID); - - return $noGroup; + $this->createGroupAll($current_user); + $groups = $current_user->getGroups()->toArray(); + return $this->createForm(FilterByGroupFormType::class, ['groups' => $groups]); } - private function createGroupFilterForm(User $current_user): FormInterface + private function createGroupAll(User $current_user): Group { - $groups = new ArrayObject($current_user->getGroups()); - $groups = $groups->getArrayCopy(); - array_unshift($groups, self::getDefaultGroup()); + $group_all = $this->group_repository->findOneBy( + [ + 'name' => $this->translator->trans('groups.filter.no_filter'), + 'author' => $current_user->getId() + ] + ); + if ($group_all instanceof Group) { + return $group_all; + } - return $this->createForm(FilterByGroupFormType::class, ['groups' => $groups]); + $group_all = new Group(); + $group_all->setId($group_all::$GROUP_ALL_ID); + $group_all->setAuthor($current_user); + $group_all->setName($this->translator->trans('groups.filter.no_filter')); + $this->entity_manager->persist($group_all); + $this->entity_manager->flush(); + + return $group_all; } } diff --git a/src/Controller/CapsuleGroupController.php b/src/Controller/CapsuleGroupController.php index 6374a92b387d9c23b0eaa63a94429ea36627a617..798e9055a53080efca59bd544fa9366e671c2c87 100755 --- a/src/Controller/CapsuleGroupController.php +++ b/src/Controller/CapsuleGroupController.php @@ -10,7 +10,6 @@ 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; @@ -55,18 +54,30 @@ class CapsuleGroupController extends AbstractController throw new \Exception("Capsule does not exist"); } - $groups = $capsule->getGroups(); - $editor_groups_not_added = $this->getEditorGroupsNotAdded($current_user, $groups); + $group_all = $this->group_repository->findOneBy( + [ + 'name' => $this->translator->trans('groups.filter.no_filter'), + 'author' => $current_user->getId() + ] + ); + + $current_user->getGroups()->removeElement($group_all); + + $capsule_groups_by_author = $current_user->getGroupsByCapsule($capsule); + $author_capsule_groups_not_added = $capsule->getNotAddedGroupsByAuthor($current_user); - $form = $this->createForm(SelectCapsuleGroupsFormType::class, ['groups' => $editor_groups_not_added]); + $form = $this->createForm( + SelectCapsuleGroupsFormType::class, + ['groups' => $author_capsule_groups_not_added] + ); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $groups = $this->addGroups($form, $capsule); + $capsule_groups = $this->addGroups($form, $capsule); $groups_names = []; - foreach ($groups as $group) { + foreach ($capsule_groups as $group) { $groups_names[] = $group->getName(); } @@ -89,8 +100,8 @@ class CapsuleGroupController extends AbstractController return $this->render('capsule/groups/edit.html.twig', [ 'selectCapsuleGroupsForm' => $form->createView(), 'capsule' => $capsule, - 'groups' => $groups, - 'existing_groups' => $editor_groups_not_added + 'groups' => $capsule_groups_by_author, + 'groups_not_added' => $author_capsule_groups_not_added ]); } @@ -111,11 +122,14 @@ class CapsuleGroupController extends AbstractController throw new \Exception("Capsule does not exist"); } - $form = $this->createForm(CreateCapsuleGroupsFormType::class); + $group = new Group(); + $group->setAuthor($current_user); + + $form = $this->createForm(CreateCapsuleGroupsFormType::class, $group); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $group = $this->createAGroup($form, $capsule); + $group = $this->createAGroup($form, $capsule, $group); $this->addFlash( 'success', @@ -167,6 +181,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,11 +235,12 @@ class CapsuleGroupController extends AbstractController return $groups; } - private function createAGroup(FormInterface $form, Capsule $capsule): Group - { + private function createAGroup( + FormInterface $form, + Capsule $capsule, + Group $group + ): Group { $group_name = $form->get('name')->getData(); - - $group = new Group(); $group->setName($group_name); $this->entity_manager->persist($group); @@ -232,19 +251,4 @@ class CapsuleGroupController extends AbstractController 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..11ef8ba22994396f56587442b7c96cd72198a062 100644 --- a/src/DataFixtures/GroupFixtures.php +++ b/src/DataFixtures/GroupFixtures.php @@ -3,24 +3,40 @@ 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; +use Symfony\Contracts\Translation\TranslatorInterface; -class GroupFixtures extends Fixture +class GroupFixtures extends Fixture implements DependentFixtureInterface { + private TranslatorInterface $translator; + + public function __construct(TranslatorInterface $translator) + { + $this->translator = $translator; + } + public function load(ObjectManager $manager): void { + $user_repository = $manager->getRepository(User::class); + $group_author = $user_repository->findOneBy(['email' => "defaultUser@localhost.com"]); + if (! $group_author instanceof User) { + throw new \Exception("User does not exist."); + } + $group_all = new Group(); - $group_all->setName("All"); + $group_all->setName($this->translator->trans('groups.filter.no_filter')); + $group_all->setAuthor($group_author); $manager->persist($group_all); $manager->flush(); } - /** @phpstan-ignore-next-line */ 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..50bde465c451432e02af10df612a2448b3338953 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[] */ 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/templates/capsule/groups/edit.html.twig b/templates/capsule/groups/edit.html.twig index 6248ac5e488e16f7b67378628dea051165909003..fc833d20e5103cc9f1dd1fb023a8cb807468c1dc 100644 --- a/templates/capsule/groups/edit.html.twig +++ b/templates/capsule/groups/edit.html.twig @@ -38,7 +38,7 @@ </h5> <ul class="ps-0"> {% for group in groups %} - {% if group.getName() != 'All' %} + {% if group.getName() != 'groups.filter.no_filter'|trans %} <li class="text-capitalize text-secondary list-unstyled p-1"> {{ group.getName() }} - @@ -59,7 +59,7 @@ </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/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">