Initial commit
This commit is contained in:
45
application/Espo/Tools/Pdf/AttachmentProvider.php
Normal file
45
application/Espo/Tools/Pdf/AttachmentProvider.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @template TEntity of Entity
|
||||
* @since 9.2.0
|
||||
*/
|
||||
interface AttachmentProvider
|
||||
{
|
||||
/**
|
||||
* @param TEntity $entity
|
||||
* @return AttachmentWrapper[]
|
||||
*/
|
||||
public function get(Entity $entity, Params $params): array;
|
||||
}
|
||||
53
application/Espo/Tools/Pdf/AttachmentWrapper.php
Normal file
53
application/Espo/Tools/Pdf/AttachmentWrapper.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Espo\Entities\Attachment;
|
||||
|
||||
/**
|
||||
* @since 9.2.0
|
||||
*/
|
||||
readonly class AttachmentWrapper
|
||||
{
|
||||
public function __construct(
|
||||
private Attachment $attachment,
|
||||
private ?string $description = null,
|
||||
) {}
|
||||
|
||||
public function getAttachment(): Attachment
|
||||
{
|
||||
return $this->attachment;
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
}
|
||||
74
application/Espo/Tools/Pdf/Builder.php
Normal file
74
application/Espo/Tools/Pdf/Builder.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use RuntimeException;
|
||||
|
||||
class Builder
|
||||
{
|
||||
private ?Template $template = null;
|
||||
private ?string $engine = null;
|
||||
|
||||
public function __construct(private InjectableFactory $injectableFactory) {}
|
||||
|
||||
public function setTemplate(Template $template): self
|
||||
{
|
||||
$this->template = $template;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEngine(string $engine): self
|
||||
{
|
||||
$this->engine = $engine;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build(): PrinterController
|
||||
{
|
||||
if (!$this->engine) {
|
||||
throw new RuntimeException('Engine is not set.');
|
||||
}
|
||||
|
||||
if (!$this->template) {
|
||||
throw new RuntimeException('Template is not set.');
|
||||
}
|
||||
|
||||
return $this->injectableFactory->createWith(
|
||||
PrinterController::class,
|
||||
[
|
||||
'template' => $this->template,
|
||||
'engine' => $this->engine,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
40
application/Espo/Tools/Pdf/CollectionPrinter.php
Normal file
40
application/Espo/Tools/Pdf/CollectionPrinter.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Espo\ORM\Collection;
|
||||
|
||||
interface CollectionPrinter
|
||||
{
|
||||
/**
|
||||
* @param Collection<\Espo\ORM\Entity> $collection
|
||||
*/
|
||||
public function print(Template $template, Collection $collection, Params $params, IdDataMap $idDataMap): Contents;
|
||||
}
|
||||
41
application/Espo/Tools/Pdf/Contents.php
Normal file
41
application/Espo/Tools/Pdf/Contents.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
interface Contents
|
||||
{
|
||||
public function getStream(): StreamInterface;
|
||||
|
||||
public function getString(): string;
|
||||
|
||||
public function getLength(): int;
|
||||
}
|
||||
77
application/Espo/Tools/Pdf/ContentsString.php
Normal file
77
application/Espo/Tools/Pdf/ContentsString.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ContentsString implements Contents
|
||||
{
|
||||
private string $contents;
|
||||
|
||||
private function __construct(string $contents)
|
||||
{
|
||||
$this->contents = $contents;
|
||||
}
|
||||
|
||||
public function getStream(): StreamInterface
|
||||
{
|
||||
$resource = fopen('php://temp', 'r+');
|
||||
|
||||
if ($resource === false) {
|
||||
throw new RuntimeException("Could not open temp.");
|
||||
}
|
||||
|
||||
fwrite($resource, $this->getString());
|
||||
rewind($resource);
|
||||
|
||||
return new Stream($resource);
|
||||
}
|
||||
|
||||
public function getString(): string
|
||||
{
|
||||
return $this->contents;
|
||||
}
|
||||
|
||||
public function getLength(): int
|
||||
{
|
||||
return strlen($this->contents);
|
||||
}
|
||||
|
||||
public static function createFromString(string $contents): ContentsString
|
||||
{
|
||||
$obj = new self($contents);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
84
application/Espo/Tools/Pdf/Data.php
Normal file
84
application/Espo/Tools/Pdf/Data.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class Data
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private $additionalTemplateData = [];
|
||||
/** @var AttachmentWrapper[] */
|
||||
private $attachments = [];
|
||||
|
||||
public function getAdditionalTemplateData(): stdClass
|
||||
{
|
||||
return (object) $this->additionalTemplateData;
|
||||
}
|
||||
|
||||
public function withAdditionalTemplateData(stdClass $additionalTemplateData): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$obj->additionalTemplateData = array_merge(
|
||||
$obj->additionalTemplateData,
|
||||
get_object_vars($additionalTemplateData)
|
||||
);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AttachmentWrapper[] $attachments
|
||||
*/
|
||||
public function withAttachmentsAdded(array $attachments): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
$obj->attachments[] = $attachment;
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AttachmentWrapper[]
|
||||
*/
|
||||
public function getAttachments(): array
|
||||
{
|
||||
return $this->attachments;
|
||||
}
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
||||
40
application/Espo/Tools/Pdf/Data/DataLoader.php
Normal file
40
application/Espo/Tools/Pdf/Data/DataLoader.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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\Data;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Pdf\Params;
|
||||
|
||||
use stdClass;
|
||||
|
||||
interface DataLoader
|
||||
{
|
||||
public function load(Entity $entity, Params $params): stdClass;
|
||||
}
|
||||
99
application/Espo/Tools/Pdf/Data/DataLoaderManager.php
Normal file
99
application/Espo/Tools/Pdf/Data/DataLoaderManager.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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\Data;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Tools\Pdf\AttachmentProvider;
|
||||
use Espo\Tools\Pdf\Data;
|
||||
use Espo\Tools\Pdf\Params;
|
||||
|
||||
class DataLoaderManager
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private InjectableFactory $injectableFactory,
|
||||
) {}
|
||||
|
||||
public function load(Entity $entity, ?Params $params = null, ?Data $data = null): Data
|
||||
{
|
||||
if (!$params) {
|
||||
$params = Params::create();
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
$data = Data::create();
|
||||
}
|
||||
|
||||
$defs = $this->metadata->get("pdfDefs.{$entity->getEntityType()}") ?? [];
|
||||
|
||||
/** @var class-string<DataLoader>[] $loaderClassList */
|
||||
$loaderClassList = $defs['dataLoaderClassNameList'] ?? [];
|
||||
|
||||
foreach ($loaderClassList as $className) {
|
||||
$loadedData = $this->createLoader($className)
|
||||
->load($entity, $params);
|
||||
|
||||
$data = $data->withAdditionalTemplateData($loadedData);
|
||||
}
|
||||
|
||||
/** @var class-string<AttachmentProvider<Entity>>[] $attachmentProviderClassList */
|
||||
$attachmentProviderClassList = $defs['attachmentProviderClassNameList'] ?? [];
|
||||
|
||||
foreach ($attachmentProviderClassList as $className) {
|
||||
$provider = $this->createProvider($className);
|
||||
|
||||
$attachments = $provider->get($entity, $params);
|
||||
|
||||
$data = $data->withAttachmentsAdded($attachments);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<DataLoader> $className
|
||||
*/
|
||||
private function createLoader(string $className): DataLoader
|
||||
{
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<AttachmentProvider<Entity>> $className
|
||||
* @return AttachmentProvider<Entity>
|
||||
*/
|
||||
private function createProvider(string $className): AttachmentProvider
|
||||
{
|
||||
/** @var AttachmentProvider<Entity> */
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
}
|
||||
73
application/Espo/Tools/Pdf/Dompdf/Contents.php
Normal file
73
application/Espo/Tools/Pdf/Dompdf/Contents.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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\Tools\Pdf\Contents as ContentsInterface;
|
||||
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Dompdf\Dompdf;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class Contents implements ContentsInterface
|
||||
{
|
||||
private ?string $string = null;
|
||||
|
||||
public function __construct(private Dompdf $pdf) {}
|
||||
|
||||
public function getStream(): StreamInterface
|
||||
{
|
||||
$resource = fopen('php://temp', 'r+');
|
||||
|
||||
if ($resource === false) {
|
||||
throw new RuntimeException("Could not open temp.");
|
||||
}
|
||||
|
||||
fwrite($resource, $this->getString());
|
||||
rewind($resource);
|
||||
|
||||
return new Stream($resource);
|
||||
}
|
||||
|
||||
public function getString(): string
|
||||
{
|
||||
if ($this->string === null) {
|
||||
$this->string = $this->pdf->output();
|
||||
}
|
||||
|
||||
return $this->string ?? '';
|
||||
}
|
||||
|
||||
public function getLength(): int
|
||||
{
|
||||
return strlen($this->getString());
|
||||
}
|
||||
}
|
||||
96
application/Espo/Tools/Pdf/Dompdf/DompdfInitializer.php
Normal file
96
application/Espo/Tools/Pdf/Dompdf/DompdfInitializer.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 Dompdf\Dompdf;
|
||||
use Dompdf\Options;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Tools\Pdf\Params;
|
||||
use Espo\Tools\Pdf\Template;
|
||||
|
||||
class DompdfInitializer
|
||||
{
|
||||
private string $defaultFontFace = 'DejaVu Sans';
|
||||
|
||||
private const PT = 2.83465;
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
) {}
|
||||
|
||||
public function initialize(Template $template, Params $params): Dompdf
|
||||
{
|
||||
$options = new Options();
|
||||
|
||||
$options->setIsPdfAEnabled($params->isPdfA());
|
||||
$options->setDefaultFont($this->getFontFace($template));
|
||||
|
||||
$pdf = new Dompdf($options);
|
||||
|
||||
if ($params->isPdfA()) {
|
||||
$this->mapFonts($pdf);
|
||||
}
|
||||
|
||||
$size = $template->getPageFormat() === Template::PAGE_FORMAT_CUSTOM ?
|
||||
[0.0, 0.0, $template->getPageWidth() * self::PT, $template->getPageHeight() * self::PT] :
|
||||
$template->getPageFormat();
|
||||
|
||||
$orientation = $template->getPageOrientation() === Template::PAGE_ORIENTATION_PORTRAIT ?
|
||||
'portrait' :
|
||||
'landscape';
|
||||
|
||||
$pdf->setPaper($size, $orientation);
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
private function getFontFace(Template $template): string
|
||||
{
|
||||
return
|
||||
$template->getFontFace() ??
|
||||
$this->config->get('pdfFontFace') ??
|
||||
$this->defaultFontFace;
|
||||
}
|
||||
|
||||
private function mapFonts(Dompdf $pdf): void
|
||||
{
|
||||
// Fonts are included in PDF/A. Map standard fonts to open source analogues.
|
||||
$fontMetrics = $pdf->getFontMetrics();
|
||||
|
||||
$fontMetrics->setFontFamily('courier', $fontMetrics->getFamily('DejaVu Sans Mono'));
|
||||
$fontMetrics->setFontFamily('fixed', $fontMetrics->getFamily('DejaVu Sans Mono'));
|
||||
$fontMetrics->setFontFamily('helvetica', $fontMetrics->getFamily('DejaVu Sans'));
|
||||
$fontMetrics->setFontFamily('monospace', $fontMetrics->getFamily('DejaVu Sans Mono'));
|
||||
$fontMetrics->setFontFamily('sans-serif', $fontMetrics->getFamily('DejaVu Sans'));
|
||||
$fontMetrics->setFontFamily('serif', $fontMetrics->getFamily('DejaVu Serif'));
|
||||
$fontMetrics->setFontFamily('times', $fontMetrics->getFamily('DejaVu Serif'));
|
||||
$fontMetrics->setFontFamily('times-roman', $fontMetrics->getFamily('DejaVu Serif'));
|
||||
}
|
||||
}
|
||||
95
application/Espo/Tools/Pdf/Dompdf/EntityPrinter.php
Normal file
95
application/Espo/Tools/Pdf/Dompdf/EntityPrinter.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 Dompdf\Adapter\CPDF;
|
||||
use Dompdf\Dompdf;
|
||||
use Espo\Core\FileStorage\Manager;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Pdf\Contents;
|
||||
use Espo\Tools\Pdf\Data;
|
||||
use Espo\Tools\Pdf\Dompdf\Contents as DompdfContents;
|
||||
use Espo\Tools\Pdf\EntityPrinter as EntityPrinterInterface;
|
||||
use Espo\Tools\Pdf\Params;
|
||||
use Espo\Tools\Pdf\Template;
|
||||
use RuntimeException;
|
||||
|
||||
class EntityPrinter implements EntityPrinterInterface
|
||||
{
|
||||
public function __construct(
|
||||
private DompdfInitializer $dompdfInitializer,
|
||||
private HtmlComposer $htmlComposer,
|
||||
private Manager $fileStorageManager,
|
||||
) {}
|
||||
|
||||
public function print(Template $template, Entity $entity, Params $params, Data $data): Contents
|
||||
{
|
||||
$pdf = $this->dompdfInitializer->initialize($template, $params);
|
||||
|
||||
$headHtml = $this->htmlComposer->composeHead($template, $entity);
|
||||
$headerFooterHtml = $this->htmlComposer->composeHeaderFooter($template, $entity, $params, $data);
|
||||
$mainHtml = $this->htmlComposer->composeMain($template, $entity, $params, $data);
|
||||
|
||||
$html = $headHtml . "\n<body>" . $headerFooterHtml . $mainHtml . "</body>";
|
||||
|
||||
$pdf->loadHtml($html);
|
||||
$pdf->render();
|
||||
|
||||
$this->addAttachments($pdf, $data);
|
||||
|
||||
return new DompdfContents($pdf);
|
||||
}
|
||||
|
||||
private function addAttachments(Dompdf $pdf, Data $data): void
|
||||
{
|
||||
if ($data->getAttachments() === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$canvas = $pdf->getCanvas();
|
||||
|
||||
if (!$canvas instanceof CPDF) {
|
||||
throw new RuntimeException("Non CPDF canvas");
|
||||
}
|
||||
|
||||
$cPdf = $canvas->get_cpdf();
|
||||
|
||||
foreach ($data->getAttachments() as $i => $attachmentWrapper) {
|
||||
$attachment = $attachmentWrapper->getAttachment();
|
||||
|
||||
$path = $this->fileStorageManager->getLocalFilePath($attachment);
|
||||
|
||||
$name = $attachment->getName() ?? 'file-' . $i;
|
||||
$description = $attachmentWrapper->getDescription() ?? '';
|
||||
|
||||
$cPdf->addEmbeddedFile($path, $name, $description);
|
||||
}
|
||||
}
|
||||
}
|
||||
313
application/Espo/Tools/Pdf/Dompdf/HtmlComposer.php
Normal file
313
application/Espo/Tools/Pdf/Dompdf/HtmlComposer.php
Normal file
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 = "<title>" . htmlspecialchars($title) . "</title>";
|
||||
}
|
||||
|
||||
$templateStyle = $template->getStyle() ?? '';
|
||||
|
||||
/** @noinspection HtmlRequiredTitleElement */
|
||||
return "
|
||||
<head>
|
||||
{$titleHtml}
|
||||
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
|
||||
</head>
|
||||
<style>
|
||||
@page {
|
||||
margin: {$topMargin}mm {$rightMargin}mm {$bottomMargin}mm {$leftMargin}mm;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: {$fontSize}pt;
|
||||
}
|
||||
|
||||
table.bordered, table.bordered td, table.bordered th {
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
> header {
|
||||
position: fixed;
|
||||
margin-top: -{$topMargin}mm;
|
||||
margin-left: -{$rightMargin}mm;
|
||||
margin-right: -{$leftMargin}mm;
|
||||
top: {$headerPosition}mm;
|
||||
left: {$leftMargin}mm;
|
||||
right: {$rightMargin}mm;
|
||||
}
|
||||
|
||||
> footer {
|
||||
position: fixed;
|
||||
margin-bottom: -{$bottomMargin}mm;
|
||||
margin-left: -{$leftMargin}mm;
|
||||
margin-right: -{$rightMargin}mm;
|
||||
height: {$footerPosition}mm;
|
||||
bottom: 0;
|
||||
left: {$leftMargin}mm;
|
||||
right: {$rightMargin}mm;
|
||||
}
|
||||
|
||||
> header .page-number:after,
|
||||
> footer .page-number:after {
|
||||
content: counter(page);
|
||||
}
|
||||
|
||||
$templateStyle
|
||||
</style>
|
||||
";
|
||||
}
|
||||
|
||||
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 .= "<header>$htmlHeader</header>";
|
||||
}
|
||||
|
||||
if ($template->hasFooter()) {
|
||||
$htmlFooter = $renderer->renderTemplate($template->getFooter());
|
||||
|
||||
$htmlFooter = $this->replaceHeadTags($htmlFooter);
|
||||
|
||||
$html .= "<footer>$htmlFooter</footer>";
|
||||
}
|
||||
|
||||
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 "<main>$html</main>";
|
||||
}
|
||||
|
||||
private function replaceTags(string $html): string
|
||||
{
|
||||
/** @noinspection HtmlUnknownAttribute */
|
||||
$html = str_replace('<br pagebreak="true">', '<div style="page-break-after: always;"></div>', $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(
|
||||
'/<barcodeimage data="([^"]+)"\/>/',
|
||||
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}', '<span class="page-number"></span>', $html);
|
||||
|
||||
return $this->replaceTags($html);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array<string, mixed> $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 "<img src=\"$code\" style=\"$css\">";
|
||||
}
|
||||
|
||||
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 "<img src=\"data:image/svg+xml;base64,$encoded\" style=\"$css\">";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
71
application/Espo/Tools/Pdf/Dompdf/ImageSourceProvider.php
Normal file
71
application/Espo/Tools/Pdf/Dompdf/ImageSourceProvider.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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\Exceptions\Forbidden;
|
||||
use Espo\Core\FileStorage\Manager as FileStorageManager;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\Attachment\Checker;
|
||||
|
||||
class ImageSourceProvider
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private Checker $checker,
|
||||
private FileStorageManager $fileStorageManager,
|
||||
) {}
|
||||
|
||||
public function get(string $id): ?string
|
||||
{
|
||||
/** @var Attachment $attachment */
|
||||
$attachment = $this->entityManager->getEntityById(Attachment::ENTITY_TYPE, $id);
|
||||
|
||||
if (!$attachment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->checker->checkTypeImage($attachment);
|
||||
} catch (Forbidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $attachment->getType();
|
||||
|
||||
if (!$type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$contents = $this->fileStorageManager->getContents($attachment);
|
||||
|
||||
return 'data:' . $type . ';base64,' . base64_encode($contents);
|
||||
}
|
||||
}
|
||||
37
application/Espo/Tools/Pdf/EntityPrinter.php
Normal file
37
application/Espo/Tools/Pdf/EntityPrinter.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
interface EntityPrinter
|
||||
{
|
||||
public function print(Template $template, Entity $entity, Params $params, Data $data): Contents;
|
||||
}
|
||||
53
application/Espo/Tools/Pdf/IdDataMap.php
Normal file
53
application/Espo/Tools/Pdf/IdDataMap.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
class IdDataMap
|
||||
{
|
||||
/**
|
||||
* @var array<string,Data>
|
||||
*/
|
||||
private $map = [];
|
||||
|
||||
public function set(string $id, Data $data): void
|
||||
{
|
||||
$this->map[$id] = $data;
|
||||
}
|
||||
|
||||
public function get(string $id): ?Data
|
||||
{
|
||||
return $this->map[$id] ?? null;
|
||||
}
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
||||
69
application/Espo/Tools/Pdf/Jobs/RemoveMassFile.php
Normal file
69
application/Espo/Tools/Pdf/Jobs/RemoveMassFile.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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\Jobs;
|
||||
|
||||
use Espo\Core\Job\Job;
|
||||
use Espo\Core\Job\Job\Data;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\ORM\EntityManager;
|
||||
use RuntimeException;
|
||||
|
||||
class RemoveMassFile implements Job
|
||||
{
|
||||
private const ATTACHMENT_MASS_PDF_ROLE = 'Mass Pdf';
|
||||
private EntityManager $entityManager;
|
||||
|
||||
public function __construct(EntityManager $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function run(Data $data): void
|
||||
{
|
||||
$id = $data->getTargetId();
|
||||
|
||||
if (!$id) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
/** @var ?Attachment $attachment */
|
||||
$attachment = $this->entityManager->getEntityById(Attachment::ENTITY_TYPE, $id);
|
||||
|
||||
if (!$attachment) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($attachment->getRole() !== self::ATTACHMENT_MASS_PDF_ROLE) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$this->entityManager->removeEntity($attachment);
|
||||
}
|
||||
}
|
||||
207
application/Espo/Tools/Pdf/MassService.php
Normal file
207
application/Espo/Tools/Pdf/MassService.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use DateTime;
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\FileStorage\Manager as FileStorageManager;
|
||||
use Espo\Core\Job\Job\Data as JobData;
|
||||
use Espo\Core\Job\JobSchedulerFactory;
|
||||
use Espo\Core\Job\QueueName;
|
||||
use Espo\Core\Record\ServiceContainer;
|
||||
use Espo\Core\Select\SelectBuilderFactory;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\Entities\Template as TemplateEntity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\Tools\Pdf\Data\DataLoaderManager;
|
||||
use Espo\Tools\Pdf\Jobs\RemoveMassFile;
|
||||
|
||||
class MassService
|
||||
{
|
||||
private const DEFAULT_ENGINE = 'Dompdf';
|
||||
private const ATTACHMENT_MASS_PDF_ROLE = 'Mass Pdf';
|
||||
private const REMOVE_MASS_PDF_PERIOD = '1 hour';
|
||||
|
||||
public function __construct(
|
||||
private ServiceContainer $serviceContainer,
|
||||
private Config $config,
|
||||
private EntityManager $entityManager,
|
||||
private Acl $acl,
|
||||
private DataLoaderManager $dataLoaderManager,
|
||||
private SelectBuilderFactory $selectBuilderFactory,
|
||||
private Builder $builder,
|
||||
private Language $defaultLanguage,
|
||||
private JobSchedulerFactory $jobSchedulerFactory,
|
||||
private FileStorageManager $fileStorageManager,
|
||||
private Metadata $metadata,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Generate a PDF for multiple records.
|
||||
*
|
||||
* @param string[] $idList
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
* @throws Forbidden
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function generate(
|
||||
string $entityType,
|
||||
array $idList,
|
||||
string $templateId,
|
||||
bool $withAcl = true
|
||||
): string {
|
||||
|
||||
$service = $this->serviceContainer->get($entityType);
|
||||
|
||||
$maxCount = $this->config->get('massPrintPdfMaxCount');
|
||||
|
||||
if ($maxCount && count($idList) > $maxCount) {
|
||||
throw new Error("Mass print to PDF max count exceeded.");
|
||||
}
|
||||
|
||||
/** @var ?TemplateEntity $template */
|
||||
$template = $this->entityManager->getEntityById(TemplateEntity::ENTITY_TYPE, $templateId);
|
||||
|
||||
if (!$template) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$params = Params::create();
|
||||
|
||||
if ($withAcl) {
|
||||
if (!$this->acl->check($template)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
if (!$this->acl->checkScope($entityType)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$params = $params->withAcl();
|
||||
}
|
||||
|
||||
$selectBuilder = $this->selectBuilderFactory
|
||||
->create()
|
||||
->from($entityType);
|
||||
|
||||
if ($withAcl) {
|
||||
$selectBuilder->withAccessControlFilter();
|
||||
}
|
||||
|
||||
$collection = $this->entityManager
|
||||
->getRDBRepository($entityType)
|
||||
->clone($selectBuilder->build())
|
||||
->where([
|
||||
Attribute::ID => $idList,
|
||||
])
|
||||
->find();
|
||||
|
||||
$idDataMap = IdDataMap::create();
|
||||
|
||||
$pdfA = $this->metadata->get("pdfDefs.$entityType.pdfA") ?? false;
|
||||
|
||||
$params = $params->withPdfA($pdfA);
|
||||
|
||||
foreach ($collection as $entity) {
|
||||
$service->loadAdditionalFields($entity);
|
||||
|
||||
$idDataMap->set(
|
||||
$entity->getId(),
|
||||
$this->dataLoaderManager->load($entity, $params)
|
||||
);
|
||||
|
||||
// For bc.
|
||||
if (method_exists($service, 'loadAdditionalFieldsForPdf')) {
|
||||
$service->loadAdditionalFieldsForPdf($entity);
|
||||
}
|
||||
}
|
||||
|
||||
$templateWrapper = new TemplateWrapper($template);
|
||||
|
||||
$engine = $this->config->get('pdfEngine') ?? self::DEFAULT_ENGINE;
|
||||
|
||||
$printer = $this->builder
|
||||
->setTemplate($templateWrapper)
|
||||
->setEngine($engine)
|
||||
->build();
|
||||
|
||||
$contents = $printer->printCollection($collection, $params, $idDataMap);
|
||||
|
||||
$entityTypeTranslated = $this->defaultLanguage->translateLabel($entityType, 'scopeNamesPlural');
|
||||
|
||||
$type = $contents instanceof ZipContents ?
|
||||
'application/zip' :
|
||||
'application/pdf';
|
||||
|
||||
$filename = $contents instanceof ZipContents ?
|
||||
Util::sanitizeFileName($entityTypeTranslated) . '.zip' :
|
||||
Util::sanitizeFileName($entityTypeTranslated) . '.pdf';
|
||||
|
||||
/** @var Attachment $attachment */
|
||||
$attachment = $this->entityManager->getNewEntity(Attachment::ENTITY_TYPE);
|
||||
|
||||
$attachment
|
||||
->setName($filename)
|
||||
->setType($type)
|
||||
->setRole(self::ATTACHMENT_MASS_PDF_ROLE)
|
||||
->setSize($contents->getStream()->getSize());
|
||||
|
||||
$this->entityManager->saveEntity($attachment);
|
||||
|
||||
$this->fileStorageManager->putStream($attachment, $contents->getStream());
|
||||
|
||||
$this->jobSchedulerFactory
|
||||
->create()
|
||||
->setClassName(RemoveMassFile::class)
|
||||
->setData(
|
||||
JobData
|
||||
::create()
|
||||
->withTargetId($attachment->getId())
|
||||
->withTargetType(Attachment::ENTITY_TYPE)
|
||||
)
|
||||
->setTime(
|
||||
(new DateTime())->modify('+' . self::REMOVE_MASS_PDF_PERIOD)
|
||||
)
|
||||
->setQueue(QueueName::Q1)
|
||||
->schedule();
|
||||
|
||||
return $attachment->getId();
|
||||
}
|
||||
}
|
||||
70
application/Espo/Tools/Pdf/Params.php
Normal file
70
application/Espo/Tools/Pdf/Params.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class Params
|
||||
{
|
||||
private bool $applyAcl = false;
|
||||
private bool $pdfA = false;
|
||||
|
||||
public function applyAcl(): bool
|
||||
{
|
||||
return $this->applyAcl;
|
||||
}
|
||||
|
||||
public function isPdfA(): bool
|
||||
{
|
||||
return $this->pdfA;
|
||||
}
|
||||
|
||||
public function withPdfA(bool $pdfA = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->pdfA = $pdfA;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withAcl(bool $applyAcl = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->applyAcl = $applyAcl;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
}
|
||||
137
application/Espo/Tools/Pdf/PrinterController.php
Normal file
137
application/Espo/Tools/Pdf/PrinterController.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Collection;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class PrinterController
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Template $template,
|
||||
private string $engine
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function printEntity(Entity $entity, ?Params $params, ?Data $data = null): Contents
|
||||
{
|
||||
$params = $params ?? new Params();
|
||||
$data = $data ?? new Data();
|
||||
|
||||
return $this->createEntityPrinter()->print($this->template, $entity, $params, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<Entity> $collection
|
||||
* @throws Error
|
||||
*/
|
||||
public function printCollection(
|
||||
Collection $collection,
|
||||
?Params $params,
|
||||
?IdDataMap $idDataMap = null
|
||||
): Contents {
|
||||
|
||||
$params = $params ?? new Params();
|
||||
$idDataMap = $idDataMap ?? new IdDataMap();
|
||||
|
||||
if ($this->hasCollectionPrinter()) {
|
||||
return $this->createCollectionPrinter()->print($this->template, $collection, $params, $idDataMap);
|
||||
}
|
||||
|
||||
$printer = $this->createEntityPrinter();
|
||||
|
||||
$zipper = new Zipper();
|
||||
|
||||
foreach ($collection as $entity) {
|
||||
$data = $idDataMap->get($entity->getId()) ?? new Data();
|
||||
|
||||
$itemContents = $printer->print($this->template, $entity, $params, $data);
|
||||
|
||||
$zipper->add($itemContents, $entity->getId());
|
||||
}
|
||||
|
||||
$zipper->archive();
|
||||
|
||||
return new ZipContents($zipper->getFilePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
private function createEntityPrinter(): EntityPrinter
|
||||
{
|
||||
/** @var ?class-string<EntityPrinter> $className */
|
||||
$className = $this->metadata
|
||||
->get(['app', 'pdfEngines', $this->engine, 'implementationClassNameMap', 'entity']) ?? null;
|
||||
|
||||
if (!$className) {
|
||||
throw new Error("Unknown PDF engine '{$this->engine}', type 'entity'.");
|
||||
}
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
private function createCollectionPrinter(): CollectionPrinter
|
||||
{
|
||||
$className = $this->getCollectionPrinterClassName();
|
||||
|
||||
if (!$className) {
|
||||
throw new Error("Unknown PDF engine '{$this->engine}', type 'collection'.");
|
||||
}
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
|
||||
private function hasCollectionPrinter(): bool
|
||||
{
|
||||
return (bool) $this->getCollectionPrinterClassName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<CollectionPrinter>
|
||||
*/
|
||||
private function getCollectionPrinterClassName(): ?string
|
||||
{
|
||||
/** @var ?class-string<CollectionPrinter> */
|
||||
return $this->metadata
|
||||
->get(['app', 'pdfEngines', $this->engine, 'implementationClassNameMap', 'collection']) ?? null;
|
||||
}
|
||||
|
||||
}
|
||||
135
application/Espo/Tools/Pdf/Service.php
Normal file
135
application/Espo/Tools/Pdf/Service.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Record\ServiceContainer;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\Template as TemplateEntity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\Pdf\Data\DataLoaderManager;
|
||||
|
||||
class Service
|
||||
{
|
||||
private const DEFAULT_ENGINE = 'Dompdf';
|
||||
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private Acl $acl,
|
||||
private ServiceContainer $serviceContainer,
|
||||
private DataLoaderManager $dataLoaderManager,
|
||||
private Config $config,
|
||||
private Builder $builder,
|
||||
private Metadata $metadata,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Generate a PDF.
|
||||
*
|
||||
* @param string $entityType An entity type.
|
||||
* @param string $id A record ID.
|
||||
* @param string $templateId A template ID.
|
||||
* @param ?Params $params Params. If null, a params with the apply-acl will be used.
|
||||
* @params ?Data $data Data.
|
||||
*
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function generate(
|
||||
string $entityType,
|
||||
string $id,
|
||||
string $templateId,
|
||||
?Params $params = null,
|
||||
?Data $data = null
|
||||
): Contents {
|
||||
|
||||
$params = $params ?? Params::create()->withAcl(true);
|
||||
|
||||
$applyAcl = $params->applyAcl();
|
||||
|
||||
$entity = $this->entityManager->getEntityById($entityType, $id);
|
||||
|
||||
if (!$entity) {
|
||||
throw new NotFound("Record not found.");
|
||||
}
|
||||
|
||||
$template = $this->entityManager
|
||||
->getRDBRepositoryByClass(TemplateEntity::class)
|
||||
->getById($templateId);
|
||||
|
||||
if (!$template) {
|
||||
throw new NotFound("Template not found.");
|
||||
}
|
||||
|
||||
if (!$template->isActive()) {
|
||||
throw new Forbidden("Template is not active.");
|
||||
}
|
||||
|
||||
if ($applyAcl && !$this->acl->checkEntityRead($entity)) {
|
||||
throw new Forbidden("No access to record.");
|
||||
}
|
||||
|
||||
if ($applyAcl && !$this->acl->checkEntityRead($template)) {
|
||||
throw new Forbidden("No access to template.");
|
||||
}
|
||||
|
||||
$service = $this->serviceContainer->get($entityType);
|
||||
|
||||
$service->loadAdditionalFields($entity);
|
||||
|
||||
if (method_exists($service, 'loadAdditionalFieldsForPdf')) {
|
||||
// For bc.
|
||||
$service->loadAdditionalFieldsForPdf($entity);
|
||||
}
|
||||
|
||||
if ($template->getTargetEntityType() !== $entityType) {
|
||||
throw new Error("Not matching entity types.");
|
||||
}
|
||||
|
||||
$pdfA = $this->metadata->get("pdfDefs.{$entity->getEntityType()}.pdfA") ?? false;
|
||||
|
||||
$params = $params->withPdfA($pdfA);
|
||||
|
||||
$data = $this->dataLoaderManager->load($entity, $params, $data);
|
||||
$engine = $this->config->get('pdfEngine') ?? self::DEFAULT_ENGINE;
|
||||
|
||||
$printer = $this->builder
|
||||
->setTemplate(new TemplateWrapper($template))
|
||||
->setEngine($engine)
|
||||
->build();
|
||||
|
||||
return $printer->printEntity($entity, $params, $data);
|
||||
}
|
||||
}
|
||||
76
application/Espo/Tools/Pdf/Template.php
Normal file
76
application/Espo/Tools/Pdf/Template.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
interface Template
|
||||
{
|
||||
public const PAGE_FORMAT_CUSTOM = 'Custom';
|
||||
|
||||
public const PAGE_ORIENTATION_PORTRAIT = 'Portrait';
|
||||
public const PAGE_ORIENTATION_LANDSCAPE = 'Landscape';
|
||||
|
||||
public function getFontFace(): ?string;
|
||||
|
||||
public function getBottomMargin(): float;
|
||||
|
||||
public function getTopMargin(): float;
|
||||
|
||||
public function getLeftMargin(): float;
|
||||
|
||||
public function getRightMargin(): float;
|
||||
|
||||
public function hasFooter(): bool;
|
||||
|
||||
public function getFooter(): string;
|
||||
|
||||
public function getFooterPosition(): float;
|
||||
|
||||
public function hasHeader(): bool;
|
||||
|
||||
public function getHeader(): string;
|
||||
|
||||
public function getHeaderPosition(): float;
|
||||
|
||||
public function getBody(): string;
|
||||
|
||||
public function getPageOrientation(): string;
|
||||
|
||||
public function getPageFormat(): string;
|
||||
|
||||
public function getPageWidth(): float;
|
||||
|
||||
public function getPageHeight(): float;
|
||||
|
||||
public function hasTitle(): bool;
|
||||
|
||||
public function getTitle(): string;
|
||||
|
||||
public function getStyle(): ?string;
|
||||
}
|
||||
137
application/Espo/Tools/Pdf/TemplateWrapper.php
Normal file
137
application/Espo/Tools/Pdf/TemplateWrapper.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Espo\Entities\Template as TemplateEntity;
|
||||
|
||||
class TemplateWrapper implements Template
|
||||
{
|
||||
protected TemplateEntity $template;
|
||||
|
||||
public function __construct(TemplateEntity $template)
|
||||
{
|
||||
$this->template = $template;
|
||||
}
|
||||
|
||||
public function getFontFace(): ?string
|
||||
{
|
||||
return $this->template->get('fontFace');
|
||||
}
|
||||
|
||||
public function getBottomMargin(): float
|
||||
{
|
||||
return $this->template->get('bottomMargin') ?? 0.0;
|
||||
}
|
||||
|
||||
public function getTopMargin(): float
|
||||
{
|
||||
return $this->template->get('topMargin') ?? 0.0;
|
||||
}
|
||||
|
||||
public function getLeftMargin(): float
|
||||
{
|
||||
return $this->template->get('leftMargin') ?? 0.0;
|
||||
}
|
||||
|
||||
public function getRightMargin(): float
|
||||
{
|
||||
return $this->template->get('rightMargin') ?? 0.0;
|
||||
}
|
||||
|
||||
public function hasFooter(): bool
|
||||
{
|
||||
return $this->template->get('printFooter') ?? false;
|
||||
}
|
||||
|
||||
public function getFooter(): string
|
||||
{
|
||||
return $this->template->get('footer') ?? '';
|
||||
}
|
||||
|
||||
public function getFooterPosition(): float
|
||||
{
|
||||
return $this->template->get('footerPosition') ?? 0.0;
|
||||
}
|
||||
|
||||
public function hasHeader(): bool
|
||||
{
|
||||
return $this->template->get('printHeader') ?? false;
|
||||
}
|
||||
|
||||
public function getHeader(): string
|
||||
{
|
||||
return $this->template->get('header') ?? '';
|
||||
}
|
||||
|
||||
public function getHeaderPosition(): float
|
||||
{
|
||||
return $this->template->get('headerPosition') ?? 0.0;
|
||||
}
|
||||
|
||||
public function getBody(): string
|
||||
{
|
||||
return $this->template->get('body') ?? '';
|
||||
}
|
||||
|
||||
public function getPageOrientation(): string
|
||||
{
|
||||
return $this->template->get('pageOrientation') ?? 'Portrait';
|
||||
}
|
||||
|
||||
public function getPageFormat(): string
|
||||
{
|
||||
return $this->template->get('pageFormat') ?? 'A4';
|
||||
}
|
||||
|
||||
public function getPageWidth(): float
|
||||
{
|
||||
return $this->template->get('pageWidth') ?? 0.0;
|
||||
}
|
||||
|
||||
public function getPageHeight(): float
|
||||
{
|
||||
return $this->template->get('pageHeight') ?? 0.0;
|
||||
}
|
||||
|
||||
public function hasTitle(): bool
|
||||
{
|
||||
return $this->template->get('title') !== null;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->template->get('title') ?? '';
|
||||
}
|
||||
|
||||
public function getStyle(): ?string
|
||||
{
|
||||
return $this->template->get('style') ?? null;
|
||||
}
|
||||
}
|
||||
61
application/Espo/Tools/Pdf/ZipContents.php
Normal file
61
application/Espo/Tools/Pdf/ZipContents.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ZipContents implements Contents
|
||||
{
|
||||
public function __construct(private string $filePath) {}
|
||||
|
||||
public function getStream(): StreamInterface
|
||||
{
|
||||
$resource = fopen($this->filePath, 'r+');
|
||||
|
||||
if ($resource === false) {
|
||||
throw new RuntimeException("Could not open {$this->filePath}.");
|
||||
}
|
||||
|
||||
return new Stream($resource);
|
||||
}
|
||||
|
||||
public function getString(): string
|
||||
{
|
||||
return $this->getStream()->getContents();
|
||||
}
|
||||
|
||||
public function getLength(): int
|
||||
{
|
||||
return (int) $this->getStream()->getSize();
|
||||
}
|
||||
}
|
||||
93
application/Espo/Tools/Pdf/Zipper.php
Normal file
93
application/Espo/Tools/Pdf/Zipper.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use ZipArchive;
|
||||
|
||||
class Zipper
|
||||
{
|
||||
private ?string $filePath = null;
|
||||
/** @var array{string, string}[] */
|
||||
private array $itemList = [];
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
public function add(Contents $contents, string $name): void
|
||||
{
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'espo-pdf-zip-item');
|
||||
|
||||
if ($tempPath === false) {
|
||||
throw new RuntimeException("Could not create a temp file.");
|
||||
}
|
||||
|
||||
$fp = fopen($tempPath, 'w');
|
||||
|
||||
if ($fp === false) {
|
||||
throw new RuntimeException("Could not open a temp file {$tempPath}.");
|
||||
}
|
||||
|
||||
fwrite($fp, $contents->getString());
|
||||
fclose($fp);
|
||||
|
||||
$this->itemList[] = [$tempPath, Util::sanitizeFileName($name) . '.pdf'];
|
||||
}
|
||||
|
||||
public function archive(): void
|
||||
{
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'espo-pdf-zip');
|
||||
|
||||
if ($tempPath === false) {
|
||||
throw new RuntimeException("Could not create a temp file.");
|
||||
}
|
||||
|
||||
$archive = new ZipArchive();
|
||||
$archive->open($tempPath, ZipArchive::CREATE);
|
||||
|
||||
foreach ($this->itemList as $item) {
|
||||
$archive->addFile($item[0], $item[1]);
|
||||
}
|
||||
|
||||
$archive->close();
|
||||
|
||||
$this->filePath = $tempPath;
|
||||
}
|
||||
|
||||
public function getFilePath(): string
|
||||
{
|
||||
if (!$this->filePath) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
return $this->filePath;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user