mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-10-02 19:39:45 +00:00
Compare commits
18 Commits
chill-bund
...
signature-
Author | SHA1 | Date | |
---|---|---|---|
7aa63a25a7
|
|||
610239930b
|
|||
b65e2c62c4 | |||
89f5231649
|
|||
99818c211d
|
|||
a9f0059743
|
|||
5bc542a567
|
|||
c8ccce83fd
|
|||
4a229ebf6b | |||
6db36d5ab6
|
|||
59f721934e
|
|||
84ce8a93f3
|
|||
ab5f2ffb65
|
|||
73bae8ccb9
|
|||
dcfa569e3a
|
|||
4b07fe3622
|
|||
48bf359d2e
|
|||
60c7ea601c
|
6
.changes/unreleased/Feature-20240530-160003.yaml
Normal file
6
.changes/unreleased/Feature-20240530-160003.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: |
|
||||
Upgrade import of address list to the last version of compiled addresses of belgian-best-address
|
||||
time: 2024-05-30T16:00:03.440767606+02:00
|
||||
custom:
|
||||
Issue: ""
|
6
.changes/unreleased/Feature-20240531-190242.yaml
Normal file
6
.changes/unreleased/Feature-20240531-190242.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: |
|
||||
Upgrade CKEditor and refactor configuration with use of typescript
|
||||
time: 2024-05-31T19:02:42.776662753+02:00
|
||||
custom:
|
||||
Issue: ""
|
8
.changes/unreleased/Feature-20240614-153236.yaml
Normal file
8
.changes/unreleased/Feature-20240614-153236.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
kind: Feature
|
||||
body: |-
|
||||
Electronic signature
|
||||
|
||||
Implementation of the electronic signature for documents within chill.
|
||||
time: 2024-06-14T15:32:36.875891692+02:00
|
||||
custom:
|
||||
Issue: ""
|
@@ -32,6 +32,7 @@
|
||||
"phpoffice/phpspreadsheet": "^1.16",
|
||||
"ramsey/uuid-doctrine": "^1.7",
|
||||
"sensio/framework-extra-bundle": "^5.5",
|
||||
"smalot/pdfparser": "^2.10",
|
||||
"spomky-labs/base64url": "^2.0",
|
||||
"symfony/asset": "^5.4",
|
||||
"symfony/browser-kit": "^5.4",
|
||||
@@ -97,7 +98,6 @@
|
||||
"symfony/debug-bundle": "^5.4",
|
||||
"symfony/dotenv": "^5.4",
|
||||
"symfony/maker-bundle": "^1.20",
|
||||
"symfony/phpunit-bridge": "^5.4",
|
||||
"symfony/runtime": "^5.4",
|
||||
"symfony/stopwatch": "^5.4",
|
||||
"symfony/var-dumper": "^5.4"
|
||||
@@ -149,6 +149,7 @@
|
||||
"scripts": {
|
||||
"auto-scripts": {
|
||||
"cache:clear": "symfony-cmd"
|
||||
}
|
||||
},
|
||||
"php-cs-fixer": "php-cs-fixer fix --config=./.php-cs-fixer.dist.php --show-progress=none"
|
||||
}
|
||||
}
|
||||
|
15
package.json
15
package.json
@@ -6,15 +6,16 @@
|
||||
"@apidevtools/swagger-cli": "^4.0.4",
|
||||
"@babel/core": "^7.20.5",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@ckeditor/ckeditor5-build-classic": "^35.3.2",
|
||||
"@ckeditor/ckeditor5-dev-utils": "^31.1.13",
|
||||
"@ckeditor/ckeditor5-build-classic": "^41.4.2",
|
||||
"@ckeditor/ckeditor5-dev-utils": "^40.2.0",
|
||||
"@ckeditor/ckeditor5-dev-webpack-plugin": "^31.1.13",
|
||||
"@ckeditor/ckeditor5-markdown-gfm": "^35.3.2",
|
||||
"@ckeditor/ckeditor5-theme-lark": "^35.3.2",
|
||||
"@ckeditor/ckeditor5-vue": "^4.0.1",
|
||||
"@ckeditor/ckeditor5-dev-translations": "^40.2.0",
|
||||
"@ckeditor/ckeditor5-markdown-gfm": "^41.4.2",
|
||||
"@ckeditor/ckeditor5-theme-lark": "^41.4.2",
|
||||
"@ckeditor/ckeditor5-vue": "^5.1.0",
|
||||
"@symfony/webpack-encore": "^4.1.0",
|
||||
"@tsconfig/node14": "^1.0.1",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"bindings": "^1.5.0",
|
||||
"bootstrap": "5.2.3",
|
||||
"chokidar": "^3.5.1",
|
||||
@@ -31,7 +32,7 @@
|
||||
"select2-bootstrap-theme": "0.1.0-beta.10",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-loader": "^9.3.1",
|
||||
"typescript": "^4.7.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vue-loader": "^17.0.0",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
|
@@ -60,7 +60,7 @@ final class TranslatableActivityTypeTest extends KernelTestCase
|
||||
$this->assertInstanceOf(
|
||||
ActivityType::class,
|
||||
$form->getData()['type'],
|
||||
'The data is an instance of Chill\\ActivityBundle\\Entity\\ActivityType'
|
||||
'The data is an instance of Chill\ActivityBundle\Entity\ActivityType'
|
||||
);
|
||||
$this->assertEquals($type->getId(), $form->getData()['type']->getId());
|
||||
|
||||
|
@@ -37,12 +37,12 @@ class RemoteEventConverter
|
||||
* valid when the remote string contains also a timezone, like in
|
||||
* lastModifiedDate.
|
||||
*/
|
||||
final public const REMOTE_DATETIMEZONE_FORMAT = 'Y-m-d\\TH:i:s.u?P';
|
||||
final public const REMOTE_DATETIMEZONE_FORMAT = 'Y-m-d\TH:i:s.u?P';
|
||||
|
||||
/**
|
||||
* Same as above, but sometimes the date is expressed with only 6 milliseconds.
|
||||
*/
|
||||
final public const REMOTE_DATETIMEZONE_FORMAT_ALT = 'Y-m-d\\TH:i:s.uP';
|
||||
final public const REMOTE_DATETIMEZONE_FORMAT_ALT = 'Y-m-d\TH:i:s.uP';
|
||||
|
||||
private const REMOTE_DATE_FORMAT = 'Y-m-d\TH:i:s.u0';
|
||||
|
||||
|
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Controller;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Service\Signature\Driver\BaseSigner\RequestPdfSignMessage;
|
||||
use Chill\DocStoreBundle\Service\Signature\PDFPage;
|
||||
use Chill\DocStoreBundle\Service\Signature\PDFSignatureZone;
|
||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class SignatureRequestController
|
||||
{
|
||||
public function __construct(
|
||||
private MessageBusInterface $messageBus,
|
||||
private StoredObjectManagerInterface $storedObjectManager,
|
||||
) {}
|
||||
|
||||
#[Route('/api/1.0/document/workflow/{id}/signature-request', name: 'chill_docstore_signature_request')]
|
||||
public function processSignature(StoredObject $storedObject): Response
|
||||
{
|
||||
$content = $this->storedObjectManager->read($storedObject);
|
||||
|
||||
$this->messageBus->dispatch(new RequestPdfSignMessage(
|
||||
0,
|
||||
new PDFSignatureZone(10.0, 10.0, 180.0, 180.0, new PDFPage(0, 500.0, 800.0)),
|
||||
0,
|
||||
'test signature',
|
||||
$content
|
||||
));
|
||||
|
||||
return new Response('<html><head><title>test</title></head><body><p>ok</p></body></html>');
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Service\Signature\Driver\BaseSigner;
|
||||
|
||||
use Chill\DocStoreBundle\Service\Signature\PDFSignatureZone;
|
||||
|
||||
final readonly class RequestPdfSignMessage
|
||||
{
|
||||
public function __construct(
|
||||
public int $signatureId,
|
||||
public PDFSignatureZone $PDFSignatureZone,
|
||||
public int $signatureZoneIndex,
|
||||
public string $reason,
|
||||
public string $content,
|
||||
) {}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Service\Signature\Driver\BaseSigner;
|
||||
|
||||
use Chill\DocStoreBundle\Service\Signature\PDFSignatureZone;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
|
||||
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
|
||||
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
final readonly class RequestPdfSignMessageSerializer implements SerializerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private NormalizerInterface $normalizer,
|
||||
private DenormalizerInterface $denormalizer,
|
||||
) {}
|
||||
|
||||
public function decode(array $encodedEnvelope): Envelope
|
||||
{
|
||||
$body = $encodedEnvelope['body'];
|
||||
$headers = $encodedEnvelope['headers'];
|
||||
|
||||
if (RequestPdfSignMessage::class !== ($headers['Message'] ?? null)) {
|
||||
throw new MessageDecodingFailedException('serializer does not support this message');
|
||||
}
|
||||
|
||||
$data = json_decode($body, true);
|
||||
|
||||
$zoneSignature = $this->denormalizer->denormalize($data['signatureZone'], PDFSignatureZone::class, 'json', [
|
||||
AbstractNormalizer::GROUPS => ['write'],
|
||||
]);
|
||||
|
||||
$message = new RequestPdfSignMessage(
|
||||
$data['signatureId'],
|
||||
$zoneSignature,
|
||||
$data['signatureZoneIndex'],
|
||||
$data['reason'],
|
||||
base64_decode($data['content']),
|
||||
);
|
||||
|
||||
// in case of redelivery, unserialize any stamps
|
||||
$stamps = [];
|
||||
if (isset($headers['stamps'])) {
|
||||
$stamps = unserialize($headers['stamps']);
|
||||
}
|
||||
|
||||
return new Envelope($message, $stamps);
|
||||
}
|
||||
|
||||
public function encode(Envelope $envelope): array
|
||||
{
|
||||
$message = $envelope->getMessage();
|
||||
|
||||
if (!$message instanceof RequestPdfSignMessage) {
|
||||
throw new MessageDecodingFailedException('Message is not a RequestPdfSignMessage');
|
||||
}
|
||||
|
||||
$data = [
|
||||
'signatureId' => $message->signatureId,
|
||||
'signatureZoneIndex' => $message->signatureZoneIndex,
|
||||
'signatureZone' => $this->normalizer->normalize($message->PDFSignatureZone, 'json', [AbstractNormalizer::GROUPS => ['read']]),
|
||||
'reason' => $message->reason,
|
||||
'content' => base64_encode($message->content),
|
||||
];
|
||||
|
||||
$allStamps = [];
|
||||
foreach ($envelope->all() as $stamp) {
|
||||
if ($stamp instanceof NonSendableStampInterface) {
|
||||
continue;
|
||||
}
|
||||
$allStamps = [...$allStamps, ...$stamp];
|
||||
}
|
||||
|
||||
return [
|
||||
'body' => json_encode($data, JSON_THROW_ON_ERROR, 512),
|
||||
'headers' => [
|
||||
'stamps' => serialize($allStamps),
|
||||
'Message' => RequestPdfSignMessage::class,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
33
src/Bundle/ChillDocStoreBundle/Service/Signature/PDFPage.php
Normal file
33
src/Bundle/ChillDocStoreBundle/Service/Signature/PDFPage.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Service\Signature;
|
||||
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
final readonly class PDFPage
|
||||
{
|
||||
public function __construct(
|
||||
#[Groups(['read'])]
|
||||
public int $index,
|
||||
#[Groups(['read'])]
|
||||
public float $width,
|
||||
#[Groups(['read'])]
|
||||
public float $height,
|
||||
) {}
|
||||
|
||||
public function equals(self $page): bool
|
||||
{
|
||||
return $page->index === $this->index
|
||||
&& round($page->width, 2) === round($this->width, 2)
|
||||
&& round($page->height, 2) === round($this->height, 2);
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Service\Signature;
|
||||
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
final readonly class PDFSignatureZone
|
||||
{
|
||||
public function __construct(
|
||||
#[Groups(['read'])]
|
||||
public float $x,
|
||||
#[Groups(['read'])]
|
||||
public float $y,
|
||||
#[Groups(['read'])]
|
||||
public float $height,
|
||||
#[Groups(['read'])]
|
||||
public float $width,
|
||||
#[Groups(['read'])]
|
||||
public PDFPage $PDFPage,
|
||||
) {}
|
||||
|
||||
public function equals(self $other): bool
|
||||
{
|
||||
return
|
||||
$this->x == $other->x
|
||||
&& $this->y == $other->y
|
||||
&& $this->height == $other->height
|
||||
&& $this->width == $other->width
|
||||
&& $this->PDFPage->equals($other->PDFPage);
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Service\Signature;
|
||||
|
||||
use Smalot\PdfParser\Parser;
|
||||
|
||||
class PDFSignatureZoneParser
|
||||
{
|
||||
public const ZONE_SIGNATURE_START = 'signature_zone';
|
||||
|
||||
private Parser $parser;
|
||||
|
||||
public function __construct(
|
||||
public float $defaultHeight = 180.0,
|
||||
public float $defaultWidth = 180.0,
|
||||
) {
|
||||
$this->parser = new Parser();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<PDFSignatureZone>
|
||||
*/
|
||||
public function findSignatureZones(string $fileContent): array
|
||||
{
|
||||
$pdf = $this->parser->parseContent($fileContent);
|
||||
$zones = [];
|
||||
|
||||
$defaults = $pdf->getObjectsByType('Pages');
|
||||
$defaultPage = reset($defaults);
|
||||
$defaultPageDetails = $defaultPage->getDetails();
|
||||
|
||||
foreach ($pdf->getPages() as $index => $page) {
|
||||
$details = $page->getDetails();
|
||||
$pdfPage = new PDFPage(
|
||||
$index,
|
||||
(float) ($details['MediaBox'][2] ?? $defaultPageDetails['MediaBox'][2]),
|
||||
(float) ($details['MediaBox'][3] ?? $defaultPageDetails['MediaBox'][3]),
|
||||
);
|
||||
|
||||
foreach ($page->getDataTm() as $dataTm) {
|
||||
if (str_starts_with($dataTm[1], self::ZONE_SIGNATURE_START)) {
|
||||
$zones[] = new PDFSignatureZone((float) $dataTm[0][4], (float) $dataTm[0][5], $this->defaultHeight, $this->defaultWidth, $pdfPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $zones;
|
||||
}
|
||||
}
|
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Tests\Service\Signature\Driver\BaseSigner;
|
||||
|
||||
use Chill\DocStoreBundle\Service\Signature\PDFPage;
|
||||
use Chill\DocStoreBundle\Service\Signature\PDFSignatureZone;
|
||||
use Chill\DocStoreBundle\Service\Signature\Driver\BaseSigner\RequestPdfSignMessage;
|
||||
use Chill\DocStoreBundle\Service\Signature\Driver\BaseSigner\RequestPdfSignMessageSerializer;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class RequestPdfSignMessageSerializerTest extends TestCase
|
||||
{
|
||||
public function testEncode(): void
|
||||
{
|
||||
$serializer = $this->buildSerializer();
|
||||
|
||||
$envelope = new Envelope(
|
||||
$request = new RequestPdfSignMessage(
|
||||
0,
|
||||
new PDFSignatureZone(10.0, 10.0, 180.0, 180.0, new PDFPage(0, 500.0, 800.0)),
|
||||
0,
|
||||
'metadata to add to the signature',
|
||||
'abc'
|
||||
),
|
||||
);
|
||||
|
||||
$actual = $serializer->encode($envelope);
|
||||
$expectedBody = json_encode([
|
||||
'signatureId' => $request->signatureId,
|
||||
'signatureZoneIndex' => $request->signatureZoneIndex,
|
||||
'signatureZone' => ['x' => 10.0],
|
||||
'reason' => $request->reason,
|
||||
'content' => base64_encode($request->content),
|
||||
]);
|
||||
|
||||
self::assertIsArray($actual);
|
||||
self::assertArrayHasKey('body', $actual);
|
||||
self::assertArrayHasKey('headers', $actual);
|
||||
self::assertEquals($expectedBody, $actual['body']);
|
||||
}
|
||||
|
||||
public function testDecode(): void
|
||||
{
|
||||
$serializer = $this->buildSerializer();
|
||||
|
||||
$request = new RequestPdfSignMessage(
|
||||
0,
|
||||
new PDFSignatureZone(10.0, 10.0, 180.0, 180.0, new PDFPage(0, 500.0, 800.0)),
|
||||
0,
|
||||
'metadata to add to the signature',
|
||||
'abc'
|
||||
);
|
||||
|
||||
$bodyAsString = json_encode([
|
||||
'signatureId' => $request->signatureId,
|
||||
'signatureZoneIndex' => $request->signatureZoneIndex,
|
||||
'signatureZone' => ['x' => 10.0],
|
||||
'reason' => $request->reason,
|
||||
'content' => base64_encode($request->content),
|
||||
], JSON_THROW_ON_ERROR);
|
||||
|
||||
$actual = $serializer->decode([
|
||||
'body' => $bodyAsString,
|
||||
'headers' => [
|
||||
'Message' => RequestPdfSignMessage::class,
|
||||
],
|
||||
]);
|
||||
|
||||
self::assertInstanceOf(RequestPdfSignMessage::class, $actual->getMessage());
|
||||
self::assertEquals($request->signatureId, $actual->getMessage()->signatureId);
|
||||
self::assertEquals($request->signatureZoneIndex, $actual->getMessage()->signatureZoneIndex);
|
||||
self::assertEquals($request->reason, $actual->getMessage()->reason);
|
||||
self::assertEquals($request->content, $actual->getMessage()->content);
|
||||
self::assertNotNull($actual->getMessage()->PDFSignatureZone);
|
||||
}
|
||||
|
||||
private function buildSerializer(): RequestPdfSignMessageSerializer
|
||||
{
|
||||
$normalizer =
|
||||
new class () implements NormalizerInterface {
|
||||
public function normalize($object, ?string $format = null, array $context = []): array
|
||||
{
|
||||
if (!$object instanceof PDFSignatureZone) {
|
||||
throw new UnexpectedValueException('expected RequestPdfSignMessage');
|
||||
}
|
||||
|
||||
return [
|
||||
'x' => $object->x,
|
||||
];
|
||||
}
|
||||
|
||||
public function supportsNormalization($data, ?string $format = null): bool
|
||||
{
|
||||
return $data instanceof PDFSignatureZone;
|
||||
}
|
||||
};
|
||||
$denormalizer = new class () implements DenormalizerInterface {
|
||||
public function denormalize($data, string $type, ?string $format = null, array $context = [])
|
||||
{
|
||||
return new PDFSignatureZone(10.0, 10.0, 180.0, 180.0, new PDFPage(0, 500.0, 800.0));
|
||||
}
|
||||
|
||||
public function supportsDenormalization($data, string $type, ?string $format = null)
|
||||
{
|
||||
return PDFSignatureZone::class === $type;
|
||||
}
|
||||
};
|
||||
|
||||
$serializer = new Serializer([$normalizer, $denormalizer]);
|
||||
|
||||
return new RequestPdfSignMessageSerializer($serializer, $serializer);
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Tests\Service\Signature;
|
||||
|
||||
use Chill\DocStoreBundle\Service\Signature\PDFPage;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Chill\DocStoreBundle\Service\Signature\PDFSignatureZone;
|
||||
use Chill\DocStoreBundle\Service\Signature\PDFSignatureZoneParser;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class PDFSignatureZoneParserTest extends TestCase
|
||||
{
|
||||
private static PDFSignatureZoneParser $parser;
|
||||
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
self::$parser = new PDFSignatureZoneParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideFiles
|
||||
*
|
||||
* @param list<PDFSignatureZone> $expected
|
||||
*/
|
||||
public function testFindSignatureZones(string $filePath, array $expected): void
|
||||
{
|
||||
$content = file_get_contents($filePath);
|
||||
|
||||
if (false === $content) {
|
||||
throw new \LogicException("Unable to read file {$filePath}");
|
||||
}
|
||||
|
||||
$actual = self::$parser->findSignatureZones($content);
|
||||
|
||||
self::assertEquals(count($expected), count($actual));
|
||||
|
||||
foreach ($actual as $index => $signatureZone) {
|
||||
self::assertObjectEquals($expected[$index], $signatureZone);
|
||||
}
|
||||
}
|
||||
|
||||
public static function provideFiles(): iterable
|
||||
{
|
||||
yield [
|
||||
__DIR__.'/data/signature_2_signature_page_1.pdf',
|
||||
[
|
||||
new PDFSignatureZone(
|
||||
127.7,
|
||||
95.289,
|
||||
180.0,
|
||||
180.0,
|
||||
$page = new PDFPage(0, 595.30393, 841.8897)
|
||||
),
|
||||
new PDFSignatureZone(
|
||||
269.5,
|
||||
95.289,
|
||||
180.0,
|
||||
180.0,
|
||||
$page,
|
||||
),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
Binary file not shown.
@@ -211,7 +211,7 @@ class SearchController extends AbstractController
|
||||
$builder = $this
|
||||
->get('form.factory')
|
||||
->createNamedBuilder(
|
||||
null,
|
||||
'',
|
||||
FormType::class,
|
||||
$data,
|
||||
['method' => Request::METHOD_POST]
|
||||
|
@@ -43,7 +43,7 @@ class ShortMessageCompilerPass implements CompilerPassInterface
|
||||
$defaultTransporter = new Reference(NullShortMessageSender::class);
|
||||
} elseif ('ovh' === $dsn['scheme']) {
|
||||
if (!class_exists('\\'.\Ovh\Api::class)) {
|
||||
throw new RuntimeException('Class \\Ovh\\Api not found');
|
||||
throw new RuntimeException('Class \Ovh\Api not found');
|
||||
}
|
||||
|
||||
foreach (['user', 'host', 'pass'] as $component) {
|
||||
|
@@ -190,7 +190,7 @@ class ExportManager
|
||||
// throw an error if the export require other modifier, which is
|
||||
// not allowed when the export return a `NativeQuery`
|
||||
if (\count($export->supportsModifiers()) > 0) {
|
||||
throw new \LogicException("The export with alias `{$exportAlias}` return ".'a `\\Doctrine\\ORM\\NativeQuery` and supports modifiers, which is not allowed. Either the method `supportsModifiers` should return an empty array, or return a `Doctrine\\ORM\\QueryBuilder`');
|
||||
throw new \LogicException("The export with alias `{$exportAlias}` return ".'a `\Doctrine\ORM\NativeQuery` and supports modifiers, which is not allowed. Either the method `supportsModifiers` should return an empty array, or return a `Doctrine\ORM\QueryBuilder`');
|
||||
}
|
||||
} elseif ($query instanceof QueryBuilder) {
|
||||
// handle filters
|
||||
@@ -203,7 +203,7 @@ class ExportManager
|
||||
'dql' => $query->getDQL(),
|
||||
]);
|
||||
} else {
|
||||
throw new \UnexpectedValueException('The method `intiateQuery` should return a `\\Doctrine\\ORM\\NativeQuery` or a `Doctrine\\ORM\\QueryBuilder` object.');
|
||||
throw new \UnexpectedValueException('The method `intiateQuery` should return a `\Doctrine\ORM\NativeQuery` or a `Doctrine\ORM\QueryBuilder` object.');
|
||||
}
|
||||
|
||||
$result = $export->getResult($query, $data[ExportType::EXPORT_KEY]);
|
||||
|
@@ -59,6 +59,10 @@ export const ISOToDatetime = (str: string|null): Date|null => {
|
||||
[hours, minutes, seconds] = time.split(':').map(s => parseInt(s));
|
||||
;
|
||||
|
||||
if ('0000' === timezone) {
|
||||
return new Date(Date.UTC(year, month-1, date, hours, minutes, seconds));
|
||||
}
|
||||
|
||||
return new Date(year, month-1, date, hours, minutes, seconds);
|
||||
}
|
||||
|
||||
|
@@ -15,9 +15,9 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
var mime = require('mime')
|
||||
import mime from 'mime';
|
||||
|
||||
var download_report = (url, container) => {
|
||||
export const download_report = (url, container) => {
|
||||
var download_text = container.dataset.downloadText,
|
||||
alias = container.dataset.alias;
|
||||
|
||||
@@ -63,5 +63,3 @@ var download_report = (url, container) => {
|
||||
.replaceChild(problem_text, container.firstChild);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = download_report;
|
||||
|
@@ -39,23 +39,5 @@ ClassicEditor.defaultConfig = {
|
||||
'redo'
|
||||
]
|
||||
},
|
||||
language: 'fr'
|
||||
language: 'fr',
|
||||
};
|
||||
|
||||
|
||||
let Fields = [];
|
||||
Fields.push.apply(Fields, document.querySelectorAll('textarea[ckeditor]'));
|
||||
// enable for custom fields
|
||||
//Fields.push.apply(Fields, document.querySelectorAll('.cf-fields textarea'));
|
||||
|
||||
Fields.forEach(function(field) {
|
||||
ClassicEditor
|
||||
.create( field )
|
||||
.then( editor => {
|
||||
//console.log( 'CkEditor was initialized', editor );
|
||||
})
|
||||
.catch( error => {
|
||||
console.error( error.stack );
|
||||
})
|
||||
;
|
||||
});
|
@@ -0,0 +1,15 @@
|
||||
import ClassicEditor from "./editor_config";
|
||||
|
||||
const ckeditorFields: NodeListOf<HTMLTextAreaElement> = document.querySelectorAll('textarea[ckeditor]');
|
||||
ckeditorFields.forEach((field: HTMLTextAreaElement): void => {
|
||||
ClassicEditor
|
||||
.create( field )
|
||||
.then( editor => {
|
||||
//console.log( 'CkEditor was initialized', editor );
|
||||
})
|
||||
.catch( error => {
|
||||
console.error( error.stack );
|
||||
})
|
||||
;
|
||||
});
|
||||
//Fields.push.apply(Fields, document.querySelectorAll('.cf-fields textarea'));
|
@@ -0,0 +1,16 @@
|
||||
import {download_report} from "../../lib/download-report/download-report";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", function(e) {
|
||||
const export_generate_url = window.export_generate_url;
|
||||
|
||||
if (typeof export_generate_url === 'undefined') {
|
||||
console.error('Alias not found!');
|
||||
throw new Error('Alias not found!');
|
||||
}
|
||||
|
||||
const query = window.location.search,
|
||||
container = document.querySelector("#download_container")
|
||||
;
|
||||
|
||||
download_report(export_generate_url + "?" + query.toString(), container);
|
||||
});
|
@@ -0,0 +1,306 @@
|
||||
{% extends '@ChillMain/layout.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
SASS Assets Catalogue
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<style media="screen">
|
||||
h2 { margin: 1.5em 0; }
|
||||
div.flex-table ul, div.flex-bloc ul { padding-left: 1rem; }
|
||||
div.flex-table div.item-bloc div.item-row div.item-col:first-child { flex-basis: 20%; }
|
||||
div.flex-bloc div.item-bloc { flex-basis: 50%; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-10">
|
||||
<h1 class="display-4">{{ block('title') }}</h1>
|
||||
|
||||
<b>Voir aussi: </b>
|
||||
<a href="{{ path('sass_assets_test1') }}">Test 1</a> |
|
||||
<a href="{{ path('sass_assets_test2') }}">Test 2</a>
|
||||
|
||||
<h2>Flex-table et flex-bloc</h2>
|
||||
<p>Base d'un placement flex alternatif à l'usage des tables.
|
||||
Flex-table et flex-bloc utilisent la même structure html (seul la root class change).
|
||||
Le placement est responsive.
|
||||
La bordure utilise box-shadow pour simuler border-collapse (table).
|
||||
</p>
|
||||
<p>Une classe separator peut être appliquée sur item-row</p>
|
||||
|
||||
<xmp>
|
||||
<div class="flex-table">
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col"></div>
|
||||
<div class="item-col"></div>
|
||||
</div>
|
||||
<div class="item-row separator">
|
||||
<div class="item-col"></div>
|
||||
<div class="item-col"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col"></div>
|
||||
<div class="item-col"></div>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<div class="item-col"></div>
|
||||
<div class="item-col"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xmp>
|
||||
|
||||
<h3>Flex-table</h3>
|
||||
<p>On fixe manuellement la largeur de la première colonne :
|
||||
<pre>div.flex-table div.item-bloc div.item-row div.item-col:first-child { flex-basis: 20%; }</pre>
|
||||
</p>
|
||||
<div class="flex-table debug">
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col">Title row1</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content">
|
||||
<li>Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</li>
|
||||
<li>Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-row separator">
|
||||
<div class="item-col">Title row2</div>
|
||||
<div class="item-col">Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</div>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<div class="item-col">Title row3</div>
|
||||
<div class="item-col">Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col">Title row1</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content">
|
||||
<li>Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</li>
|
||||
<li>Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<div class="item-col">Title row2</div>
|
||||
<div class="item-col">Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Flex-bloc</h3>
|
||||
<p>On fixe manuellement la largeur des blocs :
|
||||
<pre>div.flex-bloc div.item-bloc { flex-basis: 50%; }</pre>
|
||||
</p>
|
||||
<div class="flex-bloc debug">
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col">Title row1</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content">
|
||||
<li>Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</li>
|
||||
<li>Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-row separator">
|
||||
<div class="item-col">Title row2</div>
|
||||
<div class="item-col">Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</div>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<div class="item-col">Title row3</div>
|
||||
<div class="item-col">Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col">Title row1</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content">
|
||||
<li>Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</li>
|
||||
<li>Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<div class="item-col">Title row2</div>
|
||||
<div class="item-col">Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac viverra et a dui.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Wrap-list</h2>
|
||||
<p>Une liste inline qui s'aligne, puis glisse sous son titre.</p>
|
||||
<div class="wrap-list debug">
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">Usagers concernés</div>
|
||||
<div class="wl-col list">
|
||||
<p class="wl-item"><a href="#">Gaston Bah</a></p>
|
||||
<p class="wl-item"><a href="#">Alain Bah</a></p>
|
||||
<p class="wl-item"><a href="#">Adèle Gaillot</a></p>
|
||||
<p class="wl-item"><a href="#">Corentine Bah</a></p>
|
||||
<p class="wl-item"><a href="#">Justin Bah</a></p>
|
||||
<p class="wl-item"><a href="#">Michel Sardou</a></p>
|
||||
<p class="wl-item"><a href="#">Carine Rousseau</a></p>
|
||||
<p class="wl-item"><a href="#">Mohamed Martin</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">Problématiques sociales</div>
|
||||
<div class="wl-col list">
|
||||
<p class="wl-item"><a href="#">Gaston Bah</a></p>
|
||||
<p class="wl-item"><a href="#">Alain Bah</a></p>
|
||||
<p class="wl-item"><a href="#">Adèle Gaillot</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<xmp>
|
||||
<div class="wrap-list">
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">title</div>
|
||||
<div class="wl-col list">
|
||||
<p class="wl-item">item</p>
|
||||
<p class="wl-item">item</p>
|
||||
...
|
||||
</div>
|
||||
</div>
|
||||
...
|
||||
</div>
|
||||
</xmp>
|
||||
|
||||
<h2>Wrap-header</h2>
|
||||
<p>Réglage d'une zone de titre sur 2 lignes.</p>
|
||||
<div class="wrap-header debug">
|
||||
<div class="wh-row">
|
||||
<div class="wh-col">
|
||||
<span class="h3"><b>Title</b></span>
|
||||
<span class="badge rounded-pill bg-danger">badge</span>
|
||||
</div>
|
||||
<div class="wh-col">
|
||||
<span class="badge rounded-pill bg-primary">badge</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wh-row">
|
||||
<div class="wh-col">from startdate to enddate</div>
|
||||
<div class="wh-col">text</div>
|
||||
</div>
|
||||
</div>
|
||||
<xmp>
|
||||
<div class="wrap-header">
|
||||
<div class="wh-row">
|
||||
<div class="wh-col">line1 left</div>
|
||||
<div class="wh-col">line1 right</div>
|
||||
</div>
|
||||
<div class="wh-row">
|
||||
<div class="wh-col">line2 left</div>
|
||||
<div class="wh-col">line2 right</div>
|
||||
</div>
|
||||
</div>
|
||||
</xmp>
|
||||
|
||||
<h2>Float-button top</h2>
|
||||
<p>Une zone de bouton flotte à droite d'un contenu. On peut voir en faisant varier la largeur que celui-ci vient s'adapter harmonieusement autour des boutons.</p>
|
||||
<div class="float-button top debug">
|
||||
<div class="box">
|
||||
<div class="action">
|
||||
<ul class="record_actions">
|
||||
<li><button type="button" name="button">Annuler</button></li>
|
||||
<li><button type="button" name="button">Voir</button></li>
|
||||
<li><button type="button" name="button">Enregistrer</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac. Proin fermentum mauris quam, ut suscipit nisl auctor at. Ut vestibulum ligula eget ex congue, efficitur interdum ipsum tincidunt. Integer id sapien et nibh tristique viverra et a dui. Ut blandit pharetra consectetur. Sed scelerisque eget purus at tempus. Etiam sit amet tellus et eros semper tempor. Curabitur suscipit pulvinar enim at lobortis. Ut nisl augue, cursus vel hendrerit sed, posuere vel sapien. Proin hendrerit arcu velit, eu ultrices dui interdum eget. Vestibulum consectetur sodales enim a accumsan. In vitae tristique leo, a fringilla nisl.
|
||||
</div>
|
||||
</div>
|
||||
<xmp>
|
||||
<div class="float-button top">
|
||||
<div class="box">
|
||||
<div class="action">
|
||||
floating button
|
||||
</div>
|
||||
content ...
|
||||
</div>
|
||||
</div>
|
||||
</xmp>
|
||||
|
||||
<h2>Float-button bottom</h2>
|
||||
<p>Avec la même structure, on accroche la zone de bouton en bas, toujours à droite. Voir <a href="https://css-tricks.com/float-an-element-to-the-bottom-corner/">source</a>. </p>
|
||||
<div class="float-button bottom debug">
|
||||
<div class="box">
|
||||
<div class="action">
|
||||
<ul class="record_actions">
|
||||
<li><button type="button" name="button">Annuler</button></li>
|
||||
<li><button type="button" name="button">Voir</button></li>
|
||||
<li><button type="button" name="button">Enregistrer</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac. Proin fermentum mauris quam, ut suscipit nisl auctor at. Ut vestibulum ligula eget ex congue, efficitur interdum ipsum tincidunt. Integer id sapien et nibh tristique viverra et a dui. Ut blandit pharetra consectetur. Sed scelerisque eget purus at tempus. Etiam sit amet tellus et eros semper tempor. Curabitur suscipit pulvinar enim at lobortis. Ut nisl augue, cursus vel hendrerit sed, posuere vel sapien. Proin hendrerit arcu velit, eu ultrices dui interdum eget. Vestibulum consectetur sodales enim a accumsan. In vitae tristique leo, a fringilla nisl.
|
||||
</div>
|
||||
</div>
|
||||
<xmp>
|
||||
<div class="float-button bottom">
|
||||
<div class="box">
|
||||
<div class="action">
|
||||
floating button
|
||||
</div>
|
||||
content ...
|
||||
</div>
|
||||
</div>
|
||||
</xmp>
|
||||
|
||||
|
||||
<h1>Buttons</h1>
|
||||
<ul class="record_actions">
|
||||
<li><a href="#" class="btn btn-submit">submit</a></li>
|
||||
<li><a href="#" class="btn btn-save">save</a></li>
|
||||
<li><a href="#" class="btn btn-create">create</a></li>
|
||||
<li><a href="#" class="btn btn-new">new</a></li>
|
||||
<li><a href="#" class="btn btn-duplicate">duplicate</a></li>
|
||||
<li><a href="#" class="btn btn-not-duplicate">not-duplicate</a></li>
|
||||
<li><a href="#" class="btn btn-reset">reset</a></li>
|
||||
<li><a href="#" class="btn btn-delete">delete</a></li>
|
||||
<li><a href="#" class="btn btn-danger">danger</a></li>
|
||||
<li><a href="#" class="btn btn-remove">remove</a></li>
|
||||
<li><a href="#" class="btn btn-unlink">unlink</a></li>
|
||||
<li><a href="#" class="btn btn-action">action</a></li>
|
||||
<li><a href="#" class="btn btn-edit">edit</a></li>
|
||||
<li><a href="#" class="btn btn-update">update</a></li>
|
||||
<li><a href="#" class="btn btn-show">show</a></li>
|
||||
<li><a href="#" class="btn btn-view">view</a></li>
|
||||
<li><a href="#" class="btn btn-misc">misc</a></li>
|
||||
<li><a href="#" class="btn btn-cancel">cancel</a></li>
|
||||
<li><a href="#" class="btn btn-choose">choose</a></li>
|
||||
<li><a href="#" class="btn btn-notify">notify</a></li>
|
||||
<li><a href="#" class="btn btn-tpchild">tpchild</a></li>
|
||||
<li><a href="#" class="btn btn-chill-beige">my button</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Variants of <pre>record_actions</pre></h2>
|
||||
|
||||
<h3><pre>small</pre></h3>
|
||||
|
||||
<ul class="record_actions small">
|
||||
<li><a href="#" class="btn btn-create"></a></li>
|
||||
</ul>
|
||||
|
||||
<h3><pre>inline</pre></h3>
|
||||
|
||||
<div>
|
||||
This is inline and small
|
||||
|
||||
<ul class="record_actions small inline">
|
||||
<li><a href="#" class="btn btn-create"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<xmp><a class="btn btn-submit">Text</a></xmp>
|
||||
Toutes les classes btn-* de bootstrap sont fonctionnelles
|
||||
</div>
|
||||
{% endblock %}
|
@@ -0,0 +1,84 @@
|
||||
{% extends '@ChillMain/layout.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
SASS Assets Tests - page 1
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<style media="screen">
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-8">
|
||||
<h1>CSS Tests - page 1 : float-button</h1>
|
||||
|
||||
<h2>1) avec des li</h2>
|
||||
<div class="float-button bottom debug">
|
||||
<div class="box">
|
||||
<div class="action">
|
||||
<ul class="record_actions">
|
||||
<li><button type="button" name="button">Annuler</button></li>
|
||||
<li><button type="button" name="button">Voir</button></li>
|
||||
<li><button type="button" name="button">Enregistrer</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="list-content fa-ul">
|
||||
<li><i class="fa fa-li fa-file-text-o"></i>Sed efficitur magna vel massa efficitur venenatis. Sed odio massa, scelerisque sit amet mauris eu, tristique dictum arcu. Sed posuere, elit eget cursus rhoncus, arcu ligula blandit nisi, in vulputate eros massa non risus.</li>
|
||||
<li><i class="fa fa-li fa-map-marker"></i>
|
||||
<div class="chill-entity entity-address my-3" data-v-8b2170ec="">
|
||||
<div class="address multiline" data-v-8b2170ec="">
|
||||
<p class="street" data-v-8b2170ec="">97, chemin Franck Julien, </p>
|
||||
<p class="postcode" data-v-8b2170ec="">1000 Bruxelles</p>
|
||||
<p class="country" data-v-8b2170ec="">Belgique</p>
|
||||
</div>
|
||||
<div class="address-more" data-v-8b2170ec="">
|
||||
<div data-v-8b2170ec="">
|
||||
<span class="corridor" data-v-8b2170ec="">
|
||||
<b data-v-8b2170ec="">Couloir</b>: 3
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><i class="fa fa-li fa-mobile"></i><a href="tel: +33 8 27 17 12 19">+33 8 27 17 12 19</a></li>
|
||||
<li><i class="fa fa-li fa-envelope-o"></i><a href="mailto: gusikowski.yesenia@hotmail.com">gusikowski.yesenia@hotmail.com</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<h2>2) avec des p</h2>
|
||||
|
||||
<div class="float-button bottom debug">
|
||||
<div class="box">
|
||||
<div class="action">
|
||||
<ul class="record_actions">
|
||||
<li><button type="button" name="button">Annuler</button></li>
|
||||
<li><button type="button" name="button">Voir</button></li>
|
||||
<li><button type="button" name="button">Enregistrer</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>Voir <a href="https://css-tricks.com/float-an-element-to-the-bottom-corner/">trick</a>.</p>
|
||||
<p>Sed efficitur magna vel massa efficitur venenatis. Sed odio massa, scelerisque sit amet mauris eu, tristique dictum arcu. Sed posuere, elit eget cursus rhoncus, arcu ligula blandit nisi, in vulputate eros massa non risus. Proin lacinia, sapien in pharetra ultricies, justo urna fermentum lectus, non tempor ipsum leo a ante. Aenean porta, ipsum in fringilla hendrerit, nisi justo vestibulum ex, non lacinia risus felis vitae diam. Curabitur sem eros, consectetur a auctor vel, facilisis sit amet sem.</p>
|
||||
<p>Aenean finibus a nisl a scelerisque. Donec bibendum facilisis odio id euismod. Pellentesque luctus justo ligula, eget dictum ligula ultrices quis. Pellentesque at nunc est. Aenean luctus, tortor in lacinia porta, ex nisl dignissim magna, non vehicula elit risus at elit. Suspendisse in velit non augue egestas laoreet. Etiam blandit lacus at semper aliquam. Integer leo nunc, condimentum sagittis accumsan sit amet, consectetur vel massa. Aenean convallis nibh vel augue ullamcorper tempus. Integer eu laoreet sapien.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>3) avec des div</h2>
|
||||
<div class="float-button bottom debug">
|
||||
<div class="box">
|
||||
<div class="action">
|
||||
<ul class="record_actions">
|
||||
<li><button type="button" name="button">Annuler</button></li>
|
||||
<li><button type="button" name="button">Voir</button></li>
|
||||
<li><button type="button" name="button">Enregistrer</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>Voir <a href="https://css-tricks.com/float-an-element-to-the-bottom-corner/">trick</a>.</div>
|
||||
<div>Sed efficitur magna vel massa efficitur venenatis. Sed odio massa, scelerisque sit amet mauris eu, tristique dictum arcu. Sed posuere, elit eget cursus rhoncus, arcu ligula blandit nisi, in vulputate eros massa non risus. Proin lacinia, sapien in pharetra ultricies, justo urna fermentum lectus, non tempor ipsum leo a ante. Aenean porta, ipsum in fringilla hendrerit, nisi justo vestibulum ex, non lacinia risus felis vitae diam.
|
||||
<a href="#">Curabitur</a> sem eros, consectetur a auctor vel, facilisis sit amet sem.</div>
|
||||
<div>Aenean finibus a nisl a scelerisque. Donec bibendum facilisis odio id euismod. Pellentesque luctus justo ligula, eget dictum ligula ultrices quis. Pellentesque at nunc est. Aenean luctus, tortor in lacinia porta, ex nisl dignissim magna, non vehicula elit risus at elit. Suspendisse in velit non augue egestas laoreet. Etiam blandit lacus at semper aliquam. Integer leo nunc, condimentum sagittis accumsan sit amet, consectetur vel massa. Aenean convallis nibh vel augue ullamcorper tempus. Integer eu laoreet sapien.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
@@ -0,0 +1,78 @@
|
||||
{% extends '@ChillMain/layout.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
SASS Assets Tests - page 2
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-10">
|
||||
<h1>CSS Tests - page 2: grid layout</h1>
|
||||
|
||||
<h2>1) mgrid 1-2: avec grid-column et grid-row</h2>
|
||||
|
||||
<div class="mgrid debug">
|
||||
<div class="area1">
|
||||
Nam rhoncus tristique ligula, tincidunt iaculis augue tincidunt ac. Proin fermentum mauris quam, ut suscipit nisl auctor at. Ut vestibulum ligula eget ex congue, efficitur interdum ipsum tincidunt. Integer id sapien et nibh tristique viverra et a dui. Ut blandit pharetra consectetur. Sed scelerisque eget purus at tempus. Etiam sit amet tellus et eros semper tempor. Curabitur suscipit pulvinar enim at lobortis. Ut nisl augue, cursus vel hendrerit sed, posuere vel sapien. Proin hendrerit arcu velit, eu ultrices dui interdum eget. Vestibulum consectetur sodales enim a accumsan. In vitae tristique leo, a fringilla nisl.
|
||||
</div>
|
||||
<div class="area2">
|
||||
<ul class="record_actions">
|
||||
<li><button type="button" name="button">Annuler</button></li>
|
||||
<li><button type="button" name="button">Voir</button></li>
|
||||
<li><button type="button" name="button">Enregistrer</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>2) lgrid 3-4: avec grid-template-areas</h2>
|
||||
|
||||
<div class="lgrid debug">
|
||||
<div class="area3">
|
||||
<i>La zone qu'on crée avec les noms doit être rectangulaires. Actuellement, il n'existe pas de méthode pour créer une zone avec une forme de L (bien que la spécification indique qu'une prochaine version pourrait couvrir cette fonctionnalité).
|
||||
[...] Si des zones ne sont pas rectangulaires, cela sera également considéré comme invalide.</i>
|
||||
Voir sur MDN: <a target="_blank" href="https://developer.mozilla.org/fr/docs/Web/CSS/CSS_Grid_Layout/Grid_Template_Areas#occuper_plusieurs_cellules">Définir des zones sur une grille</a>
|
||||
|
||||
</div>
|
||||
<div class="area4">
|
||||
<ul class="record_actions">
|
||||
<li><button type="button" name="button">Annuler</button></li>
|
||||
<li><button type="button" name="button">Voir</button></li>
|
||||
<li><button type="button" name="button">Enregistrer</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>3) cgrid 5-6-7-8: avec masonry</h2>
|
||||
<p>Expérimental: dans FF <i>about:config</i>, il faut mettre <i>layout.css.grid-template-masonry-value.enabled = true</i></p>
|
||||
|
||||
<div class="cgrid debug">
|
||||
<div class="item">
|
||||
1 Integer id sapien et nibh tristique viverra et a dui. Ut blandit pharetra consectetur. Sed scelerisque eget purus at tempus. Etiam sit amet tellus et eros semper tempor. Curabitur suscipit pulvinar enim at lobortis. Ut nisl augue, cursus vel hendrerit sed, posuere vel sapien. Proin hendrerit arcu velit, eu ultrices dui interdum eget. Vestibulum consectetur sodales enim a accumsan. In vitae tristique leo, a fringilla nisl.
|
||||
</div>
|
||||
<div class="item">
|
||||
2 Sed scelerisque eget purus at tempus. Etiam sit amet tellus et eros semper tempor. Curabitur suscipit pulvinar enim at lobortis. Ut nisl augue, cursus vel hendrerit sed, posuere vel sapien. Proin hendrerit arcu velit, eu ultrices dui interdum eget. Vestibulum consectetur sodales enim a accumsan. In vitae tristique leo, a fringilla nisl.
|
||||
</div>
|
||||
<div class="item">
|
||||
3 Curabitur suscipit pulvinar enim at lobortis. Ut nisl augue, cursus vel hendrerit sed, posuere vel sapien. Proin hendrerit arcu velit, eu ultrices dui interdum eget. Vestibulum consectetur sodales enim a accumsan. In vitae tristique leo, a fringilla nisl.
|
||||
</div>
|
||||
<div class="item">
|
||||
4 Proin hendrerit arcu velit, eu ultrices dui interdum eget. Vestibulum consectetur sodales enim a accumsan. In vitae tristique leo, a fringilla nisl.
|
||||
</div>
|
||||
<div class="item">
|
||||
5 Proin hendrerit arcu velit, eu ultrices dui interdum eget. Vestibulum consectetur sodales enim a accumsan.
|
||||
</div>
|
||||
<div class="item">
|
||||
6 Proin hendrerit arcu velit, eu ultrices dui interdum eget. Vestibulum consectetur sodales enim.
|
||||
</div>
|
||||
<div class="item">
|
||||
7 Proin hendrerit arcu velit, eu ultrices dui interdum eget.
|
||||
</div>
|
||||
<div class="item">
|
||||
8 Eu ultrices dui interdum eget.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
@@ -22,15 +22,13 @@
|
||||
|
||||
{% block js %}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener("DOMContentLoaded", function(e) {
|
||||
var url = "{{ path('chill_main_export_generate', { 'alias' : alias } ) }}",
|
||||
query = window.location.search,
|
||||
container = document.querySelector("#download_container")
|
||||
;
|
||||
|
||||
chill.download_report(url+query, container);
|
||||
});
|
||||
window.export_generate_url = "{{ path('chill_main_export_generate', { 'alias' : alias } ) }}";
|
||||
</script>
|
||||
{{ encore_entry_link_tags('page_download_exports') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_script_tags('page_download_exports') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@@ -14,9 +14,9 @@ namespace Chill\MainBundle\Search\Utils;
|
||||
class ExtractDateFromPattern
|
||||
{
|
||||
private const DATE_PATTERN = [
|
||||
['([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))', 'Y-m-d'], // 1981-05-12
|
||||
['((0[1-9]|[12]\\d|3[01])\\/(0[1-9]|1[0-2])\\/([12]\\d{3}))', 'd/m/Y'], // 15/12/1980
|
||||
['((0[1-9]|[12]\\d|3[01])-(0[1-9]|1[0-2])-([12]\\d{3}))', 'd-m-Y'], // 15/12/1980
|
||||
['([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))', 'Y-m-d'], // 1981-05-12
|
||||
['((0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/([12]\d{3}))', 'd/m/Y'], // 15/12/1980
|
||||
['((0[1-9]|[12]\d|3[01])-(0[1-9]|1[0-2])-([12]\d{3}))', 'd-m-Y'], // 15/12/1980
|
||||
];
|
||||
|
||||
public function extractDates(string $subject): SearchExtractionResult
|
||||
|
@@ -16,7 +16,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
|
||||
class ExtractPhonenumberFromPattern
|
||||
{
|
||||
private const PATTERN = '([\\+]{0,1}[0-9\\ ]{5,})';
|
||||
private const PATTERN = '([\+]{0,1}[0-9\ ]{5,})';
|
||||
|
||||
private readonly string $defaultCarrierCode;
|
||||
|
||||
|
@@ -18,7 +18,7 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class AddressReferenceBEFromBestAddress
|
||||
{
|
||||
private const RELEASE = 'https://gitea.champs-libres.be/api/v1/repos/Chill-project/belgian-bestaddresses-transform/releases/tags/v1.0.0';
|
||||
private const RELEASE = 'https://gitea.champs-libres.be/api/v1/repos/Chill-project/belgian-bestaddresses-transform/releases/tags/v1.1.1';
|
||||
|
||||
public function __construct(private readonly HttpClientInterface $client, private readonly AddressReferenceBaseImporter $baseImporter, private readonly AddressToReferenceMatcher $addressToReferenceMatcher) {}
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
const CKEditorWebpackPlugin = require( '@ckeditor/ckeditor5-dev-webpack-plugin' );
|
||||
const { styles } = require( '@ckeditor/ckeditor5-dev-utils' );
|
||||
const {CKEditorTranslationsPlugin} = require("@ckeditor/ckeditor5-dev-translations");
|
||||
|
||||
buildCKEditor = function(encore)
|
||||
{
|
||||
encore
|
||||
.addPlugin( new CKEditorWebpackPlugin( {
|
||||
.addPlugin( new CKEditorTranslationsPlugin( {
|
||||
language: 'fr',
|
||||
addMainLanguageTranslationsToAllAssets: true,
|
||||
verbose: !encore.isProduction(),
|
||||
@@ -52,12 +52,14 @@ module.exports = function(encore, entries)
|
||||
Tabs: __dirname + '/Resources/public/lib/tabs'
|
||||
});
|
||||
|
||||
|
||||
// Page entrypoints
|
||||
encore.addEntry('page_login', __dirname + '/Resources/public/page/login/index.js');
|
||||
encore.addEntry('page_location', __dirname + '/Resources/public/page/location/index.js');
|
||||
encore.addEntry('page_workflow_show', __dirname + '/Resources/public/page/workflow-show/index.js');
|
||||
encore.addEntry('page_homepage_widget', __dirname + '/Resources/public/page/homepage_widget/index.js');
|
||||
encore.addEntry('page_export', __dirname + '/Resources/public/page/export/index.js');
|
||||
encore.addEntry('page_download_exports', __dirname + '/Resources/public/page/export/download-export.js');
|
||||
|
||||
buildCKEditor(encore);
|
||||
|
||||
@@ -65,7 +67,7 @@ module.exports = function(encore, entries)
|
||||
encore.addEntry('mod_collection', __dirname + '/Resources/public/module/collection/index.ts');
|
||||
encore.addEntry('mod_forkawesome', __dirname + '/Resources/public/module/forkawesome/index.js');
|
||||
encore.addEntry('mod_bootstrap', __dirname + '/Resources/public/module/bootstrap/index.js');
|
||||
encore.addEntry('mod_ckeditor5', __dirname + '/Resources/public/module/ckeditor5/index.js');
|
||||
encore.addEntry('mod_ckeditor5', __dirname + '/Resources/public/module/ckeditor5/index');
|
||||
encore.addEntry('mod_disablebuttons', __dirname + '/Resources/public/module/disable-buttons/index.js');
|
||||
encore.addEntry('mod_blur', __dirname + '/Resources/public/module/blur/index.js');
|
||||
encore.addEntry('mod_input_address', __dirname + '/Resources/public/vuejs/Address/mod_input_address_index.js');
|
||||
|
@@ -52,7 +52,7 @@
|
||||
|
||||
<script>
|
||||
import CKEditor from '@ckeditor/ckeditor5-vue';
|
||||
import ClassicEditor from "ChillMainAssets/module/ckeditor5";
|
||||
import ClassicEditor from "../../../../../../ChillMainBundle/Resources/public/module/ckeditor5/editor_config";
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
|
@@ -41,7 +41,7 @@
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
import CKEditor from '@ckeditor/ckeditor5-vue';
|
||||
import ClassicEditor from "ChillMainAssets/module/ckeditor5";
|
||||
import ClassicEditor from "ChillMainAssets/module/ckeditor5/editor_config";
|
||||
|
||||
export default {
|
||||
name: "WriteComment",
|
||||
|
@@ -331,7 +331,7 @@
|
||||
import {mapState, mapGetters,} from 'vuex';
|
||||
import {dateToISO, ISOToDate, ISOToDatetime} from 'ChillMainAssets/chill/js/date';
|
||||
import CKEditor from '@ckeditor/ckeditor5-vue';
|
||||
import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js';
|
||||
import ClassicEditor from 'ChillMainAssets/module/ckeditor5/editor_config';
|
||||
import AddResult from './components/AddResult.vue';
|
||||
import AddEvaluation from './components/AddEvaluation.vue';
|
||||
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
|
||||
|
@@ -195,7 +195,7 @@
|
||||
<script>
|
||||
import {dateToISO, ISOToDate, ISOToDatetime} from 'ChillMainAssets/chill/js/date';
|
||||
import CKEditor from '@ckeditor/ckeditor5-vue';
|
||||
import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js';
|
||||
import ClassicEditor from 'ChillMainAssets/module/ckeditor5/editor_config';
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
|
||||
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
|
||||
|
@@ -75,7 +75,7 @@ div.participation-details {
|
||||
import { mapGetters } from 'vuex';
|
||||
import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue';
|
||||
import CKEditor from '@ckeditor/ckeditor5-vue';
|
||||
import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js';
|
||||
import ClassicEditor from 'ChillMainAssets/module/ckeditor5/editor_config';
|
||||
|
||||
export default {
|
||||
name: 'MemberDetails',
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
<script>
|
||||
import CKEditor from '@ckeditor/ckeditor5-vue';
|
||||
import ClassicEditor from "ChillMainAssets/module/ckeditor5";
|
||||
import ClassicEditor from "ChillMainAssets/module/ckeditor5/editor_config";
|
||||
|
||||
export default {
|
||||
name: "PersonComment.vue",
|
||||
|
@@ -15,7 +15,7 @@ const personMessages = {
|
||||
person: {
|
||||
firstname: "Prénom",
|
||||
lastname: "Nom",
|
||||
born: (ctx) => {
|
||||
born: (ctx: {gender: "man"|"woman"|"unknown"}) => {
|
||||
if (ctx.gender === 'man') {
|
||||
return 'Né le';
|
||||
} else if (ctx.gender === 'woman') {
|
@@ -74,7 +74,7 @@ final class AccompanyingCourseControllerTest extends WebTestCase
|
||||
$this->assertResponseRedirects();
|
||||
$location = $this->client->getResponse()->headers->get('Location');
|
||||
|
||||
$this->assertEquals(1, \preg_match('|^\\/[^\\/]+\\/parcours/([\\d]+)/edit$|', (string) $location));
|
||||
$this->assertEquals(1, \preg_match('|^\/[^\/]+\/parcours/([\d]+)/edit$|', (string) $location));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +93,7 @@ final class AccompanyingCourseControllerTest extends WebTestCase
|
||||
$location = $this->client->getResponse()->headers->get('Location');
|
||||
$matches = [];
|
||||
|
||||
$this->assertEquals(1, \preg_match('|^\\/[^\\/]+\\/parcours/([\\d]+)/edit$|', (string) $location, $matches));
|
||||
$this->assertEquals(1, \preg_match('|^\/[^\/]+\/parcours/([\d]+)/edit$|', (string) $location, $matches));
|
||||
$id = $matches[1];
|
||||
|
||||
$period = self::getContainer()->get(EntityManagerInterface::class)
|
||||
|
@@ -41,7 +41,7 @@ final class ChillReportExtensionTest extends KernelTestCase
|
||||
}
|
||||
|
||||
if (!$reportFounded) {
|
||||
throw new \Exception('Class Chill\\ReportBundle\\Entity\\Report not found in chill_custom_fields.customizables_entities', 1);
|
||||
throw new \Exception('Class Chill\ReportBundle\Entity\Report not found in chill_custom_fields.customizables_entities', 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -32,10 +32,10 @@ return static function (ContainerConfigurator $container) {
|
||||
->autoconfigure();
|
||||
|
||||
$services
|
||||
->load('Chill\\WopiBundle\\Service\\', __DIR__.'/../../Service');
|
||||
->load('Chill\WopiBundle\Service\\', __DIR__.'/../../Service');
|
||||
|
||||
$services
|
||||
->load('Chill\\WopiBundle\\Controller\\', __DIR__.'/../../Controller')
|
||||
->load('Chill\WopiBundle\Controller\\', __DIR__.'/../../Controller')
|
||||
->tag('controller.service_arguments');
|
||||
|
||||
$services
|
||||
|
@@ -43,6 +43,4 @@ return [
|
||||
ChampsLibres\WopiBundle\WopiBundle::class => ['all' => true],
|
||||
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
||||
\Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
|
||||
Chill\EventBundle\ChillEventBundle::class => ['all' => true],
|
||||
|
||||
];
|
||||
|
@@ -38,3 +38,5 @@ when@test:
|
||||
test: true
|
||||
session:
|
||||
storage_factory_id: session.storage.factory.mock_file
|
||||
assets:
|
||||
json_manifest_path: null
|
||||
|
@@ -1,6 +0,0 @@
|
||||
framework:
|
||||
test: true
|
||||
session:
|
||||
storage_id: session.storage.mock_file
|
||||
assets:
|
||||
json_manifest_path: NULL
|
Reference in New Issue
Block a user