export refactoring

This commit is contained in:
Yuri Kuznetsov
2021-05-08 11:33:17 +03:00
parent b79bd9a30e
commit 14d313686c
20 changed files with 1168 additions and 486 deletions

View 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);
}
}

View File

@@ -85,9 +85,4 @@ class Notification extends RecordBase
return true;
}
public function beforeExport(): void
{
throw new Error();
}
}

View File

@@ -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;

View File

@@ -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.
*

View File

@@ -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"
}
}

View File

@@ -0,0 +1,3 @@
{
"exportDisabled": true
}

View File

@@ -252,6 +252,14 @@
"action": "process"
}
},
{
"route": "/Export",
"method": "post",
"params": {
"controller": "Export",
"action": "process"
}
},
{
"route": "/Kanban/:entityType",
"method": "get",

View File

@@ -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();
}

View File

@@ -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();
}

View 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);
}
}

View 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;
}
}

View 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;
}

View 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));
}
}

View 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);
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View 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();
}
}

View File

@@ -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);
},