diff --git a/Controller/TaskController.php b/Controller/TaskController.php new file mode 100644 index 000000000..decb68944 --- /dev/null +++ b/Controller/TaskController.php @@ -0,0 +1,66 @@ +getDoctrine() + ->getManager(); + // collect parameters for filter + $params = []; + + $params['person'] = $person; + + $singleTasks = $this->get('chill_task.single_task_repository') + ->findByParameters($params, $this->getUser()); + + return $this->render('ChillTaskBundle:Task:index.html.twig', [ + 'single_tasks' => $singleTasks, + 'person' => $person + ]); + } + + protected function getPersonParam(Request $request, EntityManagerInterface $em) + { + $person = $em->getRepository(Person::class) + ->find($request->query->getInt('person_id')) + ; + + if (NULL === $person) { + throw $this->createNotFoundException('person not found'); + } + + $this->denyAccessUnlessGranted(PersonVoter::SEE, $person, "You are " + . "not allowed to see this person"); + + return $person; + } + + protected function getUserParam(Request $request, EntityManagerInterface $em) + { + $user = $em->getRepository(User::class) + ->find($request->query->getInt('user_id')) + ; + + if (NULL === $user) { + throw $this->createNotFoundException('user not found'); + } + + return $user; + } + +} diff --git a/DependencyInjection/ChillTaskExtension.php b/DependencyInjection/ChillTaskExtension.php index 2506082da..b9c44e94f 100644 --- a/DependencyInjection/ChillTaskExtension.php +++ b/DependencyInjection/ChillTaskExtension.php @@ -6,13 +6,15 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\Loader; +use Chill\TaskBundle\Security\Authorization\TaskVoter; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; /** * This is the class that loads and manages your bundle configuration. * * @link http://symfony.com/doc/current/cookbook/bundles/extension.html */ -class ChillTaskExtension extends Extension +class ChillTaskExtension extends Extension implements PrependExtensionInterface { /** * {@inheritdoc} @@ -25,5 +27,22 @@ class ChillTaskExtension extends Extension $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services/controller.yml'); $loader->load('services/security.yml'); + $loader->load('services/repositories.yml'); } + + public function prepend(ContainerBuilder $container) + { + $this->prependAuthorization($container); + } + + public function prependAuthorization(ContainerBuilder $container) + { + $container->prependExtensionConfig('security', array( + 'role_hierarchy' => array( + TaskVoter::UPDATE => [TaskVoter::SHOW], + TaskVoter::CREATE => [TaskVoter::SHOW] + ) + )); + } + } diff --git a/Entity/SingleTask.php b/Entity/SingleTask.php index 3a7861fdb..c56c584ef 100644 --- a/Entity/SingleTask.php +++ b/Entity/SingleTask.php @@ -142,6 +142,28 @@ class SingleTask extends AbstractTask return $this->warningInterval; } + /** + * Get the Warning date, computed from the difference between the + * end date and the warning interval + * + * Return null if warningDate or endDate is null + * + * @return \DateTimeImmutable + */ + public function getWarningDate() + { + if ($this->getWarningInterval() === null) { + return null; + } + + if ($this->getEndDate() === null) { + return null; + } + + return \DateTimeImmutable::createFromMutable($this->getEndDate()) + ->sub($this->getWarningInterval()); + } + function getRecurringTask(): RecurringTask { return $this->recurringTask; diff --git a/Repository/SingleTaskRepository.php b/Repository/SingleTaskRepository.php index 321358766..26583bd8f 100644 --- a/Repository/SingleTaskRepository.php +++ b/Repository/SingleTaskRepository.php @@ -2,12 +2,83 @@ namespace Chill\TaskBundle\Repository; +use Chill\MainBundle\Entity\User; +use Doctrine\ORM\QueryBuilder; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Component\Security\Core\Role\Role; +use Chill\TaskBundle\Security\Authorization\TaskVoter; + /** * SingleTaskRepository - * - * This class was generated by the Doctrine ORM. Add your own custom - * repository methods below. */ class SingleTaskRepository extends \Doctrine\ORM\EntityRepository { + /** + * + * @var AuthorizationHelper + */ + protected $authorizationHelper; + + public function setAuthorizationHelper(AuthorizationHelper $authorizationHelper) + { + $this->authorizationHelper = $authorizationHelper; + } + + public function findByParameters($params, User $currentUser) + { + $qb = $this->createQueryBuilder('st'); + + $this->buildQuery($qb, $params, $currentUser); + + return $qb + ->getQuery() + ->getResult() + ; + } + + protected function buildQuery(QueryBuilder $qb, $params, User $currentUser) + { + $this->buildACLQuery($qb, $currentUser); + + if (\array_key_exists('person', $params)) { + $qb->andWhere($qb->expr()->eq('st.person', ':person')); + $qb->setParameter('person', $params['person']); + } + } + + protected function buildACLQuery(QueryBuilder $qb, User $currentUser) + { + if (NULL === $this->authorizationHelper) { + throw new \LogicException("Injecting the authorization helper is " + . "required to run this query. Please use dependency injection " + . "to initialize this repository or use the method " + . "`setAuthorizationHelper`"); + } + + $role = new Role(TaskVoter::SHOW); + $qb->join('st.person', 'p'); + + $centers = $this->authorizationHelper + ->getReachableCenters($currentUser, $role) + ; + + $i = 0; + $where = $qb->expr()->orX(); + + foreach($centers as $center) { + $circles = $this->authorizationHelper + ->getReachableCircles($currentUser, $role, $center); + + $centerWhere = $qb->expr()->andX(); + + $centerWhere->add($qb->expr()->eq('p.center', ':center_'.$i)); + $qb->setParameter('center_'.$i, $center); + $centerWhere->add($qb->expr()->in('st.circle', ':circles_'.$i)); + $qb->setParameter('circles_'.$i, $circles); + $where->add($centerWhere); + $i ++; + } + + $qb->where($where); + } } diff --git a/Resources/config/services/repositories.yml b/Resources/config/services/repositories.yml new file mode 100644 index 000000000..3951cdeed --- /dev/null +++ b/Resources/config/services/repositories.yml @@ -0,0 +1,10 @@ +services: + chill_task.single_task_repository: + class: Chill\TaskBundle\Repository\SingleTaskRepository + factory: ['@doctrine.orm.entity_manager', getRepository] + arguments: + - 'Chill\TaskBundle\Entity\SingleTask' + calls: + - method: setAuthorizationHelper + arguments: + - "@chill.main.security.authorization.helper" diff --git a/Resources/views/Task/index.html.twig b/Resources/views/Task/index.html.twig new file mode 100644 index 000000000..c5de9e688 --- /dev/null +++ b/Resources/views/Task/index.html.twig @@ -0,0 +1,106 @@ +{# + * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . +#} +{% extends "ChillPersonBundle::layout.html.twig" %} + +{% set activeRouteKey = 'chill_task_single_task_new' %} + +{% block title %}{{ 'Task list'|trans }}{% endblock %} + +{% macro thead() %} + + + {{ 'Title'|trans }} + {{ 'Task type'|trans }} + {{ 'Task status'|trans }} + {{ 'Task start date'|trans }} + {{ 'Task warning date'|trans }} + {{ 'Task end date'|trans }} + + +{% endmacro %} + +{% macro row(task) %} + + {{ task.title }} + {{ task.type }} + todo + {{ task.startDate|localizeddate('medium', 'none') }} + {{ task.warningDate|localizeddate('medium', 'none') }} + {{ task.endDate|localizeddate('medium', 'none') }} + +{% endmacro %} + +{% import _self as helper %} + +{# filter tasks #} + +{% set ended_tasks = [] %} +{% for task in single_tasks if (task.endDate|date("U") > 'now'|date("U")) %} + {% set ended_tasks = ended_tasks|merge([ task ]) %} +{% endfor %} + +{% set warning_tasks = [] %} +{% for task in single_tasks if (task.warningDate|date('U') > 'now'|date("U") and task not in ended_tasks) %} + {% set warning_tasks = warning_tasks|merge([ task ]) %} +{% endfor %} + +{% set rest_tasks = [] %} +{% for task in single_tasks if (task not in ended_tasks and task not in warning_tasks) %} + {% set rest_tasks = rest_tasks|merge([ task ]) %} +{% endfor %} + +{% block personcontent %} +

{{ 'Task list'|trans }}

+ + {% if ended_tasks|length > 0 %} +

{{ 'Task with expired deadline'|trans }}

+ + {{ helper.thead() }} + + {% for task in ended_tasks %} + {{ helper.row(task) }} + {% endfor %} + +
+ {% endif %} + + {% if warning_tasks|length > 0 %} +

{{ 'Task with warning'|trans }}

+ + {{ helper.thead() }} + + {% for task in warning_tasks %} + {{ helper.row(task) }} + {% endfor %} + +
+ {% endif %} + + {% if rest_tasks|length > 0 %} +

{{ 'Task '|trans }}

+ + {{ helper.thead() }} + + {% for task in rest_tasks %} + {{ helper.row(task) }} + {% endfor %} + +
+ {% endif %} + + +{% endblock %}