mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge branch '106_tasks_to_parcours'
This commit is contained in:
commit
2d33ae26d8
@ -11,7 +11,8 @@ and this project adheres to
|
||||
## Unreleased
|
||||
|
||||
<!-- write down unreleased development here -->
|
||||
|
||||
* [tasks] improve tasks with filter order
|
||||
* [tasks] refactor singleControllerTasks: limit the number of conditions from the context
|
||||
|
||||
|
||||
## Test releases
|
||||
|
@ -17,6 +17,7 @@
|
||||
*/
|
||||
namespace Chill\CalendarBundle\Menu;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Chill\TaskBundle\Templating\UI\CountNotificationTask;
|
||||
@ -72,17 +73,19 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
$user = $this->tokenStorage->getToken()->getUser();
|
||||
|
||||
if ($this->authorizationChecker->isGranted('ROLE_USER')){
|
||||
$menu->addChild("My calendar list", [
|
||||
'route' => 'chill_calendar_calendar_list',
|
||||
'routeParameters' => [
|
||||
'user_id' => $user->getId(),
|
||||
]
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 9,
|
||||
'icon' => 'tasks'
|
||||
]);
|
||||
if ($this->authorizationChecker->isGranted('ROLE_USER')
|
||||
&& $user instanceof User
|
||||
) {
|
||||
$menu->addChild("My calendar list", [
|
||||
'route' => 'chill_calendar_calendar_list',
|
||||
'routeParameters' => [
|
||||
'user_id' => $user->getId(),
|
||||
]
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 9,
|
||||
'icon' => 'tasks'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,5 +93,5 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
return [ 'user' ];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ use Chill\MainBundle\Controller\AddressApiController;
|
||||
use Chill\MainBundle\Controller\LocationController;
|
||||
use Chill\MainBundle\Controller\LocationTypeController;
|
||||
use Chill\MainBundle\Controller\UserController;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength;
|
||||
use Chill\MainBundle\Doctrine\DQL\STContains;
|
||||
use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
@ -96,6 +97,10 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
|
||||
$configuration = $this->getConfiguration($configs, $container);
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
|
||||
// replace all config with a main key:
|
||||
$container->setParameter('chill_main', $config);
|
||||
|
||||
// legacy config
|
||||
$container->setParameter('chill_main.installation_name',
|
||||
$config['installation_name']);
|
||||
|
||||
@ -199,6 +204,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
|
||||
'OVERLAPSI' => OverlapsI::class,
|
||||
'STRICT_WORD_SIMILARITY_OPS' => StrictWordSimilarityOPS::class,
|
||||
'ST_CONTAINS' => STContains::class,
|
||||
'JSONB_ARRAY_LENGTH' => JsonbArrayLength::class,
|
||||
],
|
||||
],
|
||||
'hydrators' => [
|
||||
|
@ -16,23 +16,23 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
*/
|
||||
class Configuration implements ConfigurationInterface
|
||||
{
|
||||
|
||||
|
||||
use AddWidgetConfigurationTrait;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @var ContainerBuilder
|
||||
*/
|
||||
private $containerBuilder;
|
||||
|
||||
|
||||
public function __construct(array $widgetFactories = array(),
|
||||
|
||||
public function __construct(array $widgetFactories = array(),
|
||||
ContainerBuilder $containerBuilder)
|
||||
{
|
||||
$this->setWidgetFactories($widgetFactories);
|
||||
$this->containerBuilder = $containerBuilder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@ -97,6 +97,14 @@ class Configuration implements ConfigurationInterface
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('acl')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->booleanNode('form_show_scopes')
|
||||
->defaultTrue()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('redis')
|
||||
->children()
|
||||
->scalarNode('host')
|
||||
@ -247,7 +255,7 @@ class Configuration implements ConfigurationInterface
|
||||
->end() // end of root
|
||||
;
|
||||
|
||||
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
}
|
||||
|
35
src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbArrayLength.php
Normal file
35
src/Bundle/ChillMainBundle/Doctrine/DQL/JsonbArrayLength.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/*
|
||||
*
|
||||
*
|
||||
*/
|
||||
namespace Chill\MainBundle\Doctrine\DQL;
|
||||
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
/**
|
||||
* Return the length of an array
|
||||
*/
|
||||
class JsonbArrayLength extends FunctionNode
|
||||
{
|
||||
private $expr1;
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker): string
|
||||
{
|
||||
return sprintf(
|
||||
'jsonb_array_length(%s)',
|
||||
$this->expr1->dispatch($sqlWalker),
|
||||
);
|
||||
}
|
||||
|
||||
public function parse(Parser $parser): void
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
$this->expr1 = $parser->StringPrimary();
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
/*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
namespace Chill\MainBundle\Doctrine\DQL;
|
||||
|
||||
@ -11,19 +11,18 @@ use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
@author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class JsonbExistsInArray extends FunctionNode
|
||||
{
|
||||
private $expr1;
|
||||
private $expr2;
|
||||
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker): string
|
||||
{
|
||||
return sprintf(
|
||||
'jsonb_exists(%s, %s)',
|
||||
'%s ?? %s',
|
||||
$this->expr1->dispatch($sqlWalker),
|
||||
$sqlWalker->walkInputParameter($this->expr2)
|
||||
);
|
||||
|
@ -3,9 +3,12 @@
|
||||
namespace Chill\MainBundle\Form\Type\Listing;
|
||||
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SearchType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
final class FilterOrderType extends \Symfony\Component\Form\AbstractType
|
||||
@ -29,9 +32,32 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
$checkboxesBuilder = $builder->create('checkboxes', null, [ 'compound' => true ]);
|
||||
foreach ($helper->getCheckboxes() as $name => $c) {
|
||||
|
||||
$choices = \array_combine(
|
||||
\array_map(function($c, $t) {
|
||||
if ($t !== NULL) { return $t; }
|
||||
else { return $c; }
|
||||
}, $c['choices'], $c['trans']),
|
||||
$c['choices']
|
||||
);
|
||||
|
||||
$checkboxesBuilder->add($name, ChoiceType::class, [
|
||||
'choices' => $choices,
|
||||
'expanded' => true,
|
||||
'multiple' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
if (0 < count($helper->getCheckboxes())) {
|
||||
$builder->add($checkboxesBuilder);
|
||||
}
|
||||
|
||||
foreach ($this->requestStack->getCurrentRequest()->query->getIterator() as $key => $value) {
|
||||
switch($key) {
|
||||
case 'q':
|
||||
case 'checkboxes'.$key:
|
||||
break;
|
||||
case 'page':
|
||||
$builder->add($key, HiddenType::class, [
|
||||
@ -47,6 +73,17 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType
|
||||
}
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
/** @var FilterOrderHelper $helper */
|
||||
$helper = $options['helper'];
|
||||
$view->vars['has_search_box'] = $helper->hasSearchBox();
|
||||
$view->vars['checkboxes'] = [];
|
||||
foreach ($helper->getCheckboxes() as $name => $c) {
|
||||
$view->vars['checkboxes'][$name] = [];
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(\Symfony\Component\OptionsResolver\OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setRequired('helper')
|
||||
|
@ -23,7 +23,9 @@ use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper;
|
||||
use Chill\MainBundle\Repository\ScopeRepository;
|
||||
use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
@ -36,6 +38,7 @@ use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
/**
|
||||
* Allow to pick amongst available scope for the current
|
||||
@ -46,14 +49,10 @@ use Symfony\Component\Security\Core\Role\Role;
|
||||
* - `center`: the center of the entity
|
||||
* - `role` : the role of the user
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class ScopePickerType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @var AuthorizationHelper
|
||||
*/
|
||||
protected $authorizationHelper;
|
||||
protected AuthorizationHelperInterface $authorizationHelper;
|
||||
|
||||
/**
|
||||
* @var TokenStorageInterface
|
||||
@ -70,22 +69,26 @@ class ScopePickerType extends AbstractType
|
||||
*/
|
||||
protected $translatableStringHelper;
|
||||
|
||||
protected Security $security;
|
||||
|
||||
public function __construct(
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
AuthorizationHelperInterface $authorizationHelper,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
ScopeRepository $scopeRepository,
|
||||
Security $security,
|
||||
TranslatableStringHelper $translatableStringHelper
|
||||
) {
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->scopeRepository = $scopeRepository;
|
||||
$this->security = $security;
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$query = $this->buildAccessibleScopeQuery($options['center'], $options['role']);
|
||||
$items = $query->getQuery()->execute();
|
||||
$items = $this->authorizationHelper->getReachableScopes($this->security->getUser(),
|
||||
$options['role'], $options['center']);
|
||||
|
||||
if (1 !== count($items)) {
|
||||
$builder->add('scope', EntityType::class, [
|
||||
@ -94,9 +97,7 @@ class ScopePickerType extends AbstractType
|
||||
'choice_label' => function (Scope $c) {
|
||||
return $this->translatableStringHelper->localize($c->getName());
|
||||
},
|
||||
'query_builder' => function () use ($options) {
|
||||
return $this->buildAccessibleScopeQuery($options['center'], $options['role']);
|
||||
},
|
||||
'choices' => $items,
|
||||
]);
|
||||
$builder->setDataMapper(new ScopePickerDataMapper());
|
||||
} else {
|
||||
@ -121,19 +122,22 @@ class ScopePickerType extends AbstractType
|
||||
$resolver
|
||||
// create `center` option
|
||||
->setRequired('center')
|
||||
->setAllowedTypes('center', [Center::class])
|
||||
->setAllowedTypes('center', [Center::class, 'array', 'null'])
|
||||
// create ``role` option
|
||||
->setRequired('role')
|
||||
->setAllowedTypes('role', ['string', Role::class]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Center|array|Center[] $center
|
||||
* @param string $role
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
*/
|
||||
protected function buildAccessibleScopeQuery(Center $center, Role $role)
|
||||
protected function buildAccessibleScopeQuery($center, $role)
|
||||
{
|
||||
$roles = $this->authorizationHelper->getParentRoles($role);
|
||||
$roles[] = $role;
|
||||
$centers = $center instanceof Center ? [$center]: $center;
|
||||
|
||||
$qb = $this->scopeRepository->createQueryBuilder('s');
|
||||
$qb
|
||||
@ -142,8 +146,8 @@ class ScopePickerType extends AbstractType
|
||||
->join('rs.permissionsGroups', 'pg')
|
||||
->join('pg.groupCenters', 'gc')
|
||||
// add center constraint
|
||||
->where($qb->expr()->eq('IDENTITY(gc.center)', ':center'))
|
||||
->setParameter('center', $center->getId())
|
||||
->where($qb->expr()->in('IDENTITY(gc.center)', ':centers'))
|
||||
->setParameter('centers', \array_map(fn(Center $c) => $c->getId(), $centers))
|
||||
// role constraints
|
||||
->andWhere($qb->expr()->in('rs.role', ':roles'))
|
||||
->setParameter('roles', $roles)
|
||||
|
@ -17,6 +17,8 @@
|
||||
*/
|
||||
namespace Chill\MainBundle\Form\Type;
|
||||
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
@ -56,14 +58,18 @@ class UserPickerType extends AbstractType
|
||||
|
||||
protected UserRepository $userRepository;
|
||||
|
||||
protected UserACLAwareRepositoryInterface $userACLAwareRepository;
|
||||
|
||||
public function __construct(
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
UserRepository $userRepository
|
||||
UserRepository $userRepository,
|
||||
UserACLAwareRepositoryInterface $userACLAwareRepository
|
||||
) {
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->userACLAwareRepository = $userACLAwareRepository;
|
||||
}
|
||||
|
||||
|
||||
@ -72,7 +78,7 @@ class UserPickerType extends AbstractType
|
||||
$resolver
|
||||
// create `center` option
|
||||
->setRequired('center')
|
||||
->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class ])
|
||||
->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class, 'null', 'array' ])
|
||||
// create ``role` option
|
||||
->setRequired('role')
|
||||
->setAllowedTypes('role', ['string', \Symfony\Component\Security\Core\Role\Role::class ])
|
||||
@ -86,17 +92,19 @@ class UserPickerType extends AbstractType
|
||||
->setDefault('choice_label', function(User $u) {
|
||||
return $u->getUsername();
|
||||
})
|
||||
->setDefault('scope', null)
|
||||
->setAllowedTypes('scope', [Scope::class, 'array', 'null'])
|
||||
->setNormalizer('choices', function(Options $options) {
|
||||
|
||||
$users = $this->authorizationHelper
|
||||
->findUsersReaching($options['role'], $options['center']);
|
||||
|
||||
|
||||
$users = $this->userACLAwareRepository
|
||||
->findUsersByReachedACL($options['role'], $options['center'], $options['scope'], true);
|
||||
|
||||
if (NULL !== $options['having_permissions_group_flag']) {
|
||||
return $this->userRepository
|
||||
->findUsersHavingFlags($options['having_permissions_group_flag'], $users)
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
return $users;
|
||||
})
|
||||
;
|
||||
|
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
|
||||
class UserACLAwareRepository implements UserACLAwareRepositoryInterface
|
||||
{
|
||||
|
||||
private AuthorizationHelper $authorizationHelper;
|
||||
|
||||
public function findUsersByReachedACL(string $role, $center, $scope = null, bool $onlyEnabled = true): array
|
||||
{
|
||||
$parents = $this->authorizationHelper->getParentRoles($role);
|
||||
$parents[] = $role;
|
||||
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
|
||||
$qb
|
||||
->select('u')
|
||||
->from(User::class, 'u')
|
||||
->join('u.groupCenters', 'gc')
|
||||
->join('gc.permissionsGroup', 'pg')
|
||||
->join('pg.roleScopes', 'rs')
|
||||
->where($qb->expr()->in('rs.role', $parents))
|
||||
;
|
||||
|
||||
if ($onlyEnabled) {
|
||||
$qb->andWhere($qb->expr()->eq('u.enabled', "'TRUE'"));
|
||||
}
|
||||
|
||||
if (NULL !== $center) {
|
||||
$centers = $center instanceof Center ? [$center] : $center;
|
||||
$qb
|
||||
->andWhere($qb->expr()->in('gc.center', ':centers'))
|
||||
->setParameter('centers', $centers)
|
||||
;
|
||||
}
|
||||
|
||||
if (NULL !== $scope) {
|
||||
$scopes = $scope instanceof Scope ? [$scope] : $scope;
|
||||
$qb
|
||||
->andWhere($qb->expr()->in('rs.scope', ':scopes'))
|
||||
->setParameter('scopes', $scopes)
|
||||
;
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
|
||||
interface UserACLAwareRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Find the users reaching the given center and scope, for the given role
|
||||
*
|
||||
* @param string $role
|
||||
* @param Center|Center[]|array $center
|
||||
* @param Scope|Scope[]|array|null $scope
|
||||
* @param bool $onlyActive true if get only active users
|
||||
*
|
||||
* @return User[]
|
||||
*/
|
||||
public function findUsersByReachedACL(string $role, $center, $scope = null, bool $onlyActive = true): array;
|
||||
}
|
@ -1,12 +1,39 @@
|
||||
{{ form_start(form) }}
|
||||
<div class="chill_filter_order container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="input-group mb-3">
|
||||
{{ form_widget(form.q)}}
|
||||
<button type="submit" class="btn btn-chill-l-gray"><i class="fa fa-search"></i></button>
|
||||
{% if form.vars.has_search_box %}
|
||||
<div class="col-md-12">
|
||||
<div class="input-group mb-3">
|
||||
{{ form_widget(form.q)}}
|
||||
<button type="submit" class="btn btn-chill-l-gray"><i class="fa fa-search"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if form.checkboxes|length > 0 %}
|
||||
{% for checkbox_name, options in form.checkboxes %}
|
||||
<div class="row gx-0">
|
||||
<div class="col-md-12">
|
||||
{% for c in form['checkboxes'][checkbox_name].children %}
|
||||
<div class="form-check form-check-inline">
|
||||
{{ form_widget(c) }}
|
||||
{{ form_label(c) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% if loop.last %}
|
||||
<div class="row gx-0">
|
||||
<div class="col-md-12">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button type="submit" class="btn btn-misc"><i class="fa fa-filter"></i></button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
<ul class="record_actions">
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path(cancel_route, cancel_parameters|default( { } ) ) }}" class="btn btn-cancel">
|
||||
{{ 'Cancel'|trans }}
|
||||
@ -18,5 +18,5 @@
|
||||
{{ form_widget(form.submit, { 'attr' : { 'class' : "btn btn-delete" } } ) }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
@ -23,6 +23,8 @@ use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\HasCenterInterface;
|
||||
use Chill\MainBundle\Entity\HasScopeInterface;
|
||||
use Chill\MainBundle\Repository\UserACLAwareRepository;
|
||||
use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
|
||||
use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
|
||||
@ -62,6 +64,8 @@ class AuthorizationHelper implements AuthorizationHelperInterface
|
||||
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
private UserACLAwareRepositoryInterface $userACLAwareRepository;
|
||||
|
||||
public function __construct(
|
||||
RoleHierarchyInterface $roleHierarchy,
|
||||
ParameterBagInterface $parameterBag,
|
||||
@ -320,33 +324,15 @@ class AuthorizationHelper implements AuthorizationHelperInterface
|
||||
|
||||
/**
|
||||
*
|
||||
* @deprecated use UserACLAwareRepositoryInterface::findUsersByReachedACL instead
|
||||
* @param Center|Center[]|array $center
|
||||
* @param Scope|Scope[]|array|null $scope
|
||||
* @param bool $onlyActive true if get only active users
|
||||
* @return User[]
|
||||
*/
|
||||
public function findUsersReaching(string $role, Center $center, Scope $circle = null): array
|
||||
public function findUsersReaching(string $role, $center, $scope = null, bool $onlyEnabled = true): array
|
||||
{
|
||||
$parents = $this->getParentRoles($role);
|
||||
$parents[] = $role;
|
||||
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
$qb
|
||||
->select('u')
|
||||
->from(User::class, 'u')
|
||||
->join('u.groupCenters', 'gc')
|
||||
->join('gc.permissionsGroup', 'pg')
|
||||
->join('pg.roleScopes', 'rs')
|
||||
->where('gc.center = :center')
|
||||
->andWhere($qb->expr()->in('rs.role', $parents))
|
||||
;
|
||||
|
||||
$qb->setParameter('center', $center);
|
||||
|
||||
if ($circle !== null) {
|
||||
$qb->andWhere('rs.scope = :circle')
|
||||
->setParameter('circle', $circle)
|
||||
;
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
return $this->userACLAwareRepository->findUsersByReachedACL($role, $center, $scope, $onlyEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,6 +12,12 @@ class FilterOrderHelper
|
||||
private FormFactoryInterface $formFactory;
|
||||
private RequestStack $requestStack;
|
||||
private ?array $searchBoxFields = null;
|
||||
private array $checkboxes = [];
|
||||
private ?array $submitted = null;
|
||||
private ?string $formName = 'filter';
|
||||
private string $formType = FilterOrderType::class;
|
||||
private array $formOptions = [];
|
||||
|
||||
|
||||
public function __construct(
|
||||
FormFactoryInterface $formFactory,
|
||||
@ -28,6 +34,32 @@ class FilterOrderHelper
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addCheckbox(string $name, array $choices, ?array $default = [], ?array $trans = []): self
|
||||
{
|
||||
$missing = count($choices) - count($trans) - 1;
|
||||
$this->checkboxes[$name] = [
|
||||
'choices' => $choices, 'default' => $default,
|
||||
'trans' =>
|
||||
\array_merge(
|
||||
$trans,
|
||||
0 < $missing ?
|
||||
array_fill(0, $missing, null) : []
|
||||
)
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCheckboxData(string $name): array
|
||||
{
|
||||
return $this->getFormData()['checkboxes'][$name];
|
||||
}
|
||||
|
||||
public function getCheckboxes(): array
|
||||
{
|
||||
return $this->checkboxes;
|
||||
}
|
||||
|
||||
public function hasSearchBox(): bool
|
||||
{
|
||||
return $this->searchBoxFields !== null;
|
||||
@ -35,26 +67,41 @@ class FilterOrderHelper
|
||||
|
||||
private function getFormData(): array
|
||||
{
|
||||
return [
|
||||
'q' => $this->getQueryString()
|
||||
];
|
||||
if (NULL === $this->submitted) {
|
||||
$this->submitted = $this->buildForm()
|
||||
->getData();
|
||||
}
|
||||
|
||||
return $this->submitted;
|
||||
}
|
||||
|
||||
private function getDefaultData(): array
|
||||
{
|
||||
$r = [];
|
||||
|
||||
if ($this->hasSearchBox()) {
|
||||
$r['q'] = '';
|
||||
}
|
||||
foreach ($this->checkboxes as $name => $c) {
|
||||
$r['checkboxes'][$name] = $c['default'];
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
public function getQueryString(): ?string
|
||||
{
|
||||
$q = $this->requestStack->getCurrentRequest()
|
||||
->query->get('q', null);
|
||||
|
||||
return empty($q) ? NULL : $q;
|
||||
return $this->getFormData()['q'];
|
||||
}
|
||||
|
||||
public function buildForm($name = null, string $type = FilterOrderType::class, array $options = []): FormInterface
|
||||
public function buildForm(): FormInterface
|
||||
{
|
||||
return $this->formFactory
|
||||
->createNamed($name, $type, $this->getFormData(), \array_merge([
|
||||
->createNamed($this->formName, $this->formType, $this->getDefaultData(), \array_merge([
|
||||
'helper' => $this,
|
||||
'method' => 'GET',
|
||||
'csrf_protection' => false,
|
||||
], $options));
|
||||
], $this->formOptions))
|
||||
->handleRequest($this->requestStack->getCurrentRequest());
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use Symfony\Component\HttpFoundation\RequestStack;
|
||||
class FilterOrderHelperBuilder
|
||||
{
|
||||
private ?array $searchBoxFields = null;
|
||||
private array $checkboxes = [];
|
||||
private FormFactoryInterface $formFactory;
|
||||
private RequestStack $requestStack;
|
||||
|
||||
@ -26,6 +27,13 @@ class FilterOrderHelperBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addCheckbox(string $name, array $choices, ?array $default = [], ?array $trans = []): self
|
||||
{
|
||||
$this->checkboxes[$name] = [ 'choices' => $choices, 'default' => $default, 'trans' => $trans];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build(): FilterOrderHelper
|
||||
{
|
||||
$helper = new FilterOrderHelper(
|
||||
@ -34,6 +42,13 @@ class FilterOrderHelperBuilder
|
||||
);
|
||||
|
||||
$helper->setSearchBox($this->searchBoxFields);
|
||||
foreach ($this->checkboxes as $name => [
|
||||
'choices' => $choices,
|
||||
'default' => $default,
|
||||
'trans' => $trans
|
||||
]) {
|
||||
$helper->addCheckbox($name, $choices, $default, $trans);
|
||||
}
|
||||
|
||||
return $helper;
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ services:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\MainBundle\Repository\UserACLAwareRepositoryInterface: '@Chill\MainBundle\Repository\UserACLAwareRepository'
|
||||
|
||||
Chill\MainBundle\Serializer\Normalizer\:
|
||||
resource: '../Serializer/Normalizer'
|
||||
autoconfigure: true
|
||||
|
@ -28,6 +28,7 @@ use Chill\MainBundle\Entity\HasCentersInterface;
|
||||
use Chill\MainBundle\Entity\HasScopesInterface;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment;
|
||||
@ -502,7 +503,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $collection->count() > 0 ? $collection->first() : NULL;
|
||||
}
|
||||
|
||||
public function getOPenParticipations(): Collection
|
||||
public function getOpenParticipations(): Collection
|
||||
{
|
||||
return $this
|
||||
->getParticipations()
|
||||
@ -513,6 +514,11 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
||||
);
|
||||
}
|
||||
|
||||
public function getCurrentParticipations(): Collection
|
||||
{
|
||||
return $this->getOpenParticipations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array with open participations sorted by household
|
||||
* [
|
||||
@ -1015,6 +1021,15 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this->addressLocation;
|
||||
}
|
||||
|
||||
public function getCenter(): ?Center
|
||||
{
|
||||
if (count($this->getPersons()) === 0){
|
||||
return null;
|
||||
} else {
|
||||
return $this->getPersons()->first()->getCenter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
|
@ -66,7 +66,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
|
||||
'routeParameters' => [
|
||||
'id' => $period->getId()
|
||||
]])
|
||||
->setExtras(['order' => 50]);
|
||||
->setExtras(['order' => 40]);
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,11 +2,17 @@
|
||||
|
||||
namespace Chill\TaskBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverInterface;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
|
||||
use Chill\PersonBundle\Privacy\PrivacyEvent;
|
||||
use Chill\TaskBundle\Repository\SingleTaskAclAwareRepositoryInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@ -22,97 +28,129 @@ use Chill\TaskBundle\Repository\SingleTaskRepository;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Chill\PersonBundle\Repository\PersonRepository;
|
||||
use Chill\MainBundle\Entity\UserRepository;
|
||||
use Chill\TaskBundle\Event\TaskEvent;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
use Chill\TaskBundle\Event\UI\UIEvent;
|
||||
use Chill\MainBundle\Repository\CenterRepository;
|
||||
use Chill\MainBundle\Timeline\TimelineBuilder;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface as TranslationTranslatorInterface;
|
||||
|
||||
/**
|
||||
* Class SingleTaskController
|
||||
*
|
||||
* @package Chill\TaskBundle\Controller
|
||||
*/
|
||||
class SingleTaskController extends AbstractController
|
||||
final class SingleTaskController extends AbstractController
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var TimelineBuilder
|
||||
*/
|
||||
protected $timelineBuilder;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* SingleTaskController constructor.
|
||||
*
|
||||
* @param EventDispatcherInterface $eventDispatcher
|
||||
*/
|
||||
|
||||
private EventDispatcherInterface $eventDispatcher;
|
||||
private TimelineBuilder $timelineBuilder;
|
||||
private LoggerInterface $logger;
|
||||
private CenterResolverDispatcher $centerResolverDispatcher;
|
||||
private TranslatorInterface $translator;
|
||||
private PaginatorFactory $paginatorFactory;
|
||||
private SingleTaskAclAwareRepositoryInterface $singleTaskAclAwareRepository;
|
||||
private FilterOrderHelperFactoryInterface $filterOrderHelperFactory;
|
||||
|
||||
public function __construct(
|
||||
CenterResolverDispatcher $centerResolverDispatcher,
|
||||
PaginatorFactory $paginatorFactory,
|
||||
SingleTaskAclAwareRepositoryInterface $singleTaskAclAwareRepository,
|
||||
TranslatorInterface $translator,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
TimelineBuilder $timelineBuilder,
|
||||
LoggerInterface $logger
|
||||
LoggerInterface $logger,
|
||||
FilterOrderHelperFactoryInterface $filterOrderHelperFactory
|
||||
) {
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->timelineBuilder = $timelineBuilder;
|
||||
$this->logger = $logger;
|
||||
$this->translator = $translator;
|
||||
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||
$this->paginatorFactory = $paginatorFactory;
|
||||
$this->singleTaskAclAwareRepository = $singleTaskAclAwareRepository;
|
||||
$this->filterOrderHelperFactory = $filterOrderHelperFactory;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function getEntityContext(Request $request)
|
||||
{
|
||||
if ($request->query->has('person_id')) {
|
||||
return 'person';
|
||||
} else if ($request->query->has('course_id')) {
|
||||
return 'course';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "/{_locale}/task/single-task/new",
|
||||
* name="chill_task_single_task_new"
|
||||
* )
|
||||
*/
|
||||
public function newAction(
|
||||
Request $request,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
public function newAction(Request $request) {
|
||||
|
||||
$task = (new SingleTask())
|
||||
->setAssignee($this->getUser())
|
||||
->setType('task_default')
|
||||
;
|
||||
|
||||
if ($request->query->has('person_id')) {
|
||||
|
||||
$personId = $request->query->getInt('person_id', 0); // sf4 check:
|
||||
// prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given`
|
||||
$entityType = $this->getEntityContext($request);
|
||||
|
||||
if ($personId === null) {
|
||||
return new Response("You must provide a person_id", Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$person = $this->getDoctrine()->getManager()
|
||||
->getRepository(Person::class)
|
||||
->find($personId);
|
||||
|
||||
if ($person === null) {
|
||||
$this->createNotFoundException("Invalid person id");
|
||||
}
|
||||
|
||||
$task->setPerson($person);
|
||||
if (NULL === $entityType) {
|
||||
throw new BadRequestHttpException("You must provide a entity_type");
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not '
|
||||
. 'allowed to create this task');
|
||||
$entityId = $request->query->getInt("{$entityType}_id", 0);
|
||||
|
||||
$form = $this->setCreateForm($task, new Role(TaskVoter::CREATE));
|
||||
if ($entityId === null) {
|
||||
return new BadRequestHttpException("You must provide a {$entityType}_id");
|
||||
}
|
||||
|
||||
switch ($entityType) {
|
||||
case 'person':
|
||||
$person = $this->getDoctrine()->getManager()
|
||||
->getRepository(Person::class)
|
||||
->find($entityId);
|
||||
|
||||
if ($person === null) {
|
||||
$this->createNotFoundException("Invalid person id");
|
||||
}
|
||||
|
||||
$task->setPerson($person);
|
||||
$role = TaskVoter::CREATE_PERSON;
|
||||
break;
|
||||
case 'course':
|
||||
$course = $this->getDoctrine()->getManager()
|
||||
->getRepository(AccompanyingPeriod::class)
|
||||
->find($entityId);
|
||||
|
||||
if ($course === null) {
|
||||
$this->createNotFoundException("Invalid accompanying course id");
|
||||
}
|
||||
|
||||
$task->setCourse($course);
|
||||
$role = TaskVoter::CREATE_COURSE;
|
||||
break;
|
||||
default:
|
||||
return new BadRequestHttpException("context with {$entityType} is not supported");
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted($role, $task, 'You are not '
|
||||
. 'allowed to create this task');
|
||||
|
||||
$form = $this->setCreateForm($task, new Role($role));
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
|
||||
if ($form->isSubmitted()) {
|
||||
if ($form->isValid()) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
@ -122,72 +160,78 @@ class SingleTaskController extends AbstractController
|
||||
|
||||
$em->flush();
|
||||
|
||||
$this->addFlash('success', $translator->trans("The task is created"));
|
||||
$this->addFlash('success', $this->translator->trans("The task is created"));
|
||||
|
||||
return $this->redirectToRoute('chill_task_singletask_list', [
|
||||
'person_id' => $task->getPerson()->getId()
|
||||
]);
|
||||
if ($request->query->has('returnPath')) {
|
||||
return $this->redirect($request->query->get('returnPath'));
|
||||
}
|
||||
|
||||
if ($entityType === 'person') {
|
||||
return $this->redirectToRoute('chill_task_singletask_by-person_list', [
|
||||
'id' => $task->getPerson()->getId()
|
||||
]);
|
||||
} elseif ($entityType === 'course') {
|
||||
return $this->redirectToRoute('chill_task_singletask_by-course_list', [
|
||||
'id' => $task->getCourse()->getId()
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('error', $translator->trans("This form contains errors"));
|
||||
$this->addFlash('error', $this->translator->trans("This form contains errors"));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('ChillTaskBundle:SingleTask:new.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
'task' => $task
|
||||
));
|
||||
switch ($entityType) {
|
||||
case 'person':
|
||||
return $this->render('@ChillTask/SingleTask/Person/new.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
'task' => $task,
|
||||
'person' => $task->getPerson(),
|
||||
));
|
||||
case 'course':
|
||||
return $this->render('@ChillTask/SingleTask/AccompanyingCourse/new.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
'task' => $task,
|
||||
'accompanyingCourse' => $task->getCourse(),
|
||||
));
|
||||
default:
|
||||
throw new \LogicException("entity context not supported");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "/{_locale}/task/single-task/{id}/show",
|
||||
* name="chill_task_single_task_show"
|
||||
* )
|
||||
*/
|
||||
public function showAction(Request $request, $id)
|
||||
public function showAction(SingleTask $task, Request $request)
|
||||
{
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$task = $em->getRepository(SingleTask::class)->find($id);
|
||||
$this->denyAccessUnlessGranted(TaskVoter::SHOW, $task);
|
||||
|
||||
if ($task->getPerson() !== null) {
|
||||
$personId = $task->getPerson()->getId();
|
||||
|
||||
if ($personId === null) {
|
||||
return new Response("You must provide a person_id", Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$person = $this->getDoctrine()->getManager()
|
||||
->getRepository(Person::class)
|
||||
->find($personId);
|
||||
|
||||
if ($person === null) {
|
||||
throw $this->createNotFoundException("Invalid person id");
|
||||
}
|
||||
}
|
||||
$this->denyAccessUnlessGranted(TaskVoter::SHOW, $task, 'You are not '
|
||||
. 'allowed to view this task');
|
||||
|
||||
if (!$task) {
|
||||
throw $this->createNotFoundException('Unable to find Task entity.');
|
||||
if ($person = $task->getContext() instanceof Person) {
|
||||
$event = new PrivacyEvent($person, array(
|
||||
'element_class' => SingleTask::class,
|
||||
'element_id' => $task->getId(),
|
||||
'action' => 'show'
|
||||
));
|
||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||
}
|
||||
|
||||
$timeline = $this->timelineBuilder
|
||||
->getTimelineHTML('task', array('task' => $task));
|
||||
|
||||
$event = new PrivacyEvent($person, array(
|
||||
'element_class' => SingleTask::class,
|
||||
'element_id' => $task->getId(),
|
||||
'action' => 'show'
|
||||
));
|
||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||
|
||||
return $this->render('ChillTaskBundle:SingleTask:show.html.twig', array(
|
||||
'task' => $task,
|
||||
'timeline' => $timeline
|
||||
));
|
||||
|
||||
if ($task->getContext() instanceof Person) {
|
||||
return $this->render('@ChillTask/SingleTask/Person/show.html.twig', array(
|
||||
'task' => $task,
|
||||
'timeline' => $timeline
|
||||
));
|
||||
} else {
|
||||
return $this->render('@ChillTask/SingleTask/AccompanyingCourse/show.html.twig', array(
|
||||
'task' => $task,
|
||||
'timeline' => $timeline
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -198,41 +242,18 @@ class SingleTaskController extends AbstractController
|
||||
* )
|
||||
*/
|
||||
public function editAction(
|
||||
Request $request,
|
||||
$id,
|
||||
TranslatorInterface $translator
|
||||
SingleTask $task,
|
||||
Request $request
|
||||
) {
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$task = $em->getRepository(SingleTask::class)->find($id);
|
||||
|
||||
if ($task->getPerson() !== null) {
|
||||
$personId = $task->getPerson()->getId();
|
||||
if ($personId === null) {
|
||||
return new Response("You must provide a person_id", Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$person = $this->getDoctrine()->getManager()
|
||||
->getRepository(Person::class)
|
||||
->find($personId);
|
||||
|
||||
if ($person === null) {
|
||||
throw $this->createNotFoundException("Invalid person id");
|
||||
}
|
||||
}
|
||||
$this->denyAccessUnlessGranted(TaskVoter::UPDATE, $task, 'You are not '
|
||||
. 'allowed to edit this task');
|
||||
|
||||
if (!$task) {
|
||||
throw $this->createNotFoundException('Unable to find Task entity.');
|
||||
}
|
||||
|
||||
$event = (new UIEvent('single-task', $task))
|
||||
->setForm($this->setCreateForm($task, new Role(TaskVoter::UPDATE)))
|
||||
;
|
||||
|
||||
$this->eventDispatcher->dispatch(UIEvent::EDIT_FORM, $event);
|
||||
|
||||
|
||||
$form = $event->getForm();
|
||||
|
||||
$form->handleRequest($request);
|
||||
@ -244,43 +265,64 @@ class SingleTaskController extends AbstractController
|
||||
|
||||
$em->flush();
|
||||
|
||||
$this->addFlash('success', $translator
|
||||
$this->addFlash('success', $this->translator
|
||||
->trans("The task has been updated"));
|
||||
|
||||
$event = new PrivacyEvent($person, array(
|
||||
|
||||
if ($task->getContext() instanceof Person) {
|
||||
$event = new PrivacyEvent($task->getPerson(), array(
|
||||
'element_class' => SingleTask::class,
|
||||
'element_id' => $task->getId(),
|
||||
'action' => 'update'
|
||||
));
|
||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||
));
|
||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||
|
||||
return $this->redirectToRoute(
|
||||
'chill_task_singletask_list',
|
||||
$request->query->get('list_params', [])
|
||||
);
|
||||
if ($request->query->has('returnPath')) {
|
||||
return $this->redirect($request->query->get('returnPath'));
|
||||
}
|
||||
|
||||
return $this->redirectToRoute(
|
||||
'chill_task_singletask_list',
|
||||
);
|
||||
} else {
|
||||
if ($request->query->has('returnPath')) {
|
||||
return $this->redirect($request->query->get('returnPath'));
|
||||
}
|
||||
|
||||
return $this->redirectToRoute(
|
||||
'chill_task_singletask_by-course_list', ['id' => $task->getCourse()->getId()]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('error', $translator->trans("This form contains errors"));
|
||||
$this->addFlash('error', $this->translator->trans("This form contains errors"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$this->eventDispatcher->dispatch(UIEvent::EDIT_PAGE, $event);
|
||||
|
||||
|
||||
if ($event->hasResponse()) {
|
||||
return $event->getResponse();
|
||||
}
|
||||
|
||||
$event = new PrivacyEvent($person, array(
|
||||
'element_class' => SingleTask::class,
|
||||
'element_id' => $task->getId(),
|
||||
'action' => 'edit'
|
||||
));
|
||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||
|
||||
return $this->render('ChillTaskBundle:SingleTask:edit.html.twig', array(
|
||||
'task' => $task,
|
||||
'form' => $form->createView()
|
||||
));
|
||||
|
||||
if ($task->getContext() instanceof Person) {
|
||||
$event = new PrivacyEvent($task->getPerson(), array(
|
||||
'element_class' => SingleTask::class,
|
||||
'element_id' => $task->getId(),
|
||||
'action' => 'edit'
|
||||
));
|
||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||
|
||||
return $this->render('@ChillTask/SingleTask/Person/edit.html.twig', array(
|
||||
'task' => $task,
|
||||
'form' => $form->createView()
|
||||
));
|
||||
} else {
|
||||
return $this->render('@ChillTask/SingleTask/AccompanyingCourse/edit.html.twig', array(
|
||||
'task' => $task,
|
||||
'form' => $form->createView(),
|
||||
'accompanyingCourse' => $task->getCourse()
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -292,10 +334,9 @@ class SingleTaskController extends AbstractController
|
||||
*/
|
||||
public function deleteAction(
|
||||
Request $request,
|
||||
$id,
|
||||
TranslatorInterface $translator
|
||||
$id
|
||||
) {
|
||||
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$task = $em->getRepository(SingleTask::class)->find($id);
|
||||
|
||||
@ -317,10 +358,25 @@ class SingleTaskController extends AbstractController
|
||||
throw $this->createNotFoundException("Invalid person id");
|
||||
}
|
||||
|
||||
} else {
|
||||
$courseId = $task->getCourse()->getId();
|
||||
if ($courseId === null){
|
||||
return new Response("You must provide a course_id", Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$course = $this->getDoctrine()->getManager()
|
||||
->getRepository(AccompanyingPeriod::class)
|
||||
->find($courseId);
|
||||
|
||||
if($course === null){
|
||||
throw $this->createNotFoundException("Invalid accompanying period id");
|
||||
}
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted(TaskVoter::DELETE, $task, 'You are not '
|
||||
. 'allowed to delete this task');
|
||||
// TODO: reactivate right to delete
|
||||
|
||||
// $this->denyAccessUnlessGranted(TaskVoter::DELETE, $task, 'You are not '
|
||||
// . 'allowed to delete this task');
|
||||
|
||||
$form = $this->createDeleteForm($id);
|
||||
|
||||
@ -328,38 +384,50 @@ class SingleTaskController extends AbstractController
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
|
||||
|
||||
$this->logger->notice("A task has been removed", array(
|
||||
'by_user' => $this->getUser()->getUsername(),
|
||||
'task_id' => $task->getId(),
|
||||
'description' => $task->getDescription(),
|
||||
'assignee' => $task->getAssignee(),
|
||||
'scope_id' => $task->getScope()->getId(),
|
||||
//'start_date' => $task->getStartDate()->format('Y-m-d'),
|
||||
//'end_date' => $task->getEndDate()->format('Y-m-d'),
|
||||
//'warning_interval' => $task->getWarningInterval()->format('Y-m-d')
|
||||
// TODO reimplement scope
|
||||
// 'scope_id' => $task->getScope()->getId(),
|
||||
));
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->remove($task);
|
||||
$em->flush();
|
||||
|
||||
$this->addFlash('success', $translator
|
||||
$this->addFlash('success', $this->translator
|
||||
->trans("The task has been successfully removed."));
|
||||
|
||||
return $this->redirect($this->generateUrl(
|
||||
'chill_task_singletask_list',
|
||||
$request->query->get('list_params', [
|
||||
'person_id' => $person->getId()
|
||||
])));
|
||||
if ($task->getContext() instanceof Person) {
|
||||
return $this->redirect($this->generateUrl(
|
||||
'chill_task_singletask_by-person_list',
|
||||
[ 'id' => $task->getPerson()->getId() ]
|
||||
));
|
||||
} else {
|
||||
return $this->redirect($this->generateUrl(
|
||||
'chill_task_singletask_by-course_list',
|
||||
['id' => $task->getCourse()->getId()]
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($task->getContext() instanceof Person){
|
||||
return $this->render('@ChillTask/SingleTask/Person/confirm_delete.html.twig', array(
|
||||
'task' => $task,
|
||||
'delete_form' => $form->createView()
|
||||
));
|
||||
} else {
|
||||
return $this->render('@ChillTask/SingleTask/AccompanyingCourse/confirm_delete.html.twig', array(
|
||||
'task' => $task,
|
||||
'delete_form' => $form->createView(),
|
||||
'accompanyingCourse' => $course
|
||||
));
|
||||
}
|
||||
|
||||
return $this->render('ChillTaskBundle:SingleTask:confirm_delete.html.twig', array(
|
||||
'task' => $task,
|
||||
'delete_form' => $form->createView()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -371,8 +439,7 @@ class SingleTaskController extends AbstractController
|
||||
protected function setCreateForm(SingleTask $task, Role $role)
|
||||
{
|
||||
$form = $this->createForm(SingleTaskType::class, $task, [
|
||||
'center' => $task->getCenter(),
|
||||
'role' => $role
|
||||
'role' => $role,
|
||||
]);
|
||||
|
||||
$form->add('submit', SubmitType::class);
|
||||
@ -388,220 +455,111 @@ class SingleTaskController extends AbstractController
|
||||
* name="chill_task_single_my_tasks"
|
||||
* )
|
||||
*/
|
||||
public function myTasksAction(TranslatorInterface $translator)
|
||||
public function myTasksAction()
|
||||
{
|
||||
return $this->redirectToRoute('chill_task_singletask_list', [
|
||||
'user_id' => $this->getUser()->getId(),
|
||||
'hide_form' => true,
|
||||
'title' => $translator->trans('My tasks')
|
||||
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||
|
||||
$filterOrder = $this->buildFilterOrder();
|
||||
$flags = \array_merge(
|
||||
$filterOrder->getCheckboxData('status'),
|
||||
\array_map(fn ($i) => 'state_'.$i, $filterOrder->getCheckboxData('states'))
|
||||
);
|
||||
$nb = $this->singleTaskAclAwareRepository->countByCurrentUsersTasks(
|
||||
$filterOrder->getQueryString(),
|
||||
$flags
|
||||
);
|
||||
$paginator = $this->paginatorFactory->create($nb);
|
||||
$tasks = $this->singleTaskAclAwareRepository->findByCurrentUsersTasks(
|
||||
$filterOrder->getQueryString(),
|
||||
$flags,
|
||||
$paginator->getCurrentPageFirstItemNumber(),
|
||||
$paginator->getItemsPerPage(),
|
||||
[
|
||||
'startDate' => 'DESC',
|
||||
'endDate' => 'DESC',
|
||||
]
|
||||
);
|
||||
|
||||
return $this->render('@ChillTask/SingleTask/List/index.html.twig', [
|
||||
'tasks' => $tasks,
|
||||
'paginator' => $paginator,
|
||||
'filter_order' => $filterOrder,
|
||||
]);
|
||||
}
|
||||
|
||||
private function buildFilterOrder(): FilterOrderHelper
|
||||
{
|
||||
$statuses = ['no-alert', 'warning', 'alert'];
|
||||
$statusTrans = [
|
||||
'Tasks without alert',
|
||||
'Tasks near deadline',
|
||||
'Tasks over deadline',
|
||||
];
|
||||
$states = [
|
||||
// todo: get a list of possible states dynamically
|
||||
'new', 'in_progress', 'closed', 'canceled'
|
||||
];
|
||||
return $this->filterOrderHelperFactory
|
||||
->create(self::class)
|
||||
->addSearchBox()
|
||||
->addCheckbox('status', $statuses, $statuses, $statusTrans)
|
||||
->addCheckbox('states', $states, ['new', 'in_progress'])
|
||||
->build()
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Arguments:
|
||||
* - user_id
|
||||
* - scope_id
|
||||
* - s
|
||||
* - person_id
|
||||
* - hide_form (hide the form to filter the tasks)
|
||||
* - status: date state, amongst SingleTaskRepository::DATE_STATUSES, or 'closed'
|
||||
*
|
||||
* @Route(
|
||||
* "/{_locale}/task/singletask/list",
|
||||
* "/{_locale}/task/single-task/list",
|
||||
* name="chill_task_singletask_list"
|
||||
* )
|
||||
*/
|
||||
public function listAction(
|
||||
Request $request,
|
||||
PaginatorFactory $paginatorFactory,
|
||||
SingleTaskRepository $taskRepository,
|
||||
PersonRepository $personRepository,
|
||||
CenterRepository $centerRepository,
|
||||
FormFactoryInterface $formFactory
|
||||
Request $request
|
||||
) {
|
||||
/* @var $viewParams array The parameters for the view */
|
||||
/* @var $params array The parameters for the query */
|
||||
$this->denyAccessUnlessGranted(TaskVoter::SHOW, null);
|
||||
|
||||
$viewParams['person'] = null;
|
||||
$params['person'] = null;
|
||||
$viewParams['user'] = null;
|
||||
$params['user'] = null;
|
||||
$viewParams['center'] = null;
|
||||
$params['types'] = null;
|
||||
$filterOrder = $this->buildFilterOrder();
|
||||
$flags = \array_merge(
|
||||
$filterOrder->getCheckboxData('status'),
|
||||
\array_map(fn ($i) => 'state_'.$i, $filterOrder->getCheckboxData('states'))
|
||||
);
|
||||
$nb = $this->singleTaskAclAwareRepository->countByAllViewable(
|
||||
$filterOrder->getQueryString(),
|
||||
$flags
|
||||
);
|
||||
$paginator = $this->paginatorFactory->create($nb);
|
||||
|
||||
|
||||
// Get parameters from url
|
||||
if (!empty($request->query->get('person_id', NULL))) {
|
||||
|
||||
$personId = $request->query->getInt('person_id', 0);
|
||||
$person = $personRepository->find($personId);
|
||||
|
||||
if ($person === null) {
|
||||
throw $this->createNotFoundException("This person ' $personId ' does not exist.");
|
||||
}
|
||||
$this->denyAccessUnlessGranted(PersonVoter::SEE, $person);
|
||||
|
||||
$viewParams['person'] = $person;
|
||||
$params['person'] = $person;
|
||||
}
|
||||
|
||||
if (!empty($request->query->get('center_id', NULL))) {
|
||||
$center = $centerRepository->find($request->query->getInt('center_id'));
|
||||
if ($center === null) {
|
||||
throw $this->createNotFoundException('center not found');
|
||||
}
|
||||
$params['center'] = $center;
|
||||
}
|
||||
|
||||
if(!empty($request->query->get('types', []))) {
|
||||
$types = $request->query->get('types', []);
|
||||
if (count($types) > 0) {
|
||||
$params['types'] = $types;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($request->query->get('user_id', null))) {
|
||||
if ($request->query->get('user_id') === '_unassigned') {
|
||||
$params['unassigned'] = true;
|
||||
} else {
|
||||
|
||||
$userId = $request->query->getInt('user_id', 0);
|
||||
$user = $this->getDoctrine()->getManager()
|
||||
->getRepository('ChillMainBundle:User')
|
||||
->find($userId);
|
||||
|
||||
if ($user === null) {
|
||||
throw $this->createNotFoundException("This user ' $userId ' does not exist.");
|
||||
}
|
||||
|
||||
$viewParams['user'] = $user;
|
||||
$params['user'] = $user;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($request->query->get('scope_id'))) {
|
||||
|
||||
$scopeId = $request->query->getInt('scope_id', 0);
|
||||
|
||||
$scope = $this->getDoctrine()->getManager()
|
||||
->getRepository('ChillMainBundle:Scope')
|
||||
->find($scopeId);
|
||||
|
||||
if ($scope === null) {
|
||||
throw $this->createNotFoundException("This scope' $scopeId 'does not exist.");
|
||||
}
|
||||
|
||||
$viewParams['scope'] = $scope;
|
||||
$params['scope'] = $scope;
|
||||
}
|
||||
|
||||
// collect parameters for filter
|
||||
$possibleStatuses = \array_merge(SingleTaskRepository::DATE_STATUSES, [ 'closed' ]);
|
||||
$statuses = $request->query->get('status', $possibleStatuses);
|
||||
|
||||
// check for invalid statuses
|
||||
$diff = \array_diff($statuses, $possibleStatuses);
|
||||
if (count($diff) > 0) {
|
||||
return new Response(
|
||||
'date_status not allowed: '. \implode(', ', $diff),
|
||||
Response::HTTP_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
$viewParams['isSingleStatus'] = $singleStatus = count($statuses) === 1;
|
||||
|
||||
$tasks_count = 0;
|
||||
|
||||
foreach($statuses as $status) {
|
||||
if($request->query->has('status')
|
||||
&& FALSE === \in_array($status, $statuses)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// different query if regarding to date or 'closed'
|
||||
if (in_array($status, SingleTaskRepository::DATE_STATUSES)) {
|
||||
$params['date_status'] = $status;
|
||||
$params['is_closed'] = false;
|
||||
} else {
|
||||
$params['date_status'] = null;
|
||||
$params['is_closed'] = true;
|
||||
}
|
||||
|
||||
$count = $taskRepository
|
||||
->countByParameters($params, $this->getUser())
|
||||
;
|
||||
$paginator = $paginatorFactory->create($count);
|
||||
|
||||
$viewParams['single_task_'.$status.'_count'] = $count;
|
||||
$viewParams['single_task_'.$status.'_paginator'] = $paginator;
|
||||
$viewParams['single_task_'.$status.'_tasks'] = $taskRepository
|
||||
->findByParameters($params, $this->getUser(),
|
||||
$singleStatus ? $paginator->getCurrentPage()->getFirstItemNumber() : 0,
|
||||
$singleStatus ? $paginator->getItemsPerPage() : 10)
|
||||
;
|
||||
|
||||
$tasks_count = $tasks_count + $count;
|
||||
}
|
||||
|
||||
// total number of tasks
|
||||
$viewParams['tasks_count'] = $tasks_count;
|
||||
|
||||
if ($viewParams['person'] !== null){
|
||||
$viewParams['layout'] = '@ChillPerson/Person/layout.html.twig';
|
||||
if (0 < $nb) {
|
||||
$tasks = $this->singleTaskAclAwareRepository->findByAllViewable(
|
||||
$filterOrder->getQueryString(),
|
||||
$flags,
|
||||
$paginator->getCurrentPageFirstItemNumber(),
|
||||
$paginator->getItemsPerPage(),
|
||||
[
|
||||
'startDate' => 'DESC',
|
||||
'endDate' => 'DESC',
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$viewParams['layout'] = '@ChillMain/layout.html.twig';
|
||||
$tasks = [];
|
||||
}
|
||||
|
||||
// Form for filtering tasks
|
||||
$form = $formFactory->createNamed(null, SingleTaskListType::class, null, [
|
||||
'person' => $viewParams['person'],
|
||||
'method' => Request::METHOD_GET,
|
||||
'csrf_protection' => false,
|
||||
'add_type' => true
|
||||
]);
|
||||
return $this->render('@ChillTask/SingleTask/List/index.html.twig', [
|
||||
'tasks' => $tasks,
|
||||
'paginator' => $paginator,
|
||||
'filter_order' => $filterOrder
|
||||
]);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if (isset($person)) {
|
||||
$event = new PrivacyEvent($person, array(
|
||||
'element_class' => SingleTask::class,
|
||||
'action' => 'list'
|
||||
));
|
||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||
}
|
||||
|
||||
return $this->render('ChillTaskBundle:SingleTask:index.html.twig',
|
||||
\array_merge($viewParams, [ 'form' => $form->createView() ]));
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -623,4 +581,103 @@ class SingleTaskController extends AbstractController
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "/{_locale}/task/single-task/by-course/{id}",
|
||||
* name="chill_task_singletask_by-course_list")
|
||||
*/
|
||||
public function listCourseTasks(
|
||||
AccompanyingPeriod $course,
|
||||
FormFactoryInterface $formFactory,
|
||||
Request $request
|
||||
): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(TaskVoter::SHOW, $course);
|
||||
|
||||
$filterOrder = $this->buildFilterOrder();
|
||||
$flags = \array_merge(
|
||||
$filterOrder->getCheckboxData('status'),
|
||||
\array_map(fn ($i) => 'state_'.$i, $filterOrder->getCheckboxData('states'))
|
||||
);
|
||||
$nb = $this->singleTaskAclAwareRepository->countByCourse(
|
||||
$course,
|
||||
$filterOrder->getQueryString(),
|
||||
$flags
|
||||
);
|
||||
$paginator = $this->paginatorFactory->create($nb);
|
||||
|
||||
if (0 < $nb) {
|
||||
$tasks = $this->singleTaskAclAwareRepository->findByCourse(
|
||||
$course,
|
||||
$filterOrder->getQueryString(),
|
||||
$flags,
|
||||
$paginator->getCurrentPageFirstItemNumber(),
|
||||
$paginator->getItemsPerPage(),
|
||||
[
|
||||
'startDate' => 'DESC',
|
||||
'endDate' => 'DESC',
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$tasks = [];
|
||||
}
|
||||
|
||||
return $this->render(
|
||||
'@ChillTask/SingleTask/AccompanyingCourse/list.html.twig',
|
||||
[
|
||||
'tasks' => $tasks,
|
||||
'accompanyingCourse' => $course,
|
||||
'paginator' => $paginator,
|
||||
'filter_order' => $filterOrder
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "/{_locale}/task/single-task/by-person/{id}",
|
||||
* name="chill_task_singletask_by-person_list")
|
||||
*/
|
||||
public function listPersonTasks(
|
||||
Person $person
|
||||
): Response {
|
||||
|
||||
$this->denyAccessUnlessGranted(TaskVoter::SHOW, $person);
|
||||
|
||||
$filterOrder = $this->buildFilterOrder();
|
||||
$flags = \array_merge(
|
||||
$filterOrder->getCheckboxData('status'),
|
||||
\array_map(fn ($i) => 'state_'.$i, $filterOrder->getCheckboxData('states'))
|
||||
);
|
||||
$nb = $this->singleTaskAclAwareRepository->countByPerson(
|
||||
$person,
|
||||
$filterOrder->getQueryString(),
|
||||
$flags
|
||||
);
|
||||
$paginator = $this->paginatorFactory->create($nb);
|
||||
|
||||
if (0 < $nb) {
|
||||
$tasks = $this->singleTaskAclAwareRepository->findByPerson(
|
||||
$person,
|
||||
$filterOrder->getQueryString(),
|
||||
$flags,
|
||||
$paginator->getCurrentPageFirstItemNumber(),
|
||||
$paginator->getItemsPerPage(),
|
||||
[
|
||||
'startDate' => 'DESC',
|
||||
'endDate' => 'DESC',
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$tasks = [];
|
||||
}
|
||||
|
||||
return $this->render(
|
||||
'@ChillTask/SingleTask/Person/list.html.twig',
|
||||
[
|
||||
'tasks' => $tasks,
|
||||
'person' => $person,
|
||||
'paginator' => $paginator,
|
||||
'filter_order' => $filterOrder
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ class TaskController extends AbstractController
|
||||
'id' => $task->getId(),
|
||||
'list_params' => $request->query->get('list_params', [])
|
||||
]);
|
||||
$defaultTemplate = '@ChillTask/SingleTask/transition.html.twig';
|
||||
$task->getCourse() === null ? $defaultTemplate = '@ChillTask/SingleTask/Person/transition.html.twig' : $defaultTemplate = '@ChillTask/SingleTask/AccompanyingCourse/transition.html.twig';
|
||||
break;
|
||||
default:
|
||||
return new Response("The type '$kind' is not implemented",
|
||||
|
@ -40,7 +40,7 @@ class LoadTaskACL extends AbstractFixture implements OrderedFixtureInterface
|
||||
return 16000;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) {
|
||||
@ -58,33 +58,38 @@ class LoadTaskACL extends AbstractFixture implements OrderedFixtureInterface
|
||||
case 'direction':
|
||||
if (in_array($scope->getName()['en'], array('administrative', 'social'))) {
|
||||
break 2; // we do not want any power on social or administrative
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
printf("Adding CHILL_TASK_TASK_UPDATE & CHILL_TASK_TASK_CREATE & Chill_TASK_TASK_DELETE permissions to %s "
|
||||
. "permission group, scope '%s' \n",
|
||||
. "permission group, scope '%s' \n",
|
||||
$permissionsGroup->getName(), $scope->getName()['en']);
|
||||
$roleScopeUpdate = (new RoleScope())
|
||||
->setRole(TaskVoter::UPDATE)
|
||||
->setScope($scope);
|
||||
$permissionsGroup->addRoleScope($roleScopeUpdate);
|
||||
$roleScopeCreate = (new RoleScope())
|
||||
->setRole(TaskVoter::CREATE)
|
||||
$roleScopeCreateP = (new RoleScope())
|
||||
->setRole(TaskVoter::CREATE_PERSON)
|
||||
->setScope($scope);
|
||||
$permissionsGroup->addRoleScope($roleScopeCreate);
|
||||
$permissionsGroup->addRoleScope($roleScopeCreateP);
|
||||
$roleScopeCreateC = (new RoleScope())
|
||||
->setRole(TaskVoter::CREATE_COURSE)
|
||||
->setScope($scope);
|
||||
$permissionsGroup->addRoleScope($roleScopeCreateC);
|
||||
$roleScopeDelete = (new RoleScope())
|
||||
->setRole(TaskVoter::DELETE)
|
||||
->setScope($scope);
|
||||
$permissionsGroup->addRoleScope($roleScopeDelete);
|
||||
|
||||
|
||||
$manager->persist($roleScopeUpdate);
|
||||
$manager->persist($roleScopeCreate);
|
||||
$manager->persist($roleScopeCreateP);
|
||||
$manager->persist($roleScopeCreateC);
|
||||
$manager->persist($roleScopeDelete);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface
|
||||
$this->prependRoute($container);
|
||||
$this->prependWorkflows($container);
|
||||
}
|
||||
|
||||
|
||||
protected function prependRoute(ContainerBuilder $container)
|
||||
{
|
||||
//declare routes for task bundle
|
||||
@ -56,17 +56,18 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
protected function prependAuthorization(ContainerBuilder $container)
|
||||
{
|
||||
$container->prependExtensionConfig('security', array(
|
||||
'role_hierarchy' => array(
|
||||
TaskVoter::UPDATE => [TaskVoter::SHOW],
|
||||
TaskVoter::CREATE => [TaskVoter::SHOW]
|
||||
TaskVoter::CREATE_COURSE => [TaskVoter::SHOW],
|
||||
TaskVoter::CREATE_PERSON => [TaskVoter::SHOW],
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
protected function prependWorkflows(ContainerBuilder $container)
|
||||
{
|
||||
$container->prependExtensionConfig('framework', [
|
||||
|
@ -10,15 +10,12 @@ use Chill\MainBundle\Entity\HasScopeInterface;
|
||||
use Chill\MainBundle\Entity\HasCenterInterface;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistency;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
|
||||
/**
|
||||
* AbstractTask
|
||||
*
|
||||
* @ORM\MappedSuperclass()
|
||||
* @UserCircleConsistency(
|
||||
* "CHILL_TASK_TASK_SHOW",
|
||||
* getUserFunction="getAssignee"
|
||||
* )
|
||||
*/
|
||||
abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
|
||||
{
|
||||
@ -51,7 +48,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
|
||||
* @ORM\Column(name="description", type="text")
|
||||
*/
|
||||
private $description = '';
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @var User
|
||||
@ -60,36 +57,42 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
|
||||
* )
|
||||
*/
|
||||
private $assignee;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @var Person
|
||||
* @ORM\ManyToOne(
|
||||
* targetEntity="\Chill\PersonBundle\Entity\Person"
|
||||
* )
|
||||
* @Assert\NotNull()
|
||||
*/
|
||||
private $person;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @var AccompanyingPeriod
|
||||
* @ORM\ManyToOne(targetEntity="\Chill\PersonBundle\Entity\AccompanyingPeriod")
|
||||
*/
|
||||
|
||||
private $course;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var Scope
|
||||
* @ORM\ManyToOne(
|
||||
* targetEntity="\Chill\MainBundle\Entity\Scope"
|
||||
* )
|
||||
* @Assert\NotNull()
|
||||
* )
|
||||
*/
|
||||
private $circle;
|
||||
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
* @ORM\Column(name="closed", type="boolean", options={ "default"=false })
|
||||
*/
|
||||
private $closed = false;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,7 +121,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
|
||||
|
||||
/**
|
||||
* Set currentStates
|
||||
*
|
||||
*
|
||||
* The current states are sorted in a single array, non associative.
|
||||
*
|
||||
* @param $currentStates
|
||||
@ -134,8 +137,8 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
|
||||
|
||||
/**
|
||||
* Get currentStates
|
||||
*
|
||||
* The states are returned as required by marking store format.
|
||||
*
|
||||
* The states are returned as required by marking store format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@ -191,7 +194,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
|
||||
public function getAssignee(): ?User
|
||||
{
|
||||
return $this->assignee;
|
||||
@ -202,11 +205,16 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
|
||||
return $this->person;
|
||||
}
|
||||
|
||||
public function getCourse(): ?AccompanyingPeriod
|
||||
{
|
||||
return $this->course;
|
||||
}
|
||||
|
||||
public function getCircle(): ?Scope
|
||||
{
|
||||
return $this->circle;
|
||||
}
|
||||
|
||||
|
||||
public function setAssignee(User $assignee = null)
|
||||
{
|
||||
$this->assignee = $assignee;
|
||||
@ -219,6 +227,12 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCourse(AccompanyingPeriod $course)
|
||||
{
|
||||
$this->course = $course;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCircle(Scope $circle)
|
||||
{
|
||||
$this->circle = $circle;
|
||||
@ -229,12 +243,19 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
|
||||
{
|
||||
if ($this->getPerson() instanceof Person) {
|
||||
return $this->getPerson()->getCenter();
|
||||
} else {
|
||||
return $this->getCourse()->getCenter();
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function getContext()
|
||||
{
|
||||
return $this->getPerson() ?? $this->getCourse();
|
||||
}
|
||||
|
||||
public function getScope(): ?\Chill\MainBundle\Entity\Scope
|
||||
{
|
||||
return $this->getCircle();
|
||||
@ -247,9 +268,9 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
|
||||
{
|
||||
return $this->closed;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param bool $closed
|
||||
*/
|
||||
public function setClosed(bool $closed)
|
||||
|
@ -19,6 +19,10 @@ use Doctrine\Common\Collections\Collection;
|
||||
* @ORM\Index(
|
||||
* name="by_current_state",
|
||||
* columns={ "current_states" }
|
||||
* ),
|
||||
* @ORM\Index(
|
||||
* name="by_end_date",
|
||||
* columns={ "end_date" }
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
@ -40,12 +44,12 @@ class SingleTask extends AbstractTask
|
||||
*
|
||||
* @ORM\Column(name="start_date", type="date", nullable=true)
|
||||
* @Assert\Date()
|
||||
*
|
||||
*
|
||||
* @Assert\Expression(
|
||||
* "value === null or this.getEndDate() === null or value < this.getEndDate()",
|
||||
* message="The start date must be before the end date"
|
||||
* )
|
||||
*
|
||||
*
|
||||
* @Assert\Expression(
|
||||
* "value === null or this.getWarningDate() === null or this.getWarningDate() > this.getStartDate()",
|
||||
* message="The start date must be before warning date"
|
||||
@ -66,16 +70,16 @@ class SingleTask extends AbstractTask
|
||||
* and this.getEndDate() === null
|
||||
*
|
||||
* @ORM\Column(name="warning_interval", type="dateinterval", nullable=true)
|
||||
*
|
||||
*
|
||||
* @Assert\Expression(
|
||||
* "!(value != null and this.getEndDate() == null)",
|
||||
* message="An end date is required if a warning interval is set"
|
||||
* )
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
private $warningInterval;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @var RecurringTask
|
||||
@ -85,7 +89,7 @@ class SingleTask extends AbstractTask
|
||||
* )
|
||||
*/
|
||||
private $recurringTask;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @var \Doctrine\Common\Collections\Collection
|
||||
@ -96,15 +100,15 @@ class SingleTask extends AbstractTask
|
||||
* )
|
||||
*/
|
||||
private $taskPlaceEvents;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->taskPlaceEvents = $events = new \Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get id
|
||||
*
|
||||
@ -186,13 +190,13 @@ class SingleTask extends AbstractTask
|
||||
{
|
||||
return $this->warningInterval;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Warning date, computed from the difference between the
|
||||
* 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()
|
||||
@ -200,15 +204,15 @@ class SingleTask extends AbstractTask
|
||||
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;
|
||||
@ -227,7 +231,7 @@ class SingleTask extends AbstractTask
|
||||
public function setTaskPlaceEvents(Collection $taskPlaceEvents)
|
||||
{
|
||||
$this->taskPlaceEvents = $taskPlaceEvents;
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -284,6 +284,7 @@ class SingleTaskListType extends AbstractType
|
||||
->setDefined('person')
|
||||
->setDefault('person', null)
|
||||
->setAllowedTypes('person', [Person::class, 'null'])
|
||||
->setDefined('accompanyingCourse')
|
||||
->setDefined('add_status')
|
||||
->setDefault('add_status', false)
|
||||
->setAllowedTypes('add_status', ['bool'])
|
||||
|
@ -17,6 +17,10 @@
|
||||
*/
|
||||
namespace Chill\TaskBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
|
||||
use Chill\TaskBundle\Security\Authorization\TaskVoter;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||
@ -29,15 +33,26 @@ use Symfony\Component\Security\Core\Role\Role;
|
||||
use Chill\MainBundle\Form\Type\DateIntervalType;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class SingleTaskType extends AbstractType
|
||||
{
|
||||
private ParameterBagInterface $parameterBag;
|
||||
private CenterResolverDispatcher $centerResolverDispatcher;
|
||||
private ScopeResolverDispatcher $scopeResolverDispatcher;
|
||||
|
||||
public function __construct(ParameterBagInterface $parameterBag, CenterResolverDispatcher $centerResolverDispatcher, ScopeResolverDispatcher $scopeResolverDispatcher)
|
||||
{
|
||||
$this->parameterBag = $parameterBag;
|
||||
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||
$this->scopeResolverDispatcher = $scopeResolverDispatcher;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
if (NULL !== $task = $options['data']) {
|
||||
$center = $this->centerResolverDispatcher->resolveCenter($task);
|
||||
$isScopeConcerned = $this->scopeResolverDispatcher->isConcerned($task);
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('title', TextType::class)
|
||||
->add('description', ChillTextareaType::class, [
|
||||
@ -45,14 +60,10 @@ class SingleTaskType extends AbstractType
|
||||
])
|
||||
->add('assignee', UserPickerType::class, [
|
||||
'required' => false,
|
||||
'center' => $options['center'],
|
||||
'role' => $options['role'],
|
||||
'center' => $center,
|
||||
'role' => TaskVoter::SHOW,
|
||||
'placeholder' => 'Not assigned'
|
||||
])
|
||||
->add('circle', ScopePickerType::class, [
|
||||
'center' => $options['center'],
|
||||
'role' => $options['role']
|
||||
])
|
||||
])
|
||||
->add('startDate', ChillDateType::class, [
|
||||
'required' => false
|
||||
])
|
||||
@ -61,17 +72,24 @@ class SingleTaskType extends AbstractType
|
||||
])
|
||||
->add('warningInterval', DateIntervalType::class, [
|
||||
'required' => false
|
||||
])
|
||||
;
|
||||
]);
|
||||
|
||||
if ($this->parameterBag->get('chill_main')['acl']['form_show_scopes']
|
||||
&& $isScopeConcerned) {
|
||||
$builder
|
||||
->add('circle', ScopePickerType::class, [
|
||||
'center' => $center,
|
||||
'role' => $options['role'],
|
||||
'required' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setRequired('center')
|
||||
->setAllowedTypes('center', [ Center::class ])
|
||||
->setRequired('role')
|
||||
->setAllowedTypes('role', [ Role::class ])
|
||||
->setAllowedTypes('role', [ Role::class, 'string' ])
|
||||
;
|
||||
}
|
||||
}
|
||||
|
@ -24,24 +24,24 @@ use Chill\TaskBundle\Security\Authorization\TaskVoter;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class PersonMenuBuilder implements LocalMenuBuilderInterface
|
||||
class MenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @var AuthorizationCheckerInterface
|
||||
*/
|
||||
protected $authorizationChecker;
|
||||
|
||||
|
||||
public function __construct(
|
||||
AuthorizationCheckerInterface $authorizationChecker,
|
||||
TranslatorInterface $translator)
|
||||
@ -50,31 +50,58 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
|
||||
$this->authorizationChecker = $authorizationChecker;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
{
|
||||
/* @var $person \Chill\PersonBundle\Entity\Person */
|
||||
$person = $parameters['person'] ?? null;
|
||||
|
||||
if ($this->authorizationChecker->isGranted(TaskVoter::SHOW, $person)) {
|
||||
$menu->addChild(
|
||||
$this->translator->trans('Tasks'), [
|
||||
'route' => 'chill_task_singletask_list',
|
||||
'routeParameters' => $menuId === 'person' ?
|
||||
[ 'person_id' => $person->getId() ]
|
||||
:
|
||||
null,
|
||||
])
|
||||
->setExtra('order', 400)
|
||||
;
|
||||
if ($menuId === 'section') {
|
||||
$menu->setExtra('icons', 'tasks');
|
||||
}
|
||||
switch($menuId) {
|
||||
case 'person':
|
||||
$this->buildPersonMenu($menu, $parameters);
|
||||
break;
|
||||
case 'accompanyingCourse':
|
||||
$this->buildAccompanyingCourseMenu($menu, $parameters);
|
||||
break;
|
||||
case 'section':
|
||||
$menu->setExtras('icons', 'tasks');
|
||||
break;
|
||||
default:
|
||||
throw new \LogicException("this menuid $menuId is not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public function buildPersonMenu($menu, $parameters){
|
||||
|
||||
//var $person \Chill\PersonBundle\Entity\Person */
|
||||
$person = $parameters['person'] ?? null;
|
||||
|
||||
if ($this->authorizationChecker->isGranted(TaskVoter::SHOW, $person)) {
|
||||
$menu->addChild(
|
||||
$this->translator->trans('Tasks'), [
|
||||
'route' => 'chill_task_singletask_by-person_list',
|
||||
'routeParameters' =>
|
||||
[ 'id' => $person->getId() ]
|
||||
])
|
||||
->setExtra('order', 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function buildAccompanyingCourseMenu($menu, $parameters){
|
||||
|
||||
$course = $parameters['accompanyingCourse'];
|
||||
|
||||
if ($this->authorizationChecker->isGranted(TaskVoter::SHOW, $course)) {
|
||||
$menu->addChild(
|
||||
$this->translator->trans('Tasks'), [
|
||||
'route' => 'chill_task_singletask_by-course_list',
|
||||
'routeParameters' =>
|
||||
[ 'id' => $course->getId() ]
|
||||
])
|
||||
->setExtra('order', 400);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function getMenuIds(): array
|
||||
{
|
||||
return ['person'];
|
||||
return ['person', 'accompanyingCourse'];
|
||||
}
|
||||
}
|
@ -0,0 +1,335 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\TaskBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\TaskBundle\Entity\SingleTask;
|
||||
use Chill\TaskBundle\Security\Authorization\TaskVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final class SingleTaskAclAwareRepository implements SingleTaskAclAwareRepositoryInterface
|
||||
{
|
||||
private AuthorizationHelperInterface $authorizationHelper;
|
||||
private EntityManagerInterface $em;
|
||||
private Security $security;
|
||||
private CenterResolverDispatcher $centerResolverDispatcher;
|
||||
|
||||
public function __construct(
|
||||
CenterResolverDispatcher $centerResolverDispatcher,
|
||||
EntityManagerInterface $em,
|
||||
Security $security,
|
||||
AuthorizationHelperInterface $authorizationHelper
|
||||
) {
|
||||
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||
$this->em = $em;
|
||||
$this->security = $security;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
}
|
||||
|
||||
public function findByCurrentUsersTasks(
|
||||
?string $pattern = null,
|
||||
?array $flags = [],
|
||||
?int $start = 0,
|
||||
?int $limit = 50,
|
||||
?array $orderBy = []
|
||||
): array {
|
||||
$qb = $this->buildQueryMyTasks($pattern, $flags);
|
||||
|
||||
return $this->getResult($qb, $start, $limit, $orderBy);
|
||||
}
|
||||
|
||||
public function countByCurrentUsersTasks(
|
||||
?string $pattern = null,
|
||||
?array $flags = []
|
||||
): int {
|
||||
return $this->buildQueryMyTasks($pattern, $flags)
|
||||
->select('COUNT(t)')
|
||||
->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function findByCourse(
|
||||
AccompanyingPeriod $course,
|
||||
?string $pattern = null,
|
||||
?array $flags = [],
|
||||
?int $start = 0,
|
||||
?int $limit = 50,
|
||||
?array $orderBy = []
|
||||
): array {
|
||||
$qb = $this->buildQueryByCourse($course, $pattern, $flags);
|
||||
$qb = $this->addACL($qb, $course);
|
||||
|
||||
return $this->getResult($qb, $start, $limit, $orderBy);
|
||||
}
|
||||
|
||||
public function countByCourse(
|
||||
AccompanyingPeriod $course,
|
||||
?string $pattern = null,
|
||||
?array $flags = []
|
||||
): int {
|
||||
$qb = $this->buildQueryByCourse($course, $pattern, $flags);
|
||||
|
||||
return $this
|
||||
->addACL($qb, $course)
|
||||
->select('COUNT(t)')
|
||||
->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function findByPerson(
|
||||
Person $person,
|
||||
?string $pattern = null,
|
||||
?array $flags = [],
|
||||
?int $start = 0,
|
||||
?int $limit = 50,
|
||||
?array $orderBy = []
|
||||
): array {
|
||||
$qb = $this->buildQueryByPerson($person, $pattern, $flags);
|
||||
$qb = $this->addACL($qb, $person);
|
||||
|
||||
return $this->getResult($qb, $start, $limit, $orderBy);
|
||||
}
|
||||
|
||||
public function countByPerson(
|
||||
Person $person,
|
||||
?string $pattern = null,
|
||||
?array $flags = []
|
||||
): int {
|
||||
$qb = $this->buildQueryByPerson($person, $pattern, $flags);
|
||||
|
||||
return $this
|
||||
->addACL($qb, $person)
|
||||
->select('COUNT(t)')
|
||||
->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countByAllViewable(
|
||||
?string $pattern = null,
|
||||
?array $flags = []
|
||||
): int {
|
||||
$qb = $this->buildBaseQuery($pattern, $flags);
|
||||
|
||||
return $this
|
||||
->addACLGlobal($qb)
|
||||
->select('COUNT(t)')
|
||||
->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function findByAllViewable(
|
||||
?string $pattern = null,
|
||||
?array $flags = [],
|
||||
?int $start = 0,
|
||||
?int $limit = 50,
|
||||
?array $orderBy = []
|
||||
): array {
|
||||
$qb = $this->buildBaseQuery($pattern, $flags);
|
||||
$qb = $this->addACLGlobal($qb);
|
||||
|
||||
return $this->getResult($qb, $start, $limit, $orderBy);
|
||||
}
|
||||
|
||||
public function buildQueryByCourse(
|
||||
AccompanyingPeriod $course,
|
||||
?string $pattern = null,
|
||||
?array $flags = []
|
||||
) : QueryBuilder {
|
||||
$qb = $this->buildBaseQuery($pattern, $flags);
|
||||
|
||||
return $qb
|
||||
->andWhere($qb->expr()->eq('t.course', ':course'))
|
||||
->setParameter('course', $course)
|
||||
;
|
||||
}
|
||||
|
||||
public function buildQueryByPerson(
|
||||
Person $person,
|
||||
?string $pattern = null,
|
||||
?array $flags = []
|
||||
): QueryBuilder
|
||||
{
|
||||
$qb = $this->buildBaseQuery($pattern, $flags);
|
||||
|
||||
return $qb
|
||||
->andWhere($qb->expr()->eq('t.person', ':person'))
|
||||
->setParameter('person', $person);
|
||||
}
|
||||
|
||||
public function buildQueryMyTasks(
|
||||
?string $pattern = null,
|
||||
?array $flags = []
|
||||
): QueryBuilder {
|
||||
$qb = $this->buildBaseQuery($pattern, $flags);
|
||||
|
||||
return $qb
|
||||
->andWhere($qb->expr()->eq('t.assignee', ':user'))
|
||||
->setParameter('user', $this->security->getUser())
|
||||
;
|
||||
}
|
||||
|
||||
public function getResult(
|
||||
QueryBuilder $qb,
|
||||
?int $start = 0,
|
||||
?int $limit = 50,
|
||||
?array $orderBy = []
|
||||
): array {
|
||||
$qb->select('t');
|
||||
|
||||
$qb
|
||||
->setFirstResult($start)
|
||||
->setMaxResults($limit)
|
||||
;
|
||||
|
||||
foreach ($orderBy as $field => $direction) {
|
||||
$qb->addOrderBy('t.'.$field, $direction);
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
private function addACL(
|
||||
QueryBuilder $qb,
|
||||
$entity
|
||||
): QueryBuilder {
|
||||
$scopes = $this->authorizationHelper->getReachableScopes($this->security->getUser(),
|
||||
TaskVoter::SHOW, $this->centerResolverDispatcher->resolveCenter($entity));
|
||||
|
||||
return $qb->andWhere($qb->expr()->in('t.circle', ':scopes'))
|
||||
->setParameter('scopes', $scopes);
|
||||
}
|
||||
|
||||
private function addACLGlobal(
|
||||
QueryBuilder $qb
|
||||
): QueryBuilder {
|
||||
$allowedCenters = $this->authorizationHelper
|
||||
->getReachableCenters($this->security->getUser(), TaskVoter::SHOW);
|
||||
|
||||
if ([] === $allowedCenters) {
|
||||
$qb
|
||||
->andWhere($qb->expr()->lt('t.id', ':falseid'))
|
||||
->setParameter('falseid', -1);
|
||||
}
|
||||
|
||||
$qb->leftJoin('t.person', 'person')
|
||||
->leftJoin('t.course', 'course')
|
||||
->leftJoin('course.participations', 'participation')
|
||||
->leftJoin('participation.person', 'person_p')
|
||||
;
|
||||
$qb->distinct(true);
|
||||
|
||||
$k = 0;
|
||||
$orX = $qb->expr()->orX();
|
||||
foreach ($allowedCenters as $center) {
|
||||
$allowedScopes = $this->authorizationHelper->getReachableScopes($this->security->getUser(),
|
||||
TaskVoter::SHOW, $center);
|
||||
|
||||
$and = $qb->expr()->andX(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('person.center', ':center_'.$k),
|
||||
$qb->expr()->eq('person_p.center', ':center_'.$k)
|
||||
),
|
||||
$qb->expr()->in('t.circle', ':scopes_'.$k)
|
||||
);
|
||||
$qb
|
||||
->setParameter('center_'.$k, $center)
|
||||
->setParameter('scopes_'.$k, $allowedScopes);
|
||||
$orX->add($and);
|
||||
|
||||
$k++;
|
||||
}
|
||||
$qb->andWhere($orX);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
public function buildBaseQuery (
|
||||
?string $pattern = null,
|
||||
?array $flags = []
|
||||
): QueryBuilder {
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
$qb
|
||||
->from(SingleTask::class, 't')
|
||||
;
|
||||
|
||||
if (!empty($pattern)) {
|
||||
$qb->andWhere($qb->expr()->like('LOWER(UNACCENT(t.title))', 'LOWER(UNACCENT(:pattern))'))
|
||||
->setParameter('pattern', '%'.$pattern.'%')
|
||||
;
|
||||
}
|
||||
|
||||
if (count($flags) > 0) {
|
||||
$orXDate = $qb->expr()->orX();
|
||||
$orXState = $qb->expr()->orX();
|
||||
$now = new \DateTime();
|
||||
|
||||
foreach ($flags as $key => $flag) {
|
||||
switch ($flag) {
|
||||
case 'no-alert':
|
||||
$orXDate
|
||||
->add(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->isNull('t.endDate'),
|
||||
$qb->expr()->gte('t.endDate - COALESCE(t.warningInterval, :intervalBlank)', ':now')
|
||||
)
|
||||
);
|
||||
$qb
|
||||
->setParameter('intervalBlank', new \DateInterval('P0D'))
|
||||
->setParameter('now', $now);
|
||||
break;
|
||||
case 'warning':
|
||||
$orXDate
|
||||
->add(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->not($qb->expr()->isNull('t.endDate')),
|
||||
$qb->expr()->not($qb->expr()->isNull('t.warningInterval')),
|
||||
$qb->expr()->gte('t.endDate - t.warningInterval', ':now'),
|
||||
$qb->expr()->lt('t.endDate', ':now')
|
||||
)
|
||||
);
|
||||
$qb
|
||||
->setParameter('now', $now);
|
||||
break;
|
||||
case 'alert':
|
||||
$orXDate
|
||||
->add(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->not($qb->expr()->isNull('t.endDate')),
|
||||
$qb->expr()->lte('t.endDate', ':now')
|
||||
)
|
||||
);
|
||||
$qb
|
||||
->setParameter('now', $now);
|
||||
break;
|
||||
case 'state_new':
|
||||
$orXState
|
||||
->add(
|
||||
"JSONB_ARRAY_LENGTH(t.currentStates) = 0"
|
||||
);
|
||||
break;
|
||||
case \substr($flag, 0, 6) === 'state_':
|
||||
$state = \substr($flag, 6);
|
||||
$orXState
|
||||
->add(
|
||||
"JSONB_EXISTS_IN_ARRAY(t.currentStates, :state_$key) = 'TRUE'"
|
||||
);
|
||||
$qb->setParameter("state_$key", $state);
|
||||
break;
|
||||
default:
|
||||
throw new \LogicException("this flag is not supported: $flag");
|
||||
}
|
||||
}
|
||||
|
||||
if ($orXDate->count() > 0) {
|
||||
$qb->andWhere($orXDate);
|
||||
}
|
||||
if ($orXState->count() > 0) {
|
||||
$qb->andWhere($orXState);
|
||||
}
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\TaskBundle\Repository;
|
||||
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
|
||||
interface SingleTaskAclAwareRepositoryInterface
|
||||
{
|
||||
public function findByCurrentUsersTasks(?string $pattern = null, ?array $flags = [], ?int $start = 0, ?int $limit = 50, ?array $orderBy = []): array;
|
||||
|
||||
public function countByCurrentUsersTasks(?string $pattern = null, ?array $flags = []): int;
|
||||
|
||||
public function findByCourse(
|
||||
AccompanyingPeriod $course,
|
||||
?string $pattern = null,
|
||||
?array $flags = [],
|
||||
?int $start = 0,
|
||||
?int $limit = 50,
|
||||
?array $orderBy = []
|
||||
): array;
|
||||
|
||||
public function countByCourse(
|
||||
AccompanyingPeriod $course,
|
||||
?string $pattern = null,
|
||||
?array $flags = []
|
||||
): int;
|
||||
|
||||
public function findByPerson(
|
||||
Person $person,
|
||||
?string $pattern = null,
|
||||
?array $flags = [],
|
||||
?int $start = 0,
|
||||
?int $limit = 50,
|
||||
?array $orderBy = []
|
||||
): array;
|
||||
|
||||
public function countByPerson(
|
||||
Person $person,
|
||||
?string $pattern = null,
|
||||
?array $flags = []
|
||||
): int;
|
||||
|
||||
public function countByAllViewable(
|
||||
?string $pattern = null,
|
||||
?array $flags = []
|
||||
): int;
|
||||
|
||||
public function findByAllViewable(
|
||||
?string $pattern = null,
|
||||
?array $flags = [],
|
||||
?int $start = 0,
|
||||
?int $limit = 50,
|
||||
?array $orderBy = []
|
||||
): array;
|
||||
}
|
@ -1,13 +1,21 @@
|
||||
/*
|
||||
!!!!!!!
|
||||
This is a legacy version for task list. The new task are now
|
||||
layed out in page/tile_list/index.js
|
||||
!!!!!!
|
||||
*/
|
||||
|
||||
table.chill-task-list {
|
||||
|
||||
.chill-task-list__row > div {
|
||||
margin-bottom: 0.50rem;
|
||||
}
|
||||
|
||||
|
||||
.chill-task-list__row__title {
|
||||
font-weight: bold;
|
||||
font-size: 1.40rem;
|
||||
}
|
||||
|
||||
|
||||
.chill-task-list__row__type {
|
||||
font-variant: small-caps;
|
||||
display: inline;
|
||||
@ -21,31 +29,31 @@ table.chill-task-list {
|
||||
border: 1px solid var(--chill-dark-gray);
|
||||
color: var(--chill-dark-gray);
|
||||
}
|
||||
|
||||
|
||||
.chill-task-list__row__person-for {
|
||||
display: inline;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.chill-task-list__row__assignee {
|
||||
display: inline;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
.chill_task-list__row__assignee_by {
|
||||
display: inline;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.chill-task-list__row__dates {
|
||||
& > ul {
|
||||
display: inline;
|
||||
list-style: none;
|
||||
|
||||
|
||||
& > li {
|
||||
display: inline;
|
||||
margin-right: 0.25rem;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,22 @@
|
||||
// Access to Bootstrap variables and mixins
|
||||
@import '~ChillMainAssets/module/bootstrap/shared';
|
||||
|
||||
.task-status {
|
||||
&.box {
|
||||
font-variant: small-caps;
|
||||
display: inline;
|
||||
padding: .2em .6em .3em;
|
||||
font-size: 0.88rem;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: .25em;
|
||||
color: white;
|
||||
|
||||
// 'new', 'in_progress', 'closed', 'canceled'
|
||||
&.place-new {
|
||||
background-color: $chill-yellow;
|
||||
}
|
||||
|
||||
&.type-task_default {
|
||||
// 'new', 'in_progress', 'closed', 'canceled'
|
||||
&.place-new {
|
||||
background-color: var(--chill-yellow);
|
||||
}
|
||||
|
||||
&.place-in_progress {
|
||||
background-color: var(--chill-green);
|
||||
}
|
||||
&.place-in_progress {
|
||||
background-color: $chill-green;
|
||||
}
|
||||
|
||||
&.place-closed {
|
||||
background-color: var(--chill-blue);
|
||||
}
|
||||
&.place-closed {
|
||||
background-color: $chill-blue;
|
||||
}
|
||||
|
||||
&.place-canceled {
|
||||
background-color: var(--chill-beige);
|
||||
}
|
||||
&.place-canceled {
|
||||
background-color: $chill-beige;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
require("./task_list.scss");
|
@ -0,0 +1,24 @@
|
||||
.chill-task-list {
|
||||
.task-type {
|
||||
font-variant: small-caps;
|
||||
display: inline;
|
||||
padding: 0.05rem .15rem;
|
||||
font-size: 0.88rem;
|
||||
font-weight: light;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border: 1px solid var(--chill-dark-gray);
|
||||
color: var(--chill-dark-gray);
|
||||
}
|
||||
|
||||
.assignee {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dates {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_task_task_list' %}
|
||||
{% set course = task.course %}
|
||||
|
||||
{% block title 'Remove task'|trans %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ include('@ChillMain/Util/confirmation_template.html.twig',
|
||||
{
|
||||
'title' : 'Remove task'|trans,
|
||||
'confirm_question' : 'Are you sure you want to remove the task "%title%" ?'|trans({ '%title%' : task.title } ),
|
||||
'cancel_route' : 'chill_task_singletask_by-course_list',
|
||||
'cancel_parameters' : {'id' : task.course.id },
|
||||
'form' : delete_form,
|
||||
} ) }}
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,15 @@
|
||||
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_task_single_task_edit' %}
|
||||
{% set course = task.course %}
|
||||
|
||||
{% block title %}
|
||||
{{ 'Edit task'|trans }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% include '@ChillTask/SingleTask/_edit.html.twig' %}
|
||||
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,44 @@
|
||||
{% extends '@ChillPerson/AccompanyingCourse/layout.html.twig' %}
|
||||
|
||||
{% block title 'Tasks for this accompanying period'|trans %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<h1>{{ block('title') }}</h1>
|
||||
|
||||
{{ filter_order|chill_render_filter_order_helper }}
|
||||
|
||||
{% if tasks|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'Any tasks'|trans }}</p>
|
||||
{% else %}
|
||||
<div class="flex-table chill-task-list">
|
||||
{% for task in tasks %}
|
||||
{% include 'ChillTaskBundle:SingleTask/List:index_item.html.twig' with { 'showContext' : false } %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{ chill_pagination(paginator) }}
|
||||
|
||||
{% if is_granted('CHILL_TASK_TASK_CREATE_FOR_COURSE', person) %}
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
{% if accompanyingCourse is not null %}
|
||||
<a href="{{ path('chill_task_single_task_new', {'course_id': accompanyingCourse.id}) }}" class="btn btn-create">
|
||||
{{ 'Create' | trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('page_task_list') }}
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
{{ encore_entry_script_tags('page_task_list') }}
|
||||
{% endblock %}
|
@ -0,0 +1,13 @@
|
||||
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_task_single_task_new' %}
|
||||
{# {% set person = task.person %} #}
|
||||
|
||||
{% block title %}
|
||||
{{ 'New task'|trans }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include '@ChillTask/SingleTask/_new.html.twig' %}
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,17 @@
|
||||
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
||||
|
||||
|
||||
{% set activeRouteKey = 'chill_task_single_task_show' %}
|
||||
{% set accompanyingCourse = task.course %}
|
||||
|
||||
{% block title %}
|
||||
{{ 'Task'|trans }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% include '@ChillTask/SingleTask/_show.html.twig' %}
|
||||
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,12 @@
|
||||
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_task_task_list' %}
|
||||
{% set accompanyingCourse = task.course %}
|
||||
|
||||
{% block title 'Remove task'|trans %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% include '@ChillTask/SingleTask/_transition.html.twig' %}
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,32 @@
|
||||
{% extends 'ChillMainBundle::layout.html.twig' %}
|
||||
|
||||
{% block title 'My tasks'|trans %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<h1>{{ block('title') }}</h1>
|
||||
|
||||
{{ filter_order|chill_render_filter_order_helper }}
|
||||
|
||||
{% if tasks|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'Any tasks'|trans }}</p>
|
||||
{% else %}
|
||||
<div class="flex-table chill-task-list">
|
||||
{% for task in tasks %}
|
||||
{% include 'ChillTaskBundle:SingleTask/List:index_item.html.twig' with { 'showContext' : true} %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{ chill_pagination(paginator) }}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('page_task_list') }}
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
{{ encore_entry_script_tags('page_task_list') }}
|
||||
{% endblock %}
|
@ -0,0 +1,125 @@
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<div class="denomination h2">
|
||||
{{ task.title }}
|
||||
{% for place in workflow_marked_places(task) %}
|
||||
<span class="task-status badge type-{{ task.type }} place-{{ place }}">
|
||||
{{ place|trans }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if task.type != 'task_default'%}
|
||||
<span class="task-type">
|
||||
{{ task_workflow_metadata(task, 'definition.name')|trans }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if showContext %}
|
||||
<div>
|
||||
{% if task.person is not null %}
|
||||
<span class="chill-task-list__row__person">
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
targetEntity: { name: 'person', id: task.person.id },
|
||||
action: 'show',
|
||||
displayBadge: true,
|
||||
buttonText: task.person|chill_entity_render_string
|
||||
} %}
|
||||
</span>
|
||||
{% elseif task.course is not null %}
|
||||
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': task.course.id }) }}"
|
||||
class="btn btn-sm btn-outline-primary" title="{{ 'See accompanying period'|trans }}">
|
||||
<i class="fa fa-random fa-fw"></i>
|
||||
</a>
|
||||
|
||||
{% for part in task.course.currentParticipations %}
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
targetEntity: { name: 'person', id: part.person.id },
|
||||
action: 'show',
|
||||
displayBadge: true,
|
||||
buttonText: part.person|chill_entity_render_string
|
||||
} %}
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="item-col">
|
||||
<div class="container">
|
||||
{% if task.assignee is not null %}
|
||||
<div class="assignee row">
|
||||
<span>{{ task.assignee|chill_entity_render_box }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if task.startDate is not null or task.warningDate is not null or task.endDate is not null %}
|
||||
<div class="dates row">
|
||||
<ul class="column">
|
||||
{% if task.startDate is not null %}
|
||||
<li title="{{ 'Start'|trans|escape('html_attr') }}">
|
||||
<i class="fa fa-play"></i>
|
||||
{{ task.startDate|format_date('medium') }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if task.warningDate is not null %}
|
||||
<li title="{{ 'Warning'|trans|escape('html_attr') }}">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
{{ task.warningDate|format_date('medium') }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if task.endDate is not null %}
|
||||
<li title="{{ 'End'|trans|escape('html_attr') }}">
|
||||
<i class="fa fa-hourglass-end"></i>
|
||||
{{ task.endDate|format_date('medium') }}
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="item-row separator">
|
||||
<ul class="record_actions">
|
||||
{% if workflow_transitions(task)|length > 0 %}
|
||||
<li>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-task-exchange dropdown-toggle" href="#" role="button" id="taskExchange" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{{'Change task status'|trans}}
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="taskExchange">
|
||||
{% for transition in workflow_transitions(task) %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ path('chill_task_task_transition', { 'taskId': task.id, 'transition': transition.name, 'kind': 'single-task', 'list_params': app.request.query.all }) }}" class="{{ task_workflow_metadata(task, 'transition.class', transition)|e('html_attr') }}">
|
||||
{{ task_workflow_metadata(task, 'transition.verb', transition)|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_task_single_task_show', { 'id': task.id }) }}" class="btn btn-show "></a>
|
||||
</li>
|
||||
|
||||
{% if is_granted('CHILL_TASK_TASK_UPDATE', task) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_task_single_task_edit', { 'id': task.id }) }}" class="btn btn-update "></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('CHILL_TASK_TASK_DELETE', task) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_task_single_task_delete', { 'id': task.id } ) }}" class="btn btn-delete "></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,31 @@
|
||||
{#
|
||||
* Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
{% extends person is defined ? "@ChillPerson/Person/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_task_single_task_edit' %}
|
||||
{% set person = task.person %}
|
||||
|
||||
{% block title %}
|
||||
{{ 'Edit task'|trans }}
|
||||
{% endblock %}
|
||||
|
||||
{% block personcontent %}
|
||||
|
||||
{% include '@ChillTask/SingleTask/_edit.html.twig' %}
|
||||
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,44 @@
|
||||
{% extends '@ChillPerson/Person/layout.html.twig' %}
|
||||
|
||||
{% set activeRouteKey = '' %}
|
||||
|
||||
{% block title 'Tasks for {{ name }}'|trans({ '{{ name }}' : person|chill_entity_render_string }) %}
|
||||
|
||||
{% block personcontent %}
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<h1>{{ block('title') }}</h1>
|
||||
|
||||
{{ filter_order|chill_render_filter_order_helper }}
|
||||
|
||||
{% if tasks|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'Any tasks'|trans }}</p>
|
||||
{% else %}
|
||||
<div class="flex-table chill-task-list">
|
||||
{% for task in tasks %}
|
||||
{% include 'ChillTaskBundle:SingleTask/List:index_item.html.twig' with { 'showContext' : false } %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{ chill_pagination(paginator) }}
|
||||
|
||||
{% if is_granted('CHILL_TASK_TASK_CREATE_FOR_PERSON', person) %}
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
<a href="{{ path('chill_task_single_task_new', {'person_id': person.id}) }}" class="btn btn-create">
|
||||
{{ 'Create' | trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('page_task_list') }}
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
{{ encore_entry_script_tags('page_task_list') }}
|
||||
{% endblock %}
|
@ -14,37 +14,17 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
{% extends "@ChillPerson/Person/layout.html.twig" %}
|
||||
{% extends person is defined ? "@ChillPerson/Person/layout.html.twig" : "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_task_single_task_new' %}
|
||||
{% set person = task.person %}
|
||||
|
||||
{% block title %}{{ 'New task'|trans }}{% endblock %}
|
||||
{% block title %}
|
||||
{{ 'New task'|trans }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block personcontent %}
|
||||
<div class="task-new">
|
||||
|
||||
<h1>{{ 'New task'|trans }}</h1>
|
||||
{% include '@ChillTask/SingleTask/_new.html.twig' %}
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
{{ form_errors(form) }}
|
||||
|
||||
{{ form_row(form.title) }}
|
||||
{{ form_row(form.description) }}
|
||||
{{ form_row(form.assignee) }}
|
||||
{{ form_row(form.circle) }}
|
||||
{{ form_row(form.startDate) }}
|
||||
{{ form_row(form.endDate) }}
|
||||
{{ form_row(form.warningInterval) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
{{ form_widget(form.submit, { 'label': 'Add a new task'|trans, 'attr': {'class': 'btn btn-save'} }) }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,33 @@
|
||||
{#
|
||||
* Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
{% extends person is defined ? "@ChillPerson/Person/layout.html.twig" : "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
||||
|
||||
|
||||
{% set activeRouteKey = 'chill_task_single_task_show' %}
|
||||
{% set person = task.person %}
|
||||
|
||||
{% block title %}
|
||||
{{ 'Task'|trans }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block personcontent %}
|
||||
|
||||
{% include '@ChillTask/SingleTask/_show.html.twig' %}
|
||||
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,12 @@
|
||||
{% extends "@ChillPerson/Person/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_task_task_list' %}
|
||||
{% set person = task.person %}
|
||||
|
||||
{% block title 'Remove task'|trans %}
|
||||
|
||||
{% block personcontent %}
|
||||
|
||||
{% include '@ChillTask/SingleTask/_transition.html.twig' %}
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,27 @@
|
||||
<div class="task-edit">
|
||||
|
||||
<h1>{{ 'Edit task'|trans }}</h1>
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
{{ form_row(form.title) }}
|
||||
{{ form_row(form.description) }}
|
||||
{{ form_row(form.assignee) }}
|
||||
{% if form.circle is defined %}
|
||||
{{ form_row(form.circle) }}
|
||||
{% endif %}
|
||||
{{ form_row(form.startDate) }}
|
||||
{{ form_row(form.endDate) }}
|
||||
{{ form_row(form.warningInterval) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a class="btn btn-cancel" href={% if task.person is not null %}"{{ chill_return_path_or('chill_task_singletask_list', { 'person_id': task.person.id } ) }}"{% else %}"{{ chill_return_path_or('chill_task_singletask_courselist', {'course_id': task.course.id}) }}" {% endif %}>
|
||||
{{ 'Cancel'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
{{ form_widget(form.submit, { 'label': 'Save task', 'attr': {'class' : 'btn btn-update'}})}}
|
||||
</li>
|
||||
</ul>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
@ -1,244 +0,0 @@
|
||||
{% macro date_status(title, tasks, count, paginator, status, isSingleStatus, person, user) %}
|
||||
{% if tasks|length > 0 %}
|
||||
<h3>{{ title|trans }}</h3>
|
||||
|
||||
<table class="table table-bordered border-dark chill-task-list">
|
||||
<tbody>
|
||||
{% for task in tasks %}
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
<span class="chill-task-list__row__title">{{ task.title }}</span>
|
||||
</div>
|
||||
|
||||
{% if person is null %}
|
||||
<div>
|
||||
<span class="chill-task-list__row__person-for">{{ 'For person'|trans }} :</span> <span class="chill-task-list__row__person"><a href="{{ path('chill_person_view', {person_id : task.person.Id}) }}">{{ task.person}}</a></span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<span class="chill-task-list__row__type">{{ task_workflow_metadata(task, 'definition.name')|trans }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{% for place in workflow_marked_places(task) %}
|
||||
<span class="task-status box type-{{ task.type }} place-{{ place }}">{{ place|trans }}</span>
|
||||
{% endfor %}
|
||||
{% if task.assignee is not null %}
|
||||
<div class="chill-task-list__row__assignee"><span class="chill_task-list__row__assignee_by">{{ 'By'|trans }} :</span> {{ task.assignee.username }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if task.startDate is not null or task.warningDate is not null or task.endDate is not null %}
|
||||
<div class="chill-task-list__row__dates">
|
||||
<ul class="record_actions column">
|
||||
{% if task.startDate is not null %}
|
||||
<li title="{{ 'Start'|trans|escape('html_attr') }}">
|
||||
<i class="fa fa-play" ></i> {{ task.startDate|format_date('medium') }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if task.warningDate is not null %}
|
||||
<li title="{{ 'Warning'|trans|escape('html_attr') }}">
|
||||
<i class="fa fa-exclamation-triangle"></i> {{ task.warningDate|format_date('medium') }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if task.endDate is not null %}
|
||||
<li title="{{ 'End'|trans|escape('html_attr') }}">
|
||||
<i class="fa fa-hourglass-end"></i> {{ task.endDate|format_date('medium') }}
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
{% if workflow_transitions(task)|length > 0 %}
|
||||
<li>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-task-exchange dropdown-toggle" href="#" role="button"
|
||||
id="taskExchange" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{{'Change task status'|trans}}
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="taskExchange">
|
||||
{% for transition in workflow_transitions(task) %}
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ path('chill_task_task_transition', { 'taskId': task.id, 'transition': transition.name, 'kind': 'single-task', 'list_params': app.request.query.all }) }}" class="{{ task_workflow_metadata(task, 'transition.class', transition)|e('html_attr') }}">
|
||||
{{ task_workflow_metadata(task, 'transition.verb', transition)|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li>
|
||||
<a href="{{ path('chill_task_single_task_show', { 'id': task.id, 'list_params': app.request.query.all }) }}" class="btn btn-show "></a>
|
||||
</li>
|
||||
|
||||
{% if is_granted('CHILL_TASK_TASK_UPDATE', task) %}
|
||||
<li>
|
||||
<a href="{{ path('chill_task_single_task_edit', { 'id': task.id, 'list_params': app.request.query.all }) }}" class="btn btn-update "></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('CHILL_TASK_TASK_DELETE', task) %}
|
||||
<li>
|
||||
<a href="{{ path('chill_task_single_task_delete', { 'id': task.id, 'list_params': app.request.query.all } ) }}" class="btn btn-delete "></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if isSingleStatus %}
|
||||
{% if tasks|length < paginator.getTotalItems %}
|
||||
{{ chill_pagination(paginator) }}
|
||||
{% endif %}
|
||||
|
||||
<!-- lien retour -->
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
{% if person is not null %}
|
||||
<a href="{{ path('chill_task_singletask_list', {'person_id': person.id}) }}" class="btn btn-cancel">
|
||||
{{ 'Back to the list' | trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if user is not null %}
|
||||
<a href="{{ path('chill_task_singletask_list') }}" class="btn btn-cancel">
|
||||
{{ 'Back to the list' | trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li>
|
||||
{% if person is not null %}
|
||||
<a href="{{ path('chill_task_single_task_new', {'person_id': person.id}) }}" class="btn btn-create">
|
||||
{{ 'Add a new task' | trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if user is not null %}
|
||||
<a href="{{ path('chill_task_single_task_new') }}" class="btn btn-create">
|
||||
{{ 'Add a new task' | trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_task_singletask_list', app.request.query.all|merge({ 'status': [ status ] })) }}" class="btn btn-misc">
|
||||
{{ 'See more' | trans }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% import _self as helper %}
|
||||
|
||||
<h1>{{ app.request.query.get('title', null)|escape('html')|default('Task list'|trans) }}</h1>
|
||||
|
||||
{% if false == app.request.query.boolean('hide_form', false) %}
|
||||
<h2>{{ 'Filter the tasks'|trans }}</h2>
|
||||
{{ form_start(form) }}
|
||||
{{ form_row(form.user_id) }}
|
||||
|
||||
{% if form.status is defined %}
|
||||
{{ form_row(form.status) }}
|
||||
{% endif %}
|
||||
|
||||
{% if form.types is defined %}
|
||||
{{ form_row(form.types) }}
|
||||
{% endif %}
|
||||
|
||||
{% if form.person_id is defined %}
|
||||
{{ form_row(form.person_id) }}
|
||||
{% endif %}
|
||||
|
||||
{% if form.center_id is defined %}
|
||||
{{ form_row(form.center_id) }}
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button type="submit" class="btn btn-submit">{{ 'Filter'|trans }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form)}}
|
||||
{% endif %}
|
||||
|
||||
{% if tasks_count == 0 %}
|
||||
<p class="chill-no-data-statement">{{ "There is no tasks."|trans }}</p>
|
||||
{% if person is not null and is_granted('CHILL_TASK_TASK_CREATE', person) %}
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
{% if person is not null %}
|
||||
<a href="{{ path('chill_task_single_task_new', {'person_id': person.id}) }}" class="btn btn-create">
|
||||
{{ 'Add a new task' | trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
|
||||
{% if false == app.request.query.boolean('hide_form', false) %}
|
||||
<h2>{{ 'Tasks'|trans }}</h2>
|
||||
{% endif %}
|
||||
|
||||
{% if person is not null and is_granted('CHILL_TASK_TASK_CREATE', person) %}
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
{% if person is not null %}
|
||||
<a href="{{ path('chill_task_single_task_new', {'person_id': person.id}) }}" class="btn btn-create">
|
||||
{{ 'Add a new task' | trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if single_task_ended_tasks is defined %}
|
||||
{{ helper.date_status('Tasks with expired deadline', single_task_ended_tasks, single_task_ended_count, single_task_ended_paginator, 'ended', isSingleStatus, person) }}
|
||||
{% endif %}
|
||||
|
||||
{% if single_task_warning_tasks is defined %}
|
||||
{{ helper.date_status('Tasks with warning deadline reached', single_task_warning_tasks, single_task_warning_count, single_task_warning_paginator, 'warning', isSingleStatus, person) }}
|
||||
{% endif %}
|
||||
|
||||
{% if single_task_current_tasks is defined %}
|
||||
{{ helper.date_status('Current tasks', single_task_current_tasks, single_task_current_count, single_task_current_paginator, 'current', isSingleStatus, person) }}
|
||||
{% endif %}
|
||||
|
||||
{% if single_task_not_started_tasks is defined %}
|
||||
{{ helper.date_status('Tasks not started', single_task_not_started_tasks, single_task_not_started_count, single_task_not_started_paginator, 'not_started', isSingleStatus, person) }}
|
||||
{% endif %}
|
||||
|
||||
{% if single_task_closed_tasks is defined %}
|
||||
{{ helper.date_status('Closed tasks', single_task_closed_tasks, single_task_closed_count, single_task_closed_paginator, 'closed', isSingleStatus, person) }}
|
||||
{% endif %}
|
||||
|
||||
{% if isSingleStatus == false %}
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
{% if person is not null and is_granted('CHILL_TASK_TASK_CREATE', person) %}
|
||||
<a href="{{ path('chill_task_single_task_new', {'person_id': person.id}) }}" class="btn btn-create">
|
||||
{{ 'Add a new task' | trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
@ -0,0 +1,32 @@
|
||||
<div class="task-new">
|
||||
|
||||
<h1>{{ 'New task'|trans }}</h1>
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
{{ form_errors(form) }}
|
||||
|
||||
{{ form_row(form.title) }}
|
||||
{{ form_row(form.description) }}
|
||||
{{ form_row(form.assignee) }}
|
||||
{% if form.circle is defined %}
|
||||
{{ form_row(form.circle) }}
|
||||
{% endif %}
|
||||
{{ form_row(form.startDate) }}
|
||||
{{ form_row(form.endDate) }}
|
||||
{{ form_row(form.warningInterval) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a class="btn btn-cancel" href={% if task.person is not null %} "{{ path('chill_task_singletask_list', { 'person_id': task.person.id, 'list_params': app.request.query.get('list_params', {} )} ) }}" {% else %} "{{ chill_return_path_or('chill_task_singletask_by-course_list', {'id': task.course.id}) }}" {% endif %}>
|
||||
{{'Cancel'|trans}}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{{ form_widget(form.submit, { 'label': 'Add a new task'|trans, 'attr': {'class': 'btn btn-save'} }) }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
</div>
|
@ -0,0 +1,113 @@
|
||||
<div class="task-show">
|
||||
|
||||
<h1>{{ 'Task'|trans }}</h1>
|
||||
|
||||
<h2>{{ task.title }}
|
||||
{% for place in workflow_marked_places(task) %}
|
||||
<span class="task-status box type-{{ task.type }} place-{{ place }}">{{ place|trans }}</span>
|
||||
{% endfor %}
|
||||
</h2>
|
||||
|
||||
<dl class="chill_view_data">
|
||||
|
||||
<dt class="inline">{{ 'Description'|trans }}</dt>
|
||||
<dd>
|
||||
{% if task.description is empty %}
|
||||
<span class="chill-no-data-statement">{{"No description"|trans}}</span>
|
||||
{% else %}
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ task.description|chill_markdown_to_html }}
|
||||
</blockquote>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="inline">{{ 'Assignee'|trans }}</dt>
|
||||
<dd>
|
||||
{% if task.assignee is null %}
|
||||
<span class="chill-no-data-statement">{{"No one assignee"|trans}}</span>
|
||||
{% else %}
|
||||
{{ task.assignee }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
{% if task.scope is not null %}
|
||||
<dt class="inline">{{ 'Scope'|trans }}</dt>
|
||||
<dd>
|
||||
<span class="scope">{{ task.scope.name|localize_translatable_string }}</span>
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
<h3>{{"Dates"|trans}}</h3>
|
||||
{% if task.startDate is null and task.endDate is null and task.warningDate is null %}
|
||||
<dt></dt>
|
||||
<dd>
|
||||
<span class="chill-no-data-statement">{{"No dates specified"|trans}}</span>
|
||||
</dd>
|
||||
</dt>
|
||||
{% else %}
|
||||
{% if task.startDate is not null %}
|
||||
<dt class="inline">{{ 'Start'|trans }}</dt>
|
||||
<dd>{{ task.startDate|format_date('long') }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if task.endDate is not null %}
|
||||
<dt class="inline">{{ 'End'|trans }}</dt>
|
||||
<dd>{{ task.endDate|format_date('long') }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if task.warningDate is not null %}
|
||||
<dt class="inline">{{ 'Warning'|trans }}</dt>
|
||||
<dd>{{ task.warningDate|format_date('long') }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
{% if timeline is not null %}
|
||||
<h3>{{"Timeline"|trans}}</h3>
|
||||
{{ timeline|raw }}
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a class="btn btn-cancel" href={% if task.person is not null %} "{{ chill_return_path_or('chill_task_singletask_list', { 'person_id': task.person.id } ) }}" {% else %} "{{ chill_return_path_or('chill_task_singletask_by-course_list', {'id': task.course.id}) }}" {% endif %}>
|
||||
{{'Cancel'|trans}}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if workflow_transitions(task)|length > 0 %}
|
||||
<li>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-task-exchange dropdown-toggle" href="#" role="button" id="taskExchange" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{{'Change task status'|trans}}
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="taskExchange">
|
||||
{% for transition in workflow_transitions(task) %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ path('chill_task_task_transition', { 'taskId': task.id, 'transition': transition.name, 'kind': 'single-task', 'return_path': app.request.uri }) }}" class="{{ task_workflow_metadata(task, 'transition.class', transition)|e('html_attr') }}">
|
||||
{{ task_workflow_metadata(task, 'transition.verb', transition)|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('CHILL_TASK_TASK_DELETE', task) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_forward_return_path('chill_task_single_task_delete', { 'id': task.id } ) }}" class="btn btn-delete">
|
||||
{{ 'Delete'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('CHILL_TASK_TASK_UPDATE', task) %}
|
||||
<li>
|
||||
<a class="btn btn-update" href="{{ chill_path_forward_return_path('chill_task_single_task_edit', { 'id': task.id }) }}">
|
||||
{{ 'Edit the task'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul></div>
|
@ -0,0 +1,23 @@
|
||||
<h2>{{ 'Apply transition on task <em>%title%</em>'|trans({ '%title%': task.title } )|raw }}</h2>
|
||||
|
||||
|
||||
{% if task_workflow_metadata(task, 'transition.sentence_confirmation', transition) is not empty %}
|
||||
<p class="message-confirm">{{ task_workflow_metadata(task, 'transition.sentence_confirmation', transition)|trans }}</p>
|
||||
{% else %}
|
||||
<p>{{ 'Are you sure to apply the transition %name% on this task ?'|trans({ '%name%': task_workflow_metadata(task, 'transition.name', transition)|default(transition.name)|trans }) }}</p>
|
||||
{% endif %}
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
<ul class="record_actions">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_task_singletask_list', app.request.query.get('list_params', { }) ) }}" class="btn btn-cancel">
|
||||
{{ 'Back to the list'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{{ form_widget(form.submit, { 'attr' : { 'class' : "btn btn-task-exchange green" }, 'label': task_workflow_metadata(task, 'transition.apply_transition_submit_label', transition)|default('apply')|trans } ) }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
@ -1,59 +0,0 @@
|
||||
{#
|
||||
* Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
{% extends "@ChillPerson/Person/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_task_single_task_edit' %}
|
||||
{% set person = task.person %}
|
||||
|
||||
{% block title %}{{ 'Edit task'|trans }}{% endblock %}
|
||||
|
||||
{% block personcontent %}
|
||||
<div class="task-edit">
|
||||
|
||||
<h1>{{ 'Edit task'|trans }}</h1>
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
{{ form_row(form.title) }}
|
||||
{{ form_row(form.description) }}
|
||||
{{ form_row(form.assignee) }}
|
||||
{{ form_row(form.circle) }}
|
||||
{{ form_row(form.startDate) }}
|
||||
{{ form_row(form.endDate) }}
|
||||
{{ form_row(form.warningInterval) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a class="btn btn-cancel" href="{% apply spaceless %}
|
||||
{% if app.request.query.has('returnPath') %}
|
||||
{{ app.request.query.get('returnPath')|escape('html_attr') }}">
|
||||
{% else %}
|
||||
{{ path('chill_task_single_task_show', { 'id': task.id, 'list_params': app.request.query.get('list_params', { } )} ) }}
|
||||
{% endif %}
|
||||
{% endapply %}">
|
||||
{{ app.request.query.get('returnLabel')|default('Cancel'|trans) }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{{ form_widget(form.submit, { 'label': 'Save task', 'attr': {'class' : 'btn btn-update'}})}}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
@ -15,30 +15,32 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
|
||||
{% extends '@ChillPerson/Person/layout.html.twig' %}
|
||||
{% extends layout %}
|
||||
|
||||
{% set activeRouteKey = 'chill_task_single_task_new' %}
|
||||
|
||||
{% block title %}{{ 'Task list'|trans }}{% endblock %}
|
||||
{% block title %}
|
||||
{{ 'Task list'|trans }}
|
||||
{% endblock %}
|
||||
|
||||
{% macro thead() %}
|
||||
{% endmacro %}
|
||||
{% macro thead() %}{% endmacro %}
|
||||
|
||||
{% macro row(task) %}
|
||||
{% endmacro %}
|
||||
{% macro row(task) %}{% endmacro %}
|
||||
|
||||
{% block filtertasks %}
|
||||
{% if person is not null %}
|
||||
{% block personcontent %}
|
||||
<div class="tasks">
|
||||
{% include 'ChillTaskBundle:SingleTask:_list.html.twig' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% else %}
|
||||
{% block content %}
|
||||
<div class="col-md-10 col-xxl tasks">
|
||||
{% include 'ChillTaskBundle:SingleTask:_list.html.twig' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% if person is not null %}
|
||||
{% block personcontent %}
|
||||
<div class="tasks">
|
||||
{% include '@ChillTask/SingleTask/Person/list.html.twig' %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% else %}
|
||||
{% block content %}
|
||||
<div class="col-md-10 col-xxl tasks">
|
||||
{% include '@ChillTask/SingleTask/AccompanyingCourse/list.html.twig' %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -1,139 +0,0 @@
|
||||
{#
|
||||
* Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
{% extends "@ChillPerson/Person/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_task_single_task_show' %}
|
||||
{% set person = task.person %}
|
||||
|
||||
{% block title %}{{ 'Task'|trans }}{% endblock %}
|
||||
|
||||
|
||||
{% block personcontent %}
|
||||
<div class="task-show">
|
||||
|
||||
<h1>{{ 'Task'|trans }}</h1>
|
||||
|
||||
<h2>{{ task.title }} {% for place in workflow_marked_places(task) %}
|
||||
<span class="task-status box type-{{ task.type }} place-{{ place }}">{{ place|trans }}</span>
|
||||
{% endfor %}</h2>
|
||||
|
||||
<dl class="chill_view_data">
|
||||
|
||||
<dt class="inline">{{ 'Description'|trans }}</dt>
|
||||
<dd>
|
||||
{% if task.description is empty %}
|
||||
<span class="chill-no-data-statement">{{"No description"|trans}}</span>
|
||||
{% else %}
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ task.description|chill_markdown_to_html }}
|
||||
</blockquote>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="inline">{{ 'Assignee'|trans }}</dt>
|
||||
<dd>
|
||||
{% if task.assignee is null %}
|
||||
<span class="chill-no-data-statement">{{"No one assignee"|trans}}</span>
|
||||
{% else %}
|
||||
{{ task.assignee }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="inline">{{ 'Scope'|trans }}</dt>
|
||||
<dd><span class="scope">{{ task.scope.name|localize_translatable_string }}</span></dd>
|
||||
|
||||
<h3>{{"Dates"|trans}}</h3>
|
||||
{% if task.startDate is null and task.endDate is null and task.warningDate is null %}
|
||||
<dt>
|
||||
<dd><span class="chill-no-data-statement">{{"No dates specified"|trans}}</span></dd>
|
||||
</dt>
|
||||
{% else %}
|
||||
{% if task.startDate is not null %}
|
||||
<dt class="inline">{{ 'Start'|trans }}</dt>
|
||||
<dd>{{ task.startDate|format_date('long') }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if task.endDate is not null %}
|
||||
<dt class="inline">{{ 'End'|trans }}</dt>
|
||||
<dd>{{ task.endDate|format_date('long') }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if task.warningDate is not null %}
|
||||
<dt class="inline">{{ 'Warning'|trans }}</dt>
|
||||
<dd>{{ task.warningDate|format_date('long') }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
{% if timeline is not null %}
|
||||
<h3>{{"Timeline"|trans}}</h3>
|
||||
{{ timeline|raw }}
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a class="btn btn-cancel" href="{%- if app.request.query.has('returnPath') -%}
|
||||
{{ app.request.query.get('returnPath')|escape('html_attr') }}
|
||||
{%- else -%}
|
||||
{{ path('chill_task_singletask_list', app.request.query.get('list_params', {}) ) }}
|
||||
{%- endif -%}">
|
||||
{{ app.request.query.get('returnLabel')|default('Back to the list'|trans) }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if workflow_transitions(task)|length > 0 %}
|
||||
<li>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-task-exchange dropdown-toggle" href="#" role="button"
|
||||
id="taskExchange" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{{'Change task status'|trans}}
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="taskExchange">
|
||||
{% for transition in workflow_transitions(task) %}
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ path('chill_task_task_transition', { 'taskId': task.id, 'transition': transition.name, 'kind': 'single-task', 'return_path': app.request.uri }) }}" class="{{ task_workflow_metadata(task, 'transition.class', transition)|e('html_attr') }}">
|
||||
{{ task_workflow_metadata(task, 'transition.verb', transition)|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('CHILL_TASK_TASK_UPDATE', task) %}
|
||||
<li>
|
||||
<a class="btn btn-update" href="{{ path('chill_task_single_task_edit', { 'id': task.id }) }}">
|
||||
{{ 'Edit the task'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('CHILL_TASK_TASK_CREATE', task) %}
|
||||
<li>
|
||||
<a href="{{ path('chill_task_single_task_delete', { 'id': task.id } ) }}" class="btn btn-delete">
|
||||
{{ 'Delete'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -1,34 +0,0 @@
|
||||
{% extends "@ChillPerson/Person/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_task_task_list' %}
|
||||
{% set person = task.person %}
|
||||
|
||||
{% block title 'Remove task'|trans %}
|
||||
|
||||
{% block personcontent %}
|
||||
|
||||
<h2>{{ 'Apply transition on task <em>%title%</em>'|trans({ '%title%': task.title } )|raw }}</h2>
|
||||
|
||||
|
||||
{% if task_workflow_metadata(task, 'transition.sentence_confirmation', transition) is not empty %}
|
||||
<p class="message-confirm">{{ task_workflow_metadata(task, 'transition.sentence_confirmation', transition)|trans }}</p>
|
||||
{% else %}
|
||||
<p>{{ 'Are you sure to apply the transition %name% on this task ?'|trans({ '%name%': task_workflow_metadata(task, 'transition.name', transition)|default(transition.name)|trans }) }}</p>
|
||||
{% endif %}
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
<ul class="record_actions">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_task_singletask_list', app.request.query.get('list_params', { }) ) }}" class="btn btn-cancel">
|
||||
{{ 'Back to the list'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{{ form_widget(form.submit, { 'attr' : { 'class' : "btn btn-task-exchange green" }, 'label': task_workflow_metadata(task, 'transition.apply_transition_submit_label', transition)|default('apply')|trans } ) }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
{% endblock %}
|
@ -32,23 +32,27 @@ use Psr\Log\LoggerInterface;
|
||||
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Chill\TaskBundle\Security\Authorization\AuthorizationEvent;
|
||||
|
||||
final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
|
||||
{
|
||||
const CREATE = 'CHILL_TASK_TASK_CREATE';
|
||||
const UPDATE = 'CHILL_TASK_TASK_UPDATE';
|
||||
const SHOW = 'CHILL_TASK_TASK_SHOW';
|
||||
const CREATE_COURSE = 'CHILL_TASK_TASK_CREATE_FOR_COURSE';
|
||||
const CREATE_PERSON = 'CHILL_TASK_TASK_CREATE_FOR_PERSON';
|
||||
const DELETE = 'CHILL_TASK_TASK_DELETE';
|
||||
const SHOW = 'CHILL_TASK_TASK_SHOW';
|
||||
const UPDATE = 'CHILL_TASK_TASK_UPDATE';
|
||||
|
||||
const ROLES = [
|
||||
self::CREATE,
|
||||
self::UPDATE,
|
||||
self::CREATE_COURSE,
|
||||
self::CREATE_PERSON,
|
||||
self::DELETE,
|
||||
self::SHOW,
|
||||
self::DELETE
|
||||
self::UPDATE,
|
||||
];
|
||||
|
||||
protected AuthorizationHelper $authorizationHelper;
|
||||
@ -63,7 +67,9 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
|
||||
|
||||
protected VoterHelperInterface $voter;
|
||||
|
||||
|
||||
public function __construct(
|
||||
VoterHelperFactoryInterface $voterHelperFactory,
|
||||
AccessDecisionManagerInterface $accessDecisionManager,
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
@ -80,7 +86,8 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
|
||||
$this->voter = $voterFactory
|
||||
->generate(AbstractTask::class)
|
||||
->addCheckFor(AbstractTask::class, self::ROLES)
|
||||
->addCheckFor(Person::class, [self::SHOW])
|
||||
->addCheckFor(Person::class, [self::SHOW, self::CREATE_PERSON])
|
||||
->addCheckFor(AccompanyingPeriod::class, [self::SHOW, self::CREATE_COURSE])
|
||||
->addCheckFor(null, [self::SHOW])
|
||||
->build()
|
||||
;
|
||||
@ -89,14 +96,6 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
|
||||
public function supports($attribute, $subject)
|
||||
{
|
||||
return $this->voter->supports($attribute, $subject);
|
||||
/*
|
||||
return ($subject instanceof AbstractTask && in_array($attribute, self::ROLES))
|
||||
||
|
||||
($subject instanceof Person && \in_array($attribute, [ self::CREATE, self::SHOW ]))
|
||||
||
|
||||
(NULL === $subject && $attribute === self::SHOW )
|
||||
;
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,50 +131,24 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
|
||||
// do pre-flight check, relying on other decision manager
|
||||
// those pre-flight check concern associated entities
|
||||
if ($subject instanceof AbstractTask) {
|
||||
// a user can always see his own tasks
|
||||
if ($subject->getAssignee() === $token->getUser()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (NULL !== $person = $subject->getPerson()) {
|
||||
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
|
||||
return false;
|
||||
}
|
||||
} elseif (false) {
|
||||
// here will come the test if the task is associated to an accompanying course
|
||||
} elseif (NULL !== $period = $subject->getCourse()) {
|
||||
if (!$this->accessDecisionManager->decide($token, [AccompanyingPeriodVoter::SEE], $period)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do regular check.
|
||||
return $this->voter->voteOnAttribute($attribute, $subject, $token);
|
||||
|
||||
/*
|
||||
if ($subject instanceof AbstractTask) {
|
||||
if ($subject->getPerson() === null) {
|
||||
throw new \LogicException("You should associate a person with task "
|
||||
. "in order to check autorizations");
|
||||
}
|
||||
|
||||
$person = $subject->getPerson();
|
||||
} elseif ($subject instanceof Person) {
|
||||
$person = $subject;
|
||||
} else {
|
||||
// subject is null. We check that at least one center is reachable
|
||||
$centers = $this->authorizationHelper->getReachableCenters($token->getUser(), new Role($attribute));
|
||||
|
||||
return count($centers) > 0;
|
||||
}
|
||||
|
||||
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
|
||||
return false;
|
||||
}
|
||||
$center = $this->centerResolverDispatcher->resolveCenter($subject);
|
||||
|
||||
if (NULL === $center) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->authorizationHelper->userHasAccess(
|
||||
$token->getUser(),
|
||||
$subject,
|
||||
$attribute
|
||||
);
|
||||
*/
|
||||
}
|
||||
|
||||
public function getRoles()
|
||||
|
@ -1,4 +1,6 @@
|
||||
module.exports = function(encore, entries)
|
||||
{
|
||||
entries.push(__dirname + '/Resources/public/chill/index.js');
|
||||
|
||||
encore.addEntry('page_task_list', __dirname + '/Resources/public/page/tile_list/index.js');
|
||||
};
|
||||
|
@ -1,11 +1,6 @@
|
||||
services:
|
||||
Chill\TaskBundle\Controller\:
|
||||
resource: '../../Controller'
|
||||
tags: ['controller.service_arguments']
|
||||
|
||||
Chill\TaskBundle\Controller\SingleTaskController:
|
||||
arguments:
|
||||
$eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface'
|
||||
$timelineBuilder: '@chill_main.timeline_builder'
|
||||
$logger: '@chill.main.logger'
|
||||
tags: ['controller.service_arguments']
|
||||
Chill\TaskBundle\Controller\:
|
||||
resource: "../../Controller"
|
||||
autowire: true
|
||||
autoconfigure: ture
|
||||
tags: ["controller.service_arguments"]
|
||||
|
@ -1,4 +1,9 @@
|
||||
services:
|
||||
Chill\TaskBundle\Form\:
|
||||
resource: '../../Form/'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\TaskBundle\Form\SingleTaskListType:
|
||||
arguments:
|
||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||
|
@ -1,23 +1,22 @@
|
||||
services:
|
||||
Chill\TaskBundle\Menu\UserMenuBuilder:
|
||||
arguments:
|
||||
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
|
||||
$counter: '@Chill\TaskBundle\Templating\UI\CountNotificationTask'
|
||||
$translator: '@Symfony\Component\Translation\TranslatorInterface'
|
||||
$authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
|
||||
tags:
|
||||
- { name: 'chill.menu_builder' }
|
||||
|
||||
Chill\TaskBundle\Menu\PersonMenuBuilder:
|
||||
arguments:
|
||||
$authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
|
||||
$translator: '@Symfony\Component\Translation\TranslatorInterface'
|
||||
tags:
|
||||
- { name: 'chill.menu_builder' }
|
||||
|
||||
Chill\TaskBundle\Menu\SectionMenuBuilder:
|
||||
arguments:
|
||||
$authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
|
||||
$translator: '@Symfony\Component\Translation\TranslatorInterface'
|
||||
tags:
|
||||
- { name: 'chill.menu_builder' }
|
||||
Chill\TaskBundle\Menu\UserMenuBuilder:
|
||||
arguments:
|
||||
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
|
||||
$counter: '@Chill\TaskBundle\Templating\UI\CountNotificationTask'
|
||||
$translator: '@Symfony\Component\Translation\TranslatorInterface'
|
||||
$authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
|
||||
tags:
|
||||
- { name: "chill.menu_builder" }
|
||||
|
||||
Chill\TaskBundle\Menu\MenuBuilder:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
tags:
|
||||
- { name: "chill.menu_builder" }
|
||||
|
||||
Chill\TaskBundle\Menu\SectionMenuBuilder:
|
||||
arguments:
|
||||
$authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
|
||||
$translator: '@Symfony\Component\Translation\TranslatorInterface'
|
||||
tags:
|
||||
- { name: "chill.menu_builder" }
|
||||
|
@ -9,3 +9,9 @@ services:
|
||||
arguments:
|
||||
- "@chill.main.security.authorization.helper"
|
||||
Chill\TaskBundle\Repository\SingleTaskRepository: '@chill_task.single_task_repository'
|
||||
|
||||
Chill\TaskBundle\Repository\SingleTaskAclAwareRepository:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\TaskBundle\Repository\SingleTaskAclAwareRepositoryInterface: '@Chill\TaskBundle\Repository\SingleTaskAclAwareRepository'
|
||||
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\Task;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20210909153533 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
|
||||
$this->addSql('ALTER TABLE chill_task.recurring_task ADD course_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE chill_task.recurring_task ADD CONSTRAINT FK_9F663B90591CC992 FOREIGN KEY (course_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('CREATE INDEX IDX_9F663B90591CC992 ON chill_task.recurring_task (course_id)');
|
||||
$this->addSql('ALTER TABLE chill_task.single_task ADD course_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE chill_task.single_task ADD CONSTRAINT FK_194CB3D8591CC992 FOREIGN KEY (course_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('CREATE INDEX IDX_194CB3D8591CC992 ON chill_task.single_task (course_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_task.recurring_task DROP CONSTRAINT FK_9F663B90591CC992');
|
||||
$this->addSql('ALTER TABLE chill_task.recurring_task DROP course_id');
|
||||
$this->addSql('ALTER TABLE chill_task.single_task DROP CONSTRAINT FK_194CB3D8591CC992');
|
||||
$this->addSql('ALTER TABLE chill_task.single_task DROP course_id');
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\Task;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20211029213909 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add index for task state and end date';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE INDEX by_end_date ON chill_task.single_task (end_date DESC NULLS FIRST)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP INDEX chill_task.by_end_date');
|
||||
}
|
||||
}
|
@ -1,53 +1,55 @@
|
||||
Tasks: 'Tâches'
|
||||
'New task': 'Nouvelle tâche'
|
||||
'Add a new task': 'Ajouter une nouvelle tâche'
|
||||
Tasks: "Tâches"
|
||||
"New task": "Nouvelle tâche"
|
||||
"Add a new task": "Ajouter une nouvelle tâche"
|
||||
Title: Titre
|
||||
Description: Description
|
||||
Assignee: 'Personne assignée'
|
||||
Assignee: "Personne assignée"
|
||||
Scope: Cercle
|
||||
'Start date': 'Date de début'
|
||||
'End date': "Date d'échéance"
|
||||
'Warning date': "Date d'avertissement"
|
||||
'Warning interval': "Délai d'avertissement avant la date d'échéance"
|
||||
'Unknown dates': 'Dates non spécifiées'
|
||||
'N': ''
|
||||
'Unit': ''
|
||||
"Start date": "Date de début"
|
||||
"End date": "Date d'échéance"
|
||||
"Warning date": "Date d'avertissement"
|
||||
"Warning interval": "Délai d'avertissement avant la date d'échéance"
|
||||
"Unknown dates": "Dates non spécifiées"
|
||||
"N": ""
|
||||
"Unit": ""
|
||||
Task: Tâche
|
||||
Details: Détails
|
||||
Person: Personne
|
||||
Date: Date
|
||||
Dates: Dates
|
||||
User: Utilisateur
|
||||
'Task list': 'Liste des tâches'
|
||||
'Tasks with expired deadline': "Tâches avec une date d'échéance dépassée"
|
||||
'Tasks with warning deadline reached': "Tâches avec une date d'avertissement atteinte"
|
||||
'Current tasks': 'Tâches en cours'
|
||||
'Closed tasks': Tâches terminées
|
||||
'Tasks not started': 'Tâches non commencées'
|
||||
'Task start date': 'Date de début'
|
||||
'Task warning date': "Date d'avertissement"
|
||||
'Task end date': "Date d'échéance"
|
||||
'Start': 'Début'
|
||||
'Warning': 'Avertissement'
|
||||
'End': 'Échéance'
|
||||
'Task type': 'Type'
|
||||
'Task status': 'Statut'
|
||||
'Edit the task': 'Modifier la tâche'
|
||||
'Edit task': 'Modifier la tâche'
|
||||
'Save task': 'Enregistrer la tâche'
|
||||
'View the task': 'Voir la tâche'
|
||||
'Update the task': 'Mettre à jour la tâche'
|
||||
'Remove task': 'Supprimer la tâche'
|
||||
'Delete': 'Supprimer'
|
||||
'Change task status': 'Changer le statut'
|
||||
"Task list": "Liste des tâches"
|
||||
"Tasks with expired deadline": "Tâches avec une date d'échéance dépassée"
|
||||
"Tasks with warning deadline reached": "Tâches avec une date d'avertissement atteinte"
|
||||
"Current tasks": "Tâches en cours"
|
||||
"Closed tasks": Tâches terminées
|
||||
"Tasks not started": "Tâches non commencées"
|
||||
"Task start date": "Date de début"
|
||||
"Task warning date": "Date d'avertissement"
|
||||
"Task end date": "Date d'échéance"
|
||||
"Start": "Début"
|
||||
"Warning": "Avertissement"
|
||||
"End": "Échéance"
|
||||
"Task type": "Type"
|
||||
"Task status": "Statut"
|
||||
"Edit the task": "Modifier la tâche"
|
||||
"Edit task": "Modifier la tâche"
|
||||
"Save task": "Enregistrer la tâche"
|
||||
"View the task": "Voir la tâche"
|
||||
"Update the task": "Mettre à jour la tâche"
|
||||
"Remove task": "Supprimer la tâche"
|
||||
"Delete": "Supprimer"
|
||||
"Change task status": "Changer le statut"
|
||||
'Are you sure you want to remove the task about "%name%" ?': 'Êtes-vous sûr·e de vouloir supprimer la tâche de "%name%"?'
|
||||
'See more': 'Voir plus'
|
||||
'Associated tasks': 'Tâches associées'
|
||||
'My tasks': 'Mes tâches'
|
||||
'No description': 'Pas de description'
|
||||
'No dates specified': 'Dates non spécifiées'
|
||||
'No one assignee': 'Aucune personne assignée'
|
||||
'Task types': Types de tâches
|
||||
'Are you sure you want to remove the task "%title%" ?': 'Êtes-vous sûr·e de vouloir supprimer la tâche "%title%" ?'
|
||||
"See more": "Voir plus"
|
||||
"Associated tasks": "Tâches associées"
|
||||
"My tasks": "Mes tâches"
|
||||
"Tasks for this accompanying period": "Tâches pour ce parcours d'accompagnement"
|
||||
"No description": "Pas de description"
|
||||
"No dates specified": "Dates non spécifiées"
|
||||
"No one assignee": "Aucune personne assignée"
|
||||
"Task types": Types de tâches
|
||||
Days: Jour(s)
|
||||
Weeks: Semaine(s)
|
||||
Months: Mois
|
||||
@ -61,38 +63,43 @@ Default task: Tâche par défaut
|
||||
Not assigned: Aucun utilisateur assigné
|
||||
For person: Pour
|
||||
By: Par
|
||||
Any tasks: Aucune tâche
|
||||
|
||||
# transitions - default task definition
|
||||
'new': 'nouvelle'
|
||||
'in_progress': 'en cours'
|
||||
'closed': 'fermée'
|
||||
'canceled': 'supprimée'
|
||||
"new": "nouvelle"
|
||||
"in_progress": "en cours"
|
||||
"closed": "fermée"
|
||||
"canceled": "supprimée"
|
||||
start: démarrer
|
||||
close: clotûrer
|
||||
cancel: annuler
|
||||
Start_verb: Démarrer
|
||||
Close_verb: Clotûrer
|
||||
Set this task to cancel state: Marquer cette tâche comme annulée
|
||||
'%user% has closed the task': '%user% a fermé la tâche'
|
||||
'%user% has canceled the task': '%user% a annulé la tâche'
|
||||
'%user% has started the task': '%user% a commencé la tâche'
|
||||
'%user% has created the task': '%user% a introduit la tâche'
|
||||
"%user% has closed the task": "%user% a fermé la tâche"
|
||||
"%user% has canceled the task": "%user% a annulé la tâche"
|
||||
"%user% has started the task": "%user% a commencé la tâche"
|
||||
"%user% has created the task": "%user% a introduit la tâche"
|
||||
Are you sure you want to close this task ?: Êtes-vous sûrs de vouloir clotûrer cette tâche ?
|
||||
Are you sure you want to cancel this task ?: Êtes-vous sûrs de vouloir annuler cette tâche ?
|
||||
Are you sure you want to start this task ?: Êtes-vous sûrs de vouloir démarrer cette tâche ?
|
||||
|
||||
#Flash messages
|
||||
'The task is created': 'La tâche a été créée'
|
||||
'There is no tasks.': Aucune tâche.
|
||||
'The task has been successfully removed.': 'La tâche a bien été supprimée'
|
||||
'This form contains errors': 'Ce formulaire contient des erreurs'
|
||||
'The task has been updated': 'La tâche a été mise à jour'
|
||||
'The transition is successfully applied': 'La transition a bien été effectuée'
|
||||
'The transition could not be applied': "La transition n'a pas pu être appliquée"
|
||||
"The task is created": "La tâche a été créée"
|
||||
"There is no tasks.": Aucune tâche.
|
||||
"The task has been successfully removed.": "La tâche a bien été supprimée"
|
||||
"This form contains errors": "Ce formulaire contient des erreurs"
|
||||
"The task has been updated": "La tâche a été mise à jour"
|
||||
"The transition is successfully applied": "La transition a bien été effectuée"
|
||||
"The transition could not be applied": "La transition n'a pas pu être appliquée"
|
||||
|
||||
#widget
|
||||
'%number% tasks over deadline': '{0} Aucune tâche dépassée|{1} Une tâche dépassée | ]1,Inf[ %count% tâches dépassées'
|
||||
'%number% tasks near deadline': '{0} Aucune tâche en rappel|{1} Une tâche en rappel | ]1,Inf[ %count% tâches en rappel'
|
||||
"%number% tasks over deadline": "{0} Aucune tâche dépassée|{1} Une tâche dépassée | ]1,Inf[ %count% tâches dépassées"
|
||||
"%number% tasks near deadline": "{0} Aucune tâche en rappel|{1} Une tâche en rappel | ]1,Inf[ %count% tâches en rappel"
|
||||
|
||||
Tasks near deadline: Tâches à échéance proche
|
||||
Tasks over deadline: Tâches à échéance dépassée
|
||||
Tasks without alert: Tâches à échéance future ou sans échéance
|
||||
|
||||
#title
|
||||
My tasks near deadline: Mes tâches à échéance proche
|
||||
@ -107,4 +114,4 @@ All centers: Tous les centres
|
||||
CHILL_TASK_TASK_CREATE: Ajouter une tâche
|
||||
CHILL_TASK_TASK_DELETE: Supprimer une tâche
|
||||
CHILL_TASK_TASK_SHOW: Voir une tâche
|
||||
CHILL_TASK_TASK_UPDATE: Modifier une tâche
|
||||
CHILL_TASK_TASK_UPDATE: Modifier une tâche
|
||||
|
Loading…
x
Reference in New Issue
Block a user