diff --git a/entity_translation.admin.inc b/entity_translation.admin.inc index 4c2c054..68fe2fb 100644 --- a/entity_translation.admin.inc +++ b/entity_translation.admin.inc @@ -489,3 +489,207 @@ function entity_translation_delete_confirm_submit($form, &$form_state) { $form_state['redirect'] = "{$handler->getBasePath()}/translate"; } + +/* + * Confirm form for changing field translatability. + */ +function entity_translation_warning_form($form, &$form_state, $field_name) { + + $field = field_info_field($field_name); + + $warning = t('By submitting this form you will trigger a batch operation.'); + if ($field['translatable']) { + $title = t('Are you sure you want to make the field @name untranslatable?', array('@name' => $field_name)); + $warning .= "
" . t("All translations of this field will be deleted.
This action cannot be undone."); + } + else { + $title = t('Are you sure you want to make the field @name translatable?', array('@name' => $field_name)); + } + + $form = confirm_form($form, $title, '', $warning, t('Convert'), t('Cancel')); + + // We need to keep some information for later processing. + $form['#etfield'] = $field['field_name']; + $form['translatable'] = array( + '#type' => 'hidden', + '#default_value' => $field['translatable'], + ); + + return $form; +} + +/** + * Submit handler for the field settings form. + * + * This submit handler maintains consistency between the translatability of an + * entity and the language under which the field data is stored. When a field is + * marked as translatable, all the data in $entity->{field_name}[LANGUAGE_NONE] + * is moved to $entity->{field_name}[$entity_language]. When a field is marked + * as untranslatable the opposite process occurs. Note that marking a field as + * untranslatable will cause all of its translations to be permanently removed, + * with the exception of the one corresponding to the entity language. + */ +function entity_translation_warning_form_submit($form, $form_state) { + // This is the current state that we want to reverse. + $translatable = $form_state['values']['translatable']; + $field_name = $form['#etfield']; + $field = field_info_field($field_name); + + if ($field['translatable'] !== $translatable) { + // Field translatability has changed since form creation, abort. + return; + } + + // If a field is untranslatable, it can have no data except under + // LANGUAGE_NONE. Thus we need a field to be translatable before we convert + // data to the entity language. Conversely we need to switch data back to + // LANGUAGE_NONE before making a field untranslatable lest we lose + // information. + if ($translatable) { + $opts = array( + array('entity_translation_translatable_batch', array(!$translatable, $field_name)), + array('entity_translation_set_field_translatable', array(!$translatable, $field_name)), + ); + } + else { + $opts = array( + array('entity_translation_set_field_translatable', array(!$translatable, $field_name)), + array('entity_translation_translatable_batch', array(!$translatable, $field_name)), + ); + } + + $batch = array( + 'title' => t('Converting @field to be @translatable.', array( + '@field' => $field_name, + '@translatable' => $translatable ? 'untranslatable' : 'translatable', + )), + 'operations' => $opts, + 'finished' => 'entity_translation_translatable_batch_done', + 'file' => drupal_get_path('module', 'entity_translation') . '/entity_translation.admin.inc', + ); + + batch_set($batch); +} + +/* + * Toggle translatability of given field. + * + * This is called from a batch operation, but should only run once per field. + */ +function entity_translation_set_field_translatable($translatable, $field_name) { + $field = field_info_field($field_name); + + if ($field['translatable'] === $translatable) { + return; + } + + $field['translatable'] = $translatable; + field_update_field($field); + + // This is important for versions 7.10 and lower (and maybe some others.). + // See http://drupal.org/node/1380660 for details. + drupal_static_reset('field_available_languages'); +} + +/** + * Batch operation. Convert field data to or from LANGUAGE_NONE. + */ +function entity_translation_translatable_batch($translatable, $field_name, &$context) { + if (empty($context['sandbox'])) { + $context['sandbox']['progress'] = 0; + + // How many entities will need processing? + $query = new EntityFieldQuery(); + $count = $query + ->fieldCondition($field_name) + ->count() + ->execute(); + + if (intval($count) === 0) { + // Nothing to do. + $context['finished'] = 1; + return; + } + $context['sandbox']['max'] = $count; + } + + // Number of entities to be processed for each step. + $limit = variable_get('entity_translation_translatable_batch_limit', 10); + + $query = new EntityFieldQuery(); + $result = $query + ->fieldCondition($field_name) + ->range($context['sandbox']['progress'], $limit) + ->execute(); + + foreach ($result as $entity_type => $entities) { + foreach (entity_load($entity_type, array_keys($entities)) as $id => $entity) { + $context['sandbox']['progress']++; + + $handler = entity_translation_get_handler($entity_type, $entity); + $langcode = $handler->getLanguage(); + + if ($translatable && isset($entity->{$field_name}[LANGUAGE_NONE])) { + // If the field is being switched to translatable and has data for + // LANGUAGE_NONE then we need to move the data to the right language. + $entity->{$field_name}[$langcode] = $entity->{$field_name}[LANGUAGE_NONE]; + $entity->{$field_name}[LANGUAGE_NONE] = array(); + } + elseif (!$translatable && isset($entity->{$field_name}[$langcode])) { + // The field has been marked untranslatable and has data in the entity + // language: we need to move it to LANGUAGE_NONE and drop the other + // translations. + $data = $entity->{$field_name}[$langcode]; + + // Check if the entity has any more translatable fields. If not, then + // we must delete the translation handler. + $has_translatables = FALSE; + $fields = field_info_instances($entity_type, $entity->type); + foreach ($fields as $field => $value) { + if ($field === $field_name) { + // We're setting this to untranslatable later. + continue; + } + $field = field_info_field($field); + if ($field['translatable']) { + $has_translatables = TRUE; + break; + } + } + dsm($has_translatables, "as"); + + foreach ($entity->{$field_name} as $lang => $items) { + if (!$has_translatables) { + // This also deletes the field data in $lang. + $handler->removeTranslation($lang); + } + else { + $entity->{$field_name}[$lang] = array(); + } + + } + $entity->{$field_name}[LANGUAGE_NONE] = $data; + } + else { + // No need to save unchanged entities. + continue; + } + field_attach_presave($entity_type, $entity); + field_attach_update($entity_type, $entity); + } + } + $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; +} + +/** + * Check the exit status of the batch operation. + */ +function entity_translation_translatable_batch_done($success, $results, $operations) { + if ($success) { + drupal_set_message(t("Data successfully converted.")); + } + else { + // @todo: Do something about this case. + drupal_set_message(t("Something went wrong when converting the data. Some nodes may appear to have lost fields.")); + } +} diff --git a/entity_translation.module b/entity_translation.module index 56f42d0..bbc39ad 100644 --- a/entity_translation.module +++ b/entity_translation.module @@ -120,6 +120,23 @@ function entity_translation_enabled($entity_type, $skip_handler = FALSE) { } /** + * Implments hook_menu(). + */ +function entity_translation_menu() { + $items = array(); + + $items['admin/config/regional/entity_translation/translate/%'] = array( + 'title' => 'Confirm Change in translatability.', + 'description' => 'Confirm page for changing field translatability.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('entity_translation_warning_form', 5), + 'access arguments' => array('change field translatability'), + 'file' => 'entity_translation.admin.inc', + ); + return $items; +} + +/** * Implements hook_menu_alter(). */ function entity_translation_menu_alter(&$items) { @@ -211,6 +228,7 @@ function entity_translation_menu_alter(&$items) { return $items; } + /** * Implements hook_admin_paths(). */ @@ -276,6 +294,10 @@ function entity_translation_permission() { 'title' => t('Administer entity translation'), 'description' => t('Select which entities can be translated.'), ), + 'change field translatability' => array( + 'title' => t('Change field translatability'), + 'description' => t('Change translatability of fields and perform bulk updates.'), + ), ); foreach (entity_get_info() as $entity_type => $info) { if ($info['fieldable']) { @@ -481,12 +503,32 @@ function entity_translation_edit_form_submit($form, &$form_state) { * Enable a selector to choose whether a field is translatable. */ function entity_translation_form_field_ui_field_settings_form_alter(&$form, $form_state) { - $instance = $form_state['build_info']['args'][0]; - $field = field_info_field($instance['field_name']); + $field_name = $form['field']['field_name']['#value']; + $field = field_info_field($field_name); + $translatable = $field['translatable']; + + if ($translatable) { + $status = t('This field is currently translatable.'); + } + else { + $status = t('This field is currently untranslatable.'); + } + + $path = "admin/config/regional/entity_translation/translate/$field_name"; + $form['field']['translatable'] = array( - '#type' => 'checkbox', - '#title' => t('Users may translate this field.'), - '#default_value' => $field['translatable'], + '#prefix' => '
', + '#suffix' => '
', + 'message' => array( + '#markup' => $status . ' ', + ), + 'link' => array( + '#type' => 'link', + '#title' => t('Change translatability.'), + '#href' => $path, + '#options' => array('query' => drupal_get_destination()), + '#access' => user_access('change field translatability'), + ), ); } diff --git a/tests/entity_translation.test b/tests/entity_translation.test index 08cf41a..8f35abe 100644 --- a/tests/entity_translation.test +++ b/tests/entity_translation.test @@ -48,6 +48,7 @@ class EntityTranslationTestCase extends DrupalWebTestCase { 'administer blocks', 'access administration pages', 'administer site configuration', + 'change field translatability', )); } return $this->admin_user; @@ -147,11 +148,16 @@ class EntityTranslationTestCase extends DrupalWebTestCase { $this->assertRaw(t('Saved %field configuration.', array('%field' => 'Body')), t('Body field settings have been updated.')); // Set body field to translatable. - $this->drupalGet('admin/structure/types/manage/page/fields/body/field-settings'); - $edit = array(); - $edit['field[translatable]'] = 1; - $this->drupalPost('admin/structure/types/manage/page/fields/body/field-settings', $edit, t('Save field settings')); - $this->assertRaw(t('Updated field %field field settings.', array('%field' => 'Body'))); + $this->drupalGet('admin/config/regional/entity_translation/translate/body', array( + 'query' => array( + 'destination' => 'admin/structure/types/manage/page/fields/body/field-settings') + ) + ); + $edit = array( + 'translatable' => 0 + ); + $this->drupalPost('admin/config/regional/entity_translation/translate/body', $edit, t('Convert')); + $this->assertRaw(t('Data successfully converted.')); // Check if the setting works. $this->drupalGet('node/add/page');