diff --git a/UPGRADE.md b/UPGRADE.md index 3930e5c9..72ce1ff0 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,7 @@ +### UPGRADE FROM 0.20.0 TO 0.21.0 + +1. `Sylius\InvoicingPlugin\Generator\InvoicePdfFileGenerator` takes `Sylius\InvoicingPlugin\Generator\PdfOptionsGeneratorInterface` as the third argument after `Knp\Snappy\GeneratorInterface`. + ### UPGRADE FROM 0.19.0 TO 0.20.0 1. Since 0.20.0, the recommended Sylius version to use with InvoicingPlugin is `1.11.*`. If you would like to upgrade Sylius to v1.11.0, diff --git a/spec/Generator/InvoicePdfFileGeneratorSpec.php b/spec/Generator/InvoicePdfFileGeneratorSpec.php index 1da9e4a0..1018c9bf 100644 --- a/spec/Generator/InvoicePdfFileGeneratorSpec.php +++ b/spec/Generator/InvoicePdfFileGeneratorSpec.php @@ -19,6 +19,7 @@ use Sylius\InvoicingPlugin\Entity\InvoiceInterface; use Sylius\InvoicingPlugin\Generator\InvoiceFileNameGeneratorInterface; use Sylius\InvoicingPlugin\Generator\InvoicePdfFileGeneratorInterface; +use Sylius\InvoicingPlugin\Generator\PdfOptionsGeneratorInterface; use Sylius\InvoicingPlugin\Model\InvoicePdf; use Symfony\Component\Config\FileLocatorInterface; use Twig\Environment; @@ -28,12 +29,14 @@ final class InvoicePdfFileGeneratorSpec extends ObjectBehavior function let( Environment $twig, GeneratorInterface $pdfGenerator, + PdfOptionsGeneratorInterface $pdfOptionsGenerator, FileLocatorInterface $fileLocator, InvoiceFileNameGeneratorInterface $invoiceFileNameGenerator ): void { $this->beConstructedWith( $twig, $pdfGenerator, + $pdfOptionsGenerator, $fileLocator, $invoiceFileNameGenerator, 'invoiceTemplate.html.twig', @@ -50,6 +53,7 @@ function it_creates_invoice_pdf_with_generated_content_and_filename_basing_on_in FileLocatorInterface $fileLocator, Environment $twig, GeneratorInterface $pdfGenerator, + PdfOptionsGeneratorInterface $pdfOptionsGenerator, InvoiceFileNameGeneratorInterface $invoiceFileNameGenerator, InvoiceInterface $invoice, ChannelInterface $channel @@ -64,8 +68,19 @@ function it_creates_invoice_pdf_with_generated_content_and_filename_basing_on_in ->willReturn('I am an invoice pdf file content') ; - $pdfGenerator->getOutputFromHtml('I am an invoice pdf file content')->willReturn('PDF FILE'); + $pdfOptionsGenerator + ->generate() + ->willReturn(['allow' => ['located-path/sylius-logo.png']]) + ; + + $pdfGenerator + ->getOutputFromHtml('I am an invoice pdf file content', ['allow' => ['located-path/sylius-logo.png']]) + ->willReturn('PDF FILE') + ; - $this->generate($invoice)->shouldBeLike(new InvoicePdf('2015_05_00004444.pdf', 'PDF FILE')); + $this + ->generate($invoice) + ->shouldBeLike(new InvoicePdf('2015_05_00004444.pdf', 'PDF FILE')) + ; } } diff --git a/spec/Generator/PdfOptionsGeneratorSpec.php b/spec/Generator/PdfOptionsGeneratorSpec.php new file mode 100644 index 00000000..27cec88d --- /dev/null +++ b/spec/Generator/PdfOptionsGeneratorSpec.php @@ -0,0 +1,52 @@ +beConstructedWith( + $fileLocator, + ['allow' => 'allowed_file_in_knp_snappy_config.png'], + ['swans.png'] + ); + } + + function it_is_pdf_options_generator_interface(): void + { + $this->shouldImplement(PdfOptionsGeneratorInterface::class); + } + + function it_generates_pdf_options(FileLocatorInterface $fileLocator): void + { + $fileLocator + ->locate('swans.png') + ->willReturn('located-path/swans.png'); + + $this + ->generate() + ->shouldBeLike([ + 'allow' => [ + 'allowed_file_in_knp_snappy_config.png', + 'located-path/swans.png', + ], + ]) + ; + } +} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 58e4f4fd..69274dfd 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -32,6 +32,7 @@ use Sylius\InvoicingPlugin\Factory\InvoiceShopBillingDataFactory; use Sylius\InvoicingPlugin\Factory\LineItemFactory; use Sylius\InvoicingPlugin\Factory\TaxItemFactory; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -42,7 +43,15 @@ public function getConfigTreeBuilder(): TreeBuilder $treeBuilder = new TreeBuilder('sylius_invoicing_plugin'); $rootNode = $treeBuilder->getRootNode(); - $rootNode + $this->addResourcesSection($rootNode); + $this->addPdfGeneratorSection($rootNode); + + return $treeBuilder; + } + + private function addResourcesSection(ArrayNodeDefinition $node): void + { + $node ->children() ->arrayNode('resources') ->addDefaultsIfNotSet() @@ -78,63 +87,63 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->end() ->end() - ->end() - ->arrayNode('shop_billing_data') - ->addDefaultsIfNotSet() - ->children() - ->variableNode('options')->end() - ->arrayNode('classes') - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('model')->defaultValue(InvoiceShopBillingData::class)->cannotBeEmpty()->end() - ->scalarNode('interface')->defaultValue(InvoiceShopBillingDataInterface::class)->cannotBeEmpty()->end() - ->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end() - ->scalarNode('factory')->defaultValue(InvoiceShopBillingDataFactory::class)->cannotBeEmpty()->end() - ->scalarNode('repository')->cannotBeEmpty()->end() - ->end() + ->end() + ->arrayNode('shop_billing_data') + ->addDefaultsIfNotSet() + ->children() + ->variableNode('options')->end() + ->arrayNode('classes') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('model')->defaultValue(InvoiceShopBillingData::class)->cannotBeEmpty()->end() + ->scalarNode('interface')->defaultValue(InvoiceShopBillingDataInterface::class)->cannotBeEmpty()->end() + ->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end() + ->scalarNode('factory')->defaultValue(InvoiceShopBillingDataFactory::class)->cannotBeEmpty()->end() + ->scalarNode('repository')->cannotBeEmpty()->end() ->end() ->end() ->end() - ->arrayNode('line_item') - ->addDefaultsIfNotSet() - ->children() - ->variableNode('options')->end() - ->arrayNode('classes') - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('model')->defaultValue(LineItem::class)->cannotBeEmpty()->end() - ->scalarNode('interface')->defaultValue(LineItemInterface::class)->cannotBeEmpty()->end() - ->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end() - ->scalarNode('factory')->defaultValue(LineItemFactory::class)->cannotBeEmpty()->end() - ->scalarNode('repository')->cannotBeEmpty()->end() - ->end() + ->end() + ->arrayNode('line_item') + ->addDefaultsIfNotSet() + ->children() + ->variableNode('options')->end() + ->arrayNode('classes') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('model')->defaultValue(LineItem::class)->cannotBeEmpty()->end() + ->scalarNode('interface')->defaultValue(LineItemInterface::class)->cannotBeEmpty()->end() + ->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end() + ->scalarNode('factory')->defaultValue(LineItemFactory::class)->cannotBeEmpty()->end() + ->scalarNode('repository')->cannotBeEmpty()->end() ->end() ->end() ->end() - ->arrayNode('tax_item') - ->addDefaultsIfNotSet() - ->children() - ->variableNode('options')->end() - ->arrayNode('classes') - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('model')->defaultValue(TaxItem::class)->cannotBeEmpty()->end() - ->scalarNode('interface')->defaultValue(TaxItemInterface::class)->cannotBeEmpty()->end() - ->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end() - ->scalarNode('factory')->defaultValue(TaxItemFactory::class)->cannotBeEmpty()->end() - ->scalarNode('repository')->cannotBeEmpty()->end() - ->end() + ->end() + ->arrayNode('tax_item') + ->addDefaultsIfNotSet() + ->children() + ->variableNode('options')->end() + ->arrayNode('classes') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('model')->defaultValue(TaxItem::class)->cannotBeEmpty()->end() + ->scalarNode('interface')->defaultValue(TaxItemInterface::class)->cannotBeEmpty()->end() + ->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end() + ->scalarNode('factory')->defaultValue(TaxItemFactory::class)->cannotBeEmpty()->end() + ->scalarNode('repository')->cannotBeEmpty()->end() ->end() ->end() ->end() - ->arrayNode('invoice_sequence') - ->addDefaultsIfNotSet() - ->children() - ->variableNode('options')->end() - ->arrayNode('classes') - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('model')->defaultValue(InvoiceSequence::class)->cannotBeEmpty()->end() + ->end() + ->arrayNode('invoice_sequence') + ->addDefaultsIfNotSet() + ->children() + ->variableNode('options')->end() + ->arrayNode('classes') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('model')->defaultValue(InvoiceSequence::class)->cannotBeEmpty()->end() ->scalarNode('interface')->defaultValue(InvoiceSequenceInterface::class)->cannotBeEmpty()->end() ->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end() ->scalarNode('factory')->defaultValue(Factory::class)->cannotBeEmpty()->end() @@ -147,7 +156,22 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->end() ; + } - return $treeBuilder; + private function addPdfGeneratorSection(ArrayNodeDefinition $node): void + { + $node + ->children() + ->arrayNode('pdf_generator') + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('allowed_files') + ->useAttributeAsKey('name') + ->variablePrototype()->end() + ->end() + ->end() + ->end() + ->end() + ; } } diff --git a/src/DependencyInjection/SyliusInvoicingExtension.php b/src/DependencyInjection/SyliusInvoicingExtension.php index 56dc6846..0f95e355 100644 --- a/src/DependencyInjection/SyliusInvoicingExtension.php +++ b/src/DependencyInjection/SyliusInvoicingExtension.php @@ -32,6 +32,8 @@ public function load(array $config, ContainerBuilder $container): void $this->registerResources('sylius_invoicing_plugin', 'doctrine/orm', $config['resources'], $container); $loader->load('services.xml'); + + $container->setParameter('sylius_invoicing.pdf_generator.allowed_files', $config['pdf_generator']['allowed_files']); } public function prepend(ContainerBuilder $container): void diff --git a/src/Generator/InvoicePdfFileGenerator.php b/src/Generator/InvoicePdfFileGenerator.php index a828e0ee..17fca564 100644 --- a/src/Generator/InvoicePdfFileGenerator.php +++ b/src/Generator/InvoicePdfFileGenerator.php @@ -21,32 +21,15 @@ final class InvoicePdfFileGenerator implements InvoicePdfFileGeneratorInterface { - private Environment $templatingEngine; - - private GeneratorInterface $pdfGenerator; - - private FileLocatorInterface $fileLocator; - - private InvoiceFileNameGeneratorInterface $invoiceFileNameGenerator; - - private string $template; - - private string $invoiceLogoPath; - public function __construct( - Environment $templatingEngine, - GeneratorInterface $pdfGenerator, - FileLocatorInterface $fileLocator, - InvoiceFileNameGeneratorInterface $invoiceFileNameGenerator, - string $template, - string $invoiceLogoPath + private Environment $templatingEngine, + private GeneratorInterface $pdfGenerator, + private PdfOptionsGeneratorInterface $pdfOptionsGenerator, + private FileLocatorInterface $fileLocator, + private InvoiceFileNameGeneratorInterface $invoiceFileNameGenerator, + private string $template, + private string $invoiceLogoPath ) { - $this->templatingEngine = $templatingEngine; - $this->pdfGenerator = $pdfGenerator; - $this->fileLocator = $fileLocator; - $this->invoiceFileNameGenerator = $invoiceFileNameGenerator; - $this->template = $template; - $this->invoiceLogoPath = $invoiceLogoPath; } public function generate(InvoiceInterface $invoice): InvoicePdf @@ -58,7 +41,8 @@ public function generate(InvoiceInterface $invoice): InvoicePdf 'invoice' => $invoice, 'channel' => $invoice->channel(), 'invoiceLogoPath' => $this->fileLocator->locate($this->invoiceLogoPath), - ]) + ]), + $this->pdfOptionsGenerator->generate() ); return new InvoicePdf($filename, $pdf); diff --git a/src/Generator/PdfOptionsGenerator.php b/src/Generator/PdfOptionsGenerator.php new file mode 100644 index 00000000..ed20ffc6 --- /dev/null +++ b/src/Generator/PdfOptionsGenerator.php @@ -0,0 +1,48 @@ +knpSnappyOptions; + + if (empty($this->allowedFiles)) { + return $options; + } + + if (!isset($options['allow'])) { + $options['allow'] = []; + } elseif (!is_array($options['allow'])) { + $options['allow'] = [$options['allow']]; + } + + $options['allow'] = array_merge( + $options['allow'], + array_map(fn ($file) => $this->fileLocator->locate($file), $this->allowedFiles) + ); + + return $options; + } +} diff --git a/src/Generator/PdfOptionsGeneratorInterface.php b/src/Generator/PdfOptionsGeneratorInterface.php new file mode 100644 index 00000000..f88b494b --- /dev/null +++ b/src/Generator/PdfOptionsGeneratorInterface.php @@ -0,0 +1,19 @@ + + @SyliusInvoicingPlugin/Invoice/Download/pdf.html.twig @@ -50,5 +51,11 @@ + + + + %knp_snappy.pdf.options% + %sylius_invoicing.pdf_generator.allowed_files% + diff --git a/tests/Application/config/packages/knp_snappy.yaml b/tests/Application/config/packages/knp_snappy.yaml index 3b277fcd..cb0b804a 100644 --- a/tests/Application/config/packages/knp_snappy.yaml +++ b/tests/Application/config/packages/knp_snappy.yaml @@ -2,5 +2,4 @@ knp_snappy: pdf: enabled: true binary: '%env(resolve:WKHTMLTOPDF_PATH)%' - options: - enable-local-file-access: true + options: [] diff --git a/tests/Unit/DependencyInjection/SyliusInvoicingConfigurationTest.php b/tests/Unit/DependencyInjection/SyliusInvoicingConfigurationTest.php new file mode 100644 index 00000000..5c48b473 --- /dev/null +++ b/tests/Unit/DependencyInjection/SyliusInvoicingConfigurationTest.php @@ -0,0 +1,48 @@ +assertProcessedConfigurationEquals( + [[]], + ['pdf_generator' => ['allowed_files' => []]], + 'pdf_generator' + ); + } + + /** @test */ + public function it_allows_to_define_allowed_files(): void + { + $this->assertProcessedConfigurationEquals( + [['pdf_generator' => ['allowed_files' => ['swans.png', 'product.png']]]], + ['pdf_generator' => ['allowed_files' => ['swans.png', 'product.png']]], + 'pdf_generator' + ); + } + + protected function getConfiguration(): Configuration + { + return new Configuration(); + } +}