From 325ab0daf3832427e52e312261f7315c92096c27 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 8 Mar 2022 15:48:26 +0100 Subject: [PATCH 001/119] chore: Update `composer.json` files. --- composer.json | 6 ++++-- src/Bundle/ChillDocGeneratorBundle/composer.json | 1 + src/Bundle/ChillDocStoreBundle/composer.json | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index c96738df8..d0fc51c0e 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "knplabs/knp-time-bundle": "^1.12", "league/csv": "^9.7.1", "nyholm/psr7": "^1.4", - "ocramius/package-versions": "^1.10", + "ocramius/package-versions": "^1.10 || ^2", "odolbeau/phone-number-bundle": "^3.6", "phpoffice/phpspreadsheet": "^1.16", "ramsey/uuid-doctrine": "^1.7", @@ -33,6 +33,7 @@ "symfony/expression-language": "^4.4", "symfony/form": "^4.4", "symfony/framework-bundle": "^4.4", + "symfony/http-foundation": "^4.4", "symfony/intl": "^4.4", "symfony/mailer": "^5.4", "symfony/mime": "^5.4", @@ -103,7 +104,8 @@ "ergebnis/composer-normalize": true, "ocramius/package-versions": true, "phpro/grumphp": true, - "phpstan/extension-installer": true + "phpstan/extension-installer": true, + "roave/you-are-using-it-wrong": true }, "bin-dir": "bin", "optimize-autoloader": true, diff --git a/src/Bundle/ChillDocGeneratorBundle/composer.json b/src/Bundle/ChillDocGeneratorBundle/composer.json index 4ae119636..42939bd2b 100644 --- a/src/Bundle/ChillDocGeneratorBundle/composer.json +++ b/src/Bundle/ChillDocGeneratorBundle/composer.json @@ -18,6 +18,7 @@ } ], "require": { + "spomky-labs/base64url": "^2" }, "require-dev": { }, diff --git a/src/Bundle/ChillDocStoreBundle/composer.json b/src/Bundle/ChillDocStoreBundle/composer.json index c011ce29b..aa636535e 100644 --- a/src/Bundle/ChillDocStoreBundle/composer.json +++ b/src/Bundle/ChillDocStoreBundle/composer.json @@ -8,7 +8,8 @@ } }, "require": { - "symfony/mime": "^4 || ^5" + "symfony/mime": "^4 || ^5", + "symfony/http-foundation": "^4" }, "license": "AGPL-3.0" } From 6ddbf35a7b334e0e6745e03e0984c4b088ee0908 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 8 Mar 2022 15:46:50 +0100 Subject: [PATCH 002/119] misc: Add return types. --- src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php index c512dba73..d5afd4a28 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php @@ -109,12 +109,12 @@ class StoredObject implements AsyncFileInterface, Document return $this->id; } - public function getIv() + public function getIv(): array { return $this->iv; } - public function getKeyInfos() + public function getKeyInfos(): array { return $this->keyInfos; } @@ -163,21 +163,21 @@ class StoredObject implements AsyncFileInterface, Document return $this; } - public function setIv($iv) + public function setIv(array $iv) { $this->iv = $iv; return $this; } - public function setKeyInfos($keyInfos) + public function setKeyInfos(array $keyInfos) { $this->keyInfos = $keyInfos; return $this; } - public function setType($type) + public function setType(string $type) { $this->type = $type; From b8992b8eeba281559cf9b226c10d194a54bd92d9 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 8 Mar 2022 15:47:33 +0100 Subject: [PATCH 003/119] misc: Update interface doc. --- .../Context/ContextManager.php | 5 +--- .../Context/ContextManagerInterface.php | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 src/Bundle/ChillDocGeneratorBundle/Context/ContextManagerInterface.php diff --git a/src/Bundle/ChillDocGeneratorBundle/Context/ContextManager.php b/src/Bundle/ChillDocGeneratorBundle/Context/ContextManager.php index 8dfecb730..b8f4d0eed 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Context/ContextManager.php +++ b/src/Bundle/ChillDocGeneratorBundle/Context/ContextManager.php @@ -14,7 +14,7 @@ namespace Chill\DocGeneratorBundle\Context; use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; -class ContextManager +final class ContextManager implements ContextManagerInterface { /** * @var DocGeneratorContextInterface[]|iterable @@ -26,9 +26,6 @@ class ContextManager $this->contexts = $contexts; } - /** - * @throw ContextNotFoundException when the context is not found - */ public function getContextByDocGeneratorTemplate(DocGeneratorTemplate $docGeneratorTemplate): DocGeneratorContextInterface { foreach ($this->contexts as $key => $context) { diff --git a/src/Bundle/ChillDocGeneratorBundle/Context/ContextManagerInterface.php b/src/Bundle/ChillDocGeneratorBundle/Context/ContextManagerInterface.php new file mode 100644 index 000000000..3468d787d --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Context/ContextManagerInterface.php @@ -0,0 +1,30 @@ + Date: Tue, 8 Mar 2022 15:46:31 +0100 Subject: [PATCH 004/119] refactor: Return a string instead of a resource. --- .../GeneratorDriver/DriverInterface.php | 7 +-- .../GeneratorDriver/RelatorioDriver.php | 46 +++++++++++++------ 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php index 2572484e1..d787523c9 100644 --- a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php +++ b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php @@ -13,10 +13,5 @@ namespace Chill\DocGeneratorBundle\GeneratorDriver; interface DriverInterface { - /** - * @param resource $template - * - * @return resource - */ - public function generateFromResource($template, string $resourceType, array $data, ?string $templateName = null); + public function generateFromString(string $template, string $resourceType, array $data, ?string $templateName = null): string; } diff --git a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/RelatorioDriver.php b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/RelatorioDriver.php index 7a3e4ac69..73a4ba9e0 100644 --- a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/RelatorioDriver.php +++ b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/RelatorioDriver.php @@ -20,40 +20,40 @@ use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; +use Throwable; -class RelatorioDriver implements DriverInterface +final class RelatorioDriver implements DriverInterface { - private LoggerInterface $logger; + private HttpClientInterface $client; - private HttpClientInterface $relatorioClient; + private LoggerInterface $logger; private string $url; public function __construct( - HttpClientInterface $relatorioClient, + HttpClientInterface $client, ParameterBagInterface $parameterBag, LoggerInterface $logger ) { - $this->relatorioClient = $relatorioClient; + $this->client = $client; $this->logger = $logger; $this->url = $parameterBag->get('chill_doc_generator')['driver']['relatorio']['url']; } - public function generateFromResource($template, string $resourceType, array $data, ?string $templateName = null) + public function generateFromString(string $template, string $resourceType, array $data, ?string $templateName = null): string { - $formFields = [ - 'variables' => json_encode($data), - 'template' => new DataPart($template, $templateName ?? uniqid('template_'), $resourceType), - ]; - $form = new FormDataPart($formFields); + $form = new FormDataPart( + [ + 'variables' => json_encode($data), + 'template' => new DataPart($template, $templateName ?? uniqid('template_'), $resourceType), + ] + ); try { - $response = $this->relatorioClient->request('POST', $this->url, [ + $response = $this->client->request('POST', $this->url, [ 'headers' => $form->getPreparedHeaders()->toArray(), 'body' => $form->bodyToIterable(), ]); - - return $response->toStream(); } catch (HttpExceptionInterface $e) { $content = $e->getResponse()->getContent(false); @@ -88,5 +88,23 @@ class RelatorioDriver implements DriverInterface throw $e; } + + try { + $content = $response->getContent(); + } catch (Throwable $exception) { + $this + ->logger + ->error( + 'relatorio: Unable to get content from response.', + [ + 'msg' => $exception->getMessage(), + 'e' => $exception->getTraceAsString(), + ] + ); + + throw $exception; + } + + return $content; } } From 62af980ea545dbcfa018ee793d8d515d1e45cb88 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 8 Mar 2022 15:45:39 +0100 Subject: [PATCH 005/119] feat: Add new `StoredObjectManager` service. To read and write onto `StoredObject` document using a common interface. --- .../StoredObjectManagerException.php | 40 ++++ .../Service/StoredObjectManager.php | 133 +++++++++++++ .../Service/StoredObjectManagerInterface.php | 34 ++++ .../Tests/StoredObjectManagerTest.php | 183 ++++++++++++++++++ 4 files changed, 390 insertions(+) create mode 100644 src/Bundle/ChillDocStoreBundle/Exception/StoredObjectManagerException.php create mode 100644 src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php create mode 100644 src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/StoredObjectManagerTest.php diff --git a/src/Bundle/ChillDocStoreBundle/Exception/StoredObjectManagerException.php b/src/Bundle/ChillDocStoreBundle/Exception/StoredObjectManagerException.php new file mode 100644 index 000000000..b5d0bf679 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Exception/StoredObjectManagerException.php @@ -0,0 +1,40 @@ +client = $client; + $this->tempUrlGenerator = $tempUrlGenerator; + } + + public function read(StoredObject $document): string + { + try { + $response = $this + ->client + ->request( + Request::METHOD_GET, + $this + ->tempUrlGenerator + ->generate( + Request::METHOD_GET, + $document->getFilename() + ) + ->url + ); + } catch (Throwable $e) { + throw StoredObjectManagerException::errorDuringHttpRequest($e); + } + + if ($response->getStatusCode() !== Response::HTTP_OK) { + throw StoredObjectManagerException::invalidStatusCode($response->getStatusCode()); + } + + try { + $data = $response->getContent(); + } catch (Throwable $e) { + throw StoredObjectManagerException::unableToGetResponseContent($e); + } + + if (false === $this->hasKeysAndIv($document)) { + return $data; + } + + $clearData = openssl_decrypt( + $data, + self::ALGORITHM, + // TODO: Why using this library and not use base64_decode() ? + Base64Url::decode($document->getKeyInfos()['k']), + OPENSSL_RAW_DATA, + pack('C*', ...$document->getIv()) + ); + + if (false === $clearData) { + throw StoredObjectManagerException::unableToDecrypt(openssl_error_string()); + } + + return $clearData; + } + + public function write(StoredObject $document, string $clearContent): void + { + $encryptedContent = $this->hasKeysAndIv($document) + ? openssl_encrypt( + $clearContent, + self::ALGORITHM, + // TODO: Why using this library and not use base64_decode() ? + Base64Url::decode($document->getKeyInfos()['k']), + OPENSSL_RAW_DATA, + pack('C*', ...$document->getIv()) + ) + : $clearContent; + + try { + $response = $this + ->client + ->request( + Request::METHOD_PUT, + $this + ->tempUrlGenerator + ->generate( + Request::METHOD_PUT, + $document->getFilename() + ) + ->url, + [ + 'body' => $encryptedContent, + ] + ); + } catch (TransportExceptionInterface $exception) { + throw StoredObjectManagerException::errorDuringHttpRequest($exception); + } + + if ($response->getStatusCode() !== Response::HTTP_CREATED) { + throw StoredObjectManagerException::invalidStatusCode($response->getStatusCode()); + } + } + + private function hasKeysAndIv(StoredObject $storedObject): bool + { + return ([] !== $storedObject->getKeyInfos()) && ([] !== $storedObject->getIv()); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php new file mode 100644 index 000000000..3cf67cb0c --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php @@ -0,0 +1,34 @@ +setFilename('encrypted.txt') + ->setKeyInfos(['k' => base64_encode('S9NIHMaFHOWzLPez3jZOIHBaNfBrMQUR5zvqBz6kme8')]) + ->setIv(unpack('C*', 'abcdefghijklmnop')), + hex2bin('741237d255fd4f7eddaaa9058912a84caae28a41b10b34d4e3e3abe41d3b9b47cb0dd8f22c3c883d4f0e9defa75ff662'), // Binary encoded string + 'The quick brown fox jumps over the lazy dog', // clear + ]; + + // Non-encrypted object + yield [ + (new StoredObject())->setFilename('non-encrypted.txt'), // The StoredObject + 'The quick brown fox jumps over the lazy dog', // Encrypted + 'The quick brown fox jumps over the lazy dog', // Clear + ]; + + /* UNHAPPY SCENARIO */ + + // Encrypted object with issue during HTTP communication + yield [ + (new StoredObject()) + ->setFilename('error_during_http_request.txt') + ->setKeyInfos(['k' => base64_encode('S9NIHMaFHOWzLPez3jZOIHBaNfBrMQUR5zvqBz6kme8')]) + ->setIv(unpack('C*', 'abcdefghijklmnop')), + hex2bin('741237d255fd4f7eddaaa9058912a84caae28a41b10b34d4e3e3abe41d3b9b47cb0dd8f22c3c883d4f0e9defa75ff662'), // Binary encoded string + 'The quick brown fox jumps over the lazy dog', // clear + StoredObjectManagerException::class, + ]; + + // Encrypted object with issue during HTTP communication: Invalid status code + yield [ + (new StoredObject()) + ->setFilename('invalid_statuscode.txt') + ->setKeyInfos(['k' => base64_encode('S9NIHMaFHOWzLPez3jZOIHBaNfBrMQUR5zvqBz6kme8')]) + ->setIv(unpack('C*', 'abcdefghijklmnop')), + hex2bin('741237d255fd4f7eddaaa9058912a84caae28a41b10b34d4e3e3abe41d3b9b47cb0dd8f22c3c883d4f0e9defa75ff662'), // Binary encoded string + 'The quick brown fox jumps over the lazy dog', // clear + StoredObjectManagerException::class, + ]; + + // Erroneous encrypted: Unable to decrypt exception. + yield [ + (new StoredObject()) + ->setFilename('unable_to_decrypt.txt') + ->setKeyInfos(['k' => base64_encode('WRONG_PASS_PHRASE')]) + ->setIv(unpack('C*', 'abcdefghijklmnop')), + 'WRONG_ENCODED_VALUE', // Binary encoded string + 'The quick brown fox jumps over the lazy dog', // clear + StoredObjectManagerException::class, + ]; + } + + /** + * @dataProvider getDataProvider + */ + public function testRead(StoredObject $storedObject, string $encodedContent, string $clearContent, ?string $exceptionClass = null) + { + if (null !== $exceptionClass) { + $this->expectException($exceptionClass); + } + + $storedObjectManager = $this->getSubject($storedObject, $encodedContent); + + self::assertEquals($clearContent, $storedObjectManager->read($storedObject)); + } + + /** + * @dataProvider getDataProvider + */ + public function testWrite(StoredObject $storedObject, string $encodedContent, string $clearContent, ?string $exceptionClass = null) + { + if (null !== $exceptionClass) { + $this->expectException($exceptionClass); + } + + $storedObjectManager = $this->getSubject($storedObject, $encodedContent); + + $storedObjectManager->write($storedObject, $clearContent); + + self::assertEquals($clearContent, $storedObjectManager->read($storedObject)); + } + + private function getHttpClient(string $encodedContent): HttpClientInterface + { + $callback = static function ($method, $url, $options) use ($encodedContent) { + if (Request::METHOD_GET === $method) { + switch ($url) { + case 'https://example.com/non-encrypted.txt': + case 'https://example.com/encrypted.txt': + return new MockResponse($encodedContent, ['http_code' => 200]); + + case 'https://example.com/error_during_http_request.txt': + return new TransportException('error_during_http_request.txt'); + + case 'https://example.com/invalid_statuscode.txt': + return new MockResponse($encodedContent, ['http_code' => 404]); + } + } + + if (Request::METHOD_PUT === $method) { + switch ($url) { + case 'https://example.com/non-encrypted.txt': + case 'https://example.com/encrypted.txt': + return new MockResponse($encodedContent, ['http_code' => 201]); + + case 'https://example.com/error_during_http_request.txt': + throw new TransportException('error_during_http_request.txt'); + + case 'https://example.com/invalid_statuscode.txt': + return new MockResponse($encodedContent, ['http_code' => 404]); + } + } + + return new MockResponse('Not found'); + }; + + return new MockHttpClient($callback); + } + + private function getSubject(StoredObject $storedObject, string $encodedContent): StoredObjectManagerInterface + { + return new StoredObjectManager( + $this->getHttpClient($encodedContent), + $this->getTempUrlGenerator($storedObject) + ); + } + + private function getTempUrlGenerator(StoredObject $storedObject): TempUrlGeneratorInterface + { + $response = new stdClass(); + $response->url = $storedObject->getFilename(); + + $tempUrlGenerator = $this + ->getMockBuilder(TempUrlGeneratorInterface::class) + ->getMock(); + + $tempUrlGenerator + ->method('generate') + ->withAnyParameters() + ->willReturn($response); + + return $tempUrlGenerator; + } +} From 35d723e5fbc22ea324ce1c174f299c3794ee5d76 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 8 Mar 2022 15:48:52 +0100 Subject: [PATCH 006/119] refactor: Use `StoredObjectManager`. --- .../DocGeneratorTemplateController.php | 206 +++++++++--------- .../src/Service/Wopi/ChillDocumentManager.php | 70 +----- 2 files changed, 113 insertions(+), 163 deletions(-) diff --git a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php index 000fcb03a..2f64ea81e 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php +++ b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php @@ -11,8 +11,6 @@ declare(strict_types=1); namespace Chill\DocGeneratorBundle\Controller; -use Base64Url\Base64Url; -use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; use Chill\DocGeneratorBundle\Context\ContextManager; use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface; use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException; @@ -21,11 +19,11 @@ use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface; use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException; use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository; use Chill\DocStoreBundle\Entity\StoredObject; +use Chill\DocStoreBundle\Service\StoredObjectManagerInterface; use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Serializer\Model\Collection; +use Doctrine\ORM\EntityManagerInterface; use Exception; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\TransferException; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\FileType; @@ -34,14 +32,14 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; // TODO à mettre dans services use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Contracts\HttpClient\HttpClientInterface; +use Throwable; +use function strlen; final class DocGeneratorTemplateController extends AbstractController { @@ -53,13 +51,13 @@ final class DocGeneratorTemplateController extends AbstractController private DriverInterface $driver; - private KernelInterface $kernel; + private EntityManagerInterface $entityManager; private LoggerInterface $logger; private PaginatorFactory $paginatorFactory; - private TempUrlGeneratorInterface $tempUrlGenerator; + private StoredObjectManagerInterface $storedObjectManager; public function __construct( ContextManager $contextManager, @@ -67,18 +65,18 @@ final class DocGeneratorTemplateController extends AbstractController DriverInterface $driver, LoggerInterface $logger, PaginatorFactory $paginatorFactory, - TempUrlGeneratorInterface $tempUrlGenerator, - KernelInterface $kernel, - HttpClientInterface $client + HttpClientInterface $client, + StoredObjectManagerInterface $storedObjectManager, + EntityManagerInterface $entityManager ) { $this->contextManager = $contextManager; $this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository; $this->driver = $driver; $this->logger = $logger; $this->paginatorFactory = $paginatorFactory; - $this->tempUrlGenerator = $tempUrlGenerator; - $this->kernel = $kernel; $this->client = $client; + $this->storedObjectManager = $storedObjectManager; + $this->entityManager = $entityManager; } /** @@ -177,8 +175,10 @@ final class DocGeneratorTemplateController extends AbstractController return $this->redirectToRoute( 'chill_docgenerator_test_generate_from_template', - ['template' => $template, 'entityClassName' => $entityClassName, 'entityId' => $entityId, - 'returnPath' => $request->query->get('returnPath', '/'), ] + [ + 'template' => $template, 'entityClassName' => $entityClassName, 'entityId' => $entityId, + 'returnPath' => $request->query->get('returnPath', '/'), + ] ); } @@ -192,16 +192,26 @@ final class DocGeneratorTemplateController extends AbstractController try { $context = $this->contextManager->getContextByDocGeneratorTemplate($template); } catch (ContextNotFoundException $e) { - throw new NotFoundHttpException($e->getMessage(), $e); + throw new NotFoundHttpException( + 'Context not found.', + $e + ); } - $entity = $this->getDoctrine()->getRepository($context->getEntityClass())->find($entityId); + $entity = $this + ->entityManager + ->getRepository($context->getEntityClass()) + ->find($entityId); if (null === $entity) { - throw new NotFoundHttpException("Entity with classname {$entityClassName} and id {$entityId} is not found"); + throw new NotFoundHttpException( + sprintf('Entity with classname %s and id %s is not found', $entityClassName, $entityId) + ); } - $contextGenerationData = []; + $contextGenerationData = [ + 'test_file' => null, + ]; if ( $context instanceof DocGeneratorContextWithPublicFormInterface @@ -235,123 +245,109 @@ final class DocGeneratorTemplateController extends AbstractController $contextGenerationData = $form->getData(); } elseif (!$form->isSubmitted() || ($form->isSubmitted() && !$form->isValid())) { $templatePath = '@ChillDocGenerator/Generator/basic_form.html.twig'; - $templateOptions = ['entity' => $entity, 'form' => $form->createView(), - 'template' => $template, 'context' => $context, ]; + $templateOptions = [ + 'entity' => $entity, 'form' => $form->createView(), + 'template' => $template, 'context' => $context, + ]; return $this->render($templatePath, $templateOptions); } } - if ($isTest && null !== $contextGenerationData['test_file']) { - /** @var File $file */ - $file = $contextGenerationData['test_file']; - $templateResource = fopen($file->getPathname(), 'rb'); + $document = $template->getFile(); + + if ($isTest && ($contextGenerationData['test_file'] instanceof File)) { + $dataDecrypted = file_get_contents($contextGenerationData['test_file']->getPathname()); } else { - $getUrlGen = $this->tempUrlGenerator->generate( - 'GET', - $template->getFile()->getFilename() - ); - - $data = $this->client->request('GET', $getUrlGen->url); - - $iv = $template->getFile()->getIv(); // iv as an Array - $ivGoodFormat = pack('C*', ...$iv); // iv as a String (ok for openssl_decrypt) - - $method = 'AES-256-CBC'; - - $key = $template->getFile()->getKeyInfos()['k']; - $keyGoodFormat = Base64Url::decode($key); - - $dataDecrypted = openssl_decrypt($data->getContent(), $method, $keyGoodFormat, 1, $ivGoodFormat); - - if (false === $dataDecrypted) { - throw new Exception('Error during Decrypt ', 1); + try { + $dataDecrypted = $this->storedObjectManager->read($document); + } catch (Throwable $exception) { + throw $exception; } - - if (false === $templateResource = fopen('php://memory', 'r+b')) { - $this->logger->error('Could not write data to memory'); - - throw new HttpException(500); - } - fwrite($templateResource, $dataDecrypted); - rewind($templateResource); } - $datas = $context->getData($template, $entity, $contextGenerationData); try { - $generatedResource = $this->driver->generateFromResource($templateResource, $template->getFile()->getType(), $datas, $template->getFile()->getFilename()); + $generatedResource = $this + ->driver + ->generateFromString( + $dataDecrypted, + $template->getFile()->getType(), + $context->getData($template, $entity, $contextGenerationData), + $template->getFile()->getFilename() + ); } catch (TemplateException $e) { - $msg = implode("\n", $e->getErrors()); - - return new Response($msg, 400, [ - 'Content-Type' => 'text/plain', - ]); + return new Response( + implode("\n", $e->getErrors()), + 400, + [ + 'Content-Type' => 'text/plain', + ] + ); } - fclose($templateResource); - if ($isTest) { - return new StreamedResponse( - static function () use ($generatedResource) { - fpassthru($generatedResource); - fclose($generatedResource); - }, + return new Response( + $generatedResource, Response::HTTP_OK, [ 'Content-Transfer-Encoding', 'binary', 'Content-Type' => 'application/vnd.oasis.opendocument.text', - 'Content-Disposition' => sprintf('attachment; filename="%s.odt"', 'generated'), - 'Content-Length' => fstat($generatedResource)['size'], + 'Content-Disposition' => 'attachment; filename="generated.odt"', + 'Content-Length' => strlen($generatedResource), ], ); } - $genDocName = 'doc_' . sprintf('%010d', mt_rand()) . 'odt'; - - $getUrlGen = $this->tempUrlGenerator->generate( - 'PUT', - $genDocName - ); - - $client = new Client(); + /** @var StoredObject $storedObject */ + $storedObject = (new ObjectNormalizer()) + ->denormalize( + [ + 'type' => $template->getFile()->getType(), + 'filename' => sprintf('%s_odt', uniqid('doc_', true)), + ], + StoredObject::class + ); try { - $putResponse = $client->request('PUT', $getUrlGen->url, [ - 'body' => $generatedResource, - ]); + $this->storedObjectManager->write($storedObject, $generatedResource); + } catch (Throwable $exception) { + throw $exception; + } - if ($putResponse->getStatusCode() === 201) { - $em = $this->getDoctrine()->getManager(); - $storedObject = new StoredObject(); - $storedObject - ->setType($template->getFile()->getType()) - ->setFilename($genDocName); + $this->entityManager->persist($storedObject); - $em->persist($storedObject); - - try { - $context->storeGenerated($template, $storedObject, $entity, $contextGenerationData); - } catch (Exception $e) { - $this->logger->error('Could not store the associated document to entity', [ + try { + $context + ->storeGenerated( + $template, + $storedObject, + $entity, + $contextGenerationData + ); + } catch (Exception $e) { + $this + ->logger + ->error( + 'Unable to store the associated document to entity', + [ 'entityClassName' => $entityClassName, 'entityId' => $entityId, 'contextKey' => $context->getName(), - ]); + ] + ); - throw $e; - } - - $em->flush(); - - return $this->redirectToRoute('chill_wopi_file_edit', [ - 'fileId' => $storedObject->getUuid(), - 'returnPath' => $request->query->get('returnPath', '/'), - ]); - } - } catch (TransferException $e) { throw $e; } - throw new Exception('Unable to generate document.'); + $this->entityManager->flush(); + + return $this + ->redirectToRoute( + 'chill_wopi_file_edit', + [ + 'fileId' => $storedObject->getUuid(), + 'returnPath' => $request->query->get('returnPath', '/'), + ] + ); } } diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php index 19ff3c62e..7cc71188f 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php @@ -11,12 +11,12 @@ declare(strict_types=1); namespace Chill\WopiBundle\Service\Wopi; -use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; use ChampsLibres\WopiLib\Contract\Entity\Document; use ChampsLibres\WopiLib\Contract\Service\DocumentLockManagerInterface; use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Repository\StoredObjectRepository; +use Chill\DocStoreBundle\Service\StoredObjectManagerInterface; use DateTimeInterface; use Doctrine\ORM\EntityManagerInterface; use Error; @@ -28,8 +28,6 @@ use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Mime\MimeTypes; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Contracts\HttpClient\HttpClientInterface; -use Throwable; use function strlen; @@ -39,33 +37,29 @@ final class ChillDocumentManager implements DocumentManagerInterface private EntityManagerInterface $entityManager; - private HttpClientInterface $httpClient; - private Psr17Interface $psr17; private RequestInterface $request; private StoredObjectRepository $storedObjectRepository; - private TempUrlGeneratorInterface $tempUrlGenerator; + private StoredObjectManagerInterface $storedObjectManager; public function __construct( DocumentLockManagerInterface $documentLockManager, EntityManagerInterface $entityManager, - HttpClientInterface $httpClient, - Psr17Interface $psr17, - StoredObjectRepository $storedObjectRepository, - TempUrlGeneratorInterface $tempUrlGenerator, HttpMessageFactoryInterface $httpMessageFactory, - RequestStack $requestStack + Psr17Interface $psr17, + RequestStack $requestStack, + StoredObjectManagerInterface $storedObjectManager, + StoredObjectRepository $storedObjectRepository ) { + $this->documentLockManager = $documentLockManager; $this->entityManager = $entityManager; $this->psr17 = $psr17; - $this->storedObjectRepository = $storedObjectRepository; - $this->documentLockManager = $documentLockManager; - $this->tempUrlGenerator = $tempUrlGenerator; - $this->httpClient = $httpClient; $this->request = $httpMessageFactory->createRequest($requestStack->getCurrentRequest()); + $this->storedObjectManager = $storedObjectManager; + $this->storedObjectRepository = $storedObjectRepository; } public function create(array $data): Document @@ -197,18 +191,7 @@ final class ChillDocumentManager implements DocumentManagerInterface public function remove(Document $document): void { - $entityIsDeleted = false; - - try { - $this->entityManager->remove($document); - $entityIsDeleted = true; - } catch (Throwable $e) { - $entityIsDeleted = false; - } - - if (true === $entityIsDeleted) { - $this->deleteContent($document); - } + // TODO: To implement when we have a clearer view and API. } public function write(Document $document, array $properties = []): void @@ -216,42 +199,13 @@ final class ChillDocumentManager implements DocumentManagerInterface $this->setContent($document, $properties['content']); } - private function deleteContent(StoredObject $storedObject): void - { - /** @var StdClass $object */ - $object = $this->tempUrlGenerator->generate('DELETE', $storedObject->getFilename()); - - $response = $this->httpClient->request('DELETE', $object->url); - - if (200 !== $response->getStatusCode()) { - throw new Error('Unable to delete stored object.'); - } - } - private function getContent(StoredObject $storedObject): string { - /** @var StdClass $object */ - $object = $this->tempUrlGenerator->generate('GET', $storedObject->getFilename()); - - $response = $this->httpClient->request('GET', $object->url); - - if (200 !== $response->getStatusCode()) { - throw new Error('Unable to retrieve stored object.'); - } - - return $response->getContent(); + return $this->storedObjectManager->read($storedObject); } private function setContent(StoredObject $storedObject, string $content): void { - // TODO: Add strict typing in champs-libres/async-uploader-bundle - /** @var StdClass $object */ - $object = $this->tempUrlGenerator->generate('PUT', $storedObject->getFilename()); - - $response = $this->httpClient->request('PUT', $object->url, ['body' => $content]); - - if (201 !== $response->getStatusCode()) { - throw new Error('Unable to save stored object.'); - } + $this->storedObjectManager->write($storedObject, $content); } } From 3d7fcd99a8d5ad3e65f45fb6d583706e16e1aece Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 15 Mar 2022 11:25:19 +0100 Subject: [PATCH 007/119] fix: Remove `console.log`. --- .../ChillWopiBundle/src/Resources/public/page/editor/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Bundle/ChillWopiBundle/src/Resources/public/page/editor/index.js b/src/Bundle/ChillWopiBundle/src/Resources/public/page/editor/index.js index 613a51d63..bb684e406 100644 --- a/src/Bundle/ChillWopiBundle/src/Resources/public/page/editor/index.js +++ b/src/Bundle/ChillWopiBundle/src/Resources/public/page/editor/index.js @@ -18,7 +18,6 @@ window.addEventListener('DOMContentLoaded', function(e) { frameholder.appendChild(office_frame); document.getElementById('office_form').submit(); - console.log(office_frame); const url = new URL(editor_url); const editor_domain = url.origin; From 22755de1dda819aa584bc0c99d43b2b36c23a705 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 15 Mar 2022 13:42:13 +0100 Subject: [PATCH 008/119] fix: Remove `PrependExtensionInterface`. It is now handled by `chill_main` bundle. --- .../DependencyInjection/ChillWopiExtension.php | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/Bundle/ChillWopiBundle/src/DependencyInjection/ChillWopiExtension.php b/src/Bundle/ChillWopiBundle/src/DependencyInjection/ChillWopiExtension.php index 9e8596336..37a9e2fa6 100644 --- a/src/Bundle/ChillWopiBundle/src/DependencyInjection/ChillWopiExtension.php +++ b/src/Bundle/ChillWopiBundle/src/DependencyInjection/ChillWopiExtension.php @@ -11,14 +11,12 @@ declare(strict_types=1); namespace Chill\WopiBundle\DependencyInjection; -use Ramsey\Uuid\Doctrine\UuidType; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; -final class ChillWopiExtension extends Extension implements PrependExtensionInterface +final class ChillWopiExtension extends Extension { public function load(array $configs, ContainerBuilder $container) { @@ -33,18 +31,4 @@ final class ChillWopiExtension extends Extension implements PrependExtensionInte $loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('services.php'); } - - public function prepend(ContainerBuilder $container) - { - $container->prependExtensionConfig( - 'doctrine', - [ - 'dbal' => [ - 'types' => [ - 'uuid' => UuidType::class, - ], - ], - ] - ); - } } From 3a3eb682882e3d3556366c63bf05605b5f0320c3 Mon Sep 17 00:00:00 2001 From: nobohan Date: Fri, 1 Apr 2022 16:17:04 +0200 Subject: [PATCH 009/119] household address: add a form for editing the validFrom date --- .../Controller/HouseholdController.php | 41 +++++++++++++++++++ .../address_valid_from_edit.html.twig | 34 +++++++++++++++ .../views/Household/addresses.html.twig | 7 +++- .../translations/messages.fr.yml | 3 ++ 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php index 0a8b7eea1..c0f0baab2 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Controller; use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Form\Type\AddressType; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Form\HouseholdType; use Chill\PersonBundle\Repository\Household\PositionRepository; @@ -136,6 +137,46 @@ class HouseholdController extends AbstractController ); } + /** + * @Route( + * "/{household_id}/address/edit_valid_from", + * name="chill_person_household_address_valid_from_edit", + * methods={"GET", "HEAD", "POST"} + * ) + * @ParamConverter("household", options={"id": "household_id"}) + */ + public function addressValidFromEdit(Request $request, Household $household) + { + // TODO ACL + + $address_id = $request->query->get('address_id'); + $address = $this->getDoctrine()->getManager() + ->getRepository(Address::class) + ->find($address_id); + + $form = $this->createForm(AddressType::class, $address, []); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->getDoctrine()->getManager()->persist($address); + $this->getDoctrine()->getManager()->flush(); + + return $this->redirectToRoute('chill_person_household_addresses', [ + 'household_id' => $household->getId() + ]); + } + + return $this->render( + '@ChillPerson/Household/address_valid_from_edit.html.twig', + [ + 'household' => $household, + 'address' => $address, + 'form' => $form->createView() + ] + ); + } + /** * @Route( * "/{household_id}/addresses", diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig new file mode 100644 index 000000000..a58e29de0 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig @@ -0,0 +1,34 @@ +{% extends '@ChillPerson/Household/layout.html.twig' %} + +{% block title 'Edit household address valid from'|trans %} + +{% block content %} +

{{ block('title') }}

+
+ {{ address|chill_entity_render_box}} +
+ + {{ form_start(form) }} + {{ form_errors(form) }} + +
+ {{ form_row(form.validFrom) }} +
+ + + + + {{ form_end(form) }} + +{% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/addresses.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/addresses.html.twig index 2802c62a7..d61e0b63e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Household/addresses.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/addresses.html.twig @@ -64,9 +64,12 @@ }) }} diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index ae7687324..5395ba983 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -498,6 +498,9 @@ Concerns household n°%id%: Concerne le ménage n°%id% Composition: Composition Budget: Budget The composition has been successfully removed.: La composition a été supprimée. +edit address valid from: Changer la date du déménagement +Edit household address valid from: Changer la date du déménagement + # accompanying course work Accompanying Course Actions: Actions d'accompagnements From ddce7603ad72d3e7742a4619375586d3d7db92b0 Mon Sep 17 00:00:00 2001 From: nobohan Date: Sat, 2 Apr 2022 10:53:54 +0200 Subject: [PATCH 010/119] household address: add condition for minimum value for vaildFrom date --- .../Controller/HouseholdController.php | 18 ++++++++++++++++-- .../address_valid_from_edit.html.twig | 7 ++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php index c0f0baab2..311d2eaad 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php @@ -158,10 +158,23 @@ class HouseholdController extends AbstractController $form->handleRequest($request); + $addresses = $household->getAddressesOrdered(); + + foreach($addresses as $a) { + if ($a->getId() === $address->getId()){ + $currentValueIndex = array_search($a, $addresses); + } + } + if (count($addresses) > 1 && $currentValueIndex > 0) { + $previousAddress = $addresses[$currentValueIndex - 1]; + $minValidFrom = $previousAddress->getValidTo(); + } else { + $minValidFrom = null; + } + if ($form->isSubmitted() && $form->isValid()) { $this->getDoctrine()->getManager()->persist($address); $this->getDoctrine()->getManager()->flush(); - return $this->redirectToRoute('chill_person_household_addresses', [ 'household_id' => $household->getId() ]); @@ -172,7 +185,8 @@ class HouseholdController extends AbstractController [ 'household' => $household, 'address' => $address, - 'form' => $form->createView() + 'form' => $form->createView(), + 'minValidFrom' => $minValidFrom ] ); } diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig index a58e29de0..78c7b0fe1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig @@ -11,8 +11,13 @@ {{ form_start(form) }} {{ form_errors(form) }} +
- {{ form_row(form.validFrom) }} + {% if minValidFrom is not null %} + {{ form_row(form.validFrom, {'attr': {'min': minValidFrom|date('Y-m-d') }}) }} + {% else %} + {{ form_row(form.validFrom) }} + {% endif %}
    From 6a4edd000fee5216658dd38dafe2b2b0232436b3 Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 6 Apr 2022 15:31:17 +0200 Subject: [PATCH 016/119] household address: add max date for changing the validFrom date --- .../Controller/HouseholdController.php | 10 +++++++++- .../Household/address_valid_from_edit.html.twig | 12 +++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php index 311d2eaad..c8f14ba21 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php @@ -172,6 +172,13 @@ class HouseholdController extends AbstractController $minValidFrom = null; } + if (count($addresses) > 1 && count($addresses) > $currentValueIndex + 1) { + $nextAddress = $addresses[$currentValueIndex + 1]; + $maxValidFrom = $nextAddress->getValidFrom(); + } else { + $maxValidFrom = null; + } + if ($form->isSubmitted() && $form->isValid()) { $this->getDoctrine()->getManager()->persist($address); $this->getDoctrine()->getManager()->flush(); @@ -186,7 +193,8 @@ class HouseholdController extends AbstractController 'household' => $household, 'address' => $address, 'form' => $form->createView(), - 'minValidFrom' => $minValidFrom + 'minValidFrom' => $minValidFrom, + 'maxValidFrom' => $maxValidFrom ] ); } diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig index 9f0a438fb..a2b8753f5 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig @@ -13,10 +13,16 @@
    - {% if minValidFrom is not null %} - {{ form_row(form.validFrom, {'attr': {'min': minValidFrom|date('Y-m-d') }}) }} + {% if minValidFrom is not null and maxValidFrom is not null %} + {{ form_row(form.validFrom, {'attr': {'min': minValidFrom|date('Y-m-d'), 'max': maxValidFrom|date('Y-m-d') }}) }} {% else %} - {{ form_row(form.validFrom) }} + {% if minValidFrom is not null %} + {{ form_row(form.validFrom, {'attr': {'min': minValidFrom|date('Y-m-d')}}) }} + {% elseif maxValidFrom is not null %} + {{ form_row(form.validFrom, {'attr': {'max': maxValidFrom|date('Y-m-d')}}) }} + {% else %} + {{ form_row(form.validFrom) }} + {% endif %} {% endif %}
    -{% if display_action is defined and display_action == true %} - {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', evaluation.accompanyingPeriodWork) %} -
      -
    • {{ m.download_button(doc.storedObject, doc.title) }}
    • - {% if chill_document_is_editable(doc.storedObject) %} + {% if display_action is defined and display_action == true %} + {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', evaluation.accompanyingPeriodWork) %} +
        +
      • {{ m.download_button(doc.storedObject, doc.title) }}
      • + {% if chill_document_is_editable(doc.storedObject) %} +
      • + {{ doc.storedObject|chill_document_edit_button }} +
      • + {% endif %}
      • - {{ doc.storedObject|chill_document_edit_button }} + + {{ 'Show'|trans }} +
      • +
      {% endif %} -
    • - - {{ 'Show'|trans }} - -
    • -
    {% endif %} {% endif %} diff --git a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php index d7ce1ed4b..578d8e195 100644 --- a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php +++ b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php @@ -48,6 +48,12 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW { $doc = $this->getRelatedEntity($entityWorkflow); + if (null === $doc) { + return [ + 'persons' => [], + ]; + } + return [ 'persons' => $doc->getAccompanyingPeriodWorkEvaluation() ->getAccompanyingPeriodWork()->getPersons(), @@ -58,6 +64,10 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW { $doc = $this->getRelatedEntity($entityWorkflow); + if (null === $doc) { + return $this->translator->trans('workflow.doc for evaluation deleted'); + } + return $this->translator->trans( 'workflow.Doc for evaluation (n°%eval%)', ['%eval%' => $entityWorkflow->getRelatedEntityId()] @@ -98,7 +108,7 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW return [ 'entity_workflow' => $entityWorkflow, - 'evaluation' => $doc->getAccompanyingPeriodWorkEvaluation(), + 'evaluation' => null !== $doc ? $doc->getAccompanyingPeriodWorkEvaluation() : $doc, 'doc' => $doc, ]; } diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index ae7687324..ab8ab0c67 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -581,6 +581,7 @@ My accompanying periods in draft: Mes parcours brouillons workflow: Doc for evaluation (n°%eval%): Document de l'évaluation n°%eval% + doc for evaluation deleted: Document supprimé dans une évaluation period_by_user_list: Period by user: Parcours d'accompagnement par utilisateur From f64409e5e63988b3ba11d7af9c67b8f3de99916a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Apr 2022 16:06:26 +0200 Subject: [PATCH 023/119] allow every person which has part for a workflow to see the workflow page --- .../Security/Authorization/EntityWorkflowVoter.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php index d8d74fbb9..82dfc7681 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php @@ -65,7 +65,17 @@ class EntityWorkflowVoter extends Voter return true; } - return $this->security->isGranted($entityAttribute, $relatedEntity); + if ($this->security->isGranted($entityAttribute, $relatedEntity)) { + return true; + } + + foreach ($subject->getSteps() as $step) { + if ($step->getAllDestUser()->contains($token->getUser())) { + return true; + } + } + + return false; case self::DELETE: return $subject->getStep() === 'initial'; From 26a4577420f7a7d62599c72c3a489da82db2cf8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Apr 2022 21:08:11 +0200 Subject: [PATCH 024/119] order query for location and add pagination in list --- .../ChillMainBundle/Controller/LocationController.php | 6 ++++++ .../Resources/views/Location/index.html.twig | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/Bundle/ChillMainBundle/Controller/LocationController.php b/src/Bundle/ChillMainBundle/Controller/LocationController.php index 10087ec1e..97deac9c5 100644 --- a/src/Bundle/ChillMainBundle/Controller/LocationController.php +++ b/src/Bundle/ChillMainBundle/Controller/LocationController.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Controller; use Chill\MainBundle\CRUD\Controller\CRUDController; +use Chill\MainBundle\Pagination\PaginatorInterface; use Symfony\Component\HttpFoundation\Request; class LocationController extends CRUDController @@ -29,4 +30,9 @@ class LocationController extends CRUDController { $query->where('e.availableForUsers = true'); //TODO not working } + + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) + { + return $query->addOrderBy('e.name', 'DESC'); + } } diff --git a/src/Bundle/ChillMainBundle/Resources/views/Location/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Location/index.html.twig index a04ae73a9..aa883c603 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Location/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Location/index.html.twig @@ -47,6 +47,8 @@ + {{ chill_pagination(paginator) }} +
    • From 572c602387ab08e3d03f6c501f36433a9b872870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Apr 2022 21:47:56 +0200 Subject: [PATCH 025/119] filter users which are disabled --- .../ChillMainBundle/Search/Entity/SearchUserApiProvider.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php b/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php index 56470c78b..baace22c1 100644 --- a/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php +++ b/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php @@ -49,11 +49,12 @@ class SearchUserApiProvider implements SearchApiInterface SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical))', [$pattern, $pattern]) ->setFromClause('users AS u') ->setWhereClauses(' + u.enabled IS TRUE and ( SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15 OR u.usernamecanonical LIKE \'%\' || LOWER(UNACCENT(?)) || \'%\' OR SIMILARITY(LOWER(UNACCENT(?)), LOWER(UNACCENT(u.label))) > 0.15 OR u.label LIKE \'%\' || LOWER(UNACCENT(?)) || \'%\' - ', [$pattern, $pattern, $pattern, $pattern]); + )', [$pattern, $pattern, $pattern, $pattern]); return $query; } From 6e1cabc8fc48980ea2e52c3a6112a173abfba61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Apr 2022 21:55:04 +0200 Subject: [PATCH 026/119] fix detecting of non-read notification --- .../Resources/public/module/notification/toggle_read.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/notification/toggle_read.js b/src/Bundle/ChillMainBundle/Resources/public/module/notification/toggle_read.js index 6308f2f11..ae092f8e2 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/notification/toggle_read.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/notification/toggle_read.js @@ -26,7 +26,7 @@ window.addEventListener('DOMContentLoaded', function (e) { buttonClass: el.dataset.buttonClass, buttonNoText: 'false' === el.dataset.buttonText, showUrl: el.dataset.showButtonUrl, - isRead: 1 === el.dataset.notificationCurrentIsRead, + isRead: 1 === Number.parseInt(el.dataset.notificationCurrentIsRead), container: el.dataset.container } }, From 009030b5fbf39f2bea92af3d95cf945705b32294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Apr 2022 21:59:02 +0200 Subject: [PATCH 027/119] notification list: move action buttons outside of the toggle --- .../views/Notification/_list_item.html.twig | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig index 817f7e6a7..02a02b4d0 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig @@ -61,17 +61,28 @@ +{% endmacro %} + +{% macro actions(c) %} {% if c.action_button is not defined or c.action_button != false %}
      - + {% if c.notification.comments|length > 0 %}
      @@ -79,16 +90,16 @@
      {% endif %} - +
      • {# Vue component #}
      • {% if is_granted('CHILL_MAIN_NOTIFICATION_UPDATE', c.notification) %} @@ -122,24 +133,25 @@ {{ _self.header(_context) }} - +
      - + {{ _self.content(_context) }} -
      + {{ _self.actions(_context) }} {% else %} {{ _self.title(_context) }} {{ _self.header(_context) }} {{ _self.content(_context) }} + {{ _self.actions(_context) }} {% endif %}
      From fc55567a641e1cc2dad99c01c9ae10340bf1949b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Apr 2022 22:12:13 +0200 Subject: [PATCH 028/119] update changelog --- CHANGELOG.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c278ca93e..b9a272e53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,26 @@ and this project adheres to ## Unreleased + + +## Test releases + +### 2021-04-07 + +* notification list: move action buttons outside of the toggle +* fix detecting of non-read notification +* filter users which are disabled in search user api +* order query for location and add pagination in list +* allow every person which has part for a workflow to see the workflow page +* able to see the workflow if the evaluation document has been deleted +* hardcode the list of supported mime types for edition with collabora + +### 2021-04-06 + * [main] notification toggle read: correct js syntax for compilation in production (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/548) * [parcours] Display of interlocuteurs changed to flex-table in parcours edit page to prevent cut-off of information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/535) * [activity] espace entre les boutons pour supprimer les documents -## Test releases ### continuous release in February and March From 0f926e9dfe8bc665386c7fb01dd4da00be0d3e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Apr 2022 22:40:20 +0200 Subject: [PATCH 029/119] allow to display pinned comment in item of accompanying course list --- CHANGELOG.md | 1 + .../AccompanyingPeriod/_list_item.html.twig | 40 +++++++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a272e53..abaa3fdd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to * allow every person which has part for a workflow to see the workflow page * able to see the workflow if the evaluation document has been deleted * hardcode the list of supported mime types for edition with collabora +* list of accompanying course: allow to see the pinned comment in list_item ### 2021-04-06 diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list_item.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list_item.html.twig index 65e514b14..62d0682df 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list_item.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list_item.html.twig @@ -116,14 +116,40 @@ {% endif %} -
      - {% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %} - {% if notif_counter.total > 0 %} -
      - {{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) }} + + {% if show_pinned_comment|default(false) and period.pinnedComment is not empty%} +
      +
      +

      {{ 'Pinned comment'|trans }}

      +
      + {{ period.pinnedComment.content|u.truncate(750, '…', false)|chill_markdown_to_html }} + {% if period.pinnedComment.content|length > 750 %} + {{ 'Read more'|trans }} + {% endif %} + +
      - {% endif %} - {% if itemMeta is defined %} +
      + {% endif %} + +
      + {% if itemMeta is not defined %} + {% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %} + {% if notif_counter.total > 0 %} +
      + {{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) }} +
      + {% endif %} + {% else %} {{ itemMeta }} {% endif %}
      From 7ffb3dc74ffe4486eb1366c7b3293f3714a5c1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Apr 2022 23:49:32 +0200 Subject: [PATCH 030/119] task: fix route name --- .../ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig index d0a906c9c..d7f923a46 100644 --- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig +++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig @@ -16,7 +16,7 @@
      • - + {{ 'Cancel'|trans }}
      • From 4257a918f30c05848c27f884c7f8463fa24bbb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 8 Apr 2022 00:11:08 +0200 Subject: [PATCH 031/119] fix error when search pattern is empty --- src/Bundle/ChillMainBundle/Controller/SearchController.php | 2 +- .../Repository/PersonACLAwareRepository.php | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Controller/SearchController.php b/src/Bundle/ChillMainBundle/Controller/SearchController.php index b9f5d19d8..41349336a 100644 --- a/src/Bundle/ChillMainBundle/Controller/SearchController.php +++ b/src/Bundle/ChillMainBundle/Controller/SearchController.php @@ -122,7 +122,7 @@ class SearchController extends AbstractController public function searchAction(Request $request, $_format) { - $pattern = $request->query->get('q', ''); + $pattern = trim($request->query->get('q', '')); if ('' === $pattern) { switch ($_format) { diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php index 5ed9762c8..d5dc3cd7d 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php @@ -136,8 +136,11 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac $andWhereSearchClause = []; $andWhereSearchClauseArgs = []; - if ('' !== $default) { + if ('' !== trim($default)) { foreach (explode(' ', $default) as $str) { + if ('' === trim($str)) { + continue; + } $pertinence[] = 'STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), person.fullnamecanonical) + ' . "(person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%')::int + " . From bb65909bfa66543eff7289d17997f504fcc48534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 16:52:11 +0200 Subject: [PATCH 032/119] add docgen context for a list of activities in a course --- .../Repository/ActivityACLAwareRepository.php | 89 ++++++ .../ActivityACLAwareRepositoryInterface.php | 13 + ...tActivitiesByAccompanyingPeriodContext.php | 276 ++++++++++++++++++ .../translations/messages.fr.yml | 2 + 4 files changed, 380 insertions(+) create mode 100644 src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php index 48f750c26..31b293a40 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -12,13 +12,21 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Repository; use Chill\ActivityBundle\Entity\Activity; +use Chill\ActivityBundle\Entity\ActivityPresence; +use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; +use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; +use Chill\MainBundle\Entity\Location; +use Chill\MainBundle\Entity\LocationType; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Query\ResultSetMappingBuilder; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Security; @@ -72,6 +80,87 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte ->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy); } + public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array + { + $rsm = new ResultSetMappingBuilder($this->em); + + $sql = " + SELECT + a.id AS activity_id, + date, + CASE WHEN durationtime IS NOT NULL THEN (EXTRACT(EPOCH from durationtime) / 60)::int ELSE 0 END AS durationtimeminute, + attendee_id, + comment_comment, + emergency, + sentreceived, + CASE WHEN traveltime IS NOT NULL THEN (EXTRACT(EPOCH from traveltime) / 60)::int ELSE 0 END AS traveltimeminute, + t.id AS type_id, t.name as type_name, + p.id AS presence_id, p.name AS presence_name, + location.id AS location_id, location.address_id, location.name AS location_name, location.phonenumber1, location.phonenumber2, location.email, + location.locationtype_id, locationtype.title AS locationtype_title, + users.userids AS userids, + thirdparties.thirdpartyids, + persons.personids, + actions.socialactionids, + issues.socialissueids + + FROM activity a + LEFT JOIN chill_main_location location ON a.location_id = location.id + LEFT JOIN chill_main_location_type locationtype ON location.locationtype_id = locationtype.id + LEFT JOIN activitytpresence p ON a.attendee_id = p.id + LEFT JOIN activitytype t ON a.type_id = t.id + LEFT JOIN LATERAL (SELECT jsonb_agg(user_id) userids, activity_id FROM activity_user AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS users ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(thirdparty_id) thirdpartyids, activity_id FROM activity_thirdparty AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS thirdparties ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(person_id) personids, activity_id FROM activity_person AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS persons ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(socialaction_id) socialactionids, activity_id FROM postgres.public.chill_activity_activity_chill_person_socialaction AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS actions ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(socialissue_id) socialissueids, activity_id FROM postgres.public.chill_activity_activity_chill_person_socialissue AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS issues ON TRUE + + WHERE accompanyingperiod_id = ? + ORDER BY a.date DESC, a.id DESC + LIMIT ? + "; + + $rsm + ->addEntityResult(Activity::class, 'a') + ->addFieldResult('a', 'activity_id', 'id') + ->addFieldResult('a', 'date', 'date') + ->addFieldResult('a', 'comment', 'comment') + ->addFieldResult('a', 'sentreceived', 'sentReceived') + ->addFieldResult('a', 'emergency', 'emergency') + ->addJoinedEntityResult(Location::class, 'location', 'a', 'location') + ->addFieldResult('location', 'location_id', 'id') + ->addFieldResult('location', 'location_name', 'name') + ->addFieldResult('location', 'phonenumber1', 'phonenumber1') + ->addFieldResult('location', 'phonenumber2', 'phonenumber2') + ->addFieldResult('location', 'email', 'email') + ->addJoinedEntityResult(LocationType::class,'locationType', 'location', 'locationType' ) + ->addFieldResult('locationType', 'locationtype_id', 'id') + ->addFieldResult('locationType', 'locationtype_title', 'title') + ->addJoinedEntityResult(ActivityType::class, 'activityType', 'a', 'activityType') + ->addFieldResult('activityType', 'type_id', 'id') + ->addFieldResult('activityType', 'type_name', 'name') + ->addJoinedEntityResult(ActivityPresence::class, 'activityPresence', 'a', 'attendee') + ->addFieldResult('activityPresence', 'presence_id', 'id') + ->addFieldResult('activityPresence', 'presence_name', 'name') + + // results which cannot be mapped into entity + ->addScalarResult('comment_comment', 'comment', Types::TEXT) + ->addScalarResult('userids', 'userIds', Types::JSON) + ->addScalarResult('thirdpartyids', 'thirdPartyIds', Types::JSON) + ->addScalarResult('personids', 'personIds', Types::JSON) + ->addScalarResult('socialactionids', 'socialActionIds', Types::JSON) + ->addScalarResult('socialissueids', 'socialIssueIds', Types::JSON) + ->addScalarResult('durationtimeminute', 'durationTimeMinute', Types::INTEGER) + ->addScalarResult('traveltimeminute', 'travelTimeMinute', Types::INTEGER) + ; + + $nq = $this->em->createNativeQuery($sql, $rsm); + + $nq->setParameter(0, $period->getId())->setParameter(1, $limit); + + return $nq->getResult(AbstractQuery::HYDRATE_ARRAY); + } + /** * @param array $orderBy * diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php index 56fb112f9..a6f8fe934 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php @@ -21,6 +21,19 @@ interface ActivityACLAwareRepositoryInterface */ public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array; + /** + * Return a list of activities, simplified as array (not object). + * + * The aim of this method is to get a long list of activities and keep performance. + * + * @param AccompanyingPeriod $period + * @param string $role + * @param int|null $limit + * @param array|null $orderBy + * @return array an array of array, each item representing an activity + */ + public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array; + /** * @return Activity[]|array */ diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php new file mode 100644 index 000000000..3868545d9 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php @@ -0,0 +1,276 @@ +accompanyingPeriodContext = $accompanyingPeriodContext; + $this->activityACLAwareRepository = $activityACLAwareRepository; + $this->normalizer = $normalizer; + $this->personRepository = $personRepository; + $this->socialActionRepository = $socialActionRepository; + $this->socialIssueRepository = $socialIssueRepository; + $this->thirdPartyRepository = $thirdPartyRepository; + $this->translatableStringHelper = $translatableStringHelper; + $this->userRepository = $userRepository; + } + + public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array + { + $data = $this->accompanyingPeriodContext->getData($template, $entity, $contextGenerationData); + + $data['activities'] = $this->getActivitiesSimplified($entity); + + return $data; + } + + private function getActivitiesSimplified(AccompanyingPeriod $period) + { + $activities = + $this->activityACLAwareRepository->findByAccompanyingPeriodSimplified($period); + $results = []; + + foreach ($activities as $row) { + $activity = $row[0]; + + $activity['date'] = $this->normalizer->normalize($activity['date'], 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => \DateTime::class + ]); + + if (null === $activity['location']) { + $activity['location'] = $this->normalizer->normalize(null, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Location::class + ]); + $activity['location']['type'] = 'location'; + } else { + $activity['location']['isNull'] = false; + $activity['location']['type'] = 'location'; + foreach (['1', '2'] as $key) { + $activity['location']['phonenumber'.$key] = $this->normalizer->normalize( + $activity['location']['phonenumber'.$key], + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => PhoneNumber::class] + ); + } + } + + if (is_numeric($activity['location']['locationType']['id'])) { + $activity['location']['locationType']['title'] = $this->translatableStringHelper->localize( + $activity['location']['locationType']['title'] + ); + $activity['location']['locationType']['isNull'] = false; + $activity['location']['locationType']['type'] = 'locationType'; + } + + if (null !== $activity['activityType']) { + $activity['activityType']['name'] = $this->translatableStringHelper->localize( + $activity['activityType']['name'] + ); + $activity['activityType']['isNull'] = false; + $activity['activityType']['type'] = 'activityType'; + } else { + $activity['activityType'] = $this->normalizer->normalize(null, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityType::class + ]); + } + + if (null !== $activity['attendee']) { + $activity['attendee']['name'] = $this->translatableStringHelper->localize( + $activity['attendee']['name'] + ); + $activity['attendee']['isNull'] = false; + $activity['attendee']['type'] = 'activityPresence'; + } else { + $activity['attendee'] = $this->normalizer->normalize(null, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityPresence::class + ]); + } + + $activity['comment'] = (string) $row['comment']; + $activity['travelTimeMinute'] = $row['travelTimeMinute']; + $activity['durationTimeMinute'] = $row['durationTimeMinute']; + + if (null !== $row['userIds']) { + foreach ($row['userIds'] as $id) { + $activity['users'][] = $this->normalizer->normalize( + $this->userRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => User::class] + ); + } + } else { + $activity['users'] = []; + } + + if (null !== $row['personIds']) { + foreach ($row['personIds'] as $id) { + $activity['persons'][] = $this->normalizer->normalize( + $this->personRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Person::class] + ); + } + } else { + $activity['persons'] = []; + } + + if (null !== $row['thirdPartyIds']) { + foreach ($row['thirdPartyIds'] as $id) { + $activity['thirdParties'][] = $this->normalizer->normalize( + $this->thirdPartyRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ThirdParty::class] + ); + } + } else { + $activity['thirdParties'] = []; + } + + if (null !== $row['socialActionIds']) { + foreach ($row['socialActionIds'] as $id) { + $activity['socialActions'][] = $this->normalizer->normalize( + $this->socialActionRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => SocialAction::class] + ); + } + } else { + $activity['socialActions'] = []; + } + + if (null !== $row['socialIssueIds']) { + foreach ($row['socialIssueIds'] as $id) { + $activity['socialIssues'][] = $this->normalizer->normalize( + $this->socialIssueRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => SocialIssue::class] + ); + } + } else { + $activity['socialIssues'] = []; + } + + $results[] = $activity; + } + + return $results; + } + + public function getDescription(): string + { + return 'docgen.Accompanying period with a list of activities description'; + } + + public function getEntityClass(): string + { + return AccompanyingPeriod::class; + } + + public static function getKey(): string + { + return self::class; + } + + public function getName(): string + { + return 'docgen.Accompanying period with a list of activities'; + } + + public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void + { + $this->accompanyingPeriodContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData); + } + + public function adminFormReverseTransform(array $data): array + { + return $this->accompanyingPeriodContext->adminFormReverseTransform($data); + } + + public function adminFormTransform(array $data): array + { + return $this->accompanyingPeriodContext->adminFormTransform($data); + } + + public function buildAdminForm(FormBuilderInterface $builder): void + { + $this->accompanyingPeriodContext->buildAdminForm($builder); + } + + public function hasAdminForm(): bool + { + return $this->accompanyingPeriodContext->hasAdminForm(); + } + + public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void + { + $this->accompanyingPeriodContext->buildPublicForm($builder, $template, $entity); + } + + public function getFormData(DocGeneratorTemplate $template, $entity): array + { + return $this->accompanyingPeriodContext->getFormData($template, $entity); + } + + public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool + { + return $this->accompanyingPeriodContext->hasPublicForm($template, $entity); + } + +} diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml index 051497b69..49217dd5d 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -232,3 +232,5 @@ This is the minimal activity data: Activité n° docgen: Activity basic: Echange A basic context for activity: Contexte pour les échanges + Accompanying period with a list of activities: Parcours d'accompagnement avec liste des échanges + Accompanying period with a list of activities description: Ce contexte reprend les informations du parcours, et tous les échanges pour un parcours. Les échanges ne sont pas filtrés. From 9765bc5663402447fa25e842527d7a162d217082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 17:12:59 +0200 Subject: [PATCH 033/119] fix cs --- .../Repository/ActivityACLAwareRepository.php | 10 +- .../ActivityACLAwareRepositoryInterface.php | 4 - ...tActivitiesByAccompanyingPeriodContext.php | 154 ++++++++++-------- 3 files changed, 86 insertions(+), 82 deletions(-) diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php index 31b293a40..2778945eb 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -15,7 +15,6 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityPresence; use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; -use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\LocationType; use Chill\MainBundle\Entity\Scope; @@ -84,7 +83,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte { $rsm = new ResultSetMappingBuilder($this->em); - $sql = " + $sql = ' SELECT a.id AS activity_id, date, @@ -118,7 +117,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte WHERE accompanyingperiod_id = ? ORDER BY a.date DESC, a.id DESC LIMIT ? - "; + '; $rsm ->addEntityResult(Activity::class, 'a') @@ -133,7 +132,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte ->addFieldResult('location', 'phonenumber1', 'phonenumber1') ->addFieldResult('location', 'phonenumber2', 'phonenumber2') ->addFieldResult('location', 'email', 'email') - ->addJoinedEntityResult(LocationType::class,'locationType', 'location', 'locationType' ) + ->addJoinedEntityResult(LocationType::class, 'locationType', 'location', 'locationType') ->addFieldResult('locationType', 'locationtype_id', 'id') ->addFieldResult('locationType', 'locationtype_title', 'title') ->addJoinedEntityResult(ActivityType::class, 'activityType', 'a', 'activityType') @@ -151,8 +150,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte ->addScalarResult('socialactionids', 'socialActionIds', Types::JSON) ->addScalarResult('socialissueids', 'socialIssueIds', Types::JSON) ->addScalarResult('durationtimeminute', 'durationTimeMinute', Types::INTEGER) - ->addScalarResult('traveltimeminute', 'travelTimeMinute', Types::INTEGER) - ; + ->addScalarResult('traveltimeminute', 'travelTimeMinute', Types::INTEGER); $nq = $this->em->createNativeQuery($sql, $rsm); diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php index a6f8fe934..1fc6d22b1 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php @@ -26,10 +26,6 @@ interface ActivityACLAwareRepositoryInterface * * The aim of this method is to get a long list of activities and keep performance. * - * @param AccompanyingPeriod $period - * @param string $role - * @param int|null $limit - * @param array|null $orderBy * @return array an array of array, each item representing an activity */ public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array; diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php index 3868545d9..0a442cec7 100644 --- a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php @@ -1,5 +1,14 @@ userRepository = $userRepository; } + public function adminFormReverseTransform(array $data): array + { + return $this->accompanyingPeriodContext->adminFormReverseTransform($data); + } + + public function adminFormTransform(array $data): array + { + return $this->accompanyingPeriodContext->adminFormTransform($data); + } + + public function buildAdminForm(FormBuilderInterface $builder): void + { + $this->accompanyingPeriodContext->buildAdminForm($builder); + } + + public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void + { + $this->accompanyingPeriodContext->buildPublicForm($builder, $template, $entity); + } + public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array { $data = $this->accompanyingPeriodContext->getData($template, $entity, $contextGenerationData); @@ -81,6 +111,46 @@ class ListActivitiesByAccompanyingPeriodContext implements return $data; } + public function getDescription(): string + { + return 'docgen.Accompanying period with a list of activities description'; + } + + public function getEntityClass(): string + { + return AccompanyingPeriod::class; + } + + public function getFormData(DocGeneratorTemplate $template, $entity): array + { + return $this->accompanyingPeriodContext->getFormData($template, $entity); + } + + public static function getKey(): string + { + return self::class; + } + + public function getName(): string + { + return 'docgen.Accompanying period with a list of activities'; + } + + public function hasAdminForm(): bool + { + return $this->accompanyingPeriodContext->hasAdminForm(); + } + + public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool + { + return $this->accompanyingPeriodContext->hasPublicForm($template, $entity); + } + + public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void + { + $this->accompanyingPeriodContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData); + } + private function getActivitiesSimplified(AccompanyingPeriod $period) { $activities = @@ -91,20 +161,21 @@ class ListActivitiesByAccompanyingPeriodContext implements $activity = $row[0]; $activity['date'] = $this->normalizer->normalize($activity['date'], 'docgen', [ - AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => \DateTime::class + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => DateTime::class, ]); if (null === $activity['location']) { - $activity['location'] = $this->normalizer->normalize(null, 'docgen', [ - AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Location::class + $activity['location'] = $this->normalizer->normalize(null, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Location::class, ]); $activity['location']['type'] = 'location'; } else { $activity['location']['isNull'] = false; $activity['location']['type'] = 'location'; + foreach (['1', '2'] as $key) { - $activity['location']['phonenumber'.$key] = $this->normalizer->normalize( - $activity['location']['phonenumber'.$key], + $activity['location']['phonenumber' . $key] = $this->normalizer->normalize( + $activity['location']['phonenumber' . $key], 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => PhoneNumber::class] ); @@ -127,7 +198,7 @@ class ListActivitiesByAccompanyingPeriodContext implements $activity['activityType']['type'] = 'activityType'; } else { $activity['activityType'] = $this->normalizer->normalize(null, 'docgen', [ - AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityType::class + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityType::class, ]); } @@ -139,7 +210,7 @@ class ListActivitiesByAccompanyingPeriodContext implements $activity['attendee']['type'] = 'activityPresence'; } else { $activity['attendee'] = $this->normalizer->normalize(null, 'docgen', [ - AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityPresence::class + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityPresence::class, ]); } @@ -212,65 +283,4 @@ class ListActivitiesByAccompanyingPeriodContext implements return $results; } - - public function getDescription(): string - { - return 'docgen.Accompanying period with a list of activities description'; - } - - public function getEntityClass(): string - { - return AccompanyingPeriod::class; - } - - public static function getKey(): string - { - return self::class; - } - - public function getName(): string - { - return 'docgen.Accompanying period with a list of activities'; - } - - public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void - { - $this->accompanyingPeriodContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData); - } - - public function adminFormReverseTransform(array $data): array - { - return $this->accompanyingPeriodContext->adminFormReverseTransform($data); - } - - public function adminFormTransform(array $data): array - { - return $this->accompanyingPeriodContext->adminFormTransform($data); - } - - public function buildAdminForm(FormBuilderInterface $builder): void - { - $this->accompanyingPeriodContext->buildAdminForm($builder); - } - - public function hasAdminForm(): bool - { - return $this->accompanyingPeriodContext->hasAdminForm(); - } - - public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void - { - $this->accompanyingPeriodContext->buildPublicForm($builder, $template, $entity); - } - - public function getFormData(DocGeneratorTemplate $template, $entity): array - { - return $this->accompanyingPeriodContext->getFormData($template, $entity); - } - - public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool - { - return $this->accompanyingPeriodContext->hasPublicForm($template, $entity); - } - } From 7fbb3dfd07d10f03e16d513f264d208525368354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 17:43:31 +0200 Subject: [PATCH 034/119] docgen: add more persons choices in person1, person2 and mainPerson Allow to pick amongst: * requestor (if person) * resources of course (if person) * resources of person (if person) --- .../AccompanyingPeriodContext.php | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php b/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php index 495989b29..dc6e9c912 100644 --- a/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php +++ b/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php @@ -152,8 +152,25 @@ class AccompanyingPeriodContext implements $options = $template->getOptions(); $persons = $entity->getCurrentParticipations()->map(static function (AccompanyingPeriodParticipation $p) { return $p->getPerson(); - }) - ->toArray(); + }); + + foreach ($entity->getCurrentParticipations() as $p) { + foreach ($p->getPerson()->getResources() as $r) { + if (null !== $r->getPerson() && !$persons->contains($r->getPerson())) { + $persons->add($r->getPerson()); + } + } + } + + if (null !== $entity->getRequestorPerson() && !$persons->contains($entity->getRequestorPerson())) { + $persons->add($entity->getRequestorPerson()); + } + + foreach ($entity->getResources() as $r) { + if (null !== $r->getPerson() && !$persons->contains($r->getPerson())) { + $persons->add($r->getPerson()); + } + } foreach (['mainPerson', 'person1', 'person2'] as $key) { if ($options[$key] ?? false) { From 910245f8557abaffe020936ec02a84cc38494512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 17:45:57 +0200 Subject: [PATCH 035/119] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index abaa3fdd8..dcf61d70e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to ## Unreleased +* [docgen] add more persons choices in docgen for course: amongst requestor (if person), resources of course (if person), and PersonResource (if person); +* [docgen] add a new context with a list of activities in course ## Test releases From 69b2dce7ee8b289f52c53769099bf28fdbf69712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 18:10:10 +0200 Subject: [PATCH 036/119] docgen/budget: add comment for each budget line (resource and charge) The comments are cocatenated by lines of each type, and separated by a `|` --- CHANGELOG.md | 1 + .../Service/Summary/SummaryBudget.php | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcf61d70e..a61c63a73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to * [docgen] add more persons choices in docgen for course: amongst requestor (if person), resources of course (if person), and PersonResource (if person); * [docgen] add a new context with a list of activities in course +* [docgen] add a comment in budget lines ## Test releases diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php index 8e46feb7b..63cb69ad5 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php @@ -25,13 +25,13 @@ use function count; */ class SummaryBudget implements SummaryBudgetInterface { - private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, type FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; - private const QUERY_CHARGE_BY_PERSON = 'select SUM(amount) AS sum, type FROM chill_budget.charge WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_CHARGE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.charge WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; - private const QUERY_RESOURCE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, type FROM chill_budget.resource WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_RESOURCE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.resource WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; - private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, type FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; private array $chargeLabels; @@ -110,7 +110,9 @@ class SummaryBudget implements SummaryBudgetInterface $rsm = new ResultSetMapping(); $rsm ->addScalarResult('sum', 'sum') - ->addScalarResult('type', 'type'); + ->addScalarResult('type', 'type') + ->addScalarResult('comment', 'comment') + ; return $rsm; } @@ -121,7 +123,7 @@ class SummaryBudget implements SummaryBudgetInterface $labels = $this->chargeLabels; return array_combine($keys, array_map(function ($i) use ($labels) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i])]; + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), 'comment' => '']; }, $keys)); } @@ -131,7 +133,7 @@ class SummaryBudget implements SummaryBudgetInterface $labels = $this->resourcesLabels; return array_combine($keys, array_map(function ($i) use ($labels) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i])]; + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), 'comment' => '']; }, $keys)); } @@ -158,6 +160,7 @@ class SummaryBudget implements SummaryBudgetInterface $result[$row['type']] = [ 'sum' => (float) $row['sum'], 'label' => $this->translatableStringHelper->localize($label[$row['type']]), + 'comment' => (string) $row['comment'], ]; } From 01c571ab06e610427b2bcec36c8d85c2e7737122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 21:04:36 +0200 Subject: [PATCH 037/119] fix path to table --- .../Repository/ActivityACLAwareRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php index 2778945eb..d960a0c21 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -111,8 +111,8 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte LEFT JOIN LATERAL (SELECT jsonb_agg(user_id) userids, activity_id FROM activity_user AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS users ON TRUE LEFT JOIN LATERAL (SELECT jsonb_agg(thirdparty_id) thirdpartyids, activity_id FROM activity_thirdparty AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS thirdparties ON TRUE LEFT JOIN LATERAL (SELECT jsonb_agg(person_id) personids, activity_id FROM activity_person AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS persons ON TRUE - LEFT JOIN LATERAL (SELECT jsonb_agg(socialaction_id) socialactionids, activity_id FROM postgres.public.chill_activity_activity_chill_person_socialaction AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS actions ON TRUE - LEFT JOIN LATERAL (SELECT jsonb_agg(socialissue_id) socialissueids, activity_id FROM postgres.public.chill_activity_activity_chill_person_socialissue AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS issues ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(socialaction_id) socialactionids, activity_id FROM chill_activity_activity_chill_person_socialaction AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS actions ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(socialissue_id) socialissueids, activity_id FROM chill_activity_activity_chill_person_socialissue AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS issues ON TRUE WHERE accompanyingperiod_id = ? ORDER BY a.date DESC, a.id DESC From ccf7c885bbde5d318a9157c399cffc7e5dc6b2e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 09:44:21 +0200 Subject: [PATCH 038/119] fix normalization for phonenumber on person when phonenumber is null --- .../Service/Summary/SummaryBudget.php | 3 +- .../Normalizer/PhonenumberNormalizerTest.php | 54 +++++++++++++++++++ .../Normalizer/PersonDocGenNormalizer.php | 8 +-- 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/PhonenumberNormalizerTest.php diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php index 63cb69ad5..243cd4749 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php @@ -111,8 +111,7 @@ class SummaryBudget implements SummaryBudgetInterface $rsm ->addScalarResult('sum', 'sum') ->addScalarResult('type', 'type') - ->addScalarResult('comment', 'comment') - ; + ->addScalarResult('comment', 'comment'); return $rsm; } diff --git a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/PhonenumberNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/PhonenumberNormalizerTest.php new file mode 100644 index 000000000..1e0d4ef29 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/PhonenumberNormalizerTest.php @@ -0,0 +1,54 @@ +parse('+32486123465'), 'docgen', ['docgen:expects' => PhoneNumber::class], '0486 12 34 65']; + + yield [null, 'docgen', ['docgen:expects' => PhoneNumber::class], '']; + } + + /** + * @dataProvider dataProviderNormalizePhonenumber + * + * @param mixed $format + * @param mixed $context + * @param mixed $expected + */ + public function testNormalize(?Phonenumber $phonenumber, $format, $context, $expected) + { + $parameterBag = $this->prophesize(ParameterBagInterface::class); + $parameterBag->get(Argument::exact('chill_main'))->willReturn(['phone_helper' => ['default_carrier_code' => 'BE']]); + $normalizer = new PhonenumberNormalizer($parameterBag->reveal()); + + $this->assertEquals($expected, $normalizer->normalize($phonenumber, $format, $context)); + } +} diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php index 490a7e2af..89f01bf94 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php @@ -23,6 +23,7 @@ use Chill\PersonBundle\Repository\Relationships\RelationshipRepository; use Chill\PersonBundle\Templating\Entity\PersonRenderInterface; use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; +use libphonenumber\PhoneNumber; use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; @@ -71,6 +72,7 @@ class PersonDocGenNormalizer implements $dateContext = $context; $dateContext['docgen:expects'] = DateTimeInterface::class; $addressContext = array_merge($context, ['docgen:expects' => Address::class]); + $phonenumberContext = array_merge($context, ['docgen:expects' => PhoneNumber::class]); $personResourceContext = array_merge($context, [ 'docgen:expects' => Person\PersonResource::class, // we simplify the list of attributes for the embedded persons @@ -113,9 +115,9 @@ class PersonDocGenNormalizer implements 'maritalStatus' => null !== ($ms = $person->getMaritalStatus()) ? $this->translatableStringHelper->localize($ms->getName()) : '', 'maritalStatusDate' => $this->normalizer->normalize($person->getMaritalStatusDate(), $format, $dateContext), 'email' => $person->getEmail(), - 'firstPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber() ?? $person->getMobilenumber(), $format, $context), - 'fixPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber(), $format, $context), - 'mobilePhoneNumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $context), + 'firstPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber() ?? $person->getMobilenumber(), $format, $phonenumberContext), + 'fixPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber(), $format, $phonenumberContext), + 'mobilePhoneNumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $phonenumberContext), 'nationality' => null !== ($c = $person->getNationality()) ? $this->translatableStringHelper->localize($c->getName()) : '', 'placeOfBirth' => $person->getPlaceOfBirth(), 'memo' => $person->getMemo(), From ef9fd80ad59144327fc76ed68d4aa80760b6ad0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 18:02:11 +0200 Subject: [PATCH 039/119] update schema to send to emails --- .../ChillMainBundle/Entity/Notification.php | 13 +++++++ .../migrations/Version20220413154743.php | 39 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20220413154743.php diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index b2fe40c60..5257a7a90 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -105,12 +105,25 @@ class Notification implements TrackUpdateInterface */ private ?User $updatedBy; + /** + * a list of destinee which will receive notifications + * @var array|string[] + * @ORM\Column(type="json") + */ + public array $adressesEmails = []; + + /** + * @ORM\Column(type="text", nullable=false) + */ + private string $accessKey; + public function __construct() { $this->addressees = new ArrayCollection(); $this->unreadBy = new ArrayCollection(); $this->comments = new ArrayCollection(); $this->setDate(new DateTimeImmutable()); + $this->accessKey = bin2hex(openssl_random_pseudo_bytes(24)); } public function addAddressee(User $addressee): self diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php b/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php new file mode 100644 index 000000000..88a31c92c --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php @@ -0,0 +1,39 @@ +addSql('ALTER TABLE chill_main_notification ADD adressesEmails JSON NOT NULL DEFAULT \'[]\';'); + $this->addSql('ALTER TABLE chill_main_notification ADD accessKey TEXT DEFAULT NULL'); + $this->addSql('WITH randoms AS (select + n.id, + string_agg(substr(characters, (random() * length(characters) + 0.5)::integer, 1), \'\') as random_word + from (values(\'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\')) as symbols(characters) + -- length of word + join generate_series(1, 16) on 1 = 1 + JOIN chill_main_notification n ON true + GROUP BY n.id) + UPDATE chill_main_notification SET accessKey = randoms.random_word FROM randoms WHERE chill_main_notification.id = randoms.id'); + $this->addSql('ALTER TABLE chill_main_notification ALTER accessKey DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_main_notification ALTER accessKey SET NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_notification DROP adressesEmails'); + $this->addSql('ALTER TABLE chill_main_notification DROP accessKey'); + } +} From a8db07a3836d92bcec9bd46292d47437f59abe4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 21:33:18 +0200 Subject: [PATCH 040/119] notification / add email: fix entity Notification --- .../ChillMainBundle/Entity/Notification.php | 49 ++++++++++++++++++- .../Tests/Entity/NotificationTest.php | 18 +++++++ .../migrations/Version20220413154743.php | 2 +- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index 5257a7a90..9b2dded40 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -110,7 +110,14 @@ class Notification implements TrackUpdateInterface * @var array|string[] * @ORM\Column(type="json") */ - public array $adressesEmails = []; + private array $addressesEmails = []; + + /** + * a list of emails adresses which were added to the notification + * + * @var array|string[] + */ + private array $addressesEmailsAdded = []; /** * @ORM\Column(type="text", nullable=false) @@ -126,6 +133,46 @@ class Notification implements TrackUpdateInterface $this->accessKey = bin2hex(openssl_random_pseudo_bytes(24)); } + /** + * @return array|string[] + */ + public function getAddressesEmails(): array + { + return $this->addressesEmails; + } + + /** + * @return array|string[] + */ + public function getAddressesEmailsAdded(): array + { + return $this->addressesEmailsAdded; + } + + /** + * @return string + */ + public function getAccessKey(): string + { + return $this->accessKey; + } + + public function addAddressesEmail(string $email) + { + if (!in_array($email, $this->addressesEmails)) { + $this->addressesEmails[] = $email; + $this->addressesEmailsAdded[] = $email; + } + } + + public function removeAddressesEmail(string $email) + { + if (in_array($email, $this->addressesEmails)) { + $this->addressesEmails = array_filter($this->addressesEmails, fn ($e) => $e !== $email); + $this->addressesEmailsAdded = array_filter($this->addressesEmailsAdded, fn ($e) => $e !== $email); + } + } + public function addAddressee(User $addressee): self { if (!$this->addressees->contains($addressee)) { diff --git a/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php b/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php index 8110fa11d..9fc0d6cb2 100644 --- a/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php @@ -122,4 +122,22 @@ final class NotificationTest extends KernelTestCase $this->assertContains($addresseeId, $unreadIds); } } + + public function testAddressesEmail(): void + { + $notification = new Notification(); + + $notification->addAddressesEmail('test'); + $notification->addAddressesEmail('other'); + + $this->assertContains('test', $notification->getAddressesEmails()); + $this->assertContains('other', $notification->getAddressesEmails()); + $this->assertContains('test', $notification->getAddressesEmailsAdded()); + $this->assertContains('other', $notification->getAddressesEmailsAdded()); + + $notification->removeAddressesEmail('other'); + + $this->assertNotContains('other', $notification->getAddressesEmails()); + $this->assertNotContains('other', $notification->getAddressesEmailsAdded()); + } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php b/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php index 88a31c92c..87da6a7f8 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php @@ -16,7 +16,7 @@ final class Version20220413154743 extends AbstractMigration public function up(Schema $schema): void { - $this->addSql('ALTER TABLE chill_main_notification ADD adressesEmails JSON NOT NULL DEFAULT \'[]\';'); + $this->addSql('ALTER TABLE chill_main_notification ADD addressesEmails JSON NOT NULL DEFAULT \'[]\';'); $this->addSql('ALTER TABLE chill_main_notification ADD accessKey TEXT DEFAULT NULL'); $this->addSql('WITH randoms AS (select n.id, From 4425f2ad4998bf97d174f2f86a8d4a397f623b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 21:43:41 +0200 Subject: [PATCH 041/119] fix type for Notification email addresses --- src/Bundle/ChillMainBundle/Entity/Notification.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index 9b2dded40..62994497e 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -159,7 +159,7 @@ class Notification implements TrackUpdateInterface public function addAddressesEmail(string $email) { - if (!in_array($email, $this->addressesEmails)) { + if (!in_array($email, $this->addressesEmails, true)) { $this->addressesEmails[] = $email; $this->addressesEmailsAdded[] = $email; } @@ -167,7 +167,7 @@ class Notification implements TrackUpdateInterface public function removeAddressesEmail(string $email) { - if (in_array($email, $this->addressesEmails)) { + if (in_array($email, $this->addressesEmails, true)) { $this->addressesEmails = array_filter($this->addressesEmails, fn ($e) => $e !== $email); $this->addressesEmailsAdded = array_filter($this->addressesEmailsAdded, fn ($e) => $e !== $email); } From 24d28b0a5252201fc7ab6ed4dccdf07ab0653816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 22:11:01 +0200 Subject: [PATCH 042/119] notification: alter form type to add and remove email addresses --- .../ChillMainBundle/Entity/Notification.php | 17 +++++++++++++- .../ChillMainBundle/Form/NotificationType.php | 23 +++++++++++++++++++ .../views/Notification/create.html.twig | 4 +++- .../views/Notification/edit.html.twig | 2 ++ .../translations/messages.fr.yml | 5 ++++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index 62994497e..9dd3694b1 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -18,6 +18,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @ORM\Entity @@ -36,7 +37,6 @@ class Notification implements TrackUpdateInterface /** * @ORM\ManyToMany(targetEntity=User::class) * @ORM\JoinTable(name="chill_main_notification_addresses_user") - * @Assert\Count(min="1", minMessage="notification.At least one addressee") */ private Collection $addressees; @@ -400,4 +400,19 @@ class Notification implements TrackUpdateInterface return $this; } + + /** + * @Assert\Callback() + * @param ExecutionContextInterface $context + * @param array $payload + * @return void + */ + public function assertCountAddresses(ExecutionContextInterface $context, $payload): void + { + if (0 === (count($this->getAddressesEmails()) + count($this->getAddressees()))) { + $context->buildViolation('notification.At least one addressee') + ->atPath('addressees') + ->addViolation(); + } + } } diff --git a/src/Bundle/ChillMainBundle/Form/NotificationType.php b/src/Bundle/ChillMainBundle/Form/NotificationType.php index b24513524..d0c8bc2cd 100644 --- a/src/Bundle/ChillMainBundle/Form/NotificationType.php +++ b/src/Bundle/ChillMainBundle/Form/NotificationType.php @@ -12,12 +12,17 @@ declare(strict_types=1); namespace Chill\MainBundle\Form; use Chill\MainBundle\Entity\Notification; +use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\PickUserDynamicType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\Email; +use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\NotNull; class NotificationType extends AbstractType { @@ -30,9 +35,27 @@ class NotificationType extends AbstractType ]) ->add('addressees', PickUserDynamicType::class, [ 'multiple' => true, + 'required' => false, ]) ->add('message', ChillTextareaType::class, [ 'required' => false, + ]) + ->add('addressesEmails', ChillCollectionType::class, [ + 'label' => 'notification.dest by email', + 'help' => 'notification.dest by email help', + 'by_reference' => false, + 'allow_add' => true, + 'allow_delete' => true, + 'entry_type' => EmailType::class, + 'button_add_label' => 'notification.Add an email', + 'button_remove_label' => 'notification.Remove an email', + 'empty_collection_explain' => 'notification.Any email', + 'entry_options' => [ + 'constraints' => [ + new NotNull(), new NotBlank(), new Email(['checkMX' => true]), + ], + 'label' => 'Email', + ], ]); } diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/create.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/create.html.twig index f15380083..4dfd340b6 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Notification/create.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/create.html.twig @@ -20,7 +20,9 @@ {{ form_row(form.title, { 'label': 'notification.subject'|trans }) }} {{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }} - + + {{ form_row(form.addressesEmails) }} + {% include handler.template(notification) with handler.templateData(notification) %}
        diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/edit.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/edit.html.twig index b51cc4dab..fd3b3b6bf 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Notification/edit.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/edit.html.twig @@ -21,6 +21,8 @@ {{ form_row(form.title, { 'label': 'notification.subject'|trans }) }} {{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }} + {{ form_row(form.addressesEmails) }} + {% include handler.template(notification) with handler.templateData(notification) %}
        diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 685a0c373..6e9f0374a 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -450,4 +450,9 @@ notification: subject: Objet see_comments_thread: Voir le fil de commentaires associé object_prefix: "[CHILL] notification - " + dest by email: Lien d'accès par email + Any email: Aucun email + Add an email: Ajouter un email + dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Un compte utilisateur sera toujours nécessaire. + Remove an email: Supprimer l'adresse email From a41d6cf7440870c63b4dd626e3181c5a683ef8d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 22:50:32 +0200 Subject: [PATCH 043/119] notification: send an email to addressesEmails --- .../Controller/NotificationController.php | 11 ++++++ .../ChillMainBundle/Entity/Notification.php | 8 ++++ .../Notification/Email/NotificationMailer.php | 38 +++++++++++++++++++ ...m_notification_content_to_email.fr.md.twig | 20 ++++++++++ .../config/services/notification.yaml | 9 +++++ 5 files changed, 86 insertions(+) create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content_to_email.fr.md.twig diff --git a/src/Bundle/ChillMainBundle/Controller/NotificationController.php b/src/Bundle/ChillMainBundle/Controller/NotificationController.php index af9e1b2b1..0e02c0a82 100644 --- a/src/Bundle/ChillMainBundle/Controller/NotificationController.php +++ b/src/Bundle/ChillMainBundle/Controller/NotificationController.php @@ -65,6 +65,17 @@ class NotificationController extends AbstractController $this->translator = $translator; } + /** + * @Route("/{id}/access_key", name="chill_main_notification_grant_access_by_access_key") + */ + public function getAccessByAccessKey(Notification $notification, Request $request): Response + { + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); + + return new Response('Invalid access key'); + + } + /** * @Route("/create", name="chill_main_notification_create") */ diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index 9dd3694b1..d3d4dd0eb 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -183,6 +183,14 @@ class Notification implements TrackUpdateInterface return $this; } + /** + * @return array + */ + public function getAddedAddresses(): array + { + return $this->addedAddresses; + } + public function addComment(NotificationComment $comment): self { if (!$this->comments->contains($comment)) { diff --git a/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php b/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php index ce5587ed9..69fcf66f0 100644 --- a/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php +++ b/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php @@ -73,6 +73,17 @@ class NotificationMailer * Send a email after a notification is persisted. */ public function postPersistNotification(Notification $notification, LifecycleEventArgs $eventArgs): void + { + $this->sendNotificationEmailsToAddresses($notification); + $this->sendNotificationEmailsToAddressesEmails($notification); + } + + public function postUpdateNotification(Notification $notification, LifecycleEventArgs $eventArgs): void + { + $this->sendNotificationEmailsToAddressesEmails($notification); + } + + private function sendNotificationEmailsToAddresses(Notification $notification): void { foreach ($notification->getAddressees() as $addressee) { if (null === $addressee->getEmail()) { @@ -108,4 +119,31 @@ class NotificationMailer } } } + + private function sendNotificationEmailsToAddressesEmails(Notification $notification): void + { + foreach ($notification->getAddressesEmailsAdded() as $emailAddress) { + $email = new TemplatedEmail(); + $email + ->textTemplate('@ChillMain/Notification/email_non_system_notification_content_to_email.fr.md.twig') + ->context([ + 'notification' => $notification, + 'dest' => $emailAddress, + ]); + + $email + ->subject($notification->getTitle()) + ->to($emailAddress); + + try { + $this->mailer->send($email); + } catch (TransportExceptionInterface $e) { + $this->logger->warning('[NotificationMailer] could not send an email notification', [ + 'to' => $emailAddress, + 'error_message' => $e->getMessage(), + 'error_trace' => $e->getTraceAsString(), + ]); + } + } + } } diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content_to_email.fr.md.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content_to_email.fr.md.twig new file mode 100644 index 000000000..9a32f0c15 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content_to_email.fr.md.twig @@ -0,0 +1,20 @@ +{{ dest }}, + +{{ notification.sender.label }} a créé une notification pour vous: + +> {{ notification.title }} +> +> +{%- for line in notification.message|split("\n") %} +> {{ line }} +{%- if not loop.last %} +> +{%- endif %} +{%- endfor %} + +Vous pouvez cliquer sur ce lien pour obtenir un accès permanent à la notification: + +{{ absolute_url(path('chill_main_notification_grant_access_by_access_key', {'_locale': 'fr', 'id': notification.id, 'accessKey': notification.accessKey, 'email': dest})) }} + +-- +Le logiciel Chill diff --git a/src/Bundle/ChillMainBundle/config/services/notification.yaml b/src/Bundle/ChillMainBundle/config/services/notification.yaml index b972dced7..544f5544c 100644 --- a/src/Bundle/ChillMainBundle/config/services/notification.yaml +++ b/src/Bundle/ChillMainBundle/config/services/notification.yaml @@ -61,6 +61,15 @@ services: # set the 'lazy' option to TRUE to only instantiate listeners when they are used lazy: true method: 'postPersistNotification' + + - + name: 'doctrine.orm.entity_listener' + event: 'postUpdate' + entity: 'Chill\MainBundle\Entity\Notification' + # set the 'lazy' option to TRUE to only instantiate listeners when they are used + lazy: true + method: 'postUpdateNotification' + - name: 'doctrine.orm.entity_listener' event: 'postPersist' From e7f0cd50c9f712a748ebef7b0567cb421dd42368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 23:05:38 +0200 Subject: [PATCH 044/119] controller to grant access to notification by access key --- .../Controller/NotificationController.php | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Controller/NotificationController.php b/src/Bundle/ChillMainBundle/Controller/NotificationController.php index 0e02c0a82..5c1508321 100644 --- a/src/Bundle/ChillMainBundle/Controller/NotificationController.php +++ b/src/Bundle/ChillMainBundle/Controller/NotificationController.php @@ -22,6 +22,7 @@ use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Repository\NotificationRepository; use Chill\MainBundle\Security\Authorization\NotificationVoter; use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -39,6 +40,10 @@ class NotificationController extends AbstractController { private EntityManagerInterface $em; + private LoggerInterface $chillLogger; + + private LoggerInterface $logger; + private NotificationHandlerManager $notificationHandlerManager; private NotificationRepository $notificationRepository; @@ -51,6 +56,8 @@ class NotificationController extends AbstractController public function __construct( EntityManagerInterface $em, + LoggerInterface $chillLogger, + LoggerInterface $logger, Security $security, NotificationRepository $notificationRepository, NotificationHandlerManager $notificationHandlerManager, @@ -58,6 +65,8 @@ class NotificationController extends AbstractController TranslatorInterface $translator ) { $this->em = $em; + $this->logger = $logger; + $this->chillLogger = $chillLogger; $this->security = $security; $this->notificationRepository = $notificationRepository; $this->notificationHandlerManager = $notificationHandlerManager; @@ -72,8 +81,40 @@ class NotificationController extends AbstractController { $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); - return new Response('Invalid access key'); + if (!$this->security->getUser() instanceof User) { + throw new AccessDeniedHttpException('You must be authenticated and a user to create a notification'); + } + foreach (['accessKey', 'email'] as $param) { + if (!$request->query->has($param)) { + throw new BadRequestHttpException("Missing $param parameter"); + } + } + + if ($notification->getAccessKey() !== $request->query->getAlnum('accessKey')) { + throw new AccessDeniedHttpException('access key is invalid'); + } + + if (!in_array($request->query->get('email'), $notification->getAddressesEmails())) { + return (new Response('The email address is no more associated with this notification')) + ->setStatusCode(Response::HTTP_FORBIDDEN); + } + + $notification->addAddressee($this->security->getUser()); + + $this->getDoctrine()->getManager()->flush(); + + $logMsg = '[Notification] a user is granted access to notification trough an access key'; + $context = [ + 'notificationId' => $notification->getId(), + 'email' => $request->query->get('email'), + 'user' => $this->security->getUser()->getId(), + ]; + + $this->logger->info($logMsg, $context); + $this->chillLogger->info($logMsg, $context); + + return $this->redirectToRoute('chill_main_notification_show', ['id' => $notification->getId()]); } /** From 2a53fb9341cea1a873aa3c2ee9b016c0f208c6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 23:16:02 +0200 Subject: [PATCH 045/119] show email adresses on notification list --- .../Resources/views/Notification/_list_item.html.twig | 5 +++++ src/Bundle/ChillMainBundle/translations/messages.fr.yml | 1 + 2 files changed, 6 insertions(+) diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig index 02a02b4d0..1024d56b3 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig @@ -40,6 +40,11 @@ {{ a|chill_entity_render_string }} {% endfor %} + {% for a in c.notification.addressesEmails %} + + {{ a }} + + {% endfor %}
      • {% endif %}
      diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 6e9f0374a..452ad306c 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -455,4 +455,5 @@ notification: Add an email: Ajouter un email dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Un compte utilisateur sera toujours nécessaire. Remove an email: Supprimer l'adresse email + Email with access link: Adresse email ayant reçu un lien d'accès From 35c7d55b8c9d45640254296af34ff6c817724fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 23:17:16 +0200 Subject: [PATCH 046/119] fix cs --- .../Controller/NotificationController.php | 91 ++++++----- .../ChillMainBundle/Entity/Notification.php | 154 +++++++++--------- .../ChillMainBundle/Form/NotificationType.php | 2 +- .../Tests/Entity/NotificationTest.php | 36 ++-- .../migrations/Version20220413154743.php | 19 ++- 5 files changed, 153 insertions(+), 149 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Controller/NotificationController.php b/src/Bundle/ChillMainBundle/Controller/NotificationController.php index 5c1508321..f40c85ffb 100644 --- a/src/Bundle/ChillMainBundle/Controller/NotificationController.php +++ b/src/Bundle/ChillMainBundle/Controller/NotificationController.php @@ -32,16 +32,17 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Security; use Symfony\Contracts\Translation\TranslatorInterface; +use function in_array; /** * @Route("/{_locale}/notification") */ class NotificationController extends AbstractController { - private EntityManagerInterface $em; - private LoggerInterface $chillLogger; + private EntityManagerInterface $em; + private LoggerInterface $logger; private NotificationHandlerManager $notificationHandlerManager; @@ -74,49 +75,6 @@ class NotificationController extends AbstractController $this->translator = $translator; } - /** - * @Route("/{id}/access_key", name="chill_main_notification_grant_access_by_access_key") - */ - public function getAccessByAccessKey(Notification $notification, Request $request): Response - { - $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); - - if (!$this->security->getUser() instanceof User) { - throw new AccessDeniedHttpException('You must be authenticated and a user to create a notification'); - } - - foreach (['accessKey', 'email'] as $param) { - if (!$request->query->has($param)) { - throw new BadRequestHttpException("Missing $param parameter"); - } - } - - if ($notification->getAccessKey() !== $request->query->getAlnum('accessKey')) { - throw new AccessDeniedHttpException('access key is invalid'); - } - - if (!in_array($request->query->get('email'), $notification->getAddressesEmails())) { - return (new Response('The email address is no more associated with this notification')) - ->setStatusCode(Response::HTTP_FORBIDDEN); - } - - $notification->addAddressee($this->security->getUser()); - - $this->getDoctrine()->getManager()->flush(); - - $logMsg = '[Notification] a user is granted access to notification trough an access key'; - $context = [ - 'notificationId' => $notification->getId(), - 'email' => $request->query->get('email'), - 'user' => $this->security->getUser()->getId(), - ]; - - $this->logger->info($logMsg, $context); - $this->chillLogger->info($logMsg, $context); - - return $this->redirectToRoute('chill_main_notification_show', ['id' => $notification->getId()]); - } - /** * @Route("/create", name="chill_main_notification_create") */ @@ -202,6 +160,49 @@ class NotificationController extends AbstractController ]); } + /** + * @Route("/{id}/access_key", name="chill_main_notification_grant_access_by_access_key") + */ + public function getAccessByAccessKey(Notification $notification, Request $request): Response + { + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); + + if (!$this->security->getUser() instanceof User) { + throw new AccessDeniedHttpException('You must be authenticated and a user to create a notification'); + } + + foreach (['accessKey', 'email'] as $param) { + if (!$request->query->has($param)) { + throw new BadRequestHttpException("Missing {$param} parameter"); + } + } + + if ($notification->getAccessKey() !== $request->query->getAlnum('accessKey')) { + throw new AccessDeniedHttpException('access key is invalid'); + } + + if (!in_array($request->query->get('email'), $notification->getAddressesEmails(), true)) { + return (new Response('The email address is no more associated with this notification')) + ->setStatusCode(Response::HTTP_FORBIDDEN); + } + + $notification->addAddressee($this->security->getUser()); + + $this->getDoctrine()->getManager()->flush(); + + $logMsg = '[Notification] a user is granted access to notification trough an access key'; + $context = [ + 'notificationId' => $notification->getId(), + 'email' => $request->query->get('email'), + 'user' => $this->security->getUser()->getId(), + ]; + + $this->logger->info($logMsg, $context); + $this->chillLogger->info($logMsg, $context); + + return $this->redirectToRoute('chill_main_notification_show', ['id' => $notification->getId()]); + } + /** * @Route("/inbox", name="chill_main_notification_my") */ diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index d3d4dd0eb..8dad39d12 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -19,6 +19,8 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; +use function count; +use function in_array; /** * @ORM\Entity @@ -32,6 +34,11 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; */ class Notification implements TrackUpdateInterface { + /** + * @ORM\Column(type="text", nullable=false) + */ + private string $accessKey; + private array $addedAddresses = []; /** @@ -40,6 +47,21 @@ class Notification implements TrackUpdateInterface */ private Collection $addressees; + /** + * a list of destinee which will receive notifications. + * + * @var array|string[] + * @ORM\Column(type="json") + */ + private array $addressesEmails = []; + + /** + * a list of emails adresses which were added to the notification. + * + * @var array|string[] + */ + private array $addressesEmailsAdded = []; + private ?ArrayCollection $addressesOnLoad = null; /** @@ -105,25 +127,6 @@ class Notification implements TrackUpdateInterface */ private ?User $updatedBy; - /** - * a list of destinee which will receive notifications - * @var array|string[] - * @ORM\Column(type="json") - */ - private array $addressesEmails = []; - - /** - * a list of emails adresses which were added to the notification - * - * @var array|string[] - */ - private array $addressesEmailsAdded = []; - - /** - * @ORM\Column(type="text", nullable=false) - */ - private string $accessKey; - public function __construct() { $this->addressees = new ArrayCollection(); @@ -133,46 +136,6 @@ class Notification implements TrackUpdateInterface $this->accessKey = bin2hex(openssl_random_pseudo_bytes(24)); } - /** - * @return array|string[] - */ - public function getAddressesEmails(): array - { - return $this->addressesEmails; - } - - /** - * @return array|string[] - */ - public function getAddressesEmailsAdded(): array - { - return $this->addressesEmailsAdded; - } - - /** - * @return string - */ - public function getAccessKey(): string - { - return $this->accessKey; - } - - public function addAddressesEmail(string $email) - { - if (!in_array($email, $this->addressesEmails, true)) { - $this->addressesEmails[] = $email; - $this->addressesEmailsAdded[] = $email; - } - } - - public function removeAddressesEmail(string $email) - { - if (in_array($email, $this->addressesEmails, true)) { - $this->addressesEmails = array_filter($this->addressesEmails, fn ($e) => $e !== $email); - $this->addressesEmailsAdded = array_filter($this->addressesEmailsAdded, fn ($e) => $e !== $email); - } - } - public function addAddressee(User $addressee): self { if (!$this->addressees->contains($addressee)) { @@ -183,12 +146,12 @@ class Notification implements TrackUpdateInterface return $this; } - /** - * @return array - */ - public function getAddedAddresses(): array + public function addAddressesEmail(string $email) { - return $this->addedAddresses; + if (!in_array($email, $this->addressesEmails, true)) { + $this->addressesEmails[] = $email; + $this->addressesEmailsAdded[] = $email; + } } public function addComment(NotificationComment $comment): self @@ -210,6 +173,30 @@ class Notification implements TrackUpdateInterface return $this; } + /** + * @Assert\Callback + * + * @param array $payload + */ + public function assertCountAddresses(ExecutionContextInterface $context, $payload): void + { + if (0 === (count($this->getAddressesEmails()) + count($this->getAddressees()))) { + $context->buildViolation('notification.At least one addressee') + ->atPath('addressees') + ->addViolation(); + } + } + + public function getAccessKey(): string + { + return $this->accessKey; + } + + public function getAddedAddresses(): array + { + return $this->addedAddresses; + } + /** * @return Collection|User[] */ @@ -223,6 +210,22 @@ class Notification implements TrackUpdateInterface return $this->addressees; } + /** + * @return array|string[] + */ + public function getAddressesEmails(): array + { + return $this->addressesEmails; + } + + /** + * @return array|string[] + */ + public function getAddressesEmailsAdded(): array + { + return $this->addressesEmailsAdded; + } + public function getComments(): Collection { return $this->comments; @@ -339,6 +342,14 @@ class Notification implements TrackUpdateInterface return $this; } + public function removeAddressesEmail(string $email) + { + if (in_array($email, $this->addressesEmails, true)) { + $this->addressesEmails = array_filter($this->addressesEmails, static fn ($e) => $e !== $email); + $this->addressesEmailsAdded = array_filter($this->addressesEmailsAdded, static fn ($e) => $e !== $email); + } + } + public function removeComment(NotificationComment $comment): self { $this->comments->removeElement($comment); @@ -408,19 +419,4 @@ class Notification implements TrackUpdateInterface return $this; } - - /** - * @Assert\Callback() - * @param ExecutionContextInterface $context - * @param array $payload - * @return void - */ - public function assertCountAddresses(ExecutionContextInterface $context, $payload): void - { - if (0 === (count($this->getAddressesEmails()) + count($this->getAddressees()))) { - $context->buildViolation('notification.At least one addressee') - ->atPath('addressees') - ->addViolation(); - } - } } diff --git a/src/Bundle/ChillMainBundle/Form/NotificationType.php b/src/Bundle/ChillMainBundle/Form/NotificationType.php index d0c8bc2cd..22fd19baf 100644 --- a/src/Bundle/ChillMainBundle/Form/NotificationType.php +++ b/src/Bundle/ChillMainBundle/Form/NotificationType.php @@ -40,7 +40,7 @@ class NotificationType extends AbstractType ->add('message', ChillTextareaType::class, [ 'required' => false, ]) - ->add('addressesEmails', ChillCollectionType::class, [ + ->add('addressesEmails', ChillCollectionType::class, [ 'label' => 'notification.dest by email', 'help' => 'notification.dest by email help', 'by_reference' => false, diff --git a/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php b/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php index 9fc0d6cb2..0575fa52a 100644 --- a/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php @@ -88,6 +88,24 @@ final class NotificationTest extends KernelTestCase $this->assertNotContains($user1, $notification->getUnreadBy()->toArray()); } + public function testAddressesEmail(): void + { + $notification = new Notification(); + + $notification->addAddressesEmail('test'); + $notification->addAddressesEmail('other'); + + $this->assertContains('test', $notification->getAddressesEmails()); + $this->assertContains('other', $notification->getAddressesEmails()); + $this->assertContains('test', $notification->getAddressesEmailsAdded()); + $this->assertContains('other', $notification->getAddressesEmailsAdded()); + + $notification->removeAddressesEmail('other'); + + $this->assertNotContains('other', $notification->getAddressesEmails()); + $this->assertNotContains('other', $notification->getAddressesEmailsAdded()); + } + /** * @dataProvider generateNotificationData */ @@ -122,22 +140,4 @@ final class NotificationTest extends KernelTestCase $this->assertContains($addresseeId, $unreadIds); } } - - public function testAddressesEmail(): void - { - $notification = new Notification(); - - $notification->addAddressesEmail('test'); - $notification->addAddressesEmail('other'); - - $this->assertContains('test', $notification->getAddressesEmails()); - $this->assertContains('other', $notification->getAddressesEmails()); - $this->assertContains('test', $notification->getAddressesEmailsAdded()); - $this->assertContains('other', $notification->getAddressesEmailsAdded()); - - $notification->removeAddressesEmail('other'); - - $this->assertNotContains('other', $notification->getAddressesEmails()); - $this->assertNotContains('other', $notification->getAddressesEmailsAdded()); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php b/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php index 87da6a7f8..174419cda 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE chill_main_notification DROP adressesEmails'); + $this->addSql('ALTER TABLE chill_main_notification DROP accessKey'); + } + public function getDescription(): string { return 'add access keys and emails dest to notifications'; @@ -30,10 +43,4 @@ final class Version20220413154743 extends AbstractMigration $this->addSql('ALTER TABLE chill_main_notification ALTER accessKey DROP DEFAULT'); $this->addSql('ALTER TABLE chill_main_notification ALTER accessKey SET NOT NULL'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE chill_main_notification DROP adressesEmails'); - $this->addSql('ALTER TABLE chill_main_notification DROP accessKey'); - } } From 8770188d5487ec368959512d7f233ee3ddceacff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 23:18:50 +0200 Subject: [PATCH 047/119] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a61c63a73..451a672ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to * [docgen] add more persons choices in docgen for course: amongst requestor (if person), resources of course (if person), and PersonResource (if person); * [docgen] add a new context with a list of activities in course * [docgen] add a comment in budget lines +* [notifications] allow to send a notification to an email address. The address receive an access link ## Test releases From 86ec020f804a79510dac8247856a8d6a9812cc0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 14 Apr 2022 00:02:52 +0200 Subject: [PATCH 048/119] fixes on address valid from edit for household --- .../Form/Type/AddressDateType.php | 29 ++++++++++++ .../Controller/HouseholdController.php | 47 +++++++++---------- .../Entity/Household/Household.php | 3 ++ .../address_valid_from_edit.html.twig | 21 +-------- .../views/Household/addresses.html.twig | 21 +++++---- .../translations/messages.fr.yml | 4 +- 6 files changed, 67 insertions(+), 58 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Form/Type/AddressDateType.php diff --git a/src/Bundle/ChillMainBundle/Form/Type/AddressDateType.php b/src/Bundle/ChillMainBundle/Form/Type/AddressDateType.php new file mode 100644 index 000000000..15cb2c778 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/AddressDateType.php @@ -0,0 +1,29 @@ +add( + 'validFrom', + ChillDateType::class, + [ + 'required' => true, + ] + ); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefault('data_class', Address::class); + } + +} diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php index 1f5f21c40..8cee2982f 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php @@ -12,14 +12,17 @@ declare(strict_types=1); namespace Chill\PersonBundle\Controller; use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Form\Type\AddressDateType; use Chill\MainBundle\Form\Type\AddressType; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Form\HouseholdType; use Chill\PersonBundle\Repository\Household\PositionRepository; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; +use Chill\PersonBundle\Security\Authorization\HouseholdVoter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Security; @@ -197,41 +200,35 @@ class HouseholdController extends AbstractController */ public function addressValidFromEdit(Request $request, Household $household) { - // TODO ACL + $this->denyAccessUnlessGranted(HouseholdVoter::EDIT, $household); - $address_id = $request->query->get('address_id'); - $address = $this->getDoctrine()->getManager() - ->getRepository(Address::class) - ->find($address_id); + if (!$request->query->has('address_id')) { + throw new BadRequestException('parameter address_id is missing'); + } - $form = $this->createForm(AddressType::class, $address, []); + $address_id = $request->query->getInt('address_id'); - $form->handleRequest($request); - $addresses = $household->getAddressesOrdered(); - - foreach ($addresses as $a) { - if ($a->getId() === $address->getId()) { - $currentValueIndex = array_search($a, $addresses, true); + // loop over adresses of the household, to be sure that the household is associated + // to the edited address + $address = null; + foreach ($household->getAddresses() as $householdAddress) { + if ($address_id === $householdAddress->getId()) { + $address = $householdAddress; } } - if (count($addresses) > 1 && 0 < $currentValueIndex) { - $previousAddress = $addresses[$currentValueIndex - 1]; - $minValidFrom = $previousAddress->getValidTo(); - } else { - $minValidFrom = null; + if (null === $address) { + throw new BadRequestException('The edited address does not belongs to the household'); } - if (count($addresses) > 1 && count($addresses) > $currentValueIndex + 1) { - $nextAddress = $addresses[$currentValueIndex + 1]; - $maxValidFrom = $nextAddress->getValidFrom(); - } else { - $maxValidFrom = null; - } + $form = $this->createForm(AddressDateType::class, $address, []); + + $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $this->getDoctrine()->getManager()->persist($address); + $household->makeAddressConsistent(); + $this->getDoctrine()->getManager()->flush(); return $this->redirectToRoute('chill_person_household_addresses', [ @@ -245,8 +242,6 @@ class HouseholdController extends AbstractController 'household' => $household, 'address' => $address, 'form' => $form->createView(), - 'minValidFrom' => $minValidFrom, - 'maxValidFrom' => $maxValidFrom, ] ); } diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php index fed9f0af0..19bda3785 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php @@ -151,6 +151,9 @@ class Household return $this->addresses; } + /** + * @return array|Address[] + */ public function getAddressesOrdered(): array { $addresses = $this->getAddresses()->toArray(); diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig index a2b8753f5..b03538fa9 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/address_valid_from_edit.html.twig @@ -13,26 +13,7 @@
      - {% if minValidFrom is not null and maxValidFrom is not null %} - {{ form_row(form.validFrom, {'attr': {'min': minValidFrom|date('Y-m-d'), 'max': maxValidFrom|date('Y-m-d') }}) }} - {% else %} - {% if minValidFrom is not null %} - {{ form_row(form.validFrom, {'attr': {'min': minValidFrom|date('Y-m-d')}}) }} - {% elseif maxValidFrom is not null %} - {{ form_row(form.validFrom, {'attr': {'max': maxValidFrom|date('Y-m-d')}}) }} - {% else %} - {{ form_row(form.validFrom) }} - {% endif %} - {% endif %} -
      -
        diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/addresses.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/addresses.html.twig index d61e0b63e..6242e6aae 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Household/addresses.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/addresses.html.twig @@ -62,16 +62,17 @@ 'extended_infos': true, 'has_no_address': true }) }} - + {% if is_granted('CHILL_PERSON_HOUSEHOLD_EDIT', household) %} + + {% endif %}
      diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 08e21675c..c6baf9513 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -498,8 +498,8 @@ Concerns household n°%id%: Concerne le ménage n°%id% Composition: Composition Budget: Budget The composition has been successfully removed.: La composition a été supprimée. -edit address valid from: Changer la date du déménagement -Edit household address valid from: Changer la date du déménagement +edit address valid from: Modifier la date du déménagement +Edit household address valid from: Modifier la date du déménagement # accompanying course work From e6d59bc8374c986b58402892f016b3478870d587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 14 Apr 2022 00:11:15 +0200 Subject: [PATCH 049/119] fix cs --- .../ChillMainBundle/Form/Type/AddressDateType.php | 10 +++++++++- .../Controller/HouseholdController.php | 5 ++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Form/Type/AddressDateType.php b/src/Bundle/ChillMainBundle/Form/Type/AddressDateType.php index 15cb2c778..299728d6e 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/AddressDateType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/AddressDateType.php @@ -1,5 +1,14 @@ setDefault('data_class', Address::class); } - } diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php index 8cee2982f..7b6dc201e 100644 --- a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php +++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php @@ -13,7 +13,6 @@ namespace Chill\PersonBundle\Controller; use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Form\Type\AddressDateType; -use Chill\MainBundle\Form\Type\AddressType; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Form\HouseholdType; use Chill\PersonBundle\Repository\Household\PositionRepository; @@ -208,12 +207,12 @@ class HouseholdController extends AbstractController $address_id = $request->query->getInt('address_id'); - // loop over adresses of the household, to be sure that the household is associated // to the edited address $address = null; + foreach ($household->getAddresses() as $householdAddress) { - if ($address_id === $householdAddress->getId()) { + if ($householdAddress->getId() === $address_id) { $address = $householdAddress; } } From b95ff337b6f011d31cd291bda75f558c462cca4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 14 Apr 2022 00:41:03 +0200 Subject: [PATCH 050/119] add more time duration for aside activities --- .../ChillActivityBundle/translations/messages.fr.yml | 8 ++++++++ .../src/DependencyInjection/Configuration.php | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml index 49217dd5d..9d7fdd288 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -66,6 +66,14 @@ Choose a type: Choisir un type 1 hour 30: 1 heure 30 1 hour 45: 1 heure 45 2 hours: 2 heures +2 hours 15: 2 heures 15 +2 hours 30: 2 heures 30 +2 hours 45: 2 heures 45 +3 hours: 3 heures +3 hours 30: 3 heures 30 +4 hours: 4 heures +4 hours 30: 4 heures 30 +5 hours: 5 heures Concerned groups: Parties concernées Persons in accompanying course: Usagers du parcours Third persons: Tiers non-pro. diff --git a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php index a077ce902..76bcebca5 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php @@ -31,6 +31,10 @@ class Configuration implements ConfigurationInterface ->isRequired() ->defaultValue( [ + ['label' => '1 minutes', 'seconds' => 60], + ['label' => '2 minutes', 'seconds' => 120], + ['label' => '3 minutes', 'seconds' => 180], + ['label' => '4 minutes', 'seconds' => 240], ['label' => '5 minutes', 'seconds' => 300], ['label' => '10 minutes', 'seconds' => 600], ['label' => '15 minutes', 'seconds' => 900], From a52f29bb78330ef984dd7df7868a502aeee72bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 14 Apr 2022 01:11:14 +0200 Subject: [PATCH 051/119] accompanyingWorkEditor: add label on form document title --- .../components/FormEvaluation.vue | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue index 5f4fdedec..53e2b72f3 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue @@ -67,14 +67,17 @@
      -
      - +
      + +
      + +
      From afa38e9dd5854edc5260cb0f4e0b9f682b0b4b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 14 Apr 2022 01:11:39 +0200 Subject: [PATCH 052/119] addresses: add constraint in database to avoid error later --- src/Bundle/ChillMainBundle/Entity/Address.php | 1 + .../migrations/Version20220413225830.php | 27 +++++++++++++++++++ .../migrations/Version20220413230159.php | 26 ++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20220413225830.php create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20220413230159.php diff --git a/src/Bundle/ChillMainBundle/Entity/Address.php b/src/Bundle/ChillMainBundle/Entity/Address.php index 10679d2e0..d3eee0b2a 100644 --- a/src/Bundle/ChillMainBundle/Entity/Address.php +++ b/src/Bundle/ChillMainBundle/Entity/Address.php @@ -140,6 +140,7 @@ class Address /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode") + * @ORM\JoinColumn(nullable=false) * @Groups({"write"}) */ private ?PostalCode $postcode = null; diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220413225830.php b/src/Bundle/ChillMainBundle/migrations/Version20220413225830.php new file mode 100644 index 000000000..43ef753bb --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20220413225830.php @@ -0,0 +1,27 @@ +addSql('ALTER TABLE chill_main_address ALTER COLUMN postcode_id SET NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_address ALTER COLUMN postcode_id DROP NOT NULL'); + + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220413230159.php b/src/Bundle/ChillMainBundle/migrations/Version20220413230159.php new file mode 100644 index 000000000..7a2ce476b --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20220413230159.php @@ -0,0 +1,26 @@ +addSql('ALTER TABLE chill_main_address ADD CONSTRAINT chill_custom_validfrom_before_validto CHECK (validfrom <= COALESCE(validto, \'infinity\'::date))'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_address DROP CONSTRAINT chill_custom_validfrom_before_validto'); + } +} From 124a6a58fcf570128ae44119e091dc195a115e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 14 Apr 2022 01:13:16 +0200 Subject: [PATCH 053/119] fix cs --- CHANGELOG.md | 2 ++ .../migrations/Version20220413225830.php | 18 ++++++++++++------ .../migrations/Version20220413230159.php | 17 ++++++++++++----- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d08ce42a..b6ae8c947 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to * [docgen] add a new context with a list of activities in course * [docgen] add a comment in budget lines * [notifications] allow to send a notification to an email address. The address receive an access link +* [adresses] add constraints in database to avoid errors later: postcode not null, and validfrom <= validto +* [accompanying work editor] add a label on document title input ## Test releases diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220413225830.php b/src/Bundle/ChillMainBundle/migrations/Version20220413225830.php index 43ef753bb..4fdb48de3 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20220413225830.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20220413225830.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE chill_main_address ALTER COLUMN postcode_id DROP NOT NULL'); + } + public function getDescription(): string { return 'Set postal code in chill_main_address NOT NULL'; @@ -18,10 +30,4 @@ final class Version20220413225830 extends AbstractMigration { $this->addSql('ALTER TABLE chill_main_address ALTER COLUMN postcode_id SET NOT NULL'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE chill_main_address ALTER COLUMN postcode_id DROP NOT NULL'); - - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220413230159.php b/src/Bundle/ChillMainBundle/migrations/Version20220413230159.php index 7a2ce476b..52e4a4fff 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20220413230159.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20220413230159.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE chill_main_address DROP CONSTRAINT chill_custom_validfrom_before_validto'); + } + public function getDescription(): string { return 'in addresses, validfrom must be lower than validto'; @@ -18,9 +30,4 @@ final class Version20220413230159 extends AbstractMigration { $this->addSql('ALTER TABLE chill_main_address ADD CONSTRAINT chill_custom_validfrom_before_validto CHECK (validfrom <= COALESCE(validto, \'infinity\'::date))'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE chill_main_address DROP CONSTRAINT chill_custom_validfrom_before_validto'); - } } From 617473f6a5c9b6e61984e72a443b769c2b1d3a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 14 Apr 2022 01:36:01 +0200 Subject: [PATCH 054/119] notification - grant access by key - temporarily disable email verification due to character escaping in email --- .../ChillMainBundle/Controller/NotificationController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Controller/NotificationController.php b/src/Bundle/ChillMainBundle/Controller/NotificationController.php index f40c85ffb..d717f01eb 100644 --- a/src/Bundle/ChillMainBundle/Controller/NotificationController.php +++ b/src/Bundle/ChillMainBundle/Controller/NotificationController.php @@ -171,7 +171,7 @@ class NotificationController extends AbstractController throw new AccessDeniedHttpException('You must be authenticated and a user to create a notification'); } - foreach (['accessKey', 'email'] as $param) { + foreach (['accessKey'/*, 'email'*/] as $param) { if (!$request->query->has($param)) { throw new BadRequestHttpException("Missing {$param} parameter"); } @@ -181,10 +181,13 @@ class NotificationController extends AbstractController throw new AccessDeniedHttpException('access key is invalid'); } + /* + desactivated due to escaped '&' in email links if (!in_array($request->query->get('email'), $notification->getAddressesEmails(), true)) { return (new Response('The email address is no more associated with this notification')) ->setStatusCode(Response::HTTP_FORBIDDEN); } + */ $notification->addAddressee($this->security->getUser()); From ebc4ec0d7c2b5eba3d571b1de57b1fd21c45d332 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 19 Apr 2022 15:09:25 +0200 Subject: [PATCH 055/119] updating openingdate parcours fixed --- .../components/StartDate.vue | 26 ++++++++++++++----- .../vuejs/AccompanyingCourse/js/i18n.js | 2 +- .../vuejs/AccompanyingCourse/store/index.js | 6 +---- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue index 349581452..b6a0344e8 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue @@ -5,9 +5,9 @@
      - -
      - +
      + +
      @@ -23,10 +23,12 @@ import { mapState, mapGetters } from 'vuex'; export default { name: 'startDate', methods: { - updateStartDate(event) { - const date = event.target.value; - // console.log(date) + updateStartDate(e) { + e.preventDefault(); + const date = e.target.previousSibling.value; + this.$store.dispatch('updateStartDate', date) + .then(this.$toast.open({type: 'success', message: this.$t('startdate.update')})) .catch(({name, violations}) => { if (name === 'ValidationException' || name === 'AccessException') { violations.forEach((violation) => this.$toast.open({message: violation})); @@ -43,4 +45,14 @@ export default { } } - \ No newline at end of file + + + \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js index f1e2de958..a3a8ede2d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js @@ -153,7 +153,7 @@ const appMessages = { }, startdate: { change: "Date d'ouverture", - date: "Date d'ouverture", + update: "La nouvelle date d'ouverture a été bien enregistrée" }, // catch errors 'Error while updating AccompanyingPeriod Course.': "Erreur du serveur lors de la mise à jour du parcours d'accompagnement.", diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js index 06a3606ab..5d73aa2ed 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js @@ -322,7 +322,6 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou } }, updateStartDate(state, date) { - console.log('new state date', date) state.accompanyingCourse.openingDate = date; } }, @@ -814,11 +813,8 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou }) }, updateStartDate({commit}, payload) { - console.log('payload', payload) - const date = ISOToDate(payload); const url = `/api/1.0/person/accompanying-course/${id}.json`; - const body = { type: "accompanying_period", openingDate: { datetime: datetimeToISO(date) }}; - console.log('body', body) + const body = { type: "accompanying_period", openingDate: { datetime: datetimeToISO(ISOToDate(payload)) }}; return makeFetch('PATCH', url, body) .then((response) => { commit('updateStartDate', response.openingDate); From c757fa068a33eb3b1ee58af35b514e74401ed67b Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 19 Apr 2022 15:21:23 +0200 Subject: [PATCH 056/119] Translations added for error message in parcours --- src/Bundle/ChillPersonBundle/translations/validators.fr.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml index 492f50396..13fcb54fb 100644 --- a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml @@ -57,4 +57,8 @@ Only the referrer can change the confidentiality of a parcours: 'Seul le référ # resource You must associate at least one entity: Associez un usager, un tiers ou indiquez une description libre -You cannot associate a resource with the same person: Vous ne pouvez pas ajouter la personne elle-même en tant que ressource. \ No newline at end of file +You cannot associate a resource with the same person: Vous ne pouvez pas ajouter la personne elle-même en tant que ressource. + +#location +The period must remain located: 'Un parcours doit être localisé' +The person where the course is located must be associated to the course. Change course's location before removing the person.: "Le parcours est localisé auprès cet usager. Veuillez changer la localisation du parcours avant de suprimer l'usager" From 7048f670ed664a1a9f811460536279d4d96f0b79 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 19 Apr 2022 15:45:47 +0200 Subject: [PATCH 057/119] Fix #552: Return `AccessDeniedException` when trying to access a document and user is not logged in. --- .../src/Controller/{Test.php => Editor.php} | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) rename src/Bundle/ChillWopiBundle/src/Controller/{Test.php => Editor.php} (91%) diff --git a/src/Bundle/ChillWopiBundle/src/Controller/Test.php b/src/Bundle/ChillWopiBundle/src/Controller/Editor.php similarity index 91% rename from src/Bundle/ChillWopiBundle/src/Controller/Test.php rename to src/Bundle/ChillWopiBundle/src/Controller/Editor.php index 1687bce13..cca6f86e8 100644 --- a/src/Bundle/ChillWopiBundle/src/Controller/Test.php +++ b/src/Bundle/ChillWopiBundle/src/Controller/Editor.php @@ -18,6 +18,7 @@ use Chill\DocStoreBundle\Entity\StoredObject; use Chill\WopiBundle\Service\Controller\ResponderInterface; use Exception; use loophp\psr17\Psr17Interface; +use Symfony\Component\Finder\Exception\AccessDeniedException; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -28,7 +29,7 @@ use Symfony\Component\Security\Core\Security; * @internal * @coversNothing */ -final class Test +final class Editor { private DocumentManagerInterface $documentManager; @@ -64,6 +65,10 @@ final class Test public function __invoke(string $fileId): Response { + if (null === $user = $this->security->getUser()->getUsername()) { + throw new AccessDeniedException('You must be logged in to access to this resource.'); + } + $configuration = $this->wopiConfiguration->jsonSerialize(); /** @var StoredObject $storedObject */ $storedObject = $this->documentManager->findByDocumentId($fileId); @@ -77,7 +82,7 @@ final class Test } $configuration['favIconUrl'] = ''; - $configuration['access_token'] = $this->security->getUser()->getUsername(); + $configuration['access_token'] = $user; $configuration['server'] = $this ->psr17 From 26d5bf03bd3f2c66e852b1827efd0c1b0feb968c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 19 Apr 2022 15:46:10 +0200 Subject: [PATCH 058/119] add order by on document list --- .../Controller/DocumentAccompanyingCourseController.php | 2 +- .../ChillDocStoreBundle/Controller/DocumentPersonController.php | 2 +- .../Repository/PersonDocumentACLAwareRepository.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php b/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php index ae8db17f6..41991b1bb 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php @@ -155,7 +155,7 @@ class DocumentAccompanyingCourseController extends AbstractController $documents = $this->courseRepository ->findBy( ['course' => $course], - ['date' => 'DESC'], + ['date' => 'DESC', 'id' => 'DESC'], $pagination->getItemsPerPage(), $pagination->getCurrentPageFirstItemNumber() ); diff --git a/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php b/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php index 42e49ad3c..062ac3e4a 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php @@ -178,7 +178,7 @@ class DocumentPersonController extends AbstractController $documents = $this->personDocumentACLAwareRepository->findByPerson( $person, - [], + ['date' => 'DESC', 'id' => 'DESC'], $pagination->getItemsPerPage(), $pagination->getCurrentPageFirstItemNumber() ); diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php index 0f85a74b2..540390aef 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php @@ -64,7 +64,7 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito $this->addACL($qb, $person); - foreach ($orderBy as [$field, $order]) { + foreach ($orderBy as $field => $order) { $qb->addOrderBy($field, $order); } From 6427fc64cd7a15414ff82b18078201bdcb5240b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 20 Apr 2022 09:58:46 +0200 Subject: [PATCH 059/119] invert 'incoming' and 'receiving' --- CHANGELOG.md | 9 ++++++--- src/Bundle/ChillActivityBundle/Form/ActivityType.php | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6ae8c947..50b700983 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ and this project adheres to ## Unreleased +* invert 'incoming' and 'receiving' in Activity form + + +## Test releases + +### 2021-04-13 * [person] household address: add a form for editing the validFrom date (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/541) * [person] householdmemberseditor: fix composition type bug in select form (vuejs) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/543) * [docgen] add more persons choices in docgen for course: amongst requestor (if person), resources of course (if person), and PersonResource (if person); @@ -20,9 +26,6 @@ and this project adheres to * [adresses] add constraints in database to avoid errors later: postcode not null, and validfrom <= validto * [accompanying work editor] add a label on document title input - -## Test releases - ### 2021-04-07 * notification list: move action buttons outside of the toggle diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityType.php b/src/Bundle/ChillActivityBundle/Form/ActivityType.php index 1e1daead5..6e75bde25 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityType.php @@ -373,8 +373,8 @@ class ActivityType extends AbstractType 'label' => $activityType->getLabel('sentReceived'), 'required' => $activityType->isRequired('sentReceived'), 'choices' => [ - 'Sent' => Activity::SENTRECEIVED_SENT, 'Received' => Activity::SENTRECEIVED_RECEIVED, + 'Sent' => Activity::SENTRECEIVED_SENT, ], ]); } From 7f97215fe9a0252934001abf48069a6053af8390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 20 Apr 2022 10:03:58 +0200 Subject: [PATCH 060/119] activity form: keep the same order for attendee field between edit and new form --- CHANGELOG.md | 3 ++- .../Resources/views/Activity/new.html.twig | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50b700983..3c85e41af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ and this project adheres to ## Unreleased -* invert 'incoming' and 'receiving' in Activity form +* [Activity form] invert 'incoming' and 'receiving' in Activity form +* [Activity form] keep the same order for 'attendee' field in new and edit form ## Test releases diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/new.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/new.html.twig index a7751cd5a..8e078702c 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/new.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/new.html.twig @@ -85,15 +85,15 @@ {{ form_row(form.comment) }} {% endif %} +{%- if form.attendee is defined -%} + {{ form_row(form.attendee) }} +{% endif %} + {%- if form.documents is defined -%} {{ form_row(form.documents) }}
      {% endif %} -{%- if form.attendee is defined -%} - {{ form_row(form.attendee) }} -{% endif %} - {# TODO .. status #}
        From 8512a5e1bcea1fe55f32431f312200d164ee627e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 20 Apr 2022 11:24:20 +0200 Subject: [PATCH 061/119] person list with period: use sameas twig operator instead of using '==' operator to introduce requestor --- CHANGELOG.md | 1 + .../Resources/views/Person/list_with_period.html.twig | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c85e41af..0f26e64a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to * [Activity form] invert 'incoming' and 'receiving' in Activity form * [Activity form] keep the same order for 'attendee' field in new and edit form +* [list with period] use "sameas" test operator to introduce requestor in list ## Test releases diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig index e705ab499..c43806a71 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig @@ -99,7 +99,7 @@ {% endif %}
      - {% if acp.requestoranonymous == false and acp.requestorPerson == person %} + {% if acp.requestoranonymous == false and acp.requestorPerson is same as(person) %} {{ 'Requestor'|trans({'gender': person.gender}) }} From 8882c99f5a56a44edc7f5024576c30fe3ae257f4 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 5 Apr 2022 15:56:02 +0200 Subject: [PATCH 062/119] fix datepicker for social action --- .../public/vuejs/AccompanyingCourseWorkCreate/App.vue | 9 ++++----- .../public/vuejs/AccompanyingCourseWorkCreate/store.js | 7 ++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue index 503a00c59..758cd7e5e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue @@ -227,19 +227,18 @@ export default { }, startDate: { get() { - let d = this.$store.state.startDate; - return dateToISO(d); + return this.$store.state.startDate; }, set(value) { - this.$store.commit('setStartDate', ISOToDate(value)); + this.$store.commit('setStartDate', value); } }, endDate: { get() { - return dateToISO(this.$store.state.endDate); + return this.$store.state.endDate; }, set(value) { - this.$store.commit('setEndDate', ISOToDate(value)); + this.$store.commit('setEndDate', value); } }, setSocialIssue: { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js index 408115257..7b6c2b22e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js @@ -1,6 +1,6 @@ import { createStore } from 'vuex'; -import { datetimeToISO } from 'ChillMainAssets/chill/js/date.js'; +import { datetimeToISO, dateToISO, ISOToDate, ISOToDatetime } from 'ChillMainAssets/chill/js/date.js'; import { findSocialActionsBySocialIssue } from 'ChillPersonAssets/vuejs/_api/SocialWorkSocialAction.js'; // import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js'; import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods'; @@ -47,7 +47,7 @@ const store = createStore({ id: state.socialActionPicked.id }, startDate: { - datetime: datetimeToISO(state.startDate) + datetime: datetimeToISO(ISOToDate(state.startDate)) }, persons: [] }; @@ -61,7 +61,7 @@ const store = createStore({ if (null !== state.endDate) { payload.endDate = { - datetime: datetimeToISO(state.endDate) + datetime: datetimeToISO(ISOToDate(state.endDate)) }; } @@ -111,6 +111,7 @@ const store = createStore({ state.startDate = date; }, setEndDate(state, date) { + console.log(date) state.endDate = date; }, setPersonsPickedIds(state, ids) { From 66ab38c60ff03db6e6f852be2e5f0cba04b97819 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 5 Apr 2022 16:03:07 +0200 Subject: [PATCH 063/119] prefill startdate with now --- .../public/vuejs/AccompanyingCourseWorkCreate/store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js index 7b6c2b22e..3c2e034d7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js @@ -20,7 +20,7 @@ const store = createStore({ .map(p => p.person), personsReachables: window.accompanyingCourse.participations.filter(p => p.endDate == null) .map(p => p.person), - startDate: new Date(), + startDate: dateToISO(new Date()), endDate: null, isLoadingSocialActions: false, isPostingWork: false, From de9d2aa88527de3390b70b53c44ad0968fda5ccf Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 6 Apr 2022 11:24:01 +0200 Subject: [PATCH 064/119] fix datepicker for householdmember editor --- .../components/Dates.vue | 19 ++++++++++--------- .../HouseholdMembersEditor/store/index.js | 6 +++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue index 040d316bb..03feb0329 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue @@ -66,18 +66,19 @@ export default { }, startDate: { get() { - return [ - this.$store.state.startDate.getFullYear(), - (this.$store.state.startDate.getMonth() + 1).toString().padStart(2, '0'), - this.$store.state.startDate.getDate().toString().padStart(2, '0') - ].join('-'); + return this.$store.state.startDate; + // return [ + // this.$store.state.startDate.getFullYear(), + // (this.$store.state.startDate.getMonth() + 1).toString().padStart(2, '0'), + // this.$store.state.startDate.getDate().toString().padStart(2, '0') + // ].join('-'); }, set(value) { - let - [year, month, day] = value.split('-'), - dValue = new Date(year, month-1, day); + // let + // [year, month, day] = value.split('-'), + // dValue = new Date(year, month-1, day); - this.$store.dispatch('setStartDate', dValue); + this.$store.dispatch('setStartDate', value); } } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js index f09b19787..87e465a8d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js @@ -2,7 +2,7 @@ import { createStore } from 'vuex'; import { householdMove, fetchHouseholdSuggestionByAccompanyingPeriod, fetchAddressSuggestionByPerson} from './../api.js'; import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods.js' import { fetchHouseholdByAddressReference } from 'ChillPersonAssets/lib/household.js'; -import { datetimeToISO } from 'ChillMainAssets/chill/js/date.js'; +import { datetimeToISO, dateToISO, ISOToDate } from 'ChillMainAssets/chill/js/date.js'; const debug = process.env.NODE_ENV !== 'production'; @@ -30,7 +30,7 @@ const store = createStore({ } return 0; }), - startDate: new Date(), + startDate: dateToISO(new Date()), /** * Indicates if the destination is: * @@ -278,7 +278,7 @@ const store = createStore({ type: conc.person.type }, start_date: { - datetime: datetimeToISO(state.startDate) + datetime: datetimeToISO(ISOToDate(state.startDate)) } }; From 65e6471a02ac72453c79151188025915755b3e08 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 6 Apr 2022 11:26:04 +0200 Subject: [PATCH 065/119] fix datepicker for social action edit form. still a problem with the display of already set evaluation dates... need to be transformed to correct format to display --- .../vuejs/AccompanyingCourseWorkEdit/App.vue | 8 ++++---- .../components/FormEvaluation.vue | 12 ++++++------ .../vuejs/AccompanyingCourseWorkEdit/store.js | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue index bcfa53e45..668385b8a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue @@ -439,18 +439,18 @@ export default { ]), startDate: { get() { - return dateToISO(this.$store.state.startDate); + return this.$store.state.startDate; }, set(v) { - this.$store.commit('setStartDate', ISOToDate(v)); + this.$store.commit('setStartDate', v); } }, endDate: { get() { - return dateToISO(this.$store.state.endDate); + return this.$store.state.endDate; }, set(v) { - this.$store.commit('setEndDate', ISOToDate(v)); + this.$store.commit('setEndDate', v); } }, note: { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue index 53e2b72f3..cad070de3 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue @@ -309,26 +309,26 @@ export default { }, startDate: { get() { - return dateToISO(this.evaluation.startDate); + return this.evaluation.startDate; }, set(v) { - this.$store.commit('setEvaluationStartDate', { key: this.evaluation.key, date: ISOToDate(v) }); + this.$store.commit('setEvaluationStartDate', { key: this.evaluation.key, date: v }); } }, endDate: { get() { - return dateToISO(this.evaluation.endDate); + return this.evaluation.endDate; }, set(v) { - this.$store.commit('setEvaluationEndDate', { key: this.evaluation.key, date: ISOToDate(v) }); + this.$store.commit('setEvaluationEndDate', { key: this.evaluation.key, date: v }); } }, maxDate: { get() { - return dateToISO(this.evaluation.maxDate); + return this.evaluation.maxDate; }, set(v) { - this.$store.commit('setEvaluationMaxDate', { key: this.evaluation.key, date: ISOToDate(v) }); + this.$store.commit('setEvaluationMaxDate', { key: this.evaluation.key, date: v }); } }, warningInterval: { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js index 4ce0b8ebe..0bfe61d94 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js @@ -1,5 +1,5 @@ import { createStore } from 'vuex'; -import { datetimeToISO, ISOToDatetime, intervalDaysToISO, intervalISOToDays } from 'ChillMainAssets/chill/js/date.js'; +import { dateToISO, ISOToDate, datetimeToISO, ISOToDatetime, intervalDaysToISO, intervalISOToDays } from 'ChillMainAssets/chill/js/date.js'; import { findSocialActionsBySocialIssue } from 'ChillPersonAssets/vuejs/_api/SocialWorkSocialAction.js'; import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js'; import { fetchResults, makeFetch } from 'ChillMainAssets/lib/api/apiMethods.js'; @@ -13,9 +13,9 @@ const store = createStore({ state: { work: window.accompanyingCourseWork, startDate: window.accompanyingCourseWork.startDate !== null ? - ISOToDatetime(window.accompanyingCourseWork.startDate.datetime) : null, + dateToISO(new Date(window.accompanyingCourseWork.startDate.datetime)) : null, endDate: window.accompanyingCourseWork.endDate !== null ? - ISOToDatetime(window.accompanyingCourseWork.endDate.datetime) : null, + dateToISO(new Date(window.accompanyingCourseWork.endDate.datetime)) : null, note: window.accompanyingCourseWork.note, goalsPicked: window.accompanyingCourseWork.goals, goalsForAction: [], @@ -73,10 +73,10 @@ const store = createStore({ type: 'accompanying_period_work', id: state.work.id, startDate: state.startDate === null ? null : { - datetime: datetimeToISO(state.startDate) + datetime: datetimeToISO(ISOToDate(state.startDate)) }, endDate: state.endDate === null ? null : { - datetime: datetimeToISO(state.endDate) + datetime: datetimeToISO(ISOToDate(state.endDate)) }, note: state.note, persons: state.personsPicked.map(p => ({id: p.id, type: p.type})), @@ -110,9 +110,9 @@ const store = createStore({ id: e.evaluation.id, type: e.evaluation.type }, - startDate: e.startDate !== null ? { datetime: datetimeToISO(e.startDate) } : null, - endDate: e.endDate !== null ? { datetime: datetimeToISO(e.endDate) } : null, - maxDate: e.maxDate !== null ? { datetime: datetimeToISO(e.maxDate) } : null, + startDate: e.startDate !== null ? { datetime: datetimeToISO(ISOToDate(e.startDate)) } : null, + endDate: e.endDate !== null ? { datetime: datetimeToISO(ISOToDate(e.endDate)) } : null, + maxDate: e.maxDate !== null ? { datetime: datetimeToISO(ISOToDate(e.maxDate)) } : null, warningInterval: intervalDaysToISO(e.warningInterval), comment: e.comment, documents: e.documents From bc550ea42aa1d13447ef0f0d658cdf6f51e2c349 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 19 Apr 2022 15:09:25 +0200 Subject: [PATCH 066/119] updating openingdate parcours fixed --- .../components/StartDate.vue | 26 ++++++++++++++----- .../vuejs/AccompanyingCourse/js/i18n.js | 2 +- .../vuejs/AccompanyingCourse/store/index.js | 6 +---- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue index 349581452..b6a0344e8 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue @@ -5,9 +5,9 @@
      - -
      - +
      + +
      @@ -23,10 +23,12 @@ import { mapState, mapGetters } from 'vuex'; export default { name: 'startDate', methods: { - updateStartDate(event) { - const date = event.target.value; - // console.log(date) + updateStartDate(e) { + e.preventDefault(); + const date = e.target.previousSibling.value; + this.$store.dispatch('updateStartDate', date) + .then(this.$toast.open({type: 'success', message: this.$t('startdate.update')})) .catch(({name, violations}) => { if (name === 'ValidationException' || name === 'AccessException') { violations.forEach((violation) => this.$toast.open({message: violation})); @@ -43,4 +45,14 @@ export default { } } - \ No newline at end of file + + + \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js index f1e2de958..a3a8ede2d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js @@ -153,7 +153,7 @@ const appMessages = { }, startdate: { change: "Date d'ouverture", - date: "Date d'ouverture", + update: "La nouvelle date d'ouverture a été bien enregistrée" }, // catch errors 'Error while updating AccompanyingPeriod Course.': "Erreur du serveur lors de la mise à jour du parcours d'accompagnement.", diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js index 06a3606ab..5d73aa2ed 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js @@ -322,7 +322,6 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou } }, updateStartDate(state, date) { - console.log('new state date', date) state.accompanyingCourse.openingDate = date; } }, @@ -814,11 +813,8 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou }) }, updateStartDate({commit}, payload) { - console.log('payload', payload) - const date = ISOToDate(payload); const url = `/api/1.0/person/accompanying-course/${id}.json`; - const body = { type: "accompanying_period", openingDate: { datetime: datetimeToISO(date) }}; - console.log('body', body) + const body = { type: "accompanying_period", openingDate: { datetime: datetimeToISO(ISOToDate(payload)) }}; return makeFetch('PATCH', url, body) .then((response) => { commit('updateStartDate', response.openingDate); From 8b7404e5316bb8119d8f6bd7dc1c5b77d5802357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 20 Apr 2022 13:05:11 +0200 Subject: [PATCH 067/119] Email on designation: allow raw characters As the content-type of the email is text/plain, we should not escape special character. Add the raw filter on string which will be escaped by twig. --- CHANGELOG.md | 1 + .../Notification/accompanying_course_designation.md.twig | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f26e64a1..b7681e5e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to * [Activity form] invert 'incoming' and 'receiving' in Activity form * [Activity form] keep the same order for 'attendee' field in new and edit form * [list with period] use "sameas" test operator to introduce requestor in list +* [notification email on course designation] allow raw string in email content generation ## Test releases diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Notification/accompanying_course_designation.md.twig b/src/Bundle/ChillPersonBundle/Resources/views/Notification/accompanying_course_designation.md.twig index 879df495e..8dd6558ac 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Notification/accompanying_course_designation.md.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Notification/accompanying_course_designation.md.twig @@ -1,14 +1,14 @@ -{{ 'period_notification.You are designated to a new period'|trans }} +{{ 'period_notification.You are designated to a new period'|trans|raw }} -{{ 'period_notification.See it online'|trans }}: +{{ 'period_notification.See it online'|trans|raw }}: {{ absolute_url(path('chill_person_accompanying_course_index', {'accompanying_period_id': accompanyingCourse.id}, false)) }} -{{ 'period_notification.Persons are'|trans }}: +{{ 'period_notification.Persons are'|trans|raw }}: {% for p in accompanyingCourse.getCurrentParticipations %} * {{ p.person|chill_entity_render_string }} {% endfor %} -{{ 'period_notification.Social issues are'|trans }}: {% for s in accompanyingCourse.socialIssues %}{{ s|chill_entity_render_string }}{% if not loop.last %}, {% endif %}{% endfor %}. +{{ 'period_notification.Social issues are'|trans|raw }}: {% for s in accompanyingCourse.socialIssues %}{{ s|chill_entity_render_string }}{% if not loop.last %}, {% endif %}{% endfor %}. From 0ce23230da611069b094b90f2a554233df3df069 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 20 Apr 2022 13:32:51 +0200 Subject: [PATCH 068/119] fix evaluation datepickers in edit --- .../vuejs/AccompanyingCourseWorkEdit/App.vue | 10 +-- .../components/FormEvaluation.vue | 81 ++++++++++--------- .../vuejs/AccompanyingCourseWorkEdit/store.js | 7 +- 3 files changed, 50 insertions(+), 48 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue index 668385b8a..bab64018a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue @@ -521,12 +521,12 @@ export default { this.$store.commit('removeReferrer', u); }, goToGenerateWorkflow({link}) { - console.log('save before leave to generate workflow') - const callback = (data) => { - window.location.assign(link); - }; + // console.log('save before leave to generate workflow') + const callback = (data) => { + window.location.assign(link); + }; - return this.$store.dispatch('submit', callback) + return this.$store.dispatch('submit', callback) .catch(e => { console.log(e); throw e; }); }, submit() { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue index cad070de3..ca0d16eec 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue @@ -298,38 +298,39 @@ export default { } }, computed: { - ...mapState([ - 'isPosting' - ]), + ...mapState([ + 'isPosting' + ]), getTemplatesAvailables() { - return this.$store.getters.getTemplatesAvailablesForEvaluation(this.evaluation.evaluation); + return this.$store.getters.getTemplatesAvailablesForEvaluation(this.evaluation.evaluation); }, canGenerate() { - return !this.$store.state.isPosting && this.template !== null; + return !this.$store.state.isPosting && this.template !== null; }, startDate: { - get() { - return this.evaluation.startDate; - }, - set(v) { - this.$store.commit('setEvaluationStartDate', { key: this.evaluation.key, date: v }); - } + get() { + console.log('evaluation', this.evaluation); + return this.evaluation.startDate; + }, + set(v) { + this.$store.commit('setEvaluationStartDate', { key: this.evaluation.key, date: v }); + } }, endDate: { - get() { - return this.evaluation.endDate; - }, - set(v) { - this.$store.commit('setEvaluationEndDate', { key: this.evaluation.key, date: v }); - } + get() { + return this.evaluation.endDate; + }, + set(v) { + this.$store.commit('setEvaluationEndDate', { key: this.evaluation.key, date: v }); + } }, maxDate: { - get() { - return this.evaluation.maxDate; - }, - set(v) { - this.$store.commit('setEvaluationMaxDate', { key: this.evaluation.key, date: v }); - } + get() { + return this.evaluation.maxDate; + }, + set(v) { + this.$store.commit('setEvaluationMaxDate', { key: this.evaluation.key, date: v }); + } }, warningInterval: { get() { return this.evaluation.warningInterval; }, @@ -344,7 +345,7 @@ export default { ISOToDatetime, canEditDocument(document) { return 'storedObject' in document ? - this.mime.includes(document.storedObject.type) && document.storedObject.keyInfos.length === 0 : false; + this.mime.includes(document.storedObject.type) && document.storedObject.keyInfos.length === 0 : false; }, listAllStatus() { console.log('load all status'); @@ -360,16 +361,16 @@ export default { }, buildEditLink(storedObject) { return `/wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent( - window.location.pathname + window.location.search + window.location.hash); + window.location.pathname + window.location.search + window.location.hash); }, submitBeforeGenerate({template}) { - const callback = (data) => { - let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id; + const callback = (data) => { + let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id; - window.location.assign(buildLink(template, evaluationId, 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation')); - }; + window.location.assign(buildLink(template, evaluationId, 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation')); + }; - return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; }); + return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; }); }, onInputDocumentTitle(event) { const id = Number(event.target.id); @@ -395,20 +396,20 @@ export default { }, removeDocument(document) { if (window.confirm("Êtes-vous sûr·e de vouloir supprimer le document qui a pour titre \"" + document.title +"\" ?")) { - this.$store.commit('removeDocument', {key: this.evaluation.key, document: document}); + this.$store.commit('removeDocument', {key: this.evaluation.key, document: document}); } }, goToGenerateWorkflowEvaluationDocument({event, link, workflowName, payload}) { - const callback = (data) => { - let evaluation = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key); - let updatedDocument = evaluation.documents.find(d => d.key === payload.doc.key); - window.location.assign(buildLinkCreate(workflowName, - 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', updatedDocument.id)); - }; + const callback = (data) => { + let evaluation = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key); + let updatedDocument = evaluation.documents.find(d => d.key === payload.doc.key); + window.location.assign(buildLinkCreate(workflowName, + 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', updatedDocument.id)); + }; - return this.$store.dispatch('submit', callback) - .catch(e => { console.log(e); throw e; }); - }, + return this.$store.dispatch('submit', callback) + .catch(e => { console.log(e); throw e; }); + }, }, } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js index 0bfe61d94..570a3b670 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js @@ -132,9 +132,9 @@ const store = createStore({ var k = Object.assign(e, { key: index, editEvaluation: false, - startDate: e.startDate !== null ? ISOToDatetime(e.startDate.datetime) : null, - endDate: e.endDate !== null ? ISOToDatetime(e.endDate.datetime) : null, - maxDate: e.maxDate !== null ? ISOToDatetime(e.maxDate.datetime) : null, + startDate: e.startDate !== null ? dateToISO(new Date(e.startDate.datetime)) : null, + endDate: e.endDate !== null ? dateToISO(new Date(e.endDate.datetime)) : null, + maxDate: e.maxDate !== null ? dateToISO(new Date(e.maxDate.datetime)) : null, warningInterval: e.warningInterval !== null ? intervalISOToDays(e.warningInterval) : null, documents: e.documents.map((d, docIndex) => { return Object.assign(d, { @@ -264,6 +264,7 @@ const store = createStore({ .startDate = date; }, setEvaluationEndDate(state, {key, date}) { + console.log('commit date', date) state.evaluationsPicked.find(e => e.key === key) .endDate = date; }, From bf0578b6d7a8b612263e4049089c67a413a6f901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 20 Apr 2022 13:49:47 +0200 Subject: [PATCH 069/119] [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older --- CHANGELOG.md | 1 + .../Entity/AccompanyingPeriod/AccompanyingPeriodWork.php | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7681e5e8..459f95005 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to * [Activity form] keep the same order for 'attendee' field in new and edit form * [list with period] use "sameas" test operator to introduce requestor in list * [notification email on course designation] allow raw string in email content generation +* [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older ## Test releases diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php index 95ec008d9..055507162 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php @@ -57,6 +57,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues * orphanRemoval=true * ) * @Serializer\Groups({"read", "docgen:read"}) + * @ORM\OrderBy({"startDate": "DESC", "id": "DESC"}) * * @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer` */ From ad1e7b576cfefe67976709d2aaff262b5b8cb964 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 20 Apr 2022 13:58:45 +0200 Subject: [PATCH 070/119] remove success toast for parcours startdate + take empty string values into account for action dates --- .../public/vuejs/AccompanyingCourse/components/StartDate.vue | 3 +-- .../public/vuejs/AccompanyingCourseWorkEdit/store.js | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue index b6a0344e8..564fc74b7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue @@ -28,14 +28,13 @@ export default { const date = e.target.previousSibling.value; this.$store.dispatch('updateStartDate', date) - .then(this.$toast.open({type: 'success', message: this.$t('startdate.update')})) .catch(({name, violations}) => { if (name === 'ValidationException' || name === 'AccessException') { violations.forEach((violation) => this.$toast.open({message: violation})); } else { this.$toast.open({message: 'An error occurred'}) } - }); + }) }, }, computed: { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js index 570a3b670..77cc4800e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js @@ -69,13 +69,14 @@ const store = createStore({ return []; }, buildPayload(state) { + console.log('end date', state.endDate); return { type: 'accompanying_period_work', id: state.work.id, - startDate: state.startDate === null ? null : { + startDate: state.startDate === null || state.startDate === '' ? null : { datetime: datetimeToISO(ISOToDate(state.startDate)) }, - endDate: state.endDate === null ? null : { + endDate: state.endDate === null || state.endDate === '' ? null : { datetime: datetimeToISO(ISOToDate(state.endDate)) }, note: state.note, From be77c3729b7700b5827482d9346d544dd7dca62e Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 20 Apr 2022 14:09:28 +0200 Subject: [PATCH 071/119] take empty date strings into account --- .../public/vuejs/AccompanyingCourse/store/index.js | 3 ++- .../public/vuejs/AccompanyingCourseWorkEdit/store.js | 6 +++--- .../public/vuejs/HouseholdMembersEditor/store/index.js | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js index 5d73aa2ed..57415565b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js @@ -814,7 +814,8 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou }, updateStartDate({commit}, payload) { const url = `/api/1.0/person/accompanying-course/${id}.json`; - const body = { type: "accompanying_period", openingDate: { datetime: datetimeToISO(ISOToDate(payload)) }}; + const date = payload === null || payload === '' ? null : { datetime: datetimeToISO(ISOToDate(payload)) } + const body = { type: "accompanying_period", openingDate: date}; return makeFetch('PATCH', url, body) .then((response) => { commit('updateStartDate', response.openingDate); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js index 77cc4800e..502704716 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js @@ -111,9 +111,9 @@ const store = createStore({ id: e.evaluation.id, type: e.evaluation.type }, - startDate: e.startDate !== null ? { datetime: datetimeToISO(ISOToDate(e.startDate)) } : null, - endDate: e.endDate !== null ? { datetime: datetimeToISO(ISOToDate(e.endDate)) } : null, - maxDate: e.maxDate !== null ? { datetime: datetimeToISO(ISOToDate(e.maxDate)) } : null, + startDate: e.startDate === null || e.startDate === '' ? null : { datetime: datetimeToISO(ISOToDate(e.startDate)) }, + endDate: e.endDate === null || e.endDate === '' ? null : { datetime: datetimeToISO(ISOToDate(e.endDate)) }, + maxDate: e.maxDate === null || e.maxDate === '' ? null : { datetime: datetimeToISO(ISOToDate(e.maxDate)) }, warningInterval: intervalDaysToISO(e.warningInterval), comment: e.comment, documents: e.documents diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js index 87e465a8d..75b3c215f 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js @@ -278,7 +278,7 @@ const store = createStore({ type: conc.person.type }, start_date: { - datetime: datetimeToISO(ISOToDate(state.startDate)) + datetime: state.startDate === null || state.startDate === '' ? null : datetimeToISO(ISOToDate(state.startDate)) } }; From 92d394b6695055d322c46c9e675d5e6d077e7c07 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 20 Apr 2022 14:22:35 +0200 Subject: [PATCH 072/119] changelog updated --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f26e64a1..626d41658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to * [Activity form] invert 'incoming' and 'receiving' in Activity form * [Activity form] keep the same order for 'attendee' field in new and edit form * [list with period] use "sameas" test operator to introduce requestor in list +* [Datepickers] datepickers fixed when using keyboard to enter date (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/545) ## Test releases From b24de76d77f56f8e5b5c04d5056692b6641bb979 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 20 Apr 2022 15:16:26 +0200 Subject: [PATCH 073/119] display agents traitants --- CHANGELOG.md | 2 +- .../AccompanyingCourseWork/_item.html.twig | 32 +++++++++---------- ...st_recent_by_accompanying_period.html.twig | 9 ++++++ .../translations/messages.fr.yml | 2 +- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 459f95005..44915aa63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ and this project adheres to * [list with period] use "sameas" test operator to introduce requestor in list * [notification email on course designation] allow raw string in email content generation * [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older - +* [social_action] Display 'agents traitants' in parcours resumé and social action list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/568) ## Test releases diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig index 8ff8a0617..3d4f46425 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig @@ -25,22 +25,6 @@
      - {% if w.createdBy %} -
      -
      -

      {{ 'Referrers'|trans }}

      -
      -
      -

      - {% for u in w.referrers %} - {{ u|chill_entity_render_box }} - {% if not loop.last %}, {% endif %} - {% endfor %} -

      -
      -
      - {% endif %} - {%- if w.persons -%}
      @@ -78,6 +62,22 @@
      {% endif %} + {%- if w.referrers -%} +
      +
      +

      {{ 'Referrers'|trans }}

      +
      +
      + {% for u in w.referrers %} + + {{ u|chill_entity_render_box }} + {% if not loop.last %}, {% endif %} + + {% endfor %} +
      +
      + {% endif %} + {%- if w.socialAction.issue -%}
      diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig index 7f2fb0177..4621cb97a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig @@ -28,6 +28,15 @@ {{ w.handlingThierParty|chill_entity_render_box }}
    • {% endif %} + {% if w.referrers %} +
    • + {{ 'Referrers'|trans ~ ' : ' }} + {% for u in w.referrers %} + {{ u|chill_entity_render_box }} + {% if not loop.last %}, {% endif %} + {% endfor %} +
    • + {% endif %}
    • {{ 'Participants'|trans ~ ' : ' }} {% for p in w.persons %} diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index c6baf9513..535985d23 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -214,7 +214,7 @@ No requestor: Pas de demandeur No resources: "Pas d'interlocuteurs privilégiés" Persons associated: Usagers concernés Referrer: Référent -Referrers: Référents +Referrers: Agents traitants Some peoples does not belong to any household currently. Add them to an household soon: Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur ménage dès que possible. Add to household now: Ajouter à un ménage Any resource for this accompanying course: Aucun interlocuteur privilégié pour ce parcours From daa3eabd438a847a9490d3e7c00f2f2242a0c00e Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 20 Apr 2022 16:12:58 +0200 Subject: [PATCH 074/119] Change wording for save document button --- CHANGELOG.md | 1 + .../Resources/views/AccompanyingCourseDocument/new.html.twig | 2 +- .../Resources/views/PersonDocument/new.html.twig | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 459f95005..da3ee1a3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to * [list with period] use "sameas" test operator to introduce requestor in list * [notification email on course designation] allow raw string in email content generation * [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older +* [Documents] Change wording 'créer' to 'enregistrer' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/634) ## Test releases diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/new.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/new.html.twig index 94cfb8828..01be1a5d7 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/new.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/new.html.twig @@ -30,7 +30,7 @@
    • - +
    {{ form_end(form) }} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/new.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/new.html.twig index df947c933..3187714a6 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/new.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/new.html.twig @@ -47,7 +47,7 @@
  • - +
{{ form_end(form) }} From a70c56159631ab8e18c48f231c33e982a1e52183 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 20 Apr 2022 16:23:08 +0200 Subject: [PATCH 075/119] Number of parcours of user displayed in blue pill --- CHANGELOG.md | 1 + .../views/AccompanyingPeriod/user_periods_list.html.twig | 2 ++ src/Bundle/ChillPersonBundle/translations/messages.fr.yml | 1 + 3 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da3ee1a3a..e8201ceb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to * [notification email on course designation] allow raw string in email content generation * [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older * [Documents] Change wording 'créer' to 'enregistrer' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/634) +* [Parcours]: The number of 'mes parcours' displayed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/572) ## Test releases diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/user_periods_list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/user_periods_list.html.twig index 7395fceda..031e0728c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/user_periods_list.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/user_periods_list.html.twig @@ -17,6 +17,8 @@

{{ 'My accompanying periods'|trans }}

+

{{ 'Number of periods'|trans }}: {{ pagination.totalItems }}

+
{% for period in accompanyingPeriods %} {% include '@ChillPerson/AccompanyingPeriod/_list_item.html.twig' with {'period': period, 'recordAction': _self.recordAction(period)} %} diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index c6baf9513..2a4077e1c 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -581,6 +581,7 @@ Linked evaluations: Évaluations associées # Accompanying period per user My accompanying periods: Mes parcours My accompanying periods in draft: Mes parcours brouillons +Number of periods: Nombre de parcours workflow: Doc for evaluation (n°%eval%): Document de l'évaluation n°%eval% From 9a0eb1c74c60ca5d3623c22621aea3977e8a045f Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 20 Apr 2022 16:33:05 +0200 Subject: [PATCH 076/119] Renaming of tabs and removal of social actions tab --- CHANGELOG.md | 1 + .../public/vuejs/HomepageWidget/App.vue | 6 +-- .../public/vuejs/HomepageWidget/MyWorks.vue | 1 + .../public/vuejs/HomepageWidget/js/i18n.js | 6 +-- .../public/vuejs/HomepageWidget/js/store.js | 50 +++++++++---------- 5 files changed, 33 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8201ceb6..e074d731f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to * [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older * [Documents] Change wording 'créer' to 'enregistrer' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/634) * [Parcours]: The number of 'mes parcours' displayed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/572) +* [Hompage_widget]: Renaming of tabs and removal of social actions tab (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/570) ## Test releases diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/App.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/App.vue index 343b638da..fbf453b47 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/App.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/App.vue @@ -25,14 +25,14 @@ {{ $t('my_accompanying_courses.tab') }} -