View source
<?php
namespace Drupal\cms_content_sync\Plugin;
use Drupal\cms_content_sync\Entity\EntityStatus;
use Drupal\cms_content_sync\Entity\Flow;
use Drupal\cms_content_sync\Event\BeforeEntityPull;
use Drupal\cms_content_sync\Event\BeforeEntityPush;
use Drupal\cms_content_sync\Exception\SyncException;
use Drupal\cms_content_sync\Plugin\Type\EntityHandlerPluginManager;
use Drupal\cms_content_sync\PullIntent;
use Drupal\cms_content_sync\PushIntent;
use Drupal\cms_content_sync\SyncIntent;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\RevisionableEntityBundleInterface;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Render\RenderContext;
use Drupal\crop\Entity\Crop;
use Drupal\menu_link_content\Plugin\Menu\MenuLinkContent;
use Drupal\node\NodeInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
abstract class EntityHandlerBase extends PluginBase implements ContainerFactoryPluginInterface, EntityHandlerInterface {
public const USER_PROPERTY = null;
public const USER_REVISION_PROPERTY = null;
public const REVISION_TRANSLATION_AFFECTED_PROPERTY = null;
protected $logger;
protected $entityTypeName;
protected $bundleName;
protected $settings;
protected $flow;
public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerInterface $logger) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->logger = $logger;
$this->entityTypeName = $configuration['entity_type_name'];
$this->bundleName = $configuration['bundle_name'];
$this->settings = $configuration['settings'];
$this->flow = $configuration['sync'];
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('logger.factory')
->get('cms_content_sync'));
}
public function getAllowedPushOptions() {
return [
PushIntent::PUSH_DISABLED,
PushIntent::PUSH_AUTOMATICALLY,
PushIntent::PUSH_AS_DEPENDENCY,
PushIntent::PUSH_MANUALLY,
];
}
public function getAllowedPullOptions() {
return [
PullIntent::PULL_DISABLED,
PullIntent::PULL_MANUALLY,
PullIntent::PULL_AUTOMATICALLY,
PullIntent::PULL_AS_DEPENDENCY,
];
}
public function updateEntityTypeDefinition(&$definition) {
}
public function getHandlerSettings($current_values, $type = 'both') {
$options = [];
$no_menu_link_push = [
'brick',
'field_collection_item',
'menu_link_content',
'paragraph',
];
if (!in_array($this->entityTypeName, $no_menu_link_push) && 'pull' !== $type) {
$options['export_menu_items'] = [
'#type' => 'checkbox',
'#title' => 'Push menu items',
'#default_value' => isset($current_values['export_menu_items']) && 0 === $current_values['export_menu_items'] ? 0 : 1,
];
}
return $options;
}
public function validateHandlerSettings(array &$form, FormStateInterface $form_state, $settings_key, $current_values) {
}
public function pull(PullIntent $intent) {
$action = $intent
->getAction();
if ($this
->ignorePull($intent)) {
return false;
}
$entity = $intent
->getEntity();
if (SyncIntent::ACTION_DELETE == $action) {
if ($entity) {
return $this
->deleteEntity($entity);
}
if ($intent
->getEntityStatus()
->isDeleted()) {
return true;
}
return false;
}
if ($entity) {
if ($bundle_entity_type = $entity
->getEntityType()
->getBundleEntityType()) {
$bundle_entity_type = \Drupal::entityTypeManager()
->getStorage($bundle_entity_type)
->load($entity
->bundle());
if ($bundle_entity_type instanceof RevisionableEntityBundleInterface && $bundle_entity_type
->shouldCreateNewRevision() || 'field_collection' == $bundle_entity_type
->getEntityTypeId()) {
$entity
->setNewRevision(true);
}
}
}
else {
$entity = $this
->createNew($intent);
if (!$entity) {
throw new SyncException(SyncException::CODE_ENTITY_API_FAILURE);
}
$intent
->setEntity($entity);
}
if ($entity instanceof FieldableEntityInterface && !$this
->setEntityValues($intent)) {
return false;
}
\Drupal::service('event_dispatcher')
->dispatch(BeforeEntityPull::EVENT_NAME, new BeforeEntityPull($entity, $intent));
return true;
}
public function getForbiddenFields() {
$entity_type_entity = \Drupal::service('entity_type.manager')
->getStorage($this->entityTypeName)
->getEntityType();
return [
$entity_type_entity
->getKey('id'),
$entity_type_entity
->getKey('revision'),
$entity_type_entity
->getKey('bundle'),
$entity_type_entity
->getKey('uuid'),
$entity_type_entity
->getKey('label'),
'revision_default',
'revision_translation_affected',
'content_translation_outdated',
];
}
public function push(PushIntent $intent, EntityInterface $entity = null) {
if ($this
->ignorePush($intent)) {
return false;
}
if (!$entity) {
$entity = $intent
->getEntity();
}
$intent
->getOperation()
->setName($entity
->label(), $intent
->getActiveLanguage());
if ($this
->pushReferencedMenuItems()) {
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
$menu_items = $menu_link_manager
->loadLinksByRoute('entity.' . $this->entityTypeName . '.canonical', [
$this->entityTypeName => $entity
->id(),
]);
$values = [];
$form_values = _cms_content_sync_submit_cache($entity
->getEntityTypeId(), $entity
->uuid());
foreach ($menu_items as $menu_item) {
if (!$menu_item instanceof MenuLinkContent) {
continue;
}
$item = \Drupal::service('entity.repository')
->loadEntityByUuid('menu_link_content', $menu_item
->getDerivativeId());
if (!$item) {
continue;
}
if (isset($form_values['menu']) && $form_values['menu']['id'] == 'menu_link_content:' . $item
->uuid()) {
if (!$form_values['menu']['enabled']) {
continue;
}
}
$details = [];
$details['enabled'] = $item
->get('enabled')->value;
$values[] = $intent
->addDependency($item, $details);
}
$intent
->setProperty('menu_items', $values);
}
$view_mode = $this->flow
->getPreviewType($entity
->getEntityTypeId(), $entity
->bundle());
if (Flow::PREVIEW_DISABLED != $view_mode) {
$entityTypeManager = \Drupal::entityTypeManager();
$view_builder = $entityTypeManager
->getViewBuilder($this->entityTypeName);
$preview = $view_builder
->view($entity, $view_mode);
$rendered = \Drupal::service('renderer');
$html = $rendered
->executeInRenderContext(new RenderContext(), function () use ($rendered, $preview) {
return $rendered
->render($preview);
});
$intent
->getOperation()
->setPreviewHtml($html, $intent
->getActiveLanguage());
}
else {
$intent
->getOperation()
->setPreviewHtml('<em>Previews are disabled for this entity.</em>', $intent
->getActiveLanguage());
}
$this
->setSourceUrl($intent, $entity);
if ($entity instanceof FieldableEntityInterface) {
$entityFieldManager = \Drupal::service('entity_field.manager');
$type = $entity
->getEntityTypeId();
$bundle = $entity
->bundle();
$field_definitions = $entityFieldManager
->getFieldDefinitions($type, $bundle);
foreach ($field_definitions as $key => $field) {
$handler = $this->flow
->getFieldHandler($type, $bundle, $key);
if (!$handler) {
continue;
}
$handler
->push($intent);
}
}
if (!$intent
->getActiveLanguage() && $this
->isEntityTypeTranslatable($entity)) {
$languages = array_keys($entity
->getTranslationLanguages(false));
foreach ($languages as $language) {
$intent
->changeTranslationLanguage($language);
$translation = $entity
->getTranslation($language);
$this
->push($intent, $translation);
}
$intent
->changeTranslationLanguage();
}
\Drupal::service('event_dispatcher')
->dispatch(BeforeEntityPush::EVENT_NAME, new BeforeEntityPush($entity, $intent));
return true;
}
protected function pushReferencedMenuItems() {
if (!isset($this->settings['handler_settings']['export_menu_items'])) {
return true;
}
return 0 !== $this->settings['handler_settings']['export_menu_items'];
}
protected function ignorePull(PullIntent $intent) {
$reason = $intent
->getReason();
$action = $intent
->getAction();
if (PullIntent::PULL_AUTOMATICALLY == $reason) {
if (PullIntent::PULL_MANUALLY == $this->settings['import']) {
if (PullIntent::PULL_AUTOMATICALLY != $reason || PullIntent::PULL_MANUALLY != $this->settings['import'] || SyncIntent::ACTION_CREATE == $action) {
return true;
}
}
}
if (SyncIntent::ACTION_UPDATE == $action) {
$behavior = $this->settings['import_updates'];
if (PullIntent::PULL_UPDATE_IGNORE == $behavior) {
return true;
}
}
return false;
}
protected function hasLabelProperty() {
return true;
}
protected function createNew(PullIntent $intent) {
$entity_type = \Drupal::entityTypeManager()
->getDefinition($intent
->getEntityType());
$base_data = [];
if (EntityHandlerPluginManager::isEntityTypeConfiguration($intent
->getEntityType())) {
$base_data['id'] = $intent
->getId();
}
if ($this
->hasLabelProperty()) {
$base_data[$entity_type
->getKey('label')] = $intent
->getOperation()
->getName();
}
$base_data[$entity_type
->getKey('bundle')] = $intent
->getBundle();
$base_data[$entity_type
->getKey('uuid')] = $intent
->getUuid();
if ($entity_type
->getKey('langcode')) {
$base_data[$entity_type
->getKey('langcode')] = $intent
->getProperty($entity_type
->getKey('langcode'));
}
$storage = \Drupal::entityTypeManager()
->getStorage($intent
->getEntityType());
return $storage
->create($base_data);
}
protected function deleteEntity(EntityInterface $entity) {
try {
$entity
->delete();
} catch (\Exception $e) {
throw new SyncException(SyncException::CODE_ENTITY_API_FAILURE, $e);
}
return true;
}
protected function saveEntity($entity, $intent) {
$entity
->save();
}
protected function setEntityValues(PullIntent $intent, FieldableEntityInterface $entity = null) {
if (!$entity) {
$entity = $intent
->getEntity();
}
$entityFieldManager = \Drupal::service('entity_field.manager');
$type = $entity
->getEntityTypeId();
$bundle = $entity
->bundle();
$field_definitions = $entityFieldManager
->getFieldDefinitions($type, $bundle);
$entity_type = \Drupal::entityTypeManager()
->getDefinition($intent
->getEntityType());
$label = $entity_type
->getKey('label');
if ($label && !$intent
->shouldMergeChanges() && $this
->hasLabelProperty()) {
$entity
->set($label, $intent
->getOperation()
->getName($intent
->getActiveLanguage()));
}
$static_fields = $this
->getStaticFields();
$is_translatable = $this
->isEntityTypeTranslatable($entity);
$is_translation = boolval($intent
->getActiveLanguage());
$user = \Drupal::currentUser();
if (static::USER_PROPERTY && $entity
->hasField(static::USER_PROPERTY) && !$intent
->getEntityStatus()
->isOverriddenLocally()) {
$entity
->set(static::USER_PROPERTY, [
'target_id' => $user
->id(),
]);
}
if (static::USER_REVISION_PROPERTY && $entity
->hasField(static::USER_REVISION_PROPERTY)) {
$entity
->set(static::USER_REVISION_PROPERTY, [
'target_id' => $user
->id(),
]);
}
if (static::REVISION_TRANSLATION_AFFECTED_PROPERTY && $entity
->hasField(static::REVISION_TRANSLATION_AFFECTED_PROPERTY)) {
$entity
->set(static::REVISION_TRANSLATION_AFFECTED_PROPERTY, 1);
}
foreach ($field_definitions as $key => $field) {
$handler = $this->flow
->getFieldHandler($type, $bundle, $key);
if (!$handler) {
continue;
}
if (in_array($key, $static_fields) && SyncIntent::ACTION_CREATE != $intent
->getAction()) {
continue;
}
if ($is_translatable && $is_translation && !$field
->isTranslatable()) {
continue;
}
if ('image' == $field
->getType() || 'file' == $field
->getType()) {
$data = $intent
->getProperty($key);
foreach ($data as &$value) {
$file = $intent
->loadEmbeddedEntity($value);
if ($file) {
if ('image' == $field
->getType()) {
$moduleHandler = \Drupal::service('module_handler');
if ($moduleHandler
->moduleExists('crop') && $moduleHandler
->moduleExists('focal_point')) {
$crop = Crop::findCrop($file
->getFileUri(), 'focal_point');
if ($crop) {
$position = $crop
->position();
$size = getimagesize($file
->getFileUri());
$value['focal_point'] = $position['x'] / $size[0] * 100 . ',' . $position['y'] / $size[1] * 100;
}
}
}
}
}
$intent
->overwriteProperty($key, $data);
continue;
}
$handler
->pull($intent);
}
if (PullIntent::PULL_UPDATE_UNPUBLISHED === $this->flow
->getEntityTypeConfig($this->entityTypeName, $this->bundleName)['import_updates']) {
if ($entity instanceof NodeInterface) {
if ($entity
->id()) {
$entity
->isDefaultRevision(false);
}
else {
$entity
->setPublished(false);
}
}
}
try {
$this
->saveEntity($entity, $intent);
} catch (\Exception $e) {
throw new SyncException(SyncException::CODE_ENTITY_API_FAILURE, $e);
}
$changed = false;
foreach ($field_definitions as $key => $field) {
$handler = $this->flow
->getFieldHandler($type, $bundle, $key);
if (!$handler) {
continue;
}
if (in_array($key, $static_fields) && SyncIntent::ACTION_CREATE != $intent
->getAction()) {
continue;
}
if ($is_translatable && $is_translation && !$field
->isTranslatable()) {
continue;
}
if ('image' != $field
->getType() && 'file' != $field
->getType()) {
continue;
}
$handler
->pull($intent);
$changed = true;
}
if (!$intent
->getActiveLanguage()) {
$created = $intent
->getProperty('created');
if ($created && method_exists($entity, 'getCreatedTime') && method_exists($entity, 'setCreatedTime')) {
if ($created !== $entity
->getCreatedTime()) {
$entity
->setCreatedTime($created);
$changed = true;
}
}
if ($entity instanceof EntityChangedInterface) {
$entity
->setChangedTime(time());
$changed = true;
}
}
if ($changed) {
try {
$this
->saveEntity($entity, $intent);
} catch (\Exception $e) {
throw new SyncException(SyncException::CODE_ENTITY_API_FAILURE, $e);
}
}
if ($is_translatable && !$intent
->getActiveLanguage()) {
$languages = $intent
->getTranslationLanguages();
foreach ($languages as $language) {
if ($entity
->hasTranslation($language)) {
$translation = $entity
->getTranslation($language);
}
else {
$translation = $entity
->addTranslation($language);
}
$intent
->changeTranslationLanguage($language);
if (!$this
->ignorePull($intent)) {
$this
->setEntityValues($intent, $translation);
}
}
if (boolval($this->settings['import_deletion_settings']['import_deletion'])) {
$existing = $entity
->getTranslationLanguages(false);
foreach ($existing as &$language) {
$language = $language
->getId();
}
$languages = array_diff($existing, $languages);
if (count($languages)) {
foreach ($languages as $language) {
$entity
->removeTranslation($language);
}
try {
$this
->saveEntity($entity, $intent);
} catch (\Exception $e) {
throw new SyncException(SyncException::CODE_ENTITY_API_FAILURE, $e);
}
}
}
$intent
->changeTranslationLanguage();
}
return true;
}
protected function setSourceUrl(PushIntent $intent, EntityInterface $entity) {
if ($entity
->hasLinkTemplate('canonical')) {
try {
$url = $entity
->toUrl('canonical', [
'absolute' => true,
])
->toString(true)
->getGeneratedUrl();
$intent
->getOperation()
->setSourceDeepLink($url, $intent
->getActiveLanguage());
} catch (\Exception $e) {
throw new SyncException(SyncException::CODE_UNEXPECTED_EXCEPTION, $e);
}
}
}
protected function ignorePush(PushIntent $intent) {
$reason = $intent
->getReason();
$action = $intent
->getAction();
if (PushIntent::PUSH_AUTOMATICALLY == $reason) {
if (PushIntent::PUSH_MANUALLY == $this->settings['export']) {
return true;
}
}
if (SyncIntent::ACTION_UPDATE == $action) {
foreach (EntityStatus::getInfosForEntity($intent
->getEntityType(), $intent
->getUuid()) as $info) {
$flow = $info
->getFlow();
if (!$flow) {
continue;
}
if (!$info
->getLastPull()) {
continue;
}
if (!$info
->isSourceEntity()) {
break;
}
$config = $flow
->getEntityTypeConfig($intent
->getEntityType(), $intent
->getBundle());
if (PullIntent::PULL_UPDATE_FORCE_UNLESS_OVERRIDDEN == $config['import_updates']) {
return true;
}
}
}
return false;
}
protected function getStaticFields() {
return [];
}
protected function isEntityTypeTranslatable($entity) {
return $entity instanceof TranslatableInterface && $entity
->getEntityType()
->getKey('langcode');
}
}