mirror of
https://github.com/espocrm/espocrm.git
synced 2026-06-28 06:56:05 +00:00
export refactoring
This commit is contained in:
107
application/Espo/Controllers/Export.php
Normal file
107
application/Espo/Controllers/Export.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\{
|
||||
Api\Request,
|
||||
Exceptions\BadRequest,
|
||||
};
|
||||
|
||||
use Espo\Tools\Export\{
|
||||
Service,
|
||||
Params,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
|
||||
class Export
|
||||
{
|
||||
private $service;
|
||||
|
||||
public function __construct(Service $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
public function postActionProcess(Request $request): StdClass
|
||||
{
|
||||
$params = $this->fetchRawParamsFromRequest($request);
|
||||
|
||||
$result = $this->service->process($params);
|
||||
|
||||
return (object) [
|
||||
'id' => $result->getAttachmentId(),
|
||||
];
|
||||
}
|
||||
|
||||
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 ?? 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;
|
||||
}
|
||||
|
||||
return Params::fromRaw($params);
|
||||
}
|
||||
}
|
||||
@@ -85,9 +85,4 @@ class Notification extends RecordBase
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function beforeExport(): void
|
||||
{
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,53 +249,6 @@ class RecordBase extends Base implements Di\EntityManagerAware
|
||||
return $this->searchParamsFetcher->fetch($request);
|
||||
}
|
||||
|
||||
public function postActionExport(Request $request): StdClass
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if ($this->config->get('exportDisabled') && !$this->user->isAdmin()) {
|
||||
throw new Forbidden("Export is disabled.");
|
||||
}
|
||||
|
||||
$ids = isset($data->ids) ?
|
||||
$data->ids : null;
|
||||
|
||||
$where = isset($data->where) ?
|
||||
json_decode(json_encode($data->where), true) : null;
|
||||
|
||||
$byWhere = isset($data->byWhere) ?
|
||||
$data->byWhere : false;
|
||||
|
||||
$selectData = isset($data->selectData) ?
|
||||
json_decode(json_encode($data->selectData), true) : null;
|
||||
|
||||
$actionParams = [];
|
||||
|
||||
if ($byWhere) {
|
||||
$actionParams['selectData'] = $selectData;
|
||||
$actionParams['where'] = $where;
|
||||
}
|
||||
else {
|
||||
$actionParams['ids'] = $ids;
|
||||
}
|
||||
|
||||
if (isset($data->attributeList)) {
|
||||
$actionParams['attributeList'] = $data->attributeList;
|
||||
}
|
||||
|
||||
if (isset($data->fieldList)) {
|
||||
$actionParams['fieldList'] = $data->fieldList;
|
||||
}
|
||||
|
||||
if (isset($data->format)) {
|
||||
$actionParams['format'] = $data->format;
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'id' => $this->getRecordService()->export($actionParams),
|
||||
];
|
||||
}
|
||||
|
||||
public function postActionGetDuplicateAttributes(Request $request): StdClass
|
||||
{
|
||||
$id = $request->getParsedBody()->id ?? null;
|
||||
|
||||
@@ -63,10 +63,6 @@ use Espo\Core\{
|
||||
FieldProcessing\LoaderParams as FieldLoaderParams,
|
||||
};
|
||||
|
||||
use Espo\Tools\{
|
||||
Export\Export as ExportTool,
|
||||
};
|
||||
|
||||
use Espo\Core\Di;
|
||||
|
||||
use StdClass;
|
||||
@@ -1657,31 +1653,6 @@ class Service implements Crud,
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an export.
|
||||
*
|
||||
* @param Raw export parameters.
|
||||
* @return An attachment ID.
|
||||
*/
|
||||
public function export(array $params): string
|
||||
{
|
||||
if ($this->acl->getPermissionLevel('exportPermission') !== AclTable::LEVEL_YES) {
|
||||
throw new ForbiddenSilent("No 'export' permission.");
|
||||
}
|
||||
|
||||
if (!$this->acl->check($this->entityType, AclTable::ACTION_READ)) {
|
||||
throw new ForbiddenSilent("No 'read' access.");
|
||||
}
|
||||
|
||||
$export = $this->injectableFactory->create(ExportTool::class);
|
||||
|
||||
return $export
|
||||
->setRecordService($this)
|
||||
->setParams($params)
|
||||
->setEntityType($this->entityType)
|
||||
->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare an entity for output. Clears not allowed attributes.
|
||||
*
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"fileExtension": "xlsx"
|
||||
}
|
||||
},
|
||||
"exportFormatClassNameMap": {
|
||||
"csv": "Espo\\Tools\\Export\\Formats\\Csv",
|
||||
"xlsx": "Espo\\Tools\\Export\\Formats\\Xlsx"
|
||||
"processorClassNameMap": {
|
||||
"csv": "Espo\\Tools\\Export\\Processors\\Csv",
|
||||
"xlsx": "Espo\\Tools\\Export\\Processors\\Xlsx"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"exportDisabled": true
|
||||
}
|
||||
@@ -252,6 +252,14 @@
|
||||
"action": "process"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route": "/Export",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"controller": "Export",
|
||||
"action": "process"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route": "/Kanban/:entityType",
|
||||
"method": "get",
|
||||
|
||||
@@ -50,6 +50,7 @@ use Espo\Core\Di;
|
||||
|
||||
use Espo\Tools\{
|
||||
Export\Export as ExportTool,
|
||||
Export\Params as ExportParams,
|
||||
};
|
||||
|
||||
class Record extends RecordService implements
|
||||
@@ -259,13 +260,13 @@ class Record extends RecordService implements
|
||||
throw new ForbiddenSilent("No 'read' access.");
|
||||
}
|
||||
|
||||
$params['entityType'] = $this->entityType;
|
||||
|
||||
$export = $this->injectableFactory->create(ExportTool::class);
|
||||
|
||||
return $export
|
||||
->setRecordService($this)
|
||||
->setParams($params)
|
||||
->setParams(ExportParams::fromRaw($params))
|
||||
->setCollection($collection)
|
||||
->setEntityType($this->entityType)
|
||||
->run();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,56 +30,56 @@
|
||||
namespace Espo\Tools\Export;
|
||||
|
||||
use Espo\Core\{
|
||||
Exceptions\BadRequest,
|
||||
Exceptions\Error,
|
||||
Utils\Json,
|
||||
Di,
|
||||
Select\SearchParams,
|
||||
Select\SelectBuilderFactory,
|
||||
Acl,
|
||||
Acl\Table,
|
||||
Record\ServiceContainer,
|
||||
Utils\Metadata,
|
||||
};
|
||||
|
||||
use Espo\{
|
||||
ORM\Entity,
|
||||
ORM\Collection,
|
||||
Services\Record,
|
||||
ORM\EntityManager,
|
||||
};
|
||||
|
||||
class Export implements
|
||||
|
||||
Di\MetadataAware,
|
||||
Di\EntityManagerAware,
|
||||
Di\SelectBuilderFactoryAware,
|
||||
Di\AclAware,
|
||||
Di\InjectableFactoryAware
|
||||
class Export
|
||||
{
|
||||
use Di\MetadataSetter;
|
||||
use Di\EntityManagerSetter;
|
||||
use Di\SelectBuilderFactorySetter;
|
||||
use Di\AclSetter;
|
||||
use Di\InjectableFactorySetter;
|
||||
private $params;
|
||||
|
||||
protected $entityType;
|
||||
private $collection = null;
|
||||
|
||||
protected $recordService;
|
||||
private $processorFactory;
|
||||
|
||||
protected $collection = null;
|
||||
private $selectBuilderFactory;
|
||||
|
||||
protected $params = [];
|
||||
private $serviceContainer;
|
||||
|
||||
public function setRecordService(Record $recordService): self
|
||||
{
|
||||
$this->recordService = $recordService;
|
||||
private $acl;
|
||||
|
||||
return $this;
|
||||
private $entityManager;
|
||||
|
||||
private $metadata;
|
||||
|
||||
public function __construct(
|
||||
ProcessorFactory $processorFactory,
|
||||
SelectBuilderFactory $selectBuilderFactor,
|
||||
ServiceContainer $serviceContainer,
|
||||
Acl $acl,
|
||||
EntityManager $entityManager,
|
||||
Metadata $metadata
|
||||
) {
|
||||
$this->processorFactory = $processorFactory;
|
||||
$this->selectBuilderFactory = $selectBuilderFactor;
|
||||
$this->serviceContainer = $serviceContainer;
|
||||
$this->acl = $acl;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
public function setEntityType(string $entityType): self
|
||||
{
|
||||
$this->entityType = $entityType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setParams(array $params): self
|
||||
public function setParams(Params $params): self
|
||||
{
|
||||
$this->params = $params;
|
||||
|
||||
@@ -95,56 +95,37 @@ class Export implements
|
||||
|
||||
/**
|
||||
* Run export.
|
||||
*
|
||||
* @return An ID of a generated attachment.
|
||||
*/
|
||||
public function run(): string
|
||||
public function run(): Result
|
||||
{
|
||||
if (!$this->params) {
|
||||
throw new Error("No params set.");
|
||||
}
|
||||
|
||||
$params = $this->params;
|
||||
|
||||
if (!$this->entityType) {
|
||||
throw new Error("Entity type is not specified.");
|
||||
}
|
||||
$entityType = $params->getEntityType();
|
||||
|
||||
if (array_key_exists('format', $params)) {
|
||||
$format = $params['format'];
|
||||
}
|
||||
else {
|
||||
$format = 'csv';
|
||||
}
|
||||
$format = $params->getFormat() ?? 'csv';
|
||||
|
||||
if (!in_array($format, $this->metadata->get(['app', 'export', 'formatList']))) {
|
||||
throw new Error('Not supported export format.');
|
||||
}
|
||||
$processor = $this->processorFactory->create($format);
|
||||
|
||||
$className = $this->metadata->get(['app', 'export', 'exportFormatClassNameMap', $format]);
|
||||
$exportAllFields = $params->getFieldList() === null;
|
||||
|
||||
if (empty($className)) {
|
||||
throw new Error();
|
||||
}
|
||||
$collection = $this->getCollection($params);
|
||||
|
||||
$exportObj = $this->injectableFactory->create($className);
|
||||
$attributeListToSkip = $this->acl->getScopeForbiddenAttributeList($entityType, Table::ACTION_READ);
|
||||
|
||||
$exportAllFields = !array_key_exists('fieldList', $params);
|
||||
|
||||
$collection = $this->getCollection();
|
||||
|
||||
$attributeListToSkip = [
|
||||
'deleted',
|
||||
];
|
||||
|
||||
foreach ($this->acl->getScopeForbiddenAttributeList($this->entityType, 'read') as $attribute) {
|
||||
$attributeListToSkip[] = $attribute;
|
||||
}
|
||||
$attributeListToSkip[] = 'deleted';
|
||||
|
||||
$attributeList = null;
|
||||
|
||||
if (array_key_exists('attributeList', $params)) {
|
||||
if ($params->getAttributeList() !== null) {
|
||||
$attributeList = [];
|
||||
|
||||
$seed = $this->entityManager->getEntity($this->entityType);
|
||||
$seed = $this->entityManager->getEntity($entityType);
|
||||
|
||||
foreach ($params['attributeList'] as $attribute) {
|
||||
foreach ($params->getAttributeList() as $attribute) {
|
||||
if (in_array($attribute, $attributeListToSkip)) {
|
||||
continue;
|
||||
}
|
||||
@@ -157,34 +138,41 @@ class Export implements
|
||||
}
|
||||
}
|
||||
|
||||
$entityDefs = $this->entityManager
|
||||
->getDefs()
|
||||
->getEntity($entityType);
|
||||
|
||||
if ($exportAllFields) {
|
||||
$fieldDefs = $this->metadata->get(['entityDefs', $this->entityType, 'fields'], []);
|
||||
$fieldList = array_keys($fieldDefs);
|
||||
$fieldList = $entityDefs->getFieldNameList();
|
||||
|
||||
array_unshift($fieldList, 'id');
|
||||
}
|
||||
else {
|
||||
$fieldList = $params['fieldList'];
|
||||
$fieldList = $params->getFieldList();
|
||||
}
|
||||
|
||||
foreach ($fieldList as $i => $field) {
|
||||
if ($this->metadata->get(['entityDefs', $this->entityType, 'fields', $field, 'exportDisabled'])) {
|
||||
if ($field === 'id') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($entityDefs->getField($field)->getParam('exportDisabled')) {
|
||||
unset($fieldList[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
$fieldList = array_values($fieldList);
|
||||
|
||||
if (method_exists($exportObj, 'filterFieldList')) {
|
||||
$fieldList = $exportObj->filterFieldList($this->entityType, $fieldList, $exportAllFields);
|
||||
if (method_exists($processor, 'filterFieldList')) {
|
||||
$fieldList = $processor->filterFieldList($entityType, $fieldList, $exportAllFields);
|
||||
}
|
||||
|
||||
if (is_null($attributeList)) {
|
||||
$attributeList = [];
|
||||
|
||||
$seed = $this->entityManager->getEntity($this->entityType);
|
||||
$seed = $this->entityManager->getEntity($entityType);
|
||||
|
||||
foreach ($seed->getAttributeList() as $attribute) {
|
||||
foreach ($entityDefs->getAttributeNameList() as $attribute) {
|
||||
if (in_array($attribute, $attributeListToSkip)) {
|
||||
continue;
|
||||
}
|
||||
@@ -197,19 +185,19 @@ class Export implements
|
||||
}
|
||||
}
|
||||
|
||||
if (method_exists($exportObj, 'addAdditionalAttributes')) {
|
||||
$exportObj->addAdditionalAttributes($this->entityType, $attributeList, $fieldList);
|
||||
if (method_exists($processor, 'addAdditionalAttributes')) {
|
||||
$processor->addAdditionalAttributes($entityType, $attributeList, $fieldList);
|
||||
}
|
||||
|
||||
$fp = fopen('php://temp', 'w');
|
||||
|
||||
foreach ($collection as $entity) {
|
||||
if ($this->recordService) {
|
||||
$this->recordService->loadAdditionalFieldsForExport($entity);
|
||||
}
|
||||
$recordService = $this->serviceContainer->get($entityType);
|
||||
|
||||
if (method_exists($exportObj, 'loadAdditionalFields')) {
|
||||
$exportObj->loadAdditionalFields($entity, $fieldList);
|
||||
foreach ($collection as $entity) {
|
||||
$recordService->loadAdditionalFieldsForExport($entity);
|
||||
|
||||
if (method_exists($processor, 'loadAdditionalFields')) {
|
||||
$processor->loadAdditionalFields($entity, $fieldList);
|
||||
}
|
||||
|
||||
$row = [];
|
||||
@@ -234,30 +222,27 @@ class Export implements
|
||||
$mimeType = $this->metadata->get(['app', 'export', 'formatDefs', $format, 'mimeType']);
|
||||
$fileExtension = $this->metadata->get(['app', 'export', 'formatDefs', $format, 'fileExtension']);
|
||||
|
||||
$fileName = null;
|
||||
$fileName = $params->getFileName();
|
||||
|
||||
if (!empty($params['fileName'])) {
|
||||
$fileName = trim($params['fileName']);
|
||||
if ($fileName !== null) {
|
||||
$fileName = trim($fileName);
|
||||
}
|
||||
|
||||
if (!empty($fileName)) {
|
||||
if ($fileName) {
|
||||
$fileName = $fileName . '.' . $fileExtension;
|
||||
} else {
|
||||
$fileName = "Export_{$this->entityType}." . $fileExtension;
|
||||
}
|
||||
else {
|
||||
$fileName = "Export_{$entityType}." . $fileExtension;
|
||||
}
|
||||
|
||||
$exportParams = [
|
||||
'attributeList' => $attributeList,
|
||||
'fileName ' => $fileName
|
||||
];
|
||||
$processorParams =
|
||||
(new ProcessorParams($fileName, $attributeList, $fieldList))
|
||||
->withName($params->getName())
|
||||
->withEntityType($params->getEntityType());
|
||||
|
||||
$exportParams['fieldList'] = $fieldList;
|
||||
$processorData = new ProcessorData($fp);
|
||||
|
||||
if (array_key_exists('exportName', $params)) {
|
||||
$exportParams['exportName'] = $params['exportName'];
|
||||
}
|
||||
|
||||
$contents = $exportObj->process($this->entityType, $exportParams, null, $fp);
|
||||
$stream = $processor->process($processorParams, $processorData);
|
||||
|
||||
fclose($fp);
|
||||
|
||||
@@ -266,11 +251,11 @@ class Export implements
|
||||
$attachment->set('name', $fileName);
|
||||
$attachment->set('role', 'Export File');
|
||||
$attachment->set('type', $mimeType);
|
||||
$attachment->set('contents', $contents);
|
||||
$attachment->set('contents', $stream->getContents());
|
||||
|
||||
$this->entityManager->saveEntity($attachment);
|
||||
|
||||
return $attachment->getId();
|
||||
return new Result($attachment->getId());
|
||||
}
|
||||
|
||||
protected function getAttributeFromEntity(Entity $entity, string $attribute)
|
||||
@@ -321,7 +306,7 @@ class Export implements
|
||||
{
|
||||
$defs = $this->entityManager->getDefs();
|
||||
|
||||
$entityDefs = $defs->getEntity($this->entityType);
|
||||
$entityDefs = $defs->getEntity($entity->getEntityType());
|
||||
|
||||
$relation = $entity->getAttributeParam($attribute, 'relation');
|
||||
$foreign = $entity->getAttributeParam($attribute, 'foreign');
|
||||
@@ -386,55 +371,26 @@ class Export implements
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getCollection(): Collection
|
||||
private function getCollection(Params $params): Collection
|
||||
{
|
||||
if ($this->collection) {
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
$params = $this->params;
|
||||
$entityType = $params->getEntityType();
|
||||
|
||||
$selectBuilder = $this->selectBuilderFactory
|
||||
$searchParams = $params->getSearchParams();
|
||||
|
||||
$query = $this->selectBuilderFactory
|
||||
->create()
|
||||
->from($this->entityType)
|
||||
->withStrictAccessControl();
|
||||
|
||||
if (array_key_exists('ids', $params)) {
|
||||
$ids = $params['ids'];
|
||||
|
||||
$queryBuilder = $selectBuilder
|
||||
->withDefaultOrder()
|
||||
->buildQueryBuilder()
|
||||
->where([
|
||||
'id' => $ids,
|
||||
]);
|
||||
}
|
||||
else if (array_key_exists('where', $params)) {
|
||||
$where = $params['where'];
|
||||
|
||||
$searchParams = [];
|
||||
|
||||
$searchParams['where'] = $where;
|
||||
|
||||
$selectData = $params['selectData'] ?? [];
|
||||
|
||||
foreach ($selectData as $k => $v) {
|
||||
$searchParams[$k] = $v;
|
||||
}
|
||||
|
||||
unset($searchParams['select']);
|
||||
|
||||
$queryBuilder = $selectBuilder
|
||||
->withSearchParams(SearchParams::fromRaw($searchParams))
|
||||
->buildQueryBuilder();
|
||||
}
|
||||
else {
|
||||
throw new BadRequest("Bad export parameters.");
|
||||
}
|
||||
->from($entityType)
|
||||
->withSearchParams($searchParams)
|
||||
->withStrictAccessControl()
|
||||
->build();
|
||||
|
||||
return $this->entityManager
|
||||
->getRepository($this->entityType)
|
||||
->clone($queryBuilder->build())
|
||||
->getRepository($entityType)
|
||||
->clone($query)
|
||||
->sth()
|
||||
->find();
|
||||
}
|
||||
|
||||
49
application/Espo/Tools/Export/Factory.php
Normal file
49
application/Espo/Tools/Export/Factory.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Export;
|
||||
|
||||
use Espo\Core\{
|
||||
InjectableFactory,
|
||||
};
|
||||
|
||||
class Factory
|
||||
{
|
||||
private $injectableFactory;
|
||||
|
||||
public function __construct(InjectableFactory $injectableFactory)
|
||||
{
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
|
||||
public function create(): Export
|
||||
{
|
||||
return $this->injectableFactory->create(Export::class);
|
||||
}
|
||||
}
|
||||
228
application/Espo/Tools/Export/Params.php
Normal file
228
application/Espo/Tools/Export/Params.php
Normal file
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Export;
|
||||
|
||||
use Espo\Core\{
|
||||
Select\SearchParams,
|
||||
};
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class Params
|
||||
{
|
||||
private $entityType;
|
||||
|
||||
private $attributeList = null;
|
||||
|
||||
private $fieldList = null;
|
||||
|
||||
private $fileName = null;
|
||||
|
||||
private $format = null;
|
||||
|
||||
private $name = null;
|
||||
|
||||
private $searchParams = null;
|
||||
|
||||
public function __construct(string $entityType)
|
||||
{
|
||||
$this->entityType = $entityType;
|
||||
}
|
||||
|
||||
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
|
||||
::fromNothing()
|
||||
->withWhere([
|
||||
[
|
||||
'type' => 'equals',
|
||||
'attribute' => 'id',
|
||||
'value' => $ids,
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
if ($searchParams) {
|
||||
$actualSearchParams = $searchParams;
|
||||
|
||||
unset($actualSearchParams['select']);
|
||||
|
||||
$obj->searchParams = SearchParams::fromRaw($actualSearchParams);
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function fromEntityType(string $entityType): self
|
||||
{
|
||||
return new self($entityType);
|
||||
}
|
||||
|
||||
public function withFormat(?string $format): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$obj->format = $format;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
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 withFieldList(?array $fieldList): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$obj->fieldList = $fieldList;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withAttributeList(?array $attributeList): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$obj->attributeList = $attributeList;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function getSearchParams(): SearchParams
|
||||
{
|
||||
if (!$this->searchParams) {
|
||||
return SearchParams::fromNothing();
|
||||
}
|
||||
|
||||
return $this->searchParams;
|
||||
}
|
||||
|
||||
public function getEntityType(): string
|
||||
{
|
||||
return $this->entityType;
|
||||
}
|
||||
|
||||
public function getFileName(): ?string
|
||||
{
|
||||
return $this->fileName;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getFormat(): ?string
|
||||
{
|
||||
return $this->format;
|
||||
}
|
||||
|
||||
public function getAttributeList(): ?array
|
||||
{
|
||||
return $this->attributeList;
|
||||
}
|
||||
|
||||
public function getFieldList(): ?array
|
||||
{
|
||||
return $this->fieldList;
|
||||
}
|
||||
}
|
||||
37
application/Espo/Tools/Export/Processor.php
Normal file
37
application/Espo/Tools/Export/Processor.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Export;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
interface Processor
|
||||
{
|
||||
public function process(ProcessorParams $params, ProcessorData $data): StreamInterface;
|
||||
}
|
||||
51
application/Espo/Tools/Export/ProcessorData.php
Normal file
51
application/Espo/Tools/Export/ProcessorData.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Export;
|
||||
|
||||
class ProcessorData
|
||||
{
|
||||
private $resource;
|
||||
|
||||
public function __construct($resource)
|
||||
{
|
||||
$this->resource = $resource;
|
||||
}
|
||||
|
||||
public function readRow(): ?array
|
||||
{
|
||||
$line = fgets($this->resource);
|
||||
|
||||
if ($line === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return unserialize(base64_decode($line));
|
||||
}
|
||||
}
|
||||
65
application/Espo/Tools/Export/ProcessorFactory.php
Normal file
65
application/Espo/Tools/Export/ProcessorFactory.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Export;
|
||||
|
||||
use Espo\Core\{
|
||||
InjectableFactory,
|
||||
Utils\Metadata,
|
||||
};
|
||||
|
||||
use LogicException;
|
||||
|
||||
class ProcessorFactory
|
||||
{
|
||||
private $injectableFactory;
|
||||
|
||||
private $metadata;
|
||||
|
||||
public function __construct(InjectableFactory $injectableFactory, Metadata $metadata)
|
||||
{
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->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}'.");
|
||||
}
|
||||
|
||||
$className = $this->metadata->get(['app', 'export', 'processorClassNameMap', $format]);
|
||||
|
||||
if (!$className) {
|
||||
throw new LogicException("No implementation for format '{$format}'.");
|
||||
}
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
}
|
||||
93
application/Espo/Tools/Export/ProcessorParams.php
Normal file
93
application/Espo/Tools/Export/ProcessorParams.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Export;
|
||||
|
||||
class ProcessorParams
|
||||
{
|
||||
private $fileName;
|
||||
|
||||
private $attributeList;
|
||||
|
||||
private $fieldList;
|
||||
|
||||
private $name = null;
|
||||
|
||||
private $entityType = null;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public function getFileName(): string
|
||||
{
|
||||
return $this->fileName;
|
||||
}
|
||||
|
||||
public function getAttributeList(): array
|
||||
{
|
||||
return $this->attributeList;
|
||||
}
|
||||
|
||||
public function getFieldList(): array
|
||||
{
|
||||
return $this->fieldList;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getEntityType(): string
|
||||
{
|
||||
return $this->entityType;
|
||||
}
|
||||
}
|
||||
@@ -27,24 +27,35 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Export\Formats;
|
||||
namespace Espo\Tools\Export\Processors;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Core\ORM\Entity;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\Config,
|
||||
Utils\Metadata,
|
||||
Utils\Json,
|
||||
};
|
||||
|
||||
use Espo\Entities\Preferences;
|
||||
|
||||
class Csv
|
||||
use Espo\Tools\Export\{
|
||||
ProcessorParams,
|
||||
ProcessorData,
|
||||
Processor,
|
||||
};
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
|
||||
class Csv implements Processor
|
||||
{
|
||||
protected $config;
|
||||
protected $preferences;
|
||||
protected $metadata;
|
||||
private $config;
|
||||
|
||||
private $preferences;
|
||||
|
||||
private $metadata;
|
||||
|
||||
public function __construct(Config $config, Preferences $preferences, Metadata $metadata)
|
||||
{
|
||||
@@ -53,13 +64,56 @@ class Csv
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
public function loadAdditionalFields(Entity $entity, $fieldList)
|
||||
public function process(ProcessorParams $params, ProcessorData $data): StreamInterface
|
||||
{
|
||||
$attributeList = $params->getAttributeList();
|
||||
|
||||
$delimiterRaw =
|
||||
$this->preferences->get('exportDelimiter') ??
|
||||
$this->config->get('exportDelimiter') ??
|
||||
',';
|
||||
|
||||
$delimiter = str_replace('\t', "\t", $delimiterRaw);
|
||||
|
||||
$fp = fopen('php://temp', 'w');
|
||||
|
||||
fputcsv($fp, $attributeList, $delimiter);
|
||||
|
||||
while (($row = $data->readRow()) !== null) {
|
||||
$preparedRow = $this->prepareRow($row);
|
||||
|
||||
fputcsv($fp, $preparedRow, $delimiter);
|
||||
}
|
||||
|
||||
rewind($fp);
|
||||
|
||||
return new Stream($fp);
|
||||
}
|
||||
|
||||
protected function prepareRow(array $row): array
|
||||
{
|
||||
$preparedRow = [];
|
||||
|
||||
foreach ($row as $item) {
|
||||
if (is_array($item) || is_object($item)) {
|
||||
$item = Json::encode($item);
|
||||
}
|
||||
|
||||
$preparedRow[] = $item;
|
||||
}
|
||||
|
||||
return $preparedRow;
|
||||
}
|
||||
|
||||
public function loadAdditionalFields(Entity $entity, array $fieldList): void
|
||||
{
|
||||
foreach ($fieldList as $field) {
|
||||
$fieldType = $this->metadata->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']);
|
||||
$fieldType = $this->metadata
|
||||
->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']);
|
||||
|
||||
if (
|
||||
$fieldType === 'linkMultiple' || $fieldType === 'attachmentMultiple'
|
||||
$fieldType === 'linkMultiple' ||
|
||||
$fieldType === 'attachmentMultiple'
|
||||
) {
|
||||
if (!$entity->has($field . 'Ids')) {
|
||||
$entity->loadLinkMultipleField($field);
|
||||
@@ -67,54 +121,4 @@ class Csv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function process(string $entityType, array $params, ?array $dataList, $dataFp = null)
|
||||
{
|
||||
if (!is_array($params['attributeList'])) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$attributeList = $params['attributeList'];
|
||||
|
||||
$delimiter = $this->preferences->get('exportDelimiter');
|
||||
if (empty($delimiter)) {
|
||||
$delimiter = $this->config->get('exportDelimiter', ',');
|
||||
}
|
||||
|
||||
$delimiter = str_replace('\t', "\t", $delimiter);
|
||||
|
||||
$fp = fopen('php://temp', 'w');
|
||||
fputcsv($fp, $attributeList, $delimiter);
|
||||
|
||||
if ($dataFp) {
|
||||
while (($line = fgets($dataFp)) !== false) {
|
||||
$row = unserialize(base64_decode($line));
|
||||
$preparedRow = $this->prepareRow($row);
|
||||
fputcsv($fp, $preparedRow, $delimiter);
|
||||
}
|
||||
} else {
|
||||
foreach ($dataList as $row) {
|
||||
$preparedRow = $this->prepareRow($row);
|
||||
fputcsv($fp, $preparedRow, $delimiter);
|
||||
}
|
||||
}
|
||||
|
||||
rewind($fp);
|
||||
$csv = stream_get_contents($fp);
|
||||
fclose($fp);
|
||||
|
||||
return $csv;
|
||||
}
|
||||
|
||||
protected function prepareRow($row)
|
||||
{
|
||||
$preparedRow = [];
|
||||
foreach ($row as $item) {
|
||||
if (is_array($item) || is_object($item)) {
|
||||
$item = \Espo\Core\Utils\Json::encode($item);
|
||||
}
|
||||
$preparedRow[] = $item;
|
||||
}
|
||||
return $preparedRow;
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,9 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Export\Formats;
|
||||
namespace Espo\Tools\Export\Processors;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\Config,
|
||||
@@ -38,12 +37,21 @@ use Espo\Core\{
|
||||
Utils\Language,
|
||||
Utils\DateTime as DateTimeUtil,
|
||||
FileStorage\Manager as FileStorageManager,
|
||||
Utils\File\Manager as FileManager,
|
||||
ORM\EntityManager,
|
||||
Fields\Address,
|
||||
Fields\Address\AddressFormatterFactory,
|
||||
};
|
||||
|
||||
use Espo\Tools\Export\{
|
||||
ProcessorParams,
|
||||
ProcessorData,
|
||||
Processor,
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -55,7 +63,10 @@ use DateTime;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
|
||||
class Xlsx
|
||||
/**
|
||||
* @todo Refactor.
|
||||
*/
|
||||
class Xlsx implements Processor
|
||||
{
|
||||
protected $config;
|
||||
|
||||
@@ -69,8 +80,6 @@ class Xlsx
|
||||
|
||||
protected $fileStorageManager;
|
||||
|
||||
protected $fileManager;
|
||||
|
||||
protected $addressFormatterFactory;
|
||||
|
||||
public function __construct(
|
||||
@@ -80,7 +89,6 @@ class Xlsx
|
||||
DateTimeUtil $dateTime,
|
||||
EntityManager $entityManager,
|
||||
FileStorageManager $fileStorageManager,
|
||||
FileManager $fileManager,
|
||||
AddressFormatterFactory $addressFormatterFactory
|
||||
) {
|
||||
$this->config = $config;
|
||||
@@ -89,136 +97,27 @@ class Xlsx
|
||||
$this->dateTime = $dateTime;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->fileStorageManager = $fileStorageManager;
|
||||
$this->fileManager = $fileManager;
|
||||
$this->addressFormatterFactory = $addressFormatterFactory;
|
||||
}
|
||||
|
||||
public function loadAdditionalFields(Entity $entity, $fieldList)
|
||||
public function process(ProcessorParams $params, ProcessorData $data): StreamInterface
|
||||
{
|
||||
foreach ($entity->getRelationList() as $link) {
|
||||
if (in_array($link, $fieldList)) {
|
||||
if ($entity->getRelationType($link) === 'belongsToParent') {
|
||||
if (!$entity->get($link . 'Name')) {
|
||||
$entity->loadParentNameField($link);
|
||||
}
|
||||
} else if (
|
||||
(
|
||||
(
|
||||
$entity->getRelationType($link) === 'belongsTo'
|
||||
&&
|
||||
$entity->getRelationParam($link, 'noJoin')
|
||||
)
|
||||
||
|
||||
$entity->getRelationType($link) === 'hasOne'
|
||||
)
|
||||
&&
|
||||
$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']);
|
||||
$entityType = $params->getEntityType();
|
||||
|
||||
if ($fieldType === 'linkMultiple' || $fieldType === 'attachmentMultiple') {
|
||||
if (!$entity->has($field . 'Ids')) {
|
||||
$entity->loadLinkMultipleField($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function filterFieldList($entityType, $fieldList, $exportAllFields)
|
||||
{
|
||||
if ($exportAllFields) {
|
||||
foreach ($fieldList as $i => $field) {
|
||||
$type = $this->metadata->get(['entityDefs', $entityType, 'fields', $field, 'type']);
|
||||
if (in_array($type, ['linkMultiple', 'attachmentMultiple'])) {
|
||||
unset($fieldList[$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($fieldList);
|
||||
}
|
||||
|
||||
public function addAdditionalAttributes($entityType, &$attributeList, $fieldList)
|
||||
{
|
||||
$linkList = [];
|
||||
|
||||
if (!in_array('id', $attributeList)) {
|
||||
$attributeList[] = 'id';
|
||||
}
|
||||
|
||||
$linkDefs = $this->metadata->get(['entityDefs', $entityType, 'links']);
|
||||
|
||||
if (is_array($linkDefs)) {
|
||||
foreach ($linkDefs as $link => $defs) {
|
||||
if (empty($defs['type'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($defs['type'] === 'belongsToParent') {
|
||||
$linkList[] = $link;
|
||||
}
|
||||
else if ($defs['type'] === 'belongsTo' && !empty($defs['noJoin'])) {
|
||||
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 === 'currencyConverted') {
|
||||
if (!in_array($field, $attributeList)) {
|
||||
$attributeList[] = $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function process(string $entityType, array $params, ?array $dataList = null, $dataFp = null)
|
||||
{
|
||||
if (!is_array($params['fieldList'])) {
|
||||
throw new Error();
|
||||
}
|
||||
$fieldList = $params->getFieldList();
|
||||
|
||||
$phpExcel = new Spreadsheet();
|
||||
|
||||
$sheet = $phpExcel->setActiveSheetIndex(0);
|
||||
|
||||
if (isset($params['exportName'])) {
|
||||
$exportName = $params['exportName'];
|
||||
}
|
||||
else {
|
||||
$exportName = $this->language->translate($entityType, 'scopeNamesPlural');
|
||||
}
|
||||
$sheetName = $this->getSheetNameFromParams($params);
|
||||
|
||||
$sheetName = mb_substr($exportName, 0, 30, 'utf-8');
|
||||
$badCharList = ['*', ':', '/', '\\', '?', '[', ']'];
|
||||
|
||||
foreach ($badCharList as $badChar) {
|
||||
$sheetName = str_replace($badCharList, ' ', $sheetName);
|
||||
}
|
||||
|
||||
$sheetName = str_replace('\'', '', $sheetName);
|
||||
$exportName =
|
||||
$params->getName() ??
|
||||
$this->language->translate($entityType, 'scopeNamesPlural');
|
||||
|
||||
$sheet->setTitle($sheetName);
|
||||
|
||||
$fieldList = $params['fieldList'];
|
||||
|
||||
$titleStyle = [
|
||||
'font' => [
|
||||
'bold' => true,
|
||||
@@ -247,6 +146,7 @@ class Xlsx
|
||||
|
||||
$azRange = range('A', 'Z');
|
||||
$azRangeCopied = $azRange;
|
||||
|
||||
foreach ($azRangeCopied as $i => $char1) {
|
||||
foreach ($azRangeCopied as $j => $char2) {
|
||||
$azRange[] = $char1 . $char2;
|
||||
@@ -276,9 +176,13 @@ class Xlsx
|
||||
|
||||
if (strpos($name, '_') !== false) {
|
||||
list($linkName, $foreignField) = explode('_', $name);
|
||||
|
||||
$foreignScope = $this->metadata->get(['entityDefs', $entityType, 'links', $linkName, 'entity']);
|
||||
|
||||
if ($foreignScope) {
|
||||
$label = $this->language->translate($linkName, 'links', $entityType) . '.' . $this->language->translate($foreignField, 'fields', $foreignScope);
|
||||
$label =
|
||||
$this->language->translate($linkName, 'links', $entityType) . '.' .
|
||||
$this->language->translate($foreignField, 'fields', $foreignScope);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -316,28 +220,14 @@ class Xlsx
|
||||
$rowNumber++;
|
||||
|
||||
$lineIndex = -1;
|
||||
if ($dataList) {
|
||||
$lineCount = count($dataList);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
$lineIndex++;
|
||||
|
||||
if ($dataFp) {
|
||||
$line = fgets($dataFp);
|
||||
$row = $data->readRow();
|
||||
|
||||
if ($line === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
$row = unserialize(base64_decode($line));
|
||||
}
|
||||
else {
|
||||
if ($lineIndex >= $lineCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
$row = $dataList[$lineIndex];
|
||||
if ($row === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
@@ -348,7 +238,7 @@ class Xlsx
|
||||
$defs = $this->metadata->get(['entityDefs', $entityType, 'fields', $name]);
|
||||
|
||||
if (!$defs) {
|
||||
$defs = array();
|
||||
$defs = [];
|
||||
$defs['type'] = 'base';
|
||||
}
|
||||
|
||||
@@ -359,43 +249,51 @@ class Xlsx
|
||||
if (strpos($name, '_') !== false) {
|
||||
list($linkName, $foreignField) = explode('_', $name);
|
||||
|
||||
$foreignScope = $this->metadata->get(['entityDefs', $entityType, 'links', $linkName, 'entity']);
|
||||
$foreignScope = $this->metadata
|
||||
->get(['entityDefs', $entityType, 'links', $linkName, 'entity']);
|
||||
|
||||
if ($foreignScope) {
|
||||
$type = $this->metadata->get(['entityDefs', $foreignScope, 'fields', $foreignField, 'type'], $type);
|
||||
$type = $this->metadata
|
||||
->get(['entityDefs', $foreignScope, 'fields', $foreignField, 'type'], $type);
|
||||
}
|
||||
}
|
||||
|
||||
if ($type === 'foreign') {
|
||||
$linkName = $this->metadata->get(['entityDefs', $entityType, 'fields', $name, 'link']);
|
||||
$foreignField = $this->metadata->get(['entityDefs', $entityType, 'fields', $name, 'field']);
|
||||
$foreignScope = $this->metadata->get(['entityDefs', $entityType, 'links', $linkName, 'entity']);
|
||||
$linkName = $this->metadata
|
||||
->get(['entityDefs', $entityType, 'fields', $name, 'link']);
|
||||
|
||||
$foreignField = $this->metadata
|
||||
->get(['entityDefs', $entityType, 'fields', $name, 'field']);
|
||||
|
||||
$foreignScope = $this->metadata
|
||||
->get(['entityDefs', $entityType, 'links', $linkName, 'entity']);
|
||||
|
||||
if ($foreignScope) {
|
||||
$type = $this->metadata->get(['entityDefs', $foreignScope, 'fields', $foreignField, 'type'], $type);
|
||||
$type = $this->metadata
|
||||
->get(['entityDefs', $foreignScope, 'fields', $foreignField, 'type'], $type);
|
||||
}
|
||||
}
|
||||
$typesCache[$name] = $type;
|
||||
|
||||
$link = null;
|
||||
|
||||
if ($type == 'link') {
|
||||
if ($type === 'link') {
|
||||
if (array_key_exists($name.'Name', $row)) {
|
||||
$sheet->setCellValue("$col$rowNumber", $row[$name.'Name']);
|
||||
}
|
||||
}
|
||||
else if ($type == 'linkParent') {
|
||||
else if ($type === 'linkParent') {
|
||||
if (array_key_exists($name.'Name', $row)) {
|
||||
$sheet->setCellValue("$col$rowNumber", $row[$name.'Name']);
|
||||
}
|
||||
}
|
||||
else if ($type == 'int') {
|
||||
else if ($type === 'int') {
|
||||
$sheet->setCellValue("$col$rowNumber", $row[$name] ?: 0);
|
||||
}
|
||||
else if ($type == 'float') {
|
||||
else if ($type === 'float') {
|
||||
$sheet->setCellValue("$col$rowNumber", $row[$name] ?: 0);
|
||||
}
|
||||
else if ($type == 'currency') {
|
||||
else if ($type === 'currency') {
|
||||
if (array_key_exists($name.'Currency', $row) && array_key_exists($name, $row)) {
|
||||
$sheet->setCellValue("$col$rowNumber", $row[$name] ? $row[$name] : '');
|
||||
|
||||
@@ -408,7 +306,7 @@ class Xlsx
|
||||
);
|
||||
}
|
||||
}
|
||||
else if ($type == 'currencyConverted') {
|
||||
else if ($type === 'currencyConverted') {
|
||||
if (array_key_exists($name, $row)) {
|
||||
$currency = $this->config->get('defaultCurrency');
|
||||
|
||||
@@ -421,7 +319,7 @@ class Xlsx
|
||||
$sheet->setCellValue("$col$rowNumber", $row[$name] ? $row[$name] : '');
|
||||
}
|
||||
}
|
||||
else if ($type == 'personName') {
|
||||
else if ($type === 'personName') {
|
||||
if (!empty($row['name'])) {
|
||||
$sheet->setCellValue("$col$rowNumber", $row['name']);
|
||||
} else {
|
||||
@@ -435,18 +333,21 @@ class Xlsx
|
||||
}
|
||||
$personName .= $row['lastName'];
|
||||
}
|
||||
$sheet->setCellValue("$col$rowNumber", $personName);
|
||||
$sheet->setCellValue($col . $rowNumber, $personName);
|
||||
}
|
||||
}
|
||||
else if ($type == 'date') {
|
||||
else if ($type === 'date') {
|
||||
if (isset($row[$name])) {
|
||||
$sheet->setCellValue("$col$rowNumber", SharedDate::PHPToExcel(strtotime($row[$name])));
|
||||
$sheet->setCellValue(
|
||||
$col . $rowNumber,
|
||||
SharedDate::PHPToExcel(strtotime($row[$name]))
|
||||
);
|
||||
}
|
||||
}
|
||||
else if ($type == 'datetime' || $type == 'datetimeOptional') {
|
||||
else if ($type === 'datetime' || $type === 'datetimeOptional') {
|
||||
$value = null;
|
||||
|
||||
if ($type == 'datetimeOptional') {
|
||||
if ($type === 'datetimeOptional') {
|
||||
if (isset($row[$name . 'Date']) && $row[$name . 'Date']) {
|
||||
$value = $row[$name . 'Date'];
|
||||
}
|
||||
@@ -463,6 +364,7 @@ class Xlsx
|
||||
$timeZone = $this->config->get('timeZone');
|
||||
|
||||
$dt = new DateTime($value);
|
||||
|
||||
$dt->setTimezone(new DateTimeZone($timeZone));
|
||||
|
||||
$value = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
|
||||
@@ -476,7 +378,7 @@ class Xlsx
|
||||
$sheet->setCellValue("$col$rowNumber", SharedDate::PHPToExcel(strtotime($value)));
|
||||
}
|
||||
}
|
||||
else if ($type == 'image') {
|
||||
else if ($type === 'image') {
|
||||
if (isset($row[$name . 'Id']) && $row[$name . 'Id']) {
|
||||
$attachment = $this->entityManager->getEntity('Attachment', $row[$name . 'Id']);
|
||||
|
||||
@@ -495,12 +397,12 @@ class Xlsx
|
||||
}
|
||||
|
||||
}
|
||||
else if ($type == 'file') {
|
||||
else if ($type === 'file') {
|
||||
if (array_key_exists($name.'Name', $row)) {
|
||||
$sheet->setCellValue("$col$rowNumber", $row[$name.'Name']);
|
||||
}
|
||||
}
|
||||
else if ($type == 'enum') {
|
||||
else if ($type === 'enum') {
|
||||
if (array_key_exists($name, $row)) {
|
||||
if ($linkName) {
|
||||
$value = $this->language->translateOption($row[$name], $foreignField, $foreignScope);
|
||||
@@ -512,7 +414,7 @@ class Xlsx
|
||||
$sheet->setCellValue("$col$rowNumber", $value);
|
||||
}
|
||||
}
|
||||
else if ($type == 'linkMultiple' || $type == 'attachmentMultiple') {
|
||||
else if ($type === 'linkMultiple' || $type === 'attachmentMultiple') {
|
||||
if (array_key_exists($name . 'Ids', $row) && array_key_exists($name . 'Names', $row)) {
|
||||
$nameList = [];
|
||||
|
||||
@@ -529,7 +431,7 @@ class Xlsx
|
||||
$sheet->setCellValue("$col$rowNumber", implode(', ', $nameList));
|
||||
}
|
||||
}
|
||||
else if ($type == 'address') {
|
||||
else if ($type === 'address') {
|
||||
$address = Address::createBuilder()
|
||||
->setStreet($row[$name . 'Street'] ?? null)
|
||||
->setCity($row[$name . 'City'] ?? null)
|
||||
@@ -579,7 +481,7 @@ class Xlsx
|
||||
$sheet->setCellValue("$col$rowNumber", $value);
|
||||
}
|
||||
}
|
||||
else if ($type == 'multiEnum' || $type == 'array') {
|
||||
else if ($type === 'multiEnum' || $type === 'array') {
|
||||
if (!empty($row[$name])) {
|
||||
$array = json_decode($row[$name]);
|
||||
|
||||
@@ -621,17 +523,17 @@ class Xlsx
|
||||
list($foreignLink, $foreignField) = explode('_', $name);
|
||||
}
|
||||
|
||||
if ($name == 'name') {
|
||||
if ($name === 'name') {
|
||||
if (array_key_exists('id', $row)) {
|
||||
$link = $this->config->getSiteUrl() . "/#".$entityType . "/view/" . $row['id'];
|
||||
}
|
||||
}
|
||||
else if ($type == 'url') {
|
||||
else if ($type === 'url') {
|
||||
if (array_key_exists($name, $row) && filter_var($row[$name], FILTER_VALIDATE_URL)) {
|
||||
$link = $row[$name];
|
||||
}
|
||||
}
|
||||
else if ($type == 'link') {
|
||||
else if ($type === 'link') {
|
||||
if (array_key_exists($name.'Id', $row)) {
|
||||
$foreignEntity = null;
|
||||
|
||||
@@ -651,27 +553,29 @@ class Xlsx
|
||||
}
|
||||
|
||||
if ($foreignEntity) {
|
||||
$link = $this->config->getSiteUrl() . "/#" . $foreignEntity. "/view/". $row[$name.'Id'];
|
||||
$link =
|
||||
$this->config->getSiteUrl() .
|
||||
"/#" . $foreignEntity. "/view/". $row[$name.'Id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($type == 'file') {
|
||||
else if ($type === 'file') {
|
||||
if (array_key_exists($name.'Id', $row)) {
|
||||
$link = $this->config->getSiteUrl() . "/?entryPoint=download&id=" . $row[$name.'Id'];
|
||||
}
|
||||
}
|
||||
else if ($type == 'linkParent') {
|
||||
else if ($type === 'linkParent') {
|
||||
if (array_key_exists($name.'Id', $row) && array_key_exists($name.'Type', $row)) {
|
||||
$link = $this->config->getSiteUrl() . "/#".$row[$name.'Type']."/view/". $row[$name.'Id'];
|
||||
}
|
||||
}
|
||||
else if ($type == 'phone') {
|
||||
else if ($type === 'phone') {
|
||||
if (array_key_exists($name, $row)) {
|
||||
$link = "tel:".$row[$name];
|
||||
}
|
||||
}
|
||||
|
||||
else if ($type == 'email' && array_key_exists($name, $row)) {
|
||||
else if ($type === 'email' && array_key_exists($name, $row)) {
|
||||
if (array_key_exists($name, $row)) {
|
||||
$link = "mailto:".$row[$name];
|
||||
}
|
||||
@@ -753,28 +657,25 @@ class Xlsx
|
||||
];
|
||||
|
||||
foreach ($linkColList as $linkColumn) {
|
||||
$sheet->getStyle($linkColumn.$startingRowNumber.':'.$linkColumn.$rowNumber)->applyFromArray($linkStyle);
|
||||
$sheet
|
||||
->getStyle($linkColumn.$startingRowNumber . ':' . $linkColumn.$rowNumber)
|
||||
->applyFromArray($linkStyle);
|
||||
}
|
||||
|
||||
$objWriter = IOFactory::createWriter($phpExcel, 'Xlsx');
|
||||
|
||||
if (!$this->fileManager->isDir('data/cache/')) {
|
||||
$this->fileManager->mkdir('data/cache/');
|
||||
}
|
||||
$resource = fopen('php://temp', 'r+');
|
||||
|
||||
$tempFileName = 'data/cache/' . 'export_' . substr(md5(rand()), 0, 7);
|
||||
$objWriter->save($resource);
|
||||
|
||||
$objWriter->save($tempFileName);
|
||||
$stream = new Stream($resource);
|
||||
|
||||
$fp = fopen($tempFileName, 'r');
|
||||
$xlsx = stream_get_contents($fp);
|
||||
$stream->seek(0);
|
||||
|
||||
$this->fileManager->unlink($tempFileName);
|
||||
|
||||
return $xlsx;
|
||||
return $stream;
|
||||
}
|
||||
|
||||
protected function getCurrencyFormatCode(string $currency) : string
|
||||
protected function getCurrencyFormatCode(string $currency): string
|
||||
{
|
||||
$currencySymbol = $this->metadata->get(['app', 'currency', 'symbolMap', $currency], '');
|
||||
|
||||
@@ -786,4 +687,120 @@ class Xlsx
|
||||
|
||||
return '[$'.$currencySymbol.'-409]#,##0.00;-[$'.$currencySymbol.'-409]#,##0.00';
|
||||
}
|
||||
|
||||
public function loadAdditionalFields(Entity $entity, array $fieldList): void
|
||||
{
|
||||
foreach ($entity->getRelationList() as $link) {
|
||||
if (!in_array($link, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($entity->getRelationType($link) === 'belongsToParent') {
|
||||
if (!$entity->get($link . 'Name')) {
|
||||
$entity->loadParentNameField($link);
|
||||
}
|
||||
}
|
||||
else if (
|
||||
(
|
||||
(
|
||||
$entity->getRelationType($link) === 'belongsTo' &&
|
||||
$entity->getRelationParam($link, 'noJoin')
|
||||
) ||
|
||||
$entity->getRelationType($link) === 'hasOne'
|
||||
) &&
|
||||
$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 === 'linkMultiple' || $fieldType === 'attachmentMultiple') {
|
||||
if (!$entity->has($field . 'Ids')) {
|
||||
$entity->loadLinkMultipleField($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public 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, ['linkMultiple', 'attachmentMultiple'])) {
|
||||
unset($fieldList[$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($fieldList);
|
||||
}
|
||||
|
||||
public function addAdditionalAttributes(string $entityType, array &$attributeList, array $fieldList): void
|
||||
{
|
||||
$linkList = [];
|
||||
|
||||
if (!in_array('id', $attributeList)) {
|
||||
$attributeList[] = 'id';
|
||||
}
|
||||
|
||||
$linkDefs = $this->metadata->get(['entityDefs', $entityType, 'links']);
|
||||
|
||||
if (is_array($linkDefs)) {
|
||||
foreach ($linkDefs as $link => $defs) {
|
||||
if (empty($defs['type'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($defs['type'] === 'belongsToParent') {
|
||||
$linkList[] = $link;
|
||||
}
|
||||
else if ($defs['type'] === 'belongsTo' && !empty($defs['noJoin'])) {
|
||||
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 === 'currencyConverted') {
|
||||
if (!in_array($field, $attributeList)) {
|
||||
$attributeList[] = $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getSheetNameFromParams(ProcessorParams $params): string
|
||||
{
|
||||
$exportName =
|
||||
$params->getName() ??
|
||||
$this->language->translate($params->getEntityType(), 'scopeNamesPlural');
|
||||
|
||||
$badCharList = ['*', ':', '/', '\\', '?', '[', ']'];
|
||||
|
||||
$sheetName = mb_substr($exportName, 0, 30, 'utf-8');
|
||||
|
||||
$sheetName = str_replace($badCharList, ' ', $sheetName);
|
||||
|
||||
$sheetName = str_replace('\'', '', $sheetName);
|
||||
|
||||
return $sheetName;
|
||||
}
|
||||
}
|
||||
45
application/Espo/Tools/Export/Result.php
Normal file
45
application/Espo/Tools/Export/Result.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Export;
|
||||
|
||||
class Result
|
||||
{
|
||||
private $attachmentId;
|
||||
|
||||
public function __construct(string $attachmentId)
|
||||
{
|
||||
$this->attachmentId = $attachmentId;
|
||||
}
|
||||
|
||||
public function getAttachmentId(): string
|
||||
{
|
||||
return $this->attachmentId;
|
||||
}
|
||||
}
|
||||
94
application/Espo/Tools/Export/Service.php
Normal file
94
application/Espo/Tools/Export/Service.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Export;
|
||||
|
||||
use Espo\Core\{
|
||||
Exceptions\ForbiddenSilent,
|
||||
Acl,
|
||||
Acl\Table,
|
||||
Utils\Config,
|
||||
Utils\Metadata,
|
||||
};
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
class Service
|
||||
{
|
||||
private $factory;
|
||||
|
||||
private $config;
|
||||
|
||||
private $acl;
|
||||
|
||||
private $user;
|
||||
|
||||
private $metadata;
|
||||
|
||||
public function __construct(
|
||||
Factory $factory,
|
||||
Config $config,
|
||||
Acl $acl,
|
||||
User $user,
|
||||
Metadata $metadata
|
||||
) {
|
||||
$this->factory = $factory;
|
||||
$this->config = $config;
|
||||
$this->acl = $acl;
|
||||
$this->user = $user;
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
public function process(Params $params): Result
|
||||
{
|
||||
if ($this->config->get('exportDisabled') && !$this->user->isAdmin()) {
|
||||
throw new ForbiddenSilent("Export disabled for non-admin users.");
|
||||
}
|
||||
|
||||
$entityType = $params->getEntityType();
|
||||
|
||||
if ($this->acl->getPermissionLevel('exportPermission') !== 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}'.");
|
||||
}
|
||||
|
||||
$export = $this->factory->create();
|
||||
|
||||
return $export
|
||||
->setParams($params)
|
||||
->run();
|
||||
}
|
||||
}
|
||||
@@ -539,18 +539,20 @@ define('views/record/list', 'view', function (Dep) {
|
||||
|
||||
export: function (data, url, fieldList) {
|
||||
if (!data) {
|
||||
data = {};
|
||||
data = {
|
||||
entityType: this.entityType,
|
||||
};
|
||||
|
||||
if (this.allResultIsChecked) {
|
||||
data.where = this.collection.getWhere();
|
||||
data.selectData = this.collection.data || {};
|
||||
data.byWhere = true;
|
||||
} else {
|
||||
data.searchData = this.collection.data || {};
|
||||
}
|
||||
else {
|
||||
data.ids = this.checkedList;
|
||||
}
|
||||
}
|
||||
|
||||
var url = url || this.entityType + '/action/export';
|
||||
var url = url || 'Export';
|
||||
|
||||
var o = {
|
||||
scope: this.entityType
|
||||
@@ -584,13 +586,16 @@ define('views/record/list', 'view', function (Dep) {
|
||||
|
||||
Espo.Ui.notify(this.translate('pleaseWait', 'messages'));
|
||||
|
||||
Espo.Ajax.postRequest(url, data, {timeout: 0}).then(function (data) {
|
||||
Espo.Ui.notify(false);
|
||||
Espo.Ajax.postRequest(url, data, {timeout: 0})
|
||||
.then(
|
||||
function (data) {
|
||||
Espo.Ui.notify(false);
|
||||
|
||||
if ('id' in data) {
|
||||
window.location = this.getBasePath() + '?entryPoint=download&id=' + data.id;
|
||||
}
|
||||
}.bind(this));
|
||||
if ('id' in data) {
|
||||
window.location = this.getBasePath() + '?entryPoint=download&id=' + data.id;
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}, this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user