diff --git a/entity_translation.module b/entity_translation.module index 053d39d..6194635 100644 --- a/entity_translation.module +++ b/entity_translation.module @@ -189,10 +189,42 @@ function entity_translation_menu() { 'access arguments' => array('administer entity translation'), 'file' => 'entity_translation.admin.inc', ); + $items['admin/structure/types/manage/%/fields/%/translatable'] = array( + 'title' => t('Confirm Change in translatability.'), + 'description' => 'Confirm page for changing field translatability.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('entity_translation_warning_form', 4, 6), + 'access arguments' => array('administer entity translation'), + ); return $items; } +/* + * Confirm form for changing field translatability. + */ +function entity_translation_warning_form($form, &$form_state, $bundle, $field_name) { + + $field = field_info_field($field_name); + + $warning = t('By submitting this form you will trigger a batch operation.'); + if ($field['translatable']) { + $warning .= "
" . t("All translations of this field will be deleted.
Please backup your data before proceeding."); + } + + $redirect = "admin/structure/types/manage/$bundle/fields/$field_name/field-settings"; + + $form = confirm_form($form, t('Warning.'), $redirect, $warning, t('Convert'), t('Cancel')); + + $form['#submit'] = array('entity_translation_form_translatable_submit'); + + // We need to attach the field name for future processing. + $form['#etfield'] = $field; + + return $form; +} + + /** * Implements hook_admin_paths(). */ @@ -463,16 +495,155 @@ 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_state['build_info']['args'][0]['field_name']; + $bundle = $form['#bundle']; + + $path = "admin/structure/types/manage/$bundle/fields/$field_name/translatable"; $form['field']['translatable'] = array( - '#type' => 'checkbox', - '#title' => t('Users may translate this field.'), - '#default_value' => $field['translatable'], + '#markup' => "
" . l(t('Change Translatability'), $path, array('query' => drupal_get_destination())) . "
", ); } /** + * 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_form_translatable_submit($form, $form_state) { + // This is the current state that we want to reverse. + $translatable = $form['#etfield']['translatable']; + + $field_name = $form['#etfield']['field_name']; + + // 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 convert 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_toggle_field_translatable', array($field_name)), + ); + } + else { + $opts = array( + array('entity_translation_toggle_field_translatable', array($field_name)), + array('entity_translation_translatable_batch', array(!$translatable, $field_name)), + ); + } + + $batch = array( + 'title' => t('Converting'), + 'operations' => $opts, + 'finished' => 'entity_translation_translatable_batch_done', + ); + + 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_toggle_field_translatable($field_name) { + $field = field_read_fields(array('field_name' => $field_name)); + + $field = $field[$field_name]; + $field['translatable'] = !$field['translatable']; + + field_update_field($field); +} + +/** + * Batch operation. Convert field data to or from LANGUAGE_NONE. + */ +function entity_translation_translatable_batch($make_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 (!$count) { + // Nothing to do. + $context['finished'] = 1; + return; + } + + $context['sandbox']['max'] = $count; + + } + // Number of entities to be processed for each step. + $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 ($make_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 (!$make_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]; + foreach ($entity->{$field_name} as $lang => $items) { + // It's not enough to simply delete the field, we need to tell + // the translation handler to wipe it out. + $handler->removeTranslation($lang); + } + // Move the original field data to LANGUAGE_NONE + $entity->{$field_name}[LANGUAGE_NONE] = $data; + } + else { + // No need to save unchanged entities. + continue; + } + // Save field data. + field_attach_update($entity_type, $entity); + } + } + $context['finished'] = $context['sandbox']['progress']/$context['sandbox']['max']; +} + +/** + * Check the exit status of the batch operation, and possibly resubmit the changed translatable status. + */ +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.")); + } +} + +/** * Translation handler factory. * * @param $entity_type