diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 0b8cb18..1db3589 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -45,5 +45,6 @@ security: - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/resetting, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS } + - { path: ^/admin/user, roles: ROLE_ADMIN } - { path: ^/admin, roles: ROLE_USER } - { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY } diff --git a/src/Controller/Auth/AuthController.php b/src/Controller/Auth/AuthController.php index ce4ec79..465a04e 100644 --- a/src/Controller/Auth/AuthController.php +++ b/src/Controller/Auth/AuthController.php @@ -98,7 +98,7 @@ class AuthController extends AbstractController EntityManager $entityManager ): Response { if ($this->getUser()) { - return $this->redirectToRoute('index'); + return $this->redirectToRoute('admin_dashboard_index'); } $account = $repository->findOneByConfirmationToken($token); diff --git a/src/Controller/Blog/CategoryAdminController.php b/src/Controller/Blog/CategoryAdminController.php index 4d81499..6d0b134 100644 --- a/src/Controller/Blog/CategoryAdminController.php +++ b/src/Controller/Blog/CategoryAdminController.php @@ -92,6 +92,7 @@ class CategoryAdminController extends AdminController { $posts = $postQuery->create() ->orderBy('.publishedAt', 'DESC') + ->orderBy('.createdAt', 'DESC') ->inCategory($entity) ->paginate(1, 10) ; diff --git a/src/Controller/User/UserAdminController.php b/src/Controller/User/UserAdminController.php new file mode 100644 index 0000000..c889236 --- /dev/null +++ b/src/Controller/User/UserAdminController.php @@ -0,0 +1,160 @@ +paginate($page); + + return $this->render('user/user_admin/index.html.twig', [ + 'pager' => $pager, + ]); + } + + /** + * @Route("/new", name="admin_user_new") + */ + public function new( + EntityFactory $factory, + EntityManager $entityManager, + UserPasswordEncoderInterface $encoder, + Request $request + ): Response + { + $entity = $factory->create($this->getUser()); + $form = $this->createForm(EntityType::class, $entity); + + if ($request->isMethod('POST')) { + $form->handleRequest($request); + + if ($form->isValid()) { + $entityManager->create($entity); + $this->addFlash('success', 'Donnée enregistrée.'); + + return $this->redirectToRoute('admin_user_edit', [ + 'entity' => $entity->getId(), + ]); + } + $this->addFlash('warning', 'Le formulaire est invalide.'); + } + + return $this->render('user/user_admin/new.html.twig', [ + 'form' => $form->createView(), + 'entity' => $entity, + ]); + } + + /** + * @Route("/edit/{entity}", name="admin_user_edit") + */ + public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response + { + $form = $this->createForm(EntityType::class, $entity); + + if ($request->isMethod('POST')) { + $form->handleRequest($request); + + if ($form->isValid()) { + $entityManager->update($entity); + $this->addFlash('success', 'Donnée enregistrée.'); + + return $this->redirectToRoute('admin_user_edit', [ + 'entity' => $entity->getId(), + ]); + } + $this->addFlash('warning', 'Le formulaire est invalide.'); + } + + return $this->render('user/user_admin/edit.html.twig', [ + 'form' => $form->createView(), + 'entity' => $entity, + ]); + } + + /** + * @Route("/show/{entity}", name="admin_user_show") + */ + public function show(Entity $entity, PostRepositoryQuery $postQuery): Response + { + $posts = $postQuery->create() + ->orderBy('.publishedAt', 'DESC') + ->orderBy('.createdAt', 'DESC') + ->filterByAuthor($entity) + ->paginate(1, 10) + ; + + return $this->render('user/user_admin/show.html.twig', [ + 'entity' => $entity, + 'posts' => $posts, + ]); + } + + /** + * @Route("/resetting_request/{entity}", name="admin_user_resetting_request", methods={"POST"}) + */ + public function requestResetting( + Entity $entity, + EntityManager $entityManager, + TokenGeneratorInterface $tokenGenerator, + EventDispatcherInterface $eventDispatcher, + Request $request + ): Response + { + if ($this->isCsrfTokenValid('resetting_request'.$entity->getId(), $request->request->get('_token'))) { + $entity->setConfirmationToken($tokenGenerator->generateToken()); + $entity->setPasswordRequestedAt(new \DateTime('now')); + + $entityManager->update($entity); + $eventDispatcher->dispatch(new PasswordRequestEvent($entity), PasswordRequestEvent::EVENT); + + $this->addFlash('success', 'Demande envoyée.'); + } + + return $this->redirectToRoute('admin_user_edit', [ + 'entity' => $entity->getId(), + ]); + } + + /** + * @Route("/delete/{entity}", name="admin_user_delete", methods={"DELETE"}) + */ + public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response + { + if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) { + $entityManager->delete($entity); + + $this->addFlash('success', 'Données supprimées.'); + } + + return $this->redirectToRoute('admin_user_index'); + } + + public function getSection(): string + { + return 'user'; + } +} diff --git a/src/Entity/Blog/Post.php b/src/Entity/Blog/Post.php index 8daafd2..d3d95f0 100644 --- a/src/Entity/Blog/Post.php +++ b/src/Entity/Blog/Post.php @@ -71,6 +71,7 @@ class Post implements EntityInterface /** * @ORM\ManyToOne(targetEntity=User::class, inversedBy="posts") + * @ORM\JoinColumn(nullable=true, onDelete="SET NULL") */ private $author; diff --git a/src/Entity/User.php b/src/Entity/User.php index 1afabed..43f9167 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -14,7 +14,6 @@ use Symfony\Component\Security\Core\User\UserInterface; /** * @ORM\Entity(repositoryClass=UserRepository::class) * @ORM\HasLifecycleCallbacks - * @ORM\Table(name="`user`") */ class User implements UserInterface, TwoFactorInterface, EntityInterface { @@ -72,6 +71,11 @@ class User implements UserInterface, TwoFactorInterface, EntityInterface */ private $posts; + /** + * @ORM\Column(type="boolean", options={"default"=0}) + */ + private $isWriter; + public function __construct() { $this->posts = new ArrayCollection(); @@ -109,9 +113,9 @@ class User implements UserInterface, TwoFactorInterface, EntityInterface */ public function getRoles(): array { - $roles = $this->roles; - // guarantee every user at least has ROLE_USER - $roles[] = 'ROLE_USER'; + if ($this->getIsWriter()) { + $roles[] = 'ROLE_WRITER'; + } if ($this->getIsAdmin()) { $roles[] = 'ROLE_ADMIN'; @@ -276,4 +280,16 @@ class User implements UserInterface, TwoFactorInterface, EntityInterface return $this; } + + public function getIsWriter(): ?bool + { + return $this->isWriter; + } + + public function setIsWriter(bool $isWriter): self + { + $this->isWriter = $isWriter; + + return $this; + } } diff --git a/src/Factory/UserFactory.php b/src/Factory/UserFactory.php new file mode 100644 index 0000000..3afac7a --- /dev/null +++ b/src/Factory/UserFactory.php @@ -0,0 +1,36 @@ + + */ +class UserFactory +{ + protected TokenGeneratorInterface $tokenGenerator; + protected UserPasswordEncoderInterface $encoder; + + public function __construct(TokenGeneratorInterface $tokenGenerator, UserPasswordEncoderInterface $encoder) + { + $this->tokenGenerator = $tokenGenerator; + $this->encoder = $encoder; + } + + public function create(): User + { + $entity = new User(); + + $entity->setPassword($this->encoder->encodePassword( + $entity, + $this->tokenGenerator->generateToken() + )); + + return $entity; + } +} diff --git a/src/Form/UserType.php b/src/Form/UserType.php new file mode 100644 index 0000000..632ea9d --- /dev/null +++ b/src/Form/UserType.php @@ -0,0 +1,92 @@ +add( + 'email', + EmailType::class, + [ + 'label' => 'E-mail', + 'required' => true, + 'attr' => [ + ], + 'constraints' => [ + new Email(), + ], + ] + ); + + $builder->add( + 'displayName', + TextType::class, + [ + 'label' => 'Nom complet', + 'required' => true, + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'displayName', + TextType::class, + [ + 'label' => 'Nom complet', + 'required' => true, + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'isAdmin', + CheckboxType::class, + [ + 'label' => 'Administrateur⋅trice', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + $builder->add( + 'isWriter', + CheckboxType::class, + [ + 'label' => 'Rédacteur⋅trice', + 'required' => false, + 'attr' => [ + ], + 'constraints' => [ + ], + ] + ); + + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); + } +} diff --git a/src/Repository/Blog/PostRepositoryQuery.php b/src/Repository/Blog/PostRepositoryQuery.php index 4d45843..5fc08bc 100644 --- a/src/Repository/Blog/PostRepositoryQuery.php +++ b/src/Repository/Blog/PostRepositoryQuery.php @@ -5,6 +5,7 @@ namespace App\Repository\Blog; use App\Entity\Blog\Category; use App\Repository\RepositoryQuery; use Knp\Component\Pager\PaginatorInterface; +use App\Entity\User; /** * class PostRepositoryQuery. @@ -30,4 +31,14 @@ class PostRepositoryQuery extends RepositoryQuery return $this; } + + public function filterByAuthor(User $user) + { + $this + ->andWhere('.author = :author') + ->setParameter(':author', $user->getId()) + ; + + return $this; + } } diff --git a/templates/admin/layout.html.twig b/templates/admin/layout.html.twig index d83e108..d13618e 100644 --- a/templates/admin/layout.html.twig +++ b/templates/admin/layout.html.twig @@ -50,49 +50,53 @@ - + {% if is_granted('ROLE_WRITER') %} + - + {% endif %} - + {% if is_granted('ROLE_ADMIN') %} + - + {% endif %}
diff --git a/templates/blog/post_admin/index.html.twig b/templates/blog/post_admin/index.html.twig index 23e0506..4f4254b 100644 --- a/templates/blog/post_admin/index.html.twig +++ b/templates/blog/post_admin/index.html.twig @@ -55,7 +55,8 @@ {% set categories = categories|merge(['' ~ category.title ~ '']) %} {% endfor %} - Dans {{ categories|join(', ')|raw }} par {{ item.author.displayName }} + Dans {{ categories|join(', ')|raw }} + par {{ item.author ? item.author.displayName : '-' }} diff --git a/templates/user/user_admin/_form.html.twig b/templates/user/user_admin/_form.html.twig new file mode 100644 index 0000000..0aabc9e --- /dev/null +++ b/templates/user/user_admin/_form.html.twig @@ -0,0 +1,12 @@ +
+
+
+ {% for item in ['displayName', 'email', 'isAdmin', 'isWriter'] %} +
+ {{ form_row(form[item]) }} +
+ {% endfor %} +
+
+
+ diff --git a/templates/user/user_admin/edit.html.twig b/templates/user/user_admin/edit.html.twig new file mode 100644 index 0000000..7bb320b --- /dev/null +++ b/templates/user/user_admin/edit.html.twig @@ -0,0 +1,64 @@ +{% extends 'admin/layout.html.twig' %} + +{% block body %} +
+
+
+

{{ entity.displayName }}

+
+ +
+
+ + + Retour à la liste + + + + Voir + + + + + + +
+
+
+
+ +
+
+
+
+ {{ include('user/user_admin/_form.html.twig') }} +
+
+
+ + {{ form_rest(form) }} +
+ +
+ + +
+ +
+ +
+{% endblock %} diff --git a/templates/user/user_admin/index.html.twig b/templates/user/user_admin/index.html.twig new file mode 100644 index 0000000..b8453b2 --- /dev/null +++ b/templates/user/user_admin/index.html.twig @@ -0,0 +1,71 @@ +{% extends 'admin/layout.html.twig' %} + +{% block body %} +
+
+
+

Utilisateurs

+
+ + +
+ + {{ knp_pagination_render(pager) }} +
+ + + + + + + + + + {% for item in pager %} + {% set edit = path('admin_user_edit', {entity: item.id}) %} + {% set show = path('admin_user_show', {entity: item.id}) %} + + + + + + {% else %} + + + + {% endfor %} + +
UtilisateursActions
+ + {{ item.displayName }} + + + {{ item.email }} + + + + + + +
+ + +
+
+
+ +
+
+ Aucun résultat +
+
+{% endblock %} diff --git a/templates/user/user_admin/new.html.twig b/templates/user/user_admin/new.html.twig new file mode 100644 index 0000000..f4d4a86 --- /dev/null +++ b/templates/user/user_admin/new.html.twig @@ -0,0 +1,39 @@ +{% extends 'admin/layout.html.twig' %} + +{% block body %} +
+
+
+

Nouveau⋅elle utilisateur⋅tice

+
+ +
+
+ + + + Retour à la liste + + + +
+
+
+
+ +
+
+
+
+ {{ include('user/user_admin/_form.html.twig') }} +
+
+
+ + {{ form_rest(form) }} +
+{% endblock %} diff --git a/templates/user/user_admin/show.html.twig b/templates/user/user_admin/show.html.twig new file mode 100644 index 0000000..b26e8d9 --- /dev/null +++ b/templates/user/user_admin/show.html.twig @@ -0,0 +1,84 @@ +{% extends 'admin/layout.html.twig' %} + +{% block body %} +
+
+
+

{{ entity.displayName }}

+
+ + +
+
+ +
+
+
    +
  • + Titre + + {{ entity.displayName }} +
  • +
  • + Sous-titre + + {{ entity.email }} +
  • +
  • + Permissions + + {{ entity.roles|join(', ')|replace({ + ROLE_USER: 'Utilisateur⋅trice', + ROLE_WRITER: 'Rédacteur⋅trice', + ROLE_ADMIN: 'Administrateur⋅trice', + }) }} +
  • +
+
+ +
+ + + + + + + + {% for item in posts %} + + + + {% else %} + + + + {% endfor %} + +
Derniers articles
+ + {{ item.title }} + +
+
+ +
+
+ Aucun résultat +
+
+
+
+{% endblock %}