diff --git a/entity_translation.module b/entity_translation.module index fcf7908..34f71bd 100644 --- a/entity_translation.module +++ b/entity_translation.module @@ -469,6 +469,230 @@ 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_toggle'); + } + else { + $form['#submit'] = array('entity_translations_translatable_toggle'); + } +} + +/** + * This callback 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. + * + * Major issue:: when making a field translatable, any nodes of language und will lose that field. Deleted from + * the database. Poof! Just totally and inexplicably gone. + */ +function entity_translation_translatable_toggle($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(); + $entity_type = $form['#entity_type']; + // Number of entities per batch operation + $increment = 10; + + // TODO: Everything following wrt nodes needs to be generalised for all entities which have a language defined. + $count = db_select('node ', 'n') + ->condition('language', LANGUAGE_NONE, '<>') + ->countQuery() + ->execute() + ->fetchField(); + + do { + $ops[] = array('entity_translation_batch', array($checked, $entity_type, $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_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. 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_translate_toggle_done', + ); + + batch_set($batch); +} + +/** + * Hacky helper function to pass the form data off to entity_translation_translate_toggle_done. + */ +function entity_translation_batch_data_store($form, $form_state, &$context) { + $context['results'] = array($form, $form_state); +} + +/** + * Function from which to build batch array. + */ +function entity_translation_batch($make_translatable, $entity_type, $field_name, $inc, &$context) { + // Figure out which nodes we're interested in. + if (isset($context['count'])) { + $count = $context['count']; + $contex['count'] += $inc; + } + else { + $count = 0; + $context['count'] = $inc; + } + + $entities = entity_translation_get_next_entities($count, $inc); + + field_attach_load($entity_type, $entities); + + foreach ($entities as $id => $entity) { + // Possibly not portable to entities in general (but much nicer than passing a separate list of languages...) + $lang = $entity->language; + + 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); + } +} + +/** + * This functions is an abstraction layer to help make the translate_toggle methods general to entities. + * + * The contract is as follows: This function will act as a stream from which we pull entities. On each call, + * the method will return $num entities starting at $start. Thus the function will need to keep all entities in + * some form of linear order. Additionally, since we're dealing here with translation, it is assumed that all + * entities returned will have a language attribute which is not LANGUAGE_NONE. + * Entities are returned as an array which can be loaded by field_attach_update. For particulars see the docs + * for that function (they're actually fairly complete). + * + * So far this is only implemented for nodes. + * + * We'll also have to do something about entity_type, since so far we're assuming they're all nodes... + */ +function entity_translation_get_next_entities($start, $num) { + + $query = db_select('node', 'n') + ->fields('n', array('nid', 'language', 'type')) + ->condition('language', LANGUAGE_NONE, '<>') + ->range($start, $num) + ->execute(); + + $entities = array(); + + foreach ($query as $node) { + $entities[$node->nid] = $node; + } + return $entities; +} + +/** + * Check the exit status of the batch operation, and possibly resubmit the changed translatable status. + */ +function entity_translation_translate_toggle_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) { + dsm(t("Data successfully converted.")); + } + else { + // TODO: Do something about this case. + dsm(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 field settings.', 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'); + } } /**