Skip to content
Snippets Groups Projects

Issue #3317768: Multilingual Support for Taxonomies

@@ -3,6 +3,8 @@
namespace Drupal\structure_sync\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\structure_sync\StructureSyncHelper;
use Drupal\taxonomy\Entity\Term;
@@ -40,7 +42,7 @@ private function getEditableConfig() {
/**
* Function to export taxonomy terms.
*/
public function exportTaxonomies(array $form = NULL, FormStateInterface $form_state = NULL) {
public function exportTaxonomies(?array $form = NULL, ?FormStateInterface $form_state = NULL) {
StructureSyncHelper::logMessage('Taxonomies export started');
if (is_object($form_state) && $form_state->hasValue('export_voc_list')) {
@@ -78,8 +80,9 @@ public function exportTaxonomies(array $form = NULL, FormStateInterface $form_st
$parents = [];
foreach ($tids as $tid) {
$parent = $this->entityTypeManager
->getStorage('taxonomy_term')->loadParents($tid);
/** @var Drupal\Taxonomy\TermStorage */
$term_storage = $this->entityTypeManager()->getStorage('taxonomy_term');
$parent = $term_storage->loadParents($tid);
$parent = reset($parent);
if (is_object($parent)) {
@@ -89,6 +92,7 @@ public function exportTaxonomies(array $form = NULL, FormStateInterface $form_st
// Build array of taxonomy terms and associated field values.
$taxonomies = [];
/** @var \Drupal\taxonomy\Entity\Term $entity */
foreach ($entities as $entity) {
$entity_properties = [
'vid' => $vocabulary,
@@ -100,49 +104,25 @@ public function exportTaxonomies(array $form = NULL, FormStateInterface $form_st
'weight' => $entity->weight->value,
'parent' => $parents[$entity->id()] ?? '0',
'uuid' => $entity->uuid(),
'translations' => [],
];
// Identify and build array of any custom fields attached to terms.
$entity_fields = [];
$entity_field_names = [];
$all_term_fields = $entity->getFields();
foreach ($all_term_fields as $field_name => $field) {
$is_custom_field = 'field_' === substr($field_name, 0, 6);
if ($is_custom_field) {
$entity_field_names[] = $field_name;
}
}
if ($entity_field_names) {
foreach ($entity_field_names as $field_name) {
$field_definition = $entity->$field_name->getFieldDefinition();
$is_entity_reference = 'entity_reference' === $field_definition->getType();
$is_term_reference = 'default:taxonomy_term' === $field_definition->getSetting('handler');
if (!$is_entity_reference && !$is_term_reference) {
$entity_fields[$field_name] = $entity->$field_name->getValue();
}
// If exporting entity reference field that references other
// taxonomy terms, export term name/VID pair in place of TID:
// Because TIDs aren't synced and may get altered using this module,
// we need to look up TIDs from the name/VID pair during the import
// step, otherwise term reference fields may lose data.
else {
$entity_reference_field_value = $entity->$field_name->getValue();
foreach ($entity_reference_field_value as $field_item) {
$target_term_entity = StructureSyncHelper::getEntityManager()
->getStorage('taxonomy_term')->load($field_item['target_id']);
if ($target_term_entity) {
$entity_fields[$field_name][] = [
'name' => $target_term_entity->getName(),
'vid' => $target_term_entity->bundle(),
];
}
}
}
if ($entity instanceof TranslatableInterface) {
foreach ($entity->getTranslationLanguages(FALSE) as $langcode => $lang) {
// @todo check if we can use $lang directly.
/** @var \Drupal\Core\Entity\TranslatableInterface $translation */
$translation = $entity->getTranslation($langcode);
$entity_properties['translations'][$langcode] = [
'name' => $translation->name->value,
'description__value' => $translation->get('description')->value,
'description__format' => $translation->get('description')->format,
'weight' => $translation->weight->value,
'parent' => $parents[$translation->id()] ?? '0',
] + $this->getEntityCustomFields($translation, TRUE);
}
}
$entity_fields = $this->getEntityCustomFields($entity);
$taxonomies[] = $entity_properties + $entity_fields;
}
@@ -156,6 +136,65 @@ public function exportTaxonomies(array $form = NULL, FormStateInterface $form_st
StructureSyncHelper::logMessage('Taxonomies exported');
}
/**
* Get the custom fields of an entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to get the custom fields from.
*
* @return array
* An array of custom fields.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function getEntityCustomFields(EntityInterface|TranslatableInterface $entity, $check_translatable = FALSE): array {
// Identify and build array of any custom fields attached to terms.
$entity_fields = [];
$entity_field_names = [];
$all_term_fields = $entity->getFields();
foreach ($all_term_fields as $field_name => $field) {
$is_custom_field = 'field_' === substr($field_name, 0, 6);
// If the flag check_translatable is TRUE, only include translatable fields.
if ($is_custom_field && (!$check_translatable || $field->getFieldDefinition()->isTranslatable())) {
$entity_field_names[] = $field_name;
}
}
if ($entity_field_names) {
foreach ($entity_field_names as $field_name) {
$field_definition = $entity->$field_name->getFieldDefinition();
$is_entity_reference = 'entity_reference' === $field_definition->getType();
$is_term_reference = 'default:taxonomy_term' === $field_definition->getSetting('handler');
if (!$is_entity_reference && !$is_term_reference) {
$entity_fields[$field_name] = $entity->$field_name->getValue();
}
// If exporting entity reference field that references other
// taxonomy terms, export term name/VID pair in place of TID:
// Because TIDs aren't synced and may get altered using this module,
// we need to look up TIDs from the name/VID pair during the import
// step, otherwise term reference fields may lose data.
else {
$entity_reference_field_value = $entity->$field_name->getValue();
foreach ($entity_reference_field_value as $field_item) {
/** @var Drupal\Taxonomy\Entity\Term $target_term_entity */
$target_term_entity = StructureSyncHelper::getEntityManager()
->getStorage('taxonomy_term')->load($field_item['target_id']);
if ($target_term_entity) {
$entity_fields[$field_name][] = [
'name' => $target_term_entity->getName(),
'vid' => $target_term_entity->bundle(),
];
}
}
}
}
}
return $entity_fields;
}
/**
* Function to import taxonomy terms.
*
@@ -163,7 +202,7 @@ public function exportTaxonomies(array $form = NULL, FormStateInterface $form_st
* an array with a key value pair for form with key 'style' and value 'full',
* 'safe' or 'force' to apply that import style.
*/
public function importTaxonomies(array $form, FormStateInterface $form_state = NULL) {
public function importTaxonomies(array $form, ?FormStateInterface $form_state = NULL) {
StructureSyncHelper::logMessage('Taxonomy import started');
// Check if the import style has been defined in the form (state) and else
@@ -359,53 +398,12 @@ public static function importTaxonomiesFull($taxonomies, &$context) {
$parent = $newTids[$taxonomy['parent']];
}
// Identify and build array of any custom fields attached to
// terms.
$entity_fields = [];
foreach ($taxonomy as $field_name => $field_value) {
$is_custom_field = 'field_' === substr($field_name, 0, 6);
if ($is_custom_field) {
$not_term_reference = empty($field_value[0]['vid']);
if ($not_term_reference) {
$entity_fields[$field_name] = $field_value;
}
// If importing entity reference field that references other
// taxonomy terms, look up associated TID from name/VID value
// pair provided during export: Because TIDs aren't synced and
// may get altered using this module, we need to look up TIDs
// from the name/VID pair during import, otherwise term
// reference fields may lose data.
else {
foreach ($field_value as $field_properties) {
$tid = StructureSyncHelper::getEntityManager()
->getStorage('taxonomy_term')
->getQuery()
->accessCheck(FALSE)
->condition('vid', $field_properties['vid'])
->condition('name', $field_properties['name'])
->execute();
if ($tid) {
$entity_fields[$field_name][] = [
'target_id' => reset($tid),
];
}
else {
// If we encounter a term reference field referencing a
// term that hasn't been imported again, trigger re-import
// following current import to update term reference
// fields once all terms are available.
$runAgain = TRUE;
}
}
}
}
}
[$entity_fields, $runAgain] = self::handleCustomFields($taxonomy, $runAgain);
if (count($tids) <= 0) {
$entity_properties = [
'vid' => $vid,
'tid' => $taxonomy['tid'],
'langcode' => $taxonomy['langcode'],
'name' => $taxonomy['name'],
'description' => [
@@ -417,7 +415,21 @@ public static function importTaxonomiesFull($taxonomies, &$context) {
'uuid' => $taxonomy['uuid'],
];
Term::create($entity_properties + $entity_fields)->save();
$term = Term::create($entity_properties + $entity_fields);
if (isset($taxonomy['translations'])) {
foreach ($taxonomy['translations'] as $langcode => $translation) {
[$entity_fields_translated, $skipRunAgain] = self::handleCustomFields($translation, $runAgain);
$term->addTranslation($langcode, [
'name' => $translation['name'],
'description' => [
'value' => $translation['description__value'],
'format' => $translation['description__format'],
],
] + $entity_fields_translated);
}
}
$term->save();
}
else {
foreach ($entities as $entity) {
@@ -438,6 +450,40 @@ public static function importTaxonomiesFull($taxonomies, &$context) {
}
}
if (isset($taxonomy['translations'])) {
foreach ($taxonomy['translations'] as $langcode => $translation) {
[$entity_fields_translated, $skipRunAgain] = self::handleCustomFields($translation, $runAgain);
// Check if the term has a translation in the given language.
if ($term->hasTranslation($langcode)) {
// Update the translation.
$term_translation = $term->getTranslation($langcode);
$term_translation->setName($translation['name']);
$term_translation->set('description', [
'value' => $translation['description__value'],
'format' => $translation['description__format'],
]);
if ($entity_fields_translated) {
foreach ($entity_fields_translated as $entity_fields_translated => $field_value) {
$term_translation->$field_name->setValue($field_value);
}
}
$term_translation->save();
}
else {
// Add the translation.
$term->addTranslation($langcode, [
'name' => $translation['name'],
'description' => [
'value' => $translation['description__value'],
'format' => $translation['description__format'],
],
] + $entity_fields_translated
);
}
}
}
$term->save();
}
}
@@ -535,6 +581,7 @@ public static function importTaxonomiesSafe($taxonomies, &$context) {
$entity_properties = [
'vid' => $vid,
'tid' => $taxonomy['tid'],
'langcode' => $taxonomy['langcode'],
'name' => $taxonomy['name'],
'description' => [
@@ -545,52 +592,24 @@ public static function importTaxonomiesSafe($taxonomies, &$context) {
'parent' => [$parent],
'uuid' => $taxonomy['uuid'],
];
// Identify and build array of any custom fields attached to
// terms.
$entity_fields = [];
foreach ($taxonomy as $field_name => $field_value) {
$is_custom_field = 'field_' === substr($field_name, 0, 6);
if ($is_custom_field) {
$not_term_reference = empty($field_value[0]['vid']);
if ($not_term_reference) {
$entity_fields[$field_name] = $field_value;
}
// If importing entity reference field that references other
// taxonomy terms, look up associated TID from name/VID
// value pair provided during export: Because TIDs aren't
// synced and may get altered using this module, we need to
// look up TIDs from the name/VID pair during import,
// otherwise term reference fields may lose data.
else {
foreach ($field_value as $field_properties) {
$tid = StructureSyncHelper::getEntityManager()
->getStorage('taxonomy_term')
->getQuery()
->accessCheck(FALSE)
->condition('vid', $field_properties['vid'])
->condition('name', $field_properties['name'])
->execute();
if ($tid) {
$entity_fields[$field_name][] = [
'target_id' => reset($tid),
];
}
else {
// If we encounter a term reference field referencing
// a term that hasn't been imported again, trigger
// re-import following current import to update term
// reference fields once all terms are available.
$runAgain = TRUE;
}
}
}
[$entity_fields, $runAgain] = self::handleCustomFields($taxonomy, $runAgain);
$term = Term::create($entity_properties + $entity_fields);
if (isset($taxonomy['translations'])) {
foreach ($taxonomy['translations'] as $langcode => $translation) {
[$entity_fields_translated, $skipRunAgain] = self::handleCustomFields($translation, $runAgain);
$term->addTranslation($langcode, [
'name' => $translation['name'],
'description' => [
'value' => $translation['description__value'],
'format' => $translation['description__format'],
],
] + $entity_fields_translated
);
}
}
Term::create($entity_properties + $entity_fields)->save();
$term->save();
$query = StructureSyncHelper::getEntityQuery('taxonomy_term');
$query->condition('vid', $vid);
@@ -700,6 +719,7 @@ public static function importTaxonomiesForce($taxonomies, &$context) {
$entity_properties = [
'vid' => $vid,
'tid' => $taxonomy['tid'],
'langcode' => $taxonomy['langcode'],
'name' => $taxonomy['name'],
'description' => [
@@ -711,51 +731,24 @@ public static function importTaxonomiesForce($taxonomies, &$context) {
'uuid' => $taxonomy['uuid'],
];
// Identify and build array of any custom fields attached to
// terms.
$entity_fields = [];
foreach ($taxonomy as $field_name => $field_value) {
$is_custom_field = 'field_' === substr($field_name, 0, 6);
if ($is_custom_field) {
$not_term_reference = empty($field_value[0]['vid']);
if ($not_term_reference) {
$entity_fields[$field_name] = $field_value;
}
// If importing entity reference field that references other
// taxonomy terms, look up associated TID from name/VID value
// pair provided during export: Because TIDs aren't synced and
// may get altered using this module, we need to look up TIDs
// from the name/VID pair during import, otherwise term
// reference fields may lose data.
else {
foreach ($field_value as $field_properties) {
$tid = StructureSyncHelper::getEntityManager()
->getStorage('taxonomy_term')
->getQuery()
->accessCheck(FALSE)
->condition('vid', $field_properties['vid'])
->condition('name', $field_properties['name'])
->execute();
if ($tid) {
$entity_fields[$field_name][] = [
'target_id' => reset($tid),
];
}
else {
// If we encounter a term reference field referencing a
// term that hasn't been imported again, trigger
// re-import following current import to update term
// reference fields once all terms are available.
$runAgain = TRUE;
}
}
}
[$entity_fields, $runAgain] = self::handleCustomFields($taxonomy, $runAgain);
$term = Term::create($entity_properties + $entity_fields);
if (isset($taxonomy['translations'])) {
foreach ($taxonomy['translations'] as $langcode => $translation) {
[$entity_fields_translated, $skipRunAgain] = self::handleCustomFields($translation, $runAgain);
$term->addTranslation($langcode, [
'name' => $translation['name'],
'description' => [
'value' => $translation['description__value'],
'format' => $translation['description__format'],
],
] + $entity_fields_translated
);
}
}
Term::create($entity_properties + $entity_fields)->save();
$term->save();
$query = StructureSyncHelper::getEntityQuery('taxonomy_term');
$query->condition('vid', $vid);
@@ -806,6 +799,61 @@ public static function importTaxonomiesForce($taxonomies, &$context) {
$context['finished'] = 1;
}
/**
* @param $taxonomy
* @param bool $runAgain
*
* @return array
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public static function handleCustomFields($taxonomy, bool $runAgain, $check_translatable = FALSE): array {
// Identify and build array of any custom fields attached to
// terms.
$entity_fields = [];
foreach ($taxonomy as $field_name => $field_value) {
$is_custom_field = 'field_' === substr($field_name, 0, 6);
if ($is_custom_field) {
$not_term_reference = empty($field_value[0]['vid']);
if ($not_term_reference) {
$entity_fields[$field_name] = $field_value;
}
// If importing entity reference field that references other
// taxonomy terms, look up associated TID from name/VID
// value pair provided during export: Because TIDs aren't
// synced and may get altered using this module, we need to
// look up TIDs from the name/VID pair during import,
// otherwise term reference fields may lose data.
else {
foreach ($field_value as $field_properties) {
$tid = StructureSyncHelper::getEntityManager()
->getStorage('taxonomy_term')
->getQuery()
->accessCheck(FALSE)
->condition('vid', $field_properties['vid'])
->condition('name', $field_properties['name'])
->execute();
if ($tid) {
$entity_fields[$field_name][] = [
'target_id' => reset($tid),
];
}
else {
// If we encounter a term reference field referencing a
// term that hasn't been imported again, trigger re-import
// following current import to update term reference
// fields once all terms are available.
$runAgain = TRUE;
}
}
}
}
}
return [$entity_fields, $runAgain];
}
/**
* Function that signals that the import of taxonomies has finished.
*/
Loading