cms_content_sync-3.0.x-dev/src/Plugin/cms_content_sync/field_handler/DefaultLinkHandler.php
src/Plugin/cms_content_sync/field_handler/DefaultLinkHandler.php
<?php
namespace Drupal\cms_content_sync\Plugin\cms_content_sync\field_handler;
use Drupal\cms_content_sync\Plugin\FieldHandlerBase;
use Drupal\cms_content_sync\PullIntent;
use Drupal\cms_content_sync\PushIntent;
use Drupal\cms_content_sync\SyncIntent;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\StreamWrapper\PublicStream;
use Drupal\Core\Url;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use EdgeBox\SyncCore\Interfaces\Configuration\IDefineEntityType;
use EdgeBox\SyncCore\Interfaces\Configuration\IDefineObjectProperty;
/**
* Providing a minimalistic implementation for any field type.
*
* @FieldHandler(
* id = "cms_content_sync_default_link_handler",
* label = @Translation("Default Link"),
* weight = 90
* )
*/
class DefaultLinkHandler extends FieldHandlerBase {
/**
* {@inheritdoc}
*/
public static function supports($entity_type, $bundle, $field_name, FieldDefinitionInterface $field) {
$allowed = ['link'];
return FALSE !== in_array($field->getType(), $allowed);
}
/**
* {@inheritdoc}
*/
public function getHandlerSettings($current_values, $type = 'both') {
$options = [];
if ('pull' !== $type) {
$options['export_as_absolute_url'] = [
'#type' => 'checkbox',
'#title' => 'Push as absolute URL',
'#default_value' => $current_values['export_as_absolute_url'] ?? FALSE,
];
}
return array_merge(parent::getHandlerSettings($current_values, $type), $options);
}
/**
* {@inheritdoc}
*/
public function pull(PullIntent $intent) {
$action = $intent->getAction();
/**
* @var \Drupal\Core\Entity\FieldableEntityInterface $entity
*/
$entity = $intent->getEntity();
// Deletion doesn't require any action on field basis for static data.
if (SyncIntent::ACTION_DELETE == $action) {
return FALSE;
}
if ($intent->shouldMergeChanges()) {
return FALSE;
}
$data = $intent->getProperty($this->fieldName);
$status = $intent->getEntityStatus();
$language = $entity instanceof TranslatableInterface ? $entity->language()->getId() : 'und';
$status->resetMissingReferences($language, $this->fieldName);
if (empty($data)) {
$entity->set($this->fieldName, NULL);
}
else {
$result = [];
$base_path = '/' . PublicStream::basePath() . '/';
foreach ($data as &$link_element) {
if (empty($link_element['uri'])) {
try {
$reference = $intent->loadEmbeddedEntity($link_element);
}
catch (\Exception $e) {
$reference = NULL;
}
$reference_data = $intent->getEmbeddedEntityData($link_element);
$reference_meta = $intent->loadReference($link_element);
if ($reference) {
if (empty($reference_data['file_uri'])) {
if (($reference_data['uri_format'] ?? NULL) === 'relative_entity') {
$uri = 'internal:/' . $reference->getEntityTypeId() . '/' . $reference->id();
}
else {
$uri = 'entity:' . $reference->getEntityTypeId() . '/' . $reference->id();
}
$result[] = [
'uri' => $uri,
'title' => $reference_data['title'],
'options' => $reference_data['options'] ?? [],
];
}
else {
$url = $base_path . substr($reference_data['file_uri'], 9);
$result[] = [
'uri' => 'internal:' . $url . (empty($reference_data['uri_anchor']) ? '' : '#' . $reference_data['uri_anchor']),
'title' => $reference_data['title'],
'options' => $reference_data['options'] ?? [],
];
}
}
elseif ($reference_meta->getType() && $reference_meta->getBundle()) {
// Menu items are created before the node as they are embedded
// entities. For the link to work however the node must already
// exist which won't work. So instead we're creating a temporary
// uri that uses the entity UUID instead of it's ID. Once the node
// is pulled it will look for this link and replace it with the
// now available entity reference by ID.
if ($entity instanceof MenuLinkContent && 'link' == $this->fieldName) {
$result[] = [
'uri' => 'internal:/',
'title' => $reference_data['title'],
'options' => $reference_data['options'] ?? [],
];
}
else {
// Shortcut: If it's just one value and a normal entity_reference field, the MissingDependencyManager will
// directly update the field value of the entity and save it. Otherwise it will request a full pull of the
// entity. So this saves some performance for simple references.
if ('entity_reference' === $this->fieldDefinition->getType() && !$this->fieldDefinition->getFieldStorageDefinition()->isMultiple()) {
$intent->saveUnresolvedDependency($link_element, $this->fieldName);
}
else {
$intent->saveUnresolvedDependency($link_element);
}
$status->addMissingReference($language, $this->fieldName, $link_element);
}
}
}
else {
$result[] = [
'uri' => $link_element['uri'],
'title' => $link_element['title'],
'options' => $link_element['options'],
];
}
}
$entity->set($this->fieldName, $result);
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function push(PushIntent $intent) {
$action = $intent->getAction();
/**
* @var \Drupal\Core\Entity\FieldableEntityInterface $entity
*/
$entity = $intent->getEntity();
// Deletion doesn't require any action on field basis for static data.
if (SyncIntent::ACTION_DELETE == $action) {
return FALSE;
}
$data = $entity->get($this->fieldName)->getValue();
$absolute = !empty($this->settings['handler_settings']['export_as_absolute_url']);
$result = [];
$base_path = '/' . PublicStream::basePath();
foreach ($data as $key => $value) {
if (empty($data[$key]['uri'])) {
continue;
}
$uri = &$data[$key]['uri'];
// Find the linked entity and replace it's id with the UUID
// References have following pattern: entity:entity_type/entity_id.
preg_match('/^entity:(.*)\/(\d*)$/', $uri, $found);
$meta_data = [];
$link_entity = NULL;
if (empty($found)) {
if (substr($uri, 0, strlen($base_path) + 10) === ('internal:' . $base_path . '/')) {
// PDF files can have a #page=... anchor attached that we want to keep.
$parts = explode('#', substr($uri, 10 + strlen($base_path)));
$path = $parts[0];
$anchor = count($parts) > 1 ? $parts[1] : '';
$file_uri = 'public://' . urldecode($path);
$files = \Drupal::entityTypeManager()
->getStorage('file')
->loadByProperties(['uri' => $file_uri]);
if (count($files)) {
$link_entity = reset($files);
$meta_data['file_uri'] = $file_uri;
if ($anchor) {
$meta_data['uri_anchor'] = $anchor;
}
}
$meta_data['uri_format'] = 'relative_file';
}
elseif (preg_match('@^internal:/(node)/([0-9]+)(#.*)?$@', $uri, $internal_route_found)) {
$link_entity = \Drupal::entityTypeManager()->getStorage($internal_route_found[1])->load($internal_route_found[2]);
$meta_data['uri_format'] = 'relative_entity';
if (!empty($internal_route_found[3]) && strlen($internal_route_found[3]) > 1) {
$meta_data['uri_anchor'] = substr($internal_route_found[3], 1);
}
}
}
else {
$meta_data['uri_format'] = 'entity_reference';
}
if ((empty($link_entity) && empty($found)) || $absolute) {
if ($absolute) {
$uri = Url::fromUri($uri, ['absolute' => TRUE])->toString();
}
$result[] = [
'uri' => $uri,
'title' => $value['title'] ?? NULL,
'options' => $value['options'],
];
}
else {
if (empty($link_entity)) {
$link_entity_type = $found[1];
$link_entity_id = $found[2];
$entity_manager = \Drupal::entityTypeManager();
$link_entity = $entity_manager->getStorage($link_entity_type)
->load($link_entity_id);
if (empty($link_entity)) {
continue;
}
}
$details = array_merge([
'title' => $value['title'],
'options' => $value['options'],
], $meta_data);
if ($intent->getFlow()->getController()->canPushEntity($link_entity, PushIntent::PUSH_AS_DEPENDENCY)) {
$reference = $intent->embed($link_entity, $details);
}
else {
try {
$view_url = $link_entity->toUrl('canonical', [
'absolute' => TRUE,
// Workaround for PathProcessorAlias::processOutbound to explicitly ignore us
// as we always want the pure, unaliased e.g. /node/:id path because
// we don't use the URL for end-users but for editors and it has to
// be reliable (aliases can be removed or change).
'alias' => TRUE,
] + ($entity instanceof TranslatableInterface ? ['language' => $entity->language()] : []))->toString();
}
catch (\Exception $e) {
$view_url = Url::fromUri($uri, ['absolute' => TRUE])->toString();
}
$reference = $intent->addReference($link_entity, $details, $view_url);
}
$reference['entity'] = $reference;
$result[] = $reference;
}
}
$intent->setProperty($this->fieldName, $result);
return TRUE;
}
/**
* {@inheritDoc}
*/
public function definePropertyAtType(IDefineEntityType $type_definition) {
$property = parent::definePropertyAtType($type_definition);
if (!$property || !($property instanceof IDefineObjectProperty)) {
return $property;
}
$property->addReferenceProperty('entity', 'Entity', FALSE, FALSE, 'entity_reference');
return $property;
}
}
