diff --git a/application/Espo/Core/Htmlizer/Htmlizer.php b/application/Espo/Core/Htmlizer/Htmlizer.php index 8b803d6936..befc142142 100644 --- a/application/Espo/Core/Htmlizer/Htmlizer.php +++ b/application/Espo/Core/Htmlizer/Htmlizer.php @@ -105,6 +105,86 @@ class Htmlizer $this->injectableFactory = $injectableFactory; } +/** + * Generate an HTML for entity by a given template. + * + * @param $cacheId @deprecated To be skipped.. + * @param $additionalData Data will be passed to the template. + * @param $skipLinks Do not process related records. + */ + public function render( + ?Entity $entity, + string $template, + ?string $cacheId = null, + ?array $additionalData = null, + bool $skipLinks = false + ): string { + + $template = str_replace('getHelpers(); + + $code = LightnCandy::compile($template, [ + 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_ERROR_EXCEPTION, + 'helpers' => $this->getHelpers(), + ]); + + $renderer = LightnCandy::prepare($code); + + if ($additionalData === null) { + $additionalData = $additionalData ?? []; + } + + $data = $entity ? + $this->getDataFromEntity($entity, $skipLinks, 0, $template, $additionalData) : + $additionalData; + + if (!array_key_exists('today', $data)) { + $data['today'] = $this->dateTime->getTodayString(); + $data['today_RAW'] = date('Y-m-d'); + } + + if (!array_key_exists('now', $data)) { + $data['now'] = $this->dateTime->getNowString(); + $data['now_RAW'] = date('Y-m-d H:i:s'); + } + + $data['__injectableFactory'] = $this->injectableFactory; + $data['__config'] = $this->config; + $data['__dateTime'] = $this->dateTime; + $data['__metadata'] = $this->metadata; + $data['__entityManager'] = $this->entityManager; + $data['__language'] = $this->language; + $data['__serviceFactory'] = $this->serviceFactory; + $data['__log'] = $this->log; + $data['__entityType'] = $entity->getEntityType(); + + $html = $renderer($data); + + $html = str_replace('?entryPoint=attachment&', '?entryPoint=attachment&', $html); + + if ($this->entityManager) { + $html = preg_replace_callback( + '/\?entryPoint=attachment\&id=([A-Za-z0-9]*)/', + function ($matches) { + $id = $matches[1]; + $attachment = $this->entityManager->getEntity('Attachment', $id); + + if ($attachment) { + $filePath = $this->entityManager + ->getRepository('Attachment') + ->getFilePath($attachment); + + return $filePath; + } + }, + $html + ); + } + + return $html; + } + protected function format($value) { if (is_float($value)) { @@ -633,83 +713,6 @@ class Htmlizer return $helpers; } - /** - * Generate an HTML for entity by a given template. - * - * @param $cacheId @deprecated Skip or specify NULL. - * @param $additionalData Data will be passed to the template. - * @param $skipLinks Do not process related records. - */ - public function render( - Entity $entity, - string $template, - ?string $cacheId = null, - ?array $additionalData = null, - bool $skipLinks = false - ): string { - - $template = str_replace('getHelpers(); - - $code = LightnCandy::compile($template, [ - 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_ERROR_EXCEPTION, - 'helpers' => $this->getHelpers(), - ]); - - $renderer = LightnCandy::prepare($code); - - - $additionalData = $additionalData ?? []; - - $data = $this->getDataFromEntity($entity, $skipLinks, 0, $template, $additionalData); - - if (!array_key_exists('today', $data)) { - $data['today'] = $this->dateTime->getTodayString(); - $data['today_RAW'] = date('Y-m-d'); - } - - if (!array_key_exists('now', $data)) { - $data['now'] = $this->dateTime->getNowString(); - $data['now_RAW'] = date('Y-m-d H:i:s'); - } - - $data['__injectableFactory'] = $this->injectableFactory; - $data['__config'] = $this->config; - $data['__dateTime'] = $this->dateTime; - $data['__metadata'] = $this->metadata; - $data['__entityManager'] = $this->entityManager; - $data['__language'] = $this->language; - $data['__serviceFactory'] = $this->serviceFactory; - $data['__log'] = $this->log; - $data['__entityType'] = $entity->getEntityType(); - - $html = $renderer($data); - - $html = str_replace('?entryPoint=attachment&', '?entryPoint=attachment&', $html); - - if ($this->entityManager) { - $html = preg_replace_callback( - '/\?entryPoint=attachment\&id=([A-Za-z0-9]*)/', - function ($matches) { - $id = $matches[1]; - $attachment = $this->entityManager->getEntity('Attachment', $id); - - if ($attachment) { - $filePath = $this->entityManager - ->getRepository('Attachment') - ->getFilePath($attachment); - - return $filePath; - } - }, - $html - ); - } - - return $html; - } - protected function getFieldType(string $entityType, string $field) { if (!$this->metadata) { diff --git a/application/Espo/Core/Htmlizer/TemplateRenderer.php b/application/Espo/Core/Htmlizer/TemplateRenderer.php new file mode 100644 index 0000000000..2274ecf58a --- /dev/null +++ b/application/Espo/Core/Htmlizer/TemplateRenderer.php @@ -0,0 +1,164 @@ +htmlizerFactory = $htmlizerFactory; + $this->aclManager = $aclManager; + } + + /** + * Setting a user also sets applyAcl. + */ + public function setUser(User $user): self + { + $this->user = $user; + + $this->setApplyAcl(); + + return $this; + } + + public function setEntity(Entity $entity): self + { + $this->entity = $entity; + + return $this; + } + + /** + * @param stdClass|array $data Additional data. + */ + public function setData($data): self + { + if (!is_array($data) && !$data instanceof stdClass) { + throw new InvalidArgumentException(); + } + + if (is_object($data)) { + $data = get_object_vars($data); + } + + $this->data = $data; + + return $this; + } + + public function setSkipRelations(bool $skipRelations = true): self + { + $this->skipRelations = $skipRelations; + + return $this; + } + + public function setApplyAcl(bool $applyAcl = true): self + { + $this->applyAcl = $applyAcl; + + return $this; + } + + public function render(): string + { + if (!$this->template) { + throw new LogicException("No template."); + } + + return $this->renderTemplate($this->template); + } + + public function renderTemplate(string $template): string + { + return $this->renderTemplateInternal($template, $this->createHtmlizer()); + } + + private function renderTemplateInternal(string $template, Htmlizer $htmlizer): string + { + return $htmlizer->render( + $this->entity, + $template, + null, + $this->data, + $this->skipRelations + ); + } + + /** + * @return string + */ + public function renderMultipleTemplates(string ...$templateList): array + { + $htmlizer = $this->createHtmlizer(); + + $resultList = []; + + foreach ($templateList as $template) { + $resultList[] = $this->renderTemplateInternal($template, $htmlizer); + } + + return $resultList; + } + + private function createHtmlizer(): Htmlizer + { + return $this->user ? + $this->htmlizerFactory->createForUser($this->user) : + $this->htmlizerFactory->create(!$this->applyAcl); + } +} diff --git a/application/Espo/Core/Htmlizer/TemplateRendererFactory.php b/application/Espo/Core/Htmlizer/TemplateRendererFactory.php new file mode 100644 index 0000000000..7f0ea18f86 --- /dev/null +++ b/application/Espo/Core/Htmlizer/TemplateRendererFactory.php @@ -0,0 +1,47 @@ +injectableFactory = $injectableFactory; + } + + public function create(): TemplateRenderer + { + return $this->injectableFactory->create(TemplateRenderer::class); + } +} diff --git a/application/Espo/Tools/Pdf/Tcpdf/EntityProcessor.php b/application/Espo/Tools/Pdf/Tcpdf/EntityProcessor.php index f24231d8b1..7b34b4c629 100644 --- a/application/Espo/Tools/Pdf/Tcpdf/EntityProcessor.php +++ b/application/Espo/Tools/Pdf/Tcpdf/EntityProcessor.php @@ -30,9 +30,11 @@ namespace Espo\Tools\Pdf\Tcpdf; use Espo\Core\Utils\Config; -use Espo\Core\Htmlizer\Htmlizer as Htmlizer; -use Espo\Core\Htmlizer\HtmlizerFactory as HtmlizerFactory; +use Espo\Core\Htmlizer\TemplateRendererFactory; +use Espo\Core\Htmlizer\TemplateRenderer; + use Espo\ORM\Entity; + use Espo\Tools\Pdf\Template; use Espo\Tools\Pdf\Data; use Espo\Tools\Pdf\Params; @@ -46,21 +48,21 @@ class EntityProcessor private $config; - private $htmlizerFactory; + private $templateRendererFactory; - public function __construct(Config $config, HtmlizerFactory $htmlizerFactory) + public function __construct(Config $config, TemplateRendererFactory $templateRendererFactory) { $this->config = $config; - $this->htmlizerFactory = $htmlizerFactory; + $this->templateRendererFactory = $templateRendererFactory; } public function process(Tcpdf $pdf, Template $template, Entity $entity, Params $params, Data $data): void { - $additionalData = get_object_vars($data->getAdditionalTemplateData()); - - $htmlizer = $params->applyAcl() ? - $this->htmlizerFactory->create() : - $this->htmlizerFactory->createNoAcl(); + $renderer = $this->templateRendererFactory + ->create() + ->setApplyAcl($params->applyAcl()) + ->setEntity($entity) + ->setData($data->getAdditionalTemplateData()); $fontFace = $this->config->get('pdfFontFace', $this->fontFace); $fontSize = $this->config->get('pdfFontSize', $this->fontSize); @@ -103,7 +105,7 @@ class EntityProcessor } if ($template->hasFooter()) { - $htmlFooter = $this->render($htmlizer, $entity, $template->getFooter(), $additionalData); + $htmlFooter = $this->render($renderer, $template->getFooter()); $pdf->setFooterFont([$fontFace, '', $this->fontSize]); $pdf->setFooterPosition($template->getFooterPosition()); @@ -115,7 +117,7 @@ class EntityProcessor } if ($template->hasHeader()) { - $htmlHeader = $this->render($htmlizer, $entity, $template->getHeader(), $additionalData); + $htmlHeader = $this->render($renderer, $template->getHeader()); $pdf->setHeaderFont([$fontFace, '', $this->fontSize]); $pdf->setHeaderPosition($template->getHeaderPosition()); @@ -128,21 +130,16 @@ class EntityProcessor $pdf->addPage($pageOrientationCode, $pageFormat); - $htmlBody = $this->render($htmlizer, $entity, $template->getBody(), $additionalData); + $htmlBody = $this->render($renderer, $template->getBody()); $pdf->writeHTML($htmlBody, true, false, true, false, ''); } - private function render(Htmlizer $htmlizer, Entity $entity, string $template, array $additionalData): string + private function render(TemplateRenderer $renderer, string $template): string { - $html = $htmlizer->render( - $entity, - $template, - null, - $additionalData - ); + $html = $renderer->renderTemplate($template); - $html = preg_replace_callback( + return preg_replace_callback( '//', function ($matches) { $dataString = $matches[1]; @@ -153,8 +150,6 @@ class EntityProcessor }, $html ); - - return $html; } private function composeBarcodeTag(array $data): string