diff --git a/sites/all/modules/entity_translation/entity_translation.module b/sites/all/modules/entity_translation/entity_translation.module index fcf7908..2709884 100644 --- a/sites/all/modules/entity_translation/entity_translation.module +++ b/sites/all/modules/entity_translation/entity_translation.module @@ -469,6 +469,240 @@ function entity_translation_form_field_ui_field_settings_form_alter(&$form, $for '#title' => t('Users may translate this field.'), '#default_value' => $field['translatable'], ); + $form['field']['warning'] = array( + '#type' => 'container', + '#states' => array( + 'visible' => array( // action to take. + ':input[name="field[translatable]"]' => array('checked' => ! $field['translatable']), + ), + ), + ); + $form['field']['warning']['warning'] = array( + '#markup' => t('By submitting this form you will trigger a batch operation. THIS OPERATION COULD RESULT IN LOSS OF DATA. Please ensure that all effected nodes are backed up before proceeding.'), + ); + + // our submit handler must run before field_ui_field_settings_form_submit() + if (isset($form['#submit'])) { + array_unshift($form['#submit'], 'entity_translation_translatable_submit'); + else { + $form['#submit'] = array('entity_translations_translatable_submit'); + } +} + +/** + * This submit handler maintains consistency between the translatability of a node and the language under which the + * field data is stored. + * + * When a field is marked as translatable, all data under $node->{field_name}['und'] is moved to + * $node->{field_name}[$node->language]. When a field is marked as untranslatable, the opposite transfer occurs. + * + * N.B. When marking a field as untranslatable, all translations of the field (except for $node->language) will be + * lost! This should be a very rare case, but a lot of anger will eventually come from it. + * + * TODO: Make a confirmation page as when anything else is deleted. + * + * For lack of a better place, notes go here: + * + * -Make sure that the confirmation screen/message don't appear when creating a new field. + */ +function entity_translation_translatable_submit($form, &$form_state) { + $checked = $form_state['values']['field']['translatable']; + $prior = $form['field']['translatable']['#default_value']; + + if ($prior == $checked) { + return; + } + + $field_name = $form_state['values']['field']['field_name']; + + $ops = array(); + + // Number of entities per batch operation + $increment = 10; + + // How many entities will need processing? + // Unfortunately EFQ->count() counts all translations of all fields, not just the entities that have them + $count = 0; + $EFQ = new EntityFieldQuery(); + $entities = $EFQ->fieldCondition($field_name) + ->execute(); + + foreach($entities as $type => $list) { + $count += count($list); + } + + do { + $ops[] = array('entity_translation_translatable_batch', array($checked, $field_name, $increment)); + $count -= $increment; + } + while ($count > 0); + + // When making field untranslatable, we need to make sure that the other submit handler + // can't see the changed translatable setting. We change it ourselves after the batch operation. + // + // For some reason, adding the custom submit handler to the batch job doesn't work; it never gets called. + // Why this is is yet to be determined, but a temporary workaround is to pass the data into the batch results + // and then call the submit handler from the finalising callback. + if (! $checked) { + $ops[] = array('entity_translation_translatable_batch_data_store', array($form, $form_state)); + $form_state['values']['field']['translatable'] = $prior; + } + + // Somehow this submit handler doesn't get called unless it comes first. So, we look through the list and + // if we find one, we call it ourselves and remove it from the list. It is removed and executed at most once. + // + // This is not a good solution, but it works. For now. fixme if you see how. + foreach ($form['#submit'] as $key => &$value) { + if ($value === 'field_ui_field_settings_form_submit') { + unset($value); + field_ui_field_settings_form_submit($form, $form_state); + break; + } + } + + $batch = array( + 'operations' => $ops, + 'finished' => 'entity_translation_translatable_batch_done', + ); + + batch_set($batch); +} + +/** + * Hacky helper function to pass the form data off to entity_translation_translatable_batch_done. + */ +function entity_translation_translatable_batch_data_store($form, $form_state, &$context) { + $context['results'] = array($form, $form_state); +} + +/** + * Function from which to build batch array. + */ +function entity_translation_translatable_batch($make_translatable, $field_name, $inc, &$context) { + // Figure out which nodes we're interested in. + if (isset($context['start'])) { + $start = $context['start']; + $contex['start'] += $inc; + } + else { + $start = 0; + $context['start'] = $inc; + } + + $EFQ = new EntityFieldQuery(); + $query = $EFQ->fieldCondition($field_name) + ->range($start, $inc) + ->execute(); + + foreach ($query as $entity_type => $entities) { + field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field_name)); + } + + foreach ($query as $entity_type => $entities) { + + // Getting the default language of the entities is still an open problem. + // When turning off translatability, the obvious place to look is the source language + // attribute in the entity_translation table, but that table doesn't exist until after + // the field in question has been made translatable, so when turning translatability + // on... + // + // I think the best thing to do is implement a hook_..._language_alter since the site admins + // likely have a much better idea of what language things are than Drupal does. + // + if (!$make_translatable) { + // This exploits the fact that in the entity_translation table, if the source is blank then the + // language is the source language. + $language = db_select('entity_translation', 'et') + ->fields('et', array('entity_id', 'language')) + ->condition('source', '', '=') + ->condition('entity_id', array_keys($entities), 'IN') + ->execute() + ->fetchAllKeyed(); + } + else { + // Far from ideal interim solution. fixme + $language = array_fill_keys(array_keys($entities), language_default()->language); + } + + foreach ($entities as $id => $entity) { + + $lang = $language[$id]; + + if ($make_translatable && isset($entity->{$field_name}[LANGUAGE_NONE])) { + // If field is to become translatable and has data in 'und' + + $entity->{$field_name}[$lang] = $entity->{$field_name}[LANGUAGE_NONE]; + //setting this to an empty array() will cause the item to be deleted + $entity->{$field_name}[LANGUAGE_NONE] = array(); + } + else if (!$make_translatable && isset($entity->{$field_name}[$lang])) { + // the field has been marked untranslatable and has data in the (entity's) default language + // mv the default language to und and drop the other translations. + //Store the field data in the default language + $data = $entity->{$field_name}[$lang]; + // Remove all translations of the field data. + foreach ($entity->{$field_name} as $langcode => $item) { + $entity->{$field_name}[$langcode] = array(); + } + // Move the original field data to LANGUAGE_NONE + $entity->{$field_name}[LANGUAGE_NONE] = $data; + } + else { + // Don't save unchanged entities. + continue; + } + // Save field data. Faster than node_save, but still too slow for comfort. + field_attach_update($entity_type, $entity); + } + } +} + +/** + * 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 (isset($results) && count($results) === 2) { + // Run our custom submit handler. + entity_translation_custom_submit($results[0], $results[1]); + } + 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.")); + } +} + +/** + * This is a shameless cut and paste of field_ui_field_settings_form_submit from field_ui.admin.inc. + * + * The reason is that we need to submit the form, but keep one property (translatabe) aside to be updated + * after we've run our batch operation; if we make a field untranslatable before moving the data we lose it. + * + * TODO: Find the right way to do this. + */ +function entity_translation_custom_submit($form, &$form_state) { + $form_values = $form_state['values']; + $field_values = $form_values['field']; + + // Merge incoming form values into the existing field. + $field = field_info_field($field_values['field_name']); + + $entity_type = $form['#entity_type']; + $bundle = $form['#bundle']; + $instance = field_info_instance($entity_type, $field['field_name'], $bundle); + + // Update the field. + $field = array_merge($field, $field_values); + + try { + field_update_field($field); + drupal_set_message(t('Updated field %label translatable setting.', array('%label' => $instance['label']))); + } + catch (FieldException $e) { + drupal_set_message(t('Attempt to update field %label failed: %message.', array('%label' => $instance['label'], '%message' => $e->getMessage())), 'error'); + } } /**