View source
<?php
namespace Drupal\node_revision_delete;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Batch\BatchBuilder;
class NodeRevisionDelete implements NodeRevisionDeleteInterface {
use StringTranslationTrait;
protected $configFactory;
protected $configurationFileName;
protected $connection;
protected $entityTypeManager;
protected $languageManager;
public function __construct(ConfigFactoryInterface $config_factory, TranslationInterface $string_translation, Connection $connection, EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager) {
$this->configurationFileName = 'node_revision_delete.settings';
$this->configFactory = $config_factory;
$this->stringTranslation = $string_translation;
$this->connection = $connection;
$this->entityTypeManager = $entity_type_manager;
$this->languageManager = $language_manager;
}
public function updateTimeMaxNumberConfig($config_name, $max_number) {
$content_types = $this
->getConfiguredContentTypes();
foreach ($content_types as $content_type) {
$config = $this->configFactory
->getEditable('node.type.' . $content_type
->id());
$third_party_settings = $config
->get('third_party_settings');
if ($max_number < $third_party_settings['node_revision_delete'][$config_name]) {
$third_party_settings['node_revision_delete'][$config_name] = $max_number;
$config
->set('third_party_settings', $third_party_settings)
->save();
}
}
}
public function getConfiguredContentTypes() {
$configured_content_types = [];
$content_types = $this->entityTypeManager
->getStorage('node_type')
->loadMultiple();
foreach ($content_types as $content_type) {
$third_party_settings = $this->configFactory
->get('node.type.' . $content_type
->id())
->get('third_party_settings');
if (isset($third_party_settings['node_revision_delete'])) {
$configured_content_types[] = $content_type;
}
}
return $configured_content_types;
}
public function getTimeString($config_name, $number) {
$config_name_time = $this->configFactory
->get($this->configurationFileName)
->get('node_revision_delete_' . $config_name . '_time');
$time = $this
->getTimeNumberString($number, $config_name_time['time']);
$result = '';
switch ($config_name) {
case 'minimum_age_to_delete':
$result = $number . ' ' . $time;
break;
case 'when_to_delete':
$result = $this
->t('After @number @time of inactivity', [
'@number' => $number,
'@time' => $time,
]);
break;
}
return $result;
}
public function getTimeNumberString($number, $time) {
$time_options = [
'days' => [
'singular' => $this
->t('day'),
'plural' => $this
->t('days'),
],
'weeks' => [
'singular' => $this
->t('week'),
'plural' => $this
->t('weeks'),
],
'months' => [
'singular' => $this
->t('month'),
'plural' => $this
->t('months'),
],
];
return $number == 1 ? $time_options[$time]['singular'] : $time_options[$time]['plural'];
}
public function saveContentTypeConfig($content_type, $minimum_revisions_to_keep, $minimum_age_to_delete, $when_to_delete) {
$config = $this->configFactory
->getEditable('node.type.' . $content_type);
$third_party_settings = $config
->get('third_party_settings');
$third_party_settings['node_revision_delete'] = [
'minimum_revisions_to_keep' => $minimum_revisions_to_keep,
'minimum_age_to_delete' => $minimum_age_to_delete,
'when_to_delete' => $when_to_delete,
];
$config
->set('third_party_settings', $third_party_settings)
->save();
}
public function deleteContentTypeConfig($content_type) {
$config = $this->configFactory
->getEditable('node.type.' . $content_type);
$third_party_settings = $config
->get('third_party_settings');
if (isset($third_party_settings['node_revision_delete'])) {
unset($third_party_settings['node_revision_delete']);
$config
->set('third_party_settings', $third_party_settings)
->save();
}
}
public function getTimeValues($index = NULL) {
$options_node_revision_delete_time = [
'-1' => $this
->t('Never'),
'0' => $this
->t('Every time cron runs'),
'3600' => $this
->t('Every hour'),
'86400' => $this
->t('Everyday'),
'604800' => $this
->t('Every week'),
'864000' => $this
->t('Every 10 days'),
'1296000' => $this
->t('Every 15 days'),
'2592000' => $this
->t('Every month'),
'7776000' => $this
->t('Every 3 months'),
'15552000' => $this
->t('Every 6 months'),
'31536000' => $this
->t('Every year'),
'63072000' => $this
->t('Every 2 years'),
];
if (isset($index) && isset($options_node_revision_delete_time[$index])) {
return $options_node_revision_delete_time[$index];
}
else {
return $options_node_revision_delete_time;
}
}
public function getPreviousRevisions($nid, $currently_deleted_revision_id) {
$node_storage = $this->entityTypeManager
->getStorage('node');
$node = $this->entityTypeManager
->getStorage('node')
->load($nid);
$langcode = $this->languageManager
->getCurrentLanguage()
->getId();
$revision_ids = $node_storage
->revisionIds($node);
$revision_ids = array_combine($revision_ids, $revision_ids);
$revision_ids[$currently_deleted_revision_id] = $currently_deleted_revision_id;
$revisions_before = [];
if (count($revision_ids) > 1) {
krsort($revision_ids);
$revision_ids = array_slice($revision_ids, array_search($currently_deleted_revision_id, array_keys($revision_ids)) + 1, NULL, TRUE);
foreach ($revision_ids as $vid) {
$revision = $node_storage
->loadRevision($vid);
if ($revision
->hasTranslation($langcode) && $revision
->getTranslation($langcode)
->isRevisionTranslationAffected()) {
$revisions_before[] = $revision;
}
}
}
return $revisions_before;
}
public function getCandidatesRevisionsByNumber($number) {
if (!is_int($number) && $number < 0) {
throw new \InvalidArgumentException("\$number parameter must be a positive integer");
}
$content_types = $this
->getConfiguredContentTypes();
$revisions = [];
foreach ($content_types as $content_type) {
$revisions = array_merge($revisions, $this
->getCandidatesRevisions($content_type
->id(), $number));
if ($number < count($revisions)) {
$revisions = array_slice($revisions, 0, $number, TRUE);
break;
}
}
return $revisions;
}
public function getCandidatesRevisions($content_type, $number = PHP_INT_MAX) {
if (!is_int($number) && $number < 0) {
throw new \InvalidArgumentException("\$number parameter must be a positive integer");
}
$candidate_revisions = [];
$content_type_config = $this
->getContentTypeConfigWithRelativeTime($content_type);
if (!empty($content_type_config)) {
$candidate_nodes = $this
->getCandidatesNodes($content_type);
foreach ($candidate_nodes as $candidate_node) {
$sub_query = $this->connection
->select('node_field_data', 'n');
$sub_query
->join('node_revision', 'r', 'r.nid = n.nid');
$sub_query
->fields('r', [
'vid',
'revision_timestamp',
]);
$sub_query
->condition('n.nid', $candidate_node);
$sub_query
->condition('changed', $content_type_config['when_to_delete'], '<');
if ($this->configFactory
->get($this->configurationFileName)
->get('delete_newer')) {
$sub_query
->where('n.vid <> r.vid');
}
else {
$sub_query
->where('n.vid > r.vid');
}
$sub_query
->groupBy('n.nid');
$sub_query
->groupBy('r.vid');
$sub_query
->groupBy('r.revision_timestamp');
$sub_query
->orderBy('revision_timestamp', 'DESC');
$sub_query
->range($content_type_config['minimum_revisions_to_keep'] - 1, $number);
$sub_query
->addTag('node_revision_delete_candidate_revisions');
$sub_query
->addTag('node_revision_delete_candidate_revisions_' . $content_type);
$query = $this->connection
->select($sub_query, 't');
$query
->fields('t', [
'vid',
]);
$query
->condition('revision_timestamp', $content_type_config['minimum_age_to_delete'], '<');
$candidate_revisions = array_merge($candidate_revisions, $query
->execute()
->fetchCol());
}
}
return $candidate_revisions;
}
public function getCandidatesRevisionsByNids(array $nids) {
$candidate_revisions = [];
if (empty($nids)) {
return $candidate_revisions;
}
$node = $this->entityTypeManager
->getStorage('node')
->load(current($nids));
$content_type = $node
->getType();
$content_type_config = $this
->getContentTypeConfigWithRelativeTime($content_type);
if (!empty($content_type_config)) {
$sub_query = $this->connection
->select('node_field_data', 'n');
$sub_query
->join('node_revision', 'r', 'r.nid = n.nid');
$sub_query
->fields('r', [
'vid',
'revision_timestamp',
]);
$sub_query
->condition('n.nid', $nids, 'IN');
$sub_query
->condition('changed', $content_type_config['when_to_delete'], '<');
if ($this->configFactory
->get($this->configurationFileName)
->get('delete_newer')) {
$sub_query
->where('n.vid <> r.vid');
}
else {
$sub_query
->where('n.vid > r.vid');
}
$sub_query
->groupBy('n.nid');
$sub_query
->groupBy('r.vid');
$sub_query
->groupBy('revision_timestamp');
$sub_query
->orderBy('revision_timestamp', 'DESC');
$sub_query
->range($content_type_config['minimum_revisions_to_keep'] - 1, PHP_INT_MAX);
$sub_query
->addTag('node_revision_delete_candidate_revisions');
$sub_query
->addTag('node_revision_delete_candidate_revisions_' . $content_type);
$query = $this->connection
->select($sub_query, 't');
$query
->fields('t', [
'vid',
]);
$query
->condition('revision_timestamp', $content_type_config['minimum_age_to_delete'], '<');
$candidate_revisions = array_merge($candidate_revisions, $query
->execute()
->fetchCol());
}
return $candidate_revisions;
}
public function getContentTypeConfigWithRelativeTime($content_type) {
$content_type_config = $this
->getContentTypeConfig($content_type);
if (!empty($content_type_config)) {
$content_type_config['minimum_age_to_delete'] = $this
->getRelativeTime('minimum_age_to_delete', $content_type_config['minimum_age_to_delete']);
$content_type_config['when_to_delete'] = $this
->getRelativeTime('when_to_delete', $content_type_config['when_to_delete']);
}
return $content_type_config;
}
public function getContentTypeConfig($content_type) {
$third_party_settings = $this->configFactory
->get('node.type.' . $content_type)
->get('third_party_settings');
if (isset($third_party_settings['node_revision_delete'])) {
return $third_party_settings['node_revision_delete'];
}
return [];
}
public function getRelativeTime($config_name, $number) {
$time_interval = $this->configFactory
->get($this->configurationFileName)
->get('node_revision_delete_' . $config_name . '_time')['time'];
$time = strtotime('-' . $number . ' ' . $time_interval);
return $time;
}
public function getCandidatesNodes($content_type) {
$result = [];
$content_type_config = $this
->getContentTypeConfigWithRelativeTime($content_type);
if (!empty($content_type_config)) {
$query = $this->connection
->select('node_field_data', 'n');
$query
->join('node_revision', 'r', 'r.nid = n.nid');
$query
->fields('n', [
'nid',
]);
$query
->addExpression('COUNT(*)', 'total');
$query
->condition('type', $content_type);
$query
->condition('revision_timestamp', $content_type_config['minimum_age_to_delete'], '<');
$query
->condition('changed', $content_type_config['when_to_delete'], '<');
$query
->groupBy('n.nid');
$query
->having('COUNT(*) > ' . $content_type_config['minimum_revisions_to_keep']);
$query
->addTag('node_revision_delete_candidates');
$query
->addTag('node_revision_delete_candidates_' . $content_type);
$result = $query
->execute()
->fetchCol();
}
return $result;
}
public function getRevisionDeletionBatch(array $revisions, $dry_run) {
$batch_builder = new BatchBuilder();
$batch_builder
->setTitle($this
->t('Deleting revisions'))
->setInitMessage($this
->t('Starting to delete revisions.'))
->setProgressMessage($this
->t('Deleted @current out of @total (@percentage%). Estimated time: @estimate.'))
->setErrorMessage($this
->t('Error deleting revisions.'))
->setFinishCallback([
NodeRevisionDeleteBatch::class,
'finish',
]);
foreach ($revisions as $revision) {
$batch_builder
->addOperation([
NodeRevisionDeleteBatch::class,
'deleteRevision',
], [
$revision,
$dry_run,
]);
}
return $batch_builder
->toArray();
}
}