Field_collection et i18n

Le module field_collection est très pratique voire indispensable lorsqu’on a des types de contenus assez poussés. Je ne vais pas expliquer à quoi il sert ici, la page du module est suffisamment explicite.

Seulement, dès qu’on utilise le module i18n pour gérer de la traduction sur son site, ça devient vite la misère si on ne fait pas attention.

Pour cela, il faut bien comprendre l’architecture utilisée par le module field_collection.

Architecture d’une collection

Voici l’architecture d’un noeud contenant un champ de type « field_collection », laquelle est composée de 3 champs.

Pour plus de clarté, je préfère un bon schéma expliquant comment sont stockées et liées les données d’un champ field_collection :

 

Si vous utilisez i18n et i18n_sync pour traduire vos contenus

Là, il faut être vigilent ! i18n va vous permettre de créer une version étrangère d’un contenu initial.

i18n_sync va permettre de conserver une synchronisation des champs souhaités, entre chaque version : vous modifiez l’anglais, le champ FR est automatiquement mis à jour.
C’est notamment très intéressant pour les taxonomies.

Pour un champ texte, ça n’a pas trop de sens, puisqu’il dépend entièrement de la langue.

Pour un champ de type field_collection, ça se corse : on ne peut synchroniser un seul ou certains champs seulement de la field_collection, mais uniquement le champ « global » de type collection.

Autrement dit, c’est le conteneur et non les champs qu’il contient, qui seront synchronisés. Ce qui est logique, puisque c’est le champ collection qui est attaché au noeud.

Du point de vue Drupal : il stocke la valeur du champ dans la colonne « value » du field, donc pour un champ texte, c’est le texte saisie que contient la colonne « value », pour un champ collection, c’est l’ID de la collection.

2 phases se distingues lorsqu’il s’agit d’i18n :

  1. la création d’une version étrangère : Drupal copie alors toutes les valeurs des champs du contenu initial vers le contenu à traduire.
  2. la modification d’une version étrangère : Drupal synchronise les champs marqués comme tels, via le module i18n_sync.

 

Pour la phase 1 : la valeur d’un champ « collection » étant l’ID de celle-ci, on se retrouve avec le même ID de collection d’une langue à l’autre. ATTENTION ATTENTION ATTENTION ! Vous vous retrouvez du coup avec la même collection en FR comme en EN par ex, si vous modifiez l’une, l’autre est modifiée puisqu’en réalité, c’est la même.

J’ai paré à ce problème en créant une nouvelle collection à la création du contenu étranger, excepté s’il est marqué comme « à synchroniser » (car dans ce cas, c’est volontaire que l’ID soit partagé d’une langue à l’autre).

 

function <modulename>_field_attach_prepare_translation_alter(&$entity, $context) {

  // Champs à synchroniser
  $sync_fields = i18n_sync_node_fields($entity->type);

  // Champs appartenant au bundle
  $entityFields = field_info_instances('node', $entity->type);

  // Pour chaque champ du bundle
  foreach ($entityFields as $entityField => $infos) {

    // Params globaux du champs
    $fieldInfo = field_info_field($entityField);

    // On ne copie pas les field_collections à ne pas synchroniser
    // car sinon, l'ITEM_ID de la FC se retrouve être le même
    // dans la version source et la version étrangère cible
    if ($fieldInfo['type'] == 'field_collection' && !in_array($entityField, $sync_fields)) {

      // au lieu de ça, on crée une nouvelle field_collection
      // qui est la copie de la source
      // pour bien dé-solidariser la FC de la cible de celle de la source

      // Liste des items de la collection source
      $fcList = $context['source_entity']->$entityField;

      // Quels sont les champs de ce field_collection ?
      $fcFields = field_info_instances('field_collection_item', $entityField);
      $fcFieldNames = array();
      foreach ($fcFields as $fcField) {
        $fcFieldNames[] = $fcField['field_name'];
      }

      // Pour chaque items, on copie les valeurs des champs
      // dans un nouvel FC item, qu'on rattache à la nouvelle entité cible
      $i = 0;
      if (isset($fcList[LANGUAGE_NONE])) {
        foreach ($fcList[LANGUAGE_NONE] as $oneFieldCollItem) {
          $fci = field_collection_item_load($oneFieldCollItem['value']);

          // Création d'une nouvelle entité field_collection vierge
          $field_collection = entity_create('field_collection_item', array(
            'field_name' => $entityField
          ));
          $field_collection->language = LANGUAGE_NONE;
          $field_collection->setHostEntity('node', $entity, $entity->language, FALSE);

          // On attache les différents champs à la collection
          // en prenant les valeurs de la source
          foreach ($fcFieldNames as $fcFieldName) {
            // copie du champ de la collection source vers la collection cible
            $field_collection->$fcFieldName = $fci->$fcFieldName;
          }

          // création en BDD
          // La limite ici : on crée la FC même si l'utilisateur annule la création de la version étrangère...
          $field_collection->save(TRUE);

          // on attache la FC à l'entité cible, grâce à l'id de l'item nouvellement créé
          $entity->{$entityField}[LANGUAGE_NONE][$i]['value'] = $field_collection->item_id;

          $i++;
        }
      }
    }
  }
}

 

Il faut avoir ajouté ce code avant de commencer à créer les contenus, sinon, les contenus créés partagent déjà l’ID de la collection avec leurs sources respectives. Ceci dit, j’avais eu le problème et créé un batch pour dissocier les FC d’une langue à l’autre a posteriori, donc c’est possible ! Rien n’est perdu !

 

Pour la phase 2, de nouveau il y a 2 cas :

  • le champ n’est pas synchronisé : tout va bien, chaque données est bien séparée par langue, car il s’agit de collection différentes d’une langue à l’autre. (si et seulement si l’ID n’a pas été copié à la création…)
  • le champ est synchronisé : la valeur du champ « collection » étant l’ID de la collection, cet ID est le même d’une langue à l’autre, donc vous modifiez exactement la même collection d’une langue à l’autre !!

 

Une fois qu’un champ est marqué comme « à synchroniser » ou « à ne pas synchronisé », il faut s’y tenir, sans quoi vous entrez de nouveaux dans tous ces problèmes…

J’espère que ces explications auront pu vous aider !