mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-20 14:43:49 +00:00
Merge remote-tracking branch 'origin/master' into cire16
This commit is contained in:
@@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle;
|
||||
|
||||
use Chill\MainBundle\Cron\CronJobInterface;
|
||||
use Chill\MainBundle\CRUD\CompilerPass\CRUDControllerCompilerPass;
|
||||
use Chill\MainBundle\DependencyInjection\CompilerPass\ACLFlagsCompilerPass;
|
||||
use Chill\MainBundle\DependencyInjection\CompilerPass\ExportsCompilerPass;
|
||||
@@ -18,6 +19,7 @@ use Chill\MainBundle\DependencyInjection\CompilerPass\GroupingCenterCompilerPass
|
||||
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;
|
||||
@@ -58,6 +60,8 @@ class ChillMainBundle extends Bundle
|
||||
->addTag('chill.count_notification.user');
|
||||
$container->registerForAutoconfiguration(EntityWorkflowHandlerInterface::class)
|
||||
->addTag('chill_main.workflow_handler');
|
||||
$container->registerForAutoconfiguration(CronJobInterface::class)
|
||||
->addTag('chill_main.cron_job');
|
||||
|
||||
$container->addCompilerPass(new SearchableServicesCompilerPass());
|
||||
$container->addCompilerPass(new ConfigConsistencyCompilerPass());
|
||||
@@ -70,5 +74,6 @@ class ChillMainBundle extends Bundle
|
||||
$container->addCompilerPass(new ACLFlagsCompilerPass());
|
||||
$container->addCompilerPass(new GroupingCenterCompilerPass());
|
||||
$container->addCompilerPass(new CRUDControllerCompilerPass());
|
||||
$container->addCompilerPass(new ShortMessageCompilerPass());
|
||||
}
|
||||
}
|
||||
|
55
src/Bundle/ChillMainBundle/Command/ExecuteCronJobCommand.php
Normal file
55
src/Bundle/ChillMainBundle/Command/ExecuteCronJobCommand.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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\Cron\CronManagerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ExecuteCronJobCommand extends Command
|
||||
{
|
||||
private CronManagerInterface $cronManager;
|
||||
|
||||
public function __construct(
|
||||
CronManagerInterface $cronManager
|
||||
) {
|
||||
parent::__construct('chill:cron-job:execute');
|
||||
|
||||
$this->cronManager = $cronManager;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDescription('Execute the cronjob(s) given as argument, or one cronjob scheduled by system.')
|
||||
->setHelp("If no job is specified, the next available cronjob will be executed by system.\nThis command should be execute every 15 minutes (more or less)")
|
||||
->addArgument('job', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'one or more job to force execute (by default, all jobs are executed)', [])
|
||||
->addUsage('');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
if ([] === $input->getArgument('job')) {
|
||||
$this->cronManager->run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
foreach ($input->getArgument('job') as $jobName) {
|
||||
$this->cronManager->run($jobName);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
@@ -11,21 +11,32 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Form\SavedExportType;
|
||||
use Chill\MainBundle\Form\Type\Export\ExportType;
|
||||
use Chill\MainBundle\Form\Type\Export\FormatterType;
|
||||
use Chill\MainBundle\Form\Type\Export\PickCenterType;
|
||||
use Chill\MainBundle\Redis\ChillRedis;
|
||||
use Chill\MainBundle\Security\Authorization\SavedExportVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use LogicException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RedisException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
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\SessionInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use function count;
|
||||
use function serialize;
|
||||
use function unserialize;
|
||||
@@ -36,35 +47,37 @@ use function unserialize;
|
||||
*/
|
||||
class ExportController extends AbstractController
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
/**
|
||||
* @var ExportManager
|
||||
*/
|
||||
protected $exportManager;
|
||||
private $exportManager;
|
||||
|
||||
/**
|
||||
* @var FormFactoryInterface
|
||||
*/
|
||||
protected $formFactory;
|
||||
private $formFactory;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @var ChillRedis
|
||||
*/
|
||||
protected $redis;
|
||||
private $redis;
|
||||
|
||||
/**
|
||||
* @var SessionInterface
|
||||
*/
|
||||
protected $session;
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
private $translator;
|
||||
|
||||
public function __construct(
|
||||
ChillRedis $chillRedis,
|
||||
@@ -72,8 +85,10 @@ class ExportController extends AbstractController
|
||||
FormFactoryInterface $formFactory,
|
||||
LoggerInterface $logger,
|
||||
SessionInterface $session,
|
||||
TranslatorInterface $translator
|
||||
TranslatorInterface $translator,
|
||||
EntityManagerInterface $entityManager
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->redis = $chillRedis;
|
||||
$this->exportManager = $exportManager;
|
||||
$this->formFactory = $formFactory;
|
||||
@@ -141,11 +156,32 @@ class ExportController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list of available exports.
|
||||
* @Route("/{_locale}/exports/generate-from-saved/{id}", name="chill_main_export_generate_from_saved")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function indexAction(Request $request)
|
||||
public function generateFromSavedExport(SavedExport $savedExport): RedirectResponse
|
||||
{
|
||||
$this->denyAccessUnlessGranted(SavedExportVoter::GENERATE, $savedExport);
|
||||
|
||||
$key = md5(uniqid((string) mt_rand(), false));
|
||||
|
||||
$this->redis->setEx($key, 3600, serialize($savedExport->getOptions()));
|
||||
|
||||
return $this->redirectToRoute(
|
||||
'chill_main_export_download',
|
||||
[
|
||||
'alias' => $savedExport->getExportAlias(),
|
||||
'key' => $key, 'prevent_save' => true,
|
||||
'returnPath' => $this->generateUrl('chill_main_export_saved_list_my'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list of available exports.
|
||||
*/
|
||||
public function indexAction(): Response
|
||||
{
|
||||
$exportManager = $this->exportManager;
|
||||
|
||||
@@ -192,33 +228,65 @@ class ExportController extends AbstractController
|
||||
case 'export':
|
||||
return $this->exportFormStep($request, $export, $alias);
|
||||
|
||||
break;
|
||||
|
||||
case 'formatter':
|
||||
return $this->formatterFormStep($request, $export, $alias);
|
||||
|
||||
break;
|
||||
|
||||
case 'generate':
|
||||
return $this->forwardToGenerate($request, $export, $alias);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw $this->createNotFoundException("The given step '{$step}' is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/export/save-from-key/{alias}/{key}", name="chill_main_export_save_from_key")
|
||||
*/
|
||||
public function saveFromKey(string $alias, string $key, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||
$user = $this->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$data = $this->rebuildRawData($key);
|
||||
|
||||
$savedExport = new SavedExport();
|
||||
$savedExport
|
||||
->setOptions($data)
|
||||
->setExportAlias($alias)
|
||||
->setUser($user);
|
||||
|
||||
$form = $this->createForm(SavedExportType::class, $savedExport);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->persist($savedExport);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('chill_main_export_index');
|
||||
}
|
||||
|
||||
return $this->render(
|
||||
'@ChillMain/SavedExport/new.html.twig',
|
||||
[
|
||||
'form' => $form->createView(),
|
||||
'saved_export' => $savedExport,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a form to show on different steps.
|
||||
*
|
||||
* @param string $alias
|
||||
* @param array $data the data from previous step. Required for steps 'formatter' and 'generate_formatter'
|
||||
* @param mixed $step
|
||||
*
|
||||
* @return \Symfony\Component\Form\Form
|
||||
*/
|
||||
protected function createCreateFormExport($alias, $step, $data = [])
|
||||
protected function createCreateFormExport($alias, $step, $data = []): FormInterface
|
||||
{
|
||||
/** @var \Chill\MainBundle\Export\ExportManager $exportManager */
|
||||
$exportManager = $this->exportManager;
|
||||
@@ -426,28 +494,7 @@ class ExportController extends AbstractController
|
||||
|
||||
protected function rebuildData($key)
|
||||
{
|
||||
if (null === $key) {
|
||||
throw $this->createNotFoundException('key does not exists');
|
||||
}
|
||||
|
||||
if ($this->redis->exists($key) !== 1) {
|
||||
$this->addFlash('error', $this->translator->trans('This report is not available any more'));
|
||||
|
||||
throw $this->createNotFoundException('key does not exists');
|
||||
}
|
||||
|
||||
$serialized = $this->redis->get($key);
|
||||
|
||||
if (false === $serialized) {
|
||||
throw new LogicException('the key could not be reached from redis');
|
||||
}
|
||||
|
||||
$rawData = unserialize($serialized);
|
||||
|
||||
$this->logger->notice('[export] choices for an export unserialized', [
|
||||
'key' => $key,
|
||||
'rawData' => json_encode($rawData),
|
||||
]);
|
||||
$rawData = $this->rebuildRawData($key);
|
||||
|
||||
$alias = $rawData['alias'];
|
||||
|
||||
@@ -476,8 +523,6 @@ class ExportController extends AbstractController
|
||||
* @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export
|
||||
* @param string $alias
|
||||
*
|
||||
* @throws type
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function selectCentersStep(Request $request, $export, $alias)
|
||||
@@ -544,6 +589,8 @@ class ExportController extends AbstractController
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -593,4 +640,32 @@ class ExportController extends AbstractController
|
||||
throw new LogicException("the step {$step} is not defined.");
|
||||
}
|
||||
}
|
||||
|
||||
private function rebuildRawData(string $key): array
|
||||
{
|
||||
if (null === $key) {
|
||||
throw $this->createNotFoundException('key does not exists');
|
||||
}
|
||||
|
||||
if ($this->redis->exists($key) !== 1) {
|
||||
$this->addFlash('error', $this->translator->trans('This report is not available any more'));
|
||||
|
||||
throw $this->createNotFoundException('key does not exists');
|
||||
}
|
||||
|
||||
$serialized = $this->redis->get($key);
|
||||
|
||||
if (false === $serialized) {
|
||||
throw new LogicException('the key could not be reached from redis');
|
||||
}
|
||||
|
||||
$rawData = unserialize($serialized);
|
||||
|
||||
$this->logger->notice('[export] choices for an export unserialized', [
|
||||
'key' => $key,
|
||||
'rawData' => json_encode($rawData),
|
||||
]);
|
||||
|
||||
return $rawData;
|
||||
}
|
||||
}
|
||||
|
185
src/Bundle/ChillMainBundle/Controller/SavedExportController.php
Normal file
185
src/Bundle/ChillMainBundle/Controller/SavedExportController.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?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\SavedExport;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Export\GroupedExportInterface;
|
||||
use Chill\MainBundle\Form\SavedExportType;
|
||||
use Chill\MainBundle\Repository\SavedExportRepositoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\SavedExportVoter;
|
||||
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\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use function count;
|
||||
|
||||
class SavedExportController
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private ExportManager $exportManager;
|
||||
|
||||
private FormFactoryInterface $formFactory;
|
||||
|
||||
private SavedExportRepositoryInterface $savedExportRepository;
|
||||
|
||||
private Security $security;
|
||||
|
||||
private SessionInterface $session;
|
||||
|
||||
private EngineInterface $templating;
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
private UrlGeneratorInterface $urlGenerator;
|
||||
|
||||
public function __construct(
|
||||
EngineInterface $templating,
|
||||
EntityManagerInterface $entityManager,
|
||||
ExportManager $exportManager,
|
||||
FormFactoryInterface $formBuilder,
|
||||
SavedExportRepositoryInterface $savedExportRepository,
|
||||
Security $security,
|
||||
SessionInterface $session,
|
||||
TranslatorInterface $translator,
|
||||
UrlGeneratorInterface $urlGenerator
|
||||
) {
|
||||
$this->exportManager = $exportManager;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->formFactory = $formBuilder;
|
||||
$this->savedExportRepository = $savedExportRepository;
|
||||
$this->security = $security;
|
||||
$this->session = $session;
|
||||
$this->templating = $templating;
|
||||
$this->translator = $translator;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/exports/saved/{id}/delete", name="chill_main_export_saved_delete")
|
||||
*/
|
||||
public function delete(SavedExport $savedExport, Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted(SavedExportVoter::DELETE, $savedExport)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$form = $this->formFactory->create();
|
||||
$form->add('submit', SubmitType::class, ['label' => 'Delete']);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->remove($savedExport);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->session->getFlashBag()->add('success', $this->translator->trans('saved_export.Export is deleted'));
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->urlGenerator->generate('chill_main_export_saved_list_my')
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
$this->templating->render(
|
||||
'@ChillMain/SavedExport/delete.html.twig',
|
||||
[
|
||||
'saved_export' => $savedExport,
|
||||
'delete_form' => $form->createView(),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/exports/saved/{id}/edit", name="chill_main_export_saved_edit")
|
||||
*/
|
||||
public function edit(SavedExport $savedExport, Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted(SavedExportVoter::EDIT, $savedExport)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$form = $this->formFactory->create(SavedExportType::class, $savedExport);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->session->getFlashBag()->add('success', $this->translator->trans('saved_export.Saved export is saved!'));
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->urlGenerator->generate('chill_main_export_saved_list_my')
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
$this->templating->render(
|
||||
'@ChillMain/SavedExport/edit.html.twig',
|
||||
[
|
||||
'form' => $form->createView(),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/exports/saved/my", name="chill_main_export_saved_list_my")
|
||||
*/
|
||||
public function list(): Response
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if (!$this->security->isGranted('ROLE_USER') || !$user instanceof User) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$exports = $this->savedExportRepository->findByUser($user, ['title' => 'ASC']);
|
||||
|
||||
// group by center
|
||||
/** @var array<string, array{saved: SavedExport, export: ExportInterface}> $exportsGrouped */
|
||||
$exportsGrouped = [];
|
||||
|
||||
foreach ($exports as $savedExport) {
|
||||
$export = $this->exportManager->getExport($savedExport->getExportAlias());
|
||||
|
||||
$exportsGrouped[
|
||||
$export instanceof GroupedExportInterface
|
||||
? $this->translator->trans($export->getGroup()) : '_'
|
||||
][] = ['saved' => $savedExport, 'export' => $export];
|
||||
}
|
||||
|
||||
ksort($exportsGrouped);
|
||||
|
||||
return new Response(
|
||||
$this->templating->render(
|
||||
'@ChillMain/SavedExport/index.html.twig',
|
||||
[
|
||||
'grouped_exports' => $exportsGrouped,
|
||||
'total' => count($exports),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
23
src/Bundle/ChillMainBundle/Cron/CronJobInterface.php
Normal file
23
src/Bundle/ChillMainBundle/Cron/CronJobInterface.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?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\Cron;
|
||||
|
||||
use Chill\MainBundle\Entity\CronJobExecution;
|
||||
|
||||
interface CronJobInterface
|
||||
{
|
||||
public function canRun(?CronJobExecution $cronJobExecution): bool;
|
||||
|
||||
public function getKey(): string;
|
||||
|
||||
public function run(): void;
|
||||
}
|
180
src/Bundle/ChillMainBundle/Cron/CronManager.php
Normal file
180
src/Bundle/ChillMainBundle/Cron/CronManager.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?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\Cron;
|
||||
|
||||
use Chill\MainBundle\Entity\CronJobExecution;
|
||||
use Chill\MainBundle\Repository\CronJobExecutionRepositoryInterface;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use function array_key_exists;
|
||||
|
||||
/**
|
||||
* Manage cronjob and execute them.
|
||||
*
|
||||
* If any :code:`job` argument is given, the :code:`CronManager` schedule job with those steps:
|
||||
*
|
||||
* - the tasks are ordered, with:
|
||||
* - a priority is given for tasks that weren't never executed;
|
||||
* - then, the tasks are ordered, the last executed are the first in the list
|
||||
*
|
||||
* Then, for each tasks, and in the given order, the first task where :code:`canRun` return :code:`TRUE` will be executed.
|
||||
*
|
||||
* The error inside job execution are catched (with the exception of _out of memory error_).
|
||||
*
|
||||
* The manager will mark the task as executed even if an error is catched. This will lead as failed job
|
||||
* will not have priority any more on other tasks.
|
||||
*
|
||||
* If a tasks is "forced", there is no test about eligibility of the task (the `canRun` method is not called),
|
||||
* and the last task execution is not recorded.
|
||||
*/
|
||||
class CronManager implements CronManagerInterface
|
||||
{
|
||||
private const LOG_PREFIX = '[cron manager] ';
|
||||
|
||||
private const UPDATE_AFTER_EXEC = 'UPDATE ' . CronJobExecution::class . ' cr SET cr.lastEnd = :now, cr.lastStatus = :status WHERE cr.key = :key';
|
||||
|
||||
private const UPDATE_BEFORE_EXEC = 'UPDATE ' . CronJobExecution::class . ' cr SET cr.lastExecution = :now WHERE cr.key = :key';
|
||||
|
||||
private CronJobExecutionRepositoryInterface $cronJobExecutionRepository;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
/**
|
||||
* @var iterable<CronJobInterface>
|
||||
*/
|
||||
private iterable $jobs;
|
||||
|
||||
private LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* @param CronJobInterface[] $jobs
|
||||
*/
|
||||
public function __construct(
|
||||
CronJobExecutionRepositoryInterface $cronJobExecutionRepository,
|
||||
EntityManagerInterface $entityManager,
|
||||
iterable $jobs,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->cronJobExecutionRepository = $cronJobExecutionRepository;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->jobs = $jobs;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function run(?string $forceJob = null): void
|
||||
{
|
||||
if (null !== $forceJob) {
|
||||
$this->runForce($forceJob);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[$orderedJobs, $lasts] = $this->getOrderedJobs();
|
||||
|
||||
foreach ($orderedJobs as $job) {
|
||||
if ($job->canRun($lasts[$job->getKey()] ?? null)) {
|
||||
if (array_key_exists($job->getKey(), $lasts)) {
|
||||
$this->entityManager
|
||||
->createQuery(self::UPDATE_BEFORE_EXEC)
|
||||
->setParameters([
|
||||
'now' => new DateTimeImmutable('now'),
|
||||
'key' => $job->getKey(),
|
||||
]);
|
||||
} else {
|
||||
$execution = new CronJobExecution($job->getKey());
|
||||
$this->entityManager->persist($execution);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
$this->entityManager->clear();
|
||||
|
||||
try {
|
||||
$this->logger->info(sprintf('%sWill run job', self::LOG_PREFIX), ['job' => $job->getKey()]);
|
||||
$job->run();
|
||||
|
||||
$this->entityManager
|
||||
->createQuery(self::UPDATE_AFTER_EXEC)
|
||||
->setParameters([
|
||||
'now' => new DateTimeImmutable('now'),
|
||||
'status' => CronJobExecution::SUCCESS,
|
||||
'key' => $job->getKey(),
|
||||
])
|
||||
->execute();
|
||||
|
||||
$this->logger->info(sprintf('%sSuccessfully run job', self::LOG_PREFIX), ['job' => $job->getKey()]);
|
||||
|
||||
return;
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error(sprintf('%sRunning job failed', self::LOG_PREFIX), ['job' => $job->getKey()]);
|
||||
$this->entityManager
|
||||
->createQuery(self::UPDATE_AFTER_EXEC)
|
||||
->setParameters([
|
||||
'now' => new DateTimeImmutable('now'),
|
||||
'status' => CronJobExecution::FAILURE,
|
||||
'key' => $job->getKey(),
|
||||
])
|
||||
->execute();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<0: CronJobInterface[], 1: array<string, CronJobExecution>>
|
||||
*/
|
||||
private function getOrderedJobs(): array
|
||||
{
|
||||
/** @var array<string, CronJobExecution> $lasts */
|
||||
$lasts = [];
|
||||
|
||||
foreach ($this->cronJobExecutionRepository->findAll() as $execution) {
|
||||
$lasts[$execution->getKey()] = $execution;
|
||||
}
|
||||
|
||||
// order by last, NULL first
|
||||
$orderedJobs = iterator_to_array($this->jobs);
|
||||
usort(
|
||||
$orderedJobs,
|
||||
static function (CronJobInterface $a, CronJobInterface $b) use ($lasts): int {
|
||||
if (
|
||||
(!array_key_exists($a->getKey(), $lasts) && !array_key_exists($b->getKey(), $lasts))
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!array_key_exists($a->getKey(), $lasts) && array_key_exists($b->getKey(), $lasts)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!array_key_exists($b->getKey(), $lasts) && array_key_exists($a->getKey(), $lasts)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return $lasts[$a->getKey()]->getLastStart() <=> $lasts[$b->getKey()]->getLastStart();
|
||||
}
|
||||
);
|
||||
|
||||
return [$orderedJobs, $lasts];
|
||||
}
|
||||
|
||||
private function runForce(string $forceJob): void
|
||||
{
|
||||
foreach ($this->jobs as $job) {
|
||||
if ($job->getKey() === $forceJob) {
|
||||
$job->run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
src/Bundle/ChillMainBundle/Cron/CronManagerInterface.php
Normal file
20
src/Bundle/ChillMainBundle/Cron/CronManagerInterface.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\Cron;
|
||||
|
||||
interface CronManagerInterface
|
||||
{
|
||||
/**
|
||||
* Execute one job, with a given priority, or the given job (identified by his key).
|
||||
*/
|
||||
public function run(?string $forceJob = null): void;
|
||||
}
|
@@ -22,17 +22,22 @@ use Chill\MainBundle\Controller\UserController;
|
||||
use Chill\MainBundle\Controller\UserJobApiController;
|
||||
use Chill\MainBundle\Controller\UserJobController;
|
||||
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
|
||||
use Chill\MainBundle\Doctrine\DQL\Age;
|
||||
use Chill\MainBundle\Doctrine\DQL\Extract;
|
||||
use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey;
|
||||
use Chill\MainBundle\Doctrine\DQL\Greatest;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonExtract;
|
||||
use Chill\MainBundle\Doctrine\DQL\Least;
|
||||
use Chill\MainBundle\Doctrine\DQL\OverlapsI;
|
||||
use Chill\MainBundle\Doctrine\DQL\Replace;
|
||||
use Chill\MainBundle\Doctrine\DQL\Similarity;
|
||||
use Chill\MainBundle\Doctrine\DQL\STContains;
|
||||
use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS;
|
||||
use Chill\MainBundle\Doctrine\DQL\STX;
|
||||
use Chill\MainBundle\Doctrine\DQL\STY;
|
||||
use Chill\MainBundle\Doctrine\DQL\ToChar;
|
||||
use Chill\MainBundle\Doctrine\DQL\Unaccent;
|
||||
use Chill\MainBundle\Doctrine\ORM\Hydration\FlatHierarchyEntityHydrator;
|
||||
@@ -205,8 +210,11 @@ class ChillMainExtension extends Extension implements
|
||||
$loader->load('services/search.yaml');
|
||||
$loader->load('services/serializer.yaml');
|
||||
$loader->load('services/mailer.yaml');
|
||||
$loader->load('services/short_message.yaml');
|
||||
|
||||
$this->configureCruds($container, $config['cruds'], $config['apis'], $loader);
|
||||
$container->setParameter('chill_main.short_messages', $config['short_messages']);
|
||||
//$this->configureSms($config['short_messages'], $container, $loader);
|
||||
}
|
||||
|
||||
public function prepend(ContainerBuilder $container)
|
||||
@@ -247,10 +255,15 @@ class ChillMainExtension extends Extension implements
|
||||
'STRICT_WORD_SIMILARITY_OPS' => StrictWordSimilarityOPS::class,
|
||||
'ST_CONTAINS' => STContains::class,
|
||||
'JSONB_ARRAY_LENGTH' => JsonbArrayLength::class,
|
||||
'ST_X' => STX::class,
|
||||
'ST_Y' => STY::class,
|
||||
'GREATEST' => Greatest::class,
|
||||
'LEAST' => LEAST::class,
|
||||
],
|
||||
'datetime_functions' => [
|
||||
'EXTRACT' => Extract::class,
|
||||
'TO_CHAR' => ToChar::class,
|
||||
'AGE' => Age::class,
|
||||
],
|
||||
],
|
||||
'hydrators' => [
|
||||
|
@@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\DependencyInjection\CompilerPass;
|
||||
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use LogicException;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
@@ -30,53 +31,19 @@ class ExportsCompilerPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->has('Chill\MainBundle\Export\ExportManager')) {
|
||||
throw new LogicException('service Chill\MainBundle\Export\ExportManager '
|
||||
if (!$container->has(ExportManager::class)) {
|
||||
throw new LogicException('service ' . ExportManager::class . ' '
|
||||
. 'is not defined. It is required by ExportsCompilerPass');
|
||||
}
|
||||
|
||||
$chillManagerDefinition = $container->findDefinition(
|
||||
'Chill\MainBundle\Export\ExportManager'
|
||||
ExportManager::class
|
||||
);
|
||||
|
||||
$this->compileExports($chillManagerDefinition, $container);
|
||||
$this->compileFilters($chillManagerDefinition, $container);
|
||||
$this->compileAggregators($chillManagerDefinition, $container);
|
||||
$this->compileFormatters($chillManagerDefinition, $container);
|
||||
$this->compileExportElementsProvider($chillManagerDefinition, $container);
|
||||
}
|
||||
|
||||
private function compileAggregators(
|
||||
Definition $chillManagerDefinition,
|
||||
ContainerBuilder $container
|
||||
) {
|
||||
$taggedServices = $container->findTaggedServiceIds(
|
||||
'chill.export_aggregator'
|
||||
);
|
||||
|
||||
$knownAliases = [];
|
||||
|
||||
foreach ($taggedServices as $id => $tagAttributes) {
|
||||
foreach ($tagAttributes as $attributes) {
|
||||
if (!isset($attributes['alias'])) {
|
||||
throw new LogicException("the 'alias' attribute is missing in your " .
|
||||
"service '{$id}' definition");
|
||||
}
|
||||
|
||||
if (array_search($attributes['alias'], $knownAliases, true)) {
|
||||
throw new LogicException('There is already a chill.export_aggregator service with alias '
|
||||
. $attributes['alias'] . '. Choose another alias.');
|
||||
}
|
||||
$knownAliases[] = $attributes['alias'];
|
||||
|
||||
$chillManagerDefinition->addMethodCall(
|
||||
'addAggregator',
|
||||
[new Reference($id), $attributes['alias']]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function compileExportElementsProvider(
|
||||
Definition $chillManagerDefinition,
|
||||
ContainerBuilder $container
|
||||
@@ -108,68 +75,6 @@ class ExportsCompilerPass implements CompilerPassInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function compileExports(
|
||||
Definition $chillManagerDefinition,
|
||||
ContainerBuilder $container
|
||||
) {
|
||||
$taggedServices = $container->findTaggedServiceIds(
|
||||
'chill.export'
|
||||
);
|
||||
|
||||
$knownAliases = [];
|
||||
|
||||
foreach ($taggedServices as $id => $tagAttributes) {
|
||||
foreach ($tagAttributes as $attributes) {
|
||||
if (!isset($attributes['alias'])) {
|
||||
throw new LogicException("the 'alias' attribute is missing in your " .
|
||||
"service '{$id}' definition");
|
||||
}
|
||||
|
||||
if (array_search($attributes['alias'], $knownAliases, true)) {
|
||||
throw new LogicException('There is already a chill.export service with alias '
|
||||
. $attributes['alias'] . '. Choose another alias.');
|
||||
}
|
||||
$knownAliases[] = $attributes['alias'];
|
||||
|
||||
$chillManagerDefinition->addMethodCall(
|
||||
'addExport',
|
||||
[new Reference($id), $attributes['alias']]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function compileFilters(
|
||||
Definition $chillManagerDefinition,
|
||||
ContainerBuilder $container
|
||||
) {
|
||||
$taggedServices = $container->findTaggedServiceIds(
|
||||
'chill.export_filter'
|
||||
);
|
||||
|
||||
$knownAliases = [];
|
||||
|
||||
foreach ($taggedServices as $id => $tagAttributes) {
|
||||
foreach ($tagAttributes as $attributes) {
|
||||
if (!isset($attributes['alias'])) {
|
||||
throw new LogicException("the 'alias' attribute is missing in your " .
|
||||
"service '{$id}' definition");
|
||||
}
|
||||
|
||||
if (array_search($attributes['alias'], $knownAliases, true)) {
|
||||
throw new LogicException('There is already a chill.export_filter service with alias '
|
||||
. $attributes['alias'] . '. Choose another alias.');
|
||||
}
|
||||
$knownAliases[] = $attributes['alias'];
|
||||
|
||||
$chillManagerDefinition->addMethodCall(
|
||||
'addFilter',
|
||||
[new Reference($id), $attributes['alias']]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function compileFormatters(
|
||||
Definition $chillManagerDefinition,
|
||||
ContainerBuilder $container
|
||||
|
@@ -0,0 +1,94 @@
|
||||
<?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;
|
||||
use function array_key_exists;
|
||||
|
||||
class ShortMessageCompilerPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$config = $container->resolveEnvPlaceholders($container->getParameter('chill_main.short_messages', null), true);
|
||||
// weird fix for special characters
|
||||
$config['dsn'] = str_replace(['%%'], ['%'], $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')) {
|
||||
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')
|
||||
->setArgument(0, $dsn['user'])
|
||||
->setArgument(1, $dsn['pass'])
|
||||
->setArgument(2, $dsn['host'])
|
||||
->setArgument(3, $dsn['queries']['consumer_key']);
|
||||
$container->setDefinition('Ovh\Api', $ovh);
|
||||
|
||||
$ovhSender = new Definition();
|
||||
$ovhSender
|
||||
->setClass(OvhShortMessageSender::class)
|
||||
->setArgument(0, new Reference('Ovh\Api'))
|
||||
->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);
|
||||
}
|
||||
}
|
@@ -102,6 +102,14 @@ class Configuration implements ConfigurationInterface
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('short_messages')
|
||||
->canBeEnabled()
|
||||
->children()
|
||||
->scalarNode('dsn')->cannotBeEmpty()->defaultValue('null://null')
|
||||
->info('the dsn for sending short message. Example: ovh://applicationKey:secret@endpoint')
|
||||
->end()
|
||||
->end()
|
||||
->end() // end for 'short_messages'
|
||||
->arrayNode('acl')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
|
54
src/Bundle/ChillMainBundle/Doctrine/DQL/Age.php
Normal file
54
src/Bundle/ChillMainBundle/Doctrine/DQL/Age.php
Normal file
@@ -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\Doctrine\DQL;
|
||||
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
class Age extends FunctionNode
|
||||
{
|
||||
private $value1;
|
||||
|
||||
private $value2;
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
if (null !== $this->value2) {
|
||||
return sprintf(
|
||||
'AGE(%s, %s)',
|
||||
$this->value1->dispatch($sqlWalker),
|
||||
$this->value2->dispatch($sqlWalker)
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'AGE(%s)',
|
||||
$this->value1->dispatch($sqlWalker),
|
||||
);
|
||||
}
|
||||
|
||||
public function parse(Parser $parser)
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$this->value1 = $parser->SimpleArithmeticExpression();
|
||||
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
|
||||
$this->value2 = $parser->SimpleArithmeticExpression();
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
57
src/Bundle/ChillMainBundle/Doctrine/DQL/Greatest.php
Normal file
57
src/Bundle/ChillMainBundle/Doctrine/DQL/Greatest.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?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\Doctrine\DQL;
|
||||
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\AST\Node;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
/**
|
||||
* Postgresql GREATEST function.
|
||||
*
|
||||
* Borrowed from https://github.com/beberlei/DoctrineExtensions/blob/master/src/Query/Postgresql/Greatest.php
|
||||
* (https://github.com/beberlei/DoctrineExtensions/blob/master/LICENSE) and
|
||||
* https://gist.github.com/olimsaidov/4bbd530b1b645ce75e1bbb781b5dd91f
|
||||
*/
|
||||
class Greatest extends FunctionNode
|
||||
{
|
||||
/**
|
||||
* @var array|Node[]
|
||||
*/
|
||||
private array $exprs = [];
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
return 'GREATEST(' . implode(', ', array_map(static function (Node $expr) use ($sqlWalker) {
|
||||
return $expr->dispatch($sqlWalker);
|
||||
}, $this->exprs)) . ')';
|
||||
}
|
||||
|
||||
public function parse(Parser $parser)
|
||||
{
|
||||
$this->exprs = [];
|
||||
|
||||
$lexer = $parser->getLexer();
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
$this->exprs[] = $parser->ArithmeticPrimary();
|
||||
|
||||
while (Lexer::T_COMMA === $lexer->lookahead['type']) {
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
$this->exprs[] = $parser->ArithmeticPrimary();
|
||||
}
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
@@ -27,7 +27,7 @@ class JsonbExistsInArray extends FunctionNode
|
||||
return sprintf(
|
||||
'%s ?? %s',
|
||||
$this->expr1->dispatch($sqlWalker),
|
||||
$sqlWalker->walkInputParameter($this->expr2)
|
||||
$this->expr2->dispatch($sqlWalker)
|
||||
);
|
||||
}
|
||||
|
||||
|
57
src/Bundle/ChillMainBundle/Doctrine/DQL/Least.php
Normal file
57
src/Bundle/ChillMainBundle/Doctrine/DQL/Least.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?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\Doctrine\DQL;
|
||||
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\AST\Node;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
/**
|
||||
* Postgresql LEAST function.
|
||||
*
|
||||
* Borrowed from https://github.com/beberlei/DoctrineExtensions/blob/master/src/Query/Postgresql/Least.php
|
||||
* (https://github.com/beberlei/DoctrineExtensions/blob/master/LICENSE) and
|
||||
* https://gist.github.com/olimsaidov/4bbd530b1b645ce75e1bbb781b5dd91f
|
||||
*/
|
||||
class Least extends FunctionNode
|
||||
{
|
||||
/**
|
||||
* @var array|Node[]
|
||||
*/
|
||||
private array $exprs = [];
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
return 'LEAST(' . implode(', ', array_map(static function (Node $expr) use ($sqlWalker) {
|
||||
return $expr->dispatch($sqlWalker);
|
||||
}, $this->exprs)) . ')';
|
||||
}
|
||||
|
||||
public function parse(Parser $parser)
|
||||
{
|
||||
$this->exprs = [];
|
||||
|
||||
$lexer = $parser->getLexer();
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
$this->exprs[] = $parser->ArithmeticPrimary();
|
||||
|
||||
while (Lexer::T_COMMA === $lexer->lookahead['type']) {
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
$this->exprs[] = $parser->ArithmeticPrimary();
|
||||
}
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
37
src/Bundle/ChillMainBundle/Doctrine/DQL/STX.php
Normal file
37
src/Bundle/ChillMainBundle/Doctrine/DQL/STX.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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\Doctrine\DQL;
|
||||
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
class STX extends FunctionNode
|
||||
{
|
||||
private $field;
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
return sprintf('ST_X(%s)', $this->field->dispatch($sqlWalker));
|
||||
}
|
||||
|
||||
public function parse(Parser $parser)
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$this->field = $parser->ArithmeticExpression();
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
37
src/Bundle/ChillMainBundle/Doctrine/DQL/STY.php
Normal file
37
src/Bundle/ChillMainBundle/Doctrine/DQL/STY.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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\Doctrine\DQL;
|
||||
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
class STY extends FunctionNode
|
||||
{
|
||||
private $field;
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
return sprintf('ST_Y(%s)', $this->field->dispatch($sqlWalker));
|
||||
}
|
||||
|
||||
public function parse(Parser $parser)
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$this->field = $parser->ArithmeticExpression();
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
@@ -17,6 +17,7 @@ use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\DateIntervalType;
|
||||
use Exception;
|
||||
|
||||
use LogicException;
|
||||
use function count;
|
||||
use function current;
|
||||
use function preg_match;
|
||||
@@ -40,7 +41,7 @@ class NativeDateIntervalType extends DateIntervalType
|
||||
return $value->format(self::FORMAT);
|
||||
}
|
||||
|
||||
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateInterval']);
|
||||
throw ConversionException::conversionFailedInvalidType($value, 'string', ['null', 'DateInterval']);
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
@@ -80,7 +81,7 @@ class NativeDateIntervalType extends DateIntervalType
|
||||
|
||||
protected function createConversionException($value, $exception = null)
|
||||
{
|
||||
return ConversionException::conversionFailedFormat($value, $this->getName(), 'xx year xx mons xx days 01:02:03', $exception);
|
||||
return ConversionException::conversionFailedFormat($value, 'string', 'xx year xx mons xx days 01:02:03', $exception);
|
||||
}
|
||||
|
||||
private function convertEntry(&$strings)
|
||||
@@ -125,5 +126,7 @@ class NativeDateIntervalType extends DateIntervalType
|
||||
|
||||
return $intervalSpec;
|
||||
}
|
||||
|
||||
throw new LogicException();
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,8 @@ use Chill\MainBundle\Doctrine\Model\Point;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
@@ -97,6 +99,23 @@ class Address
|
||||
*/
|
||||
private $floor;
|
||||
|
||||
/**
|
||||
* List of geographical units and addresses.
|
||||
*
|
||||
* This list is computed by a materialized view. It won't be populated until a refresh is done
|
||||
* on the materialized view.
|
||||
*
|
||||
* @var Collection<int, GeographicalUnit>|GeographicalUnit[]
|
||||
* @readonly
|
||||
* @ORM\ManyToMany(targetEntity=GeographicalUnit::class)
|
||||
* @ORM\JoinTable(
|
||||
* name="view_chill_main_address_geographical_unit",
|
||||
* joinColumns={@ORM\JoinColumn(name="address_id")},
|
||||
* inverseJoinColumns={@ORM\JoinColumn(name="geographical_unit_id")}
|
||||
* )
|
||||
*/
|
||||
private Collection $geographicalUnits;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
@@ -104,8 +123,9 @@ class Address
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
* @Groups({"write"})
|
||||
* @readonly
|
||||
*/
|
||||
private $id;
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* True if the address is a "no address", aka homeless person, ...
|
||||
@@ -190,6 +210,7 @@ class Address
|
||||
public function __construct()
|
||||
{
|
||||
$this->validFrom = new DateTime();
|
||||
$this->geographicalUnits = new ArrayCollection();
|
||||
}
|
||||
|
||||
public static function createFromAddress(Address $original): Address
|
||||
@@ -273,6 +294,14 @@ class Address
|
||||
return $this->floor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, GeographicalUnit>|GeographicalUnit[]
|
||||
*/
|
||||
public function getGeographicalUnits(): Collection
|
||||
{
|
||||
return $this->geographicalUnits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id.
|
||||
*
|
||||
@@ -359,6 +388,11 @@ class Address
|
||||
return $this->validTo;
|
||||
}
|
||||
|
||||
public function hasAddressReference(): bool
|
||||
{
|
||||
return null !== $this->getAddressReference();
|
||||
}
|
||||
|
||||
public function isNoAddress(): bool
|
||||
{
|
||||
return $this->getIsNoAddress();
|
||||
|
@@ -171,6 +171,11 @@ class AddressReference
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
public function hasPoint(): bool
|
||||
{
|
||||
return null !== $this->getPoint();
|
||||
}
|
||||
|
||||
public function setCreatedAt(?DateTimeImmutable $createdAt): self
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
95
src/Bundle/ChillMainBundle/Entity/CronJobExecution.php
Normal file
95
src/Bundle/ChillMainBundle/Entity/CronJobExecution.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?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 DateTimeImmutable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="chill_main_cronjob_execution")
|
||||
*/
|
||||
class CronJobExecution
|
||||
{
|
||||
public const FAILURE = 100;
|
||||
|
||||
public const SUCCESS = 1;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false)
|
||||
* @ORM\Id
|
||||
*/
|
||||
private string $key;
|
||||
|
||||
/**
|
||||
* @var DateTimeImmutable
|
||||
* @ORM\Column(type="datetime_immutable", nullable=true, options={"default": null})
|
||||
*/
|
||||
private ?DateTimeImmutable $lastEnd = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable", nullable=false)
|
||||
*/
|
||||
private DateTimeImmutable $lastStart;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer", nullable=true, options={"default": null})
|
||||
*/
|
||||
private ?int $lastStatus = null;
|
||||
|
||||
public function __construct(string $key)
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->lastStart = new DateTimeImmutable('now');
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function getLastEnd(): DateTimeImmutable
|
||||
{
|
||||
return $this->lastEnd;
|
||||
}
|
||||
|
||||
public function getLastStart(): DateTimeImmutable
|
||||
{
|
||||
return $this->lastStart;
|
||||
}
|
||||
|
||||
public function getLastStatus(): ?int
|
||||
{
|
||||
return $this->lastStatus;
|
||||
}
|
||||
|
||||
public function setLastEnd(?DateTimeImmutable $lastEnd): CronJobExecution
|
||||
{
|
||||
$this->lastEnd = $lastEnd;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setLastStart(DateTimeImmutable $lastStart): CronJobExecution
|
||||
{
|
||||
$this->lastStart = $lastStart;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setLastStatus(?int $lastStatus): CronJobExecution
|
||||
{
|
||||
$this->lastStatus = $lastStatus;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -14,15 +14,17 @@ namespace Chill\MainBundle\Entity;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="chill_main_geographical_unit")
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="chill_main_geographical_unit", uniqueConstraints={
|
||||
* @ORM\UniqueConstraint(name="geographical_unit_refid", columns={"layer_id", "unitRefId"})
|
||||
* })
|
||||
* @ORM\Entity(readOnly=true)
|
||||
*/
|
||||
class GeographicalUnit
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
*/
|
||||
private $geom;
|
||||
private string $geom;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
@@ -32,23 +34,28 @@ class GeographicalUnit
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @ORM\ManyToOne(targetEntity=GeographicalUnitLayer::class, inversedBy="units")
|
||||
*/
|
||||
private $layerName;
|
||||
private ?GeographicalUnitLayer $layer;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @ORM\Column(type="text", nullable=false, options={"default": ""})
|
||||
*/
|
||||
private $unitName;
|
||||
private string $unitName;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false, options={"default": ""})
|
||||
*/
|
||||
private string $unitRefId;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getLayerName(): ?string
|
||||
public function getLayer(): ?GeographicalUnitLayer
|
||||
{
|
||||
return $this->layerName;
|
||||
return $this->layer;
|
||||
}
|
||||
|
||||
public function getUnitName(): ?string
|
||||
@@ -56,9 +63,9 @@ class GeographicalUnit
|
||||
return $this->unitName;
|
||||
}
|
||||
|
||||
public function setLayerName(?string $layerName): self
|
||||
public function setLayer(?GeographicalUnitLayer $layer): GeographicalUnit
|
||||
{
|
||||
$this->layerName = $layerName;
|
||||
$this->layer = $layer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -69,4 +76,18 @@ class GeographicalUnit
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUnitRefId(string $unitRefId): GeographicalUnit
|
||||
{
|
||||
$this->unitRefId = $unitRefId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function setId(int $id): self
|
||||
{
|
||||
$this->id = $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,52 @@
|
||||
<?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\GeographicalUnit;
|
||||
|
||||
/**
|
||||
* Simple GeographialUnit Data Transfer Object.
|
||||
*
|
||||
* This allow to get access to id, unitName, unitRefId, and layer's id
|
||||
*/
|
||||
class SimpleGeographicalUnitDTO
|
||||
{
|
||||
/**
|
||||
* @readonly
|
||||
* @psalm-readonly
|
||||
*/
|
||||
public int $id;
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
* @psalm-readonly
|
||||
*/
|
||||
public int $layerId;
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
* @psalm-readonly
|
||||
*/
|
||||
public string $unitName;
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
* @psalm-readonly
|
||||
*/
|
||||
public string $unitRefId;
|
||||
|
||||
public function __construct(int $id, string $unitName, string $unitRefId, int $layerId)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->unitName = $unitName;
|
||||
$this->unitRefId = $unitRefId;
|
||||
$this->layerId = $layerId;
|
||||
}
|
||||
}
|
79
src/Bundle/ChillMainBundle/Entity/GeographicalUnitLayer.php
Normal file
79
src/Bundle/ChillMainBundle/Entity/GeographicalUnitLayer.php
Normal file
@@ -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\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="chill_main_geographical_unit_layer", uniqueConstraints={
|
||||
* @ORM\UniqueConstraint(name="geographical_unit_layer_refid", columns={"refId"})
|
||||
* })
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class GeographicalUnitLayer
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json", nullable=false, options={"default": "[]"})
|
||||
*/
|
||||
private array $name = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false, options={"default": ""})
|
||||
*/
|
||||
private string $refId = '';
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=GeographicalUnit::class, mappedBy="layer")
|
||||
*/
|
||||
private Collection $units;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->units = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): array
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getRefId(): string
|
||||
{
|
||||
return $this->refId;
|
||||
}
|
||||
|
||||
public function getUnits(): Collection
|
||||
{
|
||||
return $this->units;
|
||||
}
|
||||
|
||||
public function setName(array $name): GeographicalUnitLayer
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -180,6 +180,11 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this->updatedBy;
|
||||
}
|
||||
|
||||
public function hasAddress(): bool
|
||||
{
|
||||
return null !== $this->getAddress();
|
||||
}
|
||||
|
||||
public function setActive(bool $active): self
|
||||
{
|
||||
$this->active = $active;
|
||||
|
136
src/Bundle/ChillMainBundle/Entity/SavedExport.php
Normal file
136
src/Bundle/ChillMainBundle/Entity/SavedExport.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?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\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="chill_main_saved_export")
|
||||
*/
|
||||
class SavedExport implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
|
||||
use TrackUpdateTrait;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false, options={"default": ""})
|
||||
* @Assert\NotBlank
|
||||
*/
|
||||
private string $description = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false, options={"default": ""})
|
||||
*/
|
||||
private string $exportAlias;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="uuid", unique="true")
|
||||
* @ORM\GeneratedValue(strategy="NONE")
|
||||
*/
|
||||
private UuidInterface $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json", nullable=false, options={"default": "[]"})
|
||||
*/
|
||||
private array $options = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false, options={"default": ""})
|
||||
* @Assert\NotBlank
|
||||
*/
|
||||
private string $title = '';
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=User::class)
|
||||
*/
|
||||
private User $user;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = Uuid::uuid4();
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getExportAlias(): string
|
||||
{
|
||||
return $this->exportAlias;
|
||||
}
|
||||
|
||||
public function getId(): UuidInterface
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setDescription(?string $description): SavedExport
|
||||
{
|
||||
$this->description = (string) $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setExportAlias(string $exportAlias): SavedExport
|
||||
{
|
||||
$this->exportAlias = $exportAlias;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setOptions(array $options): SavedExport
|
||||
{
|
||||
$this->options = $options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTitle(?string $title): SavedExport
|
||||
{
|
||||
$this->title = (string) $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUser(User $user): SavedExport
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -28,6 +28,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
||||
*/
|
||||
class Scope
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="boolean", nullable=false, options={"default": true})
|
||||
*/
|
||||
private bool $active = true;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
@@ -88,6 +93,18 @@ class Scope
|
||||
return $this->roleScopes;
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
public function setActive(bool $active): Scope
|
||||
{
|
||||
$this->active = $active;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
*
|
||||
|
@@ -43,7 +43,7 @@ class User implements AdvancedUserInterface
|
||||
/**
|
||||
* Array where SAML attributes's data are stored.
|
||||
*
|
||||
* @ORM\Column(type="json", nullable=true)
|
||||
* @ORM\Column(type="json", nullable=false)
|
||||
*/
|
||||
private array $attributes = [];
|
||||
|
||||
@@ -359,16 +359,21 @@ class User implements AdvancedUserInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attributes.
|
||||
*
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return Report
|
||||
*/
|
||||
public function setAttributes($attributes)
|
||||
public function setAttributeByDomain(string $domain, string $key, $value): self
|
||||
{
|
||||
$this->attributes = $attributes;
|
||||
$this->attributes[$domain][$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the attributes with existing attributes.
|
||||
*
|
||||
* Only the key provided will be created or updated. For a two-level array, use @see{User::setAttributeByDomain}
|
||||
*/
|
||||
public function setAttributes(array $attributes): self
|
||||
{
|
||||
$this->attributes = array_merge($this->attributes, $attributes);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -506,4 +511,11 @@ class User implements AdvancedUserInterface
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function unsetAttribute($key): self
|
||||
{
|
||||
unset($this->attributes[$key]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@@ -133,8 +133,7 @@ class EntityWorkflowStep
|
||||
if (!$this->destUser->contains($user)) {
|
||||
$this->destUser[] = $user;
|
||||
$this->getEntityWorkflow()
|
||||
->addSubscriberToFinal($user)
|
||||
->addSubscriberToStep($user);
|
||||
->addSubscriberToFinal($user);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -145,8 +144,7 @@ class EntityWorkflowStep
|
||||
if (!$this->destUserByAccessKey->contains($user) && !$this->destUser->contains($user)) {
|
||||
$this->destUserByAccessKey[] = $user;
|
||||
$this->getEntityWorkflow()
|
||||
->addSubscriberToFinal($user)
|
||||
->addSubscriberToStep($user);
|
||||
->addSubscriberToFinal($user);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@@ -11,28 +11,22 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Export;
|
||||
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
interface DirectExportInterface extends ExportElementInterface
|
||||
{
|
||||
/**
|
||||
* Generate the export.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function generate(array $acl, array $data = []);
|
||||
public function generate(array $acl, array $data = []): Response;
|
||||
|
||||
/**
|
||||
* get a description, which will be used in UI (and translated).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription();
|
||||
public function getDescription(): string;
|
||||
|
||||
/**
|
||||
* authorized role.
|
||||
*
|
||||
* @return \Symfony\Component\Security\Core\Role\Role
|
||||
*/
|
||||
public function requiredRole();
|
||||
public function requiredRole(): string;
|
||||
}
|
||||
|
@@ -139,10 +139,8 @@ interface ExportInterface extends ExportElementInterface
|
||||
|
||||
/**
|
||||
* Return the required Role to execute the Export.
|
||||
*
|
||||
* @return \Symfony\Component\Security\Core\Role\Role
|
||||
*/
|
||||
public function requiredRole();
|
||||
public function requiredRole(): string;
|
||||
|
||||
/**
|
||||
* Inform which ModifiersInterface (i.e. AggregatorInterface, FilterInterface)
|
||||
|
@@ -13,11 +13,9 @@ namespace Chill\MainBundle\Export;
|
||||
|
||||
use Chill\MainBundle\Form\Type\Export\ExportType;
|
||||
use Chill\MainBundle\Form\Type\Export\PickCenterType;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Generator;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
@@ -42,52 +40,36 @@ class ExportManager
|
||||
/**
|
||||
* The collected aggregators, injected by DI.
|
||||
*
|
||||
* @var AggregatorInterface[]
|
||||
* @var array|AggregatorInterface[]
|
||||
*/
|
||||
private $aggregators = [];
|
||||
private array $aggregators = [];
|
||||
|
||||
/**
|
||||
* @var AuthorizationChecker
|
||||
*/
|
||||
private $authorizationChecker;
|
||||
private AuthorizationCheckerInterface $authorizationChecker;
|
||||
|
||||
/**
|
||||
* @var AuthorizationHelper
|
||||
*/
|
||||
private $authorizationHelper;
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
private AuthorizationHelperInterface $authorizationHelper;
|
||||
|
||||
/**
|
||||
* Collected Exports, injected by DI.
|
||||
*
|
||||
* @var ExportInterface[]
|
||||
* @var array|ExportInterface[]
|
||||
*/
|
||||
private $exports = [];
|
||||
private array $exports = [];
|
||||
|
||||
/**
|
||||
* The collected filters, injected by DI.
|
||||
*
|
||||
* @var FilterInterface[]
|
||||
* @var array|FilterInterface[]
|
||||
*/
|
||||
private $filters = [];
|
||||
private array $filters = [];
|
||||
|
||||
/**
|
||||
* Collected Formatters, injected by DI.
|
||||
*
|
||||
* @var FormatterInterface[]
|
||||
* @var array|FormatterInterface[]
|
||||
*/
|
||||
private $formatters = [];
|
||||
private array $formatters = [];
|
||||
|
||||
/**
|
||||
* a logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\Security\Core\User\UserInterface
|
||||
@@ -96,16 +78,28 @@ class ExportManager
|
||||
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
EntityManagerInterface $em,
|
||||
AuthorizationCheckerInterface $authorizationChecker,
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
TokenStorageInterface $tokenStorage
|
||||
AuthorizationHelperInterface $authorizationHelper,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
iterable $exports,
|
||||
iterable $aggregators,
|
||||
iterable $filters
|
||||
//iterable $formatters,
|
||||
//iterable $exportElementProvider
|
||||
) {
|
||||
$this->logger = $logger;
|
||||
$this->em = $em;
|
||||
$this->authorizationChecker = $authorizationChecker;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
$this->user = $tokenStorage->getToken()->getUser();
|
||||
$this->exports = iterator_to_array($exports);
|
||||
$this->aggregators = iterator_to_array($aggregators);
|
||||
$this->filters = iterator_to_array($filters);
|
||||
// NOTE: PHP crashes on the next line (exit error code 11). This is desactivated until further investigation
|
||||
//$this->formatters = iterator_to_array($formatters);
|
||||
|
||||
//foreach ($exportElementProvider as $prefix => $provider) {
|
||||
// $this->addExportElementsProvider($provider, $prefix);
|
||||
//}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,52 +149,17 @@ class ExportManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add an aggregator.
|
||||
*
|
||||
* @internal used by DI
|
||||
*
|
||||
* @param string $alias
|
||||
*/
|
||||
public function addAggregator(AggregatorInterface $aggregator, $alias)
|
||||
{
|
||||
$this->aggregators[$alias] = $aggregator;
|
||||
}
|
||||
|
||||
/**
|
||||
* add an export.
|
||||
*
|
||||
* @internal used by DI
|
||||
*
|
||||
* @param DirectExportInterface|ExportInterface $export
|
||||
* @param type $alias
|
||||
*/
|
||||
public function addExport($export, $alias)
|
||||
{
|
||||
if ($export instanceof ExportInterface || $export instanceof DirectExportInterface) {
|
||||
$this->exports[$alias] = $export;
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'The export with alias %s '
|
||||
. 'does not implements %s or %s.',
|
||||
$alias,
|
||||
ExportInterface::class,
|
||||
DirectExportInterface::class
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public function addExportElementsProvider(ExportElementsProviderInterface $provider, $prefix)
|
||||
{
|
||||
foreach ($provider->getExportElements() as $suffix => $element) {
|
||||
$alias = $prefix . '_' . $suffix;
|
||||
|
||||
if ($element instanceof ExportInterface) {
|
||||
$this->addExport($element, $alias);
|
||||
$this->exports[$alias] = $element;
|
||||
} elseif ($element instanceof FilterInterface) {
|
||||
$this->addFilter($element, $alias);
|
||||
$this->filters[$alias] = $element;
|
||||
} elseif ($element instanceof AggregatorInterface) {
|
||||
$this->addAggregator($element, $alias);
|
||||
$this->aggregators[$alias] = $element;
|
||||
} elseif ($element instanceof FormatterInterface) {
|
||||
$this->addFormatter($element, $alias);
|
||||
} else {
|
||||
@@ -210,24 +169,12 @@ class ExportManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add a Filter.
|
||||
*
|
||||
* @internal Normally used by the dependency injection
|
||||
*
|
||||
* @param string $alias
|
||||
*/
|
||||
public function addFilter(FilterInterface $filter, $alias)
|
||||
{
|
||||
$this->filters[$alias] = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* add a formatter.
|
||||
*
|
||||
* @internal used by DI
|
||||
*
|
||||
* @param type $alias
|
||||
* @param string $alias
|
||||
*/
|
||||
public function addFormatter(FormatterInterface $formatter, $alias)
|
||||
{
|
||||
@@ -245,7 +192,6 @@ class ExportManager
|
||||
public function generate($exportAlias, array $pickedCentersData, array $data, array $formatterData)
|
||||
{
|
||||
$export = $this->getExport($exportAlias);
|
||||
//$qb = $this->em->createQueryBuilder();
|
||||
$centers = $this->getPickedCenters($pickedCentersData);
|
||||
|
||||
if ($export instanceof DirectExportInterface) {
|
||||
@@ -547,31 +493,24 @@ class ExportManager
|
||||
. 'an ExportInterface.');
|
||||
}
|
||||
|
||||
if (null === $centers) {
|
||||
$centers = $this->authorizationHelper->getReachableCenters(
|
||||
if (null === $centers || [] !== $centers) {
|
||||
// we want to try if at least one center is reachabler
|
||||
return [] !== $this->authorizationHelper->getReachableCenters(
|
||||
$this->user,
|
||||
$role->getRole(),
|
||||
$role
|
||||
);
|
||||
}
|
||||
|
||||
if (count($centers) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($centers as $center) {
|
||||
if ($this->authorizationChecker->isGranted($role->getRole(), $center) === false) {
|
||||
if (false === $this->authorizationChecker->isGranted($role, $center)) {
|
||||
//debugging
|
||||
$this->logger->debug('user has no access to element', [
|
||||
'method' => __METHOD__,
|
||||
'type' => get_class($element),
|
||||
'center' => $center->getName(),
|
||||
'role' => $role->getRole(),
|
||||
'role' => $role,
|
||||
]);
|
||||
|
||||
///// Bypasse les autorisations qui empêche d'afficher les nouveaux exports
|
||||
return true;
|
||||
///// TODO supprimer le return true
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -594,7 +533,7 @@ class ExportManager
|
||||
'center' => $center,
|
||||
'circles' => $this->authorizationHelper->getReachableScopes(
|
||||
$this->user,
|
||||
$element->requiredRole()->getRole(),
|
||||
$element->requiredRole(),
|
||||
$center
|
||||
),
|
||||
];
|
||||
|
@@ -230,7 +230,8 @@ class SpreadSheetFormatter implements FormatterInterface
|
||||
$worksheet->fromArray(
|
||||
$sortedResults,
|
||||
null,
|
||||
'A' . $line
|
||||
'A' . $line,
|
||||
true
|
||||
);
|
||||
|
||||
return $line + count($sortedResults) + 1;
|
||||
@@ -444,6 +445,8 @@ class SpreadSheetFormatter implements FormatterInterface
|
||||
$this->initializeCache($key);
|
||||
}
|
||||
|
||||
$value = null === $value ? '' : $value;
|
||||
|
||||
return call_user_func($this->cacheDisplayableResult[$key], $value);
|
||||
}
|
||||
|
||||
@@ -495,8 +498,13 @@ class SpreadSheetFormatter implements FormatterInterface
|
||||
// 3. iterate on `keysExportElementAssociation` to store the callable
|
||||
// in cache
|
||||
foreach ($keysExportElementAssociation as $key => [$element, $data]) {
|
||||
$this->cacheDisplayableResult[$key] =
|
||||
$element->getLabels($key, array_unique($allValues[$key]), $data);
|
||||
// handle the case when there is not results lines (query is empty)
|
||||
if ([] === $allValues) {
|
||||
$this->cacheDisplayableResult[$key] = $element->getLabels($key, ['_header'], $data);
|
||||
} else {
|
||||
$this->cacheDisplayableResult[$key] =
|
||||
$element->getLabels($key, array_unique($allValues[$key]), $data);
|
||||
}
|
||||
}
|
||||
|
||||
// the cache is initialized !
|
||||
|
@@ -20,11 +20,12 @@ use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
@@ -80,7 +81,7 @@ class SpreadsheetListFormatter implements FormatterInterface
|
||||
*
|
||||
* @uses appendAggregatorForm
|
||||
*
|
||||
* @param type $exportAlias
|
||||
* @param string $exportAlias
|
||||
*/
|
||||
public function buildForm(
|
||||
FormBuilderInterface $builder,
|
||||
@@ -144,8 +145,6 @@ class SpreadsheetListFormatter implements FormatterInterface
|
||||
$i = 1;
|
||||
|
||||
foreach ($result as $row) {
|
||||
$line = [];
|
||||
|
||||
if (true === $this->formatterData['numerotation']) {
|
||||
$worksheet->setCellValue('A' . ($i + 1), (string) $i);
|
||||
}
|
||||
@@ -155,13 +154,22 @@ class SpreadsheetListFormatter implements FormatterInterface
|
||||
foreach ($row as $key => $value) {
|
||||
$row = $a . ($i + 1);
|
||||
|
||||
if ($value instanceof DateTimeInterface) {
|
||||
$worksheet->setCellValue($row, Date::PHPToExcel($value));
|
||||
$worksheet->getStyle($row)
|
||||
->getNumberFormat()
|
||||
->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY);
|
||||
$formattedValue = $this->getLabel($key, $value);
|
||||
|
||||
if ($formattedValue instanceof DateTimeInterface) {
|
||||
$worksheet->setCellValue($row, Date::PHPToExcel($formattedValue));
|
||||
|
||||
if ($formattedValue->format('His') === '000000') {
|
||||
$worksheet->getStyle($row)
|
||||
->getNumberFormat()
|
||||
->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY);
|
||||
} else {
|
||||
$worksheet->getStyle($row)
|
||||
->getNumberFormat()
|
||||
->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME);
|
||||
}
|
||||
} else {
|
||||
$worksheet->setCellValue($row, $this->getLabel($key, $value));
|
||||
$worksheet->setCellValue($row, $formattedValue);
|
||||
}
|
||||
++$a;
|
||||
}
|
||||
@@ -259,6 +267,10 @@ class SpreadsheetListFormatter implements FormatterInterface
|
||||
foreach ($keys as $key) {
|
||||
// get an array with all values for this key if possible
|
||||
$values = array_map(static function ($v) use ($key) {
|
||||
if (!array_key_exists($key, $v)) {
|
||||
throw new RuntimeException(sprintf('This key does not exists: %s. Available keys are %s', $key, implode(', ', array_keys($v))));
|
||||
}
|
||||
|
||||
return $v[$key];
|
||||
}, $this->result);
|
||||
// store the label in the labelsCache property
|
||||
|
60
src/Bundle/ChillMainBundle/Export/Helper/DateTimeHelper.php
Normal file
60
src/Bundle/ChillMainBundle/Export/Helper/DateTimeHelper.php
Normal file
@@ -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\Export\Helper;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class DateTimeHelper
|
||||
{
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function getLabel($header): callable
|
||||
{
|
||||
return function ($value) use ($header) {
|
||||
if ('_header' === $value) {
|
||||
return $this->translator->trans($header);
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// warning: won't work with DateTimeImmutable as we reset time a few lines later
|
||||
$date = DateTime::createFromFormat('Y-m-d', $value);
|
||||
$hasTime = false;
|
||||
|
||||
if (false === $date) {
|
||||
$date = DateTime::createFromFormat('Y-m-d H:i:s', $value);
|
||||
$hasTime = true;
|
||||
}
|
||||
|
||||
// check that the creation could occurs.
|
||||
if (false === $date) {
|
||||
throw new Exception(sprintf('The value %s could '
|
||||
. 'not be converted to %s', $value, DateTime::class));
|
||||
}
|
||||
|
||||
if (!$hasTime) {
|
||||
$date->setTime(0, 0, 0);
|
||||
}
|
||||
|
||||
return $date;
|
||||
};
|
||||
}
|
||||
}
|
426
src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php
Normal file
426
src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php
Normal file
@@ -0,0 +1,426 @@
|
||||
<?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\Helper;
|
||||
|
||||
use Chill\MainBundle\Entity\GeographicalUnit;
|
||||
use Chill\MainBundle\Entity\GeographicalUnitLayer;
|
||||
use Chill\MainBundle\Repository\AddressRepository;
|
||||
use Chill\MainBundle\Repository\GeographicalUnitLayerRepositoryInterface;
|
||||
use Chill\MainBundle\Templating\Entity\AddressRender;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use LogicException;
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Helps to load addresses and format them in list.
|
||||
*/
|
||||
class ExportAddressHelper
|
||||
{
|
||||
/**
|
||||
* Compute all the F_* constants.
|
||||
*/
|
||||
public const F_ALL =
|
||||
self::F_ATTRIBUTES | self::F_BUILDING | self::F_COUNTRY |
|
||||
self::F_GEOM | self::F_POSTAL_CODE | self::F_STREET | self::F_GEOGRAPHICAL_UNITS;
|
||||
|
||||
public const F_AS_STRING = 0b00010000;
|
||||
|
||||
public const F_ATTRIBUTES = 0b01000000;
|
||||
|
||||
public const F_BUILDING = 0b00001000;
|
||||
|
||||
public const F_COUNTRY = 0b00000001;
|
||||
|
||||
public const F_GEOGRAPHICAL_UNITS = 0b1000000000;
|
||||
|
||||
public const F_GEOM = 0b00100000;
|
||||
|
||||
public const F_POSTAL_CODE = 0b00000010;
|
||||
|
||||
public const F_STREET = 0b00000100;
|
||||
|
||||
private const ALL = [
|
||||
'country' => self::F_COUNTRY,
|
||||
'postal_code' => self::F_POSTAL_CODE,
|
||||
'street' => self::F_STREET,
|
||||
'building' => self::F_BUILDING,
|
||||
'string' => self::F_AS_STRING,
|
||||
'geom' => self::F_GEOM,
|
||||
'attributes' => self::F_ATTRIBUTES,
|
||||
'geographical_units' => self::F_GEOGRAPHICAL_UNITS,
|
||||
];
|
||||
|
||||
private const COLUMN_MAPPING = [
|
||||
'country' => ['country'],
|
||||
'postal_code' => ['postcode_code', 'postcode_name'],
|
||||
'street' => ['street', 'streetNumber'],
|
||||
'building' => ['buildingName', 'corridor', 'distribution', 'extra', 'flat', 'floor', 'steps'],
|
||||
'string' => ['_as_string'],
|
||||
'attributes' => ['isNoAddress', 'confidential', 'id'],
|
||||
'geom' => ['_lat', '_lon'],
|
||||
'geographical_units' => ['_unit_names', '_unit_refs'],
|
||||
];
|
||||
|
||||
private AddressRender $addressRender;
|
||||
|
||||
private AddressRepository $addressRepository;
|
||||
|
||||
private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository;
|
||||
|
||||
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||
|
||||
/**
|
||||
* @var array<string, string, GeographicalUnitLayer>>|null
|
||||
*/
|
||||
private ?array $unitNamesKeysCache = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, GeographicalUnitLayer>>|null
|
||||
*/
|
||||
private ?array $unitRefsKeysCache = [];
|
||||
|
||||
public function __construct(
|
||||
AddressRender $addressRender,
|
||||
AddressRepository $addressRepository,
|
||||
GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository,
|
||||
TranslatableStringHelperInterface $translatableStringHelper
|
||||
) {
|
||||
$this->addressRepository = $addressRepository;
|
||||
$this->geographicalUnitLayerRepository = $geographicalUnitLayerRepository;
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
$this->addressRender = $addressRender;
|
||||
}
|
||||
|
||||
public function addSelectClauses(int $params, QueryBuilder $queryBuilder, $entityName = 'address', $prefix = 'add')
|
||||
{
|
||||
foreach (self::ALL as $key => $bitmask) {
|
||||
if (($params & $bitmask) === $bitmask) {
|
||||
foreach (self::COLUMN_MAPPING[$key] as $field) {
|
||||
switch ($field) {
|
||||
case 'id':
|
||||
case '_as_string':
|
||||
$queryBuilder->addSelect(sprintf('%s.id AS %s%s', $entityName, $prefix, $field));
|
||||
|
||||
break;
|
||||
|
||||
case 'street':
|
||||
case 'streetNumber':
|
||||
case 'floor':
|
||||
case 'corridor':
|
||||
case 'steps':
|
||||
case 'buildingName':
|
||||
case 'flat':
|
||||
case 'distribution':
|
||||
case 'extra':
|
||||
$queryBuilder->addSelect(sprintf('%s.%s AS %s%s', $entityName, $field, $prefix, $field));
|
||||
|
||||
break;
|
||||
|
||||
case 'country':
|
||||
case 'postcode_name':
|
||||
case 'postcode_code':
|
||||
$postCodeAlias = sprintf('%spostcode_t', $prefix);
|
||||
|
||||
if (!in_array($postCodeAlias, $queryBuilder->getAllAliases(), true)) {
|
||||
$queryBuilder->leftJoin($entityName . '.postcode', $postCodeAlias);
|
||||
}
|
||||
|
||||
if ('postcode_name' === $field) {
|
||||
$queryBuilder->addSelect(sprintf('%s.%s AS %s%s', $postCodeAlias, 'name', $prefix, $field));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ('postcode_code' === $field) {
|
||||
$queryBuilder->addSelect(sprintf('%s.%s AS %s%s', $postCodeAlias, 'code', $prefix, $field));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$countryAlias = sprintf('%scountry_t', $prefix);
|
||||
|
||||
if (!in_array($countryAlias, $queryBuilder->getAllAliases(), true)) {
|
||||
$queryBuilder->leftJoin(sprintf('%s.country', $postCodeAlias), $countryAlias);
|
||||
}
|
||||
|
||||
$queryBuilder->addSelect(sprintf('%s.%s AS %s%s', $countryAlias, 'name', $prefix, $field));
|
||||
|
||||
break;
|
||||
|
||||
case 'isNoAddress':
|
||||
case 'confidential':
|
||||
$queryBuilder->addSelect(sprintf('CASE WHEN %s.%s = \'TRUE\' THEN 1 ELSE 0 END AS %s%s', $entityName, $field, $prefix, $field));
|
||||
|
||||
break;
|
||||
|
||||
case '_lat':
|
||||
$queryBuilder->addSelect(sprintf('ST_Y(%s.point) AS %s%s', $entityName, $prefix, $field));
|
||||
|
||||
break;
|
||||
|
||||
case '_lon':
|
||||
$queryBuilder->addSelect(sprintf('ST_X(%s.point) AS %s%s', $entityName, $prefix, $field));
|
||||
|
||||
break;
|
||||
|
||||
case '_unit_names':
|
||||
foreach ($this->generateKeysForUnitsNames($prefix) as $alias => $layer) {
|
||||
$queryBuilder
|
||||
->addSelect(
|
||||
sprintf(
|
||||
'(SELECT AGGREGATE(u_n_%s_%s.unitName) FROM %s u_n_%s_%s WHERE u_n_%s_%s MEMBER OF %s.geographicalUnits AND u_n_%s_%s.layer = :layer_%s_%s) AS %s',
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
GeographicalUnit::class,
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$entityName,
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$alias
|
||||
)
|
||||
)
|
||||
->setParameter(sprintf('layer_%s_%s', $prefix, $layer->getId()), $layer);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '_unit_refs':
|
||||
foreach ($this->generateKeysForUnitsRefs($prefix) as $alias => $layer) {
|
||||
$queryBuilder
|
||||
->addSelect(
|
||||
sprintf(
|
||||
'(SELECT AGGREGATE(u_r_%s_%s.unitRefId) FROM %s u_r_%s_%s WHERE u_r_%s_%s MEMBER OF %s.geographicalUnits AND u_r_%s_%s.layer = :layer_%s_%s) AS %s',
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
GeographicalUnit::class,
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$entityName,
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$alias
|
||||
)
|
||||
)
|
||||
->setParameter(sprintf('layer_%s_%s', $prefix, $layer->getId()), $layer);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new LogicException(sprintf('This key is not supported: %s, field %s', $key, $field));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param self::F_* $params
|
||||
*
|
||||
* @return array|string[]
|
||||
*/
|
||||
public function getKeys(int $params, string $prefix = ''): array
|
||||
{
|
||||
$prefixes = [];
|
||||
|
||||
foreach (self::ALL as $key => $bitmask) {
|
||||
if (($params & $bitmask) === $bitmask) {
|
||||
if ('geographical_units' === $key) {
|
||||
// geographical unit generate keys dynamically, depending on layers
|
||||
$prefixes = array_merge($prefixes, array_keys($this->generateKeysForUnitsNames($prefix)), array_keys($this->generateKeysForUnitsRefs($prefix)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$prefixes = array_merge(
|
||||
$prefixes,
|
||||
array_map(
|
||||
static function ($item) use ($prefix) {
|
||||
return $prefix . $item;
|
||||
},
|
||||
self::COLUMN_MAPPING[$key]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $prefixes;
|
||||
}
|
||||
|
||||
public function getLabel($key, array $values, $data, string $prefix = '', string $translationPrefix = 'export.address_helper.'): callable
|
||||
{
|
||||
$sanitizedKey = substr($key, strlen($prefix));
|
||||
|
||||
switch ($sanitizedKey) {
|
||||
case 'id':
|
||||
case 'street':
|
||||
case 'streetNumber':
|
||||
case 'buildingName':
|
||||
case 'corridor':
|
||||
case 'distribution':
|
||||
case 'extra':
|
||||
case 'flat':
|
||||
case 'floor':
|
||||
case '_lat':
|
||||
case '_lon':
|
||||
case 'steps':
|
||||
case 'postcode_code':
|
||||
case 'postcode_name':
|
||||
return static function ($value) use ($sanitizedKey, $translationPrefix) {
|
||||
if ('_header' === $value) {
|
||||
return $translationPrefix . $sanitizedKey;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $value;
|
||||
};
|
||||
|
||||
case 'country':
|
||||
return function ($value) use ($key) {
|
||||
if ('_header' === $value) {
|
||||
return 'export.list.acp' . $key;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->translatableStringHelper->localize(json_decode($value, true));
|
||||
};
|
||||
|
||||
case 'isNoAddress':
|
||||
case 'confidential':
|
||||
return static function ($value) use ($sanitizedKey, $translationPrefix) {
|
||||
if ('_header' === $value) {
|
||||
return $translationPrefix . $sanitizedKey;
|
||||
}
|
||||
|
||||
switch ($value) {
|
||||
case null:
|
||||
return '';
|
||||
|
||||
case true:
|
||||
return 1;
|
||||
|
||||
case false:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
throw new LogicException('this value is not supported for ' . $sanitizedKey . ': ' . $value);
|
||||
}
|
||||
};
|
||||
|
||||
case '_as_string':
|
||||
return function ($value) use ($sanitizedKey, $translationPrefix) {
|
||||
if ('_header' === $value) {
|
||||
return $translationPrefix . $sanitizedKey;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$address = $this->addressRepository->find($value);
|
||||
|
||||
return $this->addressRender->renderString($address, []);
|
||||
};
|
||||
|
||||
default:
|
||||
$layerNamesKeys = array_merge($this->generateKeysForUnitsNames($prefix), $this->generateKeysForUnitsRefs($prefix));
|
||||
|
||||
if (array_key_exists($key, $layerNamesKeys)) {
|
||||
return function ($value) use ($key, $layerNamesKeys) {
|
||||
if ('_header' === $value) {
|
||||
$header = $this->translatableStringHelper->localize($layerNamesKeys[$key]->getName());
|
||||
|
||||
if (str_contains($key, 'unit_ref')) {
|
||||
$header .= ' (id)';
|
||||
}
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$decodedValues = json_decode($value, true);
|
||||
|
||||
switch (count($decodedValues)) {
|
||||
case 0:
|
||||
return '';
|
||||
|
||||
case 1:
|
||||
return $decodedValues[0];
|
||||
|
||||
default:
|
||||
return implode('|', $decodedValues);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
throw new LogicException('this key is not supported: ' . $sanitizedKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, GeographicalUnitLayer>
|
||||
*/
|
||||
private function generateKeysForUnitsNames(string $prefix): array
|
||||
{
|
||||
if (array_key_exists($prefix, $this->unitNamesKeysCache)) {
|
||||
return $this->unitNamesKeysCache[$prefix];
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
|
||||
foreach ($this->geographicalUnitLayerRepository->findAllHavingUnits() as $layer) {
|
||||
$keys[$prefix . 'unit_names_' . $layer->getId()] = $layer;
|
||||
}
|
||||
|
||||
return $this->unitNamesKeysCache[$prefix] = $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, GeographicalUnitLayer>
|
||||
*/
|
||||
private function generateKeysForUnitsRefs(string $prefix): array
|
||||
{
|
||||
if (array_key_exists($prefix, $this->unitRefsKeysCache)) {
|
||||
return $this->unitRefsKeysCache[$prefix];
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
|
||||
foreach ($this->geographicalUnitLayerRepository->findAllHavingUnits() as $layer) {
|
||||
$keys[$prefix . 'unit_refs_' . $layer->getId()] = $layer;
|
||||
}
|
||||
|
||||
return $this->unitRefsKeysCache[$prefix] = $keys;
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
<?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\Helper;
|
||||
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
|
||||
/**
|
||||
* This class provides support for showing translatable string into list or exports.
|
||||
*
|
||||
* (note: the name of the class contains "ExportLabelHelper" to give a distinction
|
||||
* with TranslatableStringHelper.)
|
||||
*/
|
||||
class TranslatableStringExportLabelHelper
|
||||
{
|
||||
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||
|
||||
public function __construct(TranslatableStringHelperInterface $translatableStringHelper)
|
||||
{
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
}
|
||||
|
||||
public function getLabel(string $key, array $values, string $header)
|
||||
{
|
||||
return function ($value) use ($header) {
|
||||
if ('_header' === $value) {
|
||||
return $header;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->translatableStringHelper->localize(json_decode($value, true));
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabelMulti(string $key, array $values, string $header)
|
||||
{
|
||||
return function ($value) use ($header) {
|
||||
if ('_header' === $value) {
|
||||
return $header;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$decoded = json_decode($value, true);
|
||||
|
||||
return implode(
|
||||
'|',
|
||||
array_unique(
|
||||
array_map(
|
||||
fn (array $translatableString) => $this->translatableStringHelper->localize($translatableString),
|
||||
array_filter($decoded, static fn ($elem) => null !== $elem)
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
84
src/Bundle/ChillMainBundle/Export/Helper/UserHelper.php
Normal file
84
src/Bundle/ChillMainBundle/Export/Helper/UserHelper.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?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\Helper;
|
||||
|
||||
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||
use Chill\MainBundle\Templating\Entity\UserRender;
|
||||
use function count;
|
||||
use const SORT_NUMERIC;
|
||||
|
||||
class UserHelper
|
||||
{
|
||||
private UserRender $userRender;
|
||||
|
||||
private UserRepositoryInterface $userRepository;
|
||||
|
||||
public function __construct(UserRender $userRender, UserRepositoryInterface $userRepository)
|
||||
{
|
||||
$this->userRender = $userRender;
|
||||
$this->userRepository = $userRepository;
|
||||
}
|
||||
|
||||
public function getLabel($key, array $values, string $header): callable
|
||||
{
|
||||
return function ($value) use ($header) {
|
||||
if ('_header' === $value) {
|
||||
return $header;
|
||||
}
|
||||
|
||||
if (null === $value || null === $user = $this->userRepository->find($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->userRender->renderString($user, []);
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabelMulti($key, array $values, string $header): callable
|
||||
{
|
||||
return function ($value) {
|
||||
if ('_header' === $value) {
|
||||
return 'users name';
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$decoded = json_decode($value);
|
||||
|
||||
if (0 === count($decoded)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return
|
||||
implode(
|
||||
'|',
|
||||
array_map(
|
||||
function (int $userId) {
|
||||
$user = $this->userRepository->find($userId);
|
||||
|
||||
if (null === $user) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->userRender->renderString($user, []);
|
||||
},
|
||||
array_unique(
|
||||
array_filter($decoded, static fn (?int $userId) => null !== $userId),
|
||||
SORT_NUMERIC
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
@@ -26,9 +26,9 @@ interface ModifierInterface extends ExportElementInterface
|
||||
* If null, will used the ExportInterface::requiredRole role from
|
||||
* the current executing export.
|
||||
*
|
||||
* @return \Symfony\Component\Security\Core\Role\Role|null A role required to execute this ModifiersInterface
|
||||
* @return string|null A role required to execute this ModifiersInterface
|
||||
*/
|
||||
public function addRole();
|
||||
public function addRole(): ?string;
|
||||
|
||||
/**
|
||||
* Alter the query initiated by the export, to add the required statements
|
||||
|
@@ -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\Form\DataMapper;
|
||||
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\Exception;
|
||||
|
||||
class RollingDateDataMapper implements DataMapperInterface
|
||||
{
|
||||
public function mapDataToForms($viewData, $forms)
|
||||
{
|
||||
if (null === $viewData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$viewData instanceof RollingDate) {
|
||||
throw new Exception\UnexpectedTypeException($viewData, RollingDate::class);
|
||||
}
|
||||
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
$forms['roll']->setData($viewData->getRoll());
|
||||
$forms['fixedDate']->setData($viewData->getFixedDate());
|
||||
}
|
||||
|
||||
public function mapFormsToData($forms, &$viewData): void
|
||||
{
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
$viewData = new RollingDate(
|
||||
$forms['roll']->getData(),
|
||||
$forms['fixedDate']->getData()
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,110 @@
|
||||
<?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\Form\DataTransformer;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use function call_user_func;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
class IdToEntityDataTransformer implements DataTransformerInterface
|
||||
{
|
||||
private Closure $getId;
|
||||
|
||||
private bool $multiple = false;
|
||||
|
||||
private ObjectRepository $repository;
|
||||
|
||||
/**
|
||||
* @param Closure $getId
|
||||
*/
|
||||
public function __construct(ObjectRepository $repository, bool $multiple = false, ?callable $getId = null)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
$this->multiple = $multiple;
|
||||
$this->getId = $getId ?? static function (object $o) { return $o->getId(); };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*
|
||||
* @return array|object[]|T[]|T|object
|
||||
*/
|
||||
public function reverseTransform($value)
|
||||
{
|
||||
if ($this->multiple) {
|
||||
if (null === $value | '' === $value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
fn (string $id): ?object => $this->repository->findOneBy(['id' => (int) $id]),
|
||||
explode(',', $value)
|
||||
);
|
||||
}
|
||||
|
||||
if (null === $value | '' === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$object = $this->repository->findOneBy(['id' => (int) $value]);
|
||||
|
||||
if (null === $object) {
|
||||
throw new TransformationFailedException('could not find any object by object id');
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object|T|object[]|T[] $value
|
||||
*/
|
||||
public function transform($value): string
|
||||
{
|
||||
if ($this->multiple) {
|
||||
$ids = [];
|
||||
|
||||
foreach ($value as $v) {
|
||||
$ids[] = $id = call_user_func($this->getId, $v);
|
||||
|
||||
if (null === $id) {
|
||||
throw new TransformationFailedException('id is null');
|
||||
}
|
||||
}
|
||||
|
||||
return implode(',', $ids);
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$id = call_user_func($this->getId, $value);
|
||||
|
||||
if (null === $id) {
|
||||
throw new TransformationFailedException('id is null');
|
||||
}
|
||||
|
||||
return (string) $id;
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
<?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\Form\DataTransformer;
|
||||
|
||||
use Chill\MainBundle\Repository\LocationRepository;
|
||||
|
||||
class IdToLocationDataTransformer extends IdToEntityDataTransformer
|
||||
{
|
||||
public function __construct(LocationRepository $repository)
|
||||
{
|
||||
parent::__construct($repository, false);
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
<?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\Form\DataTransformer;
|
||||
|
||||
use Chill\MainBundle\Repository\UserRepository;
|
||||
|
||||
class IdToUserDataTransformer extends IdToEntityDataTransformer
|
||||
{
|
||||
public function __construct(UserRepository $repository)
|
||||
{
|
||||
parent::__construct($repository, false);
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
<?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\Form\DataTransformer;
|
||||
|
||||
use Chill\MainBundle\Repository\UserRepository;
|
||||
|
||||
class IdToUsersDataTransformer extends IdToEntityDataTransformer
|
||||
{
|
||||
public function __construct(UserRepository $repository)
|
||||
{
|
||||
parent::__construct($repository, true);
|
||||
}
|
||||
}
|
40
src/Bundle/ChillMainBundle/Form/SavedExportType.php
Normal file
40
src/Bundle/ChillMainBundle/Form/SavedExportType.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?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\SavedExport;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class SavedExportType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('title', TextType::class, [
|
||||
'required' => true,
|
||||
])
|
||||
->add('description', ChillTextareaType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'class' => SavedExport::class,
|
||||
]);
|
||||
}
|
||||
}
|
@@ -51,7 +51,9 @@ class EntityToJsonTransformer implements DataTransformerInterface
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function ($item) { return $this->denormalizeOne($item); },
|
||||
function ($item) {
|
||||
return $this->denormalizeOne($item);
|
||||
},
|
||||
$denormalized
|
||||
);
|
||||
}
|
||||
|
@@ -14,8 +14,7 @@ namespace Chill\MainBundle\Form\Type\Export;
|
||||
use Chill\MainBundle\Center\GroupingCenterInterface;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
@@ -24,6 +23,7 @@ use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use function array_intersect;
|
||||
use function array_key_exists;
|
||||
use function array_merge;
|
||||
@@ -38,30 +38,21 @@ class PickCenterType extends AbstractType
|
||||
{
|
||||
public const CENTERS_IDENTIFIERS = 'c';
|
||||
|
||||
/**
|
||||
* @var AuthorizationHelper
|
||||
*/
|
||||
protected $authorizationHelper;
|
||||
protected AuthorizationHelperInterface $authorizationHelper;
|
||||
|
||||
protected ExportManager $exportManager;
|
||||
|
||||
/**
|
||||
* @var ExportManager
|
||||
* @var array|GroupingCenterInterface[]
|
||||
*/
|
||||
protected $exportManager;
|
||||
protected array $groupingCenters = [];
|
||||
|
||||
/**
|
||||
* @var GroupingCenterInterface[]
|
||||
*/
|
||||
protected $groupingCenters = [];
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\Security\Core\User\UserInterface
|
||||
*/
|
||||
protected $user;
|
||||
protected UserInterface $user;
|
||||
|
||||
public function __construct(
|
||||
TokenStorageInterface $tokenStorage,
|
||||
ExportManager $exportManager,
|
||||
AuthorizationHelper $authorizationHelper
|
||||
AuthorizationHelperInterface $authorizationHelper
|
||||
) {
|
||||
$this->exportManager = $exportManager;
|
||||
$this->user = $tokenStorage->getToken()->getUser();
|
||||
@@ -78,22 +69,12 @@ class PickCenterType extends AbstractType
|
||||
$export = $this->exportManager->getExport($options['export_alias']);
|
||||
$centers = $this->authorizationHelper->getReachableCenters(
|
||||
$this->user,
|
||||
(string) $export->requiredRole()
|
||||
$export->requiredRole()
|
||||
);
|
||||
|
||||
$builder->add(self::CENTERS_IDENTIFIERS, EntityType::class, [
|
||||
'class' => Center::class,
|
||||
'query_builder' => static function (EntityRepository $er) use ($centers) {
|
||||
$qb = $er->createQueryBuilder('c');
|
||||
$ids = array_map(
|
||||
static function (Center $el) {
|
||||
return $el->getId();
|
||||
},
|
||||
$centers
|
||||
);
|
||||
|
||||
return $qb->where($qb->expr()->in('c.id', $ids));
|
||||
},
|
||||
'choices' => $centers,
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'choice_label' => static function (Center $c) {
|
||||
|
@@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Form\Type\Listing;
|
||||
|
||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
@@ -70,10 +71,38 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType
|
||||
$builder->add($checkboxesBuilder);
|
||||
}
|
||||
|
||||
if (0 < count($helper->getDateRanges())) {
|
||||
$dateRangesBuilder = $builder->create('dateRanges', null, ['compound' => true]);
|
||||
|
||||
foreach ($helper->getDateRanges() as $name => $opts) {
|
||||
$rangeBuilder = $dateRangesBuilder->create($name, null, [
|
||||
'compound' => true,
|
||||
'label' => null === $opts['label'] ? false : $opts['label'] ?? $name,
|
||||
]);
|
||||
|
||||
$rangeBuilder->add(
|
||||
'from',
|
||||
ChillDateType::class,
|
||||
['input' => 'datetime_immutable', 'required' => false]
|
||||
);
|
||||
$rangeBuilder->add(
|
||||
'to',
|
||||
ChillDateType::class,
|
||||
['input' => 'datetime_immutable', 'required' => false]
|
||||
);
|
||||
|
||||
$dateRangesBuilder->add($rangeBuilder);
|
||||
}
|
||||
|
||||
$builder->add($dateRangesBuilder);
|
||||
}
|
||||
|
||||
foreach ($this->requestStack->getCurrentRequest()->query->getIterator() as $key => $value) {
|
||||
switch ($key) {
|
||||
case 'q':
|
||||
case 'checkboxes' . $key:
|
||||
case $key . '_from':
|
||||
case $key . '_to':
|
||||
break;
|
||||
|
||||
case 'page':
|
||||
|
@@ -0,0 +1,50 @@
|
||||
<?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\Type;
|
||||
|
||||
use Chill\MainBundle\Entity\LocationType;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class PickLocationTypeType extends AbstractType
|
||||
{
|
||||
private TranslatableStringHelper $translatableStringHelper;
|
||||
|
||||
public function __construct(TranslatableStringHelper $translatableStringHelper)
|
||||
{
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setDefaults([
|
||||
'class' => LocationType::class,
|
||||
'choice_label' => function (LocationType $type) {
|
||||
return $this->translatableStringHelper->localize($type->getTitle());
|
||||
},
|
||||
'placeholder' => 'Pick a location type',
|
||||
'required' => false,
|
||||
'attr' => ['class' => 'select2'],
|
||||
'label' => 'Location type',
|
||||
'multiple' => false,
|
||||
])
|
||||
->setAllowedTypes('multiple', ['bool']);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return EntityType::class;
|
||||
}
|
||||
}
|
73
src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php
Normal file
73
src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?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\Type;
|
||||
|
||||
use Chill\MainBundle\Form\DataMapper\RollingDateDataMapper;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Callback;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
class PickRollingDateType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('roll', ChoiceType::class, [
|
||||
'choices' => array_combine(
|
||||
array_map(static fn (string $item) => 'rolling_date.' . $item, RollingDate::ALL_T),
|
||||
RollingDate::ALL_T
|
||||
),
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
'label' => 'rolling_date.roll_movement',
|
||||
])
|
||||
->add('fixedDate', ChillDateType::class, [
|
||||
'input' => 'datetime_immutable',
|
||||
'label' => 'rolling_date.fixed_date_date',
|
||||
]);
|
||||
|
||||
$builder->setDataMapper(new RollingDateDataMapper());
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
$view->vars['uniqid'] = uniqid('rollingdate-');
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'class' => RollingDate::class,
|
||||
'empty_data' => new RollingDate(RollingDate::T_TODAY),
|
||||
'constraints' => [
|
||||
new Callback([$this, 'validate']),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function validate($data, ExecutionContextInterface $context, $payload): void
|
||||
{
|
||||
/** @var RollingDate $data */
|
||||
if (RollingDate::T_FIXED_DATE === $data->getRoll() && null === $data->getFixedDate()) {
|
||||
$context
|
||||
->buildViolation('rolling_date.When fixed date is selected, you must provide a date')
|
||||
->atPath('fixedDate')
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
<?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\Type;
|
||||
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Chill\MainBundle\Repository\LocationRepository;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class PickUserLocationType extends AbstractType
|
||||
{
|
||||
private LocationRepository $locationRepository;
|
||||
|
||||
private TranslatableStringHelper $translatableStringHelper;
|
||||
|
||||
public function __construct(TranslatableStringHelper $translatableStringHelper, LocationRepository $locationRepository)
|
||||
{
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
$this->locationRepository = $locationRepository;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setDefaults([
|
||||
'class' => Location::class,
|
||||
'choices' => $this->locationRepository->findByPublicLocations(),
|
||||
'choice_label' => function (Location $entity) {
|
||||
return $entity->getName() ?
|
||||
$entity->getName() . ' (' . $this->translatableStringHelper->localize($entity->getLocationType()->getTitle()) . ')' :
|
||||
$this->translatableStringHelper->localize($entity->getLocationType()->getTitle());
|
||||
},
|
||||
'placeholder' => 'Pick a location',
|
||||
'required' => false,
|
||||
'attr' => ['class' => 'select2'],
|
||||
'label' => 'Current location',
|
||||
'multiple' => false,
|
||||
])
|
||||
->setAllowedTypes('multiple', ['bool']);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return EntityType::class;
|
||||
}
|
||||
}
|
@@ -15,9 +15,9 @@ use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper;
|
||||
use Chill\MainBundle\Repository\ScopeRepository;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use RuntimeException;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
@@ -26,11 +26,9 @@ use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
use function array_map;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
@@ -44,47 +42,39 @@ use function count;
|
||||
*/
|
||||
class ScopePickerType extends AbstractType
|
||||
{
|
||||
protected AuthorizationHelperInterface $authorizationHelper;
|
||||
private AuthorizationHelperInterface $authorizationHelper;
|
||||
|
||||
/**
|
||||
* @var ScopeRepository
|
||||
*/
|
||||
protected $scopeRepository;
|
||||
private Security $security;
|
||||
|
||||
protected Security $security;
|
||||
|
||||
/**
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
protected $tokenStorage;
|
||||
|
||||
/**
|
||||
* @var TranslatableStringHelper
|
||||
*/
|
||||
protected $translatableStringHelper;
|
||||
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||
|
||||
public function __construct(
|
||||
AuthorizationHelperInterface $authorizationHelper,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
ScopeRepository $scopeRepository,
|
||||
Security $security,
|
||||
TranslatableStringHelper $translatableStringHelper
|
||||
TranslatableStringHelperInterface $translatableStringHelper
|
||||
) {
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->scopeRepository = $scopeRepository;
|
||||
$this->security = $security;
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$items = $this->authorizationHelper->getReachableScopes(
|
||||
$this->security->getUser(),
|
||||
$options['role'] instanceof Role ? $options['role']->getRole() : $options['role'],
|
||||
$options['center']
|
||||
$items = array_filter(
|
||||
$this->authorizationHelper->getReachableScopes(
|
||||
$this->security->getUser(),
|
||||
$options['role'] instanceof Role ? $options['role']->getRole() : $options['role'],
|
||||
$options['center']
|
||||
),
|
||||
static function (Scope $s) {
|
||||
return $s->isActive();
|
||||
}
|
||||
);
|
||||
|
||||
if (0 === count($items)) {
|
||||
throw new RuntimeException('no scopes are reachable. This form should not be shown to user');
|
||||
}
|
||||
|
||||
if (1 !== count($items)) {
|
||||
$builder->add('scope', EntityType::class, [
|
||||
'class' => Scope::class,
|
||||
@@ -123,35 +113,4 @@ class ScopePickerType extends AbstractType
|
||||
->setRequired('role')
|
||||
->setAllowedTypes('role', ['string', Role::class]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|Center|Center[] $center
|
||||
* @param string $role
|
||||
*
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
*/
|
||||
protected function buildAccessibleScopeQuery($center, $role)
|
||||
{
|
||||
$roles = $this->authorizationHelper->getParentRoles($role);
|
||||
$roles[] = $role;
|
||||
$centers = $center instanceof Center ? [$center] : $center;
|
||||
|
||||
$qb = $this->scopeRepository->createQueryBuilder('s');
|
||||
$qb
|
||||
// jointure to center
|
||||
->join('s.roleScopes', 'rs')
|
||||
->join('rs.permissionsGroups', 'pg')
|
||||
->join('pg.groupCenters', 'gc')
|
||||
// add center constraint
|
||||
->where($qb->expr()->in('IDENTITY(gc.center)', ':centers'))
|
||||
->setParameter('centers', array_map(static fn (Center $c) => $c->getId(), $centers))
|
||||
// role constraints
|
||||
->andWhere($qb->expr()->in('rs.role', ':roles'))
|
||||
->setParameter('roles', $roles)
|
||||
// user contraint
|
||||
->andWhere(':user MEMBER OF gc.users')
|
||||
->setParameter('user', $this->tokenStorage->getToken()->getUser());
|
||||
|
||||
return $qb;
|
||||
}
|
||||
}
|
||||
|
@@ -11,39 +11,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Chill\MainBundle\Repository\LocationRepository;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Chill\MainBundle\Form\Type\PickUserLocationType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class UserCurrentLocationType extends AbstractType
|
||||
{
|
||||
private LocationRepository $locationRepository;
|
||||
|
||||
private TranslatableStringHelper $translatableStringHelper;
|
||||
|
||||
public function __construct(TranslatableStringHelper $translatableStringHelper, LocationRepository $locationRepository)
|
||||
{
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
$this->locationRepository = $locationRepository;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('currentLocation', EntityType::class, [
|
||||
'class' => Location::class,
|
||||
'choices' => $this->locationRepository->findByPublicLocations(),
|
||||
'choice_label' => function (Location $entity) {
|
||||
return $entity->getName() ?
|
||||
$entity->getName() . ' (' . $this->translatableStringHelper->localize($entity->getLocationType()->getTitle()) . ')' :
|
||||
$this->translatableStringHelper->localize($entity->getLocationType()->getTitle());
|
||||
},
|
||||
'placeholder' => 'Pick a location',
|
||||
'required' => false,
|
||||
'attr' => ['class' => 'select2'],
|
||||
]);
|
||||
$builder->add('currentLocation', PickUserLocationType::class);
|
||||
}
|
||||
}
|
||||
|
@@ -14,9 +14,8 @@ namespace Chill\MainBundle\Repository;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
final class CenterRepository implements ObjectRepository
|
||||
final class CenterRepository implements CenterRepositoryInterface
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
@@ -30,6 +29,11 @@ final class CenterRepository implements ObjectRepository
|
||||
return $this->repository->find($id, $lockMode, $lockVersion);
|
||||
}
|
||||
|
||||
public function findActive(): array
|
||||
{
|
||||
return $this->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Center[]
|
||||
*/
|
||||
|
@@ -0,0 +1,27 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
interface CenterRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
/**
|
||||
* Return all active centers.
|
||||
*
|
||||
* Note: this is a teaser: active will comes later on center entity
|
||||
*
|
||||
* @return Center[]
|
||||
*/
|
||||
public function findActive(): array;
|
||||
}
|
@@ -12,19 +12,40 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Civility;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* @method Civility|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method Civility|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method Civility[] findAll()
|
||||
* @method Civility[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class CivilityRepository extends ServiceEntityRepository
|
||||
class CivilityRepository implements CivilityRepositoryInterface
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
parent::__construct($registry, Civility::class);
|
||||
$this->repository = $entityManager->getRepository($this->getClassName());
|
||||
}
|
||||
|
||||
public function find($id): ?Civility
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria): ?Civility
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return Civility::class;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,34 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Civility;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
interface CivilityRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
public function find($id): ?Civility;
|
||||
|
||||
/**
|
||||
* @return array|Civility[]
|
||||
*/
|
||||
public function findAll(): array;
|
||||
|
||||
/**
|
||||
* @return array|Civility[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
|
||||
|
||||
public function findOneBy(array $criteria): ?Civility;
|
||||
|
||||
public function getClassName(): string;
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\CronJobExecution;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
class CronJobExecutionRepository implements CronJobExecutionRepositoryInterface
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository($this->getClassName());
|
||||
}
|
||||
|
||||
public function find($id): ?CronJobExecution
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|CronJobExecution[]
|
||||
*/
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|CronJobExecution[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria): ?CronJobExecution
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return CronJobExecution::class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\CronJobExecution;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
interface CronJobExecutionRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
public function find($id): ?CronJobExecution;
|
||||
|
||||
/**
|
||||
* @return array|CronJobExecution[]
|
||||
*/
|
||||
public function findAll(): array;
|
||||
|
||||
/**
|
||||
* @return array|CronJobExecution[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
|
||||
|
||||
public function findOneBy(array $criteria): ?CronJobExecution;
|
||||
|
||||
public function getClassName(): string;
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\GeographicalUnitLayer;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
final class GeographicalUnitLayerLayerRepository implements GeographicalUnitLayerRepositoryInterface
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->repository = $em->getRepository($this->getClassName());
|
||||
}
|
||||
|
||||
public function find($id): ?GeographicalUnitLayer
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|GeographicalUnitLayer[]
|
||||
*/
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findAllHavingUnits(): array
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('l');
|
||||
|
||||
return $qb->where($qb->expr()->gt('SIZE(l.units)', 0))
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|GeographicalUnitLayer[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria): ?GeographicalUnitLayer
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return GeographicalUnitLayer::class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\GeographicalUnitLayer;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
interface GeographicalUnitLayerRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
/**
|
||||
* @return array|GeographicalUnitLayer[]
|
||||
*/
|
||||
public function findAllHavingUnits(): array;
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\GeographicalUnit;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
class GeographicalUnitRepository implements GeographicalUnitRepositoryInterface
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->repository = $em->getRepository($this->getClassName());
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
public function find($id): ?GeographicalUnit
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will return only partial object, where the @see{GeographicalUnit::geom} property is not loaded.
|
||||
*
|
||||
* @return array|GeographicalUnit[]
|
||||
*/
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository
|
||||
->createQueryBuilder('gu')
|
||||
->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', GeographicalUnit\SimpleGeographicalUnitDTO::class))
|
||||
->addOrderBy('IDENTITY(gu.layer)')
|
||||
->addOrderBy(('gu.unitName'))
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): ?GeographicalUnit
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria): ?GeographicalUnit
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return GeographicalUnit::class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
interface GeographicalUnitRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
}
|
@@ -14,15 +14,14 @@ namespace Chill\MainBundle\Repository;
|
||||
use Chill\MainBundle\Entity\Language;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
final class LanguageRepository implements ObjectRepository
|
||||
final class LanguageRepository implements LanguageRepositoryInterface
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository(Language::class);
|
||||
$this->repository = $entityManager->getRepository($this->getClassName());
|
||||
}
|
||||
|
||||
public function find($id, $lockMode = null, $lockVersion = null): ?Language
|
||||
@@ -54,7 +53,7 @@ final class LanguageRepository implements ObjectRepository
|
||||
return $this->repository->findOneBy($criteria, $orderBy);
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
public function getClassName(): string
|
||||
{
|
||||
return Language::class;
|
||||
}
|
||||
|
@@ -0,0 +1,37 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Language;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
interface LanguageRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
public function find($id, $lockMode = null, $lockVersion = null): ?Language;
|
||||
|
||||
/**
|
||||
* @return Language[]
|
||||
*/
|
||||
public function findAll(): array;
|
||||
|
||||
/**
|
||||
* @param mixed|null $limit
|
||||
* @param mixed|null $offset
|
||||
*
|
||||
* @return Language[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array;
|
||||
|
||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?Language;
|
||||
|
||||
public function getClassName(): string;
|
||||
}
|
@@ -0,0 +1,82 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
/**
|
||||
* @implements ObjectRepository<SavedExport>
|
||||
*/
|
||||
class SavedExportRepository implements SavedExportRepositoryInterface
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository($this->getClassName());
|
||||
}
|
||||
|
||||
public function find($id): ?SavedExport
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|SavedExport[]
|
||||
*/
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findByUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('se');
|
||||
|
||||
$qb
|
||||
->where($qb->expr()->eq('se.user', ':user'))
|
||||
->setParameter('user', $user);
|
||||
|
||||
if (null !== $limit) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
|
||||
if (null !== $offset) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
|
||||
foreach ($orderBy as $field => $order) {
|
||||
$qb->addOrderBy('se.' . $field, $order);
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria): ?SavedExport
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return SavedExport::class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
/**
|
||||
* @implements ObjectRepository<SavedExport>
|
||||
*/
|
||||
interface SavedExportRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
public function find($id): ?SavedExport;
|
||||
|
||||
/**
|
||||
* @return array|SavedExport[]
|
||||
*/
|
||||
public function findAll(): array;
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
|
||||
|
||||
/**
|
||||
* @return array|SavedExport[]
|
||||
*/
|
||||
public function findByUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array;
|
||||
|
||||
public function findOneBy(array $criteria): ?SavedExport;
|
||||
|
||||
public function getClassName(): string;
|
||||
}
|
@@ -14,9 +14,9 @@ namespace Chill\MainBundle\Repository;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
final class ScopeRepository implements ObjectRepository
|
||||
final class ScopeRepository implements ScopeRepositoryInterface
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
@@ -25,7 +25,7 @@ final class ScopeRepository implements ObjectRepository
|
||||
$this->repository = $entityManager->getRepository(Scope::class);
|
||||
}
|
||||
|
||||
public function createQueryBuilder($alias, $indexBy = null)
|
||||
public function createQueryBuilder($alias, $indexBy = null): QueryBuilder
|
||||
{
|
||||
return $this->repository->createQueryBuilder($alias, $indexBy);
|
||||
}
|
||||
@@ -43,6 +43,15 @@ final class ScopeRepository implements ObjectRepository
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findAllActive(): array
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('s');
|
||||
|
||||
$qb->where('s.active = \'TRUE\'');
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed|null $limit
|
||||
* @param mixed|null $offset
|
||||
@@ -59,7 +68,7 @@ final class ScopeRepository implements ObjectRepository
|
||||
return $this->repository->findOneBy($criteria, $orderBy);
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
public function getClassName(): string
|
||||
{
|
||||
return Scope::class;
|
||||
}
|
||||
|
@@ -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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
interface ScopeRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
public function createQueryBuilder($alias, $indexBy = null): QueryBuilder;
|
||||
|
||||
public function find($id, $lockMode = null, $lockVersion = null): ?Scope;
|
||||
|
||||
/**
|
||||
* @return array|Scope[]
|
||||
*/
|
||||
public function findAll(): array;
|
||||
|
||||
/**
|
||||
* @return array|Scope[]
|
||||
*/
|
||||
public function findAllActive(): array;
|
||||
|
||||
/**
|
||||
* @param null|mixed $limit
|
||||
* @param null|mixed $offset
|
||||
*
|
||||
* @return Scope[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array;
|
||||
|
||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?Scope;
|
||||
|
||||
public function getClassName(): string;
|
||||
}
|
@@ -14,9 +14,8 @@ namespace Chill\MainBundle\Repository;
|
||||
use Chill\MainBundle\Entity\UserJob;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
class UserJobRepository implements ObjectRepository
|
||||
class UserJobRepository implements UserJobRepositoryInterface
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
@@ -38,6 +37,11 @@ class UserJobRepository implements ObjectRepository
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findAllActive(): array
|
||||
{
|
||||
return $this->repository->findBy(['active' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed|null $limit
|
||||
* @param mixed|null $offset
|
||||
@@ -49,12 +53,12 @@ class UserJobRepository implements ObjectRepository
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria)
|
||||
public function findOneBy(array $criteria): ?UserJob
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
public function getClassName(): string
|
||||
{
|
||||
return UserJob::class;
|
||||
}
|
||||
|
@@ -0,0 +1,42 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\UserJob;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
interface UserJobRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
public function find($id): ?UserJob;
|
||||
|
||||
/**
|
||||
* @return array|UserJob[]
|
||||
*/
|
||||
public function findAll(): array;
|
||||
|
||||
/**
|
||||
* @return array|UserJob[]
|
||||
*/
|
||||
public function findAllActive(): array;
|
||||
|
||||
/**
|
||||
* @param mixed|null $limit
|
||||
* @param mixed|null $offset
|
||||
*
|
||||
* @return array|object[]|UserJob[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null);
|
||||
|
||||
public function findOneBy(array $criteria): ?UserJob;
|
||||
|
||||
public function getClassName(): string;
|
||||
}
|
@@ -15,12 +15,14 @@ use Chill\MainBundle\Entity\GroupCenter;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
use function count;
|
||||
|
||||
final class UserRepository implements ObjectRepository
|
||||
final class UserRepository implements UserRepositoryInterface
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
@@ -42,6 +44,16 @@ final class UserRepository implements ObjectRepository
|
||||
return $this->countBy(['enabled' => true]);
|
||||
}
|
||||
|
||||
public function countByNotHavingAttribute(string $key): int
|
||||
{
|
||||
$rsm = new ResultSetMapping();
|
||||
$rsm->addScalarResult('count', 'count');
|
||||
|
||||
$sql = 'SELECT count(*) FROM users u WHERE NOT attributes ?? :key OR attributes IS NULL AND enabled IS TRUE';
|
||||
|
||||
return $this->entityManager->createNativeQuery($sql, $rsm)->setParameter(':key', $key)->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countByUsernameOrEmail(string $pattern): int
|
||||
{
|
||||
$qb = $this->queryByUsernameOrEmail($pattern);
|
||||
@@ -83,6 +95,29 @@ final class UserRepository implements ObjectRepository
|
||||
return $this->findBy(['enabled' => true], $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find users which does not have a key on attribute column.
|
||||
*
|
||||
* @return array|User[]
|
||||
*/
|
||||
public function findByNotHavingAttribute(string $key, ?int $limit = null, ?int $offset = null): array
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->entityManager);
|
||||
$rsm->addRootEntityFromClassMetadata(User::class, 'u');
|
||||
|
||||
$sql = 'SELECT ' . $rsm->generateSelectClause() . ' FROM users u WHERE NOT attributes ?? :key OR attributes IS NULL AND enabled IS TRUE';
|
||||
|
||||
if (null !== $limit) {
|
||||
$sql .= " LIMIT {$limit}";
|
||||
}
|
||||
|
||||
if (null !== $offset) {
|
||||
$sql .= " OFFSET {$offset}";
|
||||
}
|
||||
|
||||
return $this->entityManager->createNativeQuery($sql, $rsm)->setParameter(':key', $key)->getResult();
|
||||
}
|
||||
|
||||
public function findByUsernameOrEmail(string $pattern, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array
|
||||
{
|
||||
$qb = $this->queryByUsernameOrEmail($pattern);
|
||||
@@ -109,11 +144,15 @@ final class UserRepository implements ObjectRepository
|
||||
return $this->repository->findOneBy($criteria, $orderBy);
|
||||
}
|
||||
|
||||
public function findOneByUsernameOrEmail(string $pattern)
|
||||
public function findOneByUsernameOrEmail(string $pattern): ?User
|
||||
{
|
||||
$qb = $this->queryByUsernameOrEmail($pattern);
|
||||
$qb = $this->queryByUsernameOrEmail($pattern)->select('u');
|
||||
|
||||
return $qb->getQuery()->getSingleResult();
|
||||
try {
|
||||
return $qb->getQuery()->getSingleResult();
|
||||
} catch (NoResultException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,7 +210,7 @@ final class UserRepository implements ObjectRepository
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
public function getClassName(): string
|
||||
{
|
||||
return User::class;
|
||||
}
|
||||
|
@@ -0,0 +1,73 @@
|
||||
<?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\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
interface UserRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
public function countBy(array $criteria): int;
|
||||
|
||||
public function countByActive(): int;
|
||||
|
||||
public function countByNotHavingAttribute(string $key): int;
|
||||
|
||||
public function countByUsernameOrEmail(string $pattern): int;
|
||||
|
||||
public function find($id, $lockMode = null, $lockVersion = null): ?User;
|
||||
|
||||
/**
|
||||
* @return User[]
|
||||
*/
|
||||
public function findAll(): array;
|
||||
|
||||
/**
|
||||
* @param mixed|null $limit
|
||||
* @param mixed|null $offset
|
||||
*
|
||||
* @return User[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array;
|
||||
|
||||
/**
|
||||
* @return array|User[]
|
||||
*/
|
||||
public function findByActive(?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
|
||||
|
||||
/**
|
||||
* Find users which does not have a key on attribute column.
|
||||
*
|
||||
* @return array|User[]
|
||||
*/
|
||||
public function findByNotHavingAttribute(string $key, ?int $limit = null, ?int $offset = null): array;
|
||||
|
||||
public function findByUsernameOrEmail(string $pattern, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array;
|
||||
|
||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?User;
|
||||
|
||||
public function findOneByUsernameOrEmail(string $pattern): ?User;
|
||||
|
||||
/**
|
||||
* Get the users having a specific flags.
|
||||
*
|
||||
* If provided, only the users amongst "filtered users" are searched. This
|
||||
* allows to make a first search amongst users based on role and center
|
||||
* and, then filter those users having some flags.
|
||||
*
|
||||
* @param \Chill\MainBundle\Entity\User[] $amongstUsers
|
||||
* @param mixed $flag
|
||||
*/
|
||||
public function findUsersHavingFlags($flag, array $amongstUsers = []): array;
|
||||
|
||||
public function getClassName(): string;
|
||||
}
|
@@ -80,7 +80,9 @@ header {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
z-index: 1500;
|
||||
a.dropdown-item {
|
||||
padding: 0.5rem 1rem;
|
||||
width: 120%;
|
||||
border: 0;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
@@ -517,3 +519,16 @@ div.popover {
|
||||
div.v-toast {
|
||||
z-index: 10000!important;
|
||||
}
|
||||
|
||||
// export index page
|
||||
div.exports-list {
|
||||
div.flex-bloc .item-bloc {
|
||||
flex-basis: 33%;
|
||||
@include media-breakpoint-down(lg) { flex-basis: 50%; }
|
||||
@include media-breakpoint-down(sm) { flex-basis: 100%; }
|
||||
div:last-child,
|
||||
p:last-child {
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ require('./chillmain.scss');
|
||||
import { chill } from './js/chill.js';
|
||||
global.chill = chill;
|
||||
|
||||
require('./js/date.js');
|
||||
require('./js/date');
|
||||
require('./js/counter.js');
|
||||
|
||||
/// Load fonts
|
||||
|
@@ -12,7 +12,7 @@
|
||||
* Do not take time into account
|
||||
*
|
||||
*/
|
||||
const dateToISO = (date) => {
|
||||
export const dateToISO = (date: Date|null): string|null => {
|
||||
if (null === date) {
|
||||
return null;
|
||||
}
|
||||
@@ -29,7 +29,7 @@ const dateToISO = (date) => {
|
||||
*
|
||||
* **Experimental**
|
||||
*/
|
||||
const ISOToDate = (str) => {
|
||||
export const ISOToDate = (str: string|null): Date|null => {
|
||||
if (null === str) {
|
||||
return null;
|
||||
}
|
||||
@@ -38,25 +38,25 @@ const ISOToDate = (str) => {
|
||||
}
|
||||
|
||||
let
|
||||
[year, month, day] = str.split('-');
|
||||
[year, month, day] = str.split('-').map(p => parseInt(p));
|
||||
|
||||
return new Date(year, month-1, day);
|
||||
return new Date(year, month-1, day, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a date object from iso string formatted as YYYY-mm-dd:HH:MM:ss+01:00
|
||||
*
|
||||
*/
|
||||
const ISOToDatetime = (str) => {
|
||||
export const ISOToDatetime = (str: string|null): Date|null => {
|
||||
if (null === str) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let
|
||||
[cal, times] = str.split('T'),
|
||||
[year, month, date] = cal.split('-'),
|
||||
[year, month, date] = cal.split('-').map(s => parseInt(s)),
|
||||
[time, timezone] = times.split(times.charAt(8)),
|
||||
[hours, minutes, seconds] = time.split(':')
|
||||
[hours, minutes, seconds] = time.split(':').map(s => parseInt(s));
|
||||
;
|
||||
|
||||
return new Date(year, month-1, date, hours, minutes, seconds);
|
||||
@@ -66,7 +66,7 @@ const ISOToDatetime = (str) => {
|
||||
* Convert a date to ISO8601, valid for usage in api
|
||||
*
|
||||
*/
|
||||
const datetimeToISO = (date) => {
|
||||
export const datetimeToISO = (date: Date): string => {
|
||||
let cal, time, offset;
|
||||
cal = [
|
||||
date.getFullYear(),
|
||||
@@ -92,7 +92,7 @@ const datetimeToISO = (date) => {
|
||||
return x;
|
||||
};
|
||||
|
||||
const intervalDaysToISO = (days) => {
|
||||
export const intervalDaysToISO = (days: number|string|null): string => {
|
||||
if (null === days) {
|
||||
return 'P0D';
|
||||
}
|
||||
@@ -100,7 +100,7 @@ const intervalDaysToISO = (days) => {
|
||||
return `P${days}D`;
|
||||
}
|
||||
|
||||
const intervalISOToDays = (str) => {
|
||||
export const intervalISOToDays = (str: string|null): number|null => {
|
||||
if (null === str) {
|
||||
return null
|
||||
}
|
||||
@@ -154,12 +154,3 @@ const intervalISOToDays = (str) => {
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
export {
|
||||
dateToISO,
|
||||
ISOToDate,
|
||||
ISOToDatetime,
|
||||
datetimeToISO,
|
||||
intervalISOToDays,
|
||||
intervalDaysToISO,
|
||||
};
|
@@ -5,6 +5,10 @@ ul.record_actions {
|
||||
justify-content: flex-end;
|
||||
padding: 0.5em 0;
|
||||
|
||||
&.inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&.column {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -18,6 +22,13 @@ ul.record_actions {
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
&.small {
|
||||
.btn {
|
||||
padding: .25rem .5rem;
|
||||
font-size: .75rem;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
list-style-type: none;
|
||||
|
@@ -0,0 +1,4 @@
|
||||
export function fetchResults<T>(uri: string, params: {item_per_page?: number}): Promise<T[]>;
|
||||
|
||||
export function makeFetch<T, B>(method: "GET"|"POST"|"PATCH"|"DELETE", url: string, body: B, options: {[key: string]: string}): Promise<T>;
|
||||
|
@@ -1,110 +0,0 @@
|
||||
/**
|
||||
* Generic api method that can be adapted to any fetch request
|
||||
*/
|
||||
const makeFetch = (method, url, body, options) => {
|
||||
let opts = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
body: (body !== null) ? JSON.stringify(body) : null
|
||||
};
|
||||
|
||||
if (typeof options !== 'undefined') {
|
||||
opts = Object.assign(opts, options);
|
||||
}
|
||||
|
||||
return fetch(url, opts)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (response.status === 422) {
|
||||
return response.json().then(response => {
|
||||
throw ValidationException(response)
|
||||
});
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
throw AccessException(response);
|
||||
}
|
||||
|
||||
throw {
|
||||
name: 'Exception',
|
||||
sta: response.status,
|
||||
txt: response.statusText,
|
||||
err: new Error(),
|
||||
violations: response.body
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch results with certain parameters
|
||||
*/
|
||||
const _fetchAction = (page, uri, params) => {
|
||||
const item_per_page = 50;
|
||||
if (params === undefined) {
|
||||
params = {};
|
||||
}
|
||||
let url = uri + '?' + new URLSearchParams({ item_per_page, page, ...params });
|
||||
|
||||
return fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
}).then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw Error({ m: response.statusText });
|
||||
});
|
||||
};
|
||||
|
||||
const fetchResults = async (uri, params) => {
|
||||
let promises = [],
|
||||
page = 1;
|
||||
let firstData = await _fetchAction(page, uri, params);
|
||||
|
||||
promises.push(Promise.resolve(firstData.results));
|
||||
|
||||
if (firstData.pagination.more) {
|
||||
do {
|
||||
page = ++page;
|
||||
promises.push(_fetchAction(page, uri, params).then(r => Promise.resolve(r.results)));
|
||||
} while (page * firstData.pagination.items_per_page < firstData.count)
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(values => values.flat());
|
||||
};
|
||||
|
||||
const fetchScopes = () => {
|
||||
return fetchResults('/api/1.0/main/scope.json');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Error objects to be thrown
|
||||
*/
|
||||
const ValidationException = (response) => {
|
||||
const error = {};
|
||||
error.name = 'ValidationException';
|
||||
error.violations = response.violations.map((violation) => `${violation.title}: ${violation.propertyPath}`);
|
||||
error.titles = response.violations.map((violation) => violation.title);
|
||||
error.propertyPaths = response.violations.map((violation) => violation.propertyPath);
|
||||
return error;
|
||||
}
|
||||
|
||||
const AccessException = (response) => {
|
||||
const error = {};
|
||||
error.name = 'AccessException';
|
||||
error.violations = ['You are not allowed to perform this action'];
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
export {
|
||||
makeFetch,
|
||||
fetchResults,
|
||||
fetchScopes
|
||||
}
|
@@ -0,0 +1,223 @@
|
||||
import {Scope} from '../../types';
|
||||
|
||||
export type body = {[key: string]: boolean|string|number|null};
|
||||
export type fetchOption = {[key: string]: boolean|string|number|null};
|
||||
|
||||
export interface Params {
|
||||
[key: string]: number|string
|
||||
}
|
||||
|
||||
export interface PaginationResponse<T> {
|
||||
pagination: {
|
||||
more: boolean;
|
||||
items_per_page: number;
|
||||
};
|
||||
results: T[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface FetchParams {
|
||||
[K: string]: string|number|null;
|
||||
};
|
||||
|
||||
export interface TransportExceptionInterface {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ValidationExceptionInterface extends TransportExceptionInterface {
|
||||
name: 'ValidationException';
|
||||
error: object;
|
||||
violations: string[];
|
||||
titles: string[];
|
||||
propertyPaths: string[];
|
||||
}
|
||||
|
||||
export interface ValidationErrorResponse extends TransportExceptionInterface {
|
||||
violations: {
|
||||
title: string;
|
||||
propertyPath: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface AccessExceptionInterface extends TransportExceptionInterface {
|
||||
name: 'AccessException';
|
||||
violations: string[];
|
||||
}
|
||||
|
||||
export interface NotFoundExceptionInterface extends TransportExceptionInterface {
|
||||
name: 'NotFoundException';
|
||||
}
|
||||
|
||||
export interface ServerExceptionInterface extends TransportExceptionInterface {
|
||||
name: 'ServerException';
|
||||
message: string;
|
||||
code: number;
|
||||
body: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generic api method that can be adapted to any fetch request
|
||||
*/
|
||||
export const makeFetch = <Input, Output>(method: 'POST'|'GET'|'PUT'|'PATCH'|'DELETE', url: string, body?: body | Input | null, options?: FetchParams): Promise<Output> => {
|
||||
let opts = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
};
|
||||
|
||||
if (body !== null || typeof body !== 'undefined') {
|
||||
Object.assign(opts, {body: JSON.stringify(body)})
|
||||
}
|
||||
|
||||
if (typeof options !== 'undefined') {
|
||||
opts = Object.assign(opts, options);
|
||||
}
|
||||
|
||||
return fetch(url, opts)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (response.status === 422) {
|
||||
return response.json().then(response => {
|
||||
throw ValidationException(response)
|
||||
});
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
throw AccessException(response);
|
||||
}
|
||||
|
||||
throw {
|
||||
name: 'Exception',
|
||||
sta: response.status,
|
||||
txt: response.statusText,
|
||||
err: new Error(),
|
||||
violations: response.body
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch results with certain parameters
|
||||
*/
|
||||
function _fetchAction<T>(page: number, uri: string, params?: FetchParams): Promise<PaginationResponse<T>> {
|
||||
const item_per_page: number = 50;
|
||||
|
||||
let searchParams = new URLSearchParams();
|
||||
searchParams.append('item_per_page', item_per_page.toString());
|
||||
searchParams.append('page', page.toString());
|
||||
|
||||
if (params !== undefined) {
|
||||
Object.keys(params).forEach(key => {
|
||||
let v = params[key];
|
||||
if (typeof v === 'string') {
|
||||
searchParams.append(key, v);
|
||||
} else if (typeof v === 'number') {
|
||||
searchParams.append(key, v.toString());
|
||||
} else if (v === null) {
|
||||
searchParams.append(key, '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let url = uri + '?' + searchParams.toString();
|
||||
|
||||
return fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
}).then((response) => {
|
||||
if (response.ok) { return response.json(); }
|
||||
|
||||
if (response.status === 404) {
|
||||
throw NotFoundException(response);
|
||||
}
|
||||
|
||||
if (response.status === 422) {
|
||||
return response.json().then(response => {
|
||||
throw ValidationException(response)
|
||||
});
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
throw AccessException(response);
|
||||
}
|
||||
|
||||
if (response.status >= 500) {
|
||||
return response.text().then(body => {
|
||||
throw ServerException(response.status, body);
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error("other network error");
|
||||
}).catch((reason: any) => {
|
||||
console.error(reason);
|
||||
throw new Error(reason);
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchResults = async<T> (uri: string, params?: FetchParams): Promise<T[]> => {
|
||||
let promises: Promise<T[]>[] = [],
|
||||
page = 1;
|
||||
let firstData: PaginationResponse<T> = await _fetchAction(page, uri, params) as PaginationResponse<T>;
|
||||
|
||||
promises.push(Promise.resolve(firstData.results));
|
||||
|
||||
if (firstData.pagination.more) {
|
||||
do {
|
||||
page = ++page;
|
||||
promises.push(
|
||||
_fetchAction<T>(page, uri, params)
|
||||
.then(r => Promise.resolve(r.results))
|
||||
);
|
||||
} while (page * firstData.pagination.items_per_page < firstData.count)
|
||||
}
|
||||
|
||||
return Promise.all(promises).then((values) => values.flat());
|
||||
};
|
||||
|
||||
export const fetchScopes = (): Promise<Scope[]> => {
|
||||
return fetchResults('/api/1.0/main/scope.json');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Error objects to be thrown
|
||||
*/
|
||||
const ValidationException = (response: ValidationErrorResponse): ValidationExceptionInterface => {
|
||||
const error = {} as ValidationExceptionInterface;
|
||||
error.name = 'ValidationException';
|
||||
error.violations = response.violations.map((violation) => `${violation.title}: ${violation.propertyPath}`);
|
||||
error.titles = response.violations.map((violation) => violation.title);
|
||||
error.propertyPaths = response.violations.map((violation) => violation.propertyPath);
|
||||
return error;
|
||||
}
|
||||
|
||||
const AccessException = (response: Response): AccessExceptionInterface => {
|
||||
const error = {} as AccessExceptionInterface;
|
||||
error.name = 'AccessException';
|
||||
error.violations = ['You are not allowed to perform this action'];
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
const NotFoundException = (response: Response): NotFoundExceptionInterface => {
|
||||
const error = {} as NotFoundExceptionInterface;
|
||||
error.name = 'NotFoundException';
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
const ServerException = (code: number, body: string): ServerExceptionInterface => {
|
||||
const error = {} as ServerExceptionInterface;
|
||||
error.name = 'ServerException';
|
||||
error.code = code;
|
||||
error.body = body;
|
||||
|
||||
return error;
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
import {fetchResults} from "./apiMethods";
|
||||
import {Location, LocationType} from "../../types";
|
||||
|
||||
export const getLocations = (): Promise<Location[]> => fetchResults('/api/1.0/main/location.json');
|
||||
|
||||
export const getLocationTypes = (): Promise<LocationType[]> => fetchResults('/api/1.0/main/location-type.json');
|
25
src/Bundle/ChillMainBundle/Resources/public/lib/api/user.ts
Normal file
25
src/Bundle/ChillMainBundle/Resources/public/lib/api/user.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {User} from "../../types";
|
||||
import {makeFetch} from "./apiMethods";
|
||||
|
||||
export const whoami = (): Promise<User> => {
|
||||
const url = `/api/1.0/main/whoami.json`;
|
||||
return fetch(url)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw {
|
||||
msg: 'Error while getting whoami.',
|
||||
sta: response.status,
|
||||
txt: response.statusText,
|
||||
err: new Error(),
|
||||
body: response.body
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const whereami = (): Promise<Location | null> => {
|
||||
const url = `/api/1.0/main/user-current-location.json`;
|
||||
|
||||
return makeFetch<null, Location|null>("GET", url);
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -15,12 +15,12 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
var mime = require('mime-types')
|
||||
var mime = require('mime')
|
||||
|
||||
var download_report = (url, container) => {
|
||||
var download_text = container.dataset.downloadText,
|
||||
alias = container.dataset.alias;
|
||||
|
||||
|
||||
window.fetch(url, { credentials: 'same-origin' })
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
@@ -29,21 +29,21 @@ var download_report = (url, container) => {
|
||||
|
||||
return response.blob();
|
||||
}).then(blob => {
|
||||
|
||||
|
||||
var content = URL.createObjectURL(blob),
|
||||
link = document.createElement("a"),
|
||||
type = blob.type,
|
||||
hasForcedType = 'mimeType' in container.dataset,
|
||||
extension;
|
||||
|
||||
|
||||
if (hasForcedType) {
|
||||
// force a type
|
||||
type = container.dataset.mimeType;
|
||||
blob = new Blob([ blob ], { 'type': type });
|
||||
content = URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
extension = mime.extension(type);
|
||||
|
||||
extension = mime.getExtension(type);
|
||||
|
||||
link.appendChild(document.createTextNode(download_text));
|
||||
link.classList.add("btn", "btn-action");
|
||||
@@ -56,7 +56,7 @@ var download_report = (url, container) => {
|
||||
container.appendChild(link);
|
||||
}).catch(function(error) {
|
||||
console.log(error);
|
||||
var problem_text =
|
||||
var problem_text =
|
||||
document.createTextNode("Problem during download");
|
||||
|
||||
container
|
||||
@@ -64,4 +64,4 @@ var download_report = (url, container) => {
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = download_report;
|
||||
module.exports = download_report;
|
||||
|
@@ -14,9 +14,11 @@
|
||||
|
||||
// 4. Include any default map overrides here
|
||||
@import "custom/_maps";
|
||||
@import "bootstrap/scss/maps";
|
||||
|
||||
// 5. Include remainder of required parts
|
||||
@import "bootstrap/scss/mixins";
|
||||
@import "bootstrap/scss/utilities";
|
||||
@import "bootstrap/scss/root";
|
||||
|
||||
|
||||
|
@@ -6,6 +6,7 @@ import { appMessages } from 'ChillMainAssets/vuejs/PickEntity/i18n';
|
||||
const i18n = _createI18n(appMessages);
|
||||
|
||||
let appsOnPage = new Map();
|
||||
let appsPerInput = new Map();
|
||||
|
||||
function loadDynamicPicker(element) {
|
||||
|
||||
@@ -78,13 +79,14 @@ function loadDynamicPicker(element) {
|
||||
.mount(el);
|
||||
|
||||
appsOnPage.set(uniqId, app);
|
||||
appsPerInput.set(input.name, app);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener('show-hide-show', function(e) {
|
||||
loadDynamicPicker(e.detail.container)
|
||||
})
|
||||
});
|
||||
|
||||
document.addEventListener('show-hide-hide', function(e) {
|
||||
console.log('hiding event caught')
|
||||
@@ -95,13 +97,25 @@ document.addEventListener('show-hide-hide', function(e) {
|
||||
appsOnPage.delete(uniqId);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
document.addEventListener('pick-entity-type-action', function (e) {
|
||||
console.log('pick entity event', e);
|
||||
if (!appsPerInput.has(e.detail.name)) {
|
||||
console.error('no app with this name');
|
||||
return;
|
||||
}
|
||||
const app = appsPerInput.get(e.detail.name);
|
||||
if (e.detail.action === 'add') {
|
||||
app.addNewEntity(e.detail.entity);
|
||||
} else if (e.detail.action === 'remove') {
|
||||
app.removeEntity(e.detail.entity);
|
||||
} else {
|
||||
console.error('action not supported: '+e.detail.action);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function(e) {
|
||||
loadDynamicPicker(document)
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,28 @@
|
||||
import {ShowHide} from 'ChillMainAssets/lib/show_hide/index';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function(_e) {
|
||||
console.log('pick-rolling-date');
|
||||
document.querySelectorAll('div[data-rolling-date]').forEach( (picker) => {
|
||||
const
|
||||
roll_wrapper = picker.querySelector('div.roll-wrapper'),
|
||||
fixed_wrapper = picker.querySelector('div.fixed-wrapper');
|
||||
|
||||
new ShowHide({
|
||||
froms: [roll_wrapper],
|
||||
container: [fixed_wrapper],
|
||||
test: function (elems) {
|
||||
console.log('testing');
|
||||
console.log('elems', elems);
|
||||
for (let el of elems) {
|
||||
for (let select_roll of el.querySelectorAll('select[data-roll-picker]')) {
|
||||
console.log('select_roll', select_roll);
|
||||
console.log('value', select_roll.value);
|
||||
return select_roll.value === 'fixed_date';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
139
src/Bundle/ChillMainBundle/Resources/public/types.ts
Normal file
139
src/Bundle/ChillMainBundle/Resources/public/types.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
export interface DateTime {
|
||||
datetime: string;
|
||||
datetime8601: string
|
||||
}
|
||||
|
||||
export interface Civility {
|
||||
id: number;
|
||||
// TODO
|
||||
}
|
||||
|
||||
export interface Job {
|
||||
id: number;
|
||||
type: "user_job";
|
||||
label: {
|
||||
"fr": string; // could have other key. How to do that in ts ?
|
||||
}
|
||||
}
|
||||
|
||||
export interface Center {
|
||||
id: number;
|
||||
type: "center";
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Scope {
|
||||
id: number;
|
||||
type: "scope";
|
||||
name: {
|
||||
"fr": string
|
||||
}
|
||||
}
|
||||
|
||||
export interface User {
|
||||
type: "user";
|
||||
id: number;
|
||||
username: string;
|
||||
text: string;
|
||||
email: string;
|
||||
user_job: Job;
|
||||
label: string;
|
||||
// todo: mainCenter; mainJob; etc..
|
||||
}
|
||||
|
||||
export interface UserAssociatedInterface {
|
||||
type: "user";
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type TranslatableString = {
|
||||
fr?: string;
|
||||
nl?: string;
|
||||
}
|
||||
|
||||
export interface Postcode {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
center: Point;
|
||||
}
|
||||
|
||||
export type Point = {
|
||||
type: "Point";
|
||||
coordinates: [lat: number, lon: number];
|
||||
}
|
||||
|
||||
export interface Country {
|
||||
id: number;
|
||||
name: TranslatableString;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
type: "address";
|
||||
address_id: number;
|
||||
text: string;
|
||||
street: string;
|
||||
streetNumber: string;
|
||||
postcode: Postcode;
|
||||
country: Country;
|
||||
floor: string | null;
|
||||
corridor: string | null;
|
||||
steps: string | null;
|
||||
flat: string | null;
|
||||
buildingName: string | null;
|
||||
distribution: string | null;
|
||||
extra: string | null;
|
||||
confidential: boolean;
|
||||
lines: string[];
|
||||
addressReference: AddressReference | null;
|
||||
validFrom: DateTime;
|
||||
validTo: DateTime | null;
|
||||
}
|
||||
|
||||
export interface AddressReference {
|
||||
id: number;
|
||||
createdAt: DateTime | null;
|
||||
deletedAt: DateTime | null;
|
||||
municipalityCode: string;
|
||||
point: Point;
|
||||
postcode: Postcode;
|
||||
refId: string;
|
||||
source: string;
|
||||
street: string;
|
||||
streetNumber: string;
|
||||
updatedAt: DateTime | null;
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
type: "location";
|
||||
id: number;
|
||||
active: boolean;
|
||||
address: Address | null;
|
||||
availableForUsers: boolean;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
updatedAt: DateTime | null;
|
||||
updatedBy: User | null;
|
||||
email: string | null
|
||||
name: string;
|
||||
phonenumber1: string | null;
|
||||
phonenumber2: string | null;
|
||||
locationType: LocationType;
|
||||
}
|
||||
|
||||
export interface LocationAssociated {
|
||||
type: "location";
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface LocationType {
|
||||
type: "location-type";
|
||||
id: number;
|
||||
active: boolean;
|
||||
addressRequired: "optional" | "required";
|
||||
availableForUsers: boolean;
|
||||
editableByUsers: boolean;
|
||||
contactData: "optional" | "required";
|
||||
title: TranslatableString;
|
||||
}
|
@@ -54,7 +54,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { dateToISO, ISOToDate } from 'ChillMainAssets/chill/js/date.js';
|
||||
import { dateToISO, ISOToDate } from 'ChillMainAssets/chill/js/date';
|
||||
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
|
||||
import ActionButtons from './ActionButtons.vue';
|
||||
|
||||
|
@@ -146,6 +146,9 @@ export default {
|
||||
}
|
||||
},
|
||||
titleCreate() {
|
||||
if (typeof this.allowedTypes === 'undefined') {
|
||||
return 'onthefly.create.title.default';
|
||||
}
|
||||
return this.allowedTypes.every(t => t === 'person')
|
||||
? 'onthefly.create.title.person'
|
||||
: this.allowedTypes.every(t => t === 'thirdparty')
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ul class="list-suggest remove-items" v-if="picked.length">
|
||||
<ul :class="listClasses" v-if="picked.length && displayPicked">
|
||||
<li v-for="p in picked" @click="removeEntity(p)" :key="p.type+p.id">
|
||||
<span class="chill_denomination">{{ p.text }}</span>
|
||||
</li>
|
||||
@@ -40,6 +40,15 @@ export default {
|
||||
uniqid: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
removableIfSet: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
displayPicked: {
|
||||
// display picked entities.
|
||||
type: Boolean,
|
||||
default: true,
|
||||
}
|
||||
},
|
||||
emits: ['addNewEntity', 'removeEntity'],
|
||||
@@ -78,7 +87,13 @@ export default {
|
||||
} else {
|
||||
return appMessages.fr.pick_entity.modal_title_one + trans.join(', ');
|
||||
}
|
||||
}
|
||||
},
|
||||
listClasses() {
|
||||
return {
|
||||
'list-suggest': true,
|
||||
'remove-items': this.$props.removableIfSet,
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addNewEntity({ selected, modal }) {
|
||||
@@ -90,6 +105,9 @@ export default {
|
||||
modal.showModal = false;
|
||||
},
|
||||
removeEntity(entity) {
|
||||
if (!this.$props.removableIfSet) {
|
||||
return;
|
||||
}
|
||||
this.$emit('removeEntity', entity);
|
||||
}
|
||||
},
|
||||
|
@@ -20,7 +20,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods.js';
|
||||
import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods.ts';
|
||||
|
||||
export default {
|
||||
name: "EntityWorkflowVueSubscriber",
|
||||
|
@@ -2,57 +2,65 @@
|
||||
<transition name="modal">
|
||||
<div class="modal-mask">
|
||||
<!-- :: styles bootstrap :: -->
|
||||
<div class="modal-dialog" :class="modalDialogClass">
|
||||
<div class="modal fade show" style="display: block" aria-modal="true" role="dialog">
|
||||
<div class="modal-dialog" :class="modalDialogClass">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<slot name="header"></slot>
|
||||
<button class="close btn" @click="$emit('close')">
|
||||
<i class="fa fa-times" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="body-head">
|
||||
<div class="modal-header">
|
||||
<slot name="header"></slot>
|
||||
<button class="close btn" @click="$emit('close')">
|
||||
<i class="fa fa-times" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="body-head">
|
||||
<slot name="body-head"></slot>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
<div class="modal-footer" v-if="!hideFooter">
|
||||
<button class="btn btn-cancel" @click="$emit('close')">{{ $t('action.close') }}</button>
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
<div class="modal-footer" v-if="!hideFooter">
|
||||
<button class="btn btn-cancel" @click="$emit('close')">{{ $t('action.close') }}</button>
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- :: end styles bootstrap :: -->
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import {defineComponent} from "vue";
|
||||
/*
|
||||
* This Modal component is a mix between Vue3 modal implementation
|
||||
* [+] with 'v-if:showModal' directive:parameter, html scope is added/removed not just shown/hidden
|
||||
* [+] with slot we can pass content from parent component
|
||||
* [+] some classes are passed from parent component
|
||||
* and Bootstrap 4.6 _modal.scss module
|
||||
* and Bootstrap 5 _modal.scss module
|
||||
* [+] using bootstrap css classes, the modal have a responsive behaviour,
|
||||
* [+] modal design can be configured using css classes (size, scroll)
|
||||
*/
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: 'Modal',
|
||||
props: {
|
||||
modalDialogClass: {
|
||||
type: String,
|
||||
required: false
|
||||
type: Object,
|
||||
required: false,
|
||||
default: {},
|
||||
},
|
||||
hideFooter: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['close']
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/**
|
||||
* This is a mask behind the modal.
|
||||
*/
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
z-index: 9998;
|
||||
|
@@ -41,7 +41,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods.js';
|
||||
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods.ts';
|
||||
|
||||
export default {
|
||||
name: "NotificationReadToggle",
|
||||
|
@@ -1,27 +1,6 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import datetimeFormats from '../i18n/datetimeFormats';
|
||||
|
||||
const datetimeFormats = {
|
||||
fr: {
|
||||
short: {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric"
|
||||
},
|
||||
text: {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
},
|
||||
long: {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: false
|
||||
}
|
||||
}
|
||||
};
|
||||
const messages = {
|
||||
fr: {
|
||||
action: {
|
||||
@@ -76,11 +55,13 @@ const messages = {
|
||||
}
|
||||
};
|
||||
|
||||
const _createI18n = (appMessages) => {
|
||||
const _createI18n = (appMessages: any, legacy?: boolean) => {
|
||||
Object.assign(messages.fr, appMessages.fr);
|
||||
return createI18n({
|
||||
legacy: typeof legacy === undefined ? true : legacy,
|
||||
locale: 'fr',
|
||||
fallbackLocale: 'fr',
|
||||
// @ts-ignore
|
||||
datetimeFormats,
|
||||
messages,
|
||||
})
|
@@ -0,0 +1,27 @@
|
||||
export default {
|
||||
fr: {
|
||||
short: {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric"
|
||||
},
|
||||
text: {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
},
|
||||
long: {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: false
|
||||
},
|
||||
hoursOnly: {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: false,
|
||||
}
|
||||
}
|
||||
};
|
@@ -0,0 +1,6 @@
|
||||
<h6>
|
||||
<a href="{{ path('chill_main_export_index') }}" title="{{ 'Back to the list'|trans }}">
|
||||
<i class="fa fa-folder-open-o fa-fw"></i>
|
||||
</a>
|
||||
{{ export_group|trans }}
|
||||
</h6>
|
@@ -0,0 +1,12 @@
|
||||
<ul class="nav nav-pills justify-content-center">
|
||||
<li class="nav-item">
|
||||
<a href="{{ chill_path_forward_return_path('chill_main_export_index') }}" class="nav-link {% if current == 'common' %}active{% endif %}">
|
||||
{{ 'Exports list'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ chill_path_forward_return_path('chill_main_export_saved_list_my') }}" class="nav-link {% if current == 'my' %}active{% endif %}">
|
||||
{{ 'saved_export.My saved exports'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
@@ -36,10 +36,7 @@ window.addEventListener("DOMContentLoaded", function(e) {
|
||||
{% block content %}
|
||||
<div class="col-md-10">
|
||||
|
||||
<h6>
|
||||
<i class="fa fa-folder-open-o fa-fw"></i>
|
||||
{{ export_group|trans }}
|
||||
</h6>
|
||||
{{ include('@ChillMain/Export/_breadcrumb.html.twig') }}
|
||||
|
||||
<h1>{{ export.title|trans }}</h1>
|
||||
<h2>{{ "Download export"|trans }}</h2>
|
||||
@@ -52,5 +49,14 @@ window.addEventListener("DOMContentLoaded", function(e) {
|
||||
data-download-text="{{ "Download your report"|trans|escape('html_attr') }}"
|
||||
><span id="waiting_text">{{ "Waiting for your report"|trans ~ '...' }}</span></div>
|
||||
|
||||
</div>
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel"><a href="{{ chill_return_path_or('chill_main_export_index') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a></li>
|
||||
|
||||
{% if not app.request.query.has('prevent_save') %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_main_export_save_from_key', { alias: alias, key: app.request.query.get('key')}) }}" class="btn btn-save">{{ 'Save'|trans }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock content %}
|
@@ -22,23 +22,27 @@
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-10">
|
||||
<h1>{{ 'Exports list'|trans }}</h1>
|
||||
|
||||
|
||||
{{ include('@ChillMain/Export/_navbar.html.twig', {'current' : 'common'}) }}
|
||||
|
||||
<div class="col-md-10 exports-list">
|
||||
|
||||
<div class="container mt-4">
|
||||
|
||||
{% for group, exports in grouped_exports %}{% if group != '_' %}
|
||||
<h2 class="display-6">{{ group|trans }}</h2>
|
||||
<div class="row grouped">
|
||||
<div class="row flex-bloc">
|
||||
{% for export_alias, export in exports %}
|
||||
<div class="col-6 col-md-4 mb-3">
|
||||
<h2>{{ export.title|trans }}</h2>
|
||||
<p>{{ export.description|trans }}</p>
|
||||
<p>
|
||||
<a class="btn btn-action" href="{{ path('chill_main_export_new', { 'alias': export_alias } ) }}">
|
||||
{{ 'Create an export'|trans }}
|
||||
</a>
|
||||
</p>
|
||||
<div class="item-bloc">
|
||||
<div class="item-row card-body">
|
||||
<h2 class="card-title">{{ export.title|trans }}</h2>
|
||||
<p class="card-text my-3">{{ export.description|trans }}</p>
|
||||
<p>
|
||||
<a class="btn btn-action" href="{{ path('chill_main_export_new', { 'alias': export_alias } ) }}">
|
||||
{{ 'Create an export'|trans }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -48,17 +52,19 @@
|
||||
<h2 class="display-6">{{ 'Ungrouped exports'|trans }}</h2>
|
||||
{% endif %}
|
||||
|
||||
<div class="row ungrouped">
|
||||
<div class="row flex-bloc">
|
||||
{% for export_alias,export in grouped_exports['_'] %}
|
||||
|
||||
<div class="col-6 col-md-4 mb-3">
|
||||
<h2>{{ export.title|trans }}</h2>
|
||||
<p>{{ export.description|trans }}</p>
|
||||
<p>
|
||||
<a class="btn btn-action" href="{{ path('chill_main_export_new', { 'alias': export_alias } ) }}">
|
||||
{{ 'Create an export'|trans }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div class="item-bloc">
|
||||
<div class="item-row card-body">
|
||||
<h2 class="card-title">{{ export.title|trans }}</h2>
|
||||
<p class="card-text my-3">{{ export.description|trans }}</p>
|
||||
<p>
|
||||
<a class="btn btn-action" href="{{ path('chill_main_export_new', { 'alias': export_alias } ) }}">
|
||||
{{ 'Create an export'|trans }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
|
@@ -20,17 +20,24 @@
|
||||
|
||||
{% block title %}{{ export.title|trans }}{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('mod_pickentity_type') }}
|
||||
{{ encore_entry_link_tags('mod_pick_rolling_date') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ encore_entry_script_tags('mod_pickentity_type') }}
|
||||
{{ encore_entry_script_tags('page_export') }}
|
||||
{% if export_alias == 'count_social_work_actions' %}
|
||||
{{ encore_entry_script_tags('vue_export_action_goal_result') }}
|
||||
{% endif %}
|
||||
{{ encore_entry_script_tags('mod_pick_rolling_date') }}
|
||||
{% endblock js %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-10">
|
||||
|
||||
<h6>
|
||||
<i class="fa fa-folder-open-o fa-fw"></i>
|
||||
{{ export_group|trans }}
|
||||
</h6>
|
||||
{{ include('@ChillMain/Export/_breadcrumb.html.twig') }}
|
||||
|
||||
<h1>{{ export.title|trans }}</h1>
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user