diff --git a/includes/commerce_line_item.inline_entity_form.inc b/includes/commerce_line_item.inline_entity_form.inc index 642affb..0c767b9 100644 --- a/includes/commerce_line_item.inline_entity_form.inc +++ b/includes/commerce_line_item.inline_entity_form.inc @@ -98,7 +98,7 @@ class CommerceLineItemInlineEntityFormController extends EntityInlineEntityFormC '#weight' => $extra_fields['label']['weight'], '#fieldset' => 'line_item_details', ); - field_attach_form('commerce_line_item', $line_item, $entity_form, $form_state); + $entity_form = parent::entityForm($entity_form, $form_state); // Tweaks specific to product line items. if (in_array($line_item->type, $this->productLineItemTypes())) { diff --git a/includes/commerce_product.inline_entity_form.inc b/includes/commerce_product.inline_entity_form.inc index 5dcb6f2..9b95433 100644 --- a/includes/commerce_product.inline_entity_form.inc +++ b/includes/commerce_product.inline_entity_form.inc @@ -194,9 +194,7 @@ class CommerceProductInlineEntityFormController extends EntityInlineEntityFormCo '#weight' => $extra_fields['status']['weight'], ); - // Attach fields. - $langcode = entity_language('commerce_product', $product); - field_attach_form('commerce_product', $product, $entity_form, $form_state, $langcode); + $entity_form = parent::entityForm($entity_form, $form_state); // Hide or disable the SKU field if it is auto-generated by Commerce AutoSKU. if (module_exists('commerce_autosku') && $settings = commerce_autosku_get_settings($product)) { @@ -359,7 +357,10 @@ class CommerceProductInlineEntityFormController extends EntityInlineEntityFormCo // Generate the product title. Take the parent entity title as the base. if ($this->settings['autogenerate_title']) { - $entity->title = entity_label($context['parent_entity_type'], $context['parent_entity']); + // Update the entity label only with a valid value. + if ($label = entity_label($context['parent_entity_type'], $context['parent_entity'])) { + $entity->title = $label; + } $attributes = $this->attributes($entity->type); if (!empty($attributes)) { $wrapper = entity_metadata_wrapper('commerce_product', $entity); @@ -375,6 +376,18 @@ class CommerceProductInlineEntityFormController extends EntityInlineEntityFormCo $entity->title .= ' (' . implode(', ', $attribute_values) . ')'; } } + + // Update the autogenerated title field, otherwise when dealing with + // translations the source language would be used. + if (module_exists('title') && entity_label($this->entityType, $entity)) { + $legacy_field = 'title'; + list(, , $bundle) = entity_extract_ids($this->entityType, $entity); + if (title_field_replacement_enabled($this->entityType, $bundle, $legacy_field)) { + $info = title_field_replacement_info($this->entityType, $legacy_field); + $langcode = $info['field']['translatable'] ? entity_language($this->entityType, $entity) : LANGUAGE_NONE; + title_field_sync_set($this->entityType, $entity, $legacy_field, $info, $langcode); + } + } } entity_save('commerce_product', $entity); diff --git a/includes/entity.inline_entity_form.inc b/includes/entity.inline_entity_form.inc index 89a4246..6536630 100644 --- a/includes/entity.inline_entity_form.inc +++ b/includes/entity.inline_entity_form.inc @@ -272,10 +272,85 @@ class EntityInlineEntityFormController { field_attach_form($this->entityType, $entity, $entity_form, $form_state, $langcode); } + $this->entityFormTranslation($entity_form); return $entity_form; } /** + * Handles entity translation for inline entities. + * + * @param $entity_form + * The entity form. + */ + protected function entityFormTranslation(&$entity_form) { + if (($handler = module_invoke('entity_translation', 'get_handler', $this->entityType, $entity_form['#entity'])) && !$handler->isNewEntity()) { + $form_langcode = $handler->getActiveLanguage(); + $langcode = !empty($entity_form['#parent_language']) ? $entity_form['#parent_language'] : $handler->getLanguage(); + $translations = $handler->getTranslations(); + $update_langcode = $form_langcode && ($form_langcode != $langcode); + $source = $handler->getSourceLanguage(); + $new_translation = !isset($translations->data[$form_langcode]); + + if (!isset($translations->data[$form_langcode]) || count($translations->data) > 1) { + $handler->entityFormSharedElements($entity_form); + } + + // If we are creating a new translation we need to retrieve form elements + // populated with the source language values. In this case source values + // have already been populated, so we need to preserve possible changes. + // There might be situations, e.g. ajax calls, where the form language + // has not been properly initialized before calling field_attach_form(). + // In this case we need to rebuild the form with the correct form language + // and replace the field elements with the correct ones. + // Borrowed some code from entity_translation_field_attach_form() + // that won't get run because $form_state['rebuild'] is TRUE for all IEF + // forms. + if ($update_langcode || ($source && !isset($translations->data[$form_langcode]) && isset($translations->data[$source]))) { + $entity_type = $entity_form['#entity_type']; + list($id, , $bundle) = entity_extract_ids($entity_type, $entity_form['#entity']); + foreach (field_info_instances($entity_type, $bundle) as $instance) { + + $field_name = $instance['field_name']; + $field = field_info_field($field_name); + + // If we are creating a new translation we have to change the form + // item language information from source to target language, this way + // the user can find the form items already populated with the source + // values while the field form element holds the correct language + // information. + if ($field['translatable']) { + $element = &$entity_form[$field_name]; + $element['#entity_type'] = $entity_type; + $element['#entity'] = $entity_form['#entity']; + $element['#entity_id'] = $id; + $element['#field_name'] = $field_name; + $element['#source'] = $update_langcode ? $form_langcode : $source; + $element['#previous'] = NULL; + $element['#form_parents'] = $entity_form['#parents']; + + // If we are updating the form language we need to make sure that + // the wrong language is unset and the right one is stored in the + // field element (see entity_translation_prepare_element()). + if ($update_langcode) { + $element['#previous'] = $element['#language']; + $element['#language'] = $form_langcode; + } + + // Swap default values during form processing to avoid recursion. + // We try to act before any other callback so that the correct + // values are already in place for them. + if (!isset($element['#process'])) { + $element['#process'] = array(); + } + array_unshift($element['#process'], 'entity_translation_prepare_element'); + } + } + } + $entity_form['#element_validate'][] = 'entity_translation_entity_form_validate'; + } + } + + /** * Validates the entity form. * * @param $entity_form @@ -308,7 +383,7 @@ class EntityInlineEntityFormController { $info = entity_get_info($this->entityType); list(, , $bundle) = entity_extract_ids($this->entityType, $entity_form['#entity']); $entity = $entity_form['#entity']; - $entity_values = drupal_array_get_nested_value($form_state['values'], $entity_form['#parents']); + $entity_values = &drupal_array_get_nested_value($form_state['values'], $entity_form['#parents']); // Copy top-level form values that are not for fields to entity properties, // without changing existing entity properties that are not being edited by @@ -318,8 +393,61 @@ class EntityInlineEntityFormController { $entity->$key = $value; } + // Update the entity language using the parent entity submitted value. + if (!empty($info['entity keys']['language'])) { + $parent_info = entity_get_info($entity_form['#parent_entity_type']); + if (isset($parent_info['entity keys']['language']) && isset($form_state['values'][$parent_info['entity keys']['language']])) { + $entity->{$info['entity keys']['language']} = $form_state['values'][$parent_info['entity keys']['language']]; + $handler = module_invoke('entity_translation', 'get_handler', $this->entityType, $entity); + $lang = $handler->getActiveLanguage(); + if ($lang == LANGUAGE_NONE) { + if ($entity_form['#parent_language'] != LANGUAGE_NONE) { + $handler->setFormLanguage($entity_form['#parent_language']); + } + else { + $handler->setFormLanguage($form_state['values'][$parent_info['entity keys']['language']]); + } + } + } + } + if ($info['fieldable']) { + // Retrieve the current entity form language. + $langcode = entity_language($this->entityType, $entity); + + // Handle a possible language change: new language values are inserted, + // previous ones are deleted. + foreach (field_info_instances($this->entityType, $bundle) as $instance) { + $field_name = $instance['field_name']; + $field = field_info_field($field_name); + $previous_langcode = $entity_form[$field_name]['#language']; + if ($field['translatable'] && $previous_langcode != $langcode && ($langcode != LANGUAGE_NONE && $previous_langcode != language_default('language'))) { + $entity_values[$field_name][$langcode] = $entity_values[$field_name][$previous_langcode]; + $entity_values[$field_name][$previous_langcode] = array(); + } + } + field_attach_submit($this->entityType, $entity, $entity_form, $form_state); + + // Handle translation submission. + $this->entityTranslationFormSubmit($entity_form, $entity); + } + } + + /** + * Creates an entity translation if needed. + */ + protected function entityTranslationFormSubmit($entity_form, $entity) { + if (($handler = module_invoke('entity_translation', 'get_handler', $this->entityType, $entity)) && ($source = $handler->getSourceLanguage())) { + list($id, , ) = entity_extract_ids($this->entityType, $entity); + $translation = array( + 'entity_type' => $this->entityType, + 'entity_id' => $id, + 'language' => $handler->getActiveLanguage(), + 'source' => $source, + 'status' => 1, + ); + $handler->setTranslation($translation); } } @@ -362,7 +490,7 @@ class EntityInlineEntityFormController { * @param $form_state * The form state of the parent form. * - * @return + * @return int * IEF_ENTITY_UNLINK or IEF_ENTITY_UNLINK_DELETE. */ public function removeFormSubmit($remove_form, &$form_state) { @@ -383,6 +511,31 @@ class EntityInlineEntityFormController { } /** + * Returns the remove translation form to be shown through the IEF widget. + * + * @param $remove_form + * The remove form. + * @param $form_state + * The form state of the parent form. + */ + public function removeTranslationForm($remove_form, &$form_state) { + $entity = $remove_form['#entity']; + list($entity_id) = entity_extract_ids($this->entityType, $entity); + $entity_label = entity_label($this->entityType, $entity); + $langcode = entity_language($this->entityType, $entity); + $languages = language_list(); + + $remove_form['message'] = array( + '#markup' => '
' . t('Are you sure you want to remove the @language_label translation for %label?', array( + '%label' => $entity_label, + '@language_label' => isset($languages[$langcode]) ? $languages[$langcode]->name : $langcode, + )) . '
', + ); + + return $remove_form; + } + + /** * Creates a clone of the given entity. * * Copies the entity_ui_clone_entity() approach, extending it to unset diff --git a/includes/node.inline_entity_form.inc b/includes/node.inline_entity_form.inc index 4d481cb..c879b5c 100644 --- a/includes/node.inline_entity_form.inc +++ b/includes/node.inline_entity_form.inc @@ -65,10 +65,7 @@ class NodeInlineEntityFormController extends EntityInlineEntityFormController { '#weight' => 99, ); - $langcode = entity_language('node', $node); - field_attach_form('node', $node, $entity_form, $form_state, $langcode); - - return $entity_form; + return parent::entityForm($entity_form, $form_state); } /** diff --git a/includes/taxonomy_term.inline_entity_form.inc b/includes/taxonomy_term.inline_entity_form.inc index bd2ec59..eb1667c 100644 --- a/includes/taxonomy_term.inline_entity_form.inc +++ b/includes/taxonomy_term.inline_entity_form.inc @@ -85,10 +85,7 @@ class TaxonomyTermInlineEntityFormController extends EntityInlineEntityFormContr '#weight' => !empty($extra_fields['description']) ? $extra_fields['description']['weight'] : -4, ); - $langcode = entity_language('taxonomy_term', $term); - field_attach_form('taxonomy_term', $term, $entity_form, $form_state, $langcode); - - return $entity_form; + return parent::entityForm($entity_form, $form_state); } diff --git a/inline_entity_form.info b/inline_entity_form.info index a5a87db..bc6a4c3 100644 --- a/inline_entity_form.info +++ b/inline_entity_form.info @@ -14,3 +14,4 @@ files[] = includes/commerce_product.inline_entity_form.inc files[] = includes/commerce_line_item.inline_entity_form.inc files[] = tests/inline_entity_form_test_base.test files[] = tests/multiple_values_widget.test +files[] = tests/entity_translation_integration.test diff --git a/inline_entity_form.module b/inline_entity_form.module index c1a7d82..5efe745 100644 --- a/inline_entity_form.module +++ b/inline_entity_form.module @@ -191,6 +191,46 @@ function inline_entity_form_entity_delete($entity, $type) { } /** + * Implements hook_entity_translation_delete(). + * + * Deletes a translation of the referenced entity. + */ +function inline_entity_form_entity_translation_delete($entity_type, $entity, $langcode) { + list(,, $bundle) = entity_extract_ids($entity_type, $entity); + foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) { + if (strpos($instance['widget']['type'], 'inline_entity_form') === 0) { + $controller = inline_entity_form_get_controller($instance); + + // The controller specified that referenced entities should be deleted. + if ($controller && $controller->getSetting('delete_references')) { + $items = field_get_items($entity_type, $entity, $field_name, $langcode); + if ($items) { + $field = field_info_field($field_name); + $ief_settings = inline_entity_form_settings($field, $instance); + + $ids = array(); + foreach ($items as $item) { + $ids[] = $item[$ief_settings['column']]; + } + + $ref_entity_type = $ief_settings['entity_type']; + $context = array( + 'parent_entity_type' => $entity_type, + 'parent_entity' => $entity, + ); + + foreach (entity_load($ref_entity_type, $ids) as $id => $ref_entity) { + $handler = entity_translation_get_handler($ref_entity_type, $ref_entity); + $handler->removeTranslation($langcode); + $controller->save($ref_entity, $context); + } + } + } + } + } +} + +/** * Attaches theme specific CSS files. * * @param $theme_css @@ -397,6 +437,7 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins $widget = $instance['widget']; $settings = inline_entity_form_settings($field, $instance); $entity_info = entity_get_info($settings['entity_type']); + $parent_entity_info = entity_get_info($element['#entity_type']); $controller = inline_entity_form_get_controller($instance); // The current entity type is not supported, execution can't continue. if (!$controller) { @@ -406,11 +447,29 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins // Get the entity type labels for the UI strings. $labels = $controller->labels(); + // Use current form language for translatable field. + if (!empty($form_state['entity_translation']['is_translation']) && $langcode != LANGUAGE_NONE) { + $langcode = $form_state['entity_translation']['form_langcode']; + } + // Build a parents array for this element's values in the form. - $parents = array_merge($element['#field_parents'], array($element['#field_name'], $element['#language'])); + $parents = array_merge($element['#field_parents'], array($element['#field_name'], $langcode)); - // Get the langcode of the parent entity. - $parent_langcode = entity_language($element['#entity_type'], $element['#entity']); + // If the parent entity is new, the language is not yet set. Use the langcode + // from the parent form if existing in this case. + if (!isset($element['#entity']->{$parent_entity_info['entity keys']['id']}) && isset($form_state['complete form']['language'])) { + $parent_langcode = isset($form_state['complete form']['language']['#value']) ? $form_state['complete form']['language']['#value'] : $form_state['complete form']['language']['#default_value']; + $parent_is_translation = FALSE; + } + else { + // Get the langcode of the parent entity. + $parent_langcode = (!empty($form_state['entity_translation']['is_translation'])) ? $form_state['entity_translation']['form_langcode'] : entity_language($element['#entity_type'], $element['#entity']); + $handler = module_invoke('entity_translation', 'get_handler', $element['#entity_type'], $element['#entity']); + if (isset($handler)){ + $translations = $handler->getTranslations(); + $parent_is_translation = isset($translations->original) && $parent_langcode != $translations->original; + } + } // Assign a unique identifier to each IEF widget. // Since $parents can get quite long, sha1() ensures that every id has @@ -448,17 +507,35 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins // Add entity type specific CSS. _inline_entity_form_attach_css($controller->css(), $element['#attached']['css']); + // Pre-populate the widget with the values from source language. + // Do it only if translation is new. If user intends not to have any + // child items in the translatable field, then do not pre-populate. + // Normally, entity_translation_prepare_element() function + // should take care of that, however it does not work, since it fails to deal + // with the state stored in $form_state['inline_entity_form']. + // @See: https://www.drupal.org/node/2339315#comment-12235439. + // If the patch there is committed, then we could refactor the if-block below + // to be aligned with entity_translation_prepare_element() approach. + if (!empty($field['translatable']) && + $parent_is_translation && + !$items && + empty($translations->data[$parent_langcode]) && + !empty($element['#entity']->{$instance['field_name']}[$translations->original]) + ) { + $items = $element['#entity']->{$instance['field_name']}[$translations->original]; + } + // Initialize the IEF array in form state. if (empty($form_state['inline_entity_form'][$ief_id])) { $form_state['inline_entity_form'][$ief_id] = array( 'form' => NULL, 'settings' => $settings, 'instance' => $instance, + 'entities' => array(), ); // Load the entities from the $items array and store them in the form // state for further manipulation. - $form_state['inline_entity_form'][$ief_id]['entities'] = array(); $entity_ids = array(); foreach ($items as $item) { $entity_ids[] = $item[$settings['column']]; @@ -508,6 +585,8 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins '#parents' => array_merge($parents, array('form')), // Pass the current entity type. '#entity_type' => $settings['entity_type'], + '#parent_entity' => $element['#entity'], + '#parent_entity_type' => $element['#entity_type'], // Pass the langcode of the parent entity, '#parent_language' => $parent_langcode, // Identifies the IEF widget to which the form belongs. @@ -577,6 +656,8 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins '#entity' => $entity, '#entity_type' => $settings['entity_type'], // Pass the langcode of the parent entity, + '#parent_entity' => $element['#entity'], + '#parent_entity_type' => $element['#entity_type'], '#parent_language' => $parent_langcode, // Identifies the IEF widget to which the form belongs. '#ief_id' => $ief_id, @@ -608,12 +689,20 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins '#attributes' => array('class' => array('ief-entity-operations')), ); + // If we are editing a translation and the inline entity is + // translated, provide some different options. + $handler = module_invoke('entity_translation', 'get_handler', $controller->entityType(), $entity); + if (isset($handler)){ + $translations = $handler->getTranslations(); + $is_translation = $parent_is_translation && isset($translations->original) && $parent_langcode != $translations->original && isset($translations->data[$parent_langcode]); + } + // Make sure entity_access is not checked for unsaved entities. list($entity_id) = entity_extract_ids($controller->entityType(), $entity); if (empty($entity_id) || entity_access('update', $controller->entityType(), $entity)) { $row['actions']['ief_entity_edit'] = array( '#type' => 'submit', - '#value' => t('Edit'), + '#value' => $is_translation ? t('Edit translation') : ($parent_is_translation ? t('Add translation') : t('Edit')), '#name' => 'ief-' . $ief_id . '-entity-edit-' . $key, '#limit_validation_errors' => array(), '#ajax' => array( @@ -629,7 +718,7 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins // The clone form follows the same semantics as the create form, so // it's opened below the table. if ($controller->getSetting('allow_clone') && !$cardinality_reached - && entity_access('create', $controller->entityType(), $entity)) { + && entity_access('create', $controller->entityType(), $entity) && !$parent_is_translation) { $row['actions']['ief_entity_clone'] = array( '#type' => 'submit', '#value' => t('Clone'), @@ -650,19 +739,41 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins // removeForm() method. if (empty($entity_id) || $controller->getSetting('allow_existing') || entity_access('delete', $controller->entityType(), $entity)) { - $row['actions']['ief_entity_remove'] = array( - '#type' => 'submit', - '#value' => t('Remove'), - '#name' => 'ief-' . $ief_id . '-entity-remove-' . $key, - '#limit_validation_errors' => array(), - '#ajax' => array( - 'callback' => 'inline_entity_form_get_element', - 'wrapper' => $wrapper, - ), - '#submit' => array('inline_entity_form_open_row_form'), - '#ief_row_delta' => $key, - '#ief_row_form' => 'remove', - ); + + // Either provide a button to remove the translation or to remove + // the whole reference. + if ($is_translation) { + $row['actions']['ief_entity_remove_translation'] = array( + '#type' => 'submit', + '#value' => t('Remove translation'), + '#name' => 'ief-' . $ief_id . '-entity-remove-' . $key, + '#limit_validation_errors' => array(), + '#ajax' => array( + 'callback' => 'inline_entity_form_get_element', + 'wrapper' => $wrapper, + ), + '#submit' => array('inline_entity_form_open_row_form'), + '#ief_row_delta' => $key, + '#ief_row_form' => 'remove', + '#ief_row_is_translation' => $is_translation, + ); + } + if (!$parent_is_translation || !empty($field['translatable'])) { + $row['actions']['ief_entity_remove'] = array( + '#type' => 'submit', + '#value' => t('Remove'), + '#name' => 'ief-' . $ief_id . '-entity-remove-' . $key, + '#limit_validation_errors' => array(), + '#ajax' => array( + 'callback' => 'inline_entity_form_get_element', + 'wrapper' => $wrapper, + ), + '#submit' => array('inline_entity_form_open_row_form'), + '#ief_row_delta' => $key, + '#ief_row_form' => 'remove', + '#ief_row_is_translation' => $is_translation && empty($field['translatable']), + ); + } } } } @@ -714,7 +825,7 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins ); // The user is allowed to create an entity of at least one bundle. - if (count($settings['create_bundles'])) { + if (count($settings['create_bundles']) && !$parent_is_translation) { // Let the user select the bundle, if multiple are available. if (count($settings['create_bundles']) > 1) { $bundles = array(); @@ -778,6 +889,8 @@ function inline_entity_form_field_widget_form(&$form, &$form_state, $field, $ins // values in $form_state. '#parents' => array_merge($parents, array('form')), // Pass the current entity type. + '#parent_entity' => $element['#entity'], + '#parent_entity_type' => $element['#entity_type'], '#entity_type' => $settings['entity_type'], // Pass the langcode of the parent entity, '#parent_language' => $parent_langcode, @@ -904,6 +1017,16 @@ function inline_entity_form_entity_form($controller, $entity_form, &$form_state) $save_label = t('Clone @type_singular', array('@type_singular' => $labels['singular'])); } + // Register a child entity translation handler to properly deal with the + // entity form language. + list(, , $bundle) = entity_extract_ids($entity_form['#entity_type'], $entity_form['#entity']); + if (module_invoke('entity_translation', 'enabled', $entity_form['#entity_type'], $bundle) && $entity_form['#parent_entity']->translations->original != null) { + inline_entity_form_add_child_translation_handler($entity_form); + // Ensure this is executed even with cached forms. This is mainly useful + // when dealing with AJAX calls. + $entity_form['#process'][] = 'inline_entity_form_add_child_translation_handler'; + } + // Retrieve the form provided by the controller. $entity_form = $controller->entityForm($entity_form, $form_state); @@ -977,6 +1100,22 @@ function inline_entity_form_entity_form($controller, $entity_form, &$form_state) } /** + * Registers a child entity translation handler for the given element. + */ +function inline_entity_form_add_child_translation_handler($element) { + $handler = entity_translation_get_handler($element['#parent_entity_type'], $element['#parent_entity']); + + $handler->setFormLanguage($element['#parent_language']); + $source = $handler->getSourceLanguage(); + if (!($source) && $element['#parent_entity']->translations->original != $element['#parent_language']) { + $handler->setSourceLanguage($element['#parent_entity']->translations->original); + } + + $handler->addChild($element['#entity_type'], $element['#entity']); + return $element; +} + +/** * Validates an entity form. * * @param $entity_form @@ -1211,12 +1350,22 @@ function inline_entity_form_remove_form($controller, $remove_form, &$form_state) $delta = $remove_form['#ief_id'] . '-' . $remove_form['#ief_row_delta']; // Retrieve the form provided by the controller. - $remove_form = $controller->removeForm($remove_form, $form_state); + $row_is_translation = $form_state['triggering_element']['#ief_row_is_translation']; + if ($row_is_translation) { + $remove_form = inline_entity_form_add_child_translation_handler($remove_form); + $remove_form = $controller->removeTranslationForm($remove_form, $form_state); + } + else { + $remove_form = $controller->removeForm($remove_form, $form_state); + } // Add the actions $remove_form['actions'] = array( '#type' => 'container', '#weight' => 100, ); + // @todo: Compare entity orignal language with translation langauge. IF this + // is a translation, switch buttons and replace inline_entity_form_remove_confirm + // with variant that calls removeTranslationFormSubmit on the handler. $remove_form['actions']['ief_remove_confirm'] = array( '#type' => 'submit', '#value' => t('Remove'), @@ -1226,7 +1375,7 @@ function inline_entity_form_remove_form($controller, $remove_form, &$form_state) 'callback' => 'inline_entity_form_get_element', 'wrapper' => 'inline-entity-form-' . $remove_form['#ief_id'], ), - '#submit' => array('inline_entity_form_remove_confirm'), + '#submit' => array($row_is_translation ? 'inline_entity_form_remove_translation_confirm' : 'inline_entity_form_remove_confirm'), '#ief_row_delta' => $remove_form['#ief_row_delta'], ); $remove_form['actions']['ief_remove_cancel'] = array( @@ -1281,6 +1430,36 @@ function inline_entity_form_remove_confirm($form, &$form_state) { } /** + * Remove translation form submit callback. + * + * The row is identified by #ief_row_delta stored on the triggering + * element. + * This isn't an #element_validate callback to avoid processing the + * remove form when the main form is submitted. + * + * @param $form + * The complete parent form. + * @param $form_state + * The form state of the parent form. + */ +function inline_entity_form_remove_translation_confirm($form, &$form_state) { + $form_state['rebuild'] = TRUE; + $element = inline_entity_form_get_element($form, $form_state); + $ief_id = $element['#ief_id']; + $delta = $form_state['triggering_element']['#ief_row_delta']; + $remove_form = $element['entities'][$delta]['form']; + $entity = $form_state['inline_entity_form'][$ief_id]['entities'][$delta]['entity']; + + $settings = $form_state['inline_entity_form'][$ief_id]['settings']; + $handler = entity_translation_get_handler($settings['entity_type'], $entity); + $handler->removeTranslation($remove_form['#parent_language']); + $form_state['inline_entity_form'][$ief_id]['entities'][$delta]['needs_save'] = TRUE; + + // Close form. + $form_state['inline_entity_form'][$ief_id]['entities'][$delta]['form'] = NULL; +} + +/** * Button #submit callback: Triggers submission of entity forms. * * @param $form @@ -1550,6 +1729,20 @@ function inline_entity_form_field_attach_submit($parent_entity_type, $parent_ent $need_reset = FALSE; foreach ($values['entities'] as $item) { if ($item['needs_save']) { + + // Ensure the proper entity form language is set before saving. + if (module_invoke('entity_translation', 'enabled', $entity_type, $item['entity']) && ($handler = module_invoke('entity_translation', 'get_handler', $entity_type, $item['entity']))) { + // Fetch the parent language - with entity_translation this will + // return the current form language and thus match perfectly. + $parent_entity_language = entity_language($parent_entity_type, $parent_entity); + $entity_form_language = $handler->getActiveLanguage(); + // If the field is language independent set the parent entity + // language as form language of the entity. + if ($langcode == LANGUAGE_NONE && $entity_form_language != $parent_entity_language) { + $handler->setFormLanguage($parent_entity_language); + } + } + $controller->save($item['entity'], $context); $need_reset = TRUE; } diff --git a/tests/entity_translation_integration.test b/tests/entity_translation_integration.test new file mode 100644 index 0000000..8e440bf --- /dev/null +++ b/tests/entity_translation_integration.test @@ -0,0 +1,256 @@ + 'Entity translation integration', + 'description' => 'Tests the integration of Entity Translation with the Inline Entity Form.', + 'group' => 'Inline entity form', + ); + } + + /** + * @see InlineEntityFormTestBase::setUp() + */ + protected function setUp() { + $modules = array( + 'inline_entity_form', + 'inline_entity_form_test', + 'locale', + 'entity_translation', + 'entityreference', + ); + parent::setUp($modules); + + $this->administrator_user = $this->drupalCreateUser(array( + 'create ief_reference_type content', + 'edit any ief_reference_type content', + 'delete any ief_reference_type content', + 'create ief_test_multiple content', + 'edit any ief_test_multiple content', + 'delete any ief_test_multiple content', + 'bypass node access', + 'administer nodes', + 'administer fields', + 'administer languages', + 'administer content types', + 'administer blocks', + 'access administration pages', + 'administer site configuration', + 'administer entity translation', + 'translate any entity', + )); + $this->drupalLogin($this->administrator_user); + $this->addLanguage('de'); + $this->enableUrlLanguageDetection(); + $this->configureContentTypes(); + + $this->formContentAddUrl = 'node/add/ief-test-multiple'; + } + + /** + * Enable URL language detection. + */ + protected function enableUrlLanguageDetection() { + // Enable URL language detection and selection. + $edit = array( + 'language[enabled][locale-url]' => TRUE, + 'language_content[enabled][locale-interface]' => TRUE, + ); + $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings')); + $this->assertRaw(t('Language negotiation configuration saved.'), t('URL language detection enabled.')); + $this->drupalGet('admin/config/regional/language/configure'); + // Reset caches. + drupal_static_reset('locale_url_outbound_alter'); + drupal_static_reset('language_list'); + } + + /** + * Install a specified language if it has not been already. + * + * Otherwise make sure that the language is enabled. + * + * @param $langcode + * The language code to check. + */ + protected function addLanguage($langcode) { + // Check to make sure that language has not already been installed. + $this->drupalGet('admin/config/regional/language'); + + if (strpos($this->drupalGetContent(), 'enabled[' . $langcode . ']') === FALSE) { + // Doesn't have language installed so add it. + $edit = array(); + $edit['langcode'] = $langcode; + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Make sure we are not using a stale list. + drupal_static_reset('language_list'); + $languages = language_list('language'); + $this->assertTrue(array_key_exists($langcode, $languages), t('Language was installed successfully.')); + + if (array_key_exists($langcode, $languages)) { + $this->assertRaw(t('The language %language has been created and can now be used. More information is available on the help screen.', array('%language' => $languages[$langcode]->name, '@locale-help' => url('admin/help/locale'))), t('Language has been created.')); + } + } + elseif ($this->xpath('//input[@type="checkbox" and @name=:name and @checked="checked"]', array(':name' => 'enabled[' . $langcode . ']'))) { + // It is installed and enabled. No need to do anything. + $this->assertTrue(TRUE, 'Language [' . $langcode . '] already installed and enabled.'); + } + else { + // It is installed but not enabled. Enable it. + $this->assertTrue(TRUE, 'Language [' . $langcode . '] already installed.'); + $this->drupalPost(NULL, array('enabled[' . $langcode . ']' => TRUE), t('Save configuration')); + $this->assertRaw(t('Configuration saved.'), t('Language successfully enabled.')); + } + } + + /** + * Configure the "Inline entity form test" provided content types. + */ + protected function configureContentTypes() { + + // Configure Entity translation for each content type. + variable_set('entity_translation_settings_node__ief_reference_type', array( + 'default_language' => 'xx-et-current', + 'hide_language_selector' => 0, + 'exclude_language_none' => 0, + 'lock_language' => 0, + 'shared_fields_original_only' => 0, + )); + variable_set('entity_translation_settings_node__ief_test_multiple', array( + 'default_language' => 'xx-et-current', + 'hide_language_selector' => 0, + 'exclude_language_none' => 0, + 'lock_language' => 0, + 'shared_fields_original_only' => 0, + )); + + // Make the ief_reference_type and the ief_test_multiple multilingual. + foreach (array('ief_reference_type' => 'IEF reference type', 'ief_test_multiple' => 'IEF test multiple') as $ief_content_type_machine_name => $ief_content_type) { + $edit = array(); + $edit['language_content_type'] = ENTITY_TRANSLATION_ENABLED; + $this->drupalGet('admin/structure/types/manage/' . $ief_content_type_machine_name); + $this->drupalPost('admin/structure/types/manage/' . $ief_content_type_machine_name, $edit, t('Save content type')); + $this->assertRaw(t('The content type %type has been updated.', array('%type' => $ief_content_type)), t('%type content type has been updated.', array('%type' => $ief_content_type))); + } + + // Make the fields of the ief_reference_type multilingual. + $edit = array(); + $edit['field[translatable]'] = 1; + $this->drupalPost('admin/structure/types/manage/ief_reference_type/fields/field_first_name', $edit, t('Save settings')); + $this->assertRaw(t('Saved %field configuration.', array('%field' => 'First name')), t('field_first_name field settings have been updated.')); + $this->drupalPost('admin/structure/types/manage/ief_reference_type/fields/field_last_name', $edit, t('Save settings')); + $this->assertRaw(t('Saved %field configuration.', array('%field' => 'Last name')), t('field_last_name field settings have been updated.')); + + // Change the display of the entity reference to display the rendered + // entity. + $edit_field = array( + 'fields[field_multiple_nodes][type]' => 'entityreference_entity_view', + 'refresh_rows' => 'field_multiple_nodes', + ); + $this->drupalGet('admin/structure/types/manage/ief_test_multiple/display'); + $this->drupalPostAJAX(NULL, $edit_field, array('op' => t('Refresh'))); + $this->drupalPost(NULL, array(), t('Save')); + + $edit_field = array( + 'fields[field_multiple_nodes_translate][type]' => 'entityreference_entity_view', + 'refresh_rows' => 'field_multiple_nodes_translate', + ); + $this->drupalGet('admin/structure/types/manage/ief_test_multiple/display'); + $this->drupalPostAJAX(NULL, $edit_field, array('op' => t('Refresh'))); + $this->drupalPost(NULL, array(), t('Save')); + + } + + /** + * Tests translating inline entities. + */ + public function testEntityTranslation() { + $language_list = language_list(); + + // Get a node create form. + // Create a new node using IEF from a non-translatable field. + $this->drupalGet($this->formContentAddUrl); + $target_node_from_untranslatable_field_title = $this->randomName(); + $edit_en = array( + 'field_multiple_nodes[und][form][title]' => $target_node_from_untranslatable_field_title, + 'field_multiple_nodes[und][form][field_first_name][en][0][value]' => 'John ' . $target_node_from_untranslatable_field_title, + 'field_multiple_nodes[und][form][field_last_name][en][0][value]' => 'Doe ' . $target_node_from_untranslatable_field_title, + ); + $this->drupalPostAjax(NULL, $edit_en, $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-und-form-actions-ief-add-save"]')); + // Create a new node using IEF from a translatable field. + $target_node_from_translatable_field_title_en = $this->randomName(); + $edit_en = array( + 'field_multiple_nodes_translate[en][form][title]' => $target_node_from_translatable_field_title_en, + 'field_multiple_nodes_translate[en][form][field_first_name][en][0][value]' => 'John ' . $target_node_from_translatable_field_title_en, + 'field_multiple_nodes_translate[en][form][field_last_name][en][0][value]' => 'Doe ' . $target_node_from_translatable_field_title_en, + ); + $this->drupalPostAjax(NULL, $edit_en, $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-translate-en-form-actions-ief-add-save"]')); + + // Save the parent node, giving it a title. + $edit_parent = array( + 'title' => 'Source Multilingual Node', + ); + $this->drupalPost(NULL, $edit_parent, t('Save')); + $title = reset($edit_parent); + $node = $this->drupalGetNodeByTitle($title); + + // Translate the parent node, via IEF. + $this->drupalGet('node/' . $node->nid . '/edit/add/en/de'); + // Add a translation via IEF to the node referenced by the untranslatable field. + $this->drupalPostAJAX(NULL, array(), $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-und-entities-0-actions-ief-entity-edit"]')); + $edit_de = array( + 'field_multiple_nodes[und][entities][0][form][field_first_name][de][0][value]' => 'Max ' . $target_node_from_untranslatable_field_title, + 'field_multiple_nodes[und][entities][0][form][field_last_name][de][0][value]' => 'Muster ' . $target_node_from_untranslatable_field_title, + ); + $this->drupalPostAJAX(NULL, $edit_de, $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-und-entities-0-form-actions-ief-edit-save"]')); + // Create a new node using IEF, in German, via the translatable field. + $target_node_from_translatable_field_title_de = $this->randomName(); + $edit_de = array( + 'field_multiple_nodes_translate[de][form][title]' => $target_node_from_translatable_field_title_de, + 'field_multiple_nodes_translate[de][form][field_first_name][de][0][value]' => 'Max ' . $target_node_from_translatable_field_title_de, + 'field_multiple_nodes_translate[de][form][field_last_name][de][0][value]' => 'Muster ' . $target_node_from_translatable_field_title_de, + ); + $this->drupalPostAjax(NULL, $edit_de, $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-translate-de-form-actions-ief-add-save"]')); + $this->drupalPost(NULL, array(), t('Save')); + + // Check the parent node's display in English. + $this->drupalGet('node/' . $node->nid); + $this->assertText('John ' . $target_node_from_untranslatable_field_title); + $this->assertText('Doe ' . $target_node_from_untranslatable_field_title); + $this->assertText('John ' . $target_node_from_translatable_field_title_en); + $this->assertText('Doe ' . $target_node_from_translatable_field_title_en); + + // Check the parent node's display in German. + $this->drupalGet('node/' . $node->nid, array('language' => $language_list['de'])); + $this->assertText('Max ' . $target_node_from_untranslatable_field_title); + $this->assertText('Muster ' . $target_node_from_untranslatable_field_title); + $this->assertText('Max ' . $target_node_from_translatable_field_title_de); + $this->assertText('Muster ' . $target_node_from_translatable_field_title_de); + + // Check the parent form's edit values in English. + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-und-entities-0-actions-ief-entity-edit"]')); + $this->assertFieldById('edit-field-multiple-nodes-und-entities-0-form-field-first-name-en-0-value', 'John ' . $target_node_from_untranslatable_field_title); + $this->assertFieldById('edit-field-multiple-nodes-und-entities-0-form-field-last-name-en-0-value', 'Doe ' . $target_node_from_untranslatable_field_title); + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-translate-en-entities-0-actions-ief-entity-edit"]')); + $this->assertFieldById('edit-field-multiple-nodes-translate-en-entities-0-form-field-first-name-en-0-value', 'John ' . $target_node_from_translatable_field_title_en); + $this->assertFieldById('edit-field-multiple-nodes-translate-en-entities-0-form-field-last-name-en-0-value', 'Doe ' . $target_node_from_translatable_field_title_en); + + // Check the parent form's edit values in German. + $this->drupalGet('node/' . $node->nid . '/edit', array('language' => $language_list['de'])); + $this->drupalPostAjax(NULL, array(), $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-und-entities-0-actions-ief-entity-edit"]')); + $this->assertFieldById('edit-field-multiple-nodes-und-entities-0-form-field-first-name-de-0-value', 'Max ' . $target_node_from_untranslatable_field_title); + $this->assertFieldById('edit-field-multiple-nodes-und-entities-0-form-field-last-name-de-0-value', 'Muster ' . $target_node_from_untranslatable_field_title); + $this->drupalPostAJAX(NULL, array(), $this->getButtonName('//input[@type="submit" and @id="edit-field-multiple-nodes-translate-de-entities-0-actions-ief-entity-edit"]')); + $this->assertFieldById('edit-field-multiple-nodes-translate-de-entities-0-form-field-first-name-de-0-value', 'Max ' . $target_node_from_translatable_field_title_de); + $this->assertFieldById('edit-field-multiple-nodes-translate-de-entities-0-form-field-last-name-de-0-value', 'Muster ' . $target_node_from_translatable_field_title_de); + } + +} diff --git a/tests/modules/inline_entity_form_test/inline_entity_form_test.module b/tests/modules/inline_entity_form_test/inline_entity_form_test.module index 8f46348..76d71fb 100644 --- a/tests/modules/inline_entity_form_test/inline_entity_form_test.module +++ b/tests/modules/inline_entity_form_test/inline_entity_form_test.module @@ -146,6 +146,24 @@ function inline_entity_form_test_install() { ); field_create_field($field); + $field = array( + 'field_name' => 'field_multiple_nodes_translate', + 'type' => 'entityreference', + 'cardinality' => -1, + 'translatable' => TRUE, + 'required' => TRUE, + 'settings' => array( + 'target_type' => 'node', + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array( + 'ief_reference_type' => 'ief_reference_type', + ), + ), + ), + ); + field_create_field($field); + $field_bases['field_positive_int'] = array( 'active' => 1, 'cardinality' => 1, @@ -306,6 +324,31 @@ function inline_entity_form_test_install() { field_create_instance($instance); $instance = array( + 'bundle' => 'ief_test_multiple', + 'entity_type' => 'node', + 'field_name' => 'field_multiple_nodes_translate', + 'label' => 'Multiple nodes (translatable)', + 'required' => TRUE, + 'widget' => array( + 'type' => 'inline_entity_form', + 'settings' => array( + 'fields' => array(), + 'type_settings' => array( + 'allow_clone' => 0, + 'allow_existing' => 0, + 'allow_new' => 1, + 'delete_references' => 0, + 'label_plural' => 'nodes', + 'label_singular' => 'node', + 'match_operator' => 'CONTAINS', + 'override_labels' => 0, + ), + ), + ), + ); + field_create_instance($instance); + + $instance = array( 'bundle' => 'ief_test_custom', 'entity_type' => 'node', 'field_name' => 'field_positive_int',