- Issue created by @solideogloria
I improved the workaround so that it gets the file fields first and only loops through those. This should improve the performance.
If a file is used by a node's revisions, but not the current revision, the file is never marked temporary or removed when the node is deleted.
$config['file.settings']['make_unused_managed_files_temporary'] = TRUE;
in settings.phpExpectation: the file status should be Temporary
, and the "Used in" column for the file should be 0
.
Note: When deleting individual revisions of a node with a file on /node/NID/revisions, the usage of the file does get updated properly, and upon deleting the last revision that used the file, the file will be marked Temporary.
This could be added to file.module. This is a generic solution that works for me.
Replace MODULE
with the actual module name in both the function name and in the line $file_usage->delete($file, 'MODULE', $entity->getEntityTypeId(), $entity->id(), 0);
. Note that if you use this as a workaround, either apply a patch with Composer or use a custom module, don't modify core/contrib module files manually.
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\RevisionableStorageInterface;
use Drupal\file\Entity\File;
use Drupal\file\Plugin\Field\FieldType\FileFieldItemList;
use Drupal\file\Plugin\Field\FieldType\FileItem;
/**
* Implements hook_entity_predelete().
*/
function MODULE_entity_predelete(EntityInterface $entity) {
$entity_type_manager = \Drupal::entityTypeManager();
$entity_type_id = $entity->getEntityTypeId();
$entity_type = $entity_type_manager->getDefinition($entity_type_id);
// Check if the entity type supports revisions and has fields.
if (!$entity_type->isRevisionable() || !($entity instanceof FieldableEntityInterface)) {
return;
}
// Get the fields that have files.
$file_fields = [];
foreach ($entity->getFields() as $field_name => $field_list) {
if ($field_list instanceof FileFieldItemList) {
$file_fields[$field_name] = $field_name;
continue;
}
// Not sure if checking the field items is necessary.
$field_item = $field_list->first();
if ($field_item instanceof FileItem) {
$file_fields[$field_name] = $field_name;
}
}
if (count($file_fields) === 0) {
return;
}
$entity_storage = $entity_type_manager->getStorage($entity_type_id);
if ($entity_storage instanceof RevisionableStorageInterface) {
// Load all revision IDs of the entity.
$revision_ids = $entity_storage
->getQuery()
->allRevisions()
->condition($entity_type->getKey('id'), $entity->id())
->accessCheck(FALSE)
->execute();
// Loop through each revision and get the referenced files.
$fids = [];
foreach (array_keys($revision_ids) as $revision_id) {
/** @var \Drupal\Core\Entity\FieldableEntityInterface&\Drupal\Core\Entity\RevisionableInterface|null $revision */
$revision = $entity_storage->loadRevision($revision_id);
if ($revision === NULL) {
continue;
}
$revision_fields = $revision->getFields();
// Iterate over all file fields of the entity getting all file IDs.
foreach ($file_fields as $field_name) {
// Iterate over all values of the field.
foreach ($revision_fields[$field_name] as $field) {
// Ensure the field is an instance of FileItem.
if ($field instanceof FileItem) {
if ($field->target_id !== NULL) {
$fids[$field->target_id] = $field->target_id;
}
}
}
}
}
}
// Delete the file usage for this entity's files for all revisions.
if ($fids) {
// This could be done in chunks, if desired.
$files = File::loadMultiple($fids);
/** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */
$file_usage = \Drupal::service('file.usage');
foreach ($files as $file) {
$file_usage->delete($file, 'MODULE', $entity->getEntityTypeId(), $entity->id(), 0);
}
}
Active
11.0 🔥
I improved the workaround so that it gets the file fields first and only loops through those. This should improve the performance.