<?php
namespace Drupal\tmgmt_content\Plugin\tmgmt\Source;
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Plugin\DataType\EntityReference;
use Drupal\Core\Entity\RevisionLogInterface;
use Drupal\Core\Entity\TranslatableRevisionableStorageInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Url;
use Drupal\entity_reference_revisions\EntityNeedsSaveInterface;
use Drupal\entity_reference_revisions\EntityReferenceRevisionsFieldItemList;
use Drupal\tmgmt\JobItemInterface;
use Drupal\tmgmt\SourcePluginBase;
use Drupal\tmgmt\SourcePreviewInterface;
use Drupal\tmgmt\ContinuousSourceInterface;
use Drupal\tmgmt\TMGMTException;
use Drupal\Core\Render\Element;
use Drupal\Core\Form\FormStateInterface;
use Drupal\tmgmt\Entity\Job;
class ContentEntitySource extends SourcePluginBase implements SourcePreviewInterface, ContinuousSourceInterface {
protected function getEntity(JobItemInterface $job_item) {
return \Drupal::entityTypeManager()
->getStorage($job_item
->getItemType())
->load($job_item
->getItemId());
}
public static function loadMultiple($entity_type_id, array $entity_ids, $langcode = NULL) {
$storage = \Drupal::entityTypeManager()
->getStorage($entity_type_id);
$entities = $storage
->loadMultiple($entity_ids);
if ($storage
->getEntityType()
->isRevisionable() && $storage instanceof TranslatableRevisionableStorageInterface) {
foreach ($entities as $entity_id => $entity) {
$translation_langcode = $langcode ?: $entity
->language()
->getId();
$revision_id = $storage
->getLatestTranslationAffectedRevisionId($entity
->id(), $translation_langcode);
if ($revision_id && $entity
->getRevisionId() != $revision_id) {
$revision = $storage
->loadRevision($revision_id);
if (!$revision
->wasDefaultRevision()) {
$entities[$entity_id] = $revision;
}
}
}
}
return $entities;
}
public static function load($entity_type_id, $id, $langcode = NULL) {
$entities = static::loadMultiple($entity_type_id, [
$id,
], $langcode);
return isset($entities[$id]) ? $entities[$id] : NULL;
}
public function getLabel(JobItemInterface $job_item) {
$langcode = $job_item
->getJob() ? $job_item
->getJob()
->getSourceLangcode() : NULL;
$label = FALSE;
if ($entity = static::load($job_item
->getItemType(), $job_item
->getItemId(), $langcode)) {
$label = $entity
->label() ?: $entity
->id();
}
return is_bool($label) ? $label : (string) $label;
}
public function getUrl(JobItemInterface $job_item) {
$langcode = $job_item
->getJob() ? $job_item
->getJob()
->getSourceLangcode() : NULL;
if ($entity = static::load($job_item
->getItemType(), $job_item
->getItemId(), $langcode)) {
if ($entity
->hasLinkTemplate('canonical')) {
$anonymous = new AnonymousUserSession();
$url = $entity
->toUrl();
$anonymous_access = \Drupal::config('tmgmt.settings')
->get('anonymous_access');
if ($url && $anonymous_access && !$entity
->access('view', $anonymous)) {
$url
->setOption('query', [
'key' => \Drupal::service('tmgmt_content.key_access')
->getKey($job_item),
]);
}
return $url;
}
}
return NULL;
}
public function getData(JobItemInterface $job_item) {
$langcode = $job_item
->getJob() ? $job_item
->getJob()
->getSourceLangcode() : NULL;
$entity = static::load($job_item
->getItemType(), $job_item
->getItemId(), $langcode);
if (!$entity) {
throw new TMGMTException(t('Unable to load entity %type with id %id', array(
'%type' => $job_item
->getItemType(),
'%id' => $job_item
->getItemId(),
)));
}
$languages = \Drupal::languageManager()
->getLanguages();
$id = $entity
->language()
->getId();
if (!isset($languages[$id])) {
throw new TMGMTException(t('Entity %entity could not be translated because the language %language is not applicable', array(
'%entity' => $entity
->language()
->getId(),
'%language' => $entity
->language()
->getName(),
)));
}
if (!$entity
->hasTranslation($job_item
->getJob()
->getSourceLangcode())) {
throw new TMGMTException(t('The %type entity %id with translation %lang does not exist.', array(
'%type' => $entity
->getEntityTypeId(),
'%id' => $entity
->id(),
'%lang' => $job_item
->getJob()
->getSourceLangcode(),
)));
}
$translation = $entity
->getTranslation($job_item
->getJob()
->getSourceLangcode());
$data = $this
->extractTranslatableData($translation);
$entity_form_display = \Drupal::service('entity_display.repository')
->getFormDisplay($job_item
->getItemType(), $entity
->bundle(), 'default');
uksort($data, function ($a, $b) use ($entity_form_display) {
$a_weight = NULL;
$b_weight = NULL;
if ($entity_form_display
->getComponent($a) && (isset($entity_form_display
->getComponent($a)['weight']) && !is_null($entity_form_display
->getComponent($a)['weight']))) {
$a_weight = (int) $entity_form_display
->getComponent($a)['weight'];
}
if ($entity_form_display
->getComponent($b) && (isset($entity_form_display
->getComponent($b)['weight']) && !is_null($entity_form_display
->getComponent($b)['weight']))) {
$b_weight = (int) $entity_form_display
->getComponent($b)['weight'];
}
if ($a_weight === NULL && $b_weight === NULL) {
return $a > $b ? 1 : -1;
}
elseif ($a_weight === NULL) {
return 1;
}
elseif ($b_weight === NULL) {
return -1;
}
elseif ($a_weight == $b_weight) {
return 0;
}
else {
return $a_weight > $b_weight ? 1 : -1;
}
});
return $data;
}
public function extractTranslatableData(ContentEntityInterface $entity) {
$field_definitions = $entity
->getFieldDefinitions();
$exclude_field_types = [
'language',
'metatag_computed',
];
$exclude_field_names = [
'moderation_state',
];
$content_translation_manager = \Drupal::service('content_translation.manager');
$is_bundle_translatable = $content_translation_manager
->isEnabled($entity
->getEntityTypeId(), $entity
->bundle());
$translatable_fields = array_filter($field_definitions, function (FieldDefinitionInterface $field_definition) use ($exclude_field_types, $exclude_field_names, $is_bundle_translatable) {
if ($is_bundle_translatable) {
if (!$field_definition
->isTranslatable()) {
return FALSE;
}
}
elseif (!$field_definition
->getFieldStorageDefinition()
->isTranslatable()) {
return FALSE;
}
if (in_array($field_definition
->getType(), $exclude_field_types)) {
return FALSE;
}
if (in_array($field_definition
->getName(), $exclude_field_names)) {
return FALSE;
}
if ($field_definition instanceof ThirdPartySettingsInterface) {
$is_excluded = $field_definition
->getThirdPartySetting('tmgmt_content', 'excluded', FALSE);
if ($is_excluded) {
return FALSE;
}
}
return TRUE;
});
\Drupal::moduleHandler()
->alter('tmgmt_translatable_fields', $entity, $translatable_fields);
$data = array();
foreach ($translatable_fields as $field_name => $field_definition) {
$field = $entity
->get($field_name);
$data[$field_name] = $this
->getFieldProcessor($field_definition
->getType())
->extractTranslatableData($field);
}
$embeddable_fields = static::getEmbeddableFields($entity);
foreach ($embeddable_fields as $field_name => $field_definition) {
$field = $entity
->get($field_name);
foreach ($field as $delta => $field_item) {
foreach ($field_item
->getProperties(TRUE) as $property_key => $property) {
if ($property instanceof EntityReference && $property
->getValue() instanceof ContentEntityInterface) {
$data[$field_name]['#label'] = $field_definition
->getLabel();
if (count($field) > 1) {
$data[$field_name][$delta]['#label'] = t('Delta #@delta', array(
'@delta' => $delta,
));
}
$referenced_entity = $property
->getValue();
$langcode = $entity
->language()
->getId();
if ($content_translation_manager
->isEnabled($referenced_entity
->getEntityTypeId(), $referenced_entity
->bundle()) && $referenced_entity
->hasTranslation($langcode)) {
$referenced_entity = $referenced_entity
->getTranslation($langcode);
}
$data[$field_name][$delta][$property_key] = $this
->extractTranslatableData($referenced_entity);
$data[$field_name][$delta][$property_key]['#id'] = $property
->getValue()
->id();
}
}
}
}
return $data;
}
public static function isModeratedEntity(EntityInterface $entity) {
if (!\Drupal::moduleHandler()
->moduleExists('content_moderation')) {
return FALSE;
}
return \Drupal::service('content_moderation.moderation_information')
->isModeratedEntity($entity);
}
public static function getEmbeddableFields(ContentEntityInterface $entity) {
$field_definitions = $entity
->getFieldDefinitions();
$embeddable_field_names = \Drupal::config('tmgmt_content.settings')
->get('embedded_fields');
$embeddable_fields = array_filter($field_definitions, function (FieldDefinitionInterface $field_definition) use ($embeddable_field_names) {
return isset($embeddable_field_names[$field_definition
->getTargetEntityTypeId()][$field_definition
->getName()]);
});
$content_translation_manager = \Drupal::service('content_translation.manager');
foreach ($field_definitions as $field_name => $field_definition) {
$storage_definition = $field_definition
->getFieldStorageDefinition();
$property_definitions = $storage_definition
->getPropertyDefinitions();
foreach ($property_definitions as $property_definition) {
if (in_array($property_definition
->getDataType(), [
'entity_reference',
'entity_revision_reference',
]) && ($target_type_id = $storage_definition
->getSetting('target_type'))) {
$is_target_type_enabled = $content_translation_manager
->isEnabled($target_type_id);
$target_entity_type = \Drupal::entityTypeManager()
->getDefinition($target_type_id);
if ($target_entity_type
->get('entity_revision_parent_type_field') && ($is_target_type_enabled || $entity
->getEntityType()
->get('entity_revision_parent_type_field'))) {
$embeddable_fields[$field_name] = $field_definition;
}
}
}
}
return $embeddable_fields;
}
public function saveTranslation(JobItemInterface $job_item, $target_langcode) {
$entity = $this
->getEntity($job_item);
if (!$entity) {
$job_item
->addMessage('The entity %id of type %type does not exist, the job can not be completed.', array(
'%id' => $job_item
->getItemId(),
'%type' => $job_item
->getItemType(),
), 'error');
return FALSE;
}
if ($entity_revision = $this
->getPendingRevisionWithCompositeReferenceField($job_item)) {
$title = $entity_revision
->hasLinkTemplate('latest-version') ? $entity_revision
->toLink(NULL, 'latest-version')
->toString() : $entity_revision
->label();
$job_item
->addMessage('This translation cannot be accepted as there is a pending revision in the default translation. You must publish %title first before saving this translation.', [
'%title' => $title,
], 'error');
return FALSE;
}
$data = $job_item
->getData();
$this
->doSaveTranslations($entity, $data, $target_langcode, $job_item);
return TRUE;
}
public function getItemTypes() {
$entity_types = \Drupal::entityTypeManager()
->getDefinitions();
$types = array();
$content_translation_manager = \Drupal::service('content_translation.manager');
foreach ($entity_types as $entity_type_name => $entity_type) {
if ($entity_type
->get('entity_revision_parent_type_field')) {
continue;
}
if ($content_translation_manager
->isEnabled($entity_type
->id())) {
$types[$entity_type_name] = $entity_type
->getLabel();
}
}
return $types;
}
public function getItemTypeLabel($type) {
return \Drupal::entityTypeManager()
->getDefinition($type)
->getLabel();
}
public function getType(JobItemInterface $job_item) {
if ($entity = $this
->getEntity($job_item)) {
$bundles = \Drupal::service('entity_type.bundle.info')
->getBundleInfo($job_item
->getItemType());
$entity_type = $entity
->getEntityType();
$bundle = $entity
->bundle();
if (isset($bundles[$bundle]) && $bundle != $job_item
->getItemType()) {
return t('@type (@bundle)', array(
'@type' => $entity_type
->getLabel(),
'@bundle' => $bundles[$bundle]['label'],
));
}
return $entity_type
->getLabel();
}
}
public function getSourceLangCode(JobItemInterface $job_item) {
if (!($entity = $this
->getEntity($job_item))) {
return FALSE;
}
return $entity
->getUntranslated()
->language()
->getId();
}
public function getExistingLangCodes(JobItemInterface $job_item) {
if ($entity = $this
->getEntity($job_item)) {
return array_keys($entity
->getTranslationLanguages());
}
return array();
}
protected function doSaveTranslations(ContentEntityInterface $entity, array $data, $target_langcode, JobItemInterface $item, $save = TRUE) {
if (!$entity
->hasTranslation($target_langcode)) {
$entity
->addTranslation($target_langcode, $entity
->toArray());
}
$translation = $entity
->getTranslation($target_langcode);
$manager = \Drupal::service('content_translation.manager');
if ($manager
->isEnabled($translation
->getEntityTypeId(), $translation
->bundle())) {
$manager
->getTranslationMetadata($translation)
->setSource($entity
->language()
->getId());
}
foreach (Element::children($data) as $field_name) {
$field_data = $data[$field_name];
if (!$translation
->hasField($field_name)) {
$message = 'Skipping field %field for job item @item because it does not exist on entity <em>@type/@id</em>.';
$item_label = $translation
->hasLinkTemplate('canonical') ? $translation
->toLink()
->toString() : $translation
->label();
$args = [
'%field' => $field_name,
'@item' => $item_label,
'@type' => $translation
->getEntityTypeId(),
'@id' => $translation
->id(),
];
$item
->addMessage($message, $args, 'warning');
continue;
}
$field = $translation
->get($field_name);
$field_processor = $this
->getFieldProcessor($field
->getFieldDefinition()
->getType());
$field_processor
->setTranslations($field_data, $field);
}
$embeddable_fields = static::getEmbeddableFields($entity);
foreach ($embeddable_fields as $field_name => $field_definition) {
if (!isset($data[$field_name])) {
continue;
}
$field = $translation
->get($field_name);
$target_type = $field
->getFieldDefinition()
->getFieldStorageDefinition()
->getSetting('target_type');
$is_target_type_translatable = $manager
->isEnabled($target_type);
if (!$is_target_type_translatable) {
$field = clone $entity
->get($field_name);
if (!$translation
->get($field_name)
->isEmpty()) {
$translation
->set($field_name, NULL);
}
}
foreach (Element::children($data[$field_name]) as $delta) {
$field_item = $data[$field_name][$delta];
foreach (Element::children($field_item) as $property) {
if ($target_entity = $this
->findReferencedEntity($field, $field_item, $delta, $property, $is_target_type_translatable)) {
if ($is_target_type_translatable) {
$target_save = TRUE;
if ($field
->getFieldDefinition()
->getType() == 'entity_reference_revisions' && $target_entity instanceof EntityNeedsSaveInterface) {
$target_save = FALSE;
$target_entity
->setNeedsSave(TRUE);
}
$this
->doSaveTranslations($target_entity, $field_item[$property], $target_langcode, $item, $target_save);
}
else {
$duplicate = $this
->createTranslationDuplicate($target_entity, $target_langcode);
$this
->doSaveTranslations($duplicate, $field_item[$property], $target_langcode, $item, FALSE);
$translation
->get($field_name)
->set($delta, $duplicate);
}
}
}
}
}
if (static::isModeratedEntity($translation)) {
if (isset($data['#moderation_state'][0])) {
$moderation_state = $data['#moderation_state'][0];
}
else {
$moderation_info = \Drupal::service('content_moderation.moderation_information');
$workflow = $moderation_info
->getWorkflowForEntity($entity);
$moderation_state = \Drupal::config('tmgmt_content.settings')
->get('default_moderation_states.' . $workflow
->id());
}
if ($moderation_state) {
$translation
->set('moderation_state', $moderation_state);
}
}
elseif (isset($data['#published'][0]) && $translation instanceof EntityPublishedInterface) {
if ($data['#published'][0]) {
$translation
->setPublished();
}
else {
$translation
->setUnpublished();
}
}
if ($entity
->getEntityType()
->isRevisionable()) {
$storage = \Drupal::entityTypeManager()
->getStorage($entity
->getEntityTypeId());
if ($storage instanceof TranslatableRevisionableStorageInterface) {
$translation = $storage
->createRevision($translation, $translation
->isDefaultRevision());
if ($entity instanceof RevisionLogInterface) {
$translation
->setRevisionLogMessage($this
->t('Created by translation job <a href=":url">@label</a>.', [
':url' => $item
->getJob()
->toUrl()
->toString(),
'@label' => $item
->label(),
]));
}
}
}
if ($save) {
$translation
->save();
}
}
protected function createTranslationDuplicate(ContentEntityInterface $target_entity, $langcode) {
$duplicate = $target_entity
->createDuplicate();
if ($duplicate
->getEntityType()
->hasKey('langcode')) {
$duplicate
->set($duplicate
->getEntityType()
->getKey('langcode'), $langcode);
}
return $duplicate;
}
public function getPreviewUrl(JobItemInterface $job_item) {
if ($job_item
->getJob()
->isActive() && !($job_item
->isAborted() || $job_item
->isAccepted())) {
return new Url('tmgmt_content.job_item_preview', [
'tmgmt_job_item' => $job_item
->id(),
], [
'query' => [
'key' => \Drupal::service('tmgmt_content.key_access')
->getKey($job_item),
],
]);
}
else {
return NULL;
}
}
public function continuousSettingsForm(array &$form, FormStateInterface $form_state, Job $job) {
$continuous_settings = $job
->getContinuousSettings();
$element = array();
$item_types = $this
->getItemTypes();
asort($item_types);
$entity_type_manager = \Drupal::entityTypeManager();
foreach ($item_types as $item_type => $item_type_label) {
$entity_type = $entity_type_manager
->getDefinition($item_type);
$element[$entity_type
->id()]['enabled'] = array(
'#type' => 'checkbox',
'#title' => $item_type_label,
'#default_value' => isset($continuous_settings[$this
->getPluginId()][$entity_type
->id()]) ? $continuous_settings[$this
->getPluginId()][$entity_type
->id()]['enabled'] : FALSE,
);
if ($entity_type
->hasKey('bundle')) {
$bundles = \Drupal::service('entity_type.bundle.info')
->getBundleInfo($item_type);
$element[$entity_type
->id()]['bundles'] = array(
'#title' => $this
->getBundleLabel($entity_type),
'#type' => 'details',
'#open' => TRUE,
'#states' => array(
'invisible' => array(
'input[name="continuous_settings[' . $this
->getPluginId() . '][' . $entity_type
->id() . '][enabled]"]' => array(
'checked' => FALSE,
),
),
),
);
foreach ($bundles as $bundle => $bundle_label) {
if (\Drupal::service('content_translation.manager')
->isEnabled($entity_type
->id(), $bundle)) {
$element[$entity_type
->id()]['bundles'][$bundle] = array(
'#type' => 'checkbox',
'#title' => $bundle_label['label'],
'#default_value' => isset($continuous_settings[$this
->getPluginId()][$entity_type
->id()]['bundles'][$bundle]) ? $continuous_settings[$this
->getPluginId()][$entity_type
->id()]['bundles'][$bundle] : FALSE,
);
}
}
}
}
return $element;
}
public function shouldCreateContinuousItem(Job $job, $plugin, $item_type, $item_id) {
$continuous_settings = $job
->getContinuousSettings();
$entity = static::load($item_type, $item_id, $job
->getSourceLangcode());
$translation_manager = \Drupal::service('content_translation.manager');
$translation = $entity
->hasTranslation($job
->getTargetLangcode()) ? $entity
->getTranslation($job
->getTargetLangcode()) : NULL;
$metadata = isset($translation) ? $translation_manager
->getTranslationMetadata($translation) : NULL;
if (isset($translation) && !$metadata
->isOutdated()) {
return FALSE;
}
else {
if ($entity && $entity
->getEntityType()
->hasKey('bundle')) {
if (!empty($continuous_settings[$plugin][$item_type]['bundles'][$entity
->bundle()]) && !empty($continuous_settings[$plugin][$item_type]['enabled'])) {
return TRUE;
}
}
elseif (!empty($continuous_settings[$plugin][$item_type]['enabled'])) {
return TRUE;
}
}
return FALSE;
}
protected function getBundleLabel(EntityTypeInterface $entity_type) {
if ($entity_type
->getBundleLabel()) {
return $entity_type
->getBundleLabel();
}
if ($entity_type
->getBundleEntityType()) {
return \Drupal::entityTypeManager()
->getDefinition($entity_type
->getBundleEntityType())
->getLabel();
}
return $this
->t('@label type', [
'@label' => $entity_type
->getLabel(),
]);
}
protected function getFieldProcessor($field_type) {
$definition = \Drupal::service('plugin.manager.field.field_type')
->getDefinition($field_type);
return \Drupal::service('class_resolver')
->getInstanceFromDefinition($definition['tmgmt_field_processor']);
}
protected function findReferencedEntity(FieldItemListInterface $field, array $field_item, $delta, $property, $is_target_type_translatable = TRUE) {
if (isset($field_item[$property]['#id'])) {
foreach ($field as $item_delta => $item) {
if ($item->{$property} instanceof ContentEntityInterface) {
$referenced_entity = $item->{$property};
if ($referenced_entity
->id() == $field_item[$property]['#id'] || $item_delta === $delta && !$is_target_type_translatable) {
return $referenced_entity;
}
}
}
}
elseif ($field
->offsetExists($delta) && $field
->offsetGet($delta)->{$property} instanceof ContentEntityInterface) {
return $field
->offsetGet($delta)->{$property};
}
}
public function getPendingRevisionWithCompositeReferenceField(JobItemInterface $job_item) {
$entity = static::load($job_item
->getItemType(), $job_item
->getItemId());
if (!$entity) {
return NULL;
}
if (!$entity
->isDefaultRevision()) {
foreach ($entity
->getFieldDefinitions() as $definition) {
if (in_array($definition
->getType(), [
'entity_reference',
'entity_reference_revisions',
]) && !$definition
->isTranslatable()) {
$target_type_id = $definition
->getSetting('target_type');
$entity_type_manager = \Drupal::entityTypeManager();
if (!$entity_type_manager
->hasDefinition($target_type_id)) {
continue;
}
if ($entity_type_manager
->getDefinition($target_type_id)
->get('entity_revision_parent_type_field')) {
return $entity;
}
}
}
}
return NULL;
}
}