View source
<?php
namespace Drupal\entity_reference_revisions;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\StringTranslation\StringTranslationTrait;
class EntityReferenceRevisionsOrphanPurger {
use StringTranslationTrait;
const PARENT_VALID = 0;
const PARENT_INVALID_SKIP = 1;
const PARENT_INVALID_DELETE = 2;
protected $entityTypeManager;
protected $entityFieldManager;
protected $dateFormatter;
protected $time;
protected $database;
protected $messenger;
protected $validParents = [];
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, DateFormatterInterface $date_formatter, TimeInterface $time, Connection $database, MessengerInterface $messenger) {
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
$this->dateFormatter = $date_formatter;
$this->time = $time;
$this->database = $database;
$this->messenger = $messenger;
}
public function deleteUnusedRevision(ContentEntityInterface $composite_revision) {
$composite_storage = $this->entityTypeManager
->getStorage($composite_revision
->getEntityTypeId());
if ($composite_revision
->isDefaultRevision()) {
$count = $composite_storage
->getQuery()
->accessCheck(FALSE)
->allRevisions()
->condition($composite_storage
->getEntityType()
->getKey('id'), $composite_revision
->id())
->count()
->execute();
if ($count <= 1) {
$composite_revision
->delete();
return TRUE;
}
}
else {
$composite_storage
->deleteRevision($composite_revision
->getRevisionId());
return TRUE;
}
return FALSE;
}
public function deleteOrphansBatchOperation($entity_type_id, &$context) {
$composite_type = $this->entityTypeManager
->getDefinition($entity_type_id);
$composite_revision_key = $composite_type
->getKey('revision');
$composite_storage = $this->entityTypeManager
->getStorage($entity_type_id);
$batch_size = Settings::get('entity_update_batch_size', 50);
if (empty($context['sandbox']['total'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['current_revision_id'] = -1;
$context['sandbox']['total'] = (int) $composite_storage
->getQuery()
->allRevisions()
->accessCheck(FALSE)
->count()
->execute();
}
if (!isset($context['results'][$entity_type_id])) {
$context['results'][$entity_type_id]['entity_count'] = 0;
$context['results'][$entity_type_id]['revision_count'] = 0;
$context['results'][$entity_type_id]['start'] = $this->time
->getRequestTime();
}
$revision_table = $composite_type
->getRevisionTable();
$entity_revision_ids = $this->database
->select($revision_table, 'r')
->fields('r', [
$composite_revision_key,
])
->range(0, $batch_size)
->orderBy($composite_revision_key)
->condition($composite_revision_key, $context['sandbox']['current_revision_id'], '>')
->execute()
->fetchCol();
foreach ($composite_storage
->loadMultipleRevisions($entity_revision_ids) as $composite_revision) {
$context['sandbox']['progress']++;
$context['sandbox']['current_revision_id'] = $composite_revision
->getRevisionId();
if ($this
->isUsed($composite_revision)) {
continue;
}
if ($this
->deleteUnusedRevision($composite_revision)) {
$context['results'][$entity_type_id]['revision_count']++;
if ($composite_revision
->isDefaultRevision()) {
$context['results'][$entity_type_id]['entity_count']++;
}
}
}
if ($entity_revision_ids && $context['sandbox']['progress'] < $context['sandbox']['total']) {
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['total'];
}
else {
$context['finished'] = 1;
$context['results'][$entity_type_id]['end'] = $this->time
->getRequestTime();
}
$interval = $this->dateFormatter
->formatInterval($this->time
->getRequestTime() - $context['results'][$entity_type_id]['start']);
$context['message'] = t('Checked @entity_type revisions for orphans: @current of @total in @interval (@deletions deleted)', [
'@entity_type' => $composite_type
->getLabel(),
'@current' => $context['sandbox']['progress'],
'@total' => $context['sandbox']['total'],
'@interval' => $interval,
'@deletions' => $context['results'][$entity_type_id]['revision_count'],
]);
}
public static function batchSubmitFinished($success, $results, $operations) {
return \Drupal::service('entity_reference_revisions.orphan_purger')
->doBatchSubmitFinished($success, $results, $operations);
}
public function setBatch(array $composite_entity_type_ids) {
if (empty($composite_entity_type_ids)) {
return;
}
$operations = [];
foreach ($composite_entity_type_ids as $entity_type_id) {
$operations[] = [
'_entity_reference_revisions_orphan_purger_batch_dispatcher',
[
'entity_reference_revisions.orphan_purger:deleteOrphansBatchOperation',
$entity_type_id,
],
];
}
$batch = [
'operations' => $operations,
'finished' => [
EntityReferenceRevisionsOrphanPurger::class,
'batchSubmitFinished',
],
'title' => $this
->t('Removing orphaned entities.'),
'progress_message' => $this
->t('Processed @current of @total entity types.'),
'error_message' => $this
->t('This batch encountered an error.'),
];
batch_set($batch);
}
public function doBatchSubmitFinished($success, $results, $operations) {
if ($success) {
foreach ($results as $entity_type_id => $result) {
if ($this->entityTypeManager
->hasDefinition($entity_type_id)) {
$entity_type = $this->entityTypeManager
->getDefinition($entity_type_id);
$interval = $this->dateFormatter
->formatInterval($result['end'] - $result['start']);
$this->messenger
->addMessage($this
->t('@label: Deleted @revision_count revisions (@entity_count entities) in @interval.', [
'@label' => $entity_type
->getLabel(),
'@revision_count' => $result['revision_count'],
'@entity_count' => $result['entity_count'],
'@interval' => $interval,
]));
}
}
}
else {
$error_operation = reset($operations);
$this->messenger
->addError($this
->t('An error occurred while processing @operation with arguments : @args', [
'@operation' => $error_operation[0],
'@args' => print_r($error_operation[0], TRUE),
]));
}
}
public function isUsed(ContentEntityInterface $composite_revision) {
$composite_type = $this->entityTypeManager
->getDefinition($composite_revision
->getEntityTypeId());
$parent_type_field = $composite_type
->get('entity_revision_parent_type_field');
$parent_type = $composite_revision
->get($parent_type_field)->value;
$parent_field_name_field = $composite_type
->get('entity_revision_parent_field_name_field');
$parent_field_name = $composite_revision
->get($parent_field_name_field)->value;
$status = $this
->isValidParent($parent_type, $parent_field_name);
if ($status !== static::PARENT_VALID) {
return $status == static::PARENT_INVALID_SKIP ? TRUE : FALSE;
}
$query = $this->entityTypeManager
->getStorage($parent_type)
->getQuery()
->condition("{$parent_field_name}.target_revision_id", $composite_revision
->getRevisionId())
->range(0, 1)
->accessCheck(FALSE);
if ($this->entityTypeManager
->getDefinition($parent_type)
->isRevisionable()) {
$query = $query
->allRevisions();
}
$revisions = $query
->execute();
return !empty($revisions);
}
protected function isValidParent($parent_type, $parent_field_name) {
if ($parent_type == NULL) {
return static::PARENT_INVALID_SKIP;
}
if (isset($this->validParents[$parent_type][$parent_field_name])) {
return $this->validParents[$parent_type][$parent_field_name];
}
$status = static::PARENT_VALID;
if (!$this->entityTypeManager
->hasDefinition($parent_type)) {
$status = static::PARENT_INVALID_DELETE;
}
else {
$parent_field_definitions = $this->entityFieldManager
->getFieldStorageDefinitions($parent_type);
if (!isset($parent_field_definitions[$parent_field_name])) {
$status = static::PARENT_INVALID_DELETE;
}
elseif (empty($parent_field_definitions[$parent_field_name]
->getSchema()['columns']['target_revision_id'])) {
$status = static::PARENT_INVALID_SKIP;
}
}
$this->validParents[$parent_type][$parent_field_name] = $status;
return $status;
}
public function getCompositeEntityTypes() {
$composite_entity_types = [];
$entity_types = $this->entityTypeManager
->getDefinitions();
foreach ($entity_types as $entity_type) {
$has_parent_type_field = $entity_type
->get('entity_revision_parent_type_field');
$has_parent_id_field = $entity_type
->get('entity_revision_parent_id_field');
$has_parent_field_name_field = $entity_type
->get('entity_revision_parent_field_name_field');
if ($has_parent_type_field && $has_parent_id_field && $has_parent_field_name_field) {
$composite_entity_types[] = $entity_type;
}
}
return $composite_entity_types;
}
}