export in idle
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
<?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\Classes\Cleanup;
|
||||
|
||||
use Espo\Core\Cleanup\Cleanup;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Core\Field\DateTime;
|
||||
|
||||
use Espo\Entities\Export;
|
||||
|
||||
class Exports implements Cleanup
|
||||
{
|
||||
private $config;
|
||||
|
||||
private $entityManager;
|
||||
|
||||
private $cleanupPeriod = '2 days';
|
||||
|
||||
public function __construct(Config $config, EntityManager $entityManager)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
$period = '-' . $this->config->get('cleanupExportsPeriod', $this->cleanupPeriod);
|
||||
|
||||
$before = DateTime::createNow()
|
||||
->modify($period)
|
||||
->getString();
|
||||
|
||||
$delete = $this->entityManager
|
||||
->getQueryBuilder()
|
||||
->delete()
|
||||
->from(Export::ENTITY_TYPE)
|
||||
->where([
|
||||
'createdAt<' => $before,
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($delete);
|
||||
}
|
||||
}
|
||||
@@ -29,17 +29,15 @@
|
||||
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\{
|
||||
Api\Request,
|
||||
Exceptions\BadRequest,
|
||||
};
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
|
||||
use Espo\Tools\Export\{
|
||||
Service,
|
||||
Params,
|
||||
};
|
||||
use Espo\Tools\Export\Service;
|
||||
use Espo\Tools\Export\ServiceParams;
|
||||
use Espo\Tools\Export\Params;
|
||||
|
||||
use StdClass;
|
||||
use stdClass;
|
||||
|
||||
class Export
|
||||
{
|
||||
@@ -50,17 +48,52 @@ class Export
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
public function postActionProcess(Request $request): StdClass
|
||||
public function postActionProcess(Request $request): stdClass
|
||||
{
|
||||
$params = $this->fetchRawParamsFromRequest($request);
|
||||
|
||||
$result = $this->service->process($params);
|
||||
$serviceParams = ServiceParams::create()
|
||||
->withIsIdle(
|
||||
$request->getParsedBody()->idle ?? false
|
||||
);
|
||||
|
||||
$result = $this->service->process($params, $serviceParams);
|
||||
|
||||
if ($result->hasResult()) {
|
||||
return (object) [
|
||||
'id' => $result->getResult()->getAttachmentId(),
|
||||
];
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'id' => $result->getAttachmentId(),
|
||||
'exportId' => $result->getId(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getActionStatus(Request $request): stdClass
|
||||
{
|
||||
$id = $request->getQueryParam('id');
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
return $this->service->getStatusData($id);
|
||||
}
|
||||
|
||||
public function postActionSubscribeToNotificationOnSuccess(Request $request, Response $response): void
|
||||
{
|
||||
$id = $request->getParsedBody()->id ?? null;
|
||||
|
||||
if (!$id || !is_string($id)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$this->service->subscribeToNotificationOnSuccess($id);
|
||||
|
||||
$response->writeBody('true');
|
||||
}
|
||||
|
||||
private function fetchRawParamsFromRequest(Request $request): Params
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
<?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\Entities;
|
||||
|
||||
use Espo\Core\ORM\Entity;
|
||||
use Espo\Core\Field\DateTime;
|
||||
use Espo\Core\Field\Link;
|
||||
|
||||
use Espo\Tools\Export\Params;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class Export extends Entity
|
||||
{
|
||||
public const ENTITY_TYPE = 'Export';
|
||||
|
||||
public const STATUS_PENDING = 'Pending';
|
||||
|
||||
public const STATUS_RUNNING = 'Running';
|
||||
|
||||
public const STATUS_SUCCESS = 'Success';
|
||||
|
||||
public const STATUS_FAILED = 'Failed';
|
||||
|
||||
public function getParams(): Params
|
||||
{
|
||||
$raw = $this->get('params');
|
||||
|
||||
if (!is_string($raw)) {
|
||||
throw new Error("No 'params'.");
|
||||
}
|
||||
|
||||
/** @var Params $params */
|
||||
$params = unserialize($raw);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
public function getStatus(): string
|
||||
{
|
||||
$value = $this->get('status');
|
||||
|
||||
if (!is_string($value)) {
|
||||
throw new Error("No 'status'.");
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getAttachmentId(): ?string
|
||||
{
|
||||
/** @var ?string */
|
||||
return $this->get('attachmentId');
|
||||
}
|
||||
|
||||
public function notifyOnFinish(): bool
|
||||
{
|
||||
return (bool) $this->get('notifyOnFinish');
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTime
|
||||
{
|
||||
$value = $this->getValueObject('createdAt');
|
||||
|
||||
if (!$value instanceof DateTime) {
|
||||
throw new Error("No 'createdAt'.");
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getCreatedBy(): Link
|
||||
{
|
||||
$value = $this->getValueObject('createdBy');
|
||||
|
||||
if (!$value instanceof Link) {
|
||||
throw new Error("No 'createdBy'.");
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function setStatus(string $status): self
|
||||
{
|
||||
$this->set('status', $status);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAttachmentId(string $attachmentId): self
|
||||
{
|
||||
$this->set('attachmentId', $attachmentId);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setNotifyOnFinish(bool $notifyOnFinish = true): self
|
||||
{
|
||||
$this->set('notifyOnFinish', $notifyOnFinish);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -291,4 +291,5 @@ return [
|
||||
'ldapPortalUserLdapAuth' => false,
|
||||
'passwordGenerateLength' => 10,
|
||||
'massActionIdleCountThreshold' => 100,
|
||||
'exportIdleCountThreshold' => 1000,
|
||||
];
|
||||
|
||||
@@ -2,12 +2,23 @@
|
||||
"fields": {
|
||||
"exportAllFields": "Export all fields",
|
||||
"fieldList": "Field List",
|
||||
"format": "Format"
|
||||
"format": "Format",
|
||||
"status": "Status"
|
||||
},
|
||||
"options": {
|
||||
"format": {
|
||||
"csv": "CSV",
|
||||
"xlsx": "XLSX (Excel)"
|
||||
},
|
||||
"status": {
|
||||
"Pending": "Pending",
|
||||
"Running": "Running",
|
||||
"Success": "Success",
|
||||
"Failed": "Failed"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"exportProcessed": "Export has been processed. Download the [file]({url}).",
|
||||
"infoText": "The export is being processed in idle by cron. It can take some time to finish. Closing this modal dialog won't affect the execution process."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +265,8 @@
|
||||
"Save & New": "Save & New",
|
||||
"Field": "Field",
|
||||
"Resolution": "Resolution",
|
||||
"Resolve Conflict": "Resolve Conflict"
|
||||
"Resolve Conflict": "Resolve Conflict",
|
||||
"Download": "Download"
|
||||
},
|
||||
"messages": {
|
||||
"pleaseWait": "Please wait...",
|
||||
|
||||
@@ -12,5 +12,8 @@
|
||||
},
|
||||
"massActions": {
|
||||
"className": "Espo\\Classes\\Cleanup\\MassActions"
|
||||
},
|
||||
"exports": {
|
||||
"className": "Espo\\Classes\\Cleanup\\Exports"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"fields": {
|
||||
"status": {
|
||||
"type": "enum",
|
||||
"options": ["Pending", "Running", "Success", "Failed"],
|
||||
"default": "Pending"
|
||||
},
|
||||
"params": {
|
||||
"type": "text"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "link",
|
||||
"required": true
|
||||
},
|
||||
"notifyOnFinish": {
|
||||
"type": "bool",
|
||||
"default": false
|
||||
},
|
||||
"attachment": {
|
||||
"type": "link",
|
||||
"entity": "Attachment"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"createdBy": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"languageIsGlobal": true
|
||||
}
|
||||
@@ -29,21 +29,38 @@
|
||||
|
||||
namespace Espo\Tools\Export;
|
||||
|
||||
use Espo\Core\{
|
||||
InjectableFactory,
|
||||
};
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
|
||||
class Factory
|
||||
{
|
||||
private $injectableFactory;
|
||||
private InjectableFactory $injectableFactory;
|
||||
|
||||
public function __construct(InjectableFactory $injectableFactory)
|
||||
private AclManager $aclManager;
|
||||
|
||||
public function __construct(InjectableFactory $injectableFactory, AclManager $aclManager)
|
||||
{
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->aclManager = $aclManager;
|
||||
}
|
||||
|
||||
public function create(): Export
|
||||
{
|
||||
return $this->injectableFactory->create(Export::class);
|
||||
}
|
||||
|
||||
public function createForUser(User $user): Export
|
||||
{
|
||||
$bindingContainer = BindingContainerBuilder::create()
|
||||
->bindInstance(User::class, $user)
|
||||
->bindInstance(Acl::class, $this->aclManager->createUserAcl($user))
|
||||
->build();
|
||||
|
||||
return $this->injectableFactory->createWithBinding(Export::class, $bindingContainer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
<?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\Jobs;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Core\Job\Job;
|
||||
use Espo\Core\Job\Job\Data as JobData;
|
||||
|
||||
use Espo\Tools\Export\Factory;
|
||||
use Espo\Tools\Export\Result;
|
||||
|
||||
use Espo\Core\Utils\Language;
|
||||
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Entities\Export as ExportEntity;
|
||||
use Espo\Entities\Notification;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class Process implements Job
|
||||
{
|
||||
private EntityManager $entityManager;
|
||||
|
||||
private Factory $factory;
|
||||
|
||||
private Language $language;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
Factory $factory,
|
||||
Language $language
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->factory = $factory;
|
||||
$this->language = $language;
|
||||
}
|
||||
|
||||
public function run(JobData $data): void
|
||||
{
|
||||
$id = $data->getTargetId();
|
||||
|
||||
if ($id === null) {
|
||||
throw new Error("ID not passed to the mass action job.");
|
||||
}
|
||||
|
||||
/** @var ExportEntity|null $entity */
|
||||
$entity = $this->entityManager->getEntity(ExportEntity::ENTITY_TYPE, $id);
|
||||
|
||||
if ($entity === null) {
|
||||
throw new Error("Export '{$id}' not found.");
|
||||
}
|
||||
|
||||
/** @var User|null $user */
|
||||
$user = $this->entityManager->getEntity(User::ENTITY_TYPE, $entity->getCreatedBy()->getId());
|
||||
|
||||
if (!$user) {
|
||||
throw new Error("Export entity '{$id}', user not found.");
|
||||
}
|
||||
|
||||
try {
|
||||
$export = $this->factory->createForUser($user);
|
||||
|
||||
$this->setRunning($entity);
|
||||
|
||||
$result = $export
|
||||
->setParams($entity->getParams())
|
||||
->run();
|
||||
}
|
||||
catch (Throwable $e) {
|
||||
$this->setFailed($entity);
|
||||
|
||||
throw new Error("Export job error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->setSuccess($entity, $result);
|
||||
|
||||
$this->entityManager->refreshEntity($entity);
|
||||
|
||||
if ($entity->notifyOnFinish()) {
|
||||
$this->notifyFinish($entity);
|
||||
}
|
||||
}
|
||||
|
||||
private function notifyFinish(ExportEntity $entity): void
|
||||
{
|
||||
/** @var Notification $notification */
|
||||
$notification = $this->entityManager->getNewEntity(Notification::ENTITY_TYPE);
|
||||
|
||||
$url = '?entryPoint=download&id=' . $entity->getAttachmentId();
|
||||
|
||||
$message = str_replace(
|
||||
'{url}',
|
||||
$url,
|
||||
$this->language->translate('exportProcessed', 'messages', 'Export')
|
||||
);
|
||||
|
||||
$notification
|
||||
->setType(Notification::TYPE_MESSAGE)
|
||||
->setMessage($message)
|
||||
->setUserId($entity->getCreatedBy()->getId());
|
||||
|
||||
$this->entityManager->saveEntity($notification);
|
||||
}
|
||||
|
||||
private function setFailed(ExportEntity $entity): void
|
||||
{
|
||||
$entity->setStatus(ExportEntity::STATUS_FAILED);
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
}
|
||||
|
||||
private function setRunning(ExportEntity $entity): void
|
||||
{
|
||||
$entity->setStatus(ExportEntity::STATUS_RUNNING);
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
}
|
||||
|
||||
private function setSuccess(ExportEntity $entity, Result $result): void
|
||||
{
|
||||
$entity
|
||||
->setStatus(ExportEntity::STATUS_SUCCESS)
|
||||
->setAttachmentId($result->getAttachmentId());
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
}
|
||||
}
|
||||
@@ -29,43 +29,60 @@
|
||||
|
||||
namespace Espo\Tools\Export;
|
||||
|
||||
use Espo\Core\{
|
||||
Exceptions\ForbiddenSilent,
|
||||
Acl,
|
||||
Acl\Table,
|
||||
Utils\Config,
|
||||
Utils\Metadata,
|
||||
};
|
||||
use Espo\Core\Exceptions\ForbiddenSilent;
|
||||
use Espo\Core\Exceptions\NotFoundSilent;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Job\JobSchedulerFactory;
|
||||
use Espo\Core\Job\Job\Data as JobData;
|
||||
|
||||
use Espo\Tools\Export\Jobs\Process;
|
||||
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Entities\Export as ExportEntity;
|
||||
use Espo\Entities\User;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class Service
|
||||
{
|
||||
private $factory;
|
||||
private Factory $factory;
|
||||
|
||||
private $config;
|
||||
private Config $config;
|
||||
|
||||
private $acl;
|
||||
private Acl $acl;
|
||||
|
||||
private $user;
|
||||
private User $user;
|
||||
|
||||
private $metadata;
|
||||
private Metadata $metadata;
|
||||
|
||||
private EntityManager $entityManager;
|
||||
|
||||
private JobSchedulerFactory $jobSchedulerFactory;
|
||||
|
||||
public function __construct(
|
||||
Factory $factory,
|
||||
Config $config,
|
||||
Acl $acl,
|
||||
User $user,
|
||||
Metadata $metadata
|
||||
Metadata $metadata,
|
||||
EntityManager $entityManager,
|
||||
JobSchedulerFactory $jobSchedulerFactory
|
||||
) {
|
||||
$this->factory = $factory;
|
||||
$this->config = $config;
|
||||
$this->acl = $acl;
|
||||
$this->user = $user;
|
||||
$this->metadata = $metadata;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->jobSchedulerFactory = $jobSchedulerFactory;
|
||||
}
|
||||
|
||||
public function process(Params $params): Result
|
||||
public function process(Params $params, ServiceParams $serviceParams): ServiceResult
|
||||
{
|
||||
if ($this->config->get('exportDisabled') && !$this->user->isAdmin()) {
|
||||
throw new ForbiddenSilent("Export disabled for non-admin users.");
|
||||
@@ -85,10 +102,76 @@ class Service
|
||||
throw new ForbiddenSilent("Export disabled for '{$entityType}'.");
|
||||
}
|
||||
|
||||
if ($serviceParams->isIdle()) {
|
||||
if ($this->user->isPortal()) {
|
||||
throw new ForbiddenSilent("Idle export is not allowed for portal users.");
|
||||
}
|
||||
|
||||
return $this->schedule($params);
|
||||
}
|
||||
|
||||
$export = $this->factory->create();
|
||||
|
||||
return $export
|
||||
$result = $export
|
||||
->setParams($params)
|
||||
->run();
|
||||
|
||||
return ServiceResult::createWithResult($result);
|
||||
}
|
||||
|
||||
public function getStatusData(string $id): stdClass
|
||||
{
|
||||
/** @var ExportEntity|null $entity */
|
||||
$entity = $this->entityManager->getEntity(ExportEntity::ENTITY_TYPE, $id);
|
||||
|
||||
if (!$entity) {
|
||||
throw new NotFoundSilent();
|
||||
}
|
||||
|
||||
if ($entity->getCreatedBy()->getId() !== $this->user->getId()) {
|
||||
throw new ForbiddenSilent();
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'status' => $entity->getStatus(),
|
||||
'attachmentId' => $entity->getAttachmentId(),
|
||||
];
|
||||
}
|
||||
|
||||
public function subscribeToNotificationOnSuccess(string $id): void
|
||||
{
|
||||
/** @var ExportEntity|null $entity */
|
||||
$entity = $this->entityManager->getEntity(ExportEntity::ENTITY_TYPE, $id);
|
||||
|
||||
if (!$entity) {
|
||||
throw new NotFoundSilent();
|
||||
}
|
||||
|
||||
if ($entity->getCreatedBy()->getId() !== $this->user->getId()) {
|
||||
throw new ForbiddenSilent();
|
||||
}
|
||||
|
||||
$entity->setNotifyOnFinish();
|
||||
|
||||
$this->entityManager->saveEntity($entity);
|
||||
}
|
||||
|
||||
private function schedule(Params $params): ServiceResult
|
||||
{
|
||||
$entity = $this->entityManager->createEntity(ExportEntity::ENTITY_TYPE, [
|
||||
'params' => serialize($params),
|
||||
]);
|
||||
|
||||
$this->jobSchedulerFactory
|
||||
->create()
|
||||
->setClassName(Process::class)
|
||||
->setData(
|
||||
JobData::create()
|
||||
->withTargetId($entity->getId())
|
||||
->withTargetType($entity->getEntityType())
|
||||
)
|
||||
->schedule();
|
||||
|
||||
return ServiceResult::createWithId($entity->getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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 ServiceParams
|
||||
{
|
||||
private bool $isIdle = false;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function isIdle(): bool
|
||||
{
|
||||
return $this->isIdle;
|
||||
}
|
||||
|
||||
public function withIsIdle(bool $isIdle = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
|
||||
$obj->isIdle = $isIdle;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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 ServiceResult
|
||||
{
|
||||
private ?Result $result = null;
|
||||
|
||||
private ?string $id = null;
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
public function hasResult(): bool
|
||||
{
|
||||
return $this->result !== null;
|
||||
}
|
||||
|
||||
public function getId(): ?string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getResult(): ?Result
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
public static function createWithId(string $id): self
|
||||
{
|
||||
$obj = new self;
|
||||
$obj->id = $id;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function createWithResult(Result $result): self
|
||||
{
|
||||
$obj = new self;
|
||||
$obj->result = $result;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="record no-side-margin">{{{record}}}</div>
|
||||
|
||||
<div class="well info-text">{{complexText infoText}}</div>
|
||||
|
||||
<div class="margin-top download-container hidden">
|
||||
<button type="button" class="btn btn-default download-button" data-action="download">{{translate 'Download'}}</button>
|
||||
</div>
|
||||
@@ -0,0 +1,74 @@
|
||||
/************************************************************************
|
||||
* 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.
|
||||
************************************************************************/
|
||||
|
||||
define('helpers/export', ['lib!espo'], function (Espo) {
|
||||
|
||||
return class {
|
||||
constructor(view) {
|
||||
this.view = view;
|
||||
|
||||
this.config = view.getConfig();
|
||||
}
|
||||
|
||||
checkIsIdle(totalCount) {
|
||||
if (this.view.getUser().isPortal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof totalCount === 'undefined') {
|
||||
totalCount = this.view.options.totalCount;
|
||||
}
|
||||
|
||||
return totalCount === -1 || totalCount > this.config.get('exportIdleCountThreshold');
|
||||
}
|
||||
|
||||
process(id) {
|
||||
Espo.Ui.notify(false);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.view
|
||||
.createView('dialog', 'views/export/modals/idle', {
|
||||
id: id,
|
||||
})
|
||||
.then(view => {
|
||||
view.render();
|
||||
|
||||
resolve(view);
|
||||
|
||||
this.view.listenToOnce(view, 'success', data => {
|
||||
resolve(data);
|
||||
|
||||
this.view.listenToOnce(view, 'close', () => {
|
||||
view.trigger('close:success', data);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,168 @@
|
||||
/************************************************************************
|
||||
* 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.
|
||||
************************************************************************/
|
||||
|
||||
define('views/export/modals/idle', ['views/modal', 'model'], function (Dep, Model) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
className: 'dialog dialog-record',
|
||||
|
||||
template: 'export/modals/idle',
|
||||
|
||||
checkInterval: 4000,
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
infoText: this.translate('infoText', 'messages', 'Export'),
|
||||
};
|
||||
},
|
||||
|
||||
events: {
|
||||
'click [data-action="download"]': function () {
|
||||
this.actionDownload();
|
||||
},
|
||||
},
|
||||
|
||||
setup: function () {
|
||||
this.action = this.options.action;
|
||||
this.id = this.options.id;
|
||||
this.status = 'Pending';
|
||||
|
||||
this.headerHtml = this.getHelper().escapeString(
|
||||
this.translate('Export')
|
||||
);
|
||||
|
||||
this.model = new Model();
|
||||
this.model.name = 'Export';
|
||||
|
||||
this.model.setDefs({
|
||||
fields: {
|
||||
'status': {
|
||||
type: 'enum',
|
||||
readOnly: true,
|
||||
options: [
|
||||
'Pending',
|
||||
'Running',
|
||||
'Success',
|
||||
'Failed',
|
||||
],
|
||||
style: {
|
||||
'Success': 'success',
|
||||
'Failed': 'danger',
|
||||
},
|
||||
},
|
||||
'attachmentId': {
|
||||
type: 'varchar',
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
this.model.set({
|
||||
status: this.status,
|
||||
processedCount: null,
|
||||
});
|
||||
|
||||
this.createView('record', 'views/record/edit-for-modal', {
|
||||
scope: 'None',
|
||||
model: this.model,
|
||||
el: this.getSelector() + ' .record',
|
||||
detailLayout: [
|
||||
{
|
||||
rows: [
|
||||
[
|
||||
{
|
||||
name: 'status',
|
||||
labelText: this.translate('status', 'fields', 'Export'),
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
this.on('close', () => {
|
||||
if (this.model.get('status') !== 'Pending') {
|
||||
return;
|
||||
}
|
||||
|
||||
Espo.Ajax.postRequest('Export/action/subscribeToNotificationOnSuccess', {
|
||||
id: this.id,
|
||||
});
|
||||
});
|
||||
|
||||
this.checkStatus();
|
||||
},
|
||||
|
||||
checkStatus: function () {
|
||||
Espo.Ajax
|
||||
.getRequest('Export/action/status', {
|
||||
id: this.id,
|
||||
})
|
||||
.then(response => {
|
||||
let status = response.status;
|
||||
|
||||
if (status === 'Pending') {
|
||||
setTimeout(() => this.checkStatus(), this.checkInterval);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.set({
|
||||
status: response.status,
|
||||
attachmentId: response.attachmentId,
|
||||
});
|
||||
|
||||
if (status === 'Success') {
|
||||
this.trigger('success', {
|
||||
attachmentId: response.attachmentId,
|
||||
});
|
||||
|
||||
this.showDownload();
|
||||
}
|
||||
|
||||
if (this.$el) {
|
||||
this.$el.find('.info-text').addClass('hidden');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showDownload: function () {
|
||||
this.$el.find('.download-container').removeClass('hidden');
|
||||
|
||||
let $download = this.$el.find('[data-action="download"]');
|
||||
|
||||
$download.removeClass('hidden');
|
||||
},
|
||||
|
||||
actionDownload: function () {
|
||||
this.trigger('download', this.model.get('attachmentId'));
|
||||
|
||||
this.close();
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -26,7 +26,10 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/record/list', ['view', 'helpers/mass-action'], function (Dep, MassActionHelper) {
|
||||
define(
|
||||
'views/record/list',
|
||||
['view', 'helpers/mass-action', 'helpers/export', 'lib!espo'],
|
||||
function (Dep, MassActionHelper, ExportHelper, Espo) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
@@ -589,7 +592,7 @@ define('views/record/list', ['view', 'helpers/mass-action'], function (Dep, Mass
|
||||
|
||||
if (this.allResultIsChecked) {
|
||||
data.where = this.collection.getWhere();
|
||||
data.searchParams = this.collection.data || {};
|
||||
data.searchParams = this.collection.data || null;
|
||||
data.searchData = this.collection.data || {}; // for bc;
|
||||
}
|
||||
else {
|
||||
@@ -618,6 +621,13 @@ define('views/record/list', ['view', 'helpers/mass-action'], function (Dep, Mass
|
||||
o.fieldList = layoutFieldList;
|
||||
}
|
||||
|
||||
let helper = new ExportHelper(this);
|
||||
let idle = this.allResultIsChecked && helper.checkIsIdle(this.collection.total);
|
||||
|
||||
let proceedDownload = (attachmentId) => {
|
||||
window.location = this.getBasePath() + '?entryPoint=download&id=' + attachmentId;
|
||||
};
|
||||
|
||||
this.createView('dialogExport', 'views/export/modals/export', o, (view) => {
|
||||
view.render();
|
||||
|
||||
@@ -627,18 +637,35 @@ define('views/record/list', ['view', 'helpers/mass-action'], function (Dep, Mass
|
||||
data.fieldList = dialogData.fieldList;
|
||||
}
|
||||
|
||||
data.idle = idle;
|
||||
data.format = dialogData.format;
|
||||
|
||||
Espo.Ui.notify(this.translate('pleaseWait', 'messages'));
|
||||
|
||||
Espo.Ajax
|
||||
.postRequest(url, data, {timeout: 0})
|
||||
.then((data) => {
|
||||
.then(response => {
|
||||
Espo.Ui.notify(false);
|
||||
|
||||
if ('id' in data) {
|
||||
window.location = this.getBasePath() + '?entryPoint=download&id=' + data.id;
|
||||
if (response.exportId) {
|
||||
helper
|
||||
.process(response.exportId)
|
||||
.then(view => {
|
||||
this.listenToOnce(view, 'download', id => {
|
||||
proceedDownload(id);
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.id) {
|
||||
throw new Error("No attachment-id.");
|
||||
}
|
||||
|
||||
window.location = this.getBasePath() + '?entryPoint=download&id=' + response.id;
|
||||
|
||||
proceedDownload(response.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user