contentserialize-8.x-1.x-dev/modules/vcsnormalizer/src/Normalizer/LinkNormalizer.php
modules/vcsnormalizer/src/Normalizer/LinkNormalizer.php
<?php
namespace Drupal\vcsnormalizer\Normalizer;
use Drupal\Component\Uuid\Uuid;
use Drupal\contentserialize\Event\ImportEvents;
use Drupal\contentserialize\Event\MissingReferenceEvent;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\link\Plugin\Field\FieldType\LinkItem;
use Drupal\menu_link_content\MenuLinkContentInterface;
use Drupal\serialization\Normalizer\NormalizerBase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Normalizes link field items preserving node links.
*/
class LinkNormalizer extends NormalizerBase implements DenormalizerInterface {
/**
* {@inheritdoc}
*/
protected $supportedInterfaceOrClass = LinkItem::class;
/**
* The field item normalizer service.
*
* It's contained rather than extended to avoid backwards-compatibility
* problems.
*
* @var \Symfony\Component\Serializer\Normalizer\NormalizerInterface|\Symfony\Component\Serializer\Normalizer\DenormalizerInterface
*/
protected $fieldItemNormalizer;
/**
* The node storage provider if available.
*
* @var \Drupal\node\NodeStorageInterface|null
*/
protected $nodeStorage;
/**
* The event dispatcher service.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* Create the link normalizer.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service.
* @param \Symfony\Component\Serializer\Normalizer\NormalizerInterface $field_item_normalizer
* The field item normalizer service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher service.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, NormalizerInterface $field_item_normalizer, EventDispatcherInterface $event_dispatcher) {
// The object just acts as a transparent proxy if nodes aren't available.
if ($entity_type_manager->getDefinition('node', FALSE)) {
$this->nodeStorage = $entity_type_manager->getStorage('node');
}
$this->fieldItemNormalizer = $field_item_normalizer;
$this->eventDispatcher = $event_dispatcher;
}
/**
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []) {
$data = $this->fieldItemNormalizer->normalize($object, $format, $context);
// Don't do anything special if the node module isn't installed.
if ($this->nodeStorage) {
$data = $this->replaceLinkId($data, '\d+', function ($nid) {
$node = $this->nodeStorage->load($nid);
if (!$node) {
throw new UnexpectedValueException("Menu link content has link to non-existant node $nid");
}
return $node->uuid();
});
}
return $data;
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = NULL, array $context = []) {
if (!isset($context['target_instance'])) {
throw new InvalidArgumentException('$context[\'target_instance\'] must be set to denormalize with the FieldItemNormalizer');
}
if ($context['target_instance']->getParent() == NULL) {
throw new InvalidArgumentException('The field item passed in via $context[\'target_instance\'] must have a parent set.');
}
// Don't do anything special if the node module isn't installed.
if ($this->nodeStorage) {
/** @var \Drupal\link\LinkItemInterface $field_item */
$field_item = $context['target_instance'];
$parent = $field_item->getParent()->getParent()->getValue();
$data = $this->replaceLinkId($data, Uuid::VALID_PATTERN, function ($uuid) use ($parent, $context) {
$results = $this->nodeStorage->getQuery()
->condition('uuid', $uuid)
->execute();
if (!$results) {
// If it's not found dispatch an event and return an empty value.
if (($parent instanceof ContentEntityInterface)) {
$event = new MissingReferenceEvent(
$parent->getEntityTypeId(),
$parent->uuid(),
'node',
$uuid,
function (MenuLinkContentInterface $entity, $target_id, $target_vid) {
$entity->link = 'entity:node/' . $target_id;
},
$context
);
$this->eventDispatcher->dispatch(ImportEvents::MISSING_REFERENCE, $event);
}
return '';
}
return current($results);
});
}
return $this->fieldItemNormalizer->denormalize($data, $class, $format, $context);
}
/**
* Replace the ID in a link to a node.
*
* @param array $data
* The normalized array for the menu_link_content entity.
* @param $pattern_fragment
* A regular expression fragment that will match the ID.
* @param callable $callback
* A callable that accepts the ID currently in the link and returns the new
* ID to be used.
*
* @return array
* The updated normalized array for the menu_link_content_entity.
*/
protected function replaceLinkId(array $data, $pattern_fragment, callable $callback) {
$uri = preg_replace_callback('~^(entity:node/)(' . $pattern_fragment . ')$~', function ($matches) use ($callback) {
return $matches[1] . $callback($matches[2]);
}, $data['uri']);
if ($uri == 'entity:node/') {
// @todo Log this?
$uri = 'route:<nolink>';
}
$data['uri'] = $uri;
return $data;
}
}
