diff --git a/application/Espo/Classes/ConsoleCommands/Import.php b/application/Espo/Classes/ConsoleCommands/Import.php index 418686ad0f..640dc88de4 100644 --- a/application/Espo/Classes/ConsoleCommands/Import.php +++ b/application/Espo/Classes/ConsoleCommands/Import.php @@ -32,26 +32,16 @@ namespace Espo\Classes\ConsoleCommands; use Espo\Tools\Import\Service; use Espo\Core\Utils\File\Manager as FileManager; - -use Espo\Core\{ - Console\Command, - Console\Command\Params, - Console\IO, -}; +use Espo\Core\Console\Command; +use Espo\Core\Console\Command\Params; +use Espo\Core\Console\IO; use Throwable; class Import implements Command { - private Service $service; - - private FileManager $fileManager; - - public function __construct(Service $service, FileManager $fileManager) - { - $this->service = $service; - $this->fileManager = $fileManager; - } + public function __construct(private Service $service, private FileManager $fileManager) + {} public function run(Params $params, IO $io) : void { diff --git a/application/Espo/Controllers/Import.php b/application/Espo/Controllers/Import.php index 63c1390967..80e1948adf 100644 --- a/application/Espo/Controllers/Import.php +++ b/application/Espo/Controllers/Import.php @@ -30,16 +30,16 @@ namespace Espo\Controllers; use Espo\Core\Exceptions\BadRequest; +use Espo\Core\Exceptions\Error; use Espo\Core\Exceptions\Forbidden; +use Espo\Core\Exceptions\NotFound; use Espo\Tools\Import\Params as ImportParams; use Espo\Tools\Import\Service as Service; -use Espo\Core\{ - Controllers\Record, - Api\Request, - Api\Response, -}; +use Espo\Core\Api\Request; +use Espo\Core\Api\Response; +use Espo\Core\Controllers\Record; use Espo\Core\Di\InjectableFactoryAware; use Espo\Core\Di\InjectableFactorySetter; @@ -57,112 +57,6 @@ class Import extends Record return $this->acl->check('Import'); } - private function getImportService(): Service - { - return $this->injectableFactory->create(Service::class); - } - - public function postActionUploadFile(Request $request): stdClass - { - $contents = $request->getBodyContents() ?? ''; - - $attachmentId = $this->getImportService()->uploadFile($contents); - - return (object) [ - 'attachmentId' => $attachmentId - ]; - } - - public function postActionRevert(Request $request): bool - { - $data = $request->getParsedBody(); - - $this->getImportService()->revert($data->id); - - return true; - } - - public function postActionRemoveDuplicates(Request $request): bool - { - $data = $request->getParsedBody(); - - if (empty($data->id)) { - throw new BadRequest(); - } - - $this->getImportService()->removeDuplicates($data->id); - - return true; - } - - public function postActionCreate(Request $request, Response $response): stdClass - { - $data = $request->getParsedBody(); - - $entityType = $data->entityType ?? null; - $attributeList = $data->attributeList ?? null; - $attachmentId = $data->attachmentId ?? null; - - if (!is_array($attributeList)) { - throw new BadRequest("No attributeList."); - } - - if (!$attachmentId) { - throw new BadRequest("No attachmentId."); - } - - if (!$entityType) { - throw new BadRequest("No entityType."); - } - - $params = ImportParams::fromRaw($data); - - $result = $this->getImportService()->import( - $entityType, - $attributeList, - $attachmentId, - $params - ); - - return $result->getValueMap(); - } - - public function postActionUnmarkAsDuplicate(Request $request): bool - { - $data = $request->getParsedBody(); - - if ( - empty($data->id) || - empty($data->entityType) || - empty($data->entityId) - ) { - throw new BadRequest(); - } - - $this->getImportService()->unmarkAsDuplicate($data->id, $data->entityType, $data->entityId); - - return true; - } - - /** - * @throws BadRequest - * @throws \Espo\Core\Exceptions\NotFound - */ - public function postActionExportErrors(Request $request): stdClass - { - $id = $request->getParsedBody()->id ?? null; - - if (!$id) { - throw new BadRequest("No `id`."); - } - - $attachmentId = $this->getImportService()->exportErrors($id); - - return (object) [ - 'attachmentId' => $attachmentId, - ]; - } - public function putActionUpdate(Request $request, Response $response): stdClass { throw new Forbidden(); diff --git a/application/Espo/Resources/routes.json b/application/Espo/Resources/routes.json index 4a1303b8ca..f8e607e40c 100644 --- a/application/Espo/Resources/routes.json +++ b/application/Espo/Resources/routes.json @@ -254,6 +254,36 @@ "method": "post", "actionClassName": "Espo\\Tools\\Export\\Api\\PostSubscribe" }, + { + "route": "/Import", + "method": "post", + "actionClassName": "Espo\\Tools\\Import\\Api\\Post" + }, + { + "route": "/Import/file", + "method": "post", + "actionClassName": "Espo\\Tools\\Import\\Api\\PostFile" + }, + { + "route": "/Import/:id/revert", + "method": "post", + "actionClassName": "Espo\\Tools\\Import\\Api\\PostRevert" + }, + { + "route": "/Import/:id/removeDuplicates", + "method": "post", + "actionClassName": "Espo\\Tools\\Import\\Api\\PostRemoveDuplicates" + }, + { + "route": "/Import/:id/unmarkDuplicates", + "method": "post", + "actionClassName": "Espo\\Tools\\Import\\Api\\PostUnmarkDuplicates" + }, + { + "route": "/Import/:id/exportErrors", + "method": "post", + "actionClassName": "Espo\\Tools\\Import\\Api\\PostExportErrors" + }, { "route": "/Kanban/order", "method": "put", @@ -344,11 +374,6 @@ "method": "get", "actionClassName": "Espo\\Tools\\EmailAddress\\Api\\GetSearch" }, - { - "route": "/Email/insertFieldData", - "method": "get", - "actionClassName": "Espo\\Tools\\Email\\Api\\GetInsertFieldData" - }, { "route": "/User/:id/acl", "method": "get", diff --git a/application/Espo/Tools/Import/Api/Post.php b/application/Espo/Tools/Import/Api/Post.php new file mode 100644 index 0000000000..fa62b88f40 --- /dev/null +++ b/application/Espo/Tools/Import/Api/Post.php @@ -0,0 +1,80 @@ +acl->checkScope(Import::ENTITY_TYPE)) { + throw new Forbidden(); + } + + $data = $request->getParsedBody(); + + $entityType = $data->entityType ?? null; + $attributeList = $data->attributeList ?? null; + $attachmentId = $data->attachmentId ?? null; + + if (!is_array($attributeList)) { + throw new BadRequest("No `attributeList`."); + } + + if (!$attachmentId) { + throw new BadRequest("No `attachmentId`."); + } + + if (!$entityType) { + throw new BadRequest("No `entityType`."); + } + + $params = ImportParams::fromRaw($data); + + $result = $this->service->import($entityType, $attributeList, $attachmentId, $params); + + return ResponseComposer::json($result->getValueMap()); + } +} diff --git a/application/Espo/Tools/Import/Api/PostExportErrors.php b/application/Espo/Tools/Import/Api/PostExportErrors.php new file mode 100644 index 0000000000..2cff170daf --- /dev/null +++ b/application/Espo/Tools/Import/Api/PostExportErrors.php @@ -0,0 +1,65 @@ +acl->checkScope(Import::ENTITY_TYPE)) { + throw new Forbidden(); + } + + $id = $request->getRouteParam('id'); + + if (!$id) { + throw new BadRequest(); + } + + $attachmentId = $this->service->exportErrors($id); + + return ResponseComposer::json(['attachmentId' => $attachmentId]); + } +} diff --git a/application/Espo/Tools/Import/Api/PostFile.php b/application/Espo/Tools/Import/Api/PostFile.php new file mode 100644 index 0000000000..a9151c07b1 --- /dev/null +++ b/application/Espo/Tools/Import/Api/PostFile.php @@ -0,0 +1,60 @@ +acl->checkScope(Import::ENTITY_TYPE)) { + throw new Forbidden(); + } + + $contents = $request->getBodyContents() ?? ''; + + $attachmentId = $this->service->uploadFile($contents); + + return ResponseComposer::json(['attachmentId' => $attachmentId]); + } +} diff --git a/application/Espo/Tools/Import/Api/PostRemoveDuplicates.php b/application/Espo/Tools/Import/Api/PostRemoveDuplicates.php new file mode 100644 index 0000000000..c746b1d5e4 --- /dev/null +++ b/application/Espo/Tools/Import/Api/PostRemoveDuplicates.php @@ -0,0 +1,65 @@ +acl->checkScope(Import::ENTITY_TYPE)) { + throw new Forbidden(); + } + + $id = $request->getRouteParam('id'); + + if (!$id) { + throw new BadRequest(); + } + + $this->service->removeDuplicates($id); + + return ResponseComposer::json(true); + } +} diff --git a/application/Espo/Tools/Import/Api/PostRevert.php b/application/Espo/Tools/Import/Api/PostRevert.php new file mode 100644 index 0000000000..34c6b6c9d6 --- /dev/null +++ b/application/Espo/Tools/Import/Api/PostRevert.php @@ -0,0 +1,65 @@ +acl->checkScope(Import::ENTITY_TYPE)) { + throw new Forbidden(); + } + + $id = $request->getRouteParam('id'); + + if (!$id) { + throw new BadRequest(); + } + + $this->service->revert($id); + + return ResponseComposer::json(true); + } +} diff --git a/application/Espo/Tools/Import/Api/PostUnmarkDuplicates.php b/application/Espo/Tools/Import/Api/PostUnmarkDuplicates.php new file mode 100644 index 0000000000..b46098266d --- /dev/null +++ b/application/Espo/Tools/Import/Api/PostUnmarkDuplicates.php @@ -0,0 +1,74 @@ +acl->checkScope(Import::ENTITY_TYPE)) { + throw new Forbidden(); + } + + $id = $request->getRouteParam('id'); + + if (!$id) { + throw new BadRequest(); + } + + $data = $request->getParsedBody(); + + $entityType = $data->entityType ?? null; + $entityId = $data->entityId ?? null; + + if (!$entityType || !$entityId) { + throw new BadRequest("No `entityType` or `entityId`."); + } + + $this->service->unmarkAsDuplicate($id, $entityType, $entityId); + + return ResponseComposer::json(true); + } +} diff --git a/application/Espo/Tools/Import/Service.php b/application/Espo/Tools/Import/Service.php index a260b766da..b9e8e1a9fd 100644 --- a/application/Espo/Tools/Import/Service.php +++ b/application/Espo/Tools/Import/Service.php @@ -75,6 +75,10 @@ class Service Params $params ): Result { + if (!$this->acl->checkScope(ImportEntity::ENTITY_TYPE)) { + throw new Forbidden("No access to Import scope."); + } + if (!$this->acl->check($entityType, Table::ACTION_CREATE)) { throw new Forbidden("No create access for '{$entityType}'."); } @@ -113,7 +117,7 @@ class Service } /** @var ?ImportEntity $source */ - $source = $this->entityManager->getEntity(ImportEntity::ENTITY_TYPE, $importParamsId); + $source = $this->entityManager->getEntityById(ImportEntity::ENTITY_TYPE, $importParamsId); if (!$source) { throw new Error("Import '{$importParamsId}' not found."); @@ -193,14 +197,18 @@ class Service */ public function revert(string $id): void { - $import = $this->entityManager->getEntity('Import', $id); + if (!$this->acl->checkScope(ImportEntity::ENTITY_TYPE)) { + throw new Forbidden("No access to Import scope."); + } + + $import = $this->entityManager->getEntityById(ImportEntity::ENTITY_TYPE, $id); if (!$import) { throw new NotFound("Could not find import record."); } if (!$this->acl->checkEntityDelete($import)) { - throw new Forbidden("No access import record."); + throw new Forbidden("No access to import record."); } $importEntityList = $this->entityManager @@ -272,9 +280,14 @@ class Service /** * @return string Attachment ID. + * @throws Forbidden */ public function uploadFile(string $contents): string { + if (!$this->acl->checkScope(ImportEntity::ENTITY_TYPE)) { + throw new Forbidden("No access to Import scope."); + } + $attachment = $this->entityManager->getNewEntity(Attachment::ENTITY_TYPE); $attachment->set('type', 'text/csv'); @@ -293,7 +306,11 @@ class Service */ public function removeDuplicates(string $id): void { - $import = $this->entityManager->getEntity(ImportEntity::ENTITY_TYPE, $id); + if (!$this->acl->checkScope(ImportEntity::ENTITY_TYPE)) { + throw new Forbidden("No access to Import scope."); + } + + $import = $this->entityManager->getEntityById(ImportEntity::ENTITY_TYPE, $id); if (!$import) { throw new NotFound("Import '{$id}' not found."); @@ -349,9 +366,14 @@ class Service /** * @throws NotFound + * @throws Forbidden */ public function unmarkAsDuplicate(string $importId, string $entityType, string $entityId): void { + if (!$this->acl->checkScope(ImportEntity::ENTITY_TYPE)) { + throw new Forbidden("No access to Import scope."); + } + $entity = $this->entityManager ->getRDBRepository(ImportEntityEntity::ENTITY_TYPE) ->where([ diff --git a/client/src/handlers/import.js b/client/src/handlers/import.js index fccb4032a5..a42d05b69e 100644 --- a/client/src/handlers/import.js +++ b/client/src/handlers/import.js @@ -35,9 +35,7 @@ define('handlers/import', ['action-handler'], function (Dep) { actionErrorExport() { Espo.Ajax - .postRequest('Import/action/exportErrors', { - id: this.view.model.id - }) + .postRequest(`Import/${this.view.model.id}/exportErrors`) .then(data => { if (!data.attachmentId) { let message = this.view.translate('noErrors', 'messages', 'Import'); @@ -47,7 +45,7 @@ define('handlers/import', ['action-handler'], function (Dep) { return; } - window.location = this.view.getBasePath() +'?entryPoint=download&id=' + data.attachmentId; + window.location = this.view.getBasePath() + '?entryPoint=download&id=' + data.attachmentId; }); } } diff --git a/client/src/views/import/detail.js b/client/src/views/import/detail.js index ede9b26715..0c2827c4a0 100644 --- a/client/src/views/import/detail.js +++ b/client/src/views/import/detail.js @@ -148,7 +148,7 @@ define('views/import/detail', 'views/detail', function (Dep) { Espo.Ui.notify(this.translate('pleaseWait', 'messages')); Espo.Ajax - .postRequest('Import/action/revert', {id: this.model.id}) + .postRequest(`Import/${this.model.id}/revert`) .then(() => { this.getRouter().navigate('#Import/list', {trigger: true}); }); @@ -162,7 +162,7 @@ define('views/import/detail', 'views/detail', function (Dep) { Espo.Ui.notify(this.translate('pleaseWait', 'messages')); Espo.Ajax - .postRequest('Import/action/removeDuplicates', {id: this.model.id}) + .postRequest(`Import/${this.model.id}/removeDuplicates`) .then(() => { this.removeMenuItem('removeDuplicates', true); diff --git a/client/src/views/import/record/panels/duplicates.js b/client/src/views/import/record/panels/duplicates.js index 9435d42b61..98002ea1a5 100644 --- a/client/src/views/import/record/panels/duplicates.js +++ b/client/src/views/import/record/panels/duplicates.js @@ -26,7 +26,7 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -Espo.define('views/import/record/panels/duplicates', 'views/import/record/panels/imported', function (Dep) { +define('views/import/record/panels/duplicates', ['views/import/record/panels/imported'], function (Dep) { return Dep.extend({ @@ -34,23 +34,22 @@ Espo.define('views/import/record/panels/duplicates', 'views/import/record/panels setup: function () { this.title = this.title || this.translate('Duplicates', 'labels', 'Import'); + Dep.prototype.setup.call(this); }, actionUnmarkAsDuplicate: function (data) { - var id = data.id; - var type = data.type; + let id = data.id; + let type = data.type; - this.confirm(this.translate('confirmation', 'messages'), function () { - this.ajaxPostRequest('Import/action/unmarkAsDuplicate', { - id: this.model.id, + this.confirm(this.translate('confirmation', 'messages'), () => { + Espo.Ajax.postRequest(`Import/${this.model.id}/unmarkDuplicates`, { entityId: id, - entityType: type - }).then(function () { + entityType: type, + }).then(() => { this.collection.fetch(); }); - }, this); - } - + }); + }, }); }); diff --git a/client/src/views/import/step2.js b/client/src/views/import/step2.js index 99a493a66f..a98ed8a762 100644 --- a/client/src/views/import/step2.js +++ b/client/src/views/import/step2.js @@ -512,7 +512,7 @@ define('views/import/step2', 'view', function (Dep) { this.notify('File uploading...'); - Espo.Ajax.postRequest('Import/action/uploadFile', null, { + Espo.Ajax.postRequest('Import/file', null, { timeout: 0, contentType: 'text/csv', data: this.getParentView().fileContents,