.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Tools\Pdf\Dompdf;
use Espo\Core\Htmlizer\TemplateRendererFactory;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Log;
use Espo\ORM\Entity;
use Espo\Tools\Pdf\Data;
use Espo\Tools\Pdf\Params;
use Espo\Tools\Pdf\Template;
use Picqer\Barcode\BarcodeGeneratorSVG;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
class HtmlComposer
{
public function __construct(
private Config $config,
private TemplateRendererFactory $templateRendererFactory,
private ImageSourceProvider $imageSourceProvider,
private Log $log
) {}
public function composeHead(Template $template, Entity $entity): string
{
$topMargin = $template->getTopMargin();
$rightMargin = $template->getRightMargin();
$bottomMargin = $template->getBottomMargin();
$leftMargin = $template->getLeftMargin();
$fontSize = $this->config->get('pdfFontSize') ?? 12;
$headerPosition = $template->getHeaderPosition();
$footerPosition = $template->getFooterPosition();
$titleHtml = '';
if ($template->hasTitle()) {
$title = $this->replacePlaceholders($template->getTitle(), $entity);
$titleHtml = "
" . htmlspecialchars($title) . "";
}
$templateStyle = $template->getStyle() ?? '';
/** @noinspection HtmlRequiredTitleElement */
return "
{$titleHtml}
";
}
public function composeHeaderFooter(Template $template, Entity $entity, Params $params, Data $data): string
{
$html = "";
$renderer = $this->templateRendererFactory
->create()
->setApplyAcl($params->applyAcl())
->setEntity($entity)
->setSkipInlineAttachmentHandling()
->setData($data->getAdditionalTemplateData());
if ($template->hasHeader()) {
$htmlHeader = $renderer->renderTemplate($template->getHeader());
$htmlHeader = $this->replaceHeadTags($htmlHeader);
$html .= "";
}
if ($template->hasFooter()) {
$htmlFooter = $renderer->renderTemplate($template->getFooter());
$htmlFooter = $this->replaceHeadTags($htmlFooter);
$html .= "";
}
return $html;
}
public function composeMain(
Template $template,
Entity $entity,
Params $params,
Data $data
): string {
$renderer = $this->templateRendererFactory
->create()
->setApplyAcl($params->applyAcl())
->setEntity($entity)
->setSkipInlineAttachmentHandling()
->setData($data->getAdditionalTemplateData());
$bodyTemplate = $template->getBody();
$html = $renderer->renderTemplate($bodyTemplate);
$html = $this->replaceTags($html);
return "$html";
}
private function replaceTags(string $html): string
{
/** @noinspection HtmlUnknownAttribute */
$html = str_replace('
', '', $html);
$html = preg_replace('/src="@([A-Za-z0-9+\/]*={0,2})"/', 'src="data:image/jpeg;base64,$1"', $html);
$html = str_replace('?entryPoint=attachment&', '?entryPoint=attachment&', $html ?? '');
$html = preg_replace_callback(
'//',
function ($matches) {
$dataString = $matches[1];
$data = json_decode(urldecode($dataString), true);
return $this->composeBarcode($data);
},
$html
) ?? '';
return preg_replace_callback(
"/src=\"\?entryPoint=attachment&id=([A-Za-z0-9\-]*)\"/",
function ($matches) {
$id = $matches[1];
if (!$id) {
return '';
}
$src = $this->imageSourceProvider->get($id);
if (!$src) {
return '';
}
return "src=\"$src\"";
},
$html
) ?? '';
}
private function replaceHeadTags(string $html): string
{
$html = str_replace('{pageNumber}', '', $html);
return $this->replaceTags($html);
}
/**
*
* @param array $data
* @return string
*/
private function composeBarcode(array $data): string
{
$value = $data['value'] ?? null;
if ($value === null) {
return '';
}
$codeType = $data['type'] ?? 'CODE128';
/** @noinspection SpellCheckingInspection */
$typeMap = [
"CODE128" => 'C128',
"CODE128A" => 'C128A',
"CODE128B" => 'C128B',
"CODE128C" => 'C128C',
"EAN13" => 'EAN13',
"EAN8" => 'EAN8',
"EAN5" => 'EAN5',
"EAN2" => 'EAN2',
"UPC" => 'UPCA',
"UPCE" => 'UPCE',
"ITF14" => 'I25',
"pharmacode" => 'PHARMA',
"QRcode" => 'QRCODE,H',
];
$type = $typeMap[$codeType] ?? null;
/** @noinspection SpellCheckingInspection */
if ($codeType === 'QRcode') {
$width = $data['width'] ?? 40;
$height = $data['height'] ?? 40;
//$color = $data['color'] ?? '#000';
$options = new QROptions();
$options->outputType = QRCode::OUTPUT_MARKUP_SVG;
$options->eccLevel = QRCode::ECC_H;
$code = (new QRCode($options))->render($value);
$css = "width: {$width}mm; height: {$height}mm;";
/** @noinspection HtmlRequiredAltAttribute */
return "
";
}
if (!$type || $type === 'QRCODE,H') {
$this->log->warning("Not supported barcode type $codeType.");
return '';
}
$width = $data['width'] ?? 60;
$height = $data['height'] ?? 30;
$color = $data['color'] ?? '#000';
$code = (new BarcodeGeneratorSVG())->getBarcode($value, $type, 2, $height, $color);
$encoded = base64_encode($code);
$css = "width: {$width}mm; height: {$height}mm;";
/** @noinspection HtmlRequiredAltAttribute */
return "
";
}
private function replacePlaceholders(string $string, Entity $entity): string
{
$newString = $string;
$attributeList = ['name'];
foreach ($attributeList as $attribute) {
$value = (string) ($entity->get($attribute) ?? '');
$newString = str_replace('{$' . $attribute . '}', $value, $newString);
}
return $newString;
}
}