Revert "Merge branch 'signature-app/wp-576-restorestored-object-version' into 'master'"

This reverts merge request !586
This commit is contained in:
2024-09-19 13:26:12 +00:00
parent 671bb6d593
commit e4d0705e84
1701 changed files with 14541 additions and 35017 deletions

View File

@@ -1,24 +0,0 @@
<?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;
/**
* Message which is received when a pdf is signed.
*/
final readonly class PdfSignedMessage
{
public function __construct(
public readonly int $signatureId,
public readonly ?int $signatureZoneIndex,
public readonly string $content,
) {}
}

View File

@@ -1,61 +0,0 @@
<?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\StoredObjectManagerInterface;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowStepSignatureRepository;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Chill\MainBundle\Workflow\SignatureStepStateChanger;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
final readonly class PdfSignedMessageHandler implements MessageHandlerInterface
{
/**
* log prefix.
*/
private const P = '[pdf signed message] ';
public function __construct(
private LoggerInterface $logger,
private EntityWorkflowManager $entityWorkflowManager,
private StoredObjectManagerInterface $storedObjectManager,
private EntityWorkflowStepSignatureRepository $entityWorkflowStepSignatureRepository,
private EntityManagerInterface $entityManager,
private SignatureStepStateChanger $signatureStepStateChanger,
) {}
public function __invoke(PdfSignedMessage $message): void
{
$this->logger->info(self::P.'a message is received', ['signaturedId' => $message->signatureId]);
$signature = $this->entityWorkflowStepSignatureRepository->find($message->signatureId);
if (null === $signature) {
throw new \RuntimeException('no signature found');
}
$storedObject = $this->entityWorkflowManager->getAssociatedStoredObject($signature->getStep()->getEntityWorkflow());
if (null === $storedObject) {
throw new \RuntimeException('no stored object found');
}
$this->storedObjectManager->write($storedObject, $message->content);
$this->signatureStepStateChanger->markSignatureAsSigned($signature, $message->signatureZoneIndex);
$this->entityManager->flush();
$this->entityManager->clear();
}
}

View File

@@ -1,67 +0,0 @@
<?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 Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
/**
* Decode (and requeue) @see{PdfSignedMessage}, which comes from an external producer.
*/
final readonly class PdfSignedMessageSerializer implements SerializerInterface
{
public function decode(array $encodedEnvelope): Envelope
{
$body = $encodedEnvelope['body'];
try {
$decoded = json_decode((string) $body, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new MessageDecodingFailedException('Could not deserialize message', previous: $e);
}
if (!array_key_exists('signatureId', $decoded) || !array_key_exists('content', $decoded)) {
throw new MessageDecodingFailedException('Could not find expected keys: signatureId or content');
}
$content = base64_decode((string) $decoded['content'], true);
if (false === $content) {
throw new MessageDecodingFailedException('Invalid character found in the base64 encoded content');
}
$message = new PdfSignedMessage($decoded['signatureId'], $decoded['signatureZoneIndex'], $content);
return new Envelope($message);
}
public function encode(Envelope $envelope): array
{
$message = $envelope->getMessage();
if (!$message instanceof PdfSignedMessage) {
throw new MessageDecodingFailedException('Expected a PdfSignedMessage');
}
$data = [
'signatureId' => $message->signatureId,
'signatureZoneIndex' => $message->signatureZoneIndex,
'content' => base64_encode($message->content),
];
return [
'body' => json_encode($data, JSON_THROW_ON_ERROR),
'headers' => [],
];
}
}

View File

@@ -1,29 +0,0 @@
<?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;
/**
* Message which is sent when we request a signature on a pdf.
*/
final readonly class RequestPdfSignMessage
{
public function __construct(
public int $signatureId,
public PDFSignatureZone $PDFSignatureZone,
public ?int $signatureZoneIndex,
public string $reason,
public string $signerText,
public string $content,
) {}
}

View File

@@ -1,105 +0,0 @@
<?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;
/**
* Serialize a RequestPdfSignMessage, for external consumer.
*/
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((string) $body, true);
$zoneSignature = $this->denormalizer->denormalize($data['signatureZone'], PDFSignatureZone::class, 'json', [
AbstractNormalizer::GROUPS => ['write'],
]);
$content = base64_decode((string) $data['content'], true);
if (false === $content) {
throw new MessageDecodingFailedException('the content could not be converted from base64 encoding');
}
$message = new RequestPdfSignMessage(
$data['signatureId'],
$zoneSignature,
$data['signatureZoneIndex'],
$data['reason'],
$data['signerText'],
$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,
'signerText' => $message->signerText,
'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,
],
];
}
}

View File

@@ -1,33 +0,0 @@
<?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);
}
}

View File

@@ -1,43 +0,0 @@
<?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 ?int $index,
#[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->index == $other->index
&& $this->x == $other->x
&& $this->y == $other->y
&& $this->height == $other->height
&& $this->width == $other->width
&& $this->PDFPage->equals($other->PDFPage);
}
}

View File

@@ -1,69 +0,0 @@
<?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 Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
class PDFSignatureZoneAvailable
{
public function __construct(
private readonly EntityWorkflowManager $entityWorkflowManager,
private readonly PDFSignatureZoneParser $pdfSignatureZoneParser,
private readonly StoredObjectManagerInterface $storedObjectManager,
) {}
/**
* @return list<PDFSignatureZone>
*/
public function getAvailableSignatureZones(EntityWorkflow $entityWorkflow): array
{
$storedObject = $this->entityWorkflowManager->getAssociatedStoredObject($entityWorkflow);
if (null === $storedObject) {
throw new \RuntimeException('No stored object found');
}
if ('application/pdf' !== $storedObject->getType()) {
throw new \RuntimeException('Only PDF documents are supported');
}
$zones = $this->pdfSignatureZoneParser->findSignatureZones($this->storedObjectManager->read($storedObject));
$signatureZonesIndexes = array_map(
fn (EntityWorkflowStepSignature $step) => $step->getZoneSignatureIndex(),
$this->collectSignaturesInUse($entityWorkflow)
);
return array_values(array_filter($zones, fn (PDFSignatureZone $zone) => !in_array($zone->index, $signatureZonesIndexes, true)));
}
/**
* @return list<EntityWorkflowStepSignature>
*/
private function collectSignaturesInUse(EntityWorkflow $entityWorkflow): array
{
return array_reduce($entityWorkflow->getSteps()->toArray(), function (array $result, EntityWorkflowStep $step) {
$current = [...$result];
foreach ($step->getSignatures() as $signature) {
if (EntityWorkflowSignatureStateEnum::SIGNED === $signature->getState()) {
$current[] = $signature;
}
}
return $current;
}, []);
}
}

View File

@@ -1,60 +0,0 @@
<?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 readonly Parser $parser;
public function __construct(
public float $defaultHeight = 90.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();
$zoneIndex = 0;
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((string) $dataTm[1], self::ZONE_SIGNATURE_START)) {
$zones[] = new PDFSignatureZone($zoneIndex, (float) $dataTm[0][4], (float) $dataTm[0][5], $this->defaultHeight, $this->defaultWidth, $pdfPage);
++$zoneIndex;
}
}
}
return $zones;
}
}