mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge branch 'signature-app/parse-pdf' into 'signature-app-master'
Add PDF signature zone parsing functionality See merge request Chill-Projet/chill-bundles!703
This commit is contained in:
commit
b65e2c62c4
@ -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"
|
||||
|
@ -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';
|
||||
|
||||
|
28
src/Bundle/ChillDocStoreBundle/Service/Signature/PDFPage.php
Normal file
28
src/Bundle/ChillDocStoreBundle/Service/Signature/PDFPage.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocStoreBundle\Service\Signature;
|
||||
|
||||
final readonly class PDFPage
|
||||
{
|
||||
public function __construct(
|
||||
public int $index,
|
||||
public float $width,
|
||||
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,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;
|
||||
|
||||
final readonly class PDFSignatureZone
|
||||
{
|
||||
public function __construct(
|
||||
public float $x,
|
||||
public float $y,
|
||||
public float $height,
|
||||
public float $width,
|
||||
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,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.
@ -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]);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user