Compare commits

...

4 Commits

41 changed files with 120 additions and 1057 deletions

View File

@@ -67,7 +67,7 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
->setPerson($person)
->setDate($this->faker->dateTimeThisYear())
->setDurationTime($this->faker->dateTime(36000))
->setType($this->getRandomActivityType())
->setActivityType($this->getRandomActivityType())
->setScope($this->getRandomScope());
// ->setAttendee($this->faker->boolean())

View File

@@ -672,7 +672,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
}
/**
* @deprecated
* @deprecated use @link{self::setActivityType} instead
*/
public function setType(ActivityType $activityType): self
{

View File

@@ -16,6 +16,7 @@ use Doctrine\ORM\EntityManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Intl\Countries;
use Symfony\Component\Intl\Intl;
class LoadCountriesCommand extends Command
@@ -32,11 +33,10 @@ class LoadCountriesCommand extends Command
public static function prepareCountryList($languages)
{
$regionBundle = Intl::getRegionBundle();
$countries = [];
foreach ($languages as $language) {
$countries[$language] = $regionBundle->getCountryNames($language);
$countries[$language] = Countries::getNames([$language]);
}
$countryEntities = [];
@@ -90,6 +90,7 @@ class LoadCountriesCommand extends Command
}
$em->flush();
return 0;
}
}

View File

@@ -529,7 +529,7 @@ class PermissionsGroupController extends AbstractController
static fn (Role $role) => $role->getRole(),
$this->roleHierarchy
->getReachableRoles(
[new Role($roleScope->getRole())]
[$roleScope->getRole()]
)
);
}

View File

@@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Intl\Intl;
use Symfony\Component\Intl\Languages;
use function in_array;
/**
@@ -46,9 +47,7 @@ class LoadLanguages extends AbstractFixture implements ContainerAwareInterface,
public function load(ObjectManager $manager)
{
echo "loading languages... \n";
foreach (Intl::getLanguageBundle()->getLanguageNames() as $code => $language) {
foreach (Languages::getLanguageCodes() as $code) {
if (
!in_array($code, $this->regionalVersionToInclude, true)
&& !in_array($code, $this->ancientToExclude, true)
@@ -78,7 +77,7 @@ class LoadLanguages extends AbstractFixture implements ContainerAwareInterface,
$names = [];
foreach ($this->container->getParameter('chill_main.available_languages') as $lang) {
$names[$lang] = Intl::getLanguageBundle()->getLanguageName($languageCode);
$names[$lang] = Languages::getName($languageCode);
}
return $names;

View File

@@ -12,8 +12,9 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form\Event;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\EventDispatcher\Event;
class CustomizeFormEvent extends \Symfony\Component\EventDispatcher\Event
class CustomizeFormEvent extends Event
{
final public const NAME = 'chill_main.customize_form';

View File

@@ -69,6 +69,8 @@ use Symfony\Component\Security\Core\Role\Role;
*
* }
* ```
*
* @deprecated use @link{\Chill\MainBundle\Form\Type\ScopePickerType:class} instead
*/
trait AppendScopeChoiceTypeTrait
{
@@ -97,7 +99,7 @@ trait AppendScopeChoiceTypeTrait
*/
protected function appendScopeChoices(
FormBuilderInterface $builder,
Role $role,
string $role,
Center $center,
User $user,
AuthorizationHelper $authorizationHelper,

View File

@@ -52,7 +52,7 @@ class ScopePickerType extends AbstractType
array_filter(
$this->authorizationHelper->getReachableScopes(
$this->security->getUser(),
$options['role'] instanceof Role ? $options['role']->getRole() : $options['role'],
$options['role'],
$options['center']
),
static fn (Scope $s) => $s->isActive()
@@ -92,6 +92,6 @@ class ScopePickerType extends AbstractType
->setAllowedTypes('center', [Center::class, 'array', 'null'])
// create ``role` option
->setRequired('role')
->setAllowedTypes('role', ['string', Role::class]);
->setAllowedTypes('role', ['string']);
}
}

View File

@@ -63,7 +63,7 @@ class UserPickerType extends AbstractType
->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class, 'null', 'array'])
// create ``role` option
->setRequired('role')
->setAllowedTypes('role', ['string', \Symfony\Component\Security\Core\Role\Role::class]);
->setAllowedTypes('role', ['string']);
$resolver
->setDefault('having_permissions_group_flag', null)

View File

@@ -133,11 +133,11 @@ final class NotificationRepository implements ObjectRepository
{
$query = $this->queryByAddressee($addressee)->select('n');
if ($limit) {
if (null !== $limit) {
$query = $query->setMaxResults($limit);
}
if ($offset) {
if (null !== $offset) {
$query = $query->setFirstResult($offset);
}

View File

@@ -34,8 +34,13 @@ use function get_class;
*/
class AuthorizationHelper implements AuthorizationHelperInterface
{
public function __construct(private readonly CenterResolverManagerInterface $centerResolverManager, private readonly LoggerInterface $logger, private readonly ScopeResolverDispatcher $scopeResolverDispatcher, private readonly UserACLAwareRepositoryInterface $userACLAwareRepository, private readonly ParentRoleHelper $parentRoleHelper)
{
public function __construct(
private readonly CenterResolverManagerInterface $centerResolverManager,
private readonly LoggerInterface $logger,
private readonly ScopeResolverDispatcher $scopeResolverDispatcher,
private readonly UserACLAwareRepositoryInterface $userACLAwareRepository,
private readonly ParentRoleHelper $parentRoleHelper
) {
}
/**
@@ -94,10 +99,6 @@ class AuthorizationHelper implements AuthorizationHelperInterface
*/
public function getReachableCenters(UserInterface $user, string $role, ?Scope $scope = null): array
{
if ($role instanceof Role) {
$role = $role->getRole();
}
if (!$user instanceof User) {
return [];
}
@@ -132,10 +133,10 @@ class AuthorizationHelper implements AuthorizationHelperInterface
/**
* Return all reachable circle for a given user, center and role.
*
* @param Center|Center[] $center
* @param Center|array<Center> $center
* @return Scope[]
*/
public function getReachableCircles(UserInterface $user, \Symfony\Component\Security\Core\Role\Role|string $role, \Chill\MainBundle\Entity\Center|array $center)
public function getReachableCircles(UserInterface $user, string $role, \Chill\MainBundle\Entity\Center|array $center): array
{
$scopes = [];
@@ -223,11 +224,8 @@ class AuthorizationHelper implements AuthorizationHelperInterface
* the scope is taken into account.
*
* @param mixed $entity the entity may also implement HasScopeInterface
* @param Role|string $attribute
*
* @return bool true if the user has access
*/
public function userHasAccess(User $user, mixed $entity, \Symfony\Component\Security\Core\Role\Role|string $attribute)
public function userHasAccess(User $user, mixed $entity, string $attribute): bool
{
$centers = $this->centerResolverManager->resolveCenters($entity);
@@ -248,7 +246,7 @@ class AuthorizationHelper implements AuthorizationHelperInterface
*
* @return bool true if the child role is granted by parent role
*/
private function isRoleReached(string $childRole, string $parentRole)
private function isRoleReached(string $childRole, string $parentRole): bool
{
return $this->parentRoleHelper->isRoleReached($childRole, $parentRole);
}

View File

@@ -14,12 +14,16 @@ namespace Chill\MainBundle\Security\Authorization;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use function in_array;
final readonly class DefaultVoterHelper implements VoterHelperInterface
{
public function __construct(private AuthorizationHelper $authorizationHelper, private CenterResolverDispatcherInterface $centerResolverDispatcher, private array $configuration)
{
public function __construct(
private AuthorizationHelper $authorizationHelper,
private CenterResolverManagerInterface $centerResolverDispatcher,
private array $configuration
) {
}
public function supports($attribute, $subject): bool

View File

@@ -12,11 +12,14 @@ declare(strict_types=1);
namespace Chill\MainBundle\Security\Authorization;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
class DefaultVoterHelperFactory implements VoterHelperFactoryInterface
{
public function __construct(protected AuthorizationHelper $authorizationHelper, protected CenterResolverDispatcherInterface $centerResolverDispatcher)
{
public function __construct(
protected AuthorizationHelper $authorizationHelper,
protected CenterResolverManagerInterface $centerResolverDispatcher
) {
}
public function generate($context): VoterGeneratorInterface

View File

@@ -12,13 +12,16 @@ declare(strict_types=1);
namespace Chill\MainBundle\Security\Authorization;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
final class DefaultVoterHelperGenerator implements VoterGeneratorInterface
{
private array $configuration = [];
public function __construct(private readonly AuthorizationHelper $authorizationHelper, private readonly CenterResolverDispatcherInterface $centerResolverDispatcher)
{
public function __construct(
private readonly AuthorizationHelper $authorizationHelper,
private readonly CenterResolverManagerInterface $centerResolverDispatcher
) {
}
public function addCheckFor(?string $subject, array $attributes): self

View File

@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Security\PasswordRecover;
use Chill\MainBundle\Entity\User;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\Event;
class PasswordRecoverEvent extends Event
{

View File

@@ -59,7 +59,7 @@ class ChillTwigRoutingHelper extends AbstractExtension
];
}
public function getLabelReturnPath($default)
public function getLabelReturnPath(?string $default = null): string|null
{
$request = $this->requestStack->getCurrentRequest();
@@ -68,21 +68,14 @@ class ChillTwigRoutingHelper extends AbstractExtension
/**
* Build an url with a returnPath parameter to current page.
*
* @param string $name
* @param array $parameters
* @param bool $relative
* @param mixed|null $label
*
* @return string
*/
public function getPathAddReturnPath($name, $parameters = [], $relative = false, $label = null)
public function getPathAddReturnPath(string $name, array $parameters = [], bool $relative = false, ?string $label = null): string
{
$request = $this->requestStack->getCurrentRequest();
$parameters['returnPath'] = $request->getRequestUri();
if ($label) {
if (null !== $label) {
$parameters['returnPathLabel'] = $label;
}
@@ -91,14 +84,8 @@ class ChillTwigRoutingHelper extends AbstractExtension
/**
* Build an url with a returnPath parameter to current page.
*
* @param string $name
* @param array $parameters
* @param bool $relative
*
* @return string
*/
public function getPathForwardReturnPath($name, $parameters = [], $relative = false)
public function getPathForwardReturnPath(string $name, array $parameters = [], bool $relative = false): string
{
$request = $this->requestStack->getCurrentRequest();
@@ -116,14 +103,8 @@ class ChillTwigRoutingHelper extends AbstractExtension
/**
* Return the return path if it exists, or generate the path if not.
*
* @param string $name
* @param array $parameters
* @param bool $relative
*
* @return string
*/
public function getReturnPathOr($name, $parameters = [], $relative = false)
public function getReturnPathOr(string $name, array $parameters = [], bool $relative = false): string
{
$request = $this->requestStack->getCurrentRequest();
@@ -134,7 +115,7 @@ class ChillTwigRoutingHelper extends AbstractExtension
return $this->originalExtension->getPath($name, $parameters, $relative);
}
public function isUrlGenerationSafe(Node $argsNode)
public function isUrlGenerationSafe(Node $argsNode): array|string
{
return $this->originalExtension->isUrlGenerationSafe($argsNode);
}

View File

@@ -13,7 +13,7 @@ namespace Chill\MainBundle\Templating\Events;
use ArrayAccess;
use RuntimeException;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\Event;
/**
* This event is transmitted on event chill_block.*.

View File

@@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Actions;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Event triggered when an entity attached to a person is removed.

View File

@@ -19,7 +19,7 @@ use Chill\PersonBundle\Form\AccompanyingCourseType;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -39,7 +39,7 @@ use function is_array;
/**
* Class AccompanyingCourseController.
*/
class AccompanyingCourseController extends Controller
class AccompanyingCourseController extends AbstractController
{
public function __construct(protected SerializerInterface $serializer, protected EventDispatcherInterface $dispatcher, protected ValidatorInterface $validator, private readonly AccompanyingPeriodWorkRepository $workRepository, private readonly Registry $registry, private readonly TranslatorInterface $translator)
{

View File

@@ -1,483 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Controller;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\AccompanyingPeriodType;
use Chill\PersonBundle\Privacy\PrivacyEvent;
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use DateTime;
use Doctrine\DBAL\Exception;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use function array_filter;
use function count;
/**
* Class AccompanyingPeriodController.
*/
class AccompanyingPeriodController extends AbstractController
{
/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* @var ValidatorInterface
*/
protected $validator;
public function __construct(
protected AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository,
EventDispatcherInterface $eventDispatcher,
ValidatorInterface $validator
) {
$this->eventDispatcher = $eventDispatcher;
$this->validator = $validator;
}
/**
* @throws \Exception
*/
public function closeAction(int $person_id, Request $request): Response
{
$person = $this->_getPerson($person_id);
$this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, 'You are not allowed to update this person');
if ($person->isOpen() === false) {
$this->get('session')->getFlashBag()
->add('error', $this->get('translator')
->trans(
'Beware period is closed',
['%name%' => $person->__toString()]
));
return $this->redirect(
$this->generateUrl('chill_person_accompanying_period_list', [
'person_id' => $person->getId(),
])
);
}
$current = $person->getCurrentAccompanyingPeriod();
$form = $this->createForm(AccompanyingPeriodType::class, $current, [
'period_action' => 'close',
'center' => $person->getCenter(),
]);
if ($request->getMethod() === 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$person->close($current);
$errors = $this->_validatePerson($person);
if (count($errors) === 0) {
$this->get('session')->getFlashBag()
->add('success', $this->get('translator')
->trans('An accompanying period has been closed.', [
'%name%' => $person->__toString(),
]));
$this->getDoctrine()->getManager()->flush();
return $this->redirect(
$this->generateUrl('chill_person_accompanying_period_list', [
'person_id' => $person->getId(),
])
);
}
$this->get('session')->getFlashBag()
->add('error', $this->get('translator')
->trans('Error! Period not closed!'));
foreach ($errors as $error) {
$this->get('session')->getFlashBag()
->add('info', $error->getMessage());
}
} else { //if form is not valid
$this->get('session')->getFlashBag()
->add(
'error',
$this->get('translator')
->trans('Pediod closing form is not valid')
);
foreach ($form->getErrors() as $error) {
$this->get('session')->getFlashBag()
->add('info', $error->getMessage());
}
}
}
return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig', [
'form' => $form->createView(),
'person' => $person,
'accompanying_period' => $current,
]);
}
public function createAction(int $person_id, Request $request): Response
{
$person = $this->_getPerson($person_id);
$this->denyAccessUnlessGranted(
PersonVoter::UPDATE,
$person,
'You are not allowed to update this person'
);
$accompanyingPeriod = new AccompanyingPeriod(new DateTime('now'));
$accompanyingPeriod->setClosingDate(new DateTime('now'));
$accompanyingPeriod->addPerson($person);
//or $person->addAccompanyingPeriod($accompanyingPeriod);
$form = $this->createForm(
AccompanyingPeriodType::class,
$accompanyingPeriod,
[
'period_action' => 'create',
'center' => $person->getCenter(),
]
);
if ($request->getMethod() === 'POST') {
$form->handleRequest($request);
$errors = $this->_validatePerson($person);
$flashBag = $this->get('session')->getFlashBag();
if (
$form->isValid(['Default', 'closed'])
&& count($errors) === 0
) {
$em = $this->getDoctrine()->getManager();
$em->persist($accompanyingPeriod);
$em->flush();
$flashBag->add(
'success',
$this->get('translator')->trans(
'A period has been created.'
)
);
return $this->redirect(
$this->generateUrl('chill_person_accompanying_period_list', [
'person_id' => $person->getId(),
])
);
}
$flashBag->add('error', $this->get('translator')
->trans('Error! Period not created!'));
foreach ($errors as $error) {
$flashBag->add('info', $error->getMessage());
}
}
return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig', [
'form' => $form->createView(),
'person' => $person,
'accompanying_period' => $accompanyingPeriod,
]);
}
/**
* @ParamConverter("person", options={"id": "person_id"})
*/
public function listAction(Person $person): Response
{
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $person);
$event = new PrivacyEvent($person, [
'element_class' => AccompanyingPeriod::class,
'action' => 'list',
]);
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
$accompanyingPeriodsRaw = $this->accompanyingPeriodACLAwareRepository
->findByPerson($person, AccompanyingPeriodVoter::SEE);
usort($accompanyingPeriodsRaw, static fn ($a, $b) => $b->getOpeningDate() > $a->getOpeningDate());
// filter visible or not visible
$accompanyingPeriods = array_filter($accompanyingPeriodsRaw, fn (AccompanyingPeriod $ap) => $this->isGranted(AccompanyingPeriodVoter::SEE, $ap));
return $this->render('@ChillPerson/AccompanyingPeriod/list.html.twig', [
'accompanying_periods' => $accompanyingPeriods,
'person' => $person,
]);
}
public function openAction(int $person_id, Request $request): Response
{
$person = $this->_getPerson($person_id);
$this->denyAccessUnlessGranted(
PersonVoter::UPDATE,
$person,
'You are not allowed to update this person'
);
//in case the person is already open
if ($person->isOpen()) {
$this->get('session')->getFlashBag()
->add('error', $this->get('translator')
->trans(
'Error! Period %name% is not closed ; it can be open',
['%name%' => $person->__toString()]
));
return $this->redirect(
$this->generateUrl('chill_person_accompanying_period_list', [
'person_id' => $person->getId(),
])
);
}
$accompanyingPeriod = new AccompanyingPeriod(new DateTime());
$form = $this->createForm(
AccompanyingPeriodType::class,
$accompanyingPeriod,
[
'period_action' => 'open',
'center' => $person->getCenter(),
]
);
if ($request->getMethod() === 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$person->open($accompanyingPeriod);
$errors = $this->_validatePerson($person);
if (count($errors) <= 0) {
$this->get('session')->getFlashBag()
->add('success', $this->get('translator')
->trans(
'An accompanying period has been opened.',
['%name%' => $person->__toString()]
));
$this->getDoctrine()->getManager()->flush();
return $this->redirect(
$this->generateUrl('chill_person_accompanying_period_list', [
'person_id' => $person->getId(),
])
);
}
$this->get('session')->getFlashBag()
->add('error', $this->get('translator')
->trans('Period not opened'));
foreach ($errors as $error) {
$this->get('session')->getFlashBag()
->add('info', $error->getMessage());
}
} else { // if errors in forms
$this->get('session')->getFlashBag()
->add(
'error',
$this->get('translator')
->trans('Period not opened : form is invalid')
);
}
}
return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig', [
'form' => $form->createView(),
'person' => $person,
'accompanying_period' => $accompanyingPeriod,
]);
}
public function reOpenAction(int $person_id, int $period_id, Request $request): Response
{
/** @var Person $person */
$person = $this->_getPerson($person_id);
/** @var AccompanyingPeriod $period */
$period = array_filter(
$person->getAccompanyingPeriods(),
static fn (AccompanyingPeriod $p) => $p->getId() === ($period_id)
)[0] ?? null;
if (null === $period) {
throw $this->createNotFoundException('period not found');
}
$confirm = $request->query->getBoolean('confirm', false);
if (true === $confirm && $period->canBeReOpened($person)) {
$period->reOpen();
$this->_validatePerson($person);
$this->getDoctrine()->getManager()->flush();
$this->addFlash('success', $this->get('translator')->trans(
'The period has been re-opened'
));
return $this->redirectToRoute('chill_person_accompanying_period_list', [
'person_id' => $person->getId(),
]);
}
if (false === $confirm && $period->canBeReOpened($person)) {
return $this->render('ChillPersonBundle:AccompanyingPeriod:re_open.html.twig', [
'period' => $period,
'person' => $person,
]);
}
return (new Response())
->setStatusCode(Response::HTTP_BAD_REQUEST)
->setContent('You cannot re-open this period');
}
/**
* @throws Exception
*/
public function updateAction(int $person_id, int $period_id, Request $request): Response
{
$em = $this->getDoctrine()->getManager();
/** @var AccompanyingPeriod $accompanyingPeriod */
$accompanyingPeriod = $em->getRepository(AccompanyingPeriod::class)->find($period_id);
if (null === $accompanyingPeriod) {
throw $this->createNotFoundException('Period with id ' . $period_id . ' is not found');
}
/** @var Person $person */
$person = $this->_getPerson($person_id);
// CHECK
if (!$accompanyingPeriod->containsPerson($person)) {
throw new Exception('Accompanying period ' . $period_id . ' does not contain person ' . $person_id);
}
$this->denyAccessUnlessGranted(
PersonVoter::UPDATE,
$person,
'You are not allowed to update this person'
);
$form = $this->createForm(
AccompanyingPeriodType::class,
$accompanyingPeriod,
[
'period_action' => 'update',
'center' => $person->getCenter(),
]
);
if ($request->getMethod() === 'POST') {
$form->handleRequest($request);
$errors = $this->_validatePerson($person);
$flashBag = $this->get('session')->getFlashBag();
if (
$form->isValid(['Default', 'closed'])
&& count($errors) === 0
) {
$em->flush();
$flashBag->add(
'success',
$this->get('translator')->trans('An accompanying period has been updated.')
);
return $this->redirect(
$this->generateUrl('chill_person_accompanying_period_list', [
'person_id' => $person->getId(),
])
);
}
$flashBag->add('error', $this->get('translator')
->trans('Error when updating the period'));
foreach ($errors as $error) {
$flashBag->add('info', $error->getMessage());
}
}
return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig', [
'form' => $form->createView(),
'person' => $person,
'accompanying_period' => $accompanyingPeriod,
]);
}
/**
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
private function _getPerson(int $id): Person
{
$person = $this->getDoctrine()->getManager()
->getRepository(\Chill\PersonBundle\Entity\Person::class)->find($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;
}
private function _validatePerson(Person $person): ConstraintViolationListInterface
{
$errors = $this->validator->validate(
$person,
null,
['Default']
);
// Can be disabled with config
if (false === $this->container->getParameter('chill_person.allow_multiple_simultaneous_accompanying_periods')) {
$errors_accompanying_period = $this->validator->validate(
$person,
null,
['accompanying_period_consistent']
);
foreach ($errors_accompanying_period as $error) {
$errors->add($error);
}
}
return $errors;
}
}

View File

@@ -25,6 +25,7 @@ use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Search\SimilarPersonMatcher;
use Chill\TaskBundle\Entity\SingleTask;
use http\Exception\InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
@@ -32,10 +33,15 @@ use Symfony\Contracts\Translation\TranslatorInterface;
use function count;
class PersonDuplicateController extends Controller
class PersonDuplicateController extends AbstractController
{
public function __construct(private readonly SimilarPersonMatcher $similarPersonMatcher, private readonly TranslatorInterface $translator, private readonly PersonRepository $personRepository, private readonly PersonMove $personMove, private readonly EventDispatcherInterface $eventDispatcher)
{
public function __construct(
private readonly SimilarPersonMatcher $similarPersonMatcher,
private readonly TranslatorInterface $translator,
private readonly PersonRepository $personRepository,
private readonly PersonMove $personMove,
private readonly EventDispatcherInterface $eventDispatcher
) {
}
public function confirmAction($person1_id, $person2_id, Request $request)

View File

@@ -26,6 +26,8 @@ use Symfony\Component\Security\Core\Role\Role;
/**
* Class AccompanyingPeriodType.
*
* @deprecated to be removed
*/
class AccompanyingPeriodType extends AbstractType
{
@@ -81,7 +83,7 @@ class AccompanyingPeriodType extends AbstractType
if ('visible' === $this->config['user']) {
$builder->add('user', UserPickerType::class, [
'center' => $options['center'],
'role' => new Role(PersonVoter::SEE),
'role' => PersonVoter::SEE,
]);
}

View File

@@ -213,7 +213,7 @@ class PersonType extends AbstractType
]);
}
if ($options['cFGroup']) {
if (null !== $options['cFGroup']) {
$builder
->add(
'cFData',

View File

@@ -44,6 +44,8 @@ use function is_array;
* `Chill\MainBundle\Entity\Center`. By default, all the reachable centers as selected.
* - with the `role` option, only the people belonging to the reachable center for the
* given role are displayed.
*
* @deprecated use @link{\Chill\PersonBundle\Form\Type\PickPersonDynamicType::class} instead
*/
class PickPersonType extends AbstractType
{
@@ -107,7 +109,7 @@ class PickPersonType extends AbstractType
->addAllowedTypes('centers', ['array', Center::class, 'null'])
->setDefault('centers', null)
->setDefined('role')
->addAllowedTypes('role', [Role::class, 'null'])
->addAllowedTypes('role', ['string', 'null'])
->setDefault('role', null);
// add the default options

View File

@@ -32,7 +32,7 @@ namespace Chill\PersonBundle\Privacy;
*/
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\Event;
class AccompanyingPeriodPrivacyEvent extends Event
{

View File

@@ -32,8 +32,8 @@ namespace Chill\PersonBundle\Privacy;
*/
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\Event;
use function count;
/**

View File

@@ -18,7 +18,7 @@ use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
@@ -35,16 +35,18 @@ use function count;
final readonly class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodACLAwareRepositoryInterface
{
public function __construct(private AccompanyingPeriodRepository $accompanyingPeriodRepository, private Security $security, private AuthorizationHelper $authorizationHelper, private CenterResolverDispatcherInterface $centerResolverDispatcher)
{
public function __construct(
private AccompanyingPeriodRepository $accompanyingPeriodRepository,
private Security $security,
private AuthorizationHelper $authorizationHelper,
private CenterResolverManagerInterface $centerResolverDispatcher
) {
}
/**
* @param array|PostalCode[]
*
* @return QueryBuilder
* @param array<PostalCode> $postalCodes
*/
public function buildQueryOpenedAccompanyingCourseByUser(?User $user, array $postalCodes = [])
public function buildQueryOpenedAccompanyingCourseByUser(?User $user, array $postalCodes = []): QueryBuilder
{
$qb = $this->accompanyingPeriodRepository->createQueryBuilder('ap');
@@ -139,7 +141,7 @@ final readonly class AccompanyingPeriodACLAwareRepository implements Accompanyin
->getReachableCircles(
$this->security->getUser(),
$role,
$this->centerResolverDispatcher->resolveCenter($person)
$this->centerResolverDispatcher->resolveCenters($person)
);
if (0 === count($scopes)) {

View File

@@ -58,7 +58,7 @@ class PersonListWidget implements WidgetInterface
// show only the person from the authorized centers
$and = $qb->expr()->andX();
$centers = $this->authorizationHelper
->getReachableCenters($this->getUser(), new Role(PersonVoter::SEE));
->getReachableCenters($this->getUser(), PersonVoter::SEE);
$and->add($qb->expr()->in('person.center', ':centers'));
$qb->setParameter('centers', $centers);

View File

@@ -10,11 +10,6 @@ services:
$paginatorFactory: '@chill_main.paginator_factory'
tags: ['controller.service_arguments']
Chill\PersonBundle\Controller\AccompanyingPeriodController:
autowire: true
autoconfigure: true
tags: ['controller.service_arguments']
Chill\PersonBundle\Controller\PersonAddressController:
arguments:
$validator: '@Symfony\Component\Validator\Validator\ValidatorInterface'

View File

@@ -219,7 +219,7 @@ class ReportController extends AbstractController
$reachableScopes = $this->authorizationHelper
->getReachableScopes(
$this->getUser(),
new Role('CHILL_REPORT_SEE'),
'CHILL_REPORT_SEE',
$person->getCenter()
);
@@ -555,7 +555,7 @@ class ReportController extends AbstractController
),
'method' => 'POST',
'cFGroup' => $cFGroup,
'role' => new Role('CHILL_REPORT_CREATE'),
'role' => 'CHILL_REPORT_CREATE',
'center' => $person->getCenter(),
]);
}
@@ -577,7 +577,7 @@ class ReportController extends AbstractController
),
'method' => 'PUT',
'cFGroup' => $entity->getCFGroup(),
'role' => new Role('CHILL_REPORT_UPDATE'),
'role' => 'CHILL_REPORT_UPDATE',
'center' => $entity->getPerson()->getCenter(),
]);
}

View File

@@ -1,159 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ReportBundle\Search;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Search\AbstractSearch;
use Chill\MainBundle\Search\ParsingException;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use RuntimeException;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role;
use function array_key_exists;
/**
* Search amongst reports.
*/
class ReportSearch extends AbstractSearch implements ContainerAwareInterface
{
use \Symfony\Component\DependencyInjection\ContainerAwareTrait;
/**
* @var \Chill\MainBundle\Entity\User
*/
private $user;
public function __construct(
private readonly EntityManagerInterface $em,
private readonly AuthorizationHelper $helper,
TokenStorageInterface $tokenStorage
) {
if (!$tokenStorage->getToken()->getUser() instanceof \Chill\MainBundle\Entity\User) {
throw new RuntimeException('an user must be associated with token');
}
$this->user = $tokenStorage->getToken()->getUser();
}
public function getOrder()
{
return 10000;
}
public function isActiveByDefault()
{
return false;
}
public function renderResult(array $terms, $start = 0, $limit = 50, array $options = [], $format = 'html')
{
return $this->container->get('templating')->render('ChillReportBundle:Search:results.html.twig', [
'reports' => $this->getReports($terms, $start, $limit),
'total' => $this->count($terms),
'pattern' => $this->recomposePattern($terms, ['date'], 'report'),
]);
}
public function supports($domain, $format = 'html')
{
return 'report' === $domain;
}
private function addACL(QueryBuilder $qb)
{
//adding join
$qb->join('r.person', 'p');
$role = new Role('CHILL_REPORT_SEE');
$reachableCenters = $this->helper->getReachableCenters($this->user, $role);
$whereElement = $qb->expr()->orX();
$i = 0;
foreach ($reachableCenters as $center) {
$reachableScopesId = array_map(
static fn (Scope $scope) => $scope->getId(),
$this->helper->getReachableScopes($this->user, $role, $center)
);
$whereElement->add(
$qb->expr()->andX(
$qb->expr()->eq('p.center', ':center_' . $i),
$qb->expr()->in('r.scope', ':reachable_scopes_' . $i)
)
);
$qb->setParameter('center_' . $i, $center);
$qb->setParameter('reachable_scopes_' . $i, $reachableScopesId);
}
return $whereElement;
}
/**
* @param array $terms the terms
*
* @return \Doctrine\ORM\QueryBuilder
*/
private function buildQuery(array $terms)
{
$query = $this->em->createQueryBuilder();
$query->from('ChillReportBundle:Report', 'r');
//throw a parsing exception if key 'date' and default is set
if (array_key_exists('date', $terms) && '' !== $terms['_default']) {
throw new ParsingException('You may not set a date argument and a date in default');
}
//throw a parsing exception if no argument except report
if (!array_key_exists('date', $terms) && '' === $terms['_default']) {
throw new ParsingException('You must provide either a date:YYYY-mm-dd argument or a YYYY-mm-dd default search');
}
if (array_key_exists('date', $terms)) {
$query->andWhere($query->expr()->eq('r.date', ':date'))
->setParameter('date', $this->parseDate($terms['date']));
} elseif (array_key_exists('_default', $terms)) {
$query->andWhere($query->expr()->eq('r.date', ':date'))
->setParameter('date', $this->parseDate($terms['_default']));
}
$query->andWhere($this->addACL($query));
return $query;
}
private function count(array $terms)
{
$qb = $this->buildQuery($terms);
$qb->select('COUNT(r.id)');
return $qb->getQuery()->getSingleScalarResult();
}
private function getReports(array $terms, $start, $limit)
{
$qb = $this->buildQuery($terms);
$qb->select('r')
->setMaxResults($limit)
->setFirstResult($start)
->orderBy('r.date', 'desc');
$reportQuery = $qb->getQuery();
$reportQuery->setFetchMode(\Chill\ReportBundle\Entity\Report::class, 'person', \Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER);
return $reportQuery->getResult();
}
}

View File

@@ -3,18 +3,6 @@ services:
# class: Chill\ReportBundle\Example
# arguments: [@service_id, "plain_value", %parameter%]
services:
chill.report.search:
class: Chill\ReportBundle\Search\ReportSearch
arguments:
- '@doctrine.orm.entity_manager'
- '@chill.main.security.authorization.helper'
- '@security.token_storage'
calls:
- [setContainer, ["@service_container"]]
tags:
- { name: chill.search, alias: 'report' }
chill.report.timeline:
class: Chill\ReportBundle\Timeline\TimelineReportProvider
arguments:

View File

@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\TaskBundle\Controller;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\MainBundle\Serializer\Model\Counter;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
@@ -39,7 +39,6 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_map;
@@ -47,8 +46,15 @@ use function array_merge;
final class SingleTaskController extends AbstractController
{
public function __construct(private readonly CenterResolverDispatcherInterface $centerResolverDispatcher, private readonly PaginatorFactory $paginatorFactory, private readonly SingleTaskAclAwareRepositoryInterface $singleTaskAclAwareRepository, private readonly TranslatorInterface $translator, private readonly EventDispatcherInterface $eventDispatcher, private readonly TimelineBuilder $timelineBuilder, private readonly LoggerInterface $logger, private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory)
{
public function __construct(
private readonly PaginatorFactory $paginatorFactory,
private readonly SingleTaskAclAwareRepositoryInterface $singleTaskAclAwareRepository,
private readonly TranslatorInterface $translator,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly TimelineBuilder $timelineBuilder,
private readonly LoggerInterface $logger,
private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory
) {
}
/**
@@ -259,9 +265,8 @@ final class SingleTaskController extends AbstractController
* name="chill_task_singletask_list"
* )
*/
public function listAction(
Request $request
) {
public function listAction()
{
$this->denyAccessUnlessGranted(TaskVoter::SHOW, null);
$filterOrder = $this->buildFilterOrder();
@@ -303,9 +308,7 @@ final class SingleTaskController extends AbstractController
* name="chill_task_singletask_by-course_list")
*/
public function listCourseTasks(
AccompanyingPeriod $course,
FormFactoryInterface $formFactory,
Request $request
AccompanyingPeriod $course
): Response {
$this->denyAccessUnlessGranted(TaskVoter::SHOW, $course);
@@ -398,7 +401,6 @@ final class SingleTaskController extends AbstractController
}
/**
* @return Response
* @Route(
* "/{_locale}/task/single-task/list/my",
* name="chill_task_singletask_my_tasks",
@@ -409,7 +411,7 @@ final class SingleTaskController extends AbstractController
* defaults={"_format": "json"}
* )
*/
public function myTasksAction(string $_format, Request $request)
public function myTasksAction(string $_format, Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_USER');

View File

@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\TaskBundle\Event;
use Chill\TaskBundle\Entity\AbstractTask;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\Event;
class TaskEvent extends Event
{

View File

@@ -12,10 +12,10 @@ declare(strict_types=1);
namespace Chill\TaskBundle\Event\UI;
use Chill\TaskBundle\Entity\AbstractTask;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Workflow\Transition;
use Symfony\Contracts\EventDispatcher\Event;
class UIEvent extends Event
{

View File

@@ -1,276 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\TaskBundle\Form;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\DataTransformer\PersonToIdTransformer;
use Chill\PersonBundle\Form\Type\PickPersonType;
use Chill\TaskBundle\Entity\SingleTask;
use Chill\TaskBundle\Repository\SingleTaskRepository;
use Chill\TaskBundle\Security\Authorization\TaskVoter;
use Chill\TaskBundle\Workflow\TaskWorkflowManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role;
use function array_combine;
use function array_map;
use function count;
class SingleTaskListType extends AbstractType
{
/**
* @var AuthorizationHelper
*/
protected $authorizationHelper;
/**
* @var EntityManagerInterface
*/
protected $em;
/**
* @var TaskWorkflowManager
*/
protected $taskWorkflowManager;
/**
* @var TokenStorageInterface
*/
protected $tokenStorage;
public function __construct(
EntityManagerInterface $em,
TokenStorageInterface $tokenStorage,
AuthorizationHelper $authorizationHelper,
TaskWorkflowManager $taskWorkflowManager
) {
$this->em = $em;
$this->tokenStorage = $tokenStorage;
$this->authorizationHelper = $authorizationHelper;
$this->taskWorkflowManager = $taskWorkflowManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$statuses = [
'Tasks not started' => SingleTaskRepository::DATE_STATUS_NOT_STARTED,
'Tasks with expired deadline' => SingleTaskRepository::DATE_STATUS_ENDED,
'Tasks with warning deadline reached' => SingleTaskRepository::DATE_STATUS_WARNING,
'Current tasks' => SingleTaskRepository::DATE_STATUS_CURRENT,
'Closed tasks' => 'closed',
];
$builder
->add('user_id', ChoiceType::class, [
'choices' => $this->getUserChoices($options),
'placeholder' => 'Any user',
'required' => false,
'label' => 'Assignee',
]);
if ($options['add_status']) {
$builder
->add('status', ChoiceType::class, [
'choices' => $statuses,
'expanded' => true,
'multiple' => true,
'label' => 'status',
]);
}
if ($options['add_type']) {
$types = $this->getTaskTypesChoices($options);
if (count($types) > 0) {
$builder->add('types', ChoiceType::class, [
'choices' => $types,
'required' => false,
'expanded' => true,
'multiple' => true,
'label' => 'Task types',
]);
}
}
if (null === $options['person']) {
$builder
->add('person_id', PickPersonType::class, [
'centers' => $this->authorizationHelper
->getReachableCenters(
$this->tokenStorage->getToken()->getUser(),
new Role(TaskVoter::SHOW)
),
'required' => false,
'label' => 'Associated person',
]);
$reachablesCenters = $this->getReachablesCenters();
if (count($reachablesCenters) > 1) {
$builder
->add('center_id', EntityType::class, [
'class' => \Chill\MainBundle\Entity\Center::class,
'choices' => $reachablesCenters,
'label' => 'Center',
'required' => false,
'placeholder' => 'All centers',
]);
}
} else {
// add a hidden field
$builder
->add('person_id', HiddenType::class);
$builder->get('person_id')
->addModelTransformer(new PersonToIdTransformer($this->em));
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefined('person')
->setDefault('person', null)
->setAllowedTypes('person', [Person::class, 'null'])
->setDefined('accompanyingCourse')
->setDefined('add_status')
->setDefault('add_status', false)
->setAllowedTypes('add_status', ['bool'])
->setDefined('add_type')
->setDefault('add_type', false)
->setAllowedTypes('add_type', ['bool']);
}
protected function getReachablesCenters()
{
$user = $this->tokenStorage->getToken()->getUser();
$role = new Role(TaskVoter::SHOW);
return $this->authorizationHelper->getReachableCenters($user, $role);
}
protected function getTaskTypesChoices($options)
{
$qb = $this->em->createQueryBuilder();
$user = $this->tokenStorage->getToken()->getUser();
$role = new Role(TaskVoter::SHOW);
$centers = $this->authorizationHelper->getReachableCenters($user, $role);
$qb->select('DISTINCT task.type AS type')
->from(SingleTask::class, 'task')
->join('task.person', 'person');
$i = 0;
$orCenters = $qb->expr()->orX();
foreach ($centers as $center) {
$circles = $this->authorizationHelper->getReachableCircles($user, $role, $center);
if (count($circles) > 0) {
$andX = $qb->expr()->andX();
$andX
->add($qb->expr()->eq('person.center', ':center_' . $i))
->add($qb->expr()->in('task.circle', ':circles_' . $i));
$orCenters->add($andX);
$qb
->setParameter('center_' . $i, $center)
->setParameter('circles_' . $i, $circles);
++$i;
}
}
if (0 < $i) {
$qb->where($orCenters);
}
$types = $qb->getQuery()->getResult();
$choices = [];
foreach ($types as $row) {
$fake = (new SingleTask())->setType($row['type']);
$label = $this->taskWorkflowManager->getWorkflowMetadata($fake, 'definition.name');
$choices[$label] = $row['type'];
}
return $choices;
}
protected function getUserChoices($options)
{
$users = $this->getUsersAssigneedToTask($options);
$choices = array_combine(
// get usernames
array_map(static fn (User $user) => $user->getUsername(), $users),
// get ids
array_map(static fn (User $user) => $user->getId(), $users)
);
$choices['Unassigned'] = '_unassigned';
return $choices;
}
/**
* Return a list of user having a task assigned.
*
*
* @return User[]
*/
protected function getUsersAssigneedToTask(mixed $options)
{
$qb = $this->em->createQueryBuilder();
$user = $this->tokenStorage->getToken()->getUser();
$role = new Role(TaskVoter::SHOW);
$centers = $this->authorizationHelper->getReachableCenters($user, $role);
$qb->select('DISTINCT user')
->from(User::class, 'user')
->join(SingleTask::class, 'task', \Doctrine\ORM\Query\Expr\Join::WITH, 'task.assignee = user')
->join('task.person', 'person')
->where("user.enabled = 'TRUE'");
if (null !== $options['person']) {
$qb
->andWhere($qb->expr()->eq('task.person', ':person'))
->setParameter('person', $options['person']);
}
$i = 0;
$circleCenterCond = $qb->expr()->orX();
foreach ($centers as $center) {
$circles = $this->authorizationHelper->getReachableCircles($user, $role, $center);
// add condition about person and circle
$circleCenterCond->add(
$qb->expr()->andX()
->add($qb->expr()->eq('person.center', ':center_' . $i))
->add($qb->expr()->in('task.circle', ':circles_' . $i))
);
$qb->setParameter('center_' . $i, $center)
->setParameter('circles_' . $i, $circles);
// increase counter
++$i;
}
$qb->andWhere($circleCenterCond);
return $qb->getQuery()->getResult();
}
}

View File

@@ -17,6 +17,7 @@ use Chill\MainBundle\Form\Type\DateIntervalType;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Form\Type\UserPickerType;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
use Chill\TaskBundle\Security\Authorization\TaskVoter;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
@@ -28,17 +29,17 @@ use Symfony\Component\Security\Core\Role\Role;
class SingleTaskType extends AbstractType
{
public function __construct(private readonly ParameterBagInterface $parameterBag, private readonly CenterResolverDispatcherInterface $centerResolverDispatcher, private readonly ScopeResolverDispatcher $scopeResolverDispatcher)
public function __construct(private readonly ParameterBagInterface $parameterBag, private readonly CenterResolverManagerInterface $centerResolverDispatcher, private readonly ScopeResolverDispatcher $scopeResolverDispatcher)
{
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$center = null;
$centers = null;
$isScopeConcerned = false;
if (null !== $task = $options['data']) {
$center = $this->centerResolverDispatcher->resolveCenter($task);
$centers = $this->centerResolverDispatcher->resolveCenters($task);
$isScopeConcerned = $this->scopeResolverDispatcher->isConcerned($task);
}
@@ -49,7 +50,7 @@ class SingleTaskType extends AbstractType
])
->add('assignee', UserPickerType::class, [
'required' => false,
'center' => $center,
'center' => $centers,
'role' => TaskVoter::SHOW,
'placeholder' => 'Not assigned',
'attr' => ['class' => ' select2 '],
@@ -67,7 +68,7 @@ class SingleTaskType extends AbstractType
if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) {
$builder
->add('circle', ScopePickerType::class, [
'center' => $center,
'center' => $centers,
'role' => $options['role'],
'required' => true,
]);
@@ -78,6 +79,6 @@ class SingleTaskType extends AbstractType
{
$resolver
->setRequired('role')
->setAllowedTypes('role', [Role::class, 'string']);
->setAllowedTypes('role', ['string']);
}
}

View File

@@ -159,7 +159,7 @@ class SingleTaskRepository extends EntityRepository
. '`setAuthorizationHelper`');
}
$role = new Role(TaskVoter::SHOW);
$role = TaskVoter::SHOW;
$qb->join('st.person', 'p');
$centers = $this->authorizationHelper

View File

@@ -14,8 +14,8 @@ namespace Chill\TaskBundle\Security\Authorization;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\TaskBundle\Entity\AbstractTask;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Contracts\EventDispatcher\Event;
class AuthorizationEvent extends Event
{

View File

@@ -168,7 +168,7 @@ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
foreach (
$this->authorizationHelper->getReachableCenters(
$this->security->getUser(),
new Role(ActivityVoter::SEE_DETAILS)
ActivityVoter::SEE_DETAILS
) as $center
) {
if (false === in_array($center, $centers, true)) {
@@ -181,7 +181,7 @@ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
// we loop over circles
$circles = $this->authorizationHelper->getReachableCircles(
$this->security->getUser(),
new Role(ActivityVoter::SEE_DETAILS),
ActivityVoter::SEE_DETAILS,
$center
);
$circleIds = [];
@@ -234,7 +234,7 @@ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
// we loop over circles
$circles = $this->authorizationHelper->getReachableCircles(
$this->security->getUser(),
new Role(ActivityVoter::SEE_DETAILS),
ActivityVoter::SEE_DETAILS,
$personArg->getCenter()
);

View File

@@ -3,12 +3,3 @@ services:
resource: '../../Form/'
autowire: true
autoconfigure: true
Chill\TaskBundle\Form\SingleTaskListType:
arguments:
$em: '@Doctrine\ORM\EntityManagerInterface'
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
$taskWorkflowManager: '@Chill\TaskBundle\Workflow\TaskWorkflowManager'
tags:
- { name: form.type }