diff --git a/application/Espo/Classes/Jobs/Cleanup.php b/application/Espo/Classes/Jobs/Cleanup.php index dbbd1f78fa..97ad0d2371 100644 --- a/application/Espo/Classes/Jobs/Cleanup.php +++ b/application/Espo/Classes/Jobs/Cleanup.php @@ -81,6 +81,9 @@ class Cleanup implements JobDataLess private string $cleanupBackupPeriod = '2 month'; private string $cleanupDeletedRecordsPeriod = '2 months'; + private const LIMIT = 5000; + private const SCHEDULED_JOB_LOG_LIMIT = 10; + public function __construct( private Config $config, private EntityManager $entityManager, @@ -171,30 +174,24 @@ class Cleanup implements JobDataLess private function cleanupScheduledJobLog(): void { - /** @var iterable $scheduledJobList */ $scheduledJobList = $this->entityManager - ->getRDBRepository(ScheduledJob::ENTITY_TYPE) + ->getRDBRepositoryByClass(ScheduledJob::class) ->select([Attribute::ID]) ->find(); foreach ($scheduledJobList as $scheduledJob) { $scheduledJobId = $scheduledJob->getId(); - /** @var iterable $ignoreLogRecordList */ $ignoreLogRecordList = $this->entityManager - ->getRDBRepository(ScheduledJobLogRecord::ENTITY_TYPE) + ->getRDBRepositoryByClass(ScheduledJobLogRecord::class) ->select([Attribute::ID]) ->where([ 'scheduledJobId' => $scheduledJobId, ]) ->order(Field::CREATED_AT, 'DESC') - ->limit(0, 10) + ->limit(0, self::SCHEDULED_JOB_LOG_LIMIT) ->find(); - if (!is_countable($ignoreLogRecordList)) { - continue; - } - if (!count($ignoreLogRecordList)) { continue; } @@ -287,191 +284,15 @@ class Cleanup implements JobDataLess private function cleanupAttachments(): void { $period = '-' . $this->config->get('cleanupAttachmentsPeriod', $this->cleanupAttachmentsPeriod); - $datetime = $this->createDateTimeFromPeriod($period); - $collection = $this->entityManager - ->getRDBRepository(Attachment::ENTITY_TYPE) - ->sth() - ->where([ - 'OR' => [ - [ - 'role' => [ - Attachment::ROLE_EXPORT_FILE, - 'Mail Merge', - 'Mass Pdf', - ] - ] - ], - 'createdAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT), - ]) - ->limit(0, 5000) - ->find(); - - foreach ($collection as $entity) { - $this->entityManager->removeEntity($entity); - } - - if ($this->config->get('cleanupOrphanAttachments')) { - try { - $orphanQueryBuilder = $this->selectBuilderFactory - ->create() - ->from(Attachment::ENTITY_TYPE) - ->withPrimaryFilter('orphan') - ->buildQueryBuilder(); - } catch (BadRequest|Forbidden $e) { - throw new RuntimeException('', 0, $e); - } - - $orphanQueryBuilder->where([ - 'createdAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT), - 'createdAt>' => '2018-01-01 00:00:00', - ]); - - $collection = $this->entityManager - ->getRDBRepository(Attachment::ENTITY_TYPE) - ->clone($orphanQueryBuilder->build()) - ->sth() - ->limit(0, 5000) - ->find(); - - foreach ($collection as $entity) { - $this->entityManager->removeEntity($entity); - } - } - - $fromPeriod = '-' . $this->config->get('cleanupAttachmentsFromPeriod', $this->cleanupAttachmentsFromPeriod); - - $datetimeFrom = $this->createDateTimeFromPeriod($fromPeriod); - - /** @var string[] $scopeList */ - $scopeList = array_keys($this->metadata->get(['scopes'])); - - foreach ($scopeList as $scope) { - if (!$this->metadata->get(['scopes', $scope, 'entity'])) { - continue; - } - - if (!$this->metadata->get(['scopes', $scope, 'object']) && $scope !== Note::ENTITY_TYPE) { - continue; - } - - if (!$this->metadata->get(['entityDefs', $scope, 'fields', Field::MODIFIED_AT])) { - continue; - } - - $hasAttachmentField = false; - - if ($scope === Note::ENTITY_TYPE) { - $hasAttachmentField = true; - } - - if (!$hasAttachmentField) { - foreach ($this->metadata->get(['entityDefs', $scope, 'fields']) as $defs) { - if (empty($defs['type'])) { - continue; - } - - if ( - in_array($defs['type'], [ - FieldType::FILE, - FieldType::IMAGE, - FieldType::ATTACHMENT_MULTIPLE, - ]) - ) { - $hasAttachmentField = true; - - break; - } - } - } - - if (!$hasAttachmentField) { - continue; - } - - if (!$this->entityManager->hasRepository($scope)) { - continue; - } - - $repository = $this->entityManager->getRepository($scope); - - if (!method_exists($repository, 'find')) { - continue; - } - - if (!method_exists($repository, 'clone')) { - continue; - } - - $query = $this->entityManager - ->getQueryBuilder() - ->select(['id']) - ->from($scope) - ->withDeleted() - ->where([ - Attribute::DELETED => true, - 'modifiedAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT), - 'modifiedAt>' => $datetimeFrom->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT), - ]) - ->build(); - - $deletedEntities = $repository - ->clone($query) - ->sth() - ->find(); - - foreach ($deletedEntities as $deletedEntity) { - $attachmentToRemoveList = $this->entityManager - ->getRDBRepository(Attachment::ENTITY_TYPE) - ->sth() - ->where([ - 'OR' => [ - [ - 'relatedType' => $scope, - 'relatedId' => $deletedEntity->getId(), - ], - [ - 'parentType' => $scope, - 'parentId' => $deletedEntity->getId(), - ] - ] - ]) - ->find(); - - foreach ($attachmentToRemoveList as $attachmentToRemove) { - $this->entityManager->removeEntity($attachmentToRemove); - } - } - } - - $isBeingUploadedCollection = $this->entityManager - ->getRDBRepository(Attachment::ENTITY_TYPE) - ->sth() - ->where([ - 'isBeingUploaded' => true, - 'createdAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT), - ]) - ->find(); - - foreach ($isBeingUploadedCollection as $e) { - $this->entityManager->removeEntity($e); - } - - $delete = $this->entityManager - ->getQueryBuilder() - ->delete() - ->from(Attachment::ENTITY_TYPE) - ->where([ - Attribute::DELETED => true, - 'createdAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT), - ]) - ->build(); - - $this->entityManager->getQueryExecutor()->execute($delete); + $this->deleteTemporaryAttachments($datetime); + $this->deleteOrphanAttachments($datetime); + $this->deleteRelatedToDeletedAttachments($datetime); + $this->deleteBeingUploadedAttachments($datetime); + $this->fullDeleteDeletedAttachments($datetime); } - private function cleanupNotifications(): void { $period = '-' . $this->config->get('cleanupNotificationsPeriod', $this->cleanupNotificationsPeriod); @@ -775,4 +596,201 @@ class Cleanup implements JobDataLess ->deleteFromDb($arrayValue->getId()); } } + + private function deleteOrphanAttachments(DateTime $datetime): void + { + if (!$this->config->get('cleanupOrphanAttachments')) { + return; + } + + try { + $orphanQueryBuilder = $this->selectBuilderFactory + ->create() + ->from(Attachment::ENTITY_TYPE) + ->withPrimaryFilter('orphan') + ->buildQueryBuilder(); + } catch (BadRequest|Forbidden $e) { + throw new RuntimeException('', 0, $e); + } + + $orphanQueryBuilder->where([ + 'createdAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT), + 'createdAt>' => '2018-01-01 00:00:00', + ]); + + $collection = $this->entityManager + ->getRDBRepository(Attachment::ENTITY_TYPE) + ->clone($orphanQueryBuilder->build()) + ->sth() + ->limit(0, self::LIMIT) + ->find(); + + foreach ($collection as $entity) { + $this->entityManager->removeEntity($entity); + } + } + + private function deleteTemporaryAttachments(DateTime $datetime): void + { + $collection = $this->entityManager + ->getRDBRepository(Attachment::ENTITY_TYPE) + ->sth() + ->where([ + 'OR' => [ + [ + 'role' => [ + Attachment::ROLE_EXPORT_FILE, + 'Mail Merge', + 'Mass Pdf', + ] + ] + ], + 'createdAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT), + ]) + ->limit(0, self::LIMIT) + ->find(); + + foreach ($collection as $entity) { + $this->entityManager->removeEntity($entity); + } + } + + private function deleteBeingUploadedAttachments(DateTime $datetime): void + { + $isBeingUploadedCollection = $this->entityManager + ->getRDBRepository(Attachment::ENTITY_TYPE) + ->sth() + ->where([ + 'isBeingUploaded' => true, + 'createdAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT), + ]) + ->find(); + + foreach ($isBeingUploadedCollection as $e) { + $this->entityManager->removeEntity($e); + } + } + + private function deleteRelatedToDeletedAttachments(DateTime $datetime): void + { + $fromPeriod = '-' . $this->config->get('cleanupAttachmentsFromPeriod', $this->cleanupAttachmentsFromPeriod); + + $datetimeFrom = $this->createDateTimeFromPeriod($fromPeriod); + + /** @var string[] $scopeList */ + $scopeList = array_keys($this->metadata->get(['scopes'])); + + foreach ($scopeList as $scope) { + if (!$this->metadata->get(['scopes', $scope, 'entity'])) { + continue; + } + + if (!$this->metadata->get(['scopes', $scope, 'object']) && $scope !== Note::ENTITY_TYPE) { + continue; + } + + if (!$this->metadata->get(['entityDefs', $scope, 'fields', Field::MODIFIED_AT])) { + continue; + } + + $hasAttachmentField = false; + + if ($scope === Note::ENTITY_TYPE) { + $hasAttachmentField = true; + } + + if (!$hasAttachmentField) { + foreach ($this->metadata->get(['entityDefs', $scope, 'fields']) as $defs) { + if (empty($defs['type'])) { + continue; + } + + if ( + in_array($defs['type'], [ + FieldType::FILE, + FieldType::IMAGE, + FieldType::ATTACHMENT_MULTIPLE, + ]) + ) { + $hasAttachmentField = true; + + break; + } + } + } + + if (!$hasAttachmentField) { + continue; + } + + if (!$this->entityManager->hasRepository($scope)) { + continue; + } + + $repository = $this->entityManager->getRepository($scope); + + if (!method_exists($repository, 'find')) { + continue; + } + + if (!method_exists($repository, 'clone')) { + continue; + } + + $query = $this->entityManager + ->getQueryBuilder() + ->select(['id']) + ->from($scope) + ->withDeleted() + ->where([ + Attribute::DELETED => true, + 'modifiedAt<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT), + 'modifiedAt>' => $datetimeFrom->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT), + ]) + ->build(); + + $deletedEntities = $repository + ->clone($query) + ->sth() + ->find(); + + foreach ($deletedEntities as $deletedEntity) { + $attachmentToRemoveList = $this->entityManager + ->getRDBRepository(Attachment::ENTITY_TYPE) + ->sth() + ->where([ + 'OR' => [ + [ + 'relatedType' => $scope, + 'relatedId' => $deletedEntity->getId(), + ], + [ + 'parentType' => $scope, + 'parentId' => $deletedEntity->getId(), + ] + ] + ]) + ->find(); + + foreach ($attachmentToRemoveList as $attachmentToRemove) { + $this->entityManager->removeEntity($attachmentToRemove); + } + } + } + } + + private function fullDeleteDeletedAttachments(DateTime $datetime): void + { + $delete = $this->entityManager + ->getQueryBuilder() + ->delete() + ->from(Attachment::ENTITY_TYPE) + ->where([ + Attribute::DELETED => true, + Field::CREATED_AT . '<' => $datetime->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT), + ]) + ->build(); + + $this->entityManager->getQueryExecutor()->execute($delete); + } }