66
77use Nowo \PdfSignableBundle \AcroForm \AcroFormFieldPatch ;
88use Nowo \PdfSignableBundle \AcroForm \AcroFormOverrides ;
9- use Nowo \PdfSignableBundle \AcroForm \PythonProcessEnv ;
109use Nowo \PdfSignableBundle \AcroForm \Exception \AcroFormEditorException ;
1110use Nowo \PdfSignableBundle \AcroForm \PdfAcroFormEditorInterface ;
11+ use Nowo \PdfSignableBundle \AcroForm \PythonProcessEnv ;
1212use Nowo \PdfSignableBundle \AcroForm \Storage \AcroFormOverridesStorageInterface ;
1313use Nowo \PdfSignableBundle \Event \AcroFormApplyRequestEvent ;
1414use Nowo \PdfSignableBundle \Event \AcroFormModifiedPdfProcessedEvent ;
1515use Nowo \PdfSignableBundle \Event \PdfSignableEvents ;
16+ use Psr \Log \LoggerInterface ;
1617use Symfony \Bundle \FrameworkBundle \Controller \AbstractController ;
1718use Symfony \Component \DependencyInjection \Attribute \Autowire ;
1819use Symfony \Component \EventDispatcher \EventDispatcherInterface ;
2526use Symfony \Component \Routing \Attribute \Route ;
2627use Symfony \Contracts \HttpClient \Exception \ExceptionInterface ;
2728use 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
0 commit comments