From c1d28655daa78ed1f0ac36bac9a1b698c952ab3d Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sun, 21 Jan 2024 18:26:39 +0200 Subject: [PATCH] template iterate --- application/Espo/Core/Htmlizer/Htmlizer.php | 97 ++++++++++++++++++++- composer.json | 1 + 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/application/Espo/Core/Htmlizer/Htmlizer.php b/application/Espo/Core/Htmlizer/Htmlizer.php index ebfb53b330..a5dd8641a4 100644 --- a/application/Espo/Core/Htmlizer/Htmlizer.php +++ b/application/Espo/Core/Htmlizer/Htmlizer.php @@ -30,6 +30,10 @@ namespace Espo\Core\Htmlizer; use Closure; +use DOMDocument; +use DOMElement; +use DOMException; +use DOMXPath; use Espo\Core\ORM\Entity as CoreEntity; use Espo\Entities\Attachment; use Espo\Repositories\Attachment as AttachmentRepository; @@ -51,6 +55,7 @@ use Espo\ORM\EntityManager; use LightnCandy\Flags; use LightnCandy\LightnCandy as LightnCandy; +use LogicException; use RuntimeException; use stdClass; @@ -95,7 +100,7 @@ class Htmlizer bool $skipInlineAttachmentHandling = false ): string { - $template = str_replace('prepare($template); $code = LightnCandy::compile($template, [ 'flags' => Flags::FLAG_HANDLEBARSJS | Flags::FLAG_ERROR_EXCEPTION, @@ -833,4 +838,94 @@ class Htmlizer return [[$orderBy, $order]]; } + + private function handleIteration(string $template): string + { + if (!extension_loaded('dom')) { + $this->log?->warning("Extension 'dom' is not enabled. HTML templating functionality is restricted."); + + return $template; + } + + $xml = new DOMDocument(); + + $loadResult = $xml->loadHTML($template); + + if ($loadResult === false) { + $this->log?->warning("HTML template parsing error."); + + return $template; + } + + $xpath = new DOMXPath($xml); + + $found = false; + + $elements = $xpath->query("//*[@iterate]"); + + if (!$elements) { + return $template; + } + + foreach ($elements as $element) { + if (!$element instanceof DOMElement) { + continue; + } + + try { + $wrapperElement = $xml->createElement('iteration-wrapper'); + + if (!$wrapperElement) { + throw new LogicException(); + } + + $wrapperElement->setAttribute('v', $element->getAttribute('iterate')); + } + catch (DOMException $e) { + throw new LogicException($e->getMessage()); + } + + $parentNode = $element->parentNode; + + if (!$parentNode) { + throw new LogicException(); + } + + $newElement = $xml->importNode($element->cloneNode(true)); + + if (!$newElement instanceof DOMElement) { + throw new LogicException(); + } + + $newElement->removeAttribute('iterate'); + + $wrapperElement->appendChild($newElement); + $parentNode->replaceChild($wrapperElement, $element); + + $found = true; + } + + if (!$found) { + return $template; + } + + $newTemplate = $xml->saveXML(); + + if ($newTemplate === false || !is_string($newTemplate)) { + $this->log?->warning("DOM save error."); + + return $template; + } + + $newTemplate = str_replace('', '{{/each}}', $newTemplate); + + return preg_replace('//', '{{#each $1}}', $newTemplate) ?? ''; + } + + private function prepare(string $template): string + { + $template = str_replace('handleIteration($template); + } } diff --git a/composer.json b/composer.json index eb02eed440..f571de0fbd 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "ext-gd": "*", "ext-mbstring": "*", "ext-xml": "*", + "ext-dom": "*", "ext-curl": "*", "ext-exif": "*", "ext-pdo": "*",