-
-
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