{"id":14609,"date":"2024-10-04T17:36:06","date_gmt":"2024-10-04T11:36:06","guid":{"rendered":"https:\/\/shra.ru\/?p=14609"},"modified":"2024-10-04T17:41:03","modified_gmt":"2024-10-04T11:41:03","slug":"problema-s-sokhraneniem-asymmetric-paragraphs-v-tmgmt","status":"publish","type":"post","link":"https:\/\/shra.ru\/2024\/10\/problema-s-sokhraneniem-asymmetric-paragraphs-v-tmgmt\/","title":{"rendered":"\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0441 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u043c asymmetric paragraphs \u0432 tmgmt"},"content":{"rendered":"\n
\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043e\u043f\u0438\u0441\u0430\u043d\u0430 \u0432\u043e\u0442 \u0442\u0443\u0442 https:\/\/www.drupal.org\/project\/tmgmt\/issues\/3134922, \u0438 \u0442\u0430\u043c \u0434\u0430\u0436\u0435 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f \u043f\u0430\u0442\u0447 — https:\/\/www.drupal.org\/files\/issues\/2024-05-28\/3134922-40.patch, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u043e \u0440\u0435\u0448\u0430\u0435\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443, \u043d\u043e \u0432 #43 siavash (Sia) \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0435, \u0438 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u044f\u0441\u043d\u043e, \u0447\u0442\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043d\u0435 \u0440\u0435\u0448\u0435\u043d\u0430.<\/p>\n\n\n\n\n\n\n\n
\u041f\u043e \u0438\u0434\u0435\u0435, \u043f\u0430\u0442\u0447 \u0434\u043e\u043b\u0436\u0435\u043d \u0437\u0430\u0442\u0440\u0430\u0433\u0438\u0432\u0430\u0442\u044c \u043a\u0430\u043a tmgmt<\/em> \u043c\u043e\u0434\u0443\u043b\u044c, \u0442\u0430\u043a \u0438 paragraphs_asymmetric_translation_widgets<\/em>. \u0410 \u0442\u0430\u043a \u0436\u0435 \u043d\u0430\u0434\u043e \u043f\u043e\u0444\u0438\u043a\u0441\u0438\u0442\u044c \u0443\u0436\u0435 \u043d\u0435 \u0432\u0435\u0440\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0431\u0430\u0437\u0435.<\/p>\n\n\n\n \u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043f\u0430\u0442\u0447 \u043a\u043e\u0441\u043c\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0440\u0435\u0448\u0430\u0435\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u0434\u043e \u043a\u0430\u043a\u043e\u0433\u043e \u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442\u0430. \u041f\u043e\u0442\u043e\u043c \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u044b \u0434\u043b\u044f \u043f\u0430\u0440\u0430\u0433\u0440\u0430\u0444\u043e\u0432, \u0433\u0434\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d paragraphs_asymmetric_translation_widgets<\/em>, \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u044e\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c.<\/p>\n\n\n\n Sia \u043d\u0435 \u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043b \u0441\u0432\u043e\u0439 \u043f\u0430\u0442\u0447, \u0442.\u043a. \u043f\u043e\u043a\u0430 \u044d\u0442\u043e \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f, \u0438 \u0432\u0435\u0441\u044c \u043a\u043e\u0434 \u043d\u0430\u043f\u0438\u0441\u0430\u043d \u0432 tmgmt<\/em> \u043c\u043e\u0434\u0443\u043b\u0435. \u041d\u043e \u0441\u043e \u043c\u043d\u043e\u0439 \u043e\u043d \u043f\u043e\u0434\u0435\u043b\u0438\u043b\u0441\u044f \u0441\u0432\u043e\u0438\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c.<\/p>\n\n\n\n \u041f\u0443\u0431\u043b\u0438\u043a\u0443\u044e \u0432 \u0432\u0438\u0434\u0435 \u043f\u0430\u0442\u0447\u0430. <\/p>\n\n\n\n \u042f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043b \u0434\u0430\u043d\u043d\u044b\u0439 \u043f\u0430\u0442\u0447, \u0438 \u043e\u043d \u0440\u0435\u0448\u0430\u0435\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u0441 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430\u043c\u0438 \u0434\u043b\u044f \u043c\u0435\u043d\u044f. \u0410\u0441\u0438\u043c\u043c\u0435\u0442\u0440\u0438\u0447\u043d\u044b\u0439 \u043f\u0430\u0440\u0430\u0433\u0440\u0430\u0444\u044b \u0441\u043d\u043e\u0432\u0430 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442\u044c\u0441\u044f, \u0430 \u0434\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u0432 \u0431\u0430\u0437\u0443. \u0411\u0443\u0434\u0443 \u0436\u0434\u0430\u0442\u044c \u043e\u043a\u043e\u043d\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e, \u0435\u0441\u043b\u0438 Sia \u043a\u043e\u0433\u0434\u0430-\u043d\u0438\u0431\u0443\u0434\u044c \u0432\u044b\u043b\u043e\u0436\u0438\u0442 \u0435\u0451.<\/p>\n","protected":false},"excerpt":{"rendered":" \u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043e\u043f\u0438\u0441\u0430\u043d\u0430 \u0432\u043e\u0442 \u0442\u0443\u0442 https:\/\/www.drupal.org\/project\/tmgmt\/issues\/3134922, \u0438 \u0442\u0430\u043c \u0434\u0430\u0436\u0435 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f \u043f\u0430\u0442\u0447 — https:\/\/www.drupal.org\/files\/issues\/2024-05-28\/3134922-40.patch, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u043e \u0440\u0435\u0448\u0430\u0435\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443, \u043d\u043e \u0432 #43 siavash (Sia) \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0435, \u0438 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u044f\u0441\u043d\u043e, \u0447\u0442\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043d\u0435 \u0440\u0435\u0448\u0435\u043d\u0430.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,7],"tags":[134,190],"acf":[],"yoast_head":"\ndiff --git docroot\/modules\/contrib\/tmgmt\/sources\/content\/src\/Plugin\/tmgmt\/Source\/ContentEntitySource.php docroot\/modules\/contrib\/tmgmt\/sources\/content\/src\/Plugin\/tmgmt\/Source\/ContentEntitySource.php\nindex 45d8763612..0c528bbfbb 100644\n--- docroot\/modules\/contrib\/tmgmt\/sources\/content\/src\/Plugin\/tmgmt\/Source\/ContentEntitySource.php\n+++ docroot\/modules\/contrib\/tmgmt\/sources\/content\/src\/Plugin\/tmgmt\/Source\/ContentEntitySource.php\n@@ -486,14 +486,19 @@ public function getExistingLangCodes(JobItemInterface $job_item) {\n * @param bool $save\n * (optional) Whether to save the translation or not.\n *\n+ * @throws \\Exception\n+ * Thrown when a field or field offset is missing.\n *\/\n protected function doSaveTranslations(ContentEntityInterface $entity, array $data, $target_langcode, JobItemInterface $item, $save = TRUE) {\n- \/\/ If the translation for this language does not exist yet, initialize it.\n- if (!$entity->hasTranslation($target_langcode)) {\n- $entity->addTranslation($target_langcode, $entity->toArray());\n+ \/\/ If the translation for this language already exists, we need to update it\n+ \/\/ rather than create a new one.\n+ if ($entity->hasTranslation($target_langcode)) {\n+ $translation = $entity->getTranslation($target_langcode);\n+ }\n+ else {\n+ $translation = $entity->addTranslation($target_langcode, $entity->toArray());\n }\n \n- $translation = $entity->getTranslation($target_langcode);\n $manager = \\Drupal::service('content_translation.manager');\n if ($manager->isEnabled($translation->getEntityTypeId(), $translation->bundle())) {\n $manager->getTranslationMetadata($translation)->setSource($entity->language()->getId());\n@@ -503,17 +508,7 @@ protected function doSaveTranslations(ContentEntityInterface $entity, array $dat\n $field_data = $data[$field_name];\n \n if (!$translation->hasField($field_name)) {\n- $message = 'Skipping field %field for job item <a href=\"@link\">@item<\/a> because it does not exist on entity <em>@type\/@id<\/em>.';\n- $args = [\n- '%field' => $field_name,\n- '@link' => $item->toUrl()->toString(),\n- '@item' => $item->id(),\n- '@type' => $translation->getEntityTypeId(),\n- '@id' => $translation->id(),\n- ];\n- $item->addMessage($message, $args, 'warning');\n-\n- continue;\n+ throw new \\Exception(\"Field '$field_name' does not exist on entity \" . $translation->getEntityTypeId() . '\/' . $translation->id());\n }\n \n $field = $translation->get($field_name);\n@@ -523,7 +518,6 @@ protected function doSaveTranslations(ContentEntityInterface $entity, array $dat\n \n $embeddable_fields = static::getEmbeddableFields($entity);\n foreach ($embeddable_fields as $field_name => $field_definition) {\n-\n if (!isset($data[$field_name])) {\n continue;\n }\n@@ -531,50 +525,31 @@ protected function doSaveTranslations(ContentEntityInterface $entity, array $dat\n $field = $translation->get($field_name);\n $target_type = $field->getFieldDefinition()->getFieldStorageDefinition()->getSetting('target_type');\n $is_target_type_translatable = $manager->isEnabled($target_type);\n- \/\/ Special handling for asymmetric paragraphs translations.\n- \/\/ @see https:\/\/www.drupal.org\/project\/paragraphs_asymmetric_translation_widgets\n- \/\/ @see https:\/\/www.drupal.org\/project\/tmgmt\/issues\/3134922\n $is_asymmetric_translation_mode = $target_type == 'paragraph' && $field->getFieldDefinition()->getFieldStorageDefinition()->isTranslatable();\n- \/\/ In case the target type is not translatable, the referenced entity will\n- \/\/ be duplicated. As a consequence, remove all the field items from the\n- \/\/ translation, update the field value to use the field object from the\n- \/\/ source language.\n- if (!$is_target_type_translatable) {\n- $field = clone $entity->get($field_name);\n-\n- if (!$translation->get($field_name)->isEmpty()) {\n- $translation->set($field_name, NULL);\n- }\n- }\n \n- foreach (Element::children($data[$field_name]) as $delta) {\n- $field_item = $data[$field_name][$delta];\n- foreach (Element::children($field_item) as $property) {\n- \/\/ Find the referenced entity. In case we are dealing with\n- \/\/ untranslatable target types, the source entity will be returned.\n- if ($target_entity = $this->findReferencedEntity($field, $field_item, $delta, $property, $is_target_type_translatable, $is_asymmetric_translation_mode)) {\n- if ($is_target_type_translatable) {\n- \/\/ If the field is an embeddable reference and the property is a\n- \/\/ content entity, process it recursively.\n-\n- \/\/ If the field is ERR and the target entity supports\n- \/\/ the needs saving interface, do not save it immediately to avoid\n- \/\/ creating two versions when content moderation is used but just\n- \/\/ ensure it will be saved.\n- $target_save = TRUE;\n- if ($field->getFieldDefinition()->getType() == 'entity_reference_revisions' && $target_entity instanceof EntityNeedsSaveInterface) {\n- $target_save = FALSE;\n- $target_entity->needsSave();\n+ \/\/ Handle asymmetric paragraphs translations\n+ if ($is_asymmetric_translation_mode) {\n+ $this->handleAsymmetricParagraphsTranslation($translation, $field_name, $data[$field_name], $target_langcode, $item);\n+ }\n+ else {\n+ \/\/ Original logic for non-asymmetric fields\n+ foreach (Element::children($data[$field_name]) as $delta) {\n+ $field_item = $data[$field_name][$delta];\n+ foreach (Element::children($field_item) as $property) {\n+ if ($target_entity = $this->findReferencedEntity($field, $field_item, $delta, $property, $is_target_type_translatable, $is_asymmetric_translation_mode)) {\n+ if ($is_target_type_translatable) {\n+ $target_save = TRUE;\n+ if ($field->getFieldDefinition()->getType() == 'entity_reference_revisions' && $target_entity instanceof EntityNeedsSaveInterface) {\n+ $target_save = FALSE;\n+ $target_entity->needsSave();\n+ }\n+ $this->doSaveTranslations($target_entity, $field_item[$property], $target_langcode, $item, $target_save);\n+ }\n+ else {\n+ $duplicate = $this->createTranslationDuplicate($target_entity, $target_langcode);\n+ $this->doSaveTranslations($duplicate, $field_item[$property], $target_langcode, $item, FALSE);\n+ $translation->get($field_name)->set($delta, $duplicate);\n }\n-\n- $this->doSaveTranslations($target_entity, $field_item[$property], $target_langcode, $item, $target_save);\n- }\n- else {\n- $duplicate = $this->createTranslationDuplicate($target_entity, $target_langcode);\n- \/\/ Do not save the duplicate as it's going to be saved with the\n- \/\/ main entity.\n- $this->doSaveTranslations($duplicate, $field_item[$property], $target_langcode, $item, FALSE);\n- $translation->get($field_name)->set($delta, $duplicate);\n }\n }\n }\n@@ -627,7 +602,96 @@ protected function doSaveTranslations(ContentEntityInterface $entity, array $dat\n $translation->save();\n }\n }\n+\/**\n+ * Handles asymmetric paragraphs translation.\n+ *\n+ * @param \\Drupal\\Core\\Entity\\ContentEntityInterface $translation\n+ * The entity translation.\n+ * @param string $field_name\n+ * The field name.\n+ * @param array $field_data\n+ * The field data.\n+ * @param string $target_langcode\n+ * The target language code.\n+ * @param \\Drupal\\tmgmt\\JobItemInterface $item\n+ * The job item.\n+ *\/\n+ protected function handleAsymmetricParagraphsTranslation(ContentEntityInterface $translation, $field_name, array $field_data, $target_langcode, JobItemInterface $item) {\n+ $field = $translation->get($field_name);\n+ $new_field_value = [];\n+ $paragraph_storage = \\Drupal::entityTypeManager()->getStorage('paragraph');\n+\n+ foreach (Element::children($field_data) as $delta) {\n+ $field_item = $field_data[$delta];\n+ foreach (Element::children($field_item) as $property) {\n+ $target_entity = $this->findReferencedEntity($field, $field_item, $delta, $property, TRUE, TRUE);\n+ if ($target_entity) {\n+ \/\/ Check if a translation already exists\n+ if ($target_entity->hasTranslation($target_langcode)) {\n+ $duplicate = $target_entity->getTranslation($target_langcode);\n+ } else {\n+ $duplicate = $this->createAsymmetricParagraphDuplicate($target_entity, $target_langcode);\n+ }\n+\n+ $this->doSaveTranslations($duplicate, $field_item[$property], $target_langcode, $item, FALSE);\n+\n+ \/\/ Ensure the duplicate is saved before adding it to the field\n+ if ($duplicate->isNew()) {\n+ $duplicate->save();\n+ }\n+\n+ $new_field_value[] = [\n+ 'target_id' => $duplicate->id(),\n+ 'target_revision_id' => $duplicate->getRevisionId(),\n+ ];\n+ }\n+ }\n+ }\n+\n+ \/\/ Set the new field value\n+ $translation->set($field_name, $new_field_value);\n+\n+ \/\/ Log the number of paragraphs processed\n+ \\Drupal::logger('tmgmt_content')->info('Processed @count paragraphs for field @field in @entity_type @entity_id', [\n+ '@count' => count($new_field_value),\n+ '@field' => $field_name,\n+ '@entity_type' => $translation->getEntityTypeId(),\n+ '@entity_id' => $translation->id(),\n+ ]);\n+ }\n \n+ \/**\n+ * Creates an asymmetric paragraph duplicate.\n+ *\n+ * @param \\Drupal\\Core\\Entity\\ContentEntityInterface $paragraph\n+ * The original paragraph entity.\n+ * @param string $target_langcode\n+ * The target language code.\n+ *\n+ * @return \\Drupal\\Core\\Entity\\ContentEntityInterface\n+ * The duplicated paragraph entity.\n+ *\/\n+ protected function createAsymmetricParagraphDuplicate(ContentEntityInterface $paragraph, $target_langcode) {\n+ $duplicate = $paragraph->createDuplicate();\n+ $duplicate->set($duplicate->getEntityType()->getKey('langcode'), $target_langcode);\n+\n+ \/\/ Recursively duplicate nested paragraphs\n+ foreach ($duplicate->getFields() as $field_name => $field) {\n+ if ($field->getFieldDefinition()->getType() == 'entity_reference_revisions' &&\n+ $field->getFieldDefinition()->getSetting('target_type') == 'paragraph') {\n+ $new_field_value = [];\n+ foreach ($field as $delta => $item) {\n+ if ($item->entity) {\n+ $nested_duplicate = $this->createAsymmetricParagraphDuplicate($item->entity, $target_langcode);\n+ $new_field_value[] = ['entity' => $nested_duplicate];\n+ }\n+ }\n+ $duplicate->set($field_name, $new_field_value);\n+ }\n+ }\n+\n+ return $duplicate;\n+ }\n \/**\n * Creates a translation duplicate of the given entity.\n *\n<\/code><\/pre>\n\n\n\n