From 0f4391bdd16b6b018d220155d8e279cac9c79e7e Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 11 May 2021 18:18:41 +0300 Subject: [PATCH] pdf refactoring and fixes --- application/Espo/Controllers/Pdf.php | 25 +++- .../ExtGroup/PdfGroup/GenerateType.php | 34 +++-- application/Espo/Core/Htmlizer/Factory.php | 9 +- application/Espo/Core/Htmlizer/Htmlizer.php | 4 - application/Espo/EntryPoints/Pdf.php | 34 ++++- application/Espo/Services/Pdf.php | 126 ++++++++++-------- application/Espo/Tools/Pdf/Data.php | 27 +++- .../Espo/Tools/Pdf/Tcpdf/EntityProcessor.php | 4 +- 8 files changed, 176 insertions(+), 87 deletions(-) diff --git a/application/Espo/Controllers/Pdf.php b/application/Espo/Controllers/Pdf.php index 205f7ea912..1952576d94 100644 --- a/application/Espo/Controllers/Pdf.php +++ b/application/Espo/Controllers/Pdf.php @@ -29,18 +29,32 @@ namespace Espo\Controllers; -use Espo\Core\Exceptions\Forbidden; -use Espo\Core\Exceptions\BadRequest; +use Espo\Core\{ + Exceptions\Forbidden, + Exceptions\BadRequest, +}; use Espo\Core\{ - Controllers\Base, Api\Request, + Acl, }; +use Espo\Services\Pdf as Service; + use StdClass; -class Pdf extends Base +class Pdf { + private $service; + + private $acl; + + public function __construct(Service $service, Acl $acl) + { + $this->service = $service; + $this->acl = $acl; + } + public function postActionMassPrint(Request $request): StdClass { $data = $request->getParsedBody(); @@ -66,8 +80,7 @@ class Pdf extends Base } return (object) [ - 'id' => $this->getServiceFactory() - ->create('Pdf') + 'id' => $this->service ->massGenerate( $data->entityType, $data->idList, diff --git a/application/Espo/Core/Formula/Functions/ExtGroup/PdfGroup/GenerateType.php b/application/Espo/Core/Formula/Functions/ExtGroup/PdfGroup/GenerateType.php index f014aba104..adbe48cc50 100644 --- a/application/Espo/Core/Formula/Functions/ExtGroup/PdfGroup/GenerateType.php +++ b/application/Espo/Core/Formula/Functions/ExtGroup/PdfGroup/GenerateType.php @@ -32,12 +32,17 @@ namespace Espo\Core\Formula\Functions\ExtGroup\PdfGroup; use Espo\Core\Formula\{ Functions\BaseFunction, ArgumentList, + Exceptions\Error, }; use Espo\Core\Utils\Util; +use Espo\Tools\Pdf\Data; + use Espo\Core\Di; +use Exception; + class GenerateType extends BaseFunction implements Di\EntityManagerAware, Di\ServiceFactoryAware @@ -61,12 +66,15 @@ class GenerateType extends BaseFunction implements if (!$entityType || !is_string($entityType)) { $this->throwBadArgumentType(1, 'string'); } + if (!$id || !is_string($id)) { $this->throwBadArgumentType(2, 'string'); } + if (!$templateId || !is_string($templateId)) { $this->throwBadArgumentType(3, 'string'); } + if ($fileName && !is_string($fileName)) { $this->throwBadArgumentType(4, 'string'); } @@ -75,21 +83,25 @@ class GenerateType extends BaseFunction implements try { $entity = $em->getEntity($entityType, $id); - } catch (\Exception $e) { + } + catch (Exception $e) { $this->log("Message: " . $e->getMessage() . "."); - return null; + + throw new Error(); } if (!$entity) { $this->log("Record {$entityType} {$id} does not exist."); - return null; + + throw new Error(); } $template = $em->getEntity('Template', $templateId); if (!$template) { $this->log("Template {$templateId} does not exist."); - return null; + + throw new Error(); } if ($fileName) { @@ -100,11 +112,17 @@ class GenerateType extends BaseFunction implements $fileName = Util::sanitizeFileName($entity->get('name')) . '.pdf'; } + $data = Data::fromNothing()->withAcl(false); + try { - $contents = $this->serviceFactory->create('Pdf')->buildFromTemplate($entity, $template); - } catch (\Exception $e) { + $contents = $this->serviceFactory + ->create('Pdf') + ->generate($entity, $template, $data); + } + catch (Exception $e) { $this->log("Error while generating. Message: " . $e->getMessage() . ".", 'error'); - return null; + + throw new Error(); } $attachment = $em->createEntity('Attachment', [ @@ -116,6 +134,6 @@ class GenerateType extends BaseFunction implements 'role' => 'Attachment', ]); - return $attachment->id; + return $attachment->getId(); } } diff --git a/application/Espo/Core/Htmlizer/Factory.php b/application/Espo/Core/Htmlizer/Factory.php index 69bd97ba90..e45b7e4da5 100644 --- a/application/Espo/Core/Htmlizer/Factory.php +++ b/application/Espo/Core/Htmlizer/Factory.php @@ -34,11 +34,12 @@ use Espo\Core\Utils\DateTime; class Factory { - protected $injectableFactory; + private $injectableFactory; - protected $dateTime; + private $dateTime; - public function __construct(InjectableFactory $injectableFactory, DateTime $dateTime) { + public function __construct(InjectableFactory $injectableFactory, DateTime $dateTime) + { $this->injectableFactory = $injectableFactory; $this->dateTime = $dateTime; } @@ -46,6 +47,7 @@ class Factory public function create(bool $skipAcl = false, ?string $timezone = null): Htmlizer { $with = []; + if ($skipAcl) { $with['acl'] = null; } @@ -53,6 +55,7 @@ class Factory if ($timezone) { $dateTime = clone($this->dateTime); $dateTime->setTimezone($timezone); + $with['dateTime'] = $dateTime; } diff --git a/application/Espo/Core/Htmlizer/Htmlizer.php b/application/Espo/Core/Htmlizer/Htmlizer.php index dea602768c..470f0b1efd 100644 --- a/application/Espo/Core/Htmlizer/Htmlizer.php +++ b/application/Espo/Core/Htmlizer/Htmlizer.php @@ -133,10 +133,6 @@ class Htmlizer $data = get_object_vars($entity->getValueMap()); - //$data = $entity->toArray(); - - //print_r($data); - $attributeList = $entity->getAttributeList(); $forbiddenAttributeList = []; diff --git a/application/Espo/EntryPoints/Pdf.php b/application/Espo/EntryPoints/Pdf.php index ef015aff76..de6b535743 100644 --- a/application/Espo/EntryPoints/Pdf.php +++ b/application/Espo/EntryPoints/Pdf.php @@ -34,21 +34,23 @@ use Espo\Core\{ Exceptions\BadRequest, EntryPoint\EntryPoint, ORM\EntityManager, - ServiceFactory, Api\Request, Api\Response, + Utils\Util, }; +use Espo\Services\Pdf as Service; + class Pdf implements EntryPoint { - protected $entityManager; + private $entityManager; - protected $serviceFactory; + private $service; - public function __construct(EntityManager $entityManager, ServiceFactory $serviceFactory) + public function __construct(EntityManager $entityManager, Service $service) { $this->entityManager = $entityManager; - $this->serviceFactory = $serviceFactory; + $this->service = $service; } public function run(Request $request, Response $response): void @@ -68,8 +70,26 @@ class Pdf implements EntryPoint throw new NotFound(); } - $this->serviceFactory->create('Pdf')->buildFromTemplate($entity, $template, true); + $contents = $this->service->generate($entity, $template); - exit; + $fileName = Util::sanitizeFileName( + $entity->get('name') ?? 'unnamed' + ); + + $fileName = $fileName . '.pdf'; + + $response + ->setHeader('Content-Type', 'application/pdf') + ->setHeader('Cache-Control', 'private, must-revalidate, post-check=0, pre-check=0, max-age=1') + ->setHeader('Pragma', 'public') + ->setHeader('Expires', 'Sat, 26 Jul 1997 05:00:00 GMT') + ->setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT') + ->setHeader('Content', 'inline; filename="' . basename($fileName) . '"'); + + if (!$request->getServerParam('HTTP_ACCEPT_ENCODING')) { + $response->setHeader('Content-Length', $contents->getLength()); + } + + $response->writeBody($contents); } } diff --git a/application/Espo/Services/Pdf.php b/application/Espo/Services/Pdf.php index edb0d162d7..6e758af7f8 100644 --- a/application/Espo/Services/Pdf.php +++ b/application/Espo/Services/Pdf.php @@ -36,18 +36,16 @@ use Espo\Core\Exceptions\{ }; use Espo\Core\{ - ServiceFactory, Acl, + Acl\Table, Utils\Config, Utils\Metadata, Utils\Language, Utils\Util, - Htmlizer\Htmlizer, - Htmlizer\Factory as HtmlizerFactory, ORM\EntityManager, ORM\Entity, - Pdf\Tcpdf, Select\SelectBuilderFactory, + Record\ServiceContainer, }; use Espo\{ @@ -57,60 +55,57 @@ use Espo\{ Tools\Pdf\Data, }; -use Espo\ORM\{ - QueryParams\Select, -}; +use Espo\Entities\Template; use DateTime; +use StdClass; class Pdf { + protected const DEFAULT_ENGINE = 'Tcpdf'; + protected $removeMassFilePeriod = '1 hour'; - protected $config; + private $config; - protected $serviceFactory; + private $metadata; - protected $metadata; + private $entityManager; - protected $entityManager; + private $acl; - protected $acl; + private $defaultLanguage; - protected $defaultLanguage; + private $selectBuilderFactory; - protected $htmlizerFactory; + private $builder; - protected $selectBuilderFactory; - - protected $builder; + private $serviceContanier; public function __construct( Config $config, - ServiceFactory $serviceFactory, Metadata $metadata, EntityManager $entityManager, Acl $acl, Language $defaultLanguage, - HtmlizerFactory $htmlizerFactory, SelectBuilderFactory $selectBuilderFactory, - Builder $builder + Builder $builder, + ServiceContainer $serviceContanier ) { $this->config = $config; - $this->serviceFactory = $serviceFactory; $this->metadata = $metadata; $this->entityManager = $entityManager; $this->acl = $acl; $this->defaultLanguage = $defaultLanguage; - $this->htmlizerFactory = $htmlizerFactory; $this->selectBuilderFactory = $selectBuilderFactory; $this->builder = $builder; + $this->serviceContanier = $serviceContanier; } public function generateMailMerge( string $entityType, iterable $entityList, - Entity $template, + Template $template, string $name, ?string $campaignId = null ): string { @@ -121,12 +116,7 @@ class Pdf $collection[] = $entity; } - if ($this->serviceFactory->checkExists($entityType)) { - $service = $this->serviceFactory->create($entityType); - } - else { - $service = $this->serviceFactory->create('Record'); - } + $service = $this->serviceContanier->get($entityType); foreach ($entityList as $entity) { $service->loadAdditionalFields($entity); @@ -140,7 +130,7 @@ class Pdf $printer = $this->builder ->setTemplate($templateWrapper) - ->setEngine('Tcpdf') + ->setEngine(self::DEFAULT_ENGINE) ->build(); $contents = $printer->printCollection($collection); @@ -170,11 +160,7 @@ class Pdf bool $checkAcl = false ): string { - if ($this->serviceFactory->checkExists($entityType)) { - $service = $this->serviceFactory->create($entityType); - } else { - $service = $this->serviceFactory->create('Record'); - } + $service = $this->serviceContanier->get($entityType); $maxCount = $this->config->get('massPrintPdfMaxCount'); @@ -224,7 +210,7 @@ class Pdf $templateWrapper = new TemplateWrapper($template); - $engine = $this->config->get('pdfEngine') ?? 'Tcpdf'; + $engine = $this->config->get('pdfEngine') ?? self::DEFAULT_ENGINE; $printer = $this->builder ->setTemplate($templateWrapper) @@ -265,7 +251,7 @@ class Pdf return $attachment->id; } - public function removeMassFileJob($data) + public function removeMassFileJob(StdClass $data): void { if (empty($data->id)) { return; @@ -284,20 +270,38 @@ class Pdf $this->entityManager->removeEntity($attachment); } + /** + * Generate PDF. ACL check is processed if `$data` is null. + */ + public function generate(Entity $entity, Template $template, ?Data $data = null): string + { + return $this->buildFromTemplateInternal($entity, $template, false, null, $data); + } + + /** + * @deprecated + */ public function buildFromTemplate( Entity $entity, - Entity $template, + Template $template, bool $displayInline = false, ?array $additionalData = null ): ?string { + return $this->buildFromTemplateInternal($entity, $template, $displayInline, $additionalData); + } + + private function buildFromTemplateInternal( + Entity $entity, + Entity $template, + bool $displayInline = false, + ?array $additionalData = null, + ?Data $data = null + ): ?string { + $entityType = $entity->getEntityType(); - if ($this->serviceFactory->checkExists($entityType)) { - $service = $this->serviceFactory->create($entityType); - } else { - $service = $this->serviceFactory->create('Record'); - } + $service = $this->serviceContanier->get($entityType); $service->loadAdditionalFields($entity); @@ -309,17 +313,31 @@ class Pdf throw new Error("Not matching entity types."); } - if (!$this->acl->check($entity, 'read') || !$this->acl->check($template, 'read')) { - throw new Forbidden(); + $applyAcl = true; + + if ($data) { + $applyAcl = $data->applyAcl(); + } + + if ($applyAcl) { + if ( + !$this->acl->check($entity, Table::ACTION_READ) || + !$this->acl->check($template, Table::ACTION_READ) + ) { + throw new Forbidden(); + } } $templateWrapper = new TemplateWrapper($template); - $data = Data::createFromArray([ - 'additionalTemplateData' => $additionalData, - ]); + if (!$data) { + $data = Data + ::fromNothing() + ->withAdditionalTemplateData($additionalData ?? []) + ->withAcl($applyAcl); + } - $engine = $this->config->get('pdfEngine') ?? 'Tcpdf'; + $engine = $this->config->get('pdfEngine') ?? self::DEFAULT_ENGINE; $printer = $this->builder ->setTemplate($templateWrapper) @@ -337,7 +355,10 @@ class Pdf return $contents->getString(); } - protected function displayInline(Entity $entity, Contents $contents) + /** + * @deprecated + */ + private function displayInline(Entity $entity, Contents $contents): void { $fileName = Util::sanitizeFileName( $entity->get('name') ?? 'unnamed' @@ -358,9 +379,4 @@ class Pdf echo $contents->getString(); } - - protected function createHtmlizer() - { - return $this->htmlizerFactory->create(); - } } diff --git a/application/Espo/Tools/Pdf/Data.php b/application/Espo/Tools/Pdf/Data.php index cffe282396..8a6cde5f12 100644 --- a/application/Espo/Tools/Pdf/Data.php +++ b/application/Espo/Tools/Pdf/Data.php @@ -36,22 +36,43 @@ class Data { private $additionalTemplateData = []; + private $applyAcl = false; + public function setAdditionalTemplateData(array $additionalTemplateData) { $this->additionalTemplateData = $additionalTemplateData; } + public function applyAcl(): bool + { + return $this->applyAcl; + } + public function getAdditionalTemplateData(): array { return $this->additionalTemplateData; } - public static function createFromArray(array $data): Data + public function withAdditionalTemplateData(array $additionalTemplateData): self { - $obj = new self(); + $obj = clone $this; - $obj->additionalTemplateData = $data['additionalTemplateData'] ?? []; + $obj->additionalTemplateData = $additionalTemplateData; return $obj; } + + public function withAcl(bool $applyAcl = true): self + { + $obj = clone $this; + + $obj->applyAcl = $applyAcl; + + return $obj; + } + + public static function fromNothing(): self + { + return new self(); + } } diff --git a/application/Espo/Tools/Pdf/Tcpdf/EntityProcessor.php b/application/Espo/Tools/Pdf/Tcpdf/EntityProcessor.php index e09779c841..00ed6d3152 100644 --- a/application/Espo/Tools/Pdf/Tcpdf/EntityProcessor.php +++ b/application/Espo/Tools/Pdf/Tcpdf/EntityProcessor.php @@ -62,7 +62,9 @@ class EntityProcessor { $additionalData = $data->getAdditionalTemplateData(); - $htmlizer = $this->htmlizerFactory->create(); + $htmlizer = $data->applyAcl() ? + $this->htmlizerFactory->create() : + $this->htmlizerFactory->createNoAcl(); $fontFace = $this->config->get('pdfFontFace', $this->fontFace);