From 0b9b8688f767e93ecdec510c8f7a6313d2313632 Mon Sep 17 00:00:00 2001 From: Steven Renaux <59167761+StevenRenaux@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:10:00 +0100 Subject: [PATCH] Add release 8.10.0 (#113) --- docs/configuration.md | 38 ++++- docs/pdf/builders_api/HtmlPdfBuilder.md | 2 + .../pdf/builders_api/LibreOfficePdfBuilder.md | 5 + docs/pdf/builders_api/MarkdownPdfBuilder.md | 2 + docs/pdf/builders_api/UrlPdfBuilder.md | 2 + docs/pdf/customization.md | 50 +++++- docs/pdf/office-builder.md | 61 +++++++- .../builders_api/HtmlScreenshotBuilder.md | 2 + .../builders_api/MarkdownScreenshotBuilder.md | 2 + .../builders_api/UrlScreenshotBuilder.md | 2 + docs/screenshot/customization.md | 50 ++++++ src/Builder/DownloadFromTrait.php | 54 +++++++ .../Pdf/AbstractChromiumPdfBuilder.php | 1 + src/Builder/Pdf/AbstractPdfBuilder.php | 11 ++ src/Builder/Pdf/ConvertPdfBuilder.php | 3 +- src/Builder/Pdf/HtmlPdfBuilder.php | 2 +- src/Builder/Pdf/LibreOfficePdfBuilder.php | 14 +- src/Builder/Pdf/MarkdownPdfBuilder.php | 2 +- src/Builder/Pdf/MergePdfBuilder.php | 3 +- .../AbstractChromiumScreenshotBuilder.php | 1 + .../Screenshot/AbstractScreenshotBuilder.php | 28 ++-- .../Screenshot/HtmlScreenshotBuilder.php | 2 +- .../Screenshot/MarkdownScreenshotBuilder.php | 2 +- src/DependencyInjection/Configuration.php | 142 +++++++++++------- .../Builder/Pdf/LibreOfficePdfBuilderTest.php | 3 + .../DependencyInjection/ConfigurationTest.php | 44 ++++++ .../SensiolabsGotenbergExtensionTest.php | 21 +++ 27 files changed, 463 insertions(+), 86 deletions(-) create mode 100644 src/Builder/DownloadFromTrait.php diff --git a/docs/configuration.md b/docs/configuration.md index 4af6eb85..e34802c1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -67,6 +67,7 @@ sensiolabs_gotenberg: pdf_format: null # None pdf_universal_access: null # false metadata: null # None + download_from: null # None url: header: template: null # None @@ -100,6 +101,7 @@ sensiolabs_gotenberg: pdf_format: null # None pdf_universal_access: null # false metadata: null # None + download_from: null # None markdown: header: template: null # None @@ -133,6 +135,7 @@ sensiolabs_gotenberg: pdf_format: null # None pdf_universal_access: null # false metadata: null # None + download_from: null # None office: landscape: null # false native_page_ranges: null # All pages @@ -159,13 +162,17 @@ sensiolabs_gotenberg: quality: null # 90 reduce_image_resolution: null # false max_image_resolution: null # 300 + password: null # None + download_from: null # None merge: pdf_format: null # None pdf_universal_access: null # false metadata: null # None + download_from: null # None convert: pdf_format: null # None pdf_universal_access: null # false + download_from: null # None screenshot: html: width: null # 800 @@ -184,6 +191,7 @@ sensiolabs_gotenberg: fail_on_http_status_codes: null # [499-599] fail_on_console_exceptions: null # false skip_network_idle_event: null # false + download_from: null # None url: width: null # 800 height: null # 600 @@ -201,6 +209,7 @@ sensiolabs_gotenberg: fail_on_http_status_codes: null # [499-599] fail_on_console_exceptions: null # false skip_network_idle_event: null # false + download_from: null # None markdown: width: null # 800 height: null # 600 @@ -218,6 +227,7 @@ sensiolabs_gotenberg: fail_on_http_status_codes: null # [499-599] fail_on_console_exceptions: null # false skip_network_idle_event: null # false + download_from: null # None ``` > [!TIP] @@ -298,4 +308,30 @@ sensiolabs_gotenberg: Whenever a controller returns something other than a `Response` object, the [`kernel.view`](https://symfony.com/doc/current/reference/events.html#kernel-view) event is fired. That listener listen to this event and detects if it is a `GotenbergFileResult` object. If so it automatically calls the `->stream()` method to convert it to a Response object. -Enabled byd efault but can be disabled via the `sensiolabs_gotenberg.controller_listener` configuration. +Enabled by default but can be disabled via the `sensiolabs_gotenberg.controller_listener` configuration. + +## Download from + +> [!WARNING] +> URL of the file. It MUST return a `Content-Disposition` header with a filename parameter. + +To download files resource from URLs. + +``` yaml +sensiolabs_gotenberg: + default_options: + pdf: + html: + download_from: + - url: 'http://example.com/url/to/file'' + extraHttpHeaders: + - name: 'MyHeader' + value: 'MyValue' + - name: 'User-Agent' + value: 'MyValue' + +``` + +> [!TIP] +> For more information go to [Gotenberg documentations](https://gotenberg.dev/docs/routes#download-from). + diff --git a/docs/pdf/builders_api/HtmlPdfBuilder.md b/docs/pdf/builders_api/HtmlPdfBuilder.md index 2598b015..28100c2e 100644 --- a/docs/pdf/builders_api/HtmlPdfBuilder.md +++ b/docs/pdf/builders_api/HtmlPdfBuilder.md @@ -127,6 +127,8 @@ Resets the metadata. * `addMetadata(string $key, string $value)`: The metadata to write. +* `downloadFrom(array $downloadFrom)`: + * `fileName(string $fileName, string $headerDisposition)`: * `processor(Sensiolabs\GotenbergBundle\Processor\ProcessorInterface $processor)`: diff --git a/docs/pdf/builders_api/LibreOfficePdfBuilder.md b/docs/pdf/builders_api/LibreOfficePdfBuilder.md index 0c1eb061..18390398 100644 --- a/docs/pdf/builders_api/LibreOfficePdfBuilder.md +++ b/docs/pdf/builders_api/LibreOfficePdfBuilder.md @@ -1,5 +1,8 @@ # LibreOfficePdfBuilder +* `password(string $password)`: +Set the password for opening the source file. + * `landscape(bool $bool)`: Sets the paper orientation to landscape. @@ -82,6 +85,8 @@ Specify if the resolution of each image is reduced to the resolution specified b * `maxImageResolution(Sensiolabs\GotenbergBundle\Enumeration\ImageResolutionDPI $resolution)`: If the form field reduceImageResolution is set to true, tell if all images will be reduced to the given value in DPI. Possible values are: 75, 150, 300, 600 and 1200. +* `downloadFrom(array $downloadFrom)`: + * `fileName(string $fileName, string $headerDisposition)`: * `processor(Sensiolabs\GotenbergBundle\Processor\ProcessorInterface $processor)`: diff --git a/docs/pdf/builders_api/MarkdownPdfBuilder.md b/docs/pdf/builders_api/MarkdownPdfBuilder.md index e47d72a4..8ac0c84f 100644 --- a/docs/pdf/builders_api/MarkdownPdfBuilder.md +++ b/docs/pdf/builders_api/MarkdownPdfBuilder.md @@ -130,6 +130,8 @@ Resets the metadata. * `addMetadata(string $key, string $value)`: The metadata to write. +* `downloadFrom(array $downloadFrom)`: + * `fileName(string $fileName, string $headerDisposition)`: * `processor(Sensiolabs\GotenbergBundle\Processor\ProcessorInterface $processor)`: diff --git a/docs/pdf/builders_api/UrlPdfBuilder.md b/docs/pdf/builders_api/UrlPdfBuilder.md index 60608be5..6aedc168 100644 --- a/docs/pdf/builders_api/UrlPdfBuilder.md +++ b/docs/pdf/builders_api/UrlPdfBuilder.md @@ -129,6 +129,8 @@ Resets the metadata. * `addMetadata(string $key, string $value)`: The metadata to write. +* `downloadFrom(array $downloadFrom)`: + * `fileName(string $fileName, string $headerDisposition)`: * `processor(Sensiolabs\GotenbergBundle\Processor\ProcessorInterface $processor)`: diff --git a/docs/pdf/customization.md b/docs/pdf/customization.md index 8a733ac2..479492d7 100644 --- a/docs/pdf/customization.md +++ b/docs/pdf/customization.md @@ -27,6 +27,7 @@ ### Additional content [header and footer](#header-and-footer) [headerFile and footerFile](#headerfile-and-footerfile) +[download from](#download-from) ### Style [assets](../assets.md) @@ -47,8 +48,8 @@ [skipNetworkIdleEvent](#skipNetworkIdleEvent) ### Formatting -[metadata](#metadata) -[addMetadata](#addMetadata) +[metadata](#metadata) +[addMetadata](#addMetadata) [pdfFormat](#pdfFormat) [pdfUniversalAccess](#pdfUniversalAccess) @@ -526,6 +527,51 @@ class YourController } ``` +### download from + +> [!WARNING] +> URL of the file. It MUST return a `Content-Disposition` header with a filename parameter. + +To download files resource from URLs. + +```php +namespace App\Controller; + +use Sensiolabs\GotenbergBundle\GotenbergPdfInterface; + +class YourController +{ + public function yourControllerMethod(GotenbergPdfInterface $gotenberg): Response + { + return $gotenberg + ->html() + ->downloadFrom([ + [ + 'url' => 'http://example.com/url/to/file', + 'extraHttpHeaders' => + [ + 'MyHeader' => 'MyValue', + ], + ], + [ + 'url' => 'http://example.com/url/to/file', + 'extraHttpHeaders' => + [ + 'MyHeaderOne' => 'MyValue', + 'MyHeaderTwo' => 'MyValue', + ], + ], + ]) + ->generate() + ->stream() + ; + } +} +``` + +> [!TIP] +> For more information go to [Gotenberg documentations](https://gotenberg.dev/docs/routes#download-from). + ## Request ### waitDelay diff --git a/docs/pdf/office-builder.md b/docs/pdf/office-builder.md index f6ec9ef4..0b8fe11c 100644 --- a/docs/pdf/office-builder.md +++ b/docs/pdf/office-builder.md @@ -771,19 +771,72 @@ class YourController > [!TIP] > For more information go to [Gotenberg documentations](https://gotenberg.dev/docs/routes#images-libreoffice). +### download from +> [!WARNING] +> URL of the file. It MUST return a `Content-Disposition` header with a filename parameter. +To download files resource from URLs. +```php +namespace App\Controller; +use Sensiolabs\GotenbergBundle\GotenbergPdfInterface; +class YourController +{ + public function yourControllerMethod(GotenbergPdfInterface $gotenberg): Response + { + return $gotenberg + ->office() + ->downloadFrom([ + [ + 'url' => 'http://url/to/file.com', + 'extraHttpHeaders' => + [ + 'MyHeader' => 'MyValue', + ], + ], + [ + 'url' => 'http://url/to/file.com', + 'extraHttpHeaders' => + [ + 'MyHeaderOne' => 'MyValue', + 'MyHeaderTwo' => 'MyValue', + ], + ], + ]) + ->generate() + ->stream() + ; + } +} +``` +> [!TIP] +> For more information go to [Gotenberg documentations](https://gotenberg.dev/docs/routes#download-from). +## password +Default: `None` + Set the password for opening the source file. +```php +namespace App\Controller; +use Sensiolabs\GotenbergBundle\GotenbergPdfInterface; - - - - +class YourController +{ + public function yourControllerMethod(GotenbergPdfInterface $gotenberg): Response + { + return $gotenberg->office() + ->files('document.txt') + ->password('My password') + ->generate() + ->stream() + ; + } +} +``` diff --git a/docs/screenshot/builders_api/HtmlScreenshotBuilder.md b/docs/screenshot/builders_api/HtmlScreenshotBuilder.md index 75627e28..d70c992c 100644 --- a/docs/screenshot/builders_api/HtmlScreenshotBuilder.md +++ b/docs/screenshot/builders_api/HtmlScreenshotBuilder.md @@ -72,6 +72,8 @@ Adds additional files, like images, fonts, stylesheets, and so on (overrides any * `addAsset(string $path)`: Adds a file, like an image, font, stylesheet, and so on. +* `downloadFrom(array $downloadFrom)`: + * `fileName(string $fileName, string $headerDisposition)`: * `processor(Sensiolabs\GotenbergBundle\Processor\ProcessorInterface $processor)`: diff --git a/docs/screenshot/builders_api/MarkdownScreenshotBuilder.md b/docs/screenshot/builders_api/MarkdownScreenshotBuilder.md index c7e0d8d4..3627b5a1 100644 --- a/docs/screenshot/builders_api/MarkdownScreenshotBuilder.md +++ b/docs/screenshot/builders_api/MarkdownScreenshotBuilder.md @@ -75,6 +75,8 @@ Adds additional files, like images, fonts, stylesheets, and so on (overrides any * `addAsset(string $path)`: Adds a file, like an image, font, stylesheet, and so on. +* `downloadFrom(array $downloadFrom)`: + * `fileName(string $fileName, string $headerDisposition)`: * `processor(Sensiolabs\GotenbergBundle\Processor\ProcessorInterface $processor)`: diff --git a/docs/screenshot/builders_api/UrlScreenshotBuilder.md b/docs/screenshot/builders_api/UrlScreenshotBuilder.md index 65496b31..35e20de4 100644 --- a/docs/screenshot/builders_api/UrlScreenshotBuilder.md +++ b/docs/screenshot/builders_api/UrlScreenshotBuilder.md @@ -74,6 +74,8 @@ Adds additional files, like images, fonts, stylesheets, and so on (overrides any * `addAsset(string $path)`: Adds a file, like an image, font, stylesheet, and so on. +* `downloadFrom(array $downloadFrom)`: + * `fileName(string $fileName, string $headerDisposition)`: * `processor(Sensiolabs\GotenbergBundle\Processor\ProcessorInterface $processor)`: diff --git a/docs/screenshot/customization.md b/docs/screenshot/customization.md index 804b4a0a..7df7eef1 100644 --- a/docs/screenshot/customization.md +++ b/docs/screenshot/customization.md @@ -9,6 +9,9 @@ [quality](#quality) [omitBackground](#omitBackground) +### Additional content +[download from](#download-from) + ### Style [assets](../assets.md) [addAsset](../assets.md) @@ -182,6 +185,53 @@ class YourController } ``` +## Additional content + +### download from + +> [!WARNING] +> URL of the file. It MUST return a `Content-Disposition` header with a filename parameter. + +To download files resource from URLs. + +```php +namespace App\Controller; + +use Sensiolabs\GotenbergBundle\GotenbergScreenshotInterface; + +class YourController +{ + public function yourControllerMethod(GotenbergScreenshotInterface $gotenberg): Response + { + return $gotenberg + ->html() + ->downloadFrom([ + [ + 'url' => 'http://example.com/url/to/file', + 'extraHttpHeaders' => + [ + 'MyHeader' => 'MyValue', + ], + ], + [ + 'url' => 'http://example.com/url/to/file', + 'extraHttpHeaders' => + [ + 'MyHeaderOne' => 'MyValue', + 'MyHeaderTwo' => 'MyValue', + ], + ], + ]) + ->generate() + ->stream() + ; + } +} +``` + +> [!TIP] +> For more information go to [Gotenberg documentations](https://gotenberg.dev/docs/routes#download-from). + ## Request ### optimizeForSpeed diff --git a/src/Builder/DownloadFromTrait.php b/src/Builder/DownloadFromTrait.php new file mode 100644 index 00000000..e77909fc --- /dev/null +++ b/src/Builder/DownloadFromTrait.php @@ -0,0 +1,54 @@ +}> $downloadFrom + */ + abstract public function downloadFrom(array $downloadFrom): static; + + /** + * @param array{downloadFrom?: array}>} $formFields + * @param list}> $downloadFrom + */ + private function withDownloadFrom(array &$formFields, array $downloadFrom): static + { + if ([] === $downloadFrom) { + unset($formFields['downloadFrom']); + + return $this; + } + + $formFields['downloadFrom'] = []; + + foreach ($downloadFrom as $file) { + if (!\array_key_exists('url', $file)) { + throw new MissingRequiredFieldException('Missing field "url"'); + } + + $formFields['downloadFrom'][$file['url']] = $file; + } + + return $this; + } + + /** + * @param array}> $value + * + * @return array|string|\Stringable|int|float|bool|\BackedEnum|DataPart> + */ + private function downloadFromNormalizer(array $value, callable $encoder): array + { + return $encoder('downloadFrom', array_values($value)); + } +} diff --git a/src/Builder/Pdf/AbstractChromiumPdfBuilder.php b/src/Builder/Pdf/AbstractChromiumPdfBuilder.php index c4089845..90c454b6 100644 --- a/src/Builder/Pdf/AbstractChromiumPdfBuilder.php +++ b/src/Builder/Pdf/AbstractChromiumPdfBuilder.php @@ -588,6 +588,7 @@ protected function addConfiguration(string $configurationName, mixed $value): vo 'fail_on_console_exceptions' => $this->failOnConsoleExceptions($value), 'skip_network_idle_event' => $this->skipNetworkIdleEvent($value), 'metadata' => $this->metadata($value), + 'download_from' => $this->downloadFrom($value), default => throw new InvalidBuilderConfiguration(\sprintf('Invalid option "%s": no method does not exist in class "%s" to configured it.', $configurationName, static::class)), }; } diff --git a/src/Builder/Pdf/AbstractPdfBuilder.php b/src/Builder/Pdf/AbstractPdfBuilder.php index 9eddae3d..658cab32 100644 --- a/src/Builder/Pdf/AbstractPdfBuilder.php +++ b/src/Builder/Pdf/AbstractPdfBuilder.php @@ -3,12 +3,14 @@ namespace Sensiolabs\GotenbergBundle\Builder\Pdf; use Sensiolabs\GotenbergBundle\Builder\DefaultBuilderTrait; +use Sensiolabs\GotenbergBundle\Builder\DownloadFromTrait; use Sensiolabs\GotenbergBundle\Client\GotenbergClientInterface; use Sensiolabs\GotenbergBundle\Formatter\AssetBaseDirFormatter; abstract class AbstractPdfBuilder implements PdfBuilderInterface { use DefaultBuilderTrait; + use DownloadFromTrait; public function __construct( GotenbergClientInterface $gotenbergClient, @@ -21,6 +23,15 @@ public function __construct( 'metadata' => function (mixed $value): array { return $this->encodeData('metadata', $value); }, + 'downloadFrom' => fn (array $value): array => $this->downloadFromNormalizer($value, $this->encodeData(...)), ]; } + + /** + * @param list}> $downloadFrom + */ + public function downloadFrom(array $downloadFrom): static + { + return $this->withDownloadFrom($this->formFields, $downloadFrom); + } } diff --git a/src/Builder/Pdf/ConvertPdfBuilder.php b/src/Builder/Pdf/ConvertPdfBuilder.php index e072e0f9..33916fa1 100644 --- a/src/Builder/Pdf/ConvertPdfBuilder.php +++ b/src/Builder/Pdf/ConvertPdfBuilder.php @@ -67,7 +67,7 @@ public function getMultipartFormData(): array throw new MissingRequiredFieldException('At least "pdfa" or "pdfua" must be provided.'); } - if ([] === ($this->formFields['files'] ?? [])) { + if ([] === ($this->formFields['files'] ?? []) && [] === ($this->formFields['downloadFrom'] ?? [])) { throw new MissingRequiredFieldException('At least one PDF file is required'); } @@ -84,6 +84,7 @@ private function addConfiguration(string $configurationName, mixed $value): void match ($configurationName) { 'pdf_format' => $this->pdfFormat(PdfFormat::from($value)), 'pdf_universal_access' => $this->pdfUniversalAccess($value), + 'download_from' => $this->downloadFrom($value), default => throw new InvalidBuilderConfiguration(\sprintf('Invalid option "%s": no method does not exist in class "%s" to configured it.', $configurationName, static::class)), }; } diff --git a/src/Builder/Pdf/HtmlPdfBuilder.php b/src/Builder/Pdf/HtmlPdfBuilder.php index 1334f5e7..c168feb0 100644 --- a/src/Builder/Pdf/HtmlPdfBuilder.php +++ b/src/Builder/Pdf/HtmlPdfBuilder.php @@ -31,7 +31,7 @@ public function contentFile(string $path): self public function getMultipartFormData(): array { - if (!\array_key_exists(Part::Body->value, $this->formFields)) { + if (!\array_key_exists(Part::Body->value, $this->formFields) && [] === ($this->formFields['downloadFrom'] ?? [])) { throw new MissingRequiredFieldException('Content is required'); } diff --git a/src/Builder/Pdf/LibreOfficePdfBuilder.php b/src/Builder/Pdf/LibreOfficePdfBuilder.php index 557b81cc..38272193 100644 --- a/src/Builder/Pdf/LibreOfficePdfBuilder.php +++ b/src/Builder/Pdf/LibreOfficePdfBuilder.php @@ -40,6 +40,16 @@ public function setConfigurations(array $configurations): static return $this; } + /** + * Set the password for opening the source file. + */ + public function password(#[\SensitiveParameter] string $password): self + { + $this->formFields['password'] = $password; + + return $this; + } + /** * Sets the paper orientation to landscape. */ @@ -330,7 +340,7 @@ public function maxImageResolution(ImageResolutionDPI $resolution): self public function getMultipartFormData(): array { - if ([] === ($this->formFields['files'] ?? [])) { + if ([] === ($this->formFields['files'] ?? []) && [] === ($this->formFields['downloadFrom'] ?? [])) { throw new MissingRequiredFieldException('At least one office file is required'); } @@ -345,6 +355,7 @@ protected function getEndpoint(): string private function addConfiguration(string $configurationName, mixed $value): void { match ($configurationName) { + 'password' => $this->password($value), 'pdf_format' => $this->pdfFormat(PdfFormat::from($value)), 'pdf_universal_access' => $this->pdfUniversalAccess($value), 'landscape' => $this->landscape($value), @@ -370,6 +381,7 @@ private function addConfiguration(string $configurationName, mixed $value): void 'quality' => $this->quality($value), 'reduce_image_resolution' => $this->reduceImageResolution($value), 'max_image_resolution' => $this->maxImageResolution(ImageResolutionDPI::from($value)), + 'download_from' => $this->downloadFrom($value), default => throw new InvalidBuilderConfiguration(\sprintf('Invalid option "%s": no method does not exist in class "%s" to configured it.', $configurationName, static::class)), }; } diff --git a/src/Builder/Pdf/MarkdownPdfBuilder.php b/src/Builder/Pdf/MarkdownPdfBuilder.php index e7530a1e..08a7258f 100644 --- a/src/Builder/Pdf/MarkdownPdfBuilder.php +++ b/src/Builder/Pdf/MarkdownPdfBuilder.php @@ -54,7 +54,7 @@ public function getMultipartFormData(): array throw new MissingRequiredFieldException('HTML template is required'); } - if ([] === ($this->formFields['files'] ?? [])) { + if ([] === ($this->formFields['files'] ?? []) && [] === ($this->formFields['downloadFrom'] ?? [])) { throw new MissingRequiredFieldException('At least one markdown file is required'); } diff --git a/src/Builder/Pdf/MergePdfBuilder.php b/src/Builder/Pdf/MergePdfBuilder.php index b59d892d..6ff9b8b5 100644 --- a/src/Builder/Pdf/MergePdfBuilder.php +++ b/src/Builder/Pdf/MergePdfBuilder.php @@ -89,7 +89,7 @@ public function addMetadata(string $key, string $value): static public function getMultipartFormData(): array { - if ([] === ($this->formFields['files'] ?? [])) { + if ([] === ($this->formFields['files'] ?? []) && [] === ($this->formFields['downloadFrom'] ?? [])) { throw new MissingRequiredFieldException('At least one PDF file is required'); } @@ -107,6 +107,7 @@ private function addConfiguration(string $configurationName, mixed $value): void 'pdf_format' => $this->pdfFormat(PdfFormat::from($value)), 'pdf_universal_access' => $this->pdfUniversalAccess($value), 'metadata' => $this->metadata($value), + 'download_from' => $this->downloadFrom($value), default => throw new InvalidBuilderConfiguration(\sprintf('Invalid option "%s": no method does not exist in class "%s" to configured it.', $configurationName, self::class)), }; } diff --git a/src/Builder/Screenshot/AbstractChromiumScreenshotBuilder.php b/src/Builder/Screenshot/AbstractChromiumScreenshotBuilder.php index e3a64568..b41ba859 100644 --- a/src/Builder/Screenshot/AbstractChromiumScreenshotBuilder.php +++ b/src/Builder/Screenshot/AbstractChromiumScreenshotBuilder.php @@ -383,6 +383,7 @@ private function addConfiguration(string $configurationName, mixed $value): void 'fail_on_http_status_codes' => $this->failOnHttpStatusCodes($value), 'fail_on_console_exceptions' => $this->failOnConsoleExceptions($value), 'skip_network_idle_event' => $this->skipNetworkIdleEvent($value), + 'download_from' => $this->downloadFrom($value), default => throw new InvalidBuilderConfiguration(\sprintf('Invalid option "%s": no method exists in class "%s" to configured it.', $configurationName, static::class)), }; } diff --git a/src/Builder/Screenshot/AbstractScreenshotBuilder.php b/src/Builder/Screenshot/AbstractScreenshotBuilder.php index dc6a25c9..ecfc50f5 100644 --- a/src/Builder/Screenshot/AbstractScreenshotBuilder.php +++ b/src/Builder/Screenshot/AbstractScreenshotBuilder.php @@ -3,14 +3,14 @@ namespace Sensiolabs\GotenbergBundle\Builder\Screenshot; use Sensiolabs\GotenbergBundle\Builder\DefaultBuilderTrait; +use Sensiolabs\GotenbergBundle\Builder\DownloadFromTrait; use Sensiolabs\GotenbergBundle\Client\GotenbergClientInterface; -use Sensiolabs\GotenbergBundle\Enumeration\Part; use Sensiolabs\GotenbergBundle\Formatter\AssetBaseDirFormatter; -use Symfony\Component\Mime\Part\DataPart; abstract class AbstractScreenshotBuilder implements ScreenshotBuilderInterface { use DefaultBuilderTrait; + use DownloadFromTrait; public function __construct( GotenbergClientInterface $gotenbergClient, @@ -20,21 +20,15 @@ public function __construct( $this->asset = $asset; $this->normalizers = [ - 'extraHttpHeaders' => function (mixed $value): array { - return $this->encodeData('extraHttpHeaders', $value); - }, - 'assets' => static function (array $value): array { - return ['files' => $value]; - }, - Part::Body->value => static function (DataPart $value): array { - return ['files' => $value]; - }, - 'failOnHttpStatusCodes' => function (mixed $value): array { - return $this->encodeData('failOnHttpStatusCodes', $value); - }, - 'cookies' => function (mixed $value): array { - return $this->encodeData('cookies', array_values($value)); - }, + 'downloadFrom' => fn (array $value): array => $this->downloadFromNormalizer($value, $this->encodeData(...)), ]; } + + /** + * @param list}> $downloadFrom + */ + public function downloadFrom(array $downloadFrom): static + { + return $this->withDownloadFrom($this->formFields, $downloadFrom); + } } diff --git a/src/Builder/Screenshot/HtmlScreenshotBuilder.php b/src/Builder/Screenshot/HtmlScreenshotBuilder.php index 259771cc..ee3c10f9 100644 --- a/src/Builder/Screenshot/HtmlScreenshotBuilder.php +++ b/src/Builder/Screenshot/HtmlScreenshotBuilder.php @@ -31,7 +31,7 @@ public function contentFile(string $path): self public function getMultipartFormData(): array { - if (!\array_key_exists(Part::Body->value, $this->formFields)) { + if (!\array_key_exists(Part::Body->value, $this->formFields) && [] === ($this->formFields['downloadFrom'] ?? [])) { throw new MissingRequiredFieldException('Content is required'); } diff --git a/src/Builder/Screenshot/MarkdownScreenshotBuilder.php b/src/Builder/Screenshot/MarkdownScreenshotBuilder.php index 943d8477..ce601f7d 100644 --- a/src/Builder/Screenshot/MarkdownScreenshotBuilder.php +++ b/src/Builder/Screenshot/MarkdownScreenshotBuilder.php @@ -54,7 +54,7 @@ public function getMultipartFormData(): array throw new MissingRequiredFieldException('HTML template is required'); } - if ([] === ($this->formFields['files'] ?? [])) { + if ([] === ($this->formFields['files'] ?? []) && [] === ($this->formFields['downloadFrom'] ?? [])) { throw new MissingRequiredFieldException('At least one markdown file is required'); } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 2f193ca2..32c5c3a9 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -259,7 +259,7 @@ private function addChromiumPdfOptionsNode(ArrayNodeDefinition $parent): void ->info('The JavaScript expression to wait before converting an HTML document into PDF until it returns true - default None. https://gotenberg.dev/docs/routes#wait-before-rendering') ->defaultNull() ->validate() - ->ifTrue(static function ($option) { + ->ifTrue(static function ($option): bool { return !\is_string($option); }) ->thenInvalid('Invalid value %s') @@ -299,37 +299,13 @@ private function addChromiumPdfOptionsNode(ArrayNodeDefinition $parent): void ->info('Override the default User-Agent HTTP header. - default None. https://gotenberg.dev/docs/routes#custom-http-headers-chromium') ->defaultNull() ->validate() - ->ifTrue(static function ($option) { + ->ifTrue(static function ($option): bool { return !\is_string($option); }) ->thenInvalid('Invalid value %s') ->end() ->end() - ->arrayNode('extra_http_headers') - ->info('HTTP headers to send by Chromium while loading the HTML document - default None. https://gotenberg.dev/docs/routes#custom-http-headers') - ->defaultValue([]) - ->useAttributeAsKey('name') - ->arrayPrototype() - ->children() - ->scalarNode('name') - ->validate() - ->ifTrue(static function ($option) { - return !\is_string($option); - }) - ->thenInvalid('Invalid header name %s') - ->end() - ->end() - ->scalarNode('value') - ->validate() - ->ifTrue(static function ($option) { - return !\is_string($option); - }) - ->thenInvalid('Invalid header value %s') - ->end() - ->end() - ->end() - ->end() - ->end() + ->append($this->addExtraHttpHeaders()) ->arrayNode('fail_on_http_status_codes') ->info('Return a 409 Conflict response if the HTTP status code from the main page is not acceptable. - default [499,599]. https://gotenberg.dev/docs/routes#invalid-http-status-codes-chromium') ->defaultValue([499, 599]) @@ -345,9 +321,10 @@ private function addChromiumPdfOptionsNode(ArrayNodeDefinition $parent): void ->defaultNull() ->end() ->append($this->addPdfMetadata()) + ->append($this->addDownloadFrom()) ->end() ->validate() - ->ifTrue(function ($v) { + ->ifTrue(function ($v): bool { return isset($v['paper_standard_size']) && (isset($v['paper_height']) || isset($v['paper_width'])); }) ->thenInvalid('You cannot use "paper_standard_size" when "paper_height", "paper_width" or both are set".') @@ -406,7 +383,7 @@ private function addChromiumScreenshotOptionsNode(ArrayNodeDefinition $parent): ->info('The JavaScript expression to wait before converting an HTML document into PDF until it returns true - default None. https://gotenberg.dev/docs/routes#wait-before-rendering') ->defaultNull() ->validate() - ->ifTrue(static function ($option) { + ->ifTrue(static function ($option): bool { return !\is_string($option); }) ->thenInvalid('Invalid value %s') @@ -446,37 +423,13 @@ private function addChromiumScreenshotOptionsNode(ArrayNodeDefinition $parent): ->info('Override the default User-Agent HTTP header. - default None. https://gotenberg.dev/docs/routes#custom-http-headers-chromium') ->defaultNull() ->validate() - ->ifTrue(static function ($option) { + ->ifTrue(static function ($option): bool { return !\is_string($option); }) ->thenInvalid('Invalid value %s') ->end() ->end() - ->arrayNode('extra_http_headers') - ->info('HTTP headers to send by Chromium while loading the HTML document - default None. https://gotenberg.dev/docs/routes#custom-http-headers-chromium') - ->defaultValue([]) - ->useAttributeAsKey('name') - ->arrayPrototype() - ->children() - ->scalarNode('name') - ->validate() - ->ifTrue(static function ($option) { - return !\is_string($option); - }) - ->thenInvalid('Invalid header name %s') - ->end() - ->end() - ->scalarNode('value') - ->validate() - ->ifTrue(static function ($option) { - return !\is_string($option); - }) - ->thenInvalid('Invalid header value %s') - ->end() - ->end() - ->end() - ->end() - ->end() + ->append($this->addExtraHttpHeaders()) ->arrayNode('fail_on_http_status_codes') ->info('Return a 409 Conflict response if the HTTP status code from the main page is not acceptable. - default [499,599]. https://gotenberg.dev/docs/routes#invalid-http-status-codes-chromium') ->defaultValue([499, 599]) @@ -491,6 +444,7 @@ private function addChromiumScreenshotOptionsNode(ArrayNodeDefinition $parent): ->info('Do not wait for Chromium network to be idle. - default false. https://gotenberg.dev/docs/routes#performance-mode-chromium') ->defaultNull() ->end() + ->append($this->addDownloadFrom()) ->end() ; } @@ -502,6 +456,10 @@ private function addPdfOfficeNode(): NodeDefinition $treeBuilder->getRootNode() ->addDefaultsIfNotSet() ->children() + ->scalarNode('password') + ->info('Set the password for opening the source file. https://gotenberg.dev/docs/routes#page-properties-libreoffice') + ->defaultNull() + ->end() ->booleanNode('landscape') ->info('The paper orientation to landscape - default false. https://gotenberg.dev/docs/routes#page-properties-chromium') ->defaultNull() @@ -600,6 +558,7 @@ private function addPdfOfficeNode(): NodeDefinition ->values(array_map(static fn (ImageResolutionDPI $case): int => $case->value, ImageResolutionDPI::cases())) ->defaultNull() ->end() + ->append($this->addDownloadFrom()) ->end() ; @@ -612,6 +571,9 @@ private function addPdfConvertNode(): NodeDefinition { $treeBuilder = new TreeBuilder('convert'); $this->addPdfFormat($treeBuilder->getRootNode()); + $treeBuilder->getRootNode() + ->append($this->addDownloadFrom()) + ->end(); return $treeBuilder->getRootNode(); } @@ -622,6 +584,7 @@ private function addPdfMergeNode(): NodeDefinition $this->addPdfFormat($treeBuilder->getRootNode()); $treeBuilder->getRootNode() ->append($this->addPdfMetadata()) + ->append($this->addDownloadFrom()) ->end(); return $treeBuilder->getRootNode(); @@ -667,4 +630,73 @@ private function addPdfMetadata(): NodeDefinition ->end() ; } + + private function addDownloadFrom(): NodeDefinition + { + $treeBuilder = new TreeBuilder('download_from'); + + return $treeBuilder->getRootNode() + ->info('URLs to download files from (JSON format). - default None. https://gotenberg.dev/docs/routes#download-from') + ->defaultValue([]) + ->arrayPrototype() + ->children() + ->scalarNode('url')->end() + ->arrayNode('extraHttpHeaders') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('name') + ->validate() + ->ifTrue(static function ($option): bool { + return !\is_string($option); + }) + ->thenInvalid('Invalid header name %s') + ->end() + ->end() + ->scalarNode('value') + ->validate() + ->ifTrue(static function ($option): bool { + return !\is_string($option); + }) + ->thenInvalid('Invalid header value %s') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addExtraHttpHeaders(): NodeDefinition + { + $treeBuilder = new TreeBuilder('extra_http_headers'); + + return $treeBuilder->getRootNode() + ->info('HTTP headers to send by Chromium while loading the HTML document - default None. https://gotenberg.dev/docs/routes#custom-http-headers') + ->defaultValue([]) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('name') + ->validate() + ->ifTrue(static function ($option): bool { + return !\is_string($option); + }) + ->thenInvalid('Invalid header name %s') + ->end() + ->end() + ->scalarNode('value') + ->validate() + ->ifTrue(static function ($option): bool { + return !\is_string($option); + }) + ->thenInvalid('Invalid header value %s') + ->end() + ->end() + ->end() + ->end() + ; + } } diff --git a/tests/Builder/Pdf/LibreOfficePdfBuilderTest.php b/tests/Builder/Pdf/LibreOfficePdfBuilderTest.php index d5742ac3..9a0b0f1b 100644 --- a/tests/Builder/Pdf/LibreOfficePdfBuilderTest.php +++ b/tests/Builder/Pdf/LibreOfficePdfBuilderTest.php @@ -116,6 +116,9 @@ public static function configurationIsCorrectlySetProvider(): \Generator yield 'max_image_resolution' => ['max_image_resolution', 300, [ 'maxImageResolution' => 300, ]]; + yield 'password' => ['password', 'My password', [ + 'password' => 'My password', + ]]; } /** diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php index feb625cb..79cc05b1 100644 --- a/tests/DependencyInjection/ConfigurationTest.php +++ b/tests/DependencyInjection/ConfigurationTest.php @@ -82,6 +82,40 @@ public function testWithExtraHeadersConfiguration(): void ], $config); } + public function testWithDownloadFromConfiguration(): void + { + $processor = new Processor(); + /** @var array{'default_options': array} $config */ + $config = $processor->processConfiguration(new Configuration(), [ + [ + 'http_client' => 'http_client', + 'default_options' => [ + 'pdf' => [ + 'html' => [ + 'download_from' => [ + [ + 'url' => 'http://url/to/file.com', + 'extraHttpHeaders' => [['name' => 'MyHeader', 'value' => 'MyValue'], ['name' => 'User-Agent', 'value' => 'MyValue']], + ], + ], + ], + ], + ], + ], + ]); + + $config = $this->cleanOptions($config['default_options']['pdf']['html']); + self::assertEquals([ + 'download_from' => [ + [ + 'url' => 'http://url/to/file.com', + 'extraHttpHeaders' => ['MyHeader' => 'MyValue', 'User-Agent' => 'MyValue'], + ], + ], + 'fail_on_http_status_codes' => ['499', '599'], + ], $config); + } + /** * @return iterable>> */ @@ -177,6 +211,7 @@ private static function getBundleDefaultConfig(): array 'skip_network_idle_event' => null, 'pdf_format' => null, 'pdf_universal_access' => null, + 'download_from' => [], ], 'url' => [ 'single_page' => null, @@ -204,6 +239,7 @@ private static function getBundleDefaultConfig(): array 'skip_network_idle_event' => null, 'pdf_format' => null, 'pdf_universal_access' => null, + 'download_from' => [], ], 'markdown' => [ 'single_page' => null, @@ -231,8 +267,10 @@ private static function getBundleDefaultConfig(): array 'skip_network_idle_event' => null, 'pdf_format' => null, 'pdf_universal_access' => null, + 'download_from' => [], ], 'office' => [ + 'password' => null, 'landscape' => null, 'native_page_ranges' => null, 'do_not_export_form_fields' => null, @@ -257,14 +295,17 @@ private static function getBundleDefaultConfig(): array 'quality' => null, 'reduce_image_resolution' => null, 'max_image_resolution' => null, + 'download_from' => [], ], 'merge' => [ 'pdf_format' => null, 'pdf_universal_access' => null, + 'download_from' => [], ], 'convert' => [ 'pdf_format' => null, 'pdf_universal_access' => null, + 'download_from' => [], ], ], 'screenshot' => [ @@ -285,6 +326,7 @@ private static function getBundleDefaultConfig(): array 'fail_on_http_status_codes' => [499, 599], 'fail_on_console_exceptions' => null, 'skip_network_idle_event' => null, + 'download_from' => [], ], 'url' => [ 'width' => null, @@ -303,6 +345,7 @@ private static function getBundleDefaultConfig(): array 'fail_on_http_status_codes' => [499, 599], 'fail_on_console_exceptions' => null, 'skip_network_idle_event' => null, + 'download_from' => [], ], 'markdown' => [ 'width' => null, @@ -321,6 +364,7 @@ private static function getBundleDefaultConfig(): array 'fail_on_http_status_codes' => [499, 599], 'fail_on_console_exceptions' => null, 'skip_network_idle_event' => null, + 'download_from' => [], ], ], ], diff --git a/tests/DependencyInjection/SensiolabsGotenbergExtensionTest.php b/tests/DependencyInjection/SensiolabsGotenbergExtensionTest.php index 227d1dde..b7b97b2d 100644 --- a/tests/DependencyInjection/SensiolabsGotenbergExtensionTest.php +++ b/tests/DependencyInjection/SensiolabsGotenbergExtensionTest.php @@ -63,6 +63,7 @@ public function testGotenbergConfiguredWithValidConfig(): void 'skip_network_idle_event' => true, 'pdf_format' => 'PDF/A-1b', 'pdf_universal_access' => true, + 'download_from' => [], ], 'url' => [ 'paper_width' => 21, @@ -87,6 +88,7 @@ public function testGotenbergConfiguredWithValidConfig(): void 'pdf_format' => PdfFormat::Pdf2b->value, 'pdf_universal_access' => false, 'cookies' => [], + 'download_from' => [], ], 'markdown' => [ 'paper_width' => 30, @@ -111,6 +113,7 @@ public function testGotenbergConfiguredWithValidConfig(): void 'pdf_format' => PdfFormat::Pdf3b->value, 'pdf_universal_access' => true, 'cookies' => [], + 'download_from' => [], ], 'office' => [ 'landscape' => false, @@ -118,14 +121,17 @@ public function testGotenbergConfiguredWithValidConfig(): void 'merge' => true, 'pdf_format' => 'PDF/A-1b', 'pdf_universal_access' => true, + 'download_from' => [], ], 'merge' => [ 'pdf_format' => 'PDF/A-3b', 'pdf_universal_access' => true, + 'download_from' => [], ], 'convert' => [ 'pdf_format' => 'PDF/A-2b', 'pdf_universal_access' => true, + 'download_from' => [], ], ], 'screenshot' => [ @@ -152,6 +158,7 @@ public function testGotenbergConfiguredWithValidConfig(): void 'fail_on_http_status_codes' => [401], 'fail_on_console_exceptions' => true, 'skip_network_idle_event' => true, + 'download_from' => [], ], 'url' => [ 'width' => 1000, @@ -177,6 +184,7 @@ public function testGotenbergConfiguredWithValidConfig(): void 'fail_on_http_status_codes' => [401, 403], 'fail_on_console_exceptions' => false, 'skip_network_idle_event' => true, + 'download_from' => [], ], 'markdown' => [ 'width' => 1000, @@ -201,6 +209,7 @@ public function testGotenbergConfiguredWithValidConfig(): void 'fail_on_http_status_codes' => [401, 403], 'fail_on_console_exceptions' => false, 'skip_network_idle_event' => false, + 'download_from' => [], ], ], ]; @@ -307,6 +316,7 @@ public function testDataCollectorIsProperlyConfiguredIfEnabled(): void 'cookies' => [], 'extra_http_headers' => [], 'fail_on_http_status_codes' => [], + 'download_from' => [], ], 'url' => [ 'metadata' => [ @@ -315,6 +325,7 @@ public function testDataCollectorIsProperlyConfiguredIfEnabled(): void 'cookies' => [], 'extra_http_headers' => [], 'fail_on_http_status_codes' => [], + 'download_from' => [], ], 'markdown' => [ 'metadata' => [ @@ -323,19 +334,23 @@ public function testDataCollectorIsProperlyConfiguredIfEnabled(): void 'cookies' => [], 'extra_http_headers' => [], 'fail_on_http_status_codes' => [], + 'download_from' => [], ], 'office' => [ 'metadata' => [ 'Author' => 'SensioLabs OFFICE', ], + 'download_from' => [], ], 'merge' => [ 'metadata' => [ 'Author' => 'SensioLabs MERGE', ], + 'download_from' => [], ], 'convert' => [ 'pdf_format' => 'PDF/A-2b', + 'download_from' => [], ], ], ], @@ -353,6 +368,7 @@ public function testDataCollectorIsProperlyConfiguredIfEnabled(): void 'cookies' => [], 'extra_http_headers' => [], 'fail_on_http_status_codes' => [], + 'download_from' => [], ], 'url' => [ 'metadata' => [ @@ -361,6 +377,7 @@ public function testDataCollectorIsProperlyConfiguredIfEnabled(): void 'cookies' => [], 'extra_http_headers' => [], 'fail_on_http_status_codes' => [], + 'download_from' => [], ], 'markdown' => [ 'metadata' => [ @@ -369,19 +386,23 @@ public function testDataCollectorIsProperlyConfiguredIfEnabled(): void 'cookies' => [], 'extra_http_headers' => [], 'fail_on_http_status_codes' => [], + 'download_from' => [], ], 'office' => [ 'metadata' => [ 'Author' => 'SensioLabs OFFICE', ], + 'download_from' => [], ], 'merge' => [ 'metadata' => [ 'Author' => 'SensioLabs MERGE', ], + 'download_from' => [], ], 'convert' => [ 'pdf_format' => 'PDF/A-2b', + 'download_from' => [], ], ], $dataCollectorOptions); }