tableau-mkt-archived / entity_xliff

Drupal module that provides an API for entity serialization in the XLIFF format.
https://drupal.org/project/entity_xliff
2 stars 0 forks source link

Fix bug preventing XLIFF imports when content structure has changed #81

Closed iamEAP closed 8 years ago

iamEAP commented 8 years ago

Todo

Closes #80

iamEAP commented 8 years ago

I see two directions to hit this problem:

Bottom Up

Try and fix it at the spot where it's detected... Specifically, when SomeTranslatable::getTargetEntity($lang) is called and within it, $this->entity->value() === NULL.

We could resolve the issue in this scenario by "creating" a blank entity/bundle, wrapping it, and setting it as the target. Although we can figure out the entity type and even field name relatively easily (based on data in the metadata wrapper), there's no easy way to determine the bundle of the wrapped entity...

In order to do that, we'd have to traverse the wrapper's parents until we got to the root entity... Determine the source entity (because we're looking at the target at this point), then dive back down into its children (same route as we took back up) just to get the bundle.

Summary: Trivial issue detection, difficult resolution.

Top down

Try and fix it before it happens... Specifically, when a target entity is being pulled, check the entity reference fields of the target compared to the source entity... If there are values in the source that are NULL in the target, clone them to the target before wrapping it.

Summary: Messier detection, relatively simple resolution.

There may be more exotic solutions...

iamEAP commented 8 years ago

Even the "top down" approach ends up being extremely cumbersome and I still couldn't get the regression tests to pass. Pasting here for reference...


  /**
   * Prepares an existing target entity for translation. Used to account for new
   * content structure on the source that does not yet exist on the target.
   *
   * @param object $source
   * @param object $target
   * @return object
   */
  public function prepareTarget($source, $target) {
    $translatableFields = $this->getTranslatableFields();
    $this->drupal->alter('entity_xliff_translatable_fields', $translatableFields, $this->entity);

    foreach ($translatableFields as $field) {
      $info = $this->entity->getPropertyInfo($field);
      $pattern = '/^(list)?<?(.*?)(?:>)?$/';
      if (isset($info['type']) && preg_match($pattern, $info['type'], $matches)) {
        $type = $matches[2];
        $isList = isset($matches[1]) && !empty($matches[1]);
        $isEntity = isset($this->entityInfo[$type]);

        if ($isEntity) {
          $sourceWrapper = entity_metadata_wrapper('node', $source);
          $sourceField = $sourceWrapper->{$field};
          $targetWrapper = entity_metadata_wrapper('node', $target);
          $targetField = $targetWrapper->{$field};
          $entityCreateArray = function($entityInfo, $source) {
            $keys = array();
            foreach ($entityInfo['entity keys'] as $keyId => $key) {
              if (!in_array($keyId, array('id', 'revision'))) {
                $keys[$key] = $source->{$key};
              }
            }
            return $keys;
          };

          if ($isList && count($sourceField->value()) > count($targetField->value())) {
            foreach ($sourceField->value() as $delta => $valueWrapper) {
              if (!isset($targetField[$delta])) {
                $template = entity_create($type, $entityCreateArray($this->entityInfo[$type], $valueWrapper));
                $template->save(TRUE);
                $targetWrapper->{$field}[$delta] = $template;
              }
            }
          }
          elseif ($sourceField->value() !== NULL && $targetField->value() === NULL) {
            $template = entity_create($type, $entityCreateArray($this->entityInfo[$type], $sourceField->value()));
            $template->save(TRUE);
            $targetWrapper->{$field} = $template;
          }
        }
      }
    }

    return isset($targetWrapper) ? $targetWrapper->value() : $target;

What I ended up doing is essentially re-implementing translation_node_prepare()...

...Which gave me the idea to just re-use the function against the source node, and then simply remove the $node->is_new flag and paste in the nid/vid of the existing translation. It's a bit more wasteful in terms of Paragraphs/Field Collections (because new ones are created each time, rather than re-using existing versions), but I believe it's the only way to rid ourselves of this problem...

iamEAP commented 8 years ago

Still requires manual testing...

iamEAP commented 8 years ago

Manual testing has revealed that, although this fixes the fatal error... It dissociates the translation from the source node...

iamEAP commented 8 years ago

Manual testing of the changes above check out with expectations.