Initial commit
This commit is contained in:
40
application/Espo/Tools/Export/AdditionalFieldsLoader.php
Normal file
40
application/Espo/Tools/Export/AdditionalFieldsLoader.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\Export;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
interface AdditionalFieldsLoader
|
||||
{
|
||||
/**
|
||||
* @param string[] $fieldList
|
||||
*/
|
||||
public function load(Entity $entity, array $fieldList): void;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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\Export;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
use LogicException;
|
||||
|
||||
class AdditionalFieldsLoaderFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function create(string $format): AdditionalFieldsLoader
|
||||
{
|
||||
$className = $this->getClassName($format);
|
||||
|
||||
if (!$className) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
|
||||
public function isCreatable(string $format): bool
|
||||
{
|
||||
return (bool) $this->getClassName($format);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<AdditionalFieldsLoader>
|
||||
*/
|
||||
private function getClassName(string $format): ?string
|
||||
{
|
||||
return $this->metadata->get(['app', 'export', 'formatDefs', $format, 'additionalFieldsLoaderClassName']);
|
||||
}
|
||||
}
|
||||
59
application/Espo/Tools/Export/Api/GetStatus.php
Normal file
59
application/Espo/Tools/Export/Api/GetStatus.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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\Export\Api;
|
||||
|
||||
use Espo\Core\Api\Action;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Api\ResponseComposer;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Tools\Export\Service;
|
||||
|
||||
/**
|
||||
* Export status.
|
||||
*/
|
||||
class GetStatus implements Action
|
||||
{
|
||||
public function __construct(private Service $service)
|
||||
{}
|
||||
|
||||
public function process(Request $request): Response
|
||||
{
|
||||
$id = $request->getRouteParam('id');
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$result = $this->service->getStatusData($id);
|
||||
|
||||
return ResponseComposer::json($result);
|
||||
}
|
||||
}
|
||||
126
application/Espo/Tools/Export/Api/PostProcess.php
Normal file
126
application/Espo/Tools/Export/Api/PostProcess.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?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\Export\Api;
|
||||
|
||||
use Espo\Core\Api\Action;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Api\ResponseComposer;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Tools\Export\Params;
|
||||
use Espo\Tools\Export\Service;
|
||||
use Espo\Tools\Export\ServiceParams;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class PostProcess implements Action
|
||||
{
|
||||
public function __construct(private Service $service)
|
||||
{}
|
||||
|
||||
public function process(Request $request): Response
|
||||
{
|
||||
$params = $this->fetchRawParamsFromRequest($request);
|
||||
|
||||
$serviceParams = ServiceParams::create()
|
||||
->withIsIdle($request->getParsedBody()->idle ?? false);
|
||||
|
||||
$result = $this->service->process($params, $serviceParams);
|
||||
|
||||
if ($result->hasResult()) {
|
||||
$subResult = $result->getResult();
|
||||
|
||||
assert($subResult !== null);
|
||||
|
||||
return ResponseComposer::json([
|
||||
'id' => $subResult->getAttachmentId()
|
||||
]);
|
||||
}
|
||||
|
||||
return ResponseComposer::json([
|
||||
'exportId' => $result->getId()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function fetchRawParamsFromRequest(Request $request): Params
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
$entityType = $data->entityType ?? null;
|
||||
|
||||
if (!$entityType) {
|
||||
throw new BadRequest("No entityType.");
|
||||
}
|
||||
|
||||
$params['entityType'] = $entityType;
|
||||
|
||||
$where = $data->where ?? null;
|
||||
$searchParams = $data->searchParams ?? $data->selectData ?? null;
|
||||
$ids = $data->ids ?? null;
|
||||
|
||||
if (!is_null($where) || !is_null($searchParams)) {
|
||||
if (!is_null($where)) {
|
||||
$params['where'] = json_decode(Json::encode($where), true);
|
||||
}
|
||||
|
||||
if (!is_null($searchParams)) {
|
||||
$params['searchParams'] = json_decode(Json::encode($searchParams), true);
|
||||
}
|
||||
} else if (!is_null($ids)) {
|
||||
$params['ids'] = $ids;
|
||||
}
|
||||
|
||||
if (isset($data->attributeList)) {
|
||||
$params['attributeList'] = $data->attributeList;
|
||||
}
|
||||
|
||||
if (isset($data->fieldList)) {
|
||||
$params['fieldList'] = $data->fieldList;
|
||||
}
|
||||
|
||||
if (isset($data->format)) {
|
||||
$params['format'] = $data->format;
|
||||
}
|
||||
|
||||
$obj = Params::fromRaw($params);
|
||||
|
||||
if (isset($data->params) && $data->params instanceof stdClass) {
|
||||
foreach (get_object_vars($data->params) as $key => $value) {
|
||||
$obj = $obj->withParam($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
59
application/Espo/Tools/Export/Api/PostSubscribe.php
Normal file
59
application/Espo/Tools/Export/Api/PostSubscribe.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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\Export\Api;
|
||||
|
||||
use Espo\Core\Api\Action;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Api\ResponseComposer;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Tools\Export\Service;
|
||||
|
||||
/**
|
||||
* Subscribes to a notification on export success.
|
||||
*/
|
||||
class PostSubscribe implements Action
|
||||
{
|
||||
public function __construct(private Service $service)
|
||||
{}
|
||||
|
||||
public function process(Request $request): Response
|
||||
{
|
||||
$id = $request->getRouteParam('id');
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$this->service->subscribeToNotificationOnSuccess($id);
|
||||
|
||||
return ResponseComposer::json(true);
|
||||
}
|
||||
}
|
||||
96
application/Espo/Tools/Export/Collection.php
Normal file
96
application/Espo/Tools/Export/Collection.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\Export;
|
||||
|
||||
use Espo\Core\FieldProcessing\ListLoadProcessor;
|
||||
use Espo\Core\FieldProcessing\Loader\Params as LoaderParams;
|
||||
use Espo\Core\Record\Service as RecordService;
|
||||
use Espo\ORM\Collection as OrmCollection;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Processor\Params as ProcessorParams;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* A lazy-iterable collection of entities.
|
||||
*
|
||||
* @implements IteratorAggregate<int, Entity>
|
||||
*/
|
||||
class Collection implements IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @param OrmCollection<Entity> $collection
|
||||
* @param RecordService<Entity> $recordService
|
||||
*/
|
||||
public function __construct(
|
||||
private OrmCollection $collection,
|
||||
private ListLoadProcessor $listLoadProcessor,
|
||||
private LoaderParams $loaderParams,
|
||||
private ?AdditionalFieldsLoader $additionalFieldsLoader,
|
||||
private RecordService $recordService,
|
||||
private ProcessorParams $processorParams
|
||||
) {}
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return (function () {
|
||||
foreach ($this->collection as $entity) {
|
||||
$this->prepareEntity($entity);
|
||||
|
||||
yield $entity;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
private function prepareEntity(Entity $entity): void
|
||||
{
|
||||
$this->listLoadProcessor->process($entity, $this->loaderParams);
|
||||
|
||||
/** For bc. */
|
||||
if (method_exists($this->recordService, 'loadAdditionalFieldsForExport')) {
|
||||
$this->recordService->loadAdditionalFieldsForExport($entity);
|
||||
}
|
||||
|
||||
if ($this->additionalFieldsLoader && $this->processorParams->getFieldList()) {
|
||||
$this->additionalFieldsLoader->load($entity, $this->processorParams->getFieldList());
|
||||
}
|
||||
|
||||
foreach ($entity->getAttributeList() as $attribute) {
|
||||
$this->prepareEntityValue($entity, $attribute);
|
||||
}
|
||||
}
|
||||
|
||||
private function prepareEntityValue(Entity $entity, string $attribute): void
|
||||
{
|
||||
if (!in_array($attribute, $this->processorParams->getAttributeList())) {
|
||||
$entity->clear($attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
459
application/Espo/Tools/Export/Export.php
Normal file
459
application/Espo/Tools/Export/Export.php
Normal file
@@ -0,0 +1,459 @@
|
||||
<?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\Export;
|
||||
|
||||
use Espo\Core\ORM\Defs\AttributeParam;
|
||||
use Espo\Core\ORM\Repository\Option\SaveOption;
|
||||
use Espo\Core\Record\Select\ApplierClassNameListProvider;
|
||||
use Espo\ORM\Defs\Params\AttributeParam as OrmAttributeParam;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\Tools\Export\Collection as ExportCollection;
|
||||
use Espo\Tools\Export\Processor\Params as ProcessorParams;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\BaseEntity;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Acl\GlobalRestriction;
|
||||
use Espo\Core\FieldProcessing\ListLoadProcessor;
|
||||
use Espo\Core\FieldProcessing\Loader\Params as LoaderParams;
|
||||
use Espo\Core\FileStorage\Manager as FileStorageManager;
|
||||
use Espo\Core\Record\ServiceContainer;
|
||||
use Espo\Core\Select\SelectBuilderFactory;
|
||||
use Espo\Core\Utils\FieldUtil;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Collection;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use RuntimeException;
|
||||
use LogicException;
|
||||
|
||||
class Export
|
||||
{
|
||||
private const DEFAULT_FORMAT = 'csv';
|
||||
|
||||
/** @var ?Params */
|
||||
private ?Params $params = null;
|
||||
/** @var ?Collection<Entity> */
|
||||
private ?Collection $collection = null;
|
||||
|
||||
public function __construct(
|
||||
private ProcessorFactory $processorFactory,
|
||||
private ProcessorParamsHandlerFactory $processorParamsHandlerFactory,
|
||||
private AdditionalFieldsLoaderFactory $additionalFieldsLoaderFactory,
|
||||
private SelectBuilderFactory $selectBuilderFactory,
|
||||
private ServiceContainer $serviceContainer,
|
||||
private Acl $acl,
|
||||
private EntityManager $entityManager,
|
||||
private Metadata $metadata,
|
||||
private FileStorageManager $fileStorageManager,
|
||||
private ListLoadProcessor $listLoadProcessor,
|
||||
private FieldUtil $fieldUtil,
|
||||
private User $user,
|
||||
private ApplierClassNameListProvider $applierClassNameListProvider
|
||||
) {}
|
||||
|
||||
public function setParams(Params $params): self
|
||||
{
|
||||
$this->params = $params;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<Entity> $collection
|
||||
*/
|
||||
public function setCollection(Collection $collection): self
|
||||
{
|
||||
$this->collection = $collection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run export.
|
||||
*/
|
||||
public function run(): Result
|
||||
{
|
||||
if (!$this->params) {
|
||||
throw new LogicException("No params set.");
|
||||
}
|
||||
|
||||
$params = $this->params;
|
||||
|
||||
$entityType = $params->getEntityType();
|
||||
$format = $params->getFormat() ?? self::DEFAULT_FORMAT;
|
||||
$collection = $this->getCollection($params);
|
||||
|
||||
$processor = $this->processorFactory->create($format);
|
||||
|
||||
$processorParams = $this->createProcessorParams($params)
|
||||
->withAttributeList($this->getAttributeList($params))
|
||||
->withFieldList($this->getFieldList($params));
|
||||
|
||||
if ($this->processorParamsHandlerFactory->isCreatable($format)) {
|
||||
$processorParams = $this->processorParamsHandlerFactory
|
||||
->create($format)
|
||||
->handle($params, $processorParams);
|
||||
}
|
||||
|
||||
$loaderParams = LoaderParams::create()
|
||||
->withSelect($processorParams->getAttributeList());
|
||||
|
||||
$recordService = $this->serviceContainer->get($entityType);
|
||||
|
||||
$loader = $this->additionalFieldsLoaderFactory->isCreatable($format) ?
|
||||
$this->additionalFieldsLoaderFactory->create($format) : null;
|
||||
|
||||
$exportCollection = new ExportCollection(
|
||||
collection: $collection,
|
||||
listLoadProcessor: $this->listLoadProcessor,
|
||||
loaderParams: $loaderParams,
|
||||
additionalFieldsLoader: $loader,
|
||||
recordService: $recordService,
|
||||
processorParams: $processorParams
|
||||
) ;
|
||||
|
||||
$stream = $processor->process($processorParams, $exportCollection);
|
||||
|
||||
$mimeType = $this->metadata->get(['app', 'export', 'formatDefs', $format, 'mimeType']);
|
||||
|
||||
/** @var Attachment $attachment */
|
||||
$attachment = $this->entityManager->getRepositoryByClass(Attachment::class)->getNew();
|
||||
|
||||
$attachment
|
||||
->setName($processorParams->getFileName())
|
||||
->setRole(Attachment::ROLE_EXPORT_FILE)
|
||||
->setType($mimeType)
|
||||
->setSize($stream->getSize());
|
||||
|
||||
$this->entityManager->saveEntity($attachment, [
|
||||
SaveOption::CREATED_BY_ID => $this->user->getId(),
|
||||
]);
|
||||
|
||||
$this->fileStorageManager->putStream($attachment, $stream);
|
||||
|
||||
return new Result($attachment->getId());
|
||||
}
|
||||
|
||||
private function createProcessorParams(Params $params): ProcessorParams
|
||||
{
|
||||
$fileName = $params->getFileName();
|
||||
$format = $params->getFormat() ?? self::DEFAULT_FORMAT;
|
||||
$entityType = $params->getEntityType();
|
||||
$attributeList = $params->getAttributeList() ?? [];
|
||||
$fieldList = $params->getFieldList();
|
||||
|
||||
$fileExtension = $this->metadata->get(['app', 'export', 'formatDefs', $format, 'fileExtension']);
|
||||
|
||||
if ($fileName !== null) {
|
||||
$fileName = trim($fileName);
|
||||
}
|
||||
|
||||
$fileName = $fileName ?
|
||||
$fileName . '.' . $fileExtension :
|
||||
"Export_$entityType.$fileExtension";
|
||||
|
||||
$processorParams = (new ProcessorParams($fileName, $attributeList, $fieldList))
|
||||
->withName($params->getName())
|
||||
->withEntityType($params->getEntityType());
|
||||
|
||||
foreach ($params->getParamList() as $n) {
|
||||
$processorParams = $processorParams->withParam($n, $params->getParam($n));
|
||||
}
|
||||
|
||||
return $processorParams;
|
||||
}
|
||||
|
||||
private function getForeignAttributeType(Entity $entity, string $attribute): ?string
|
||||
{
|
||||
$defs = $this->entityManager->getDefs();
|
||||
$entityDefs = $defs->getEntity($entity->getEntityType());
|
||||
|
||||
[$relation, $foreign] = str_contains($attribute, '_') ?
|
||||
explode('_', $attribute) :
|
||||
[
|
||||
$this->getAttributeParam($entity, $attribute, OrmAttributeParam::RELATION),
|
||||
$this->getAttributeParam($entity, $attribute, 'foreign')
|
||||
];
|
||||
|
||||
if (!$relation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$foreign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_string($foreign)) {
|
||||
return Entity::VARCHAR;
|
||||
}
|
||||
|
||||
if (!$entityDefs->hasRelation($relation)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$entityDefs->getRelation($relation)->hasForeignEntityType()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$entityType = $entityDefs->getRelation($relation)->getForeignEntityType();
|
||||
|
||||
if (!$defs->hasEntity($entityType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$foreignEntityDefs = $defs->getEntity($entityType);
|
||||
|
||||
if (!$foreignEntityDefs->hasAttribute($foreign)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $foreignEntityDefs->getAttribute($foreign)->getType();
|
||||
}
|
||||
|
||||
private function checkAttributeIsAllowedForExport(
|
||||
Entity $entity,
|
||||
string $attribute,
|
||||
bool $exportAllFields = false
|
||||
): bool {
|
||||
|
||||
$type = $entity->getAttributeType($attribute);
|
||||
|
||||
if ($type === Entity::FOREIGN || str_contains($attribute, '_')) {
|
||||
$type = $this->getForeignAttributeType($entity, $attribute) ?? $type;
|
||||
}
|
||||
|
||||
if ($type === Entity::PASSWORD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->getAttributeParam($entity, $attribute, AttributeParam::NOT_EXPORTABLE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$exportAllFields) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->getAttributeParam($entity, $attribute, AttributeParam::IS_LINK_MULTIPLE_ID_LIST)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->getAttributeParam($entity, $attribute, AttributeParam::IS_LINK_MULTIPLE_NAME_MAP)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Revise.
|
||||
if ($this->getAttributeParam($entity, $attribute, 'isLinkStub')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<Entity>
|
||||
*/
|
||||
private function getCollection(Params $params): Collection
|
||||
{
|
||||
if ($this->collection) {
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
$entityType = $params->getEntityType();
|
||||
|
||||
$searchParams = $params->getSearchParams();
|
||||
|
||||
$builder = $this->selectBuilderFactory
|
||||
->create()
|
||||
->forUser($this->user)
|
||||
->from($entityType)
|
||||
->withAdditionalApplierClassNameList(
|
||||
$this->applierClassNameListProvider->get($entityType)
|
||||
)
|
||||
->withSearchParams($searchParams);
|
||||
|
||||
if ($params->applyAccessControl()) {
|
||||
$builder->withStrictAccessControl();
|
||||
}
|
||||
|
||||
$query = $builder->build();
|
||||
|
||||
/** @var Collection<Entity> */
|
||||
return $this->entityManager
|
||||
->getRDBRepository($entityType)
|
||||
->clone($query)
|
||||
->sth()
|
||||
->find();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getAttributeList(Params $params): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
$entityType = $params->getEntityType();
|
||||
|
||||
$entityDefs = $this->entityManager
|
||||
->getDefs()
|
||||
->getEntity($entityType);
|
||||
|
||||
$attributeListToSkip = $params->applyAccessControl() ?
|
||||
$this->acl->getScopeForbiddenAttributeList($entityType) :
|
||||
$this->acl->getScopeRestrictedAttributeList($entityType, [
|
||||
GlobalRestriction::TYPE_FORBIDDEN,
|
||||
GlobalRestriction::TYPE_INTERNAL,
|
||||
]);
|
||||
|
||||
$attributeListToSkip[] = Attribute::DELETED;
|
||||
|
||||
$initialAttributeList = $params->getAttributeList();
|
||||
|
||||
if (
|
||||
$params->getAttributeList() === null &&
|
||||
$params->getFieldList() !== null
|
||||
) {
|
||||
$initialAttributeList = $this->getAttributeListFromFieldList($params);
|
||||
}
|
||||
|
||||
if (
|
||||
$params->getAttributeList() === null &&
|
||||
$params->getFieldList() === null
|
||||
) {
|
||||
$initialAttributeList = $entityDefs->getAttributeNameList();
|
||||
}
|
||||
|
||||
assert($initialAttributeList !== null);
|
||||
|
||||
$seed = $this->entityManager->getNewEntity($entityType);
|
||||
|
||||
foreach ($initialAttributeList as $attribute) {
|
||||
if (in_array($attribute, $attributeListToSkip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->checkAttributeIsAllowedForExport($seed, $attribute, $params->allFields())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[] = $attribute;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function getAttributeListFromFieldList(Params $params): array
|
||||
{
|
||||
$entityType = $params->getEntityType();
|
||||
|
||||
$fieldList = $params->getFieldList();
|
||||
|
||||
if ($fieldList === null) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$attributeList = [];
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
$attributeList = array_merge(
|
||||
$attributeList,
|
||||
$this->fieldUtil->getAttributeList($entityType, $field)
|
||||
);
|
||||
}
|
||||
|
||||
return $attributeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?string[]
|
||||
*/
|
||||
private function getFieldList(Params $params): ?array
|
||||
{
|
||||
$entityDefs = $this->entityManager
|
||||
->getDefs()
|
||||
->getEntity($params->getEntityType());
|
||||
|
||||
$fieldList = $params->getFieldList();
|
||||
|
||||
if ($params->allFields()) {
|
||||
$fieldList = $entityDefs->getFieldNameList();
|
||||
|
||||
array_unshift($fieldList, 'id');
|
||||
}
|
||||
|
||||
if ($fieldList === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($fieldList as $i => $field) {
|
||||
if ($field === 'id') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$entityDefs->hasField($field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($entityDefs->getField($field)->getParam('exportDisabled')) {
|
||||
unset($fieldList[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
$fieldList = array_filter($fieldList, function ($item) use ($params) {
|
||||
return $this->acl->checkField($params->getEntityType(), $item);
|
||||
});
|
||||
|
||||
return array_values($fieldList);
|
||||
}
|
||||
|
||||
private function getAttributeParam(Entity $entity, string $attribute, string $param): mixed
|
||||
{
|
||||
if ($entity instanceof BaseEntity) {
|
||||
return $entity->getAttributeParam($attribute, $param);
|
||||
}
|
||||
|
||||
$entityDefs = $this->entityManager
|
||||
->getDefs()
|
||||
->getEntity($entity->getEntityType());
|
||||
|
||||
if (!$entityDefs->hasAttribute($attribute)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $entityDefs->getAttribute($attribute)->getParam($param);
|
||||
}
|
||||
}
|
||||
66
application/Espo/Tools/Export/Factory.php
Normal file
66
application/Espo/Tools/Export/Factory.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?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\Export;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
|
||||
class Factory
|
||||
{
|
||||
private InjectableFactory $injectableFactory;
|
||||
|
||||
private AclManager $aclManager;
|
||||
|
||||
public function __construct(InjectableFactory $injectableFactory, AclManager $aclManager)
|
||||
{
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->aclManager = $aclManager;
|
||||
}
|
||||
|
||||
public function create(): Export
|
||||
{
|
||||
return $this->injectableFactory->create(Export::class);
|
||||
}
|
||||
|
||||
public function createForUser(User $user): Export
|
||||
{
|
||||
$bindingContainer = BindingContainerBuilder::create()
|
||||
->bindInstance(User::class, $user)
|
||||
->bindInstance(Acl::class, $this->aclManager->createUserAcl($user))
|
||||
->build();
|
||||
|
||||
return $this->injectableFactory->createWithBinding(Export::class, $bindingContainer);
|
||||
}
|
||||
}
|
||||
46
application/Espo/Tools/Export/Format/CellValuePreparator.php
Normal file
46
application/Espo/Tools/Export/Format/CellValuePreparator.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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\Export\Format;
|
||||
|
||||
use Espo\Core\Field\Currency;
|
||||
use Espo\Core\Field\Date;
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
interface CellValuePreparator
|
||||
{
|
||||
/**
|
||||
* @param string $name A field name.
|
||||
*/
|
||||
public function prepare(
|
||||
Entity $entity,
|
||||
string $name
|
||||
): string|bool|int|float|Date|DateTime|Currency|null;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?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\Export\Format;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Tools\Export\Format\Xlsx\CellValuePreparators\General;
|
||||
|
||||
class CellValuePreparatorFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function create(string $format, string $fieldType): CellValuePreparator
|
||||
{
|
||||
/** @var class-string<CellValuePreparator> $className */
|
||||
$className = $this->metadata
|
||||
->get(['app', 'export', 'formatDefs', $format, 'cellValuePreparatorClassNameMap', $fieldType]) ??
|
||||
General::class;
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?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\Export\Format\Csv;
|
||||
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\AdditionalFieldsLoader as AdditionalFieldsLoaderInterface;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class AdditionalFieldsLoader implements AdditionalFieldsLoaderInterface
|
||||
{
|
||||
public function __construct(private Metadata $metadata) {}
|
||||
|
||||
public function load(Entity $entity, array $fieldList): void
|
||||
{
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
$fieldType = $this->metadata
|
||||
->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']);
|
||||
|
||||
if (
|
||||
$fieldType === FieldType::LINK_MULTIPLE ||
|
||||
$fieldType === FieldType::ATTACHMENT_MULTIPLE
|
||||
) {
|
||||
if (!$entity->has($field . 'Ids') && $entity->hasLinkMultipleField($field)) {
|
||||
$entity->loadLinkMultipleField($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
123
application/Espo/Tools/Export/Format/Csv/Processor.php
Normal file
123
application/Espo/Tools/Export/Format/Csv/Processor.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?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\Export\Format\Csv;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Entities\Preferences;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Collection;
|
||||
use Espo\Tools\Export\Processor as ProcessorInterface;
|
||||
use Espo\Tools\Export\Processor\Params;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
use const JSON_UNESCAPED_UNICODE;
|
||||
|
||||
class Processor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private Preferences $preferences
|
||||
) {}
|
||||
|
||||
public function process(Params $params, Collection $collection): StreamInterface
|
||||
{
|
||||
$attributeList = $params->getAttributeList();
|
||||
|
||||
$delimiterRaw =
|
||||
$this->preferences->get('exportDelimiter') ??
|
||||
$this->config->get('exportDelimiter') ??
|
||||
',';
|
||||
|
||||
$delimiter = str_replace('\t', "\t", $delimiterRaw);
|
||||
|
||||
$fp = fopen('php://temp', 'w');
|
||||
|
||||
if ($fp === false) {
|
||||
throw new RuntimeException("Could not open temp.");
|
||||
}
|
||||
|
||||
fputcsv($fp, $attributeList, $delimiter);
|
||||
|
||||
foreach ($collection as $entity) {
|
||||
$preparedRow = $this->prepareRow($entity, $attributeList);
|
||||
|
||||
fputcsv($fp, $preparedRow, $delimiter, '"' , "\0");
|
||||
}
|
||||
|
||||
rewind($fp);
|
||||
|
||||
return new Stream($fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $attributeList
|
||||
* @return string[]
|
||||
*/
|
||||
private function prepareRow(Entity $entity, array $attributeList): array
|
||||
{
|
||||
$preparedRow = [];
|
||||
|
||||
foreach ($attributeList as $attribute) {
|
||||
$value = $entity->get($attribute);
|
||||
|
||||
if (is_array($value) || is_object($value)) {
|
||||
$value = Json::encode($value, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
$value = (string) $value;
|
||||
|
||||
$preparedRow[] = $this->sanitizeCellValue($value);
|
||||
}
|
||||
|
||||
return $preparedRow;
|
||||
}
|
||||
|
||||
private function sanitizeCellValue(string $value): string
|
||||
{
|
||||
if ($value === '') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (in_array($value[0], ['+', '-', '@', '='])) {
|
||||
return "'" . $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?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\Export\Format\Xlsx;
|
||||
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\AdditionalFieldsLoader as AdditionalFieldsLoaderInterface;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class AdditionalFieldsLoader implements AdditionalFieldsLoaderInterface
|
||||
{
|
||||
public function __construct(private Metadata $metadata)
|
||||
{}
|
||||
|
||||
public function load(Entity $entity, array $fieldList): void
|
||||
{
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($entity->getRelationList() as $link) {
|
||||
if (!in_array($link, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($entity->getRelationType($link) === Entity::BELONGS_TO_PARENT) {
|
||||
if (!$entity->get($link . 'Name')) {
|
||||
$entity->loadParentNameField($link);
|
||||
}
|
||||
} else if (
|
||||
(
|
||||
(
|
||||
$entity->getRelationType($link) === Entity::BELONGS_TO &&
|
||||
$entity->getRelationParam($link, RelationParam::NO_JOIN)
|
||||
) ||
|
||||
$entity->getRelationType($link) === Entity::HAS_ONE
|
||||
) &&
|
||||
$entity->hasAttribute($link . 'Name')
|
||||
) {
|
||||
if (!$entity->get($link . 'Name') || !$entity->get($link . 'Id')) {
|
||||
$entity->loadLinkField($link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
$fieldType = $this->metadata
|
||||
->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']);
|
||||
|
||||
if ($fieldType === FieldType::LINK_MULTIPLE || $fieldType === FieldType::ATTACHMENT_MULTIPLE) {
|
||||
if (!$entity->has($field . 'Ids') && $entity->hasLinkMultipleField($field)) {
|
||||
$entity->loadLinkMultipleField($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\Core\Field\Address\AddressFactory;
|
||||
use Espo\Core\Field\Address\AddressFormatterFactory;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
class Address implements CellValuePreparator
|
||||
{
|
||||
public function __construct(
|
||||
private AddressFormatterFactory $formatterFactory
|
||||
) {}
|
||||
|
||||
public function prepare(Entity $entity, string $name): ?string
|
||||
{
|
||||
$address = (new AddressFactory())->createFromEntity($entity, $name);
|
||||
|
||||
$formatter = $this->formatterFactory->createDefault();
|
||||
|
||||
return $formatter->format($address) ?: null;
|
||||
}
|
||||
}
|
||||
@@ -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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
class Boolean implements CellValuePreparator
|
||||
{
|
||||
public function prepare(Entity $entity, string $name): bool
|
||||
{
|
||||
return (bool) $entity->get($name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\Core\Field\Currency as CurrencyValue;
|
||||
use Espo\Core\Field\Currency\CurrencyFactory;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
class Currency implements CellValuePreparator
|
||||
{
|
||||
public function prepare(Entity $entity, string $name): ?CurrencyValue
|
||||
{
|
||||
$factory = new CurrencyFactory();
|
||||
|
||||
if (!$factory->isCreatableFromEntity($entity, $name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $factory->createFromEntity($entity, $name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\Core\Field\Currency as CurrencyValue;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
class CurrencyConverted implements CellValuePreparator
|
||||
{
|
||||
private string $code;
|
||||
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
$this->code = $config->get('defaultCurrency');
|
||||
}
|
||||
|
||||
public function prepare(Entity $entity, string $name): ?CurrencyValue
|
||||
{
|
||||
$value = $entity->get($name);
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return CurrencyValue::create($value, $this->code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\Core\Field\Date as DateValue;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
class Date implements CellValuePreparator
|
||||
{
|
||||
public function prepare(Entity $entity, string $name): ?DateValue
|
||||
{
|
||||
$value = $entity->get($name);
|
||||
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return DateValue::fromString($value);
|
||||
}
|
||||
}
|
||||
@@ -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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\Core\Field\DateTime as DateTimeValue;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
use DateTimeZone;
|
||||
|
||||
class DateTime implements CellValuePreparator
|
||||
{
|
||||
private string $timezone;
|
||||
|
||||
public function __construct(Config\ApplicationConfig $applicationConfig)
|
||||
{
|
||||
$this->timezone = $applicationConfig->getTimeZone();
|
||||
}
|
||||
|
||||
public function prepare(Entity $entity, string $name): ?DateTimeValue
|
||||
{
|
||||
$value = $entity->get($name);
|
||||
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return DateTimeValue::fromString($value)
|
||||
->withTimezone(
|
||||
new DateTimeZone($this->timezone)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\Core\Field\DateTime as DateTimeValue;
|
||||
use Espo\Core\Field\Date as DateValue;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
use DateTimeZone;
|
||||
|
||||
class DateTimeOptional implements CellValuePreparator
|
||||
{
|
||||
private string $timezone;
|
||||
|
||||
public function __construct(Config\ApplicationConfig $applicationConfig)
|
||||
{
|
||||
$this->timezone = $applicationConfig->getTimeZone();
|
||||
}
|
||||
|
||||
public function prepare(Entity $entity, string $name): DateTimeValue|DateValue|null
|
||||
{
|
||||
$dateValue = $entity->get($name . 'Date');
|
||||
|
||||
if ($dateValue !== null) {
|
||||
return DateValue::fromString($dateValue);
|
||||
}
|
||||
|
||||
$value = $entity->get($name);
|
||||
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return DateTimeValue::fromString($value)
|
||||
->withTimezone(
|
||||
new DateTimeZone($this->timezone)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
class Duration implements CellValuePreparator
|
||||
{
|
||||
public function __construct(private Language $language)
|
||||
{}
|
||||
|
||||
public function prepare(Entity $entity, string $name): ?string
|
||||
{
|
||||
$value = $entity->get($name);
|
||||
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$seconds = intval($value);
|
||||
|
||||
$days = intval(floor($seconds / 86400));
|
||||
$seconds = $seconds - $days * 86400;
|
||||
$hours = intval(floor($seconds / 3600));
|
||||
$seconds = $seconds - $hours * 3600;
|
||||
$minutes = intval(floor($seconds / 60));
|
||||
|
||||
$value = '';
|
||||
|
||||
if ($days) {
|
||||
$value .= $days . $this->language->translateLabel('d', 'durationUnits');
|
||||
|
||||
if ($minutes || $hours) {
|
||||
$value .= ' ';
|
||||
}
|
||||
}
|
||||
|
||||
if ($hours) {
|
||||
$value .= $hours . $this->language->translateLabel('h', 'durationUnits');
|
||||
|
||||
if ($minutes) {
|
||||
$value .= ' ';
|
||||
}
|
||||
}
|
||||
|
||||
if ($minutes) {
|
||||
$value .= $minutes . $this->language->translateLabel('m', 'durationUnits');
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
use Espo\Tools\Export\Format\Xlsx\FieldHelper;
|
||||
|
||||
class Enumeration implements CellValuePreparator
|
||||
{
|
||||
public function __construct(
|
||||
private Defs $ormDefs,
|
||||
private Language $language,
|
||||
private FieldHelper $fieldHelper
|
||||
) {}
|
||||
|
||||
public function prepare(Entity $entity, string $name): ?string
|
||||
{
|
||||
if (!$entity->has($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = $entity->get($name);
|
||||
|
||||
$fieldData = $this->fieldHelper->getData($entity->getEntityType(), $name);
|
||||
|
||||
if (!$fieldData) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$entityType = $fieldData->getEntityType();
|
||||
$field = $fieldData->getField();
|
||||
|
||||
$translation = $this->ormDefs
|
||||
->getEntity($entityType)
|
||||
->getField($field)
|
||||
->getParam('translation');
|
||||
|
||||
if (!$translation) {
|
||||
return $this->language->translateOption($value, $field, $entityType);
|
||||
}
|
||||
|
||||
$map = $this->language->get($translation);
|
||||
|
||||
if (!is_array($map)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $map[$value] ?? $value;
|
||||
}
|
||||
}
|
||||
@@ -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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
class Floating implements CellValuePreparator
|
||||
{
|
||||
public function prepare(Entity $entity, string $name): float
|
||||
{
|
||||
return $entity->get($name) ?? 0.0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
class General implements CellValuePreparator
|
||||
{
|
||||
public function prepare(Entity $entity, string $name): string|bool|int|float|null
|
||||
{
|
||||
$value = $entity->get($name);
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
!is_string($value) &&
|
||||
!is_int($value) &&
|
||||
!is_float($value) &&
|
||||
!is_bool($value)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
class Integer implements CellValuePreparator
|
||||
{
|
||||
public function prepare(Entity $entity, string $name): int
|
||||
{
|
||||
/** @var int */
|
||||
return $entity->get($name) ?? 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
class Link implements CellValuePreparator
|
||||
{
|
||||
public function prepare(Entity $entity, string $name): ?string
|
||||
{
|
||||
/** @var ?string */
|
||||
return $entity->get($name . 'Name');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
use stdClass;
|
||||
|
||||
class LinkMultiple implements CellValuePreparator
|
||||
{
|
||||
public function prepare(Entity $entity, string $name): ?string
|
||||
{
|
||||
if (
|
||||
!$entity->has($name . 'Ids') ||
|
||||
!$entity->has($name . 'Names')
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var string[] $ids */
|
||||
$ids = $entity->get($name . 'Ids');
|
||||
/** @var ?stdClass $names */
|
||||
$names = $entity->get($name . 'Names');
|
||||
|
||||
$nameList = array_map(function ($id) use ($names) {
|
||||
return $names->$id ?? $id;
|
||||
}, $ids);
|
||||
|
||||
return implode(',', $nameList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\ORM\Defs;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
use Espo\Tools\Export\Format\Xlsx\FieldHelper;
|
||||
|
||||
class MultiEnum implements CellValuePreparator
|
||||
{
|
||||
public function __construct(
|
||||
private Defs $ormDefs,
|
||||
private Language $language,
|
||||
private FieldHelper $fieldHelper
|
||||
) {}
|
||||
|
||||
public function prepare(Entity $entity, string $name): ?string
|
||||
{
|
||||
if (!$entity->has($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$list = $entity->get($name);
|
||||
|
||||
if (!is_array($list)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var string[] $list */
|
||||
|
||||
$fieldData = $this->fieldHelper->getData($entity->getEntityType(), $name);
|
||||
|
||||
if (!$fieldData) {
|
||||
return $this->joinList($list);
|
||||
}
|
||||
|
||||
$entityType = $fieldData->getEntityType();
|
||||
$field = $fieldData->getField();
|
||||
|
||||
$translation = $this->ormDefs
|
||||
->getEntity($entityType)
|
||||
->getField($field)
|
||||
->getParam('translation');
|
||||
|
||||
if (!$translation) {
|
||||
return $this->joinList(
|
||||
array_map(
|
||||
function ($item) use ($field, $entityType) {
|
||||
return $this->language->translateOption($item, $field, $entityType);
|
||||
},
|
||||
$list
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$map = $this->language->get($translation);
|
||||
|
||||
if (!is_array($map)) {
|
||||
return $this->joinList($list);
|
||||
}
|
||||
|
||||
return $this->joinList(
|
||||
array_map(
|
||||
function ($item) use ($map) {
|
||||
return $map[$item] ?? $item;
|
||||
},
|
||||
$list
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $list
|
||||
*/
|
||||
private function joinList(array $list): string
|
||||
{
|
||||
return implode(', ', $list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?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\Export\Format\Xlsx\CellValuePreparators;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
|
||||
class PersonName implements CellValuePreparator
|
||||
{
|
||||
public function prepare(Entity $entity, string $name): ?string
|
||||
{
|
||||
$value = $entity->get($name);
|
||||
|
||||
if ($value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
|
||||
$firstName = $entity->get('first' . ucfirst($name));
|
||||
$lastName = $entity->get('last' . ucfirst($name));
|
||||
|
||||
if ($firstName) {
|
||||
$arr[] = $firstName;
|
||||
}
|
||||
|
||||
if ($lastName) {
|
||||
$arr[] = $lastName;
|
||||
}
|
||||
|
||||
return implode(' ', $arr) ?: null;
|
||||
}
|
||||
}
|
||||
60
application/Espo/Tools/Export/Format/Xlsx/FieldData.php
Normal file
60
application/Espo/Tools/Export/Format/Xlsx/FieldData.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?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\Export\Format\Xlsx;
|
||||
|
||||
class FieldData
|
||||
{
|
||||
public function __construct(
|
||||
private string $entityType,
|
||||
private string $field,
|
||||
private string $type,
|
||||
private ?string $link
|
||||
) {}
|
||||
|
||||
public function getEntityType(): string
|
||||
{
|
||||
return $this->entityType;
|
||||
}
|
||||
|
||||
public function getField(): string
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function getLink(): ?string
|
||||
{
|
||||
return $this->link;
|
||||
}
|
||||
}
|
||||
115
application/Espo/Tools/Export/Format/Xlsx/FieldHelper.php
Normal file
115
application/Espo/Tools/Export/Format/Xlsx/FieldHelper.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?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\Export\Format\Xlsx;
|
||||
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\ORM\Defs;
|
||||
|
||||
class FieldHelper
|
||||
{
|
||||
public function __construct(
|
||||
private Defs $ormDefs
|
||||
) {}
|
||||
|
||||
public function isForeignReference(string $name): bool
|
||||
{
|
||||
return str_contains($name, '_');
|
||||
}
|
||||
|
||||
private function isForeign(string $entityType, string $name): bool
|
||||
{
|
||||
if ($this->isForeignReference($name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$entityDefs = $this->ormDefs->getEntity($entityType);
|
||||
|
||||
return
|
||||
$entityDefs->hasField($name) &&
|
||||
$entityDefs->getField($name)->getType() === FieldType::FOREIGN;
|
||||
}
|
||||
|
||||
public function getData(string $entityType, string $name): ?FieldData
|
||||
{
|
||||
$entityDefs = $this->ormDefs->getEntity($entityType);
|
||||
|
||||
if (!$this->isForeign($entityType, $name)) {
|
||||
if (!$entityDefs->hasField($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $entityDefs
|
||||
->getField($name)
|
||||
->getType();
|
||||
|
||||
return new FieldData($entityType, $name, $type, null);
|
||||
}
|
||||
|
||||
$link = null;
|
||||
$field = null;
|
||||
|
||||
if (
|
||||
$entityDefs->hasField($name) &&
|
||||
$entityDefs->getField($name)->getType() === FieldType::FOREIGN
|
||||
) {
|
||||
$fieldDefs = $entityDefs->getField($name);
|
||||
|
||||
$link = $fieldDefs->getParam('link');
|
||||
$field = $fieldDefs->getParam('field');
|
||||
} else if (str_contains($name, '_')) {
|
||||
[$link, $field] = explode('_', $name);
|
||||
}
|
||||
|
||||
if (!$link || !$field) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$entityDefs = $this->ormDefs->getEntity($entityType);
|
||||
|
||||
if (!$entityDefs->hasRelation($link)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$relationDefs = $entityDefs->getRelation($link);
|
||||
|
||||
if (!$relationDefs->hasForeignEntityType()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$foreignEntityType = $relationDefs->getForeignEntityType();
|
||||
|
||||
$type = $this->ormDefs
|
||||
->getEntity($foreignEntityType)
|
||||
->getField($field)
|
||||
->getType();
|
||||
|
||||
return new FieldData($foreignEntityType, $field, $type, $link);
|
||||
}
|
||||
}
|
||||
296
application/Espo/Tools/Export/Format/Xlsx/OpenSpoutProcessor.php
Normal file
296
application/Espo/Tools/Export/Format/Xlsx/OpenSpoutProcessor.php
Normal file
@@ -0,0 +1,296 @@
|
||||
<?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\Export\Format\Xlsx;
|
||||
|
||||
use Espo\Core\Field\Currency;
|
||||
use Espo\Core\Field\Date;
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Collection;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
use Espo\Tools\Export\Format\CellValuePreparatorFactory;
|
||||
use Espo\Tools\Export\Processor as ProcessorInterface;
|
||||
use Espo\Tools\Export\Processor\Params;
|
||||
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use LogicException;
|
||||
use OpenSpout\Common\Entity\Cell;
|
||||
use OpenSpout\Common\Entity\Style\Style;
|
||||
use OpenSpout\Common\Exception\InvalidArgumentException;
|
||||
use OpenSpout\Common\Exception\IOException;
|
||||
use OpenSpout\Writer\Exception\WriterNotOpenedException;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
use OpenSpout\Writer\XLSX\Entity\SheetView;
|
||||
use OpenSpout\Writer\XLSX\Options;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use RuntimeException;
|
||||
|
||||
class OpenSpoutProcessor implements ProcessorInterface
|
||||
{
|
||||
private const FORMAT = 'xlsx';
|
||||
|
||||
/** @var array<string, CellValuePreparator> */
|
||||
private array $preparatorsCache = [];
|
||||
/** @var array<string, string> */
|
||||
private array $typesCache = [];
|
||||
|
||||
public function __construct(
|
||||
private FieldHelper $fieldHelper,
|
||||
private CellValuePreparatorFactory $cellValuePreparatorFactory,
|
||||
private Language $language,
|
||||
private DateTimeUtil $dateTime,
|
||||
private Config $config,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
* @throws WriterNotOpenedException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function process(Params $params, Collection $collection): StreamInterface
|
||||
{
|
||||
if (!$params->getFieldList()) {
|
||||
throw new LogicException("No field list.");
|
||||
}
|
||||
|
||||
$filePath = tempnam(sys_get_temp_dir(), 'espo-export');
|
||||
|
||||
if (!$filePath) {
|
||||
throw new RuntimeException("Could not create a temp file.");
|
||||
}
|
||||
|
||||
$options = new Options();
|
||||
$options->setColumnWidthForRange(20, 1, count($params->getFieldList()));
|
||||
|
||||
$writer = new Writer($options);
|
||||
|
||||
$writer->openToFile($filePath);
|
||||
|
||||
$sheetView = new SheetView();
|
||||
$sheetView->setFreezeRow(2);
|
||||
|
||||
$writer->getCurrentSheet()->setSheetView($sheetView);
|
||||
|
||||
$headerCells = [];
|
||||
|
||||
foreach ($params->getFieldList() as $name) {
|
||||
$label = $this->translateLabel($params->getEntityType(), $name);
|
||||
|
||||
$headerCells[] = Cell::fromValue($label, (new Style())->setFontBold());
|
||||
}
|
||||
|
||||
$writer->addRow(new Row($headerCells));
|
||||
|
||||
foreach ($collection as $entity) {
|
||||
$this->processRow($params, $entity, $writer);
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
$resource = fopen($filePath, 'r+');
|
||||
|
||||
if ($resource === false) {
|
||||
throw new RuntimeException("Could not open temp.");
|
||||
}
|
||||
|
||||
$stream = new Stream($resource);
|
||||
$stream->seek(0);
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
private function translateLabel(string $entityType, string $name): string
|
||||
{
|
||||
$label = $name;
|
||||
|
||||
$fieldData = $this->fieldHelper->getData($entityType, $name);
|
||||
$isForeignReference = $this->fieldHelper->isForeignReference($name);
|
||||
|
||||
if ($isForeignReference && $fieldData && $fieldData->getLink()) {
|
||||
$label =
|
||||
$this->language->translateLabel($fieldData->getLink(), 'links', $entityType) . '.' .
|
||||
$this->language->translateLabel($fieldData->getField(), 'fields', $fieldData->getEntityType());
|
||||
}
|
||||
|
||||
if (!$isForeignReference) {
|
||||
$label = $this->language->translateLabel($name, 'fields', $entityType);
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
private function processRow(Params $params, Entity $entity, Writer $writer): void
|
||||
{
|
||||
$cells = [];
|
||||
|
||||
foreach ($params->getFieldList() ?? [] as $name) {
|
||||
$cells[] = $this->prepareCell($params, $entity, $name);
|
||||
}
|
||||
|
||||
$writer->addRow(new Row($cells));
|
||||
}
|
||||
|
||||
private function prepareCell(Params $params, Entity $entity, mixed $name): Cell
|
||||
{
|
||||
$type = $this->getFieldType($params->getEntityType(), $name);
|
||||
|
||||
$value = $this->getPreparator($type)
|
||||
->prepare($entity, $name);
|
||||
|
||||
if (is_string($value)) {
|
||||
$value = $this->sanitizeCellValue($value);
|
||||
|
||||
return Cell\StringCell::fromValue($value);
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
return Cell\NumericCell::fromValue($value);
|
||||
}
|
||||
|
||||
if (is_float($value)) {
|
||||
return Cell\NumericCell::fromValue($value);
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
return Cell\BooleanCell::fromValue($value);
|
||||
}
|
||||
|
||||
if ($value instanceof Date) {
|
||||
$dateFormat = self::convertDateFormat($this->dateTime->getDateFormat());
|
||||
|
||||
$style = new Style();
|
||||
$style->setFormat($dateFormat);
|
||||
|
||||
return Cell\DateTimeCell::fromValue($value->toDateTime(), $style);
|
||||
}
|
||||
|
||||
if ($value instanceof DateTime) {
|
||||
$dateTimeFormat = self::convertDateFormat($this->dateTime->getDateTimeFormat());
|
||||
|
||||
$style = new Style();
|
||||
$style->setFormat($dateTimeFormat);
|
||||
|
||||
return Cell\DateTimeCell::fromValue($value->toDateTime(), $style);
|
||||
}
|
||||
|
||||
if ($value instanceof Currency) {
|
||||
$format = $this->getCurrencyFormat($value->getCode());
|
||||
|
||||
$style = new Style();
|
||||
$style->setFormat($format);
|
||||
|
||||
return Cell\NumericCell::fromValue($value->getAmount(), $style);
|
||||
}
|
||||
|
||||
return Cell::fromValue('');
|
||||
}
|
||||
|
||||
private function getFieldType(string $entityType, string $name): string
|
||||
{
|
||||
$key = $entityType . '-' . $name;
|
||||
|
||||
$type = $this->typesCache[$key] ?? null;
|
||||
|
||||
if (!$type) {
|
||||
$fieldData = $this->fieldHelper->getData($entityType, $name);
|
||||
$type = $fieldData ? $fieldData->getType() : 'base';
|
||||
$this->typesCache[$key] = $type;
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
private function getPreparator(string $type): CellValuePreparator
|
||||
{
|
||||
if (!array_key_exists($type, $this->preparatorsCache)) {
|
||||
$this->preparatorsCache[$type] = $this->cellValuePreparatorFactory->create(self::FORMAT, $type);
|
||||
}
|
||||
|
||||
return $this->preparatorsCache[$type];
|
||||
}
|
||||
|
||||
private static function convertDateFormat(string $format): string
|
||||
{
|
||||
$map = [
|
||||
'MM' => 'mm',
|
||||
'DD' => 'dd',
|
||||
'YYYY' => 'yyyy',
|
||||
'HH' => 'hh',
|
||||
'mm' => 'mm',
|
||||
'hh' => 'hh',
|
||||
'A' => 'AM/PM',
|
||||
'a' => 'AM/PM',
|
||||
'ss' => 'ss',
|
||||
];
|
||||
|
||||
return str_replace(
|
||||
array_keys($map),
|
||||
array_values($map),
|
||||
$format
|
||||
);
|
||||
}
|
||||
|
||||
private function getCurrencyFormat(string $code): string
|
||||
{
|
||||
$currencySymbol = $this->metadata->get(['app', 'currency', 'symbolMap', $code], '');
|
||||
|
||||
$currencyFormat = $this->config->get('currencyFormat') ?? 2;
|
||||
|
||||
if ($currencyFormat === 3) {
|
||||
return '#,##0.00_-"' . $currencySymbol . '"';
|
||||
}
|
||||
|
||||
return '[$' . $currencySymbol . '-409]#,##0.00;-[$' . $currencySymbol . '-409]#,##0.00';
|
||||
}
|
||||
|
||||
private function sanitizeCellValue(string $value): string
|
||||
{
|
||||
if ($value === '') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (in_array($value[0], ['+', '-', '@', '='])) {
|
||||
return "'" . $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
134
application/Espo/Tools/Export/Format/Xlsx/ParamsHandler.php
Normal file
134
application/Espo/Tools/Export/Format/Xlsx/ParamsHandler.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?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\Export\Format\Xlsx;
|
||||
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Params;
|
||||
use Espo\Tools\Export\Processor;
|
||||
use Espo\Tools\Export\ProcessorParamsHandler;
|
||||
|
||||
class ParamsHandler implements ProcessorParamsHandler
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function handle(Params $params, Processor\Params $processorParams): Processor\Params
|
||||
{
|
||||
$fieldList = $processorParams->getFieldList();
|
||||
|
||||
if ($fieldList === null) {
|
||||
return $processorParams;
|
||||
}
|
||||
|
||||
$fieldList = $this->filterFieldList($params->getEntityType(), $fieldList, $params->allFields());
|
||||
|
||||
$attributeList = $processorParams->getAttributeList();
|
||||
|
||||
$this->addAdditionalAttributes($params->getEntityType(), $attributeList, $fieldList);
|
||||
|
||||
return $processorParams
|
||||
->withAttributeList($attributeList)
|
||||
->withFieldList($fieldList);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $fieldList
|
||||
* @return string[]
|
||||
*/
|
||||
private function filterFieldList(string $entityType, array $fieldList, bool $exportAllFields): array
|
||||
{
|
||||
if ($exportAllFields) {
|
||||
foreach ($fieldList as $i => $field) {
|
||||
$type = $this->metadata->get(['entityDefs', $entityType, 'fields', $field, 'type']);
|
||||
|
||||
if (in_array($type, [FieldType::LINK_MULTIPLE, FieldType::ATTACHMENT_MULTIPLE])) {
|
||||
unset($fieldList[$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($fieldList);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $attributeList
|
||||
* @param string[] $fieldList
|
||||
*/
|
||||
private function addAdditionalAttributes(string $entityType, array &$attributeList, array $fieldList): void
|
||||
{
|
||||
$linkList = [];
|
||||
|
||||
if (!in_array('id', $attributeList)) {
|
||||
$attributeList[] = 'id';
|
||||
}
|
||||
|
||||
$linkDefs = $this->metadata->get(['entityDefs', $entityType, 'links']) ?? [];
|
||||
|
||||
foreach ($linkDefs as $link => $defs) {
|
||||
$linkType = $defs['type'] ?? null;
|
||||
|
||||
if (!$linkType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($linkType === Entity::BELONGS_TO_PARENT) {
|
||||
$linkList[] = $link;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($linkType === Entity::BELONGS_TO && !empty($defs[RelationParam::NO_JOIN])) {
|
||||
if ($this->metadata->get(['entityDefs', $entityType, 'fields', $link])) {
|
||||
$linkList[] = $link;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($linkList as $item) {
|
||||
if (in_array($item, $fieldList) && !in_array($item . 'Name', $attributeList)) {
|
||||
$attributeList[] = $item . 'Name';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
$type = $this->metadata->get(['entityDefs', $entityType, 'fields', $field, 'type']);
|
||||
|
||||
if ($type === FieldType::CURRENCY_CONVERTED) {
|
||||
if (!in_array($field, $attributeList)) {
|
||||
$attributeList[] = $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,657 @@
|
||||
<?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\Export\Format\Xlsx;
|
||||
|
||||
use Espo\Core\Field\Currency;
|
||||
use Espo\Core\Field\Date;
|
||||
use Espo\Core\Field\DateTime as DateTimeValue;
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Config\ApplicationConfig;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\Core\FileStorage\Manager as FileStorageManager;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Export\Collection;
|
||||
use Espo\Tools\Export\Format\CellValuePreparator;
|
||||
use Espo\Tools\Export\Format\CellValuePreparatorFactory;
|
||||
use Espo\Tools\Export\Processor as ProcessorInterface;
|
||||
use Espo\Tools\Export\Processor\Params;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use RuntimeException;
|
||||
|
||||
class PhpSpreadsheetProcessor implements ProcessorInterface
|
||||
{
|
||||
private const FORMAT = 'xlsx';
|
||||
private const PARAM_RECORD_LINKS = 'recordLinks';
|
||||
private const PARAM_TITLE = 'title';
|
||||
|
||||
/** @var array<string, CellValuePreparator> */
|
||||
private array $preparatorsCache = [];
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
private array $titleStyle = [
|
||||
'font' => [
|
||||
'bold' => true,
|
||||
'size' => 12,
|
||||
]
|
||||
];
|
||||
/** @var array<string, mixed> */
|
||||
private array $dateStyle = [
|
||||
'font' => [
|
||||
'size' => 12,
|
||||
]
|
||||
];
|
||||
/** @var array<string, mixed> */
|
||||
private array $headerStyle = [
|
||||
'font' => [
|
||||
'bold' => true,
|
||||
'size' => 12,
|
||||
]
|
||||
];
|
||||
/** @var array<string, mixed> */
|
||||
private array $linkStyle = [
|
||||
'font' => [
|
||||
'color' => ['rgb' => '345b7c'],
|
||||
'underline' => 'single',
|
||||
]
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private Metadata $metadata,
|
||||
private Language $language,
|
||||
private DateTimeUtil $dateTime,
|
||||
private EntityManager $entityManager,
|
||||
private FileStorageManager $fileStorageManager,
|
||||
private FieldHelper $fieldHelper,
|
||||
private CellValuePreparatorFactory $cellValuePreparatorFactory,
|
||||
private ApplicationConfig $applicationConfig,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws SpreadsheetException
|
||||
* @throws WriterException
|
||||
*/
|
||||
public function process(Params $params, Collection $collection): StreamInterface
|
||||
{
|
||||
$entityType = $params->getEntityType();
|
||||
$fieldList = $params->getFieldList();
|
||||
|
||||
if ($fieldList === null) {
|
||||
throw new RuntimeException("Field list is required.");
|
||||
}
|
||||
|
||||
$sheetName = $this->getSheetNameFromParams($params);
|
||||
$exportName = $params->getName() ??
|
||||
$this->language->translate($entityType, 'scopeNamesPlural');
|
||||
|
||||
$phpExcel = new Spreadsheet();
|
||||
|
||||
$headerRowNumber = $params->getParam(self::PARAM_TITLE) ? 3 : 1;
|
||||
|
||||
$sheet = $phpExcel->setActiveSheetIndex(0)
|
||||
->setTitle($sheetName)
|
||||
->freezePane('A' . ($headerRowNumber + 1));
|
||||
|
||||
$now = new DateTime();
|
||||
$now->setTimezone(new DateTimeZone($this->config->get('timeZone', 'UTC')));
|
||||
|
||||
if ($params->getParam(self::PARAM_TITLE)) {
|
||||
$sheet
|
||||
->setCellValue('A1', $this->sanitizeCellValue($exportName))
|
||||
->setCellValue('A2',
|
||||
SharedDate::PHPToExcel(strtotime($now->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT)))
|
||||
);
|
||||
|
||||
$sheet->getStyle('A1')->applyFromArray($this->titleStyle);
|
||||
$sheet->getStyle('A2')->applyFromArray($this->dateStyle);
|
||||
$sheet->getStyle('A2')
|
||||
->getNumberFormat()
|
||||
->setFormatCode($this->dateTime->getDateTimeFormat());
|
||||
}
|
||||
|
||||
$azRange = $this->getColumnsRange($fieldList);
|
||||
|
||||
$rowNumber = $headerRowNumber;
|
||||
$linkColList = [];
|
||||
$lastIndex = 0;
|
||||
|
||||
foreach ($fieldList as $i => $name) {
|
||||
$col = $azRange[$i];
|
||||
$type = 'base';
|
||||
|
||||
$label = $this->translateLabel($entityType, $name);
|
||||
|
||||
$fieldData = $this->fieldHelper->getData($entityType, $name);
|
||||
|
||||
if ($fieldData) {
|
||||
$type = $fieldData->getType();
|
||||
}
|
||||
|
||||
$sheet->setCellValue($col . $rowNumber, $this->sanitizeCellValue($label));
|
||||
$sheet->getColumnDimension($col)->setAutoSize(true);
|
||||
|
||||
$linkTypeList = $params->getParam(self::PARAM_RECORD_LINKS) ?
|
||||
[FieldType::URL, FieldType::PHONE, FieldType::EMAIL, FieldType::LINK, FieldType::LINK_PARENT] :
|
||||
['url'];
|
||||
|
||||
if (
|
||||
in_array($type, $linkTypeList) ||
|
||||
$params->getParam(self::PARAM_RECORD_LINKS) && $name === 'name'
|
||||
) {
|
||||
$linkColList[] = $col;
|
||||
}
|
||||
|
||||
$lastIndex = $i;
|
||||
}
|
||||
|
||||
$col = $azRange[$lastIndex];
|
||||
|
||||
$sheet->getStyle("A$rowNumber:$col$rowNumber")->applyFromArray($this->headerStyle);
|
||||
$sheet->setAutoFilter("A$rowNumber:$col$rowNumber");
|
||||
|
||||
$typesCache = [];
|
||||
|
||||
$rowNumber++;
|
||||
|
||||
foreach ($collection as $entity) {
|
||||
$this->processRow(
|
||||
$entity,
|
||||
$sheet,
|
||||
$rowNumber,
|
||||
$fieldList,
|
||||
$azRange,
|
||||
$typesCache
|
||||
);
|
||||
|
||||
$rowNumber++;
|
||||
}
|
||||
|
||||
$sheet->getStyle("A$headerRowNumber:A$rowNumber")
|
||||
->getNumberFormat()
|
||||
->setFormatCode(NumberFormat::FORMAT_TEXT);
|
||||
|
||||
$startingRowNumber = 2;
|
||||
|
||||
if ($params->getParam(self::PARAM_TITLE)) {
|
||||
$startingRowNumber += 2;
|
||||
}
|
||||
|
||||
foreach ($fieldList as $i => $name) {
|
||||
$col = $azRange[$i];
|
||||
|
||||
if (!array_key_exists($name, $typesCache)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$type = $typesCache[$name];
|
||||
|
||||
$coordinate = "$col$startingRowNumber:$col$rowNumber";
|
||||
|
||||
switch ($type) {
|
||||
case FieldType::CURRENCY:
|
||||
case FieldType::CURRENCY_CONVERTED:
|
||||
break;
|
||||
|
||||
case FieldType::INT:
|
||||
$sheet->getStyle($coordinate)
|
||||
->getNumberFormat()
|
||||
->setFormatCode('0');
|
||||
|
||||
break;
|
||||
|
||||
case FieldType::FLOAT:
|
||||
$sheet->getStyle($coordinate)
|
||||
->getNumberFormat()
|
||||
->setFormatCode(NumberFormat::FORMAT_NUMBER_COMMA_SEPARATED1);
|
||||
break;
|
||||
|
||||
case FieldType::DATE:
|
||||
$sheet->getStyle($coordinate)
|
||||
->getNumberFormat()
|
||||
->setFormatCode($this->dateTime->getDateFormat());
|
||||
|
||||
break;
|
||||
|
||||
case FieldType::DATETIME_OPTIONAL:
|
||||
case FieldType::DATETIME:
|
||||
$sheet->getStyle($coordinate)
|
||||
->getNumberFormat()
|
||||
->setFormatCode($this->dateTime->getDateTimeFormat());
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$sheet->getStyle($coordinate)
|
||||
->getNumberFormat()
|
||||
->setFormatCode('@');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($linkColList as $linkColumn) {
|
||||
$sheet
|
||||
->getStyle($linkColumn . $startingRowNumber . ':' . $linkColumn . $rowNumber)
|
||||
->applyFromArray($this->linkStyle);
|
||||
}
|
||||
|
||||
$objWriter = IOFactory::createWriter($phpExcel, 'Xlsx');
|
||||
|
||||
$resource = fopen('php://temp', 'r+');
|
||||
|
||||
if ($resource === false) {
|
||||
throw new RuntimeException("Could not open temp.");
|
||||
}
|
||||
|
||||
$objWriter->save($resource);
|
||||
|
||||
$stream = new Stream($resource);
|
||||
$stream->seek(0);
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
private function translateLabel(string $entityType, string $name): string
|
||||
{
|
||||
$label = $name;
|
||||
|
||||
$fieldData = $this->fieldHelper->getData($entityType, $name);
|
||||
$isForeignReference = $this->fieldHelper->isForeignReference($name);
|
||||
|
||||
if ($isForeignReference && $fieldData && $fieldData->getLink()) {
|
||||
$label =
|
||||
$this->language->translateLabel($fieldData->getLink(), 'links', $entityType) . '.' .
|
||||
$this->language->translateLabel($fieldData->getField(), 'fields', $fieldData->getEntityType());
|
||||
}
|
||||
|
||||
if (!$isForeignReference) {
|
||||
$label = $this->language->translateLabel($name, 'fields', $entityType);
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $fieldList
|
||||
* @return string[]
|
||||
*/
|
||||
private function getColumnsRange(array $fieldList): array
|
||||
{
|
||||
$azRange = range('A', 'Z');
|
||||
$azRangeCopied = $azRange;
|
||||
|
||||
foreach ($azRangeCopied as $i => $char1) {
|
||||
foreach ($azRangeCopied as $j => $char2) {
|
||||
$azRange[] = $char1 . $char2;
|
||||
|
||||
if ($i * count($azRangeCopied) + $j === count($fieldList)) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $azRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $fieldList
|
||||
* @param string[] $azRange
|
||||
* @param array<string, string> $typesCache
|
||||
* @throws SpreadsheetException
|
||||
*/
|
||||
private function processRow(
|
||||
Entity $entity,
|
||||
Worksheet $sheet,
|
||||
int $rowNumber,
|
||||
array $fieldList,
|
||||
array $azRange,
|
||||
array &$typesCache
|
||||
): void {
|
||||
|
||||
foreach ($fieldList as $i => $name) {
|
||||
$col = $azRange[$i];
|
||||
|
||||
$coordinate = $col . $rowNumber;
|
||||
|
||||
$this->processCell(
|
||||
$entity,
|
||||
$sheet,
|
||||
$rowNumber,
|
||||
$coordinate,
|
||||
$name,
|
||||
$typesCache
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $typesCache
|
||||
* @throws SpreadsheetException
|
||||
*/
|
||||
private function processCell(
|
||||
Entity $entity,
|
||||
Worksheet $sheet,
|
||||
int $rowNumber,
|
||||
string $coordinate,
|
||||
string $name,
|
||||
array &$typesCache
|
||||
): void {
|
||||
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$type = $typesCache[$name] ?? null;
|
||||
|
||||
if (!$type) {
|
||||
$fieldData = $this->fieldHelper->getData($entityType, $name);
|
||||
$type = $fieldData ? $fieldData->getType() : 'base';
|
||||
$typesCache[$name] = $type;
|
||||
}
|
||||
|
||||
$preparator = $this->getPreparator($type);
|
||||
|
||||
$value = $preparator->prepare($entity, $name);
|
||||
|
||||
if ($type === FieldType::IMAGE) {
|
||||
$this->applyImage(
|
||||
$entity,
|
||||
$coordinate,
|
||||
$sheet,
|
||||
$rowNumber,
|
||||
$name
|
||||
);
|
||||
|
||||
$value = null;
|
||||
}
|
||||
|
||||
$value = $this->sanitizeCellValue($value);
|
||||
|
||||
if (is_string($value)) {
|
||||
$sheet->setCellValueExplicit($coordinate, $value, DataType::TYPE_STRING);
|
||||
} else if (is_int($value) || is_float($value)) {
|
||||
$sheet->setCellValueExplicit($coordinate, $value, DataType::TYPE_NUMERIC);
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
$sheet->setCellValueExplicit($coordinate, $value, DataType::TYPE_BOOL);
|
||||
} else if ($value instanceof Date) {
|
||||
$sheet->setCellValue(
|
||||
$coordinate,
|
||||
SharedDate::PHPToExcel(
|
||||
strtotime($value->toString())
|
||||
)
|
||||
);
|
||||
} else if ($value instanceof DateTimeValue) {
|
||||
$sheet->setCellValue(
|
||||
$coordinate,
|
||||
SharedDate::PHPToExcel(
|
||||
strtotime($value->toDateTime()->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT))
|
||||
)
|
||||
);
|
||||
} else if ($value instanceof Currency) {
|
||||
$sheet->setCellValue($coordinate, $value->getAmount());
|
||||
|
||||
$sheet->getStyle($coordinate)
|
||||
->getNumberFormat()
|
||||
->setFormatCode($this->getCurrencyFormatCode($value->getCode()));
|
||||
}
|
||||
|
||||
$this->applyLinks(
|
||||
$type,
|
||||
$entity,
|
||||
$sheet,
|
||||
$coordinate,
|
||||
$name
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SpreadsheetException
|
||||
*/
|
||||
private function applyImage(
|
||||
Entity $entity,
|
||||
string $coordinate,
|
||||
Worksheet $sheet,
|
||||
int $rowNumber,
|
||||
string $name
|
||||
): void {
|
||||
|
||||
$attachmentId = $entity->get($name . 'Id');
|
||||
|
||||
if (!$attachmentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var ?Attachment $attachment */
|
||||
$attachment = $this->entityManager->getEntityById(Attachment::ENTITY_TYPE, $attachmentId);
|
||||
|
||||
if (!$attachment) {
|
||||
return;
|
||||
}
|
||||
|
||||
$objDrawing = new Drawing();
|
||||
$filePath = $this->fileStorageManager->getLocalFilePath($attachment);
|
||||
|
||||
if (!$filePath || !file_exists($filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$objDrawing->setPath($filePath);
|
||||
$objDrawing->setHeight(100);
|
||||
$objDrawing->setCoordinates($coordinate);
|
||||
$objDrawing->setWorksheet($sheet);
|
||||
|
||||
$sheet->getRowDimension($rowNumber)->setRowHeight(100);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SpreadsheetException
|
||||
*/
|
||||
private function applyLinks(
|
||||
string $type,
|
||||
Entity $entity,
|
||||
Worksheet $sheet,
|
||||
string $coordinate,
|
||||
string $name
|
||||
): void {
|
||||
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$link = null;
|
||||
|
||||
$foreignLink = null;
|
||||
$foreignField = null;
|
||||
|
||||
if (strpos($name, '_')) {
|
||||
[$foreignLink, $foreignField] = explode('_', $name);
|
||||
}
|
||||
|
||||
$siteUrl = $this->applicationConfig->getSiteUrl();
|
||||
|
||||
if ($name === 'name') {
|
||||
if ($entity->hasId()) {
|
||||
$link = "$siteUrl/#$entityType/view/{$entity->getId()}";
|
||||
}
|
||||
} else if ($type === FieldType::URL) {
|
||||
$value = $entity->get($name);
|
||||
|
||||
if ($value) {
|
||||
$link = $this->sanitizeUrl($value);
|
||||
}
|
||||
} else if ($type === FieldType::LINK) {
|
||||
$idValue = $entity->get($name . 'Id');
|
||||
|
||||
if ($idValue && $foreignField) {
|
||||
if (!$foreignLink) {
|
||||
$foreignEntity =
|
||||
$this->metadata->get(['entityDefs', $entityType, 'links', $name, RelationParam::ENTITY]);
|
||||
} else {
|
||||
$foreignEntity1 = $this->metadata
|
||||
->get(['entityDefs', $entityType, 'links', $foreignLink, 'entity']);
|
||||
|
||||
$foreignEntity = $this->metadata
|
||||
->get(['entityDefs', $foreignEntity1, 'links', $foreignField, 'entity']);
|
||||
}
|
||||
|
||||
if ($foreignEntity) {
|
||||
$link = "$siteUrl/#$foreignEntity/view/$idValue";
|
||||
}
|
||||
}
|
||||
} else if ($type === FieldType::FILE) {
|
||||
$idValue = $entity->get($name . 'Id');
|
||||
|
||||
if ($idValue) {
|
||||
$link = "$siteUrl/?entryPoint=download&id=$idValue";
|
||||
}
|
||||
} else if ($type === FieldType::LINK_PARENT) {
|
||||
$idValue = $entity->get($name . 'Id');
|
||||
$typeValue = $entity->get($name . 'Type');
|
||||
|
||||
if ($idValue && $typeValue) {
|
||||
$link = "$siteUrl/#$typeValue/view/$idValue";
|
||||
}
|
||||
} else if ($type === FieldType::PHONE) {
|
||||
$value = $entity->get($name);
|
||||
|
||||
if ($value) {
|
||||
$link = "tel:$value";
|
||||
}
|
||||
} else if ($type === FieldType::EMAIL) {
|
||||
$value = $entity->get($name);
|
||||
|
||||
if ($value) {
|
||||
$link = "mailto:$value";
|
||||
}
|
||||
}
|
||||
|
||||
if (!$link) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cell = $sheet->getCell($coordinate);
|
||||
|
||||
$hyperLink = $cell->getHyperlink();
|
||||
|
||||
$hyperLink->setUrl($link);
|
||||
$hyperLink->setTooltip($link);
|
||||
}
|
||||
|
||||
private function getPreparator(string $type): CellValuePreparator
|
||||
{
|
||||
if (!array_key_exists($type, $this->preparatorsCache)) {
|
||||
$this->preparatorsCache[$type] = $this->cellValuePreparatorFactory->create(self::FORMAT, $type);
|
||||
}
|
||||
|
||||
return $this->preparatorsCache[$type];
|
||||
}
|
||||
|
||||
private function getCurrencyFormatCode(string $currency): string
|
||||
{
|
||||
$currencySymbol = $this->metadata->get(['app', 'currency', 'symbolMap', $currency], '');
|
||||
|
||||
$currencyFormat = $this->config->get('currencyFormat') ?? 2;
|
||||
|
||||
if ($currencyFormat == 3) {
|
||||
return '#,##0.00_-"' . $currencySymbol . '"';
|
||||
}
|
||||
|
||||
return '[$'.$currencySymbol.'-409]#,##0.00;-[$'.$currencySymbol.'-409]#,##0.00';
|
||||
}
|
||||
|
||||
private function getSheetNameFromParams(Params $params): string
|
||||
{
|
||||
$exportName =
|
||||
$params->getName() ??
|
||||
$this->language->translateLabel($params->getEntityType(), 'scopeNamesPlural');
|
||||
|
||||
$badCharList = ['*', ':', '/', '\\', '?', '[', ']'];
|
||||
|
||||
$sheetName = mb_substr($exportName, 0, 30, 'utf-8');
|
||||
$sheetName = str_replace($badCharList, ' ', $sheetName);
|
||||
|
||||
return str_replace('\'', '', $sheetName);
|
||||
}
|
||||
|
||||
private function sanitizeCellValue(mixed $value): mixed
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value === '') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (in_array($value[0], ['+', '-', '@', '='])) {
|
||||
return "'" . $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function sanitizeUrl(string $value): ?string
|
||||
{
|
||||
$link = $value;
|
||||
|
||||
if (!preg_match("/[a-z]+:\/\//", $link)) {
|
||||
$link = 'https://' . $link;
|
||||
}
|
||||
|
||||
if (filter_var($link, FILTER_VALIDATE_URL)) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
83
application/Espo/Tools/Export/Format/Xlsx/Processor.php
Normal file
83
application/Espo/Tools/Export/Format/Xlsx/Processor.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?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\Export\Format\Xlsx;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Tools\Export\Collection;
|
||||
use Espo\Tools\Export\Processor as ProcessorInterface;
|
||||
use Espo\Tools\Export\Processor\Params;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
class Processor implements ProcessorInterface
|
||||
{
|
||||
private const PARAM_LITE = 'lite';
|
||||
|
||||
public function __construct(
|
||||
private PhpSpreadsheetProcessor $phpSpreadsheetProcessor,
|
||||
private OpenSpoutProcessor $openSpoutProcessor,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function process(Params $params, Collection $collection): StreamInterface
|
||||
{
|
||||
return $params->getParam(self::PARAM_LITE) ?
|
||||
$this->processOpenSpout($params, $collection) :
|
||||
$this->processPhpSpreadsheet($params, $collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
private function processPhpSpreadsheet(Params $params, Collection $collection): StreamInterface
|
||||
{
|
||||
try {
|
||||
return $this->phpSpreadsheetProcessor->process($params, $collection);
|
||||
} catch (SpreadsheetException|WriterException $e) {
|
||||
throw new Error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
private function processOpenSpout(Params $params, Collection $collection): StreamInterface
|
||||
{
|
||||
try {
|
||||
return $this->openSpoutProcessor->process($params, $collection);
|
||||
} catch (\Throwable $e) {
|
||||
throw new Error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
143
application/Espo/Tools/Export/Jobs/Process.php
Normal file
143
application/Espo/Tools/Export/Jobs/Process.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?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\Export\Jobs;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Job\Job;
|
||||
use Espo\Core\Job\Job\Data as JobData;
|
||||
use Espo\Tools\Export\Factory;
|
||||
use Espo\Tools\Export\Result;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Entities\Export;
|
||||
use Espo\Entities\Notification;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class Process implements Job
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private Factory $factory,
|
||||
private Language $language,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function run(JobData $data): void
|
||||
{
|
||||
$id = $data->getTargetId();
|
||||
|
||||
if ($id === null) {
|
||||
throw new Error("ID not passed to the mass action job.");
|
||||
}
|
||||
|
||||
/** @var Export|null $entity */
|
||||
$entity = $this->entityManager->getEntityById(Export::ENTITY_TYPE, $id);
|
||||
|
||||
if ($entity === null) {
|
||||
throw new Error("Export '$id' not found.");
|
||||
}
|
||||
|
||||
/** @var User|null $user */
|
||||
$user = $this->entityManager->getEntityById(User::ENTITY_TYPE, $entity->getCreatedBy()->getId());
|
||||
|
||||
if (!$user) {
|
||||
throw new Error("Export entity '$id', user not found.");
|
||||
}
|
||||
|
||||
try {
|
||||
$export = $this->factory->createForUser($user);
|
||||
|
||||
$this->setRunning($entity);
|
||||
|
||||
$result = $export
|
||||
->setParams($entity->getParams())
|
||||
->run();
|
||||
} catch (Throwable $e) {
|
||||
$this->setFailed($entity);
|
||||
|
||||
throw new Error("Export job error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->setSuccess($entity, $result);
|
||||
|
||||
$this->entityManager->refreshEntity($entity);
|
||||
|
||||
if ($entity->notifyOnFinish()) {
|
||||
$this->notifyFinish($entity);
|
||||
}
|
||||
}
|
||||
|
||||
private function notifyFinish(Export $entity): void
|
||||
{
|
||||
$notification = $this->entityManager->getRDBRepositoryByClass(Notification::class)->getNew();
|
||||
|
||||
$url = '?entryPoint=download&id=' . $entity->getAttachmentId();
|
||||
|
||||
$message = str_replace(
|
||||
'{url}',
|
||||
$url,
|
||||
$this->language->translateLabel('exportProcessed', 'messages', 'Export')
|
||||
);
|
||||
|
||||
$notification
|
||||
->setType(Notification::TYPE_MESSAGE)
|
||||
->setMessage($message)
|
||||
->setUserId($entity->getCreatedBy()->getId());
|
||||
|
||||
$this->entityManager->saveEntity($notification);
|
||||
}
|
||||
|
||||
private function setFailed(Export $entity): void
|
||||
{
|
||||
$entity->setStatus(Export::STATUS_FAILED);
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
}
|
||||
|
||||
private function setRunning(Export $entity): void
|
||||
{
|
||||
$entity->setStatus(Export::STATUS_RUNNING);
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
}
|
||||
|
||||
private function setSuccess(Export $entity, Result $result): void
|
||||
{
|
||||
$entity
|
||||
->setStatus(Export::STATUS_SUCCESS)
|
||||
->setAttachmentId($result->getAttachmentId());
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
}
|
||||
}
|
||||
324
application/Espo/Tools/Export/Params.php
Normal file
324
application/Espo/Tools/Export/Params.php
Normal file
@@ -0,0 +1,324 @@
|
||||
<?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\Export;
|
||||
|
||||
use Espo\Core\Select\SearchParams;
|
||||
use Espo\Core\Select\Where\Item as WhereItem;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class Params
|
||||
{
|
||||
private string $entityType;
|
||||
/** @var ?string[] */
|
||||
private $attributeList = null;
|
||||
/** @var ?string[] */
|
||||
private $fieldList = null;
|
||||
private ?string $fileName = null;
|
||||
private ?string $format = null;
|
||||
private ?string $name = null;
|
||||
/** @var array<string, mixed> */
|
||||
private array $params = [];
|
||||
private ?SearchParams $searchParams = null;
|
||||
private bool $applyAccessControl = true;
|
||||
|
||||
public function __construct(string $entityType)
|
||||
{
|
||||
$this->entityType = $entityType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function fromRaw(array $params): self
|
||||
{
|
||||
$entityType = $params['entityType'] ?? null;
|
||||
|
||||
if (!$entityType) {
|
||||
throw new RuntimeException("No entityType.");
|
||||
}
|
||||
|
||||
$obj = new self($entityType);
|
||||
|
||||
$obj->name = $params['name'] ?? $params['exportName'] ?? null;
|
||||
|
||||
$obj->fileName = $params['fileName'] ?? null;
|
||||
$obj->format = $params['format'] ?? null;
|
||||
$obj->attributeList = $params['attributeList'] ?? null;
|
||||
$obj->fieldList = $params['fieldList'] ?? null;
|
||||
|
||||
$where = $params['where'] ?? null;
|
||||
$ids = $params['ids'] ?? null;
|
||||
|
||||
$searchParams = $params['searchParams'] ?? null;
|
||||
|
||||
if ($where && !is_array($where)) {
|
||||
throw new RuntimeException("Bad 'where'.");
|
||||
}
|
||||
|
||||
if ($searchParams && !is_array($searchParams)) {
|
||||
throw new RuntimeException("Bad 'searchParams'.");
|
||||
}
|
||||
|
||||
if ($where && $searchParams) {
|
||||
$searchParams['where'] = $where;
|
||||
}
|
||||
|
||||
if ($where && !$searchParams) {
|
||||
$searchParams = [
|
||||
'where' => $where,
|
||||
];
|
||||
}
|
||||
|
||||
if ($searchParams) {
|
||||
if ($ids) {
|
||||
throw new RuntimeException("Can't combine 'ids' and search params.");
|
||||
}
|
||||
} else if ($ids) {
|
||||
if (!is_array($ids)) {
|
||||
throw new RuntimeException("Bad 'ids'.");
|
||||
}
|
||||
|
||||
$obj->searchParams = SearchParams
|
||||
::create()
|
||||
->withWhere(
|
||||
WhereItem::fromRaw([
|
||||
'type' => 'equals',
|
||||
'attribute' => 'id',
|
||||
'value' => $ids,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if ($searchParams) {
|
||||
$actualSearchParams = $searchParams;
|
||||
|
||||
unset($actualSearchParams['select']);
|
||||
|
||||
$obj->searchParams = SearchParams::fromRaw($actualSearchParams);
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function create(string $entityType): self
|
||||
{
|
||||
return new self($entityType);
|
||||
}
|
||||
|
||||
public function withFormat(?string $format): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->format = $format;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function withFileName(?string $fileName): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->fileName = $fileName;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withName(?string $name): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->name = $name;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withSearchParams(?SearchParams $searchParams): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->searchParams = $searchParams;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withParam(string $name, mixed $value): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->params[$name] = $value;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string[] $fieldList
|
||||
*/
|
||||
public function withFieldList(?array $fieldList): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->fieldList = $fieldList;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string[] $attributeList
|
||||
*/
|
||||
public function withAttributeList(?array $attributeList): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->attributeList = $attributeList;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withAccessControl(bool $applyAccessControl = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->applyAccessControl = $applyAccessControl;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get search params.
|
||||
*/
|
||||
public function getSearchParams(): SearchParams
|
||||
{
|
||||
$searchParams = $this->searchParams ?? SearchParams::create();
|
||||
|
||||
if ($searchParams->getSelect() !== null) {
|
||||
return $searchParams;
|
||||
}
|
||||
|
||||
if ($this->getAttributeList()) {
|
||||
$searchParams = $searchParams->withSelect($this->getAttributeList());
|
||||
} else {
|
||||
$searchParams = $searchParams->withSelect(['*']);
|
||||
}
|
||||
|
||||
return $searchParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a target entity type.
|
||||
*/
|
||||
public function getEntityType(): string
|
||||
{
|
||||
return $this->entityType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a filename for a result export file.
|
||||
*/
|
||||
public function getFileName(): ?string
|
||||
{
|
||||
return $this->fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a name.
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a format.
|
||||
*/
|
||||
public function getFormat(): ?string
|
||||
{
|
||||
return $this->format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attributes to be exported.
|
||||
*
|
||||
* @return ?string[]
|
||||
*/
|
||||
public function getAttributeList(): ?array
|
||||
{
|
||||
return $this->attributeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fields to be exported.
|
||||
*
|
||||
* @return ?string[]
|
||||
*/
|
||||
public function getFieldList(): ?array
|
||||
{
|
||||
return $this->fieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parameter list.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getParamList(): array
|
||||
{
|
||||
return array_keys($this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parameter value.
|
||||
*/
|
||||
public function getParam(string $name): mixed
|
||||
{
|
||||
return $this->params[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has a parameter.
|
||||
*/
|
||||
public function hasParam(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether all fields should be exported.
|
||||
*/
|
||||
public function allFields(): bool
|
||||
{
|
||||
return $this->fieldList === null && $this->attributeList === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to apply access control.
|
||||
*/
|
||||
public function applyAccessControl(): bool
|
||||
{
|
||||
return $this->applyAccessControl;
|
||||
}
|
||||
}
|
||||
39
application/Espo/Tools/Export/Processor.php
Normal file
39
application/Espo/Tools/Export/Processor.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?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\Export;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
use Espo\Tools\Export\Processor\Params;
|
||||
|
||||
interface Processor
|
||||
{
|
||||
public function process(Params $params, Collection $collection): StreamInterface;
|
||||
}
|
||||
161
application/Espo/Tools/Export/Processor/Params.php
Normal file
161
application/Espo/Tools/Export/Processor/Params.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?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\Export\Processor;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class Params
|
||||
{
|
||||
private string $fileName;
|
||||
/** @var string[] */
|
||||
private array $attributeList;
|
||||
/** @var ?string[] */
|
||||
private ?array $fieldList = null;
|
||||
private ?string $name = null;
|
||||
private ?string $entityType = null;
|
||||
/** @var array<string, mixed> */
|
||||
private array $params = [];
|
||||
|
||||
/**
|
||||
* @param string[] $attributeList
|
||||
* @param ?string[] $fieldList
|
||||
*/
|
||||
public function __construct(string $fileName, array $attributeList, ?array $fieldList)
|
||||
{
|
||||
$this->fileName = $fileName;
|
||||
$this->attributeList = $attributeList;
|
||||
$this->fieldList = $fieldList;
|
||||
}
|
||||
|
||||
public function withEntityType(string $entityType): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->entityType = $entityType;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withName(?string $name): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->name = $name;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string[] $fieldList
|
||||
*/
|
||||
public function withFieldList(?array $fieldList): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->fieldList = $fieldList;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $attributeList
|
||||
*/
|
||||
public function withAttributeList(array $attributeList): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->attributeList = $attributeList;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withParam(string $name, mixed $value): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->params[$name] = $value;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* An export file name.
|
||||
*/
|
||||
public function getFileName(): string
|
||||
{
|
||||
return $this->fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attributes to export.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributeList(): array
|
||||
{
|
||||
return $this->attributeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fields to export.
|
||||
*
|
||||
* @return ?string[]
|
||||
*/
|
||||
public function getFieldList(): ?array
|
||||
{
|
||||
return $this->fieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* An export name.
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* An entity type.
|
||||
*/
|
||||
public function getEntityType(): string
|
||||
{
|
||||
if ($this->entityType === null) {
|
||||
throw new RuntimeException("No entity-type.");
|
||||
}
|
||||
|
||||
return $this->entityType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parameter value.
|
||||
*/
|
||||
public function getParam(string $name): mixed
|
||||
{
|
||||
return $this->params[$name] ?? null;
|
||||
}
|
||||
}
|
||||
59
application/Espo/Tools/Export/ProcessorFactory.php
Normal file
59
application/Espo/Tools/Export/ProcessorFactory.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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\Export;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
use LogicException;
|
||||
|
||||
class ProcessorFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function create(string $format): Processor
|
||||
{
|
||||
if (!in_array($format, $this->metadata->get(['app', 'export', 'formatList']))) {
|
||||
throw new LogicException("Not supported export format '{$format}'.");
|
||||
}
|
||||
|
||||
/** @var ?class-string<Processor> $className */
|
||||
$className = $this->metadata->get(['app', 'export', 'formatDefs', $format, 'processorClassName']);
|
||||
|
||||
if (!$className) {
|
||||
throw new LogicException("No implementation for format '{$format}'.");
|
||||
}
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
}
|
||||
35
application/Espo/Tools/Export/ProcessorParamsHandler.php
Normal file
35
application/Espo/Tools/Export/ProcessorParamsHandler.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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\Export;
|
||||
|
||||
interface ProcessorParamsHandler
|
||||
{
|
||||
public function handle(Params $params, Processor\Params $processorParams): Processor\Params;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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\Export;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
use LogicException;
|
||||
|
||||
class ProcessorParamsHandlerFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function create(string $format): ProcessorParamsHandler
|
||||
{
|
||||
$className = $this->getClassName($format);
|
||||
|
||||
if (!$className) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
|
||||
public function isCreatable(string $format): bool
|
||||
{
|
||||
return (bool) $this->getClassName($format);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<ProcessorParamsHandler>
|
||||
*/
|
||||
private function getClassName(string $format): ?string
|
||||
{
|
||||
return $this->metadata->get(['app', 'export', 'formatDefs', $format, 'processorParamsHandler']);
|
||||
}
|
||||
}
|
||||
48
application/Espo/Tools/Export/Result.php
Normal file
48
application/Espo/Tools/Export/Result.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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\Export;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class Result
|
||||
{
|
||||
private string $attachmentId;
|
||||
|
||||
public function __construct(string $attachmentId)
|
||||
{
|
||||
$this->attachmentId = $attachmentId;
|
||||
}
|
||||
|
||||
public function getAttachmentId(): string
|
||||
{
|
||||
return $this->attachmentId;
|
||||
}
|
||||
}
|
||||
165
application/Espo/Tools/Export/Service.php
Normal file
165
application/Espo/Tools/Export/Service.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?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\Export;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\ForbiddenSilent;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\NotFoundSilent;
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Job\JobSchedulerFactory;
|
||||
use Espo\Core\Job\Job\Data as JobData;
|
||||
use Espo\Tools\Export\Jobs\Process;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Entities\Export as ExportEntity;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class Service
|
||||
{
|
||||
public function __construct(
|
||||
private Factory $factory,
|
||||
private Config $config,
|
||||
private Acl $acl,
|
||||
private User $user,
|
||||
private Metadata $metadata,
|
||||
private EntityManager $entityManager,
|
||||
private JobSchedulerFactory $jobSchedulerFactory
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function process(Params $params, ServiceParams $serviceParams): ServiceResult
|
||||
{
|
||||
if ($this->config->get('exportDisabled') && !$this->user->isAdmin()) {
|
||||
throw new ForbiddenSilent("Export disabled for non-admin users.");
|
||||
}
|
||||
|
||||
$entityType = $params->getEntityType();
|
||||
|
||||
if ($this->acl->getPermissionLevel(Acl\Permission::EXPORT) !== Table::LEVEL_YES) {
|
||||
throw new ForbiddenSilent("No 'export' permission.");
|
||||
}
|
||||
|
||||
if (!$this->acl->check($entityType, Table::ACTION_READ)) {
|
||||
throw new ForbiddenSilent("No 'read' access.");
|
||||
}
|
||||
|
||||
if ($this->metadata->get(['recordDefs', $entityType, 'exportDisabled'])) {
|
||||
throw new ForbiddenSilent("Export disabled for '$entityType'.");
|
||||
}
|
||||
|
||||
if ($serviceParams->isIdle()) {
|
||||
if ($this->user->isPortal()) {
|
||||
throw new ForbiddenSilent("Idle export is not allowed for portal users.");
|
||||
}
|
||||
|
||||
return $this->schedule($params);
|
||||
}
|
||||
|
||||
$export = $this->factory->create();
|
||||
|
||||
$result = $export
|
||||
->setParams($params)
|
||||
->run();
|
||||
|
||||
return ServiceResult::createWithResult($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function getStatusData(string $id): stdClass
|
||||
{
|
||||
/** @var ?ExportEntity $entity */
|
||||
$entity = $this->entityManager->getEntityById(ExportEntity::ENTITY_TYPE, $id);
|
||||
|
||||
if (!$entity) {
|
||||
throw new NotFoundSilent();
|
||||
}
|
||||
|
||||
if ($entity->getCreatedBy()->getId() !== $this->user->getId()) {
|
||||
throw new ForbiddenSilent();
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'status' => $entity->getStatus(),
|
||||
'attachmentId' => $entity->getAttachmentId(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function subscribeToNotificationOnSuccess(string $id): void
|
||||
{
|
||||
/** @var ?ExportEntity $entity */
|
||||
$entity = $this->entityManager->getEntityById(ExportEntity::ENTITY_TYPE, $id);
|
||||
|
||||
if (!$entity) {
|
||||
throw new NotFoundSilent();
|
||||
}
|
||||
|
||||
if ($entity->getCreatedBy()->getId() !== $this->user->getId()) {
|
||||
throw new ForbiddenSilent();
|
||||
}
|
||||
|
||||
$entity->setNotifyOnFinish();
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
}
|
||||
|
||||
private function schedule(Params $params): ServiceResult
|
||||
{
|
||||
$entity = $this->entityManager->createEntity(ExportEntity::ENTITY_TYPE, [
|
||||
// Additional encoding to handle null-character issue in PostgreSQL.
|
||||
'params' => base64_encode(serialize($params)),
|
||||
]);
|
||||
|
||||
$this->jobSchedulerFactory
|
||||
->create()
|
||||
->setClassName(Process::class)
|
||||
->setData(
|
||||
JobData::create()
|
||||
->withTargetId($entity->getId())
|
||||
->withTargetType($entity->getEntityType())
|
||||
)
|
||||
->schedule();
|
||||
|
||||
return ServiceResult::createWithId($entity->getId());
|
||||
}
|
||||
}
|
||||
54
application/Espo/Tools/Export/ServiceParams.php
Normal file
54
application/Espo/Tools/Export/ServiceParams.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?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\Export;
|
||||
|
||||
class ServiceParams
|
||||
{
|
||||
private bool $isIdle = false;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function isIdle(): bool
|
||||
{
|
||||
return $this->isIdle;
|
||||
}
|
||||
|
||||
public function withIsIdle(bool $isIdle = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$obj->isIdle = $isIdle;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
72
application/Espo/Tools/Export/ServiceResult.php
Normal file
72
application/Espo/Tools/Export/ServiceResult.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?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\Export;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class ServiceResult
|
||||
{
|
||||
private ?Result $result = null;
|
||||
private ?string $id = null;
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
public function hasResult(): bool
|
||||
{
|
||||
return $this->result !== null;
|
||||
}
|
||||
|
||||
public function getId(): ?string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getResult(): ?Result
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
public static function createWithId(string $id): self
|
||||
{
|
||||
$obj = new self;
|
||||
$obj->id = $id;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function createWithResult(Result $result): self
|
||||
{
|
||||
$obj = new self;
|
||||
$obj->result = $result;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user