ref
This commit is contained in:
@@ -29,32 +29,36 @@
|
||||
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\{
|
||||
Exceptions\Forbidden,
|
||||
Exceptions\BadRequest,
|
||||
};
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
|
||||
use Espo\Core\{
|
||||
Api\Request,
|
||||
Acl,
|
||||
};
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Api\Request;
|
||||
|
||||
use Espo\Services\Pdf as Service;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Entities\Template as TemplateEntity;
|
||||
use Espo\Tools\Pdf\MassService;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class Pdf
|
||||
{
|
||||
private $service;
|
||||
private MassService $service;
|
||||
private Acl $acl;
|
||||
|
||||
private $acl;
|
||||
|
||||
public function __construct(Service $service, Acl $acl)
|
||||
public function __construct(MassService $service, Acl $acl)
|
||||
{
|
||||
$this->service = $service;
|
||||
$this->acl = $acl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function postActionMassPrint(Request $request): stdClass
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
@@ -71,7 +75,7 @@ class Pdf
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if (!$this->acl->checkScope('Template')) {
|
||||
if (!$this->acl->checkScope(TemplateEntity::ENTITY_TYPE)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
@@ -79,14 +83,10 @@ class Pdf
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$id = $this->service->generate($data->entityType, $data->idList, $data->templateId);
|
||||
|
||||
return (object) [
|
||||
'id' => $this->service
|
||||
->massGenerate(
|
||||
$data->entityType,
|
||||
$data->idList,
|
||||
$data->templateId,
|
||||
true
|
||||
)
|
||||
'id' => $id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,28 +29,27 @@
|
||||
|
||||
namespace Espo\Core\Formula\Functions\ExtGroup\PdfGroup;
|
||||
|
||||
use Espo\Core\Formula\{
|
||||
Functions\BaseFunction,
|
||||
ArgumentList,
|
||||
Exceptions\Error,
|
||||
};
|
||||
|
||||
use Espo\Services\Pdf as Service;
|
||||
|
||||
use Espo\Core\Field\LinkParent;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\Entities\Template;
|
||||
use Espo\Core\Formula\ArgumentList;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\Formula\Functions\BaseFunction;
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
use Espo\Tools\Pdf\Params;
|
||||
|
||||
use Espo\Core\Di;
|
||||
|
||||
use Espo\Tools\Pdf\Service;
|
||||
use Exception;
|
||||
|
||||
class GenerateType extends BaseFunction implements
|
||||
Di\EntityManagerAware,
|
||||
Di\ServiceFactoryAware
|
||||
Di\InjectableFactoryAware,
|
||||
Di\FileStorageManagerAware
|
||||
{
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\ServiceFactorySetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
use Di\FileStorageManagerSetter;
|
||||
|
||||
public function process(ArgumentList $args)
|
||||
{
|
||||
@@ -98,7 +97,8 @@ class GenerateType extends BaseFunction implements
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$template = $em->getEntity('Template', $templateId);
|
||||
/** @var ?Template $template */
|
||||
$template = $em->getEntityById(Template::ENTITY_TYPE, $templateId);
|
||||
|
||||
if (!$template) {
|
||||
$this->log("Template {$templateId} does not exist.");
|
||||
@@ -117,10 +117,14 @@ class GenerateType extends BaseFunction implements
|
||||
$params = Params::create()->withAcl(false);
|
||||
|
||||
try {
|
||||
/** @var Service $service */
|
||||
$service = $this->serviceFactory->create('Pdf');
|
||||
$service = $this->injectableFactory->create(Service::class);
|
||||
|
||||
$contents = $service->generate($entity, $template, $params);
|
||||
$contents = $service->generate(
|
||||
$entity->getEntityType(),
|
||||
$entity->getId(),
|
||||
$template->getId(),
|
||||
$params
|
||||
);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->log("Error while generating. Message: " . $e->getMessage() . ".", 'error');
|
||||
@@ -128,14 +132,19 @@ class GenerateType extends BaseFunction implements
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$attachment = $em->createEntity('Attachment', [
|
||||
'name' => $fileName,
|
||||
'type' => 'application/pdf',
|
||||
'contents' => $contents,
|
||||
'relatedId' => $id,
|
||||
'relatedType' => $entityType,
|
||||
'role' => 'Attachment',
|
||||
]);
|
||||
/** @var Attachment $attachment */
|
||||
$attachment = $em->getNewEntity(Attachment::ENTITY_TYPE);
|
||||
|
||||
$attachment
|
||||
->setName($fileName)
|
||||
->setType('application/pdf')
|
||||
->setSize($contents->getStream()->getSize())
|
||||
->setRelated(LinkParent::create($entityType, $id))
|
||||
->setRole(Attachment::ROLE_ATTACHMENT);
|
||||
|
||||
$em->saveEntity($attachment);
|
||||
|
||||
$this->fileStorageManager->putStream($attachment, $contents->getStream());
|
||||
|
||||
return $attachment->getId();
|
||||
}
|
||||
|
||||
@@ -29,7 +29,21 @@
|
||||
|
||||
namespace Espo\Entities;
|
||||
|
||||
class Template extends \Espo\Core\ORM\Entity
|
||||
use Espo\Core\ORM\Entity;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class Template extends Entity
|
||||
{
|
||||
public const ENTITY_TYPE = 'Template';
|
||||
|
||||
public function getTargetEntityType(): string
|
||||
{
|
||||
$entityType = $this->get('entityType');
|
||||
|
||||
if ($entityType === null) {
|
||||
throw new UnexpectedValueException();
|
||||
}
|
||||
|
||||
return $entityType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,23 +29,22 @@
|
||||
|
||||
namespace Espo\EntryPoints;
|
||||
|
||||
use Espo\Core\{
|
||||
Exceptions\NotFound,
|
||||
Exceptions\BadRequest,
|
||||
EntryPoint\EntryPoint,
|
||||
ORM\EntityManager,
|
||||
Api\Request,
|
||||
Api\Response,
|
||||
Utils\Util,
|
||||
};
|
||||
|
||||
use Espo\Services\Pdf as Service;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\EntryPoint\EntryPoint;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Entities\Template;
|
||||
use Espo\Tools\Pdf\Service;
|
||||
|
||||
class Pdf implements EntryPoint
|
||||
{
|
||||
private $entityManager;
|
||||
|
||||
private $service;
|
||||
private EntityManager $entityManager;
|
||||
private Service $service;
|
||||
|
||||
public function __construct(EntityManager $entityManager, Service $service)
|
||||
{
|
||||
@@ -53,6 +52,12 @@ class Pdf implements EntryPoint
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function run(Request $request, Response $response): void
|
||||
{
|
||||
$entityId = $request->getQueryParam('entityId');
|
||||
@@ -63,18 +68,17 @@ class Pdf implements EntryPoint
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$entity = $this->entityManager->getEntity($entityType, $entityId);
|
||||
$template = $this->entityManager->getEntity('Template', $templateId);
|
||||
$entity = $this->entityManager->getEntityById($entityType, $entityId);
|
||||
/** @var ?Template $template */
|
||||
$template = $this->entityManager->getEntityById(Template::ENTITY_TYPE, $templateId);
|
||||
|
||||
if (!$entity || !$template) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$contents = $this->service->generate($entity, $template);
|
||||
$contents = $this->service->generate($entityType, $entityId, $templateId);
|
||||
|
||||
$fileName = Util::sanitizeFileName(
|
||||
$entity->get('name') ?? 'unnamed'
|
||||
);
|
||||
$fileName = Util::sanitizeFileName($entity->get('name') ?? 'unnamed');
|
||||
|
||||
$fileName = $fileName . '.pdf';
|
||||
|
||||
@@ -87,9 +91,9 @@ class Pdf implements EntryPoint
|
||||
->setHeader('Content-Disposition', 'inline; filename="' . basename($fileName) . '"');
|
||||
|
||||
if (!$request->getServerParam('HTTP_ACCEPT_ENCODING')) {
|
||||
$response->setHeader('Content-Length', (string) strlen($contents));
|
||||
$response->setHeader('Content-Length', (string) $contents->getStream()->getSize());
|
||||
}
|
||||
|
||||
$response->writeBody($contents);
|
||||
$response->writeBody($contents->getStream());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,336 +29,72 @@
|
||||
|
||||
namespace Espo\Services;
|
||||
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\Job\QueueName;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
use Espo\Core\Record\ServiceContainer;
|
||||
use Espo\Core\Select\SelectBuilderFactory;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
use Espo\Tools\Pdf\Builder;
|
||||
use Espo\Tools\Pdf\Contents;
|
||||
use Espo\Tools\Pdf\Data;
|
||||
use Espo\Tools\Pdf\Data\DataLoaderManager;
|
||||
use Espo\Tools\Pdf\IdDataMap;
|
||||
use Espo\Tools\Pdf\Params;
|
||||
use Espo\Tools\Pdf\TemplateWrapper;
|
||||
|
||||
use Espo\Tools\Pdf\Service;
|
||||
use Espo\Entities\Template;
|
||||
|
||||
use DateTime;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @deprecated Left for bc.
|
||||
*/
|
||||
class Pdf
|
||||
{
|
||||
private const DEFAULT_ENGINE = 'Tcpdf';
|
||||
|
||||
private string $removeMassFilePeriod = '1 hour';
|
||||
|
||||
private $config;
|
||||
private $entityManager;
|
||||
private $acl;
|
||||
private $defaultLanguage;
|
||||
private $selectBuilderFactory;
|
||||
private $builder;
|
||||
private $serviceContainer;
|
||||
private $dataLoaderManager;
|
||||
private Service $service;
|
||||
|
||||
public function __construct(
|
||||
Config $config,
|
||||
EntityManager $entityManager,
|
||||
Acl $acl,
|
||||
Language $defaultLanguage,
|
||||
SelectBuilderFactory $selectBuilderFactory,
|
||||
Builder $builder,
|
||||
ServiceContainer $serviceContainer,
|
||||
DataLoaderManager $dataLoaderManager
|
||||
Service $service
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->acl = $acl;
|
||||
$this->defaultLanguage = $defaultLanguage;
|
||||
$this->selectBuilderFactory = $selectBuilderFactory;
|
||||
$this->builder = $builder;
|
||||
$this->serviceContainer = $serviceContainer;
|
||||
$this->dataLoaderManager = $dataLoaderManager;
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $idList
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function massGenerate(
|
||||
string $entityType,
|
||||
array $idList,
|
||||
string $templateId,
|
||||
bool $checkAcl = false
|
||||
): string {
|
||||
|
||||
$service = $this->serviceContainer->get($entityType);
|
||||
|
||||
$maxCount = $this->config->get('massPrintPdfMaxCount');
|
||||
|
||||
if ($maxCount) {
|
||||
if (count($idList) > $maxCount) {
|
||||
throw new Error("Mass print to PDF max count exceeded.");
|
||||
}
|
||||
}
|
||||
|
||||
$template = $this->entityManager->getEntity('Template', $templateId);
|
||||
|
||||
if (!$template) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$params = Params::create();
|
||||
|
||||
if ($checkAcl) {
|
||||
if (!$this->acl->check($template)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
if (!$this->acl->checkScope($entityType)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$params = $params->withAcl();
|
||||
}
|
||||
|
||||
$query = $this->selectBuilderFactory
|
||||
->create()
|
||||
->from($entityType)
|
||||
->withAccessControlFilter()
|
||||
->build();
|
||||
|
||||
$collection = $this->entityManager
|
||||
->getRDBRepository($entityType)
|
||||
->clone($query)
|
||||
->where([
|
||||
'id' => $idList,
|
||||
])
|
||||
->find();
|
||||
|
||||
$idDataMap = IdDataMap::create();
|
||||
|
||||
foreach ($collection as $entity) {
|
||||
$service->loadAdditionalFields($entity);
|
||||
|
||||
$idDataMap->set(
|
||||
$entity->getId(),
|
||||
$this->dataLoaderManager->load($entity, $params)
|
||||
);
|
||||
|
||||
// deprecated
|
||||
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');
|
||||
|
||||
$filename = Util::sanitizeFileName($entityTypeTranslated) . '.pdf';
|
||||
|
||||
$attachment = $this->entityManager->getNewEntity('Attachment');
|
||||
|
||||
$attachment->set([
|
||||
'name' => $filename,
|
||||
'type' => 'application/pdf',
|
||||
'role' => 'Mass Pdf',
|
||||
'contents' => $contents->getString(),
|
||||
]);
|
||||
|
||||
$this->entityManager->saveEntity($attachment);
|
||||
|
||||
$job = $this->entityManager->getNewEntity('Job');
|
||||
|
||||
$job->set([
|
||||
'serviceName' => 'Pdf',
|
||||
'methodName' => 'removeMassFileJob',
|
||||
'data' => [
|
||||
'id' => $attachment->getId(),
|
||||
],
|
||||
'executeTime' => (new DateTime())->modify('+' . $this->removeMassFilePeriod)->format('Y-m-d H:i:s'),
|
||||
'queue' => QueueName::Q1,
|
||||
]);
|
||||
|
||||
$this->entityManager->saveEntity($job);
|
||||
|
||||
return $attachment->getId();
|
||||
}
|
||||
|
||||
public function removeMassFileJob(stdClass $data): void
|
||||
{
|
||||
if (empty($data->id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attachment = $this->entityManager->getEntity('Attachment', $data->id);
|
||||
|
||||
if (!$attachment) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($attachment->get('role') !== 'Mass Pdf') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->entityManager->removeEntity($attachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate PDF. ACL check is processed if `$params` is null.
|
||||
*
|
||||
* @throws Error
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function generate(Entity $entity, Template $template, ?Params $params = null, ?Data $data = null): string
|
||||
{
|
||||
if ($params === null) {
|
||||
$params = Params::create()->withAcl();
|
||||
$additionalData = null;
|
||||
|
||||
if ($data) {
|
||||
$additionalData = get_object_vars($data->getAdditionalTemplateData());
|
||||
}
|
||||
|
||||
$result = $this->buildFromTemplateInternal($entity, $template, false, null, $params, $data);
|
||||
|
||||
assert($result !== null);
|
||||
|
||||
return $result;
|
||||
return $this->buildFromTemplate($entity, $template, false, $additionalData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?array<string,mixed> $additionalData
|
||||
* @throws Error
|
||||
* @throws Forbidden
|
||||
* @deprecated
|
||||
* @throws NotFound
|
||||
*
|
||||
* @deprecated Left for bc.
|
||||
*/
|
||||
public function buildFromTemplate(
|
||||
Entity $entity,
|
||||
Template $template,
|
||||
bool $displayInline = false,
|
||||
?array $additionalData = null
|
||||
): ?string {
|
||||
): string {
|
||||
|
||||
return $this->buildFromTemplateInternal($entity, $template, $displayInline, $additionalData);
|
||||
}
|
||||
$data = Data::create()
|
||||
->withAdditionalTemplateData(
|
||||
(object) ($additionalData ?? [])
|
||||
);
|
||||
|
||||
/**
|
||||
* @param ?array<string,mixed> $additionalData
|
||||
* @throws Error
|
||||
* @throws Forbidden
|
||||
* @deprecated
|
||||
*/
|
||||
private function buildFromTemplateInternal(
|
||||
Entity $entity,
|
||||
Template $template,
|
||||
bool $displayInline = false,
|
||||
?array $additionalData = null,
|
||||
?Params $params = null,
|
||||
?Data $data = null
|
||||
): ?string {
|
||||
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$service = $this->serviceContainer->get($entityType);
|
||||
|
||||
$service->loadAdditionalFields($entity);
|
||||
|
||||
if (method_exists($service, 'loadAdditionalFieldsForPdf')) {
|
||||
// deprecated
|
||||
$service->loadAdditionalFieldsForPdf($entity);
|
||||
}
|
||||
|
||||
if ($template->get('entityType') !== $entityType) {
|
||||
throw new Error("Not matching entity types.");
|
||||
}
|
||||
|
||||
$applyAcl = true;
|
||||
|
||||
if ($params) {
|
||||
$applyAcl = $params->applyAcl();
|
||||
}
|
||||
|
||||
if ($applyAcl) {
|
||||
if (
|
||||
!$this->acl->check($entity, Table::ACTION_READ) ||
|
||||
!$this->acl->check($template, Table::ACTION_READ)
|
||||
) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
$templateWrapper = new TemplateWrapper($template);
|
||||
|
||||
if (!$data) {
|
||||
$data = Data::create()
|
||||
->withAdditionalTemplateData(
|
||||
(object) ($additionalData ?? [])
|
||||
);
|
||||
}
|
||||
|
||||
$data = $this->dataLoaderManager->load($entity, $params, $data);
|
||||
|
||||
$engine = $this->config->get('pdfEngine') ?? self::DEFAULT_ENGINE;
|
||||
|
||||
$printer = $this->builder
|
||||
->setTemplate($templateWrapper)
|
||||
->setEngine($engine)
|
||||
->build();
|
||||
|
||||
$contents = $printer->printEntity($entity, $params, $data);
|
||||
|
||||
if ($displayInline) {
|
||||
$this->displayInline($entity, $contents);
|
||||
|
||||
return null;
|
||||
}
|
||||
$contents = $this->service->generate(
|
||||
$entity->getEntityType(),
|
||||
$entity->getId(),
|
||||
$template->getId(),
|
||||
null,
|
||||
$data
|
||||
);
|
||||
|
||||
return $contents->getString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
private function displayInline(Entity $entity, Contents $contents): void
|
||||
{
|
||||
$fileName = Util::sanitizeFileName(
|
||||
$entity->get('name') ?? 'unnamed'
|
||||
);
|
||||
|
||||
$fileName = $fileName . '.pdf';
|
||||
|
||||
header('Content-Type: application/pdf');
|
||||
header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
|
||||
header('Pragma: public');
|
||||
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
|
||||
header('Content-Disposition: inline; filename="'.basename($fileName).'"');
|
||||
|
||||
if (!isset($_SERVER['HTTP_ACCEPT_ENCODING']) or empty($_SERVER['HTTP_ACCEPT_ENCODING'])) {
|
||||
header('Content-Length: '. $contents->getLength());
|
||||
}
|
||||
|
||||
echo $contents->getString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ use Espo\Tools\Pdf\Params;
|
||||
class DataLoaderManager
|
||||
{
|
||||
private Metadata $metadata;
|
||||
|
||||
private InjectableFactory $injectableFactory;
|
||||
|
||||
public function __construct(Metadata $metadata, InjectableFactory $injectableFactory)
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2022 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2022 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU 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\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\Util;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\Entities\Template as TemplateEntity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\Pdf\Data\DataLoaderManager;
|
||||
use Espo\Tools\Pdf\Jobs\RemoveMassFile;
|
||||
|
||||
class MassService
|
||||
{
|
||||
private const DEFAULT_ENGINE = 'Tcpdf';
|
||||
private const ATTACHMENT_MASS_PDF_ROLE = 'Mass Pdf';
|
||||
private const REMOVE_MASS_PDF_PERIOD = '1 hour';
|
||||
|
||||
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;
|
||||
|
||||
public function __construct(
|
||||
ServiceContainer $serviceContainer,
|
||||
Config $config,
|
||||
EntityManager $entityManager,
|
||||
Acl $acl,
|
||||
DataLoaderManager $dataLoaderManager,
|
||||
SelectBuilderFactory $selectBuilderFactory,
|
||||
Builder $builder,
|
||||
Language $defaultLanguage,
|
||||
JobSchedulerFactory $jobSchedulerFactory,
|
||||
FileStorageManager $fileStorageManager
|
||||
) {
|
||||
$this->serviceContainer = $serviceContainer;
|
||||
$this->config = $config;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->acl = $acl;
|
||||
$this->dataLoaderManager = $dataLoaderManager;
|
||||
$this->selectBuilderFactory = $selectBuilderFactory;
|
||||
$this->builder = $builder;
|
||||
$this->defaultLanguage = $defaultLanguage;
|
||||
$this->jobSchedulerFactory = $jobSchedulerFactory;
|
||||
$this->fileStorageManager = $fileStorageManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a PDF for multiple records.
|
||||
*
|
||||
* @param string[] $idList
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
* @throws Forbidden
|
||||
*/
|
||||
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([
|
||||
'id' => $idList,
|
||||
])
|
||||
->find();
|
||||
|
||||
$idDataMap = IdDataMap::create();
|
||||
|
||||
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');
|
||||
|
||||
$filename = Util::sanitizeFileName($entityTypeTranslated) . '.pdf';
|
||||
|
||||
/** @var Attachment $attachment */
|
||||
$attachment = $this->entityManager->getNewEntity(Attachment::ENTITY_TYPE);
|
||||
|
||||
$attachment
|
||||
->setName($filename)
|
||||
->setType('application/pdf')
|
||||
->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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2022 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU 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\Entities\Template as TemplateEntity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\Pdf\Data\DataLoaderManager;
|
||||
|
||||
class Service
|
||||
{
|
||||
private const DEFAULT_ENGINE = 'Tcpdf';
|
||||
|
||||
private EntityManager $entityManager;
|
||||
private Acl $acl;
|
||||
private ServiceContainer $serviceContainer;
|
||||
private DataLoaderManager $dataLoaderManager;
|
||||
private Config $config;
|
||||
private Builder $builder;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
Acl $acl,
|
||||
ServiceContainer $serviceContainer,
|
||||
DataLoaderManager $dataLoaderManager,
|
||||
Config $config,
|
||||
Builder $builder
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->acl = $acl;
|
||||
$this->serviceContainer = $serviceContainer;
|
||||
$this->dataLoaderManager = $dataLoaderManager;
|
||||
$this->config = $config;
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
}
|
||||
|
||||
/** @var ?TemplateEntity $template */
|
||||
$template = $this->entityManager->getEntityById(TemplateEntity::ENTITY_TYPE, $templateId);
|
||||
|
||||
if (!$template) {
|
||||
throw new NotFound("Template not found.");
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user