I've noticed that Paragraph entities are not deleted from the database. They rather unlink from the parent node.
This is noticeable if you create a view, that lists Paragraphs and attach a contextual filter, that filters by Parent ID
.
For now, I've found a workaround, to create a view that lists Content. Attach relationship to a Paragraph. This way it ensures that only linked paragraphs are displayed.
There is still an issue of having hundreds of orphan Paragraphs and field data in the database. Is there a way of cleaning them?
EDIT: This is apparently a major bug and could be found in Paragraph module's issue tracker: Removed paragraph entities are not deleted from database
Now that I've figured out that this is a bug and it is not fixed yet, my main goal is to just clean orphan Paragraphs.
Someone created this module: Paragraph clean, but I'm not a fan of using modules for such purposes.
So, below I will post my first successful attempt to solve it. I must warn this isn't safe, because it deletes Paragraphs!
The solution is not tested for using revisions, content translation, etc. So this might ruin your day. Backup your site.
Using Devel
module, go to Development
> Execute PHP Code
. Paste and execute following code:
// get all paragraphs
$deleted = [];
$paragraph_ids = \Drupal::entityQuery('paragraph')->execute();
$paragraphs = \Drupal::entityTypeManager()->getStorage('paragraph')->loadMultiple($paragraph_ids);
foreach ($paragraphs as $target_id => $paragraph) {
// get parent entity (node, taxonomy, paragraph, etc.)
$parent = $paragraph->getParentEntity();
$field_name = $paragraph->parent_field_name->value;
// Check if current paragraph exists in parent entity field values
$exists = FALSE;
$values = is_null($parent) ? [] : $parent->get($field_name)->getValue();
foreach($values as $value) {
if ($value['target_id'] == $target_id) {
$exists = TRUE;
}
}
// Delete paragraphs that aren't linked to an entity they claim as a parent
if (!$exists) {
$paragraph->delete();
$deleted[] = $target_id;
}
}
print "Deleted paragraph IDs: " . implode(', ', $deleted);
We created a module called paragraphs_clean that implements hook_entity_update() (be careful: module name must be equal to the prefix of the entity_update() hook function) taking inspiration from here: https://www.drupal.org/project/paragraphs/issues/2741937#comment-13181377.
As it is stated, it seem to work well even if there are revisions and translations. Also we had to adapt the original php code to function in the case when paragraphs have paragraphs, too (i.e. nested paragraphs).
This code is more efficient than the above as it will only load orphan paragraphs of the entity from the context.
Here is the code:
<?php
/**
* @file
* Paragraphs clean module.
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\field\Entity\FieldConfig;
/**
* Implements hook_entity_update().
*
* When form updates, delete any paragraph entities that were removed.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
*/
function paragraphs_clean_entity_update(EntityInterface $entity) {
// Only act on content entities.
if (!($entity instanceof FieldableEntityInterface)) {
return;
}
$fieldManager = \Drupal::service('entity_field.manager');
$parentEntities = $fieldManager->getFieldMapByFieldType('entity_reference_revisions');
if (!array_key_exists($entity->getEntityTypeId(), $parentEntities)) {
return;
}
$paragraph_definitions = [];
// loop through all paragraph types
foreach ($parentEntities[$entity->getEntityTypeId()] as $field_id => $settings) {
if ($configField = FieldConfig::loadByName($entity->getEntityTypeId(), $entity->bundle(), $field_id)) {
$paragraph_definitions[] = $configField;
}
}
if (empty($paragraph_definitions)) {
return;
}
foreach ($paragraph_definitions as $paragraph_definition) {
//get entity type name to make it work with any kind of parent entity (node, paragraph, etc.)
$entityTypeName = $entity->getEntityTypeId();
// Remove orphaned paragraphs.
$query = \Drupal::database()->select('paragraphs_item_field_data', 'pfd')
->fields('pfd', ['id'])
->condition('pfd.parent_type', $entityTypeName)
->condition('pfd.parent_id', $entity->id())
->condition('pfd.parent_field_name', $paragraph_definition->getName());
$query->addJoin('left', $entityTypeName.'__'.$paragraph_definition->getName(),'nt','pfd.id=nt.'.$paragraph_definition->getName().'_target_id');
$query->isNull('nt.'.$paragraph_definition->getName().'_target_id');
$query->distinct();
$paragraph_ids = $query->execute()->fetchCol();
if ($paragraph_ids) {
$para_storage = \Drupal::entityTypeManager()->getStorage('paragraph');
foreach ($paragraph_ids as $paragraph_id) {
if ($para = $para_storage->load($paragraph_id)) {
$para->delete();
drupal_set_message(t('Paragraph of type "%type" has been deleted: %id', ['%id' => $paragraph_id, '%type' => $paragraph_definition->getName()]));
}
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With