Skip to content
Snippets Groups Projects

Resolve #3002332 "Track paragraphs on host"

Files
12
<?php
namespace Drupal\entity_usage\Plugin\EntityUsage\Track;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\entity_usage\EntityUpdateManagerInterface;
use Drupal\entity_usage\EntityUsageTrackBase;
use Drupal\entity_usage\EntityUsageTrackMultipleLoadInterface;
use Drupal\entity_usage\ParagraphInheritedUsageUpdater;
use Drupal\paragraphs\ParagraphInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Tracks usage of entities referenced in referenced paragraphs.
*
* @EntityUsageTrack(
* id = "paragraph_inherited",
* label = @Translation("Paragraph (inherited)"),
* description = @Translation("Tracks entity relationships found inside direct and nested paragraphs as if they belong to the (outer) host entity."),
* field_types = {
* "entity_reference_revisions",
* },
* source_entity_class = "Drupal\Core\Entity\FieldableEntityInterface",
* )
*/
class ParagraphInherited extends EntityUsageTrackBase implements EntityUsageTrackMultipleLoadInterface {
/**
* The entity update manager.
*/
protected EntityUpdateManagerInterface $entityUpdateManager;
/**
* The paragraph inherited usage updated.
*/
protected ParagraphInheritedUsageUpdater $paragraphInheritedUsageUpdater;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
$tracker = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$tracker->entityUpdateManager = $container->get('entity_usage.entity_update_manager');
$tracker->paragraphInheritedUsageUpdater = $container->get('entity_usage.paragraph_inherited_usage_updater');
return $tracker;
}
/**
* {@inheritdoc}
*/
public function getTargetEntities(FieldItemInterface $item): array {
$parent = $item->getParent();
return $parent instanceof FieldItemListInterface
? $this->doGetTargetEntities($parent, $item)
: [];
}
/**
* {@inheritdoc}
*/
public function getTargetEntitiesFromField(FieldItemListInterface $field): array {
return $this->doGetTargetEntities($field);
}
/**
* Gets the target entities.
*
* @param \Drupal\Core\Field\FieldItemListInterface<\Drupal\Core\Field\FieldItemInterface> $field
* The field.
* @param \Drupal\Core\Field\FieldItemInterface|null $field_item
* (optional) The field item.
*
* @return array<int, string|array{target_type: string, target_id: string|int, field_name?: string, method?: string}>
* An indexed array where each value can be a string or an array:
* - if a string: the target entity type and ID concatenated with a "|"
* character.
* - if an array: an array with the target_type and target_id keys, and
* optionally the method and field_name keys.
* Will return an empty array if no target entities could be retrieved from
* the received field item value.
*/
private function doGetTargetEntities(FieldItemListInterface $field, ?FieldItemInterface $field_item = NULL): array {
$target_entities = [];
$target_type_id = $field->getFieldDefinition()->getSetting('target_type');
if ($target_type_id !== 'paragraph') {
return $target_entities;
}
/** @var \Drupal\Core\Entity\EntityInterface $parent_entity */
$parent_entity = $field->getParent()->getValue();
/** @var \Drupal\paragraphs\ParagraphInterface[] $referenced_paragraphs */
$referenced_paragraphs = $field_item instanceof FieldItemInterface
? array_filter([$field_item->entity ?? NULL])
: $field->referencedEntities();
foreach ($referenced_paragraphs as $referenced_paragraph) {
// @todo Is this logic correct?
if ($referenced_paragraph instanceof TranslatableInterface
&& $referenced_paragraph->language()->getId() !== $parent_entity->language()->getId()
&& $referenced_paragraph->hasTranslation($parent_entity->language()->getId())
) {
$referenced_paragraph = $referenced_paragraph->getTranslation($parent_entity->language()->getId());
}
foreach ($this->entityUpdateManager->getEnabledPlugins() as $plugin) {
$trackable_field_types = $plugin->getApplicableFieldTypes();
$fields = array_keys($this->getReferencingFields($referenced_paragraph, $trackable_field_types));
foreach ($fields as $field_name) {
if (!$referenced_paragraph->hasField($field_name) || $referenced_paragraph->{$field_name}->isEmpty()) {
continue;
}
$field_target_entities = [];
try {
if ($plugin instanceof EntityUsageTrackMultipleLoadInterface) {
$field_target_entities = $plugin->getTargetEntitiesFromField($referenced_paragraph->{$field_name});
}
else {
/** @var \Drupal\Core\Field\FieldItemInterface $field_item */
foreach ($referenced_paragraph->get($field_name) as $field_item) {
$field_target_entities = $plugin->getTargetEntities($field_item);
}
}
}
catch (\Exception $e) {
$this->logTrackingException($e, $referenced_paragraph, $field_name);
continue;
}
foreach ($field_target_entities as $field_target_entity) {
$target_entities[] = $this->normalizeTargetEntity($field_target_entity) + [
'method' => "{$this->getPluginId()}|{$plugin->getPluginId()}",
'field_name' => "{$referenced_paragraph->getRevisionId()}:{$field_name}",
];
}
}
}
// Since this paragraph has now been processed, we can safely remove it
// from the usage updater queue. This prevents the usage tracking from
// happening multiple times (for example, when a user saves a paragraph
// through the paragraphs field widget).
$this
->paragraphInheritedUsageUpdater
->removeEntityFromQueue($referenced_paragraph);
}
foreach ($target_entities as &$target_entity) {
$field_name = $field->getName();
if ($parent_entity instanceof ParagraphInterface) {
$field_name = "{$parent_entity->getRevisionId()}:{$field_name}";
}
$target_entity['field_name'] = "$field_name|{$target_entity['field_name']}";
}
return $target_entities;
}
}
Loading