Skip to content

Commit e5c5616

Browse files
committed
Merge branch 'master' of github.com:nowo-tech/PdfSignableBundle
2 parents 4bbaf30 + 81ee1fb commit e5c5616

21 files changed

Lines changed: 144 additions & 130 deletions

src/AcroForm/AcroFormFieldEdit.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public static function fromArray(array $data): self
6060
$lines = [];
6161
foreach ($data['options'] as $o) {
6262
if (\is_array($o) && isset($o['value'])) {
63-
$lines[] = isset($o['label']) && $o['label'] !== $o['value'] ? $o['value'] . '|' . $o['label'] : $o['value'];
63+
$lines[] = isset($o['label']) && $o['label'] !== $o['value'] ? $o['value'].'|'.$o['label'] : $o['value'];
6464
} elseif (\is_string($o)) {
6565
$lines[] = $o;
6666
}

src/AcroForm/AcroFormFieldPatch.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function __construct(
4949
* Supports both camelCase (fieldId, defaultValue, …) and snake_case (field_id, default_value, …) keys.
5050
*
5151
* @param array<string, mixed> $data Associative array with at least fieldId (or field_id)
52-
* @return self
52+
*
5353
* @throws \InvalidArgumentException If fieldId is missing or empty
5454
*/
5555
public static function fromArray(array $data): self

src/AcroForm/AcroFormOverrides.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
final class AcroFormOverrides
1414
{
1515
/**
16-
* @param array<string, array<string, mixed>> $overrides Map fieldId → override data
17-
* @param array<int, array<string, mixed>>|null $fields Optional list of PDF field definitions (id, rect, fieldType, page, value?)
16+
* @param array<string, array<string, mixed>> $overrides Map fieldId → override data
17+
* @param array<int, array<string, mixed>>|null $fields Optional list of PDF field definitions (id, rect, fieldType, page, value?)
1818
*/
1919
public function __construct(
2020
public readonly array $overrides,
@@ -37,7 +37,7 @@ public static function fromArray(array $data): self
3737
$documentKey = null;
3838
}
3939
$fields = $data['fields'] ?? null;
40-
if ($fields !== null && !\is_array($fields)) {
40+
if (null !== $fields && !\is_array($fields)) {
4141
$fields = null;
4242
}
4343

@@ -53,7 +53,7 @@ public function toArray(): array
5353
if (null !== $this->documentKey) {
5454
$out['document_key'] = $this->documentKey;
5555
}
56-
if (null !== $this->fields && $this->fields !== []) {
56+
if (null !== $this->fields && [] !== $this->fields) {
5757
$out['fields'] = $this->fields;
5858
}
5959

src/AcroForm/PythonProcessEnv.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ public static function build(): array
3737
);
3838
$env['PATH'] = '/usr/local/bin:/usr/bin:/bin'.(isset($env['PATH']) && '' !== $env['PATH'] ? ':'.$env['PATH'] : '');
3939

40-
return array_filter($env, static fn ($v): bool => $v !== false);
40+
return array_filter($env, static fn ($v): bool => false !== $v);
4141
}
4242
}

src/AcroForm/Storage/SessionAcroFormOverridesStorage.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ public function __construct(
2828
) {
2929
}
3030

31-
/**
32-
* {@inheritdoc}
33-
*/
3431
public function get(string $documentKey): ?AcroFormOverrides
3532
{
3633
$session = $this->requestStack->getCurrentRequest()?->getSession();
@@ -46,9 +43,6 @@ public function get(string $documentKey): ?AcroFormOverrides
4643
return AcroFormOverrides::fromArray($data);
4744
}
4845

49-
/**
50-
* {@inheritdoc}
51-
*/
5246
public function set(string $documentKey, AcroFormOverrides $overrides): void
5347
{
5448
$session = $this->requestStack->getCurrentRequest()?->getSession();
@@ -59,9 +53,6 @@ public function set(string $documentKey, AcroFormOverrides $overrides): void
5953
$session->set($key, $overrides->toArray());
6054
}
6155

62-
/**
63-
* {@inheritdoc}
64-
*/
6556
public function remove(string $documentKey): void
6657
{
6758
$session = $this->requestStack->getCurrentRequest()?->getSession();

src/Controller/AcroFormOverridesController.php

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66

77
use Nowo\PdfSignableBundle\AcroForm\AcroFormFieldPatch;
88
use Nowo\PdfSignableBundle\AcroForm\AcroFormOverrides;
9-
use Nowo\PdfSignableBundle\AcroForm\PythonProcessEnv;
109
use Nowo\PdfSignableBundle\AcroForm\Exception\AcroFormEditorException;
1110
use Nowo\PdfSignableBundle\AcroForm\PdfAcroFormEditorInterface;
11+
use Nowo\PdfSignableBundle\AcroForm\PythonProcessEnv;
1212
use Nowo\PdfSignableBundle\AcroForm\Storage\AcroFormOverridesStorageInterface;
1313
use Nowo\PdfSignableBundle\Event\AcroFormApplyRequestEvent;
1414
use Nowo\PdfSignableBundle\Event\AcroFormModifiedPdfProcessedEvent;
1515
use Nowo\PdfSignableBundle\Event\PdfSignableEvents;
16+
use Psr\Log\LoggerInterface;
1617
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1718
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1819
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -25,7 +26,6 @@
2526
use Symfony\Component\Routing\Attribute\Route;
2627
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
2728
use Symfony\Contracts\Translation\TranslatorInterface;
28-
use Psr\Log\LoggerInterface;
2929

3030
/**
3131
* AcroForm overrides and PDF apply/process REST controller.
@@ -51,20 +51,20 @@ final class AcroFormOverridesController extends AbstractController
5151
private const DOCUMENT_KEY_MAX_LENGTH = 256;
5252

5353
/**
54-
* @param bool $enabled Whether the AcroForm editor endpoints are enabled
55-
* @param AcroFormOverridesStorageInterface $storage Storage for overrides (session or custom service)
56-
* @param bool $allowPdfModify Whether POST /acroform/apply is allowed
57-
* @param PdfAcroFormEditorInterface|null $editor Optional PHP-based editor to apply patches
58-
* @param EventDispatcherInterface $eventDispatcher Dispatcher for ACROFORM_APPLY_REQUEST and ACROFORM_MODIFIED_PDF_PROCESSED
59-
* @param TranslatorInterface $translator Used for error messages
60-
* @param list<string> $proxyUrlAllowlist URL allowlist for pdf_url (when fetching PDFs)
61-
* @param int $maxPdfSize Max PDF size in bytes for apply/process
62-
* @param int $maxPatches Max number of patches per apply request
63-
* @param string|null $fieldsExtractorScript Path to Python script to extract AcroForm fields
64-
* @param string|null $processScript Path to Python script to process modified PDF
65-
* @param string $processScriptCommand Executable to run process_script (e.g. python3)
66-
* @param bool $debug When true, allow validate_only in apply (dry-run)
67-
* @param LoggerInterface|null $logger Optional logger for apply debug (when debug=true)
54+
* @param bool $enabled Whether the AcroForm editor endpoints are enabled
55+
* @param AcroFormOverridesStorageInterface $storage Storage for overrides (session or custom service)
56+
* @param bool $allowPdfModify Whether POST /acroform/apply is allowed
57+
* @param PdfAcroFormEditorInterface|null $editor Optional PHP-based editor to apply patches
58+
* @param EventDispatcherInterface $eventDispatcher Dispatcher for ACROFORM_APPLY_REQUEST and ACROFORM_MODIFIED_PDF_PROCESSED
59+
* @param TranslatorInterface $translator Used for error messages
60+
* @param list<string> $proxyUrlAllowlist URL allowlist for pdf_url (when fetching PDFs)
61+
* @param int $maxPdfSize Max PDF size in bytes for apply/process
62+
* @param int $maxPatches Max number of patches per apply request
63+
* @param string|null $fieldsExtractorScript Path to Python script to extract AcroForm fields
64+
* @param string|null $processScript Path to Python script to process modified PDF
65+
* @param string $processScriptCommand Executable to run process_script (e.g. python3)
66+
* @param bool $debug When true, allow validate_only in apply (dry-run)
67+
* @param LoggerInterface|null $logger Optional logger for apply debug (when debug=true)
6868
*/
6969
public function __construct(
7070
#[Autowire(param: 'nowo_pdf_signable.acroform.enabled')]
@@ -99,6 +99,7 @@ public function __construct(
9999
* document_key can be passed as query parameter or (for consistency) in the request body.
100100
*
101101
* @param Request $request Request containing document_key (query or body)
102+
*
102103
* @return Response JSON with overrides and document_key, or 400 if document_key missing/invalid, 404 if not found
103104
*/
104105
#[Route('/acroform/overrides', name: 'nowo_pdf_signable_acroform_overrides', methods: ['GET'])]
@@ -129,6 +130,7 @@ public function getOverrides(Request $request): Response
129130
* - If extraction fails, response includes fields_extractor_error (message only; overrides still returned).
130131
*
131132
* @param Request $request Request body must contain document_key; may contain pdf_url, pdf_content, or fields
133+
*
132134
* @return Response JSON with overrides, document_key, and optionally fields and fields_extractor_error
133135
*/
134136
#[Route('/acroform/overrides/load', name: 'nowo_pdf_signable_acroform_overrides_load', methods: ['POST'])]
@@ -231,6 +233,7 @@ public function loadOverrides(Request $request): Response
231233
* Overrides are stored per document_key; existing value is replaced.
232234
*
233235
* @param Request $request Request body with document_key, overrides, and optionally fields
236+
*
234237
* @return Response JSON with saved overrides and document_key, or 400 if document_key missing/invalid
235238
*/
236239
#[Route('/acroform/overrides', name: 'nowo_pdf_signable_acroform_overrides_save', methods: ['POST'])]
@@ -250,7 +253,7 @@ public function saveOverrides(Request $request): Response
250253
$overridesData = [];
251254
}
252255
$fields = $data['fields'] ?? null;
253-
if ($fields !== null && !\is_array($fields)) {
256+
if (null !== $fields && !\is_array($fields)) {
254257
$fields = null;
255258
}
256259
$overrides = new AcroFormOverrides($overridesData, $documentKey, $fields);
@@ -266,6 +269,7 @@ public function saveOverrides(Request $request): Response
266269
* The script is invoked with a single argument (path to a temporary PDF file); stdout must be JSON array.
267270
*
268271
* @param Request $request Request body with pdf_url or pdf_content
272+
*
269273
* @return Response JSON with "fields" array of field descriptors, or 400/404/503 on error
270274
*/
271275
#[Route('/acroform/fields/extract', name: 'nowo_pdf_signable_acroform_fields_extract', methods: ['POST'])]
@@ -329,6 +333,7 @@ public function extractFields(Request $request): Response
329333
* No error is returned if no overrides existed for that key.
330334
*
331335
* @param Request $request Request containing document_key (query or body)
336+
*
332337
* @return Response 204 No Content on success, or 400 if document_key missing/invalid
333338
*/
334339
#[Route('/acroform/overrides', name: 'nowo_pdf_signable_acroform_overrides_remove', methods: ['DELETE'])]
@@ -358,6 +363,7 @@ public function removeOverrides(Request $request): Response
358363
* provides a PDF, returns 501 Not Implemented.
359364
*
360365
* @param Request $request Request body with patches and pdf_url or pdf_content; optional validate_only when debug
366+
*
361367
* @return Response application/pdf with modified PDF, or JSON (validation/error), or 501 with plain text
362368
*/
363369
#[Route('/acroform/apply', name: 'nowo_pdf_signable_acroform_apply', methods: ['POST'])]
@@ -430,7 +436,7 @@ public function apply(Request $request): Response
430436
}
431437

432438
$validateOnly = $this->debug && !empty($data['validate_only']);
433-
if ($this->debug && $this->logger !== null) {
439+
if ($this->debug && null !== $this->logger) {
434440
$this->logger->info('AcroForm apply request', [
435441
'has_pdf_content' => isset($data['pdf_content']),
436442
'has_pdf_url' => isset($data['pdf_url']),
@@ -444,26 +450,29 @@ public function apply(Request $request): Response
444450
$this->eventDispatcher->dispatch($event, PdfSignableEvents::ACROFORM_APPLY_REQUEST);
445451

446452
if (null !== $event->getValidationResult()) {
447-
if ($this->debug && $this->logger !== null) {
453+
if ($this->debug && null !== $this->logger) {
448454
$this->logger->info('AcroForm apply response: validation_result (JSON)');
449455
}
456+
450457
return new JsonResponse($event->getValidationResult(), Response::HTTP_OK);
451458
}
452459
if (null !== $event->getError()) {
453460
$payload = ['error' => $event->getError()->getMessage()];
454461
if (null !== $event->getErrorDetail()) {
455462
$payload['detail'] = $event->getErrorDetail();
456463
}
457-
if ($this->debug && $this->logger !== null) {
464+
if ($this->debug && null !== $this->logger) {
458465
$this->logger->warning('AcroForm apply response: error', ['error' => $payload['error'], 'detail' => $payload['detail'] ?? null]);
459466
}
467+
460468
return new JsonResponse($payload, Response::HTTP_BAD_REQUEST);
461469
}
462470
if (null !== $event->getModifiedPdf()) {
463471
$modified = $event->getModifiedPdf();
464-
if ($this->debug && $this->logger !== null) {
472+
if ($this->debug && null !== $this->logger) {
465473
$this->logger->info('AcroForm apply response: modified PDF', ['pdf_output_bytes' => \strlen($modified)]);
466474
}
475+
467476
return new Response($modified, Response::HTTP_OK, [
468477
'Content-Type' => 'application/pdf',
469478
'Content-Disposition' => 'inline; filename="document.pdf"',
@@ -493,9 +502,10 @@ public function apply(Request $request): Response
493502
}
494503
}
495504

496-
if ($this->debug && $this->logger !== null) {
505+
if ($this->debug && null !== $this->logger) {
497506
$this->logger->warning('AcroForm apply response: 501 No editor configured and event did not set modified PDF');
498507
}
508+
499509
return new Response('No editor configured', Response::HTTP_NOT_IMPLEMENTED);
500510
}
501511

@@ -507,6 +517,7 @@ public function apply(Request $request): Response
507517
* processed PDF to the output path. Then ACROFORM_MODIFIED_PDF_PROCESSED is dispatched with the result.
508518
*
509519
* @param Request $request Request body with pdf_content (base64), optional document_key
520+
*
510521
* @return Response 200 JSON { success: true, document_key?: string }, or application/pdf if Accept header requests it
511522
*/
512523
#[Route('/acroform/process', name: 'nowo_pdf_signable_acroform_process', methods: ['POST'])]
@@ -558,6 +569,7 @@ public function process(Request $request): Response
558569
if (!$proc->isSuccessful()) {
559570
$err = $proc->getErrorOutput()."\n".$proc->getOutput();
560571
$isPythonNotFound = str_contains(strtolower($err), 'not found') && str_contains(strtolower($err), 'python');
572+
561573
return new JsonResponse([
562574
'error' => $isPythonNotFound
563575
? 'Process script failed: Python 3 is not installed or not in PATH. Install python3 on the server or set process_script_command to the full path of your Python executable.'
@@ -601,6 +613,7 @@ public function process(Request $request): Response
601613
* or pdf_content (base64-decoded). Uses the same allowlist and SSRF rules as the apply endpoint.
602614
*
603615
* @param Request $request Request whose body may contain pdf_url or pdf_content
616+
*
604617
* @return string|null PDF binary content, or null if missing, invalid, or URL not allowed
605618
*/
606619
private function resolvePdfContentsFromRequest(Request $request): ?string
@@ -647,8 +660,9 @@ private function resolvePdfContentsFromRequest(Request $request): ?string
647660
*
648661
* Prefer body['document_key'] when body is provided; otherwise query or request parameter.
649662
*
650-
* @param Request $request Request to read document_key from
651-
* @param array<string, mixed>|null $body Optional pre-parsed body (e.g. from $request->toArray())
663+
* @param Request $request Request to read document_key from
664+
* @param array<string, mixed>|null $body Optional pre-parsed body (e.g. from $request->toArray())
665+
*
652666
* @return string|null The document key if present and valid, null otherwise
653667
*/
654668
private function resolveDocumentKey(Request $request, ?array $body): ?string
@@ -670,6 +684,7 @@ private function resolveDocumentKey(Request $request, ?array $body): ?string
670684
* Allowed: non-empty, length <= DOCUMENT_KEY_MAX_LENGTH, characters [a-zA-Z0-9_.-].
671685
*
672686
* @param string $documentKey The document key to validate
687+
*
673688
* @return bool True if the key is valid
674689
*/
675690
private function isValidDocumentKey(string $documentKey): bool
@@ -687,6 +702,7 @@ private function isValidDocumentKey(string $documentKey): bool
687702
* Blocks: localhost, ::1, 127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 169.254.0.0/16, and IPv6 link-local (fe80::).
688703
*
689704
* @param string $url Full URL (e.g. https://example.com/doc.pdf)
705+
*
690706
* @return bool True if the URL should be blocked
691707
*/
692708
private function isUrlBlockedForSsrf(string $url): bool
@@ -732,6 +748,7 @@ private function isUrlBlockedForSsrf(string $url): bool
732748
* Each allowlist entry is either a substring (URL must contain it) or a regex if prefixed with '#'.
733749
*
734750
* @param string $url Full URL to check
751+
*
735752
* @return bool True if the URL matches at least one allowlist entry
736753
*/
737754
private function isUrlAllowedByAllowlist(string $url): bool

src/Controller/SignatureController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Nowo\PdfSignableBundle\Form\SignatureCoordinatesType;
1313
use Nowo\PdfSignableBundle\Model\AuditMetadata;
1414
use Nowo\PdfSignableBundle\Model\SignatureCoordinatesModel;
15+
use Psr\Log\LoggerInterface;
1516
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1617
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1718
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -21,7 +22,6 @@
2122
use Symfony\Component\HttpFoundation\Response;
2223
use Symfony\Component\HttpKernel\Attribute\AsController;
2324
use Symfony\Component\Routing\Attribute\Route;
24-
use Psr\Log\LoggerInterface;
2525
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
2626
use Symfony\Contracts\Translation\TranslatorInterface;
2727

@@ -227,6 +227,7 @@ public function proxyPdf(Request $request): Response
227227
'reason' => $e->getMessage(),
228228
'exception' => $e,
229229
]);
230+
230231
// Do not expose exception message to the client (information disclosure)
231232
return new Response(
232233
$this->translator->trans('proxy.error_load', [], 'nowo_pdf_signable'),

src/Event/AcroFormApplyRequestEvent.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ final class AcroFormApplyRequestEvent extends Event
3030
private ?array $validationResult = null;
3131

3232
/**
33-
* @param string $pdfContents Original PDF bytes
34-
* @param list<AcroFormFieldPatch> $patches Patches to apply
35-
* @param bool $validateOnly When true, listener may run dry-run and set validationResult instead of modifiedPdf
33+
* @param string $pdfContents Original PDF bytes
34+
* @param list<AcroFormFieldPatch> $patches Patches to apply
35+
* @param bool $validateOnly When true, listener may run dry-run and set validationResult instead of modifiedPdf
3636
*/
3737
public function __construct(
3838
private readonly string $pdfContents,

0 commit comments

Comments
 (0)