mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2026-03-13 09:27:45 +00:00
Merge branch 'refs/heads/master' into ticket-app-master
# Conflicts: # composer.json # config/bundles.php # config/packages/doctrine_migrations_chill.yaml # package.json # src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUserGroup.php # src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php # src/Bundle/ChillMainBundle/Entity/UserGroup.php # src/Bundle/ChillMainBundle/Resources/public/chill/js/date.ts # src/Bundle/ChillMainBundle/Resources/public/lib/download-report/download-report.js # src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/editor_config.ts # src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts # src/Bundle/ChillMainBundle/Resources/public/page/export/download-export.js # src/Bundle/ChillMainBundle/Resources/public/types.ts # src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig # src/Bundle/ChillMainBundle/Templating/Entity/UserGroupRender.php # src/Bundle/ChillMainBundle/chill.api.specs.yaml # src/Bundle/ChillMainBundle/chill.webpack.config.js # src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue # src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue # src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue # src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue # src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue # src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue # src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue # src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/MemberDetails.vue # src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue # src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue # src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue # src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue # src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts # tests/app/config/bootstrap.php
This commit is contained in:
@@ -63,7 +63,6 @@ abstract class AbstractCRUDController extends AbstractController
|
||||
parent::getSubscribedServices(),
|
||||
[
|
||||
'chill_main.paginator_factory' => PaginatorFactory::class,
|
||||
ManagerRegistry::class => ManagerRegistry::class,
|
||||
'translator' => TranslatorInterface::class,
|
||||
AuthorizationHelper::class => AuthorizationHelper::class,
|
||||
EventDispatcherInterface::class => EventDispatcherInterface::class,
|
||||
@@ -213,7 +212,7 @@ abstract class AbstractCRUDController extends AbstractController
|
||||
|
||||
protected function getManagerRegistry(): ManagerRegistry
|
||||
{
|
||||
return $this->container->get(ManagerRegistry::class);
|
||||
return $this->container->get('doctrine');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,7 +225,7 @@ abstract class AbstractCRUDController extends AbstractController
|
||||
|
||||
protected function getValidator(): ValidatorInterface
|
||||
{
|
||||
return $this->get('validator');
|
||||
return $this->container->get('validator');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -580,7 +580,7 @@ class CRUDController extends AbstractController
|
||||
string $action,
|
||||
mixed $entity,
|
||||
Request $request,
|
||||
array $defaultTemplateParameters = []
|
||||
array $defaultTemplateParameters = [],
|
||||
) {
|
||||
return $defaultTemplateParameters;
|
||||
}
|
||||
@@ -685,7 +685,7 @@ class CRUDController extends AbstractController
|
||||
Request $request,
|
||||
int $totalItems,
|
||||
PaginatorInterface $paginator,
|
||||
?FilterOrderHelper $filterOrder = null
|
||||
?FilterOrderHelper $filterOrder = null,
|
||||
) {
|
||||
$query = $this->queryEntities($action, $request, $paginator, $filterOrder);
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ use Chill\MainBundle\DependencyInjection\CompilerPass\ExportsCompilerPass;
|
||||
use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass;
|
||||
use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass;
|
||||
use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass;
|
||||
use Chill\MainBundle\DependencyInjection\CompilerPass\ShortMessageCompilerPass;
|
||||
use Chill\MainBundle\DependencyInjection\CompilerPass\TimelineCompilerClass;
|
||||
use Chill\MainBundle\DependencyInjection\CompilerPass\WidgetsCompilerPass;
|
||||
use Chill\MainBundle\DependencyInjection\ConfigConsistencyCompilerPass;
|
||||
@@ -73,6 +72,5 @@ class ChillMainBundle extends Bundle
|
||||
$container->addCompilerPass(new MenuCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
|
||||
$container->addCompilerPass(new ACLFlagsCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
|
||||
$container->addCompilerPass(new CRUDControllerCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
|
||||
$container->addCompilerPass(new ShortMessageCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class ChillImportUsersCommand extends Command
|
||||
protected LoggerInterface $logger,
|
||||
protected \Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface $passwordEncoder,
|
||||
protected ValidatorInterface $validator,
|
||||
protected UserRepository $userRepository
|
||||
protected UserRepository $userRepository,
|
||||
) {
|
||||
parent::__construct('chill:main:import-users');
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class ChillUserSendRenewPasswordCodeCommand extends Command
|
||||
LoggerInterface $logger,
|
||||
EntityManagerInterface $em,
|
||||
RecoverPasswordHelper $recoverPasswordHelper,
|
||||
EventDispatcherInterface $eventDispatcher
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
) {
|
||||
$this->logger = $logger;
|
||||
$this->em = $em;
|
||||
|
||||
@@ -22,7 +22,7 @@ class ExecuteCronJobCommand extends Command
|
||||
protected static $defaultDescription = 'Execute the cronjob(s) given as argument, or one cronjob scheduled by system.';
|
||||
|
||||
public function __construct(
|
||||
private readonly CronManagerInterface $cronManager
|
||||
private readonly CronManagerInterface $cronManager,
|
||||
) {
|
||||
parent::__construct('chill:cron-job:execute');
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use Chill\MainBundle\Service\Import\PostalCodeBEFromBestAddress;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class LoadAddressesBEFromBestAddressCommand extends Command
|
||||
@@ -24,7 +25,7 @@ class LoadAddressesBEFromBestAddressCommand extends Command
|
||||
|
||||
public function __construct(
|
||||
private readonly AddressReferenceBEFromBestAddress $addressImporter,
|
||||
private readonly PostalCodeBEFromBestAddress $postalCodeBEFromBestAddressImporter
|
||||
private readonly PostalCodeBEFromBestAddress $postalCodeBEFromBestAddressImporter,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
@@ -34,14 +35,19 @@ class LoadAddressesBEFromBestAddressCommand extends Command
|
||||
$this
|
||||
->setName('chill:main:address-ref-from-best-addresses')
|
||||
->addArgument('lang', InputArgument::REQUIRED, "Language code, for example 'fr'")
|
||||
->addArgument('list', InputArgument::IS_ARRAY, "The list to add, for example 'full', or 'extract' (dev) or '1xxx' (brussel CP)");
|
||||
->addArgument('list', InputArgument::IS_ARRAY, "The list to add, for example 'full', or 'extract' (dev) or '1xxx' (brussel CP)")
|
||||
->addOption('send-report-email', 's', InputOption::VALUE_REQUIRED, 'Email address where a list of unimported addresses can be send');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->postalCodeBEFromBestAddressImporter->import();
|
||||
|
||||
$this->addressImporter->import($input->getArgument('lang'), $input->getArgument('list'));
|
||||
$this->addressImporter->import(
|
||||
$input->getArgument('lang'),
|
||||
$input->getArgument('list'),
|
||||
$input->hasOption('send-report-email') ? $input->getOption('send-report-email') : null
|
||||
);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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\Command;
|
||||
|
||||
use Chill\MainBundle\Service\Import\AddressReferenceFromBAN;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class LoadAddressesFRFromBANCommand extends Command
|
||||
{
|
||||
protected static $defaultDescription = 'Import FR addresses from BAN (see https://adresses.data.gouv.fr';
|
||||
|
||||
public function __construct(private readonly AddressReferenceFromBAN $addressReferenceFromBAN)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('chill:main:address-ref-from-ban')
|
||||
->addArgument('departementNo', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'a list of departement numbers')
|
||||
->addOption('send-report-email', 's', InputOption::VALUE_REQUIRED, 'Email address where a list of unimported addresses can be send');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
dump(__METHOD__);
|
||||
foreach ($input->getArgument('departementNo') as $departementNo) {
|
||||
$output->writeln('Import addresses for '.$departementNo);
|
||||
|
||||
$this->addressReferenceFromBAN->import($departementNo, $input->hasOption('send-report-email') ? $input->getOption('send-report-email') : null);
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ use Chill\MainBundle\Service\Import\AddressReferenceFromBano;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class LoadAddressesFRFromBANOCommand extends Command
|
||||
@@ -29,7 +30,8 @@ class LoadAddressesFRFromBANOCommand extends Command
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('chill:main:address-ref-from-bano')
|
||||
->addArgument('departementNo', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'a list of departement numbers');
|
||||
->addArgument('departementNo', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'a list of departement numbers')
|
||||
->addOption('send-report-email', 's', InputOption::VALUE_REQUIRED, 'Email address where a list of unimported addresses can be send');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
@@ -37,7 +39,7 @@ class LoadAddressesFRFromBANOCommand extends Command
|
||||
foreach ($input->getArgument('departementNo') as $departementNo) {
|
||||
$output->writeln('Import addresses for '.$departementNo);
|
||||
|
||||
$this->addressReferenceFromBano->import($departementNo);
|
||||
$this->addressReferenceFromBano->import($departementNo, $input->hasOption('send-report-email') ? $input->getOption('send-report-email') : null);
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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\Command;
|
||||
|
||||
use Chill\MainBundle\Service\Import\AddressReferenceLU;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class LoadAddressesLUFromBDAddressCommand extends Command
|
||||
{
|
||||
protected static $defaultDescription = 'Import LUX addresses from BD addresses (see https://data.public.lu/fr/datasets/adresses-georeferencees-bd-adresses/)';
|
||||
|
||||
public function __construct(
|
||||
private readonly AddressReferenceLU $addressImporter,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('chill:main:address-ref-lux')
|
||||
->addOption('send-report-email', 's', InputOption::VALUE_REQUIRED, 'Email address where a list of unimported addresses can be send');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->addressImporter->import(
|
||||
$input->hasOption('send-report-email') ? $input->getOption('send-report-email') : null,
|
||||
);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -632,7 +632,7 @@ class ExportController extends AbstractController
|
||||
}
|
||||
}
|
||||
|
||||
private function rebuildRawData(string $key): array
|
||||
private function rebuildRawData(?string $key): array
|
||||
{
|
||||
if (null === $key) {
|
||||
throw $this->createNotFoundException('key does not exists');
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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\CRUD\Controller\ApiController;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class GenderApiController extends ApiController
|
||||
{
|
||||
protected function customizeQuery(string $action, Request $request, $query): void
|
||||
{
|
||||
$query
|
||||
->andWhere(
|
||||
$query->expr()->eq('e.active', "'TRUE'")
|
||||
);
|
||||
}
|
||||
|
||||
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
|
||||
{
|
||||
return $query->addOrderBy('e.order', 'ASC');
|
||||
}
|
||||
}
|
||||
26
src/Bundle/ChillMainBundle/Controller/GenderController.php
Normal file
26
src/Bundle/ChillMainBundle/Controller/GenderController.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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\CRUD\Controller\CRUDController;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class GenderController extends CRUDController
|
||||
{
|
||||
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
|
||||
{
|
||||
$query->addOrderBy('e.order', 'ASC');
|
||||
|
||||
return parent::orderQuery($action, $query, $request, $paginator);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class NewsItemApiController
|
||||
public function __construct(
|
||||
private readonly NewsItemRepository $newsItemRepository,
|
||||
private readonly SerializerInterface $serializer,
|
||||
private readonly PaginatorFactory $paginatorFactory
|
||||
private readonly PaginatorFactory $paginatorFactory,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
||||
@@ -94,4 +94,38 @@ class NotificationApiController
|
||||
|
||||
return new JsonResponse(null, JsonResponse::HTTP_ACCEPTED, [], false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/mark/allread", name="chill_api_main_notification_mark_allread", methods={"POST"})
|
||||
*/
|
||||
public function markAllRead(): JsonResponse
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new \RuntimeException('Invalid user');
|
||||
}
|
||||
|
||||
$modifiedNotificationIds = $this->notificationRepository->markAllNotificationAsReadForUser($user);
|
||||
|
||||
return new JsonResponse($modifiedNotificationIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/mark/undoallread", name="chill_api_main_notification_mark_undoallread", methods={"POST"})
|
||||
*/
|
||||
public function undoAllRead(Request $request): JsonResponse
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new \RuntimeException('Invalid user');
|
||||
}
|
||||
|
||||
$ids = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
$touchedIds = $this->notificationRepository->markAllNotificationAsUnreadForUser($user, $ids);
|
||||
|
||||
return new JsonResponse($touchedIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ class NotificationController extends AbstractController
|
||||
#[Route(path: '/inbox', name: 'chill_main_notification_my')]
|
||||
public function inboxAction(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||
$currentUser = $this->security->getUser();
|
||||
|
||||
$notificationsNbr = $this->notificationRepository->countAllForAttendee($currentUser);
|
||||
@@ -177,8 +177,8 @@ class NotificationController extends AbstractController
|
||||
|
||||
$notifications = $this->notificationRepository->findAllForAttendee(
|
||||
$currentUser,
|
||||
$limit = $paginator->getItemsPerPage(),
|
||||
$offset = $paginator->getCurrentPage()->getFirstItemNumber()
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPage()->getFirstItemNumber()
|
||||
);
|
||||
|
||||
return $this->render('@ChillMain/Notification/list.html.twig', [
|
||||
@@ -309,6 +309,7 @@ class NotificationController extends AbstractController
|
||||
$templateData[] = [
|
||||
'template' => $this->notificationHandlerManager->getTemplate($notification),
|
||||
'template_data' => $this->notificationHandlerManager->getTemplateData($notification),
|
||||
'handler' => $this->notificationHandlerManager->getHandler($notification),
|
||||
'notification' => $notification,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ final class PasswordController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Symfony\Component\Form\Form
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
private function passwordForm(User $user)
|
||||
{
|
||||
|
||||
@@ -61,8 +61,6 @@ final class PermissionsGroupController extends AbstractController
|
||||
$form = $this->createAddRoleScopeForm($permissionsGroup);
|
||||
$form->handleRequest($request);
|
||||
|
||||
dump($form->isSubmitted());
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$roleScope = $this->getPersistentRoleScopeBy(
|
||||
$form['composed_role_scope']->getData()->getRole(),
|
||||
@@ -153,7 +151,7 @@ final class PermissionsGroupController extends AbstractController
|
||||
/**
|
||||
* remove an association between permissionsGroup and roleScope.
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/permissionsgroup/{pgid}/delete_link_role_scope/{rsid}', name: 'admin_permissionsgroup_delete_role_scope', methods: ['DELETE'])]
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/permissionsgroup/{pgid}/delete_link_role_scope/{rsid}', name: 'admin_permissionsgroup_delete_role_scope', methods: ['POST'])]
|
||||
public function deleteLinkRoleScopeAction(int $pgid, int $rsid): Response
|
||||
{
|
||||
$permissionsGroup = $this->permissionsGroupRepository->find($pgid);
|
||||
@@ -445,7 +443,7 @@ final class PermissionsGroupController extends AbstractController
|
||||
*/
|
||||
private function createDeleteRoleScopeForm(
|
||||
PermissionsGroup $permissionsGroup,
|
||||
RoleScope $roleScope
|
||||
RoleScope $roleScope,
|
||||
): FormInterface {
|
||||
return $this->createFormBuilder()
|
||||
->setAction($this->generateUrl(
|
||||
|
||||
@@ -27,7 +27,7 @@ class ScopeController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry
|
||||
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
||||
@@ -96,7 +96,7 @@ class SearchController extends AbstractController
|
||||
return $this->render('@ChillMain/Search/choose_list.html.twig');
|
||||
}
|
||||
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/search.{_format}', name: 'chill_main_search', requirements: ['_format' => 'html|json'], defaults: ['_format' => 'html'])]
|
||||
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/search.{_format}', name: 'chill_main_search', requirements: ['_format' => 'html|json', '_locale' => '[a-z]{1,3}'], defaults: ['_format' => 'html'])]
|
||||
public function searchAction(Request $request, mixed $_format)
|
||||
{
|
||||
$pattern = trim((string) $request->query->get('q', ''));
|
||||
|
||||
@@ -20,7 +20,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
class UIController extends AbstractController
|
||||
{
|
||||
public function showNotificationUserCounterAction(
|
||||
CountNotificationUser $counter
|
||||
CountNotificationUser $counter,
|
||||
) {
|
||||
$nb = $counter->getSumNotification($this->getUser());
|
||||
|
||||
|
||||
@@ -12,7 +12,9 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\GroupCenter;
|
||||
use Chill\MainBundle\Entity\PermissionsGroup;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Form\Type\ComposedGroupCenterType;
|
||||
use Chill\MainBundle\Form\UserCurrentLocationType;
|
||||
@@ -46,7 +48,7 @@ class UserController extends CRUDController
|
||||
protected ParameterBagInterface $parameterBag,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly ChillSecurity $security,
|
||||
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry
|
||||
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
|
||||
) {}
|
||||
|
||||
#[Route(path: '/{_locale}/admin/main/user/{uid}/add_link_groupcenter', name: 'admin_user_add_groupcenter')]
|
||||
@@ -64,10 +66,14 @@ class UserController extends CRUDController
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$groupCenter = $this->getPersistedGroupCenter(
|
||||
$form[self::FORM_GROUP_CENTER_COMPOSED]->getData()
|
||||
);
|
||||
$user->addGroupCenter($groupCenter);
|
||||
|
||||
$formData = $form[self::FORM_GROUP_CENTER_COMPOSED]->getData();
|
||||
$selectedCenters = $formData['center'];
|
||||
|
||||
foreach ($selectedCenters as $center) {
|
||||
$groupCenter = $this->getPersistedGroupCenter($center, $formData['permissionsgroup']);
|
||||
$user->addGroupCenter($groupCenter);
|
||||
}
|
||||
|
||||
if (0 === $this->validator->validate($user)->count()) {
|
||||
$em->flush();
|
||||
@@ -214,7 +220,7 @@ class UserController extends CRUDController
|
||||
|
||||
return $this->redirect(
|
||||
$request->query->has('returnPath') ? $request->query->get('returnPath') :
|
||||
$this->generateUrl('chill_main_homepage')
|
||||
$this->generateUrl('chill_main_homepage')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -249,7 +255,7 @@ class UserController extends CRUDController
|
||||
|
||||
return $this->redirect(
|
||||
$request->query->has('returnPath') ? $request->query->get('returnPath') :
|
||||
$this->generateUrl('chill_crud_admin_user_edit', ['id' => $user->getId()])
|
||||
$this->generateUrl('chill_crud_admin_user_edit', ['id' => $user->getId()])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -264,6 +270,7 @@ class UserController extends CRUDController
|
||||
return $this->getFilterOrderHelperFactory()
|
||||
->create(self::class)
|
||||
->addSearchBox(['label'])
|
||||
->addCheckbox('activeFilter', [true => 'Active', false => 'Inactive'], ['Active'])
|
||||
->build();
|
||||
}
|
||||
|
||||
@@ -273,11 +280,7 @@ class UserController extends CRUDController
|
||||
return parent::countEntities($action, $request, $filterOrder);
|
||||
}
|
||||
|
||||
if (null === $filterOrder->getQueryString()) {
|
||||
return parent::countEntities($action, $request, $filterOrder);
|
||||
}
|
||||
|
||||
return $this->userRepository->countByUsernameOrEmail($filterOrder->getQueryString());
|
||||
return $this->userRepository->countFilteredUsers($filterOrder->getQueryString(), $filterOrder->getCheckboxData('activeFilter'));
|
||||
}
|
||||
|
||||
protected function createFormFor(string $action, $entity, ?string $formClass = null, array $formOptions = []): FormInterface
|
||||
@@ -324,7 +327,7 @@ class UserController extends CRUDController
|
||||
Request $request,
|
||||
int $totalItems,
|
||||
PaginatorInterface $paginator,
|
||||
?FilterOrderHelper $filterOrder = null
|
||||
?FilterOrderHelper $filterOrder = null,
|
||||
) {
|
||||
if (0 === $totalItems) {
|
||||
return [];
|
||||
@@ -334,16 +337,13 @@ class UserController extends CRUDController
|
||||
return parent::getQueryResult($action, $request, $totalItems, $paginator, $filterOrder);
|
||||
}
|
||||
|
||||
if (null === $filterOrder->getQueryString()) {
|
||||
return parent::getQueryResult($action, $request, $totalItems, $paginator, $filterOrder);
|
||||
}
|
||||
$queryString = $filterOrder->getQueryString();
|
||||
$activeFilter = $filterOrder->getCheckboxData('activeFilter');
|
||||
$nb = $this->userRepository->countFilteredUsers($queryString, $activeFilter);
|
||||
|
||||
return $this->userRepository->findByUsernameOrEmail(
|
||||
$filterOrder->getQueryString(),
|
||||
['usernameCanonical' => 'ASC'],
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPageFirstItemNumber()
|
||||
);
|
||||
$paginator = $this->getPaginatorFactory()->create($nb);
|
||||
|
||||
return $this->userRepository->findFilteredUsers($queryString, $activeFilter, $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage());
|
||||
}
|
||||
|
||||
protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request)
|
||||
@@ -374,10 +374,12 @@ class UserController extends CRUDController
|
||||
$returnPathParams = $request->query->has('returnPath') ? ['returnPath' => $request->query->get('returnPath')] : [];
|
||||
|
||||
return $this->createFormBuilder()
|
||||
->setAction($this->generateUrl(
|
||||
'admin_user_add_groupcenter',
|
||||
array_merge($returnPathParams, ['uid' => $user->getId()])
|
||||
))
|
||||
->setAction(
|
||||
$this->generateUrl(
|
||||
'admin_user_add_groupcenter',
|
||||
array_merge($returnPathParams, ['uid' => $user->getId()])
|
||||
)
|
||||
)
|
||||
->setMethod('POST')
|
||||
->add(self::FORM_GROUP_CENTER_COMPOSED, ComposedGroupCenterType::class)
|
||||
->add('submit', SubmitType::class, ['label' => 'Add a new groupCenter'])
|
||||
@@ -392,10 +394,12 @@ class UserController extends CRUDController
|
||||
$returnPathParams = $request->query->has('returnPath') ? ['returnPath' => $request->query->get('returnPath')] : [];
|
||||
|
||||
return $this->createFormBuilder()
|
||||
->setAction($this->generateUrl(
|
||||
'admin_user_delete_groupcenter',
|
||||
array_merge($returnPathParams, ['uid' => $user->getId(), 'gcid' => $groupCenter->getId()])
|
||||
))
|
||||
->setAction(
|
||||
$this->generateUrl(
|
||||
'admin_user_delete_groupcenter',
|
||||
array_merge($returnPathParams, ['uid' => $user->getId(), 'gcid' => $groupCenter->getId()])
|
||||
)
|
||||
)
|
||||
->setMethod('DELETE')
|
||||
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
||||
->getForm();
|
||||
@@ -421,17 +425,21 @@ class UserController extends CRUDController
|
||||
}
|
||||
}
|
||||
|
||||
private function getPersistedGroupCenter(GroupCenter $groupCenter)
|
||||
private function getPersistedGroupCenter(Center $center, PermissionsGroup $permissionsGroup)
|
||||
{
|
||||
$em = $this->managerRegistry->getManager();
|
||||
|
||||
$groupCenterManaged = $em->getRepository(GroupCenter::class)
|
||||
->findOneBy([
|
||||
'center' => $groupCenter->getCenter(),
|
||||
'permissionsGroup' => $groupCenter->getPermissionsGroup(),
|
||||
'center' => $center,
|
||||
'permissionsGroup' => $permissionsGroup,
|
||||
]);
|
||||
|
||||
if (!$groupCenterManaged) {
|
||||
$groupCenter = new GroupCenter();
|
||||
$groupCenter->setCenter($center);
|
||||
$groupCenter->setPermissionsGroup($permissionsGroup);
|
||||
|
||||
$em->persist($groupCenter);
|
||||
|
||||
return $groupCenter;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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\CRUD\Controller\CRUDController;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class UserGroupAdminController extends CRUDController
|
||||
{
|
||||
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
|
||||
{
|
||||
$query->addSelect('JSON_EXTRACT(e.label, :lang) AS HIDDEN labeli18n')
|
||||
->setParameter('lang', $request->getLocale());
|
||||
$query->addOrderBy('labeli18n', 'ASC');
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
177
src/Bundle/ChillMainBundle/Controller/UserGroupController.php
Normal file
177
src/Bundle/ChillMainBundle/Controller/UserGroupController.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<?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\User;
|
||||
use Chill\MainBundle\Entity\UserGroup;
|
||||
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
|
||||
use Chill\MainBundle\Repository\UserGroupRepositoryInterface;
|
||||
use Chill\MainBundle\Routing\ChillUrlGeneratorInterface;
|
||||
use Chill\MainBundle\Security\Authorization\UserGroupVoter;
|
||||
use Chill\MainBundle\Templating\Entity\ChillEntityRenderManagerInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* Controller to see and manage user groups.
|
||||
*/
|
||||
final readonly class UserGroupController
|
||||
{
|
||||
public function __construct(
|
||||
private UserGroupRepositoryInterface $userGroupRepository,
|
||||
private Security $security,
|
||||
private PaginatorFactoryInterface $paginatorFactory,
|
||||
private Environment $twig,
|
||||
private FormFactoryInterface $formFactory,
|
||||
private ChillUrlGeneratorInterface $chillUrlGenerator,
|
||||
private EntityManagerInterface $objectManager,
|
||||
private ChillEntityRenderManagerInterface $chillEntityRenderManager,
|
||||
) {}
|
||||
|
||||
#[Route('/{_locale}/main/user-groups/my', name: 'chill_main_user_groups_my')]
|
||||
public function myUserGroups(): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$nb = $this->userGroupRepository->countByUser($user);
|
||||
$paginator = $this->paginatorFactory->create($nb);
|
||||
|
||||
$groups = $this->userGroupRepository->findByUser($user, true, $paginator->getItemsPerPage(), $paginator->getCurrentPageFirstItemNumber());
|
||||
$forms = new \SplObjectStorage();
|
||||
|
||||
foreach ($groups as $group) {
|
||||
$forms->attach($group, $this->createFormAppendUserForGroup($group)?->createView());
|
||||
}
|
||||
|
||||
return new Response($this->twig->render('@ChillMain/UserGroup/my_user_groups.html.twig', [
|
||||
'groups' => $groups,
|
||||
'paginator' => $paginator,
|
||||
'forms' => $forms,
|
||||
]));
|
||||
}
|
||||
|
||||
#[Route('/{_locale}/main/user-groups/{id}/append', name: 'chill_main_user_groups_append_users')]
|
||||
public function appendUsersToGroup(UserGroup $userGroup, Request $request, Session $session): Response
|
||||
{
|
||||
if (!$this->security->isGranted(UserGroupVoter::APPEND_TO_GROUP, $userGroup)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$form = $this->createFormAppendUserForGroup($userGroup);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
foreach ($form['users']->getData() as $user) {
|
||||
$userGroup->addUser($user);
|
||||
|
||||
$session->getFlashBag()->add(
|
||||
'success',
|
||||
new TranslatableMessage(
|
||||
'user_group.user_added',
|
||||
[
|
||||
'user_group' => $this->chillEntityRenderManager->renderString($userGroup, []),
|
||||
'user' => $this->chillEntityRenderManager->renderString($user, []),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->objectManager->flush();
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->chillUrlGenerator->returnPathOr('chill_main_user_groups_my')
|
||||
);
|
||||
}
|
||||
if ($form->isSubmitted()) {
|
||||
$errors = [];
|
||||
foreach ($form->getErrors() as $error) {
|
||||
$errors[] = $error->getMessage();
|
||||
}
|
||||
|
||||
return new Response(implode(', ', $errors));
|
||||
}
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->chillUrlGenerator->returnPathOr('chill_main_user_groups_my')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ParamConverter("user", class=User::class, options={"id" = "userId"})
|
||||
*/
|
||||
#[Route('/{_locale}/main/user-group/{id}/user/{userId}/remove', name: 'chill_main_user_groups_remove_user')]
|
||||
public function removeUserToGroup(UserGroup $userGroup, User $user, Session $session): Response
|
||||
{
|
||||
if (!$this->security->isGranted(UserGroupVoter::APPEND_TO_GROUP, $userGroup)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$userGroup->removeUser($user);
|
||||
$this->objectManager->flush();
|
||||
|
||||
$session->getFlashBag()->add(
|
||||
'success',
|
||||
new TranslatableMessage(
|
||||
'user_group.user_removed',
|
||||
[
|
||||
'user_group' => $this->chillEntityRenderManager->renderString($userGroup, []),
|
||||
'user' => $this->chillEntityRenderManager->renderString($user, []),
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->chillUrlGenerator->returnPathOr('chill_main_user_groups_my')
|
||||
);
|
||||
}
|
||||
|
||||
private function createFormAppendUserForGroup(UserGroup $group): ?FormInterface
|
||||
{
|
||||
if (!$this->security->isGranted(UserGroupVoter::APPEND_TO_GROUP, $group)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$builder = $this->formFactory->createBuilder(FormType::class, ['users' => []], [
|
||||
'action' => $this->chillUrlGenerator->generateWithReturnPath('chill_main_user_groups_append_users', ['id' => $group->getId()]),
|
||||
]);
|
||||
$builder->add('users', PickUserDynamicType::class, [
|
||||
'submit_on_adding_new_entity' => true,
|
||||
'label' => 'user_group.append_users',
|
||||
'mapped' => false,
|
||||
'multiple' => true,
|
||||
]);
|
||||
|
||||
return $builder->getForm();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?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\DocStoreBundle\Service\Signature\PDFSignatureZoneAvailable;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature;
|
||||
use Chill\MainBundle\Security\Authorization\EntityWorkflowStepSignatureVoter;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
final readonly class WorkflowAddSignatureController
|
||||
{
|
||||
public function __construct(
|
||||
private EntityWorkflowManager $entityWorkflowManager,
|
||||
private PDFSignatureZoneAvailable $PDFSignatureZoneAvailable,
|
||||
private NormalizerInterface $normalizer,
|
||||
private Environment $twig,
|
||||
private UrlGeneratorInterface $urlGenerator,
|
||||
private Security $security,
|
||||
) {}
|
||||
|
||||
#[Route(path: '/{_locale}/main/workflow/signature/{id}/sign', name: 'chill_main_workflow_signature_add', methods: 'GET')]
|
||||
public function __invoke(EntityWorkflowStepSignature $signature, Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted(EntityWorkflowStepSignatureVoter::SIGN, $signature)) {
|
||||
throw new AccessDeniedHttpException('not authorized to sign this step');
|
||||
}
|
||||
|
||||
$entityWorkflow = $signature->getStep()->getEntityWorkflow();
|
||||
|
||||
if (EntityWorkflowSignatureStateEnum::PENDING !== $signature->getState()) {
|
||||
if ($request->query->has('returnPath')) {
|
||||
return new RedirectResponse($request->query->get('returnPath'));
|
||||
}
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->urlGenerator->generate('chill_main_workflow_show', ['id' => $entityWorkflow->getId()])
|
||||
);
|
||||
}
|
||||
|
||||
$storedObject = $this->entityWorkflowManager->getAssociatedStoredObject($entityWorkflow);
|
||||
if (null === $storedObject) {
|
||||
throw new NotFoundHttpException('No stored object found');
|
||||
}
|
||||
|
||||
$zones = $this->PDFSignatureZoneAvailable->getAvailableSignatureZones($entityWorkflow);
|
||||
|
||||
$signatureClient = [];
|
||||
$signatureClient['id'] = $signature->getId();
|
||||
$signatureClient['storedObject'] = $this->normalizer->normalize($storedObject, 'json');
|
||||
$signatureClient['zones'] = $zones;
|
||||
|
||||
return new Response(
|
||||
$this->twig->render(
|
||||
'@ChillMain/Workflow/signature_sign.html.twig',
|
||||
['signature' => $signatureClient]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?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\Workflow\EntityWorkflow;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowAttachment;
|
||||
use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter;
|
||||
use Chill\MainBundle\Workflow\Attachment\AddAttachmentAction;
|
||||
use Chill\MainBundle\Workflow\Attachment\AddAttachmentRequestDTO;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
class WorkflowAttachmentController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Security $security,
|
||||
private readonly SerializerInterface $serializer,
|
||||
private readonly ValidatorInterface $validator,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly AddAttachmentAction $addAttachmentAction,
|
||||
) {}
|
||||
|
||||
#[Route('/api/1.0/main/workflow/{id}/attachment', methods: ['POST'])]
|
||||
public function addAttachment(EntityWorkflow $entityWorkflow, Request $request): JsonResponse
|
||||
{
|
||||
if (!$this->security->isGranted(EntityWorkflowVoter::SEE, $entityWorkflow)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$dto = new AddAttachmentRequestDTO($entityWorkflow);
|
||||
$this->serializer->deserialize($request->getContent(), AddAttachmentRequestDTO::class, 'json', [
|
||||
AbstractNormalizer::OBJECT_TO_POPULATE => $dto, AbstractNormalizer::GROUPS => ['write'],
|
||||
]);
|
||||
|
||||
$errors = $this->validator->validate($dto);
|
||||
|
||||
if (count($errors) > 0) {
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize($errors, 'json'),
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
json: true
|
||||
);
|
||||
}
|
||||
|
||||
$attachment = ($this->addAttachmentAction)($dto);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize($attachment, 'json', [AbstractNormalizer::GROUPS => ['read']]),
|
||||
json: true
|
||||
);
|
||||
}
|
||||
|
||||
#[Route('/api/1.0/main/workflow/attachment/{id}', methods: ['DELETE'])]
|
||||
public function removeAttachment(EntityWorkflowAttachment $attachment): Response
|
||||
{
|
||||
if (!$this->security->isGranted(EntityWorkflowVoter::SEE, $attachment->getEntityWorkflow())) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$this->entityManager->remove($attachment);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new Response(null, Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
#[Route('/api/1.0/main/workflow/{id}/attachment', methods: ['GET'])]
|
||||
public function listAttachmentsForEntityWorkflow(EntityWorkflow $entityWorkflow): JsonResponse
|
||||
{
|
||||
if (!$this->security->isGranted(EntityWorkflowVoter::SEE, $entityWorkflow)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize(
|
||||
$entityWorkflow->getAttachments(),
|
||||
'json',
|
||||
[AbstractNormalizer::GROUPS => ['read']]
|
||||
),
|
||||
json: true
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,23 +13,27 @@ namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowComment;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||
use Chill\MainBundle\Form\EntityWorkflowCommentType;
|
||||
use Chill\MainBundle\Form\WorkflowSignatureMetadataType;
|
||||
use Chill\MainBundle\Form\WorkflowStepType;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
|
||||
use Chill\MainBundle\Repository\Workflow\EntityWorkflowStepSignatureRepository;
|
||||
use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter;
|
||||
use Chill\MainBundle\Security\ChillSecurity;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NonUniqueResultException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
@@ -38,7 +42,19 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class WorkflowController extends AbstractController
|
||||
{
|
||||
public function __construct(private readonly EntityWorkflowManager $entityWorkflowManager, private readonly EntityWorkflowRepository $entityWorkflowRepository, private readonly ValidatorInterface $validator, private readonly PaginatorFactory $paginatorFactory, private readonly Registry $registry, private readonly EntityManagerInterface $entityManager, private readonly TranslatorInterface $translator, private readonly ChillSecurity $security, private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry) {}
|
||||
public function __construct(
|
||||
private readonly EntityWorkflowManager $entityWorkflowManager,
|
||||
private readonly EntityWorkflowRepository $entityWorkflowRepository,
|
||||
private readonly ValidatorInterface $validator,
|
||||
private readonly PaginatorFactory $paginatorFactory,
|
||||
private readonly Registry $registry,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly ChillSecurity $security,
|
||||
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
|
||||
private readonly ClockInterface $clock,
|
||||
private readonly EntityWorkflowStepSignatureRepository $entityWorkflowStepSignatureRepository,
|
||||
) {}
|
||||
|
||||
#[Route(path: '/{_locale}/main/workflow/create', name: 'chill_main_workflow_create')]
|
||||
public function create(Request $request): Response
|
||||
@@ -268,6 +284,9 @@ class WorkflowController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NonUniqueResultException
|
||||
*/
|
||||
#[Route(path: '/{_locale}/main/workflow/{id}/show', name: 'chill_main_workflow_show')]
|
||||
public function show(EntityWorkflow $entityWorkflow, Request $request): Response
|
||||
{
|
||||
@@ -276,24 +295,17 @@ class WorkflowController extends AbstractController
|
||||
$handler = $this->entityWorkflowManager->getHandler($entityWorkflow);
|
||||
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
|
||||
$errors = [];
|
||||
$signatures = $entityWorkflow->getCurrentStep()->getSignatures();
|
||||
|
||||
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]);
|
||||
}
|
||||
$stepDTO = new WorkflowTransitionContextDTO($entityWorkflow);
|
||||
|
||||
$transitionForm = $this->createForm(
|
||||
WorkflowStepType::class,
|
||||
$entityWorkflow->getCurrentStep(),
|
||||
$stepDTO,
|
||||
[
|
||||
'transition' => true,
|
||||
'entity_workflow' => $entityWorkflow,
|
||||
'suggested_users' => $usersInvolved,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -310,12 +322,14 @@ class WorkflowController extends AbstractController
|
||||
throw $this->createAccessDeniedException(sprintf("not allowed to apply transition {$transition}: %s", implode(', ', $msgs)));
|
||||
}
|
||||
|
||||
// TODO symfony 5: add those "future" on context ($workflow->apply($entityWorkflow, $transition, $context)
|
||||
$entityWorkflow->futureCcUsers = $transitionForm['future_cc_users']->getData() ?? [];
|
||||
$entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData() ?? [];
|
||||
$entityWorkflow->futureDestEmails = $transitionForm['future_dest_emails']->getData() ?? [];
|
||||
$byUser = $this->security->getUser();
|
||||
|
||||
$workflow->apply($entityWorkflow, $transition);
|
||||
$workflow->apply($entityWorkflow, $transition, [
|
||||
'context' => $stepDTO,
|
||||
'byUser' => $byUser,
|
||||
'transition' => $transition,
|
||||
'transitionAt' => $this->clock->now(),
|
||||
]);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
@@ -327,22 +341,6 @@ class WorkflowController extends AbstractController
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
$commentForm = $this->createForm(EntityWorkflowCommentType::class, $newComment = new EntityWorkflowComment());
|
||||
$commentForm->handleRequest($request);
|
||||
|
||||
if ($commentForm->isSubmitted() && $commentForm->isValid()) {
|
||||
$this->entityManager->persist($newComment);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', $this->translator->trans('workflow.Comment added'));
|
||||
|
||||
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
|
||||
} elseif ($commentForm->isSubmitted() && !$commentForm->isValid()) {
|
||||
$this->addFlash('error', $this->translator->trans('This form contains errors'));
|
||||
}
|
||||
*/
|
||||
|
||||
return $this->render(
|
||||
'@ChillMain/Workflow/index.html.twig',
|
||||
[
|
||||
@@ -352,7 +350,8 @@ class WorkflowController extends AbstractController
|
||||
'transition_form' => isset($transitionForm) ? $transitionForm->createView() : null,
|
||||
'entity_workflow' => $entityWorkflow,
|
||||
'transition_form_errors' => $errors,
|
||||
// 'comment_form' => $commentForm->createView(),
|
||||
'signatures' => $signatures,
|
||||
'related_accompanying_period' => $this->entityWorkflowManager->getRelatedAccompanyingPeriod($entityWorkflow),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -371,4 +370,65 @@ class WorkflowController extends AbstractController
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
#[Route(path: '/{_locale}/main/workflow/signature/{signature_id}/metadata', name: 'chill_main_workflow_signature_metadata')]
|
||||
public function addSignatureMetadata(int $signature_id, Request $request): Response
|
||||
{
|
||||
$signature = $this->entityWorkflowStepSignatureRepository->find($signature_id);
|
||||
|
||||
if (null === $signature) {
|
||||
throw new NotFoundHttpException('signature not found');
|
||||
}
|
||||
|
||||
if ($signature->isSigned()) {
|
||||
$this->addFlash(
|
||||
'notice',
|
||||
$this->translator->trans('workflow.signature_zone.already_signed_alert')
|
||||
);
|
||||
|
||||
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $signature->getStep()->getEntityWorkflow()->getId()]);
|
||||
}
|
||||
|
||||
if ($signature->getSigner() instanceof User) {
|
||||
return $this->redirectToRoute('chill_main_workflow_signature_add', [
|
||||
'id' => $signature_id,
|
||||
'returnPath' => $request->query->get('returnPath', null),
|
||||
]);
|
||||
}
|
||||
|
||||
$metadataForm = $this->createForm(WorkflowSignatureMetadataType::class);
|
||||
$metadataForm->add('submit', SubmitType::class, ['label' => $this->translator->trans('Save')]);
|
||||
|
||||
$metadataForm->handleRequest($request);
|
||||
|
||||
if ($metadataForm->isSubmitted() && $metadataForm->isValid()) {
|
||||
$data = $metadataForm->getData();
|
||||
|
||||
$signature->setSignatureMetadata(
|
||||
[
|
||||
'base_signer' => [
|
||||
'document_type' => $data['documentType'],
|
||||
'document_number' => $data['documentNumber'],
|
||||
'expiration_date' => $data['expirationDate'],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->entityManager->persist($signature);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('chill_main_workflow_signature_add', [
|
||||
'id' => $signature_id,
|
||||
'returnPath' => $request->query->get('returnPath', null),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render(
|
||||
'@ChillMain/Workflow/signature_metadata.html.twig',
|
||||
[
|
||||
'metadata_form' => $metadataForm->createView(),
|
||||
'person' => $signature->getSigner(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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\User;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepHold;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
|
||||
class WorkflowOnHoldController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly Security $security,
|
||||
private readonly Registry $registry,
|
||||
private readonly UrlGeneratorInterface $urlGenerator,
|
||||
) {}
|
||||
|
||||
#[Route(path: '/{_locale}/main/workflow/{id}/hold', name: 'chill_main_workflow_on_hold')]
|
||||
public function putOnHold(EntityWorkflow $entityWorkflow, Request $request): Response
|
||||
{
|
||||
$currentStep = $entityWorkflow->getCurrentStep();
|
||||
$currentUser = $this->security->getUser();
|
||||
|
||||
if (!$currentUser instanceof User) {
|
||||
throw new AccessDeniedHttpException('only user can put a workflow on hold');
|
||||
}
|
||||
|
||||
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
|
||||
$enabledTransitions = $workflow->getEnabledTransitions($entityWorkflow);
|
||||
|
||||
if (0 === count($enabledTransitions)) {
|
||||
throw new AccessDeniedHttpException('You are not allowed to apply any transitions to this workflow, therefore you cannot toggle the hold status.');
|
||||
}
|
||||
|
||||
$stepHold = new EntityWorkflowStepHold($currentStep, $currentUser);
|
||||
|
||||
$this->entityManager->persist($stepHold);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->urlGenerator->generate(
|
||||
'chill_main_workflow_show',
|
||||
['id' => $entityWorkflow->getId()]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[Route(path: '/{_locale}/main/workflow/{id}/remove_hold', name: 'chill_main_workflow_remove_hold')]
|
||||
public function removeOnHold(EntityWorkflowStep $entityWorkflowStep): Response
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new AccessDeniedHttpException('only user can remove workflow on hold');
|
||||
}
|
||||
|
||||
if (!$entityWorkflowStep->isOnHoldByUser($user)) {
|
||||
throw new AccessDeniedHttpException('You are not allowed to remove workflow on hold');
|
||||
}
|
||||
|
||||
$hold = $entityWorkflowStep->getHoldsOnStep()->findFirst(fn (int $index, EntityWorkflowStepHold $entityWorkflowStepHold) => $user === $entityWorkflowStepHold->getByUser());
|
||||
|
||||
if (null === $hold) {
|
||||
// this should not happens...
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
$this->entityManager->remove($hold);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->urlGenerator->generate(
|
||||
'chill_main_workflow_show',
|
||||
['id' => $entityWorkflowStep->getEntityWorkflow()->getId()]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?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\Workflow\EntityWorkflowStepSignature;
|
||||
use Chill\MainBundle\Routing\ChillUrlGeneratorInterface;
|
||||
use Chill\MainBundle\Security\Authorization\EntityWorkflowStepSignatureVoter;
|
||||
use Chill\MainBundle\Workflow\SignatureStepStateChanger;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Twig\Environment;
|
||||
|
||||
final readonly class WorkflowSignatureStateChangeController
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private Security $security,
|
||||
private FormFactoryInterface $formFactory,
|
||||
private Environment $twig,
|
||||
private SignatureStepStateChanger $signatureStepStateChanger,
|
||||
private ChillUrlGeneratorInterface $chillUrlGenerator,
|
||||
) {}
|
||||
|
||||
#[Route('/{_locale}/main/workflow/signature/{id}/cancel', name: 'chill_main_workflow_signature_cancel')]
|
||||
public function cancelSignature(EntityWorkflowStepSignature $signature, Request $request): Response
|
||||
{
|
||||
return $this->markSignatureAction(
|
||||
$signature,
|
||||
$request,
|
||||
EntityWorkflowStepSignatureVoter::CANCEL,
|
||||
function (EntityWorkflowStepSignature $signature) {$this->signatureStepStateChanger->markSignatureAsCanceled($signature); },
|
||||
'@ChillMain/WorkflowSignature/cancel.html.twig',
|
||||
);
|
||||
}
|
||||
|
||||
#[Route('/{_locale}/main/workflow/signature/{id}/reject', name: 'chill_main_workflow_signature_reject')]
|
||||
public function rejectSignature(EntityWorkflowStepSignature $signature, Request $request): Response
|
||||
{
|
||||
return $this->markSignatureAction(
|
||||
$signature,
|
||||
$request,
|
||||
EntityWorkflowStepSignatureVoter::REJECT,
|
||||
function (EntityWorkflowStepSignature $signature) {$this->signatureStepStateChanger->markSignatureAsRejected($signature); },
|
||||
'@ChillMain/WorkflowSignature/reject.html.twig',
|
||||
);
|
||||
}
|
||||
|
||||
private function markSignatureAction(
|
||||
EntityWorkflowStepSignature $signature,
|
||||
Request $request,
|
||||
string $permissionAttribute,
|
||||
callable $markSignature,
|
||||
string $template,
|
||||
): Response {
|
||||
|
||||
if (!$this->security->isGranted($permissionAttribute, $signature)) {
|
||||
throw new AccessDeniedHttpException('not allowed to cancel this signature');
|
||||
}
|
||||
|
||||
$form = $this->formFactory->create();
|
||||
$form->add('confirm', SubmitType::class, ['label' => 'Confirm']);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->wrapInTransaction(function () use ($signature, $markSignature) {
|
||||
$markSignature($signature);
|
||||
});
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->chillUrlGenerator->returnPathOr('chill_main_workflow_show', ['id' => $signature->getStep()->getEntityWorkflow()->getId()])
|
||||
);
|
||||
}
|
||||
|
||||
return
|
||||
new Response(
|
||||
$this->twig->render(
|
||||
$template,
|
||||
['form' => $form->createView(), 'signature' => $signature]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?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\Workflow\EntityWorkflowSend;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSendView;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||
use Chill\MainBundle\Workflow\Exception\HandlerWithPublicViewNotFoundException;
|
||||
use Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage;
|
||||
use Chill\MainBundle\Workflow\Templating\EntityWorkflowViewMetadataDTO;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Twig\Environment;
|
||||
|
||||
final readonly class WorkflowViewSendPublicController
|
||||
{
|
||||
public const LOG_PREFIX = '[workflow-view-send-public-controller] ';
|
||||
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private LoggerInterface $chillLogger,
|
||||
private EntityWorkflowManager $entityWorkflowManager,
|
||||
private ClockInterface $clock,
|
||||
private Environment $environment,
|
||||
private MessageBusInterface $messageBus,
|
||||
) {}
|
||||
|
||||
#[Route('/public/main/workflow/send/{uuid}/view/{verificationKey}', name: 'chill_main_workflow_send_view_public', methods: ['GET'])]
|
||||
public function __invoke(EntityWorkflowSend $workflowSend, string $verificationKey, Request $request): Response
|
||||
{
|
||||
if (50 < $workflowSend->getNumberOfErrorTrials()) {
|
||||
throw new AccessDeniedHttpException('number of trials exceeded, no more access allowed');
|
||||
}
|
||||
|
||||
if ($verificationKey !== $workflowSend->getPrivateToken()) {
|
||||
$this->chillLogger->info(self::LOG_PREFIX.'Invalid trial for this send', ['client_ip' => $request->getClientIp()]);
|
||||
$workflowSend->increaseErrorTrials();
|
||||
$this->entityManager->flush();
|
||||
|
||||
throw new AccessDeniedHttpException('invalid verification key');
|
||||
}
|
||||
|
||||
if ($this->clock->now() > $workflowSend->getExpireAt()) {
|
||||
return new Response(
|
||||
$this->environment->render('@ChillMain/Workflow/workflow_view_send_public_expired.html.twig'),
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
if (100 < $workflowSend->getViews()->count()) {
|
||||
$this->chillLogger->info(self::LOG_PREFIX.'100 view reached, not allowed to see it again');
|
||||
throw new AccessDeniedHttpException('100 views reached, not allowed to see it again');
|
||||
}
|
||||
|
||||
try {
|
||||
$metadata = new EntityWorkflowViewMetadataDTO(
|
||||
$workflowSend->getViews()->count(),
|
||||
100 - $workflowSend->getViews()->count(),
|
||||
);
|
||||
$response = new Response(
|
||||
$this->entityWorkflowManager->renderPublicView($workflowSend, $metadata),
|
||||
);
|
||||
|
||||
$view = new EntityWorkflowSendView($workflowSend, $this->clock->now(), $request->getClientIp());
|
||||
$this->entityManager->persist($view);
|
||||
$this->messageBus->dispatch(new PostPublicViewMessage($view->getId()));
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $response;
|
||||
} catch (HandlerWithPublicViewNotFoundException $e) {
|
||||
throw new \RuntimeException('Could not render the public view', previous: $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,9 @@ interface CronJobInterface
|
||||
*
|
||||
* If data is returned, this data is passed as argument on the next execution
|
||||
*
|
||||
* @param array $lastExecutionData the data which was returned from the previous execution
|
||||
* @param array<string|int, int|float|string|bool|array<int|string, int|float|string|bool>> $lastExecutionData the data which was returned from the previous execution
|
||||
*
|
||||
* @return array|null optionally return an array with the same data than the previous execution
|
||||
* @return array<string|int, int|float|string|bool|array<int|string, int|float|string|bool>>|null optionally return an array with the same data than the previous execution
|
||||
*/
|
||||
public function run(array $lastExecutionData): ?array;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ final readonly class CronManager implements CronManagerInterface
|
||||
private CronJobExecutionRepositoryInterface $cronJobExecutionRepository,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private iterable $jobs,
|
||||
private LoggerInterface $logger
|
||||
private LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function run(?string $forceJob = null): void
|
||||
|
||||
@@ -19,25 +19,25 @@ use Doctrine\Persistence\ObjectManager;
|
||||
*/
|
||||
trait LoadAbstractNotificationsTrait
|
||||
{
|
||||
public function load(ObjectManager $manager)
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
return;
|
||||
|
||||
foreach ($this->notifs as $notif) {
|
||||
$entityId = $this->getReference($notif['entityRef'])->getId();
|
||||
$entityId = $this->getReference($notif['entityRef'], Notification::class)->getId();
|
||||
|
||||
echo 'Adding notification for '.$notif['entityClass'].'(entity id:'.$entityId.")\n";
|
||||
|
||||
$newNotif = (new Notification())
|
||||
->setMessage($notif['message'])
|
||||
->setSender($this->getReference($notif['sender']))
|
||||
->setSender($this->getReference($notif['sender'], Notification::class))
|
||||
->setRelatedEntityClass($notif['entityClass'])
|
||||
->setRelatedEntityId($entityId)
|
||||
->setDate(new \DateTimeImmutable('now'))
|
||||
->setRead([]);
|
||||
|
||||
foreach ($notif['addressees'] as $addressee) {
|
||||
$newNotif->addAddressee($this->getReference($addressee));
|
||||
$newNotif->addAddressee($this->getReference($addressee, Notification::class));
|
||||
}
|
||||
|
||||
$manager->persist($newNotif);
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Chill\MainBundle\DataFixtures\ORM;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\Point;
|
||||
use Chill\MainBundle\Entity\AddressReference;
|
||||
use Chill\MainBundle\Entity\PostalCode;
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
@@ -33,12 +34,12 @@ class LoadAddressReferences extends AbstractFixture implements ContainerAwareInt
|
||||
$this->faker = \Faker\Factory::create('fr_FR');
|
||||
}
|
||||
|
||||
public function getOrder()
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 51;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
echo "loading some reference address... \n";
|
||||
|
||||
@@ -69,7 +70,8 @@ class LoadAddressReferences extends AbstractFixture implements ContainerAwareInt
|
||||
$ar->setStreetNumber((string) random_int(0, 199));
|
||||
$ar->setPoint($this->getRandomPoint());
|
||||
$ar->setPostcode($this->getReference(
|
||||
LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)]
|
||||
LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)],
|
||||
PostalCode::class
|
||||
));
|
||||
|
||||
$ar->setMunicipalityCode($ar->getPostcode()->getCode());
|
||||
|
||||
@@ -31,12 +31,12 @@ class LoadCenters extends AbstractFixture implements OrderedFixtureInterface
|
||||
|
||||
public static $refs = [];
|
||||
|
||||
public function getOrder()
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
foreach (static::$centers as $new) {
|
||||
$center = new Center();
|
||||
|
||||
@@ -25,12 +25,12 @@ class LoadCountries extends AbstractFixture implements ContainerAwareInterface,
|
||||
{
|
||||
private ?ContainerInterface $container = null;
|
||||
|
||||
public function getOrder()
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
echo "loading countries... \n";
|
||||
|
||||
|
||||
63
src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadGenders.php
Normal file
63
src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadGenders.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?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\DataFixtures\ORM;
|
||||
|
||||
use Chill\MainBundle\Entity\Gender;
|
||||
use Chill\MainBundle\Entity\GenderEnum;
|
||||
use Chill\MainBundle\Entity\GenderIconEnum;
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
class LoadGenders extends AbstractFixture implements OrderedFixtureInterface
|
||||
{
|
||||
private array $genders = [
|
||||
[
|
||||
'label' => ['en' => 'man', 'fr' => 'homme'],
|
||||
'genderTranslation' => GenderEnum::MALE,
|
||||
'icon' => GenderIconEnum::MALE,
|
||||
],
|
||||
[
|
||||
'label' => ['en' => 'woman', 'fr' => 'femme'],
|
||||
'genderTranslation' => GenderEnum::FEMALE,
|
||||
'icon' => GenderIconEnum::FEMALE,
|
||||
],
|
||||
[
|
||||
'label' => ['en' => 'neutral', 'fr' => 'neutre'],
|
||||
'genderTranslation' => GenderEnum::NEUTRAL,
|
||||
'icon' => GenderIconEnum::NEUTRAL,
|
||||
],
|
||||
];
|
||||
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
echo "loading genders... \n";
|
||||
|
||||
foreach ($this->genders as $g) {
|
||||
echo $g['label']['fr'].' ';
|
||||
$new_g = new Gender();
|
||||
$new_g->setGenderTranslation($g['genderTranslation']);
|
||||
$new_g->setLabel($g['label']);
|
||||
$new_g->setIcon($g['icon']);
|
||||
|
||||
$this->addReference('g_'.$g['genderTranslation']->value, $new_g);
|
||||
$manager->persist($new_g);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\DataFixtures\ORM;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\GroupCenter;
|
||||
use Chill\MainBundle\Entity\PermissionsGroup;
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
@@ -20,18 +22,18 @@ class LoadGroupCenters extends AbstractFixture implements OrderedFixtureInterfac
|
||||
{
|
||||
public static $refs = [];
|
||||
|
||||
public function getOrder()
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 500;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
foreach (LoadCenters::$refs as $centerRef) {
|
||||
foreach (LoadPermissionsGroup::$refs as $permissionGroupRef) {
|
||||
$GroupCenter = new GroupCenter();
|
||||
$GroupCenter->setCenter($this->getReference($centerRef));
|
||||
$GroupCenter->setPermissionsGroup($this->getReference($permissionGroupRef));
|
||||
$GroupCenter->setCenter($this->getReference($centerRef, Center::class));
|
||||
$GroupCenter->setPermissionsGroup($this->getReference($permissionGroupRef, PermissionsGroup::class));
|
||||
|
||||
$manager->persist($GroupCenter);
|
||||
|
||||
|
||||
@@ -33,12 +33,12 @@ class LoadLanguages extends AbstractFixture implements ContainerAwareInterface,
|
||||
// This array contains regional code to not exclude
|
||||
private array $regionalVersionToInclude = ['ro_MD'];
|
||||
|
||||
public function getOrder()
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
echo "loading languages... \n";
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class LoadLocationType extends AbstractFixture implements ContainerAwareInterfac
|
||||
{
|
||||
private ?ContainerInterface $container = null;
|
||||
|
||||
public function getOrder()
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 52;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\DataFixtures\ORM;
|
||||
|
||||
use Chill\MainBundle\Entity\PermissionsGroup;
|
||||
use Chill\MainBundle\Entity\RoleScope;
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
@@ -47,19 +48,19 @@ class LoadPermissionsGroup extends AbstractFixture implements OrderedFixtureInte
|
||||
|
||||
public static $refs = [];
|
||||
|
||||
public function getOrder()
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 400;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
foreach (static::$permissionGroup as $new) {
|
||||
$permissionGroup = new PermissionsGroup();
|
||||
$permissionGroup->setName($new['name']);
|
||||
|
||||
foreach ($new['role_scopes'] as $roleScopeRef) {
|
||||
$permissionGroup->addRoleScope($this->getReference($roleScopeRef));
|
||||
$permissionGroup->addRoleScope($this->getReference($roleScopeRef, RoleScope::class));
|
||||
}
|
||||
|
||||
$manager->persist($permissionGroup);
|
||||
|
||||
@@ -325,12 +325,12 @@ class LoadPostalCodes extends AbstractFixture implements OrderedFixtureInterface
|
||||
85800,GIVRAND,FR,85100,46.6822701061,-1.8787272243,INSEE
|
||||
EOF;
|
||||
|
||||
public function getOrder()
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
echo "loading postal codes... \n";
|
||||
$this->loadPostalCodeCSV($manager, self::$postalCodeBelgium, 'BE');
|
||||
@@ -365,7 +365,7 @@ class LoadPostalCodes extends AbstractFixture implements OrderedFixtureInterface
|
||||
$manager->persist($c);
|
||||
$ref = 'postal_code_'.$code[0];
|
||||
|
||||
if (!$this->hasReference($ref)) {
|
||||
if (!$this->hasReference($ref, PostalCode::class)) {
|
||||
$this->addReference($ref, $c);
|
||||
self::$refs[] = $ref;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\DataFixtures\ORM;
|
||||
|
||||
use Chill\MainBundle\Entity\RoleScope;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
@@ -44,19 +45,19 @@ class LoadRoleScopes extends AbstractFixture implements OrderedFixtureInterface
|
||||
|
||||
public static $references = [];
|
||||
|
||||
public function getOrder()
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 300;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
foreach (static::$permissions as $key => $permission) {
|
||||
foreach (LoadScopes::$references as $scopeReference) {
|
||||
$roleScope = new RoleScope();
|
||||
$roleScope->setRole($key)
|
||||
->setScope($this->getReference($scopeReference));
|
||||
$reference = 'role_scope_'.$key.'_'.$this->getReference($scopeReference)->getName()['en'];
|
||||
->setScope($this->getReference($scopeReference, Scope::class));
|
||||
$reference = 'role_scope_'.$key.'_'.$this->getReference($scopeReference, Scope::class)->getName()['en'];
|
||||
echo "Creating {$reference} \n";
|
||||
$this->addReference($reference, $roleScope);
|
||||
$manager->persist($roleScope);
|
||||
|
||||
@@ -46,12 +46,12 @@ class LoadScopes extends AbstractFixture implements OrderedFixtureInterface
|
||||
],
|
||||
];
|
||||
|
||||
public function getOrder()
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
foreach ($this->scopes as $new) {
|
||||
$scope = new \Chill\MainBundle\Entity\Scope();
|
||||
|
||||
@@ -24,7 +24,7 @@ class LoadUserGroup extends Fixture implements FixtureGroupInterface
|
||||
return ['user-group'];
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
$centerASocial = $manager->getRepository(User::class)->findOneBy(['username' => 'center a_social']);
|
||||
$centerBSocial = $manager->getRepository(User::class)->findOneBy(['username' => 'center b_social']);
|
||||
|
||||
@@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\DataFixtures\ORM;
|
||||
|
||||
use Chill\MainBundle\Entity\GroupCenter;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
|
||||
@@ -54,12 +55,12 @@ class LoadUsers extends AbstractFixture implements ContainerAwareInterface, Orde
|
||||
|
||||
private ?ContainerInterface $container = null;
|
||||
|
||||
public function getOrder()
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 1000;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
foreach (self::$refs as $username => $params) {
|
||||
$user = new User();
|
||||
@@ -80,7 +81,7 @@ class LoadUsers extends AbstractFixture implements ContainerAwareInterface, Orde
|
||||
->setEmail(sprintf('%s@chill.social', \str_replace(' ', '', (string) $username)));
|
||||
|
||||
foreach ($params['groupCenterRefs'] as $groupCenterRef) {
|
||||
$user->addGroupCenter($this->getReference($groupCenterRef));
|
||||
$user->addGroupCenter($this->getReference($groupCenterRef, GroupCenter::class));
|
||||
}
|
||||
|
||||
echo 'Creating user '.$username."... \n";
|
||||
|
||||
@@ -17,6 +17,8 @@ use Chill\MainBundle\Controller\CivilityApiController;
|
||||
use Chill\MainBundle\Controller\CivilityController;
|
||||
use Chill\MainBundle\Controller\CountryApiController;
|
||||
use Chill\MainBundle\Controller\CountryController;
|
||||
use Chill\MainBundle\Controller\GenderApiController;
|
||||
use Chill\MainBundle\Controller\GenderController;
|
||||
use Chill\MainBundle\Controller\GeographicalUnitApiController;
|
||||
use Chill\MainBundle\Controller\LanguageController;
|
||||
use Chill\MainBundle\Controller\LocationController;
|
||||
@@ -24,6 +26,7 @@ use Chill\MainBundle\Controller\LocationTypeController;
|
||||
use Chill\MainBundle\Controller\NewsItemController;
|
||||
use Chill\MainBundle\Controller\RegroupmentController;
|
||||
use Chill\MainBundle\Controller\UserController;
|
||||
use Chill\MainBundle\Controller\UserGroupAdminController;
|
||||
use Chill\MainBundle\Controller\UserGroupApiController;
|
||||
use Chill\MainBundle\Controller\UserJobApiController;
|
||||
use Chill\MainBundle\Controller\UserJobController;
|
||||
@@ -53,6 +56,7 @@ use Chill\MainBundle\Doctrine\Type\PointType;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Civility;
|
||||
use Chill\MainBundle\Entity\Country;
|
||||
use Chill\MainBundle\Entity\Gender;
|
||||
use Chill\MainBundle\Entity\GeographicalUnitLayer;
|
||||
use Chill\MainBundle\Entity\Language;
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
@@ -65,11 +69,13 @@ use Chill\MainBundle\Entity\UserJob;
|
||||
use Chill\MainBundle\Form\CenterType;
|
||||
use Chill\MainBundle\Form\CivilityType;
|
||||
use Chill\MainBundle\Form\CountryType;
|
||||
use Chill\MainBundle\Form\GenderType;
|
||||
use Chill\MainBundle\Form\LanguageType;
|
||||
use Chill\MainBundle\Form\LocationFormType;
|
||||
use Chill\MainBundle\Form\LocationTypeType;
|
||||
use Chill\MainBundle\Form\NewsItemType;
|
||||
use Chill\MainBundle\Form\RegroupmentType;
|
||||
use Chill\MainBundle\Form\UserGroupType;
|
||||
use Chill\MainBundle\Form\UserJobType;
|
||||
use Chill\MainBundle\Form\UserType;
|
||||
use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType;
|
||||
@@ -228,6 +234,8 @@ class ChillMainExtension extends Extension implements
|
||||
|
||||
public function prepend(ContainerBuilder $container)
|
||||
{
|
||||
$this->prependNotifierTexterWithLegacyData($container);
|
||||
|
||||
// add installation_name and date_format to globals
|
||||
$chillMainConfig = $container->getExtensionConfig($this->getAlias());
|
||||
$config = $this->processConfiguration($this
|
||||
@@ -337,7 +345,7 @@ class ChillMainExtension extends Extension implements
|
||||
ContainerBuilder $container,
|
||||
array $crudConfig,
|
||||
array $apiConfig,
|
||||
Loader\YamlFileLoader $loader
|
||||
Loader\YamlFileLoader $loader,
|
||||
): void {
|
||||
if (0 === \count($crudConfig)) {
|
||||
return;
|
||||
@@ -351,10 +359,81 @@ class ChillMainExtension extends Extension implements
|
||||
// Note: the controller are loaded inside compiler pass
|
||||
}
|
||||
|
||||
/**
|
||||
* This method prepend framework configuration with legacy configuration from "ovhCloudTransporter".
|
||||
*
|
||||
* It can be safely removed when the option chill_main.short_message.dsn will be removed.
|
||||
*/
|
||||
private function prependNotifierTexterWithLegacyData(ContainerBuilder $container): void
|
||||
{
|
||||
foreach (array_reverse($container->getExtensionConfig('framework')) as $c) {
|
||||
// we look into each configuration for framework. If there is a configuration for
|
||||
// texter_transports in one of them, we don't configure anything else
|
||||
if (null !== $notifConfig = $c['notifier'] ?? null) {
|
||||
if (null !== ($notifConfig['texter_transports'] ?? null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// there is no texter config, we try to configure one
|
||||
$configs = $container->getExtensionConfig('chill_main');
|
||||
$notifierSet = false;
|
||||
foreach (array_reverse($configs) as $config) {
|
||||
if (!array_key_exists('short_messages', $config)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array_key_exists('dsn', $config['short_messages'])) {
|
||||
$container->prependExtensionConfig('framework', [
|
||||
'notifier' => [
|
||||
'texter_transports' => [
|
||||
'ovh_legacy' => $config['short_messages']['dsn'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$notifierSet = true;
|
||||
}
|
||||
}
|
||||
if (!$notifierSet) {
|
||||
$container->prependExtensionConfig('framework', [
|
||||
'notifier' => [
|
||||
'texter_transports' => [
|
||||
'dummy' => 'null://null',
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected function prependCruds(ContainerBuilder $container)
|
||||
{
|
||||
$container->prependExtensionConfig('chill_main', [
|
||||
'cruds' => [
|
||||
[
|
||||
'class' => UserGroup::class,
|
||||
'controller' => UserGroupAdminController::class,
|
||||
'name' => 'admin_user_group',
|
||||
'base_path' => '/admin/main/user-group',
|
||||
'base_role' => 'ROLE_ADMIN',
|
||||
'form_class' => UserGroupType::class,
|
||||
'actions' => [
|
||||
'index' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillMain/UserGroup/index.html.twig',
|
||||
],
|
||||
'new' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillMain/UserGroup/new.html.twig',
|
||||
],
|
||||
'edit' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillMain/UserGroup/edit.html.twig',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'class' => UserJob::class,
|
||||
'controller' => UserJobController::class,
|
||||
@@ -487,6 +566,28 @@ class ChillMainExtension extends Extension implements
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'class' => Gender::class,
|
||||
'name' => 'main_gender',
|
||||
'base_path' => '/admin/main/gender',
|
||||
'base_role' => 'ROLE_ADMIN',
|
||||
'form_class' => GenderType::class,
|
||||
'controller' => GenderController::class,
|
||||
'actions' => [
|
||||
'index' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillMain/Gender/index.html.twig',
|
||||
],
|
||||
'new' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillMain/Gender/new.html.twig',
|
||||
],
|
||||
'edit' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillMain/Gender/edit.html.twig',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'class' => Language::class,
|
||||
'name' => 'main_language',
|
||||
@@ -790,6 +891,21 @@ class ChillMainExtension extends Extension implements
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'class' => Gender::class,
|
||||
'name' => 'gender',
|
||||
'base_path' => '/api/1.0/main/gender',
|
||||
'base_role' => 'ROLE_USER',
|
||||
'controller' => GenderApiController::class,
|
||||
'actions' => [
|
||||
'_index' => [
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'class' => GeographicalUnitLayer::class,
|
||||
'controller' => GeographicalUnitApiController::class,
|
||||
|
||||
@@ -44,7 +44,7 @@ class ExportsCompilerPass implements CompilerPassInterface
|
||||
|
||||
private function compileExportElementsProvider(
|
||||
Definition $chillManagerDefinition,
|
||||
ContainerBuilder $container
|
||||
ContainerBuilder $container,
|
||||
) {
|
||||
$taggedServices = $container->findTaggedServiceIds(
|
||||
'chill.export_elements_provider'
|
||||
@@ -73,7 +73,7 @@ class ExportsCompilerPass implements CompilerPassInterface
|
||||
|
||||
private function compileFormatters(
|
||||
Definition $chillManagerDefinition,
|
||||
ContainerBuilder $container
|
||||
ContainerBuilder $container,
|
||||
) {
|
||||
$taggedServices = $container->findTaggedServiceIds(
|
||||
'chill.export_formatter'
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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\DependencyInjection\CompilerPass;
|
||||
|
||||
use Chill\MainBundle\Service\ShortMessage\NullShortMessageSender;
|
||||
use Chill\MainBundle\Service\ShortMessage\ShortMessageTransporter;
|
||||
use Chill\MainBundle\Service\ShortMessageOvh\OvhShortMessageSender;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class ShortMessageCompilerPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$config = $container->resolveEnvPlaceholders($container->getParameter('chill_main.short_messages'), true);
|
||||
// weird fix for special characters
|
||||
$config['dsn'] = str_replace(['%%'], ['%'], (string) $config['dsn']);
|
||||
$dsn = parse_url($config['dsn']);
|
||||
parse_str($dsn['query'] ?? '', $dsn['queries']);
|
||||
|
||||
if ('null' === $dsn['scheme'] || false === $config['enabled']) {
|
||||
$defaultTransporter = new Reference(NullShortMessageSender::class);
|
||||
} elseif ('ovh' === $dsn['scheme']) {
|
||||
if (!class_exists('\\'.\Ovh\Api::class)) {
|
||||
throw new RuntimeException('Class \\Ovh\\Api not found');
|
||||
}
|
||||
|
||||
foreach (['user', 'host', 'pass'] as $component) {
|
||||
if (!\array_key_exists($component, $dsn)) {
|
||||
throw new RuntimeException(sprintf('The component %s does not exist in dsn. Please provide a dsn like ovh://applicationKey:applicationSecret@endpoint?consumerKey=xxxx&sender=yyyy&service_name=zzzz', $component));
|
||||
}
|
||||
|
||||
$container->setParameter('chill_main.short_messages.ovh_config_'.$component, $dsn[$component]);
|
||||
}
|
||||
|
||||
foreach (['consumer_key', 'sender', 'service_name'] as $param) {
|
||||
if (!\array_key_exists($param, $dsn['queries'])) {
|
||||
throw new RuntimeException(sprintf('The parameter %s does not exist in dsn. Please provide a dsn like ovh://applicationKey:applicationSecret@endpoint?consumerKey=xxxx&sender=yyyy&service_name=zzzz', $param));
|
||||
}
|
||||
$container->setParameter('chill_main.short_messages.ovh_config_'.$param, $dsn['queries'][$param]);
|
||||
}
|
||||
|
||||
$ovh = new Definition();
|
||||
$ovh
|
||||
->setClass('\\'.\Ovh\Api::class)
|
||||
->setArgument(0, $dsn['user'])
|
||||
->setArgument(1, $dsn['pass'])
|
||||
->setArgument(2, $dsn['host'])
|
||||
->setArgument(3, $dsn['queries']['consumer_key']);
|
||||
$container->setDefinition(\Ovh\Api::class, $ovh);
|
||||
|
||||
$ovhSender = new Definition();
|
||||
$ovhSender
|
||||
->setClass(OvhShortMessageSender::class)
|
||||
->setArgument(0, new Reference(\Ovh\Api::class))
|
||||
->setArgument(1, $dsn['queries']['service_name'])
|
||||
->setArgument(2, $dsn['queries']['sender'])
|
||||
->setArgument(3, new Reference(LoggerInterface::class))
|
||||
->setArgument(4, new Reference(PhoneNumberUtil::class));
|
||||
$container->setDefinition(OvhShortMessageSender::class, $ovhSender);
|
||||
|
||||
$defaultTransporter = new Reference(OvhShortMessageSender::class);
|
||||
} else {
|
||||
throw new RuntimeException(sprintf('Cannot find a sender for this dsn: %s', $config['dsn']));
|
||||
}
|
||||
|
||||
$container->getDefinition(ShortMessageTransporter::class)
|
||||
->setArgument(0, $defaultTransporter);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class Configuration implements ConfigurationInterface
|
||||
|
||||
public function __construct(
|
||||
array $widgetFactories,
|
||||
private readonly ContainerBuilder $containerBuilder
|
||||
private readonly ContainerBuilder $containerBuilder,
|
||||
) {
|
||||
$this->setWidgetFactories($widgetFactories);
|
||||
}
|
||||
@@ -85,6 +85,29 @@ class Configuration implements ConfigurationInterface
|
||||
->end()
|
||||
->end()
|
||||
->end() // end of notifications
|
||||
->arrayNode('workflow_signature')
|
||||
->children()
|
||||
->arrayNode('base_signer')
|
||||
->children()
|
||||
->arrayNode('document_kinds')
|
||||
->arrayPrototype()
|
||||
->children()
|
||||
->scalarNode('key')->cannotBeEmpty()->end()
|
||||
->arrayNode('labels')
|
||||
->arrayPrototype()
|
||||
->children()
|
||||
->scalarNode('lang')->cannotBeEmpty()->end()
|
||||
->scalarNode('label')->cannotBeEmpty()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end() // end of workflow signature document types
|
||||
->arrayNode('phone_helper')
|
||||
->canBeUnset()
|
||||
->children()
|
||||
@@ -100,6 +123,7 @@ class Configuration implements ConfigurationInterface
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('short_messages')
|
||||
->setDeprecated('chill-project/chill-bundles', '3.7.0', 'Since 3.7.0, Chill use the Notifier component to send message. Configure the notifier instead. In the meantime, the previous available OVH configuration will be append to the notifier component.')
|
||||
->canBeEnabled()
|
||||
->children()
|
||||
->scalarNode('dsn')->cannotBeEmpty()->defaultValue('null://null')
|
||||
|
||||
@@ -122,7 +122,7 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
|
||||
public function doProcess(
|
||||
ContainerBuilder $container,
|
||||
$extension,
|
||||
$containerWidgetConfigParameterName
|
||||
$containerWidgetConfigParameterName,
|
||||
) {
|
||||
if (!$container->hasDefinition(self::WIDGET_MANAGER)) {
|
||||
throw new \LogicException('the service '.self::WIDGET_MANAGER.' should be present. It is required by '.self::class);
|
||||
@@ -230,7 +230,7 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
|
||||
|
||||
// check the alias does not exists yet
|
||||
if (\array_key_exists($attr[self::WIDGET_SERVICE_TAG_ALIAS], $this->widgetServices)) {
|
||||
throw new InvalidArgumentException('a service has already be defined with the '.self::WIDGET_SERVICE_TAG_ALIAS.' '.$attr[self::WIDGET_SERVICE_TAG_ALIAS]);
|
||||
throw new InvalidConfigurationException('a service has already be defined with the '.self::WIDGET_SERVICE_TAG_ALIAS.' '.$attr[self::WIDGET_SERVICE_TAG_ALIAS]);
|
||||
}
|
||||
|
||||
// register the service as available
|
||||
@@ -259,7 +259,7 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
|
||||
|
||||
// check the alias does not exists yet
|
||||
if (\array_key_exists($alias, $this->widgetServices)) {
|
||||
throw new InvalidArgumentException('a service has already be defined with the '.self::WIDGET_SERVICE_TAG_ALIAS.' '.$alias);
|
||||
throw new InvalidConfigurationException('a service has already be defined with the '.self::WIDGET_SERVICE_TAG_ALIAS.' '.$alias);
|
||||
}
|
||||
|
||||
// register the factory as available
|
||||
@@ -280,7 +280,7 @@ abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface
|
||||
WidgetFactoryInterface $factory,
|
||||
$place,
|
||||
$order,
|
||||
array $config
|
||||
array $config,
|
||||
) {
|
||||
$serviceId = $factory->getServiceId($container, $place, $order, $config);
|
||||
$definition = $factory->createDefinition(
|
||||
|
||||
@@ -92,7 +92,7 @@ class Address implements TrackCreationInterface, TrackUpdateInterface
|
||||
* This list is computed by a materialized view. It won't be populated until a refresh is done
|
||||
* on the materialized view.
|
||||
*
|
||||
* @var Collection<GeographicalUnit>
|
||||
* @var Collection<int, GeographicalUnit>
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
@@ -446,7 +446,7 @@ class Address implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setLinkedToThirdParty($linkedToThirdParty): self
|
||||
public function setLinkedToThirdParty(?ThirdParty $linkedToThirdParty): self
|
||||
{
|
||||
$this->linkedToThirdParty = $linkedToThirdParty;
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
class Center implements HasCenterInterface, \Stringable
|
||||
{
|
||||
/**
|
||||
* @var Collection<GroupCenter>
|
||||
* @var Collection<int, GroupCenter>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: GroupCenter::class, mappedBy: 'center')]
|
||||
#[ORM\OneToMany(mappedBy: 'center', targetEntity: GroupCenter::class)]
|
||||
private Collection $groupCenters;
|
||||
|
||||
#[Serializer\Groups(['docgen:read'])]
|
||||
@@ -36,11 +36,11 @@ class Center implements HasCenterInterface, \Stringable
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
|
||||
private string $name = '';
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN)]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => true])]
|
||||
private bool $isActive = true;
|
||||
|
||||
/**
|
||||
* @var Collection<Regroupment>
|
||||
* @var Collection<int, Regroupment>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: Regroupment::class, mappedBy: 'centers')]
|
||||
private Collection $regroupments;
|
||||
@@ -111,7 +111,7 @@ class Center implements HasCenterInterface, \Stringable
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name)
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class Country
|
||||
* @var array<string, string>
|
||||
*/
|
||||
#[Groups(['read', 'docgen:read'])]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, options: ['default' => '[]', 'jsonb' => true])]
|
||||
#[Context(['is-translatable' => true], groups: ['docgen:read'])]
|
||||
private array $name = [];
|
||||
|
||||
|
||||
@@ -30,13 +30,13 @@ class CronJobExecution
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true, options: ['default' => null])]
|
||||
private ?int $lastStatus = null;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, options: ['default' => "'{}'::jsonb", 'jsonb' => true])]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, options: ['default' => '{}', 'jsonb' => true])]
|
||||
private array $lastExecutionData = [];
|
||||
|
||||
public function __construct(
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false)]
|
||||
#[ORM\Id]
|
||||
private string $key
|
||||
private string $key,
|
||||
) {
|
||||
$this->lastStart = new \DateTimeImmutable('now');
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class DashboardConfigItem
|
||||
private ?User $user = null;
|
||||
|
||||
#[Serializer\Groups(['dashboardConfigItem:read'])]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, options: ['default' => '[]', 'jsonb' => true])]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, options: ['default' => '{}', 'jsonb' => true])]
|
||||
private array $metadata = [];
|
||||
|
||||
public function getId(): ?int
|
||||
|
||||
@@ -61,10 +61,7 @@ class CommentEmbeddable
|
||||
$this->date = $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
*/
|
||||
public function setUserId($userId)
|
||||
public function setUserId(?int $userId)
|
||||
{
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class PrivateCommentEmbeddable
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setComments($comments)
|
||||
public function setComments(array $comments)
|
||||
{
|
||||
$this->comments = $comments;
|
||||
|
||||
|
||||
104
src/Bundle/ChillMainBundle/Entity/Gender.php
Normal file
104
src/Bundle/ChillMainBundle/Entity/Gender.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?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\Entity;
|
||||
|
||||
use Chill\MainBundle\Repository\GenderRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['chill_main_gender' => Gender::class])]
|
||||
#[ORM\Entity(repositoryClass: GenderRepository::class)]
|
||||
#[ORM\Table(name: 'chill_main_gender')]
|
||||
class Gender
|
||||
{
|
||||
#[Serializer\Groups(['read', 'docgen:read'])]
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
||||
private ?int $id = null;
|
||||
|
||||
#[Serializer\Groups(['read', 'docgen:read'])]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)]
|
||||
private array $label = [];
|
||||
|
||||
#[Serializer\Groups(['read'])]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN)]
|
||||
private bool $active = true;
|
||||
|
||||
#[Assert\NotNull(message: 'You must choose a gender translation')]
|
||||
#[Serializer\Groups(['read', 'docgen:read'])]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, enumType: GenderEnum::class)]
|
||||
private GenderEnum $genderTranslation;
|
||||
|
||||
#[Serializer\Groups(['read'])]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, enumType: GenderIconEnum::class)]
|
||||
private GenderIconEnum $icon;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::FLOAT, name: 'ordering', nullable: true, options: ['default' => '0.0'])]
|
||||
private float $order = 0;
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getLabel(): array
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setLabel(array $label): void
|
||||
{
|
||||
$this->label = $label;
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
public function setActive(bool $active): void
|
||||
{
|
||||
$this->active = $active;
|
||||
}
|
||||
|
||||
public function getGenderTranslation(): GenderEnum
|
||||
{
|
||||
return $this->genderTranslation;
|
||||
}
|
||||
|
||||
public function setGenderTranslation(GenderEnum $genderTranslation): void
|
||||
{
|
||||
$this->genderTranslation = $genderTranslation;
|
||||
}
|
||||
|
||||
public function getIcon(): GenderIconEnum
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
public function setIcon(GenderIconEnum $icon): void
|
||||
{
|
||||
$this->icon = $icon;
|
||||
}
|
||||
|
||||
public function getOrder(): float
|
||||
{
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
public function setOrder(float $order): void
|
||||
{
|
||||
$this->order = $order;
|
||||
}
|
||||
}
|
||||
20
src/Bundle/ChillMainBundle/Entity/GenderEnum.php
Normal file
20
src/Bundle/ChillMainBundle/Entity/GenderEnum.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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\Entity;
|
||||
|
||||
enum GenderEnum: string
|
||||
{
|
||||
case MALE = 'man';
|
||||
case FEMALE = 'woman';
|
||||
case NEUTRAL = 'neutral';
|
||||
case UNKNOWN = 'unknown';
|
||||
}
|
||||
22
src/Bundle/ChillMainBundle/Entity/GenderIconEnum.php
Normal file
22
src/Bundle/ChillMainBundle/Entity/GenderIconEnum.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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\Entity;
|
||||
|
||||
enum GenderIconEnum: string
|
||||
{
|
||||
case MALE = 'bi bi-gender-male';
|
||||
case FEMALE = 'bi bi-gender-female';
|
||||
case NEUTRAL = 'bi bi-gender-neuter';
|
||||
case AMBIGUOUS = 'bi bi-gender-ambiguous';
|
||||
case TRANS = 'bi bi-gender-trans';
|
||||
case UNKNOWN = 'bi bi-question';
|
||||
}
|
||||
@@ -48,6 +48,6 @@ class SimpleGeographicalUnitDTO
|
||||
* @psalm-readonly
|
||||
*/
|
||||
#[Serializer\Groups(['read'])]
|
||||
public int $layerId
|
||||
public int $layerId,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ class GeographicalUnitLayer
|
||||
private string $refId = '';
|
||||
|
||||
/**
|
||||
* @var Collection<GeographicalUnit>
|
||||
* @var Collection<int, GeographicalUnit>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: GeographicalUnit::class, mappedBy: 'layer')]
|
||||
#[ORM\OneToMany(mappedBy: 'layer', targetEntity: GeographicalUnit::class)]
|
||||
private Collection $units;
|
||||
|
||||
public function __construct()
|
||||
|
||||
@@ -34,7 +34,7 @@ class GroupCenter
|
||||
private ?PermissionsGroup $permissionsGroup = null;
|
||||
|
||||
/**
|
||||
* @var Collection<User::class>
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class, mappedBy: 'groupCenters')]
|
||||
private Collection $users;
|
||||
|
||||
@@ -59,11 +59,9 @@ class Language
|
||||
/**
|
||||
* Set id.
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return Language
|
||||
*/
|
||||
public function setId($id)
|
||||
public function setId(?string $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
|
||||
@@ -77,7 +75,7 @@ class Language
|
||||
*
|
||||
* @return Language
|
||||
*/
|
||||
public function setName($name)
|
||||
public function setName(array $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
class Location implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
#[Serializer\Groups(['read'])]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, nullable: true)]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => true])]
|
||||
private bool $active = true;
|
||||
|
||||
#[Serializer\Groups(['read', 'write', 'docgen:read'])]
|
||||
|
||||
@@ -34,7 +34,7 @@ class LocationType
|
||||
final public const STATUS_REQUIRED = 'required';
|
||||
|
||||
#[Serializer\Groups(['read'])]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, nullable: true)]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => true])]
|
||||
private bool $active = true;
|
||||
|
||||
#[Serializer\Groups(['read'])]
|
||||
@@ -54,7 +54,7 @@ class LocationType
|
||||
private ?string $defaultFor = null;
|
||||
|
||||
#[Serializer\Groups(['read'])]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN)]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => true])]
|
||||
private bool $editableByUsers = true;
|
||||
|
||||
#[Serializer\Groups(['read', 'docgen:read'])]
|
||||
|
||||
@@ -30,7 +30,7 @@ class Notification implements TrackUpdateInterface
|
||||
private array $addedAddresses = [];
|
||||
|
||||
/**
|
||||
* @var Collection<User>
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||
#[ORM\JoinTable(name: 'chill_main_notification_addresses_user')]
|
||||
@@ -41,7 +41,7 @@ class Notification implements TrackUpdateInterface
|
||||
*
|
||||
* @var array|string[]
|
||||
*/
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, options: ['default' => '[]', 'jsonb' => true])]
|
||||
private array $addressesEmails = [];
|
||||
|
||||
/**
|
||||
@@ -54,9 +54,9 @@ class Notification implements TrackUpdateInterface
|
||||
private ?ArrayCollection $addressesOnLoad = null;
|
||||
|
||||
/**
|
||||
* @var Collection<NotificationComment>
|
||||
* @var Collection<int, NotificationComment>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: NotificationComment::class, mappedBy: 'notification', orphanRemoval: true)]
|
||||
#[ORM\OneToMany(mappedBy: 'notification', targetEntity: NotificationComment::class, orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['createdAt' => \Doctrine\Common\Collections\Criteria::ASC])]
|
||||
private Collection $comments;
|
||||
|
||||
@@ -88,7 +88,7 @@ class Notification implements TrackUpdateInterface
|
||||
private string $title = '';
|
||||
|
||||
/**
|
||||
* @var Collection<User>
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||
#[ORM\JoinTable(name: 'chill_main_notification_addresses_unread')]
|
||||
|
||||
@@ -24,13 +24,13 @@ class PermissionsGroup
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, options: ['default' => '[]', 'jsonb' => true])]
|
||||
private array $flags = [];
|
||||
|
||||
/**
|
||||
* @var Collection<GroupCenter>
|
||||
* @var Collection<int, GroupCenter>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: GroupCenter::class, mappedBy: 'permissionsGroup')]
|
||||
#[ORM\OneToMany(mappedBy: 'permissionsGroup', targetEntity: GroupCenter::class)]
|
||||
private Collection $groupCenters;
|
||||
|
||||
#[ORM\Id]
|
||||
@@ -42,7 +42,7 @@ class PermissionsGroup
|
||||
private string $name = '';
|
||||
|
||||
/**
|
||||
* @var Collection<RoleScope>
|
||||
* @var Collection<int, RoleScope>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: RoleScope::class, inversedBy: 'permissionsGroups', cascade: ['persist'])]
|
||||
#[ORM\Cache(usage: 'NONSTRICT_READ_WRITE')]
|
||||
@@ -137,7 +137,7 @@ class PermissionsGroup
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name)
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
|
||||
@@ -157,11 +157,9 @@ class PostalCode implements TrackUpdateInterface, TrackCreationInterface
|
||||
/**
|
||||
* Set code.
|
||||
*
|
||||
* @param string $code
|
||||
*
|
||||
* @return PostalCode
|
||||
*/
|
||||
public function setCode($code)
|
||||
public function setCode(?string $code)
|
||||
{
|
||||
$this->code = $code;
|
||||
|
||||
@@ -183,11 +181,9 @@ class PostalCode implements TrackUpdateInterface, TrackCreationInterface
|
||||
/**
|
||||
* Set name.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return PostalCode
|
||||
*/
|
||||
public function setName($name)
|
||||
public function setName(?string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
@@ -197,11 +193,9 @@ class PostalCode implements TrackUpdateInterface, TrackCreationInterface
|
||||
/**
|
||||
* Set origin.
|
||||
*
|
||||
* @param int $origin
|
||||
*
|
||||
* @return PostalCode
|
||||
*/
|
||||
public function setOrigin($origin)
|
||||
public function setOrigin(int $origin)
|
||||
{
|
||||
$this->origin = $origin;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ use Doctrine\ORM\Mapping as ORM;
|
||||
class Regroupment
|
||||
{
|
||||
/**
|
||||
* @var Collection<Center>
|
||||
* @var Collection<int, Center>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: Center::class, inversedBy: 'regroupments')]
|
||||
#[ORM\Id]
|
||||
|
||||
@@ -26,7 +26,7 @@ class RoleScope
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @var Collection<PermissionsGroup>
|
||||
* @var Collection<int, PermissionsGroup>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: PermissionsGroup::class, mappedBy: 'roleScopes')]
|
||||
private Collection $permissionsGroups;
|
||||
|
||||
@@ -42,9 +42,9 @@ class Scope
|
||||
private array $name = [];
|
||||
|
||||
/**
|
||||
* @var Collection<RoleScope>
|
||||
* @var Collection<int, RoleScope>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: RoleScope::class, mappedBy: 'scope')]
|
||||
#[ORM\OneToMany(mappedBy: 'scope', targetEntity: RoleScope::class)]
|
||||
#[ORM\Cache(usage: 'NONSTRICT_READ_WRITE')]
|
||||
private Collection $roleScopes;
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
/**
|
||||
* Array where SAML attributes's data are stored.
|
||||
*/
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false)]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]', 'jsonb' => true])]
|
||||
private array $attributes = [];
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Civility::class)]
|
||||
@@ -64,7 +64,7 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
private bool $enabled = true;
|
||||
|
||||
/**
|
||||
* @var Collection<GroupCenter>
|
||||
* @var Collection<int, GroupCenter>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: GroupCenter::class, inversedBy: 'users')]
|
||||
#[ORM\Cache(usage: 'NONSTRICT_READ_WRITE')]
|
||||
@@ -83,9 +83,9 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
private ?Location $mainLocation = null;
|
||||
|
||||
/**
|
||||
* @var Collection&Selectable<int, UserScopeHistory>
|
||||
* @var Collection<int, UserScopeHistory>&Selectable
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: UserScopeHistory::class, mappedBy: 'user', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OneToMany(mappedBy: 'user', targetEntity: UserScopeHistory::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
private Collection&Selectable $scopeHistories;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
|
||||
@@ -98,9 +98,9 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
private ?string $salt = null;
|
||||
|
||||
/**
|
||||
* @var Collection&Selectable<int, UserJobHistory>
|
||||
* @var Collection<int, UserJobHistory>&Selectable
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: UserJobHistory::class, mappedBy: 'user', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OneToMany(mappedBy: 'user', targetEntity: UserJobHistory::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
private Collection&Selectable $jobHistories;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 80)]
|
||||
@@ -216,13 +216,13 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
return $this->mainLocation;
|
||||
}
|
||||
|
||||
public function getMainScope(?\DateTimeImmutable $at = null): ?Scope
|
||||
public function getMainScope(?\DateTimeImmutable $atDate = null): ?Scope
|
||||
{
|
||||
$at ??= new \DateTimeImmutable('now');
|
||||
$atDate ??= new \DateTimeImmutable('now');
|
||||
|
||||
foreach ($this->scopeHistories as $scopeHistory) {
|
||||
if ($at >= $scopeHistory->getStartDate() && (
|
||||
null === $scopeHistory->getEndDate() || $at < $scopeHistory->getEndDate()
|
||||
if ($atDate >= $scopeHistory->getStartDate() && (
|
||||
null === $scopeHistory->getEndDate() || $atDate < $scopeHistory->getEndDate()
|
||||
)) {
|
||||
return $scopeHistory->getScope();
|
||||
}
|
||||
@@ -265,13 +265,13 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
return $this->salt;
|
||||
}
|
||||
|
||||
public function getUserJob(?\DateTimeImmutable $at = null): ?UserJob
|
||||
public function getUserJob(?\DateTimeImmutable $atDate = null): ?UserJob
|
||||
{
|
||||
$at ??= new \DateTimeImmutable('now');
|
||||
$atDate ??= new \DateTimeImmutable('now');
|
||||
|
||||
foreach ($this->jobHistories as $jobHistory) {
|
||||
if ($at >= $jobHistory->getStartDate() && (
|
||||
null === $jobHistory->getEndDate() || $at < $jobHistory->getEndDate()
|
||||
if ($atDate >= $jobHistory->getStartDate() && (
|
||||
null === $jobHistory->getEndDate() || $atDate < $jobHistory->getEndDate()
|
||||
)) {
|
||||
return $jobHistory->getJob();
|
||||
}
|
||||
@@ -285,6 +285,11 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
return $this->jobHistories;
|
||||
}
|
||||
|
||||
public function getUserScopeHistories(): Collection
|
||||
{
|
||||
return $this->scopeHistories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection|UserJobHistory[]
|
||||
*/
|
||||
@@ -432,7 +437,7 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setEmail($email)
|
||||
public function setEmail(?string $email)
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
@@ -442,7 +447,7 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setEmailCanonical($emailCanonical)
|
||||
public function setEmailCanonical(?string $emailCanonical)
|
||||
{
|
||||
$this->emailCanonical = $emailCanonical;
|
||||
|
||||
@@ -516,7 +521,7 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setPassword($password)
|
||||
public function setPassword(string $password)
|
||||
{
|
||||
$this->password = $password;
|
||||
|
||||
@@ -526,7 +531,7 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setSalt($salt)
|
||||
public function setSalt(?string $salt)
|
||||
{
|
||||
$this->salt = $salt;
|
||||
|
||||
@@ -588,7 +593,7 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setUsernameCanonical($usernameCanonical)
|
||||
public function setUsernameCanonical(?string $usernameCanonical)
|
||||
{
|
||||
$this->usernameCanonical = $usernameCanonical;
|
||||
|
||||
|
||||
@@ -13,37 +13,49 @@ namespace Chill\MainBundle\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\Common\Collections\ReadableCollection;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'chill_main_user_group')]
|
||||
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['user_group' => UserGroup::class])]
|
||||
// this discriminator key is required for automated denormalization
|
||||
#[DiscriminatorMap('type', mapping: ['user_group' => UserGroup::class])]
|
||||
class UserGroup
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: false)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, nullable: false, options: ['default' => true])]
|
||||
private bool $active = true;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]'])]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private array $label = [];
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Collections\Collection<int, \Chill\MainBundle\Entity\User>
|
||||
* @var Collection<int, User>&Selectable<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||
#[ORM\JoinTable(name: 'chill_main_user_group_user')]
|
||||
private Collection $users;
|
||||
private Collection&Selectable $users;
|
||||
|
||||
/**
|
||||
* @var Collection<int, User>&Selectable<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||
#[ORM\JoinTable(name: 'chill_main_user_group_user_admin')]
|
||||
private Collection&Selectable $adminUsers;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => '#ffffffff'])]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private string $backgroundColor = '#ffffffff';
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => '#000000ff'])]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private string $foregroundColor = '#000000ff';
|
||||
|
||||
/**
|
||||
@@ -53,14 +65,46 @@ class UserGroup
|
||||
* An empty string means "no exclusion"
|
||||
*/
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private string $excludeKey = '';
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])]
|
||||
#[Assert\Email]
|
||||
private string $email = '';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->adminUsers = new ArrayCollection();
|
||||
$this->users = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
public function setActive(bool $active): self
|
||||
{
|
||||
$this->active = $active;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addAdminUser(User $user): self
|
||||
{
|
||||
if (!$this->adminUsers->contains($user)) {
|
||||
$this->adminUsers[] = $user;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAdminUser(User $user): self
|
||||
{
|
||||
$this->adminUsers->removeElement($user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addUser(User $user): self
|
||||
{
|
||||
if (!$this->users->contains($user)) {
|
||||
@@ -89,11 +133,22 @@ class UserGroup
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function getUsers(): Collection
|
||||
/**
|
||||
* @return Selectable<int, User>&Collection<int, User>
|
||||
*/
|
||||
public function getUsers(): Collection&Selectable
|
||||
{
|
||||
return $this->users;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Selectable<int, User>&Collection<int, User>
|
||||
*/
|
||||
public function getAdminUsers(): Collection&Selectable
|
||||
{
|
||||
return $this->adminUsers;
|
||||
}
|
||||
|
||||
public function getForegroundColor(): string
|
||||
{
|
||||
return $this->foregroundColor;
|
||||
@@ -137,6 +192,23 @@ class UserGroup
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setEmail(string $email): self
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasEmail(): bool
|
||||
{
|
||||
return '' !== $this->email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current object is an instance of the UserGroup class.
|
||||
*
|
||||
@@ -148,4 +220,25 @@ class UserGroup
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function contains(User $user): bool
|
||||
{
|
||||
return $this->users->contains($user);
|
||||
}
|
||||
|
||||
public function getUserListByLabelAscending(): ReadableCollection
|
||||
{
|
||||
$criteria = Criteria::create();
|
||||
$criteria->orderBy(['label' => Order::Ascending]);
|
||||
|
||||
return $this->getUsers()->matching($criteria);
|
||||
}
|
||||
|
||||
public function getAdminUserListByLabelAscending(): ReadableCollection
|
||||
{
|
||||
$criteria = Criteria::create();
|
||||
$criteria->orderBy(['label' => Order::Ascending]);
|
||||
|
||||
return $this->getAdminUsers()->matching($criteria);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,10 @@ use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Workflow\Validator\EntityWorkflowCreation;
|
||||
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
@@ -35,38 +36,9 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
use TrackUpdateTrait;
|
||||
|
||||
/**
|
||||
* a list of future cc users for the next steps.
|
||||
*
|
||||
* @var array|User[]
|
||||
* @var Collection<int, EntityWorkflowComment>
|
||||
*/
|
||||
public array $futureCcUsers = [];
|
||||
|
||||
/**
|
||||
* a list of future dest emails for the next steps.
|
||||
*
|
||||
* This is in used in order to let controller inform who will be the future emails which will validate
|
||||
* the next step. This is necessary to perform some computation about the next emails, before they are
|
||||
* associated to the entity EntityWorkflowStep.
|
||||
*
|
||||
* @var array|string[]
|
||||
*/
|
||||
public array $futureDestEmails = [];
|
||||
|
||||
/**
|
||||
* a list of future dest users for the next steps.
|
||||
*
|
||||
* This is in used in order to let controller inform who will be the future users which will validate
|
||||
* the next step. This is necessary to perform some computation about the next users, before they are
|
||||
* associated to the entity EntityWorkflowStep.
|
||||
*
|
||||
* @var array|User[]
|
||||
*/
|
||||
public array $futureDestUsers = [];
|
||||
|
||||
/**
|
||||
* @var Collection<EntityWorkflowComment>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: EntityWorkflowComment::class, mappedBy: 'entityWorkflow', orphanRemoval: true)]
|
||||
#[ORM\OneToMany(mappedBy: 'entityWorkflow', targetEntity: EntityWorkflowComment::class, orphanRemoval: true)]
|
||||
private Collection $comments;
|
||||
|
||||
#[ORM\Id]
|
||||
@@ -81,12 +53,12 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
private int $relatedEntityId;
|
||||
|
||||
/**
|
||||
* @var Collection<EntityWorkflowStep>
|
||||
* @var Collection<int, EntityWorkflowStep>&Selectable<int, EntityWorkflowStep>
|
||||
*/
|
||||
#[Assert\Valid(traverse: true)]
|
||||
#[ORM\OneToMany(targetEntity: EntityWorkflowStep::class, mappedBy: 'entityWorkflow', orphanRemoval: true, cascade: ['persist'])]
|
||||
#[ORM\OneToMany(mappedBy: 'entityWorkflow', targetEntity: EntityWorkflowStep::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['transitionAt' => \Doctrine\Common\Collections\Criteria::ASC, 'id' => 'ASC'])]
|
||||
private Collection $steps;
|
||||
private Collection&Selectable $steps;
|
||||
|
||||
/**
|
||||
* @var array|EntityWorkflowStep[]|null
|
||||
@@ -94,14 +66,14 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
private ?array $stepsChainedCache = null;
|
||||
|
||||
/**
|
||||
* @var Collection<User>
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||
#[ORM\JoinTable(name: 'chill_main_workflow_entity_subscriber_to_final')]
|
||||
private Collection $subscriberToFinal;
|
||||
|
||||
/**
|
||||
* @var Collection<User>
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||
#[ORM\JoinTable(name: 'chill_main_workflow_entity_subscriber_to_step')]
|
||||
@@ -115,12 +87,19 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
|
||||
private string $workflowName;
|
||||
|
||||
/**
|
||||
* @var Collection<int, EntityWorkflowAttachment>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'entityWorkflow', targetEntity: EntityWorkflowAttachment::class, cascade: ['remove'], orphanRemoval: true)]
|
||||
private Collection $attachments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->subscriberToFinal = new ArrayCollection();
|
||||
$this->subscriberToStep = new ArrayCollection();
|
||||
$this->comments = new ArrayCollection();
|
||||
$this->steps = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
|
||||
$initialStep = new EntityWorkflowStep();
|
||||
$initialStep
|
||||
@@ -170,6 +149,35 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*
|
||||
* @internal use @{EntityWorkflowAttachement::__construct} instead
|
||||
*/
|
||||
public function addAttachment(EntityWorkflowAttachment $attachment): self
|
||||
{
|
||||
if (!$this->attachments->contains($attachment)) {
|
||||
$this->attachments[] = $attachment;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, EntityWorkflowAttachment>
|
||||
*/
|
||||
public function getAttachments(): Collection
|
||||
{
|
||||
return $this->attachments;
|
||||
}
|
||||
|
||||
public function removeAttachment(EntityWorkflowAttachment $attachment): self
|
||||
{
|
||||
$this->attachments->removeElement($attachment);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getComments(): Collection
|
||||
{
|
||||
return $this->comments;
|
||||
@@ -271,17 +279,24 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
throw new \RuntimeException();
|
||||
}
|
||||
|
||||
public function getSteps(): ArrayCollection|Collection
|
||||
/**
|
||||
* @return Selectable<int, EntityWorkflowStep>&Collection<int, EntityWorkflowStep>
|
||||
*/
|
||||
public function getSteps(): Collection&Selectable
|
||||
{
|
||||
return $this->steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getStepsChained(): array
|
||||
{
|
||||
if (\is_array($this->stepsChainedCache)) {
|
||||
return $this->stepsChainedCache;
|
||||
}
|
||||
|
||||
/** @var \ArrayIterator $iterator */
|
||||
$iterator = $this->steps->getIterator();
|
||||
$current = null;
|
||||
$steps = [];
|
||||
@@ -342,7 +357,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
}
|
||||
}
|
||||
|
||||
return $usersInvolved;
|
||||
return array_values($usersInvolved);
|
||||
}
|
||||
|
||||
public function getWorkflowName(): string
|
||||
@@ -363,8 +378,6 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
|
||||
public function isFreeze(): bool
|
||||
{
|
||||
$steps = $this->getStepsChained();
|
||||
|
||||
foreach ($this->getStepsChained() as $step) {
|
||||
if ($step->isFreezeAfter()) {
|
||||
return true;
|
||||
@@ -374,6 +387,22 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isOnHoldByUser(User $user): bool
|
||||
{
|
||||
return $this->getCurrentStep()->isOnHoldByUser($user);
|
||||
}
|
||||
|
||||
public function isUserInvolved(User $user): bool
|
||||
{
|
||||
foreach ($this->getSteps() as $step) {
|
||||
if ($step->getAllDestUser()->contains($user)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isUserSubscribedToFinal(User $user): bool
|
||||
{
|
||||
return $this->subscriberToFinal->contains($user);
|
||||
@@ -438,15 +467,59 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Method use by marking store.
|
||||
* Method used by marking store.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setStep(string $step): self
|
||||
{
|
||||
public function setStep(
|
||||
string $step,
|
||||
WorkflowTransitionContextDTO $transitionContextDTO,
|
||||
string $transition,
|
||||
\DateTimeImmutable $transitionAt,
|
||||
?User $byUser = null,
|
||||
): self {
|
||||
$previousStep = $this->getCurrentStep();
|
||||
|
||||
$previousStep
|
||||
->setComment($transitionContextDTO->comment)
|
||||
->setTransitionAfter($transition)
|
||||
->setTransitionAt($transitionAt)
|
||||
->setTransitionBy($byUser);
|
||||
|
||||
$newStep = new EntityWorkflowStep();
|
||||
$newStep->setCurrentStep($step);
|
||||
|
||||
foreach ($transitionContextDTO->futureCcUsers as $user) {
|
||||
$newStep->addCcUser($user);
|
||||
}
|
||||
|
||||
foreach ($transitionContextDTO->getFutureDestUsers() as $user) {
|
||||
$newStep->addDestUser($user);
|
||||
}
|
||||
|
||||
foreach ($transitionContextDTO->getFutureDestUserGroups() as $userGroup) {
|
||||
$newStep->addDestUserGroup($userGroup);
|
||||
}
|
||||
|
||||
if (null !== $transitionContextDTO->futureUserSignature) {
|
||||
$newStep->addDestUser($transitionContextDTO->futureUserSignature);
|
||||
}
|
||||
|
||||
if (null !== $transitionContextDTO->futureUserSignature) {
|
||||
new EntityWorkflowStepSignature($newStep, $transitionContextDTO->futureUserSignature);
|
||||
} else {
|
||||
foreach ($transitionContextDTO->futurePersonSignatures as $personSignature) {
|
||||
new EntityWorkflowStepSignature($newStep, $personSignature);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($transitionContextDTO->futureDestineeThirdParties as $thirdParty) {
|
||||
new EntityWorkflowSend($newStep, $thirdParty, $transitionAt->add(new \DateInterval('P30D')));
|
||||
}
|
||||
foreach ($transitionContextDTO->futureDestineeEmails as $email) {
|
||||
new EntityWorkflowSend($newStep, $email, $transitionAt->add(new \DateInterval('P30D')));
|
||||
}
|
||||
|
||||
// copy the freeze
|
||||
if ($this->isFreeze()) {
|
||||
$newStep->setFreezeAfter(true);
|
||||
@@ -472,4 +545,41 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
|
||||
return $this->steps->get($this->steps->count() - 2);
|
||||
}
|
||||
|
||||
public function isOnHoldAtCurrentStep(): bool
|
||||
{
|
||||
return $this->getCurrentStep()->getHoldsOnStep()->count() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the workflow has become stale after a given date.
|
||||
*
|
||||
* This function checks the creation date and the transition states of the workflow steps.
|
||||
* A workflow is considered stale if:
|
||||
* - The creation date is before the given date and no transitions have occurred since the creation.
|
||||
* - Or if there are no transitions after the given date.
|
||||
*
|
||||
* @param \DateTimeImmutable $at the date to compare against the workflow's status
|
||||
*
|
||||
* @return bool true if the workflow is stale after the given date, false otherwise
|
||||
*/
|
||||
public function isStaledAt(\DateTimeImmutable $at): bool
|
||||
{
|
||||
// if there is no transition since the creation, then the workflow is staled
|
||||
if ('initial' === $this->getCurrentStep()->getCurrentStep()
|
||||
&& null === $this->getCurrentStep()->getTransitionAt()
|
||||
) {
|
||||
if (null === $this->getCreatedAt()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->getCreatedAt() < $at) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->getCurrentStepChained()->getPrevious()->getTransitionAt() < $at;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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\Entity\Workflow;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity()]
|
||||
#[ORM\Table(name: 'chill_main_workflow_entity_attachment')]
|
||||
#[ORM\UniqueConstraint(name: 'unique_generic_doc_by_workflow', columns: ['relatedGenericDocKey', 'relatedGenericDocIdentifiers', 'entityworkflow_id'])]
|
||||
class EntityWorkflowAttachment implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
use TrackUpdateTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
private ?int $id = null;
|
||||
|
||||
public function __construct(
|
||||
#[ORM\Column(name: 'relatedGenericDocKey', type: Types::STRING, length: 255, nullable: false)]
|
||||
private string $relatedGenericDocKey,
|
||||
#[ORM\Column(name: 'relatedGenericDocIdentifiers', type: Types::JSON, nullable: false, options: ['jsonb' => true])]
|
||||
private array $relatedGenericDocIdentifiers,
|
||||
#[ORM\ManyToOne(targetEntity: EntityWorkflow::class, inversedBy: 'attachments')]
|
||||
#[ORM\JoinColumn(nullable: false, name: 'entityworkflow_id')]
|
||||
private EntityWorkflow $entityWorkflow,
|
||||
|
||||
/**
|
||||
* Stored object related to the generic doc.
|
||||
*
|
||||
* This is a story to keep track more easily to stored object
|
||||
*/
|
||||
#[ORM\ManyToOne(targetEntity: StoredObject::class)]
|
||||
#[ORM\JoinColumn(nullable: false, name: 'storedobject_id')]
|
||||
private StoredObject $proxyStoredObject,
|
||||
) {
|
||||
$this->entityWorkflow->addAttachment($this);
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getEntityWorkflow(): EntityWorkflow
|
||||
{
|
||||
return $this->entityWorkflow;
|
||||
}
|
||||
|
||||
public function getRelatedGenericDocIdentifiers(): array
|
||||
{
|
||||
return $this->relatedGenericDocIdentifiers;
|
||||
}
|
||||
|
||||
public function getRelatedGenericDocKey(): string
|
||||
{
|
||||
return $this->relatedGenericDocKey;
|
||||
}
|
||||
|
||||
public function getProxyStoredObject(): StoredObject
|
||||
{
|
||||
return $this->proxyStoredObject;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,11 @@ use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Contains comment for entity workflow.
|
||||
*
|
||||
* **NOTE**: for now, this class is not in used. Comments are, for now, stored in the EntityWorkflowStep.
|
||||
*/
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table('chill_main_workflow_entity_comment')]
|
||||
class EntityWorkflowComment implements TrackCreationInterface, TrackUpdateInterface
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
<?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\Entity\Workflow;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use Random\Randomizer;
|
||||
|
||||
/**
|
||||
* An entity which stores then sending of a workflow's content to
|
||||
* some external entity.
|
||||
*/
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'chill_main_workflow_entity_send')]
|
||||
class EntityWorkflowSend implements TrackCreationInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: ThirdParty::class)]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?ThirdParty $destineeThirdParty = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT, nullable: false, options: ['default' => ''])]
|
||||
private string $destineeEmail = '';
|
||||
|
||||
#[ORM\Column(type: 'uuid', unique: true, nullable: false)]
|
||||
private UuidInterface $uuid;
|
||||
|
||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: false)]
|
||||
private string $privateToken;
|
||||
|
||||
#[ORM\Column(type: Types::INTEGER, nullable: false, options: ['default' => 0])]
|
||||
private int $numberOfErrorTrials = 0;
|
||||
|
||||
/**
|
||||
* @var Collection<int, EntityWorkflowSendView>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'send', targetEntity: EntityWorkflowSendView::class, cascade: ['remove'])]
|
||||
private Collection $views;
|
||||
|
||||
public function __construct(
|
||||
#[ORM\ManyToOne(targetEntity: EntityWorkflowStep::class, inversedBy: 'sends')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private EntityWorkflowStep $entityWorkflowStep,
|
||||
string|ThirdParty $destinee,
|
||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: false)]
|
||||
private \DateTimeImmutable $expireAt,
|
||||
) {
|
||||
$this->uuid = Uuid::uuid4();
|
||||
$random = new Randomizer();
|
||||
$this->privateToken = bin2hex($random->getBytes(48));
|
||||
|
||||
$this->entityWorkflowStep->addSend($this);
|
||||
|
||||
if ($destinee instanceof ThirdParty) {
|
||||
$this->destineeThirdParty = $destinee;
|
||||
} else {
|
||||
$this->destineeEmail = $destinee;
|
||||
}
|
||||
|
||||
$this->views = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal use the @see{EntityWorkflowSendView}'s constructor instead
|
||||
*/
|
||||
public function addView(EntityWorkflowSendView $view): self
|
||||
{
|
||||
if (!$this->views->contains($view)) {
|
||||
$this->views->add($view);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDestineeEmail(): string
|
||||
{
|
||||
return $this->destineeEmail;
|
||||
}
|
||||
|
||||
public function getDestineeThirdParty(): ?ThirdParty
|
||||
{
|
||||
return $this->destineeThirdParty;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getNumberOfErrorTrials(): int
|
||||
{
|
||||
return $this->numberOfErrorTrials;
|
||||
}
|
||||
|
||||
public function getPrivateToken(): string
|
||||
{
|
||||
return $this->privateToken;
|
||||
}
|
||||
|
||||
public function getEntityWorkflowStep(): EntityWorkflowStep
|
||||
{
|
||||
return $this->entityWorkflowStep;
|
||||
}
|
||||
|
||||
public function getEntityWorkflowStepChained(): ?EntityWorkflowStep
|
||||
{
|
||||
foreach ($this->getEntityWorkflowStep()->getEntityWorkflow()->getStepsChained() as $step) {
|
||||
if ($this->getEntityWorkflowStep() === $step) {
|
||||
return $step;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getUuid(): UuidInterface
|
||||
{
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function getExpireAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->expireAt;
|
||||
}
|
||||
|
||||
public function getViews(): Collection
|
||||
{
|
||||
return $this->views;
|
||||
}
|
||||
|
||||
public function increaseErrorTrials(): void
|
||||
{
|
||||
$this->numberOfErrorTrials = $this->numberOfErrorTrials + 1;
|
||||
}
|
||||
|
||||
public function getDestinee(): string|ThirdParty
|
||||
{
|
||||
if (null !== $this->getDestineeThirdParty()) {
|
||||
return $this->getDestineeThirdParty();
|
||||
}
|
||||
|
||||
return $this->getDestineeEmail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the kind of destinee based on whether the destinee is a thirdParty or an emailAddress.
|
||||
*
|
||||
* @return 'thirdParty'|'email' 'thirdParty' if the destinee is a third party, 'email' otherwise
|
||||
*/
|
||||
public function getDestineeKind(): string
|
||||
{
|
||||
if (null !== $this->getDestineeThirdParty()) {
|
||||
return 'thirdParty';
|
||||
}
|
||||
|
||||
return 'email';
|
||||
}
|
||||
|
||||
public function isViewed(): bool
|
||||
{
|
||||
return $this->views->count() > 0;
|
||||
}
|
||||
|
||||
public function isExpired(?\DateTimeImmutable $now = null): bool
|
||||
{
|
||||
return ($now ?? new \DateTimeImmutable('now')) >= $this->expireAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the most recent view.
|
||||
*
|
||||
* @return EntityWorkflowSendView|null returns the last view or null if there are no views
|
||||
*/
|
||||
public function getLastView(): ?EntityWorkflowSendView
|
||||
{
|
||||
$last = null;
|
||||
foreach ($this->views as $view) {
|
||||
if (null === $last) {
|
||||
$last = $view;
|
||||
} else {
|
||||
if ($view->getViewAt() > $last->getViewAt()) {
|
||||
$last = $view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of views grouped by their remote IP address.
|
||||
*
|
||||
* @return array<string, list<EntityWorkflowSendView>> an associative array where the keys are IP addresses and the values are arrays of views associated with those IPs
|
||||
*/
|
||||
public function getViewsByIp(): array
|
||||
{
|
||||
$views = [];
|
||||
|
||||
foreach ($this->getViews() as $view) {
|
||||
$views[$view->getRemoteIp()][] = $view;
|
||||
}
|
||||
|
||||
return $views;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?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\Entity\Workflow;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Register the viewing action from an external destinee.
|
||||
*/
|
||||
#[ORM\Entity(readOnly: true)]
|
||||
#[ORM\Table(name: 'chill_main_workflow_entity_send_views')]
|
||||
class EntityWorkflowSendView
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
private ?int $id = null;
|
||||
|
||||
public function __construct(
|
||||
#[ORM\ManyToOne(targetEntity: EntityWorkflowSend::class, inversedBy: 'views')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private EntityWorkflowSend $send,
|
||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
|
||||
private \DateTimeInterface $viewAt,
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
private string $remoteIp = '',
|
||||
) {
|
||||
$this->send->addView($this);
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getRemoteIp(): string
|
||||
{
|
||||
return $this->remoteIp;
|
||||
}
|
||||
|
||||
public function getSend(): EntityWorkflowSend
|
||||
{
|
||||
return $this->send;
|
||||
}
|
||||
|
||||
public function getViewAt(): \DateTimeInterface
|
||||
{
|
||||
return $this->viewAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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\Entity\Workflow;
|
||||
|
||||
enum EntityWorkflowSignatureStateEnum: string
|
||||
{
|
||||
case PENDING = 'pending';
|
||||
case SIGNED = 'signed';
|
||||
case REJECTED = 'rejected';
|
||||
case CANCELED = 'canceled';
|
||||
}
|
||||
@@ -12,12 +12,22 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Entity\Workflow;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\UserGroup;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
/**
|
||||
* A step for each EntityWorkflow.
|
||||
*
|
||||
* The step contains the history of position. The current one is the one which transitionAt or transitionAfter is NULL.
|
||||
*
|
||||
* The comments field is populated by the comment of the one who apply the transition, it means that the comment for the
|
||||
* "next" step is stored in the EntityWorkflowStep in the previous step.
|
||||
*
|
||||
* DestUsers are the one added at the transition. DestUserByAccessKey are the users who obtained permission after having
|
||||
* clicked on a link to get access (email notification to groups).
|
||||
*/
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table('chill_main_workflow_entity_step')]
|
||||
class EntityWorkflowStep
|
||||
@@ -26,7 +36,7 @@ class EntityWorkflowStep
|
||||
private string $accessKey;
|
||||
|
||||
/**
|
||||
* @var Collection<User>
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||
#[ORM\JoinTable(name: 'chill_main_workflow_entity_step_cc_user')]
|
||||
@@ -42,19 +52,32 @@ class EntityWorkflowStep
|
||||
private array $destEmail = [];
|
||||
|
||||
/**
|
||||
* @var Collection<User>
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||
#[ORM\JoinTable(name: 'chill_main_workflow_entity_step_user')]
|
||||
private Collection $destUser;
|
||||
|
||||
/**
|
||||
* @var Collection<User>
|
||||
* @var Collection<int, UserGroup>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: UserGroup::class)]
|
||||
#[ORM\JoinTable(name: 'chill_main_workflow_entity_step_user_group')]
|
||||
private Collection $destUserGroups;
|
||||
|
||||
/**
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||
#[ORM\JoinTable(name: 'chill_main_workflow_entity_step_user_by_accesskey')]
|
||||
private Collection $destUserByAccessKey;
|
||||
|
||||
/**
|
||||
* @var Collection <int, EntityWorkflowStepSignature>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'step', targetEntity: EntityWorkflowStepSignature::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
private Collection $signatures;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: EntityWorkflow::class, inversedBy: 'steps')]
|
||||
private ?EntityWorkflow $entityWorkflow = null;
|
||||
|
||||
@@ -66,6 +89,11 @@ class EntityWorkflowStep
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* If this is the final step.
|
||||
*
|
||||
* This property is filled by a listener.
|
||||
*/
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])]
|
||||
private bool $isFinal = false;
|
||||
|
||||
@@ -92,11 +120,27 @@ class EntityWorkflowStep
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: true)]
|
||||
private ?string $transitionByEmail = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, EntityWorkflowStepHold>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'step', targetEntity: EntityWorkflowStepHold::class)]
|
||||
private Collection $holdsOnStep;
|
||||
|
||||
/**
|
||||
* @var Collection<int, EntityWorkflowSend>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'entityWorkflowStep', targetEntity: EntityWorkflowSend::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
private Collection $sends;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->ccUser = new ArrayCollection();
|
||||
$this->destUser = new ArrayCollection();
|
||||
$this->destUserGroups = new ArrayCollection();
|
||||
$this->destUserByAccessKey = new ArrayCollection();
|
||||
$this->signatures = new ArrayCollection();
|
||||
$this->holdsOnStep = new ArrayCollection();
|
||||
$this->sends = new ArrayCollection();
|
||||
$this->accessKey = bin2hex(openssl_random_pseudo_bytes(32));
|
||||
}
|
||||
|
||||
@@ -109,6 +153,9 @@ class EntityWorkflowStep
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function addDestEmail(string $email): self
|
||||
{
|
||||
if (!\in_array($email, $this->destEmail, true)) {
|
||||
@@ -127,6 +174,22 @@ class EntityWorkflowStep
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addDestUserGroup(UserGroup $userGroup): self
|
||||
{
|
||||
if (!$this->destUserGroups->contains($userGroup)) {
|
||||
$this->destUserGroups[] = $userGroup;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeDestUserGroup(UserGroup $userGroup): self
|
||||
{
|
||||
$this->destUserGroups->removeElement($userGroup);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addDestUserByAccessKey(User $user): self
|
||||
{
|
||||
if (!$this->destUserByAccessKey->contains($user) && !$this->destUser->contains($user)) {
|
||||
@@ -136,6 +199,39 @@ class EntityWorkflowStep
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal use @see{EntityWorkflowStepSignature}'s constructor instead
|
||||
*/
|
||||
public function addSignature(EntityWorkflowStepSignature $signature): self
|
||||
{
|
||||
if (!$this->signatures->contains($signature)) {
|
||||
$this->signatures[] = $signature;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal use @see{EntityWorkflowSend}'s constructor instead
|
||||
*/
|
||||
public function addSend(EntityWorkflowSend $send): self
|
||||
{
|
||||
if (!$this->sends->contains($send)) {
|
||||
$this->sends[] = $send;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeSignature(EntityWorkflowStepSignature $signature): self
|
||||
{
|
||||
if ($this->signatures->contains($signature)) {
|
||||
$this->signatures->removeElement($signature);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAccessKey(): string
|
||||
{
|
||||
return $this->accessKey;
|
||||
@@ -143,7 +239,9 @@ class EntityWorkflowStep
|
||||
|
||||
/**
|
||||
* get all the users which are allowed to apply a transition: those added manually, and
|
||||
* those added automatically bu using an access key.
|
||||
* those added automatically by using an access key.
|
||||
*
|
||||
* This method exclude the users associated with user groups
|
||||
*
|
||||
* @psalm-suppress DuplicateArrayKey
|
||||
*/
|
||||
@@ -157,11 +255,24 @@ class EntityWorkflowStep
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, UserGroup>
|
||||
*/
|
||||
public function getDestUserGroups(): Collection
|
||||
{
|
||||
return $this->destUserGroups;
|
||||
}
|
||||
|
||||
public function getCcUser(): Collection
|
||||
{
|
||||
return $this->ccUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the comment from the one who apply the transition.
|
||||
*
|
||||
* It means that it must be saved when the user apply a transition.
|
||||
*/
|
||||
public function getComment(): string
|
||||
{
|
||||
return $this->comment;
|
||||
@@ -172,6 +283,11 @@ class EntityWorkflowStep
|
||||
return $this->currentStep;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string>
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public function getDestEmail(): array
|
||||
{
|
||||
return $this->destEmail;
|
||||
@@ -198,6 +314,22 @@ class EntityWorkflowStep
|
||||
return $this->entityWorkflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, EntityWorkflowStepSignature>
|
||||
*/
|
||||
public function getSignatures(): Collection
|
||||
{
|
||||
return $this->signatures;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, EntityWorkflowSend>
|
||||
*/
|
||||
public function getSends(): Collection
|
||||
{
|
||||
return $this->sends;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
@@ -233,6 +365,9 @@ class EntityWorkflowStep
|
||||
return $this->transitionByEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool true if this is the end of the EntityWorkflow
|
||||
*/
|
||||
public function isFinal(): bool
|
||||
{
|
||||
return $this->isFinal;
|
||||
@@ -243,6 +378,20 @@ class EntityWorkflowStep
|
||||
return $this->freezeAfter;
|
||||
}
|
||||
|
||||
public function isOnHoldByUser(User $user): bool
|
||||
{
|
||||
foreach ($this->getHoldsOnStep() as $onHold) {
|
||||
if ($onHold->getByUser() === $user) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool if the EntityWorkflowStep is waiting for a transition, and is not the final step
|
||||
*/
|
||||
public function isWaitingForTransition(): bool
|
||||
{
|
||||
if (null !== $this->transitionAfter) {
|
||||
@@ -377,23 +526,17 @@ class EntityWorkflowStep
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[Assert\Callback]
|
||||
public function validateOnCreation(ExecutionContextInterface $context, mixed $payload): void
|
||||
public function getHoldsOnStep(): Collection
|
||||
{
|
||||
return;
|
||||
return $this->holdsOnStep;
|
||||
}
|
||||
|
||||
if ($this->isFinalizeAfter()) {
|
||||
if (0 !== \count($this->getDestUser())) {
|
||||
$context->buildViolation('workflow.No dest users when the workflow is finalized')
|
||||
->atPath('finalizeAfter')
|
||||
->addViolation();
|
||||
}
|
||||
} else {
|
||||
if (0 === \count($this->getDestUser())) {
|
||||
$context->buildViolation('workflow.The next step must count at least one dest')
|
||||
->atPath('finalizeAfter')
|
||||
->addViolation();
|
||||
}
|
||||
public function addOnHold(EntityWorkflowStepHold $onHold): self
|
||||
{
|
||||
if (!$this->holdsOnStep->contains($onHold)) {
|
||||
$this->holdsOnStep->add($onHold);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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\Entity\Workflow;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table('chill_main_workflow_entity_step_hold')]
|
||||
#[ORM\UniqueConstraint(name: 'chill_main_workflow_hold_unique_idx', columns: ['step_id', 'byUser_id'])]
|
||||
class EntityWorkflowStepHold implements TrackCreationInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
||||
private ?int $id = null;
|
||||
|
||||
public function __construct(#[ORM\ManyToOne(targetEntity: EntityWorkflowStep::class)]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private EntityWorkflowStep $step, #[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private User $byUser)
|
||||
{
|
||||
$step->addOnHold($this);
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getStep(): EntityWorkflowStep
|
||||
{
|
||||
return $this->step;
|
||||
}
|
||||
|
||||
public function getByUser(): User
|
||||
{
|
||||
return $this->byUser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?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\Entity\Workflow;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'chill_main_workflow_entity_step_signature')]
|
||||
class EntityWorkflowStepSignature implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
use TrackUpdateTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, unique: true)]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?User $userSigner = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Person::class)]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?Person $personSigner = null;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 50, nullable: false, enumType: EntityWorkflowSignatureStateEnum::class)]
|
||||
private EntityWorkflowSignatureStateEnum $state = EntityWorkflowSignatureStateEnum::PENDING;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIMETZ_IMMUTABLE, nullable: true, options: ['default' => null])]
|
||||
private ?\DateTimeImmutable $stateDate = null;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]'])]
|
||||
private array $signatureMetadata = [];
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true, options: ['default' => null])]
|
||||
private ?int $zoneSignatureIndex = null;
|
||||
|
||||
public function __construct(
|
||||
#[ORM\ManyToOne(targetEntity: EntityWorkflowStep::class, inversedBy: 'signatures')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private EntityWorkflowStep $step,
|
||||
User|Person $signer,
|
||||
) {
|
||||
$this->step->addSignature($this);
|
||||
$this->setSigner($signer);
|
||||
}
|
||||
|
||||
private function setSigner(User|Person $signer): void
|
||||
{
|
||||
if ($signer instanceof User) {
|
||||
$this->userSigner = $signer;
|
||||
} else {
|
||||
$this->personSigner = $signer;
|
||||
}
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getStep(): EntityWorkflowStep
|
||||
{
|
||||
return $this->step;
|
||||
}
|
||||
|
||||
public function getSigner(): User|Person
|
||||
{
|
||||
if (null !== $this->userSigner) {
|
||||
return $this->userSigner;
|
||||
}
|
||||
|
||||
return $this->personSigner;
|
||||
}
|
||||
|
||||
public function getSignatureMetadata(): array
|
||||
{
|
||||
return $this->signatureMetadata;
|
||||
}
|
||||
|
||||
public function setSignatureMetadata(array $signatureMetadata): EntityWorkflowStepSignature
|
||||
{
|
||||
$this->signatureMetadata = $signatureMetadata;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getState(): EntityWorkflowSignatureStateEnum
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*
|
||||
* @internal You should not use this method directly, use @see{Chill\MainBundle\Workflow\SignatureStepStateChanger} instead
|
||||
*/
|
||||
public function setState(EntityWorkflowSignatureStateEnum $state): EntityWorkflowStepSignature
|
||||
{
|
||||
$this->state = $state;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStateDate(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->stateDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*
|
||||
* @internal You should not use this method directly, use @see{Chill\MainBundle\Workflow\SignatureStepStateChanger} instead
|
||||
*/
|
||||
public function setStateDate(?\DateTimeImmutable $stateDate): EntityWorkflowStepSignature
|
||||
{
|
||||
$this->stateDate = $stateDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getZoneSignatureIndex(): ?int
|
||||
{
|
||||
return $this->zoneSignatureIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*
|
||||
* @internal You should not use this method directly, use @see{Chill\MainBundle\Workflow\SignatureStepStateChanger} instead
|
||||
*/
|
||||
public function setZoneSignatureIndex(?int $zoneSignatureIndex): EntityWorkflowStepSignature
|
||||
{
|
||||
$this->zoneSignatureIndex = $zoneSignatureIndex;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isSigned(): bool
|
||||
{
|
||||
return EntityWorkflowSignatureStateEnum::SIGNED == $this->getState();
|
||||
}
|
||||
|
||||
public function isPending(): bool
|
||||
{
|
||||
return EntityWorkflowSignatureStateEnum::PENDING == $this->getState();
|
||||
}
|
||||
|
||||
public function isCanceled(): bool
|
||||
{
|
||||
return EntityWorkflowSignatureStateEnum::CANCELED === $this->getState();
|
||||
}
|
||||
|
||||
public function isRejected(): bool
|
||||
{
|
||||
return EntityWorkflowSignatureStateEnum::REJECTED === $this->getState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether all signatures associated with a given workflow step are not pending.
|
||||
*
|
||||
* Iterates over each signature in the provided workflow step, and returns false if any signature
|
||||
* is found to be pending. If all signatures are not pending, returns true.
|
||||
*
|
||||
* @param EntityWorkflowStep $step the workflow step whose signatures are to be checked
|
||||
*
|
||||
* @return bool true if all signatures are not pending, false otherwise
|
||||
*/
|
||||
public static function isAllSignatureNotPendingForStep(EntityWorkflowStep $step): bool
|
||||
{
|
||||
foreach ($step->getSignatures() as $signature) {
|
||||
if ($signature->isPending()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 'person'|'user'
|
||||
*/
|
||||
public function getSignerKind(): string
|
||||
{
|
||||
if ($this->personSigner instanceof Person) {
|
||||
return 'person';
|
||||
}
|
||||
|
||||
return 'user';
|
||||
}
|
||||
}
|
||||
@@ -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\Export;
|
||||
|
||||
/**
|
||||
* Transform data from filter.
|
||||
*
|
||||
* This interface defines a method for transforming filter's form data before it is processed.
|
||||
*
|
||||
* You can implement this interface on @see{FilterInterface} or @see{AggregatorInterface}, to allow to transform existing data in saved exports
|
||||
* and replace it with some default values, or new default values.
|
||||
*/
|
||||
interface DataTransformerInterface
|
||||
{
|
||||
public function transformData(?array $before): array;
|
||||
}
|
||||
@@ -126,7 +126,7 @@ final readonly class ExportFormHelper
|
||||
|
||||
private function savedExportDataToFormDataStepExport(
|
||||
SavedExport $savedExport,
|
||||
array $formOptions
|
||||
array $formOptions,
|
||||
): array {
|
||||
$builder = $this->formFactory
|
||||
->createBuilder(
|
||||
@@ -148,7 +148,7 @@ final readonly class ExportFormHelper
|
||||
|
||||
private function savedExportDataToFormDataStepFormatter(
|
||||
SavedExport $savedExport,
|
||||
array $formOptions
|
||||
array $formOptions,
|
||||
): array {
|
||||
$builder = $this->formFactory
|
||||
->createBuilder(
|
||||
|
||||
@@ -62,7 +62,7 @@ class ExportManager
|
||||
private readonly TokenStorageInterface $tokenStorage,
|
||||
iterable $exports,
|
||||
iterable $aggregators,
|
||||
iterable $filters
|
||||
iterable $filters,
|
||||
// iterable $formatters,
|
||||
// iterable $exportElementProvider
|
||||
) {
|
||||
@@ -190,7 +190,7 @@ class ExportManager
|
||||
// throw an error if the export require other modifier, which is
|
||||
// not allowed when the export return a `NativeQuery`
|
||||
if (\count($export->supportsModifiers()) > 0) {
|
||||
throw new \LogicException("The export with alias `{$exportAlias}` return ".'a `\\Doctrine\\ORM\\NativeQuery` and supports modifiers, which is not allowed. Either the method `supportsModifiers` should return an empty array, or return a `Doctrine\\ORM\\QueryBuilder`');
|
||||
throw new \LogicException("The export with alias `{$exportAlias}` return ".'a `\Doctrine\ORM\NativeQuery` and supports modifiers, which is not allowed. Either the method `supportsModifiers` should return an empty array, or return a `Doctrine\ORM\QueryBuilder`');
|
||||
}
|
||||
} elseif ($query instanceof QueryBuilder) {
|
||||
// handle filters
|
||||
@@ -203,7 +203,7 @@ class ExportManager
|
||||
'dql' => $query->getDQL(),
|
||||
]);
|
||||
} else {
|
||||
throw new \UnexpectedValueException('The method `intiateQuery` should return a `\\Doctrine\\ORM\\NativeQuery` or a `Doctrine\\ORM\\QueryBuilder` object.');
|
||||
throw new \UnexpectedValueException('The method `intiateQuery` should return a `\Doctrine\ORM\NativeQuery` or a `Doctrine\ORM\QueryBuilder` object.');
|
||||
}
|
||||
|
||||
$result = $export->getResult($query, $data[ExportType::EXPORT_KEY]);
|
||||
@@ -451,7 +451,7 @@ class ExportManager
|
||||
public function isGrantedForElement(
|
||||
DirectExportInterface|ExportInterface|ModifierInterface $element,
|
||||
DirectExportInterface|ExportInterface|null $export = null,
|
||||
?array $centers = null
|
||||
?array $centers = null,
|
||||
): bool {
|
||||
if ($element instanceof ExportInterface || $element instanceof DirectExportInterface) {
|
||||
$role = $element->requiredRole();
|
||||
@@ -533,7 +533,7 @@ class ExportManager
|
||||
ExportInterface $export,
|
||||
QueryBuilder $qb,
|
||||
array $data,
|
||||
array $center
|
||||
array $center,
|
||||
) {
|
||||
$aggregators = $this->retrieveUsedAggregators($data);
|
||||
|
||||
@@ -560,7 +560,7 @@ class ExportManager
|
||||
ExportInterface $export,
|
||||
QueryBuilder $qb,
|
||||
mixed $data,
|
||||
array $centers
|
||||
array $centers,
|
||||
) {
|
||||
$filters = $this->retrieveUsedFilters($data);
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ interface FilterInterface extends ModifierInterface
|
||||
|
||||
/**
|
||||
* Get the default data, that can be use as "data" for the form.
|
||||
*
|
||||
* In case of adding new parameters to a filter, you can implement a @see{DataTransformerFilterInterface} to
|
||||
* transforme the filters's data saved in an export to the desired state.
|
||||
*/
|
||||
public function getFormDefaultData(): array;
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class CSVFormatter implements FormatterInterface
|
||||
|
||||
public function __construct(
|
||||
protected TranslatorInterface $translator,
|
||||
ExportManager $manager
|
||||
ExportManager $manager,
|
||||
) {
|
||||
$this->exportManager = $manager;
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class CSVFormatter implements FormatterInterface
|
||||
$exportAlias,
|
||||
array $exportData,
|
||||
array $filtersData,
|
||||
array $aggregatorsData
|
||||
array $aggregatorsData,
|
||||
) {
|
||||
$this->result = $result;
|
||||
$this->orderingHeaders($formatterData);
|
||||
|
||||
@@ -68,7 +68,7 @@ class CSVListFormatter implements FormatterInterface
|
||||
public function buildForm(
|
||||
FormBuilderInterface $builder,
|
||||
$exportAlias,
|
||||
array $aggregatorAliases
|
||||
array $aggregatorAliases,
|
||||
) {
|
||||
$builder->add('numerotation', ChoiceType::class, [
|
||||
'choices' => [
|
||||
@@ -108,7 +108,7 @@ class CSVListFormatter implements FormatterInterface
|
||||
$exportAlias,
|
||||
array $exportData,
|
||||
array $filtersData,
|
||||
array $aggregatorsData
|
||||
array $aggregatorsData,
|
||||
) {
|
||||
$this->result = $result;
|
||||
$this->exportAlias = $exportAlias;
|
||||
|
||||
@@ -66,7 +66,7 @@ class CSVPivotedListFormatter implements FormatterInterface
|
||||
public function buildForm(
|
||||
FormBuilderInterface $builder,
|
||||
$exportAlias,
|
||||
array $aggregatorAliases
|
||||
array $aggregatorAliases,
|
||||
) {
|
||||
$builder->add('numerotation', ChoiceType::class, [
|
||||
'choices' => [
|
||||
@@ -107,7 +107,7 @@ class CSVPivotedListFormatter implements FormatterInterface
|
||||
$exportAlias,
|
||||
array $exportData,
|
||||
array $filtersData,
|
||||
array $aggregatorsData
|
||||
array $aggregatorsData,
|
||||
) {
|
||||
$this->result = $result;
|
||||
$this->exportAlias = $exportAlias;
|
||||
|
||||
@@ -129,7 +129,7 @@ class SpreadSheetFormatter implements FormatterInterface
|
||||
public function buildForm(
|
||||
FormBuilderInterface $builder,
|
||||
$exportAlias,
|
||||
array $aggregatorAliases
|
||||
array $aggregatorAliases,
|
||||
) {
|
||||
// choosing between formats
|
||||
$builder->add('format', ChoiceType::class, [
|
||||
@@ -178,7 +178,7 @@ class SpreadSheetFormatter implements FormatterInterface
|
||||
$exportAlias,
|
||||
array $exportData,
|
||||
array $filtersData,
|
||||
array $aggregatorsData
|
||||
array $aggregatorsData,
|
||||
): Response {
|
||||
// store all data when the process is initiated
|
||||
$this->result = $result;
|
||||
@@ -219,7 +219,7 @@ class SpreadSheetFormatter implements FormatterInterface
|
||||
protected function addContentTable(
|
||||
Worksheet $worksheet,
|
||||
$sortedResults,
|
||||
$line
|
||||
$line,
|
||||
) {
|
||||
$worksheet->fromArray(
|
||||
$sortedResults,
|
||||
@@ -274,7 +274,7 @@ class SpreadSheetFormatter implements FormatterInterface
|
||||
protected function addHeaders(
|
||||
Worksheet &$worksheet,
|
||||
array $globalKeys,
|
||||
$line
|
||||
$line,
|
||||
) {
|
||||
// get the displayable form of headers
|
||||
$displayables = [];
|
||||
|
||||
@@ -72,7 +72,7 @@ class SpreadsheetListFormatter implements FormatterInterface
|
||||
public function buildForm(
|
||||
FormBuilderInterface $builder,
|
||||
$exportAlias,
|
||||
array $aggregatorAliases
|
||||
array $aggregatorAliases,
|
||||
) {
|
||||
$builder
|
||||
->add('format', ChoiceType::class, [
|
||||
@@ -120,7 +120,7 @@ class SpreadsheetListFormatter implements FormatterInterface
|
||||
$exportAlias,
|
||||
array $exportData,
|
||||
array $filtersData,
|
||||
array $aggregatorsData
|
||||
array $aggregatorsData,
|
||||
) {
|
||||
$this->result = $result;
|
||||
$this->exportAlias = $exportAlias;
|
||||
|
||||
@@ -31,7 +31,7 @@ interface FormatterInterface
|
||||
public function buildForm(
|
||||
FormBuilderInterface $builder,
|
||||
$exportAlias,
|
||||
array $aggregatorAliases
|
||||
array $aggregatorAliases,
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -58,7 +58,7 @@ interface FormatterInterface
|
||||
$exportAlias,
|
||||
array $exportData,
|
||||
array $filtersData,
|
||||
array $aggregatorsData
|
||||
array $aggregatorsData,
|
||||
);
|
||||
|
||||
public function getType();
|
||||
|
||||
@@ -35,7 +35,7 @@ class IdToEntityDataTransformer implements DataTransformerInterface
|
||||
public function __construct(
|
||||
private readonly ObjectRepository $repository,
|
||||
private readonly bool $multiple = false,
|
||||
?callable $getId = null
|
||||
?callable $getId = null,
|
||||
) {
|
||||
$this->getId = $getId ?? static fn (object $o) => $o->getId();
|
||||
}
|
||||
|
||||
64
src/Bundle/ChillMainBundle/Form/GenderType.php
Normal file
64
src/Bundle/ChillMainBundle/Form/GenderType.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?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\Form;
|
||||
|
||||
use Chill\MainBundle\Entity\Gender;
|
||||
use Chill\MainBundle\Entity\GenderEnum;
|
||||
use Chill\MainBundle\Entity\GenderIconEnum;
|
||||
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class GenderType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('label', TranslatableStringFormType::class, [
|
||||
'required' => true,
|
||||
])
|
||||
->add('icon', EnumType::class, [
|
||||
'class' => GenderIconEnum::class,
|
||||
'choices' => GenderIconEnum::cases(),
|
||||
'expanded' => true,
|
||||
'multiple' => false,
|
||||
'mapped' => true,
|
||||
'choice_label' => fn (GenderIconEnum $enum) => '<i class="'.strtolower($enum->value).'"></i>',
|
||||
'choice_value' => fn (?GenderIconEnum $enum) => null !== $enum ? $enum->value : null,
|
||||
'label' => 'gender.admin.Select gender icon',
|
||||
'label_html' => true,
|
||||
])
|
||||
->add('genderTranslation', EnumType::class, [
|
||||
'class' => GenderEnum::class,
|
||||
'choice_label' => fn (GenderEnum $enum) => $enum->value,
|
||||
'label' => 'gender.admin.Select gender translation',
|
||||
])
|
||||
->add('active', ChoiceType::class, [
|
||||
'choices' => [
|
||||
'Active' => true,
|
||||
'Inactive' => false,
|
||||
],
|
||||
])
|
||||
->add('order', NumberType::class);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Gender::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ class NewsItemType extends AbstractType
|
||||
])
|
||||
->add('content', ChillTextareaType::class, [
|
||||
'required' => false,
|
||||
'empty_data' => '',
|
||||
])
|
||||
->add(
|
||||
'startDate',
|
||||
|
||||
@@ -103,7 +103,7 @@ trait AppendScopeChoiceTypeTrait
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
TranslatableStringHelper $translatableStringHelper,
|
||||
ObjectManager $om,
|
||||
$name = 'scope'
|
||||
$name = 'scope',
|
||||
) {
|
||||
$reachableScopes = $authorizationHelper
|
||||
->getReachableScopes($user, $role, $center);
|
||||
|
||||
@@ -36,6 +36,7 @@ class ChillCollectionType extends AbstractType
|
||||
$view->vars['identifier'] = $options['identifier'];
|
||||
$view->vars['empty_collection_explain'] = $options['empty_collection_explain'];
|
||||
$view->vars['js_caller'] = $options['js_caller'];
|
||||
$view->vars['uniqid'] = uniqid();
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
|
||||
@@ -13,36 +13,30 @@ namespace Chill\MainBundle\Form\Type;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\PermissionsGroup;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Chill\MainBundle\Repository\CenterRepository;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ComposedGroupCenterType extends AbstractType
|
||||
{
|
||||
public function __construct(private readonly CenterRepository $centerRepository) {}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$centers = $this->centerRepository->findActive();
|
||||
|
||||
$builder->add('permissionsgroup', EntityType::class, [
|
||||
'class' => PermissionsGroup::class,
|
||||
'choice_label' => static fn (PermissionsGroup $group) => $group->getName(),
|
||||
])->add('center', EntityType::class, [
|
||||
'class' => Center::class,
|
||||
'query_builder' => static function (EntityRepository $er) {
|
||||
$qb = $er->createQueryBuilder('c');
|
||||
$qb->where($qb->expr()->eq('c.isActive', 'TRUE'))
|
||||
->orderBy('c.name', 'ASC');
|
||||
|
||||
return $qb;
|
||||
},
|
||||
])->add('center', ChoiceType::class, [
|
||||
'choices' => $centers,
|
||||
'choice_label' => fn (Center $center) => $center->getName(),
|
||||
'multiple' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefault('data_class', \Chill\MainBundle\Entity\GroupCenter::class);
|
||||
}
|
||||
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'composed_groupcenter';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user