-
-
Save nikunjkotecha/3a61598bbe49c98fcdc693c8fc050901 to your computer and use it in GitHub Desktop.
<?php | |
namespace Drupal\module\Helper; | |
use Drupal\Core\Entity\EntityInterface; | |
use Drupal\Core\Entity\EntityTypeManagerInterface; | |
use Drupal\Core\Logger\LoggerChannelInterface; | |
use Drupal\paragraphs\Entity\Paragraph; | |
/** | |
* Class MigrateAsymmetricToSymmetric. | |
* | |
* @package Drupal\module\Helper | |
*/ | |
class MigrateAsymmetricToSymmetric { | |
/** | |
* Entity Type Manager. | |
* | |
* @var \Drupal\Core\Entity\EntityTypeManagerInterface | |
*/ | |
protected $entityTypeManager; | |
/** | |
* Paragraph Storage. | |
* | |
* @var \Drupal\Core\Entity\EntityStorageInterface | |
*/ | |
protected $paragraphStorage; | |
public static $fields = [ | |
'paragraph' => [ | |
'field_1_row_1_col', | |
'field_1_row_2_col', | |
'field_promo_block', | |
], | |
'node' => [ | |
'field_promo_blocks', | |
'field_banner', | |
'field_paragraph_content', | |
], | |
'taxonomy_term' => [ | |
'field_paragraph_content', | |
], | |
'block_content' => [ | |
'field_paragraph_content', | |
], | |
]; | |
/** | |
* Logger. | |
* | |
* @var \Drupal\Core\Logger\LoggerChannelInterface | |
*/ | |
protected $logger; | |
/** | |
* MigrateAsymmetricToSymmetric constructor. | |
* | |
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager | |
* Entity Type Manager. | |
* @param \Drupal\Core\Logger\LoggerChannelInterface $logger | |
* Logger. | |
*/ | |
public function __construct(EntityTypeManagerInterface $entity_type_manager, | |
LoggerChannelInterface $logger) { | |
$this->entityTypeManager = $entity_type_manager; | |
$this->logger = $logger; | |
$this->paragraphStorage = $this->entityTypeManager->getStorage('paragraph'); | |
} | |
/** | |
* Migrate paragraphs for particular entity. | |
* | |
* @param \Drupal\Core\Entity\EntityInterface $entity | |
* Node/Term/Block entity to migrate. | |
*/ | |
public function migrateEntity(EntityInterface $entity) { | |
/** @var \Drupal\node\NodeInterface $entity */ | |
// Hard coded languages here, if we ever get three languages anything | |
// below won't work. Code below works only for two languages. | |
foreach (['en', 'ar'] as $langcode) { | |
if (!($entity->hasTranslation($langcode))) { | |
continue; | |
} | |
$entities[$langcode] = $entity->getTranslation($langcode); | |
if ($entities[$langcode]->isDefaultTranslation()) { | |
$defaultLangcode = $langcode; | |
} | |
} | |
// There is only one translation, no need to go further. | |
if (count($entities) < 2) { | |
return; | |
} | |
/** @var \Drupal\node\NodeInterface $default */ | |
$default = $entities[$defaultLangcode]; | |
$translationLangcode = $defaultLangcode === 'en' ? 'ar' : 'en'; | |
$translation = $entities[$translationLangcode]; | |
$this->migrateContent($default, $translation, $defaultLangcode, $translationLangcode); | |
} | |
/** | |
* Helper recursive function to migrate child entities. | |
* | |
* @param \Drupal\Core\Entity\EntityInterface $original | |
* Entity's default translation. | |
* @param \Drupal\Core\Entity\EntityInterface $translation | |
* Entity's translation. | |
* @param string $defaultLangcode | |
* Default translation language code. | |
* @param string $translationLangcode | |
* Target translation language code. | |
*/ | |
private function migrateContent(EntityInterface $original, EntityInterface $translation, string $defaultLangcode, string $translationLangcode) { | |
$this->logger->info('Migrating content for @type @id', [ | |
'@id' => $original->id(), | |
'@type' => $original->getEntityTypeId(), | |
]); | |
/** @var \Drupal\node\NodeInterface $entity */ | |
$fields = self::$fields[$original->getEntityTypeId()]; | |
foreach ($fields as $field) { | |
if ($original->hasField($field) && $translation->hasField($field)) { | |
$defaultValues = $original->get($field)->getValue(); | |
$translatedValues = $translation->get($field)->getValue(); | |
if (count($defaultValues) !== count($translatedValues)) { | |
$this->logger->error('Content structure do not match for @type id: @id', [ | |
'@id' => $original->id(), | |
'@type' => $original->getEntityTypeId(), | |
]); | |
} | |
$entities = []; | |
$this->prepareBundleEntities($entities, $defaultValues, $defaultLangcode); | |
$this->prepareBundleEntities($entities, $translatedValues, $translationLangcode); | |
foreach ($entities as $bundleEntities) { | |
foreach ($bundleEntities[$defaultLangcode] ?? [] as $index => $paragraph) { | |
/** @var \Drupal\paragraphs\Entity\Paragraph $translatedParagraph */ | |
$translatedParagraph = $bundleEntities[$translationLangcode][$index] ?? NULL; | |
if (empty($translatedParagraph)) { | |
continue; | |
} | |
$this->migrateContent($paragraph, $translatedParagraph, $defaultLangcode, $translationLangcode); | |
if ($paragraph->hasTranslation($translationLangcode)) { | |
$paragraph->removeTranslation($translationLangcode); | |
} | |
$translatedParagraph = $this->getParagraph($translatedParagraph->getRevisionId(), $translationLangcode); | |
$newTranslatedValues = $this->getTranslatedValues($translatedParagraph); | |
$newTranslatedParagraph = $paragraph->addTranslation($translationLangcode, $newTranslatedValues); | |
unset($newTranslatedParagraph->original); | |
$this->logger->info('New Translated value: @value', [ | |
'@value' => json_encode($newTranslatedValues), | |
]); | |
try { | |
$newTranslatedParagraph->save(); | |
} | |
catch (\Exception $e) { | |
$this->logger->error('Error occurred while saving new translation for paragraph: @row. Message: @message', [ | |
'@row' => json_encode($newTranslatedValues), | |
'@message' => $e->getMessage(), | |
]); | |
} | |
} | |
} | |
} | |
} | |
} | |
/** | |
* Get translated values from paragraph entity. | |
* | |
* @param \Drupal\paragraphs\Entity\Paragraph $paragraph | |
* Entity to get translated values from. | |
* | |
* @return array | |
* Cleaned translated values array. | |
*/ | |
private function getTranslatedValues(Paragraph $paragraph): array { | |
$translatedValues = $paragraph->toArray(); | |
// Remove unwanted values. | |
$fields_to_remove = [ | |
'id', | |
'uuid', | |
'revision_id', | |
'langcode', | |
'type', | |
'default_langcode', | |
'revision_translation_affected', | |
'content_translation_source', | |
'content_translation_outdated', | |
'content_translation_changed', | |
'behavior_settings', | |
'parent_id', | |
'parent_type', | |
'parent_field_name', | |
'status', | |
'revision_default', | |
'uid', | |
'revision_uid', | |
'created', | |
]; | |
foreach ($fields_to_remove as $field_to_remove) { | |
unset($translatedValues[$field_to_remove]); | |
} | |
return $translatedValues; | |
} | |
/** | |
* Get fresh paragraph entity translated in requested language. | |
* | |
* @param mixed $revision_id | |
* Revision id. | |
* @param string $langcode | |
* Language code. | |
* | |
* @return \Drupal\paragraphs\Entity\Paragraph|null | |
* Paragraph entity translated in requested language if found. | |
*/ | |
private function getParagraph($revision_id, string $langcode): ?Paragraph { | |
$this->paragraphStorage->resetCache(); | |
$paragraph = $this->paragraphStorage->loadRevision($revision_id); | |
if (!($paragraph instanceof Paragraph)) { | |
return NULL; | |
} | |
if ($paragraph->hasTranslation($langcode)) { | |
$paragraph = $paragraph->getTranslation($langcode); | |
} | |
return $paragraph; | |
} | |
/** | |
* Prepare entities array grouped by bundle. | |
* | |
* @param array $entities | |
* Entities array - reference. | |
* @param array $values | |
* Values to add to entities array. | |
* @param string $langcode | |
* Language code. | |
*/ | |
private function prepareBundleEntities(array &$entities, array $values, string $langcode) { | |
foreach ($values as $value) { | |
$paragraph = $this->getParagraph($value['target_revision_id'], $langcode); | |
if (!empty($paragraph)) { | |
$entities[$paragraph->bundle()][$langcode][] = $paragraph; | |
} | |
} | |
} | |
} |
<?php | |
function module_update_8001() { | |
// Step 1 | |
// Update the configuration of any non-paragraph fields to be set | |
// to translatable inside paragraphs that are not translatable yet | |
// but works because of asymmetric translations. | |
// Step 2 | |
// Migration all entities (node/block_content/taxonomy_term) using | |
// which have data for paragraphs. | |
// Step 3 | |
// Make the paragraph fields non-translatable. It is important | |
// to migrate content before doing step 3 to be able to get | |
// asymmetric translations properly and add symmetric translations | |
// for them. | |
} |
Only thing I would like to add is the getter/setter for the $fields (non-static) so that these can be changed when required
@joshirohit100 adding it now would mean we would need to check code again, I would like to avoid any changes like that.
That's exactly what I'm looking for. Maybe add comment on the static $field part and mention what to write there. And on the language part, what do you suggest if I have more than 2 languages ? Execute it more than once ?
script was executed on contents with two languages,
$fields needs to contain all fields
field.field.node.field_paragraph becomes $fields[node][field_paragraph]
I'll be able to comment in detail on Monday, out on vacation right now
Oh okay, thanks for your fast answer still. Enjoy your vacation (but a bit more details would be welcomed :-)
Looks good