Skip to content

Commit

Permalink
Add merge route (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
StevenRenaux committed Jun 17, 2024
1 parent 0fa5fda commit 338204d
Show file tree
Hide file tree
Showing 11 changed files with 301 additions and 2 deletions.
11 changes: 11 additions & 0 deletions config/builder_pdf.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use Sensiolabs\GotenbergBundle\Builder\Pdf\HtmlPdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\LibreOfficePdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\MarkdownPdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\MergePdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\UrlPdfBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
Expand Down Expand Up @@ -58,4 +59,14 @@
->call('setLogger', [service('logger')->nullOnInvalid()])
->tag('sensiolabs_gotenberg.pdf_builder')
;

$services->set('.sensiolabs_gotenberg.pdf_builder.merge', MergePdfBuilder::class)
->share(false)
->args([
service('sensiolabs_gotenberg.client'),
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
])
->call('setLogger', [service('logger')->nullOnInvalid()])
->tag('sensiolabs_gotenberg.pdf_builder')
;
};
113 changes: 113 additions & 0 deletions src/Builder/Pdf/MergePdfBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

namespace Sensiolabs\GotenbergBundle\Builder\Pdf;

use Sensiolabs\GotenbergBundle\Enumeration\PdfFormat;
use Sensiolabs\GotenbergBundle\Exception\InvalidBuilderConfiguration;
use Sensiolabs\GotenbergBundle\Exception\MissingRequiredFieldException;
use Symfony\Component\Mime\Part\DataPart;
use Symfony\Component\Mime\Part\File as DataPartFile;

final class MergePdfBuilder extends AbstractPdfBuilder
{
private const ENDPOINT = '/forms/pdfengines/merge';

/**
* To set configurations by an array of configurations.
*
* @param array<string, mixed> $configurations
*/
public function setConfigurations(array $configurations): self
{
foreach ($configurations as $property => $value) {
$this->addConfiguration($property, $value);
}

return $this;
}

/**
* Convert the resulting PDF into the given PDF/A format.
*/
public function pdfFormat(PdfFormat $format): self
{
$this->formFields['pdfa'] = $format->value;

return $this;
}

/**
* Enable PDF for Universal Access for optimal accessibility.
*/
public function pdfUniversalAccess(bool $bool = true): self
{
$this->formFields['pdfua'] = $bool;

return $this;
}

public function files(string ...$paths): self
{
$this->formFields['files'] = [];

foreach ($paths as $path) {
$this->assertFileExtension($path, ['pdf']);

$dataPart = new DataPart(new DataPartFile($this->asset->resolve($path)));

$this->formFields['files'][$path] = $dataPart;
}

return $this;
}

/**
* Resets the metadata.
*
* @see https://gotenberg.dev/docs/routes#metadata-chromium
* @see https://exiftool.org/TagNames/XMP.html#pdf
*
* @param array<string, mixed> $metadata
*/
public function metadata(array $metadata): static
{
$this->formFields['metadata'] = $metadata;

return $this;
}

/**
* The metadata to write.
*/
public function addMetadata(string $key, string $value): static
{
$this->formFields['metadata'] ??= [];
$this->formFields['metadata'][$key] = $value;

return $this;
}

public function getMultipartFormData(): array
{
if ([] === ($this->formFields['files'] ?? [])) {
throw new MissingRequiredFieldException('At least one PDF file is required');
}

return parent::getMultipartFormData();
}

protected function getEndpoint(): string
{
return self::ENDPOINT;
}

private function addConfiguration(string $configurationName, mixed $value): void
{
match ($configurationName) {
'pdf_format' => $this->pdfFormat(PdfFormat::from($value)),
'pdf_universal_access' => $this->pdfUniversalAccess($value),
'metadata' => $this->metadata($value),
default => throw new InvalidBuilderConfiguration(sprintf('Invalid option "%s": no method does not exist in class "%s" to configured it.', $configurationName, self::class)),
};
}
}
18 changes: 18 additions & 0 deletions src/Debug/TraceableGotenbergPdf.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Sensiolabs\GotenbergBundle\Builder\Pdf\HtmlPdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\LibreOfficePdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\MarkdownPdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\MergePdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\PdfBuilderInterface;
use Sensiolabs\GotenbergBundle\Builder\Pdf\UrlPdfBuilder;
use Sensiolabs\GotenbergBundle\Debug\Builder\TraceablePdfBuilder;
Expand Down Expand Up @@ -103,6 +104,23 @@ public function markdown(): PdfBuilderInterface
return $traceableBuilder;
}

/**
* @return MergePdfBuilder|TraceablePdfBuilder
*/
public function merge(): PdfBuilderInterface
{
/** @var MergePdfBuilder|TraceablePdfBuilder $traceableBuilder */
$traceableBuilder = $this->inner->merge();

if (!$traceableBuilder instanceof TraceablePdfBuilder) {
return $traceableBuilder;
}

$this->builders[] = ['merge', $traceableBuilder];

return $traceableBuilder;
}

/**
* @return list<array{string, TraceablePdfBuilder}>
*/
Expand Down
30 changes: 30 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->append($this->addPdfUrlNode())
->append($this->addPdfMarkdownNode())
->append($this->addPdfOfficeNode())
->append($this->addPdfMergeNode())
->end()
->arrayNode('screenshot')
->addDefaultsIfNotSet()
Expand Down Expand Up @@ -513,6 +514,35 @@ private function addPdfOfficeNode(): NodeDefinition
;
}

private function addPdfMergeNode(): NodeDefinition
{
$treeBuilder = new TreeBuilder('merge');
$this->addPdfFormat($treeBuilder->getRootNode());
$treeBuilder->getRootNode()
->append($this->addPdfMetadata())
->end();

return $treeBuilder->getRootNode();
}

private function addPdfFormat(ArrayNodeDefinition $parent): void
{
$parent
->addDefaultsIfNotSet()
->children()
->enumNode('pdf_format')
->info('Convert PDF into the given PDF/A format - default None.')
->values(array_map(static fn (PdfFormat $case): string => $case->value, PdfFormat::cases()))
->defaultNull()
->end()
->booleanNode('pdf_universal_access')
->info('Enable PDF for Universal Access for optimal accessibility - default false.')
->defaultNull()
->end()
->end()
;
}

private function addPdfMetadata(): NodeDefinition
{
$treeBuilder = new TreeBuilder('metadata');
Expand Down
6 changes: 5 additions & 1 deletion src/DependencyInjection/SensiolabsGotenbergExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();

/** @var array{base_uri: string, http_client: string|null, request_context?: array{base_uri?: string}, assets_directory: string, default_options: array{pdf: array{html: array<string, mixed>, url: array<string, mixed>, markdown: array<string, mixed>, office: array<string, mixed>}, screenshot: array{html: array<string, mixed>, url: array<string, mixed>, markdown: array<string, mixed>}}} $config */
/** @var array{base_uri: string, http_client: string|null, request_context?: array{base_uri?: string}, assets_directory: string, default_options: array{pdf: array{html: array<string, mixed>, url: array<string, mixed>, markdown: array<string, mixed>, office: array<string, mixed>, merge: array<string, mixed>}, screenshot: array{html: array<string, mixed>, url: array<string, mixed>, markdown: array<string, mixed>}}} $config */
$config = $this->processConfiguration($configuration, $configs);

$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../../config'));
Expand All @@ -34,6 +34,7 @@ public function load(array $configs, ContainerBuilder $container): void
'url' => $this->cleanUserOptions($config['default_options']['pdf']['url']),
'markdown' => $this->cleanUserOptions($config['default_options']['pdf']['markdown']),
'office' => $this->cleanUserOptions($config['default_options']['pdf']['office']),
'merge' => $this->cleanUserOptions($config['default_options']['pdf']['merge']),
])
;
}
Expand Down Expand Up @@ -73,6 +74,9 @@ public function load(array $configs, ContainerBuilder $container): void
$definition = $container->getDefinition('.sensiolabs_gotenberg.pdf_builder.office');
$definition->addMethodCall('setConfigurations', [$this->cleanUserOptions($config['default_options']['pdf']['office'])]);

$definition = $container->getDefinition('.sensiolabs_gotenberg.pdf_builder.merge');
$definition->addMethodCall('setConfigurations', [$this->cleanUserOptions($config['default_options']['pdf']['merge'])]);

$definition = $container->getDefinition('.sensiolabs_gotenberg.screenshot_builder.html');
$definition->addMethodCall('setConfigurations', [$this->cleanUserOptions($config['default_options']['screenshot']['html'])]);

Expand Down
8 changes: 7 additions & 1 deletion src/GotenbergPdf.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ public function get(string $builder): PdfBuilderInterface
}

/**
* @param 'html'|'url'|'markdown'|'office' $key
* @param 'html'|'url'|'markdown'|'office'|'merge' $key
*
* @return ($key is 'html' ? HtmlPdfBuilder :
* $key is 'url' ? UrlPdfBuilder :
* $key is 'markdown' ? MarkdownPdfBuilder :
* $key is 'office' ? LibreOfficePdfBuilder :
* $key is 'merge' ? MergePdfBuilder :
* PdfBuilderInterface)
* )
*/
Expand Down Expand Up @@ -55,4 +56,9 @@ public function markdown(): PdfBuilderInterface
{
return $this->getInternal('markdown');
}

public function merge(): PdfBuilderInterface
{
return $this->getInternal('merge');
}
}
6 changes: 6 additions & 0 deletions src/GotenbergPdfInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Sensiolabs\GotenbergBundle\Builder\Pdf\HtmlPdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\LibreOfficePdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\MarkdownPdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\MergePdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\PdfBuilderInterface;
use Sensiolabs\GotenbergBundle\Builder\Pdf\UrlPdfBuilder;

Expand Down Expand Up @@ -38,4 +39,9 @@ public function office(): PdfBuilderInterface;
* @return MarkdownPdfBuilder
*/
public function markdown(): PdfBuilderInterface;

/**
* @return MergePdfBuilder
*/
public function merge(): PdfBuilderInterface;
}
87 changes: 87 additions & 0 deletions tests/Builder/Pdf/MergePdfBuilderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace Sensiolabs\GotenbergBundle\Tests\Builder\Pdf;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\UsesClass;
use Sensiolabs\GotenbergBundle\Builder\Pdf\AbstractPdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Pdf\MergePdfBuilder;
use Sensiolabs\GotenbergBundle\Exception\MissingRequiredFieldException;
use Sensiolabs\GotenbergBundle\Formatter\AssetBaseDirFormatter;
use Sensiolabs\GotenbergBundle\Tests\Builder\AbstractBuilderTestCase;

#[CoversClass(MergePdfBuilder::class)]
#[UsesClass(AbstractPdfBuilder::class)]
#[UsesClass(AssetBaseDirFormatter::class)]
final class MergePdfBuilderTest extends AbstractBuilderTestCase
{
private const PDF_DOCUMENTS_DIR = 'pdf';

public function testEndpointIsCorrect(): void
{
$this->gotenbergClient
->expects($this->once())
->method('call')
->with(
$this->equalTo('/forms/pdfengines/merge'),
$this->anything(),
$this->anything(),
)
;

$this->getMergePdfBuilder()
->files(
self::PDF_DOCUMENTS_DIR.'/simple_pdf.pdf',
self::PDF_DOCUMENTS_DIR.'/simple_pdf_1.pdf',
)
->generate()
;
}

public static function configurationIsCorrectlySetProvider(): \Generator
{
yield 'pdf_format' => ['pdf_format', 'PDF/A-1b', [
'pdfa' => 'PDF/A-1b',
]];
yield 'pdf_universal_access' => ['pdf_universal_access', false, [
'pdfua' => 'false',
]];
yield 'metadata' => ['metadata', ['Author' => 'SensioLabs'], [
'metadata' => '{"Author":"SensioLabs"}',
]];
}

/**
* @param array<mixed> $expected
*/
#[DataProvider('configurationIsCorrectlySetProvider')]
public function testConfigurationIsCorrectlySet(string $key, mixed $value, array $expected): void
{
$builder = $this->getMergePdfBuilder();
$builder->setConfigurations([
$key => $value,
]);
$builder->files(
self::PDF_DOCUMENTS_DIR.'/simple_pdf.pdf',
self::PDF_DOCUMENTS_DIR.'/simple_pdf_1.pdf',
);

self::assertEquals($expected, $builder->getMultipartFormData()[0]);
}

public function testRequiredFormData(): void
{
$builder = $this->getMergePdfBuilder();

$this->expectException(MissingRequiredFieldException::class);
$this->expectExceptionMessage('At least one PDF file is required');

$builder->getMultipartFormData();
}

private function getMergePdfBuilder(): MergePdfBuilder
{
return new MergePdfBuilder($this->gotenbergClient, self::$assetBaseDirFormatter);
}
}
5 changes: 5 additions & 0 deletions tests/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public function testWithExtraHeadersConfiguration(): void
* 'url': array<string, mixed>,
* 'markdown': array<string, mixed>,
* 'office': array<string, mixed>,
* 'merge': array<string, mixed>,
* }
* }
* }
Expand Down Expand Up @@ -170,6 +171,10 @@ private static function getBundleDefaultConfig(): array
'pdf_format' => null,
'pdf_universal_access' => null,
],
'merge' => [
'pdf_format' => null,
'pdf_universal_access' => null,
],
],
'screenshot' => [
'html' => [
Expand Down
Loading

0 comments on commit 338204d

Please sign in to comment.