Merge remote-tracking branch 'origin/master' into track-address-reference-update

This commit is contained in:
2023-04-12 09:45:19 +02:00
273 changed files with 8869 additions and 3001 deletions

View File

@@ -34,6 +34,29 @@ abstract class AbstractCRUDController extends AbstractController
*/
protected array $crudConfig = [];
/**
* get the role given from the config.
*
* @param mixed $entity
* @param mixed $_format
*/
protected function getRoleFor(string $action, Request $request, $entity, $_format): string
{
$actionConfig = $this->getActionConfig($action);
if (null !== $actionConfig['roles'][$request->getMethod()]) {
return $actionConfig['roles'][$request->getMethod()];
}
if ($this->crudConfig['base_role']) {
return $this->crudConfig['base_role'];
}
throw new \RuntimeException(sprintf('the config does not have any role for the ' .
'method %s nor a global role for the whole action. Add those to your ' .
'configuration or override the required method', $request->getMethod()));
}
public static function getSubscribedServices(): array
{
return array_merge(

View File

@@ -280,11 +280,13 @@ class ApiController extends AbstractCRUDController
switch ($request->getMethod()) {
case Request::METHOD_DELETE:
// oups... how to use property accessor to remove element ?
/* @phpstan-ignore-next-line as we do not find a simpler way to do this */
$entity->{'remove' . ucfirst($property)}($postedData);
break;
case Request::METHOD_POST:
/* @phpstan-ignore-next-line as we do not find a simpler way to do this */
$entity->{'add' . ucfirst($property)}($postedData);
break;
@@ -499,28 +501,6 @@ class ApiController extends AbstractCRUDController
return ['groups' => ['read']];
}
/**
* get the role given from the config.
*
* @param mixed $entity
* @param mixed $_format
*/
protected function getRoleFor(string $action, Request $request, $entity, $_format): string
{
$actionConfig = $this->getActionConfig($action);
if (null !== $actionConfig['roles'][$request->getMethod()]) {
return $actionConfig['roles'][$request->getMethod()];
}
if ($this->crudConfig['base_role']) {
return $this->crudConfig['base_role'];
}
throw new RuntimeException(sprintf('the config does not have any role for the ' .
'method %s nor a global role for the whole action. Add those to your ' .
'configuration or override the required method', $request->getMethod()));
}
protected function getSerializer(): SerializerInterface
{

View File

@@ -101,7 +101,7 @@ class CRUDRoutesLoader extends Loader
$singleCollection = $action['single_collection'] ?? '_entity' === $name ? 'single' : null;
if ('collection' === $singleCollection) {
// continue;
// continue;
}
// compute default action

View File

@@ -1,5 +1,14 @@
<?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\MainBundle\Controller;
use Chill\MainBundle\Entity\Address;

View File

@@ -1,5 +1,14 @@
<?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\MainBundle\Controller;
use Chill\MainBundle\Entity\Address;

View File

@@ -30,6 +30,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\TransitionBlocker;
@@ -48,11 +49,13 @@ class WorkflowController extends AbstractController
private Registry $registry;
private Security $security;
private TranslatorInterface $translator;
private ValidatorInterface $validator;
public function __construct(EntityWorkflowManager $entityWorkflowManager, EntityWorkflowRepository $entityWorkflowRepository, ValidatorInterface $validator, PaginatorFactory $paginatorFactory, Registry $registry, EntityManagerInterface $entityManager, TranslatorInterface $translator)
public function __construct(EntityWorkflowManager $entityWorkflowManager, EntityWorkflowRepository $entityWorkflowRepository, ValidatorInterface $validator, PaginatorFactory $paginatorFactory, Registry $registry, EntityManagerInterface $entityManager, TranslatorInterface $translator, Security $security)
{
$this->entityWorkflowManager = $entityWorkflowManager;
$this->entityWorkflowRepository = $entityWorkflowRepository;
@@ -61,6 +64,7 @@ class WorkflowController extends AbstractController
$this->registry = $registry;
$this->entityManager = $entityManager;
$this->translator = $translator;
$this->security = $security;
}
/**
@@ -291,10 +295,18 @@ class WorkflowController extends AbstractController
if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) {
// possible transition
$usersInvolved = $entityWorkflow->getUsersInvolved();
$currentUserFound = array_search($this->security->getUser(), $usersInvolved, true);
if (false !== $currentUserFound) {
unset($usersInvolved[$currentUserFound]);
}
$transitionForm = $this->createForm(
WorkflowStepType::class,
$entityWorkflow->getCurrentStep(),
['transition' => true, 'entity_workflow' => $entityWorkflow]
['transition' => true, 'entity_workflow' => $entityWorkflow, 'suggested_users' => $usersInvolved]
);
$transitionForm->handleRequest($request);

View File

@@ -50,8 +50,8 @@ class LoadLanguages extends AbstractFixture implements ContainerAwareInterface,
foreach (Intl::getLanguageBundle()->getLanguageNames() as $code => $language) {
if (
!in_array($code, $this->regionalVersionToInclude, true)
&& !in_array($code, $this->ancientToExclude, true)
!in_array($code, $this->regionalVersionToInclude, true)
&& !in_array($code, $this->ancientToExclude, true)
) {
$lang = (new Language())
->setId($code)

View File

@@ -274,7 +274,7 @@ class Configuration implements ConfigurationInterface
->end()
->end() // end of root/children
->end() // end of root
;
;
$rootNode->children()
->arrayNode('add_address')->addDefaultsIfNotSet()->children()

View File

@@ -211,7 +211,7 @@ trait AddWidgetConfigurationTrait
*
* @throws InvalidConfigurationException if a service's tag does not have the "alias" key
*
* @return type
* @return array
*/
protected function getWidgetAliasesbyPlace($place, ContainerBuilder $containerBuilder)
{

View File

@@ -523,12 +523,12 @@ class Address implements TrackCreationInterface, TrackUpdateInterface
/**
* Update the ref status
*
<<<<<<< HEAD
* <<<<<<< HEAD
* @param Address::ADDR_REFERENCE_STATUS_* $refStatus
* @param bool|null $updateLastUpdate Also update the "refStatusLastUpdate"
=======
* =======
* The refstatuslast update is also updated
>>>>>>> 31152616d (Feature: Provide api endpoint for reviewing addresses)
* >>>>>>> 31152616d (Feature: Provide api endpoint for reviewing addresses)
*/
public function setRefStatus(string $refStatus, ?bool $updateLastUpdate = true): self
{

View File

@@ -43,36 +43,20 @@ class Country
private ?int $id = null;
/**
* @var string
* @var array<string, string>
*
* @ORM\Column(type="json")
* @groups({"read", "docgen:read"})
* @Context({"is-translatable": true}, groups={"docgen:read"})
*/
private $name;
private array $name = [];
/**
* @return string
*/
public function __toString()
{
return $this->getName();
}
/**
* @return the string
*/
public function getCountryCode()
public function getCountryCode(): string
{
return $this->countryCode;
}
/**
* Get id.
*
* @return int
*/
public function getId()
public function getId(): ?int
{
return $this->id;
}
@@ -80,31 +64,23 @@ class Country
/**
* Get name.
*
* @return string
*/
public function getName()
public function getName(): array
{
return $this->name;
}
/**
* @param string $countryCode
*/
public function setCountryCode($countryCode)
public function setCountryCode(?string $countryCode): self
{
$this->countryCode = $countryCode;
$this->countryCode = (string) $countryCode;
return $this;
}
/**
* Set name.
*
* @param string $name
*
* @return Country
* @param array<string, string> $name
*/
public function setName($name)
public function setName(array $name): self
{
$this->name = $name;

View File

@@ -51,10 +51,7 @@ class CommentEmbeddable
return $this->date;
}
/**
* @return interger $userId
*/
public function getUserId()
public function getUserId(): ?int
{
return $this->userId;
}

View File

@@ -17,6 +17,7 @@ use DateTimeImmutable;
use DateTimeInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
@@ -133,7 +134,7 @@ class NotificationComment implements TrackCreationInterface, TrackUpdateInterfac
/**
* @ORM\PrePersist
*/
public function onPrePersist(LifecycleEventArgs $eventArgs): void
public function onPrePersist(PrePersistEventArgs $eventArgs): void
{
$this->recentlyPersisted = true;
}

View File

@@ -348,6 +348,23 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
return $this->transitionningStep;
}
/**
* @return User[]
*/
public function getUsersInvolved(): array
{
$usersInvolved = [];
$usersInvolved[spl_object_hash($this->getCreatedBy())] = $this->getCreatedBy();
foreach ($this->steps as $step) {
foreach ($step->getDestUser() as $u) {
$usersInvolved[spl_object_hash($u)] = $u;
}
}
return $usersInvolved;
}
public function getWorkflowName(): string
{
return $this->workflowName;

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Export;
use Doctrine\ORM\NativeQuery;
use Doctrine\ORM\QueryBuilder;
/**
@@ -23,6 +24,7 @@ use Doctrine\ORM\QueryBuilder;
* aggregation, use `ListInterface`.
*
* @example Chill\PersonBundle\Export\CountPerson an example of implementation
* @template Q of QueryBuilder|NativeQuery
*/
interface ExportInterface extends ExportElementInterface
{
@@ -84,7 +86,7 @@ interface ExportInterface extends ExportElementInterface
* @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR')
* @param mixed $data The data from the export's form (as defined in `buildForm`)
*
* @return pure-callable(null|string|int|float|'_header' $value):string|int|\DateTimeInterface where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }`
* @return callable(null|string|int|float|'_header' $value): string|int|\DateTimeInterface where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }`
*/
public function getLabels($key, array $values, $data);
@@ -102,7 +104,7 @@ interface ExportInterface extends ExportElementInterface
/**
* Return the results of the query builder.
*
* @param \Doctrine\ORM\NativeQuery|QueryBuilder $query
* @param Q $query
* @param mixed[] $data the data from the export's fomr (added by self::buildForm)
*
* @return mixed[] an array of results
@@ -132,7 +134,7 @@ interface ExportInterface extends ExportElementInterface
* @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )`
* @param array $data the data from the form, if any
*
* @return \Doctrine\ORM\NativeQuery|QueryBuilder the query to execute.
* @return Q the query to execute.
*/
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []);

View File

@@ -43,11 +43,13 @@ class AddressDataMapper implements DataMapperInterface
/** @var FormInterface $form */
switch ($key) {
case 'streetAddress1':
/** @phpstan-ignore-next-line */
$form->setData($address->getStreetAddress1());
break;
case 'streetAddress2':
/** @phpstan-ignore-next-line */
$form->setData($address->getStreetAddress2());
break;
@@ -110,11 +112,13 @@ class AddressDataMapper implements DataMapperInterface
return;
}
/** @phpstan-ignore-next-line */
$address->setStreetAddress1($form->getData());
break;
case 'streetAddress2':
/** @phpstan-ignore-next-line */
$address->setStreetAddress2($form->getData());
break;

View File

@@ -24,15 +24,7 @@ class ExportPickCenterDataMapper implements DataMapperInterface
{
protected RegroupmentRepository $regroupmentRepository;
/**
* @param array|Center[] $data
* @param $forms
*
* @throws Exception
*
* @return void
*/
public function mapDataToForms($data, $forms)
public function mapDataToForms($data, $forms): void
{
if (null === $data) {
return;
@@ -44,7 +36,9 @@ class ExportPickCenterDataMapper implements DataMapperInterface
$pickedRegroupment = [];
foreach ($this->regroupmentRepository->findAll() as $regroupment) {
[$contained, $notContained] = $regroupment->getCenters()->partition(static function (Center $center) {
/** @phpstan-ignore-next-line */
[$contained, $notContained] = $regroupment->getCenters()->partition(static function (Center $center): bool {
return false;
});
if (0 === count($notContained)) {
@@ -56,13 +50,7 @@ class ExportPickCenterDataMapper implements DataMapperInterface
$form['centers']->setData($data);
}
/**
* @param iterable $forms
* @param array $data
*
* @return void
*/
public function mapFormsToData($forms, &$data)
public function mapFormsToData($forms, &$data): void
{
/** @var array<string, FormInterface> $forms */
$forms = iterator_to_array($forms);
@@ -74,8 +62,8 @@ class ExportPickCenterDataMapper implements DataMapperInterface
}
if (array_key_exists('regroupment', $forms)) {
/** @var Regroupment $regroupment */
foreach ($forms['regroupment']->getData() as $regroupment) {
/** @var Regroupment $regroupment */
foreach ($regroupment->getCenters() as $center) {
$centers[spl_object_hash($center)] = $center;
}

View File

@@ -42,7 +42,9 @@ class IdToEntityDataTransformer implements DataTransformerInterface
{
$this->repository = $repository;
$this->multiple = $multiple;
$this->getId = $getId ?? static function (object $o) { return $o->getId(); };
$this->getId = $getId ?? static function (object $o) {
return $o->getId();
};
}
/**

View File

@@ -56,12 +56,7 @@ class CommentType extends AbstractType
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars = array_replace(
$view->vars,
[
'fullWidth' => true,
]
);
$view->vars['fullWidth'] = true;
}
public function configureOptions(OptionsResolver $resolver)

View File

@@ -31,8 +31,6 @@ class MultipleObjectsToIdTransformer implements DataTransformerInterface
* Transforms a string (id) to an object (item).
*
* @param mixed $array
*
* @return ArrayCollection
*/
public function reverseTransform($array)
{
@@ -53,10 +51,8 @@ class MultipleObjectsToIdTransformer implements DataTransformerInterface
* Transforms an object (use) to a string (id).
*
* @param array $array
*
* @return ArrayCollection
*/
public function transform($array)
public function transform($array): array
{
$ret = [];

View File

@@ -19,6 +19,7 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\SerializerInterface;
/**
@@ -28,12 +29,15 @@ class PickUserDynamicType extends AbstractType
{
private DenormalizerInterface $denormalizer;
private NormalizerInterface $normalizer;
private SerializerInterface $serializer;
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer)
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, NormalizerInterface $normalizer)
{
$this->denormalizer = $denormalizer;
$this->serializer = $serializer;
$this->normalizer = $normalizer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -46,6 +50,11 @@ class PickUserDynamicType extends AbstractType
$view->vars['multiple'] = $options['multiple'];
$view->vars['types'] = ['user'];
$view->vars['uniqid'] = uniqid('pick_user_dyn');
$view->vars['suggested'] = [];
foreach ($options['suggested'] as $user) {
$view->vars['suggested'][] = $this->normalizer->normalize($user, 'json', ['groups' => 'read']);
}
}
public function configureOptions(OptionsResolver $resolver)
@@ -53,7 +62,8 @@ class PickUserDynamicType extends AbstractType
$resolver
->setDefault('multiple', false)
->setAllowedTypes('multiple', ['bool'])
->setDefault('compound', false);
->setDefault('compound', false)
->setDefault('suggested', []);
}
public function getBlockPrefix()

View File

@@ -39,7 +39,7 @@ class PrivateCommentType extends AbstractType
$builder
->add('comments', ChillTextareaType::class, [
'disable_editor' => $options['disable_editor'],
'label' => false,
'label' => $options['label'],
])
->setDataMapper($this->dataMapper);
}

View File

@@ -95,12 +95,7 @@ class ScopePickerType extends AbstractType
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars = array_replace(
$view->vars,
[
'fullWidth' => true,
]
);
$view->vars['fullWidth'] = true;
}
public function configureOptions(OptionsResolver $resolver)

View File

@@ -137,8 +137,8 @@ class WorkflowStepType extends AbstractType
$meta = $workflow->getMetadataStore()->getPlaceMetadata($to);
if (
!array_key_exists('isFinal', $meta) || false === $meta['isFinal']
) {
!array_key_exists('isFinal', $meta) || false === $meta['isFinal']
) {
$toFinal = false;
}
}
@@ -154,6 +154,7 @@ class WorkflowStepType extends AbstractType
'label' => 'workflow.dest for next steps',
'multiple' => true,
'mapped' => false,
'suggested' => $options['suggested_users'],
])
->add('future_dest_emails', ChillCollectionType::class, [
'label' => 'workflow.dest by email',
@@ -200,6 +201,7 @@ class WorkflowStepType extends AbstractType
->setAllowedTypes('transition', 'bool')
->setRequired('entity_workflow')
->setAllowedTypes('entity_workflow', EntityWorkflow::class)
->setDefault('suggested_users', [])
->setDefault('constraints', [
new Callback(
function ($step, ExecutionContextInterface $context, $payload) {

View File

@@ -17,6 +17,8 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\NotificationRepository;
use Chill\MainBundle\Templating\UI\NotificationCounterInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PostPersistEventArgs;
use Doctrine\ORM\Event\PostUpdateEventArgs;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Security\Core\User\UserInterface;
@@ -68,7 +70,7 @@ final class NotificationByUserCounter implements NotificationCounterInterface
return 'chill_main_notif_unread_by_' . $user->getId();
}
public function onEditNotificationComment(NotificationComment $notificationComment, LifecycleEventArgs $eventArgs): void
public function onEditNotificationComment(NotificationComment $notificationComment, PostPersistEventArgs $eventArgs): void
{
$this->resetCacheForNotification($notificationComment->getNotification());
}

View File

@@ -14,6 +14,8 @@ namespace Chill\MainBundle\Notification\Email;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\NotificationComment;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PostPersistEventArgs;
use Doctrine\ORM\Event\PostUpdateEventArgs;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
@@ -36,7 +38,7 @@ class NotificationMailer
$this->translator = $translator;
}
public function postPersistComment(NotificationComment $comment, LifecycleEventArgs $eventArgs): void
public function postPersistComment(NotificationComment $comment, PostPersistEventArgs $eventArgs): void
{
foreach (
array_merge(
@@ -72,13 +74,13 @@ class NotificationMailer
/**
* Send a email after a notification is persisted.
*/
public function postPersistNotification(Notification $notification, LifecycleEventArgs $eventArgs): void
public function postPersistNotification(Notification $notification, PostPersistEventArgs $eventArgs): void
{
$this->sendNotificationEmailsToAddresses($notification);
$this->sendNotificationEmailsToAddressesEmails($notification);
}
public function postUpdateNotification(Notification $notification, LifecycleEventArgs $eventArgs): void
public function postUpdateNotification(Notification $notification, PostUpdateEventArgs $eventArgs): void
{
$this->sendNotificationEmailsToAddressesEmails($notification);
}

View File

@@ -21,37 +21,36 @@ class Page implements PageInterface
/**
* the number of item per page.
*
* @var int
*/
protected $itemPerPage;
protected int $itemPerPage;
/**
* the number of the current page.
*
* @var int
*/
protected $number;
protected int $number;
/**
* The route for the current page.
*
* @var string
*/
protected $route;
protected string $route;
/**
* Parameters for the route to the current page.
*
* @var array
*/
protected $routeParameters;
protected array $routeParameters;
/**
* The number of items in the whole iteration.
*
* @var int
*/
protected $totalItems;
protected int $totalItems;
/**
* @var UrlGeneratorInterface
@@ -59,12 +58,12 @@ class Page implements PageInterface
protected $urlGenerator;
public function __construct(
$number,
$itemPerPage,
int $number,
int $itemPerPage,
UrlGeneratorInterface $urlGenerator,
$route,
string $route,
array $routeParameters,
$totalItems
int $totalItems
) {
$this->urlGenerator = $urlGenerator;
$this->number = $number;
@@ -74,24 +73,24 @@ class Page implements PageInterface
$this->totalItems = $totalItems;
}
public function generateUrl()
public function generateUrl(): string
{
return $this->urlGenerator->generate($this->route, $this->routeParameters);
}
public function getFirstItemNumber()
public function getFirstItemNumber(): int
{
return ($this->number - 1) * $this->itemPerPage;
}
public function getLastItemNumber()
public function getLastItemNumber(): int
{
$last = $this->number * $this->itemPerPage - 1;
return $last < $this->totalItems ? $last : $this->totalItems;
}
public function getNumber()
public function getNumber(): int
{
return $this->number;
}

View File

@@ -27,27 +27,27 @@ class PageGenerator implements Iterator
$this->paginator = $paginator;
}
public function current()
public function current(): Page
{
return $this->paginator->getPage($current);
return $this->paginator->getPage($this->current);
}
public function key()
public function key(): int
{
return $this->current;
}
public function next()
public function next(): void
{
++$this->current;
}
public function rewind()
public function rewind(): void
{
$this->current = 1;
}
public function valid()
public function valid(): bool
{
return 0 < $this->current
&& $this->paginator->countPages() >= $this->current;

View File

@@ -26,21 +26,21 @@ class Paginator implements PaginatorInterface
*
* @var int
*/
protected $currentPageNumber;
protected int $currentPageNumber;
/**
* the number of items on a single page.
*
* @var int
*/
protected $itemPerPage;
protected int $itemPerPage;
/**
* the key in the GET parameter to indicate the number of item per page.
*
* @var string
*/
protected $itemPerPageKey;
protected string $itemPerPageKey;
/**
* the key in the GET parameter to indicate the page number in
@@ -48,45 +48,45 @@ class Paginator implements PaginatorInterface
*
* @var string
*/
protected $pageKey;
protected string $pageKey;
/**
* the route of the pages.
*
* @var string
*/
protected $route;
protected string $route;
/**
* the parameters of the route.
*
* @var string[]
*/
protected $routeParameters;
protected array $routeParameters;
/**
* The number of total items.
*
* @var int
*/
protected $totalItems;
protected int $totalItems;
/**
* the generator for url.
*
* @var UrlGeneratorInterface
*/
protected $urlGenerator;
protected UrlGeneratorInterface $urlGenerator;
public function __construct(
$totalItems,
$itemPerPage,
$currentPageNumber,
$route,
int $totalItems,
int $itemPerPage,
int $currentPageNumber,
string $route,
array $routeParameters,
UrlGeneratorInterface $urlGenerator,
$pageKey,
$itemPerPageKey
string $pageKey,
string $itemPerPageKey
) {
$this->totalItems = $totalItems;
$this->itemPerPage = $itemPerPage;
@@ -98,12 +98,12 @@ class Paginator implements PaginatorInterface
$this->itemPerPageKey = $itemPerPageKey;
}
public function count()
public function count(): int
{
return $this->countPages();
}
public function countPages()
public function countPages(): int
{
if (0 === $this->itemPerPage) {
return 1;
@@ -122,20 +122,17 @@ class Paginator implements PaginatorInterface
return 0 === $nb ? 1 : (int) $nb;
}
/**
* @return \Chill\MainBundle\Pagination\Page
*/
public function getCurrentPage()
public function getCurrentPage(): Page
{
return $this->getPage($this->currentPageNumber);
}
public function getCurrentPageFirstItemNumber()
public function getCurrentPageFirstItemNumber(): int
{
return $this->getCurrentPage()->getFirstItemNumber();
}
public function getItemsPerPage()
public function getItemsPerPage(): int
{
return $this->itemPerPage;
}
@@ -145,7 +142,7 @@ class Paginator implements PaginatorInterface
*
* @return \Chill\MainBundle\Pagination\Page
*/
public function getNextPage()
public function getNextPage(): Page
{
if (!$this->hasNextPage()) {
throw new RuntimeException('this page has no next page');
@@ -155,11 +152,10 @@ class Paginator implements PaginatorInterface
}
/**
* @param type $number
*
* @return \Chill\MainBundle\Pagination\Page
*/
public function getPage($number)
public function getPage(int $number): Page
{
if (!$this->hasPage($number)) {
throw new RuntimeException("The page with number {$number} does not "
@@ -179,7 +175,7 @@ class Paginator implements PaginatorInterface
);
}
public function getPagesGenerator()
public function getPagesGenerator(): iterable
{
for ($i = 1; $this->countPages() >= $i; ++$i) {
yield $this->getPage($i);
@@ -191,7 +187,7 @@ class Paginator implements PaginatorInterface
*
* @return \Chill\MainBundle\Pagination\Page
*/
public function getPreviousPage()
public function getPreviousPage(): PageInterface
{
if (!$this->hasPreviousPage()) {
throw new RuntimeException('this page has no previous page');
@@ -200,7 +196,7 @@ class Paginator implements PaginatorInterface
return $this->getPage($this->currentPageNumber - 1);
}
public function getTotalItems()
public function getTotalItems(): int
{
return $this->totalItems;
}
@@ -208,12 +204,12 @@ class Paginator implements PaginatorInterface
/**
* @return bool
*/
public function hasNextPage()
public function hasNextPage(): bool
{
return $this->hasPage($this->currentPageNumber + 1);
}
public function hasPage($number)
public function hasPage($number): bool
{
if (0 === $this->totalItems) {
return 1 === $number;
@@ -226,18 +222,18 @@ class Paginator implements PaginatorInterface
/**
* @return bool
*/
public function hasPreviousPage()
public function hasPreviousPage(): bool
{
return $this->hasPage($this->currentPageNumber - 1);
}
public function isCurrentPage(PageInterface $page)
public function isCurrentPage(PageInterface $page): bool
{
return $page->getNumber() === $this->currentPageNumber;
}
public function setItemsPerPage($itemPerPage)
public function setItemsPerPage(int $itemsPerPage)
{
$this->itemPerPage = $itemPerPage;
$this->itemPerPage = $itemsPerPage;
}
}

View File

@@ -124,12 +124,12 @@ class PaginatorFactory
return array_merge(
$this->router->getContext()->getParameters(),
// get the route parameters
$this->requestStack
->getCurrentRequest()
->attributes->get('_route_params'),
$this->requestStack
->getCurrentRequest()
->attributes->get('_route_params'),
// get the query parameters
$this->requestStack
->getCurrentRequest()->query->all()
$this->requestStack
->getCurrentRequest()->query->all()
);
}
}

View File

@@ -32,26 +32,26 @@ interface PaginatorInterface extends Countable
*
* @return int
*/
public function countPages();
public function countPages(): int;
/**
* get the current page.
*
* @return PageInterface
*/
public function getCurrentPage();
public function getCurrentPage(): PageInterface;
/**
* get the first result for the current page.
*
* @return int
*/
public function getCurrentPageFirstItemNumber();
public function getCurrentPageFirstItemNumber(): int;
/*
* get the number of items per page
*/
public function getItemsPerPage();
public function getItemsPerPage(): int;
/**
* get the next page.
@@ -60,7 +60,7 @@ interface PaginatorInterface extends Countable
*
* @return PageInterface
*/
public function getNextPage();
public function getNextPage(): PageInterface;
/**
* get page by his number.
@@ -69,14 +69,14 @@ interface PaginatorInterface extends Countable
*
* @throws RuntimeException if the pagination has no page with specified number
*/
public function getPage($number);
public function getPage(int $number): PageInterface;
/**
* get a generator to generate pages.
*
* @return Generator which return PageInterface elements
*/
public function getPagesGenerator();
public function getPagesGenerator(): iterable;
/**
* get the previous page.
@@ -85,35 +85,35 @@ interface PaginatorInterface extends Countable
*
* @return PageInterface
*/
public function getPreviousPage();
public function getPreviousPage(): PageInterface;
/**
* get the number of results for this paginator.
*
* @return int
*/
public function getTotalItems();
public function getTotalItems(): int;
/**
* check if the current page has a next page.
*
* @return bool
*/
public function hasNextPage();
public function hasNextPage(): bool;
/**
* check if the page with the given number exists.
*
* @param int $number
* @param mixed $number
*/
public function hasPage($number);
public function hasPage($number): bool;
/**
* check if the current page has a page before.
*
* @return bool
*/
public function hasPreviousPage();
public function hasPreviousPage(): bool;
/**
* check if the given page is the current page.
@@ -122,10 +122,10 @@ interface PaginatorInterface extends Countable
*
* @return bool
*/
public function isCurrentPage(PageInterface $page);
public function isCurrentPage(PageInterface $page): bool;
/*
* set the number of items per page
*/
public function setItemsPerPage($itemsPerPage);
public function setItemsPerPage(int $itemsPerPage);
}

View File

@@ -187,7 +187,7 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
}
// filter only number
$filtered = preg_replace('/[^0-9]/', '', $phonenumber);
$filtered = preg_replace('/[^0-9]/', '', (string) $phonenumber);
$item = $this->cachePool->getItem('pnum_' . $filtered);

View File

@@ -62,7 +62,7 @@ final class GeographicalUnitRepository implements GeographicalUnitRepositoryInte
->innerJoin(Address::class, 'address', Join::WITH, 'ST_CONTAINS(gu.geom, address.point) = TRUE')
->where($qb->expr()->eq('address', ':address'))
->setParameter('address', $address)
;
;
}
public function find($id): ?GeographicalUnit

View File

@@ -18,12 +18,13 @@ function loadDynamicPicker(element) {
isMultiple = parseInt(el.dataset.multiple) === 1,
uniqId = el.dataset.uniqid,
input = element.querySelector('[data-input-uniqid="'+ el.dataset.uniqid +'"]'),
// the "picked" will always be an array, even if multiple is false
picked = isMultiple ?
JSON.parse(input.value) : (
(input.value === '[]' || input.value === '') ?
null : [ JSON.parse(input.value) ]
)
;
suggested = JSON.parse(el.dataset.suggested)
if (!isMultiple) {
if (input.value === '[]'){
@@ -37,6 +38,7 @@ function loadDynamicPicker(element) {
':types="types" ' +
':picked="picked" ' +
':uniqid="uniqid" ' +
':suggested="notPickedSuggested" ' +
'@addNewEntity="addNewEntity" ' +
'@removeEntity="removeEntity"></pick-entity>',
components: {
@@ -48,16 +50,27 @@ function loadDynamicPicker(element) {
types: JSON.parse(el.dataset.types),
picked: picked === null ? [] : picked,
uniqid: el.dataset.uniqid,
suggested: suggested
}
},
computed: {
notPickedSuggested() {
const pickedIds = new Set();
for (const p of this.picked) {
pickedIds.add(`${p.type}${p.id}`);
}
return this.suggested.filter(e => !pickedIds.has(`${e.type}${e.id}`))
}
},
methods: {
addNewEntity(entity) {
addNewEntity({entity}) {
if (this.multiple) {
if (!this.picked.some(el => {
return el.type === entity.type && el.id === entity.id;
})) {
this.picked.push(entity);
input.value = JSON.stringify(this.picked);
console.log(entity)
}
} else {
if (!this.picked.some(el => {
@@ -69,9 +82,16 @@ function loadDynamicPicker(element) {
}
}
},
removeEntity(entity) {
removeEntity({entity}) {
if (-1 === this.suggested.findIndex(e => e.type === entity.type && e.id === entity.id)) {
this.suggested.push(entity);
}
this.picked = this.picked.filter(e => !(e.type === entity.type && e.id === entity.id));
input.value = JSON.stringify(this.picked);
if (this.multiple) {
input.value = JSON.stringify(this.picked);
} else {
input.value = "";
}
},
}
})

View File

@@ -17,6 +17,9 @@
</add-persons>
</li>
</ul>
<ul class="list-suggest add-items inline">
<li v-for="s in suggested" :key="s.id" @click="addNewSuggested(s)"><span>{{ s.text }}</span></li>
</ul>
</template>
<script>
@@ -49,6 +52,10 @@ export default {
// display picked entities.
type: Boolean,
default: true,
},
suggested: {
type: Array,
default: []
}
},
emits: ['addNewEntity', 'removeEntity'],
@@ -61,55 +68,58 @@ export default {
};
},
computed: {
addPersonsOptions() {
return {
uniq: !this.multiple,
type: this.types,
priority: null,
button: {
size: 'btn-sm',
class: 'btn-submit',
},
};
},
translatedListOfTypes() {
let trans = [];
this.types.forEach(t => {
if (this.$props.multiple) {
trans.push(appMessages.fr.pick_entity[t].toLowerCase());
} else {
trans.push(appMessages.fr.pick_entity[t + '_one'].toLowerCase());
}
})
addPersonsOptions() {
return {
uniq: !this.multiple,
type: this.types,
priority: null,
button: {
size: 'btn-sm',
class: 'btn-submit',
},
};
},
translatedListOfTypes() {
let trans = [];
this.types.forEach(t => {
if (this.$props.multiple) {
trans.push(appMessages.fr.pick_entity[t].toLowerCase());
} else {
trans.push(appMessages.fr.pick_entity[t + '_one'].toLowerCase());
}
})
if (this.$props.multiple) {
return appMessages.fr.pick_entity.modal_title + trans.join(', ');
} else {
return appMessages.fr.pick_entity.modal_title_one + trans.join(', ');
}
},
listClasses() {
return {
'list-suggest': true,
'remove-items': this.$props.removableIfSet,
};
},
if (this.$props.multiple) {
return appMessages.fr.pick_entity.modal_title + trans.join(', ');
} else {
return appMessages.fr.pick_entity.modal_title_one + trans.join(', ');
}
},
listClasses() {
return {
'list-suggest': true,
'remove-items': this.$props.removableIfSet,
};
},
},
methods: {
addNewEntity({ selected, modal }) {
selected.forEach((item) => {
this.$emit('addNewEntity', item.result);
}, this
);
this.$refs.addPersons.resetSearch(); // to cast child method
modal.showModal = false;
},
removeEntity(entity) {
if (!this.$props.removableIfSet) {
return;
}
this.$emit('removeEntity', entity);
}
addNewSuggested(entity) {
this.$emit('addNewEntity', {entity: entity});
},
addNewEntity({ selected, modal }) {
selected.forEach((item) => {
this.$emit('addNewEntity', { entity: item.result});
}, this
);
this.$refs.addPersons.resetSearch(); // to cast child method
modal.showModal = false;
},
removeEntity(entity) {
if (!this.$props.removableIfSet) {
return;
}
this.$emit('removeEntity',{ entity: entity });
}
},
}
</script>

View File

@@ -68,7 +68,8 @@
{{- form_errors(form) -}}
</div>
{% else %}
<div class="col-sm">
<div class="col-12 clear">{{- form_label(form) -}}</div>
<div class="col-sm-12">
{{- form_widget(form, widget_attr) -}}
{{- form_help(form) -}}
{{- form_errors(form) -}}

View File

@@ -18,43 +18,48 @@
{% block form_row %}
{% apply spaceless %}
{% if form.vars.fullWidth is not defined or form.vars.fullWidth == false %}
<div class="mb-2">
<div class="row">
<div class="{% apply spaceless %}
{% if attr.class is defined and ('cf-title' in attr.class or 'cf-fields' in attr.class ) %}
col-sm-12
{% elseif attr.class is defined and 'multiple-cf-inline' in attr.class %}
col-sm-2 col-md-4 clear
{% else %}
col-sm-4 clear
{% if form.vars.fullWidth is not defined or form.vars.fullWidth == false %}
<div class="{% apply spaceless %}
{% if attr.class is defined and ('cf-title' in attr.class or 'cf-fields' in attr.class ) %}
col-sm-12
{% elseif attr.class is defined and 'multiple-cf-inline' in attr.class %}
col-sm-2 col-md-4 clear
{% else %}
col-sm-4 clear
{% endif %}
{% endapply %}">
{% if attr.class is not defined or ('cf-title' not in attr.class and 'cf-fields' not in attr.class ) %}
{{ form_label(form) }}
{% endif %}
{% endapply %}">
{% if attr.class is not defined or ('cf-title' not in attr.class and 'cf-fields' not in attr.class ) %}
{{ form_label(form) }}
</div>
<div class="{% apply spaceless %}
{% if attr.class is defined and 'cf-title' in attr.class %}
col-sm-12
{% elseif attr.class is defined and 'cf-fields' in attr.class %}
col-sm-12 parent
{% elseif attr.class is defined and 'multiple-cf-inline' in attr.class %}
col-sm-2 col-md-8 multiple-cf-inline
{% else %}
col-sm-8
{% endif %}
{% endapply %}">
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
{% else %}
<div class="col-12 clear">{{ form_label(form) }}</div>
<div class="col-12">{{ form_widget(form) }}</div>
{% endif %}
</div>
<div class="{% apply spaceless %}
{% if attr.class is defined and 'cf-title' in attr.class %}
col-sm-12
{% elseif attr.class is defined and 'cf-fields' in attr.class %}
col-sm-12 parent
{% elseif attr.class is defined and 'multiple-cf-inline' in attr.class %}
col-sm-2 col-md-8 multiple-cf-inline
{% else %}
col-sm-8
{% endif %}
{% endapply %}">
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
</div>
</div>
{% else %}
{{ form_widget(form) }}
{% endif %}
{% endapply %}
{% endblock form_row %}
{#
The block 'form_row' above may be removed !
Read this note: https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/502#note_1311993084
#}
{% block choice_widget_expanded %}
{% apply spaceless %}
@@ -200,7 +205,6 @@
{% block private_comment_row %}
{{ form_label(form) }}
{{ form_row(form) }}
{% endblock %}
@@ -211,7 +215,6 @@
{% endblock %}
{% block comment_row %}
{{ form_label(form) }}
{{ form_row(form) }}
{% endblock %}
@@ -249,7 +252,11 @@
{% block pick_entity_dynamic_widget %}
<input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value|escape('html_attr') }}" {% endif %} data-input-uniqid="{{ form.vars['uniqid'] }}"/>
<div data-module="pick-dynamic" data-types="{{ form.vars['types']|json_encode }}" data-multiple="{{ form.vars['multiple'] }}" data-uniqid="{{ form.vars['uniqid'] }}"></div>
<div data-module="pick-dynamic"
data-types="{{ form.vars['types']|json_encode }}"
data-multiple="{{ form.vars['multiple'] }}"
data-uniqid="{{ form.vars['uniqid'] }}"
data-suggested="{{ form.vars['suggested']|json_encode|escape('html_attr') }}"></div>
{% endblock %}
{% block pick_postal_code_widget %}
@@ -269,4 +276,4 @@
{{ form_errors(form.fixedDate) }}
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -66,6 +66,7 @@
{{ form_row(transition_form.future_dest_users) }}
{{ form_row(transition_form.future_dest_emails) }}
{{ form_errors(transition_form.future_dest_users) }}
</div>
<p>{{ form_label(transition_form.comment) }}</p>

View File

@@ -13,8 +13,18 @@ namespace Chill\MainBundle\Routing;
use Knp\Menu\MenuItem;
/**
* Implements a builder for menu
*
* @template TParams of array
*/
interface LocalMenuBuilderInterface
{
/**
* @param $menuId
* @param MenuItem $menu
* @param TParams $parameters
*/
public function buildMenu($menuId, MenuItem $menu, array $parameters);
/**

View File

@@ -11,8 +11,8 @@ declare(strict_types=1);
namespace Chill\MainBundle\Search;
use Chill\MainBundle\Pagination\Paginator;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
@@ -30,7 +30,7 @@ class SearchApi
private PaginatorFactory $paginator;
private iterable $providers = [];
private iterable $providers;
public function __construct(
EntityManagerInterface $em,
@@ -42,9 +42,6 @@ class SearchApi
$this->paginator = $paginator;
}
/**
* @return Model/Result[]
*/
public function getResults(string $pattern, array $types, array $parameters): Collection
{
$queries = $this->findQueries($pattern, $types, $parameters);
@@ -53,10 +50,10 @@ class SearchApi
throw new SearchApiNoQueryException($pattern, $types, $parameters);
}
$total = $this->countItems($queries, $types, $parameters);
$total = $this->countItems($queries);
$paginator = $this->paginator->create($total);
$rawResults = $this->fetchRawResult($queries, $types, $parameters, $paginator);
$rawResults = $this->fetchRawResult($queries, $types, $paginator);
$this->prepareProviders($rawResults);
$results = $this->buildResults($rawResults);
@@ -64,7 +61,7 @@ class SearchApi
return new Collection($results, $paginator);
}
private function buildCountQuery(array $queries, $types, $parameters)
private function buildCountQuery(array $queries): array
{
$query = 'SELECT SUM(c) AS count FROM ({union_unordered}) AS sq';
$unions = [];
@@ -88,7 +85,7 @@ class SearchApi
$items = [];
foreach ($rawResults as $r) {
foreach ($this->providers as $k => $p) {
foreach ($this->providers as $p) {
if ($p->supportsResult($r['key'], $r['metadata'])) {
$items[] = (new SearchApiResult($r['pertinence']))
->setResult(
@@ -103,7 +100,7 @@ class SearchApi
return $items;
}
private function buildUnionQuery(array $queries, $types, $parameters, Paginator $paginator)
private function buildUnionQuery(array $queries, PaginatorInterface $paginator): array
{
$query = '{unions} ORDER BY pertinence DESC LIMIT ? OFFSET ?';
$unions = [];
@@ -126,9 +123,9 @@ class SearchApi
];
}
private function countItems($providers, $types, $parameters): int
private function countItems($providers): int
{
[$countQuery, $parameters] = $this->buildCountQuery($providers, $types, $parameters);
[$countQuery, $parameters] = $this->buildCountQuery($providers);
$rsmCount = new ResultSetMappingBuilder($this->em);
$rsmCount->addScalarResult('count', 'count');
$countNq = $this->em->createNativeQuery($countQuery, $rsmCount);
@@ -137,9 +134,9 @@ class SearchApi
return (int) $countNq->getSingleScalarResult();
}
private function fetchRawResult($queries, $types, $parameters, Paginator $paginator): array
private function fetchRawResult($queries, $types, PaginatorInterface $paginator): array
{
[$union, $parameters] = $this->buildUnionQuery($queries, $types, $parameters, $paginator);
[$union, $parameters] = $this->buildUnionQuery($queries, $paginator);
$rsm = new ResultSetMappingBuilder($this->em);
$rsm->addScalarResult('key', 'key', Types::STRING)
->addScalarResult('metadata', 'metadata', Types::JSON)
@@ -172,7 +169,7 @@ class SearchApi
);
}
private function prepareProviders(array $rawResults)
private function prepareProviders(array $rawResults): void
{
$metadatas = [];
$providers = [];

View File

@@ -16,6 +16,18 @@ use function count;
use function implode;
use function strtr;
/**
* This create a query optimized for searching for the api response.
*
* When build, this class generate a SQL string and a list of a parameters which is suitable for running
* a native SQL query. This have usually the form of
*
* `SELECT '<key>' as key, <metadata> as metadata, <pertinence> as pertinence FROM <from clause> WHERE <where clause>`.
*
* The clause between `<>` are provided through the dedicated method in this class (@link{self::setSelectKey},
* @link{self::setFromClause}), etc.).
*
*/
class SearchApiQuery
{
private ?string $fromClause = null;

View File

@@ -71,9 +71,9 @@ interface SearchInterface
* @param array $terms the string to search
* @param int $start the first result (for pagination)
* @param int $limit the number of result (for pagination)
* @param string $format The format for result
* @param "html"|"json" $format The format for result
*
* @return string, an HTML string
* @return string|array a string if format is html, an array if format is json
*/
public function renderResult(array $terms, $start = 0, $limit = 50, array $options = [], $format = 'html');

View File

@@ -26,28 +26,4 @@ use const E_USER_DEPRECATED;
*/
abstract class AbstractChillVoter extends Voter implements ChillVoterInterface
{
protected function supports($attribute, $subject)
{
@trigger_error(
'This voter should implements the new `supports` '
. 'methods introduced by Symfony 3.0, and do not rely on '
. 'getSupportedAttributes and getSupportedClasses methods.',
E_USER_DEPRECATED
);
// @TODO: getSupportedAttributes() should be created in here and made abstract or in ChillVoterInterface.
// @TODO: getSupportedClasses() should be created in here and made abstract or in ChillVoterInterface.
return in_array($attribute, $this->getSupportedAttributes($attribute), true)
&& in_array(get_class($subject), $this->getSupportedClasses(), true);
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
@trigger_error('This voter should implements the new `voteOnAttribute` '
. 'methods introduced by Symfony 3.0, and do not rely on '
. 'isGranted method', E_USER_DEPRECATED);
// @TODO: isGranted() should be created in here and made abstract or in ChillVoterInterface.
return $this->isGranted($attribute, $subject, $token->getUser());
}
}

View File

@@ -16,7 +16,7 @@ use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface;
use Chill\MainBundle\Security\ParentRoleHelper;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Role\Role;
@@ -34,7 +34,7 @@ use function get_class;
*/
class AuthorizationHelper implements AuthorizationHelperInterface
{
private CenterResolverDispatcherInterface $centerResolverDispatcher;
private CenterResolverManagerInterface $centerResolverManager;
private LoggerInterface $logger;
@@ -45,13 +45,13 @@ class AuthorizationHelper implements AuthorizationHelperInterface
private UserACLAwareRepositoryInterface $userACLAwareRepository;
public function __construct(
CenterResolverDispatcherInterface $centerResolverDispatcher,
CenterResolverManagerInterface $centerResolverManager,
LoggerInterface $logger,
ScopeResolverDispatcher $scopeResolverDispatcher,
UserACLAwareRepositoryInterface $userACLAwareRepository,
ParentRoleHelper $parentRoleHelper
) {
$this->centerResolverDispatcher = $centerResolverDispatcher;
$this->centerResolverManager = $centerResolverManager;
$this->logger = $logger;
$this->scopeResolverDispatcher = $scopeResolverDispatcher;
$this->userACLAwareRepository = $userACLAwareRepository;
@@ -63,7 +63,7 @@ class AuthorizationHelper implements AuthorizationHelperInterface
*
* @param User $user The user
* @param array $centers a list of centers which are going to be filtered
* @param Center|string $role
* @param mixed $role
*/
public function filterReachableCenters(User $user, array $centers, $role): array
{
@@ -113,13 +113,14 @@ class AuthorizationHelper implements AuthorizationHelperInterface
* Get reachable Centers for the given user, role,
* and optionally Scope.
*
* @return array|Center[]
* @return list<Center>
*/
public function getReachableCenters(UserInterface $user, string $role, ?Scope $scope = null): array
{
if ($role instanceof Role) {
$role = $role->getRole();
}
/** @var array<string, Center> $centers */
$centers = [];
foreach ($user->getGroupCenters() as $groupCenter) {
@@ -129,13 +130,13 @@ class AuthorizationHelper implements AuthorizationHelperInterface
//check that the role is in the reachable roles
if ($this->isRoleReached($role, $roleScope->getRole())) {
if (null === $scope) {
$centers[] = $groupCenter->getCenter();
$centers[spl_object_hash($groupCenter->getCenter())] = $groupCenter->getCenter();
break;
}
if ($scope->getId() === $roleScope->getScope()->getId()) {
$centers[] = $groupCenter->getCenter();
$centers[spl_object_hash($groupCenter->getCenter())] = $groupCenter->getCenter();
break;
}
@@ -143,7 +144,7 @@ class AuthorizationHelper implements AuthorizationHelperInterface
}
}
return $centers;
return array_values($centers);
}
/**
@@ -194,12 +195,8 @@ class AuthorizationHelper implements AuthorizationHelperInterface
*
* @return array|Scope[]
*/
public function getReachableScopes(UserInterface $user, string $role, $center): array
public function getReachableScopes(UserInterface $user, string $role, Center|array $center): array
{
if ($role instanceof Role) {
$role = $role->getRole();
}
return $this->getReachableCircles($user, $role, $center);
}
@@ -252,27 +249,15 @@ class AuthorizationHelper implements AuthorizationHelperInterface
*/
public function userHasAccess(User $user, $entity, $attribute)
{
$center = $this->centerResolverDispatcher->resolveCenter($entity);
$centers = $this->centerResolverManager->resolveCenters($entity);
if (is_iterable($center)) {
foreach ($center as $c) {
if ($this->userHasAccessForCenter($user, $c, $entity, $attribute)) {
return true;
}
foreach ($centers as $c) {
if ($this->userHasAccessForCenter($user, $c, $entity, $attribute)) {
return true;
}
return false;
}
if ($center instanceof Center) {
return $this->userHasAccessForCenter($user, $center, $entity, $attribute);
}
if (null === $center) {
return false;
}
throw new UnexpectedValueException('could not resolver a center');
return false;
}
/**

View File

@@ -21,12 +21,12 @@ interface AuthorizationHelperInterface
* Get reachable Centers for the given user, role,
* and optionnaly Scope.
*
* @return Center[]
* @return list<Center>
*/
public function getReachableCenters(UserInterface $user, string $role, ?Scope $scope = null): array;
/**
* @param array|Center|Center[] $center
* @param Center|list<Center> $center
*/
public function getReachableScopes(UserInterface $user, string $role, $center): array;
public function getReachableScopes(UserInterface $user, string $role, Center|array $center): array;
}

View File

@@ -27,7 +27,7 @@ class Counter implements JsonSerializable
return $this->counter;
}
public function jsonSerialize()
public function jsonSerialize(): array
{
return ['count' => $this->counter];
}

View File

@@ -98,8 +98,11 @@ class AddressNormalizer implements ContextAwareNormalizerInterface, NormalizerAw
$data['validFrom'] = $address->getValidFrom();
$data['validTo'] = $address->getValidTo();
$data['refStatus'] = $address->getRefStatus();
$data['point'] = $this->normalizer->normalize($address->getPoint(), $format,
[AbstractNormalizer::GROUPS => ['read']]);
$data['point'] = $this->normalizer->normalize(
$address->getPoint(),
$format,
[AbstractNormalizer::GROUPS => ['read']]
);
$data['isNoAddress'] = $address->isNoAddress();
} elseif ('docgen' === $format) {
$dateContext = array_merge($context, ['docgen:expects' => DateTimeInterface::class]);

View File

@@ -1,5 +1,14 @@
<?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\MainBundle\Service\Import;
use Chill\MainBundle\Entity\Address;
@@ -18,7 +27,7 @@ final class AddressToReferenceMatcher
private const LOG_PREFIX = '[address_to_reference_matcher] ';
private const SQL_MARK_TO_REVIEW_ADDRESS_UNMATCHING = <<<SQL
private const SQL_MARK_TO_REVIEW_ADDRESS_UNMATCHING = <<<'SQL'
UPDATE chill_main_address a SET refstatus = '{{ to_review }}', refstatuslastupdate = NOW()
FROM chill_main_address_reference ar
WHERE
@@ -38,7 +47,7 @@ final class AddressToReferenceMatcher
)
SQL;
private const SQL_MARK_MATCHING_ADDRESSES_REVIEWED_OR_TO_REVIEW = <<<SQL
private const SQL_MARK_MATCHING_ADDRESSES_REVIEWED_OR_TO_REVIEW = <<<'SQL'
UPDATE chill_main_address a SET refstatus = '{{ matching }}', refstatuslastupdate = NOW()
FROM chill_main_address_reference ar
WHERE

View File

@@ -90,7 +90,7 @@ class ChillTwigHelper extends AbstractExtension
return $twig->render($t, array_merge([
'value' => $value,
'message' => $message ?? 'No value',
'message' => $message,
], $options));
}
}

View File

@@ -11,15 +11,10 @@ declare(strict_types=1);
namespace Chill\MainBundle\Templating\Entity;
/**
* @deprecated load @link{BoxUtilsChillEntityRenderTrait} in the render
*/
abstract class AbstractChillEntityRender implements ChillEntityRenderInterface
{
protected function getDefaultClosingBox(): string
{
return '</section>';
}
protected function getDefaultOpeningBox($classSuffix): string
{
return '<section class="chill-entity entity-' . $classSuffix . '">';
}
use BoxUtilsChillEntityRenderTrait;
}

View File

@@ -20,6 +20,9 @@ use Symfony\Component\Templating\EngineInterface;
use function array_merge;
use function strtr;
/**
* @implements ChillEntityRenderInterface<Address>
*/
class AddressRender implements ChillEntityRenderInterface
{
public const DEFAULT_OPTIONS = [
@@ -39,16 +42,12 @@ class AddressRender implements ChillEntityRenderInterface
public function __construct(
EngineInterface $templating,
TranslatableStringHelperInterface $translatableStringHelper)
{
TranslatableStringHelperInterface $translatableStringHelper
) {
$this->templating = $templating;
$this->translatableStringHelper = $translatableStringHelper;
}
/**
* @param Address addr
* @param mixed $addr
*/
public function renderBox($addr, array $options): string
{
$options = array_merge(self::DEFAULT_OPTIONS, $options);
@@ -70,8 +69,8 @@ class AddressRender implements ChillEntityRenderInterface
{
$lines = [];
if (null !== $addr->getPostCode()) {
if ($addr->getPostCode()->getCountry()->getCountryCode() === 'FR') {
if (null !== $addr->getPostcode()) {
if ($addr->getPostcode()->getCountry()->getCountryCode() === 'FR') {
$lines[] = $this->renderIntraBuildingLine($addr);
$lines[] = $this->renderBuildingLine($addr);
$lines[] = $this->renderStreetLine($addr);
@@ -118,10 +117,8 @@ class AddressRender implements ChillEntityRenderInterface
$res = trim($street . ', ' . $streetNumber, ', ');
if (null !== $addr->getPostCode()->getCountry()->getCountryCode()) {
if ($addr->getPostCode()->getCountry()->getCountryCode() === 'FR') {
$res = trim($streetNumber . ', ' . $street, ', ');
}
if ($addr->getPostcode()->getCountry()->getCountryCode() === 'FR') {
$res = trim($streetNumber . ', ' . $street, ', ');
}
if ((',' === $res) || ('' === $res)) {
@@ -131,10 +128,6 @@ class AddressRender implements ChillEntityRenderInterface
return $res;
}
/**
* @param Address addr
* @param mixed $addr
*/
public function renderString($addr, array $options): string
{
return implode(' — ', $this->renderLines($addr));
@@ -164,10 +157,8 @@ class AddressRender implements ChillEntityRenderInterface
$res = null;
}
if (null !== $addr->getPostCode()->getCountry()->getCountryCode()) {
if ($addr->getPostCode()->getCountry()->getCountryCode() === 'FR') {
$res = $addr->getBuildingName();
}
if ($addr->getPostcode()->getCountry()->getCountryCode() === 'FR') {
$res = $addr->getBuildingName();
}
return $res;

View File

@@ -0,0 +1,25 @@
<?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\MainBundle\Templating\Entity;
trait BoxUtilsChillEntityRenderTrait
{
protected function getDefaultClosingBox(): string
{
return '</section>';
}
protected function getDefaultOpeningBox($classSuffix): string
{
return '<section class="chill-entity entity-' . $classSuffix . '">';
}
}

View File

@@ -14,8 +14,10 @@ namespace Chill\MainBundle\Templating\Entity;
/**
* Render an entity using `__toString()`.
*/
class ChillEntityRender extends AbstractChillEntityRender
class ChillEntityRender implements ChillEntityRenderInterface
{
use BoxUtilsChillEntityRenderTrait;
public function renderBox($entity, array $options): string
{
return $this->getDefaultOpeningBox('default') . $entity

View File

@@ -14,6 +14,8 @@ namespace Chill\MainBundle\Templating\Entity;
/**
* Interface to implement which will render an entity in template on a custom
* manner.
*
* @template T
*/
interface ChillEntityRenderInterface
{
@@ -29,7 +31,7 @@ interface ChillEntityRenderInterface
* </span>
* ```
*
* @param type $entity
* @param T $entity
*/
public function renderBox($entity, array $options): string;
@@ -38,14 +40,12 @@ interface ChillEntityRenderInterface
*
* Example: returning the name of a person.
*
* @param object $entity
* @param T $entity
*/
public function renderString($entity, array $options): string;
/**
* Return true if the class support this object for the given options.
*
* @param type $entity
*/
public function supports($entity, array $options): bool;
public function supports(object $entity, array $options): bool;
}

View File

@@ -13,33 +13,32 @@ namespace Chill\MainBundle\Templating\Entity;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Symfony\Component\Templating\EngineInterface;
use function array_merge;
class CommentRender extends AbstractChillEntityRender
/**
* @implements ChillEntityRenderInterface<CommentEmbeddable>
*/
class CommentRender implements ChillEntityRenderInterface
{
use BoxUtilsChillEntityRenderTrait;
/**
* @var EngineInterface
*/
private $engine;
/**
* @var \Chill\MainBundle\Repository\UserRepository
*/
private $userRepository;
private UserRepositoryInterface $userRepository;
public function __construct(
UserRepository $userRepository,
UserRepositoryInterface $userRepository,
EngineInterface $engine
) {
$this->userRepository = $userRepository;
$this->engine = $engine;
}
/**
* @param CommentEmbeddable $entity
*/
public function renderBox($entity, array $options): string
{
// default options
@@ -50,7 +49,7 @@ class CommentRender extends AbstractChillEntityRender
'metadata' => true,
], $options);
if ($entity->getUserId()) {
if (null !== $entity->getUserId()) {
$user = $this->userRepository->find($entity->getUserId());
}
@@ -67,9 +66,6 @@ class CommentRender extends AbstractChillEntityRender
);
}
/**
* @param CommentEmbeddable $entity
*/
public function renderString($entity, array $options): string
{
return $entity->getComment();

View File

@@ -19,6 +19,9 @@ use Symfony\Component\Templating\EngineInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_merge;
/**
* @implements ChillEntityRenderInterface<User>
*/
class UserRender implements ChillEntityRenderInterface
{
public const DEFAULT_OPTIONS = [
@@ -50,9 +53,6 @@ class UserRender implements ChillEntityRenderInterface
]);
}
/**
* @param User $entity
*/
public function renderString($entity, array $options): string
{
$opts = array_merge(self::DEFAULT_OPTIONS, $options);

View File

@@ -70,23 +70,23 @@ class DelegatedBlockRenderingEvent extends Event implements ArrayAccess
return $this->content;
}
public function offsetExists($offset)
public function offsetExists($offset): bool
{
return isset($this->context[$offset]);
}
public function offsetGet($offset)
public function offsetGet($offset): mixed
{
return $this->context[$offset];
}
public function offsetSet($offset, $value)
public function offsetSet($offset, $value): void
{
throw new RuntimeException('The event context is read-only, you are not '
. 'allowed to update it.');
}
public function offsetUnset($offset)
public function offsetUnset($offset): void
{
throw new RuntimeException('The event context is read-only, you are not '
. 'allowed to update it.');

View File

@@ -1,5 +1,14 @@
<?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 Controller;
use Chill\MainBundle\Doctrine\Model\Point;
@@ -10,6 +19,10 @@ use Chill\MainBundle\Test\PrepareClientTrait;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* @internal
* @coversNothing
*/
class AddressToReferenceMatcherControllerTest extends WebTestCase
{
use PrepareClientTrait;

View File

@@ -1,5 +1,14 @@
<?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\MainBundle\Tests\Controller;
use Chill\MainBundle\Entity\Address;
@@ -7,6 +16,10 @@ use Chill\MainBundle\Test\PrepareClientTrait;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* @internal
* @coversNothing
*/
class GeographicalUnitByAddressApiControllerTest extends WebTestCase
{
use PrepareClientTrait;

View File

@@ -267,15 +267,15 @@ final class ExportManagerTest extends KernelTestCase
)
->willReturn(static function ($value) {
switch ($value) {
case 0:
case 1:
return $value;
case 0:
case 1:
return $value;
case '_header':
return 'export';
case '_header':
return 'export';
default: throw new RuntimeException(sprintf('The value %s is not valid', $value));
}
default: throw new RuntimeException(sprintf('The value %s is not valid', $value));
}
});
$export->getQueryKeys(Argument::Type('array'))->willReturn(['export']);
@@ -307,15 +307,15 @@ final class ExportManagerTest extends KernelTestCase
)
->willReturn(static function ($value) {
switch ($value) {
case '_header': return 'foo_header';
case '_header': return 'foo_header';
case 'cat a': return 'label cat a';
case 'cat a': return 'label cat a';
case 'cat b': return 'label cat b';
case 'cat b': return 'label cat b';
default:
throw new RuntimeException(sprintf('This value (%s) is not valid', $value));
}
default:
throw new RuntimeException(sprintf('This value (%s) is not valid', $value));
}
});
$aggregator->addRole()->willReturn(null);
//$aggregator->addRole()->shouldBeCalled();

View File

@@ -77,13 +77,13 @@ final class IdToEntityDataTransformerTest extends TestCase
public function testTransformMulti()
{
$o1 = new class() {
$o1 = new class () {
public function getId()
{
return 1;
}
};
$o2 = new class() {
$o2 = new class () {
public function getId()
{
return 2;
@@ -104,7 +104,7 @@ final class IdToEntityDataTransformerTest extends TestCase
public function testTransformSingle()
{
$o = new class() {
$o = new class () {
public function getId()
{
return 1;

View File

@@ -33,7 +33,7 @@ final class DefaultScopeResolverTest extends TestCase
public function testHasScopeInterface()
{
$scope = new Scope();
$entity = new class($scope) implements HasScopeInterface {
$entity = new class ($scope) implements HasScopeInterface {
public function __construct(Scope $scope)
{
$this->scope = $scope;
@@ -52,7 +52,7 @@ final class DefaultScopeResolverTest extends TestCase
public function testHasScopesInterface()
{
$entity = new class($scopeA = new Scope(), $scopeB = new Scope()) implements HasScopesInterface {
$entity = new class ($scopeA = new Scope(), $scopeB = new Scope()) implements HasScopesInterface {
public function __construct(Scope $scopeA, Scope $scopeB)
{
$this->scopes = [$scopeA, $scopeB];

View File

@@ -34,7 +34,7 @@ final class ScopeResolverDispatcherTest extends TestCase
public function testHasScopeInterface()
{
$scope = new Scope();
$entity = new class($scope) implements HasScopeInterface {
$entity = new class ($scope) implements HasScopeInterface {
public function __construct(Scope $scope)
{
$this->scope = $scope;
@@ -52,7 +52,7 @@ final class ScopeResolverDispatcherTest extends TestCase
public function testHasScopesInterface()
{
$entity = new class($scopeA = new Scope(), $scopeB = new Scope()) implements HasScopesInterface {
$entity = new class ($scopeA = new Scope(), $scopeB = new Scope()) implements HasScopesInterface {
public function __construct(Scope $scopeA, Scope $scopeB)
{
$this->scopes = [$scopeA, $scopeB];

View File

@@ -1,5 +1,14 @@
<?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 Services\Import;
use Chill\MainBundle\Doctrine\Model\Point;
@@ -53,7 +62,7 @@ class AddressToReferenceMatcherTest extends KernelTestCase
->setCode('78910')
->setRefPostalCodeId($refPostalCodeId = '78910'.uniqid())
->setCountry($belgium)
;
;
$this->entityManager->persist($postalCode);
$this->entityManager->flush();

View File

@@ -63,18 +63,18 @@ class RoleScopeScopePresence extends ConstraintValidator
//if the role scope should have a scope
if (
!in_array($value->getRole(), $this->roleProvider->getRolesWithoutScopes(), true)
&& $value->getScope() === null
) {
!in_array($value->getRole(), $this->roleProvider->getRolesWithoutScopes(), true)
&& $value->getScope() === null
) {
$this->context->buildViolation($constraint->messagePresenceRequired)
->setParameter('%role%', $this->translator->trans($value->getRole()))
->addViolation();
$this->logger->debug('the role scope should have a scope, but scope is null. Violation build.');
} elseif // if the scope should be null
(
in_array($value->getRole(), $this->roleProvider->getRolesWithoutScopes(), true)
&& null !== $value->getScope()
) {
(
in_array($value->getRole(), $this->roleProvider->getRolesWithoutScopes(), true)
&& null !== $value->getScope()
) {
$this->context->buildViolation($constraint->messageNullRequired)
->setParameter('%role%', $this->translator->trans($value->getRole()))
->addViolation();

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Workflow;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
interface EntityWorkflowHandlerInterface
@@ -36,6 +37,11 @@ interface EntityWorkflowHandlerInterface
*/
public function getRoleShow(EntityWorkflow $entityWorkflow): ?string;
/**
* @return User[]
*/
public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array;
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string;
public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array;

View File

@@ -2,6 +2,13 @@
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\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;

View File

@@ -2,6 +2,13 @@
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\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;

View File

@@ -2,6 +2,13 @@
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\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;

View File

@@ -2,6 +2,13 @@
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\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;

View File

@@ -45,3 +45,12 @@ workflow:
few {# workflows}
other {# workflows}
}
duration:
minute: >-
{m, plural,
=0 {Aucune durée}
one {# minute}
few {# minutes}
other {# minutes}
}

View File

@@ -1,8 +1,8 @@
"This program is free software: you can redistribute it and/or modify it under the terms of the <strong>GNU Affero General Public License</strong>": "Ce programme est un logiciel libre: vous pouvez le redistribuer et/ou le modifier selon les termes de la licence <strong>GNU Affero GPL</strong>"
User manual: Manuel d'utilisation
Search: Rechercher
"Search persons, ...": "Recherche des personnes, ..."
Person name: Nom / Prénom de la personne
"Search persons, ...": "Recherche des usagers, ..."
Person name: Nom / Prénom de l'usager
Login: Connexion
Logout: Se déconnecter
Bad credentials.: Le mot de passe et le nom d'utilisateur ne correspondent pas.