diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 7b38924..1e7714e 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -35,6 +35,7 @@ security: # Note: Only the *first* access control that matches will be used access_control: - { path: ^/task/*, roles: ROLE_USER } + - { path: ^/project/*, roles: ROLE_USER } # - { path: ^/admin, roles: ROLE_ADMIN } # - { path: ^/profile, roles: ROLE_USER } diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php new file mode 100644 index 0000000..59ee0c7 --- /dev/null +++ b/src/Controller/ProjectController.php @@ -0,0 +1,98 @@ +getDoctrine() + ->getManager() + ->getRepository(Project::class) + ->getAvailableProject($this->getUser()->getId(),$this->isGranted('ROLE_ADMIN')); + + return $this->render('project/index.html.twig', [ + 'projects' => $projects + ]); + } + + /** + * @Route("/project/create/form", name="project_create_form") + */ + public function createProjectForm(Request $request): Response + { + $project = new Project(); + $form = $this->createForm(ProjectType::class, $project); + $form->handleRequest($request); + + if($form->isSubmitted() && $form->isValid()) + { + $project->setAuthor($this->getUser()); + $project->setToken(); + $this->getDoctrine()->getManager()->persist($project); + $this->getDoctrine()->getManager()->flush(); + return $this->redirectToRoute("project_list"); + } + + return $this->render('project/create_project_form.html.twig', [ + 'form' => $form->createView() + ]); + } + + /** + * @Route("/project/{id}", name="project_byId") + */ + public function projectById($id): Response + { + $project = $this->getDoctrine()->getManager()->find(Project::class, $id); + if($project === null) + throw $this->createNotFoundException("Project with %s not found", $id); + + $this->denyAccessUnlessGranted('project_view', $project); + + + $tasks = $this->getDoctrine()->getRepository(Task::class) + ->findBy(['project' => $project]); + return $this->render('project/project.html.twig', [ + 'project' => $project, + 'tasks' => $tasks + ]); + } + + /** + * @Route("/project/{id}/edit", name="project_byId_edit") + */ + public function projectEdit($id, Request $request): Response + { + $project = $this->getDoctrine()->getManager()->find(Project::class, $id); + if($project === null) + throw $this->createNotFoundException("Project with %s not found", $id); + $this->denyAccessUnlessGranted('project_edit', $project); + + $form = $this->createForm(ProjectType::class, $project); + $form->handleRequest($request); + + if($form->isSubmitted() && $form->isValid()) + { + $this->getDoctrine()->getManager()->persist($project); + $this->getDoctrine()->getManager()->flush(); + return $this->redirectToRoute("project_list"); + } + + return $this->render('project/project_edit_form.html.twig', [ + 'form' => $form->createView() + ]); + } +} diff --git a/src/Controller/TaskController.php b/src/Controller/TaskController.php index 0d9f9ad..2fc56d2 100644 --- a/src/Controller/TaskController.php +++ b/src/Controller/TaskController.php @@ -22,7 +22,8 @@ class TaskController extends AbstractController public function create(Request $request): Response { $task = new Task(); - $form = $this->createForm(TaskType::class, $task); + $option = ['userId' => $this->getUser()->getId(), 'hasAdmin' => $this->isGranted('ROLE_ADMIN')]; + $form = $this->createForm(TaskType::class, $task, $option); $form->handleRequest($request); @@ -53,24 +54,14 @@ public function list(Request $request): Response $taskFilterForm->handleRequest($request); if ($taskFilterForm->isSubmitted() && $taskFilterForm->isValid()) { - $filter = $taskFilterForm->getData(); - if ($filter['isCompleted'] === null) { - unset($filter['isCompleted']); - } - - $tasks = $this->getDoctrine()->getRepository(Task::class) - ->findBy($filter, [ - 'dueDate' => 'DESC' - ]); - + $tasks = $this->getDoctrine() + ->getRepository(Task::class) + ->getAvaiableTaskWithFilter($this->getUser()->getId(), $filter, $this->isGranted('ROLE_ADMIN')); } else { - /** @var $tasks */ - $tasks = $this->getDoctrine()->getManager() + $tasks = $this->getDoctrine() ->getRepository(Task::class) - ->findBy([], [ - 'dueDate' => 'DESC' - ]); + ->getAvaiableTask($this->getUser()->getId(), $this->isGranted('ROLE_ADMIN')); } @@ -91,13 +82,20 @@ public function complete($id): Response /** @var Task $task */ $task = $this->getDoctrine()->getManager()->find(Task::class, $id); - $this->denyAccessUnlessGranted('complete', $task); - if ($task === null) { throw $this->createNotFoundException(sprintf("Task with id %s not found", $id)); } + $this->denyAccessUnlessGranted('complete', $task); + + if($task->isCompleted()) + { + $task->setIsCompleted(); + } + else + { + $task->setIsCompleted(true); + } - $task->setIsCompleted(true); $this->getDoctrine()->getManager()->persist($task); $this->getDoctrine()->getManager()->flush(); diff --git a/src/Entity/Project.php b/src/Entity/Project.php new file mode 100644 index 0000000..7f26e7d --- /dev/null +++ b/src/Entity/Project.php @@ -0,0 +1,92 @@ +id; + } + + public function getToken(): ?string + { + return $this->token; + } + + public function setToken(string $token = null): self + { + if ($token == null) + { + $this->token = $this->generateToken(); + return $this; + } + $this->token = $token; + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + /** + * @return User + */ + public function getAuthor(): user + { + return $this->author; + } + + /** + * @param User $user + */ + public function setAuthor(user $author): void + { + $this->author = $author; + } + + + + private function generateToken($length = 5): ?string + { + return substr(str_shuffle(str_repeat($x='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil($length/strlen($x)) )),1,$length); + } +} diff --git a/src/Entity/Task.php b/src/Entity/Task.php index 66a4b57..b27db00 100644 --- a/src/Entity/Task.php +++ b/src/Entity/Task.php @@ -44,7 +44,7 @@ class Task /** * @ORM\Column(type="boolean", nullable=false, options={"default" : 0}) - * @var boolean + * @var bool */ protected $isCompleted = false; @@ -55,6 +55,12 @@ class Task */ protected $author; + /** + * @ORM\ManyToOne(targetEntity="App\Entity\Project", inversedBy="project") + * @var Project + */ + protected $project; + /** * Create empty task */ @@ -85,6 +91,22 @@ public function getAuthor() return $this->author; } + /** + * @return Project|null + */ + public function getProject(): ?Project + { + return $this->project; + } + + /** + * @param Project $project + */ + public function setProject(Project $project = null) + { + $this->project = $project; + } + /** * @return mixed @@ -148,7 +170,7 @@ public function setDueDate($dueDate): void */ public function isCompleted() : bool { - return (boolean) $this->isCompleted; + return $this->isCompleted; } /** @@ -163,5 +185,4 @@ public function setIsCompleted(bool $isCompleted = false) - } \ No newline at end of file diff --git a/src/Repository/ProjectRepository.php b/src/Repository/ProjectRepository.php new file mode 100644 index 0000000..aff51af --- /dev/null +++ b/src/Repository/ProjectRepository.php @@ -0,0 +1,94 @@ +_em->persist($entity); + if ($flush) { + $this->_em->flush(); + } + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function remove(Project $entity, bool $flush = true): void + { + $this->_em->remove($entity); + if ($flush) { + $this->_em->flush(); + } + } + + public function getAvailableProject(int $id, Bool $hasAdmin = false): array + { + if($hasAdmin) + { + return $this->getEntityManager()->getRepository(Project::class)->findAll(); + } + else + { + return $this->getEntityManager()->getRepository(Project::class)->findBy(['author' => $id]); + } + } + + public function takeAvaiableId(int $id): array + { + return []; + } + + + // /** + // * @return Project[] Returns an array of Project objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('p') + ->andWhere('p.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('p.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Project + { + return $this->createQueryBuilder('p') + ->andWhere('p.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Repository/TaskRepository.php b/src/Repository/TaskRepository.php index 43cc1ba..e7913a4 100644 --- a/src/Repository/TaskRepository.php +++ b/src/Repository/TaskRepository.php @@ -2,6 +2,7 @@ namespace App\Repository; +use App\Entity\Project; use App\Entity\Task; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\ORM\EntityRepository; @@ -9,10 +10,65 @@ class TaskRepository extends ServiceEntityRepository { + protected $id; + public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Task::class); } + public function getAvaiableTaskWithFilter(int $id, array $filter, Bool $hasAdmin = false): array + { + + if($filter['due_date'] or $filter['due_date'] === null) + { + $duedate = array('dueDate' => 'desc'); + } + else + { + $duedate = array('dueDate' => 'asc'); + } + + foreach ($filter as $key => $value) + { + if($key === null) + { + unset($filter[$key]); + } + } + + unset($filter['due_date']); + if($hasAdmin) + { + return $this->getEntityManager()->getRepository(Task::class)->findBy($filter, $duedate); + } + else + { + $this->id = $id; + $tasks = $this->getEntityManager()->getRepository(Task::class)->findBy($filter, $duedate); + + return array_filter($tasks, function ($task) { + return $this->id === $task->getAuthor()->getId() || $this->id === $task->getProject()->getAuthor()->getId(); + }); + } + } + + public function getAvaiableTask(int $id, Bool $hasAdmin = false): array + { + if($hasAdmin) + { + return $this->getEntityManager()->getRepository(Task::class)->findAll(); + } + else + { + $this->id = $id; + $tasks = $this->getEntityManager()->getRepository(Task::class)->findAll(); + + return array_filter($tasks, function ($task) { + return $this->id === $task->getAuthor()->getId() || $this->id === $task->getProject()->getAuthor()->getId(); + }); + } + } + } \ No newline at end of file diff --git a/src/Type/ProjectType.php b/src/Type/ProjectType.php new file mode 100644 index 0000000..4c97913 --- /dev/null +++ b/src/Type/ProjectType.php @@ -0,0 +1,18 @@ +add('name', TextType::class) + ->add('save', SubmitType::class); + } +} \ No newline at end of file diff --git a/src/Type/TaskFilterType.php b/src/Type/TaskFilterType.php index 885d59a..79ec19a 100644 --- a/src/Type/TaskFilterType.php +++ b/src/Type/TaskFilterType.php @@ -2,6 +2,10 @@ namespace App\Type; +use App\Entity\Project; +use App\Entity\User; +use Doctrine\ORM\EntityRepository; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -22,6 +26,33 @@ public function buildForm(FormBuilderInterface $builder, array $options) ], 'label' => 'Выполнена' ]) + ->add("due_date", ChoiceType::class, [ + 'choices' => [ + 'Newest' => true, + 'Oldest' => false, + 'Any' => null + ], + ]) + ->add('project', EntityType::class, [ + 'class' => Project::class, + 'query_builder' => function (EntityRepository $er) { + return $er->createQueryBuilder('p') + ->orderBy('p.id', 'ASC'); + }, + 'choice_label' => 'name', + 'required' => false, + 'label' => 'Выберите проект', + ]) + ->add('author', EntityType::class, [ + 'class' => User::class, + 'query_builder' => function (EntityRepository $er) { + return $er->createQueryBuilder('p') + ->orderBy('p.id', 'ASC'); + }, + 'choice_label' => 'email', + 'required' => false, + 'label' => 'Выберите создателя' + ]) ->add('submit', SubmitType::class, [ 'label' => 'Отфильтровать' ]); diff --git a/src/Type/TaskType.php b/src/Type/TaskType.php index 3b58193..77aa8eb 100644 --- a/src/Type/TaskType.php +++ b/src/Type/TaskType.php @@ -2,24 +2,63 @@ namespace App\Type; +use App\Entity\Project; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityRepository; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; class TaskType extends AbstractType { + private $hasAdmin; + private $id; + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'userId' => null, + 'hasAdmin' => false + ]); + + $resolver->setAllowedTypes('userId', 'int'); + $resolver->setAllowedTypes('hasAdmin', 'Bool'); + } + public function buildForm(FormBuilderInterface $builder, array $options) { + $this->hasAdmin = $options['hasAdmin']; + $this->id = $options['userId']; $builder ->add('name', TextType::class) ->add('description', TextareaType::class) ->add('dueDate', DateType::class, [ 'years' => range(2022,2023) ]) + ->add('project', EntityType::class, [ + 'class' => Project::class, + 'query_builder' => function (EntityRepository $er) + { + if($this->hasAdmin) + { + return $er->createQueryBuilder('t') + ->orderBy('t.id', 'ASC'); + } + else + { + return $er->createQueryBuilder('t') + ->where(sprintf('t.author = %d', $this->id)) + ->orderBy('t.id', 'ASC'); + } + }, + 'choice_label' => 'name', + ]) ->add('save', SubmitType::class) ; + } } \ No newline at end of file diff --git a/src/Voter/ProjectVoter.php b/src/Voter/ProjectVoter.php new file mode 100644 index 0000000..a8f0d2e --- /dev/null +++ b/src/Voter/ProjectVoter.php @@ -0,0 +1,35 @@ +getUser(); + + if (!$user instanceof User) { + return false; + } + $isAdmin = in_array('ROLE_ADMIN', $user->getRoles()); + $isAuthor = ($subject->getAuthor() === $user); + + return $isAdmin || $isAuthor; + } +} diff --git a/src/Voter/TaskVoter.php b/src/Voter/TaskVoter.php index 1f84b73..e33b961 100644 --- a/src/Voter/TaskVoter.php +++ b/src/Voter/TaskVoter.php @@ -12,6 +12,7 @@ class TaskVoter extends Voter { const COMPLETE = 'complete'; + const EDIT = 'edit'; const DELETE = 'delete'; diff --git a/templates/project/create_project_form.html.twig b/templates/project/create_project_form.html.twig new file mode 100644 index 0000000..dd9b876 --- /dev/null +++ b/templates/project/create_project_form.html.twig @@ -0,0 +1,4 @@ +{% extends 'base.html.twig' %} +{% block body %} + {{ form(form) }} +{% endblock %} \ No newline at end of file diff --git a/templates/project/index.html.twig b/templates/project/index.html.twig new file mode 100644 index 0000000..e0a15cf --- /dev/null +++ b/templates/project/index.html.twig @@ -0,0 +1,44 @@ +{% extends 'base.html.twig' %} + +{% block title %}Hello ProjectController!{% endblock %} + +{% block body %} + + + + + + + + + + + + + + {% for project in projects %} + + + + + + + + {% endfor %} + +
+ Name + + Token + + Author + + View + + Edit +
{{ project.name }}{{ project.token }}{{ project.author }}ViewEdit
+ Create Project +{% endblock %} diff --git a/templates/project/project.html.twig b/templates/project/project.html.twig new file mode 100644 index 0000000..9499dff --- /dev/null +++ b/templates/project/project.html.twig @@ -0,0 +1,51 @@ +{% extends 'base.html.twig' %} +{% block body %} +

+ {{ project.name }} - {{ project.token }} +

+
+ + + + + + + + + + + + + + {% for task in tasks %} + + + + + + + + + + {% endfor %} + +
+ id + + author + + name + + description + + due date + + Status + + Complete +
{{ task.id }}{{ task.author }}{{ task.name }}{{ task.description }}{{ task.dueDate|date("d.m.Y")}}{{ task.isCompleted? "ready" : "not ready" }} + {% if (is_granted("complete", task)) %} + Complete + {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/templates/project/project_edit_form.html.twig b/templates/project/project_edit_form.html.twig new file mode 100644 index 0000000..dd9b876 --- /dev/null +++ b/templates/project/project_edit_form.html.twig @@ -0,0 +1,4 @@ +{% extends 'base.html.twig' %} +{% block body %} + {{ form(form) }} +{% endblock %} \ No newline at end of file diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig index 7490fa3..e853479 100644 --- a/templates/security/login.html.twig +++ b/templates/security/login.html.twig @@ -16,7 +16,7 @@

Please sign in

- +